2014-01-14 15:02:40 +04:00
/*
* cpuidle - powernv - idle state cpuidle driver .
* Adapted from drivers / cpuidle / cpuidle - pseries
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/moduleparam.h>
# include <linux/cpuidle.h>
# include <linux/cpu.h>
# include <linux/notifier.h>
2014-02-26 04:09:06 +04:00
# include <linux/clockchips.h>
2014-02-26 04:09:20 +04:00
# include <linux/of.h>
2015-02-18 08:34:17 +03:00
# include <linux/slab.h>
2014-01-14 15:02:40 +04:00
# include <asm/machdep.h>
# include <asm/firmware.h>
2014-12-09 21:56:51 +03:00
# include <asm/opal.h>
2014-02-17 19:59:29 +04:00
# include <asm/runlatch.h>
2014-01-14 15:02:40 +04:00
2014-02-26 04:09:20 +04:00
# define MAX_POWERNV_IDLE_STATES 8
2014-01-14 15:02:40 +04:00
struct cpuidle_driver powernv_idle_driver = {
. name = " powernv_idle " ,
. owner = THIS_MODULE ,
} ;
static int max_idle_state ;
static struct cpuidle_state * cpuidle_state_table ;
static int snooze_loop ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
local_irq_enable ( ) ;
set_thread_flag ( TIF_POLLING_NRFLAG ) ;
2014-02-17 19:59:29 +04:00
ppc64_runlatch_off ( ) ;
2014-01-14 15:02:40 +04:00
while ( ! need_resched ( ) ) {
HMT_low ( ) ;
HMT_very_low ( ) ;
}
HMT_medium ( ) ;
2014-02-17 19:59:29 +04:00
ppc64_runlatch_on ( ) ;
2014-01-14 15:02:40 +04:00
clear_thread_flag ( TIF_POLLING_NRFLAG ) ;
smp_mb ( ) ;
return index ;
}
static int nap_loop ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
2014-02-17 19:59:29 +04:00
ppc64_runlatch_off ( ) ;
2014-01-14 15:02:40 +04:00
power7_idle ( ) ;
2014-02-17 19:59:29 +04:00
ppc64_runlatch_on ( ) ;
2014-01-14 15:02:40 +04:00
return index ;
}
2014-02-26 04:09:06 +04:00
static int fastsleep_loop ( struct cpuidle_device * dev ,
struct cpuidle_driver * drv ,
int index )
{
unsigned long old_lpcr = mfspr ( SPRN_LPCR ) ;
unsigned long new_lpcr ;
if ( unlikely ( system_state < SYSTEM_RUNNING ) )
return index ;
new_lpcr = old_lpcr ;
2014-06-11 09:59:27 +04:00
/* Do not exit powersave upon decrementer as we've setup the timer
* offload .
2014-02-26 04:09:06 +04:00
*/
2014-06-11 09:59:27 +04:00
new_lpcr & = ~ LPCR_PECE1 ;
2014-02-26 04:09:06 +04:00
mtspr ( SPRN_LPCR , new_lpcr ) ;
power7_sleep ( ) ;
mtspr ( SPRN_LPCR , old_lpcr ) ;
return index ;
}
2014-01-14 15:02:40 +04:00
/*
* States for dedicated partition case .
*/
2014-02-26 04:09:20 +04:00
static struct cpuidle_state powernv_states [ MAX_POWERNV_IDLE_STATES ] = {
2014-01-14 15:02:40 +04:00
{ /* Snooze */
. name = " snooze " ,
. desc = " snooze " ,
. exit_latency = 0 ,
. target_residency = 0 ,
. enter = & snooze_loop } ,
} ;
static int powernv_cpuidle_add_cpu_notifier ( struct notifier_block * n ,
unsigned long action , void * hcpu )
{
int hotcpu = ( unsigned long ) hcpu ;
struct cpuidle_device * dev =
per_cpu ( cpuidle_devices , hotcpu ) ;
if ( dev & & cpuidle_get_driver ( ) ) {
switch ( action ) {
case CPU_ONLINE :
case CPU_ONLINE_FROZEN :
cpuidle_pause_and_lock ( ) ;
cpuidle_enable_device ( dev ) ;
cpuidle_resume_and_unlock ( ) ;
break ;
case CPU_DEAD :
case CPU_DEAD_FROZEN :
cpuidle_pause_and_lock ( ) ;
cpuidle_disable_device ( dev ) ;
cpuidle_resume_and_unlock ( ) ;
break ;
default :
return NOTIFY_DONE ;
}
}
return NOTIFY_OK ;
}
static struct notifier_block setup_hotplug_notifier = {
. notifier_call = powernv_cpuidle_add_cpu_notifier ,
} ;
/*
* powernv_cpuidle_driver_init ( )
*/
static int powernv_cpuidle_driver_init ( void )
{
int idle_state ;
struct cpuidle_driver * drv = & powernv_idle_driver ;
drv - > state_count = 0 ;
for ( idle_state = 0 ; idle_state < max_idle_state ; + + idle_state ) {
/* Is the state not enabled? */
if ( cpuidle_state_table [ idle_state ] . enter = = NULL )
continue ;
drv - > states [ drv - > state_count ] = /* structure copy */
cpuidle_state_table [ idle_state ] ;
drv - > state_count + = 1 ;
}
return 0 ;
}
2014-02-26 04:09:20 +04:00
static int powernv_add_idle_states ( void )
{
struct device_node * power_mgt ;
int nr_idle_states = 1 ; /* Snooze */
int dt_idle_states ;
2015-02-19 08:24:53 +03:00
u32 * latency_ns , * residency_ns , * flags ;
2015-02-18 08:34:17 +03:00
int i , rc ;
2014-02-26 04:09:20 +04:00
/* Currently we have snooze statically defined */
power_mgt = of_find_node_by_path ( " /ibm,opal/power-mgt " ) ;
if ( ! power_mgt ) {
pr_warn ( " opal: PowerMgmt Node not found \n " ) ;
2015-02-18 08:34:17 +03:00
goto out ;
2014-02-26 04:09:20 +04:00
}
2015-02-19 08:24:53 +03:00
/* Read values of any property to determine the num of idle states */
dt_idle_states = of_property_count_u32_elems ( power_mgt , " ibm,cpu-idle-state-flags " ) ;
if ( dt_idle_states < 0 ) {
pr_warn ( " cpuidle-powernv: no idle states found in the DT \n " ) ;
2015-02-18 08:34:17 +03:00
goto out ;
2014-02-26 04:09:20 +04:00
}
2015-02-19 08:24:53 +03:00
flags = kzalloc ( sizeof ( * flags ) * dt_idle_states , GFP_KERNEL ) ;
if ( of_property_read_u32_array ( power_mgt ,
" ibm,cpu-idle-state-flags " , flags , dt_idle_states ) ) {
pr_warn ( " cpuidle-powernv : missing ibm,cpu-idle-state-flags in DT \n " ) ;
goto out_free_flags ;
}
2015-02-18 08:34:17 +03:00
latency_ns = kzalloc ( sizeof ( * latency_ns ) * dt_idle_states , GFP_KERNEL ) ;
rc = of_property_read_u32_array ( power_mgt ,
" ibm,cpu-idle-state-latencies-ns " , latency_ns , dt_idle_states ) ;
if ( rc ) {
pr_warn ( " cpuidle-powernv: missing ibm,cpu-idle-state-latencies-ns in DT \n " ) ;
goto out_free_latency ;
2014-10-14 11:53:00 +04:00
}
2015-02-18 08:34:17 +03:00
residency_ns = kzalloc ( sizeof ( * residency_ns ) * dt_idle_states , GFP_KERNEL ) ;
rc = of_property_read_u32_array ( power_mgt ,
" ibm,cpu-idle-state-residency-ns " , residency_ns , dt_idle_states ) ;
2014-02-26 04:09:20 +04:00
for ( i = 0 ; i < dt_idle_states ; i + + ) {
2015-02-18 08:34:17 +03:00
/*
* Cpuidle accepts exit_latency and target_residency in us .
* Use default target_residency values if f / w does not expose it .
2014-10-14 11:53:00 +04:00
*/
2015-02-19 08:24:53 +03:00
if ( flags [ i ] & OPAL_PM_NAP_ENABLED ) {
2014-02-26 04:09:20 +04:00
/* Add NAP state */
strcpy ( powernv_states [ nr_idle_states ] . name , " Nap " ) ;
strcpy ( powernv_states [ nr_idle_states ] . desc , " Nap " ) ;
2014-11-12 18:03:50 +03:00
powernv_states [ nr_idle_states ] . flags = 0 ;
2015-02-18 08:34:17 +03:00
powernv_states [ nr_idle_states ] . target_residency = 100 ;
2014-02-26 04:09:20 +04:00
powernv_states [ nr_idle_states ] . enter = & nap_loop ;
2015-02-19 08:24:53 +03:00
} else if ( flags [ i ] & OPAL_PM_SLEEP_ENABLED | |
flags [ i ] & OPAL_PM_SLEEP_ENABLED_ER1 ) {
2014-02-26 04:09:20 +04:00
/* Add FASTSLEEP state */
strcpy ( powernv_states [ nr_idle_states ] . name , " FastSleep " ) ;
strcpy ( powernv_states [ nr_idle_states ] . desc , " FastSleep " ) ;
2014-11-12 18:03:50 +03:00
powernv_states [ nr_idle_states ] . flags = CPUIDLE_FLAG_TIMER_STOP ;
2015-02-18 08:34:17 +03:00
powernv_states [ nr_idle_states ] . target_residency = 300000 ;
2014-02-26 04:09:20 +04:00
powernv_states [ nr_idle_states ] . enter = & fastsleep_loop ;
}
2015-02-18 08:34:17 +03:00
powernv_states [ nr_idle_states ] . exit_latency =
( ( unsigned int ) latency_ns [ i ] ) / 1000 ;
if ( ! rc ) {
powernv_states [ nr_idle_states ] . target_residency =
( ( unsigned int ) residency_ns [ i ] ) / 1000 ;
}
nr_idle_states + + ;
2014-02-26 04:09:20 +04:00
}
2015-02-18 08:34:17 +03:00
kfree ( residency_ns ) ;
out_free_latency :
kfree ( latency_ns ) ;
2015-02-19 08:24:53 +03:00
out_free_flags :
kfree ( flags ) ;
2015-02-18 08:34:17 +03:00
out :
2014-02-26 04:09:20 +04:00
return nr_idle_states ;
}
2014-01-14 15:02:40 +04:00
/*
* powernv_idle_probe ( )
* Choose state table for shared versus dedicated partition
*/
static int powernv_idle_probe ( void )
{
if ( cpuidle_disable ! = IDLE_NO_OVERRIDE )
return - ENODEV ;
if ( firmware_has_feature ( FW_FEATURE_OPALv3 ) ) {
cpuidle_state_table = powernv_states ;
2014-02-26 04:09:20 +04:00
/* Device tree can indicate more idle states */
max_idle_state = powernv_add_idle_states ( ) ;
2014-01-14 15:02:40 +04:00
} else
return - ENODEV ;
return 0 ;
}
static int __init powernv_processor_idle_init ( void )
{
int retval ;
retval = powernv_idle_probe ( ) ;
if ( retval )
return retval ;
powernv_cpuidle_driver_init ( ) ;
retval = cpuidle_register ( & powernv_idle_driver , NULL ) ;
if ( retval ) {
printk ( KERN_DEBUG " Registration of powernv driver failed. \n " ) ;
return retval ;
}
register_cpu_notifier ( & setup_hotplug_notifier ) ;
printk ( KERN_DEBUG " powernv_idle_driver registered \n " ) ;
return 0 ;
}
device_initcall ( powernv_processor_idle_init ) ;