diff options
Diffstat (limited to 'arch/arm/mach-omap2/powerdomain.c')
| -rw-r--r-- | arch/arm/mach-omap2/powerdomain.c | 232 | 
1 files changed, 179 insertions, 53 deletions
diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c index dea62a9aad0..86babd740d4 100644 --- a/arch/arm/mach-omap2/powerdomain.c +++ b/arch/arm/mach-omap2/powerdomain.c @@ -19,6 +19,7 @@  #include <linux/list.h>  #include <linux/errno.h>  #include <linux/string.h> +#include <linux/spinlock.h>  #include <trace/events/power.h>  #include "cm2xxx_3xxx.h" @@ -42,6 +43,15 @@ enum {  	PWRDM_STATE_PREV,  }; +/* + * Types of sleep_switch used internally in omap_set_pwrdm_state() + * and its associated static functions + * + * XXX Better documentation is needed here + */ +#define ALREADYACTIVE_SWITCH		0 +#define FORCEWAKEUP_SWITCH		1 +#define LOWPOWERSTATE_SWITCH		2  /* pwrdm_list contains all registered struct powerdomains */  static LIST_HEAD(pwrdm_list); @@ -101,6 +111,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm)  	pwrdm->voltdm.ptr = voltdm;  	INIT_LIST_HEAD(&pwrdm->voltdm_node);  	voltdm_add_pwrdm(voltdm, pwrdm); +	spin_lock_init(&pwrdm->_lock);  	list_add(&pwrdm->node, &pwrdm_list); @@ -112,7 +123,7 @@ static int _pwrdm_register(struct powerdomain *pwrdm)  	for (i = 0; i < pwrdm->banks; i++)  		pwrdm->ret_mem_off_counter[i] = 0; -	pwrdm_wait_transition(pwrdm); +	arch_pwrdm->pwrdm_wait_transition(pwrdm);  	pwrdm->state = pwrdm_read_pwrst(pwrdm);  	pwrdm->state_counter[pwrdm->state] = 1; @@ -143,7 +154,7 @@ static void _update_logic_membank_counters(struct powerdomain *pwrdm)  static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag)  { -	int prev, state, trace_state = 0; +	int prev, next, state, trace_state = 0;  	if (pwrdm == NULL)  		return -EINVAL; @@ -164,9 +175,10 @@ static int _pwrdm_state_switch(struct powerdomain *pwrdm, int flag)  		 * If the power domain did not hit the desired state,  		 * generate a trace event with both the desired and hit states  		 */ -		if (state != prev) { +		next = pwrdm_read_next_pwrst(pwrdm); +		if (next != prev) {  			trace_state = (PWRDM_TRACE_STATES_FLAG | -				       ((state & OMAP_POWERSTATE_MASK) << 8) | +				       ((next & OMAP_POWERSTATE_MASK) << 8) |  				       ((prev & OMAP_POWERSTATE_MASK) << 0));  			trace_power_domain_target(pwrdm->name, trace_state,  						  smp_processor_id()); @@ -199,6 +211,77 @@ static int _pwrdm_post_transition_cb(struct powerdomain *pwrdm, void *unused)  	return 0;  } +/** + * _pwrdm_save_clkdm_state_and_activate - prepare for power state change + * @pwrdm: struct powerdomain * to operate on + * @curr_pwrst: current power state of @pwrdm + * @pwrst: power state to switch to + * @hwsup: ptr to a bool to return whether the clkdm is hardware-supervised + * + * Determine whether the powerdomain needs to be turned on before + * attempting to switch power states.  Called by + * omap_set_pwrdm_state().  NOTE that if the powerdomain contains + * multiple clockdomains, this code assumes that the first clockdomain + * supports software-supervised wakeup mode - potentially a problem. + * Returns the power state switch mode currently in use (see the + * "Types of sleep_switch" comment above). + */ +static u8 _pwrdm_save_clkdm_state_and_activate(struct powerdomain *pwrdm, +					       u8 curr_pwrst, u8 pwrst, +					       bool *hwsup) +{ +	u8 sleep_switch; + +	if (curr_pwrst < PWRDM_POWER_ON) { +		if (curr_pwrst > pwrst && +		    pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE && +		    arch_pwrdm->pwrdm_set_lowpwrstchange) { +			sleep_switch = LOWPOWERSTATE_SWITCH; +		} else { +			*hwsup = clkdm_in_hwsup(pwrdm->pwrdm_clkdms[0]); +			clkdm_wakeup_nolock(pwrdm->pwrdm_clkdms[0]); +			sleep_switch = FORCEWAKEUP_SWITCH; +		} +	} else { +		sleep_switch = ALREADYACTIVE_SWITCH; +	} + +	return sleep_switch; +} + +/** + * _pwrdm_restore_clkdm_state - restore the clkdm hwsup state after pwrst change + * @pwrdm: struct powerdomain * to operate on + * @sleep_switch: return value from _pwrdm_save_clkdm_state_and_activate() + * @hwsup: should @pwrdm's first clockdomain be set to hardware-supervised mode? + * + * Restore the clockdomain state perturbed by + * _pwrdm_save_clkdm_state_and_activate(), and call the power state + * bookkeeping code.  Called by omap_set_pwrdm_state().  NOTE that if + * the powerdomain contains multiple clockdomains, this assumes that + * the first associated clockdomain supports either + * hardware-supervised idle control in the register, or + * software-supervised sleep.  No return value. + */ +static void _pwrdm_restore_clkdm_state(struct powerdomain *pwrdm, +				       u8 sleep_switch, bool hwsup) +{ +	switch (sleep_switch) { +	case FORCEWAKEUP_SWITCH: +		if (hwsup) +			clkdm_allow_idle_nolock(pwrdm->pwrdm_clkdms[0]); +		else +			clkdm_sleep_nolock(pwrdm->pwrdm_clkdms[0]); +		break; +	case LOWPOWERSTATE_SWITCH: +		if (pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE && +		    arch_pwrdm->pwrdm_set_lowpwrstchange) +			arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm); +		pwrdm_state_switch_nolock(pwrdm); +		break; +	} +} +  /* Public functions */  /** @@ -275,6 +358,30 @@ int pwrdm_complete_init(void)  }  /** + * pwrdm_lock - acquire a Linux spinlock on a powerdomain + * @pwrdm: struct powerdomain * to lock + * + * Acquire the powerdomain spinlock on @pwrdm.  No return value. + */ +void pwrdm_lock(struct powerdomain *pwrdm) +	__acquires(&pwrdm->_lock) +{ +	spin_lock_irqsave(&pwrdm->_lock, pwrdm->_lock_flags); +} + +/** + * pwrdm_unlock - release a Linux spinlock on a powerdomain + * @pwrdm: struct powerdomain * to unlock + * + * Release the powerdomain spinlock on @pwrdm.  No return value. + */ +void pwrdm_unlock(struct powerdomain *pwrdm) +	__releases(&pwrdm->_lock) +{ +	spin_unlock_irqrestore(&pwrdm->_lock, pwrdm->_lock_flags); +} + +/**   * pwrdm_lookup - look up a powerdomain by name, return a pointer   * @name: name of powerdomain   * @@ -920,65 +1027,27 @@ bool pwrdm_has_hdwr_sar(struct powerdomain *pwrdm)  	return (pwrdm && pwrdm->flags & PWRDM_HAS_HDWR_SAR) ? 1 : 0;  } -/** - * pwrdm_set_lowpwrstchange - Request a low power state change - * @pwrdm: struct powerdomain * - * - * Allows a powerdomain to transtion to a lower power sleep state - * from an existing sleep state without waking up the powerdomain. - * Returns -EINVAL if the powerdomain pointer is null or if the - * powerdomain does not support LOWPOWERSTATECHANGE, or returns 0 - * upon success. - */ -int pwrdm_set_lowpwrstchange(struct powerdomain *pwrdm) +int pwrdm_state_switch_nolock(struct powerdomain *pwrdm)  { -	int ret = -EINVAL; - -	if (!pwrdm) -		return -EINVAL; - -	if (!(pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE)) -		return -EINVAL; - -	pr_debug("powerdomain: %s: setting LOWPOWERSTATECHANGE bit\n", -		 pwrdm->name); - -	if (arch_pwrdm && arch_pwrdm->pwrdm_set_lowpwrstchange) -		ret = arch_pwrdm->pwrdm_set_lowpwrstchange(pwrdm); - -	return ret; -} - -/** - * pwrdm_wait_transition - wait for powerdomain power transition to finish - * @pwrdm: struct powerdomain * to wait for - * - * If the powerdomain @pwrdm is in the process of a state transition, - * spin until it completes the power transition, or until an iteration - * bailout value is reached. Returns -EINVAL if the powerdomain - * pointer is null, -EAGAIN if the bailout value was reached, or - * returns 0 upon success. - */ -int pwrdm_wait_transition(struct powerdomain *pwrdm) -{ -	int ret = -EINVAL; +	int ret; -	if (!pwrdm) +	if (!pwrdm || !arch_pwrdm)  		return -EINVAL; -	if (arch_pwrdm && arch_pwrdm->pwrdm_wait_transition) -		ret = arch_pwrdm->pwrdm_wait_transition(pwrdm); +	ret = arch_pwrdm->pwrdm_wait_transition(pwrdm); +	if (!ret) +		ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW);  	return ret;  } -int pwrdm_state_switch(struct powerdomain *pwrdm) +int __deprecated pwrdm_state_switch(struct powerdomain *pwrdm)  {  	int ret; -	ret = pwrdm_wait_transition(pwrdm); -	if (!ret) -		ret = _pwrdm_state_switch(pwrdm, PWRDM_STATE_NOW); +	pwrdm_lock(pwrdm); +	ret = pwrdm_state_switch_nolock(pwrdm); +	pwrdm_unlock(pwrdm);  	return ret;  } @@ -1004,6 +1073,63 @@ int pwrdm_post_transition(struct powerdomain *pwrdm)  }  /** + * omap_set_pwrdm_state - change a powerdomain's current power state + * @pwrdm: struct powerdomain * to change the power state of + * @pwrst: power state to change to + * + * Change the current hardware power state of the powerdomain + * represented by @pwrdm to the power state represented by @pwrst. + * Returns -EINVAL if @pwrdm is null or invalid or if the + * powerdomain's current power state could not be read, or returns 0 + * upon success or if @pwrdm does not support @pwrst or any + * lower-power state.  XXX Should not return 0 if the @pwrdm does not + * support @pwrst or any lower-power state: this should be an error. + */ +int omap_set_pwrdm_state(struct powerdomain *pwrdm, u8 pwrst) +{ +	u8 next_pwrst, sleep_switch; +	int curr_pwrst; +	int ret = 0; +	bool hwsup = false; + +	if (!pwrdm || IS_ERR(pwrdm)) +		return -EINVAL; + +	while (!(pwrdm->pwrsts & (1 << pwrst))) { +		if (pwrst == PWRDM_POWER_OFF) +			return ret; +		pwrst--; +	} + +	pwrdm_lock(pwrdm); + +	curr_pwrst = pwrdm_read_pwrst(pwrdm); +	if (curr_pwrst < 0) { +		ret = -EINVAL; +		goto osps_out; +	} + +	next_pwrst = pwrdm_read_next_pwrst(pwrdm); +	if (curr_pwrst == pwrst && next_pwrst == pwrst) +		goto osps_out; + +	sleep_switch = _pwrdm_save_clkdm_state_and_activate(pwrdm, curr_pwrst, +							    pwrst, &hwsup); + +	ret = pwrdm_set_next_pwrst(pwrdm, pwrst); +	if (ret) +		pr_err("%s: unable to set power state of powerdomain: %s\n", +		       __func__, pwrdm->name); + +	_pwrdm_restore_clkdm_state(pwrdm, sleep_switch, hwsup); + +osps_out: +	pwrdm_unlock(pwrdm); + +	return ret; +} + +/**   * pwrdm_get_context_loss_count - get powerdomain's context loss count   * @pwrdm: struct powerdomain * to wait for   * @@ -1054,7 +1180,7 @@ bool pwrdm_can_ever_lose_context(struct powerdomain *pwrdm)  {  	int i; -	if (IS_ERR_OR_NULL(pwrdm)) { +	if (!pwrdm) {  		pr_debug("powerdomain: %s: invalid powerdomain pointer\n",  			 __func__);  		return 1;  |