aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh/kernel/cpu/shmobile
diff options
context:
space:
mode:
authorPaul Mundt <lethal@linux-sh.org>2009-08-23 05:04:07 -0400
committerPaul Mundt <lethal@linux-sh.org>2009-08-23 05:04:07 -0400
commitc3144fc46f987413df10e83659f0bf1aad76f79e (patch)
treea09f3c13c32664a617d3981ae111436c3127ccc3 /arch/sh/kernel/cpu/shmobile
parent4f896ffca2b72f4b719746e7fbb0b623252e6ac9 (diff)
parentcc58f597afc63a57bb55ed97c2a72f7405320c93 (diff)
Merge branches 'sh/hwblk' and 'sh/pm-runtime'
Diffstat (limited to 'arch/sh/kernel/cpu/shmobile')
-rw-r--r--arch/sh/kernel/cpu/shmobile/Makefile1
-rw-r--r--arch/sh/kernel/cpu/shmobile/pm_runtime.c303
2 files changed, 304 insertions, 0 deletions
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..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
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 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}
303core_initcall(sh_pm_runtime_init);