2008-10-08 16:00:58 +04:00
/*
* linux / arch / arm / mach - omap2 / cpuidle34xx . c
*
* OMAP3 CPU IDLE Routines
*
* Copyright ( C ) 2008 Texas Instruments , Inc .
* Rajendra Nayak < rnayak @ ti . com >
*
* Copyright ( C ) 2007 Texas Instruments , Inc .
* Karthik Dasu < karthik - dp @ ti . com >
*
* Copyright ( C ) 2006 Nokia Corporation
* Tony Lindgren < tony @ atomide . com >
*
* Copyright ( C ) 2005 Texas Instruments , Inc .
* Richard Woodruff < r - woodruff2 @ ti . com >
*
* Based on pm . c for omap2
*
* 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
* published by the Free Software Foundation .
*/
2009-03-20 16:21:02 +03:00
# include <linux/sched.h>
2008-10-08 16:00:58 +04:00
# include <linux/cpuidle.h>
2011-11-08 03:58:40 +04:00
# include <linux/export.h>
2011-09-03 21:08:27 +04:00
# include <linux/cpu_pm.h>
2008-10-08 16:00:58 +04:00
# include <plat/prcm.h>
2008-10-08 16:01:22 +04:00
# include <plat/irqs.h>
2010-12-22 07:05:16 +03:00
# include "powerdomain.h"
2010-12-22 07:05:15 +03:00
# include "clockdomain.h"
2008-10-08 16:00:58 +04:00
2008-10-29 03:30:07 +03:00
# include "pm.h"
2010-10-08 21:40:20 +04:00
# include "control.h"
2011-12-05 12:46:24 +04:00
# include "common.h"
2008-10-29 03:30:07 +03:00
2008-10-08 16:00:58 +04:00
# ifdef CONFIG_CPU_IDLE
2009-12-16 03:37:18 +03:00
/*
* The latencies / thresholds for various C states have
* to be configured from the respective board files .
* These are some default values ( which might not provide
* the best power savings ) used on boards which do not
* pass these details from the board file .
*/
static struct cpuidle_params cpuidle_params_table [ ] = {
/* C1 */
2011-05-09 14:02:13 +04:00
{ 2 + 2 , 5 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C2 */
2011-05-09 14:02:13 +04:00
{ 10 + 10 , 30 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C3 */
2011-05-09 14:02:13 +04:00
{ 50 + 50 , 300 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C4 */
2011-05-09 14:02:13 +04:00
{ 1500 + 1800 , 4000 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C5 */
2011-05-09 14:02:13 +04:00
{ 2500 + 7500 , 12000 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C6 */
2011-05-09 14:02:13 +04:00
{ 3000 + 8500 , 15000 , 1 } ,
2009-12-16 03:37:18 +03:00
/* C7 */
2011-05-09 14:02:13 +04:00
{ 10000 + 30000 , 300000 , 1 } ,
2009-12-16 03:37:18 +03:00
} ;
2011-05-09 14:02:14 +04:00
# define OMAP3_NUM_STATES ARRAY_SIZE(cpuidle_params_table)
/* Mach specific information to be recorded in the C-state driver_data */
struct omap3_idle_statedata {
u32 mpu_state ;
u32 core_state ;
u8 valid ;
} ;
struct omap3_idle_statedata omap3_idle_data [ OMAP3_NUM_STATES ] ;
struct powerdomain * mpu_pd , * core_pd , * per_pd , * cam_pd ;
2009-12-16 03:37:18 +03:00
2009-03-13 19:19:16 +03:00
static int _cpuidle_allow_idle ( struct powerdomain * pwrdm ,
struct clockdomain * clkdm )
{
2011-02-26 02:06:48 +03:00
clkdm_allow_idle ( clkdm ) ;
2009-03-13 19:19:16 +03:00
return 0 ;
}
static int _cpuidle_deny_idle ( struct powerdomain * pwrdm ,
struct clockdomain * clkdm )
{
2011-02-26 02:06:48 +03:00
clkdm_deny_idle ( clkdm ) ;
2009-03-13 19:19:16 +03:00
return 0 ;
}
2012-03-21 00:22:46 +04:00
static int __omap3_enter_idle ( struct cpuidle_device * dev ,
2011-10-28 14:50:42 +04:00
struct cpuidle_driver * drv ,
2011-10-28 14:50:09 +04:00
int index )
2008-10-08 16:00:58 +04:00
{
2011-10-28 14:50:09 +04:00
struct omap3_idle_statedata * cx =
2011-10-28 14:50:33 +04:00
cpuidle_get_statedata ( & dev - > states_usage [ index ] ) ;
2008-10-29 03:30:07 +03:00
u32 mpu_state = cx - > mpu_state , core_state = cx - > core_state ;
2008-10-08 16:00:58 +04:00
local_fiq_disable ( ) ;
2008-10-28 11:59:05 +03:00
pwrdm_set_next_pwrst ( mpu_pd , mpu_state ) ;
pwrdm_set_next_pwrst ( core_pd , core_state ) ;
2008-10-08 16:01:22 +04:00
2009-03-20 16:21:02 +03:00
if ( omap_irq_pending ( ) | | need_resched ( ) )
2008-10-08 16:01:22 +04:00
goto return_sleep_time ;
2008-10-08 16:00:58 +04:00
2011-05-09 14:02:14 +04:00
/* Deny idle for C1 */
2011-10-28 14:50:09 +04:00
if ( index = = 0 ) {
2009-03-13 19:19:16 +03:00
pwrdm_for_each_clkdm ( mpu_pd , _cpuidle_deny_idle ) ;
pwrdm_for_each_clkdm ( core_pd , _cpuidle_deny_idle ) ;
}
2011-09-03 21:08:27 +04:00
/*
* Call idle CPU PM enter notifier chain so that
* VFP context is saved .
*/
if ( mpu_state = = PWRDM_POWER_OFF )
cpu_pm_enter ( ) ;
2008-10-08 16:00:58 +04:00
/* Execute ARM wfi */
omap_sram_idle ( ) ;
2011-09-03 21:08:27 +04:00
/*
* Call idle CPU PM enter notifier chain to restore
* VFP context .
*/
if ( pwrdm_read_prev_pwrst ( mpu_pd ) = = PWRDM_POWER_OFF )
cpu_pm_exit ( ) ;
2011-05-09 14:02:14 +04:00
/* Re-allow idle for C1 */
2011-10-28 14:50:09 +04:00
if ( index = = 0 ) {
2009-03-13 19:19:16 +03:00
pwrdm_for_each_clkdm ( mpu_pd , _cpuidle_allow_idle ) ;
pwrdm_for_each_clkdm ( core_pd , _cpuidle_allow_idle ) ;
}
2008-10-08 16:01:22 +04:00
return_sleep_time :
2008-10-08 16:00:58 +04:00
local_fiq_enable ( ) ;
2011-10-28 14:50:09 +04:00
return index ;
2008-10-08 16:00:58 +04:00
}
2012-03-21 00:22:46 +04:00
/**
* omap3_enter_idle - Programs OMAP3 to enter the specified state
* @ dev : cpuidle device
* @ drv : cpuidle driver
* @ index : the index of state to be entered
*
* Called from the CPUidle framework to program the device to the
* specified target state selected by the governor .
*/
static inline int omap3_enter_idle ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
return cpuidle_wrap_enter ( dev , drv , index , __omap3_enter_idle ) ;
}
2010-01-28 20:46:43 +03:00
/**
2011-05-09 14:02:16 +04:00
* next_valid_state - Find next valid C - state
2010-01-28 20:46:43 +03:00
* @ dev : cpuidle device
2011-10-28 14:50:42 +04:00
* @ drv : cpuidle driver
2011-10-28 14:50:09 +04:00
* @ index : Index of currently selected c - state
2010-01-28 20:46:43 +03:00
*
2011-10-28 14:50:09 +04:00
* If the state corresponding to index is valid , index is returned back
* to the caller . Else , this function searches for a lower c - state which is
* still valid ( as defined in omap3_power_states [ ] ) and returns its index .
2011-05-09 14:02:16 +04:00
*
* A state is valid if the ' valid ' field is enabled and
* if it satisfies the enable_off_mode condition .
2010-01-28 20:46:43 +03:00
*/
2011-10-28 14:50:09 +04:00
static int next_valid_state ( struct cpuidle_device * dev ,
2011-10-28 14:50:42 +04:00
struct cpuidle_driver * drv ,
2011-10-28 14:50:09 +04:00
int index )
2010-01-28 20:46:43 +03:00
{
2011-10-28 14:50:33 +04:00
struct cpuidle_state_usage * curr_usage = & dev - > states_usage [ index ] ;
2011-10-28 14:50:42 +04:00
struct cpuidle_state * curr = & drv - > states [ index ] ;
2011-10-28 14:50:33 +04:00
struct omap3_idle_statedata * cx = cpuidle_get_statedata ( curr_usage ) ;
2011-05-09 14:02:16 +04:00
u32 mpu_deepest_state = PWRDM_POWER_RET ;
u32 core_deepest_state = PWRDM_POWER_RET ;
2011-10-28 14:50:09 +04:00
int next_index = - 1 ;
2011-05-09 14:02:16 +04:00
if ( enable_off_mode ) {
mpu_deepest_state = PWRDM_POWER_OFF ;
/*
* Erratum i583 : valable for ES rev < Es1 .2 on 3630.
* CORE OFF mode is not supported in a stable form , restrict
* instead the CORE state to RET .
*/
if ( ! IS_PM34XX_ERRATUM ( PM_SDRC_WAKEUP_ERRATUM_i583 ) )
core_deepest_state = PWRDM_POWER_OFF ;
}
2010-01-28 20:46:43 +03:00
/* Check if current state is valid */
2011-05-09 14:02:16 +04:00
if ( ( cx - > valid ) & &
( cx - > mpu_state > = mpu_deepest_state ) & &
( cx - > core_state > = core_deepest_state ) ) {
2011-10-28 14:50:09 +04:00
return index ;
2010-01-28 20:46:43 +03:00
} else {
2011-05-09 14:02:14 +04:00
int idx = OMAP3_NUM_STATES - 1 ;
2010-01-28 20:46:43 +03:00
2011-05-09 14:02:15 +04:00
/* Reach the current state starting at highest C-state */
2011-05-09 14:02:14 +04:00
for ( ; idx > = 0 ; idx - - ) {
2011-10-28 14:50:42 +04:00
if ( & drv - > states [ idx ] = = curr ) {
2011-10-28 14:50:09 +04:00
next_index = idx ;
2010-01-28 20:46:43 +03:00
break ;
}
}
2011-05-09 14:02:15 +04:00
/* Should never hit this condition */
2011-10-28 14:50:09 +04:00
WARN_ON ( next_index = = - 1 ) ;
2010-01-28 20:46:43 +03:00
/*
* Drop to next valid state .
* Start search from the next ( lower ) state .
*/
idx - - ;
2011-05-09 14:02:14 +04:00
for ( ; idx > = 0 ; idx - - ) {
2011-10-28 14:50:33 +04:00
cx = cpuidle_get_statedata ( & dev - > states_usage [ idx ] ) ;
2011-05-09 14:02:16 +04:00
if ( ( cx - > valid ) & &
( cx - > mpu_state > = mpu_deepest_state ) & &
( cx - > core_state > = core_deepest_state ) ) {
2011-10-28 14:50:09 +04:00
next_index = idx ;
2010-01-28 20:46:43 +03:00
break ;
}
}
/*
2011-05-09 14:02:14 +04:00
* C1 is always valid .
2011-10-28 14:50:09 +04:00
* So , no need to check for ' next_index = = - 1 ' outside
* this loop .
2010-01-28 20:46:43 +03:00
*/
}
2011-10-28 14:50:09 +04:00
return next_index ;
2010-01-28 20:46:43 +03:00
}
2008-10-08 16:00:58 +04:00
/**
* omap3_enter_idle_bm - Checks for any bus activity
* @ dev : cpuidle device
2011-10-28 14:50:42 +04:00
* @ drv : cpuidle driver
2011-10-28 14:50:09 +04:00
* @ index : array index of target state to be programmed
2008-10-08 16:00:58 +04:00
*
2011-05-09 14:02:14 +04:00
* This function checks for any pending activity and then programs
* the device to the specified or a safer state .
2008-10-08 16:00:58 +04:00
*/
static int omap3_enter_idle_bm ( struct cpuidle_device * dev ,
2011-10-28 14:50:42 +04:00
struct cpuidle_driver * drv ,
2011-10-28 14:50:09 +04:00
int index )
2008-10-08 16:00:58 +04:00
{
2011-10-28 14:50:09 +04:00
int new_state_idx ;
2011-05-09 14:02:15 +04:00
u32 core_next_state , per_next_state = 0 , per_saved_state = 0 , cam_state ;
2011-05-09 14:02:14 +04:00
struct omap3_idle_statedata * cx ;
2010-09-09 03:37:42 +04:00
int ret ;
2008-10-29 03:32:11 +03:00
2010-09-09 03:37:42 +04:00
/*
* Prevent idle completely if CAM is active .
* CAM does not have wakeup capability in OMAP3 .
*/
cam_state = pwrdm_read_pwrst ( cam_pd ) ;
if ( cam_state = = PWRDM_POWER_ON ) {
2011-10-28 14:50:42 +04:00
new_state_idx = drv - > safe_state_index ;
2010-09-09 03:37:42 +04:00
goto select_state ;
}
2011-05-09 14:02:15 +04:00
/*
* FIXME : we currently manage device - specific idle states
* for PER and CORE in combination with CPU - specific
* idle states . This is wrong , and device - specific
* idle management needs to be separated out into
* its own code .
*/
2010-09-09 03:37:42 +04:00
/*
* Prevent PER off if CORE is not in retention or off as this
* would disable PER wakeups completely .
*/
2011-10-28 14:50:33 +04:00
cx = cpuidle_get_statedata ( & dev - > states_usage [ index ] ) ;
2011-05-09 14:02:15 +04:00
core_next_state = cx - > core_state ;
2010-09-09 03:37:42 +04:00
per_next_state = per_saved_state = pwrdm_read_next_pwrst ( per_pd ) ;
if ( ( per_next_state = = PWRDM_POWER_OFF ) & &
2010-10-01 19:35:47 +04:00
( core_next_state > PWRDM_POWER_RET ) )
2010-09-09 03:37:42 +04:00
per_next_state = PWRDM_POWER_RET ;
2008-10-29 03:32:11 +03:00
2010-09-09 03:37:42 +04:00
/* Are we changing PER target state? */
if ( per_next_state ! = per_saved_state )
pwrdm_set_next_pwrst ( per_pd , per_next_state ) ;
2011-10-28 14:50:42 +04:00
new_state_idx = next_valid_state ( dev , drv , index ) ;
2011-05-09 14:02:15 +04:00
2010-09-09 03:37:42 +04:00
select_state :
2011-10-28 14:50:42 +04:00
ret = omap3_enter_idle ( dev , drv , new_state_idx ) ;
2010-09-09 03:37:42 +04:00
/* Restore original PER state if it was modified */
if ( per_next_state ! = per_saved_state )
pwrdm_set_next_pwrst ( per_pd , per_saved_state ) ;
return ret ;
2008-10-08 16:00:58 +04:00
}
DEFINE_PER_CPU ( struct cpuidle_device , omap3_idle_dev ) ;
struct cpuidle_driver omap3_idle_driver = {
. name = " omap3_idle " ,
. owner = THIS_MODULE ,
2012-04-24 18:05:30 +04:00
. states = {
{
. enter = omap3_enter_idle ,
. exit_latency = 2 + 2 ,
. target_residency = 5 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C1 " ,
. desc = " MPU ON + CORE ON " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 10 + 10 ,
. target_residency = 30 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C2 " ,
. desc = " MPU ON + CORE ON " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 50 + 50 ,
. target_residency = 300 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C3 " ,
. desc = " MPU RET + CORE ON " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 1500 + 1800 ,
. target_residency = 4000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C4 " ,
. desc = " MPU OFF + CORE ON " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 2500 + 7500 ,
. target_residency = 12000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C5 " ,
. desc = " MPU RET + CORE RET " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 3000 + 8500 ,
. target_residency = 15000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C6 " ,
. desc = " MPU OFF + CORE RET " ,
} ,
{
. enter = omap3_enter_idle_bm ,
. exit_latency = 10000 + 30000 ,
. target_residency = 30000 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
. name = " C7 " ,
. desc = " MPU OFF + CORE OFF " ,
} ,
} ,
. state_count = OMAP3_NUM_STATES ,
. safe_state_index = 0 ,
2008-10-08 16:00:58 +04:00
} ;
2011-10-28 14:50:42 +04:00
/* Helper to register the driver_data */
static inline struct omap3_idle_statedata * _fill_cstate_usage (
struct cpuidle_device * dev ,
int idx )
{
struct omap3_idle_statedata * cx = & omap3_idle_data [ idx ] ;
struct cpuidle_state_usage * state_usage = & dev - > states_usage [ idx ] ;
cx - > valid = cpuidle_params_table [ idx ] . valid ;
2011-10-28 14:50:33 +04:00
cpuidle_set_statedata ( state_usage , cx ) ;
2011-05-09 14:02:14 +04:00
return cx ;
}
2008-10-08 16:00:58 +04:00
/**
* omap3_idle_init - Init routine for OMAP3 idle
*
2011-05-09 14:02:14 +04:00
* Registers the OMAP3 specific cpuidle driver to the cpuidle
2008-10-08 16:00:58 +04:00
* framework with the valid set of states .
*/
2008-09-26 12:04:20 +04:00
int __init omap3_idle_init ( void )
2008-10-08 16:00:58 +04:00
{
struct cpuidle_device * dev ;
2011-10-28 14:50:42 +04:00
struct cpuidle_driver * drv = & omap3_idle_driver ;
2011-05-09 14:02:14 +04:00
struct omap3_idle_statedata * cx ;
2008-10-08 16:00:58 +04:00
mpu_pd = pwrdm_lookup ( " mpu_pwrdm " ) ;
2008-10-08 16:01:22 +04:00
core_pd = pwrdm_lookup ( " core_pwrdm " ) ;
2010-09-09 03:37:42 +04:00
per_pd = pwrdm_lookup ( " per_pwrdm " ) ;
cam_pd = pwrdm_lookup ( " cam_pwrdm " ) ;
2008-10-08 16:00:58 +04:00
2011-10-28 14:50:42 +04:00
2008-10-08 16:00:58 +04:00
dev = & per_cpu ( omap3_idle_dev , smp_processor_id ( ) ) ;
2011-05-09 14:02:14 +04:00
/* C1 . MPU WFI + Core active */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 0 ) ;
2011-05-09 14:02:14 +04:00
cx - > valid = 1 ; /* C1 is always valid */
cx - > mpu_state = PWRDM_POWER_ON ;
cx - > core_state = PWRDM_POWER_ON ;
/* C2 . MPU WFI + Core inactive */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 1 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_ON ;
cx - > core_state = PWRDM_POWER_ON ;
/* C3 . MPU CSWR + Core inactive */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 2 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_RET ;
cx - > core_state = PWRDM_POWER_ON ;
/* C4 . MPU OFF + Core inactive */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 3 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_OFF ;
cx - > core_state = PWRDM_POWER_ON ;
/* C5 . MPU RET + Core RET */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 4 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_RET ;
cx - > core_state = PWRDM_POWER_RET ;
2008-10-08 16:00:58 +04:00
2011-05-09 14:02:14 +04:00
/* C6 . MPU OFF + Core RET */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 5 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_OFF ;
cx - > core_state = PWRDM_POWER_RET ;
/* C7 . MPU OFF + Core OFF */
2011-10-28 14:50:42 +04:00
cx = _fill_cstate_usage ( dev , 6 ) ;
2011-05-09 14:02:14 +04:00
cx - > mpu_state = PWRDM_POWER_OFF ;
cx - > core_state = PWRDM_POWER_OFF ;
2008-10-08 16:00:58 +04:00
2011-10-28 14:50:42 +04:00
drv - > state_count = OMAP3_NUM_STATES ;
cpuidle_register_driver ( & omap3_idle_driver ) ;
2008-10-08 16:00:58 +04:00
if ( cpuidle_register_device ( dev ) ) {
printk ( KERN_ERR " %s: CPUidle register device failed \n " ,
__func__ ) ;
return - EIO ;
}
return 0 ;
}
2008-09-26 12:04:20 +04:00
# else
int __init omap3_idle_init ( void )
{
return 0 ;
}
2008-10-08 16:00:58 +04:00
# endif /* CONFIG_CPU_IDLE */