2011-08-16 16:01:40 +04:00
/*
2013-04-05 16:59:03 +04:00
* OMAP4 + CPU idle Routines
2011-08-16 16:01:40 +04:00
*
2013-04-05 16:59:03 +04:00
* Copyright ( C ) 2011 - 2013 Texas Instruments , Inc .
2011-08-16 16:01:40 +04:00
* Santosh Shilimkar < santosh . shilimkar @ ti . com >
* Rajendra Nayak < rnayak @ 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
* published by the Free Software Foundation .
*/
# include <linux/sched.h>
# include <linux/cpuidle.h>
# include <linux/cpu_pm.h>
# include <linux/export.h>
2015-04-03 03:02:47 +03:00
# include <linux/tick.h>
2011-08-16 16:01:40 +04:00
2013-04-23 12:54:39 +04:00
# include <asm/cpuidle.h>
2011-08-16 16:01:40 +04:00
# include "common.h"
# include "pm.h"
# include "prm.h"
2016-11-08 02:50:11 +03:00
# include "soc.h"
2011-12-25 19:30:40 +04:00
# include "clockdomain.h"
2011-08-16 16:01:40 +04:00
2014-02-17 11:52:55 +04:00
# define MAX_CPUS 2
2012-04-24 18:05:27 +04:00
/* Machine specific information */
2013-04-05 16:59:03 +04:00
struct idle_statedata {
2011-08-16 16:01:40 +04:00
u32 cpu_state ;
u32 mpu_logic_state ;
u32 mpu_state ;
2016-11-08 02:50:11 +03:00
u32 mpu_state_vote ;
2011-08-16 16:01:40 +04:00
} ;
2013-04-05 16:59:03 +04:00
static struct idle_statedata omap4_idle_data [ ] = {
2012-04-24 18:05:26 +04:00
{
. cpu_state = PWRDM_POWER_ON ,
. mpu_state = PWRDM_POWER_ON ,
. mpu_logic_state = PWRDM_POWER_RET ,
} ,
{
. cpu_state = PWRDM_POWER_OFF ,
. mpu_state = PWRDM_POWER_RET ,
. mpu_logic_state = PWRDM_POWER_RET ,
} ,
{
. cpu_state = PWRDM_POWER_OFF ,
. mpu_state = PWRDM_POWER_RET ,
. mpu_logic_state = PWRDM_POWER_OFF ,
} ,
} ;
2011-08-16 16:01:40 +04:00
2016-11-08 02:50:11 +03:00
static struct idle_statedata omap5_idle_data [ ] = {
{
. cpu_state = PWRDM_POWER_ON ,
. mpu_state = PWRDM_POWER_ON ,
. mpu_logic_state = PWRDM_POWER_ON ,
} ,
{
. cpu_state = PWRDM_POWER_RET ,
. mpu_state = PWRDM_POWER_RET ,
. mpu_logic_state = PWRDM_POWER_RET ,
} ,
} ;
2014-02-17 11:52:55 +04:00
static struct powerdomain * mpu_pd , * cpu_pd [ MAX_CPUS ] ;
static struct clockdomain * cpu_clkdm [ MAX_CPUS ] ;
2011-08-16 16:01:40 +04:00
2012-03-15 04:26:17 +04:00
static atomic_t abort_barrier ;
2014-02-17 11:52:55 +04:00
static bool cpu_done [ MAX_CPUS ] ;
2013-04-05 16:59:03 +04:00
static struct idle_statedata * state_ptr = & omap4_idle_data [ 0 ] ;
2016-11-08 02:50:11 +03:00
static DEFINE_RAW_SPINLOCK ( mpu_lock ) ;
2011-08-16 16:01:40 +04:00
2012-12-15 12:39:19 +04:00
/* Private functions */
2011-08-16 16:01:40 +04:00
/**
2013-04-05 16:59:03 +04:00
* omap_enter_idle_ [ simple / coupled ] - OMAP4PLUS cpuidle entry functions
2011-08-16 16:01:40 +04:00
* @ 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 low power state selected by the governor .
* Returns the amount of time spent in the low power state .
*/
2013-04-05 16:59:03 +04:00
static int omap_enter_idle_simple ( struct cpuidle_device * dev ,
2011-12-25 19:30:40 +04:00
struct cpuidle_driver * drv ,
int index )
{
omap_do_wfi ( ) ;
return index ;
}
2016-11-08 02:50:11 +03:00
static int omap_enter_idle_smp ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
struct idle_statedata * cx = state_ptr + index ;
unsigned long flag ;
raw_spin_lock_irqsave ( & mpu_lock , flag ) ;
cx - > mpu_state_vote + + ;
if ( cx - > mpu_state_vote = = num_online_cpus ( ) ) {
pwrdm_set_logic_retst ( mpu_pd , cx - > mpu_logic_state ) ;
omap_set_pwrdm_state ( mpu_pd , cx - > mpu_state ) ;
}
raw_spin_unlock_irqrestore ( & mpu_lock , flag ) ;
omap4_enter_lowpower ( dev - > cpu , cx - > cpu_state ) ;
raw_spin_lock_irqsave ( & mpu_lock , flag ) ;
if ( cx - > mpu_state_vote = = num_online_cpus ( ) )
omap_set_pwrdm_state ( mpu_pd , PWRDM_POWER_ON ) ;
cx - > mpu_state_vote - - ;
raw_spin_unlock_irqrestore ( & mpu_lock , flag ) ;
return index ;
}
2013-04-05 16:59:03 +04:00
static int omap_enter_idle_coupled ( struct cpuidle_device * dev ,
2011-08-16 16:01:40 +04:00
struct cpuidle_driver * drv ,
int index )
{
2013-04-05 16:59:03 +04:00
struct idle_statedata * cx = state_ptr + index ;
2013-10-22 23:07:15 +04:00
u32 mpuss_can_lose_context = 0 ;
2011-08-16 16:01:40 +04:00
/*
2011-12-25 19:30:40 +04:00
* CPU0 has to wait and stay ON until CPU1 is OFF state .
2011-08-16 16:01:40 +04:00
* This is necessary to honour hardware recommondation
* of triggeing all the possible low power modes once CPU1 is
* out of coherency and in OFF mode .
*/
2011-12-25 19:30:40 +04:00
if ( dev - > cpu = = 0 & & cpumask_test_cpu ( 1 , cpu_online_mask ) ) {
2012-03-15 04:26:17 +04:00
while ( pwrdm_read_pwrst ( cpu_pd [ 1 ] ) ! = PWRDM_POWER_OFF ) {
2011-12-25 19:30:40 +04:00
cpu_relax ( ) ;
2012-03-15 04:26:17 +04:00
/*
* CPU1 could have already entered & exited idle
* without hitting off because of a wakeup
* or a failed attempt to hit off mode . Check for
* that here , otherwise we could spin forever
* waiting for CPU1 off .
*/
if ( cpu_done [ 1 ] )
goto fail ;
}
2011-08-16 16:01:40 +04:00
}
2013-10-22 23:07:15 +04:00
mpuss_can_lose_context = ( cx - > mpu_state = = PWRDM_POWER_RET ) & &
( cx - > mpu_logic_state = = PWRDM_POWER_OFF ) ;
2015-04-03 03:31:29 +03:00
tick_broadcast_enter ( ) ;
2014-05-13 01:37:59 +04:00
2011-08-16 16:01:40 +04:00
/*
* Call idle CPU PM enter notifier chain so that
* VFP and per CPU interrupt context is saved .
*/
2011-12-25 19:30:40 +04:00
cpu_pm_enter ( ) ;
if ( dev - > cpu = = 0 ) {
pwrdm_set_logic_retst ( mpu_pd , cx - > mpu_logic_state ) ;
omap_set_pwrdm_state ( mpu_pd , cx - > mpu_state ) ;
/*
* Call idle CPU cluster PM enter notifier chain
* to save GIC and wakeupgen context .
*/
2013-10-22 23:07:15 +04:00
if ( mpuss_can_lose_context )
cpu_cluster_pm_enter ( ) ;
2011-12-25 19:30:40 +04:00
}
2011-08-16 16:01:40 +04:00
omap4_enter_lowpower ( dev - > cpu , cx - > cpu_state ) ;
2012-03-15 04:26:17 +04:00
cpu_done [ dev - > cpu ] = true ;
2011-08-16 16:01:40 +04:00
2011-12-25 19:30:40 +04:00
/* Wakeup CPU1 only if it is not offlined */
if ( dev - > cpu = = 0 & & cpumask_test_cpu ( 1 , cpu_online_mask ) ) {
2013-10-22 23:07:15 +04:00
if ( IS_PM44XX_ERRATUM ( PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD ) & &
mpuss_can_lose_context )
gic_dist_disable ( ) ;
2016-06-30 16:15:02 +03:00
clkdm_deny_idle ( cpu_clkdm [ 1 ] ) ;
2013-02-08 21:20:58 +04:00
omap_set_pwrdm_state ( cpu_pd [ 1 ] , PWRDM_POWER_ON ) ;
2011-12-25 19:30:40 +04:00
clkdm_allow_idle ( cpu_clkdm [ 1 ] ) ;
2013-10-22 23:07:15 +04:00
if ( IS_PM44XX_ERRATUM ( PM_OMAP4_ROM_SMP_BOOT_ERRATUM_GICD ) & &
mpuss_can_lose_context ) {
while ( gic_dist_disabled ( ) ) {
udelay ( 1 ) ;
cpu_relax ( ) ;
}
gic_timer_retrigger ( ) ;
}
2011-12-25 19:30:40 +04:00
}
2011-08-16 16:01:40 +04:00
/*
* Call idle CPU PM exit notifier chain to restore
2011-12-25 19:30:40 +04:00
* VFP and per CPU IRQ context .
2011-08-16 16:01:40 +04:00
*/
2011-12-25 19:30:40 +04:00
cpu_pm_exit ( ) ;
2011-08-16 16:01:40 +04:00
/*
* Call idle CPU cluster PM exit notifier chain
* to restore GIC and wakeupgen context .
*/
2013-10-22 23:07:15 +04:00
if ( dev - > cpu = = 0 & & mpuss_can_lose_context )
2011-08-16 16:01:40 +04:00
cpu_cluster_pm_exit ( ) ;
2015-04-03 03:31:29 +03:00
tick_broadcast_exit ( ) ;
2014-05-13 01:37:59 +04:00
2012-03-15 04:26:17 +04:00
fail :
cpuidle_coupled_parallel_barrier ( dev , & abort_barrier ) ;
cpu_done [ dev - > cpu ] = false ;
2011-01-15 22:12:31 +03:00
2011-08-16 16:01:40 +04:00
return index ;
}
2014-05-13 01:37:59 +04:00
/*
* For each cpu , setup the broadcast timer because local timers
* stops for the states above C1 .
*/
static void omap_setup_broadcast_timer ( void * arg )
{
2015-04-03 03:02:47 +03:00
tick_broadcast_enable ( ) ;
2014-05-13 01:37:59 +04:00
}
2012-12-15 12:39:19 +04:00
static struct cpuidle_driver omap4_idle_driver = {
2012-03-21 00:22:47 +04:00
. name = " omap4_idle " ,
. owner = THIS_MODULE ,
2012-04-24 18:05:23 +04:00
. states = {
{
/* C1 - CPU0 ON + CPU1 ON + MPU ON */
. exit_latency = 2 + 2 ,
. target_residency = 5 ,
2013-04-05 16:59:03 +04:00
. enter = omap_enter_idle_simple ,
2012-04-24 18:05:23 +04:00
. name = " C1 " ,
2013-03-25 14:05:06 +04:00
. desc = " CPUx ON, MPUSS ON "
2012-04-24 18:05:23 +04:00
} ,
{
2012-12-15 12:39:19 +04:00
/* C2 - CPU0 OFF + CPU1 OFF + MPU CSWR */
2012-04-24 18:05:23 +04:00
. exit_latency = 328 + 440 ,
. target_residency = 960 ,
2014-11-12 18:03:50 +03:00
. flags = CPUIDLE_FLAG_COUPLED ,
2013-04-05 16:59:03 +04:00
. enter = omap_enter_idle_coupled ,
2012-04-24 18:05:23 +04:00
. name = " C2 " ,
2013-03-25 14:05:06 +04:00
. desc = " CPUx OFF, MPUSS CSWR " ,
2012-04-24 18:05:23 +04:00
} ,
{
/* C3 - CPU0 OFF + CPU1 OFF + MPU OSWR */
. exit_latency = 460 + 518 ,
. target_residency = 1100 ,
2014-11-12 18:03:50 +03:00
. flags = CPUIDLE_FLAG_COUPLED ,
2013-04-05 16:59:03 +04:00
. enter = omap_enter_idle_coupled ,
2012-04-24 18:05:23 +04:00
. name = " C3 " ,
2013-03-25 14:05:06 +04:00
. desc = " CPUx OFF, MPUSS OSWR " ,
2012-04-24 18:05:23 +04:00
} ,
} ,
2012-04-24 18:05:26 +04:00
. state_count = ARRAY_SIZE ( omap4_idle_data ) ,
2012-04-24 18:05:23 +04:00
. safe_state_index = 0 ,
2011-08-16 16:01:40 +04:00
} ;
2016-11-08 02:50:11 +03:00
static struct cpuidle_driver omap5_idle_driver = {
. name = " omap5_idle " ,
. owner = THIS_MODULE ,
. states = {
{
/* C1 - CPU0 ON + CPU1 ON + MPU ON */
. exit_latency = 2 + 2 ,
. target_residency = 5 ,
. enter = omap_enter_idle_simple ,
. name = " C1 " ,
. desc = " CPUx WFI, MPUSS ON "
} ,
{
/* C2 - CPU0 RET + CPU1 RET + MPU CSWR */
. exit_latency = 48 + 60 ,
. target_residency = 100 ,
. flags = CPUIDLE_FLAG_TIMER_STOP ,
. enter = omap_enter_idle_smp ,
. name = " C2 " ,
. desc = " CPUx CSWR, MPUSS CSWR " ,
} ,
} ,
. state_count = ARRAY_SIZE ( omap5_idle_data ) ,
. safe_state_index = 0 ,
} ;
2012-12-15 12:39:19 +04:00
/* Public functions */
2012-04-17 13:39:20 +04:00
2011-08-16 16:01:40 +04:00
/**
2013-04-05 16:59:03 +04:00
* omap4_idle_init - Init routine for OMAP4 + idle
2011-08-16 16:01:40 +04:00
*
2013-04-05 16:59:03 +04:00
* Registers the OMAP4 + specific cpuidle driver to the cpuidle
2011-08-16 16:01:40 +04:00
* framework with the valid set of states .
*/
int __init omap4_idle_init ( void )
{
2016-11-08 02:50:11 +03:00
struct cpuidle_driver * idle_driver ;
if ( soc_is_omap54xx ( ) ) {
state_ptr = & omap5_idle_data [ 0 ] ;
idle_driver = & omap5_idle_driver ;
} else {
state_ptr = & omap4_idle_data [ 0 ] ;
idle_driver = & omap4_idle_driver ;
}
2011-08-16 16:01:40 +04:00
mpu_pd = pwrdm_lookup ( " mpu_pwrdm " ) ;
2011-12-25 19:30:40 +04:00
cpu_pd [ 0 ] = pwrdm_lookup ( " cpu0_pwrdm " ) ;
cpu_pd [ 1 ] = pwrdm_lookup ( " cpu1_pwrdm " ) ;
if ( ( ! mpu_pd ) | | ( ! cpu_pd [ 0 ] ) | | ( ! cpu_pd [ 1 ] ) )
2011-08-16 16:01:40 +04:00
return - ENODEV ;
2011-12-25 19:30:40 +04:00
cpu_clkdm [ 0 ] = clkdm_lookup ( " mpu0_clkdm " ) ;
cpu_clkdm [ 1 ] = clkdm_lookup ( " mpu1_clkdm " ) ;
if ( ! cpu_clkdm [ 0 ] | | ! cpu_clkdm [ 1 ] )
2011-08-16 16:01:40 +04:00
return - ENODEV ;
2014-05-13 01:37:59 +04:00
/* Configure the broadcast timer on each cpu */
on_each_cpu ( omap_setup_broadcast_timer , NULL , 1 ) ;
2016-11-08 02:50:11 +03:00
return cpuidle_register ( idle_driver , cpu_online_mask ) ;
2011-08-16 16:01:40 +04:00
}