diff options
Diffstat (limited to 'arch/sh/kernel/cpu/shmobile')
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/Makefile | 2 | ||||
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/cpuidle.c | 113 | ||||
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/pm.c | 42 | ||||
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/pm_runtime.c | 303 | ||||
-rw-r--r-- | arch/sh/kernel/cpu/shmobile/sleep.S | 155 |
5 files changed, 538 insertions, 77 deletions
diff --git a/arch/sh/kernel/cpu/shmobile/Makefile b/arch/sh/kernel/cpu/shmobile/Makefile index 08bfa7c7db29..a39f88ea1a85 100644 --- a/arch/sh/kernel/cpu/shmobile/Makefile +++ b/arch/sh/kernel/cpu/shmobile/Makefile | |||
@@ -4,3 +4,5 @@ | |||
4 | 4 | ||
5 | # Power Management & Sleep mode | 5 | # Power Management & Sleep mode |
6 | obj-$(CONFIG_PM) += pm.o sleep.o | 6 | obj-$(CONFIG_PM) += pm.o sleep.o |
7 | obj-$(CONFIG_CPU_IDLE) += cpuidle.o | ||
8 | obj-$(CONFIG_PM_RUNTIME) += pm_runtime.o | ||
diff --git a/arch/sh/kernel/cpu/shmobile/cpuidle.c b/arch/sh/kernel/cpu/shmobile/cpuidle.c new file mode 100644 index 000000000000..1c504bd972c3 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/cpuidle.c | |||
@@ -0,0 +1,113 @@ | |||
1 | /* | ||
2 | * arch/sh/kernel/cpu/shmobile/cpuidle.c | ||
3 | * | ||
4 | * Cpuidle support code for SuperH Mobile | ||
5 | * | ||
6 | * Copyright (C) 2009 Magnus Damm | ||
7 | * | ||
8 | * This file is subject to the terms and conditions of the GNU General Public | ||
9 | * License. See the file "COPYING" in the main directory of this archive | ||
10 | * for more details. | ||
11 | */ | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/suspend.h> | ||
16 | #include <linux/cpuidle.h> | ||
17 | #include <asm/suspend.h> | ||
18 | #include <asm/uaccess.h> | ||
19 | #include <asm/hwblk.h> | ||
20 | |||
21 | static unsigned long cpuidle_mode[] = { | ||
22 | SUSP_SH_SLEEP, /* regular sleep mode */ | ||
23 | SUSP_SH_SLEEP | SUSP_SH_SF, /* sleep mode + self refresh */ | ||
24 | SUSP_SH_STANDBY | SUSP_SH_SF, /* software standby mode + self refresh */ | ||
25 | }; | ||
26 | |||
27 | static int cpuidle_sleep_enter(struct cpuidle_device *dev, | ||
28 | struct cpuidle_state *state) | ||
29 | { | ||
30 | unsigned long allowed_mode = arch_hwblk_sleep_mode(); | ||
31 | ktime_t before, after; | ||
32 | int requested_state = state - &dev->states[0]; | ||
33 | int allowed_state; | ||
34 | int k; | ||
35 | |||
36 | /* convert allowed mode to allowed state */ | ||
37 | for (k = ARRAY_SIZE(cpuidle_mode) - 1; k > 0; k--) | ||
38 | if (cpuidle_mode[k] == allowed_mode) | ||
39 | break; | ||
40 | |||
41 | allowed_state = k; | ||
42 | |||
43 | /* take the following into account for sleep mode selection: | ||
44 | * - allowed_state: best mode allowed by hardware (clock deps) | ||
45 | * - requested_state: best mode allowed by software (latencies) | ||
46 | */ | ||
47 | k = min_t(int, allowed_state, requested_state); | ||
48 | |||
49 | dev->last_state = &dev->states[k]; | ||
50 | before = ktime_get(); | ||
51 | sh_mobile_call_standby(cpuidle_mode[k]); | ||
52 | after = ktime_get(); | ||
53 | return ktime_to_ns(ktime_sub(after, before)) >> 10; | ||
54 | } | ||
55 | |||
56 | static struct cpuidle_device cpuidle_dev; | ||
57 | static struct cpuidle_driver cpuidle_driver = { | ||
58 | .name = "sh_idle", | ||
59 | .owner = THIS_MODULE, | ||
60 | }; | ||
61 | |||
62 | void sh_mobile_setup_cpuidle(void) | ||
63 | { | ||
64 | struct cpuidle_device *dev = &cpuidle_dev; | ||
65 | struct cpuidle_state *state; | ||
66 | int i; | ||
67 | |||
68 | cpuidle_register_driver(&cpuidle_driver); | ||
69 | |||
70 | for (i = 0; i < CPUIDLE_STATE_MAX; i++) { | ||
71 | dev->states[i].name[0] = '\0'; | ||
72 | dev->states[i].desc[0] = '\0'; | ||
73 | } | ||
74 | |||
75 | i = CPUIDLE_DRIVER_STATE_START; | ||
76 | |||
77 | state = &dev->states[i++]; | ||
78 | snprintf(state->name, CPUIDLE_NAME_LEN, "C0"); | ||
79 | strncpy(state->desc, "SuperH Sleep Mode", CPUIDLE_DESC_LEN); | ||
80 | state->exit_latency = 1; | ||
81 | state->target_residency = 1 * 2; | ||
82 | state->power_usage = 3; | ||
83 | state->flags = 0; | ||
84 | state->flags |= CPUIDLE_FLAG_SHALLOW; | ||
85 | state->flags |= CPUIDLE_FLAG_TIME_VALID; | ||
86 | state->enter = cpuidle_sleep_enter; | ||
87 | |||
88 | dev->safe_state = state; | ||
89 | |||
90 | state = &dev->states[i++]; | ||
91 | snprintf(state->name, CPUIDLE_NAME_LEN, "C1"); | ||
92 | strncpy(state->desc, "SuperH Sleep Mode [SF]", CPUIDLE_DESC_LEN); | ||
93 | state->exit_latency = 100; | ||
94 | state->target_residency = 1 * 2; | ||
95 | state->power_usage = 1; | ||
96 | state->flags = 0; | ||
97 | state->flags |= CPUIDLE_FLAG_TIME_VALID; | ||
98 | state->enter = cpuidle_sleep_enter; | ||
99 | |||
100 | state = &dev->states[i++]; | ||
101 | snprintf(state->name, CPUIDLE_NAME_LEN, "C2"); | ||
102 | strncpy(state->desc, "SuperH Mobile Standby Mode [SF]", CPUIDLE_DESC_LEN); | ||
103 | state->exit_latency = 2300; | ||
104 | state->target_residency = 1 * 2; | ||
105 | state->power_usage = 1; | ||
106 | state->flags = 0; | ||
107 | state->flags |= CPUIDLE_FLAG_TIME_VALID; | ||
108 | state->enter = cpuidle_sleep_enter; | ||
109 | |||
110 | dev->state_count = i; | ||
111 | |||
112 | cpuidle_register_device(dev); | ||
113 | } | ||
diff --git a/arch/sh/kernel/cpu/shmobile/pm.c b/arch/sh/kernel/cpu/shmobile/pm.c index 8c067adf6830..ee3c2aaf66fb 100644 --- a/arch/sh/kernel/cpu/shmobile/pm.c +++ b/arch/sh/kernel/cpu/shmobile/pm.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * arch/sh/kernel/cpu/sh4a/pm-sh_mobile.c | 2 | * arch/sh/kernel/cpu/shmobile/pm.c |
3 | * | 3 | * |
4 | * Power management support code for SuperH Mobile | 4 | * Power management support code for SuperH Mobile |
5 | * | 5 | * |
@@ -32,40 +32,20 @@ | |||
32 | * | 32 | * |
33 | * R-standby mode is unsupported, but will be added in the future | 33 | * R-standby mode is unsupported, but will be added in the future |
34 | * U-standby mode is low priority since it needs bootloader hacks | 34 | * U-standby mode is low priority since it needs bootloader hacks |
35 | * | ||
36 | * All modes should be tied in with cpuidle. But before that can | ||
37 | * happen we need to keep track of enabled hardware blocks so we | ||
38 | * can avoid entering sleep modes that stop clocks to hardware | ||
39 | * blocks that are in use even though the cpu core is idle. | ||
40 | */ | 35 | */ |
41 | 36 | ||
37 | #define ILRAM_BASE 0xe5200000 | ||
38 | |||
42 | extern const unsigned char sh_mobile_standby[]; | 39 | extern const unsigned char sh_mobile_standby[]; |
43 | extern const unsigned int sh_mobile_standby_size; | 40 | extern const unsigned int sh_mobile_standby_size; |
44 | 41 | ||
45 | static void sh_mobile_call_standby(unsigned long mode) | 42 | void sh_mobile_call_standby(unsigned long mode) |
46 | { | 43 | { |
47 | extern void *vbr_base; | 44 | void *onchip_mem = (void *)ILRAM_BASE; |
48 | void *onchip_mem = (void *)0xe5200000; /* ILRAM */ | 45 | void (*standby_onchip_mem)(unsigned long, unsigned long) = onchip_mem; |
49 | void (*standby_onchip_mem)(unsigned long) = onchip_mem; | ||
50 | |||
51 | /* Note: Wake up from sleep may generate exceptions! | ||
52 | * Setup VBR to point to on-chip ram if self-refresh is | ||
53 | * going to be used. | ||
54 | */ | ||
55 | if (mode & SUSP_SH_SF) | ||
56 | asm volatile("ldc %0, vbr" : : "r" (onchip_mem) : "memory"); | ||
57 | |||
58 | /* Copy the assembly snippet to the otherwise ununsed ILRAM */ | ||
59 | memcpy(onchip_mem, sh_mobile_standby, sh_mobile_standby_size); | ||
60 | wmb(); | ||
61 | ctrl_barrier(); | ||
62 | 46 | ||
63 | /* Let assembly snippet in on-chip memory handle the rest */ | 47 | /* Let assembly snippet in on-chip memory handle the rest */ |
64 | standby_onchip_mem(mode); | 48 | standby_onchip_mem(mode, ILRAM_BASE); |
65 | |||
66 | /* Put VBR back in System RAM again */ | ||
67 | if (mode & SUSP_SH_SF) | ||
68 | asm volatile("ldc %0, vbr" : : "r" (&vbr_base) : "memory"); | ||
69 | } | 49 | } |
70 | 50 | ||
71 | static int sh_pm_enter(suspend_state_t state) | 51 | static int sh_pm_enter(suspend_state_t state) |
@@ -85,7 +65,15 @@ static struct platform_suspend_ops sh_pm_ops = { | |||
85 | 65 | ||
86 | static int __init sh_pm_init(void) | 66 | static int __init sh_pm_init(void) |
87 | { | 67 | { |
68 | void *onchip_mem = (void *)ILRAM_BASE; | ||
69 | |||
70 | /* Copy the assembly snippet to the otherwise ununsed ILRAM */ | ||
71 | memcpy(onchip_mem, sh_mobile_standby, sh_mobile_standby_size); | ||
72 | wmb(); | ||
73 | ctrl_barrier(); | ||
74 | |||
88 | suspend_set_ops(&sh_pm_ops); | 75 | suspend_set_ops(&sh_pm_ops); |
76 | sh_mobile_setup_cpuidle(); | ||
89 | return 0; | 77 | return 0; |
90 | } | 78 | } |
91 | 79 | ||
diff --git a/arch/sh/kernel/cpu/shmobile/pm_runtime.c b/arch/sh/kernel/cpu/shmobile/pm_runtime.c new file mode 100644 index 000000000000..7c615b17e209 --- /dev/null +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c | |||
@@ -0,0 +1,303 @@ | |||
1 | /* | ||
2 | * arch/sh/kernel/cpu/shmobile/pm_runtime.c | ||
3 | * | ||
4 | * Runtime PM support code for SuperH Mobile | ||
5 | * | ||
6 | * Copyright (C) 2009 Magnus Damm | ||
7 | * | ||
8 | * This file is subject to the terms and conditions of the GNU General Public | ||
9 | * License. See the file "COPYING" in the main directory of this archive | ||
10 | * for more details. | ||
11 | */ | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/io.h> | ||
15 | #include <linux/pm_runtime.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/mutex.h> | ||
18 | #include <asm/hwblk.h> | ||
19 | |||
20 | static DEFINE_SPINLOCK(hwblk_lock); | ||
21 | static LIST_HEAD(hwblk_idle_list); | ||
22 | static struct work_struct hwblk_work; | ||
23 | |||
24 | extern struct hwblk_info *hwblk_info; | ||
25 | |||
26 | static void platform_pm_runtime_not_idle(struct platform_device *pdev) | ||
27 | { | ||
28 | unsigned long flags; | ||
29 | |||
30 | /* remove device from idle list */ | ||
31 | spin_lock_irqsave(&hwblk_lock, flags); | ||
32 | if (test_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags)) { | ||
33 | list_del(&pdev->archdata.entry); | ||
34 | __clear_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags); | ||
35 | } | ||
36 | spin_unlock_irqrestore(&hwblk_lock, flags); | ||
37 | } | ||
38 | |||
39 | static int __platform_pm_runtime_resume(struct platform_device *pdev) | ||
40 | { | ||
41 | struct device *d = &pdev->dev; | ||
42 | struct pdev_archdata *ad = &pdev->archdata; | ||
43 | int hwblk = ad->hwblk_id; | ||
44 | int ret = -ENOSYS; | ||
45 | |||
46 | dev_dbg(d, "__platform_pm_runtime_resume() [%d]\n", hwblk); | ||
47 | |||
48 | if (d->driver && d->driver->pm && d->driver->pm->runtime_resume) { | ||
49 | hwblk_enable(hwblk_info, hwblk); | ||
50 | ret = 0; | ||
51 | |||
52 | if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags)) { | ||
53 | ret = d->driver->pm->runtime_resume(d); | ||
54 | if (!ret) | ||
55 | clear_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); | ||
56 | else | ||
57 | hwblk_disable(hwblk_info, hwblk); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | dev_dbg(d, "__platform_pm_runtime_resume() [%d] - returns %d\n", | ||
62 | hwblk, ret); | ||
63 | |||
64 | return ret; | ||
65 | } | ||
66 | |||
67 | static int __platform_pm_runtime_suspend(struct platform_device *pdev) | ||
68 | { | ||
69 | struct device *d = &pdev->dev; | ||
70 | struct pdev_archdata *ad = &pdev->archdata; | ||
71 | int hwblk = ad->hwblk_id; | ||
72 | int ret = -ENOSYS; | ||
73 | |||
74 | dev_dbg(d, "__platform_pm_runtime_suspend() [%d]\n", hwblk); | ||
75 | |||
76 | if (d->driver && d->driver->pm && d->driver->pm->runtime_suspend) { | ||
77 | BUG_ON(!test_bit(PDEV_ARCHDATA_FLAG_IDLE, &ad->flags)); | ||
78 | |||
79 | hwblk_enable(hwblk_info, hwblk); | ||
80 | ret = d->driver->pm->runtime_suspend(d); | ||
81 | hwblk_disable(hwblk_info, hwblk); | ||
82 | |||
83 | if (!ret) { | ||
84 | set_bit(PDEV_ARCHDATA_FLAG_SUSP, &ad->flags); | ||
85 | platform_pm_runtime_not_idle(pdev); | ||
86 | hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | dev_dbg(d, "__platform_pm_runtime_suspend() [%d] - returns %d\n", | ||
91 | hwblk, ret); | ||
92 | |||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | static void platform_pm_runtime_work(struct work_struct *work) | ||
97 | { | ||
98 | struct platform_device *pdev; | ||
99 | unsigned long flags; | ||
100 | int ret; | ||
101 | |||
102 | /* go through the idle list and suspend one device at a time */ | ||
103 | do { | ||
104 | spin_lock_irqsave(&hwblk_lock, flags); | ||
105 | if (list_empty(&hwblk_idle_list)) | ||
106 | pdev = NULL; | ||
107 | else | ||
108 | pdev = list_first_entry(&hwblk_idle_list, | ||
109 | struct platform_device, | ||
110 | archdata.entry); | ||
111 | spin_unlock_irqrestore(&hwblk_lock, flags); | ||
112 | |||
113 | if (pdev) { | ||
114 | mutex_lock(&pdev->archdata.mutex); | ||
115 | ret = __platform_pm_runtime_suspend(pdev); | ||
116 | |||
117 | /* at this point the platform device may be: | ||
118 | * suspended: ret = 0, FLAG_SUSP set, clock stopped | ||
119 | * failed: ret < 0, FLAG_IDLE set, clock stopped | ||
120 | */ | ||
121 | mutex_unlock(&pdev->archdata.mutex); | ||
122 | } else { | ||
123 | ret = -ENODEV; | ||
124 | } | ||
125 | } while (!ret); | ||
126 | } | ||
127 | |||
128 | /* this function gets called from cpuidle context when all devices in the | ||
129 | * main power domain are unused but some are counted as idle, ie the hwblk | ||
130 | * counter values are (HWBLK_CNT_USAGE == 0) && (HWBLK_CNT_IDLE != 0) | ||
131 | */ | ||
132 | void platform_pm_runtime_suspend_idle(void) | ||
133 | { | ||
134 | queue_work(pm_wq, &hwblk_work); | ||
135 | } | ||
136 | |||
137 | int platform_pm_runtime_suspend(struct device *dev) | ||
138 | { | ||
139 | struct platform_device *pdev = to_platform_device(dev); | ||
140 | struct pdev_archdata *ad = &pdev->archdata; | ||
141 | unsigned long flags; | ||
142 | int hwblk = ad->hwblk_id; | ||
143 | int ret = 0; | ||
144 | |||
145 | dev_dbg(dev, "platform_pm_runtime_suspend() [%d]\n", hwblk); | ||
146 | |||
147 | /* ignore off-chip platform devices */ | ||
148 | if (!hwblk) | ||
149 | goto out; | ||
150 | |||
151 | /* interrupt context not allowed */ | ||
152 | might_sleep(); | ||
153 | |||
154 | /* catch misconfigured drivers not starting with resume */ | ||
155 | if (test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags)) { | ||
156 | ret = -EINVAL; | ||
157 | goto out; | ||
158 | } | ||
159 | |||
160 | /* serialize */ | ||
161 | mutex_lock(&ad->mutex); | ||
162 | |||
163 | /* disable clock */ | ||
164 | hwblk_disable(hwblk_info, hwblk); | ||
165 | |||
166 | /* put device on idle list */ | ||
167 | spin_lock_irqsave(&hwblk_lock, flags); | ||
168 | list_add_tail(&pdev->archdata.entry, &hwblk_idle_list); | ||
169 | __set_bit(PDEV_ARCHDATA_FLAG_IDLE, &pdev->archdata.flags); | ||
170 | spin_unlock_irqrestore(&hwblk_lock, flags); | ||
171 | |||
172 | /* increase idle count */ | ||
173 | hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_IDLE); | ||
174 | |||
175 | /* at this point the platform device is: | ||
176 | * idle: ret = 0, FLAG_IDLE set, clock stopped | ||
177 | */ | ||
178 | mutex_unlock(&ad->mutex); | ||
179 | |||
180 | out: | ||
181 | dev_dbg(dev, "platform_pm_runtime_suspend() [%d] returns %d\n", | ||
182 | hwblk, ret); | ||
183 | |||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | int platform_pm_runtime_resume(struct device *dev) | ||
188 | { | ||
189 | struct platform_device *pdev = to_platform_device(dev); | ||
190 | struct pdev_archdata *ad = &pdev->archdata; | ||
191 | int hwblk = ad->hwblk_id; | ||
192 | int ret = 0; | ||
193 | |||
194 | dev_dbg(dev, "platform_pm_runtime_resume() [%d]\n", hwblk); | ||
195 | |||
196 | /* ignore off-chip platform devices */ | ||
197 | if (!hwblk) | ||
198 | goto out; | ||
199 | |||
200 | /* interrupt context not allowed */ | ||
201 | might_sleep(); | ||
202 | |||
203 | /* serialize */ | ||
204 | mutex_lock(&ad->mutex); | ||
205 | |||
206 | /* make sure device is removed from idle list */ | ||
207 | platform_pm_runtime_not_idle(pdev); | ||
208 | |||
209 | /* decrease idle count */ | ||
210 | if (!test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags) && | ||
211 | !test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags)) | ||
212 | hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE); | ||
213 | |||
214 | /* resume the device if needed */ | ||
215 | ret = __platform_pm_runtime_resume(pdev); | ||
216 | |||
217 | /* the driver has been initialized now, so clear the init flag */ | ||
218 | clear_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); | ||
219 | |||
220 | /* at this point the platform device may be: | ||
221 | * resumed: ret = 0, flags = 0, clock started | ||
222 | * failed: ret < 0, FLAG_SUSP set, clock stopped | ||
223 | */ | ||
224 | mutex_unlock(&ad->mutex); | ||
225 | out: | ||
226 | dev_dbg(dev, "platform_pm_runtime_resume() [%d] returns %d\n", | ||
227 | hwblk, ret); | ||
228 | |||
229 | return ret; | ||
230 | } | ||
231 | |||
232 | int platform_pm_runtime_idle(struct device *dev) | ||
233 | { | ||
234 | struct platform_device *pdev = to_platform_device(dev); | ||
235 | int hwblk = pdev->archdata.hwblk_id; | ||
236 | int ret = 0; | ||
237 | |||
238 | dev_dbg(dev, "platform_pm_runtime_idle() [%d]\n", hwblk); | ||
239 | |||
240 | /* ignore off-chip platform devices */ | ||
241 | if (!hwblk) | ||
242 | goto out; | ||
243 | |||
244 | /* interrupt context not allowed, use pm_runtime_put()! */ | ||
245 | might_sleep(); | ||
246 | |||
247 | /* suspend synchronously to disable clocks immediately */ | ||
248 | ret = pm_runtime_suspend(dev); | ||
249 | out: | ||
250 | dev_dbg(dev, "platform_pm_runtime_idle() [%d] done!\n", hwblk); | ||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | static int platform_bus_notify(struct notifier_block *nb, | ||
255 | unsigned long action, void *data) | ||
256 | { | ||
257 | struct device *dev = data; | ||
258 | struct platform_device *pdev = to_platform_device(dev); | ||
259 | int hwblk = pdev->archdata.hwblk_id; | ||
260 | |||
261 | /* ignore off-chip platform devices */ | ||
262 | if (!hwblk) | ||
263 | return 0; | ||
264 | |||
265 | switch (action) { | ||
266 | case BUS_NOTIFY_ADD_DEVICE: | ||
267 | INIT_LIST_HEAD(&pdev->archdata.entry); | ||
268 | mutex_init(&pdev->archdata.mutex); | ||
269 | /* platform devices without drivers should be disabled */ | ||
270 | hwblk_enable(hwblk_info, hwblk); | ||
271 | hwblk_disable(hwblk_info, hwblk); | ||
272 | /* make sure driver re-inits itself once */ | ||
273 | __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); | ||
274 | break; | ||
275 | /* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */ | ||
276 | case BUS_NOTIFY_BOUND_DRIVER: | ||
277 | /* keep track of number of devices in use per hwblk */ | ||
278 | hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_DEVICES); | ||
279 | break; | ||
280 | case BUS_NOTIFY_UNBOUND_DRIVER: | ||
281 | /* keep track of number of devices in use per hwblk */ | ||
282 | hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_DEVICES); | ||
283 | /* make sure driver re-inits itself once */ | ||
284 | __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); | ||
285 | break; | ||
286 | case BUS_NOTIFY_DEL_DEVICE: | ||
287 | break; | ||
288 | } | ||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static struct notifier_block platform_bus_notifier = { | ||
293 | .notifier_call = platform_bus_notify | ||
294 | }; | ||
295 | |||
296 | static int __init sh_pm_runtime_init(void) | ||
297 | { | ||
298 | INIT_WORK(&hwblk_work, platform_pm_runtime_work); | ||
299 | |||
300 | bus_register_notifier(&platform_bus_type, &platform_bus_notifier); | ||
301 | return 0; | ||
302 | } | ||
303 | core_initcall(sh_pm_runtime_init); | ||
diff --git a/arch/sh/kernel/cpu/shmobile/sleep.S b/arch/sh/kernel/cpu/shmobile/sleep.S index baf2d7d46b05..a439e6c7824f 100644 --- a/arch/sh/kernel/cpu/shmobile/sleep.S +++ b/arch/sh/kernel/cpu/shmobile/sleep.S | |||
@@ -16,19 +16,52 @@ | |||
16 | #include <asm/asm-offsets.h> | 16 | #include <asm/asm-offsets.h> |
17 | #include <asm/suspend.h> | 17 | #include <asm/suspend.h> |
18 | 18 | ||
19 | /* | ||
20 | * Kernel mode register usage, see entry.S: | ||
21 | * k0 scratch | ||
22 | * k1 scratch | ||
23 | * k4 scratch | ||
24 | */ | ||
25 | #define k0 r0 | ||
26 | #define k1 r1 | ||
27 | #define k4 r4 | ||
28 | |||
19 | /* manage self-refresh and enter standby mode. | 29 | /* manage self-refresh and enter standby mode. |
20 | * this code will be copied to on-chip memory and executed from there. | 30 | * this code will be copied to on-chip memory and executed from there. |
21 | */ | 31 | */ |
22 | 32 | ||
23 | .balign 4096,0,4096 | 33 | .balign 4096,0,4096 |
24 | ENTRY(sh_mobile_standby) | 34 | ENTRY(sh_mobile_standby) |
35 | |||
36 | /* save original vbr */ | ||
37 | stc vbr, r1 | ||
38 | mova saved_vbr, r0 | ||
39 | mov.l r1, @r0 | ||
40 | |||
41 | /* point vbr to our on-chip memory page */ | ||
42 | ldc r5, vbr | ||
43 | |||
44 | /* save return address */ | ||
45 | mova saved_spc, r0 | ||
46 | sts pr, r5 | ||
47 | mov.l r5, @r0 | ||
48 | |||
49 | /* save sr */ | ||
50 | mova saved_sr, r0 | ||
51 | stc sr, r5 | ||
52 | mov.l r5, @r0 | ||
53 | |||
54 | /* save mode flags */ | ||
55 | mova saved_mode, r0 | ||
56 | mov.l r4, @r0 | ||
57 | |||
58 | /* put mode flags in r0 */ | ||
25 | mov r4, r0 | 59 | mov r4, r0 |
26 | 60 | ||
27 | tst #SUSP_SH_SF, r0 | 61 | tst #SUSP_SH_SF, r0 |
28 | bt skip_set_sf | 62 | bt skip_set_sf |
29 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 | 63 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 |
30 | /* DBSC: put memory in self-refresh mode */ | 64 | /* DBSC: put memory in self-refresh mode */ |
31 | |||
32 | mov.l dben_reg, r4 | 65 | mov.l dben_reg, r4 |
33 | mov.l dben_data0, r1 | 66 | mov.l dben_data0, r1 |
34 | mov.l r1, @r4 | 67 | mov.l r1, @r4 |
@@ -60,14 +93,6 @@ ENTRY(sh_mobile_standby) | |||
60 | #endif | 93 | #endif |
61 | 94 | ||
62 | skip_set_sf: | 95 | skip_set_sf: |
63 | tst #SUSP_SH_SLEEP, r0 | ||
64 | bt test_standby | ||
65 | |||
66 | /* set mode to "sleep mode" */ | ||
67 | bra do_sleep | ||
68 | mov #0x00, r1 | ||
69 | |||
70 | test_standby: | ||
71 | tst #SUSP_SH_STANDBY, r0 | 96 | tst #SUSP_SH_STANDBY, r0 |
72 | bt test_rstandby | 97 | bt test_rstandby |
73 | 98 | ||
@@ -85,77 +110,107 @@ test_rstandby: | |||
85 | 110 | ||
86 | test_ustandby: | 111 | test_ustandby: |
87 | tst #SUSP_SH_USTANDBY, r0 | 112 | tst #SUSP_SH_USTANDBY, r0 |
88 | bt done_sleep | 113 | bt force_sleep |
89 | 114 | ||
90 | /* set mode to "u-standby mode" */ | 115 | /* set mode to "u-standby mode" */ |
91 | mov #0x10, r1 | 116 | bra do_sleep |
117 | mov #0x10, r1 | ||
92 | 118 | ||
93 | /* fall-through */ | 119 | force_sleep: |
120 | |||
121 | /* set mode to "sleep mode" */ | ||
122 | mov #0x00, r1 | ||
94 | 123 | ||
95 | do_sleep: | 124 | do_sleep: |
96 | /* setup and enter selected standby mode */ | 125 | /* setup and enter selected standby mode */ |
97 | mov.l 5f, r4 | 126 | mov.l 5f, r4 |
98 | mov.l r1, @r4 | 127 | mov.l r1, @r4 |
128 | again: | ||
99 | sleep | 129 | sleep |
130 | bra again | ||
131 | nop | ||
132 | |||
133 | restore_jump_vbr: | ||
134 | /* setup spc with return address to c code */ | ||
135 | mov.l saved_spc, k0 | ||
136 | ldc k0, spc | ||
137 | |||
138 | /* restore vbr */ | ||
139 | mov.l saved_vbr, k0 | ||
140 | ldc k0, vbr | ||
141 | |||
142 | /* setup ssr with saved sr */ | ||
143 | mov.l saved_sr, k0 | ||
144 | ldc k0, ssr | ||
145 | |||
146 | /* get mode flags */ | ||
147 | mov.l saved_mode, k0 | ||
100 | 148 | ||
101 | done_sleep: | 149 | done_sleep: |
102 | /* reset standby mode to sleep mode */ | 150 | /* reset standby mode to sleep mode */ |
103 | mov.l 5f, r4 | 151 | mov.l 5f, k4 |
104 | mov #0x00, r1 | 152 | mov #0x00, k1 |
105 | mov.l r1, @r4 | 153 | mov.l k1, @k4 |
106 | 154 | ||
107 | tst #SUSP_SH_SF, r0 | 155 | tst #SUSP_SH_SF, k0 |
108 | bt skip_restore_sf | 156 | bt skip_restore_sf |
109 | 157 | ||
110 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 | 158 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 |
111 | /* DBSC: put memory in auto-refresh mode */ | 159 | /* DBSC: put memory in auto-refresh mode */ |
160 | mov.l dbrfpdn0_reg, k4 | ||
161 | mov.l dbrfpdn0_data0, k1 | ||
162 | mov.l k1, @k4 | ||
112 | 163 | ||
113 | mov.l dbrfpdn0_reg, r4 | 164 | nop /* sleep 140 ns */ |
114 | mov.l dbrfpdn0_data0, r1 | ||
115 | mov.l r1, @r4 | ||
116 | |||
117 | /* sleep 140 ns */ | ||
118 | nop | ||
119 | nop | 165 | nop |
120 | nop | 166 | nop |
121 | nop | 167 | nop |
122 | 168 | ||
123 | mov.l dbcmdcnt_reg, r4 | 169 | mov.l dbcmdcnt_reg, k4 |
124 | mov.l dbcmdcnt_data0, r1 | 170 | mov.l dbcmdcnt_data0, k1 |
125 | mov.l r1, @r4 | 171 | mov.l k1, @k4 |
126 | 172 | ||
127 | mov.l dbcmdcnt_reg, r4 | 173 | mov.l dbcmdcnt_reg, k4 |
128 | mov.l dbcmdcnt_data1, r1 | 174 | mov.l dbcmdcnt_data1, k1 |
129 | mov.l r1, @r4 | 175 | mov.l k1, @k4 |
130 | 176 | ||
131 | mov.l dben_reg, r4 | 177 | mov.l dben_reg, k4 |
132 | mov.l dben_data1, r1 | 178 | mov.l dben_data1, k1 |
133 | mov.l r1, @r4 | 179 | mov.l k1, @k4 |
134 | 180 | ||
135 | mov.l dbrfpdn0_reg, r4 | 181 | mov.l dbrfpdn0_reg, k4 |
136 | mov.l dbrfpdn0_data2, r1 | 182 | mov.l dbrfpdn0_data2, k1 |
137 | mov.l r1, @r4 | 183 | mov.l k1, @k4 |
138 | #else | 184 | #else |
139 | /* SBSC: set auto-refresh mode */ | 185 | /* SBSC: set auto-refresh mode */ |
140 | mov.l 1f, r4 | 186 | mov.l 1f, k4 |
141 | mov.l @r4, r2 | 187 | mov.l @k4, k0 |
142 | mov.l 4f, r3 | 188 | mov.l 4f, k1 |
143 | and r3, r2 | 189 | and k1, k0 |
144 | mov.l r2, @r4 | 190 | mov.l k0, @k4 |
145 | mov.l 6f, r4 | 191 | mov.l 6f, k4 |
146 | mov.l 7f, r1 | 192 | mov.l 8f, k0 |
147 | mov.l 8f, r2 | 193 | mov.l @k4, k1 |
148 | mov.l @r4, r3 | 194 | mov #-1, k4 |
149 | mov #-1, r4 | 195 | add k4, k1 |
150 | add r4, r3 | 196 | or k1, k0 |
151 | or r2, r3 | 197 | mov.l 7f, k1 |
152 | mov.l r3, @r1 | 198 | mov.l k0, @k1 |
153 | #endif | 199 | #endif |
154 | skip_restore_sf: | 200 | skip_restore_sf: |
155 | rts | 201 | /* jump to vbr vector */ |
202 | mov.l saved_vbr, k0 | ||
203 | mov.l offset_vbr, k4 | ||
204 | add k4, k0 | ||
205 | jmp @k0 | ||
156 | nop | 206 | nop |
157 | 207 | ||
158 | .balign 4 | 208 | .balign 4 |
209 | saved_mode: .long 0 | ||
210 | saved_spc: .long 0 | ||
211 | saved_sr: .long 0 | ||
212 | saved_vbr: .long 0 | ||
213 | offset_vbr: .long 0x600 | ||
159 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 | 214 | #ifdef CONFIG_CPU_SUBTYPE_SH7724 |
160 | dben_reg: .long 0xfd000010 /* DBEN */ | 215 | dben_reg: .long 0xfd000010 /* DBEN */ |
161 | dben_data0: .long 0 | 216 | dben_data0: .long 0 |
@@ -178,12 +233,12 @@ dbcmdcnt_data1: .long 4 | |||
178 | 7: .long 0xfe400018 /* RTCNT */ | 233 | 7: .long 0xfe400018 /* RTCNT */ |
179 | 8: .long 0xa55a0000 | 234 | 8: .long 0xa55a0000 |
180 | 235 | ||
236 | |||
181 | /* interrupt vector @ 0x600 */ | 237 | /* interrupt vector @ 0x600 */ |
182 | .balign 0x400,0,0x400 | 238 | .balign 0x400,0,0x400 |
183 | .long 0xdeadbeef | 239 | .long 0xdeadbeef |
184 | .balign 0x200,0,0x200 | 240 | .balign 0x200,0,0x200 |
185 | /* sh7722 will end up here in sleep mode */ | 241 | bra restore_jump_vbr |
186 | rte | ||
187 | nop | 242 | nop |
188 | sh_mobile_standby_end: | 243 | sh_mobile_standby_end: |
189 | 244 | ||