Commit d164f6d4f9108126f69ba2963cf6fb7ef4ba9232
Committed by
Josh Boyer
1 parent
46f5221049
Exists in
master
and in
7 other branches
powerpc/4xx: Add suspend and idle support
Add suspend/resume support for 4xx compatible CPUs. See /sys/power/state for available power states configured in. Add two different idle states (idle-wait and idle-doze) controlled via sysfs. Default is idle-wait. cat /sys/devices/system/cpu/cpu0/idle [wait] doze To save additional power, use idle-doze. echo doze > /sys/devices/system/cpu/cpu0/idle cat /sys/devices/system/cpu/cpu0/idle wait [doze] Signed-off-by: Victor Gallardo <vgallardo@apm.com> Signed-off-by: Josh Boyer <jwboyer@linux.vnet.ibm.com>
Showing 5 changed files with 413 additions and 4 deletions Side-by-side Diff
Documentation/powerpc/dts-bindings/4xx/cpm.txt
1 | +PPC4xx Clock Power Management (CPM) node | |
2 | + | |
3 | +Required properties: | |
4 | + - compatible : compatible list, currently only "ibm,cpm" | |
5 | + - dcr-access-method : "native" | |
6 | + - dcr-reg : < DCR register range > | |
7 | + | |
8 | +Optional properties: | |
9 | + - er-offset : All 4xx SoCs with a CPM controller have | |
10 | + one of two different order for the CPM | |
11 | + registers. Some have the CPM registers | |
12 | + in the following order (ER,FR,SR). The | |
13 | + others have them in the following order | |
14 | + (SR,ER,FR). For the second case set | |
15 | + er-offset = <1>. | |
16 | + - unused-units : specifier consist of one cell. For each | |
17 | + bit in the cell, the corresponding bit | |
18 | + in CPM will be set to turn off unused | |
19 | + devices. | |
20 | + - idle-doze : specifier consist of one cell. For each | |
21 | + bit in the cell, the corresponding bit | |
22 | + in CPM will be set to turn off unused | |
23 | + devices. This is usually just CPM[CPU]. | |
24 | + - standby : specifier consist of one cell. For each | |
25 | + bit in the cell, the corresponding bit | |
26 | + in CPM will be set on standby and | |
27 | + restored on resume. | |
28 | + - suspend : specifier consist of one cell. For each | |
29 | + bit in the cell, the corresponding bit | |
30 | + in CPM will be set on suspend (mem) and | |
31 | + restored on resume. Note, for standby | |
32 | + and suspend the corresponding bits can | |
33 | + be different or the same. Usually for | |
34 | + standby only class 2 and 3 units are set. | |
35 | + However, the interface does not care. | |
36 | + If they are the same, the additional | |
37 | + power saving will be seeing if support | |
38 | + is available to put the DDR in self | |
39 | + refresh mode and any additional power | |
40 | + saving techniques for the specific SoC. | |
41 | + | |
42 | +Example: | |
43 | + CPM0: cpm { | |
44 | + compatible = "ibm,cpm"; | |
45 | + dcr-access-method = "native"; | |
46 | + dcr-reg = <0x160 0x003>; | |
47 | + er-offset = <0>; | |
48 | + unused-units = <0x00000100>; | |
49 | + idle-doze = <0x02000000>; | |
50 | + standby = <0xfeff0000>; | |
51 | + suspend = <0xfeff791d>; | |
52 | +}; |
arch/powerpc/Kconfig
... | ... | @@ -212,7 +212,7 @@ |
212 | 212 | config ARCH_SUSPEND_POSSIBLE |
213 | 213 | def_bool y |
214 | 214 | depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \ |
215 | - PPC_85xx || PPC_86xx || PPC_PSERIES | |
215 | + PPC_85xx || PPC_86xx || PPC_PSERIES || 44x || 40x | |
216 | 216 | |
217 | 217 | config PPC_DCR_NATIVE |
218 | 218 | bool |
219 | 219 | |
... | ... | @@ -598,13 +598,11 @@ |
598 | 598 | |
599 | 599 | If unsure, leave blank |
600 | 600 | |
601 | -if !44x || BROKEN | |
602 | 601 | config ARCH_WANTS_FREEZER_CONTROL |
603 | 602 | def_bool y |
604 | 603 | depends on ADB_PMU |
605 | 604 | |
606 | 605 | source kernel/power/Kconfig |
607 | -endif | |
608 | 606 | |
609 | 607 | config SECCOMP |
610 | 608 | bool "Enable seccomp to safely compute untrusted bytecode" |
... | ... | @@ -684,6 +682,15 @@ |
684 | 682 | help |
685 | 683 | Freescale MPC85xx/MPC86xx power management controller support |
686 | 684 | (suspend/resume). For MPC83xx see platforms/83xx/suspend.c |
685 | + | |
686 | +config PPC4xx_CPM | |
687 | + bool | |
688 | + default y | |
689 | + depends on SUSPEND && (44x || 40x) | |
690 | + help | |
691 | + PPC4xx Clock Power Management (CPM) support (suspend/resume). | |
692 | + It also enables support for two different idle states (idle-wait | |
693 | + and idle-doze). | |
687 | 694 | |
688 | 695 | config 4xx_SOC |
689 | 696 | bool |
arch/powerpc/platforms/44x/Makefile
arch/powerpc/sysdev/Makefile
arch/powerpc/sysdev/ppc4xx_cpm.c
1 | +/* | |
2 | + * PowerPC 4xx Clock and Power Management | |
3 | + * | |
4 | + * Copyright (C) 2010, Applied Micro Circuits Corporation | |
5 | + * Victor Gallardo (vgallardo@apm.com) | |
6 | + * | |
7 | + * Based on arch/powerpc/platforms/44x/idle.c: | |
8 | + * Jerone Young <jyoung5@us.ibm.com> | |
9 | + * Copyright 2008 IBM Corp. | |
10 | + * | |
11 | + * Based on arch/powerpc/sysdev/fsl_pmc.c: | |
12 | + * Anton Vorontsov <avorontsov@ru.mvista.com> | |
13 | + * Copyright 2009 MontaVista Software, Inc. | |
14 | + * | |
15 | + * See file CREDITS for list of people who contributed to this | |
16 | + * project. | |
17 | + * | |
18 | + * This program is free software; you can redistribute it and/or | |
19 | + * modify it under the terms of the GNU General Public License as | |
20 | + * published by the Free Software Foundation; either version 2 of | |
21 | + * the License, or (at your option) any later version. | |
22 | + * | |
23 | + * This program is distributed in the hope that it will be useful, | |
24 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
26 | + * GNU General Public License for more details. | |
27 | + * | |
28 | + * You should have received a copy of the GNU General Public License | |
29 | + * along with this program; if not, write to the Free Software | |
30 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
31 | + * MA 02111-1307 USA | |
32 | + */ | |
33 | + | |
34 | +#include <linux/kernel.h> | |
35 | +#include <linux/of_platform.h> | |
36 | +#include <linux/sysfs.h> | |
37 | +#include <linux/cpu.h> | |
38 | +#include <linux/suspend.h> | |
39 | +#include <asm/dcr.h> | |
40 | +#include <asm/dcr-native.h> | |
41 | +#include <asm/machdep.h> | |
42 | + | |
43 | +#define CPM_ER 0 | |
44 | +#define CPM_FR 1 | |
45 | +#define CPM_SR 2 | |
46 | + | |
47 | +#define CPM_IDLE_WAIT 0 | |
48 | +#define CPM_IDLE_DOZE 1 | |
49 | + | |
50 | +struct cpm { | |
51 | + dcr_host_t dcr_host; | |
52 | + unsigned int dcr_offset[3]; | |
53 | + unsigned int powersave_off; | |
54 | + unsigned int unused; | |
55 | + unsigned int idle_doze; | |
56 | + unsigned int standby; | |
57 | + unsigned int suspend; | |
58 | +}; | |
59 | + | |
60 | +static struct cpm cpm; | |
61 | + | |
62 | +struct cpm_idle_mode { | |
63 | + unsigned int enabled; | |
64 | + const char *name; | |
65 | +}; | |
66 | + | |
67 | +static struct cpm_idle_mode idle_mode[] = { | |
68 | + [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ | |
69 | + [CPM_IDLE_DOZE] = { 0, "doze" }, | |
70 | +}; | |
71 | + | |
72 | +static unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) | |
73 | +{ | |
74 | + unsigned int value; | |
75 | + | |
76 | + /* CPM controller supports 3 different types of sleep interface | |
77 | + * known as class 1, 2 and 3. For class 1 units, they are | |
78 | + * unconditionally put to sleep when the corresponding CPM bit is | |
79 | + * set. For class 2 and 3 units this is not case; if they can be | |
80 | + * put to to sleep, they will. Here we do not verify, we just | |
81 | + * set them and expect them to eventually go off when they can. | |
82 | + */ | |
83 | + value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); | |
84 | + dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); | |
85 | + | |
86 | + /* return old state, to restore later if needed */ | |
87 | + return value; | |
88 | +} | |
89 | + | |
90 | +static void cpm_idle_wait(void) | |
91 | +{ | |
92 | + unsigned long msr_save; | |
93 | + | |
94 | + /* save off initial state */ | |
95 | + msr_save = mfmsr(); | |
96 | + /* sync required when CPM0_ER[CPU] is set */ | |
97 | + mb(); | |
98 | + /* set wait state MSR */ | |
99 | + mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); | |
100 | + isync(); | |
101 | + /* return to initial state */ | |
102 | + mtmsr(msr_save); | |
103 | + isync(); | |
104 | +} | |
105 | + | |
106 | +static void cpm_idle_sleep(unsigned int mask) | |
107 | +{ | |
108 | + unsigned int er_save; | |
109 | + | |
110 | + /* update CPM_ER state */ | |
111 | + er_save = cpm_set(CPM_ER, mask); | |
112 | + | |
113 | + /* go to wait state so that CPM0_ER[CPU] can take effect */ | |
114 | + cpm_idle_wait(); | |
115 | + | |
116 | + /* restore CPM_ER state */ | |
117 | + dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); | |
118 | +} | |
119 | + | |
120 | +static void cpm_idle_doze(void) | |
121 | +{ | |
122 | + cpm_idle_sleep(cpm.idle_doze); | |
123 | +} | |
124 | + | |
125 | +static void cpm_idle_config(int mode) | |
126 | +{ | |
127 | + int i; | |
128 | + | |
129 | + if (idle_mode[mode].enabled) | |
130 | + return; | |
131 | + | |
132 | + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) | |
133 | + idle_mode[i].enabled = 0; | |
134 | + | |
135 | + idle_mode[mode].enabled = 1; | |
136 | +} | |
137 | + | |
138 | +static ssize_t cpm_idle_show(struct kobject *kobj, | |
139 | + struct kobj_attribute *attr, char *buf) | |
140 | +{ | |
141 | + char *s = buf; | |
142 | + int i; | |
143 | + | |
144 | + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { | |
145 | + if (idle_mode[i].enabled) | |
146 | + s += sprintf(s, "[%s] ", idle_mode[i].name); | |
147 | + else | |
148 | + s += sprintf(s, "%s ", idle_mode[i].name); | |
149 | + } | |
150 | + | |
151 | + *(s-1) = '\n'; /* convert the last space to a newline */ | |
152 | + | |
153 | + return s - buf; | |
154 | +} | |
155 | + | |
156 | +static ssize_t cpm_idle_store(struct kobject *kobj, | |
157 | + struct kobj_attribute *attr, | |
158 | + const char *buf, size_t n) | |
159 | +{ | |
160 | + int i; | |
161 | + char *p; | |
162 | + int len; | |
163 | + | |
164 | + p = memchr(buf, '\n', n); | |
165 | + len = p ? p - buf : n; | |
166 | + | |
167 | + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { | |
168 | + if (strncmp(buf, idle_mode[i].name, len) == 0) { | |
169 | + cpm_idle_config(i); | |
170 | + return n; | |
171 | + } | |
172 | + } | |
173 | + | |
174 | + return -EINVAL; | |
175 | +} | |
176 | + | |
177 | +static struct kobj_attribute cpm_idle_attr = | |
178 | + __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); | |
179 | + | |
180 | +static void cpm_idle_config_sysfs(void) | |
181 | +{ | |
182 | + struct sys_device *sys_dev; | |
183 | + unsigned long ret; | |
184 | + | |
185 | + sys_dev = get_cpu_sysdev(0); | |
186 | + | |
187 | + ret = sysfs_create_file(&sys_dev->kobj, | |
188 | + &cpm_idle_attr.attr); | |
189 | + if (ret) | |
190 | + printk(KERN_WARNING | |
191 | + "cpm: failed to create idle sysfs entry\n"); | |
192 | +} | |
193 | + | |
194 | +static void cpm_idle(void) | |
195 | +{ | |
196 | + if (idle_mode[CPM_IDLE_DOZE].enabled) | |
197 | + cpm_idle_doze(); | |
198 | + else | |
199 | + cpm_idle_wait(); | |
200 | +} | |
201 | + | |
202 | +static int cpm_suspend_valid(suspend_state_t state) | |
203 | +{ | |
204 | + switch (state) { | |
205 | + case PM_SUSPEND_STANDBY: | |
206 | + return !!cpm.standby; | |
207 | + case PM_SUSPEND_MEM: | |
208 | + return !!cpm.suspend; | |
209 | + default: | |
210 | + return 0; | |
211 | + } | |
212 | +} | |
213 | + | |
214 | +static void cpm_suspend_standby(unsigned int mask) | |
215 | +{ | |
216 | + unsigned long tcr_save; | |
217 | + | |
218 | + /* disable decrement interrupt */ | |
219 | + tcr_save = mfspr(SPRN_TCR); | |
220 | + mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); | |
221 | + | |
222 | + /* go to sleep state */ | |
223 | + cpm_idle_sleep(mask); | |
224 | + | |
225 | + /* restore decrement interrupt */ | |
226 | + mtspr(SPRN_TCR, tcr_save); | |
227 | +} | |
228 | + | |
229 | +static int cpm_suspend_enter(suspend_state_t state) | |
230 | +{ | |
231 | + switch (state) { | |
232 | + case PM_SUSPEND_STANDBY: | |
233 | + cpm_suspend_standby(cpm.standby); | |
234 | + break; | |
235 | + case PM_SUSPEND_MEM: | |
236 | + cpm_suspend_standby(cpm.suspend); | |
237 | + break; | |
238 | + } | |
239 | + | |
240 | + return 0; | |
241 | +} | |
242 | + | |
243 | +static struct platform_suspend_ops cpm_suspend_ops = { | |
244 | + .valid = cpm_suspend_valid, | |
245 | + .enter = cpm_suspend_enter, | |
246 | +}; | |
247 | + | |
248 | +static int cpm_get_uint_property(struct device_node *np, | |
249 | + const char *name) | |
250 | +{ | |
251 | + int len; | |
252 | + const unsigned int *prop = of_get_property(np, name, &len); | |
253 | + | |
254 | + if (prop == NULL || len < sizeof(u32)) | |
255 | + return 0; | |
256 | + | |
257 | + return *prop; | |
258 | +} | |
259 | + | |
260 | +static int __init cpm_init(void) | |
261 | +{ | |
262 | + struct device_node *np; | |
263 | + int dcr_base, dcr_len; | |
264 | + int ret = 0; | |
265 | + | |
266 | + if (!cpm.powersave_off) { | |
267 | + cpm_idle_config(CPM_IDLE_WAIT); | |
268 | + ppc_md.power_save = &cpm_idle; | |
269 | + } | |
270 | + | |
271 | + np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); | |
272 | + if (!np) { | |
273 | + ret = -EINVAL; | |
274 | + goto out; | |
275 | + } | |
276 | + | |
277 | + dcr_base = dcr_resource_start(np, 0); | |
278 | + dcr_len = dcr_resource_len(np, 0); | |
279 | + | |
280 | + if (dcr_base == 0 || dcr_len == 0) { | |
281 | + printk(KERN_ERR "cpm: could not parse dcr property for %s\n", | |
282 | + np->full_name); | |
283 | + ret = -EINVAL; | |
284 | + goto out; | |
285 | + } | |
286 | + | |
287 | + cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); | |
288 | + | |
289 | + if (!DCR_MAP_OK(cpm.dcr_host)) { | |
290 | + printk(KERN_ERR "cpm: failed to map dcr property for %s\n", | |
291 | + np->full_name); | |
292 | + ret = -EINVAL; | |
293 | + goto out; | |
294 | + } | |
295 | + | |
296 | + /* All 4xx SoCs with a CPM controller have one of two | |
297 | + * different order for the CPM registers. Some have the | |
298 | + * CPM registers in the following order (ER,FR,SR). The | |
299 | + * others have them in the following order (SR,ER,FR). | |
300 | + */ | |
301 | + | |
302 | + if (cpm_get_uint_property(np, "er-offset") == 0) { | |
303 | + cpm.dcr_offset[CPM_ER] = 0; | |
304 | + cpm.dcr_offset[CPM_FR] = 1; | |
305 | + cpm.dcr_offset[CPM_SR] = 2; | |
306 | + } else { | |
307 | + cpm.dcr_offset[CPM_ER] = 1; | |
308 | + cpm.dcr_offset[CPM_FR] = 2; | |
309 | + cpm.dcr_offset[CPM_SR] = 0; | |
310 | + } | |
311 | + | |
312 | + /* Now let's see what IPs to turn off for the following modes */ | |
313 | + | |
314 | + cpm.unused = cpm_get_uint_property(np, "unused-units"); | |
315 | + cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); | |
316 | + cpm.standby = cpm_get_uint_property(np, "standby"); | |
317 | + cpm.suspend = cpm_get_uint_property(np, "suspend"); | |
318 | + | |
319 | + /* If some IPs are unused let's turn them off now */ | |
320 | + | |
321 | + if (cpm.unused) { | |
322 | + cpm_set(CPM_ER, cpm.unused); | |
323 | + cpm_set(CPM_FR, cpm.unused); | |
324 | + } | |
325 | + | |
326 | + /* Now let's export interfaces */ | |
327 | + | |
328 | + if (!cpm.powersave_off && cpm.idle_doze) | |
329 | + cpm_idle_config_sysfs(); | |
330 | + | |
331 | + if (cpm.standby || cpm.suspend) | |
332 | + suspend_set_ops(&cpm_suspend_ops); | |
333 | +out: | |
334 | + if (np) | |
335 | + of_node_put(np); | |
336 | + return ret; | |
337 | +} | |
338 | + | |
339 | +late_initcall(cpm_init); | |
340 | + | |
341 | +static int __init cpm_powersave_off(char *arg) | |
342 | +{ | |
343 | + cpm.powersave_off = 1; | |
344 | + return 0; | |
345 | +} | |
346 | +__setup("powersave=off", cpm_powersave_off); |