aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Gallardo <vgallardo@apm.com>2010-10-08 06:25:27 -0400
committerJosh Boyer <jwboyer@linux.vnet.ibm.com>2010-11-29 10:05:06 -0500
commitd164f6d4f9108126f69ba2963cf6fb7ef4ba9232 (patch)
tree9f0f97180ee4f7486974c657588e8ee65074fcc2
parent46f5221049bb46b0188aad6b6dfab5dbc778be22 (diff)
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>
-rw-r--r--Documentation/powerpc/dts-bindings/4xx/cpm.txt52
-rw-r--r--arch/powerpc/Kconfig13
-rw-r--r--arch/powerpc/platforms/44x/Makefile5
-rw-r--r--arch/powerpc/sysdev/Makefile1
-rw-r--r--arch/powerpc/sysdev/ppc4xx_cpm.c346
5 files changed, 413 insertions, 4 deletions
diff --git a/Documentation/powerpc/dts-bindings/4xx/cpm.txt b/Documentation/powerpc/dts-bindings/4xx/cpm.txt
new file mode 100644
index 000000000000..ee459806d35e
--- /dev/null
+++ b/Documentation/powerpc/dts-bindings/4xx/cpm.txt
@@ -0,0 +1,52 @@
1PPC4xx Clock Power Management (CPM) node
2
3Required properties:
4 - compatible : compatible list, currently only "ibm,cpm"
5 - dcr-access-method : "native"
6 - dcr-reg : < DCR register range >
7
8Optional 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
42Example:
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};
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 06d742c3fbcf..e16b4988f825 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -212,7 +212,7 @@ config ARCH_HIBERNATION_POSSIBLE
212config ARCH_SUSPEND_POSSIBLE 212config ARCH_SUSPEND_POSSIBLE
213 def_bool y 213 def_bool y
214 depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \ 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
217config PPC_DCR_NATIVE 217config PPC_DCR_NATIVE
218 bool 218 bool
@@ -598,13 +598,11 @@ config EXTRA_TARGETS
598 598
599 If unsure, leave blank 599 If unsure, leave blank
600 600
601if !44x || BROKEN
602config ARCH_WANTS_FREEZER_CONTROL 601config ARCH_WANTS_FREEZER_CONTROL
603 def_bool y 602 def_bool y
604 depends on ADB_PMU 603 depends on ADB_PMU
605 604
606source kernel/power/Kconfig 605source kernel/power/Kconfig
607endif
608 606
609config SECCOMP 607config SECCOMP
610 bool "Enable seccomp to safely compute untrusted bytecode" 608 bool "Enable seccomp to safely compute untrusted bytecode"
@@ -685,6 +683,15 @@ config FSL_PMC
685 Freescale MPC85xx/MPC86xx power management controller support 683 Freescale MPC85xx/MPC86xx power management controller support
686 (suspend/resume). For MPC83xx see platforms/83xx/suspend.c 684 (suspend/resume). For MPC83xx see platforms/83xx/suspend.c
687 685
686config 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).
694
688config 4xx_SOC 695config 4xx_SOC
689 bool 696 bool
690 697
diff --git a/arch/powerpc/platforms/44x/Makefile b/arch/powerpc/platforms/44x/Makefile
index 82ff326e0795..c04d16df8488 100644
--- a/arch/powerpc/platforms/44x/Makefile
+++ b/arch/powerpc/platforms/44x/Makefile
@@ -1,4 +1,7 @@
1obj-$(CONFIG_44x) := misc_44x.o idle.o 1obj-$(CONFIG_44x) += misc_44x.o
2ifneq ($(CONFIG_PPC4xx_CPM),y)
3obj-$(CONFIG_44x) += idle.o
4endif
2obj-$(CONFIG_PPC44x_SIMPLE) += ppc44x_simple.o 5obj-$(CONFIG_PPC44x_SIMPLE) += ppc44x_simple.o
3obj-$(CONFIG_EBONY) += ebony.o 6obj-$(CONFIG_EBONY) += ebony.o
4obj-$(CONFIG_SAM440EP) += sam440ep.o 7obj-$(CONFIG_SAM440EP) += sam440ep.o
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 0bef9dacb64e..9c2973479142 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_OF_RTC) += of_rtc.o
41ifeq ($(CONFIG_PCI),y) 41ifeq ($(CONFIG_PCI),y)
42obj-$(CONFIG_4xx) += ppc4xx_pci.o 42obj-$(CONFIG_4xx) += ppc4xx_pci.o
43endif 43endif
44obj-$(CONFIG_PPC4xx_CPM) += ppc4xx_cpm.o
44obj-$(CONFIG_PPC4xx_GPIO) += ppc4xx_gpio.o 45obj-$(CONFIG_PPC4xx_GPIO) += ppc4xx_gpio.o
45 46
46obj-$(CONFIG_CPM) += cpm_common.o 47obj-$(CONFIG_CPM) += cpm_common.o
diff --git a/arch/powerpc/sysdev/ppc4xx_cpm.c b/arch/powerpc/sysdev/ppc4xx_cpm.c
new file mode 100644
index 000000000000..73b86cc5ea74
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_cpm.c
@@ -0,0 +1,346 @@
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
50struct 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
60static struct cpm cpm;
61
62struct cpm_idle_mode {
63 unsigned int enabled;
64 const char *name;
65};
66
67static struct cpm_idle_mode idle_mode[] = {
68 [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */
69 [CPM_IDLE_DOZE] = { 0, "doze" },
70};
71
72static 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
90static 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
106static 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
120static void cpm_idle_doze(void)
121{
122 cpm_idle_sleep(cpm.idle_doze);
123}
124
125static 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
138static 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
156static 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
177static struct kobj_attribute cpm_idle_attr =
178 __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store);
179
180static 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
194static 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
202static 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
214static 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
229static 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
243static struct platform_suspend_ops cpm_suspend_ops = {
244 .valid = cpm_suspend_valid,
245 .enter = cpm_suspend_enter,
246};
247
248static 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
260static 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);
333out:
334 if (np)
335 of_node_put(np);
336 return ret;
337}
338
339late_initcall(cpm_init);
340
341static int __init cpm_powersave_off(char *arg)
342{
343 cpm.powersave_off = 1;
344 return 0;
345}
346__setup("powersave=off", cpm_powersave_off);