2014-04-01 11:13:26 +04:00
/*
* POWERNV cpufreq driver for the IBM POWER processors
*
* ( C ) Copyright IBM 2014
*
* Author : Vaidyanathan Srinivasan < svaidy at linux . vnet . ibm . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 , or ( at your option )
* any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# define pr_fmt(fmt) "powernv-cpufreq: " fmt
# include <linux/kernel.h>
# include <linux/sysfs.h>
# include <linux/cpumask.h>
# include <linux/module.h>
# include <linux/cpufreq.h>
# include <linux/smp.h>
# include <linux/of.h>
2014-09-29 17:49:11 +04:00
# include <linux/reboot.h>
2015-07-16 11:04:18 +03:00
# include <linux/slab.h>
2016-02-02 22:41:38 +03:00
# include <linux/cpu.h>
2016-02-02 22:41:41 +03:00
# include <trace/events/power.h>
2014-04-01 11:13:26 +04:00
# include <asm/cputhreads.h>
2014-08-03 13:24:05 +04:00
# include <asm/firmware.h>
2014-04-01 11:13:26 +04:00
# include <asm/reg.h>
2014-04-16 10:05:38 +04:00
# include <asm/smp.h> /* Required for cpu_sibling_mask() in UP configs */
2015-07-16 11:04:20 +03:00
# include <asm/opal.h>
2016-04-19 12:58:01 +03:00
# include <linux/timer.h>
2014-04-01 11:13:26 +04:00
# define POWERNV_MAX_PSTATES 256
2015-04-01 12:46:34 +03:00
# define PMSR_PSAFE_ENABLE (1UL << 30)
# define PMSR_SPR_EM_DISABLE (1UL << 31)
# define PMSR_MAX(x) ((x >> 32) & 0xFF)
2016-11-08 16:33:28 +03:00
# define LPSTATE_SHIFT 48
# define GPSTATE_SHIFT 56
# define GET_LPSTATE(x) (((x) >> LPSTATE_SHIFT) & 0xFF)
# define GET_GPSTATE(x) (((x) >> GPSTATE_SHIFT) & 0xFF)
2014-04-01 11:13:26 +04:00
2016-04-19 12:58:01 +03:00
# define MAX_RAMP_DOWN_TIME 5120
/*
* On an idle system we want the global pstate to ramp - down from max value to
* min over a span of ~ 5 secs . Also we want it to initially ramp - down slowly and
* then ramp - down rapidly later on .
*
* This gives a percentage rampdown for time elapsed in milliseconds .
* ramp_down_percentage = ( ( ms * ms ) > > 18 )
* ~ = 3.8 * ( sec * sec )
*
* At 0 ms ramp_down_percent = 0
* At 5120 ms ramp_down_percent = 100
*/
# define ramp_down_percent(time) ((time * time) >> 18)
/* Interval after which the timer is queued to bring down global pstate */
# define GPSTATE_TIMER_INTERVAL 2000
/**
* struct global_pstate_info - Per policy data structure to maintain history of
* global pstates
2016-06-30 09:23:07 +03:00
* @ highest_lpstate_idx : The local pstate index from which we are
* ramping down
2016-04-19 12:58:01 +03:00
* @ elapsed_time : Time in ms spent in ramping down from
2016-06-30 09:23:07 +03:00
* highest_lpstate_idx
2016-04-19 12:58:01 +03:00
* @ last_sampled_time : Time from boot in ms when global pstates were
* last set
2016-06-30 09:23:07 +03:00
* @ last_lpstate_idx , Last set value of local pstate and global
* last_gpstate_idx pstate in terms of cpufreq table index
2016-04-19 12:58:01 +03:00
* @ timer : Is used for ramping down if cpu goes idle for
* a long time with global pstate held high
* @ gpstate_lock : A spinlock to maintain synchronization between
* routines called by the timer handler and
* governer ' s target_index calls
*/
struct global_pstate_info {
2016-06-30 09:23:07 +03:00
int highest_lpstate_idx ;
2016-04-19 12:58:01 +03:00
unsigned int elapsed_time ;
unsigned int last_sampled_time ;
2016-06-30 09:23:07 +03:00
int last_lpstate_idx ;
int last_gpstate_idx ;
2016-04-19 12:58:01 +03:00
spinlock_t gpstate_lock ;
struct timer_list timer ;
} ;
2014-04-01 11:13:26 +04:00
static struct cpufreq_frequency_table powernv_freqs [ POWERNV_MAX_PSTATES + 1 ] ;
2015-07-16 11:04:20 +03:00
static bool rebooting , throttled , occ_reset ;
2014-04-01 11:13:26 +04:00
2016-02-02 22:41:41 +03:00
static const char * const throttle_reason [ ] = {
" No throttling " ,
" Power Cap " ,
" Processor Over Temperature " ,
" Power Supply Failure " ,
" Over Current " ,
" OCC Reset "
} ;
2016-03-22 16:27:09 +03:00
enum throttle_reason_type {
NO_THROTTLE = 0 ,
POWERCAP ,
CPU_OVERTEMP ,
POWER_SUPPLY_FAILURE ,
OVERCURRENT ,
OCC_RESET_THROTTLE ,
OCC_MAX_REASON
} ;
2015-07-16 11:04:18 +03:00
static struct chip {
unsigned int id ;
bool throttled ;
2016-02-02 22:41:41 +03:00
bool restore ;
u8 throttle_reason ;
2015-07-16 11:04:21 +03:00
cpumask_t mask ;
struct work_struct throttle ;
2016-03-22 16:27:09 +03:00
int throttle_turbo ;
int throttle_sub_turbo ;
int reason [ OCC_MAX_REASON ] ;
2015-07-16 11:04:18 +03:00
} * chips ;
static int nr_chips ;
2016-03-21 19:54:52 +03:00
static DEFINE_PER_CPU ( struct chip * , chip_info ) ;
2015-07-16 11:04:18 +03:00
2014-04-01 11:13:26 +04:00
/*
2016-06-30 09:23:07 +03:00
* Note :
* The set of pstates consists of contiguous integers .
* powernv_pstate_info stores the index of the frequency table for
* max , min and nominal frequencies . It also stores number of
* available frequencies .
2014-04-01 11:13:26 +04:00
*
2016-06-30 09:23:07 +03:00
* powernv_pstate_info . nominal indicates the index to the highest
* non - turbo frequency .
2014-04-01 11:13:26 +04:00
*/
static struct powernv_pstate_info {
2016-06-30 09:23:07 +03:00
unsigned int min ;
unsigned int max ;
unsigned int nominal ;
unsigned int nr_pstates ;
2017-01-03 14:06:00 +03:00
bool wof_enabled ;
2014-04-01 11:13:26 +04:00
} powernv_pstate_info ;
2016-06-30 09:23:07 +03:00
/* Use following macros for conversions between pstate_id and index */
static inline int idx_to_pstate ( unsigned int i )
{
2016-08-04 18:29:17 +03:00
if ( unlikely ( i > = powernv_pstate_info . nr_pstates ) ) {
pr_warn_once ( " index %u is out of bound \n " , i ) ;
return powernv_freqs [ powernv_pstate_info . nominal ] . driver_data ;
}
2016-06-30 09:23:07 +03:00
return powernv_freqs [ i ] . driver_data ;
}
static inline unsigned int pstate_to_idx ( int pstate )
{
2016-08-04 18:29:17 +03:00
int min = powernv_freqs [ powernv_pstate_info . min ] . driver_data ;
int max = powernv_freqs [ powernv_pstate_info . max ] . driver_data ;
if ( min > 0 ) {
if ( unlikely ( ( pstate < max ) | | ( pstate > min ) ) ) {
pr_warn_once ( " pstate %d is out of bound \n " , pstate ) ;
return powernv_pstate_info . nominal ;
}
} else {
if ( unlikely ( ( pstate > max ) | | ( pstate < min ) ) ) {
pr_warn_once ( " pstate %d is out of bound \n " , pstate ) ;
return powernv_pstate_info . nominal ;
}
}
2016-06-30 09:23:07 +03:00
/*
* abs ( ) is deliberately used so that is works with
* both monotonically increasing and decreasing
* pstate values
*/
return abs ( pstate - idx_to_pstate ( powernv_pstate_info . max ) ) ;
}
2016-04-19 12:58:01 +03:00
static inline void reset_gpstates ( struct cpufreq_policy * policy )
{
struct global_pstate_info * gpstates = policy - > driver_data ;
2016-06-30 09:23:07 +03:00
gpstates - > highest_lpstate_idx = 0 ;
2016-04-19 12:58:01 +03:00
gpstates - > elapsed_time = 0 ;
gpstates - > last_sampled_time = 0 ;
2016-06-30 09:23:07 +03:00
gpstates - > last_lpstate_idx = 0 ;
gpstates - > last_gpstate_idx = 0 ;
2016-04-19 12:58:01 +03:00
}
2014-04-01 11:13:26 +04:00
/*
* Initialize the freq table based on data obtained
* from the firmware passed via device - tree
*/
static int init_powernv_pstates ( void )
{
struct device_node * power_mgt ;
2016-06-30 09:23:07 +03:00
int i , nr_pstates = 0 ;
2014-04-01 11:13:26 +04:00
const __be32 * pstate_ids , * pstate_freqs ;
u32 len_ids , len_freqs ;
2016-06-30 09:23:07 +03:00
u32 pstate_min , pstate_max , pstate_nominal ;
2017-01-03 14:06:00 +03:00
u32 pstate_turbo , pstate_ultra_turbo ;
2014-04-01 11:13:26 +04:00
power_mgt = of_find_node_by_path ( " /ibm,opal/power-mgt " ) ;
if ( ! power_mgt ) {
pr_warn ( " power-mgt node not found \n " ) ;
return - ENODEV ;
}
if ( of_property_read_u32 ( power_mgt , " ibm,pstate-min " , & pstate_min ) ) {
pr_warn ( " ibm,pstate-min node not found \n " ) ;
return - ENODEV ;
}
if ( of_property_read_u32 ( power_mgt , " ibm,pstate-max " , & pstate_max ) ) {
pr_warn ( " ibm,pstate-max node not found \n " ) ;
return - ENODEV ;
}
if ( of_property_read_u32 ( power_mgt , " ibm,pstate-nominal " ,
& pstate_nominal ) ) {
pr_warn ( " ibm,pstate-nominal not found \n " ) ;
return - ENODEV ;
}
2017-01-03 14:06:00 +03:00
if ( of_property_read_u32 ( power_mgt , " ibm,pstate-ultra-turbo " ,
& pstate_ultra_turbo ) ) {
powernv_pstate_info . wof_enabled = false ;
goto next ;
}
if ( of_property_read_u32 ( power_mgt , " ibm,pstate-turbo " ,
& pstate_turbo ) ) {
powernv_pstate_info . wof_enabled = false ;
goto next ;
}
if ( pstate_turbo = = pstate_ultra_turbo )
powernv_pstate_info . wof_enabled = false ;
else
powernv_pstate_info . wof_enabled = true ;
next :
2014-04-01 11:13:26 +04:00
pr_info ( " cpufreq pstate min %d nominal %d max %d \n " , pstate_min ,
pstate_nominal , pstate_max ) ;
2017-01-03 14:06:00 +03:00
pr_info ( " Workload Optimized Frequency is %s in the platform \n " ,
( powernv_pstate_info . wof_enabled ) ? " enabled " : " disabled " ) ;
2014-04-01 11:13:26 +04:00
pstate_ids = of_get_property ( power_mgt , " ibm,pstate-ids " , & len_ids ) ;
if ( ! pstate_ids ) {
pr_warn ( " ibm,pstate-ids not found \n " ) ;
return - ENODEV ;
}
pstate_freqs = of_get_property ( power_mgt , " ibm,pstate-frequencies-mhz " ,
& len_freqs ) ;
if ( ! pstate_freqs ) {
pr_warn ( " ibm,pstate-frequencies-mhz not found \n " ) ;
return - ENODEV ;
}
2014-08-03 13:24:05 +04:00
if ( len_ids ! = len_freqs ) {
pr_warn ( " Entries in ibm,pstate-ids and "
" ibm,pstate-frequencies-mhz does not match \n " ) ;
}
2014-04-01 11:13:26 +04:00
nr_pstates = min ( len_ids , len_freqs ) / sizeof ( u32 ) ;
if ( ! nr_pstates ) {
pr_warn ( " No PStates found \n " ) ;
return - ENODEV ;
}
2016-06-30 09:23:07 +03:00
powernv_pstate_info . nr_pstates = nr_pstates ;
2014-04-01 11:13:26 +04:00
pr_debug ( " NR PStates %d \n " , nr_pstates ) ;
for ( i = 0 ; i < nr_pstates ; i + + ) {
u32 id = be32_to_cpu ( pstate_ids [ i ] ) ;
u32 freq = be32_to_cpu ( pstate_freqs [ i ] ) ;
pr_debug ( " PState id %d freq %d MHz \n " , id , freq ) ;
powernv_freqs [ i ] . frequency = freq * 1000 ; /* kHz */
2014-04-01 11:13:27 +04:00
powernv_freqs [ i ] . driver_data = id ;
2016-06-30 09:23:07 +03:00
if ( id = = pstate_max )
powernv_pstate_info . max = i ;
else if ( id = = pstate_nominal )
powernv_pstate_info . nominal = i ;
else if ( id = = pstate_min )
powernv_pstate_info . min = i ;
2017-01-03 14:06:00 +03:00
if ( powernv_pstate_info . wof_enabled & & id = = pstate_turbo ) {
int j ;
for ( j = i - 1 ; j > = ( int ) powernv_pstate_info . max ; j - - )
powernv_freqs [ j ] . flags = CPUFREQ_BOOST_FREQ ;
}
2014-04-01 11:13:26 +04:00
}
2016-06-30 09:23:07 +03:00
2014-04-01 11:13:26 +04:00
/* End of list marker entry */
powernv_freqs [ i ] . frequency = CPUFREQ_TABLE_END ;
return 0 ;
}
/* Returns the CPU frequency corresponding to the pstate_id. */
static unsigned int pstate_id_to_freq ( int pstate_id )
{
int i ;
2016-06-30 09:23:07 +03:00
i = pstate_to_idx ( pstate_id ) ;
2014-08-03 13:24:05 +04:00
if ( i > = powernv_pstate_info . nr_pstates | | i < 0 ) {
pr_warn ( " PState id %d outside of PState table, "
" reporting nominal id %d instead \n " ,
2016-06-30 09:23:07 +03:00
pstate_id , idx_to_pstate ( powernv_pstate_info . nominal ) ) ;
i = powernv_pstate_info . nominal ;
2014-08-03 13:24:05 +04:00
}
2014-04-01 11:13:26 +04:00
return powernv_freqs [ i ] . frequency ;
}
/*
* cpuinfo_nominal_freq_show - Show the nominal CPU frequency as indicated by
* the firmware
*/
static ssize_t cpuinfo_nominal_freq_show ( struct cpufreq_policy * policy ,
char * buf )
{
return sprintf ( buf , " %u \n " ,
2016-06-30 09:23:07 +03:00
powernv_freqs [ powernv_pstate_info . nominal ] . frequency ) ;
2014-04-01 11:13:26 +04:00
}
struct freq_attr cpufreq_freq_attr_cpuinfo_nominal_freq =
__ATTR_RO ( cpuinfo_nominal_freq ) ;
2017-01-03 14:06:00 +03:00
# define SCALING_BOOST_FREQS_ATTR_INDEX 2
2014-04-01 11:13:26 +04:00
static struct freq_attr * powernv_cpu_freq_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
& cpufreq_freq_attr_cpuinfo_nominal_freq ,
2017-01-03 14:06:00 +03:00
& cpufreq_freq_attr_scaling_boost_freqs ,
2014-04-01 11:13:26 +04:00
NULL ,
} ;
2016-03-22 16:27:09 +03:00
# define throttle_attr(name, member) \
static ssize_t name # # _show ( struct cpufreq_policy * policy , char * buf ) \
{ \
struct chip * chip = per_cpu ( chip_info , policy - > cpu ) ; \
\
return sprintf ( buf , " %u \n " , chip - > member ) ; \
} \
\
static struct freq_attr throttle_attr_ # # name = __ATTR_RO ( name ) \
throttle_attr ( unthrottle , reason [ NO_THROTTLE ] ) ;
throttle_attr ( powercap , reason [ POWERCAP ] ) ;
throttle_attr ( overtemp , reason [ CPU_OVERTEMP ] ) ;
throttle_attr ( supply_fault , reason [ POWER_SUPPLY_FAILURE ] ) ;
throttle_attr ( overcurrent , reason [ OVERCURRENT ] ) ;
throttle_attr ( occ_reset , reason [ OCC_RESET_THROTTLE ] ) ;
throttle_attr ( turbo_stat , throttle_turbo ) ;
throttle_attr ( sub_turbo_stat , throttle_sub_turbo ) ;
static struct attribute * throttle_attrs [ ] = {
& throttle_attr_unthrottle . attr ,
& throttle_attr_powercap . attr ,
& throttle_attr_overtemp . attr ,
& throttle_attr_supply_fault . attr ,
& throttle_attr_overcurrent . attr ,
& throttle_attr_occ_reset . attr ,
& throttle_attr_turbo_stat . attr ,
& throttle_attr_sub_turbo_stat . attr ,
NULL ,
} ;
static const struct attribute_group throttle_attr_grp = {
. name = " throttle_stats " ,
. attrs = throttle_attrs ,
} ;
2014-04-01 11:13:26 +04:00
/* Helper routines */
/* Access helpers to power mgt SPR */
static inline unsigned long get_pmspr ( unsigned long sprn )
{
switch ( sprn ) {
case SPRN_PMCR :
return mfspr ( SPRN_PMCR ) ;
case SPRN_PMICR :
return mfspr ( SPRN_PMICR ) ;
case SPRN_PMSR :
return mfspr ( SPRN_PMSR ) ;
}
BUG ( ) ;
}
static inline void set_pmspr ( unsigned long sprn , unsigned long val )
{
switch ( sprn ) {
case SPRN_PMCR :
mtspr ( SPRN_PMCR , val ) ;
return ;
case SPRN_PMICR :
mtspr ( SPRN_PMICR , val ) ;
return ;
}
BUG ( ) ;
}
/*
* Use objects of this type to query / update
* pstates on a remote CPU via smp_call_function .
*/
struct powernv_smp_call_data {
unsigned int freq ;
int pstate_id ;
2016-04-19 12:58:01 +03:00
int gpstate_id ;
2014-04-01 11:13:26 +04:00
} ;
/*
* powernv_read_cpu_freq : Reads the current frequency on this CPU .
*
* Called via smp_call_function .
*
* Note : The caller of the smp_call_function should pass an argument of
* the type ' struct powernv_smp_call_data * ' along with this function .
*
* The current frequency on this CPU will be returned via
* ( ( struct powernv_smp_call_data * ) arg ) - > freq ;
*/
static void powernv_read_cpu_freq ( void * arg )
{
unsigned long pmspr_val ;
s8 local_pstate_id ;
struct powernv_smp_call_data * freq_data = arg ;
pmspr_val = get_pmspr ( SPRN_PMSR ) ;
/*
* The local pstate id corresponds bits 48. .55 in the PMSR .
* Note : Watch out for the sign !
*/
local_pstate_id = ( pmspr_val > > 48 ) & 0xFF ;
freq_data - > pstate_id = local_pstate_id ;
freq_data - > freq = pstate_id_to_freq ( freq_data - > pstate_id ) ;
pr_debug ( " cpu %d pmsr %016lX pstate_id %d frequency %d kHz \n " ,
raw_smp_processor_id ( ) , pmspr_val , freq_data - > pstate_id ,
freq_data - > freq ) ;
}
/*
* powernv_cpufreq_get : Returns the CPU frequency as reported by the
* firmware for CPU ' cpu ' . This value is reported through the sysfs
* file cpuinfo_cur_freq .
*/
2014-05-11 11:51:20 +04:00
static unsigned int powernv_cpufreq_get ( unsigned int cpu )
2014-04-01 11:13:26 +04:00
{
struct powernv_smp_call_data freq_data ;
smp_call_function_any ( cpu_sibling_mask ( cpu ) , powernv_read_cpu_freq ,
& freq_data , 1 ) ;
return freq_data . freq ;
}
/*
* set_pstate : Sets the pstate on this CPU .
*
* This is called via an smp_call_function .
*
* The caller must ensure that freq_data is of the type
* ( struct powernv_smp_call_data * ) and the pstate_id which needs to be set
* on this CPU should be present in freq_data - > pstate_id .
*/
2016-04-19 12:58:01 +03:00
static void set_pstate ( void * data )
2014-04-01 11:13:26 +04:00
{
unsigned long val ;
2016-04-19 12:58:01 +03:00
struct powernv_smp_call_data * freq_data = data ;
unsigned long pstate_ul = freq_data - > pstate_id ;
unsigned long gpstate_ul = freq_data - > gpstate_id ;
2014-04-01 11:13:26 +04:00
val = get_pmspr ( SPRN_PMCR ) ;
val = val & 0x0000FFFFFFFFFFFFULL ;
pstate_ul = pstate_ul & 0xFF ;
2016-04-19 12:58:01 +03:00
gpstate_ul = gpstate_ul & 0xFF ;
2014-04-01 11:13:26 +04:00
/* Set both global(bits 56..63) and local(bits 48..55) PStates */
2016-04-19 12:58:01 +03:00
val = val | ( gpstate_ul < < 56 ) | ( pstate_ul < < 48 ) ;
2014-04-01 11:13:26 +04:00
pr_debug ( " Setting cpu %d pmcr to %016lX \n " ,
raw_smp_processor_id ( ) , val ) ;
set_pmspr ( SPRN_PMCR , val ) ;
}
2014-09-29 17:49:11 +04:00
/*
* get_nominal_index : Returns the index corresponding to the nominal
* pstate in the cpufreq table
*/
static inline unsigned int get_nominal_index ( void )
{
2016-06-30 09:23:07 +03:00
return powernv_pstate_info . nominal ;
2014-09-29 17:49:11 +04:00
}
2015-07-16 11:04:21 +03:00
static void powernv_cpufreq_throttle_check ( void * data )
2015-04-01 12:46:34 +03:00
{
2016-03-21 19:54:52 +03:00
struct chip * chip ;
2015-07-16 11:04:21 +03:00
unsigned int cpu = smp_processor_id ( ) ;
2015-04-01 12:46:34 +03:00
unsigned long pmsr ;
2016-03-21 19:54:52 +03:00
int pmsr_pmax ;
2016-06-30 09:23:07 +03:00
unsigned int pmsr_pmax_idx ;
2015-04-01 12:46:34 +03:00
pmsr = get_pmspr ( SPRN_PMSR ) ;
2016-03-21 19:54:52 +03:00
chip = this_cpu_read ( chip_info ) ;
2015-07-16 11:04:18 +03:00
2015-04-01 12:46:34 +03:00
/* Check for Pmax Capping */
pmsr_pmax = ( s8 ) PMSR_MAX ( pmsr ) ;
2016-06-30 09:23:07 +03:00
pmsr_pmax_idx = pstate_to_idx ( pmsr_pmax ) ;
if ( pmsr_pmax_idx ! = powernv_pstate_info . max ) {
2016-03-21 19:54:52 +03:00
if ( chip - > throttled )
2015-07-16 11:04:18 +03:00
goto next ;
2016-03-21 19:54:52 +03:00
chip - > throttled = true ;
2016-06-30 09:23:07 +03:00
if ( pmsr_pmax_idx > powernv_pstate_info . nominal ) {
pr_warn_once ( " CPU %d on Chip %u has Pmax(%d) reduced below nominal frequency(%d) \n " ,
2016-03-21 19:54:52 +03:00
cpu , chip - > id , pmsr_pmax ,
2016-06-30 09:23:07 +03:00
idx_to_pstate ( powernv_pstate_info . nominal ) ) ;
2016-03-22 16:27:09 +03:00
chip - > throttle_sub_turbo + + ;
} else {
chip - > throttle_turbo + + ;
}
2016-03-21 19:54:52 +03:00
trace_powernv_throttle ( chip - > id ,
throttle_reason [ chip - > throttle_reason ] ,
2016-02-02 22:41:41 +03:00
pmsr_pmax ) ;
2016-03-21 19:54:52 +03:00
} else if ( chip - > throttled ) {
chip - > throttled = false ;
trace_powernv_throttle ( chip - > id ,
throttle_reason [ chip - > throttle_reason ] ,
2016-02-02 22:41:41 +03:00
pmsr_pmax ) ;
2015-04-01 12:46:34 +03:00
}
2015-07-16 11:04:22 +03:00
/* Check if Psafe_mode_active is set in PMSR. */
2015-07-16 11:04:18 +03:00
next :
2015-07-16 11:04:22 +03:00
if ( pmsr & PMSR_PSAFE_ENABLE ) {
2015-04-01 12:46:34 +03:00
throttled = true ;
pr_info ( " Pstate set to safe frequency \n " ) ;
}
/* Check if SPR_EM_DISABLE is set in PMSR */
if ( pmsr & PMSR_SPR_EM_DISABLE ) {
throttled = true ;
pr_info ( " Frequency Control disabled from OS \n " ) ;
}
if ( throttled ) {
pr_info ( " PMSR = %16lx \n " , pmsr ) ;
2016-02-02 22:41:41 +03:00
pr_warn ( " CPU Frequency could be throttled \n " ) ;
2015-04-01 12:46:34 +03:00
}
}
2016-04-19 12:58:01 +03:00
/**
* calc_global_pstate - Calculate global pstate
2016-06-30 09:23:07 +03:00
* @ elapsed_time : Elapsed time in milliseconds
* @ local_pstate_idx : New local pstate
* @ highest_lpstate_idx : pstate from which its ramping down
2016-04-19 12:58:01 +03:00
*
* Finds the appropriate global pstate based on the pstate from which its
* ramping down and the time elapsed in ramping down . It follows a quadratic
* equation which ensures that it reaches ramping down to pmin in 5 sec .
*/
static inline int calc_global_pstate ( unsigned int elapsed_time ,
2016-06-30 09:23:07 +03:00
int highest_lpstate_idx ,
int local_pstate_idx )
2016-04-19 12:58:01 +03:00
{
2016-06-30 09:23:07 +03:00
int index_diff ;
2016-04-19 12:58:01 +03:00
/*
* Using ramp_down_percent we get the percentage of rampdown
* that we are expecting to be dropping . Difference between
2016-06-30 09:23:07 +03:00
* highest_lpstate_idx and powernv_pstate_info . min will give a absolute
2016-04-19 12:58:01 +03:00
* number of how many pstates we will drop eventually by the end of
* 5 seconds , then just scale it get the number pstates to be dropped .
*/
2016-06-30 09:23:07 +03:00
index_diff = ( ( int ) ramp_down_percent ( elapsed_time ) *
( powernv_pstate_info . min - highest_lpstate_idx ) ) / 100 ;
2016-04-19 12:58:01 +03:00
/* Ensure that global pstate is >= to local pstate */
2016-06-30 09:23:07 +03:00
if ( highest_lpstate_idx + index_diff > = local_pstate_idx )
return local_pstate_idx ;
2016-04-19 12:58:01 +03:00
else
2016-06-30 09:23:07 +03:00
return highest_lpstate_idx + index_diff ;
2016-04-19 12:58:01 +03:00
}
static inline void queue_gpstate_timer ( struct global_pstate_info * gpstates )
{
unsigned int timer_interval ;
/*
* Setting up timer to fire after GPSTATE_TIMER_INTERVAL ms , But
* if it exceeds MAX_RAMP_DOWN_TIME ms for ramp down time .
* Set timer such that it fires exactly at MAX_RAMP_DOWN_TIME
* seconds of ramp down time .
*/
if ( ( gpstates - > elapsed_time + GPSTATE_TIMER_INTERVAL )
> MAX_RAMP_DOWN_TIME )
timer_interval = MAX_RAMP_DOWN_TIME - gpstates - > elapsed_time ;
else
timer_interval = GPSTATE_TIMER_INTERVAL ;
2016-07-04 12:50:18 +03:00
mod_timer ( & gpstates - > timer , jiffies + msecs_to_jiffies ( timer_interval ) ) ;
2016-04-19 12:58:01 +03:00
}
/**
* gpstate_timer_handler
*
* @ data : pointer to cpufreq_policy on which timer was queued
*
* This handler brings down the global pstate closer to the local pstate
* according quadratic equation . Queues a new timer if it is still not equal
* to local pstate
*/
void gpstate_timer_handler ( unsigned long data )
{
struct cpufreq_policy * policy = ( struct cpufreq_policy * ) data ;
struct global_pstate_info * gpstates = policy - > driver_data ;
2016-11-08 16:33:28 +03:00
int gpstate_idx , lpstate_idx ;
unsigned long val ;
2016-04-19 12:58:01 +03:00
unsigned int time_diff = jiffies_to_msecs ( jiffies )
- gpstates - > last_sampled_time ;
struct powernv_smp_call_data freq_data ;
if ( ! spin_trylock ( & gpstates - > gpstate_lock ) )
return ;
2016-11-08 16:33:28 +03:00
/*
* If PMCR was last updated was using fast_swtich then
* We may have wrong in gpstate - > last_lpstate_idx
* value . Hence , read from PMCR to get correct data .
*/
val = get_pmspr ( SPRN_PMCR ) ;
freq_data . gpstate_id = ( s8 ) GET_GPSTATE ( val ) ;
freq_data . pstate_id = ( s8 ) GET_LPSTATE ( val ) ;
if ( freq_data . gpstate_id = = freq_data . pstate_id ) {
reset_gpstates ( policy ) ;
spin_unlock ( & gpstates - > gpstate_lock ) ;
return ;
}
2016-04-19 12:58:01 +03:00
gpstates - > last_sampled_time + = time_diff ;
gpstates - > elapsed_time + = time_diff ;
2016-11-08 16:33:28 +03:00
if ( gpstates - > elapsed_time > MAX_RAMP_DOWN_TIME ) {
2016-06-30 09:23:07 +03:00
gpstate_idx = pstate_to_idx ( freq_data . pstate_id ) ;
2016-11-14 14:59:27 +03:00
lpstate_idx = gpstate_idx ;
2016-04-19 12:58:01 +03:00
reset_gpstates ( policy ) ;
2016-06-30 09:23:07 +03:00
gpstates - > highest_lpstate_idx = gpstate_idx ;
2016-04-19 12:58:01 +03:00
} else {
2016-11-08 16:33:28 +03:00
lpstate_idx = pstate_to_idx ( freq_data . pstate_id ) ;
2016-06-30 09:23:07 +03:00
gpstate_idx = calc_global_pstate ( gpstates - > elapsed_time ,
gpstates - > highest_lpstate_idx ,
2016-11-08 16:33:28 +03:00
lpstate_idx ) ;
2016-04-19 12:58:01 +03:00
}
2016-11-08 16:33:28 +03:00
freq_data . gpstate_id = idx_to_pstate ( gpstate_idx ) ;
gpstates - > last_gpstate_idx = gpstate_idx ;
gpstates - > last_lpstate_idx = lpstate_idx ;
2016-04-19 12:58:01 +03:00
/*
* If local pstate is equal to global pstate , rampdown is over
* So timer is not required to be queued .
*/
2016-06-30 09:23:07 +03:00
if ( gpstate_idx ! = gpstates - > last_lpstate_idx )
2016-04-19 12:58:01 +03:00
queue_gpstate_timer ( gpstates ) ;
2016-05-03 18:19:35 +03:00
spin_unlock ( & gpstates - > gpstate_lock ) ;
2016-04-19 12:58:01 +03:00
/* Timer may get migrated to a different cpu on cpu hot unplug */
smp_call_function_any ( policy - > cpus , set_pstate , & freq_data , 1 ) ;
}
2014-04-01 11:13:26 +04:00
/*
* powernv_cpufreq_target_index : Sets the frequency corresponding to
* the cpufreq table entry indexed by new_index on the cpus in the
* mask policy - > cpus
*/
static int powernv_cpufreq_target_index ( struct cpufreq_policy * policy ,
unsigned int new_index )
{
struct powernv_smp_call_data freq_data ;
2016-06-30 09:23:07 +03:00
unsigned int cur_msec , gpstate_idx ;
2016-04-19 12:58:01 +03:00
struct global_pstate_info * gpstates = policy - > driver_data ;
2014-04-01 11:13:26 +04:00
2014-09-29 17:49:11 +04:00
if ( unlikely ( rebooting ) & & new_index ! = get_nominal_index ( ) )
return 0 ;
2016-11-08 13:39:28 +03:00
if ( ! throttled ) {
/* we don't want to be preempted while
* checking if the CPU frequency has been throttled
*/
preempt_disable ( ) ;
2015-07-16 11:04:21 +03:00
powernv_cpufreq_throttle_check ( NULL ) ;
2016-11-08 13:39:28 +03:00
preempt_enable ( ) ;
}
2015-04-01 12:46:34 +03:00
2016-04-19 12:58:01 +03:00
cur_msec = jiffies_to_msecs ( get_jiffies_64 ( ) ) ;
2016-05-03 18:19:35 +03:00
spin_lock ( & gpstates - > gpstate_lock ) ;
2016-06-30 09:23:07 +03:00
freq_data . pstate_id = idx_to_pstate ( new_index ) ;
2014-04-01 11:13:26 +04:00
2016-04-19 12:58:01 +03:00
if ( ! gpstates - > last_sampled_time ) {
2016-06-30 09:23:07 +03:00
gpstate_idx = new_index ;
gpstates - > highest_lpstate_idx = new_index ;
2016-04-19 12:58:01 +03:00
goto gpstates_done ;
}
2016-06-30 09:23:07 +03:00
if ( gpstates - > last_gpstate_idx < new_index ) {
2016-04-19 12:58:01 +03:00
gpstates - > elapsed_time + = cur_msec -
gpstates - > last_sampled_time ;
/*
* If its has been ramping down for more than MAX_RAMP_DOWN_TIME
* we should be resetting all global pstate related data . Set it
* equal to local pstate to start fresh .
*/
if ( gpstates - > elapsed_time > MAX_RAMP_DOWN_TIME ) {
reset_gpstates ( policy ) ;
2016-06-30 09:23:07 +03:00
gpstates - > highest_lpstate_idx = new_index ;
gpstate_idx = new_index ;
2016-04-19 12:58:01 +03:00
} else {
/* Elaspsed_time is less than 5 seconds, continue to rampdown */
2016-06-30 09:23:07 +03:00
gpstate_idx = calc_global_pstate ( gpstates - > elapsed_time ,
gpstates - > highest_lpstate_idx ,
new_index ) ;
2016-04-19 12:58:01 +03:00
}
} else {
reset_gpstates ( policy ) ;
2016-06-30 09:23:07 +03:00
gpstates - > highest_lpstate_idx = new_index ;
gpstate_idx = new_index ;
2016-04-19 12:58:01 +03:00
}
/*
* If local pstate is equal to global pstate , rampdown is over
* So timer is not required to be queued .
*/
2016-06-30 09:23:07 +03:00
if ( gpstate_idx ! = new_index )
2016-04-19 12:58:01 +03:00
queue_gpstate_timer ( gpstates ) ;
2016-05-03 18:19:36 +03:00
else
del_timer_sync ( & gpstates - > timer ) ;
2016-04-19 12:58:01 +03:00
gpstates_done :
2016-06-30 09:23:07 +03:00
freq_data . gpstate_id = idx_to_pstate ( gpstate_idx ) ;
2016-04-19 12:58:01 +03:00
gpstates - > last_sampled_time = cur_msec ;
2016-06-30 09:23:07 +03:00
gpstates - > last_gpstate_idx = gpstate_idx ;
gpstates - > last_lpstate_idx = new_index ;
2016-04-19 12:58:01 +03:00
2016-05-03 18:19:35 +03:00
spin_unlock ( & gpstates - > gpstate_lock ) ;
2014-04-01 11:13:26 +04:00
/*
* Use smp_call_function to send IPI and execute the
* mtspr on target CPU . We could do that without IPI
* if current CPU is within policy - > cpus ( core )
*/
smp_call_function_any ( policy - > cpus , set_pstate , & freq_data , 1 ) ;
return 0 ;
}
static int powernv_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
2016-04-19 12:58:01 +03:00
int base , i , ret ;
2016-04-19 12:58:00 +03:00
struct kernfs_node * kn ;
2016-04-19 12:58:01 +03:00
struct global_pstate_info * gpstates ;
2014-04-01 11:13:26 +04:00
base = cpu_first_thread_sibling ( policy - > cpu ) ;
for ( i = 0 ; i < threads_per_core ; i + + )
cpumask_set_cpu ( base + i , policy - > cpus ) ;
2016-04-19 12:58:00 +03:00
kn = kernfs_find_and_get ( policy - > kobj . sd , throttle_attr_grp . name ) ;
if ( ! kn ) {
2016-03-22 16:27:09 +03:00
int ret ;
ret = sysfs_create_group ( & policy - > kobj , & throttle_attr_grp ) ;
if ( ret ) {
pr_info ( " Failed to create throttle stats directory for cpu %d \n " ,
policy - > cpu ) ;
return ret ;
}
2016-04-19 12:58:00 +03:00
} else {
kernfs_put ( kn ) ;
2016-03-22 16:27:09 +03:00
}
2016-04-19 12:58:01 +03:00
gpstates = kzalloc ( sizeof ( * gpstates ) , GFP_KERNEL ) ;
if ( ! gpstates )
return - ENOMEM ;
policy - > driver_data = gpstates ;
/* initialize timer */
2016-07-04 12:50:18 +03:00
init_timer_pinned_deferrable ( & gpstates - > timer ) ;
2016-04-19 12:58:01 +03:00
gpstates - > timer . data = ( unsigned long ) policy ;
gpstates - > timer . function = gpstate_timer_handler ;
gpstates - > timer . expires = jiffies +
msecs_to_jiffies ( GPSTATE_TIMER_INTERVAL ) ;
spin_lock_init ( & gpstates - > gpstate_lock ) ;
ret = cpufreq_table_validate_and_show ( policy , powernv_freqs ) ;
2016-11-08 16:33:27 +03:00
if ( ret < 0 ) {
2016-04-19 12:58:01 +03:00
kfree ( policy - > driver_data ) ;
2016-11-08 16:33:27 +03:00
return ret ;
}
2016-04-19 12:58:01 +03:00
2016-11-08 16:33:27 +03:00
policy - > fast_switch_possible = true ;
2016-04-19 12:58:01 +03:00
return ret ;
}
static int powernv_cpufreq_cpu_exit ( struct cpufreq_policy * policy )
{
/* timer is deleted in cpufreq_cpu_stop() */
kfree ( policy - > driver_data ) ;
return 0 ;
2014-04-01 11:13:26 +04:00
}
2014-09-29 17:49:11 +04:00
static int powernv_cpufreq_reboot_notifier ( struct notifier_block * nb ,
unsigned long action , void * unused )
{
int cpu ;
struct cpufreq_policy cpu_policy ;
rebooting = true ;
for_each_online_cpu ( cpu ) {
cpufreq_get_policy ( & cpu_policy , cpu ) ;
powernv_cpufreq_target_index ( & cpu_policy , get_nominal_index ( ) ) ;
}
return NOTIFY_DONE ;
}
static struct notifier_block powernv_cpufreq_reboot_nb = {
. notifier_call = powernv_cpufreq_reboot_notifier ,
} ;
2015-07-16 11:04:21 +03:00
void powernv_cpufreq_work_fn ( struct work_struct * work )
{
struct chip * chip = container_of ( work , struct chip , throttle ) ;
2015-07-16 11:04:23 +03:00
unsigned int cpu ;
2016-02-02 22:41:38 +03:00
cpumask_t mask ;
2015-07-16 11:04:21 +03:00
2016-02-02 22:41:38 +03:00
get_online_cpus ( ) ;
cpumask_and ( & mask , & chip - > mask , cpu_online_mask ) ;
smp_call_function_any ( & mask ,
2015-07-16 11:04:21 +03:00
powernv_cpufreq_throttle_check , NULL , 0 ) ;
2015-07-16 11:04:23 +03:00
if ( ! chip - > restore )
2016-02-02 22:41:38 +03:00
goto out ;
2015-07-16 11:04:23 +03:00
chip - > restore = false ;
2016-02-02 22:41:38 +03:00
for_each_cpu ( cpu , & mask ) {
int index ;
2015-07-16 11:04:23 +03:00
struct cpufreq_policy policy ;
cpufreq_get_policy ( & policy , cpu ) ;
2016-06-27 07:29:34 +03:00
index = cpufreq_table_find_index_c ( & policy , policy . cur ) ;
2015-07-16 11:04:23 +03:00
powernv_cpufreq_target_index ( & policy , index ) ;
2016-02-02 22:41:38 +03:00
cpumask_andnot ( & mask , & mask , policy . cpus ) ;
2015-07-16 11:04:23 +03:00
}
2016-02-02 22:41:38 +03:00
out :
put_online_cpus ( ) ;
2015-07-16 11:04:21 +03:00
}
2015-07-16 11:04:20 +03:00
static int powernv_cpufreq_occ_msg ( struct notifier_block * nb ,
unsigned long msg_type , void * _msg )
{
struct opal_msg * msg = _msg ;
struct opal_occ_msg omsg ;
2015-07-16 11:04:21 +03:00
int i ;
2015-07-16 11:04:20 +03:00
if ( msg_type ! = OPAL_MSG_OCC )
return 0 ;
omsg . type = be64_to_cpu ( msg - > params [ 0 ] ) ;
switch ( omsg . type ) {
case OCC_RESET :
occ_reset = true ;
2015-08-27 12:11:44 +03:00
pr_info ( " OCC (On Chip Controller - enforces hard thermal/power limits) Resetting \n " ) ;
2015-07-16 11:04:20 +03:00
/*
* powernv_cpufreq_throttle_check ( ) is called in
* target ( ) callback which can detect the throttle state
* for governors like ondemand .
* But static governors will not call target ( ) often thus
* report throttling here .
*/
if ( ! throttled ) {
throttled = true ;
2016-02-02 22:41:41 +03:00
pr_warn ( " CPU frequency is throttled for duration \n " ) ;
2015-07-16 11:04:20 +03:00
}
2015-08-27 12:11:44 +03:00
2015-07-16 11:04:20 +03:00
break ;
case OCC_LOAD :
2015-08-27 12:11:44 +03:00
pr_info ( " OCC Loading, CPU frequency is throttled until OCC is started \n " ) ;
2015-07-16 11:04:20 +03:00
break ;
case OCC_THROTTLE :
omsg . chip = be64_to_cpu ( msg - > params [ 1 ] ) ;
omsg . throttle_status = be64_to_cpu ( msg - > params [ 2 ] ) ;
if ( occ_reset ) {
occ_reset = false ;
throttled = false ;
2015-08-27 12:11:44 +03:00
pr_info ( " OCC Active, CPU frequency is no longer throttled \n " ) ;
2015-07-16 11:04:21 +03:00
2015-07-16 11:04:23 +03:00
for ( i = 0 ; i < nr_chips ; i + + ) {
chips [ i ] . restore = true ;
2015-07-16 11:04:21 +03:00
schedule_work ( & chips [ i ] . throttle ) ;
2015-07-16 11:04:23 +03:00
}
2015-07-16 11:04:21 +03:00
2015-07-16 11:04:20 +03:00
return 0 ;
}
2016-02-02 22:41:41 +03:00
for ( i = 0 ; i < nr_chips ; i + + )
if ( chips [ i ] . id = = omsg . chip )
break ;
if ( omsg . throttle_status > = 0 & &
2016-03-22 16:27:09 +03:00
omsg . throttle_status < = OCC_MAX_THROTTLE_STATUS ) {
2016-02-02 22:41:41 +03:00
chips [ i ] . throttle_reason = omsg . throttle_status ;
2016-03-22 16:27:09 +03:00
chips [ i ] . reason [ omsg . throttle_status ] + + ;
}
2015-07-16 11:04:21 +03:00
2016-02-02 22:41:41 +03:00
if ( ! omsg . throttle_status )
chips [ i ] . restore = true ;
schedule_work ( & chips [ i ] . throttle ) ;
2015-07-16 11:04:20 +03:00
}
return 0 ;
}
static struct notifier_block powernv_cpufreq_opal_nb = {
. notifier_call = powernv_cpufreq_occ_msg ,
. next = NULL ,
. priority = 0 ,
} ;
2014-09-29 17:47:53 +04:00
static void powernv_cpufreq_stop_cpu ( struct cpufreq_policy * policy )
{
struct powernv_smp_call_data freq_data ;
2016-04-19 12:58:01 +03:00
struct global_pstate_info * gpstates = policy - > driver_data ;
2014-09-29 17:47:53 +04:00
2016-06-30 09:23:07 +03:00
freq_data . pstate_id = idx_to_pstate ( powernv_pstate_info . min ) ;
freq_data . gpstate_id = idx_to_pstate ( powernv_pstate_info . min ) ;
2014-09-29 17:47:53 +04:00
smp_call_function_single ( policy - > cpu , set_pstate , & freq_data , 1 ) ;
2016-04-19 12:58:01 +03:00
del_timer_sync ( & gpstates - > timer ) ;
2014-09-29 17:47:53 +04:00
}
2016-11-08 16:33:27 +03:00
static unsigned int powernv_fast_switch ( struct cpufreq_policy * policy ,
unsigned int target_freq )
{
int index ;
struct powernv_smp_call_data freq_data ;
index = cpufreq_table_find_index_dl ( policy , target_freq ) ;
freq_data . pstate_id = powernv_freqs [ index ] . driver_data ;
freq_data . gpstate_id = powernv_freqs [ index ] . driver_data ;
set_pstate ( & freq_data ) ;
return powernv_freqs [ index ] . frequency ;
}
2014-04-01 11:13:26 +04:00
static struct cpufreq_driver powernv_cpufreq_driver = {
. name = " powernv-cpufreq " ,
. flags = CPUFREQ_CONST_LOOPS ,
. init = powernv_cpufreq_cpu_init ,
2016-04-19 12:58:01 +03:00
. exit = powernv_cpufreq_cpu_exit ,
2014-04-01 11:13:26 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
. target_index = powernv_cpufreq_target_index ,
2016-11-08 16:33:27 +03:00
. fast_switch = powernv_fast_switch ,
2014-04-01 11:13:26 +04:00
. get = powernv_cpufreq_get ,
2014-09-29 17:47:53 +04:00
. stop_cpu = powernv_cpufreq_stop_cpu ,
2014-04-01 11:13:26 +04:00
. attr = powernv_cpu_freq_attr ,
} ;
2015-07-16 11:04:18 +03:00
static int init_chip_info ( void )
{
unsigned int chip [ 256 ] ;
unsigned int cpu , i ;
unsigned int prev_chip_id = UINT_MAX ;
2016-02-02 22:41:39 +03:00
2016-03-21 19:54:52 +03:00
for_each_possible_cpu ( cpu ) {
2015-07-16 11:04:18 +03:00
unsigned int id = cpu_to_chip_id ( cpu ) ;
if ( prev_chip_id ! = id ) {
prev_chip_id = id ;
chip [ nr_chips + + ] = id ;
}
}
2016-02-02 22:41:41 +03:00
chips = kcalloc ( nr_chips , sizeof ( struct chip ) , GFP_KERNEL ) ;
2015-07-16 11:04:18 +03:00
if ( ! chips )
2016-03-21 19:54:52 +03:00
return - ENOMEM ;
2015-07-16 11:04:18 +03:00
for ( i = 0 ; i < nr_chips ; i + + ) {
chips [ i ] . id = chip [ i ] ;
2015-07-16 11:04:21 +03:00
cpumask_copy ( & chips [ i ] . mask , cpumask_of_node ( chip [ i ] ) ) ;
INIT_WORK ( & chips [ i ] . throttle , powernv_cpufreq_work_fn ) ;
2016-03-21 19:54:52 +03:00
for_each_cpu ( cpu , & chips [ i ] . mask )
per_cpu ( chip_info , cpu ) = & chips [ i ] ;
2015-07-16 11:04:18 +03:00
}
return 0 ;
}
2016-02-26 13:36:51 +03:00
static inline void clean_chip_info ( void )
{
kfree ( chips ) ;
}
static inline void unregister_all_notifiers ( void )
{
opal_message_notifier_unregister ( OPAL_MSG_OCC ,
& powernv_cpufreq_opal_nb ) ;
unregister_reboot_notifier ( & powernv_cpufreq_reboot_nb ) ;
}
2014-04-01 11:13:26 +04:00
static int __init powernv_cpufreq_init ( void )
{
int rc = 0 ;
2014-08-03 13:24:05 +04:00
/* Don't probe on pseries (guest) platforms */
2015-12-09 09:18:20 +03:00
if ( ! firmware_has_feature ( FW_FEATURE_OPAL ) )
2014-08-03 13:24:05 +04:00
return - ENODEV ;
2014-04-01 11:13:26 +04:00
/* Discover pstates from device tree and init */
rc = init_powernv_pstates ( ) ;
2016-02-26 13:36:51 +03:00
if ( rc )
goto out ;
2014-04-01 11:13:26 +04:00
2015-07-16 11:04:18 +03:00
/* Populate chip info */
rc = init_chip_info ( ) ;
if ( rc )
2016-02-26 13:36:51 +03:00
goto out ;
2015-07-16 11:04:18 +03:00
2014-09-29 17:49:11 +04:00
register_reboot_notifier ( & powernv_cpufreq_reboot_nb ) ;
2015-07-16 11:04:20 +03:00
opal_message_notifier_register ( OPAL_MSG_OCC , & powernv_cpufreq_opal_nb ) ;
2016-02-26 13:36:51 +03:00
2017-01-03 14:06:00 +03:00
if ( powernv_pstate_info . wof_enabled )
powernv_cpufreq_driver . boost_enabled = true ;
else
powernv_cpu_freq_attr [ SCALING_BOOST_FREQS_ATTR_INDEX ] = NULL ;
2016-02-26 13:36:51 +03:00
rc = cpufreq_register_driver ( & powernv_cpufreq_driver ) ;
2017-01-03 14:06:00 +03:00
if ( rc ) {
pr_info ( " Failed to register the cpufreq driver (%d) \n " , rc ) ;
goto cleanup_notifiers ;
}
2016-02-26 13:36:51 +03:00
2017-01-03 14:06:00 +03:00
if ( powernv_pstate_info . wof_enabled )
cpufreq_enable_boost_support ( ) ;
return 0 ;
cleanup_notifiers :
2016-02-26 13:36:51 +03:00
unregister_all_notifiers ( ) ;
clean_chip_info ( ) ;
out :
pr_info ( " Platform driver disabled. System does not support PState control \n " ) ;
return rc ;
2014-04-01 11:13:26 +04:00
}
module_init ( powernv_cpufreq_init ) ;
static void __exit powernv_cpufreq_exit ( void )
{
cpufreq_unregister_driver ( & powernv_cpufreq_driver ) ;
2016-02-26 13:36:51 +03:00
unregister_all_notifiers ( ) ;
clean_chip_info ( ) ;
2014-04-01 11:13:26 +04:00
}
module_exit ( powernv_cpufreq_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Vaidyanathan Srinivasan <svaidy at linux.vnet.ibm.com> " ) ;