2011-08-16 16:01:40 +04:00
/*
* OMAP4 CPU idle Routines
*
* Copyright ( C ) 2011 Texas Instruments , Inc .
* 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>
2011-01-15 22:12:31 +03:00
# include <linux/clockchips.h>
2011-08-16 16:01:40 +04:00
# include <asm/proc-fns.h>
# include "common.h"
# include "pm.h"
# include "prm.h"
2011-12-25 19:30:40 +04:00
# include "clockdomain.h"
2011-08-16 16:01:40 +04:00
2012-04-24 18:05:27 +04:00
/* Machine specific information */
2011-08-16 16:01:40 +04:00
struct omap4_idle_statedata {
u32 cpu_state ;
u32 mpu_logic_state ;
u32 mpu_state ;
} ;
2012-04-24 18:05:26 +04:00
static struct omap4_idle_statedata omap4_idle_data [ ] = {
{
. 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
2011-12-25 19:30:40 +04:00
static struct powerdomain * mpu_pd , * cpu_pd [ NR_CPUS ] ;
static struct clockdomain * cpu_clkdm [ NR_CPUS ] ;
2011-08-16 16:01:40 +04:00
2012-03-15 04:26:17 +04:00
static atomic_t abort_barrier ;
static bool cpu_done [ NR_CPUS ] ;
2011-08-16 16:01:40 +04:00
/**
2011-12-25 19:30:40 +04:00
* omap4_enter_idle_coupled_ [ simple / coupled ] - OMAP4 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 .
*/
2011-12-25 19:30:40 +04:00
static int omap4_enter_idle_simple ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
local_fiq_disable ( ) ;
omap_do_wfi ( ) ;
local_fiq_enable ( ) ;
return index ;
}
static int omap4_enter_idle_coupled ( struct cpuidle_device * dev ,
2011-08-16 16:01:40 +04:00
struct cpuidle_driver * drv ,
int index )
{
2012-04-24 18:05:27 +04:00
struct omap4_idle_statedata * cx = & omap4_idle_data [ index ] ;
2011-01-15 22:12:31 +03:00
int cpu_id = smp_processor_id ( ) ;
2011-08-16 16:01:40 +04:00
local_fiq_disable ( ) ;
/*
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
}
2011-12-25 19:30:40 +04:00
clockevents_notify ( CLOCK_EVT_NOTIFY_BROADCAST_ENTER , & cpu_id ) ;
2011-01-15 22:12:31 +03: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 .
*/
if ( ( cx - > mpu_state = = PWRDM_POWER_RET ) & &
( cx - > mpu_logic_state = = PWRDM_POWER_OFF ) )
cpu_cluster_pm_enter ( ) ;
}
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 ) ) {
clkdm_wakeup ( cpu_clkdm [ 1 ] ) ;
clkdm_allow_idle ( cpu_clkdm [ 1 ] ) ;
}
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 .
*/
if ( omap4_mpuss_read_prev_context_state ( ) )
cpu_cluster_pm_exit ( ) ;
2011-12-25 19:30:40 +04:00
clockevents_notify ( CLOCK_EVT_NOTIFY_BROADCAST_EXIT , & cpu_id ) ;
2011-01-15 22:12:31 +03: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
local_fiq_enable ( ) ;
return index ;
}
DEFINE_PER_CPU ( struct cpuidle_device , omap4_idle_dev ) ;
struct cpuidle_driver omap4_idle_driver = {
2012-03-21 00:22:47 +04:00
. name = " omap4_idle " ,
. owner = THIS_MODULE ,
. en_core_tk_irqen = 1 ,
2012-04-24 18:05:23 +04:00
. states = {
{
/* C1 - CPU0 ON + CPU1 ON + MPU ON */
. exit_latency = 2 + 2 ,
. target_residency = 5 ,
. flags = CPUIDLE_FLAG_TIME_VALID ,
2011-12-25 19:30:40 +04:00
. enter = omap4_enter_idle_simple ,
2012-04-24 18:05:23 +04:00
. name = " C1 " ,
. desc = " MPUSS ON "
} ,
{
/* C2 - CPU0 OFF + CPU1 OFF + MPU CSWR */
. exit_latency = 328 + 440 ,
. target_residency = 960 ,
2011-12-25 19:30:40 +04:00
. flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_COUPLED ,
. enter = omap4_enter_idle_coupled ,
2012-04-24 18:05:23 +04:00
. name = " C2 " ,
. desc = " MPUSS CSWR " ,
} ,
{
/* C3 - CPU0 OFF + CPU1 OFF + MPU OSWR */
. exit_latency = 460 + 518 ,
. target_residency = 1100 ,
2011-12-25 19:30:40 +04:00
. flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_COUPLED ,
. enter = omap4_enter_idle_coupled ,
2012-04-24 18:05:23 +04:00
. name = " C3 " ,
. desc = " MPUSS OSWR " ,
} ,
} ,
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
} ;
2012-04-17 13:39:20 +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 )
{
int cpu = smp_processor_id ( ) ;
clockevents_notify ( CLOCK_EVT_NOTIFY_BROADCAST_ON , & cpu ) ;
}
2011-08-16 16:01:40 +04:00
/**
* omap4_idle_init - Init routine for OMAP4 idle
*
* Registers the OMAP4 specific cpuidle driver to the cpuidle
* framework with the valid set of states .
*/
int __init omap4_idle_init ( void )
{
struct cpuidle_device * dev ;
unsigned int cpu_id = 0 ;
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 ;
2012-04-17 13:39:20 +04:00
/* Configure the broadcast timer on each cpu */
on_each_cpu ( omap_setup_broadcast_timer , NULL , 1 ) ;
2011-12-25 19:30:40 +04:00
for_each_cpu ( cpu_id , cpu_online_mask ) {
dev = & per_cpu ( omap4_idle_dev , cpu_id ) ;
dev - > cpu = cpu_id ;
2012-08-16 00:51:54 +04:00
# ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED
2011-12-25 19:30:40 +04:00
dev - > coupled_cpus = * cpu_online_mask ;
2012-08-16 00:51:54 +04:00
# endif
2011-12-25 19:30:40 +04:00
cpuidle_register_driver ( & omap4_idle_driver ) ;
2011-08-16 16:01:40 +04:00
2011-12-25 19:30:40 +04:00
if ( cpuidle_register_device ( dev ) ) {
pr_err ( " %s: CPUidle register failed \n " , __func__ ) ;
return - EIO ;
}
2012-04-24 18:05:23 +04:00
}
2011-08-16 16:01:40 +04:00
return 0 ;
}