diff options
Diffstat (limited to 'arch/arm/mach-omap2/pm44xx.c')
| -rw-r--r-- | arch/arm/mach-omap2/pm44xx.c | 153 | 
1 files changed, 149 insertions, 4 deletions
diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index 8edb015f561..c264ef7219c 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -1,8 +1,9 @@  /*   * OMAP4 Power Management Routines   * - * Copyright (C) 2010 Texas Instruments, Inc. + * Copyright (C) 2010-2011 Texas Instruments, Inc.   * Rajendra Nayak <rnayak@ti.com> + * Santosh Shilimkar <santosh.shilimkar@ti.com>   *   * This program is free software; you can redistribute it and/or modify   * it under the terms of the GNU General Public License version 2 as @@ -17,13 +18,16 @@  #include <linux/slab.h>  #include "common.h" +#include "clockdomain.h"  #include "powerdomain.h" +#include "pm.h"  struct power_state {  	struct powerdomain *pwrdm;  	u32 next_state;  #ifdef CONFIG_SUSPEND  	u32 saved_state; +	u32 saved_logic_state;  #endif  	struct list_head node;  }; @@ -33,7 +37,50 @@ static LIST_HEAD(pwrst_list);  #ifdef CONFIG_SUSPEND  static int omap4_pm_suspend(void)  { -	do_wfi(); +	struct power_state *pwrst; +	int state, ret = 0; +	u32 cpu_id = smp_processor_id(); + +	/* Save current powerdomain state */ +	list_for_each_entry(pwrst, &pwrst_list, node) { +		pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm); +		pwrst->saved_logic_state = pwrdm_read_logic_retst(pwrst->pwrdm); +	} + +	/* Set targeted power domain states by suspend */ +	list_for_each_entry(pwrst, &pwrst_list, node) { +		omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); +		pwrdm_set_logic_retst(pwrst->pwrdm, PWRDM_POWER_OFF); +	} + +	/* +	 * For MPUSS to hit power domain retention(CSWR or OSWR), +	 * CPU0 and CPU1 power domains need to be in OFF or DORMANT state, +	 * since CPU power domain CSWR is not supported by hardware +	 * Only master CPU follows suspend path. All other CPUs follow +	 * CPU hotplug path in system wide suspend. On OMAP4, CPU power +	 * domain CSWR is not supported by hardware. +	 * More details can be found in OMAP4430 TRM section 4.3.4.2. +	 */ +	omap4_enter_lowpower(cpu_id, PWRDM_POWER_OFF); + +	/* Restore next powerdomain state */ +	list_for_each_entry(pwrst, &pwrst_list, node) { +		state = pwrdm_read_prev_pwrst(pwrst->pwrdm); +		if (state > pwrst->next_state) { +			pr_info("Powerdomain (%s) didn't enter " +			       "target state %d\n", +			       pwrst->pwrdm->name, pwrst->next_state); +			ret = -1; +		} +		omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state); +		pwrdm_set_logic_retst(pwrst->pwrdm, pwrst->saved_logic_state); +	} +	if (ret) +		pr_crit("Could not enter target state in pm_suspend\n"); +	else +		pr_info("Successfully put all powerdomains to target state\n"); +  	return 0;  } @@ -73,6 +120,22 @@ static const struct platform_suspend_ops omap_pm_ops = {  };  #endif /* CONFIG_SUSPEND */ +/* + * Enable hardware supervised mode for all clockdomains if it's + * supported. Initiate sleep transition for other clockdomains, if + * they are not used + */ +static int __init clkdms_setup(struct clockdomain *clkdm, void *unused) +{ +	if (clkdm->flags & CLKDM_CAN_ENABLE_AUTO) +		clkdm_allow_idle(clkdm); +	else if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP && +			atomic_read(&clkdm->usecount) == 0) +		clkdm_sleep(clkdm); +	return 0; +} + +  static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)  {  	struct power_state *pwrst; @@ -80,14 +143,48 @@ static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)  	if (!pwrdm->pwrsts)  		return 0; +	/* +	 * Skip CPU0 and CPU1 power domains. CPU1 is programmed +	 * through hotplug path and CPU0 explicitly programmed +	 * further down in the code path +	 */ +	if (!strncmp(pwrdm->name, "cpu", 3)) +		return 0; + +	/* +	 * FIXME: Remove this check when core retention is supported +	 * Only MPUSS power domain is added in the list. +	 */ +	if (strcmp(pwrdm->name, "mpu_pwrdm")) +		return 0; +  	pwrst = kmalloc(sizeof(struct power_state), GFP_ATOMIC);  	if (!pwrst)  		return -ENOMEM; +  	pwrst->pwrdm = pwrdm; -	pwrst->next_state = PWRDM_POWER_ON; +	pwrst->next_state = PWRDM_POWER_RET;  	list_add(&pwrst->node, &pwrst_list); -	return pwrdm_set_next_pwrst(pwrst->pwrdm, pwrst->next_state); +	return omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); +} + +/** + * omap_default_idle - OMAP4 default ilde routine.' + * + * Implements OMAP4 memory, IO ordering requirements which can't be addressed + * with default arch_idle() hook. Used by all CPUs with !CONFIG_CPUIDLE and + * by secondary CPU with CONFIG_CPUIDLE. + */ +static void omap_default_idle(void) +{ +	local_irq_disable(); +	local_fiq_disable(); + +	omap_do_wfi(); + +	local_fiq_enable(); +	local_irq_enable();  }  /** @@ -99,10 +196,17 @@ static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)  static int __init omap4_pm_init(void)  {  	int ret; +	struct clockdomain *emif_clkdm, *mpuss_clkdm, *l3_1_clkdm; +	struct clockdomain *ducati_clkdm, *l3_2_clkdm, *l4_per_clkdm;  	if (!cpu_is_omap44xx())  		return -ENODEV; +	if (omap_rev() == OMAP4430_REV_ES1_0) { +		WARN(1, "Power Management not supported on OMAP4430 ES1.0\n"); +		return -ENODEV; +	} +  	pr_err("Power Management for TI OMAP4.\n");  	ret = pwrdm_for_each(pwrdms_setup, NULL); @@ -111,10 +215,51 @@ static int __init omap4_pm_init(void)  		goto err2;  	} +	/* +	 * The dynamic dependency between MPUSS -> MEMIF and +	 * MPUSS -> L4_PER/L3_* and DUCATI -> L3_* doesn't work as +	 * expected. The hardware recommendation is to enable static +	 * dependencies for these to avoid system lock ups or random crashes. +	 */ +	mpuss_clkdm = clkdm_lookup("mpuss_clkdm"); +	emif_clkdm = clkdm_lookup("l3_emif_clkdm"); +	l3_1_clkdm = clkdm_lookup("l3_1_clkdm"); +	l3_2_clkdm = clkdm_lookup("l3_2_clkdm"); +	l4_per_clkdm = clkdm_lookup("l4_per_clkdm"); +	ducati_clkdm = clkdm_lookup("ducati_clkdm"); +	if ((!mpuss_clkdm) || (!emif_clkdm) || (!l3_1_clkdm) || +		(!l3_2_clkdm) || (!ducati_clkdm) || (!l4_per_clkdm)) +		goto err2; + +	ret = clkdm_add_wkdep(mpuss_clkdm, emif_clkdm); +	ret |= clkdm_add_wkdep(mpuss_clkdm, l3_1_clkdm); +	ret |= clkdm_add_wkdep(mpuss_clkdm, l3_2_clkdm); +	ret |= clkdm_add_wkdep(mpuss_clkdm, l4_per_clkdm); +	ret |= clkdm_add_wkdep(ducati_clkdm, l3_1_clkdm); +	ret |= clkdm_add_wkdep(ducati_clkdm, l3_2_clkdm); +	if (ret) { +		pr_err("Failed to add MPUSS -> L3/EMIF/L4PER, DUCATI -> L3 " +				"wakeup dependency\n"); +		goto err2; +	} + +	ret = omap4_mpuss_init(); +	if (ret) { +		pr_err("Failed to initialise OMAP4 MPUSS\n"); +		goto err2; +	} + +	(void) clkdm_for_each(clkdms_setup, NULL); +  #ifdef CONFIG_SUSPEND  	suspend_set_ops(&omap_pm_ops);  #endif /* CONFIG_SUSPEND */ +	/* Overwrite the default arch_idle() */ +	pm_idle = omap_default_idle; + +	omap4_idle_init(); +  err2:  	return ret;  }  |