Commit 9245ac20 authored by Zhang Rui's avatar Zhang Rui

Merge branches 'thermal-core', 'thermal-intel', 'thermal-soc-fixes' and 'thermal-reorg' into next

...@@ -22,10 +22,13 @@ Required properties: ...@@ -22,10 +22,13 @@ Required properties:
TSADC controller. TSADC controller.
- pinctrl-2 : The "sleep" pinctrl state, it will be in for suspend. - pinctrl-2 : The "sleep" pinctrl state, it will be in for suspend.
- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description. - #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-temp : The hardware-controlled shutdown temperature value.
- rockchip,hw-tshut-mode : The hardware-controlled shutdown mode 0:CRU 1:GPIO. - rockchip,hw-tshut-mode : The hardware-controlled shutdown mode 0:CRU 1:GPIO.
- rockchip,hw-tshut-polarity : The hardware-controlled active polarity 0:LOW - rockchip,hw-tshut-polarity : The hardware-controlled active polarity 0:LOW
1:HIGH. 1:HIGH.
- rockchip,grf : The phandle of the syscon node for the general register file.
Exiample: Exiample:
tsadc: tsadc@ff280000 { tsadc: tsadc@ff280000 {
......
...@@ -177,8 +177,10 @@ config THERMAL_EMULATION ...@@ -177,8 +177,10 @@ config THERMAL_EMULATION
config HISI_THERMAL config HISI_THERMAL
tristate "Hisilicon thermal driver" 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 HAS_IOMEM
depends on OF
default y
help help
Enable this to plug hisilicon's thermal sensor driver into the Linux Enable this to plug hisilicon's thermal sensor driver into the Linux
thermal framework. cpufreq is used as the cooling device to throttle thermal framework. cpufreq is used as the cooling device to throttle
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
# #
obj-$(CONFIG_THERMAL) += thermal_sys.o 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 # interface to/from other layers providing sensors
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
......
...@@ -512,6 +512,7 @@ static const struct of_device_id db8500_thermal_match[] = { ...@@ -512,6 +512,7 @@ static const struct of_device_id db8500_thermal_match[] = {
{ .compatible = "stericsson,db8500-thermal" }, { .compatible = "stericsson,db8500-thermal" },
{}, {},
}; };
MODULE_DEVICE_TABLE(of, db8500_thermal_match);
#endif #endif
static struct platform_driver db8500_thermal_driver = { static struct platform_driver db8500_thermal_driver = {
......
...@@ -238,7 +238,7 @@ get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq) ...@@ -238,7 +238,7 @@ get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq)
return 0; 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, ...@@ -262,7 +262,8 @@ get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq,
struct devfreq_cooling_power *dfc_power = dfc->power_ops; struct devfreq_cooling_power *dfc_power = dfc->power_ops;
if (dfc_power->get_dynamic_power) 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; freq_mhz = freq / 1000000;
power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage; power = (u64)dfc_power->dyn_power_coeff * freq_mhz * voltage * voltage;
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */ #define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */
#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */ #define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */
#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */ #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 */ /* Wildcat Point-LP PCH Thermal registers */
#define WPT_TEMP 0x0000 /* Temperature */ #define WPT_TEMP 0x0000 /* Temperature */
...@@ -273,37 +274,44 @@ static struct thermal_zone_device_ops tzd_ops = { ...@@ -273,37 +274,44 @@ static struct thermal_zone_device_ops tzd_ops = {
.get_trip_temp = pch_get_trip_temp, .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, static int intel_pch_thermal_probe(struct pci_dev *pdev,
const struct pci_device_id *id) 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; struct pch_thermal_device *ptd;
int err; int err;
int nr_trips; int nr_trips;
char *dev_name;
ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL); ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
if (!ptd) if (!ptd)
return -ENOMEM; return -ENOMEM;
switch (pdev->device) { ptd->ops = bi->ops;
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;
}
pci_set_drvdata(pdev, ptd); pci_set_drvdata(pdev, ptd);
ptd->pdev = pdev; ptd->pdev = pdev;
...@@ -331,11 +339,11 @@ static int intel_pch_thermal_probe(struct pci_dev *pdev, ...@@ -331,11 +339,11 @@ static int intel_pch_thermal_probe(struct pci_dev *pdev,
if (err) if (err)
goto error_cleanup; 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); &tzd_ops, NULL, 0, 0);
if (IS_ERR(ptd->tzd)) { if (IS_ERR(ptd->tzd)) {
dev_err(&pdev->dev, "Failed to register thermal zone %s\n", dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
dev_name); bi->name);
err = PTR_ERR(ptd->tzd); err = PTR_ERR(ptd->tzd);
goto error_cleanup; goto error_cleanup;
} }
...@@ -380,10 +388,16 @@ static int intel_pch_thermal_resume(struct device *device) ...@@ -380,10 +388,16 @@ static int intel_pch_thermal_resume(struct device *device)
} }
static struct pci_device_id intel_pch_thermal_id[] = { 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_HSW_1),
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL) }, .driver_data = board_hsw, },
{ 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_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, }, { 0, },
}; };
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id); MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
......
...@@ -149,6 +149,7 @@ static struct platform_device_id max77620_thermal_devtype[] = { ...@@ -149,6 +149,7 @@ static struct platform_device_id max77620_thermal_devtype[] = {
{ .name = "max77620-thermal", }, { .name = "max77620-thermal", },
{}, {},
}; };
MODULE_DEVICE_TABLE(platform, max77620_thermal_devtype);
static struct platform_driver max77620_thermal_driver = { static struct platform_driver max77620_thermal_driver = {
.driver = { .driver = {
......
...@@ -200,7 +200,7 @@ static int qpnp_tm_probe(struct platform_device *pdev) ...@@ -200,7 +200,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
struct qpnp_tm_chip *chip; struct qpnp_tm_chip *chip;
struct device_node *node; struct device_node *node;
u8 type, subtype; u8 type, subtype;
u32 res[2]; u32 res;
int ret, irq; int ret, irq;
node = pdev->dev.of_node; node = pdev->dev.of_node;
...@@ -215,7 +215,7 @@ static int qpnp_tm_probe(struct platform_device *pdev) ...@@ -215,7 +215,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
if (!chip->map) if (!chip->map)
return -ENXIO; return -ENXIO;
ret = of_property_read_u32_array(node, "reg", res, 2); ret = of_property_read_u32(node, "reg", &res);
if (ret < 0) if (ret < 0)
return ret; return ret;
...@@ -228,7 +228,7 @@ static int qpnp_tm_probe(struct platform_device *pdev) ...@@ -228,7 +228,7 @@ static int qpnp_tm_probe(struct platform_device *pdev)
if (PTR_ERR(chip->adc) == -EPROBE_DEFER) if (PTR_ERR(chip->adc) == -EPROBE_DEFER)
return PTR_ERR(chip->adc); return PTR_ERR(chip->adc);
chip->base = res[0]; chip->base = res;
ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type); ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
if (ret < 0) { if (ret < 0) {
......
...@@ -524,11 +524,6 @@ static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs, ...@@ -524,11 +524,6 @@ static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs,
regs + TSADCV2_AUTO_PERIOD_HT); regs + TSADCV2_AUTO_PERIOD_HT);
writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT,
regs + TSADCV2_HIGHT_TSHUT_DEBOUNCE); 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, ...@@ -971,6 +966,8 @@ static int rockchip_configure_from_dt(struct device *dev,
* need this property. * need this property.
*/ */
thermal->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); 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; return 0;
} }
......
...@@ -107,6 +107,7 @@ static const struct of_device_id tango_sensor_ids[] = { ...@@ -107,6 +107,7 @@ static const struct of_device_id tango_sensor_ids[] = {
}, },
{ /* sentinel */ } { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, tango_sensor_ids);
static struct platform_driver tango_thermal_driver = { static struct platform_driver tango_thermal_driver = {
.probe = tango_thermal_probe, .probe = tango_thermal_probe,
......
...@@ -5,22 +5,9 @@ ...@@ -5,22 +5,9 @@
* Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
* Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
* *
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License. * the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
...@@ -64,6 +51,13 @@ static atomic_t in_suspend; ...@@ -64,6 +51,13 @@ static atomic_t in_suspend;
static struct thermal_governor *def_governor; static struct thermal_governor *def_governor;
/*
* Governor section: set of functions to handle thermal governors
*
* Functions to help in the life cycle of thermal governors within
* the thermal core and by the thermal governor code.
*/
static struct thermal_governor *__find_governor(const char *name) static struct thermal_governor *__find_governor(const char *name)
{ {
struct thermal_governor *pos; struct thermal_governor *pos;
...@@ -142,11 +136,16 @@ int thermal_register_governor(struct thermal_governor *governor) ...@@ -142,11 +136,16 @@ int thermal_register_governor(struct thermal_governor *governor)
mutex_lock(&thermal_governor_lock); mutex_lock(&thermal_governor_lock);
err = -EBUSY; err = -EBUSY;
if (__find_governor(governor->name) == NULL) { if (!__find_governor(governor->name)) {
bool match_default;
err = 0; err = 0;
list_add(&governor->governor_list, &thermal_governor_list); list_add(&governor->governor_list, &thermal_governor_list);
if (!def_governor && !strncmp(governor->name, match_default = !strncmp(governor->name,
DEFAULT_THERMAL_GOVERNOR, THERMAL_NAME_LENGTH)) DEFAULT_THERMAL_GOVERNOR,
THERMAL_NAME_LENGTH);
if (!def_governor && match_default)
def_governor = governor; def_governor = governor;
} }
...@@ -188,7 +187,7 @@ void thermal_unregister_governor(struct thermal_governor *governor) ...@@ -188,7 +187,7 @@ void thermal_unregister_governor(struct thermal_governor *governor)
mutex_lock(&thermal_governor_lock); mutex_lock(&thermal_governor_lock);
if (__find_governor(governor->name) == NULL) if (!__find_governor(governor->name))
goto exit; goto exit;
mutex_lock(&thermal_list_lock); mutex_lock(&thermal_list_lock);
...@@ -203,195 +202,92 @@ void thermal_unregister_governor(struct thermal_governor *governor) ...@@ -203,195 +202,92 @@ void thermal_unregister_governor(struct thermal_governor *governor)
list_del(&governor->governor_list); list_del(&governor->governor_list);
exit: exit:
mutex_unlock(&thermal_governor_lock); mutex_unlock(&thermal_governor_lock);
return;
}
static int get_idr(struct idr *idr, struct mutex *lock, int *id)
{
int ret;
if (lock)
mutex_lock(lock);
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
if (lock)
mutex_unlock(lock);
if (unlikely(ret < 0))
return ret;
*id = ret;
return 0;
}
static void release_idr(struct idr *idr, struct mutex *lock, int id)
{
if (lock)
mutex_lock(lock);
idr_remove(idr, id);
if (lock)
mutex_unlock(lock);
}
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, int thermal_zone_device_set_policy(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int trip) char *policy)
{ {
struct thermal_instance *pos = NULL; struct thermal_governor *gov;
struct thermal_instance *target_instance = NULL; int ret = -EINVAL;
mutex_lock(&thermal_governor_lock);
mutex_lock(&tz->lock); mutex_lock(&tz->lock);
mutex_lock(&cdev->lock);
list_for_each_entry(pos, &tz->thermal_instances, tz_node) { gov = __find_governor(strim(policy));
if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { if (!gov)
target_instance = pos; goto exit;
break;
}
}
mutex_unlock(&cdev->lock); ret = thermal_set_governor(tz, gov);
mutex_unlock(&tz->lock);
return target_instance; exit:
} mutex_unlock(&tz->lock);
EXPORT_SYMBOL(get_thermal_instance); mutex_unlock(&thermal_governor_lock);
static void print_bind_err_msg(struct thermal_zone_device *tz, return ret;
struct thermal_cooling_device *cdev, int ret)
{
dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
tz->type, cdev->type, ret);
} }
static void __bind(struct thermal_zone_device *tz, int mask, int thermal_build_list_of_policies(char *buf)
struct thermal_cooling_device *cdev,
unsigned long *limits,
unsigned int weight)
{ {
int i, ret; struct thermal_governor *pos;
ssize_t count = 0;
ssize_t size = PAGE_SIZE;
for (i = 0; i < tz->trips; i++) { mutex_lock(&thermal_governor_lock);
if (mask & (1 << i)) {
unsigned long upper, lower;
upper = THERMAL_NO_LIMIT; list_for_each_entry(pos, &thermal_governor_list, governor_list) {
lower = THERMAL_NO_LIMIT; size = PAGE_SIZE - count;
if (limits) { count += scnprintf(buf + count, size, "%s ", pos->name);
lower = limits[i * 2];
upper = limits[i * 2 + 1];
}
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
upper, lower,
weight);
if (ret)
print_bind_err_msg(tz, cdev, ret);
}
} }
} count += scnprintf(buf + count, size, "\n");
static void __unbind(struct thermal_zone_device *tz, int mask, mutex_unlock(&thermal_governor_lock);
struct thermal_cooling_device *cdev)
{
int i;
for (i = 0; i < tz->trips; i++) return count;
if (mask & (1 << i))
thermal_zone_unbind_cooling_device(tz, i, cdev);
} }
static void bind_cdev(struct thermal_cooling_device *cdev) static int __init thermal_register_governors(void)
{ {
int i, ret; int result;
const struct thermal_zone_params *tzp;
struct thermal_zone_device *pos = NULL;
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) { result = thermal_gov_step_wise_register();
if (!pos->tzp && !pos->ops->bind) if (result)
continue; return result;
if (pos->ops->bind) { result = thermal_gov_fair_share_register();
ret = pos->ops->bind(pos, cdev); if (result)
if (ret) return result;
print_bind_err_msg(pos, cdev, ret);
continue;
}
tzp = pos->tzp; result = thermal_gov_bang_bang_register();
if (!tzp || !tzp->tbp) if (result)
continue; return result;
for (i = 0; i < tzp->num_tbps; i++) { result = thermal_gov_user_space_register();
if (tzp->tbp[i].cdev || !tzp->tbp[i].match) if (result)
continue; return result;
if (tzp->tbp[i].match(pos, cdev))
continue;
tzp->tbp[i].cdev = cdev;
__bind(pos, tzp->tbp[i].trip_mask, cdev,
tzp->tbp[i].binding_limits,
tzp->tbp[i].weight);
}
}
mutex_unlock(&thermal_list_lock); return thermal_gov_power_allocator_register();
} }
static void bind_tz(struct thermal_zone_device *tz) static void thermal_unregister_governors(void)
{ {
int i, ret; thermal_gov_step_wise_unregister();
struct thermal_cooling_device *pos = NULL; thermal_gov_fair_share_unregister();
const struct thermal_zone_params *tzp = tz->tzp; thermal_gov_bang_bang_unregister();
thermal_gov_user_space_unregister();
if (!tzp && !tz->ops->bind) thermal_gov_power_allocator_unregister();
return;
mutex_lock(&thermal_list_lock);
/* If there is ops->bind, try to use ops->bind */
if (tz->ops->bind) {
list_for_each_entry(pos, &thermal_cdev_list, node) {
ret = tz->ops->bind(tz, pos);
if (ret)
print_bind_err_msg(tz, pos, ret);
}
goto exit;
}
if (!tzp || !tzp->tbp)
goto exit;
list_for_each_entry(pos, &thermal_cdev_list, node) {
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
continue;
if (tzp->tbp[i].match(tz, pos))
continue;
tzp->tbp[i].cdev = pos;
__bind(tz, tzp->tbp[i].trip_mask, pos,
tzp->tbp[i].binding_limits,
tzp->tbp[i].weight);
}
}
exit:
mutex_unlock(&thermal_list_lock);
} }
/*
* Zone update section: main control loop applied to each zone while monitoring
*
* in polling mode. The monitoring is done using a workqueue.
* Same update may be done on a zone by calling thermal_zone_device_update().
*
* An update means:
* - Non-critical trips will invoke the governor responsible for that zone;
* - Hot trips will produce a notification to userspace;
* - Critical trip point will cause a system shutdown.
*/
static void thermal_zone_device_set_polling(struct thermal_zone_device *tz, static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
int delay) int delay)
{ {
...@@ -420,7 +316,8 @@ static void monitor_thermal_zone(struct thermal_zone_device *tz) ...@@ -420,7 +316,8 @@ static void monitor_thermal_zone(struct thermal_zone_device *tz)
} }
static void handle_non_critical_trips(struct thermal_zone_device *tz, static void handle_non_critical_trips(struct thermal_zone_device *tz,
int trip, enum thermal_trip_type trip_type) int trip,
enum thermal_trip_type trip_type)
{ {
tz->governor ? tz->governor->throttle(tz, trip) : tz->governor ? tz->governor->throttle(tz, trip) :
def_governor->throttle(tz, trip); def_governor->throttle(tz, trip);
...@@ -471,105 +368,6 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip) ...@@ -471,105 +368,6 @@ static void handle_thermal_trip(struct thermal_zone_device *tz, int trip)
monitor_thermal_zone(tz); monitor_thermal_zone(tz);
} }
/**
* 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);
static void update_temperature(struct thermal_zone_device *tz) static void update_temperature(struct thermal_zone_device *tz)
{ {
int temp, ret; int temp, ret;
...@@ -629,453 +427,38 @@ void thermal_zone_device_update(struct thermal_zone_device *tz, ...@@ -629,453 +427,38 @@ void thermal_zone_device_update(struct thermal_zone_device *tz,
} }
EXPORT_SYMBOL_GPL(thermal_zone_device_update); EXPORT_SYMBOL_GPL(thermal_zone_device_update);
static void thermal_zone_device_check(struct work_struct *work) /**
{ * thermal_notify_framework - Sensor drivers use this API to notify framework
struct thermal_zone_device *tz = container_of(work, struct * @tz: thermal zone device
thermal_zone_device, * @trip: indicates which trip point has been crossed
poll_queue.work); *
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); * This function handles the trip events from sensor drivers. It starts
} * throttling the cooling devices according to the policy configured.
* For CRITICAL and HOT trip points, this notifies the respective drivers,
/* sys I/F for thermal zone */ * and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
* The throttling policy is based on the configured platform data; if no
#define to_thermal_zone(_dev) \ * platform data is provided, this uses the step_wise throttling policy.
container_of(_dev, struct thermal_zone_device, device)
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))
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))
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))
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))
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))
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);
struct thermal_cooling_device *cdev = NULL;
int state;
if (!sscanf(buf, "%d\n", &state))
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) void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
return -EINVAL;
if (state && !tz->forced_passive) {
mutex_lock(&thermal_list_lock);
list_for_each_entry(cdev, &thermal_cdev_list, node) {
if (!strncmp("Processor", cdev->type,
sizeof("Processor")))
thermal_zone_bind_cooling_device(tz,
THERMAL_TRIPS_NONE, cdev,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT,
THERMAL_WEIGHT_DEFAULT);
}
mutex_unlock(&thermal_list_lock);
if (!tz->passive_delay)
tz->passive_delay = 1000;
} else if (!state && tz->forced_passive) {
mutex_lock(&thermal_list_lock);
list_for_each_entry(cdev, &thermal_cdev_list, node) {
if (!strncmp("Processor", cdev->type,
sizeof("Processor")))
thermal_zone_unbind_cooling_device(tz,
THERMAL_TRIPS_NONE,
cdev);
}
mutex_unlock(&thermal_list_lock);
tz->passive_delay = 0;
}
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)
{
int ret = -EINVAL;
struct thermal_zone_device *tz = to_thermal_zone(dev);
struct thermal_governor *gov;
char name[THERMAL_NAME_LENGTH];
snprintf(name, sizeof(name), "%s", buf);
mutex_lock(&thermal_governor_lock);
mutex_lock(&tz->lock);
gov = __find_governor(strim(name));
if (!gov)
goto exit;
ret = thermal_set_governor(tz, gov);
if (!ret)
ret = count;
exit:
mutex_unlock(&tz->lock);
mutex_unlock(&thermal_governor_lock);
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)
{ {
struct thermal_governor *pos; handle_thermal_trip(tz, trip);
ssize_t count = 0;
ssize_t size = PAGE_SIZE;
mutex_lock(&thermal_governor_lock);
list_for_each_entry(pos, &thermal_governor_list, governor_list) {
size = PAGE_SIZE - count;
count += scnprintf(buf + count, size, "%s ", pos->name);
}
count += scnprintf(buf + count, size, "\n");
mutex_unlock(&thermal_governor_lock);
return count;
} }
EXPORT_SYMBOL_GPL(thermal_notify_framework);
static ssize_t static void thermal_zone_device_check(struct work_struct *work)
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); struct thermal_zone_device *tz = container_of(work, struct
int ret = 0; thermal_zone_device,
int temperature; poll_queue.work);
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); thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
return ret ? ret : count;
} }
static DEVICE_ATTR(emul_temp, S_IWUSR, NULL, emul_temp_store);
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; /*
} * Power actor section: interface to power actors to estimate power
static DEVICE_ATTR(sustainable_power, S_IWUSR | S_IRUGO, sustainable_power_show, *
sustainable_power_store); * Set of functions used to interact to cooling devices that know
* how to estimate their devices power consumption.
#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
static struct device_attribute *dev_tzp_attrs[] = {
&dev_attr_sustainable_power,
&dev_attr_k_po,
&dev_attr_k_pu,
&dev_attr_k_i,
&dev_attr_k_d,
&dev_attr_integral_cutoff,
&dev_attr_slope,
&dev_attr_offset,
};
static int create_tzp_attrs(struct device *dev)
{
int i;
for (i = 0; i < ARRAY_SIZE(dev_tzp_attrs); i++) {
int ret;
struct device_attribute *dev_attr = dev_tzp_attrs[i];
ret = device_create_file(dev, dev_attr);
if (ret)
return ret;
}
return 0;
}
/** /**
* power_actor_get_max_power() - get the maximum power that a cdev can consume * power_actor_get_max_power() - get the maximum power that a cdev can consume
...@@ -1127,12 +510,13 @@ int power_actor_get_min_power(struct thermal_cooling_device *cdev, ...@@ -1127,12 +510,13 @@ int power_actor_get_min_power(struct thermal_cooling_device *cdev,
} }
/** /**
* power_actor_set_power() - limit the maximum power that a cooling device can consume * power_actor_set_power() - limit the maximum power a cooling device consumes
* @cdev: pointer to &thermal_cooling_device * @cdev: pointer to &thermal_cooling_device
* @instance: thermal instance to update * @instance: thermal instance to update
* @power: the power in milliwatts * @power: the power in milliwatts
* *
* Set the cooling device to consume at most @power milliwatts. * Set the cooling device to consume at most @power milliwatts. The limit is
* expected to be a cap at the maximum power consumption.
* *
* Return: 0 on success, -EINVAL if the cooling device does not * Return: 0 on success, -EINVAL if the cooling device does not
* implement the power actor API or -E* for other failures. * implement the power actor API or -E* for other failures.
...@@ -1159,143 +543,75 @@ int power_actor_set_power(struct thermal_cooling_device *cdev, ...@@ -1159,143 +543,75 @@ int power_actor_set_power(struct thermal_cooling_device *cdev,
return 0; return 0;
} }
static DEVICE_ATTR(type, 0444, type_show, NULL); void thermal_zone_device_rebind_exception(struct thermal_zone_device *tz,
static DEVICE_ATTR(temp, 0444, temp_show, NULL); const char *cdev_type, size_t size)
static DEVICE_ATTR(mode, 0644, mode_show, mode_store);
static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, passive_store);
static DEVICE_ATTR(policy, S_IRUGO | S_IWUSR, policy_show, policy_store);
static DEVICE_ATTR(available_policies, S_IRUGO, available_policies_show, NULL);
/* sys I/F for cooling device */
#define to_cooling_device(_dev) \
container_of(_dev, struct thermal_cooling_device, 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); struct thermal_cooling_device *cdev = NULL;
mutex_lock(&thermal_list_lock);
list_for_each_entry(cdev, &thermal_cdev_list, node) {
/* skip non matching cdevs */
if (strncmp(cdev_type, cdev->type, size))
continue;
return sprintf(buf, "%s\n", cdev->type); /* re binding the exception matching the type pattern */
thermal_zone_bind_cooling_device(tz, THERMAL_TRIPS_NONE, cdev,
THERMAL_NO_LIMIT,
THERMAL_NO_LIMIT,
THERMAL_WEIGHT_DEFAULT);
}
mutex_unlock(&thermal_list_lock);
} }
static ssize_t void thermal_zone_device_unbind_exception(struct thermal_zone_device *tz,
thermal_cooling_device_max_state_show(struct device *dev, const char *cdev_type, size_t size)
struct device_attribute *attr, char *buf)
{ {
struct thermal_cooling_device *cdev = to_cooling_device(dev); struct thermal_cooling_device *cdev = NULL;
unsigned long state;
int ret;
ret = cdev->ops->get_max_state(cdev, &state); mutex_lock(&thermal_list_lock);
if (ret) list_for_each_entry(cdev, &thermal_cdev_list, node) {
return ret; /* skip non matching cdevs */
return sprintf(buf, "%ld\n", state); if (strncmp(cdev_type, cdev->type, size))
continue;
/* unbinding the exception matching the type pattern */
thermal_zone_unbind_cooling_device(tz, THERMAL_TRIPS_NONE,
cdev);
}
mutex_unlock(&thermal_list_lock);
} }
static ssize_t /*
thermal_cooling_device_cur_state_show(struct device *dev, * Device management section: cooling devices, zones devices, and binding
struct device_attribute *attr, char *buf) *
* Set of functions provided by the thermal core for:
* - cooling devices lifecycle: registration, unregistration,
* binding, and unbinding.
* - thermal zone devices lifecycle: registration, unregistration,
* binding, and unbinding.
*/
static int get_idr(struct idr *idr, struct mutex *lock, int *id)
{ {
struct thermal_cooling_device *cdev = to_cooling_device(dev);
unsigned long state;
int ret; int ret;
ret = cdev->ops->get_cur_state(cdev, &state); if (lock)
if (ret) mutex_lock(lock);
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
if (lock)
mutex_unlock(lock);
if (unlikely(ret < 0))
return ret; return ret;
return sprintf(buf, "%ld\n", state); *id = ret;
} return 0;
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))
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 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);
}
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,
};
static 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);
} }
static ssize_t static void release_idr(struct idr *idr, struct mutex *lock, int id)
thermal_cooling_device_weight_store(struct device *dev, {
struct device_attribute *attr, if (lock)
const char *buf, size_t count) mutex_lock(lock);
{ idr_remove(idr, id);
struct thermal_instance *instance; if (lock)
int ret, weight; mutex_unlock(lock);
ret = kstrtoint(buf, 0, &weight);
if (ret)
return ret;
instance = container_of(attr, struct thermal_instance, weight_attr);
instance->weight = weight;
return count;
} }
/* Device management */
/** /**
* thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone * thermal_zone_bind_cooling_device() - bind a cooling device to a thermal zone
...@@ -1358,8 +674,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, ...@@ -1358,8 +674,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
if (lower > upper || upper > max_state) if (lower > upper || upper > max_state)
return -EINVAL; return -EINVAL;
dev = dev = kzalloc(sizeof(*dev), GFP_KERNEL);
kzalloc(sizeof(struct thermal_instance), GFP_KERNEL);
if (!dev) if (!dev)
return -ENOMEM; return -ENOMEM;
dev->tz = tz; dev->tz = tz;
...@@ -1485,8 +800,8 @@ static void thermal_release(struct device *dev) ...@@ -1485,8 +800,8 @@ static void thermal_release(struct device *dev)
sizeof("thermal_zone") - 1)) { sizeof("thermal_zone") - 1)) {
tz = to_thermal_zone(dev); tz = to_thermal_zone(dev);
kfree(tz); kfree(tz);
} else if(!strncmp(dev_name(dev), "cooling_device", } else if (!strncmp(dev_name(dev), "cooling_device",
sizeof("cooling_device") - 1)){ sizeof("cooling_device") - 1)) {
cdev = to_cooling_device(dev); cdev = to_cooling_device(dev);
kfree(cdev); kfree(cdev);
} }
...@@ -1497,6 +812,78 @@ static struct class thermal_class = { ...@@ -1497,6 +812,78 @@ static struct class thermal_class = {
.dev_release = thermal_release, .dev_release = thermal_release,
}; };
static inline
void print_bind_err_msg(struct thermal_zone_device *tz,
struct thermal_cooling_device *cdev, int ret)
{
dev_err(&tz->device, "binding zone %s with cdev %s failed:%d\n",
tz->type, cdev->type, ret);
}
static void __bind(struct thermal_zone_device *tz, int mask,
struct thermal_cooling_device *cdev,
unsigned long *limits,
unsigned int weight)
{
int i, ret;
for (i = 0; i < tz->trips; i++) {
if (mask & (1 << i)) {
unsigned long upper, lower;
upper = THERMAL_NO_LIMIT;
lower = THERMAL_NO_LIMIT;
if (limits) {
lower = limits[i * 2];
upper = limits[i * 2 + 1];
}
ret = thermal_zone_bind_cooling_device(tz, i, cdev,
upper, lower,
weight);
if (ret)
print_bind_err_msg(tz, cdev, ret);
}
}
}
static void bind_cdev(struct thermal_cooling_device *cdev)
{
int i, ret;
const struct thermal_zone_params *tzp;
struct thermal_zone_device *pos = NULL;
mutex_lock(&thermal_list_lock);
list_for_each_entry(pos, &thermal_tz_list, node) {
if (!pos->tzp && !pos->ops->bind)
continue;
if (pos->ops->bind) {
ret = pos->ops->bind(pos, cdev);
if (ret)
print_bind_err_msg(pos, cdev, ret);
continue;
}
tzp = pos->tzp;
if (!tzp || !tzp->tbp)
continue;
for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
continue;
if (tzp->tbp[i].match(pos, cdev))
continue;
tzp->tbp[i].cdev = cdev;
__bind(pos, tzp->tbp[i].trip_mask, cdev,
tzp->tbp[i].binding_limits,
tzp->tbp[i].weight);
}
}
mutex_unlock(&thermal_list_lock);
}
/** /**
* __thermal_cooling_device_register() - register a new thermal cooling device * __thermal_cooling_device_register() - register a new thermal cooling device
* @np: a pointer to a device tree node. * @np: a pointer to a device tree node.
...@@ -1529,7 +916,7 @@ __thermal_cooling_device_register(struct device_node *np, ...@@ -1529,7 +916,7 @@ __thermal_cooling_device_register(struct device_node *np,
!ops->set_cur_state) !ops->set_cur_state)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL); cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
if (!cdev) if (!cdev)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -1546,7 +933,7 @@ __thermal_cooling_device_register(struct device_node *np, ...@@ -1546,7 +933,7 @@ __thermal_cooling_device_register(struct device_node *np,
cdev->ops = ops; cdev->ops = ops;
cdev->updated = false; cdev->updated = false;
cdev->device.class = &thermal_class; cdev->device.class = &thermal_class;
cdev->device.groups = cooling_device_attr_groups; thermal_cooling_device_setup_sysfs(cdev);
cdev->devdata = devdata; cdev->devdata = devdata;
dev_set_name(&cdev->device, "cooling_device%d", cdev->id); dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
result = device_register(&cdev->device); result = device_register(&cdev->device);
...@@ -1619,12 +1006,22 @@ thermal_of_cooling_device_register(struct device_node *np, ...@@ -1619,12 +1006,22 @@ thermal_of_cooling_device_register(struct device_node *np,
} }
EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register); EXPORT_SYMBOL_GPL(thermal_of_cooling_device_register);
static void __unbind(struct thermal_zone_device *tz, int mask,
struct thermal_cooling_device *cdev)
{
int i;
for (i = 0; i < tz->trips; i++)
if (mask & (1 << i))
thermal_zone_unbind_cooling_device(tz, i, cdev);
}
/** /**
* thermal_cooling_device_unregister - removes the registered thermal cooling device * thermal_cooling_device_unregister - removes a thermal cooling device
* @cdev: the thermal cooling device to remove. * @cdev: the thermal cooling device to remove.
* *
* thermal_cooling_device_unregister() must be called when the device is no * thermal_cooling_device_unregister() must be called when a registered
* longer needed. * thermal cooling device is no longer needed.
*/ */
void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev) void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
{ {
...@@ -1668,171 +1065,49 @@ void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev) ...@@ -1668,171 +1065,49 @@ void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
mutex_unlock(&thermal_list_lock); mutex_unlock(&thermal_list_lock);
if (cdev->type[0])
device_remove_file(&cdev->device, &dev_attr_cdev_type);
device_remove_file(&cdev->device, &dev_attr_max_state);
device_remove_file(&cdev->device, &dev_attr_cur_state);
release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id);
device_unregister(&cdev->device); device_unregister(&cdev->device);
return;
} }
EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister); EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
void thermal_cdev_update(struct thermal_cooling_device *cdev) static void bind_tz(struct thermal_zone_device *tz)
{ {
struct thermal_instance *instance; int i, ret;
unsigned long target = 0; struct thermal_cooling_device *pos = NULL;
const struct thermal_zone_params *tzp = tz->tzp;
mutex_lock(&cdev->lock); if (!tzp && !tz->ops->bind)
/* cooling device is updated*/
if (cdev->updated) {
mutex_unlock(&cdev->lock);
return; 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_notify_framework - Sensor drivers use this API to notify framework
* @tz: thermal zone device
* @trip: indicates which trip point has been crossed
*
* This function handles the trip events from sensor drivers. It starts
* throttling the cooling devices according to the policy configured.
* For CRITICAL and HOT trip points, this notifies the respective drivers,
* and does actual throttling for other trip points i.e ACTIVE and PASSIVE.
* The throttling policy is based on the configured platform data; if no
* platform data is provided, this uses the step_wise throttling policy.
*/
void thermal_notify_framework(struct thermal_zone_device *tz, int trip)
{
handle_thermal_trip(tz, trip);
}
EXPORT_SYMBOL_GPL(thermal_notify_framework);
/**
* 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)
{
int indx;
int size = sizeof(struct thermal_attr) * tz->trips;
tz->trip_type_attrs = kzalloc(size, GFP_KERNEL);
if (!tz->trip_type_attrs)
return -ENOMEM;
tz->trip_temp_attrs = kzalloc(size, GFP_KERNEL); mutex_lock(&thermal_list_lock);
if (!tz->trip_temp_attrs) {
kfree(tz->trip_type_attrs);
return -ENOMEM;
}
if (tz->ops->get_trip_hyst) { /* If there is ops->bind, try to use ops->bind */
tz->trip_hyst_attrs = kzalloc(size, GFP_KERNEL); if (tz->ops->bind) {
if (!tz->trip_hyst_attrs) { list_for_each_entry(pos, &thermal_cdev_list, node) {
kfree(tz->trip_type_attrs); ret = tz->ops->bind(tz, pos);
kfree(tz->trip_temp_attrs); if (ret)
return -ENOMEM; print_bind_err_msg(tz, pos, ret);
}
} }
goto exit;
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;
device_create_file(&tz->device,
&tz->trip_type_attrs[indx].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;
} }
device_create_file(&tz->device, if (!tzp || !tzp->tbp)
&tz->trip_temp_attrs[indx].attr); goto exit;
/* create Optional trip hyst attribute */ list_for_each_entry(pos, &thermal_cdev_list, node) {
if (!tz->ops->get_trip_hyst) for (i = 0; i < tzp->num_tbps; i++) {
if (tzp->tbp[i].cdev || !tzp->tbp[i].match)
continue; continue;
snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH, if (tzp->tbp[i].match(tz, pos))
"trip_point_%d_hyst", indx); continue;
tzp->tbp[i].cdev = pos;
sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr); __bind(tz, tzp->tbp[i].trip_mask, pos,
tz->trip_hyst_attrs[indx].attr.attr.name = tzp->tbp[i].binding_limits,
tz->trip_hyst_attrs[indx].name; tzp->tbp[i].weight);
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;
}
device_create_file(&tz->device,
&tz->trip_hyst_attrs[indx].attr);
} }
return 0;
}
static void remove_trip_attrs(struct thermal_zone_device *tz)
{
int indx;
for (indx = 0; indx < tz->trips; indx++) {
device_remove_file(&tz->device,
&tz->trip_type_attrs[indx].attr);
device_remove_file(&tz->device,
&tz->trip_temp_attrs[indx].attr);
if (tz->ops->get_trip_hyst)
device_remove_file(&tz->device,
&tz->trip_hyst_attrs[indx].attr);
} }
kfree(tz->trip_type_attrs); exit:
kfree(tz->trip_temp_attrs); mutex_unlock(&thermal_list_lock);
kfree(tz->trip_hyst_attrs);
} }
/** /**
...@@ -1859,20 +1134,22 @@ static void remove_trip_attrs(struct thermal_zone_device *tz) ...@@ -1859,20 +1134,22 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
* in case of error, an ERR_PTR. Caller must check return value with * in case of error, an ERR_PTR. Caller must check return value with
* IS_ERR*() helpers. * IS_ERR*() helpers.
*/ */
struct thermal_zone_device *thermal_zone_device_register(const char *type, struct thermal_zone_device *
int trips, int mask, void *devdata, thermal_zone_device_register(const char *type, int trips, int mask,
struct thermal_zone_device_ops *ops, void *devdata, struct thermal_zone_device_ops *ops,
struct thermal_zone_params *tzp, struct thermal_zone_params *tzp, int passive_delay,
int passive_delay, int polling_delay) int polling_delay)
{ {
struct thermal_zone_device *tz; struct thermal_zone_device *tz;
enum thermal_trip_type trip_type; enum thermal_trip_type trip_type;
int trip_temp; int trip_temp;
int result; int result;
int count; int count;
int passive = 0;
struct thermal_governor *governor; struct thermal_governor *governor;
if (!type || strlen(type) == 0)
return ERR_PTR(-EINVAL);
if (type && strlen(type) >= THERMAL_NAME_LENGTH) if (type && strlen(type) >= THERMAL_NAME_LENGTH)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
...@@ -1885,7 +1162,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1885,7 +1162,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp)) if (trips > 0 && (!ops->get_trip_type || !ops->get_trip_temp))
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); tz = kzalloc(sizeof(*tz), GFP_KERNEL);
if (!tz) if (!tz)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -1898,7 +1175,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1898,7 +1175,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
return ERR_PTR(result); return ERR_PTR(result);
} }
strlcpy(tz->type, type ? : "", sizeof(tz->type)); strlcpy(tz->type, type, sizeof(tz->type));
tz->ops = ops; tz->ops = ops;
tz->tzp = tzp; tz->tzp = tzp;
tz->device.class = &thermal_class; tz->device.class = &thermal_class;
...@@ -1906,6 +1183,13 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1906,6 +1183,13 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
tz->trips = trips; tz->trips = trips;
tz->passive_delay = passive_delay; tz->passive_delay = passive_delay;
tz->polling_delay = polling_delay; tz->polling_delay = polling_delay;
/* sys I/F */
/* Add nodes that are always present via .groups */
result = thermal_zone_create_device_groups(tz, mask);
if (result)
goto unregister;
/* A new thermal zone needs to be updated anyway. */ /* A new thermal zone needs to be updated anyway. */
atomic_set(&tz->need_update, 1); atomic_set(&tz->need_update, 1);
...@@ -1917,32 +1201,9 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1917,32 +1201,9 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
return ERR_PTR(result); return ERR_PTR(result);
} }
/* sys I/F */
if (type) {
result = device_create_file(&tz->device, &dev_attr_type);
if (result)
goto unregister;
}
result = device_create_file(&tz->device, &dev_attr_temp);
if (result)
goto unregister;
if (ops->get_mode) {
result = device_create_file(&tz->device, &dev_attr_mode);
if (result)
goto unregister;
}
result = create_trip_attrs(tz, mask);
if (result)
goto unregister;
for (count = 0; count < trips; count++) { for (count = 0; count < trips; count++) {
if (tz->ops->get_trip_type(tz, count, &trip_type)) if (tz->ops->get_trip_type(tz, count, &trip_type))
set_bit(count, &tz->trips_disabled); set_bit(count, &tz->trips_disabled);
if (trip_type == THERMAL_TRIP_PASSIVE)
passive = 1;
if (tz->ops->get_trip_temp(tz, count, &trip_temp)) if (tz->ops->get_trip_temp(tz, count, &trip_temp))
set_bit(count, &tz->trips_disabled); set_bit(count, &tz->trips_disabled);
/* Check for bogus trip points */ /* Check for bogus trip points */
...@@ -1950,33 +1211,6 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -1950,33 +1211,6 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
set_bit(count, &tz->trips_disabled); set_bit(count, &tz->trips_disabled);
} }
if (!passive) {
result = device_create_file(&tz->device, &dev_attr_passive);
if (result)
goto unregister;
}
if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) {
result = device_create_file(&tz->device, &dev_attr_emul_temp);
if (result)
goto unregister;
}
/* Create policy attribute */
result = device_create_file(&tz->device, &dev_attr_policy);
if (result)
goto unregister;
/* Add thermal zone params */
result = create_tzp_attrs(&tz->device);
if (result)
goto unregister;
/* Create available_policies attribute */
result = device_create_file(&tz->device, &dev_attr_available_policies);
if (result)
goto unregister;
/* Update 'this' zone's governor information */ /* Update 'this' zone's governor information */
mutex_lock(&thermal_governor_lock); mutex_lock(&thermal_governor_lock);
...@@ -2006,7 +1240,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type, ...@@ -2006,7 +1240,7 @@ struct thermal_zone_device *thermal_zone_device_register(const char *type,
/* Bind cooling devices for this zone */ /* Bind cooling devices for this zone */
bind_tz(tz); bind_tz(tz);
INIT_DELAYED_WORK(&(tz->poll_queue), thermal_zone_device_check); INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_check);
thermal_zone_device_reset(tz); thermal_zone_device_reset(tz);
/* Update the new thermal zone and mark it as already updated. */ /* Update the new thermal zone and mark it as already updated. */
...@@ -2071,14 +1305,10 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) ...@@ -2071,14 +1305,10 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
thermal_zone_device_set_polling(tz, 0); thermal_zone_device_set_polling(tz, 0);
if (tz->type[0]) kfree(tz->trip_type_attrs);
device_remove_file(&tz->device, &dev_attr_type); kfree(tz->trip_temp_attrs);
device_remove_file(&tz->device, &dev_attr_temp); kfree(tz->trip_hyst_attrs);
if (tz->ops->get_mode) kfree(tz->trips_attribute_group.attrs);
device_remove_file(&tz->device, &dev_attr_mode);
device_remove_file(&tz->device, &dev_attr_policy);
device_remove_file(&tz->device, &dev_attr_available_policies);
remove_trip_attrs(tz);
thermal_set_governor(tz, NULL); thermal_set_governor(tz, NULL);
thermal_remove_hwmon_sysfs(tz); thermal_remove_hwmon_sysfs(tz);
...@@ -2086,7 +1316,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz) ...@@ -2086,7 +1316,7 @@ void thermal_zone_device_unregister(struct thermal_zone_device *tz)
idr_destroy(&tz->idr); idr_destroy(&tz->idr);
mutex_destroy(&tz->lock); mutex_destroy(&tz->lock);
device_unregister(&tz->device); device_unregister(&tz->device);
return; kfree(tz->device.groups);
} }
EXPORT_SYMBOL_GPL(thermal_zone_device_unregister); EXPORT_SYMBOL_GPL(thermal_zone_device_unregister);
...@@ -2128,36 +1358,6 @@ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name) ...@@ -2128,36 +1358,6 @@ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name)
} }
EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name); EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
/**
* 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);
#ifdef CONFIG_NET #ifdef CONFIG_NET
static const struct genl_multicast_group thermal_event_mcgrps[] = { static const struct genl_multicast_group thermal_event_mcgrps[] = {
{ .name = THERMAL_GENL_MCAST_GROUP_NAME, }, { .name = THERMAL_GENL_MCAST_GROUP_NAME, },
...@@ -2249,38 +1449,6 @@ static inline int genetlink_init(void) { return 0; } ...@@ -2249,38 +1449,6 @@ static inline int genetlink_init(void) { return 0; }
static inline void genetlink_exit(void) {} static inline void genetlink_exit(void) {}
#endif /* !CONFIG_NET */ #endif /* !CONFIG_NET */
static int __init thermal_register_governors(void)
{
int result;
result = thermal_gov_step_wise_register();
if (result)
return result;
result = thermal_gov_fair_share_register();
if (result)
return result;
result = thermal_gov_bang_bang_register();
if (result)
return result;
result = thermal_gov_user_space_register();
if (result)
return result;
return thermal_gov_power_allocator_register();
}
static void thermal_unregister_governors(void)
{
thermal_gov_step_wise_unregister();
thermal_gov_fair_share_unregister();
thermal_gov_bang_bang_unregister();
thermal_gov_user_space_unregister();
thermal_gov_power_allocator_unregister();
}
static int thermal_pm_notify(struct notifier_block *nb, static int thermal_pm_notify(struct notifier_block *nb,
unsigned long mode, void *_unused) unsigned long mode, void *_unused)
{ {
......
...@@ -54,8 +54,34 @@ struct thermal_instance { ...@@ -54,8 +54,34 @@ struct thermal_instance {
unsigned int weight; /* The weight of the cooling device */ 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 *); int thermal_register_governor(struct thermal_governor *);
void thermal_unregister_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 #ifdef CONFIG_THERMAL_GOV_STEP_WISE
int thermal_gov_step_wise_register(void); int thermal_gov_step_wise_register(void);
......
/*
* 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);
/*
* 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) ...@@ -1298,7 +1298,7 @@ int ti_bandgap_probe(struct platform_device *pdev)
if (IS_ERR(bgp->div_clk)) { if (IS_ERR(bgp->div_clk)) {
dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n"); dev_err(&pdev->dev, "failed to request div_ts_ck clock ref\n");
ret = PTR_ERR(bgp->div_clk); ret = PTR_ERR(bgp->div_clk);
goto free_irqs; goto put_fclock;
} }
for (i = 0; i < bgp->conf->sensor_count; i++) { for (i = 0; i < bgp->conf->sensor_count; i++) {
...@@ -1430,8 +1430,9 @@ int ti_bandgap_probe(struct platform_device *pdev) ...@@ -1430,8 +1430,9 @@ int ti_bandgap_probe(struct platform_device *pdev)
if (TI_BANDGAP_HAS(bgp, CLK_CTRL)) if (TI_BANDGAP_HAS(bgp, CLK_CTRL))
clk_disable_unprepare(bgp->fclock); clk_disable_unprepare(bgp->fclock);
put_clks: put_clks:
clk_put(bgp->fclock);
clk_put(bgp->div_clk); clk_put(bgp->div_clk);
put_fclock:
clk_put(bgp->fclock);
free_irqs: free_irqs:
if (TI_BANDGAP_HAS(bgp, TSHUT)) { if (TI_BANDGAP_HAS(bgp, TSHUT)) {
free_irq(gpio_to_irq(bgp->tshut_gpio), NULL); free_irq(gpio_to_irq(bgp->tshut_gpio), NULL);
......
...@@ -54,37 +54,33 @@ MODULE_PARM_DESC(notify_delay_ms, ...@@ -54,37 +54,33 @@ MODULE_PARM_DESC(notify_delay_ms,
* is some wrong values returned by cpuid for number of thresholds. * is some wrong values returned by cpuid for number of thresholds.
*/ */
#define MAX_NUMBER_OF_TRIPS 2 #define MAX_NUMBER_OF_TRIPS 2
/* Limit number of package temp zones */
#define MAX_PKG_TEMP_ZONE_IDS 256
struct phy_dev_entry { struct pkg_device {
struct list_head list; int cpu;
u16 phys_proc_id; bool work_scheduled;
u16 first_cpu;
u32 tj_max; u32 tj_max;
int ref_cnt; u32 msr_pkg_therm_low;
u32 start_pkg_therm_low; u32 msr_pkg_therm_high;
u32 start_pkg_therm_high; struct delayed_work work;
struct thermal_zone_device *tzone; struct thermal_zone_device *tzone;
struct cpumask cpumask;
}; };
static struct thermal_zone_params pkg_temp_tz_params = { static struct thermal_zone_params pkg_temp_tz_params = {
.no_hwmon = true, .no_hwmon = true,
}; };
/* List maintaining number of package instances */ /* Keep track of how many package pointers we allocated in init() */
static LIST_HEAD(phy_dev_list); static int max_packages __read_mostly;
static DEFINE_MUTEX(phy_dev_list_mutex); /* 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 */ /* The dynamically assigned cpu hotplug state for module_exit() */
static DEFINE_PER_CPU(struct delayed_work, pkg_temp_thermal_threshold_work); static enum cpuhp_state pkg_thermal_hp_state __read_mostly;
/* 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;
/* Debug counters to show using debugfs */ /* Debug counters to show using debugfs */
static struct dentry *debugfs; static struct dentry *debugfs;
...@@ -116,22 +112,20 @@ static int pkg_temp_debugfs_init(void) ...@@ -116,22 +112,20 @@ static int pkg_temp_debugfs_init(void)
return -ENOENT; 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); int pkgid = topology_logical_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);
if (pkgid >= 0 && pkgid < max_packages)
return packages[pkgid];
return NULL; return NULL;
} }
...@@ -141,61 +135,44 @@ static struct phy_dev_entry ...@@ -141,61 +135,44 @@ static struct phy_dev_entry
*/ */
static int get_tj_max(int cpu, u32 *tj_max) static int get_tj_max(int cpu, u32 *tj_max)
{ {
u32 eax, edx; u32 eax, edx, val;
u32 val;
int err; int err;
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err) if (err)
goto err_ret; return err;
else {
val = (eax >> 16) & 0xff; val = (eax >> 16) & 0xff;
if (val)
*tj_max = val * 1000; *tj_max = val * 1000;
else {
err = -EINVAL;
goto err_ret;
}
}
return 0; return val ? 0 : -EINVAL;
err_ret:
*tj_max = 0;
return err;
} }
static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp) static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
{ {
struct pkg_device *pkgdev = tzd->devdata;
u32 eax, edx; u32 eax, edx;
struct phy_dev_entry *phy_dev_entry;
phy_dev_entry = tzd->devdata; rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx);
rdmsr_on_cpu(phy_dev_entry->first_cpu, MSR_IA32_PACKAGE_THERM_STATUS,
&eax, &edx);
if (eax & 0x80000000) { if (eax & 0x80000000) {
*temp = phy_dev_entry->tj_max - *temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000;
((eax >> 16) & 0x7f) * 1000;
pr_debug("sys_get_curr_temp %d\n", *temp); pr_debug("sys_get_curr_temp %d\n", *temp);
return 0; return 0;
} }
return -EINVAL; return -EINVAL;
} }
static int sys_get_trip_temp(struct thermal_zone_device *tzd, static int sys_get_trip_temp(struct thermal_zone_device *tzd,
int trip, int *temp) int trip, int *temp)
{ {
u32 eax, edx; struct pkg_device *pkgdev = tzd->devdata;
struct phy_dev_entry *phy_dev_entry;
u32 mask, shift;
unsigned long thres_reg_value; unsigned long thres_reg_value;
u32 mask, shift, eax, edx;
int ret; int ret;
if (trip >= MAX_NUMBER_OF_TRIPS) if (trip >= MAX_NUMBER_OF_TRIPS)
return -EINVAL; return -EINVAL;
phy_dev_entry = tzd->devdata;
if (trip) { if (trip) {
mask = THERM_MASK_THRESHOLD1; mask = THERM_MASK_THRESHOLD1;
shift = THERM_SHIFT_THRESHOLD1; shift = THERM_SHIFT_THRESHOLD1;
...@@ -204,14 +181,14 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd, ...@@ -204,14 +181,14 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
shift = THERM_SHIFT_THRESHOLD0; shift = THERM_SHIFT_THRESHOLD0;
} }
ret = rdmsr_on_cpu(phy_dev_entry->first_cpu, ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
MSR_IA32_PACKAGE_THERM_INTERRUPT, &eax, &edx); &eax, &edx);
if (ret < 0) if (ret < 0)
return -EINVAL; return ret;
thres_reg_value = (eax & mask) >> shift; thres_reg_value = (eax & mask) >> shift;
if (thres_reg_value) if (thres_reg_value)
*temp = phy_dev_entry->tj_max - thres_reg_value * 1000; *temp = pkgdev->tj_max - thres_reg_value * 1000;
else else
*temp = 0; *temp = 0;
pr_debug("sys_get_trip_temp %d\n", *temp); pr_debug("sys_get_trip_temp %d\n", *temp);
...@@ -219,24 +196,20 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd, ...@@ -219,24 +196,20 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzd,
return 0; return 0;
} }
static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, static int
int temp) sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
{ {
u32 l, h; struct pkg_device *pkgdev = tzd->devdata;
struct phy_dev_entry *phy_dev_entry; u32 l, h, mask, shift, intr;
u32 mask, shift, intr;
int ret; int ret;
phy_dev_entry = tzd->devdata; if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max)
if (trip >= MAX_NUMBER_OF_TRIPS || temp >= phy_dev_entry->tj_max)
return -EINVAL; return -EINVAL;
ret = rdmsr_on_cpu(phy_dev_entry->first_cpu, ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
MSR_IA32_PACKAGE_THERM_INTERRUPT,
&l, &h); &l, &h);
if (ret < 0) if (ret < 0)
return -EINVAL; return ret;
if (trip) { if (trip) {
mask = THERM_MASK_THRESHOLD1; mask = THERM_MASK_THRESHOLD1;
...@@ -252,24 +225,20 @@ static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, ...@@ -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 * When users space sets a trip temperature == 0, which is indication
* that, it is no longer interested in receiving notifications. * that, it is no longer interested in receiving notifications.
*/ */
if (!temp) if (!temp) {
l &= ~intr; l &= ~intr;
else { } else {
l |= (phy_dev_entry->tj_max - temp)/1000 << shift; l |= (pkgdev->tj_max - temp)/1000 << shift;
l |= intr; l |= intr;
} }
return wrmsr_on_cpu(phy_dev_entry->first_cpu, return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
MSR_IA32_PACKAGE_THERM_INTERRUPT,
l, h);
} }
static int sys_get_trip_type(struct thermal_zone_device *thermal, static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
int trip, enum thermal_trip_type *type) enum thermal_trip_type *type)
{ {
*type = THERMAL_TRIP_PASSIVE; *type = THERMAL_TRIP_PASSIVE;
return 0; return 0;
} }
...@@ -281,7 +250,7 @@ static struct thermal_zone_device_ops tzone_ops = { ...@@ -281,7 +250,7 @@ static struct thermal_zone_device_ops tzone_ops = {
.set_trip_temp = sys_set_trip_temp, .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; return true;
} }
...@@ -289,8 +258,8 @@ static bool pkg_temp_thermal_platform_thermal_rate_control(void) ...@@ -289,8 +258,8 @@ static bool pkg_temp_thermal_platform_thermal_rate_control(void)
/* Enable threshold interrupt on local package/cpu */ /* Enable threshold interrupt on local package/cpu */
static inline void enable_pkg_thres_interrupt(void) static inline void enable_pkg_thres_interrupt(void)
{ {
u32 l, h;
u8 thres_0, thres_1; u8 thres_0, thres_1;
u32 l, h;
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
/* only enable/disable if it had valid threshold value */ /* only enable/disable if it had valid threshold value */
...@@ -307,271 +276,232 @@ static inline void enable_pkg_thres_interrupt(void) ...@@ -307,271 +276,232 @@ static inline void enable_pkg_thres_interrupt(void)
static inline void disable_pkg_thres_interrupt(void) static inline void disable_pkg_thres_interrupt(void)
{ {
u32 l, h; u32 l, h;
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h); rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
l & (~THERM_INT_THRESHOLD0_ENABLE) & l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
(~THERM_INT_THRESHOLD1_ENABLE), h); wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
} }
static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) 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 cpu = smp_processor_id();
int phy_id = topology_physical_package_id(cpu); struct pkg_device *pkgdev;
struct phy_dev_entry *phdev = pkg_temp_thermal_get_phy_entry(cpu); u64 msr_val, wr_val;
bool notify = false;
unsigned long flags;
if (!phdev)
return;
spin_lock_irqsave(&pkg_work_lock, flags); mutex_lock(&thermal_zone_mutex);
spin_lock_irq(&pkg_temp_lock);
++pkg_work_cnt; ++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; return;
} }
pkg_work_scheduled[phy_id] = 0; pkgdev->work_scheduled = false;
spin_unlock_irqrestore(&pkg_work_lock, flags);
enable_pkg_thres_interrupt();
rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val); rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
if (msr_val & THERM_LOG_THRESHOLD0) { wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, if (wr_val != msr_val) {
msr_val & ~THERM_LOG_THRESHOLD0); wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val);
notify = true; tzone = pkgdev->tzone;
}
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) enable_pkg_thres_interrupt();
{ spin_unlock_irq(&pkg_temp_lock);
unsigned long flags;
int cpu = smp_processor_id();
int phy_id = topology_physical_package_id(cpu);
/* /*
* When a package is in interrupted state, all CPU's in that package * If tzone is not NULL, then thermal_zone_mutex will prevent the
* are in the same interrupt state. So scheduling on any one CPU in * concurrent removal in the cpu offline callback.
* the package is enough and simply return for others.
*/ */
spin_lock_irqsave(&pkg_work_lock, flags); if (tzone)
++pkg_interrupt_cnt; thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED);
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);
disable_pkg_thres_interrupt(); mutex_unlock(&thermal_zone_mutex);
schedule_delayed_work_on(cpu, }
&per_cpu(pkg_temp_thermal_threshold_work, cpu),
msecs_to_jiffies(notify_delay_ms)); static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work)
return 0; {
unsigned long ms = msecs_to_jiffies(notify_delay_ms);
schedule_delayed_work_on(cpu, work, ms);
} }
static int find_siblings_cpu(int cpu) static int pkg_thermal_notify(u64 msr_val)
{ {
int i; int cpu = smp_processor_id();
int id = topology_physical_package_id(cpu); struct pkg_device *pkgdev;
unsigned long flags;
for_each_online_cpu(i) spin_lock_irqsave(&pkg_temp_lock, flags);
if (i != cpu && topology_physical_package_id(i) == id) ++pkg_interrupt_cnt;
return i;
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; return 0;
} }
static int pkg_temp_thermal_device_add(unsigned int cpu) static int pkg_temp_thermal_device_add(unsigned int cpu)
{ {
int err; int pkgid = topology_logical_package_id(cpu);
u32 tj_max; u32 tj_max, eax, ebx, ecx, edx;
struct phy_dev_entry *phy_dev_entry; struct pkg_device *pkgdev;
int thres_count; int thres_count, err;
u32 eax, ebx, ecx, edx;
u8 *temp; if (pkgid >= max_packages)
unsigned long flags; return -ENOMEM;
cpuid(6, &eax, &ebx, &ecx, &edx); cpuid(6, &eax, &ebx, &ecx, &edx);
thres_count = ebx & 0x07; thres_count = ebx & 0x07;
if (!thres_count) if (!thres_count)
return -ENODEV; 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); thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS);
err = get_tj_max(cpu, &tj_max); err = get_tj_max(cpu, &tj_max);
if (err) if (err)
goto err_ret; return err;
mutex_lock(&phy_dev_list_mutex);
phy_dev_entry = kzalloc(sizeof(*phy_dev_entry), GFP_KERNEL); pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL);
if (!phy_dev_entry) { if (!pkgdev)
err = -ENOMEM; return -ENOMEM;
goto err_ret_unlock;
}
spin_lock_irqsave(&pkg_work_lock, flags); INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn);
if (topology_physical_package_id(cpu) > max_phy_id) pkgdev->cpu = cpu;
max_phy_id = topology_physical_package_id(cpu); pkgdev->tj_max = tj_max;
temp = krealloc(pkg_work_scheduled, pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp",
(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",
thres_count, thres_count,
(thres_count == MAX_NUMBER_OF_TRIPS) ? (thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
0x03 : 0x01, pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
phy_dev_entry, &tzone_ops, &pkg_temp_tz_params, 0, 0); if (IS_ERR(pkgdev->tzone)) {
if (IS_ERR(phy_dev_entry->tzone)) { err = PTR_ERR(pkgdev->tzone);
err = PTR_ERR(phy_dev_entry->tzone); kfree(pkgdev);
goto err_ret_free; return err;
} }
/* Store MSR value for package thermal interrupt, to restore at exit */ /* Store MSR value for package thermal interrupt, to restore at exit */
rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low,
&phy_dev_entry->start_pkg_therm_low, pkgdev->msr_pkg_therm_high);
&phy_dev_entry->start_pkg_therm_high);
list_add_tail(&phy_dev_entry->list, &phy_dev_list); cpumask_set_cpu(cpu, &pkgdev->cpumask);
pr_debug("pkg_temp_thermal_device_add :phy_id %d cpu %d\n", spin_lock_irq(&pkg_temp_lock);
phy_dev_entry->phys_proc_id, cpu); packages[pkgid] = pkgdev;
spin_unlock_irq(&pkg_temp_lock);
return 0;
}
mutex_unlock(&phy_dev_list_mutex); static int pkg_thermal_cpu_offline(unsigned int cpu)
{
struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
bool lastcpu, was_target;
int target;
if (!pkgdev)
return 0; return 0;
err_ret_free: target = cpumask_any_but(&pkgdev->cpumask, cpu);
kfree(phy_dev_entry); cpumask_clear_cpu(cpu, &pkgdev->cpumask);
err_ret_unlock: lastcpu = target >= nr_cpu_ids;
mutex_unlock(&phy_dev_list_mutex); /*
* 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;
err_ret: /*
return err; * 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);
static int pkg_temp_thermal_device_remove(unsigned int cpu) thermal_zone_device_unregister(tzone);
{ }
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);
if (!phdev) /* Protect against work and interrupts */
return -ENODEV; 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;
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);
}
/* /*
* It is possible that no siblings left as this was the last cpu * If this is the last CPU in the package remove the package
* going offline. We don't need to worry about this assignment * reference from the array and restore the interrupt MSR. When we
* as the phydev entry will be removed in this case and * drop the lock neither the interrupt notify function nor the
* thermal zone is removed. * worker will see the package anymore.
*/ */
--phdev->ref_cnt; if (lastcpu) {
pr_debug("thermal_device_remove: pkg: %d cpu %d ref_cnt %d\n", packages[topology_logical_package_id(cpu)] = NULL;
phys_proc_id, cpu, phdev->ref_cnt); /* After this point nothing touches the MSR anymore. */
if (!phdev->ref_cnt) wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
list_for_each_entry_safe(phdev, n, &phy_dev_list, list) { pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high);
if (phdev->phys_proc_id == phys_proc_id) {
thermal_zone_device_unregister(phdev->tzone);
list_del(&phdev->list);
kfree(phdev);
break;
} }
/*
* 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);
} }
mutex_unlock(&phy_dev_list_mutex);
spin_unlock_irq(&pkg_temp_lock);
/* Final cleanup if this is the last cpu */
if (lastcpu)
kfree(pkgdev);
return 0; return 0;
} }
static int get_core_online(unsigned int cpu) 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); 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 */ /* Paranoia check */
if (!phdev) { if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS))
if (!cpu_has(c, X86_FEATURE_DTHERM) ||
!cpu_has(c, X86_FEATURE_PTS))
return -ENODEV; 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);
/* If the package exists, nothing to do */
if (pkgdev) {
cpumask_set_cpu(cpu, &pkgdev->cpumask);
return 0; 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; return pkg_temp_thermal_device_add(cpu);
} }
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[] = { static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS }, { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS },
{} {}
...@@ -580,71 +510,46 @@ MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids); ...@@ -580,71 +510,46 @@ MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
static int __init pkg_temp_thermal_init(void) static int __init pkg_temp_thermal_init(void)
{ {
int i; int ret;
if (!x86_match_cpu(pkg_temp_thermal_ids)) if (!x86_match_cpu(pkg_temp_thermal_ids))
return -ENODEV; return -ENODEV;
spin_lock_init(&pkg_work_lock); max_packages = topology_max_packages();
platform_thermal_package_notify = packages = kzalloc(max_packages * sizeof(struct pkg_device *), GFP_KERNEL);
pkg_temp_thermal_platform_thermal_notify; if (!packages)
platform_thermal_package_rate_control = return -ENOMEM;
pkg_temp_thermal_platform_thermal_rate_control;
cpu_notifier_register_begin(); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online",
for_each_online_cpu(i) pkg_thermal_cpu_online, pkg_thermal_cpu_offline);
if (get_core_online(i)) if (ret < 0)
goto err_ret; goto err;
__register_hotcpu_notifier(&pkg_temp_thermal_notifier);
cpu_notifier_register_done();
pkg_temp_debugfs_init(); /* Don't care if fails */ /* Store the state for module exit */
pkg_thermal_hp_state = ret;
return 0; platform_thermal_package_notify = pkg_thermal_notify;
platform_thermal_package_rate_control = pkg_thermal_rate_control;
err_ret: /* Don't care if it fails */
for_each_online_cpu(i) pkg_temp_debugfs_init();
put_core_offline(i); return 0;
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) 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_notify = NULL;
platform_thermal_package_rate_control = 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); debugfs_remove_recursive(debugfs);
kfree(packages);
} }
module_init(pkg_temp_thermal_init)
module_exit(pkg_temp_thermal_exit) module_exit(pkg_temp_thermal_exit)
MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver"); MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver");
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include <linux/devfreq.h> #include <linux/devfreq.h>
#include <linux/thermal.h> #include <linux/thermal.h>
#ifdef CONFIG_DEVFREQ_THERMAL
/** /**
* struct devfreq_cooling_power - Devfreq cooling power ops * struct devfreq_cooling_power - Devfreq cooling power ops
...@@ -37,12 +36,16 @@ ...@@ -37,12 +36,16 @@
* @dyn_power_coeff * frequency * voltage^2 * @dyn_power_coeff * frequency * voltage^2
*/ */
struct devfreq_cooling_power { struct devfreq_cooling_power {
unsigned long (*get_static_power)(unsigned long voltage); unsigned long (*get_static_power)(struct devfreq *devfreq,
unsigned long (*get_dynamic_power)(unsigned long freq, unsigned long voltage);
unsigned long (*get_dynamic_power)(struct devfreq *devfreq,
unsigned long freq,
unsigned long voltage); unsigned long voltage);
unsigned long dyn_power_coeff; unsigned long dyn_power_coeff;
}; };
#ifdef CONFIG_DEVFREQ_THERMAL
struct thermal_cooling_device * struct thermal_cooling_device *
of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
struct devfreq_cooling_power *dfc_power); struct devfreq_cooling_power *dfc_power);
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <linux/of.h> #include <linux/of.h>
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <uapi/linux/thermal.h> #include <uapi/linux/thermal.h>
...@@ -204,6 +205,7 @@ struct thermal_zone_device { ...@@ -204,6 +205,7 @@ struct thermal_zone_device {
int id; int id;
char type[THERMAL_NAME_LENGTH]; char type[THERMAL_NAME_LENGTH];
struct device device; struct device device;
struct attribute_group trips_attribute_group;
struct thermal_attr *trip_temp_attrs; struct thermal_attr *trip_temp_attrs;
struct thermal_attr *trip_type_attrs; struct thermal_attr *trip_type_attrs;
struct thermal_attr *trip_hyst_attrs; struct thermal_attr *trip_hyst_attrs;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment