2020-07-30 19:51:17 +03:00
// SPDX-License-Identifier: GPL-2.0
2015-09-10 20:09:30 +03:00
/*
* devfreq_cooling : Thermal cooling device implementation for devices using
* devfreq
*
* Copyright ( C ) 2014 - 2015 ARM Limited
*
* 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>
2020-12-10 17:30:12 +03:00
# include <linux/energy_model.h>
2015-09-10 20:09:30 +03:00
# include <linux/export.h>
# include <linux/slab.h>
# include <linux/pm_opp.h>
2020-03-18 14:45:46 +03:00
# include <linux/pm_qos.h>
2015-09-10 20:09:30 +03:00
# include <linux/thermal.h>
2021-09-08 05:57:51 +03:00
# include <linux/units.h>
2015-09-10 20:09:30 +03:00
2023-03-07 16:37:25 +03:00
# include "thermal_trace.h"
2015-09-10 20:09:31 +03:00
2020-03-18 14:45:46 +03:00
# define SCALE_ERROR_MITIGATION 100
2017-05-04 14:34:32 +03:00
2015-09-10 20:09:30 +03:00
/**
* struct devfreq_cooling_device - Devfreq cooling device
* devfreq_cooling_device registered .
* @ cdev : Pointer to associated thermal cooling device .
2022-06-13 15:43:27 +03:00
* @ cooling_ops : devfreq callbacks to thermal cooling device ops
2015-09-10 20:09:30 +03:00
* @ devfreq : Pointer to associated devfreq device .
* @ cooling_state : Current cooling state .
* @ freq_table : Pointer to a table with the frequencies sorted in descending
* order . You can index the table by cooling device state
2020-12-10 17:30:13 +03:00
* @ max_state : It is the last index , that is , one less than the number of the
* OPPs
* @ power_ops : Pointer to devfreq_cooling_power , a more precised model .
2017-05-04 14:34:32 +03:00
* @ res_util : Resource utilization scaling factor for the power .
* It is multiplied by 100 to minimize the error . It is used
* for estimation of the power budget instead of using
2020-12-10 17:30:13 +03:00
* ' utilization ' ( which is ' busy_time ' / ' total_time ' ) .
* The ' res_util ' range is from 100 to power * 100 for the
* corresponding ' state ' .
2019-11-20 18:45:13 +03:00
* @ capped_state : index to cooling state with in dynamic power budget
2020-03-18 14:45:46 +03:00
* @ req_max_freq : PM QoS request for limiting the maximum frequency
* of the devfreq device .
2020-12-15 18:42:21 +03:00
* @ em_pd : Energy Model for the associated Devfreq device
2015-09-10 20:09:30 +03:00
*/
struct devfreq_cooling_device {
struct thermal_cooling_device * cdev ;
2022-06-13 15:43:27 +03:00
struct thermal_cooling_device_ops cooling_ops ;
2015-09-10 20:09:30 +03:00
struct devfreq * devfreq ;
unsigned long cooling_state ;
u32 * freq_table ;
2020-12-10 17:30:13 +03:00
size_t max_state ;
2015-09-10 20:09:30 +03:00
struct devfreq_cooling_power * power_ops ;
2017-05-04 14:34:32 +03:00
u32 res_util ;
int capped_state ;
2020-03-18 14:45:46 +03:00
struct dev_pm_qos_request req_max_freq ;
2020-12-15 18:42:21 +03:00
struct em_perf_domain * em_pd ;
2015-09-10 20:09:30 +03:00
} ;
static int devfreq_cooling_get_max_state ( struct thermal_cooling_device * cdev ,
unsigned long * state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
2020-12-10 17:30:13 +03:00
* state = dfc - > max_state ;
2015-09-10 20:09:30 +03:00
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 ;
2020-03-18 14:45:46 +03:00
unsigned long freq ;
2020-12-10 17:30:13 +03:00
int perf_idx ;
2015-09-10 20:09:30 +03:00
if ( state = = dfc - > cooling_state )
return 0 ;
dev_dbg ( dev , " Setting cooling state %lu \n " , state ) ;
2020-12-10 17:30:13 +03:00
if ( state > dfc - > max_state )
2015-09-10 20:09:30 +03:00
return - EINVAL ;
2020-12-15 18:42:21 +03:00
if ( dfc - > em_pd ) {
2020-12-10 17:30:13 +03:00
perf_idx = dfc - > max_state - state ;
2020-12-15 18:42:21 +03:00
freq = dfc - > em_pd - > table [ perf_idx ] . frequency * 1000 ;
2020-12-10 17:30:13 +03:00
} else {
freq = dfc - > freq_table [ state ] ;
}
2020-03-18 14:45:46 +03:00
dev_pm_qos_update_request ( & dfc - > req_max_freq ,
DIV_ROUND_UP ( freq , HZ_PER_KHZ ) ) ;
2015-09-10 20:09:30 +03:00
dfc - > cooling_state = state ;
return 0 ;
}
/**
2020-12-10 17:30:13 +03:00
* get_perf_idx ( ) - get the performance index corresponding to a frequency
* @ em_pd : Pointer to device ' s Energy Model
* @ freq : frequency in kHz
2015-09-10 20:09:30 +03:00
*
2020-12-10 17:30:13 +03:00
* Return : the performance index associated with the @ freq , or
* - EINVAL if it wasn ' t found .
2015-09-10 20:09:30 +03:00
*/
2020-12-10 17:30:13 +03:00
static int get_perf_idx ( struct em_perf_domain * em_pd , unsigned long freq )
2015-09-10 20:09:30 +03:00
{
int i ;
2020-12-10 17:30:13 +03:00
for ( i = 0 ; i < em_pd - > nr_perf_states ; i + + ) {
if ( em_pd - > table [ i ] . frequency = = freq )
2015-09-10 20:09:30 +03:00
return i ;
}
2020-12-10 17:30:13 +03:00
return - EINVAL ;
2015-09-10 20:09:30 +03:00
}
2017-05-04 14:34:31 +03:00
static unsigned long get_voltage ( struct devfreq * df , unsigned long freq )
2015-09-10 20:09:30 +03:00
{
struct device * dev = df - > dev . parent ;
unsigned long voltage ;
struct dev_pm_opp * opp ;
opp = dev_pm_opp_find_freq_exact ( dev , freq , true ) ;
2017-02-07 07:10:01 +03:00
if ( PTR_ERR ( opp ) = = - ERANGE )
2015-09-10 20:09:30 +03:00
opp = dev_pm_opp_find_freq_exact ( dev , freq , false ) ;
2017-02-07 07:10:03 +03:00
if ( IS_ERR ( opp ) ) {
dev_err_ratelimited ( dev , " Failed to find OPP for frequency %lu: %ld \n " ,
freq , PTR_ERR ( opp ) ) ;
return 0 ;
}
2015-09-10 20:09:30 +03:00
voltage = dev_pm_opp_get_voltage ( opp ) / 1000 ; /* mV */
2017-01-23 07:41:47 +03:00
dev_pm_opp_put ( opp ) ;
2015-09-10 20:09:30 +03:00
if ( voltage = = 0 ) {
2017-02-07 07:10:02 +03:00
dev_err_ratelimited ( dev ,
2017-02-07 07:10:03 +03:00
" Failed to get voltage for frequency %lu \n " ,
freq ) ;
2015-09-10 20:09:30 +03:00
}
2017-05-04 14:34:31 +03:00
return voltage ;
}
2020-12-10 17:30:11 +03:00
static void _normalize_load ( struct devfreq_dev_status * status )
{
if ( status - > total_time > 0xfffff ) {
status - > total_time > > = 10 ;
status - > busy_time > > = 10 ;
}
status - > busy_time < < = 10 ;
status - > busy_time / = status - > total_time ? : 1 ;
status - > busy_time = status - > busy_time ? : 1 ;
status - > total_time = 1024 ;
}
2017-05-04 14:34:32 +03:00
2015-09-10 20:09:30 +03:00
static int devfreq_cooling_get_requested_power ( struct thermal_cooling_device * cdev ,
u32 * power )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
struct devfreq * df = dfc - > devfreq ;
2020-12-10 17:30:11 +03:00
struct devfreq_dev_status status ;
2015-09-10 20:09:30 +03:00
unsigned long state ;
2020-12-10 17:30:11 +03:00
unsigned long freq ;
2017-05-04 14:34:32 +03:00
unsigned long voltage ;
2020-12-10 17:30:13 +03:00
int res , perf_idx ;
2015-09-10 20:09:30 +03:00
2020-12-10 17:30:11 +03:00
mutex_lock ( & df - > lock ) ;
status = df - > last_status ;
mutex_unlock ( & df - > lock ) ;
freq = status . current_frequency ;
2020-12-10 17:30:13 +03:00
if ( dfc - > power_ops & & dfc - > power_ops - > get_real_power ) {
2017-05-04 14:34:32 +03:00
voltage = get_voltage ( df , freq ) ;
if ( voltage = = 0 ) {
res = - EINVAL ;
goto fail ;
}
2015-09-10 20:09:30 +03:00
2017-05-04 14:34:32 +03:00
res = dfc - > power_ops - > get_real_power ( df , power , freq , voltage ) ;
if ( ! res ) {
state = dfc - > capped_state ;
2022-07-07 10:15:52 +03:00
/* Convert EM power into milli-Watts first */
2020-12-15 18:42:21 +03:00
dfc - > res_util = dfc - > em_pd - > table [ state ] . power ;
2022-07-07 10:15:52 +03:00
dfc - > res_util / = MICROWATT_PER_MILLIWATT ;
2017-05-04 14:34:32 +03:00
dfc - > res_util * = SCALE_ERROR_MITIGATION ;
2015-09-10 20:09:30 +03:00
2017-05-04 14:34:32 +03:00
if ( * power > 1 )
dfc - > res_util / = * power ;
} else {
goto fail ;
}
} else {
2020-12-10 17:30:13 +03:00
/* Energy Model frequencies are in kHz */
2020-12-15 18:42:21 +03:00
perf_idx = get_perf_idx ( dfc - > em_pd , freq / 1000 ) ;
2020-12-10 17:30:13 +03:00
if ( perf_idx < 0 ) {
res = - EAGAIN ;
goto fail ;
}
2017-05-04 14:34:32 +03:00
2020-12-10 17:30:11 +03:00
_normalize_load ( & status ) ;
2022-07-07 10:15:52 +03:00
/* Convert EM power into milli-Watts first */
2020-12-15 18:42:21 +03:00
* power = dfc - > em_pd - > table [ perf_idx ] . power ;
2022-07-07 10:15:52 +03:00
* power / = MICROWATT_PER_MILLIWATT ;
/* Scale power for utilization */
2020-12-10 17:30:13 +03:00
* power * = status . busy_time ;
* power > > = 10 ;
2017-05-04 14:34:32 +03:00
}
2015-09-10 20:09:30 +03:00
2020-12-10 17:30:11 +03:00
trace_thermal_power_devfreq_get_power ( cdev , & status , freq , * power ) ;
2015-09-10 20:09:31 +03:00
2015-09-10 20:09:30 +03:00
return 0 ;
2017-05-04 14:34:32 +03:00
fail :
/* It is safe to set max in this case */
dfc - > res_util = SCALE_ERROR_MITIGATION ;
return res ;
2015-09-10 20:09:30 +03:00
}
static int devfreq_cooling_state2power ( struct thermal_cooling_device * cdev ,
2020-12-10 17:30:13 +03:00
unsigned long state , u32 * power )
2015-09-10 20:09:30 +03:00
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
2020-12-10 17:30:13 +03:00
int perf_idx ;
2015-09-10 20:09:30 +03:00
2020-12-10 17:30:13 +03:00
if ( state > dfc - > max_state )
2015-09-10 20:09:30 +03:00
return - EINVAL ;
2020-12-10 17:30:13 +03:00
perf_idx = dfc - > max_state - state ;
2020-12-15 18:42:21 +03:00
* power = dfc - > em_pd - > table [ perf_idx ] . power ;
2022-07-07 10:15:52 +03:00
* power / = MICROWATT_PER_MILLIWATT ;
2015-09-10 20:09:30 +03:00
return 0 ;
}
static int devfreq_cooling_power2state ( struct thermal_cooling_device * cdev ,
u32 power , unsigned long * state )
{
struct devfreq_cooling_device * dfc = cdev - > devdata ;
struct devfreq * df = dfc - > devfreq ;
2020-12-10 17:30:11 +03:00
struct devfreq_dev_status status ;
2022-07-07 10:15:52 +03:00
unsigned long freq , em_power_mw ;
2017-05-04 14:34:32 +03:00
s32 est_power ;
2015-09-10 20:09:30 +03:00
int i ;
2020-12-10 17:30:11 +03:00
mutex_lock ( & df - > lock ) ;
status = df - > last_status ;
mutex_unlock ( & df - > lock ) ;
freq = status . current_frequency ;
2020-12-10 17:30:13 +03:00
if ( dfc - > power_ops & & dfc - > power_ops - > get_real_power ) {
2017-05-04 14:34:32 +03:00
/* Scale for resource utilization */
est_power = power * dfc - > res_util ;
est_power / = SCALE_ERROR_MITIGATION ;
} else {
/* Scale dynamic power for utilization */
2020-12-10 17:30:11 +03:00
_normalize_load ( & status ) ;
2020-12-10 17:30:13 +03:00
est_power = power < < 10 ;
est_power / = status . busy_time ;
2017-05-04 14:34:32 +03:00
}
2015-09-10 20:09:30 +03:00
/*
* Find the first cooling state that is within the power
2020-12-10 17:30:13 +03:00
* budget . The EM power table is sorted ascending .
2015-09-10 20:09:30 +03:00
*/
2022-07-07 10:15:52 +03:00
for ( i = dfc - > max_state ; i > 0 ; i - - ) {
/* Convert EM power to milli-Watts to make safe comparison */
em_power_mw = dfc - > em_pd - > table [ i ] . power ;
em_power_mw / = MICROWATT_PER_MILLIWATT ;
if ( est_power > = em_power_mw )
2015-09-10 20:09:30 +03:00
break ;
2022-07-07 10:15:52 +03:00
}
2015-09-10 20:09:30 +03:00
2020-12-10 17:30:13 +03:00
* state = dfc - > max_state - i ;
dfc - > capped_state = * state ;
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 ;
}
/**
2020-12-10 17:30:13 +03:00
* devfreq_cooling_gen_tables ( ) - Generate frequency table .
* @ dfc : Pointer to devfreq cooling device .
* @ num_opps : Number of OPPs
2015-09-10 20:09:30 +03:00
*
2020-12-10 17:30:13 +03:00
* Generate frequency table which holds the frequencies in descending
* order . That way its indexed by cooling device state . This is for
* compatibility with drivers which do not register Energy Model .
2015-09-10 20:09:30 +03:00
*
* Return : 0 on success , negative error code on failure .
*/
2020-12-10 17:30:13 +03:00
static int devfreq_cooling_gen_tables ( struct devfreq_cooling_device * dfc ,
int num_opps )
2015-09-10 20:09:30 +03:00
{
struct devfreq * df = dfc - > devfreq ;
struct device * dev = df - > dev . parent ;
unsigned long freq ;
int i ;
2020-12-10 17:30:13 +03:00
dfc - > freq_table = kcalloc ( num_opps , sizeof ( * dfc - > freq_table ) ,
2015-09-10 20:09:30 +03:00
GFP_KERNEL ) ;
2020-12-10 17:30:13 +03:00
if ( ! dfc - > freq_table )
return - ENOMEM ;
2015-09-10 20:09:30 +03:00
for ( i = 0 , freq = ULONG_MAX ; i < num_opps ; i + + , freq - - ) {
struct dev_pm_opp * opp ;
opp = dev_pm_opp_find_freq_floor ( dev , & freq ) ;
if ( IS_ERR ( opp ) ) {
2020-12-10 17:30:13 +03:00
kfree ( dfc - > freq_table ) ;
return PTR_ERR ( opp ) ;
2015-09-10 20:09:30 +03:00
}
2017-01-23 07:41:47 +03:00
dev_pm_opp_put ( opp ) ;
2020-12-10 17:30:13 +03:00
dfc - > freq_table [ i ] = freq ;
2015-09-10 20:09:30 +03:00
}
return 0 ;
}
/**
* 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 ;
2020-12-10 17:30:13 +03:00
struct device * dev = df - > dev . parent ;
2015-09-10 20:09:30 +03:00
struct devfreq_cooling_device * dfc ;
2022-03-21 12:57:28 +03:00
struct em_perf_domain * em ;
2022-03-25 10:30:30 +03:00
struct thermal_cooling_device_ops * ops ;
2021-03-14 14:13:31 +03:00
char * name ;
2020-12-10 17:30:13 +03:00
int err , num_opps ;
2015-09-10 20:09:30 +03:00
2022-03-25 10:30:30 +03:00
dfc = kzalloc ( sizeof ( * dfc ) , GFP_KERNEL ) ;
2022-06-13 15:43:27 +03:00
if ( ! dfc )
return ERR_PTR ( - ENOMEM ) ;
2022-03-25 10:30:30 +03:00
2015-09-10 20:09:30 +03:00
dfc - > devfreq = df ;
2022-06-13 15:43:27 +03:00
ops = & dfc - > cooling_ops ;
ops - > get_max_state = devfreq_cooling_get_max_state ;
ops - > get_cur_state = devfreq_cooling_get_cur_state ;
ops - > set_cur_state = devfreq_cooling_set_cur_state ;
2022-03-21 12:57:28 +03:00
em = em_pd_get ( dev ) ;
if ( em & & ! em_is_artificial ( em ) ) {
dfc - > em_pd = em ;
2022-03-25 10:30:30 +03:00
ops - > get_requested_power =
2015-09-10 20:09:30 +03:00
devfreq_cooling_get_requested_power ;
2022-03-25 10:30:30 +03:00
ops - > state2power = devfreq_cooling_state2power ;
ops - > power2state = devfreq_cooling_power2state ;
2020-12-10 17:30:13 +03:00
dfc - > power_ops = dfc_power ;
2020-12-15 18:42:21 +03:00
num_opps = em_pd_nr_perf_states ( dfc - > em_pd ) ;
2020-12-10 17:30:13 +03:00
} else {
/* Backward compatibility for drivers which do not use IPA */
2022-03-21 12:57:28 +03:00
dev_dbg ( dev , " missing proper EM for cooling device \n " ) ;
2020-12-10 17:30:13 +03:00
num_opps = dev_pm_opp_get_opp_count ( dev ) ;
err = devfreq_cooling_gen_tables ( dfc , num_opps ) ;
if ( err )
goto free_dfc ;
2015-09-10 20:09:30 +03:00
}
2020-12-10 17:30:13 +03:00
if ( num_opps < = 0 ) {
err = - EINVAL ;
2015-09-10 20:09:30 +03:00
goto free_dfc ;
2020-12-10 17:30:13 +03:00
}
/* max_state is an index, not a counter */
dfc - > max_state = num_opps - 1 ;
2015-09-10 20:09:30 +03:00
2020-12-10 17:30:13 +03:00
err = dev_pm_qos_add_request ( dev , & dfc - > req_max_freq ,
2020-03-18 14:45:46 +03:00
DEV_PM_QOS_MAX_FREQUENCY ,
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE ) ;
2016-12-21 20:47:06 +03:00
if ( err < 0 )
2020-12-10 17:30:13 +03:00
goto free_table ;
2020-03-18 14:45:46 +03:00
2021-03-19 23:24:23 +03:00
err = - ENOMEM ;
2021-03-14 14:13:31 +03:00
name = kasprintf ( GFP_KERNEL , " devfreq-%s " , dev_name ( dev ) ) ;
if ( ! name )
2020-03-18 14:45:46 +03:00
goto remove_qos_req ;
2020-12-10 17:30:13 +03:00
2022-03-25 10:30:30 +03:00
cdev = thermal_of_cooling_device_register ( np , name , dfc , ops ) ;
2021-03-14 14:13:31 +03:00
kfree ( name ) ;
2015-09-10 20:09:30 +03:00
if ( IS_ERR ( cdev ) ) {
err = PTR_ERR ( cdev ) ;
2020-12-10 17:30:13 +03:00
dev_err ( dev ,
2015-09-10 20:09:30 +03:00
" Failed to register devfreq cooling device (%d) \n " ,
err ) ;
2021-03-14 14:13:31 +03:00
goto remove_qos_req ;
2015-09-10 20:09:30 +03:00
}
dfc - > cdev = cdev ;
2015-11-02 22:03:03 +03:00
return cdev ;
2015-09-10 20:09:30 +03:00
2020-03-18 14:45:46 +03:00
remove_qos_req :
dev_pm_qos_remove_request ( & dfc - > req_max_freq ) ;
2020-12-10 17:30:13 +03:00
free_table :
2015-09-10 20:09:30 +03:00
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 ) ;
2020-12-10 17:30:12 +03:00
/**
2021-06-09 10:22:30 +03:00
* devfreq_cooling_em_register ( ) - Register devfreq cooling device with
2020-12-10 17:30:12 +03:00
* power information and automatically register Energy Model ( EM )
* @ df : Pointer to devfreq device .
* @ dfc_power : Pointer to devfreq_cooling_power .
*
* Register a devfreq cooling device and automatically register EM . The
* available OPPs must be registered for the device .
*
* If @ dfc_power is provided , the cooling device is registered with the
* power extensions . It is using the simple Energy Model which requires
* " dynamic-power-coefficient " a devicetree property . To not break drivers
* which miss that DT property , the function won ' t bail out when the EM
* registration failed . The cooling device will be registered if everything
* else is OK .
*/
struct thermal_cooling_device *
devfreq_cooling_em_register ( struct devfreq * df ,
struct devfreq_cooling_power * dfc_power )
{
struct thermal_cooling_device * cdev ;
struct device * dev ;
int ret ;
if ( IS_ERR_OR_NULL ( df ) )
return ERR_PTR ( - EINVAL ) ;
dev = df - > dev . parent ;
ret = dev_pm_opp_of_register_em ( dev , NULL ) ;
if ( ret )
dev_dbg ( dev , " Unable to register EM for devfreq cooling device (%d) \n " ,
ret ) ;
cdev = of_devfreq_cooling_register_power ( dev - > of_node , df , dfc_power ) ;
if ( IS_ERR_OR_NULL ( cdev ) )
em_dev_unregister_perf_domain ( dev ) ;
return cdev ;
}
EXPORT_SYMBOL_GPL ( devfreq_cooling_em_register ) ;
2015-09-10 20:09:30 +03:00
/**
* devfreq_cooling_unregister ( ) - Unregister devfreq cooling device .
2019-11-20 18:45:13 +03:00
* @ cdev : Pointer to devfreq cooling device to unregister .
2020-12-10 17:30:12 +03:00
*
* Unregisters devfreq cooling device and related Energy Model if it was
* present .
2015-09-10 20:09:30 +03:00
*/
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 ;
2020-12-10 17:30:12 +03:00
struct device * dev ;
2015-11-02 22:03:03 +03:00
2020-12-10 17:30:12 +03:00
if ( IS_ERR_OR_NULL ( cdev ) )
2015-09-10 20:09:30 +03:00
return ;
2015-11-02 22:03:03 +03:00
dfc = cdev - > devdata ;
2020-12-10 17:30:12 +03:00
dev = dfc - > devfreq - > dev . parent ;
2015-11-02 22:03:03 +03:00
2015-09-10 20:09:30 +03:00
thermal_cooling_device_unregister ( dfc - > cdev ) ;
2020-03-18 14:45:46 +03:00
dev_pm_qos_remove_request ( & dfc - > req_max_freq ) ;
2020-12-10 17:30:12 +03:00
em_dev_unregister_perf_domain ( dev ) ;
2015-09-10 20:09:30 +03:00
kfree ( dfc - > freq_table ) ;
kfree ( dfc ) ;
}
EXPORT_SYMBOL_GPL ( devfreq_cooling_unregister ) ;