blob: abc6fc3a2ce6b101542c89933cb79721cb50f489 [file] [log] [blame]
/*
* drivers/misc/therm_est.c
*
* Copyright (c) 2010-2014, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/therm_est.h>
#include <linux/thermal.h>
#include <linux/module.h>
#include <linux/hwmon-sysfs.h>
#include <linux/suspend.h>
struct therm_estimator {
struct thermal_zone_device *thz;
int num_trips;
struct thermal_trip_info *trips;
struct thermal_zone_params *tzp;
int num_timer_trips;
struct therm_est_timer_trip_info *timer_trips;
struct delayed_work timer_trip_work;
struct mutex timer_trip_lock;
struct thermal_cooling_device *cdev; /* activation device */
struct workqueue_struct *workqueue;
struct delayed_work therm_est_work;
long cur_temp;
long low_limit;
long high_limit;
int ntemp;
long toffset;
long polling_period;
int polling_enabled;
int tc1;
int tc2;
int ndevs;
struct therm_est_subdevice *devs;
int use_activator;
#ifdef CONFIG_PM
struct notifier_block pm_nb;
#endif
};
#define TIMER_TRIP_INACTIVE -2
#define TIMER_TRIP_STATE_NONE 0
#define TIMER_TRIP_STATE_START BIT(0)
#define TIMER_TRIP_STATE_STOP BIT(1)
#define TIMER_TRIP_STATE_UP BIT(2)
#define TIMER_TRIP_STATE_DOWN BIT(3)
static int __get_trip_temp(struct thermal_zone_device *thz, int trip,
long *temp);
static struct therm_est_timer_trip_info *
__find_timer_trip(struct therm_estimator *est, int trip)
{
int i;
/* Find matched timer trip info with trip. */
for (i = 0; i < est->num_timer_trips; i++) {
if (est->timer_trips[i].trip == trip)
return &est->timer_trips[i];
}
return NULL;
}
static int __get_timer_trip_delay(struct therm_est_timer_trip_info *timer_info,
s64 now, s64 *delay)
{
int cur = timer_info->cur;
int next = (cur + 1 < timer_info->num_timers) ? cur + 1 : cur;
if (cur == next) /* No more timer on this trip. */
return -ENOENT;
*delay = timer_info->timers[next].time_after -
(now - timer_info->last_tripped);
return 0;
}
static int therm_est_subdev_match(struct thermal_zone_device *thz, void *data)
{
return strcmp((char *)data, thz->type) == 0;
}
static int therm_est_subdev_get_temp(struct thermal_zone_device *thz,
long *temp)
{
if (!thz || thz->ops->get_temp(thz, temp))
*temp = 25000;
return 0;
}
static void therm_est_update_limits(struct therm_estimator *est)
{
const int MAX_HIGH_TEMP = 128000;
long low_temp = 0, high_temp = MAX_HIGH_TEMP;
long trip_temp, passive_low_temp = MAX_HIGH_TEMP;
enum thermal_trip_type trip_type;
struct thermal_trip_info *trip_state;
int i;
for (i = 0; i < est->num_trips; i++) {
trip_state = &est->trips[i];
__get_trip_temp(est->thz, i, &trip_temp);
est->thz->ops->get_trip_type(est->thz, i, &trip_type);
if (!trip_state->tripped) { /* not tripped? update high */
if (trip_temp < high_temp)
high_temp = trip_temp;
} else { /* tripped? update low */
if (trip_type != THERMAL_TRIP_PASSIVE) {
/* get highest ACTIVE */
if (trip_temp > low_temp)
low_temp = trip_temp;
} else {
/* get lowest PASSIVE */
if (trip_temp < passive_low_temp)
passive_low_temp = trip_temp;
}
}
}
if (passive_low_temp != MAX_HIGH_TEMP)
low_temp = max(low_temp, passive_low_temp);
est->low_limit = low_temp;
est->high_limit = high_temp;
}
static void therm_est_update_timer_trips(struct therm_estimator *est)
{
struct thermal_trip_info *trip_state;
struct therm_est_timer_trip_info *timer_info;
s64 now, delay, min_delay;
int i;
mutex_lock(&est->timer_trip_lock);
min_delay = LLONG_MAX;
now = ktime_to_ms(ktime_get());
for (i = 0; i < est->num_timer_trips; i++) {
timer_info = &est->timer_trips[i];
trip_state = &est->trips[timer_info->trip];
pr_debug("%s: i %d, trip %d, tripped %d, cur %d\n",
__func__, i, timer_info->trip, trip_state->tripped,
timer_info->cur);
if ((timer_info->cur == TIMER_TRIP_INACTIVE) ||
(__get_timer_trip_delay(timer_info, now, &delay) < 0))
continue;
if (delay > 0)
min_delay = min(min_delay, delay);
pr_debug("%s: delay %lld, min_delay %lld\n",
__func__, delay, min_delay);
}
mutex_unlock(&est->timer_trip_lock);
cancel_delayed_work(&est->timer_trip_work);
if (min_delay != LLONG_MAX)
queue_delayed_work(est->workqueue, &est->timer_trip_work,
msecs_to_jiffies(min_delay));
}
static void therm_est_timer_trip_work_func(struct work_struct *work)
{
struct therm_estimator *est = container_of(work, struct therm_estimator,
timer_trip_work.work);
struct thermal_trip_info *trip_state;
struct therm_est_timer_trip_info *timer_info;
s64 now, delay;
int timer_trip_state, i;
mutex_lock(&est->timer_trip_lock);
timer_trip_state = TIMER_TRIP_STATE_NONE;
now = ktime_to_ms(ktime_get());
for (i = 0; i < est->num_timer_trips; i++) {
timer_info = &est->timer_trips[i];
trip_state = &est->trips[timer_info->trip];
pr_debug("%s: i %d, trip %d, tripped %d, cur %d\n",
__func__, i, timer_info->trip, trip_state->tripped,
timer_info->cur);
if ((timer_info->cur == TIMER_TRIP_INACTIVE) ||
(__get_timer_trip_delay(timer_info, now, &delay) < 0))
continue;
if (delay <= 0) { /* Timer on this trip has expired. */
if (timer_info->cur + 1 < timer_info->num_timers) {
timer_info->last_tripped = now;
timer_info->cur++;
timer_trip_state |= TIMER_TRIP_STATE_UP;
}
}
/* If delay > 0, timer on this trip has not yet expired.
* So need to restart timer with remaining delay. */
timer_trip_state |= TIMER_TRIP_STATE_START;
pr_debug("%s: new_cur %d, delay %lld, timer_trip_state 0x%x\n",
__func__, timer_info->cur, delay, timer_trip_state);
}
mutex_unlock(&est->timer_trip_lock);
if (timer_trip_state & (TIMER_TRIP_STATE_START | TIMER_TRIP_STATE_UP)) {
therm_est_update_timer_trips(est);
therm_est_update_limits(est);
}
}
static void therm_est_work_func(struct work_struct *work)
{
int i, j, index, sum = 0;
long temp;
struct delayed_work *dwork = container_of(work,
struct delayed_work, work);
struct therm_estimator *est = container_of(dwork,
struct therm_estimator,
therm_est_work);
for (i = 0; i < est->ndevs; i++) {
if (therm_est_subdev_get_temp(est->devs[i].sub_thz, &temp))
continue;
est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp;
}
for (i = 0; i < est->ndevs; i++) {
for (j = 0; j < HIST_LEN; j++) {
index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
sum += est->devs[i].hist[index] *
est->devs[i].coeffs[j];
}
}
est->cur_temp = sum / 100 + est->toffset;
est->ntemp++;
if (est->thz && ((est->cur_temp < est->low_limit) ||
(est->cur_temp >= est->high_limit))) {
thermal_zone_device_update(est->thz);
therm_est_update_timer_trips(est);
therm_est_update_limits(est);
}
if (est->polling_enabled > 0 || !est->use_activator) {
queue_delayed_work(est->workqueue, &est->therm_est_work,
msecs_to_jiffies(est->polling_period));
}
}
static int therm_est_bind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdev)
{
struct therm_estimator *est = thz->devdata;
struct thermal_trip_info *trip_state;
int i;
for (i = 0; i < est->num_trips; i++) {
trip_state = &est->trips[i];
if (trip_state->cdev_type &&
!strncmp(trip_state->cdev_type, cdev->type,
THERMAL_NAME_LENGTH))
thermal_zone_bind_cooling_device(thz, i, cdev,
trip_state->upper,
trip_state->lower);
}
return 0;
}
static int therm_est_unbind(struct thermal_zone_device *thz,
struct thermal_cooling_device *cdev)
{
struct therm_estimator *est = thz->devdata;
struct thermal_trip_info *trip_state;
int i;
for (i = 0; i < est->num_trips; i++) {
trip_state = &est->trips[i];
if (trip_state->cdev_type &&
!strncmp(trip_state->cdev_type, cdev->type,
THERMAL_NAME_LENGTH))
thermal_zone_unbind_cooling_device(thz, i, cdev);
}
return 0;
}
static int therm_est_get_trip_type(struct thermal_zone_device *thz,
int trip, enum thermal_trip_type *type)
{
struct therm_estimator *est = thz->devdata;
*type = est->trips[trip].trip_type;
return 0;
}
static int __get_trip_temp(struct thermal_zone_device *thz, int trip,
long *temp)
{
struct therm_estimator *est = thz->devdata;
struct thermal_trip_info *trip_state = &est->trips[trip];
struct therm_est_timer_trip_info *timer_info;
long zone_temp, trip_temp, hysteresis;
int cur = TIMER_TRIP_INACTIVE;
int ret = TIMER_TRIP_STATE_NONE;
zone_temp = thz->temperature;
trip_temp = trip_state->trip_temp;
hysteresis = trip_state->hysteresis;
timer_info = __find_timer_trip(est, trip);
if (timer_info) {
cur = timer_info->cur;
/* If timer trip is available, use trip_temp and hysteresis in
* the timer trip to trip_temp for this trip. */
if (timer_info->cur >= 0) {
trip_temp = timer_info->timers[cur].trip_temp;
hysteresis = timer_info->timers[cur].hysteresis;
}
}
if (zone_temp >= trip_temp) {
trip_temp -= hysteresis;
if (timer_info && !trip_state->tripped)
ret = TIMER_TRIP_STATE_START;
trip_state->tripped = true;
} else if (trip_state->tripped) {
trip_temp -= hysteresis;
if (zone_temp < trip_temp) {
if (!timer_info) {
trip_state->tripped = false;
} else {
if (cur == TIMER_TRIP_INACTIVE)
trip_state->tripped = false;
else
ret = TIMER_TRIP_STATE_DOWN;
}
}
}
*temp = trip_temp;
return ret;
}
static int therm_est_get_trip_temp(struct thermal_zone_device *thz,
int trip, long *temp)
{
struct therm_estimator *est = thz->devdata;
struct therm_est_timer_trip_info *timer_info;
int ret;
ret = __get_trip_temp(thz, trip, temp);
if (ret & (TIMER_TRIP_STATE_START | TIMER_TRIP_STATE_DOWN)) {
timer_info = __find_timer_trip(est, trip);
mutex_lock(&est->timer_trip_lock);
timer_info->last_tripped = ktime_to_ms(ktime_get());
if (ret & TIMER_TRIP_STATE_START) {
timer_info->cur = TIMER_TRIP_INACTIVE + 1;
} else if (ret & TIMER_TRIP_STATE_DOWN) {
if (--timer_info->cur < TIMER_TRIP_INACTIVE)
timer_info->cur = TIMER_TRIP_INACTIVE;
}
mutex_unlock(&est->timer_trip_lock);
/* Update limits, because trip temp was changed by timer trip
* changing. */
therm_est_update_limits(est);
}
return 0;
}
static int therm_est_set_trip_temp(struct thermal_zone_device *thz,
int trip, long temp)
{
struct therm_estimator *est = thz->devdata;
est->trips[trip].trip_temp = temp;
/* Update limits, because trip temp was changed. */
therm_est_update_limits(est);
return 0;
}
static int therm_est_get_temp(struct thermal_zone_device *thz, long *temp)
{
struct therm_estimator *est = thz->devdata;
*temp = est->cur_temp;
return 0;
}
static int therm_est_get_trend(struct thermal_zone_device *thz,
int trip, enum thermal_trend *trend)
{
struct therm_estimator *est = thz->devdata;
struct thermal_trip_info *trip_state = &est->trips[trip];
long trip_temp;
int new_trend;
int cur_temp;
__get_trip_temp(thz, trip, &trip_temp);
cur_temp = thz->temperature;
new_trend = (est->tc1 * (cur_temp - thz->last_temperature)) +
(est->tc2 * (cur_temp - trip_temp));
switch (trip_state->trip_type) {
case THERMAL_TRIP_ACTIVE:
/* aggressive active cooling */
*trend = THERMAL_TREND_RAISING;
break;
case THERMAL_TRIP_PASSIVE:
if (new_trend > 0)
*trend = THERMAL_TREND_RAISING;
else if (new_trend < 0)
*trend = THERMAL_TREND_DROPPING;
else
*trend = THERMAL_TREND_STABLE;
break;
default:
return -EINVAL;
}
return 0;
}
static void therm_est_init_timer_trips(struct therm_estimator *est)
{
int i;
for (i = 0; i < est->num_timer_trips; i++)
est->timer_trips[i].cur = TIMER_TRIP_INACTIVE;
}
static int therm_est_init_history(struct therm_estimator *est)
{
int i, j;
struct therm_est_subdevice *dev;
long temp;
for (i = 0; i < est->ndevs; i++) {
dev = &est->devs[i];
if (therm_est_subdev_get_temp(dev->sub_thz, &temp))
return -EINVAL;
for (j = 0; j < HIST_LEN; j++)
dev->hist[j] = temp;
}
return 0;
}
static int therm_est_polling(struct therm_estimator *est,
int polling)
{
est->polling_enabled = polling > 0;
if (est->polling_enabled > 0) {
est->low_limit = 0;
est->high_limit = 0;
therm_est_init_history(est);
therm_est_init_timer_trips(est);
queue_delayed_work(est->workqueue,
&est->therm_est_work,
msecs_to_jiffies(est->polling_period));
} else {
est->cur_temp = 25000;
cancel_delayed_work_sync(&est->therm_est_work);
}
return 0;
}
static struct thermal_zone_device_ops therm_est_ops = {
.bind = therm_est_bind,
.unbind = therm_est_unbind,
.get_trip_type = therm_est_get_trip_type,
.get_trip_temp = therm_est_get_trip_temp,
.set_trip_temp = therm_est_set_trip_temp,
.get_temp = therm_est_get_temp,
.get_trend = therm_est_get_trend,
};
static ssize_t show_coeff(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct therm_estimator *est = dev_get_drvdata(dev);
ssize_t len, total_len = 0;
int i, j;
for (i = 0; i < est->ndevs; i++) {
len = snprintf(buf + total_len,
PAGE_SIZE - total_len, "[%d]", i);
total_len += len;
for (j = 0; j < HIST_LEN; j++) {
len = snprintf(buf + total_len,
PAGE_SIZE - total_len, " %ld",
est->devs[i].coeffs[j]);
total_len += len;
}
len = snprintf(buf + total_len, PAGE_SIZE - total_len, "\n");
total_len += len;
}
return strlen(buf);
}
static ssize_t set_coeff(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct therm_estimator *est = dev_get_drvdata(dev);
int devid, scount;
long coeff[20];
if (HIST_LEN > 20)
return -EINVAL;
scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \
"%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
&devid,
&coeff[0],
&coeff[1],
&coeff[2],
&coeff[3],
&coeff[4],
&coeff[5],
&coeff[6],
&coeff[7],
&coeff[8],
&coeff[9],
&coeff[10],
&coeff[11],
&coeff[12],
&coeff[13],
&coeff[14],
&coeff[15],
&coeff[16],
&coeff[17],
&coeff[18],
&coeff[19]);
if (scount != HIST_LEN + 1)
return -1;
if (devid < 0 || devid >= est->ndevs)
return -EINVAL;
/* This has obvious locking issues but don't worry about it */
memcpy(est->devs[devid].coeffs, coeff, sizeof(coeff[0]) * HIST_LEN);
return count;
}
static ssize_t show_offset(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct therm_estimator *est = dev_get_drvdata(dev);
snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset);
return strlen(buf);
}
static ssize_t set_offset(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct therm_estimator *est = dev_get_drvdata(dev);
int offset;
if (kstrtoint(buf, 0, &offset))
return -EINVAL;
est->toffset = offset;
return count;
}
static ssize_t show_temps(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct therm_estimator *est = dev_get_drvdata(dev);
ssize_t total_len = 0;
int i, j;
int index;
/* This has obvious locking issues but don't worry about it */
for (i = 0; i < est->ndevs; i++) {
total_len += snprintf(buf + total_len,
PAGE_SIZE - total_len, "[%d]", i);
for (j = 0; j < HIST_LEN; j++) {
index = (est->ntemp - j + HIST_LEN) % HIST_LEN;
total_len += snprintf(buf + total_len,
PAGE_SIZE - total_len, " %ld",
est->devs[i].hist[index]);
}
total_len += snprintf(buf + total_len,
PAGE_SIZE - total_len, "\n");
}
return strlen(buf);
}
static ssize_t show_tc1(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct therm_estimator *est = dev_get_drvdata(dev);
snprintf(buf, PAGE_SIZE, "%d\n", est->tc1);
return strlen(buf);
}
static ssize_t set_tc1(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct therm_estimator *est = dev_get_drvdata(dev);
int tc1;
if (kstrtoint(buf, 0, &tc1))
return -EINVAL;
est->tc1 = tc1;
return count;
}
static ssize_t show_tc2(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct therm_estimator *est = dev_get_drvdata(dev);
snprintf(buf, PAGE_SIZE, "%d\n", est->tc2);
return strlen(buf);
}
static ssize_t set_tc2(struct device *dev,
struct device_attribute *da,
const char *buf, size_t count)
{
struct therm_estimator *est = dev_get_drvdata(dev);
int tc2;
if (kstrtoint(buf, 0, &tc2))
return -EINVAL;
est->tc2 = tc2;
return count;
}
static struct sensor_device_attribute therm_est_nodes[] = {
SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0),
SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0),
SENSOR_ATTR(tc1, S_IRUGO | S_IWUSR, show_tc1, set_tc1, 0),
SENSOR_ATTR(tc2, S_IRUGO | S_IWUSR, show_tc2, set_tc2, 0),
SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0),
};
#ifdef CONFIG_PM
static int therm_est_pm_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
struct therm_estimator *est = container_of(
nb,
struct therm_estimator,
pm_nb);
switch (event) {
case PM_SUSPEND_PREPARE:
cancel_delayed_work_sync(&est->therm_est_work);
cancel_delayed_work_sync(&est->timer_trip_work);
break;
case PM_POST_SUSPEND:
est->low_limit = 0;
est->high_limit = 0;
therm_est_init_history(est);
therm_est_init_timer_trips(est);
queue_delayed_work(est->workqueue,
&est->therm_est_work,
msecs_to_jiffies(est->polling_period));
break;
}
return NOTIFY_OK;
}
#endif
static int
thermal_est_activation_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
*max_state = 1;
return 0;
}
static int
thermal_est_activation_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *cur_state)
{
struct therm_estimator *est = cdev->devdata;
*cur_state = est->polling_enabled;
return 0;
}
static int
thermal_est_activation_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long cur_state)
{
struct therm_estimator *est = cdev->devdata;
if (est->use_activator)
therm_est_polling(est, cur_state > 0);
return 0;
}
static struct thermal_cooling_device_ops thermal_est_activation_device_ops = {
.get_max_state = thermal_est_activation_get_max_state,
.get_cur_state = thermal_est_activation_get_cur_state,
.set_cur_state = thermal_est_activation_set_cur_state,
};
struct thermal_cooling_device *thermal_est_activation_device_register(
struct therm_estimator *est,
char *type)
{
struct thermal_cooling_device *cdev;
cdev = thermal_cooling_device_register(
type,
est,
&thermal_est_activation_device_ops);
if (IS_ERR(cdev))
return NULL;
pr_debug("Therm_est: Cooling-device REGISTERED\n");
return cdev;
}
static int therm_est_probe(struct platform_device *pdev)
{
int i;
struct therm_estimator *est;
struct therm_est_data *data;
struct thermal_zone_device *thz;
est = kzalloc(sizeof(struct therm_estimator), GFP_KERNEL);
if (IS_ERR_OR_NULL(est))
return -ENOMEM;
platform_set_drvdata(pdev, est);
data = pdev->dev.platform_data;
for (i = 0; i < data->ndevs; i++) {
thz = thermal_zone_device_find(data->devs[i].dev_data,
therm_est_subdev_match);
if (!thz)
goto err;
data->devs[i].sub_thz = thz;
}
est->devs = data->devs;
est->ndevs = data->ndevs;
est->toffset = data->toffset;
est->polling_period = data->polling_period;
est->polling_enabled = 0; /* By default polling is switched off */
est->tc1 = data->tc1;
est->tc2 = data->tc2;
est->use_activator = data->use_activator;
/* initialize history */
therm_est_init_history(est);
/* initialize timer trips */
est->num_timer_trips = data->num_timer_trips;
est->timer_trips = data->timer_trips;
therm_est_init_timer_trips(est);
mutex_init(&est->timer_trip_lock);
INIT_DELAYED_WORK(&est->timer_trip_work,
therm_est_timer_trip_work_func);
est->workqueue = alloc_workqueue(dev_name(&pdev->dev),
WQ_HIGHPRI | WQ_UNBOUND, 1);
if (!est->workqueue)
goto err;
INIT_DELAYED_WORK(&est->therm_est_work, therm_est_work_func);
est->cdev = thermal_est_activation_device_register(est,
"therm_est_activ");
est->num_trips = data->num_trips;
est->trips = data->trips;
est->tzp = data->tzp;
est->thz = thermal_zone_device_register(dev_name(&pdev->dev),
est->num_trips,
(1ULL << est->num_trips) - 1,
est,
&therm_est_ops,
est->tzp,
data->passive_delay,
0);
if (IS_ERR_OR_NULL(est->thz))
goto err;
for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++)
device_create_file(&pdev->dev, &therm_est_nodes[i].dev_attr);
#ifdef CONFIG_PM
est->pm_nb.notifier_call = therm_est_pm_notify,
register_pm_notifier(&est->pm_nb);
#endif
if (!est->use_activator)
queue_delayed_work(est->workqueue, &est->therm_est_work,
msecs_to_jiffies(est->polling_period));
return 0;
err:
if (est->workqueue)
destroy_workqueue(est->workqueue);
kfree(est);
return -EINVAL;
}
static int therm_est_remove(struct platform_device *pdev)
{
struct therm_estimator *est = platform_get_drvdata(pdev);
int i;
cancel_delayed_work_sync(&est->therm_est_work);
cancel_delayed_work_sync(&est->timer_trip_work);
#ifdef CONFIG_PM
unregister_pm_notifier(&est->pm_nb);
#endif
for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++)
device_remove_file(&pdev->dev, &therm_est_nodes[i].dev_attr);
thermal_zone_device_unregister(est->thz);
thermal_cooling_device_unregister(est->cdev);
kfree(est->thz);
destroy_workqueue(est->workqueue);
kfree(est);
return 0;
}
static void therm_est_shutdown(struct platform_device *pdev)
{
struct therm_estimator *est = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&est->therm_est_work);
cancel_delayed_work_sync(&est->timer_trip_work);
thermal_zone_device_unregister(est->thz);
thermal_cooling_device_unregister(est->cdev);
}
static struct platform_driver therm_est_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "therm_est",
},
.probe = therm_est_probe,
.remove = therm_est_remove,
.shutdown = therm_est_shutdown,
};
static int __init therm_est_driver_init(void)
{
return platform_driver_register(&therm_est_driver);
}
module_init(therm_est_driver_init);