aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMagnus Damm <damm@igel.co.jp>2009-08-14 06:48:16 -0400
committerPaul Mundt <lethal@linux-sh.org>2009-08-23 05:03:17 -0400
commit6a93dde1e8216f7af9b2551a60fb1a5eeac4a89f (patch)
tree41a36d18a3f9441000f5658772f6263c17925279
parent0858d9c0c591ce9baa1baf72eaf6f67823f3bc25 (diff)
sh: Runtime PM for SuperH Mobile platform bus devices
This patch is V3 of the SuperH Mobile Runtime PM platform bus implentation matching Rafael's Runtime PM v16. The code gets invoked from the SuperH specific Runtime PM platform bus functions that override the weak symbols for: - platform_pm_runtime_suspend() - platform_pm_runtime_resume() - platform_pm_runtime_idle() This Runtime PM implementation performs two levels of power management. At the time of platform bus runtime suspend the clock to the device is stopped instantly. Later on if all devices within the power domain has their clocks stopped then the device driver ->runtime_suspend() callbacks are used to save hardware register state for each device. Device driver ->runtime_suspend() calls are scheduled from cpuidle context using platform_pm_runtime_suspend_idle(). When all devices have been fully suspended the processor is allowed to enter deep sleep from cpuidle. The runtime resume operation turns on clocks and also restores registers if needed. It is worth noting that the devices start in a suspended state and the device driver is responsible for calling runtime resume before accessing the actual hardware. In this particular platform bus implementation runtime resume is not allowed from interrupt context. Runtime suspend is however allowed from interrupt context as long as the synchronous functions are avoided. [ updated for v17 -- PFM. ] Signed-off-by: Magnus Damm <damm@igel.co.jp> Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r--arch/sh/include/asm/device.h9
-rw-r--r--arch/sh/include/asm/hwblk.h4
-rw-r--r--arch/sh/kernel/cpu/shmobile/Makefile1
-rw-r--r--arch/sh/kernel/cpu/shmobile/pm_runtime.c304
4 files changed, 317 insertions, 1 deletions
diff --git a/arch/sh/include/asm/device.h b/arch/sh/include/asm/device.h
index 783ecdc64e25..b16debfe8c1e 100644
--- a/arch/sh/include/asm/device.h
+++ b/arch/sh/include/asm/device.h
@@ -14,6 +14,15 @@ int platform_resource_setup_memory(struct platform_device *pdev,
14 14
15void plat_early_device_setup(void); 15void plat_early_device_setup(void);
16 16
17#define PDEV_ARCHDATA_FLAG_INIT 0
18#define PDEV_ARCHDATA_FLAG_IDLE 1
19#define PDEV_ARCHDATA_FLAG_SUSP 2
20
17struct pdev_archdata { 21struct pdev_archdata {
18 int hwblk_id; 22 int hwblk_id;
23#ifdef CONFIG_PM_RUNTIME
24 unsigned long flags;
25 struct list_head entry;
26 struct mutex mutex;
27#endif
19}; 28};
diff --git a/arch/sh/include/asm/hwblk.h b/arch/sh/include/asm/hwblk.h
index c01d72cb6757..5d3ccae4202b 100644
--- a/arch/sh/include/asm/hwblk.h
+++ b/arch/sh/include/asm/hwblk.h
@@ -5,7 +5,9 @@
5#include <asm/io.h> 5#include <asm/io.h>
6 6
7#define HWBLK_CNT_USAGE 0 7#define HWBLK_CNT_USAGE 0
8#define HWBLK_CNT_NR 1 8#define HWBLK_CNT_IDLE 1
9#define HWBLK_CNT_DEVICES 2
10#define HWBLK_CNT_NR 3
9 11
10#define HWBLK_AREA_FLAG_PARENT (1 << 0) /* valid parent */ 12#define HWBLK_AREA_FLAG_PARENT (1 << 0) /* valid parent */
11 13
diff --git a/arch/sh/kernel/cpu/shmobile/Makefile b/arch/sh/kernel/cpu/shmobile/Makefile
index e8a5111e848a..a39f88ea1a85 100644
--- a/arch/sh/kernel/cpu/shmobile/Makefile
+++ b/arch/sh/kernel/cpu/shmobile/Makefile
@@ -5,3 +5,4 @@
5# Power Management & Sleep mode 5# Power Management & Sleep mode
6obj-$(CONFIG_PM) += pm.o sleep.o 6obj-$(CONFIG_PM) += pm.o sleep.o
7obj-$(CONFIG_CPU_IDLE) += cpuidle.o 7obj-$(CONFIG_CPU_IDLE) += cpuidle.o
8obj-$(CONFIG_PM_RUNTIME) += pm_runtime.o
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..d5f5285a2253
--- /dev/null
+++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c
@@ -0,0 +1,304 @@
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
20static DEFINE_SPINLOCK(hwblk_lock);
21static LIST_HEAD(hwblk_idle_list);
22static struct work_struct hwblk_work;
23
24extern struct hwblk_info *hwblk_info;
25
26static 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
39static 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
67static 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
96static 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 */
132void platform_pm_runtime_suspend_idle(void)
133{
134 queue_work(pm_wq, &hwblk_work);
135}
136
137int 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
180out:
181 dev_dbg(dev, "platform_pm_runtime_suspend() [%d] returns %d\n",
182 hwblk, ret);
183
184 return ret;
185}
186
187int 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);
225out:
226 dev_dbg(dev, "platform_pm_runtime_resume() [%d] returns %d\n",
227 hwblk, ret);
228
229 return ret;
230}
231
232int 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);
249out:
250 dev_dbg(dev, "platform_pm_runtime_idle() [%d] done!\n", hwblk);
251 return ret;
252}
253
254static int __devinit 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
292static struct notifier_block platform_bus_notifier = {
293 .notifier_call = platform_bus_notify
294};
295
296static 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
304core_initcall(sh_pm_runtime_init);