Commit d164f6d4f9108126f69ba2963cf6fb7ef4ba9232

Authored by Victor Gallardo
Committed by Josh Boyer
1 parent 46f5221049

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
1   -obj-$(CONFIG_44x) := misc_44x.o idle.o
  1 +obj-$(CONFIG_44x) += misc_44x.o
  2 +ifneq ($(CONFIG_PPC4xx_CPM),y)
  3 +obj-$(CONFIG_44x) += idle.o
  4 +endif
2 5 obj-$(CONFIG_PPC44x_SIMPLE) += ppc44x_simple.o
3 6 obj-$(CONFIG_EBONY) += ebony.o
4 7 obj-$(CONFIG_SAM440EP) += sam440ep.o
arch/powerpc/sysdev/Makefile
... ... @@ -41,6 +41,7 @@
41 41 ifeq ($(CONFIG_PCI),y)
42 42 obj-$(CONFIG_4xx) += ppc4xx_pci.o
43 43 endif
  44 +obj-$(CONFIG_PPC4xx_CPM) += ppc4xx_cpm.o
44 45 obj-$(CONFIG_PPC4xx_GPIO) += ppc4xx_gpio.o
45 46  
46 47 obj-$(CONFIG_CPM) += cpm_common.o
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);