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 >
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
* 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>
/**
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 .
* @ allowed_cpus : all the cpus involved for this cpufreq_cooling_device .
*
* This structure is required for keeping information of each
2013-04-17 21:12:19 +04:00
* cpufreq_cooling_device registered . In order to prevent corruption of this a
2012-08-16 15:41:40 +04:00
* mutex lock cooling_cpufreq_lock is used .
*/
struct cpufreq_cooling_device {
int id ;
struct thermal_cooling_device * cool_dev ;
unsigned int cpufreq_state ;
unsigned int cpufreq_val ;
struct cpumask allowed_cpus ;
} ;
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
2012-10-30 20:48:59 +04:00
static unsigned int cpufreq_dev_count ;
2012-08-16 15:41:40 +04:00
/* notify_table passes value to the CPUFREQ_ADJUST callback function. */
# define NOTIFY_INVALID NULL
2012-11-15 10:49:44 +04:00
static struct cpufreq_cooling_device * notify_device ;
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 */
/**
2013-04-17 21:12:00 +04:00
* is_cpufreq_valid - function to check frequency transitioning capability .
2012-08-16 15:41:40 +04:00
* @ cpu : cpu for which check is needed .
2013-04-17 21:12:00 +04:00
*
* This function will check the current state of the system if
* it is capable of changing the frequency for a given @ cpu .
*
* Return : 0 if the system is not currently capable of changing
* the frequency of given cpu . ! 0 in case the frequency is changeable .
2012-08-16 15:41:40 +04:00
*/
static int is_cpufreq_valid ( int cpu )
{
struct cpufreq_policy policy ;
2013-04-17 21:11:59 +04:00
2012-08-16 15:41:40 +04:00
return ! cpufreq_get_policy ( & policy , cpu ) ;
}
2013-02-08 09:09:32 +04:00
enum cpufreq_cooling_property {
GET_LEVEL ,
GET_FREQ ,
GET_MAXL ,
} ;
2013-04-17 21:12:01 +04:00
/**
* get_property - fetch a property of interest for a give cpu .
* @ cpu : cpu for which the property is required
* @ input : query parameter
* @ output : query return
* @ property : type of query ( frequency , level , max level )
*
* This is the common function to
2013-02-08 09:09:32 +04:00
* 1. get maximum cpu cooling states
* 2. translate frequency to cooling state
* 3. translate cooling state to frequency
* Note that the code may be not in good shape
* but it is written in this way in order to :
* a ) reduce duplicate code as most of the code can be shared .
* b ) make sure the logic is consistent when translating between
* cooling states and frequencies .
2013-04-17 21:12:01 +04:00
*
* Return : 0 on success , - EINVAL when invalid parameters are passed .
*/
2013-02-08 09:09:32 +04:00
static int get_property ( unsigned int cpu , unsigned long input ,
2013-04-17 21:12:03 +04:00
unsigned int * output ,
enum cpufreq_cooling_property property )
2012-08-16 15:41:40 +04:00
{
2013-02-08 09:09:32 +04:00
int i , j ;
2013-04-17 21:11:58 +04:00
unsigned long max_level = 0 , level = 0 ;
2013-02-08 09:09:32 +04:00
unsigned int freq = CPUFREQ_ENTRY_INVALID ;
int descend = - 1 ;
2012-08-16 15:41:40 +04:00
struct cpufreq_frequency_table * table =
cpufreq_frequency_get_table ( cpu ) ;
2013-04-17 21:11:55 +04:00
2013-02-08 09:09:32 +04:00
if ( ! output )
return - EINVAL ;
2012-08-16 15:41:40 +04:00
if ( ! table )
2013-02-08 09:09:32 +04:00
return - EINVAL ;
2012-08-16 15:41:40 +04:00
2013-02-08 09:09:32 +04:00
for ( i = 0 ; table [ i ] . frequency ! = CPUFREQ_TABLE_END ; i + + ) {
/* ignore invalid entries */
2012-08-16 15:41:40 +04:00
if ( table [ i ] . frequency = = CPUFREQ_ENTRY_INVALID )
continue ;
2013-02-08 09:09:32 +04:00
/* ignore duplicate entry */
if ( freq = = table [ i ] . frequency )
continue ;
/* get the frequency order */
2013-05-28 10:22:32 +04:00
if ( freq ! = CPUFREQ_ENTRY_INVALID & & descend = = - 1 )
2013-02-08 09:09:32 +04:00
descend = ! ! ( freq > table [ i ] . frequency ) ;
2012-08-16 15:41:40 +04:00
2013-02-08 09:09:32 +04:00
freq = table [ i ] . frequency ;
max_level + + ;
2012-08-16 15:41:40 +04:00
}
2013-02-08 09:09:32 +04:00
/* get max level */
if ( property = = GET_MAXL ) {
* output = ( unsigned int ) max_level ;
return 0 ;
}
2012-08-16 15:41:40 +04:00
2013-02-08 09:09:32 +04:00
if ( property = = GET_FREQ )
2013-04-17 21:12:06 +04:00
level = descend ? input : ( max_level - input - 1 ) ;
2013-02-08 09:09:32 +04:00
for ( i = 0 , j = 0 ; table [ i ] . frequency ! = CPUFREQ_TABLE_END ; i + + ) {
/* ignore invalid entry */
2012-08-16 15:41:40 +04:00
if ( table [ i ] . frequency = = CPUFREQ_ENTRY_INVALID )
continue ;
2013-02-08 09:09:32 +04:00
/* ignore duplicate entry */
if ( freq = = table [ i ] . frequency )
continue ;
/* now we have a valid frequency entry */
freq = table [ i ] . frequency ;
if ( property = = GET_LEVEL & & ( unsigned int ) input = = freq ) {
/* get level by frequency */
* output = descend ? j : ( max_level - j - 1 ) ;
return 0 ;
}
if ( property = = GET_FREQ & & level = = j ) {
/* get frequency by level */
* output = freq ;
return 0 ;
}
j + + ;
2012-08-16 15:41:40 +04:00
}
2013-04-17 21:11:59 +04:00
2013-02-08 09:09:32 +04:00
return - EINVAL ;
}
2013-04-17 21:12:05 +04:00
/**
* cpufreq_cooling_get_level - for a give cpu , return the cooling level .
* @ 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 )
{
unsigned int val ;
if ( get_property ( cpu , ( unsigned long ) freq , & val , GET_LEVEL ) )
return THERMAL_CSTATE_INVALID ;
2013-04-17 21:11:59 +04:00
2013-02-08 10:52:06 +04:00
return ( unsigned long ) val ;
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( cpufreq_cooling_get_level ) ;
2013-02-08 10:52:06 +04:00
2013-02-08 09:09:32 +04:00
/**
* get_cpu_frequency - get the absolute value of frequency from level .
* @ cpu : cpu for which frequency is fetched .
2013-04-17 21:12:07 +04:00
* @ level : cooling level
*
* This function matches cooling level with frequency . Based on a cooling level
* of frequency , equals cooling state of cpu cooling device , it will return
* the corresponding frequency .
2013-02-08 09:09:32 +04:00
* e . g level = 0 - - > 1 st MAX FREQ , level = 1 - - - > 2 nd MAX FREQ , . . . . etc
2013-04-17 21:12:07 +04:00
*
* Return : 0 on error , the corresponding frequency otherwise .
2013-02-08 09:09:32 +04:00
*/
static unsigned int get_cpu_frequency ( unsigned int cpu , unsigned long level )
{
int ret = 0 ;
unsigned int freq ;
ret = get_property ( cpu , level , & freq , GET_FREQ ) ;
if ( ret )
return 0 ;
2013-04-17 21:11:59 +04:00
2013-02-08 09:09:32 +04:00
return freq ;
2012-08-16 15:41:40 +04:00
}
/**
* cpufreq_apply_cooling - function to apply frequency clipping .
* @ cpufreq_device : cpufreq_cooling_device pointer containing frequency
* clipping data .
* @ cooling_state : value of the cooling state .
2013-04-17 21:12:08 +04:00
*
* Function used to make sure the cpufreq layer is aware of current thermal
* limits . The limits are applied by updating the cpufreq policy .
*
* Return : 0 on success , an error code otherwise ( - EINVAL in case wrong
* cooling state ) .
2012-08-16 15:41:40 +04:00
*/
static int cpufreq_apply_cooling ( struct cpufreq_cooling_device * cpufreq_device ,
2013-04-17 21:12:11 +04:00
unsigned long cooling_state )
2012-08-16 15:41:40 +04:00
{
unsigned int cpuid , clip_freq ;
2013-03-12 14:47:50 +04:00
struct cpumask * mask = & cpufreq_device - > allowed_cpus ;
unsigned int cpu = cpumask_any ( mask ) ;
2012-08-16 15:41:40 +04:00
/* Check if the old cooling action is same as new cooling action */
if ( cpufreq_device - > cpufreq_state = = cooling_state )
return 0 ;
clip_freq = get_cpu_frequency ( cpu , cooling_state ) ;
if ( ! clip_freq )
return - EINVAL ;
cpufreq_device - > cpufreq_state = cooling_state ;
cpufreq_device - > cpufreq_val = clip_freq ;
notify_device = cpufreq_device ;
2013-03-12 14:47:50 +04:00
for_each_cpu ( cpuid , mask ) {
2012-08-16 15:41:40 +04:00
if ( is_cpufreq_valid ( cpuid ) )
cpufreq_update_policy ( cpuid ) ;
}
notify_device = NOTIFY_INVALID ;
return 0 ;
}
/**
* 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
*
* Callback to highjack the notification on cpufreq policy transition .
* 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 ;
if ( event ! = CPUFREQ_ADJUST | | notify_device = = NOTIFY_INVALID )
return 0 ;
if ( cpumask_test_cpu ( policy - > cpu , & notify_device - > allowed_cpus ) )
max_freq = notify_device - > cpufreq_val ;
2013-04-17 21:12:02 +04:00
/* Never exceed user_policy.max */
2012-08-16 15:41:40 +04:00
if ( max_freq > policy - > user_policy . max )
max_freq = policy - > user_policy . max ;
if ( policy - > max ! = max_freq )
cpufreq_verify_within_limits ( policy , 0 , max_freq ) ;
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 ;
2013-03-12 14:47:50 +04:00
struct cpumask * mask = & cpufreq_device - > allowed_cpus ;
2012-08-16 15:41:40 +04:00
unsigned int cpu ;
2013-04-17 11:18:28 +04:00
unsigned int count = 0 ;
2013-02-08 09:09:32 +04:00
int ret ;
2012-08-16 15:41:40 +04:00
2013-03-12 14:47:50 +04:00
cpu = cpumask_any ( mask ) ;
2012-08-16 15:41:40 +04:00
2013-04-17 11:18:28 +04:00
ret = get_property ( cpu , 0 , & count , GET_MAXL ) ;
2012-10-30 20:48:58 +04:00
2013-02-08 09:09:32 +04:00
if ( count > 0 )
* state = count ;
2012-08-16 15:41:40 +04:00
2013-02-08 09:09:32 +04:00
return ret ;
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 ;
2012-08-16 15:41:40 +04:00
2012-10-30 20:48:59 +04:00
return cpufreq_apply_cooling ( cpufreq_device , state ) ;
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 ,
} ;
/**
* cpufreq_cooling_register - function to create cpufreq cooling device .
* @ clip_cpus : cpumask of cpus where the frequency constraints will happen .
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
* cooling devices .
*
* 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-04-17 21:12:11 +04:00
struct thermal_cooling_device *
cpufreq_cooling_register ( const struct cpumask * clip_cpus )
2012-08-16 15:41:40 +04:00
{
struct thermal_cooling_device * cool_dev ;
struct cpufreq_cooling_device * cpufreq_dev = NULL ;
2012-10-30 20:48:59 +04:00
unsigned int min = 0 , max = 0 ;
2012-08-16 15:41:40 +04:00
char dev_name [ THERMAL_NAME_LENGTH ] ;
2012-09-26 04:43:31 +04:00
int ret = 0 , i ;
2012-08-16 15:41:40 +04:00
struct cpufreq_policy policy ;
2013-04-17 21:12:02 +04:00
/* Verify that all the clip cpus have same freq_min, freq_max limit */
2012-08-16 15:41:40 +04:00
for_each_cpu ( i , clip_cpus ) {
2013-04-17 21:12:02 +04:00
/* continue if cpufreq policy not found and not return error */
2012-08-16 15:41:40 +04:00
if ( ! cpufreq_get_policy ( & policy , i ) )
continue ;
if ( min = = 0 & & max = = 0 ) {
min = policy . cpuinfo . min_freq ;
max = policy . cpuinfo . max_freq ;
} else {
if ( min ! = policy . cpuinfo . min_freq | |
2013-04-17 21:12:11 +04:00
max ! = policy . cpuinfo . max_freq )
2012-08-16 15:41:40 +04:00
return ERR_PTR ( - EINVAL ) ;
2012-10-30 20:48:57 +04:00
}
2012-08-16 15:41:40 +04:00
}
cpufreq_dev = kzalloc ( sizeof ( struct cpufreq_cooling_device ) ,
2013-04-17 21:12:11 +04:00
GFP_KERNEL ) ;
2012-08-16 15:41:40 +04:00
if ( ! cpufreq_dev )
return ERR_PTR ( - ENOMEM ) ;
cpumask_copy ( & cpufreq_dev - > allowed_cpus , clip_cpus ) ;
ret = get_idr ( & cpufreq_idr , & cpufreq_dev - > id ) ;
if ( ret ) {
kfree ( cpufreq_dev ) ;
return ERR_PTR ( - EINVAL ) ;
}
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
cool_dev = thermal_cooling_device_register ( dev_name , cpufreq_dev ,
2013-04-17 21:12:11 +04:00
& cpufreq_cooling_ops ) ;
2012-08-16 15:41:40 +04:00
if ( ! cool_dev ) {
release_idr ( & cpufreq_idr , cpufreq_dev - > id ) ;
kfree ( cpufreq_dev ) ;
return ERR_PTR ( - EINVAL ) ;
}
cpufreq_dev - > cool_dev = cool_dev ;
cpufreq_dev - > cpufreq_state = 0 ;
mutex_lock ( & cooling_cpufreq_lock ) ;
/* Register the notifier for first cpufreq cooling device */
if ( cpufreq_dev_count = = 0 )
cpufreq_register_notifier ( & thermal_cpufreq_notifier_block ,
2013-04-17 21:12:11 +04:00
CPUFREQ_POLICY_NOTIFIER ) ;
2012-10-30 20:48:59 +04:00
cpufreq_dev_count + + ;
2012-08-16 15:41:40 +04:00
mutex_unlock ( & cooling_cpufreq_lock ) ;
2013-04-17 21:11:59 +04:00
2012-08-16 15:41:40 +04:00
return cool_dev ;
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( 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 )
{
2012-10-30 20:48:59 +04:00
struct cpufreq_cooling_device * cpufreq_dev = cdev - > devdata ;
2012-08-16 15:41:40 +04:00
mutex_lock ( & cooling_cpufreq_lock ) ;
2012-10-30 20:48:59 +04:00
cpufreq_dev_count - - ;
2012-08-16 15:41:40 +04:00
/* Unregister the notifier for the last cpufreq cooling device */
2013-04-17 21:12:18 +04:00
if ( cpufreq_dev_count = = 0 )
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 ) ;
kfree ( cpufreq_dev ) ;
}
2013-04-17 21:11:57 +04:00
EXPORT_SYMBOL_GPL ( cpufreq_cooling_unregister ) ;