OpenCloudOS-Kernel/drivers/thermal/thermal_helpers.c

262 lines
6.6 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* thermal_helpers.c - helper functions to handle thermal devices
*
* Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com>
*
* Highly based on original thermal_core.c
* Copyright (C) 2008 Intel Corp
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <trace/events/thermal.h>
#include "thermal_core.h"
int get_tz_trend(struct thermal_zone_device *tz, int trip)
{
enum thermal_trend trend;
if (tz->emul_temperature || !tz->ops->get_trend ||
tz->ops->get_trend(tz, trip, &trend)) {
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;
}
return trend;
}
EXPORT_SYMBOL(get_tz_trend);
struct thermal_instance *
get_thermal_instance(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int trip)
{
struct thermal_instance *pos = NULL;
struct thermal_instance *target_instance = NULL;
mutex_lock(&tz->lock);
mutex_lock(&cdev->lock);
list_for_each_entry(pos, &tz->thermal_instances, tz_node) {
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) {
target_instance = pos;
break;
}
}
mutex_unlock(&cdev->lock);
mutex_unlock(&tz->lock);
return target_instance;
}
EXPORT_SYMBOL(get_thermal_instance);
/**
* thermal_zone_get_temp() - returns the temperature of a thermal zone
* @tz: a valid pointer to a struct thermal_zone_device
* @temp: a valid pointer to where to store the resulting temperature.
*
* When a valid thermal zone reference is passed, it will fetch its
* temperature and fill @temp.
*
* Return: On success returns 0, an error code otherwise
*/
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp)
{
int ret = -EINVAL;
int count;
int crit_temp = INT_MAX;
enum thermal_trip_type type;
if (!tz || IS_ERR(tz) || !tz->ops->get_temp)
goto exit;
mutex_lock(&tz->lock);
ret = tz->ops->get_temp(tz, temp);
if (IS_ENABLED(CONFIG_THERMAL_EMULATION) && tz->emul_temperature) {
for (count = 0; count < tz->trips; count++) {
ret = tz->ops->get_trip_type(tz, count, &type);
if (!ret && type == THERMAL_TRIP_CRITICAL) {
ret = tz->ops->get_trip_temp(tz, count,
&crit_temp);
break;
}
}
/*
* Only allow emulating a temperature when the real temperature
* is below the critical temperature so that the emulation code
* cannot hide critical conditions.
*/
if (!ret && *temp < crit_temp)
*temp = tz->emul_temperature;
}
mutex_unlock(&tz->lock);
exit:
return ret;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
/**
* thermal_zone_set_trips - Computes the next trip points for the driver
* @tz: a pointer to a thermal zone device structure
*
* The function computes the next temperature boundaries by browsing
* the trip points. The result is the closer low and high trip points
* to the current temperature. These values are passed to the backend
* driver to let it set its own notification mechanism (usually an
* interrupt).
*
* It does not return a value
*/
void thermal_zone_set_trips(struct thermal_zone_device *tz)
{
int low = -INT_MAX;
int high = INT_MAX;
int trip_temp, hysteresis;
int i, ret;
mutex_lock(&tz->lock);
if (!tz->ops->set_trips || !tz->ops->get_trip_hyst)
goto exit;
for (i = 0; i < tz->trips; i++) {
int trip_low;
tz->ops->get_trip_temp(tz, i, &trip_temp);
tz->ops->get_trip_hyst(tz, i, &hysteresis);
trip_low = trip_temp - hysteresis;
if (trip_low < tz->temperature && trip_low > low)
low = trip_low;
if (trip_temp > tz->temperature && trip_temp < high)
high = trip_temp;
}
/* No need to change trip points */
if (tz->prev_low_trip == low && tz->prev_high_trip == high)
goto exit;
tz->prev_low_trip = low;
tz->prev_high_trip = high;
dev_dbg(&tz->device,
"new temperature boundaries: %d < x < %d\n", low, high);
/*
* Set a temperature window. When this window is left the driver
* must inform the thermal core via thermal_zone_device_update.
*/
ret = tz->ops->set_trips(tz, low, high);
if (ret)
dev_err(&tz->device, "Failed to set trips: %d\n", ret);
exit:
mutex_unlock(&tz->lock);
}
void thermal_set_delay_jiffies(unsigned long *delay_jiffies, int delay_ms)
{
*delay_jiffies = msecs_to_jiffies(delay_ms);
if (delay_ms > 1000)
*delay_jiffies = round_jiffies(*delay_jiffies);
}
static void thermal_cdev_set_cur_state(struct thermal_cooling_device *cdev,
int target)
{
if (cdev->ops->set_cur_state(cdev, target))
return;
thermal_notify_cdev_state_update(cdev->id, target);
thermal_cooling_device_stats_update(cdev, target);
}
void __thermal_cdev_update(struct thermal_cooling_device *cdev)
{
struct thermal_instance *instance;
unsigned long target = 0;
/* Make sure cdev enters the deepest cooling state */
list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
dev_dbg(&cdev->device, "zone%d->target=%lu\n",
instance->tz->id, instance->target);
if (instance->target == THERMAL_NO_TARGET)
continue;
if (instance->target > target)
target = instance->target;
}
thermal: Add cooling device's statistics in sysfs This extends the sysfs interface for thermal cooling devices and exposes some pretty useful statistics. These statistics have proven to be quite useful specially while doing benchmarks related to the task scheduler, where we want to make sure that nothing has disrupted the test, specially the cooling device which may have put constraints on the CPUs. The information exposed here tells us to what extent the CPUs were constrained by the thermal framework. The write-only "reset" file is used to reset the statistics. The read-only "time_in_state_ms" file shows the time (in msec) spent by the device in the respective cooling states, and it prints one line per cooling state. The read-only "total_trans" file shows single positive integer value showing the total number of cooling state transitions the device has gone through since the time the cooling device is registered or the time when statistics were reset last. The read-only "trans_table" file shows a two dimensional matrix, where an entry <i,j> (row i, column j) represents the number of transitions from State_i to State_j. This is how the directory structure looks like for a single cooling device: $ ls -R /sys/class/thermal/cooling_device0/ /sys/class/thermal/cooling_device0/: cur_state max_state power stats subsystem type uevent /sys/class/thermal/cooling_device0/power: autosuspend_delay_ms runtime_active_time runtime_suspended_time control runtime_status /sys/class/thermal/cooling_device0/stats: reset time_in_state_ms total_trans trans_table This is tested on ARM 64-bit Hisilicon hikey620 board running Ubuntu and ARM 64-bit Hisilicon hikey960 board running Android. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2018-04-02 18:56:25 +08:00
thermal_cdev_set_cur_state(cdev, target);
thermal: Add cooling device's statistics in sysfs This extends the sysfs interface for thermal cooling devices and exposes some pretty useful statistics. These statistics have proven to be quite useful specially while doing benchmarks related to the task scheduler, where we want to make sure that nothing has disrupted the test, specially the cooling device which may have put constraints on the CPUs. The information exposed here tells us to what extent the CPUs were constrained by the thermal framework. The write-only "reset" file is used to reset the statistics. The read-only "time_in_state_ms" file shows the time (in msec) spent by the device in the respective cooling states, and it prints one line per cooling state. The read-only "total_trans" file shows single positive integer value showing the total number of cooling state transitions the device has gone through since the time the cooling device is registered or the time when statistics were reset last. The read-only "trans_table" file shows a two dimensional matrix, where an entry <i,j> (row i, column j) represents the number of transitions from State_i to State_j. This is how the directory structure looks like for a single cooling device: $ ls -R /sys/class/thermal/cooling_device0/ /sys/class/thermal/cooling_device0/: cur_state max_state power stats subsystem type uevent /sys/class/thermal/cooling_device0/power: autosuspend_delay_ms runtime_active_time runtime_suspended_time control runtime_status /sys/class/thermal/cooling_device0/stats: reset time_in_state_ms total_trans trans_table This is tested on ARM 64-bit Hisilicon hikey620 board running Ubuntu and ARM 64-bit Hisilicon hikey960 board running Android. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2018-04-02 18:56:25 +08:00
trace_cdev_update(cdev, target);
dev_dbg(&cdev->device, "set to state %lu\n", target);
}
/**
* thermal_cdev_update - update cooling device state if needed
* @cdev: pointer to struct thermal_cooling_device
*
* Update the cooling device state if there is a need.
*/
void thermal_cdev_update(struct thermal_cooling_device *cdev)
{
mutex_lock(&cdev->lock);
if (!cdev->updated) {
__thermal_cdev_update(cdev);
cdev->updated = true;
}
mutex_unlock(&cdev->lock);
}
EXPORT_SYMBOL(thermal_cdev_update);
/**
* thermal_zone_get_slope - return the slope attribute of the thermal zone
* @tz: thermal zone device with the slope attribute
*
* Return: If the thermal zone device has a slope attribute, return it, else
* return 1.
*/
int thermal_zone_get_slope(struct thermal_zone_device *tz)
{
if (tz && tz->tzp)
return tz->tzp->slope;
return 1;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
/**
* thermal_zone_get_offset - return the offset attribute of the thermal zone
* @tz: thermal zone device with the offset attribute
*
* Return: If the thermal zone device has a offset attribute, return it, else
* return 0.
*/
int thermal_zone_get_offset(struct thermal_zone_device *tz)
{
if (tz && tz->tzp)
return tz->tzp->offset;
return 0;
}
EXPORT_SYMBOL_GPL(thermal_zone_get_offset);