Commit | Line | Data |
---|---|---|
f14c4f14 MD |
1 | /* |
2 | * arch/arm/mach-shmobile/pm_runtime.c | |
3 | * | |
4 | * Runtime PM support code for SuperH Mobile ARM | |
5 | * | |
6 | * Copyright (C) 2009-2010 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 | ||
13 | #include <linux/init.h> | |
14 | #include <linux/kernel.h> | |
15 | #include <linux/io.h> | |
16 | #include <linux/pm_runtime.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/clk.h> | |
19 | #include <linux/sh_clk.h> | |
20 | #include <linux/bitmap.h> | |
21 | ||
22 | #ifdef CONFIG_PM_RUNTIME | |
23 | #define BIT_ONCE 0 | |
24 | #define BIT_ACTIVE 1 | |
25 | #define BIT_CLK_ENABLED 2 | |
26 | ||
27 | struct pm_runtime_data { | |
28 | unsigned long flags; | |
29 | struct clk *clk; | |
30 | }; | |
31 | ||
32 | static void __devres_release(struct device *dev, void *res) | |
33 | { | |
34 | struct pm_runtime_data *prd = res; | |
35 | ||
36 | dev_dbg(dev, "__devres_release()\n"); | |
37 | ||
38 | if (test_bit(BIT_CLK_ENABLED, &prd->flags)) | |
39 | clk_disable(prd->clk); | |
40 | ||
41 | if (test_bit(BIT_ACTIVE, &prd->flags)) | |
42 | clk_put(prd->clk); | |
43 | } | |
44 | ||
45 | static struct pm_runtime_data *__to_prd(struct device *dev) | |
46 | { | |
47 | return devres_find(dev, __devres_release, NULL, NULL); | |
48 | } | |
49 | ||
50 | static void platform_pm_runtime_init(struct device *dev, | |
51 | struct pm_runtime_data *prd) | |
52 | { | |
53 | if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) { | |
54 | prd->clk = clk_get(dev, NULL); | |
55 | if (!IS_ERR(prd->clk)) { | |
56 | set_bit(BIT_ACTIVE, &prd->flags); | |
57 | dev_info(dev, "clocks managed by runtime pm\n"); | |
58 | } | |
59 | } | |
60 | } | |
61 | ||
62 | static void platform_pm_runtime_bug(struct device *dev, | |
63 | struct pm_runtime_data *prd) | |
64 | { | |
65 | if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) | |
66 | dev_err(dev, "runtime pm suspend before resume\n"); | |
67 | } | |
68 | ||
38ade3a1 | 69 | static int default_platform_runtime_suspend(struct device *dev) |
f14c4f14 MD |
70 | { |
71 | struct pm_runtime_data *prd = __to_prd(dev); | |
72 | ||
38ade3a1 | 73 | dev_dbg(dev, "%s()\n", __func__); |
f14c4f14 MD |
74 | |
75 | platform_pm_runtime_bug(dev, prd); | |
76 | ||
77 | if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { | |
78 | clk_disable(prd->clk); | |
79 | clear_bit(BIT_CLK_ENABLED, &prd->flags); | |
80 | } | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
38ade3a1 | 85 | static int default_platform_runtime_resume(struct device *dev) |
f14c4f14 MD |
86 | { |
87 | struct pm_runtime_data *prd = __to_prd(dev); | |
88 | ||
38ade3a1 | 89 | dev_dbg(dev, "%s()\n", __func__); |
f14c4f14 MD |
90 | |
91 | platform_pm_runtime_init(dev, prd); | |
92 | ||
93 | if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { | |
94 | clk_enable(prd->clk); | |
95 | set_bit(BIT_CLK_ENABLED, &prd->flags); | |
96 | } | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
38ade3a1 | 101 | static int default_platform_runtime_idle(struct device *dev) |
f14c4f14 MD |
102 | { |
103 | /* suspend synchronously to disable clocks immediately */ | |
104 | return pm_runtime_suspend(dev); | |
105 | } | |
106 | ||
38ade3a1 RW |
107 | static struct dev_power_domain default_power_domain = { |
108 | .ops = { | |
109 | .runtime_suspend = default_platform_runtime_suspend, | |
110 | .runtime_resume = default_platform_runtime_resume, | |
111 | .runtime_idle = default_platform_runtime_idle, | |
112 | USE_PLATFORM_PM_SLEEP_OPS | |
113 | }, | |
114 | }; | |
115 | ||
f14c4f14 MD |
116 | static int platform_bus_notify(struct notifier_block *nb, |
117 | unsigned long action, void *data) | |
118 | { | |
119 | struct device *dev = data; | |
120 | struct pm_runtime_data *prd; | |
121 | ||
122 | dev_dbg(dev, "platform_bus_notify() %ld !\n", action); | |
123 | ||
124 | if (action == BUS_NOTIFY_BIND_DRIVER) { | |
125 | prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL); | |
38ade3a1 | 126 | if (prd) { |
f14c4f14 | 127 | devres_add(dev, prd); |
38ade3a1 RW |
128 | dev->pwr_domain = &default_power_domain; |
129 | } else { | |
f14c4f14 | 130 | dev_err(dev, "unable to alloc memory for runtime pm\n"); |
38ade3a1 | 131 | } |
f14c4f14 MD |
132 | } |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | #else /* CONFIG_PM_RUNTIME */ | |
138 | ||
139 | static int platform_bus_notify(struct notifier_block *nb, | |
140 | unsigned long action, void *data) | |
141 | { | |
142 | struct device *dev = data; | |
143 | struct clk *clk; | |
144 | ||
145 | dev_dbg(dev, "platform_bus_notify() %ld !\n", action); | |
146 | ||
147 | switch (action) { | |
148 | case BUS_NOTIFY_BIND_DRIVER: | |
149 | clk = clk_get(dev, NULL); | |
150 | if (!IS_ERR(clk)) { | |
151 | clk_enable(clk); | |
152 | clk_put(clk); | |
153 | dev_info(dev, "runtime pm disabled, clock forced on\n"); | |
154 | } | |
155 | break; | |
156 | case BUS_NOTIFY_UNBOUND_DRIVER: | |
157 | clk = clk_get(dev, NULL); | |
158 | if (!IS_ERR(clk)) { | |
159 | clk_disable(clk); | |
160 | clk_put(clk); | |
161 | dev_info(dev, "runtime pm disabled, clock forced off\n"); | |
162 | } | |
163 | break; | |
164 | } | |
165 | ||
166 | return 0; | |
167 | } | |
168 | ||
169 | #endif /* CONFIG_PM_RUNTIME */ | |
170 | ||
171 | static struct notifier_block platform_bus_notifier = { | |
172 | .notifier_call = platform_bus_notify | |
173 | }; | |
174 | ||
175 | static int __init sh_pm_runtime_init(void) | |
176 | { | |
177 | bus_register_notifier(&platform_bus_type, &platform_bus_notifier); | |
178 | return 0; | |
179 | } | |
180 | core_initcall(sh_pm_runtime_init); |