mirror of
https://github.com/torvalds/linux.git
synced 2024-12-18 09:02:17 +00:00
Merge branches 'thermal-core', 'thermal-intel', 'thermal-soc-fixes' and 'thermal-reorg' into next
This commit is contained in:
commit
9245ac20d8
@ -22,10 +22,13 @@ Required properties:
|
||||
TSADC controller.
|
||||
- pinctrl-2 : The "sleep" pinctrl state, it will be in for suspend.
|
||||
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
|
||||
|
||||
Optional properties:
|
||||
- rockchip,hw-tshut-temp : The hardware-controlled shutdown temperature value.
|
||||
- rockchip,hw-tshut-mode : The hardware-controlled shutdown mode 0:CRU 1:GPIO.
|
||||
- rockchip,hw-tshut-polarity : The hardware-controlled active polarity 0:LOW
|
||||
1:HIGH.
|
||||
- rockchip,grf : The phandle of the syscon node for the general register file.
|
||||
|
||||
Exiample:
|
||||
tsadc: tsadc@ff280000 {
|
||||
|
@ -177,8 +177,10 @@ config THERMAL_EMULATION
|
||||
|
||||
config HISI_THERMAL
|
||||
tristate "Hisilicon thermal driver"
|
||||
depends on (ARCH_HISI && CPU_THERMAL && OF) || COMPILE_TEST
|
||||
depends on ARCH_HISI || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
depends on OF
|
||||
default y
|
||||
help
|
||||
Enable this to plug hisilicon's thermal sensor driver into the Linux
|
||||
thermal framework. cpufreq is used as the cooling device to throttle
|
||||
|
@ -3,7 +3,8 @@
|
||||
#
|
||||
|
||||
obj-$(CONFIG_THERMAL) += thermal_sys.o
|
||||
thermal_sys-y += thermal_core.o
|
||||
thermal_sys-y += thermal_core.o thermal_sysfs.o \
|
||||
thermal_helpers.o
|
||||
|
||||
# interface to/from other layers providing sensors
|
||||
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
|
||||
|
@ -512,6 +512,7 @@ static const struct of_device_id db8500_thermal_match[] = {
|
||||
{ .compatible = "stericsson,db8500-thermal" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, db8500_thermal_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver db8500_thermal_driver = {
|
||||
|
@ -238,7 +238,7 @@ get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dfc->power_ops->get_static_power(voltage);
|
||||
return dfc->power_ops->get_static_power(df, voltage);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,7 +262,8 @@ get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
|
||||
struct devfreq_cooling_power *dfc_power = dfc->power_ops;
|
||||
|
||||
if (dfc_power->get_dynamic_power)
|
||||
return dfc_power->get_dynamic_power(freq, voltage);
|
||||
return dfc_power->get_dynamic_power(dfc->devfreq, freq,
|
||||
voltage);
|
||||
|
||||
freq_mhz = freq / 1000000;
|
||||
power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
|
||||
|
@ -29,6 +29,7 @@
|
||||
#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */
|
||||
#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */
|
||||
#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */
|
||||
#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */
|
||||
|
||||
/* Wildcat Point-LP PCH Thermal registers */
|
||||
#define WPT_TEMP 0x0000 /* Temperature */
|
||||
@ -273,37 +274,44 @@ static struct thermal_zone_device_ops tzd_ops = {
|
||||
.get_trip_temp = pch_get_trip_temp,
|
||||
};
|
||||
|
||||
enum board_ids {
|
||||
board_hsw,
|
||||
board_wpt,
|
||||
board_skl,
|
||||
};
|
||||
|
||||
static const struct board_info {
|
||||
const char *name;
|
||||
const struct pch_dev_ops *ops;
|
||||
} board_info[] = {
|
||||
[board_hsw] = {
|
||||
.name = "pch_haswell",
|
||||
.ops = &pch_dev_ops_wpt,
|
||||
},
|
||||
[board_wpt] = {
|
||||
.name = "pch_wildcat_point",
|
||||
.ops = &pch_dev_ops_wpt,
|
||||
},
|
||||
[board_skl] = {
|
||||
.name = "pch_skylake",
|
||||
.ops = &pch_dev_ops_wpt,
|
||||
},
|
||||
};
|
||||
|
||||
static int intel_pch_thermal_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
enum board_ids board_id = id->driver_data;
|
||||
const struct board_info *bi = &board_info[board_id];
|
||||
struct pch_thermal_device *ptd;
|
||||
int err;
|
||||
int nr_trips;
|
||||
char *dev_name;
|
||||
|
||||
ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
|
||||
if (!ptd)
|
||||
return -ENOMEM;
|
||||
|
||||
switch (pdev->device) {
|
||||
case PCH_THERMAL_DID_WPT:
|
||||
ptd->ops = &pch_dev_ops_wpt;
|
||||
dev_name = "pch_wildcat_point";
|
||||
break;
|
||||
case PCH_THERMAL_DID_SKL:
|
||||
ptd->ops = &pch_dev_ops_wpt;
|
||||
dev_name = "pch_skylake";
|
||||
break;
|
||||
case PCH_THERMAL_DID_HSW_1:
|
||||
case PCH_THERMAL_DID_HSW_2:
|
||||
ptd->ops = &pch_dev_ops_wpt;
|
||||
dev_name = "pch_haswell";
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "unknown pch thermal device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
ptd->ops = bi->ops;
|
||||
|
||||
pci_set_drvdata(pdev, ptd);
|
||||
ptd->pdev = pdev;
|
||||
@ -331,11 +339,11 @@ static int intel_pch_thermal_probe(struct pci_dev *pdev,
|
||||
if (err)
|
||||
goto error_cleanup;
|
||||
|
||||
ptd->tzd = thermal_zone_device_register(dev_name, nr_trips, 0, ptd,
|
||||
ptd->tzd = thermal_zone_device_register(bi->name, nr_trips, 0, ptd,
|
||||
&tzd_ops, NULL, 0, 0);
|
||||
if (IS_ERR(ptd->tzd)) {
|
||||
dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
|
||||
dev_name);
|
||||
bi->name);
|
||||
err = PTR_ERR(ptd->tzd);
|
||||
goto error_cleanup;
|
||||
}
|
||||
@ -380,10 +388,16 @@ static int intel_pch_thermal_resume(struct device *device)
|
||||
}
|
||||
|
||||
static struct pci_device_id intel_pch_thermal_id[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
|
||||
.driver_data = board_hsw, },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
|
||||
.driver_data = board_hsw, },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
|
||||
.driver_data = board_wpt, },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
|
||||
.driver_data = board_skl, },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
|
||||
.driver_data = board_skl, },
|
||||
{ 0, },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
|
||||
|
@ -149,6 +149,7 @@ static struct platform_device_id max77620_thermal_devtype[] = {
|
||||
{ .name = "max77620-thermal", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, max77620_thermal_devtype);
|
||||
|
||||
static struct platform_driver max77620_thermal_driver = {
|
||||
.driver = {
|
||||
|
@ -200,7 +200,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
|
||||
struct qpnp_tm_chip *chip;
|
||||
struct device_node *node;
|
||||
u8 type, subtype;
|
||||
u32 res[2];
|
||||
u32 res;
|
||||
int ret, irq;
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
@ -215,7 +215,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
|
||||
if (!chip->map)
|
||||
return -ENXIO;
|
||||
|
||||
ret = of_property_read_u32_array(node, "reg", res, 2);
|
||||
ret = of_property_read_u32(node, "reg", &res);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@ -228,7 +228,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
|
||||
if (PTR_ERR(chip->adc) == -EPROBE_DEFER)
|
||||
return PTR_ERR(chip->adc);
|
||||
|
||||
chip->base = res[0];
|
||||
chip->base = res;
|
||||
|
||||
ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
|
||||
if (ret < 0) {
|
||||
|
@ -524,11 +524,6 @@ static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs,
|
||||
regs + TSADCV2_AUTO_PERIOD_HT);
|
||||
writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
|
||||
regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE);
|
||||
|
||||
if (IS_ERR(grf)) {
|
||||
pr_warn("%s: Missing rockchip,grf property\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -971,6 +966,8 @@ static int rockchip_configure_from_dt(struct device *dev,
|
||||
* need this property.
|
||||
*/
|
||||
thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
||||
if (IS_ERR(thermal->grf))
|
||||
dev_warn(dev, "Missing rockchip,grf property\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ static const struct of_device_id tango_sensor_ids[] = {
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tango_sensor_ids);
|
||||
|
||||
static struct platform_driver tango_thermal_driver = {
|
||||
.probe = tango_thermal_probe,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -54,8 +54,34 @@ struct thermal_instance {
|
||||
unsigned int weight; /* The weight of the cooling device */
|
||||
};
|
||||
|
||||
#define to_thermal_zone(_dev) \
|
||||
container_of(_dev, struct thermal_zone_device, device)
|
||||
|
||||
#define to_cooling_device(_dev) \
|
||||
container_of(_dev, struct thermal_cooling_device, device)
|
||||
|
||||
int thermal_register_governor(struct thermal_governor *);
|
||||
void thermal_unregister_governor(struct thermal_governor *);
|
||||
void thermal_zone_device_rebind_exception(struct thermal_zone_device *,
|
||||
const char *, size_t);
|
||||
void thermal_zone_device_unbind_exception(struct thermal_zone_device *,
|
||||
const char *, size_t);
|
||||
int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
|
||||
int thermal_build_list_of_policies(char *buf);
|
||||
|
||||
/* sysfs I/F */
|
||||
int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
|
||||
void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *);
|
||||
/* used only at binding time */
|
||||
ssize_t
|
||||
thermal_cooling_device_trip_point_show(struct device *,
|
||||
struct device_attribute *, char *);
|
||||
ssize_t thermal_cooling_device_weight_show(struct device *,
|
||||
struct device_attribute *, char *);
|
||||
|
||||
ssize_t thermal_cooling_device_weight_store(struct device *,
|
||||
struct device_attribute *,
|
||||
const char *, size_t);
|
||||
|
||||
#ifdef CONFIG_THERMAL_GOV_STEP_WISE
|
||||
int thermal_gov_step_wise_register(void);
|
||||
|
226
drivers/thermal/thermal_helpers.c
Normal file
226
drivers/thermal/thermal_helpers.c
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.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);
|
||||
|
||||
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);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_set_trips);
|
||||
|
||||
void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct thermal_instance *instance;
|
||||
unsigned long target = 0;
|
||||
|
||||
mutex_lock(&cdev->lock);
|
||||
/* cooling device is updated*/
|
||||
if (cdev->updated) {
|
||||
mutex_unlock(&cdev->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
cdev->ops->set_cur_state(cdev, target);
|
||||
cdev->updated = true;
|
||||
mutex_unlock(&cdev->lock);
|
||||
trace_cdev_update(cdev, target);
|
||||
dev_dbg(&cdev->device, "set to state %lu\n", target);
|
||||
}
|
||||
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);
|
771
drivers/thermal/thermal_sysfs.c
Normal file
771
drivers/thermal/thermal_sysfs.c
Normal file
@ -0,0 +1,771 @@
|
||||
/*
|
||||
* thermal.c - sysfs interface of 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>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
||||
/* sys I/F for thermal zone */
|
||||
|
||||
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);
|
||||
int temperature, ret;
|
||||
|
||||
ret = thermal_zone_get_temp(tz, &temperature);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%d\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) != 1)
|
||||
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;
|
||||
int temperature;
|
||||
|
||||
if (!tz->ops->set_trip_temp)
|
||||
return -EPERM;
|
||||
|
||||
if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (kstrtoint(buf, 10, &temperature))
|
||||
return -EINVAL;
|
||||
|
||||
ret = tz->ops->set_trip_temp(tz, trip, temperature);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
||||
|
||||
return 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;
|
||||
int temperature;
|
||||
|
||||
if (!tz->ops->get_trip_temp)
|
||||
return -EPERM;
|
||||
|
||||
if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
ret = tz->ops->get_trip_temp(tz, trip, &temperature);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%d\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;
|
||||
int temperature;
|
||||
|
||||
if (!tz->ops->set_trip_hyst)
|
||||
return -EPERM;
|
||||
|
||||
if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (kstrtoint(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);
|
||||
|
||||
if (!ret)
|
||||
thermal_zone_set_trips(tz);
|
||||
|
||||
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;
|
||||
int temperature;
|
||||
|
||||
if (!tz->ops->get_trip_hyst)
|
||||
return -EPERM;
|
||||
|
||||
if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
ret = tz->ops->get_trip_hyst(tz, trip, &temperature);
|
||||
|
||||
return ret ? ret : sprintf(buf, "%d\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);
|
||||
int state;
|
||||
|
||||
if (sscanf(buf, "%d\n", &state) != 1)
|
||||
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) {
|
||||
if (!tz->passive_delay)
|
||||
tz->passive_delay = 1000;
|
||||
thermal_zone_device_rebind_exception(tz, "Processor",
|
||||
sizeof("Processor"));
|
||||
} else if (!state && tz->forced_passive) {
|
||||
tz->passive_delay = 0;
|
||||
thermal_zone_device_unbind_exception(tz, "Processor",
|
||||
sizeof("Processor"));
|
||||
}
|
||||
|
||||
tz->forced_passive = state;
|
||||
|
||||
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
||||
|
||||
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 ssize_t
|
||||
policy_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
||||
char name[THERMAL_NAME_LENGTH];
|
||||
int ret;
|
||||
|
||||
snprintf(name, sizeof(name), "%s", buf);
|
||||
|
||||
ret = thermal_zone_device_set_policy(tz, name);
|
||||
if (!ret)
|
||||
ret = count;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
policy_show(struct device *dev, struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", tz->governor->name);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
available_policies_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
return thermal_build_list_of_policies(buf);
|
||||
}
|
||||
|
||||
#if (IS_ENABLED(CONFIG_THERMAL_EMULATION))
|
||||
static ssize_t
|
||||
emul_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 ret = 0;
|
||||
int temperature;
|
||||
|
||||
if (kstrtoint(buf, 10, &temperature))
|
||||
return -EINVAL;
|
||||
|
||||
if (!tz->ops->set_emul_temp) {
|
||||
mutex_lock(&tz->lock);
|
||||
tz->emul_temperature = temperature;
|
||||
mutex_unlock(&tz->lock);
|
||||
} else {
|
||||
ret = tz->ops->set_emul_temp(tz, temperature);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
||||
|
||||
return ret ? ret : count;
|
||||
}
|
||||
static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
|
||||
#endif
|
||||
|
||||
static ssize_t
|
||||
sustainable_power_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
||||
|
||||
if (tz->tzp)
|
||||
return sprintf(buf, "%u\n", tz->tzp->sustainable_power);
|
||||
else
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sustainable_power_store(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev);
|
||||
u32 sustainable_power;
|
||||
|
||||
if (!tz->tzp)
|
||||
return -EIO;
|
||||
|
||||
if (kstrtou32(buf, 10, &sustainable_power))
|
||||
return -EINVAL;
|
||||
|
||||
tz->tzp->sustainable_power = sustainable_power;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#define create_s32_tzp_attr(name) \
|
||||
static ssize_t \
|
||||
name##_show(struct device *dev, struct device_attribute *devattr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev); \
|
||||
\
|
||||
if (tz->tzp) \
|
||||
return sprintf(buf, "%d\n", tz->tzp->name); \
|
||||
else \
|
||||
return -EIO; \
|
||||
} \
|
||||
\
|
||||
static ssize_t \
|
||||
name##_store(struct device *dev, struct device_attribute *devattr, \
|
||||
const char *buf, size_t count) \
|
||||
{ \
|
||||
struct thermal_zone_device *tz = to_thermal_zone(dev); \
|
||||
s32 value; \
|
||||
\
|
||||
if (!tz->tzp) \
|
||||
return -EIO; \
|
||||
\
|
||||
if (kstrtos32(buf, 10, &value)) \
|
||||
return -EINVAL; \
|
||||
\
|
||||
tz->tzp->name = value; \
|
||||
\
|
||||
return count; \
|
||||
} \
|
||||
static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, name##_show, name##_store)
|
||||
|
||||
create_s32_tzp_attr(k_po);
|
||||
create_s32_tzp_attr(k_pu);
|
||||
create_s32_tzp_attr(k_i);
|
||||
create_s32_tzp_attr(k_d);
|
||||
create_s32_tzp_attr(integral_cutoff);
|
||||
create_s32_tzp_attr(slope);
|
||||
create_s32_tzp_attr(offset);
|
||||
#undef create_s32_tzp_attr
|
||||
|
||||
/*
|
||||
* These are thermal zone device attributes that will always be present.
|
||||
* All the attributes created for tzp (create_s32_tzp_attr) also are always
|
||||
* present on the sysfs interface.
|
||||
*/
|
||||
static DEVICE_ATTR(type, 0444, type_show, NULL);
|
||||
static DEVICE_ATTR(temp, 0444, temp_show, NULL);
|
||||
static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
|
||||
static DEVICE_ATTR(available_policies, S_IRUGO, available_policies_show, NULL);
|
||||
static DEVICE_ATTR(sustainable_power, S_IWUSR | S_IRUGO, sustainable_power_show,
|
||||
sustainable_power_store);
|
||||
|
||||
/* These thermal zone device attributes are created based on conditions */
|
||||
static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
|
||||
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
|
||||
|
||||
/* These attributes are unconditionally added to a thermal zone */
|
||||
static struct attribute *thermal_zone_dev_attrs[] = {
|
||||
&dev_attr_type.attr,
|
||||
&dev_attr_temp.attr,
|
||||
#if (IS_ENABLED(CONFIG_THERMAL_EMULATION))
|
||||
&dev_attr_emul_temp.attr,
|
||||
#endif
|
||||
&dev_attr_policy.attr,
|
||||
&dev_attr_available_policies.attr,
|
||||
&dev_attr_sustainable_power.attr,
|
||||
&dev_attr_k_po.attr,
|
||||
&dev_attr_k_pu.attr,
|
||||
&dev_attr_k_i.attr,
|
||||
&dev_attr_k_d.attr,
|
||||
&dev_attr_integral_cutoff.attr,
|
||||
&dev_attr_slope.attr,
|
||||
&dev_attr_offset.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group thermal_zone_attribute_group = {
|
||||
.attrs = thermal_zone_dev_attrs,
|
||||
};
|
||||
|
||||
/* We expose mode only if .get_mode is present */
|
||||
static struct attribute *thermal_zone_mode_attrs[] = {
|
||||
&dev_attr_mode.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t thermal_zone_mode_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
int attrno)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct thermal_zone_device *tz;
|
||||
|
||||
tz = container_of(dev, struct thermal_zone_device, device);
|
||||
|
||||
if (tz->ops->get_mode)
|
||||
return attr->mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute_group thermal_zone_mode_attribute_group = {
|
||||
.attrs = thermal_zone_mode_attrs,
|
||||
.is_visible = thermal_zone_mode_is_visible,
|
||||
};
|
||||
|
||||
/* We expose passive only if passive trips are present */
|
||||
static struct attribute *thermal_zone_passive_attrs[] = {
|
||||
&dev_attr_passive.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t thermal_zone_passive_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr,
|
||||
int attrno)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct thermal_zone_device *tz;
|
||||
enum thermal_trip_type trip_type;
|
||||
int count, passive = 0;
|
||||
|
||||
tz = container_of(dev, struct thermal_zone_device, device);
|
||||
|
||||
for (count = 0; count < tz->trips && !passive; count++) {
|
||||
tz->ops->get_trip_type(tz, count, &trip_type);
|
||||
|
||||
if (trip_type == THERMAL_TRIP_PASSIVE)
|
||||
passive = 1;
|
||||
}
|
||||
|
||||
if (!passive)
|
||||
return attr->mode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct attribute_group thermal_zone_passive_attribute_group = {
|
||||
.attrs = thermal_zone_passive_attrs,
|
||||
.is_visible = thermal_zone_passive_is_visible,
|
||||
};
|
||||
|
||||
static const struct attribute_group *thermal_zone_attribute_groups[] = {
|
||||
&thermal_zone_attribute_group,
|
||||
&thermal_zone_mode_attribute_group,
|
||||
&thermal_zone_passive_attribute_group,
|
||||
/* This is not NULL terminated as we create the group dynamically */
|
||||
};
|
||||
|
||||
/**
|
||||
* create_trip_attrs() - create attributes for trip points
|
||||
* @tz: the thermal zone device
|
||||
* @mask: Writeable trip point bitmap.
|
||||
*
|
||||
* helper function to instantiate sysfs entries for every trip
|
||||
* point and its properties of a struct thermal_zone_device.
|
||||
*
|
||||
* Return: 0 on success, the proper error value otherwise.
|
||||
*/
|
||||
static int create_trip_attrs(struct thermal_zone_device *tz, int mask)
|
||||
{
|
||||
struct attribute **attrs;
|
||||
int indx;
|
||||
|
||||
/* This function works only for zones with at least one trip */
|
||||
if (tz->trips <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
tz->trip_type_attrs = kcalloc(tz->trips, sizeof(*tz->trip_type_attrs),
|
||||
GFP_KERNEL);
|
||||
if (!tz->trip_type_attrs)
|
||||
return -ENOMEM;
|
||||
|
||||
tz->trip_temp_attrs = kcalloc(tz->trips, sizeof(*tz->trip_temp_attrs),
|
||||
GFP_KERNEL);
|
||||
if (!tz->trip_temp_attrs) {
|
||||
kfree(tz->trip_type_attrs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (tz->ops->get_trip_hyst) {
|
||||
tz->trip_hyst_attrs = kcalloc(tz->trips,
|
||||
sizeof(*tz->trip_hyst_attrs),
|
||||
GFP_KERNEL);
|
||||
if (!tz->trip_hyst_attrs) {
|
||||
kfree(tz->trip_type_attrs);
|
||||
kfree(tz->trip_temp_attrs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
attrs = kcalloc(tz->trips * 3 + 1, sizeof(*attrs), GFP_KERNEL);
|
||||
if (!attrs) {
|
||||
kfree(tz->trip_type_attrs);
|
||||
kfree(tz->trip_temp_attrs);
|
||||
if (tz->ops->get_trip_hyst)
|
||||
kfree(tz->trip_hyst_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;
|
||||
attrs[indx] = &tz->trip_type_attrs[indx].attr.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 (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) &&
|
||||
mask & (1 << indx)) {
|
||||
tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR;
|
||||
tz->trip_temp_attrs[indx].attr.store =
|
||||
trip_point_temp_store;
|
||||
}
|
||||
attrs[indx + tz->trips] = &tz->trip_temp_attrs[indx].attr.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;
|
||||
}
|
||||
attrs[indx + tz->trips * 2] =
|
||||
&tz->trip_hyst_attrs[indx].attr.attr;
|
||||
}
|
||||
attrs[tz->trips * 3] = NULL;
|
||||
|
||||
tz->trips_attribute_group.attrs = attrs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thermal_zone_create_device_groups(struct thermal_zone_device *tz,
|
||||
int mask)
|
||||
{
|
||||
const struct attribute_group **groups;
|
||||
int i, size, result;
|
||||
|
||||
/* we need one extra for trips and the NULL to terminate the array */
|
||||
size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2;
|
||||
/* This also takes care of API requirement to be NULL terminated */
|
||||
groups = kcalloc(size, sizeof(*groups), GFP_KERNEL);
|
||||
if (!groups)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < size - 2; i++)
|
||||
groups[i] = thermal_zone_attribute_groups[i];
|
||||
|
||||
if (tz->trips) {
|
||||
result = create_trip_attrs(tz, mask);
|
||||
if (result) {
|
||||
kfree(groups);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
groups[size - 2] = &tz->trips_attribute_group;
|
||||
}
|
||||
|
||||
tz->device.groups = groups;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* sys I/F for cooling 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) != 1)
|
||||
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 struct attribute *cooling_device_attrs[] = {
|
||||
&dev_attr_cdev_type.attr,
|
||||
&dev_attr_max_state.attr,
|
||||
&dev_attr_cur_state.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group cooling_device_attr_group = {
|
||||
.attrs = cooling_device_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *cooling_device_attr_groups[] = {
|
||||
&cooling_device_attr_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
cdev->device.groups = cooling_device_attr_groups;
|
||||
}
|
||||
|
||||
/* these helper will be used only at the time of bindig */
|
||||
ssize_t
|
||||
thermal_cooling_device_trip_point_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct thermal_instance *instance;
|
||||
|
||||
instance =
|
||||
container_of(attr, struct thermal_instance, attr);
|
||||
|
||||
if (instance->trip == THERMAL_TRIPS_NONE)
|
||||
return sprintf(buf, "-1\n");
|
||||
else
|
||||
return sprintf(buf, "%d\n", instance->trip);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
thermal_cooling_device_weight_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct thermal_instance *instance;
|
||||
|
||||
instance = container_of(attr, struct thermal_instance, weight_attr);
|
||||
|
||||
return sprintf(buf, "%d\n", instance->weight);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
thermal_cooling_device_weight_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct thermal_instance *instance;
|
||||
int ret, weight;
|
||||
|
||||
ret = kstrtoint(buf, 0, &weight);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
instance = container_of(attr, struct thermal_instance, weight_attr);
|
||||
instance->weight = weight;
|
||||
|
||||
return count;
|
||||
}
|
@ -1298,7 +1298,7 @@ int ti_bandgap_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(bgp->div_clk)) {
|
||||
dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n");
|
||||
ret = PTR_ERR(bgp->div_clk);
|
||||
goto free_irqs;
|
||||
goto put_fclock;
|
||||
}
|
||||
|
||||
for (i = 0; i < bgp->conf->sensor_count; i++) {
|
||||
@ -1430,8 +1430,9 @@ disable_clk:
|
||||
if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
|
||||
clk_disable_unprepare(bgp->fclock);
|
||||
put_clks:
|
||||
clk_put(bgp->fclock);
|
||||
clk_put(bgp->div_clk);
|
||||
put_fclock:
|
||||
clk_put(bgp->fclock);
|
||||
free_irqs:
|
||||
if (TI_BANDGAP_HAS(bgp, TSHUT)) {
|
||||
free_irq(gpio_to_irq(bgp->tshut_gpio), NULL);
|
||||
|
@ -54,37 +54,33 @@ MODULE_PARM_DESC(notify_delay_ms,
|
||||
* is some wrong values returned by cpuid for number of thresholds.
|
||||
*/
|
||||
#define MAX_NUMBER_OF_TRIPS 2
|
||||
/* Limit number of package temp zones */
|
||||
#define MAX_PKG_TEMP_ZONE_IDS 256
|
||||
|
||||
struct phy_dev_entry {
|
||||
struct list_head list;
|
||||
u16 phys_proc_id;
|
||||
u16 first_cpu;
|
||||
u32 tj_max;
|
||||
int ref_cnt;
|
||||
u32 start_pkg_therm_low;
|
||||
u32 start_pkg_therm_high;
|
||||
struct thermal_zone_device *tzone;
|
||||
struct pkg_device {
|
||||
int cpu;
|
||||
bool work_scheduled;
|
||||
u32 tj_max;
|
||||
u32 msr_pkg_therm_low;
|
||||
u32 msr_pkg_therm_high;
|
||||
struct delayed_work work;
|
||||
struct thermal_zone_device *tzone;
|
||||
struct cpumask cpumask;
|
||||
};
|
||||
|
||||
static struct thermal_zone_params pkg_temp_tz_params = {
|
||||
.no_hwmon = true,
|
||||
};
|
||||
|
||||
/* List maintaining number of package instances */
|
||||
static LIST_HEAD(phy_dev_list);
|
||||
static DEFINE_MUTEX(phy_dev_list_mutex);
|
||||
/* Keep track of how many package pointers we allocated in init() */
|
||||
static int max_packages __read_mostly;
|
||||
/* Array of package pointers */
|
||||
static struct pkg_device **packages;
|
||||
/* Serializes interrupt notification, work and hotplug */
|
||||
static DEFINE_SPINLOCK(pkg_temp_lock);
|
||||
/* Protects zone operation in the work function against hotplug removal */
|
||||
static DEFINE_MUTEX(thermal_zone_mutex);
|
||||
|
||||
/* Interrupt to work function schedule queue */
|
||||
static DEFINE_PER_CPU(struct delayed_work, pkg_temp_thermal_threshold_work);
|
||||
|
||||
/* To track if the work is already scheduled on a package */
|
||||
static u8 *pkg_work_scheduled;
|
||||
|
||||
/* Spin lock to prevent races with pkg_work_scheduled */
|
||||
static spinlock_t pkg_work_lock;
|
||||
static u16 max_phy_id;
|
||||
/* The dynamically assigned cpu hotplug state for module_exit() */
|
||||
static enum cpuhp_state pkg_thermal_hp_state __read_mostly;
|
||||
|
||||
/* Debug counters to show using debugfs */
|
||||
static struct dentry *debugfs;
|
||||
@ -116,22 +112,20 @@ err_out:
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static struct phy_dev_entry
|
||||
*pkg_temp_thermal_get_phy_entry(unsigned int cpu)
|
||||
/*
|
||||
* Protection:
|
||||
*
|
||||
* - cpu hotplug: Read serialized by cpu hotplug lock
|
||||
* Write must hold pkg_temp_lock
|
||||
*
|
||||
* - Other callsites: Must hold pkg_temp_lock
|
||||
*/
|
||||
static struct pkg_device *pkg_temp_thermal_get_dev(unsigned int cpu)
|
||||
{
|
||||
u16 phys_proc_id = topology_physical_package_id(cpu);
|
||||
struct phy_dev_entry *phy_ptr;
|
||||
|
||||
mutex_lock(&phy_dev_list_mutex);
|
||||
|
||||
list_for_each_entry(phy_ptr, &phy_dev_list, list)
|
||||
if (phy_ptr->phys_proc_id == phys_proc_id) {
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
return phy_ptr;
|
||||
}
|
||||
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
int pkgid = topology_logical_package_id(cpu);
|
||||
|
||||
if (pkgid >= 0 && pkgid < max_packages)
|
||||
return packages[pkgid];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -141,61 +135,44 @@ static struct phy_dev_entry
|
||||
*/
|
||||
static int get_tj_max(int cpu, u32 *tj_max)
|
||||
{
|
||||
u32 eax, edx;
|
||||
u32 val;
|
||||
u32 eax, edx, val;
|
||||
int err;
|
||||
|
||||
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (err)
|
||||
goto err_ret;
|
||||
else {
|
||||
val = (eax >> 16) & 0xff;
|
||||
if (val)
|
||||
*tj_max = val * 1000;
|
||||
else {
|
||||
err = -EINVAL;
|
||||
goto err_ret;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
err_ret:
|
||||
*tj_max = 0;
|
||||
return err;
|
||||
val = (eax >> 16) & 0xff;
|
||||
*tj_max = val * 1000;
|
||||
|
||||
return val ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
|
||||
{
|
||||
struct pkg_device *pkgdev = tzd->devdata;
|
||||
u32 eax, edx;
|
||||
struct phy_dev_entry *phy_dev_entry;
|
||||
|
||||
phy_dev_entry = tzd->devdata;
|
||||
rdmsr_on_cpu(phy_dev_entry->first_cpu, MSR_IA32_PACKAGE_THERM_STATUS,
|
||||
&eax, &edx);
|
||||
rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx);
|
||||
if (eax & 0x80000000) {
|
||||
*temp = phy_dev_entry->tj_max -
|
||||
((eax >> 16) & 0x7f) * 1000;
|
||||
*temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000;
|
||||
pr_debug("sys_get_curr_temp %d\n", *temp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int sys_get_trip_temp(struct thermal_zone_device *tzd,
|
||||
int trip, int *temp)
|
||||
int trip, int *temp)
|
||||
{
|
||||
u32 eax, edx;
|
||||
struct phy_dev_entry *phy_dev_entry;
|
||||
u32 mask, shift;
|
||||
struct pkg_device *pkgdev = tzd->devdata;
|
||||
unsigned long thres_reg_value;
|
||||
u32 mask, shift, eax, edx;
|
||||
int ret;
|
||||
|
||||
if (trip >= MAX_NUMBER_OF_TRIPS)
|
||||
return -EINVAL;
|
||||
|
||||
phy_dev_entry = tzd->devdata;
|
||||
|
||||
if (trip) {
|
||||
mask = THERM_MASK_THRESHOLD1;
|
||||
shift = THERM_SHIFT_THRESHOLD1;
|
||||
@ -204,14 +181,14 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
|
||||
shift = THERM_SHIFT_THRESHOLD0;
|
||||
}
|
||||
|
||||
ret = rdmsr_on_cpu(phy_dev_entry->first_cpu,
|
||||
MSR_IA32_PACKAGE_THERM_INTERRUPT, &eax, &edx);
|
||||
ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
&eax, &edx);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
|
||||
thres_reg_value = (eax & mask) >> shift;
|
||||
if (thres_reg_value)
|
||||
*temp = phy_dev_entry->tj_max - thres_reg_value * 1000;
|
||||
*temp = pkgdev->tj_max - thres_reg_value * 1000;
|
||||
else
|
||||
*temp = 0;
|
||||
pr_debug("sys_get_trip_temp %d\n", *temp);
|
||||
@ -219,24 +196,20 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
|
||||
int temp)
|
||||
static int
|
||||
sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
|
||||
{
|
||||
u32 l, h;
|
||||
struct phy_dev_entry *phy_dev_entry;
|
||||
u32 mask, shift, intr;
|
||||
struct pkg_device *pkgdev = tzd->devdata;
|
||||
u32 l, h, mask, shift, intr;
|
||||
int ret;
|
||||
|
||||
phy_dev_entry = tzd->devdata;
|
||||
|
||||
if (trip >= MAX_NUMBER_OF_TRIPS || temp >= phy_dev_entry->tj_max)
|
||||
if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max)
|
||||
return -EINVAL;
|
||||
|
||||
ret = rdmsr_on_cpu(phy_dev_entry->first_cpu,
|
||||
MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
&l, &h);
|
||||
ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
&l, &h);
|
||||
if (ret < 0)
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
|
||||
if (trip) {
|
||||
mask = THERM_MASK_THRESHOLD1;
|
||||
@ -252,24 +225,20 @@ static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
|
||||
* When users space sets a trip temperature == 0, which is indication
|
||||
* that, it is no longer interested in receiving notifications.
|
||||
*/
|
||||
if (!temp)
|
||||
if (!temp) {
|
||||
l &= ~intr;
|
||||
else {
|
||||
l |= (phy_dev_entry->tj_max - temp)/1000 << shift;
|
||||
} else {
|
||||
l |= (pkgdev->tj_max - temp)/1000 << shift;
|
||||
l |= intr;
|
||||
}
|
||||
|
||||
return wrmsr_on_cpu(phy_dev_entry->first_cpu,
|
||||
MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
l, h);
|
||||
return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
|
||||
}
|
||||
|
||||
static int sys_get_trip_type(struct thermal_zone_device *thermal,
|
||||
int trip, enum thermal_trip_type *type)
|
||||
static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
|
||||
enum thermal_trip_type *type)
|
||||
{
|
||||
|
||||
*type = THERMAL_TRIP_PASSIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -281,7 +250,7 @@ static struct thermal_zone_device_ops tzone_ops = {
|
||||
.set_trip_temp = sys_set_trip_temp,
|
||||
};
|
||||
|
||||
static bool pkg_temp_thermal_platform_thermal_rate_control(void)
|
||||
static bool pkg_thermal_rate_control(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -289,8 +258,8 @@ static bool pkg_temp_thermal_platform_thermal_rate_control(void)
|
||||
/* Enable threshold interrupt on local package/cpu */
|
||||
static inline void enable_pkg_thres_interrupt(void)
|
||||
{
|
||||
u32 l, h;
|
||||
u8 thres_0, thres_1;
|
||||
u32 l, h;
|
||||
|
||||
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
|
||||
/* only enable/disable if it had valid threshold value */
|
||||
@ -307,271 +276,232 @@ static inline void enable_pkg_thres_interrupt(void)
|
||||
static inline void disable_pkg_thres_interrupt(void)
|
||||
{
|
||||
u32 l, h;
|
||||
|
||||
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
|
||||
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
l & (~THERM_INT_THRESHOLD0_ENABLE) &
|
||||
(~THERM_INT_THRESHOLD1_ENABLE), h);
|
||||
|
||||
l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
|
||||
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
|
||||
}
|
||||
|
||||
static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
|
||||
{
|
||||
__u64 msr_val;
|
||||
struct thermal_zone_device *tzone = NULL;
|
||||
int cpu = smp_processor_id();
|
||||
int phy_id = topology_physical_package_id(cpu);
|
||||
struct phy_dev_entry *phdev = pkg_temp_thermal_get_phy_entry(cpu);
|
||||
bool notify = false;
|
||||
unsigned long flags;
|
||||
struct pkg_device *pkgdev;
|
||||
u64 msr_val, wr_val;
|
||||
|
||||
if (!phdev)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&pkg_work_lock, flags);
|
||||
mutex_lock(&thermal_zone_mutex);
|
||||
spin_lock_irq(&pkg_temp_lock);
|
||||
++pkg_work_cnt;
|
||||
if (unlikely(phy_id > max_phy_id)) {
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
|
||||
pkgdev = pkg_temp_thermal_get_dev(cpu);
|
||||
if (!pkgdev) {
|
||||
spin_unlock_irq(&pkg_temp_lock);
|
||||
mutex_unlock(&thermal_zone_mutex);
|
||||
return;
|
||||
}
|
||||
pkg_work_scheduled[phy_id] = 0;
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
pkgdev->work_scheduled = false;
|
||||
|
||||
rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
|
||||
wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
|
||||
if (wr_val != msr_val) {
|
||||
wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val);
|
||||
tzone = pkgdev->tzone;
|
||||
}
|
||||
|
||||
enable_pkg_thres_interrupt();
|
||||
rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
|
||||
if (msr_val & THERM_LOG_THRESHOLD0) {
|
||||
wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS,
|
||||
msr_val & ~THERM_LOG_THRESHOLD0);
|
||||
notify = true;
|
||||
}
|
||||
if (msr_val & THERM_LOG_THRESHOLD1) {
|
||||
wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS,
|
||||
msr_val & ~THERM_LOG_THRESHOLD1);
|
||||
notify = true;
|
||||
}
|
||||
if (notify) {
|
||||
pr_debug("thermal_zone_device_update\n");
|
||||
thermal_zone_device_update(phdev->tzone,
|
||||
THERMAL_EVENT_UNSPECIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
static int pkg_temp_thermal_platform_thermal_notify(__u64 msr_val)
|
||||
{
|
||||
unsigned long flags;
|
||||
int cpu = smp_processor_id();
|
||||
int phy_id = topology_physical_package_id(cpu);
|
||||
spin_unlock_irq(&pkg_temp_lock);
|
||||
|
||||
/*
|
||||
* When a package is in interrupted state, all CPU's in that package
|
||||
* are in the same interrupt state. So scheduling on any one CPU in
|
||||
* the package is enough and simply return for others.
|
||||
*/
|
||||
spin_lock_irqsave(&pkg_work_lock, flags);
|
||||
++pkg_interrupt_cnt;
|
||||
if (unlikely(phy_id > max_phy_id) || unlikely(!pkg_work_scheduled) ||
|
||||
pkg_work_scheduled[phy_id]) {
|
||||
disable_pkg_thres_interrupt();
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
pkg_work_scheduled[phy_id] = 1;
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
* If tzone is not NULL, then thermal_zone_mutex will prevent the
|
||||
* concurrent removal in the cpu offline callback.
|
||||
*/
|
||||
if (tzone)
|
||||
thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED);
|
||||
|
||||
disable_pkg_thres_interrupt();
|
||||
schedule_delayed_work_on(cpu,
|
||||
&per_cpu(pkg_temp_thermal_threshold_work, cpu),
|
||||
msecs_to_jiffies(notify_delay_ms));
|
||||
return 0;
|
||||
mutex_unlock(&thermal_zone_mutex);
|
||||
}
|
||||
|
||||
static int find_siblings_cpu(int cpu)
|
||||
static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work)
|
||||
{
|
||||
int i;
|
||||
int id = topology_physical_package_id(cpu);
|
||||
unsigned long ms = msecs_to_jiffies(notify_delay_ms);
|
||||
|
||||
for_each_online_cpu(i)
|
||||
if (i != cpu && topology_physical_package_id(i) == id)
|
||||
return i;
|
||||
schedule_delayed_work_on(cpu, work, ms);
|
||||
}
|
||||
|
||||
static int pkg_thermal_notify(u64 msr_val)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
struct pkg_device *pkgdev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pkg_temp_lock, flags);
|
||||
++pkg_interrupt_cnt;
|
||||
|
||||
disable_pkg_thres_interrupt();
|
||||
|
||||
/* Work is per package, so scheduling it once is enough. */
|
||||
pkgdev = pkg_temp_thermal_get_dev(cpu);
|
||||
if (pkgdev && !pkgdev->work_scheduled) {
|
||||
pkgdev->work_scheduled = true;
|
||||
pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&pkg_temp_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkg_temp_thermal_device_add(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
u32 tj_max;
|
||||
struct phy_dev_entry *phy_dev_entry;
|
||||
int thres_count;
|
||||
u32 eax, ebx, ecx, edx;
|
||||
u8 *temp;
|
||||
unsigned long flags;
|
||||
int pkgid = topology_logical_package_id(cpu);
|
||||
u32 tj_max, eax, ebx, ecx, edx;
|
||||
struct pkg_device *pkgdev;
|
||||
int thres_count, err;
|
||||
|
||||
if (pkgid >= max_packages)
|
||||
return -ENOMEM;
|
||||
|
||||
cpuid(6, &eax, &ebx, &ecx, &edx);
|
||||
thres_count = ebx & 0x07;
|
||||
if (!thres_count)
|
||||
return -ENODEV;
|
||||
|
||||
if (topology_physical_package_id(cpu) > MAX_PKG_TEMP_ZONE_IDS)
|
||||
return -ENODEV;
|
||||
|
||||
thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS);
|
||||
|
||||
err = get_tj_max(cpu, &tj_max);
|
||||
if (err)
|
||||
goto err_ret;
|
||||
return err;
|
||||
|
||||
mutex_lock(&phy_dev_list_mutex);
|
||||
pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL);
|
||||
if (!pkgdev)
|
||||
return -ENOMEM;
|
||||
|
||||
phy_dev_entry = kzalloc(sizeof(*phy_dev_entry), GFP_KERNEL);
|
||||
if (!phy_dev_entry) {
|
||||
err = -ENOMEM;
|
||||
goto err_ret_unlock;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&pkg_work_lock, flags);
|
||||
if (topology_physical_package_id(cpu) > max_phy_id)
|
||||
max_phy_id = topology_physical_package_id(cpu);
|
||||
temp = krealloc(pkg_work_scheduled,
|
||||
(max_phy_id+1) * sizeof(u8), GFP_ATOMIC);
|
||||
if (!temp) {
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
err = -ENOMEM;
|
||||
goto err_ret_free;
|
||||
}
|
||||
pkg_work_scheduled = temp;
|
||||
pkg_work_scheduled[topology_physical_package_id(cpu)] = 0;
|
||||
spin_unlock_irqrestore(&pkg_work_lock, flags);
|
||||
|
||||
phy_dev_entry->phys_proc_id = topology_physical_package_id(cpu);
|
||||
phy_dev_entry->first_cpu = cpu;
|
||||
phy_dev_entry->tj_max = tj_max;
|
||||
phy_dev_entry->ref_cnt = 1;
|
||||
phy_dev_entry->tzone = thermal_zone_device_register("x86_pkg_temp",
|
||||
INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn);
|
||||
pkgdev->cpu = cpu;
|
||||
pkgdev->tj_max = tj_max;
|
||||
pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp",
|
||||
thres_count,
|
||||
(thres_count == MAX_NUMBER_OF_TRIPS) ?
|
||||
0x03 : 0x01,
|
||||
phy_dev_entry, &tzone_ops, &pkg_temp_tz_params, 0, 0);
|
||||
if (IS_ERR(phy_dev_entry->tzone)) {
|
||||
err = PTR_ERR(phy_dev_entry->tzone);
|
||||
goto err_ret_free;
|
||||
(thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
|
||||
pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
|
||||
if (IS_ERR(pkgdev->tzone)) {
|
||||
err = PTR_ERR(pkgdev->tzone);
|
||||
kfree(pkgdev);
|
||||
return err;
|
||||
}
|
||||
/* Store MSR value for package thermal interrupt, to restore at exit */
|
||||
rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
&phy_dev_entry->start_pkg_therm_low,
|
||||
&phy_dev_entry->start_pkg_therm_high);
|
||||
|
||||
list_add_tail(&phy_dev_entry->list, &phy_dev_list);
|
||||
pr_debug("pkg_temp_thermal_device_add :phy_id %d cpu %d\n",
|
||||
phy_dev_entry->phys_proc_id, cpu);
|
||||
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low,
|
||||
pkgdev->msr_pkg_therm_high);
|
||||
|
||||
cpumask_set_cpu(cpu, &pkgdev->cpumask);
|
||||
spin_lock_irq(&pkg_temp_lock);
|
||||
packages[pkgid] = pkgdev;
|
||||
spin_unlock_irq(&pkg_temp_lock);
|
||||
return 0;
|
||||
|
||||
err_ret_free:
|
||||
kfree(phy_dev_entry);
|
||||
err_ret_unlock:
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
|
||||
err_ret:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pkg_temp_thermal_device_remove(unsigned int cpu)
|
||||
static int pkg_thermal_cpu_offline(unsigned int cpu)
|
||||
{
|
||||
struct phy_dev_entry *n;
|
||||
u16 phys_proc_id = topology_physical_package_id(cpu);
|
||||
struct phy_dev_entry *phdev =
|
||||
pkg_temp_thermal_get_phy_entry(cpu);
|
||||
struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
|
||||
bool lastcpu, was_target;
|
||||
int target;
|
||||
|
||||
if (!phdev)
|
||||
if (!pkgdev)
|
||||
return 0;
|
||||
|
||||
target = cpumask_any_but(&pkgdev->cpumask, cpu);
|
||||
cpumask_clear_cpu(cpu, &pkgdev->cpumask);
|
||||
lastcpu = target >= nr_cpu_ids;
|
||||
/*
|
||||
* Remove the sysfs files, if this is the last cpu in the package
|
||||
* before doing further cleanups.
|
||||
*/
|
||||
if (lastcpu) {
|
||||
struct thermal_zone_device *tzone = pkgdev->tzone;
|
||||
|
||||
/*
|
||||
* We must protect against a work function calling
|
||||
* thermal_zone_update, after/while unregister. We null out
|
||||
* the pointer under the zone mutex, so the worker function
|
||||
* won't try to call.
|
||||
*/
|
||||
mutex_lock(&thermal_zone_mutex);
|
||||
pkgdev->tzone = NULL;
|
||||
mutex_unlock(&thermal_zone_mutex);
|
||||
|
||||
thermal_zone_device_unregister(tzone);
|
||||
}
|
||||
|
||||
/* Protect against work and interrupts */
|
||||
spin_lock_irq(&pkg_temp_lock);
|
||||
|
||||
/*
|
||||
* Check whether this cpu was the current target and store the new
|
||||
* one. When we drop the lock, then the interrupt notify function
|
||||
* will see the new target.
|
||||
*/
|
||||
was_target = pkgdev->cpu == cpu;
|
||||
pkgdev->cpu = target;
|
||||
|
||||
/*
|
||||
* If this is the last CPU in the package remove the package
|
||||
* reference from the array and restore the interrupt MSR. When we
|
||||
* drop the lock neither the interrupt notify function nor the
|
||||
* worker will see the package anymore.
|
||||
*/
|
||||
if (lastcpu) {
|
||||
packages[topology_logical_package_id(cpu)] = NULL;
|
||||
/* After this point nothing touches the MSR anymore. */
|
||||
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether there is work scheduled and whether the work is
|
||||
* targeted at the outgoing CPU.
|
||||
*/
|
||||
if (pkgdev->work_scheduled && was_target) {
|
||||
/*
|
||||
* To cancel the work we need to drop the lock, otherwise
|
||||
* we might deadlock if the work needs to be flushed.
|
||||
*/
|
||||
spin_unlock_irq(&pkg_temp_lock);
|
||||
cancel_delayed_work_sync(&pkgdev->work);
|
||||
spin_lock_irq(&pkg_temp_lock);
|
||||
/*
|
||||
* If this is not the last cpu in the package and the work
|
||||
* did not run after we dropped the lock above, then we
|
||||
* need to reschedule the work, otherwise the interrupt
|
||||
* stays disabled forever.
|
||||
*/
|
||||
if (!lastcpu && pkgdev->work_scheduled)
|
||||
pkg_thermal_schedule_work(target, &pkgdev->work);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&pkg_temp_lock);
|
||||
|
||||
/* Final cleanup if this is the last cpu */
|
||||
if (lastcpu)
|
||||
kfree(pkgdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pkg_thermal_cpu_online(unsigned int cpu)
|
||||
{
|
||||
struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
|
||||
/* Paranoia check */
|
||||
if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&phy_dev_list_mutex);
|
||||
/* If we are loosing the first cpu for this package, we need change */
|
||||
if (phdev->first_cpu == cpu) {
|
||||
phdev->first_cpu = find_siblings_cpu(cpu);
|
||||
pr_debug("thermal_device_remove: first cpu switched %d\n",
|
||||
phdev->first_cpu);
|
||||
/* If the package exists, nothing to do */
|
||||
if (pkgdev) {
|
||||
cpumask_set_cpu(cpu, &pkgdev->cpumask);
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* It is possible that no siblings left as this was the last cpu
|
||||
* going offline. We don't need to worry about this assignment
|
||||
* as the phydev entry will be removed in this case and
|
||||
* thermal zone is removed.
|
||||
*/
|
||||
--phdev->ref_cnt;
|
||||
pr_debug("thermal_device_remove: pkg: %d cpu %d ref_cnt %d\n",
|
||||
phys_proc_id, cpu, phdev->ref_cnt);
|
||||
if (!phdev->ref_cnt)
|
||||
list_for_each_entry_safe(phdev, n, &phy_dev_list, list) {
|
||||
if (phdev->phys_proc_id == phys_proc_id) {
|
||||
thermal_zone_device_unregister(phdev->tzone);
|
||||
list_del(&phdev->list);
|
||||
kfree(phdev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
|
||||
return 0;
|
||||
return pkg_temp_thermal_device_add(cpu);
|
||||
}
|
||||
|
||||
static int get_core_online(unsigned int cpu)
|
||||
{
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
struct phy_dev_entry *phdev = pkg_temp_thermal_get_phy_entry(cpu);
|
||||
|
||||
/* Check if there is already an instance for this package */
|
||||
if (!phdev) {
|
||||
if (!cpu_has(c, X86_FEATURE_DTHERM) ||
|
||||
!cpu_has(c, X86_FEATURE_PTS))
|
||||
return -ENODEV;
|
||||
if (pkg_temp_thermal_device_add(cpu))
|
||||
return -ENODEV;
|
||||
} else {
|
||||
mutex_lock(&phy_dev_list_mutex);
|
||||
++phdev->ref_cnt;
|
||||
pr_debug("get_core_online: cpu %d ref_cnt %d\n",
|
||||
cpu, phdev->ref_cnt);
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
}
|
||||
INIT_DELAYED_WORK(&per_cpu(pkg_temp_thermal_threshold_work, cpu),
|
||||
pkg_temp_thermal_threshold_work_fn);
|
||||
|
||||
pr_debug("get_core_online: cpu %d successful\n", cpu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void put_core_offline(unsigned int cpu)
|
||||
{
|
||||
if (!pkg_temp_thermal_device_remove(cpu))
|
||||
cancel_delayed_work_sync(
|
||||
&per_cpu(pkg_temp_thermal_threshold_work, cpu));
|
||||
|
||||
pr_debug("put_core_offline: cpu %d\n", cpu);
|
||||
}
|
||||
|
||||
static int pkg_temp_thermal_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long) hcpu;
|
||||
|
||||
switch (action & ~CPU_TASKS_FROZEN) {
|
||||
case CPU_ONLINE:
|
||||
case CPU_DOWN_FAILED:
|
||||
get_core_online(cpu);
|
||||
break;
|
||||
case CPU_DOWN_PREPARE:
|
||||
put_core_offline(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block pkg_temp_thermal_notifier __refdata = {
|
||||
.notifier_call = pkg_temp_thermal_cpu_callback,
|
||||
};
|
||||
|
||||
static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
|
||||
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS },
|
||||
{}
|
||||
@ -580,71 +510,46 @@ MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
|
||||
|
||||
static int __init pkg_temp_thermal_init(void)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (!x86_match_cpu(pkg_temp_thermal_ids))
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_init(&pkg_work_lock);
|
||||
platform_thermal_package_notify =
|
||||
pkg_temp_thermal_platform_thermal_notify;
|
||||
platform_thermal_package_rate_control =
|
||||
pkg_temp_thermal_platform_thermal_rate_control;
|
||||
max_packages = topology_max_packages();
|
||||
packages = kzalloc(max_packages * sizeof(struct pkg_device *), GFP_KERNEL);
|
||||
if (!packages)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
for_each_online_cpu(i)
|
||||
if (get_core_online(i))
|
||||
goto err_ret;
|
||||
__register_hotcpu_notifier(&pkg_temp_thermal_notifier);
|
||||
cpu_notifier_register_done();
|
||||
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online",
|
||||
pkg_thermal_cpu_online, pkg_thermal_cpu_offline);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
pkg_temp_debugfs_init(); /* Don't care if fails */
|
||||
/* Store the state for module exit */
|
||||
pkg_thermal_hp_state = ret;
|
||||
|
||||
platform_thermal_package_notify = pkg_thermal_notify;
|
||||
platform_thermal_package_rate_control = pkg_thermal_rate_control;
|
||||
|
||||
/* Don't care if it fails */
|
||||
pkg_temp_debugfs_init();
|
||||
return 0;
|
||||
|
||||
err_ret:
|
||||
for_each_online_cpu(i)
|
||||
put_core_offline(i);
|
||||
cpu_notifier_register_done();
|
||||
kfree(pkg_work_scheduled);
|
||||
platform_thermal_package_notify = NULL;
|
||||
platform_thermal_package_rate_control = NULL;
|
||||
|
||||
return -ENODEV;
|
||||
err:
|
||||
kfree(packages);
|
||||
return ret;
|
||||
}
|
||||
module_init(pkg_temp_thermal_init)
|
||||
|
||||
static void __exit pkg_temp_thermal_exit(void)
|
||||
{
|
||||
struct phy_dev_entry *phdev, *n;
|
||||
int i;
|
||||
|
||||
cpu_notifier_register_begin();
|
||||
__unregister_hotcpu_notifier(&pkg_temp_thermal_notifier);
|
||||
mutex_lock(&phy_dev_list_mutex);
|
||||
list_for_each_entry_safe(phdev, n, &phy_dev_list, list) {
|
||||
/* Retore old MSR value for package thermal interrupt */
|
||||
wrmsr_on_cpu(phdev->first_cpu,
|
||||
MSR_IA32_PACKAGE_THERM_INTERRUPT,
|
||||
phdev->start_pkg_therm_low,
|
||||
phdev->start_pkg_therm_high);
|
||||
thermal_zone_device_unregister(phdev->tzone);
|
||||
list_del(&phdev->list);
|
||||
kfree(phdev);
|
||||
}
|
||||
mutex_unlock(&phy_dev_list_mutex);
|
||||
platform_thermal_package_notify = NULL;
|
||||
platform_thermal_package_rate_control = NULL;
|
||||
for_each_online_cpu(i)
|
||||
cancel_delayed_work_sync(
|
||||
&per_cpu(pkg_temp_thermal_threshold_work, i));
|
||||
cpu_notifier_register_done();
|
||||
|
||||
kfree(pkg_work_scheduled);
|
||||
|
||||
cpuhp_remove_state(pkg_thermal_hp_state);
|
||||
debugfs_remove_recursive(debugfs);
|
||||
kfree(packages);
|
||||
}
|
||||
|
||||
module_init(pkg_temp_thermal_init)
|
||||
module_exit(pkg_temp_thermal_exit)
|
||||
|
||||
MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver");
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include <linux/devfreq.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#ifdef CONFIG_DEVFREQ_THERMAL
|
||||
|
||||
/**
|
||||
* struct devfreq_cooling_power - Devfreq cooling power ops
|
||||
@ -37,12 +36,16 @@
|
||||
* @dyn_power_coeff * frequency * voltage^2
|
||||
*/
|
||||
struct devfreq_cooling_power {
|
||||
unsigned long (*get_static_power)(unsigned long voltage);
|
||||
unsigned long (*get_dynamic_power)(unsigned long freq,
|
||||
unsigned long (*get_static_power)(struct devfreq *devfreq,
|
||||
unsigned long voltage);
|
||||
unsigned long (*get_dynamic_power)(struct devfreq *devfreq,
|
||||
unsigned long freq,
|
||||
unsigned long voltage);
|
||||
unsigned long dyn_power_coeff;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEVFREQ_THERMAL
|
||||
|
||||
struct thermal_cooling_device *
|
||||
of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
|
||||
struct devfreq_cooling_power *dfc_power);
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <uapi/linux/thermal.h>
|
||||
|
||||
@ -204,6 +205,7 @@ struct thermal_zone_device {
|
||||
int id;
|
||||
char type[THERMAL_NAME_LENGTH];
|
||||
struct device device;
|
||||
struct attribute_group trips_attribute_group;
|
||||
struct thermal_attr *trip_temp_attrs;
|
||||
struct thermal_attr *trip_type_attrs;
|
||||
struct thermal_attr *trip_hyst_attrs;
|
||||
|
Loading…
Reference in New Issue
Block a user