diff options
Diffstat (limited to 'arch/powerpc/sysdev/ppc4xx_cpm.c')
| -rw-r--r-- | arch/powerpc/sysdev/ppc4xx_cpm.c | 346 | 
1 files changed, 346 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/ppc4xx_cpm.c b/arch/powerpc/sysdev/ppc4xx_cpm.c new file mode 100644 index 00000000000..82e2cfe35c6 --- /dev/null +++ b/arch/powerpc/sysdev/ppc4xx_cpm.c @@ -0,0 +1,346 @@ +/* + * PowerPC 4xx Clock and Power Management + * + * Copyright (C) 2010, Applied Micro Circuits Corporation + * Victor Gallardo (vgallardo@apm.com) + * + * Based on arch/powerpc/platforms/44x/idle.c: + * Jerone Young <jyoung5@us.ibm.com> + * Copyright 2008 IBM Corp. + * + * Based on arch/powerpc/sysdev/fsl_pmc.c: + * Anton Vorontsov <avorontsov@ru.mvista.com> + * Copyright 2009  MontaVista Software, Inc. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/of_platform.h> +#include <linux/sysfs.h> +#include <linux/cpu.h> +#include <linux/suspend.h> +#include <asm/dcr.h> +#include <asm/dcr-native.h> +#include <asm/machdep.h> + +#define CPM_ER	0 +#define CPM_FR	1 +#define CPM_SR	2 + +#define CPM_IDLE_WAIT	0 +#define CPM_IDLE_DOZE	1 + +struct cpm { +	dcr_host_t	dcr_host; +	unsigned int	dcr_offset[3]; +	unsigned int	powersave_off; +	unsigned int	unused; +	unsigned int	idle_doze; +	unsigned int	standby; +	unsigned int	suspend; +}; + +static struct cpm cpm; + +struct cpm_idle_mode { +	unsigned int enabled; +	const char  *name; +}; + +static struct cpm_idle_mode idle_mode[] = { +	[CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ +	[CPM_IDLE_DOZE] = { 0, "doze" }, +}; + +static unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) +{ +	unsigned int value; + +	/* CPM controller supports 3 different types of sleep interface +	 * known as class 1, 2 and 3. For class 1 units, they are +	 * unconditionally put to sleep when the corresponding CPM bit is +	 * set. For class 2 and 3 units this is not case; if they can be +	 * put to to sleep, they will. Here we do not verify, we just +	 * set them and expect them to eventually go off when they can. +	 */ +	value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); +	dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); + +	/* return old state, to restore later if needed */ +	return value; +} + +static void cpm_idle_wait(void) +{ +	unsigned long msr_save; + +	/* save off initial state */ +	msr_save = mfmsr(); +	/* sync required when CPM0_ER[CPU] is set */ +	mb(); +	/* set wait state MSR */ +	mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); +	isync(); +	/* return to initial state */ +	mtmsr(msr_save); +	isync(); +} + +static void cpm_idle_sleep(unsigned int mask) +{ +	unsigned int er_save; + +	/* update CPM_ER state */ +	er_save = cpm_set(CPM_ER, mask); + +	/* go to wait state so that CPM0_ER[CPU] can take effect */ +	cpm_idle_wait(); + +	/* restore CPM_ER state */ +	dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); +} + +static void cpm_idle_doze(void) +{ +	cpm_idle_sleep(cpm.idle_doze); +} + +static void cpm_idle_config(int mode) +{ +	int i; + +	if (idle_mode[mode].enabled) +		return; + +	for (i = 0; i < ARRAY_SIZE(idle_mode); i++) +		idle_mode[i].enabled = 0; + +	idle_mode[mode].enabled = 1; +} + +static ssize_t cpm_idle_show(struct kobject *kobj, +			     struct kobj_attribute *attr, char *buf) +{ +	char *s = buf; +	int i; + +	for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { +		if (idle_mode[i].enabled) +			s += sprintf(s, "[%s] ", idle_mode[i].name); +		else +			s += sprintf(s, "%s ", idle_mode[i].name); +	} + +	*(s-1) = '\n'; /* convert the last space to a newline */ + +	return s - buf; +} + +static ssize_t cpm_idle_store(struct kobject *kobj, +			      struct kobj_attribute *attr, +			      const char *buf, size_t n) +{ +	int i; +	char *p; +	int len; + +	p = memchr(buf, '\n', n); +	len = p ? p - buf : n; + +	for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { +		if (strncmp(buf, idle_mode[i].name, len) == 0) { +			cpm_idle_config(i); +			return n; +		} +	} + +	return -EINVAL; +} + +static struct kobj_attribute cpm_idle_attr = +	__ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); + +static void cpm_idle_config_sysfs(void) +{ +	struct device *dev; +	unsigned long ret; + +	dev = get_cpu_device(0); + +	ret = sysfs_create_file(&dev->kobj, +				&cpm_idle_attr.attr); +	if (ret) +		printk(KERN_WARNING +		       "cpm: failed to create idle sysfs entry\n"); +} + +static void cpm_idle(void) +{ +	if (idle_mode[CPM_IDLE_DOZE].enabled) +		cpm_idle_doze(); +	else +		cpm_idle_wait(); +} + +static int cpm_suspend_valid(suspend_state_t state) +{ +	switch (state) { +	case PM_SUSPEND_STANDBY: +		return !!cpm.standby; +	case PM_SUSPEND_MEM: +		return !!cpm.suspend; +	default: +		return 0; +	} +} + +static void cpm_suspend_standby(unsigned int mask) +{ +	unsigned long tcr_save; + +	/* disable decrement interrupt */ +	tcr_save = mfspr(SPRN_TCR); +	mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); + +	/* go to sleep state */ +	cpm_idle_sleep(mask); + +	/* restore decrement interrupt */ +	mtspr(SPRN_TCR, tcr_save); +} + +static int cpm_suspend_enter(suspend_state_t state) +{ +	switch (state) { +	case PM_SUSPEND_STANDBY: +		cpm_suspend_standby(cpm.standby); +		break; +	case PM_SUSPEND_MEM: +		cpm_suspend_standby(cpm.suspend); +		break; +	} + +	return 0; +} + +static struct platform_suspend_ops cpm_suspend_ops = { +	.valid		= cpm_suspend_valid, +	.enter		= cpm_suspend_enter, +}; + +static int cpm_get_uint_property(struct device_node *np, +				 const char *name) +{ +	int len; +	const unsigned int *prop = of_get_property(np, name, &len); + +	if (prop == NULL || len < sizeof(u32)) +		return 0; + +	return *prop; +} + +static int __init cpm_init(void) +{ +	struct device_node *np; +	int dcr_base, dcr_len; +	int ret = 0; + +	if (!cpm.powersave_off) { +		cpm_idle_config(CPM_IDLE_WAIT); +		ppc_md.power_save = &cpm_idle; +	} + +	np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); +	if (!np) { +		ret = -EINVAL; +		goto out; +	} + +	dcr_base = dcr_resource_start(np, 0); +	dcr_len = dcr_resource_len(np, 0); + +	if (dcr_base == 0 || dcr_len == 0) { +		printk(KERN_ERR "cpm: could not parse dcr property for %s\n", +		       np->full_name); +		ret = -EINVAL; +		goto out; +	} + +	cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); + +	if (!DCR_MAP_OK(cpm.dcr_host)) { +		printk(KERN_ERR "cpm: failed to map dcr property for %s\n", +		       np->full_name); +		ret = -EINVAL; +		goto out; +	} + +	/* All 4xx SoCs with a CPM controller have one of two +	 * different order for the CPM registers. Some have the +	 * CPM registers in the following order (ER,FR,SR). The +	 * others have them in the following order (SR,ER,FR). +	 */ + +	if (cpm_get_uint_property(np, "er-offset") == 0) { +		cpm.dcr_offset[CPM_ER] = 0; +		cpm.dcr_offset[CPM_FR] = 1; +		cpm.dcr_offset[CPM_SR] = 2; +	} else { +		cpm.dcr_offset[CPM_ER] = 1; +		cpm.dcr_offset[CPM_FR] = 2; +		cpm.dcr_offset[CPM_SR] = 0; +	} + +	/* Now let's see what IPs to turn off for the following modes */ + +	cpm.unused = cpm_get_uint_property(np, "unused-units"); +	cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); +	cpm.standby = cpm_get_uint_property(np, "standby"); +	cpm.suspend = cpm_get_uint_property(np, "suspend"); + +	/* If some IPs are unused let's turn them off now */ + +	if (cpm.unused) { +		cpm_set(CPM_ER, cpm.unused); +		cpm_set(CPM_FR, cpm.unused); +	} + +	/* Now let's export interfaces */ + +	if (!cpm.powersave_off && cpm.idle_doze) +		cpm_idle_config_sysfs(); + +	if (cpm.standby || cpm.suspend) +		suspend_set_ops(&cpm_suspend_ops); +out: +	if (np) +		of_node_put(np); +	return ret; +} + +late_initcall(cpm_init); + +static int __init cpm_powersave_off(char *arg) +{ +	cpm.powersave_off = 1; +	return 0; +} +__setup("powersave=off", cpm_powersave_off);  | 
