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 | ||
69 | int platform_pm_runtime_suspend(struct device *dev) | |
70 | { | |
71 | struct pm_runtime_data *prd = __to_prd(dev); | |
72 | ||
73 | dev_dbg(dev, "platform_pm_runtime_suspend()\n"); | |
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 | ||
85 | int platform_pm_runtime_resume(struct device *dev) | |
86 | { | |
87 | struct pm_runtime_data *prd = __to_prd(dev); | |
88 | ||
89 | dev_dbg(dev, "platform_pm_runtime_resume()\n"); | |
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 | ||
101 | int platform_pm_runtime_idle(struct device *dev) | |
102 | { | |
103 | /* suspend synchronously to disable clocks immediately */ | |
104 | return pm_runtime_suspend(dev); | |
105 | } | |
106 | ||
107 | static int platform_bus_notify(struct notifier_block *nb, | |
108 | unsigned long action, void *data) | |
109 | { | |
110 | struct device *dev = data; | |
111 | struct pm_runtime_data *prd; | |
112 | ||
113 | dev_dbg(dev, "platform_bus_notify() %ld !\n", action); | |
114 | ||
115 | if (action == BUS_NOTIFY_BIND_DRIVER) { | |
116 | prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL); | |
117 | if (prd) | |
118 | devres_add(dev, prd); | |
119 | else | |
120 | dev_err(dev, "unable to alloc memory for runtime pm\n"); | |
121 | } | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | #else /* CONFIG_PM_RUNTIME */ | |
127 | ||
128 | static int platform_bus_notify(struct notifier_block *nb, | |
129 | unsigned long action, void *data) | |
130 | { | |
131 | struct device *dev = data; | |
132 | struct clk *clk; | |
133 | ||
134 | dev_dbg(dev, "platform_bus_notify() %ld !\n", action); | |
135 | ||
136 | switch (action) { | |
137 | case BUS_NOTIFY_BIND_DRIVER: | |
138 | clk = clk_get(dev, NULL); | |
139 | if (!IS_ERR(clk)) { | |
140 | clk_enable(clk); | |
141 | clk_put(clk); | |
142 | dev_info(dev, "runtime pm disabled, clock forced on\n"); | |
143 | } | |
144 | break; | |
145 | case BUS_NOTIFY_UNBOUND_DRIVER: | |
146 | clk = clk_get(dev, NULL); | |
147 | if (!IS_ERR(clk)) { | |
148 | clk_disable(clk); | |
149 | clk_put(clk); | |
150 | dev_info(dev, "runtime pm disabled, clock forced off\n"); | |
151 | } | |
152 | break; | |
153 | } | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | #endif /* CONFIG_PM_RUNTIME */ | |
159 | ||
160 | static struct notifier_block platform_bus_notifier = { | |
161 | .notifier_call = platform_bus_notify | |
162 | }; | |
163 | ||
164 | static int __init sh_pm_runtime_init(void) | |
165 | { | |
166 | bus_register_notifier(&platform_bus_type, &platform_bus_notifier); | |
167 | return 0; | |
168 | } | |
169 | core_initcall(sh_pm_runtime_init); |