linux/drivers/thermal/thermal_sys.c

1587 lines
40 KiB
C
Raw Normal View History

/*
* 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/device.h>
#include <linux/err.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 11:04:11 +03:00
#include <linux/slab.h>
#include <linux/kdev_t.h>
#include <linux/idr.h>
#include <linux/thermal.h>
#include <linux/spinlock.h>
#include <linux/reboot.h>
#include <net/netlink.h>
#include <net/genetlink.h>
MODULE_AUTHOR("Zhang Rui");
MODULE_DESCRIPTION("Generic thermal management sysfs support");
MODULE_LICENSE("GPL");
/*
* This structure is used to describe the behavior of
* a certain cooling device on a certain trip point
* in a certain thermal zone
*/
struct thermal_cooling_device_instance {
int id;
char name[THERMAL_NAME_LENGTH];
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
int trip;
unsigned long upper; /* Highest cooling state for this trip point */
unsigned long lower; /* Lowest cooling state for this trip point */
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);
long temperature;
int ret;
if (!tz->ops->get_temp)
return -EPERM;
ret = tz->ops->get_temp(tz, &temperature);
if (ret)
return ret;
return sprintf(buf, "%ld\n", temperature);
}
static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
enum thermal_device_mode mode;
int result;
if (!tz->ops->get_mode)
return -EPERM;
result = tz->ops->get_mode(tz, &mode);
if (result)
return result;
return sprintf(buf, "%s\n", mode == THERMAL_DEVICE_ENABLED ? "enabled"
: "disabled");
}
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;
if (!strncmp(buf, "enabled", sizeof("enabled") - 1))
result = tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED);
else if (!strncmp(buf, "disabled", sizeof("disabled") - 1))
result = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED);
else
result = -EINVAL;
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);
enum thermal_trip_type type;
int trip, result;
if (!tz->ops->get_trip_type)
return -EPERM;
if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip))
return -EINVAL;
result = tz->ops->get_trip_type(tz, trip, &type);
if (result)
return result;
switch (type) {
case THERMAL_TRIP_CRITICAL:
return sprintf(buf, "critical\n");
case THERMAL_TRIP_HOT:
return sprintf(buf, "hot\n");
case THERMAL_TRIP_PASSIVE:
return sprintf(buf, "passive\n");
case THERMAL_TRIP_ACTIVE:
return sprintf(buf, "active\n");
default:
return sprintf(buf, "unknown\n");
}
}
static ssize_t
trip_point_temp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
unsigned long temperature;
if (!tz->ops->set_trip_temp)
return -EPERM;
if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
return -EINVAL;
if (kstrtoul(buf, 10, &temperature))
return -EINVAL;
ret = tz->ops->set_trip_temp(tz, trip, temperature);
return ret ? ret : count;
}
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);
int trip, ret;
long temperature;
if (!tz->ops->get_trip_temp)
return -EPERM;
if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip))
return -EINVAL;
ret = tz->ops->get_trip_temp(tz, trip, &temperature);
if (ret)
return ret;
return sprintf(buf, "%ld\n", temperature);
}
static ssize_t
trip_point_hyst_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
unsigned long temperature;
if (!tz->ops->set_trip_hyst)
return -EPERM;
if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
return -EINVAL;
if (kstrtoul(buf, 10, &temperature))
return -EINVAL;
/*
* We are not doing any check on the 'temperature' value
* here. The driver implementing 'set_trip_hyst' has to
* take care of this.
*/
ret = tz->ops->set_trip_hyst(tz, trip, temperature);
return ret ? ret : count;
}
static ssize_t
trip_point_hyst_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct thermal_zone_device *tz = to_thermal_zone(dev);
int trip, ret;
unsigned long temperature;
if (!tz->ops->get_trip_hyst)
return -EPERM;
if (!sscanf(attr->attr.name, "trip_point_%d_hyst", &trip))
return -EINVAL;
ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
return ret ? ret : sprintf(buf, "%ld\n", temperature);
}
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;
/* sanity check: values below 1000 millicelcius don't make sense
* and can cause the system to go into a thermal heart attack
*/
if (state && state < 1000)
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,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT);
}
mutex_unlock(&thermal_list_lock);
if (!tz->passive_delay)
tz->passive_delay = 1000;
} 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->passive_delay = 0;
}
tz->tc1 = 1;
tz->tc2 = 1;
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);
}
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);
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
/* 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);
unsigned long state;
int ret;
ret = cdev->ops->get_max_state(cdev, &state);
if (ret)
return ret;
return sprintf(buf, "%ld\n", state);
}
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);
unsigned long state;
int ret;
ret = cdev->ops->get_cur_state(cdev, &state);
if (ret)
return ret;
return sprintf(buf, "%ld\n", state);
}
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);
unsigned long state;
int result;
if (!sscanf(buf, "%ld\n", &state))
return -EINVAL;
if ((long)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 =
__ATTR(type, 0444, thermal_cooling_device_type_show, NULL);
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,
struct device_attribute *attr, char *buf)
{
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 */
#if defined(CONFIG_THERMAL_HWMON)
/* hwmon sys I/F */
#include <linux/hwmon.h>
/* thermal zone devices with the same type share one hwmon device */
struct thermal_hwmon_device {
char type[THERMAL_NAME_LENGTH];
struct device *device;
int count;
struct list_head tz_list;
struct list_head node;
};
struct thermal_hwmon_attr {
struct device_attribute attr;
char name[16];
};
/* one temperature input for each thermal zone */
struct thermal_hwmon_temp {
struct list_head hwmon_node;
struct thermal_zone_device *tz;
struct thermal_hwmon_attr temp_input; /* hwmon sys attr */
struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */
};
static LIST_HEAD(thermal_hwmon_list);
static ssize_t
name_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
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)
{
long temperature;
int ret;
struct thermal_hwmon_attr *hwmon_attr
= container_of(attr, struct thermal_hwmon_attr, attr);
struct thermal_hwmon_temp *temp
= container_of(hwmon_attr, struct thermal_hwmon_temp,
temp_input);
struct thermal_zone_device *tz = temp->tz;
ret = tz->ops->get_temp(tz, &temperature);
if (ret)
return ret;
return sprintf(buf, "%ld\n", temperature);
}
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_hwmon_temp *temp
= container_of(hwmon_attr, struct thermal_hwmon_temp,
temp_crit);
struct thermal_zone_device *tz = temp->tz;
long temperature;
int ret;
ret = tz->ops->get_trip_temp(tz, 0, &temperature);
if (ret)
return ret;
return sprintf(buf, "%ld\n", temperature);
}
static struct thermal_hwmon_device *
thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
{
struct thermal_hwmon_device *hwmon;
mutex_lock(&thermal_list_lock);
list_for_each_entry(hwmon, &thermal_hwmon_list, node)
if (!strcmp(hwmon->type, tz->type)) {
mutex_unlock(&thermal_list_lock);
return hwmon;
}
mutex_unlock(&thermal_list_lock);
return NULL;
}
/* Find the temperature input matching a given thermal zone */
static struct thermal_hwmon_temp *
thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
const struct thermal_zone_device *tz)
{
struct thermal_hwmon_temp *temp;
mutex_lock(&thermal_list_lock);
list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
if (temp->tz == tz) {
mutex_unlock(&thermal_list_lock);
return temp;
}
mutex_unlock(&thermal_list_lock);
return NULL;
}
static int
thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
{
struct thermal_hwmon_device *hwmon;
struct thermal_hwmon_temp *temp;
int new_hwmon_device = 1;
int result;
hwmon = thermal_hwmon_lookup_by_type(tz);
if (hwmon) {
new_hwmon_device = 0;
goto register_sys_interface;
}
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;
}
dev_set_drvdata(hwmon->device, hwmon);
result = device_create_file(hwmon->device, &dev_attr_name);
if (result)
goto free_mem;
register_sys_interface:
temp = kzalloc(sizeof(struct thermal_hwmon_temp), GFP_KERNEL);
if (!temp) {
result = -ENOMEM;
goto unregister_name;
}
temp->tz = tz;
hwmon->count++;
snprintf(temp->temp_input.name, THERMAL_NAME_LENGTH,
"temp%d_input", hwmon->count);
temp->temp_input.attr.attr.name = temp->temp_input.name;
temp->temp_input.attr.attr.mode = 0444;
temp->temp_input.attr.show = temp_input_show;
sysfs_attr_init(&temp->temp_input.attr.attr);
result = device_create_file(hwmon->device, &temp->temp_input.attr);
if (result)
goto free_temp_mem;
if (tz->ops->get_crit_temp) {
unsigned long temperature;
if (!tz->ops->get_crit_temp(tz, &temperature)) {
snprintf(temp->temp_crit.name, THERMAL_NAME_LENGTH,
"temp%d_crit", hwmon->count);
temp->temp_crit.attr.attr.name = temp->temp_crit.name;
temp->temp_crit.attr.attr.mode = 0444;
temp->temp_crit.attr.show = temp_crit_show;
sysfs_attr_init(&temp->temp_crit.attr.attr);
result = device_create_file(hwmon->device,
&temp->temp_crit.attr);
if (result)
goto unregister_input;
}
}
mutex_lock(&thermal_list_lock);
if (new_hwmon_device)
list_add_tail(&hwmon->node, &thermal_hwmon_list);
list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
mutex_unlock(&thermal_list_lock);
return 0;
unregister_input:
device_remove_file(hwmon->device, &temp->temp_input.attr);
free_temp_mem:
kfree(temp);
unregister_name:
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;
struct thermal_hwmon_temp *temp;
hwmon = thermal_hwmon_lookup_by_type(tz);
if (unlikely(!hwmon)) {
/* Should never happen... */
dev_dbg(&tz->device, "hwmon device lookup failed!\n");
return;
}
temp = thermal_hwmon_lookup_temp(hwmon, tz);
if (unlikely(!temp)) {
/* Should never happen... */
dev_dbg(&tz->device, "temperature input lookup failed!\n");
return;
}
device_remove_file(hwmon->device, &temp->temp_input.attr);
if (tz->ops->get_crit_temp)
device_remove_file(hwmon->device, &temp->temp_crit.attr);
mutex_lock(&thermal_list_lock);
list_del(&temp->hwmon_node);
kfree(temp);
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
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)
queue_delayed_work(system_freezable_wq, &(tz->poll_queue),
round_jiffies(msecs_to_jiffies(delay)));
else
queue_delayed_work(system_freezable_wq, &(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;
if (!tz->ops->get_trend ||
tz->ops->get_trend(tz, trip, (enum thermal_trend *)&trend)) {
/*
* compare the current temperature and previous temperature
* to get the thermal trend, if no special requirement
*/
if (tz->temperature > tz->last_temperature)
trend = THERMAL_TREND_RAISING;
else if (tz->temperature < tz->last_temperature)
trend = THERMAL_TREND_DROPPING;
else
trend = THERMAL_TREND_STABLE;
}
/*
* 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);
}
/**
* 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
*
* This function is usually called in the thermal zone device .bind callback.
*/
int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
int trip,
struct thermal_cooling_device *cdev,
unsigned long upper, unsigned long lower)
{
struct thermal_cooling_device_instance *dev;
struct thermal_cooling_device_instance *pos;
struct thermal_zone_device *pos1;
struct thermal_cooling_device *pos2;
unsigned long max_state;
int result;
if (trip >= tz->trips || (trip < 0 && trip != THERMAL_TRIPS_NONE))
return -EINVAL;
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)
return -EINVAL;
cdev->ops->get_max_state(cdev, &max_state);
/* lower default 0, upper default max_state */
lower = lower == THERMAL_NO_LIMIT ? 0 : lower;
upper = upper == THERMAL_NO_LIMIT ? max_state : upper;
if (lower > upper || upper > max_state)
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;
dev->upper = upper;
dev->lower = lower;
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);
sysfs_attr_init(&dev->attr.attr);
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;
}
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
*
* This function is usually called in the thermal zone device .unbind callback.
*/
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) {
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
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;
}
EXPORT_SYMBOL(thermal_zone_unbind_cooling_device);
static void thermal_release(struct device *dev)
{
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
if (!strncmp(dev_name(dev), "thermal_zone",
sizeof("thermal_zone") - 1)) {
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, void *devdata,
const struct thermal_cooling_device_ops *ops)
{
struct thermal_cooling_device *cdev;
struct thermal_zone_device *pos;
int result;
if (strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL);
if (!ops || !ops->get_max_state || !ops->get_cur_state ||
!ops->set_cur_state)
return ERR_PTR(-EINVAL);
cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL);
if (!cdev)
return ERR_PTR(-ENOMEM);
result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id);
if (result) {
kfree(cdev);
return ERR_PTR(result);
}
strcpy(cdev->type, type);
cdev->ops = ops;
cdev->device.class = &thermal_class;
cdev->devdata = devdata;
dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
result = device_register(&cdev->device);
if (result) {
release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
kfree(cdev);
return ERR_PTR(result);
}
/* sys I/F */
if (type) {
result = device_create_file(&cdev->device, &dev_attr_cdev_type);
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);
return ERR_PTR(result);
}
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])
device_remove_file(&cdev->device, &dev_attr_cdev_type);
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;
}
EXPORT_SYMBOL(thermal_cooling_device_unregister);
/**
* 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;
unsigned long cur_state, max_state;
mutex_lock(&tz->lock);
if (tz->ops->get_temp(tz, &temp)) {
/* get_temp failed - retry it later */
pr_warn("failed to read out thermal zone %d\n", tz->id);
goto leave;
}
tz->last_temperature = tz->temperature;
tz->temperature = 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:
if (temp >= trip_temp) {
if (tz->ops->notify)
ret = tz->ops->notify(tz, count,
trip_type);
if (!ret) {
pr_emerg("Critical temperature reached (%ld C), shutting down\n",
temp/1000);
orderly_poweroff(true);
}
}
break;
case THERMAL_TRIP_HOT:
if (temp >= trip_temp)
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;
cdev->ops->get_cur_state(cdev, &cur_state);
cdev->ops->get_max_state(cdev, &max_state);
if (temp >= trip_temp)
cur_state =
cur_state < instance->upper ?
(cur_state + 1) :
instance->upper;
else
cur_state =
cur_state > instance->lower ?
(cur_state - 1) :
instance->lower;
cdev->ops->set_cur_state(cdev, cur_state);
}
break;
case THERMAL_TRIP_PASSIVE:
if (temp >= trip_temp || tz->passive)
thermal_zone_device_passive(tz, temp,
trip_temp, count);
break;
}
}
if (tz->forced_passive)
thermal_zone_device_passive(tz, temp, tz->forced_passive,
THERMAL_TRIPS_NONE);
leave:
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);
else
thermal_zone_device_set_polling(tz, 0);
mutex_unlock(&tz->lock);
}
EXPORT_SYMBOL(thermal_zone_device_update);
/**
* create_trip_attrs - create attributes for trip points
* @tz: the thermal zone device
* @mask: Writeable trip point bitmap.
*/
static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
{
int indx;
int size = sizeof(struct thermal_attr) * tz->trips;
tz->trip_type_attrs = kzalloc(size, GFP_KERNEL);
if (!tz->trip_type_attrs)
return -ENOMEM;
tz->trip_temp_attrs = kzalloc(size, GFP_KERNEL);
if (!tz->trip_temp_attrs) {
kfree(tz->trip_type_attrs);
return -ENOMEM;
}
if (tz->ops->get_trip_hyst) {
tz->trip_hyst_attrs = kzalloc(size, GFP_KERNEL);
if (!tz->trip_hyst_attrs) {
kfree(tz->trip_type_attrs);
kfree(tz->trip_temp_attrs);
return -ENOMEM;
}
}
for (indx = 0; indx < tz->trips; indx++) {
/* create trip type attribute */
snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH,
"trip_point_%d_type", indx);
sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr);
tz->trip_type_attrs[indx].attr.attr.name =
tz->trip_type_attrs[indx].name;
tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO;
tz->trip_type_attrs[indx].attr.show = trip_point_type_show;
device_create_file(&tz->device,
&tz->trip_type_attrs[indx].attr);
/* create trip temp attribute */
snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH,
"trip_point_%d_temp", indx);
sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr);
tz->trip_temp_attrs[indx].attr.attr.name =
tz->trip_temp_attrs[indx].name;
tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO;
tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show;
if (mask & (1 << indx)) {
tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
tz->trip_temp_attrs[indx].attr.store =
trip_point_temp_store;
}
device_create_file(&tz->device,
&tz->trip_temp_attrs[indx].attr);
/* create Optional trip hyst attribute */
if (!tz->ops->get_trip_hyst)
continue;
snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH,
"trip_point_%d_hyst", indx);
sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr);
tz->trip_hyst_attrs[indx].attr.attr.name =
tz->trip_hyst_attrs[indx].name;
tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO;
tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show;
if (tz->ops->set_trip_hyst) {
tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR;
tz->trip_hyst_attrs[indx].attr.store =
trip_point_hyst_store;
}
device_create_file(&tz->device,
&tz->trip_hyst_attrs[indx].attr);
}
return 0;
}
static void remove_trip_attrs(struct thermal_zone_device *tz)
{
int indx;
for (indx = 0; indx < tz->trips; indx++) {
device_remove_file(&tz->device,
&tz->trip_type_attrs[indx].attr);
device_remove_file(&tz->device,
&tz->trip_temp_attrs[indx].attr);
if (tz->ops->get_trip_hyst)
device_remove_file(&tz->device,
&tz->trip_hyst_attrs[indx].attr);
}
kfree(tz->trip_type_attrs);
kfree(tz->trip_temp_attrs);
kfree(tz->trip_hyst_attrs);
}
/**
* 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
* @mask: a bit string indicating the writeablility of trip points
* @devdata: private device data
* @ops: standard thermal zone device callbacks
* @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)
*
* thermal_zone_device_unregister() must be called when the device is no
* longer needed. The passive cooling formula uses tc1 and tc2 as described in
* section 11.1.5.1 of the ACPI specification 3.0.
*/
struct thermal_zone_device *thermal_zone_device_register(const char *type,
int trips, int mask, void *devdata,
const struct thermal_zone_device_ops *ops,
int tc1, int tc2, int passive_delay, int polling_delay)
{
struct thermal_zone_device *tz;
struct thermal_cooling_device *pos;
enum thermal_trip_type trip_type;
int result;
int count;
int passive = 0;
if (strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL);
if (trips > THERMAL_MAX_TRIPS || trips < 0 || mask >> trips)
return ERR_PTR(-EINVAL);
if (!ops || !ops->get_temp)
return ERR_PTR(-EINVAL);
tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL);
if (!tz)
return ERR_PTR(-ENOMEM);
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);
return ERR_PTR(result);
}
strcpy(tz->type, type);
tz->ops = ops;
tz->device.class = &thermal_class;
tz->devdata = devdata;
tz->trips = trips;
tz->tc1 = tc1;
tz->tc2 = tc2;
tz->passive_delay = passive_delay;
tz->polling_delay = polling_delay;
dev_set_name(&tz->device, "thermal_zone%d", tz->id);
result = device_register(&tz->device);
if (result) {
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
kfree(tz);
return ERR_PTR(result);
}
/* 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;
}
result = create_trip_attrs(tz, mask);
if (result)
goto unregister;
for (count = 0; count < trips; count++) {
tz->ops->get_trip_type(tz, count, &trip_type);
if (trip_type == THERMAL_TRIP_PASSIVE)
passive = 1;
}
if (!passive)
result = device_create_file(&tz->device,
&dev_attr_passive);
if (result)
goto unregister;
result = thermal_add_hwmon_sysfs(tz);
if (result)
goto unregister;
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) {
result = ops->bind(tz, pos);
if (result)
break;
}
mutex_unlock(&thermal_list_lock);
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check);
thermal_zone_device_update(tz);
if (!result)
return tz;
unregister:
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
device_unregister(&tz->device);
return ERR_PTR(result);
}
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;
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);
thermal_zone_device_set_polling(tz, 0);
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);
remove_trip_attrs(tz);
thermal_remove_hwmon_sysfs(tz);
release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id);
idr_destroy(&tz->idr);
mutex_destroy(&tz->lock);
device_unregister(&tz->device);
return;
}
EXPORT_SYMBOL(thermal_zone_device_unregister);
#ifdef CONFIG_NET
static struct genl_family thermal_event_genl_family = {
.id = GENL_ID_GENERATE,
.name = THERMAL_GENL_FAMILY_NAME,
.version = THERMAL_GENL_VERSION,
.maxattr = THERMAL_GENL_ATTR_MAX,
};
static struct genl_multicast_group thermal_event_mcgrp = {
.name = THERMAL_GENL_MCAST_GROUP_NAME,
};
int thermal_generate_netlink_event(u32 orig, enum events event)
{
struct sk_buff *skb;
struct nlattr *attr;
struct thermal_genl_event *thermal_event;
void *msg_header;
int size;
int result;
static unsigned int thermal_event_seqnum;
/* allocate memory */
size = nla_total_size(sizeof(struct thermal_genl_event)) +
nla_total_size(0);
skb = genlmsg_new(size, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
/* add the genetlink message header */
msg_header = genlmsg_put(skb, 0, thermal_event_seqnum++,
&thermal_event_genl_family, 0,
THERMAL_GENL_CMD_EVENT);
if (!msg_header) {
nlmsg_free(skb);
return -ENOMEM;
}
/* fill the data */
attr = nla_reserve(skb, THERMAL_GENL_ATTR_EVENT,
sizeof(struct thermal_genl_event));
if (!attr) {
nlmsg_free(skb);
return -EINVAL;
}
thermal_event = nla_data(attr);
if (!thermal_event) {
nlmsg_free(skb);
return -EINVAL;
}
memset(thermal_event, 0, sizeof(struct thermal_genl_event));
thermal_event->orig = orig;
thermal_event->event = event;
/* send multicast genetlink message */
result = genlmsg_end(skb, msg_header);
if (result < 0) {
nlmsg_free(skb);
return result;
}
result = genlmsg_multicast(skb, 0, thermal_event_mcgrp.id, GFP_ATOMIC);
if (result)
pr_info("failed to send netlink event:%d\n", result);
return result;
}
EXPORT_SYMBOL(thermal_generate_netlink_event);
static int genetlink_init(void)
{
int result;
result = genl_register_family(&thermal_event_genl_family);
if (result)
return result;
result = genl_register_mc_group(&thermal_event_genl_family,
&thermal_event_mcgrp);
if (result)
genl_unregister_family(&thermal_event_genl_family);
return result;
}
static void genetlink_exit(void)
{
genl_unregister_family(&thermal_event_genl_family);
}
#else /* !CONFIG_NET */
static inline int genetlink_init(void) { return 0; }
static inline void genetlink_exit(void) {}
#endif /* !CONFIG_NET */
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);
}
result = genetlink_init();
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);
genetlink_exit();
}
fs_initcall(thermal_init);
module_exit(thermal_exit);