2015-09-10 20:09:30 +03:00
/*
* devfreq_cooling : Thermal cooling device implementation for devices using
* devfreq
*
* Copyright ( C ) 2014 - 2015 ARM Limited
*
* 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 .
*
* This program is distributed " as is " WITHOUT ANY WARRANTY of any
* kind , whether express or implied ; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* TODO :
* - If OPPs are added or removed after devfreq cooling has
* registered , the devfreq cooling won ' t react to it .
*/
# include <linux/devfreq.h>
# include <linux/devfreq_cooling.h>
# include <linux/export.h>
# include <linux/slab.h>
# include <linux/pm_opp.h>
# include <linux/thermal.h>
2015-09-10 20:09:31 +03:00
# include <trace/events/thermal.h>
2015-09-10 20:09:30 +03:00
static DEFINE_MUTEX ( devfreq_lock ) ;
static DEFINE_IDR ( devfreq_idr ) ;
/**
* struct devfreq_cooling_device - Devfreq cooling device
* @ id : unique integer value corresponding to each
* devfreq_cooling_device registered .
* @ cdev : Pointer to associated thermal cooling device .
* @ devfreq : Pointer to associated devfreq device .
* @ cooling_state : Current cooling state .
* @ power_table : Pointer to table with maximum power draw for each
* cooling state . State is the index into the table , and
* the power is in mW .
* @ freq_table : Pointer to a table with the frequencies sorted in descending
* order . You can index the table by cooling device state
* @ freq_table_size : Size of the @ freq_table and @ power_table
* @ power_ops : Pointer to devfreq_cooling_power , used to generate the
* @ power_table .
*/
struct devfreq_cooling_device {
int id ;
struct thermal_cooling_device * cdev ;
struct devfreq * devfreq ;
unsigned long cooling_state ;
u32 * power_table ;
u32 * freq_table ;
size_t freq_table_size ;
struct devfreq_cooling_power * power_ops ;
} ;
/**
* get_idr - function to get a unique id .
* @ idr : struct idr * handle used to create a id .
* @ id : int * value generated by this function .
*
* This function will populate @ id with an unique
* id , using the idr API .
*
* Return : 0 on success , an error code on failure .
*/
static int get_idr ( struct idr * idr , int * id )
{
int ret ;
mutex_lock ( & devfreq_lock ) ;
ret = idr_alloc ( idr , NULL , 0 , 0 , GFP_KERNEL ) ;
mutex_unlock ( & devfreq_lock ) ;
if ( unlikely ( ret < 0 ) )
return ret ;
* id = ret ;
return 0 ;
}
/**
* release_idr - function to free the unique id .
* @ idr : struct idr * handle used for creating the id .
* @ id : int value representing the unique id .
*/
static void release_idr ( struct idr * idr , int id )
{
mutex_lock ( & devfreq_lock ) ;
idr_remove ( idr , id ) ;
mutex_unlock ( & devfreq_lock ) ;
}
/**
* partition_enable_opps ( ) - disable all opps above a given state
* @ dfc : Pointer to devfreq we are operating on
* @ cdev_state : cooling device state we ' re setting
*
* Go through the OPPs of the device , enabling all OPPs until
* @ cdev_state and disabling those frequencies above it .
*/
static int partition_enable_opps ( struct devfreq_cooling_device * dfc ,
unsigned long cdev_state )
{
int i ;
struct device * dev = dfc - > devfreq - > dev . parent ;
for ( i = 0 ; i < dfc - > freq_table_size ; i + + ) {
struct dev_pm_opp * opp ;
int ret = 0 ;
unsigned int freq = dfc - > freq_table [ i ] ;
bool want_enable = i > = cdev_state ? true : false ;
rcu_read_lock ( ) ;
opp = dev_pm_opp_find_freq_exact ( dev , freq , ! want_enable ) ;
rcu_read_unlock ( ) ;
if ( PTR_ERR ( opp ) = = - ERANGE )
continue ;
else if ( IS_ERR ( opp ) )
return PTR_ERR ( opp ) ;
if ( want_enable )
ret = dev_pm_opp_enable ( dev , freq ) ;
else
ret = dev_pm_opp_disable ( dev , freq ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int devfreq_cooling_get_max_state ( struct thermal_cooling_device * cdev ,
unsigned long * state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
* state = dfc - > freq_table_size - 1 ;
return 0 ;
}
static int devfreq_cooling_get_cur_state ( struct thermal_cooling_device * cdev ,
unsigned long * state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
* state = dfc - > cooling_state ;
return 0 ;
}
static int devfreq_cooling_set_cur_state ( struct thermal_cooling_device * cdev ,
unsigned long state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
struct devfreq * df = dfc - > devfreq ;
struct device * dev = df - > dev . parent ;
int ret ;
if ( state = = dfc - > cooling_state )
return 0 ;
dev_dbg ( dev , " Setting cooling state %lu \n " , state ) ;
if ( state > = dfc - > freq_table_size )
return - EINVAL ;
ret = partition_enable_opps ( dfc , state ) ;
if ( ret )
return ret ;
dfc - > cooling_state = state ;
return 0 ;
}
/**
* freq_get_state ( ) - get the cooling state corresponding to a frequency
* @ dfc : Pointer to devfreq cooling device
* @ freq : frequency in Hz
*
* Return : the cooling state associated with the @ freq , or
* THERMAL_CSTATE_INVALID if it wasn ' t found .
*/
static unsigned long
freq_get_state ( struct devfreq_cooling_device * dfc , unsigned long freq )
{
int i ;
for ( i = 0 ; i < dfc - > freq_table_size ; i + + ) {
if ( dfc - > freq_table [ i ] = = freq )
return i ;
}
return THERMAL_CSTATE_INVALID ;
}
/**
* get_static_power ( ) - calculate the static power
* @ dfc : Pointer to devfreq cooling device
* @ freq : Frequency in Hz
*
* Calculate the static power in milliwatts using the supplied
* get_static_power ( ) . The current voltage is calculated using the
* OPP library . If no get_static_power ( ) was supplied , assume the
* static power is negligible .
*/
static unsigned long
get_static_power ( struct devfreq_cooling_device * dfc , unsigned long freq )
{
struct devfreq * df = dfc - > devfreq ;
struct device * dev = df - > dev . parent ;
unsigned long voltage ;
struct dev_pm_opp * opp ;
if ( ! dfc - > power_ops - > get_static_power )
return 0 ;
rcu_read_lock ( ) ;
opp = dev_pm_opp_find_freq_exact ( dev , freq , true ) ;
if ( IS_ERR ( opp ) & & ( PTR_ERR ( opp ) = = - ERANGE ) )
opp = dev_pm_opp_find_freq_exact ( dev , freq , false ) ;
voltage = dev_pm_opp_get_voltage ( opp ) / 1000 ; /* mV */
rcu_read_unlock ( ) ;
if ( voltage = = 0 ) {
dev_warn_ratelimited ( dev ,
" Failed to get voltage for frequency %lu: %ld \n " ,
freq , IS_ERR ( opp ) ? PTR_ERR ( opp ) : 0 ) ;
return 0 ;
}
2016-09-15 17:44:23 +03:00
return dfc - > power_ops - > get_static_power ( df , voltage ) ;
2015-09-10 20:09:30 +03:00
}
/**
* get_dynamic_power - calculate the dynamic power
* @ dfc : Pointer to devfreq cooling device
* @ freq : Frequency in Hz
* @ voltage : Voltage in millivolts
*
* Calculate the dynamic power in milliwatts consumed by the device at
* frequency @ freq and voltage @ voltage . If the get_dynamic_power ( )
* was supplied as part of the devfreq_cooling_power struct , then that
* function is used . Otherwise , a simple power model ( Pdyn = Coeff *
* Voltage ^ 2 * Frequency ) is used .
*/
static unsigned long
get_dynamic_power ( struct devfreq_cooling_device * dfc , unsigned long freq ,
unsigned long voltage )
{
2015-11-02 22:03:04 +03:00
u64 power ;
2015-09-10 20:09:30 +03:00
u32 freq_mhz ;
struct devfreq_cooling_power * dfc_power = dfc - > power_ops ;
if ( dfc_power - > get_dynamic_power )
2016-09-15 17:44:23 +03:00
return dfc_power - > get_dynamic_power ( dfc - > devfreq , freq ,
voltage ) ;
2015-09-10 20:09:30 +03:00
freq_mhz = freq / 1000000 ;
power = ( u64 ) dfc_power - > dyn_power_coeff * freq_mhz * voltage * voltage ;
do_div ( power , 1000000000 ) ;
return power ;
}
static int devfreq_cooling_get_requested_power ( struct thermal_cooling_device * cdev ,
struct thermal_zone_device * tz ,
u32 * power )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
struct devfreq * df = dfc - > devfreq ;
struct devfreq_dev_status * status = & df - > last_status ;
unsigned long state ;
unsigned long freq = status - > current_frequency ;
u32 dyn_power , static_power ;
/* Get dynamic power for state */
state = freq_get_state ( dfc , freq ) ;
if ( state = = THERMAL_CSTATE_INVALID )
return - EAGAIN ;
dyn_power = dfc - > power_table [ state ] ;
/* Scale dynamic power for utilization */
dyn_power = ( dyn_power * status - > busy_time ) / status - > total_time ;
/* Get static power */
static_power = get_static_power ( dfc , freq ) ;
2015-09-10 20:09:31 +03:00
trace_thermal_power_devfreq_get_power ( cdev , status , freq , dyn_power ,
static_power ) ;
2015-09-10 20:09:30 +03:00
* power = dyn_power + static_power ;
return 0 ;
}
static int devfreq_cooling_state2power ( struct thermal_cooling_device * cdev ,
struct thermal_zone_device * tz ,
unsigned long state ,
u32 * power )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
unsigned long freq ;
u32 static_power ;
2016-08-22 11:08:06 +03:00
if ( state > = dfc - > freq_table_size )
2015-09-10 20:09:30 +03:00
return - EINVAL ;
freq = dfc - > freq_table [ state ] ;
static_power = get_static_power ( dfc , freq ) ;
* power = dfc - > power_table [ state ] + static_power ;
return 0 ;
}
static int devfreq_cooling_power2state ( struct thermal_cooling_device * cdev ,
struct thermal_zone_device * tz ,
u32 power , unsigned long * state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
struct devfreq * df = dfc - > devfreq ;
struct devfreq_dev_status * status = & df - > last_status ;
unsigned long freq = status - > current_frequency ;
unsigned long busy_time ;
s32 dyn_power ;
u32 static_power ;
int i ;
static_power = get_static_power ( dfc , freq ) ;
dyn_power = power - static_power ;
dyn_power = dyn_power > 0 ? dyn_power : 0 ;
/* Scale dynamic power for utilization */
busy_time = status - > busy_time ? : 1 ;
dyn_power = ( dyn_power * status - > total_time ) / busy_time ;
/*
* Find the first cooling state that is within the power
* budget for dynamic power .
*/
for ( i = 0 ; i < dfc - > freq_table_size - 1 ; i + + )
if ( dyn_power > = dfc - > power_table [ i ] )
break ;
* state = i ;
2015-09-10 20:09:31 +03:00
trace_thermal_power_devfreq_limit ( cdev , freq , * state , power ) ;
2015-09-10 20:09:30 +03:00
return 0 ;
}
static struct thermal_cooling_device_ops devfreq_cooling_ops = {
. get_max_state = devfreq_cooling_get_max_state ,
. get_cur_state = devfreq_cooling_get_cur_state ,
. set_cur_state = devfreq_cooling_set_cur_state ,
} ;
/**
* devfreq_cooling_gen_tables ( ) - Generate power and freq tables .
* @ dfc : Pointer to devfreq cooling device .
*
* Generate power and frequency tables : the power table hold the
* device ' s maximum power usage at each cooling state ( OPP ) . The
* static and dynamic power using the appropriate voltage and
* frequency for the state , is acquired from the struct
* devfreq_cooling_power , and summed to make the maximum power draw .
*
* The frequency table holds the frequencies in descending order .
* That way its indexed by cooling device state .
*
* The tables are malloced , and pointers put in dfc . They must be
* freed when unregistering the devfreq cooling device .
*
* Return : 0 on success , negative error code on failure .
*/
static int devfreq_cooling_gen_tables ( struct devfreq_cooling_device * dfc )
{
struct devfreq * df = dfc - > devfreq ;
struct device * dev = df - > dev . parent ;
int ret , num_opps ;
unsigned long freq ;
u32 * power_table = NULL ;
u32 * freq_table ;
int i ;
num_opps = dev_pm_opp_get_opp_count ( dev ) ;
if ( dfc - > power_ops ) {
power_table = kcalloc ( num_opps , sizeof ( * power_table ) ,
GFP_KERNEL ) ;
if ( ! power_table )
2015-11-04 16:36:20 +03:00
return - ENOMEM ;
2015-09-10 20:09:30 +03:00
}
freq_table = kcalloc ( num_opps , sizeof ( * freq_table ) ,
GFP_KERNEL ) ;
if ( ! freq_table ) {
ret = - ENOMEM ;
goto free_power_table ;
}
for ( i = 0 , freq = ULONG_MAX ; i < num_opps ; i + + , freq - - ) {
unsigned long power_dyn , voltage ;
struct dev_pm_opp * opp ;
rcu_read_lock ( ) ;
opp = dev_pm_opp_find_freq_floor ( dev , & freq ) ;
if ( IS_ERR ( opp ) ) {
rcu_read_unlock ( ) ;
ret = PTR_ERR ( opp ) ;
goto free_tables ;
}
voltage = dev_pm_opp_get_voltage ( opp ) / 1000 ; /* mV */
rcu_read_unlock ( ) ;
if ( dfc - > power_ops ) {
power_dyn = get_dynamic_power ( dfc , freq , voltage ) ;
dev_dbg ( dev , " Dynamic power table: %lu MHz @ %lu mV: %lu = %lu mW \n " ,
freq / 1000000 , voltage , power_dyn , power_dyn ) ;
power_table [ i ] = power_dyn ;
}
freq_table [ i ] = freq ;
}
if ( dfc - > power_ops )
dfc - > power_table = power_table ;
dfc - > freq_table = freq_table ;
dfc - > freq_table_size = num_opps ;
return 0 ;
free_tables :
kfree ( freq_table ) ;
free_power_table :
kfree ( power_table ) ;
return ret ;
}
/**
* of_devfreq_cooling_register_power ( ) - Register devfreq cooling device ,
* with OF and power information .
* @ np : Pointer to OF device_node .
* @ df : Pointer to devfreq device .
* @ dfc_power : Pointer to devfreq_cooling_power .
*
* Register a devfreq cooling device . The available OPPs must be
* registered on the device .
*
* If @ dfc_power is provided , the cooling device is registered with the
* power extensions . For the power extensions to work correctly ,
* devfreq should use the simple_ondemand governor , other governors
* are not currently supported .
*/
2015-11-02 22:03:03 +03:00
struct thermal_cooling_device *
2015-09-10 20:09:30 +03:00
of_devfreq_cooling_register_power ( struct device_node * np , struct devfreq * df ,
struct devfreq_cooling_power * dfc_power )
{
struct thermal_cooling_device * cdev ;
struct devfreq_cooling_device * dfc ;
char dev_name [ THERMAL_NAME_LENGTH ] ;
int err ;
dfc = kzalloc ( sizeof ( * dfc ) , GFP_KERNEL ) ;
if ( ! dfc )
return ERR_PTR ( - ENOMEM ) ;
dfc - > devfreq = df ;
if ( dfc_power ) {
dfc - > power_ops = dfc_power ;
devfreq_cooling_ops . get_requested_power =
devfreq_cooling_get_requested_power ;
devfreq_cooling_ops . state2power = devfreq_cooling_state2power ;
devfreq_cooling_ops . power2state = devfreq_cooling_power2state ;
}
err = devfreq_cooling_gen_tables ( dfc ) ;
if ( err )
goto free_dfc ;
err = get_idr ( & devfreq_idr , & dfc - > id ) ;
if ( err )
goto free_tables ;
snprintf ( dev_name , sizeof ( dev_name ) , " thermal-devfreq-%d " , dfc - > id ) ;
cdev = thermal_of_cooling_device_register ( np , dev_name , dfc ,
& devfreq_cooling_ops ) ;
if ( IS_ERR ( cdev ) ) {
err = PTR_ERR ( cdev ) ;
dev_err ( df - > dev . parent ,
" Failed to register devfreq cooling device (%d) \n " ,
err ) ;
goto release_idr ;
}
dfc - > cdev = cdev ;
2015-11-02 22:03:03 +03:00
return cdev ;
2015-09-10 20:09:30 +03:00
release_idr :
release_idr ( & devfreq_idr , dfc - > id ) ;
free_tables :
kfree ( dfc - > power_table ) ;
kfree ( dfc - > freq_table ) ;
free_dfc :
kfree ( dfc ) ;
return ERR_PTR ( err ) ;
}
EXPORT_SYMBOL_GPL ( of_devfreq_cooling_register_power ) ;
/**
* of_devfreq_cooling_register ( ) - Register devfreq cooling device ,
* with OF information .
* @ np : Pointer to OF device_node .
* @ df : Pointer to devfreq device .
*/
2015-11-02 22:03:03 +03:00
struct thermal_cooling_device *
2015-09-10 20:09:30 +03:00
of_devfreq_cooling_register ( struct device_node * np , struct devfreq * df )
{
return of_devfreq_cooling_register_power ( np , df , NULL ) ;
}
EXPORT_SYMBOL_GPL ( of_devfreq_cooling_register ) ;
/**
* devfreq_cooling_register ( ) - Register devfreq cooling device .
* @ df : Pointer to devfreq device .
*/
2015-11-02 22:03:03 +03:00
struct thermal_cooling_device * devfreq_cooling_register ( struct devfreq * df )
2015-09-10 20:09:30 +03:00
{
return of_devfreq_cooling_register ( NULL , df ) ;
}
EXPORT_SYMBOL_GPL ( devfreq_cooling_register ) ;
/**
* devfreq_cooling_unregister ( ) - Unregister devfreq cooling device .
* @ dfc : Pointer to devfreq cooling device to unregister .
*/
2015-11-02 22:03:03 +03:00
void devfreq_cooling_unregister ( struct thermal_cooling_device * cdev )
2015-09-10 20:09:30 +03:00
{
2015-11-02 22:03:03 +03:00
struct devfreq_cooling_device * dfc ;
if ( ! cdev )
2015-09-10 20:09:30 +03:00
return ;
2015-11-02 22:03:03 +03:00
dfc = cdev - > devdata ;
2015-09-10 20:09:30 +03:00
thermal_cooling_device_unregister ( dfc - > cdev ) ;
release_idr ( & devfreq_idr , dfc - > id ) ;
kfree ( dfc - > power_table ) ;
kfree ( dfc - > freq_table ) ;
kfree ( dfc ) ;
}
EXPORT_SYMBOL_GPL ( devfreq_cooling_unregister ) ;