2012-08-16 15:41:40 +04:00
/*
* linux / drivers / thermal / cpu_cooling . c
*
* Copyright ( C ) 2012 Samsung Electronics Co . , Ltd ( http : //www.samsung.com)
* Copyright ( C ) 2012 Amit Daniel < amit . kachhap @ linaro . org >
*
2014-12-04 07:12:08 +03:00
* Copyright ( C ) 2014 Viresh Kumar < viresh . kumar @ linaro . org >
*
2012-08-16 15:41:40 +04:00
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
* 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 ; version 2 of the License .
*
* 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 .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <linux/module.h>
# include <linux/thermal.h>
# include <linux/cpufreq.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/cpu.h>
# include <linux/cpu_cooling.h>
2014-12-04 07:11:49 +03:00
/*
* Cooling state < - > CPUFreq frequency
*
* Cooling states are translated to frequencies throughout this driver and this
* is the relation between them .
*
* Highest cooling state corresponds to lowest possible frequency .
*
* i . e .
* level 0 - - > 1 st Max Freq
* level 1 - - > 2 nd Max Freq
* . . .
*/
2012-08-16 15:41:40 +04:00
/**
2013-04-17 21:11:56 +04:00
* struct cpufreq_cooling_device - data for cooling device with cpufreq
2012-08-16 15:41:40 +04:00
* @ id : unique integer value corresponding to each cpufreq_cooling_device
* registered .
2013-04-17 21:11:56 +04:00
* @ cool_dev : thermal_cooling_device pointer to keep track of the
* registered cooling device .
2012-08-16 15:41:40 +04:00
* @ cpufreq_state : integer value representing the current state of cpufreq
* cooling devices .
* @ cpufreq_val : integer value representing the absolute value of the clipped
* frequency .
2014-12-04 07:12:02 +03:00
* @ max_level : maximum cooling level . One less than total number of valid
* cpufreq frequencies .
2012-08-16 15:41:40 +04:00
* @ allowed_cpus : all the cpus involved for this cpufreq_cooling_device .
2014-12-15 19:55:52 +03:00
* @ node : list_head to link all cpufreq_cooling_device together .
2012-08-16 15:41:40 +04:00
*
2014-12-04 07:11:48 +03:00
* This structure is required for keeping information of each registered
* cpufreq_cooling_device .
2012-08-16 15:41:40 +04:00
*/
struct cpufreq_cooling_device {
int id ;
struct thermal_cooling_device * cool_dev ;
unsigned int cpufreq_state ;
unsigned int cpufreq_val ;
2014-12-04 07:12:02 +03:00
unsigned int max_level ;
2014-12-04 07:12:06 +03:00
unsigned int * freq_table ; /* In descending order */
2012-08-16 15:41:40 +04:00
struct cpumask allowed_cpus ;
2014-11-07 16:42:29 +03:00
struct list_head node ;
2012-08-16 15:41:40 +04:00
} ;
static DEFINE_IDR ( cpufreq_idr ) ;
2012-10-30 20:48:59 +04:00
static DEFINE_MUTEX ( cooling_cpufreq_lock ) ;
2012-08-16 15:41:40 +04:00
2014-11-07 16:42:29 +03:00
static LIST_HEAD ( cpufreq_dev_list ) ;
2012-08-16 15:41:40 +04:00
/**
* get_idr - function to get a unique id .
* @ idr : struct idr * handle used to create a id .
* @ id : int * value generated by this function .
2013-04-17 21:11:59 +04:00
*
* This function will populate @ id with an unique
* id , using the idr API .
*
* Return : 0 on success , an error code on failure .
2012-08-16 15:41:40 +04:00
*/
static int get_idr ( struct idr * idr , int * id )
{
2013-02-28 05:04:46 +04:00
int ret ;
2012-08-16 15:41:40 +04:00
mutex_lock ( & cooling_cpufreq_lock ) ;
2013-02-28 05:04:46 +04:00
ret = idr_alloc ( idr , NULL , 0 , 0 , GFP_KERNEL ) ;
2012-08-16 15:41:40 +04:00
mutex_unlock ( & cooling_cpufreq_lock ) ;
2013-02-28 05:04:46 +04:00
if ( unlikely ( ret < 0 ) )
return ret ;
* id = ret ;
2013-04-17 21:11:59 +04:00
2012-08-16 15:41:40 +04:00
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 ( & cooling_cpufreq_lock ) ;
idr_remove ( idr , id ) ;
mutex_unlock ( & cooling_cpufreq_lock ) ;
}
/* Below code defines functions to be used for cpufreq as cooling device */
/**
2014-12-04 07:12:07 +03:00
* get_level : Find the level for a particular frequency
2014-12-04 07:12:05 +03:00
* @ cpufreq_dev : cpufreq_dev for which the property is required
2014-12-04 07:12:07 +03:00
* @ freq : Frequency
2013-04-17 21:12:00 +04:00
*
2014-12-04 07:12:07 +03:00
* Return : level on success , THERMAL_CSTATE_INVALID on error .
2012-08-16 15:41:40 +04:00
*/
2014-12-04 07:12:07 +03:00
static unsigned long get_level ( struct cpufreq_cooling_device * cpufreq_dev ,
unsigned int freq )
2012-08-16 15:41:40 +04:00
{
2014-12-04 07:12:07 +03:00
unsigned long level ;
2014-01-02 07:57:48 +04:00
2014-12-04 07:12:07 +03:00
for ( level = 0 ; level < = cpufreq_dev - > max_level ; level + + ) {
if ( freq = = cpufreq_dev - > freq_table [ level ] )
return level ;
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:07 +03:00
if ( freq > cpufreq_dev - > freq_table [ level ] )
break ;
2013-02-08 09:09:32 +04:00
}
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:07 +03:00
return THERMAL_CSTATE_INVALID ;
2013-02-08 09:09:32 +04:00
}
2013-04-17 21:12:05 +04:00
/**
2014-12-04 07:11:47 +03:00
* cpufreq_cooling_get_level - for a given cpu , return the cooling level .
2013-04-17 21:12:05 +04:00
* @ cpu : cpu for which the level is required
* @ freq : the frequency of interest
*
* This function will match the cooling level corresponding to the
* requested @ freq and return it .
*
* Return : The matched cooling level on success or THERMAL_CSTATE_INVALID
* otherwise .
*/
2013-02-08 10:52:06 +04:00
unsigned long cpufreq_cooling_get_level ( unsigned int cpu , unsigned int freq )
{
2014-12-04 07:12:05 +03:00
struct cpufreq_cooling_device * cpufreq_dev ;
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:05 +03:00
mutex_lock ( & cooling_cpufreq_lock ) ;
list_for_each_entry ( cpufreq_dev , & cpufreq_dev_list , node ) {
if ( cpumask_test_cpu ( cpu , & cpufreq_dev - > allowed_cpus ) ) {
mutex_unlock ( & cooling_cpufreq_lock ) ;
2014-12-04 07:12:07 +03:00
return get_level ( cpufreq_dev , freq ) ;
2014-12-04 07:12:05 +03:00
}
2012-08-16 15:41:40 +04:00
}
2014-12-04 07:12:05 +03:00
mutex_unlock ( & cooling_cpufreq_lock ) ;
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:05 +03:00
pr_err ( " %s: cpu:%d not part of any cooling device \n " , __func__ , cpu ) ;
return THERMAL_CSTATE_INVALID ;
2012-08-16 15:41:40 +04:00
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( cpufreq_cooling_get_level ) ;
2012-08-16 15:41:40 +04:00
/**
* cpufreq_thermal_notifier - notifier callback for cpufreq policy change .
* @ nb : struct notifier_block * with callback info .
* @ event : value showing cpufreq event for which this function invoked .
* @ data : callback - specific data
2013-04-17 21:12:09 +04:00
*
2014-06-25 21:11:17 +04:00
* Callback to hijack the notification on cpufreq policy transition .
2013-04-17 21:12:09 +04:00
* Every time there is a change in policy , we will intercept and
* update the cpufreq policy with thermal constraints .
*
* Return : 0 ( success )
2012-08-16 15:41:40 +04:00
*/
static int cpufreq_thermal_notifier ( struct notifier_block * nb ,
2013-04-17 21:12:11 +04:00
unsigned long event , void * data )
2012-08-16 15:41:40 +04:00
{
struct cpufreq_policy * policy = data ;
unsigned long max_freq = 0 ;
2014-11-07 16:42:29 +03:00
struct cpufreq_cooling_device * cpufreq_dev ;
2012-08-16 15:41:40 +04:00
2014-11-07 16:42:29 +03:00
if ( event ! = CPUFREQ_ADJUST )
2012-08-16 15:41:40 +04:00
return 0 ;
2014-11-07 16:42:29 +03:00
mutex_lock ( & cooling_cpufreq_lock ) ;
list_for_each_entry ( cpufreq_dev , & cpufreq_dev_list , node ) {
if ( ! cpumask_test_cpu ( policy - > cpu ,
& cpufreq_dev - > allowed_cpus ) )
continue ;
max_freq = cpufreq_dev - > cpufreq_val ;
2012-08-16 15:41:40 +04:00
2014-11-07 16:42:29 +03:00
if ( policy - > max ! = max_freq )
cpufreq_verify_within_limits ( policy , 0 , max_freq ) ;
}
mutex_unlock ( & cooling_cpufreq_lock ) ;
2012-08-16 15:41:40 +04:00
return 0 ;
}
2013-04-17 21:12:02 +04:00
/* cpufreq cooling device callback functions are defined below */
2012-08-16 15:41:40 +04:00
/**
* cpufreq_get_max_state - callback function to get the max cooling state .
* @ cdev : thermal cooling device pointer .
* @ state : fill this variable with the max cooling state .
2013-04-17 21:12:12 +04:00
*
* Callback for the thermal cooling device to return the cpufreq
* max cooling state .
*
* Return : 0 on success , an error code otherwise .
2012-08-16 15:41:40 +04:00
*/
static int cpufreq_get_max_state ( struct thermal_cooling_device * cdev ,
unsigned long * state )
{
2012-10-30 20:48:59 +04:00
struct cpufreq_cooling_device * cpufreq_device = cdev - > devdata ;
2012-10-30 20:48:58 +04:00
2014-12-04 07:12:02 +03:00
* state = cpufreq_device - > max_level ;
return 0 ;
2012-08-16 15:41:40 +04:00
}
/**
* cpufreq_get_cur_state - callback function to get the current cooling state .
* @ cdev : thermal cooling device pointer .
* @ state : fill this variable with the current cooling state .
2013-04-17 21:12:13 +04:00
*
* Callback for the thermal cooling device to return the cpufreq
* current cooling state .
*
* Return : 0 on success , an error code otherwise .
2012-08-16 15:41:40 +04:00
*/
static int cpufreq_get_cur_state ( struct thermal_cooling_device * cdev ,
unsigned long * state )
{
2012-10-30 20:48:59 +04:00
struct cpufreq_cooling_device * cpufreq_device = cdev - > devdata ;
2012-08-16 15:41:40 +04:00
2012-10-30 20:48:59 +04:00
* state = cpufreq_device - > cpufreq_state ;
2013-04-17 21:11:59 +04:00
2012-10-30 20:48:59 +04:00
return 0 ;
2012-08-16 15:41:40 +04:00
}
/**
* cpufreq_set_cur_state - callback function to set the current cooling state .
* @ cdev : thermal cooling device pointer .
* @ state : set this variable to the current cooling state .
2013-04-17 21:12:14 +04:00
*
* Callback for the thermal cooling device to change the cpufreq
* current cooling state .
*
* Return : 0 on success , an error code otherwise .
2012-08-16 15:41:40 +04:00
*/
static int cpufreq_set_cur_state ( struct thermal_cooling_device * cdev ,
unsigned long state )
{
2012-10-30 20:48:59 +04:00
struct cpufreq_cooling_device * cpufreq_device = cdev - > devdata ;
2014-12-04 07:12:00 +03:00
unsigned int cpu = cpumask_any ( & cpufreq_device - > allowed_cpus ) ;
unsigned int clip_freq ;
2014-12-04 07:12:07 +03:00
/* Request state should be less than max_level */
if ( WARN_ON ( state > cpufreq_device - > max_level ) )
return - EINVAL ;
2014-12-04 07:12:00 +03:00
/* Check if the old cooling action is same as new cooling action */
if ( cpufreq_device - > cpufreq_state = = state )
return 0 ;
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:07 +03:00
clip_freq = cpufreq_device - > freq_table [ state ] ;
2014-12-04 07:12:00 +03:00
cpufreq_device - > cpufreq_state = state ;
cpufreq_device - > cpufreq_val = clip_freq ;
cpufreq_update_policy ( cpu ) ;
return 0 ;
2012-08-16 15:41:40 +04:00
}
/* Bind cpufreq callbacks to thermal cooling device ops */
static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
. get_max_state = cpufreq_get_max_state ,
. get_cur_state = cpufreq_get_cur_state ,
. set_cur_state = cpufreq_set_cur_state ,
} ;
/* Notifier for cpufreq policy change */
static struct notifier_block thermal_cpufreq_notifier_block = {
. notifier_call = cpufreq_thermal_notifier ,
} ;
2014-12-04 07:12:06 +03:00
static unsigned int find_next_max ( struct cpufreq_frequency_table * table ,
unsigned int prev_max )
{
struct cpufreq_frequency_table * pos ;
unsigned int max = 0 ;
cpufreq_for_each_valid_entry ( pos , table ) {
if ( pos - > frequency > max & & pos - > frequency < prev_max )
max = pos - > frequency ;
}
return max ;
}
2012-08-16 15:41:40 +04:00
/**
2013-09-13 03:26:45 +04:00
* __cpufreq_cooling_register - helper function to create cpufreq cooling device
* @ np : a valid struct device_node to the cooling device device tree node
2012-08-16 15:41:40 +04:00
* @ clip_cpus : cpumask of cpus where the frequency constraints will happen .
2014-12-04 07:11:55 +03:00
* Normally this should be same as cpufreq policy - > related_cpus .
2013-04-17 21:12:15 +04:00
*
* This interface function registers the cpufreq cooling device with the name
* " thermal-cpufreq-%x " . This api can support multiple instances of cpufreq
2013-09-13 03:26:45 +04:00
* cooling devices . It also gives the opportunity to link the cooling device
* with a device tree node , in order to bind it via the thermal DT code .
2013-04-17 21:12:15 +04:00
*
* Return : a valid struct thermal_cooling_device pointer on success ,
* on failure , it returns a corresponding ERR_PTR ( ) .
2012-08-16 15:41:40 +04:00
*/
2013-09-13 03:26:45 +04:00
static struct thermal_cooling_device *
__cpufreq_cooling_register ( struct device_node * np ,
const struct cpumask * clip_cpus )
2012-08-16 15:41:40 +04:00
{
struct thermal_cooling_device * cool_dev ;
2014-12-04 07:11:52 +03:00
struct cpufreq_cooling_device * cpufreq_dev ;
2012-08-16 15:41:40 +04:00
char dev_name [ THERMAL_NAME_LENGTH ] ;
2014-12-04 07:12:02 +03:00
struct cpufreq_frequency_table * pos , * table ;
2014-12-04 07:12:06 +03:00
unsigned int freq , i ;
2014-12-04 07:11:55 +03:00
int ret ;
2012-08-16 15:41:40 +04:00
2014-12-04 07:12:02 +03:00
table = cpufreq_frequency_get_table ( cpumask_first ( clip_cpus ) ) ;
if ( ! table ) {
2014-12-04 07:11:43 +03:00
pr_debug ( " %s: CPUFreq table not found \n " , __func__ ) ;
return ERR_PTR ( - EPROBE_DEFER ) ;
2012-08-16 15:41:40 +04:00
}
2014-12-04 07:11:43 +03:00
2014-12-04 07:11:50 +03:00
cpufreq_dev = kzalloc ( sizeof ( * cpufreq_dev ) , GFP_KERNEL ) ;
2012-08-16 15:41:40 +04:00
if ( ! cpufreq_dev )
return ERR_PTR ( - ENOMEM ) ;
2014-12-04 07:12:02 +03:00
/* Find max levels */
cpufreq_for_each_valid_entry ( pos , table )
cpufreq_dev - > max_level + + ;
2014-12-04 07:12:06 +03:00
cpufreq_dev - > freq_table = kmalloc ( sizeof ( * cpufreq_dev - > freq_table ) *
cpufreq_dev - > max_level , GFP_KERNEL ) ;
if ( ! cpufreq_dev - > freq_table ) {
cool_dev = ERR_PTR ( - ENOMEM ) ;
goto free_cdev ;
}
2014-12-04 07:12:02 +03:00
/* max_level is an index, not a counter */
cpufreq_dev - > max_level - - ;
2012-08-16 15:41:40 +04:00
cpumask_copy ( & cpufreq_dev - > allowed_cpus , clip_cpus ) ;
ret = get_idr ( & cpufreq_idr , & cpufreq_dev - > id ) ;
if ( ret ) {
2014-12-04 07:11:58 +03:00
cool_dev = ERR_PTR ( ret ) ;
2014-12-04 07:12:06 +03:00
goto free_table ;
2012-08-16 15:41:40 +04:00
}
2013-04-17 21:12:17 +04:00
snprintf ( dev_name , sizeof ( dev_name ) , " thermal-cpufreq-%d " ,
cpufreq_dev - > id ) ;
2012-08-16 15:41:40 +04:00
2013-09-13 03:26:45 +04:00
cool_dev = thermal_of_cooling_device_register ( np , dev_name , cpufreq_dev ,
& cpufreq_cooling_ops ) ;
2014-12-04 07:11:58 +03:00
if ( IS_ERR ( cool_dev ) )
goto remove_idr ;
2014-12-04 07:12:06 +03:00
/* Fill freq-table in descending order of frequencies */
for ( i = 0 , freq = - 1 ; i < = cpufreq_dev - > max_level ; i + + ) {
freq = find_next_max ( table , freq ) ;
cpufreq_dev - > freq_table [ i ] = freq ;
/* Warn for duplicate entries */
if ( ! freq )
pr_warn ( " %s: table has duplicate entries \n " , __func__ ) ;
else
pr_debug ( " %s: freq:%u KHz \n " , __func__ , freq ) ;
2012-08-16 15:41:40 +04:00
}
2014-12-04 07:12:06 +03:00
2014-12-04 07:12:07 +03:00
cpufreq_dev - > cpufreq_val = cpufreq_dev - > freq_table [ 0 ] ;
2012-08-16 15:41:40 +04:00
cpufreq_dev - > cool_dev = cool_dev ;
2014-12-04 07:11:51 +03:00
2012-08-16 15:41:40 +04:00
mutex_lock ( & cooling_cpufreq_lock ) ;
/* Register the notifier for first cpufreq cooling device */
2014-12-04 07:12:04 +03:00
if ( list_empty ( & cpufreq_dev_list ) )
2012-08-16 15:41:40 +04:00
cpufreq_register_notifier ( & thermal_cpufreq_notifier_block ,
2013-04-17 21:12:11 +04:00
CPUFREQ_POLICY_NOTIFIER ) ;
2014-11-07 16:42:29 +03:00
list_add ( & cpufreq_dev - > node , & cpufreq_dev_list ) ;
2012-08-16 15:41:40 +04:00
mutex_unlock ( & cooling_cpufreq_lock ) ;
2013-04-17 21:11:59 +04:00
2014-12-04 07:11:58 +03:00
return cool_dev ;
remove_idr :
release_idr ( & cpufreq_idr , cpufreq_dev - > id ) ;
2014-12-04 07:12:06 +03:00
free_table :
kfree ( cpufreq_dev - > freq_table ) ;
2014-12-04 07:11:58 +03:00
free_cdev :
kfree ( cpufreq_dev ) ;
2012-08-16 15:41:40 +04:00
return cool_dev ;
}
2013-09-13 03:26:45 +04:00
/**
* cpufreq_cooling_register - function to create cpufreq cooling device .
* @ clip_cpus : cpumask of cpus where the frequency constraints will happen .
*
* This interface function registers the cpufreq cooling device with the name
* " thermal-cpufreq-%x " . This api can support multiple instances of cpufreq
* cooling devices .
*
* Return : a valid struct thermal_cooling_device pointer on success ,
* on failure , it returns a corresponding ERR_PTR ( ) .
*/
struct thermal_cooling_device *
cpufreq_cooling_register ( const struct cpumask * clip_cpus )
{
return __cpufreq_cooling_register ( NULL , clip_cpus ) ;
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( cpufreq_cooling_register ) ;
2012-08-16 15:41:40 +04:00
2013-09-13 03:26:45 +04:00
/**
* of_cpufreq_cooling_register - function to create cpufreq cooling device .
* @ np : a valid struct device_node to the cooling device device tree node
* @ clip_cpus : cpumask of cpus where the frequency constraints will happen .
*
* This interface function registers the cpufreq cooling device with the name
* " thermal-cpufreq-%x " . This api can support multiple instances of cpufreq
* cooling devices . Using this API , the cpufreq cooling device will be
* linked to the device tree node provided .
*
* Return : a valid struct thermal_cooling_device pointer on success ,
* on failure , it returns a corresponding ERR_PTR ( ) .
*/
struct thermal_cooling_device *
of_cpufreq_cooling_register ( struct device_node * np ,
const struct cpumask * clip_cpus )
{
if ( ! np )
return ERR_PTR ( - EINVAL ) ;
return __cpufreq_cooling_register ( np , clip_cpus ) ;
}
EXPORT_SYMBOL_GPL ( of_cpufreq_cooling_register ) ;
2012-08-16 15:41:40 +04:00
/**
* cpufreq_cooling_unregister - function to remove cpufreq cooling device .
* @ cdev : thermal cooling device pointer .
2013-04-17 21:12:16 +04:00
*
* This interface function unregisters the " thermal-cpufreq-%x " cooling device .
2012-08-16 15:41:40 +04:00
*/
void cpufreq_cooling_unregister ( struct thermal_cooling_device * cdev )
{
2013-08-15 18:54:46 +04:00
struct cpufreq_cooling_device * cpufreq_dev ;
2012-08-16 15:41:40 +04:00
2013-08-15 18:54:46 +04:00
if ( ! cdev )
return ;
cpufreq_dev = cdev - > devdata ;
2012-08-16 15:41:40 +04:00
mutex_lock ( & cooling_cpufreq_lock ) ;
2014-11-07 16:42:29 +03:00
list_del ( & cpufreq_dev - > node ) ;
2012-08-16 15:41:40 +04:00
/* Unregister the notifier for the last cpufreq cooling device */
2014-12-04 07:12:04 +03:00
if ( list_empty ( & cpufreq_dev_list ) )
2012-08-16 15:41:40 +04:00
cpufreq_unregister_notifier ( & thermal_cpufreq_notifier_block ,
2013-04-17 21:12:11 +04:00
CPUFREQ_POLICY_NOTIFIER ) ;
2012-08-16 15:41:40 +04:00
mutex_unlock ( & cooling_cpufreq_lock ) ;
2012-10-30 20:48:59 +04:00
2012-08-16 15:41:40 +04:00
thermal_cooling_device_unregister ( cpufreq_dev - > cool_dev ) ;
release_idr ( & cpufreq_idr , cpufreq_dev - > id ) ;
2014-12-04 07:12:06 +03:00
kfree ( cpufreq_dev - > freq_table ) ;
2012-08-16 15:41:40 +04:00
kfree ( cpufreq_dev ) ;
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( cpufreq_cooling_unregister ) ;