2008-01-17 10:51:08 +03:00
/*
* thermal . c - Generic Thermal Management Sysfs support .
*
* Copyright ( C ) 2008 Intel Corp
* Copyright ( C ) 2008 Zhang Rui < rui . zhang @ intel . com >
* Copyright ( C ) 2008 Sujith Thomas < sujith . thomas @ intel . 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 ; 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/device.h>
# include <linux/err.h>
# include <linux/kdev_t.h>
# include <linux/idr.h>
# include <linux/thermal.h>
# include <linux/spinlock.h>
2008-12-03 20:55:32 +03:00
# include <linux/reboot.h>
2008-01-17 10:51:08 +03:00
2008-04-21 12:07:13 +04:00
MODULE_AUTHOR ( " Zhang Rui " ) ;
2008-01-17 10:51:08 +03:00
MODULE_DESCRIPTION ( " Generic thermal management sysfs support " ) ;
MODULE_LICENSE ( " GPL " ) ;
# define PREFIX "Thermal: "
struct thermal_cooling_device_instance {
int id ;
char name [ THERMAL_NAME_LENGTH ] ;
struct thermal_zone_device * tz ;
struct thermal_cooling_device * cdev ;
int trip ;
char attr_name [ THERMAL_NAME_LENGTH ] ;
struct device_attribute attr ;
struct list_head node ;
} ;
static DEFINE_IDR ( thermal_tz_idr ) ;
static DEFINE_IDR ( thermal_cdev_idr ) ;
static DEFINE_MUTEX ( thermal_idr_lock ) ;
static LIST_HEAD ( thermal_tz_list ) ;
static LIST_HEAD ( thermal_cdev_list ) ;
static DEFINE_MUTEX ( thermal_list_lock ) ;
static int get_idr ( struct idr * idr , struct mutex * lock , int * id )
{
int err ;
again :
if ( unlikely ( idr_pre_get ( idr , GFP_KERNEL ) = = 0 ) )
return - ENOMEM ;
if ( lock )
mutex_lock ( lock ) ;
err = idr_get_new ( idr , NULL , id ) ;
if ( lock )
mutex_unlock ( lock ) ;
if ( unlikely ( err = = - EAGAIN ) )
goto again ;
else if ( unlikely ( err ) )
return err ;
* id = * id & MAX_ID_MASK ;
return 0 ;
}
static void release_idr ( struct idr * idr , struct mutex * lock , int id )
{
if ( lock )
mutex_lock ( lock ) ;
idr_remove ( idr , id ) ;
if ( lock )
mutex_unlock ( lock ) ;
}
/* sys I/F for thermal zone */
# define to_thermal_zone(_dev) \
container_of ( _dev , struct thermal_zone_device , device )
static ssize_t
type_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
return sprintf ( buf , " %s \n " , tz - > type ) ;
}
static ssize_t
temp_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
2008-11-27 20:48:13 +03:00
long temperature ;
int ret ;
2008-01-17 10:51:08 +03:00
if ( ! tz - > ops - > get_temp )
return - EPERM ;
2008-11-27 20:48:13 +03:00
ret = tz - > ops - > get_temp ( tz , & temperature ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %ld \n " , temperature ) ;
2008-01-17 10:51:08 +03:00
}
static ssize_t
mode_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
2008-11-27 20:48:13 +03:00
enum thermal_device_mode mode ;
int result ;
2008-01-17 10:51:08 +03:00
if ( ! tz - > ops - > get_mode )
return - EPERM ;
2008-11-27 20:48:13 +03:00
result = tz - > ops - > get_mode ( tz , & mode ) ;
if ( result )
return result ;
return sprintf ( buf , " %s \n " , mode = = THERMAL_DEVICE_ENABLED ? " enabled "
: " disabled " ) ;
2008-01-17 10:51:08 +03:00
}
static ssize_t
mode_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
int result ;
if ( ! tz - > ops - > set_mode )
return - EPERM ;
2008-11-27 20:48:13 +03:00
if ( ! strncmp ( buf , " enabled " , sizeof ( " enabled " ) ) )
result = tz - > ops - > set_mode ( tz , THERMAL_DEVICE_ENABLED ) ;
else if ( ! strncmp ( buf , " disabled " , sizeof ( " disabled " ) ) )
result = tz - > ops - > set_mode ( tz , THERMAL_DEVICE_DISABLED ) ;
else
result = - EINVAL ;
2008-01-17 10:51:08 +03:00
if ( result )
return result ;
return count ;
}
static ssize_t
trip_point_type_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
2008-11-27 20:48:13 +03:00
enum thermal_trip_type type ;
int trip , result ;
2008-01-17 10:51:08 +03:00
if ( ! tz - > ops - > get_trip_type )
return - EPERM ;
if ( ! sscanf ( attr - > attr . name , " trip_point_%d_type " , & trip ) )
return - EINVAL ;
2008-11-27 20:48:13 +03:00
result = tz - > ops - > get_trip_type ( tz , trip , & type ) ;
if ( result )
return result ;
switch ( type ) {
case THERMAL_TRIP_CRITICAL :
return sprintf ( buf , " critical " ) ;
case THERMAL_TRIP_HOT :
return sprintf ( buf , " hot " ) ;
case THERMAL_TRIP_PASSIVE :
return sprintf ( buf , " passive " ) ;
case THERMAL_TRIP_ACTIVE :
return sprintf ( buf , " active " ) ;
default :
return sprintf ( buf , " unknown " ) ;
}
2008-01-17 10:51:08 +03:00
}
static ssize_t
trip_point_temp_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
2008-11-27 20:48:13 +03:00
int trip , ret ;
long temperature ;
2008-01-17 10:51:08 +03:00
if ( ! tz - > ops - > get_trip_temp )
return - EPERM ;
if ( ! sscanf ( attr - > attr . name , " trip_point_%d_temp " , & trip ) )
return - EINVAL ;
2008-11-27 20:48:13 +03:00
ret = tz - > ops - > get_trip_temp ( tz , trip , & temperature ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %ld \n " , temperature ) ;
2008-01-17 10:51:08 +03:00
}
2008-12-03 21:00:38 +03:00
static ssize_t
passive_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
struct thermal_cooling_device * cdev = NULL ;
int state ;
if ( ! sscanf ( buf , " %d \n " , & state ) )
return - EINVAL ;
if ( state & & ! tz - > forced_passive ) {
mutex_lock ( & thermal_list_lock ) ;
list_for_each_entry ( cdev , & thermal_cdev_list , node ) {
if ( ! strncmp ( " Processor " , cdev - > type ,
sizeof ( " Processor " ) ) )
thermal_zone_bind_cooling_device ( tz ,
THERMAL_TRIPS_NONE ,
cdev ) ;
}
mutex_unlock ( & thermal_list_lock ) ;
} else if ( ! state & & tz - > forced_passive ) {
mutex_lock ( & thermal_list_lock ) ;
list_for_each_entry ( cdev , & thermal_cdev_list , node ) {
if ( ! strncmp ( " Processor " , cdev - > type ,
sizeof ( " Processor " ) ) )
thermal_zone_unbind_cooling_device ( tz ,
THERMAL_TRIPS_NONE ,
cdev ) ;
}
mutex_unlock ( & thermal_list_lock ) ;
}
tz - > tc1 = 1 ;
tz - > tc2 = 1 ;
if ( ! tz - > passive_delay )
tz - > passive_delay = 1000 ;
if ( ! tz - > polling_delay )
tz - > polling_delay = 10000 ;
tz - > forced_passive = state ;
thermal_zone_device_update ( tz ) ;
return count ;
}
static ssize_t
passive_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct thermal_zone_device * tz = to_thermal_zone ( dev ) ;
return sprintf ( buf , " %d \n " , tz - > forced_passive ) ;
}
2008-01-17 10:51:08 +03:00
static DEVICE_ATTR ( type , 0444 , type_show , NULL ) ;
static DEVICE_ATTR ( temp , 0444 , temp_show , NULL ) ;
static DEVICE_ATTR ( mode , 0644 , mode_show , mode_store ) ;
2008-12-03 21:00:38 +03:00
static DEVICE_ATTR ( passive , S_IRUGO | S_IWUSR , passive_show , \
passive_store ) ;
2008-01-17 10:51:08 +03:00
static struct device_attribute trip_point_attrs [ ] = {
__ATTR ( trip_point_0_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_0_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_1_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_1_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_2_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_2_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_3_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_3_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_4_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_4_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_5_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_5_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_6_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_6_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_7_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_7_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_8_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_8_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_9_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_9_temp , 0444 , trip_point_temp_show , NULL ) ,
2008-04-16 01:34:47 +04:00
__ATTR ( trip_point_10_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_10_temp , 0444 , trip_point_temp_show , NULL ) ,
__ATTR ( trip_point_11_type , 0444 , trip_point_type_show , NULL ) ,
__ATTR ( trip_point_11_temp , 0444 , trip_point_temp_show , NULL ) ,
2008-01-17 10:51:08 +03:00
} ;
# define TRIP_POINT_ATTR_ADD(_dev, _index, result) \
do { \
result = device_create_file ( _dev , \
& trip_point_attrs [ _index * 2 ] ) ; \
if ( result ) \
break ; \
result = device_create_file ( _dev , \
& trip_point_attrs [ _index * 2 + 1 ] ) ; \
} while ( 0 )
# define TRIP_POINT_ATTR_REMOVE(_dev, _index) \
do { \
device_remove_file ( _dev , & trip_point_attrs [ _index * 2 ] ) ; \
device_remove_file ( _dev , & trip_point_attrs [ _index * 2 + 1 ] ) ; \
} while ( 0 )
/* sys I/F for cooling device */
# define to_cooling_device(_dev) \
container_of ( _dev , struct thermal_cooling_device , device )
static ssize_t
thermal_cooling_device_type_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct thermal_cooling_device * cdev = to_cooling_device ( dev ) ;
return sprintf ( buf , " %s \n " , cdev - > type ) ;
}
static ssize_t
thermal_cooling_device_max_state_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct thermal_cooling_device * cdev = to_cooling_device ( dev ) ;
2008-11-27 20:48:13 +03:00
unsigned long state ;
int ret ;
2008-01-17 10:51:08 +03:00
2008-11-27 20:48:13 +03:00
ret = cdev - > ops - > get_max_state ( cdev , & state ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %ld \n " , state ) ;
2008-01-17 10:51:08 +03:00
}
static ssize_t
thermal_cooling_device_cur_state_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct thermal_cooling_device * cdev = to_cooling_device ( dev ) ;
2008-11-27 20:48:13 +03:00
unsigned long state ;
int ret ;
2008-01-17 10:51:08 +03:00
2008-11-27 20:48:13 +03:00
ret = cdev - > ops - > get_cur_state ( cdev , & state ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %ld \n " , state ) ;
2008-01-17 10:51:08 +03:00
}
static ssize_t
thermal_cooling_device_cur_state_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct thermal_cooling_device * cdev = to_cooling_device ( dev ) ;
2008-11-27 20:48:13 +03:00
unsigned long state ;
2008-01-17 10:51:08 +03:00
int result ;
2008-11-27 20:48:13 +03:00
if ( ! sscanf ( buf , " %ld \n " , & state ) )
2008-01-17 10:51:08 +03:00
return - EINVAL ;
if ( state < 0 )
return - EINVAL ;
result = cdev - > ops - > set_cur_state ( cdev , state ) ;
if ( result )
return result ;
return count ;
}
static struct device_attribute dev_attr_cdev_type =
2008-02-08 00:55:08 +03:00
__ATTR ( type , 0444 , thermal_cooling_device_type_show , NULL ) ;
2008-01-17 10:51:08 +03:00
static DEVICE_ATTR ( max_state , 0444 ,
thermal_cooling_device_max_state_show , NULL ) ;
static DEVICE_ATTR ( cur_state , 0644 ,
thermal_cooling_device_cur_state_show ,
thermal_cooling_device_cur_state_store ) ;
static ssize_t
thermal_cooling_device_trip_point_show ( struct device * dev ,
2008-02-08 00:55:08 +03:00
struct device_attribute * attr , char * buf )
2008-01-17 10:51:08 +03:00
{
struct thermal_cooling_device_instance * instance ;
instance =
container_of ( attr , struct thermal_cooling_device_instance , attr ) ;
if ( instance - > trip = = THERMAL_TRIPS_NONE )
return sprintf ( buf , " -1 \n " ) ;
else
return sprintf ( buf , " %d \n " , instance - > trip ) ;
}
/* Device management */
2008-06-24 21:38:56 +04:00
# if defined(CONFIG_THERMAL_HWMON)
2008-04-21 12:07:52 +04:00
/* hwmon sys I/F */
# include <linux/hwmon.h>
static LIST_HEAD ( thermal_hwmon_list ) ;
static ssize_t
name_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
2009-05-01 01:43:31 +04:00
struct thermal_hwmon_device * hwmon = dev_get_drvdata ( dev ) ;
2008-04-21 12:07:52 +04:00
return sprintf ( buf , " %s \n " , hwmon - > type ) ;
}
static DEVICE_ATTR ( name , 0444 , name_show , NULL ) ;
static ssize_t
temp_input_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
2008-11-27 20:48:13 +03:00
long temperature ;
int ret ;
2008-04-21 12:07:52 +04:00
struct thermal_hwmon_attr * hwmon_attr
= container_of ( attr , struct thermal_hwmon_attr , attr ) ;
struct thermal_zone_device * tz
= container_of ( hwmon_attr , struct thermal_zone_device ,
temp_input ) ;
2008-11-27 20:48:13 +03:00
ret = tz - > ops - > get_temp ( tz , & temperature ) ;
if ( ret )
return ret ;
return sprintf ( buf , " %ld \n " , temperature ) ;
2008-04-21 12:07:52 +04:00
}
static ssize_t
temp_crit_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct thermal_hwmon_attr * hwmon_attr
= container_of ( attr , struct thermal_hwmon_attr , attr ) ;
struct thermal_zone_device * tz
= container_of ( hwmon_attr , struct thermal_zone_device ,
temp_crit ) ;
2008-11-27 20:48:13 +03:00
long temperature ;
int ret ;
ret = tz - > ops - > get_trip_temp ( tz , 0 , & temperature ) ;
if ( ret )
return ret ;
2008-04-21 12:07:52 +04:00
2008-11-27 20:48:13 +03:00
return sprintf ( buf , " %ld \n " , temperature ) ;
2008-04-21 12:07:52 +04:00
}
static int
thermal_add_hwmon_sysfs ( struct thermal_zone_device * tz )
{
struct thermal_hwmon_device * hwmon ;
int new_hwmon_device = 1 ;
int result ;
mutex_lock ( & thermal_list_lock ) ;
list_for_each_entry ( hwmon , & thermal_hwmon_list , node )
if ( ! strcmp ( hwmon - > type , tz - > type ) ) {
new_hwmon_device = 0 ;
mutex_unlock ( & thermal_list_lock ) ;
goto register_sys_interface ;
}
mutex_unlock ( & thermal_list_lock ) ;
hwmon = kzalloc ( sizeof ( struct thermal_hwmon_device ) , GFP_KERNEL ) ;
if ( ! hwmon )
return - ENOMEM ;
INIT_LIST_HEAD ( & hwmon - > tz_list ) ;
strlcpy ( hwmon - > type , tz - > type , THERMAL_NAME_LENGTH ) ;
hwmon - > device = hwmon_device_register ( NULL ) ;
if ( IS_ERR ( hwmon - > device ) ) {
result = PTR_ERR ( hwmon - > device ) ;
goto free_mem ;
}
2009-05-01 01:43:31 +04:00
dev_set_drvdata ( hwmon - > device , hwmon ) ;
2008-04-21 12:07:52 +04:00
result = device_create_file ( hwmon - > device , & dev_attr_name ) ;
if ( result )
goto unregister_hwmon_device ;
register_sys_interface :
tz - > hwmon = hwmon ;
hwmon - > count + + ;
snprintf ( tz - > temp_input . name , THERMAL_NAME_LENGTH ,
" temp%d_input " , hwmon - > count ) ;
tz - > temp_input . attr . attr . name = tz - > temp_input . name ;
tz - > temp_input . attr . attr . mode = 0444 ;
tz - > temp_input . attr . show = temp_input_show ;
result = device_create_file ( hwmon - > device , & tz - > temp_input . attr ) ;
if ( result )
goto unregister_hwmon_device ;
if ( tz - > ops - > get_crit_temp ) {
unsigned long temperature ;
if ( ! tz - > ops - > get_crit_temp ( tz , & temperature ) ) {
snprintf ( tz - > temp_crit . name , THERMAL_NAME_LENGTH ,
" temp%d_crit " , hwmon - > count ) ;
tz - > temp_crit . attr . attr . name = tz - > temp_crit . name ;
tz - > temp_crit . attr . attr . mode = 0444 ;
tz - > temp_crit . attr . show = temp_crit_show ;
result = device_create_file ( hwmon - > device ,
& tz - > temp_crit . attr ) ;
if ( result )
goto unregister_hwmon_device ;
}
}
mutex_lock ( & thermal_list_lock ) ;
if ( new_hwmon_device )
list_add_tail ( & hwmon - > node , & thermal_hwmon_list ) ;
list_add_tail ( & tz - > hwmon_node , & hwmon - > tz_list ) ;
mutex_unlock ( & thermal_list_lock ) ;
return 0 ;
unregister_hwmon_device :
device_remove_file ( hwmon - > device , & tz - > temp_crit . attr ) ;
device_remove_file ( hwmon - > device , & tz - > temp_input . attr ) ;
if ( new_hwmon_device ) {
device_remove_file ( hwmon - > device , & dev_attr_name ) ;
hwmon_device_unregister ( hwmon - > device ) ;
}
free_mem :
if ( new_hwmon_device )
kfree ( hwmon ) ;
return result ;
}
static void
thermal_remove_hwmon_sysfs ( struct thermal_zone_device * tz )
{
struct thermal_hwmon_device * hwmon = tz - > hwmon ;
tz - > hwmon = NULL ;
device_remove_file ( hwmon - > device , & tz - > temp_input . attr ) ;
device_remove_file ( hwmon - > device , & tz - > temp_crit . attr ) ;
mutex_lock ( & thermal_list_lock ) ;
list_del ( & tz - > hwmon_node ) ;
if ( ! list_empty ( & hwmon - > tz_list ) ) {
mutex_unlock ( & thermal_list_lock ) ;
return ;
}
list_del ( & hwmon - > node ) ;
mutex_unlock ( & thermal_list_lock ) ;
device_remove_file ( hwmon - > device , & dev_attr_name ) ;
hwmon_device_unregister ( hwmon - > device ) ;
kfree ( hwmon ) ;
}
# else
static int
thermal_add_hwmon_sysfs ( struct thermal_zone_device * tz )
{
return 0 ;
}
static void
thermal_remove_hwmon_sysfs ( struct thermal_zone_device * tz )
{
}
# endif
2008-12-03 20:55:32 +03:00
static void thermal_zone_device_set_polling ( struct thermal_zone_device * tz ,
int delay )
{
cancel_delayed_work ( & ( tz - > poll_queue ) ) ;
if ( ! delay )
return ;
if ( delay > 1000 )
schedule_delayed_work ( & ( tz - > poll_queue ) ,
round_jiffies ( msecs_to_jiffies ( delay ) ) ) ;
else
schedule_delayed_work ( & ( tz - > poll_queue ) ,
msecs_to_jiffies ( delay ) ) ;
}
static void thermal_zone_device_passive ( struct thermal_zone_device * tz ,
int temp , int trip_temp , int trip )
{
int trend = 0 ;
struct thermal_cooling_device_instance * instance ;
struct thermal_cooling_device * cdev ;
long state , max_state ;
/*
* Above Trip ?
* - - - - - - - - - - -
* Calculate the thermal trend ( using the passive cooling equation )
* and modify the performance limit for all passive cooling devices
* accordingly . Note that we assume symmetry .
*/
if ( temp > = trip_temp ) {
tz - > passive = true ;
trend = ( tz - > tc1 * ( temp - tz - > last_temperature ) ) +
( tz - > tc2 * ( temp - trip_temp ) ) ;
/* Heating up? */
if ( trend > 0 ) {
list_for_each_entry ( instance , & tz - > cooling_devices ,
node ) {
if ( instance - > trip ! = trip )
continue ;
cdev = instance - > cdev ;
cdev - > ops - > get_cur_state ( cdev , & state ) ;
cdev - > ops - > get_max_state ( cdev , & max_state ) ;
if ( state + + < max_state )
cdev - > ops - > set_cur_state ( cdev , state ) ;
}
} else if ( trend < 0 ) { /* Cooling off? */
list_for_each_entry ( instance , & tz - > cooling_devices ,
node ) {
if ( instance - > trip ! = trip )
continue ;
cdev = instance - > cdev ;
cdev - > ops - > get_cur_state ( cdev , & state ) ;
cdev - > ops - > get_max_state ( cdev , & max_state ) ;
if ( state > 0 )
cdev - > ops - > set_cur_state ( cdev , - - state ) ;
}
}
return ;
}
/*
* Below Trip ?
* - - - - - - - - - - -
* Implement passive cooling hysteresis to slowly increase performance
* and avoid thrashing around the passive trip point . Note that we
* assume symmetry .
*/
list_for_each_entry ( instance , & tz - > cooling_devices , node ) {
if ( instance - > trip ! = trip )
continue ;
cdev = instance - > cdev ;
cdev - > ops - > get_cur_state ( cdev , & state ) ;
cdev - > ops - > get_max_state ( cdev , & max_state ) ;
if ( state > 0 )
cdev - > ops - > set_cur_state ( cdev , - - state ) ;
if ( state = = 0 )
tz - > passive = false ;
}
}
static void thermal_zone_device_check ( struct work_struct * work )
{
struct thermal_zone_device * tz = container_of ( work , struct
thermal_zone_device ,
poll_queue . work ) ;
thermal_zone_device_update ( tz ) ;
}
2008-04-21 12:07:52 +04:00
2008-01-17 10:51:08 +03:00
/**
* thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone
* @ tz : thermal zone device
* @ trip : indicates which trip point the cooling devices is
* associated with in this thermal zone .
* @ cdev : thermal cooling device
2008-02-08 00:55:08 +03:00
*
* This function is usually called in the thermal zone device . bind callback .
2008-01-17 10:51:08 +03:00
*/
int thermal_zone_bind_cooling_device ( struct thermal_zone_device * tz ,
int trip ,
struct thermal_cooling_device * cdev )
{
struct thermal_cooling_device_instance * dev ;
struct thermal_cooling_device_instance * pos ;
2008-02-15 08:58:50 +03:00
struct thermal_zone_device * pos1 ;
struct thermal_cooling_device * pos2 ;
2008-01-17 10:51:08 +03:00
int result ;
2008-02-08 00:55:08 +03:00
if ( trip > = tz - > trips | | ( trip < 0 & & trip ! = THERMAL_TRIPS_NONE ) )
2008-01-17 10:51:08 +03:00
return - EINVAL ;
2008-02-15 08:58:50 +03:00
list_for_each_entry ( pos1 , & thermal_tz_list , node ) {
if ( pos1 = = tz )
break ;
}
list_for_each_entry ( pos2 , & thermal_cdev_list , node ) {
if ( pos2 = = cdev )
break ;
}
if ( tz ! = pos1 | | cdev ! = pos2 )
2008-01-17 10:51:08 +03:00
return - EINVAL ;
dev =
kzalloc ( sizeof ( struct thermal_cooling_device_instance ) , GFP_KERNEL ) ;
if ( ! dev )
return - ENOMEM ;
dev - > tz = tz ;
dev - > cdev = cdev ;
dev - > trip = trip ;
result = get_idr ( & tz - > idr , & tz - > lock , & dev - > id ) ;
if ( result )
goto free_mem ;
sprintf ( dev - > name , " cdev%d " , dev - > id ) ;
result =
sysfs_create_link ( & tz - > device . kobj , & cdev - > device . kobj , dev - > name ) ;
if ( result )
goto release_idr ;
sprintf ( dev - > attr_name , " cdev%d_trip_point " , dev - > id ) ;
dev - > attr . attr . name = dev - > attr_name ;
dev - > attr . attr . mode = 0444 ;
dev - > attr . show = thermal_cooling_device_trip_point_show ;
result = device_create_file ( & tz - > device , & dev - > attr ) ;
if ( result )
goto remove_symbol_link ;
mutex_lock ( & tz - > lock ) ;
list_for_each_entry ( pos , & tz - > cooling_devices , node )
if ( pos - > tz = = tz & & pos - > trip = = trip & & pos - > cdev = = cdev ) {
result = - EEXIST ;
break ;
}
if ( ! result )
list_add_tail ( & dev - > node , & tz - > cooling_devices ) ;
mutex_unlock ( & tz - > lock ) ;
if ( ! result )
return 0 ;
device_remove_file ( & tz - > device , & dev - > attr ) ;
remove_symbol_link :
sysfs_remove_link ( & tz - > device . kobj , dev - > name ) ;
release_idr :
release_idr ( & tz - > idr , & tz - > lock , dev - > id ) ;
free_mem :
kfree ( dev ) ;
return result ;
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_zone_bind_cooling_device ) ;
/**
* thermal_zone_unbind_cooling_device - unbind a cooling device from a thermal zone
* @ tz : thermal zone device
* @ trip : indicates which trip point the cooling devices is
* associated with in this thermal zone .
* @ cdev : thermal cooling device
2008-02-08 00:55:08 +03:00
*
* This function is usually called in the thermal zone device . unbind callback .
2008-01-17 10:51:08 +03:00
*/
int thermal_zone_unbind_cooling_device ( struct thermal_zone_device * tz ,
int trip ,
struct thermal_cooling_device * cdev )
{
struct thermal_cooling_device_instance * pos , * next ;
mutex_lock ( & tz - > lock ) ;
list_for_each_entry_safe ( pos , next , & tz - > cooling_devices , node ) {
2008-02-08 00:55:08 +03:00
if ( pos - > tz = = tz & & pos - > trip = = trip & & pos - > cdev = = cdev ) {
2008-01-17 10:51:08 +03:00
list_del ( & pos - > node ) ;
mutex_unlock ( & tz - > lock ) ;
goto unbind ;
}
}
mutex_unlock ( & tz - > lock ) ;
return - ENODEV ;
unbind :
device_remove_file ( & tz - > device , & pos - > attr ) ;
sysfs_remove_link ( & tz - > device . kobj , pos - > name ) ;
release_idr ( & tz - > idr , & tz - > lock , pos - > id ) ;
kfree ( pos ) ;
return 0 ;
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_zone_unbind_cooling_device ) ;
static void thermal_release ( struct device * dev )
{
struct thermal_zone_device * tz ;
struct thermal_cooling_device * cdev ;
2009-01-06 21:44:37 +03:00
if ( ! strncmp ( dev_name ( dev ) , " thermal_zone " , sizeof " thermal_zone " - 1 ) ) {
2008-01-17 10:51:08 +03:00
tz = to_thermal_zone ( dev ) ;
kfree ( tz ) ;
} else {
cdev = to_cooling_device ( dev ) ;
kfree ( cdev ) ;
}
}
static struct class thermal_class = {
. name = " thermal " ,
. dev_release = thermal_release ,
} ;
/**
* thermal_cooling_device_register - register a new thermal cooling device
* @ type : the thermal cooling device type .
* @ devdata : device private data .
* @ ops : standard thermal cooling devices callbacks .
*/
struct thermal_cooling_device * thermal_cooling_device_register ( char * type ,
2008-02-08 00:55:08 +03:00
void * devdata ,
struct
thermal_cooling_device_ops
* ops )
2008-01-17 10:51:08 +03:00
{
struct thermal_cooling_device * cdev ;
struct thermal_zone_device * pos ;
int result ;
if ( strlen ( type ) > = THERMAL_NAME_LENGTH )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - EINVAL ) ;
2008-01-17 10:51:08 +03:00
if ( ! ops | | ! ops - > get_max_state | | ! ops - > get_cur_state | |
2008-02-08 00:55:08 +03:00
! ops - > set_cur_state )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - EINVAL ) ;
2008-01-17 10:51:08 +03:00
cdev = kzalloc ( sizeof ( struct thermal_cooling_device ) , GFP_KERNEL ) ;
if ( ! cdev )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - ENOMEM ) ;
2008-01-17 10:51:08 +03:00
result = get_idr ( & thermal_cdev_idr , & thermal_idr_lock , & cdev - > id ) ;
if ( result ) {
kfree ( cdev ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
strcpy ( cdev - > type , type ) ;
cdev - > ops = ops ;
cdev - > device . class = & thermal_class ;
cdev - > devdata = devdata ;
2009-01-06 21:44:37 +03:00
dev_set_name ( & cdev - > device , " cooling_device%d " , cdev - > id ) ;
2008-01-17 10:51:08 +03:00
result = device_register ( & cdev - > device ) ;
if ( result ) {
release_idr ( & thermal_cdev_idr , & thermal_idr_lock , cdev - > id ) ;
kfree ( cdev ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
/* sys I/F */
if ( type ) {
2008-02-08 00:55:08 +03:00
result = device_create_file ( & cdev - > device , & dev_attr_cdev_type ) ;
2008-01-17 10:51:08 +03:00
if ( result )
goto unregister ;
}
result = device_create_file ( & cdev - > device , & dev_attr_max_state ) ;
if ( result )
goto unregister ;
result = device_create_file ( & cdev - > device , & dev_attr_cur_state ) ;
if ( result )
goto unregister ;
mutex_lock ( & thermal_list_lock ) ;
list_add ( & cdev - > node , & thermal_cdev_list ) ;
list_for_each_entry ( pos , & thermal_tz_list , node ) {
if ( ! pos - > ops - > bind )
continue ;
result = pos - > ops - > bind ( pos , cdev ) ;
if ( result )
break ;
}
mutex_unlock ( & thermal_list_lock ) ;
if ( ! result )
return cdev ;
unregister :
release_idr ( & thermal_cdev_idr , & thermal_idr_lock , cdev - > id ) ;
device_unregister ( & cdev - > device ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_cooling_device_register ) ;
/**
* thermal_cooling_device_unregister - removes the registered thermal cooling device
* @ cdev : the thermal cooling device to remove .
*
* thermal_cooling_device_unregister ( ) must be called when the device is no
* longer needed .
*/
void thermal_cooling_device_unregister ( struct
thermal_cooling_device
* cdev )
{
struct thermal_zone_device * tz ;
struct thermal_cooling_device * pos = NULL ;
if ( ! cdev )
return ;
mutex_lock ( & thermal_list_lock ) ;
list_for_each_entry ( pos , & thermal_cdev_list , node )
if ( pos = = cdev )
break ;
if ( pos ! = cdev ) {
/* thermal cooling device not found */
mutex_unlock ( & thermal_list_lock ) ;
return ;
}
list_del ( & cdev - > node ) ;
list_for_each_entry ( tz , & thermal_tz_list , node ) {
if ( ! tz - > ops - > unbind )
continue ;
tz - > ops - > unbind ( tz , cdev ) ;
}
mutex_unlock ( & thermal_list_lock ) ;
if ( cdev - > type [ 0 ] )
2008-02-08 00:55:08 +03:00
device_remove_file ( & cdev - > device , & dev_attr_cdev_type ) ;
2008-01-17 10:51:08 +03:00
device_remove_file ( & cdev - > device , & dev_attr_max_state ) ;
device_remove_file ( & cdev - > device , & dev_attr_cur_state ) ;
release_idr ( & thermal_cdev_idr , & thermal_idr_lock , cdev - > id ) ;
device_unregister ( & cdev - > device ) ;
return ;
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_cooling_device_unregister ) ;
2008-12-03 20:55:32 +03:00
/**
* thermal_zone_device_update - force an update of a thermal zone ' s state
* @ ttz : the thermal zone to update
*/
void thermal_zone_device_update ( struct thermal_zone_device * tz )
{
int count , ret = 0 ;
long temp , trip_temp ;
enum thermal_trip_type trip_type ;
struct thermal_cooling_device_instance * instance ;
struct thermal_cooling_device * cdev ;
mutex_lock ( & tz - > lock ) ;
tz - > ops - > get_temp ( tz , & temp ) ;
for ( count = 0 ; count < tz - > trips ; count + + ) {
tz - > ops - > get_trip_type ( tz , count , & trip_type ) ;
tz - > ops - > get_trip_temp ( tz , count , & trip_temp ) ;
switch ( trip_type ) {
case THERMAL_TRIP_CRITICAL :
2009-05-06 21:34:21 +04:00
if ( temp > = trip_temp ) {
2008-12-03 20:55:32 +03:00
if ( tz - > ops - > notify )
ret = tz - > ops - > notify ( tz , count ,
trip_type ) ;
if ( ! ret ) {
printk ( KERN_EMERG
" Critical temperature reached (%ld C), shutting down. \n " ,
temp / 1000 ) ;
orderly_poweroff ( true ) ;
}
}
break ;
case THERMAL_TRIP_HOT :
2009-05-06 21:34:21 +04:00
if ( temp > = trip_temp )
2008-12-03 20:55:32 +03:00
if ( tz - > ops - > notify )
tz - > ops - > notify ( tz , count , trip_type ) ;
break ;
case THERMAL_TRIP_ACTIVE :
list_for_each_entry ( instance , & tz - > cooling_devices ,
node ) {
if ( instance - > trip ! = count )
continue ;
cdev = instance - > cdev ;
2009-05-06 21:34:21 +04:00
if ( temp > = trip_temp )
2008-12-03 20:55:32 +03:00
cdev - > ops - > set_cur_state ( cdev , 1 ) ;
else
cdev - > ops - > set_cur_state ( cdev , 0 ) ;
}
break ;
case THERMAL_TRIP_PASSIVE :
2009-05-06 21:34:21 +04:00
if ( temp > = trip_temp | | tz - > passive )
2008-12-03 20:55:32 +03:00
thermal_zone_device_passive ( tz , temp ,
trip_temp , count ) ;
break ;
}
}
2008-12-03 21:00:38 +03:00
if ( tz - > forced_passive )
thermal_zone_device_passive ( tz , temp , tz - > forced_passive ,
THERMAL_TRIPS_NONE ) ;
2008-12-03 20:55:32 +03:00
tz - > last_temperature = temp ;
if ( tz - > passive )
thermal_zone_device_set_polling ( tz , tz - > passive_delay ) ;
else if ( tz - > polling_delay )
thermal_zone_device_set_polling ( tz , tz - > polling_delay ) ;
mutex_unlock ( & tz - > lock ) ;
}
EXPORT_SYMBOL ( thermal_zone_device_update ) ;
2008-01-17 10:51:08 +03:00
/**
* thermal_zone_device_register - register a new thermal zone device
* @ type : the thermal zone device type
* @ trips : the number of trip points the thermal zone support
* @ devdata : private device data
* @ ops : standard thermal zone device callbacks
2008-12-03 20:55:32 +03:00
* @ tc1 : thermal coefficient 1 for passive calculations
* @ tc2 : thermal coefficient 2 for passive calculations
* @ passive_delay : number of milliseconds to wait between polls when
* performing passive cooling
* @ polling_delay : number of milliseconds to wait between polls when checking
* whether trip points have been crossed ( 0 for interrupt
* driven systems )
2008-01-17 10:51:08 +03:00
*
* thermal_zone_device_unregister ( ) must be called when the device is no
2008-12-03 20:55:32 +03:00
* longer needed . The passive cooling formula uses tc1 and tc2 as described in
* section 11.1 .5 .1 of the ACPI specification 3.0 .
2008-01-17 10:51:08 +03:00
*/
struct thermal_zone_device * thermal_zone_device_register ( char * type ,
2008-02-08 00:55:08 +03:00
int trips ,
void * devdata , struct
thermal_zone_device_ops
2008-12-03 20:55:32 +03:00
* ops , int tc1 , int
tc2 ,
int passive_delay ,
int polling_delay )
2008-01-17 10:51:08 +03:00
{
struct thermal_zone_device * tz ;
struct thermal_cooling_device * pos ;
2008-12-03 21:00:38 +03:00
enum thermal_trip_type trip_type ;
2008-01-17 10:51:08 +03:00
int result ;
int count ;
2008-12-03 21:00:38 +03:00
int passive = 0 ;
2008-01-17 10:51:08 +03:00
if ( strlen ( type ) > = THERMAL_NAME_LENGTH )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - EINVAL ) ;
2008-01-17 10:51:08 +03:00
if ( trips > THERMAL_MAX_TRIPS | | trips < 0 )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - EINVAL ) ;
2008-01-17 10:51:08 +03:00
if ( ! ops | | ! ops - > get_temp )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - EINVAL ) ;
2008-01-17 10:51:08 +03:00
tz = kzalloc ( sizeof ( struct thermal_zone_device ) , GFP_KERNEL ) ;
if ( ! tz )
2008-02-15 08:59:50 +03:00
return ERR_PTR ( - ENOMEM ) ;
2008-01-17 10:51:08 +03:00
INIT_LIST_HEAD ( & tz - > cooling_devices ) ;
idr_init ( & tz - > idr ) ;
mutex_init ( & tz - > lock ) ;
result = get_idr ( & thermal_tz_idr , & thermal_idr_lock , & tz - > id ) ;
if ( result ) {
kfree ( tz ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
strcpy ( tz - > type , type ) ;
tz - > ops = ops ;
tz - > device . class = & thermal_class ;
tz - > devdata = devdata ;
tz - > trips = trips ;
2008-12-03 20:55:32 +03:00
tz - > tc1 = tc1 ;
tz - > tc2 = tc2 ;
tz - > passive_delay = passive_delay ;
tz - > polling_delay = polling_delay ;
2009-01-06 21:44:37 +03:00
dev_set_name ( & tz - > device , " thermal_zone%d " , tz - > id ) ;
2008-01-17 10:51:08 +03:00
result = device_register ( & tz - > device ) ;
if ( result ) {
release_idr ( & thermal_tz_idr , & thermal_idr_lock , tz - > id ) ;
kfree ( tz ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
/* sys I/F */
if ( type ) {
result = device_create_file ( & tz - > device , & dev_attr_type ) ;
if ( result )
goto unregister ;
}
result = device_create_file ( & tz - > device , & dev_attr_temp ) ;
if ( result )
goto unregister ;
if ( ops - > get_mode ) {
result = device_create_file ( & tz - > device , & dev_attr_mode ) ;
if ( result )
goto unregister ;
}
for ( count = 0 ; count < trips ; count + + ) {
TRIP_POINT_ATTR_ADD ( & tz - > device , count , result ) ;
if ( result )
goto unregister ;
2008-12-03 21:00:38 +03:00
tz - > ops - > get_trip_type ( tz , count , & trip_type ) ;
if ( trip_type = = THERMAL_TRIP_PASSIVE )
passive = 1 ;
2008-01-17 10:51:08 +03:00
}
2008-12-03 21:00:38 +03:00
if ( ! passive )
result = device_create_file ( & tz - > device ,
& dev_attr_passive ) ;
if ( result )
goto unregister ;
2008-04-21 12:07:52 +04:00
result = thermal_add_hwmon_sysfs ( tz ) ;
if ( result )
goto unregister ;
2008-01-17 10:51:08 +03:00
mutex_lock ( & thermal_list_lock ) ;
list_add_tail ( & tz - > node , & thermal_tz_list ) ;
if ( ops - > bind )
list_for_each_entry ( pos , & thermal_cdev_list , node ) {
2008-02-08 00:55:08 +03:00
result = ops - > bind ( tz , pos ) ;
if ( result )
break ;
2008-01-17 10:51:08 +03:00
}
mutex_unlock ( & thermal_list_lock ) ;
2008-12-03 20:55:32 +03:00
INIT_DELAYED_WORK ( & ( tz - > poll_queue ) , thermal_zone_device_check ) ;
thermal_zone_device_update ( tz ) ;
2008-01-17 10:51:08 +03:00
if ( ! result )
return tz ;
unregister :
release_idr ( & thermal_tz_idr , & thermal_idr_lock , tz - > id ) ;
device_unregister ( & tz - > device ) ;
2008-02-15 08:59:50 +03:00
return ERR_PTR ( result ) ;
2008-01-17 10:51:08 +03:00
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_zone_device_register ) ;
/**
* thermal_device_unregister - removes the registered thermal zone device
* @ tz : the thermal zone device to remove
*/
void thermal_zone_device_unregister ( struct thermal_zone_device * tz )
{
struct thermal_cooling_device * cdev ;
struct thermal_zone_device * pos = NULL ;
int count ;
if ( ! tz )
return ;
mutex_lock ( & thermal_list_lock ) ;
list_for_each_entry ( pos , & thermal_tz_list , node )
if ( pos = = tz )
break ;
if ( pos ! = tz ) {
/* thermal zone device not found */
mutex_unlock ( & thermal_list_lock ) ;
return ;
}
list_del ( & tz - > node ) ;
if ( tz - > ops - > unbind )
list_for_each_entry ( cdev , & thermal_cdev_list , node )
tz - > ops - > unbind ( tz , cdev ) ;
mutex_unlock ( & thermal_list_lock ) ;
2008-12-03 20:55:32 +03:00
thermal_zone_device_set_polling ( tz , 0 ) ;
2008-01-17 10:51:08 +03:00
if ( tz - > type [ 0 ] )
device_remove_file ( & tz - > device , & dev_attr_type ) ;
device_remove_file ( & tz - > device , & dev_attr_temp ) ;
if ( tz - > ops - > get_mode )
device_remove_file ( & tz - > device , & dev_attr_mode ) ;
for ( count = 0 ; count < tz - > trips ; count + + )
TRIP_POINT_ATTR_REMOVE ( & tz - > device , count ) ;
2008-04-21 12:07:52 +04:00
thermal_remove_hwmon_sysfs ( tz ) ;
2008-01-17 10:51:08 +03:00
release_idr ( & thermal_tz_idr , & thermal_idr_lock , tz - > id ) ;
idr_destroy ( & tz - > idr ) ;
mutex_destroy ( & tz - > lock ) ;
device_unregister ( & tz - > device ) ;
return ;
}
2008-02-08 00:55:08 +03:00
2008-01-17 10:51:08 +03:00
EXPORT_SYMBOL ( thermal_zone_device_unregister ) ;
static int __init thermal_init ( void )
{
int result = 0 ;
result = class_register ( & thermal_class ) ;
if ( result ) {
idr_destroy ( & thermal_tz_idr ) ;
idr_destroy ( & thermal_cdev_idr ) ;
mutex_destroy ( & thermal_idr_lock ) ;
mutex_destroy ( & thermal_list_lock ) ;
}
return result ;
}
static void __exit thermal_exit ( void )
{
class_unregister ( & thermal_class ) ;
idr_destroy ( & thermal_tz_idr ) ;
idr_destroy ( & thermal_cdev_idr ) ;
mutex_destroy ( & thermal_idr_lock ) ;
mutex_destroy ( & thermal_list_lock ) ;
}
subsys_initcall ( thermal_init ) ;
module_exit ( thermal_exit ) ;