summaryrefslogtreecommitdiffstats
path: root/arch/sh/kernel/cpu/shmobile/pm.c
blob: 08d27fac8d080736110008282b03188d8917cae8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
 * arch/sh/kernel/cpu/shmobile/pm.c
 *
 * Power management support code for SuperH Mobile
 *
 *  Copyright (C) 2009 Magnus Damm
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/suspend.h>
#include <asm/suspend.h>
#include <asm/uaccess.h>
#include <asm/cacheflush.h>
#include <asm/bl_bit.h>

/*
 * Notifier lists for pre/post sleep notification
 */
ATOMIC_NOTIFIER_HEAD(sh_mobile_pre_sleep_notifier_list);
ATOMIC_NOTIFIER_HEAD(sh_mobile_post_sleep_notifier_list);

/*
 * Sleep modes available on SuperH Mobile:
 *
 * Sleep mode is just plain "sleep" instruction
 * Sleep Self-Refresh mode is above plus RAM put in Self-Refresh
 * Standby Self-Refresh mode is above plus stopped clocks
 */
#define SUSP_MODE_SLEEP		(SUSP_SH_SLEEP)
#define SUSP_MODE_SLEEP_SF	(SUSP_SH_SLEEP | SUSP_SH_SF)
#define SUSP_MODE_STANDBY_SF	(SUSP_SH_STANDBY | SUSP_SH_SF)
#define SUSP_MODE_RSTANDBY_SF \
	(SUSP_SH_RSTANDBY | SUSP_SH_MMU | SUSP_SH_REGS | SUSP_SH_SF)
 /*
  * U-standby mode is unsupported since it needs bootloader hacks
  */

#ifdef CONFIG_CPU_SUBTYPE_SH7724
#define RAM_BASE 0xfd800000 /* RSMEM */
#else
#define RAM_BASE 0xe5200000 /* ILRAM */
#endif

void sh_mobile_call_standby(unsigned long mode)
{
	void *onchip_mem = (void *)RAM_BASE;
	struct sh_sleep_data *sdp = onchip_mem;
	void (*standby_onchip_mem)(unsigned long, unsigned long);

	/* code located directly after data structure */
	standby_onchip_mem = (void *)(sdp + 1);

	atomic_notifier_call_chain(&sh_mobile_pre_sleep_notifier_list,
				   mode, NULL);

	/* flush the caches if MMU flag is set */
	if (mode & SUSP_SH_MMU)
		flush_cache_all();

	/* Let assembly snippet in on-chip memory handle the rest */
	standby_onchip_mem(mode, RAM_BASE);

	atomic_notifier_call_chain(&sh_mobile_post_sleep_notifier_list,
				   mode, NULL);
}

extern char sh_mobile_sleep_enter_start;
extern char sh_mobile_sleep_enter_end;

extern char sh_mobile_sleep_resume_start;
extern char sh_mobile_sleep_resume_end;

unsigned long sh_mobile_sleep_supported = SUSP_SH_SLEEP;

void sh_mobile_register_self_refresh(unsigned long flags,
				     void *pre_start, void *pre_end,
				     void *post_start, void *post_end)
{
	void *onchip_mem = (void *)RAM_BASE;
	void *vp;
	struct sh_sleep_data *sdp;
	int n;

	/* part 0: data area */
	sdp = onchip_mem;
	sdp->addr.stbcr = 0xa4150020; /* STBCR */
	sdp->addr.bar = 0xa4150040; /* BAR */
	sdp->addr.pteh = 0xff000000; /* PTEH */
	sdp->addr.ptel = 0xff000004; /* PTEL */
	sdp->addr.ttb = 0xff000008; /* TTB */
	sdp->addr.tea = 0xff00000c; /* TEA */
	sdp->addr.mmucr = 0xff000010; /* MMUCR */
	sdp->addr.ptea = 0xff000034; /* PTEA */
	sdp->addr.pascr = 0xff000070; /* PASCR */
	sdp->addr.irmcr = 0xff000078; /* IRMCR */
	sdp->addr.ccr = 0xff00001c; /* CCR */
	sdp->addr.ramcr = 0xff000074; /* RAMCR */
	vp = sdp + 1;

	/* part 1: common code to enter sleep mode */
	n = &sh_mobile_sleep_enter_end - &sh_mobile_sleep_enter_start;
	memcpy(vp, &sh_mobile_sleep_enter_start, n);
	vp += roundup(n, 4);

	/* part 2: board specific code to enter self-refresh mode */
	n = pre_end - pre_start;
	memcpy(vp, pre_start, n);
	sdp->sf_pre = (unsigned long)vp;
	vp += roundup(n, 4);

	/* part 3: board specific code to resume from self-refresh mode */
	n = post_end - post_start;
	memcpy(vp, post_start, n);
	sdp->sf_post = (unsigned long)vp;
	vp += roundup(n, 4);

	/* part 4: common code to resume from sleep mode */
	WARN_ON(vp > (onchip_mem + 0x600));
	vp = onchip_mem + 0x600; /* located at interrupt vector */
	n = &sh_mobile_sleep_resume_end - &sh_mobile_sleep_resume_start;
	memcpy(vp, &sh_mobile_sleep_resume_start, n);
	sdp->resume = (unsigned long)vp;

	sh_mobile_sleep_supported |= flags;
}

static int sh_pm_enter(suspend_state_t state)
{
	if (!(sh_mobile_sleep_supported & SUSP_MODE_STANDBY_SF))
		return -ENXIO;

	local_irq_disable();
	set_bl_bit();
	sh_mobile_call_standby(SUSP_MODE_STANDBY_SF);
	local_irq_disable();
	clear_bl_bit();
	return 0;
}

static const struct platform_suspend_ops sh_pm_ops = {
	.enter          = sh_pm_enter,
	.valid          = suspend_valid_only_mem,
};

static int __init sh_pm_init(void)
{
	suspend_set_ops(&sh_pm_ops);
	sh_mobile_setup_cpuidle();
	return 0;
}

late_initcall(sh_pm_init);