blob: 1d926dca22bb305d92c9dc53ebae93b875461aae [file] [log] [blame]
/*
* drivers/thermal/pid_thermal_gov.c
*
* Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
* This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kobject.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/pid_thermal_gov.h>
#include "thermal_core.h"
#define DRV_NAME "pid_thermal_gov"
#define MAX_ERR_TEMP_DEFAULT 9000 /* in mC */
#define MAX_ERR_GAIN_DEFAULT 1000
#define GAIN_P_DEFAULT 1000
#define GAIN_D_DEFAULT 0
#define UP_COMPENSATION_DEFAULT 20
#define DOWN_COMPENSATION_DEFAULT 20
struct pid_thermal_gov_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
struct pid_thermal_governor {
struct kobject kobj;
int max_err_temp; /* max error temperature in mC */
int max_err_gain; /* max error gain */
int gain_p; /* proportional gain */
int gain_i; /* integral gain */
int gain_d; /* derivative gain */
/* max derivative output, percentage of max error */
unsigned long max_dout;
unsigned long up_compensation;
unsigned long down_compensation;
};
#define tz_to_gov(t) \
(t->governor_data)
#define kobj_to_gov(k) \
container_of(k, struct pid_thermal_governor, kobj)
#define attr_to_gov_attr(a) \
container_of(a, struct pid_thermal_gov_attribute, attr)
static ssize_t max_err_temp_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%d\n", gov->max_err_temp);
}
static ssize_t max_err_temp_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
int val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%d\n", &val))
return -EINVAL;
gov->max_err_temp = val;
return count;
}
static struct pid_thermal_gov_attribute max_err_temp_attr =
__ATTR(max_err_temp, 0644, max_err_temp_show, max_err_temp_store);
static ssize_t max_err_gain_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%d\n", gov->max_err_gain);
}
static ssize_t max_err_gain_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
int val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%d\n", &val))
return -EINVAL;
gov->max_err_gain = val;
return count;
}
static struct pid_thermal_gov_attribute max_err_gain_attr =
__ATTR(max_err_gain, 0644, max_err_gain_show, max_err_gain_store);
static ssize_t max_dout_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%lu\n", gov->max_dout);
}
static ssize_t max_dout_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
unsigned long val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%lu\n", &val))
return -EINVAL;
gov->max_dout = val;
return count;
}
static struct pid_thermal_gov_attribute max_dout_attr =
__ATTR(max_dout, 0644, max_dout_show, max_dout_store);
static ssize_t gain_p_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%d\n", gov->gain_p);
}
static ssize_t gain_p_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
int val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%d\n", &val))
return -EINVAL;
gov->gain_p = val;
return count;
}
static struct pid_thermal_gov_attribute gain_p_attr =
__ATTR(gain_p, 0644, gain_p_show, gain_p_store);
static ssize_t gain_d_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%d\n", gov->gain_d);
}
static ssize_t gain_d_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
int val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%d\n", &val))
return -EINVAL;
gov->gain_d = val;
return count;
}
static struct pid_thermal_gov_attribute gain_d_attr =
__ATTR(gain_d, 0644, gain_d_show, gain_d_store);
static ssize_t up_compensation_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%lu\n", gov->up_compensation);
}
static ssize_t up_compensation_store(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
unsigned long val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%lu\n", &val))
return -EINVAL;
gov->up_compensation = val;
return count;
}
static struct pid_thermal_gov_attribute up_compensation_attr =
__ATTR(up_compensation, 0644,
up_compensation_show, up_compensation_store);
static ssize_t down_compensation_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
if (!gov)
return -ENODEV;
return sprintf(buf, "%lu\n", gov->down_compensation);
}
static ssize_t down_compensation_store(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t count)
{
struct pid_thermal_governor *gov = kobj_to_gov(kobj);
unsigned long val;
if (!gov)
return -ENODEV;
if (!sscanf(buf, "%lu\n", &val))
return -EINVAL;
gov->down_compensation = val;
return count;
}
static struct pid_thermal_gov_attribute down_compensation_attr =
__ATTR(down_compensation, 0644,
down_compensation_show, down_compensation_store);
static struct attribute *pid_thermal_gov_default_attrs[] = {
&max_err_temp_attr.attr,
&max_err_gain_attr.attr,
&gain_p_attr.attr,
&gain_d_attr.attr,
&max_dout_attr.attr,
&up_compensation_attr.attr,
&down_compensation_attr.attr,
NULL,
};
static ssize_t pid_thermal_gov_show(struct kobject *kobj,
struct attribute *attr, char *buf)
{
struct pid_thermal_gov_attribute *gov_attr = attr_to_gov_attr(attr);
if (!gov_attr->show)
return -EIO;
return gov_attr->show(kobj, attr, buf);
}
static ssize_t pid_thermal_gov_store(struct kobject *kobj,
struct attribute *attr, const char *buf,
size_t len)
{
struct pid_thermal_gov_attribute *gov_attr = attr_to_gov_attr(attr);
if (!gov_attr->store)
return -EIO;
return gov_attr->store(kobj, attr, buf, len);
}
static const struct sysfs_ops pid_thermal_gov_sysfs_ops = {
.show = pid_thermal_gov_show,
.store = pid_thermal_gov_store,
};
static struct kobj_type pid_thermal_gov_ktype = {
.default_attrs = pid_thermal_gov_default_attrs,
.sysfs_ops = &pid_thermal_gov_sysfs_ops,
};
static int pid_thermal_gov_start(struct thermal_zone_device *tz)
{
struct pid_thermal_governor *gov;
struct pid_thermal_gov_params *params;
int ret;
gov = kzalloc(sizeof(struct pid_thermal_governor), GFP_KERNEL);
if (!gov) {
dev_err(&tz->device, "%s: Can't alloc governor data\n",
DRV_NAME);
return -ENOMEM;
}
ret = kobject_init_and_add(&gov->kobj, &pid_thermal_gov_ktype,
&tz->device.kobj, DRV_NAME);
if (ret) {
dev_err(&tz->device, "%s: Can't init kobject\n", DRV_NAME);
kobject_put(&gov->kobj);
kfree(gov);
return ret;
}
if (tz->tzp->governor_params) {
params = (struct pid_thermal_gov_params *)
tz->tzp->governor_params;
gov->max_err_temp = params->max_err_temp;
gov->max_err_gain = params->max_err_gain;
gov->gain_p = params->gain_p;
gov->gain_d = params->gain_d;
gov->up_compensation = params->up_compensation;
gov->down_compensation = params->down_compensation;
} else {
gov->max_err_temp = MAX_ERR_TEMP_DEFAULT;
gov->max_err_gain = MAX_ERR_GAIN_DEFAULT;
gov->gain_p = GAIN_P_DEFAULT;
gov->gain_d = GAIN_D_DEFAULT;
gov->up_compensation = UP_COMPENSATION_DEFAULT;
gov->down_compensation = DOWN_COMPENSATION_DEFAULT;
}
tz->governor_data = gov;
return 0;
}
static void pid_thermal_gov_stop(struct thermal_zone_device *tz)
{
struct pid_thermal_governor *gov = tz_to_gov(tz);
if (!gov)
return;
kobject_put(&gov->kobj);
kfree(gov);
}
static void pid_thermal_gov_update_passive(struct thermal_zone_device *tz,
enum thermal_trip_type trip_type,
unsigned long old,
unsigned long new)
{
if ((trip_type != THERMAL_TRIP_PASSIVE) &&
(trip_type != THERMAL_TRIPS_NONE))
return;
if ((old == THERMAL_NO_TARGET) && (new != THERMAL_NO_TARGET))
tz->passive++;
else if ((old != THERMAL_NO_TARGET) && (new == THERMAL_NO_TARGET))
tz->passive--;
}
static unsigned long
pid_thermal_gov_get_target(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev,
enum thermal_trip_type trip_type,
unsigned long trip_temp)
{
struct pid_thermal_governor *gov = tz_to_gov(tz);
int last_temperature = tz->passive ? tz->last_temperature : trip_temp;
int passive_delay = tz->passive ? tz->passive_delay : MSEC_PER_SEC;
s64 proportional, derivative, sum_err, max_err;
unsigned long max_state, cur_state, target, compensation;
if (cdev->ops->get_max_state(cdev, &max_state) < 0)
return 0;
if (cdev->ops->get_cur_state(cdev, &cur_state) < 0)
return 0;
max_err = (s64)gov->max_err_temp * (s64)gov->max_err_gain;
/* Calculate proportional term */
proportional = (s64)tz->temperature - (s64)trip_temp;
proportional *= gov->gain_p;
/* Calculate derivative term */
derivative = (s64)tz->temperature - (s64)last_temperature;
derivative *= gov->gain_d;
derivative *= MSEC_PER_SEC;
derivative = div64_s64(derivative, passive_delay);
if (gov->max_dout) {
s64 max_dout = div64_s64(max_err * gov->max_dout, 100);
if (derivative < 0)
derivative = -min_t(s64, abs64(derivative), max_dout);
else
derivative = min_t(s64, derivative, max_dout);
}
sum_err = max_t(s64, proportional + derivative, 0);
sum_err = min_t(s64, sum_err, max_err);
sum_err = sum_err * max_state + max_err - 1;
target = (unsigned long)div64_s64(sum_err, max_err);
/* Apply compensation */
if (target == cur_state)
return target;
if (target > cur_state) {
compensation = DIV_ROUND_UP(gov->up_compensation *
(target - cur_state), 100);
target = min(cur_state + compensation, max_state);
} else if (target < cur_state) {
compensation = DIV_ROUND_UP(gov->down_compensation *
(cur_state - target), 100);
target = (cur_state > compensation) ?
(cur_state - compensation) : 0;
}
return target;
}
static int pid_thermal_gov_throttle(struct thermal_zone_device *tz, int trip)
{
struct thermal_instance *instance;
enum thermal_trip_type trip_type;
long trip_temp;
unsigned long target;
tz->ops->get_trip_type(tz, trip, &trip_type);
tz->ops->get_trip_temp(tz, trip, &trip_temp);
mutex_lock(&tz->lock);
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
if ((instance->trip != trip) ||
((tz->temperature < trip_temp) &&
(instance->target == THERMAL_NO_TARGET)))
continue;
target = pid_thermal_gov_get_target(tz, instance->cdev,
trip_type, trip_temp);
if (target >= instance->upper)
target = instance->upper;
else if (target < instance->lower)
target = instance->lower;
if ((tz->temperature < trip_temp) &&
(instance->target == instance->lower) &&
(target == instance->lower))
target = THERMAL_NO_TARGET;
if (instance->target == target)
continue;
pid_thermal_gov_update_passive(tz, trip_type, instance->target,
target);
instance->target = target;
instance->cdev->updated = false;
}
list_for_each_entry(instance, &tz->thermal_instances, tz_node)
thermal_cdev_update(instance->cdev);
mutex_unlock(&tz->lock);
return 0;
}
static struct thermal_governor pid_thermal_gov = {
.name = DRV_NAME,
.start = pid_thermal_gov_start,
.stop = pid_thermal_gov_stop,
.throttle = pid_thermal_gov_throttle,
};
int pid_thermal_gov_register(void)
{
return thermal_register_governor(&pid_thermal_gov);
}
void pid_thermal_gov_unregister(void)
{
thermal_unregister_governor(&pid_thermal_gov);
}