541 lines
14 KiB
C
541 lines
14 KiB
C
|
/*
|
||
|
* drivers/thermal/pid_thermal_gov.c
|
||
|
*
|
||
|
* Copyright (c) 2013-2017, 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 MAX_DOUT_DEFAULT 0
|
||
|
#define UP_COMPENSATION_DEFAULT 20
|
||
|
#define DOWN_COMPENSATION_DEFAULT 20
|
||
|
|
||
|
static struct pid_thermal_gov_params pm_default = {
|
||
|
.max_err_temp = MAX_ERR_TEMP_DEFAULT,
|
||
|
.max_err_gain = MAX_ERR_GAIN_DEFAULT,
|
||
|
.gain_p = GAIN_P_DEFAULT,
|
||
|
.gain_d = GAIN_D_DEFAULT,
|
||
|
.max_dout = MAX_DOUT_DEFAULT,
|
||
|
.up_compensation = UP_COMPENSATION_DEFAULT,
|
||
|
.down_compensation = DOWN_COMPENSATION_DEFAULT,
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
struct pid_thermal_gov_params pm;
|
||
|
};
|
||
|
|
||
|
#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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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->pm.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_bind(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;
|
||
|
}
|
||
|
|
||
|
params = (struct pid_thermal_gov_params *)tz->tzp->governor_params;
|
||
|
gov->pm = params ? *params : pm_default;
|
||
|
tz_to_gov(tz) = gov;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void pid_thermal_gov_unbind(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,
|
||
|
int 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->pm.max_err_temp * (s64)gov->pm.max_err_gain;
|
||
|
|
||
|
/* Calculate proportional term */
|
||
|
proportional = (s64)tz->temperature - (s64)trip_temp;
|
||
|
proportional *= gov->pm.gain_p;
|
||
|
|
||
|
/* Calculate derivative term */
|
||
|
derivative = (s64)tz->temperature - (s64)last_temperature;
|
||
|
derivative *= gov->pm.gain_d;
|
||
|
derivative *= MSEC_PER_SEC;
|
||
|
derivative = div64_s64(derivative, passive_delay);
|
||
|
if (gov->pm.max_dout) {
|
||
|
s64 max_dout = div64_s64(max_err * gov->pm.max_dout, 100);
|
||
|
if (derivative < 0)
|
||
|
derivative = -min_t(s64, abs(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->pm.up_compensation *
|
||
|
(target - cur_state), 100);
|
||
|
target = min(cur_state + compensation, max_state);
|
||
|
} else if (target < cur_state) {
|
||
|
compensation = DIV_ROUND_UP(gov->pm.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;
|
||
|
int trip_temp, hyst = 0;
|
||
|
unsigned long target;
|
||
|
|
||
|
tz->ops->get_trip_type(tz, trip, &trip_type);
|
||
|
tz->ops->get_trip_temp(tz, trip, &trip_temp);
|
||
|
if (tz->ops->get_trip_hyst)
|
||
|
tz->ops->get_trip_hyst(tz, trip, &hyst);
|
||
|
|
||
|
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;
|
||
|
|
||
|
if (instance->upper == instance->lower) {
|
||
|
target = instance->upper;
|
||
|
} else {
|
||
|
target = pid_thermal_gov_get_target(tz, instance->cdev,
|
||
|
trip_type, trip_temp);
|
||
|
target = min(max(target, instance->lower),
|
||
|
instance->upper);
|
||
|
}
|
||
|
|
||
|
if ((tz->temperature < trip_temp - hyst) &&
|
||
|
(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 int pid_thermal_gov_of_parse(struct thermal_zone_params *tzp,
|
||
|
struct device_node *np)
|
||
|
{
|
||
|
u32 val;
|
||
|
struct pid_thermal_gov_params *gpm;
|
||
|
|
||
|
gpm = kzalloc(sizeof(struct pid_thermal_gov_params), GFP_KERNEL);
|
||
|
if (!gpm)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
*gpm = pm_default; /* start with default parameters */
|
||
|
|
||
|
/* parse and populate governor_params explicitly specified in DT */
|
||
|
if (!of_property_read_u32(np, "max_err_temp", &val))
|
||
|
gpm->max_err_temp = val;
|
||
|
if (!of_property_read_u32(np, "max_err_gain", &val))
|
||
|
gpm->max_err_gain = val;
|
||
|
if (!of_property_read_u32(np, "gain_p", &val))
|
||
|
gpm->gain_p = val;
|
||
|
if (!of_property_read_u32(np, "gain_d", &val))
|
||
|
gpm->gain_d = val;
|
||
|
if (!of_property_read_u32(np, "max_dout", &val))
|
||
|
gpm->max_dout = val;
|
||
|
if (!of_property_read_u32(np, "up_compensation", &val))
|
||
|
gpm->up_compensation = val;
|
||
|
if (!of_property_read_u32(np, "down_compensation", &val))
|
||
|
gpm->down_compensation = val;
|
||
|
|
||
|
tzp->governor_params = gpm;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct thermal_governor pid_thermal_gov = {
|
||
|
.name = DRV_NAME,
|
||
|
.bind_to_tz = pid_thermal_gov_bind,
|
||
|
.unbind_from_tz = pid_thermal_gov_unbind,
|
||
|
.throttle = pid_thermal_gov_throttle,
|
||
|
.of_parse = pid_thermal_gov_of_parse,
|
||
|
};
|
||
|
|
||
|
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);
|
||
|
}
|