From 100a8fdbf525bb11796692a713c267be6523a890 Mon Sep 17 00:00:00 2001 From: Punit Agrawal Date: Tue, 29 Jul 2014 11:50:48 +0100 Subject: thermal: trace: Trace temperature changes Create a new event to trace the temperature of a thermal zone. Using this event trace the temperature changes of the thermal zone every-time it is updated. Cc: Zhang Rui Cc: Eduardo Valentin Cc: Steven Rostedt Cc: Frederic Weisbecker Cc: Ingo Molnar Signed-off-by: Punit Agrawal Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 71b0ec0..6b32391 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -38,6 +38,9 @@ #include #include +#define CREATE_TRACE_POINTS +#include + #include "thermal_core.h" #include "thermal_hwmon.h" @@ -463,6 +466,7 @@ static void update_temperature(struct thermal_zone_device *tz) tz->temperature = temp; mutex_unlock(&tz->lock); + trace_thermal_temperature(tz); dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n", tz->last_temperature, tz->temperature); } diff --git a/include/trace/events/thermal.h b/include/trace/events/thermal.h new file mode 100644 index 0000000..8c5ca96 --- /dev/null +++ b/include/trace/events/thermal.h @@ -0,0 +1,38 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM thermal + +#if !defined(_TRACE_THERMAL_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_THERMAL_H + +#include +#include + +TRACE_EVENT(thermal_temperature, + + TP_PROTO(struct thermal_zone_device *tz), + + TP_ARGS(tz), + + TP_STRUCT__entry( + __string(thermal_zone, tz->type) + __field(int, id) + __field(int, temp_prev) + __field(int, temp) + ), + + TP_fast_assign( + __assign_str(thermal_zone, tz->type); + __entry->id = tz->id; + __entry->temp_prev = tz->last_temperature; + __entry->temp = tz->temperature; + ), + + TP_printk("thermal_zone=%s id=%d temp_prev=%d temp=%d", + __get_str(thermal_zone), __entry->id, __entry->temp_prev, + __entry->temp) +); + +#endif /* _TRACE_THERMAL_H */ + +/* This part must be outside protection */ +#include -- cgit v0.10.2 From 39811569e43a81417bc0ddca3d0c7658c3dcd4b0 Mon Sep 17 00:00:00 2001 From: Punit Agrawal Date: Tue, 29 Jul 2014 11:50:49 +0100 Subject: thermal: trace: Trace when a cooling device's state is updated Introduce and use an event to trace when a cooling device's state is updated. This is useful to follow the effect of governor decisions on cooling devices. Cc: Zhang Rui Cc: Eduardo Valentin Cc: Steven Rostedt Cc: Frederic Weisbecker Cc: Ingo Molnar Signed-off-by: Punit Agrawal Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 6b32391..c74c78d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1291,6 +1291,7 @@ void thermal_cdev_update(struct thermal_cooling_device *cdev) mutex_unlock(&cdev->lock); cdev->ops->set_cur_state(cdev, target); cdev->updated = true; + trace_cdev_update(cdev, target); dev_dbg(&cdev->device, "set to state %lu\n", target); } EXPORT_SYMBOL(thermal_cdev_update); diff --git a/include/trace/events/thermal.h b/include/trace/events/thermal.h index 8c5ca96..894a79e 100644 --- a/include/trace/events/thermal.h +++ b/include/trace/events/thermal.h @@ -32,6 +32,25 @@ TRACE_EVENT(thermal_temperature, __entry->temp) ); +TRACE_EVENT(cdev_update, + + TP_PROTO(struct thermal_cooling_device *cdev, unsigned long target), + + TP_ARGS(cdev, target), + + TP_STRUCT__entry( + __string(type, cdev->type) + __field(unsigned long, target) + ), + + TP_fast_assign( + __assign_str(type, cdev->type); + __entry->target = target; + ), + + TP_printk("type=%s target=%lu", __get_str(type), __entry->target) +); + #endif /* _TRACE_THERMAL_H */ /* This part must be outside protection */ -- cgit v0.10.2 From 208cd822a19e683bc890f6708786f2420e172d76 Mon Sep 17 00:00:00 2001 From: Punit Agrawal Date: Tue, 29 Jul 2014 11:50:50 +0100 Subject: thermal: trace: Trace when temperature is above a trip point Create a new event to trace when the temperature is above a trip point. Use the trace-point when handling non-critical and critical trip pionts. Cc: Zhang Rui Cc: Eduardo Valentin Cc: Steven Rostedt Cc: Frederic Weisbecker Cc: Ingo Molnar Signed-off-by: Punit Agrawal Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/fair_share.c b/drivers/thermal/fair_share.c index 944ba2f..6e0a3fb 100644 --- a/drivers/thermal/fair_share.c +++ b/drivers/thermal/fair_share.c @@ -23,6 +23,7 @@ */ #include +#include #include "thermal_core.h" @@ -34,6 +35,7 @@ static int get_trip_level(struct thermal_zone_device *tz) { int count = 0; unsigned long trip_temp; + enum thermal_trip_type trip_type; if (tz->trips == 0 || !tz->ops->get_trip_temp) return 0; @@ -43,6 +45,16 @@ static int get_trip_level(struct thermal_zone_device *tz) if (tz->temperature < trip_temp) break; } + + /* + * count > 0 only if temperature is greater than first trip + * point, in which case, trip_point = count - 1 + */ + if (count > 0) { + tz->ops->get_trip_type(tz, count - 1, &trip_type); + trace_thermal_zone_trip(tz, count - 1, trip_type); + } + return count; } diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index f251521..3b54c2c 100644 --- a/drivers/thermal/step_wise.c +++ b/drivers/thermal/step_wise.c @@ -23,6 +23,7 @@ */ #include +#include #include "thermal_core.h" @@ -129,8 +130,10 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) trend = get_tz_trend(tz, trip); - if (tz->temperature >= trip_temp) + if (tz->temperature >= trip_temp) { throttle = true; + trace_thermal_zone_trip(tz, trip, trip_type); + } dev_dbg(&tz->device, "Trip%d[type=%d,temp=%ld]:trend=%d,throttle=%d\n", trip, trip_type, trip_temp, trend, throttle); diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index c74c78d..454884a 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -371,6 +371,8 @@ static void handle_critical_trips(struct thermal_zone_device *tz, if (tz->temperature < trip_temp) return; + trace_thermal_zone_trip(tz, trip, trip_type); + if (tz->ops->notify) tz->ops->notify(tz, trip, trip_type); diff --git a/include/trace/events/thermal.h b/include/trace/events/thermal.h index 894a79e..0f4f95d 100644 --- a/include/trace/events/thermal.h +++ b/include/trace/events/thermal.h @@ -51,6 +51,32 @@ TRACE_EVENT(cdev_update, TP_printk("type=%s target=%lu", __get_str(type), __entry->target) ); +TRACE_EVENT(thermal_zone_trip, + + TP_PROTO(struct thermal_zone_device *tz, int trip, + enum thermal_trip_type trip_type), + + TP_ARGS(tz, trip, trip_type), + + TP_STRUCT__entry( + __string(thermal_zone, tz->type) + __field(int, id) + __field(int, trip) + __field(enum thermal_trip_type, trip_type) + ), + + TP_fast_assign( + __assign_str(thermal_zone, tz->type); + __entry->id = tz->id; + __entry->trip = trip; + __entry->trip_type = trip_type; + ), + + TP_printk("thermal_zone=%s id=%d trip=%d trip_type=%d", + __get_str(thermal_zone), __entry->id, __entry->trip, + __entry->trip_type) +); + #endif /* _TRACE_THERMAL_H */ /* This part must be outside protection */ -- cgit v0.10.2 From a020279ee611c31ac163c00b21b975e7ecbb2e9c Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Fri, 25 Jul 2014 15:31:58 +0530 Subject: thermal: add support to disable thermal zone from DTS Add support to check status of the thermal zone before registering the zone. This will help on disabling some non-existing thermal zone from the top level DTS file out of common dtsi thermalzone file. For example, we have 3 platforms almost same but thermal zones on this platform are little bit different. Platform 1 and 2 have three thermal zones and platform 3 has two thermal zones. To avoid duplication of the thermal zones entries on each DTS file of platforms,we created one common dtsi file for thermal zone and included this dtsi file from these 3 platform's top level dts file. On common thermal zone com dtsi file, all thermal zone are enabled and need to disable one of thermal zone on platform 3 dts file. For this, we just added entry status = "disabled" for that thermal zone on platform 3 dts file and along with this change to make it work. This way, we reuse the common file and control the enable/disable of the thermal zone from top level dts file. Signed-off-by: Laxman Dewangan Signed-off-by: Eduardo Valentin diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 4b2b999..f8eb625 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -401,6 +401,10 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id, struct of_phandle_args sensor_specs; int ret, id; + /* Check whether child is enabled or not */ + if (!of_device_is_available(child)) + continue; + /* For now, thermal framework supports only 1 sensor per zone */ ret = of_parse_phandle_with_args(child, "thermal-sensors", "#thermal-sensor-cells", @@ -771,6 +775,10 @@ int __init of_parse_thermal_zones(void) struct thermal_zone_device *zone; struct thermal_zone_params *tzp; + /* Check whether child is enabled or not */ + if (!of_device_is_available(child)) + continue; + tz = thermal_of_build_thermal_zone(child); if (IS_ERR(tz)) { pr_err("failed to build thermal zone %s: %ld\n", @@ -838,6 +846,10 @@ void of_thermal_destroy_zones(void) for_each_child_of_node(np, child) { struct thermal_zone_device *zone; + /* Check whether child is enabled or not */ + if (!of_device_is_available(child)) + continue; + zone = thermal_zone_get_zone_by_name(child->name); if (IS_ERR(zone)) continue; -- cgit v0.10.2 From 3c94f17e72a7bcf689756da100b6051e535c45f4 Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Wed, 6 Aug 2014 15:12:09 +0800 Subject: Thermal: imx: add i.mx6sx thermal support i.MX6SX has some new features of thermal interrupt function, there are LOW, HIGH and PANIC irq for thermal sensor, so add platform data to separate different thermal version; The reset value of LOW ALARM is 0 which means the highest temp, so the LOW ALARM will be triggered once irq is enabled, so we need to correct it before enabling thermal irq; Enable PANIC ALARM as critical trip point, it will trigger system reset via SRC module once PANIC IRQ is triggered, it is pure hardware function, so use it instead of software reset by cooling device. Signed-off-by: Anson Huang Tested-by: Shawn Guo Signed-off-by: Eduardo Valentin diff --git a/Documentation/devicetree/bindings/thermal/imx-thermal.txt b/Documentation/devicetree/bindings/thermal/imx-thermal.txt index 1f0f672..3c67bd5 100644 --- a/Documentation/devicetree/bindings/thermal/imx-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/imx-thermal.txt @@ -1,7 +1,10 @@ * Temperature Monitor (TEMPMON) on Freescale i.MX SoCs Required properties: -- compatible : "fsl,imx6q-thermal" +- compatible : "fsl,imx6q-tempmon" for i.MX6Q, "fsl,imx6sx-tempmon" for i.MX6SX. + i.MX6SX has two more IRQs than i.MX6Q, one is IRQ_LOW and the other is IRQ_PANIC, + when temperature is below than low threshold, IRQ_LOW will be triggered, when temperature + is higher than panic threshold, system will auto reboot by SRC module. - fsl,tempmon : phandle pointer to system controller that contains TEMPMON control registers, e.g. ANATOP on imx6q. - fsl,tempmon-data : phandle pointer to fuse controller that contains TEMPMON diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 2c516f2..461bf3d 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,11 @@ #define MISC0 0x0150 #define MISC0_REFTOP_SELBIASOFF (1 << 3) +#define MISC1 0x0160 +#define MISC1_IRQ_TEMPHIGH (1 << 29) +/* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */ +#define MISC1_IRQ_TEMPLOW (1 << 28) +#define MISC1_IRQ_TEMPPANIC (1 << 27) #define TEMPSENSE0 0x0180 #define TEMPSENSE0_ALARM_VALUE_SHIFT 20 @@ -43,6 +49,12 @@ #define TEMPSENSE1 0x0190 #define TEMPSENSE1_MEASURE_FREQ 0xffff +/* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ +#define TEMPSENSE2 0x0290 +#define TEMPSENSE2_LOW_VALUE_SHIFT 0 +#define TEMPSENSE2_LOW_VALUE_MASK 0xfff +#define TEMPSENSE2_PANIC_VALUE_SHIFT 16 +#define TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000 #define OCOTP_ANA1 0x04e0 @@ -66,6 +78,21 @@ enum imx_thermal_trip { #define FACTOR1 15976 #define FACTOR2 4297157 +#define TEMPMON_IMX6Q 1 +#define TEMPMON_IMX6SX 2 + +struct thermal_soc_data { + u32 version; +}; + +static struct thermal_soc_data thermal_imx6q_data = { + .version = TEMPMON_IMX6Q, +}; + +static struct thermal_soc_data thermal_imx6sx_data = { + .version = TEMPMON_IMX6SX, +}; + struct imx_thermal_data { struct thermal_zone_device *tz; struct thermal_cooling_device *cdev; @@ -79,8 +106,21 @@ struct imx_thermal_data { bool irq_enabled; int irq; struct clk *thermal_clk; + const struct thermal_soc_data *socdata; }; +static void imx_set_panic_temp(struct imx_thermal_data *data, + signed long panic_temp) +{ + struct regmap *map = data->tempmon; + int critical_value; + + critical_value = (data->c2 - panic_temp) / data->c1; + regmap_write(map, TEMPSENSE2 + REG_CLR, TEMPSENSE2_PANIC_VALUE_MASK); + regmap_write(map, TEMPSENSE2 + REG_SET, critical_value << + TEMPSENSE2_PANIC_VALUE_SHIFT); +} + static void imx_set_alarm_temp(struct imx_thermal_data *data, signed long alarm_temp) { @@ -142,13 +182,17 @@ static int imx_get_temp(struct thermal_zone_device *tz, unsigned long *temp) /* See imx_get_sensor_data() for formula derivation */ *temp = data->c2 - n_meas * data->c1; - /* Update alarm value to next higher trip point */ - if (data->alarm_temp == data->temp_passive && *temp >= data->temp_passive) - imx_set_alarm_temp(data, data->temp_critical); - if (data->alarm_temp == data->temp_critical && *temp < data->temp_passive) { - imx_set_alarm_temp(data, data->temp_passive); - dev_dbg(&tz->device, "thermal alarm off: T < %lu\n", - data->alarm_temp / 1000); + /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ + if (data->socdata->version == TEMPMON_IMX6Q) { + if (data->alarm_temp == data->temp_passive && + *temp >= data->temp_passive) + imx_set_alarm_temp(data, data->temp_critical); + if (data->alarm_temp == data->temp_critical && + *temp < data->temp_passive) { + imx_set_alarm_temp(data, data->temp_passive); + dev_dbg(&tz->device, "thermal alarm off: T < %lu\n", + data->alarm_temp / 1000); + } } if (*temp != data->last_temp) { @@ -398,8 +442,17 @@ static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) return IRQ_HANDLED; } +static const struct of_device_id of_imx_thermal_match[] = { + { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, + { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_imx_thermal_match); + static int imx_thermal_probe(struct platform_device *pdev) { + const struct of_device_id *of_id = + of_match_device(of_imx_thermal_match, &pdev->dev); struct imx_thermal_data *data; struct cpumask clip_cpus; struct regmap *map; @@ -418,6 +471,20 @@ static int imx_thermal_probe(struct platform_device *pdev) } data->tempmon = map; + data->socdata = of_id->data; + + /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ + if (data->socdata->version == TEMPMON_IMX6SX) { + regmap_write(map, MISC1 + REG_CLR, MISC1_IRQ_TEMPHIGH | + MISC1_IRQ_TEMPLOW | MISC1_IRQ_TEMPPANIC); + /* + * reset value of LOW ALARM is incorrect, set it to lowest + * value to avoid false trigger of low alarm. + */ + regmap_write(map, TEMPSENSE2 + REG_SET, + TEMPSENSE2_LOW_VALUE_MASK); + } + data->irq = platform_get_irq(pdev, 0); if (data->irq < 0) return data->irq; @@ -489,6 +556,10 @@ static int imx_thermal_probe(struct platform_device *pdev) measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq); imx_set_alarm_temp(data, data->temp_passive); + + if (data->socdata->version == TEMPMON_IMX6SX) + imx_set_panic_temp(data, data->temp_critical); + regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); @@ -550,12 +621,6 @@ static int imx_thermal_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(imx_thermal_pm_ops, imx_thermal_suspend, imx_thermal_resume); -static const struct of_device_id of_imx_thermal_match[] = { - { .compatible = "fsl,imx6q-tempmon", }, - { /* end */ } -}; -MODULE_DEVICE_TABLE(of, of_imx_thermal_match); - static struct platform_driver imx_thermal = { .driver = { .name = "imx_thermal", -- cgit v0.10.2 From e4dbf98f7f169346f57296e173e883b7330076ab Mon Sep 17 00:00:00 2001 From: Peter Feuerer Date: Tue, 22 Jul 2014 17:37:13 +0200 Subject: thermal: Added Bang-bang thermal governor The bang-bang thermal governor uses a hysteresis to switch abruptly on or off a cooling device. It is intended to control fans, which can not be throttled but just switched on or off. Bang-bang cannot be set as default governor as it is intended for special devices only. For those special devices the driver needs to explicitely request it. Cc: Andrew Morton Cc: Zhang Rui Cc: Andreas Mohr Cc: Borislav Petkov Cc: Javi Merino Cc: linux-pm@vger.kernel.org Signed-off-by: Peter Feuerer Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 693208e..2500ecc 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -84,6 +84,16 @@ config THERMAL_GOV_STEP_WISE Enable this to manage platform thermals using a simple linear governor. +config THERMAL_GOV_BANG_BANG + bool "Bang Bang thermal governor" + default n + help + Enable this to manage platform thermals using bang bang governor. + + Say 'Y' here if you want to use two point temperature regulation + used for fans without throttling. Some fan drivers depend on this + governor to be enabled (e.g. acerhdf). + config THERMAL_GOV_USER_SPACE bool "User_space thermal governor" help diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 31e232f..b7e6542 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -11,6 +11,7 @@ thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o # governors thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o +thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o diff --git a/drivers/thermal/gov_bang_bang.c b/drivers/thermal/gov_bang_bang.c new file mode 100644 index 0000000..c5dd76b --- /dev/null +++ b/drivers/thermal/gov_bang_bang.c @@ -0,0 +1,131 @@ +/* + * gov_bang_bang.c - A simple thermal throttling governor using hysteresis + * + * Copyright (C) 2014 Peter Feuerer + * + * Based on step_wise.c with following Copyrights: + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R + * + * + * 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. + * + * 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. + * + */ + +#include + +#include "thermal_core.h" + +static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) +{ + long trip_temp; + unsigned long trip_hyst; + struct thermal_instance *instance; + + tz->ops->get_trip_temp(tz, trip, &trip_temp); + tz->ops->get_trip_hyst(tz, trip, &trip_hyst); + + dev_dbg(&tz->device, "Trip%d[temp=%ld]:temp=%d:hyst=%ld\n", + trip, trip_temp, tz->temperature, + trip_hyst); + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + /* in case fan is in initial state, switch the fan off */ + if (instance->target == THERMAL_NO_TARGET) + instance->target = 0; + + /* in case fan is neither on nor off set the fan to active */ + if (instance->target != 0 && instance->target != 1) { + pr_warn("Thermal instance %s controlled by bang-bang has unexpected state: %ld\n", + instance->name, instance->target); + instance->target = 1; + } + + /* + * enable fan when temperature exceeds trip_temp and disable + * the fan in case it falls below trip_temp minus hysteresis + */ + if (instance->target == 0 && tz->temperature >= trip_temp) + instance->target = 1; + else if (instance->target == 1 && + tz->temperature < trip_temp - trip_hyst) + instance->target = 0; + + dev_dbg(&instance->cdev->device, "target=%d\n", + (int)instance->target); + + instance->cdev->updated = false; /* cdev needs update */ + } + + mutex_unlock(&tz->lock); +} + +/** + * bang_bang_control - controls devices associated with the given zone + * @tz - thermal_zone_device + * @trip - the trip point + * + * Regulation Logic: a two point regulation, deliver cooling state depending + * on the previous state shown in this diagram: + * + * Fan: OFF ON + * + * | + * | + * trip_temp: +---->+ + * | | ^ + * | | | + * | | Temperature + * (trip_temp - hyst): +<----+ + * | + * | + * | + * + * * If the fan is not running and temperature exceeds trip_temp, the fan + * gets turned on. + * * In case the fan is running, temperature must fall below + * (trip_temp - hyst) so that the fan gets turned off again. + * + */ +static int bang_bang_control(struct thermal_zone_device *tz, int trip) +{ + struct thermal_instance *instance; + + thermal_zone_trip_update(tz, trip); + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) + thermal_cdev_update(instance->cdev); + + mutex_unlock(&tz->lock); + + return 0; +} + +static struct thermal_governor thermal_gov_bang_bang = { + .name = "bang_bang", + .throttle = bang_bang_control, +}; + +int thermal_gov_bang_bang_register(void) +{ + return thermal_register_governor(&thermal_gov_bang_bang); +} + +void thermal_gov_bang_bang_unregister(void) +{ + thermal_unregister_governor(&thermal_gov_bang_bang); +} diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 71b0ec0..4c2726b 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1790,6 +1790,10 @@ static int __init thermal_register_governors(void) if (result) return result; + result = thermal_gov_bang_bang_register(); + if (result) + return result; + return thermal_gov_user_space_register(); } @@ -1797,6 +1801,7 @@ 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(); } diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 3db339f..d15d243 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -69,6 +69,14 @@ static inline int thermal_gov_fair_share_register(void) { return 0; } static inline void thermal_gov_fair_share_unregister(void) {} #endif /* CONFIG_THERMAL_GOV_FAIR_SHARE */ +#ifdef CONFIG_THERMAL_GOV_BANG_BANG +int thermal_gov_bang_bang_register(void); +void thermal_gov_bang_bang_unregister(void); +#else +static inline int thermal_gov_bang_bang_register(void) { return 0; } +static inline void thermal_gov_bang_bang_unregister(void) {} +#endif /* CONFIG_THERMAL_GOV_BANG_BANG */ + #ifdef CONFIG_THERMAL_GOV_USER_SPACE int thermal_gov_user_space_register(void); void thermal_gov_user_space_unregister(void); -- cgit v0.10.2 From 3230bbfce8a9270acc77fafd0d9ff90e94f28993 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 14 Mar 2014 00:34:05 +0800 Subject: ACPI: introduce ACPI int340x thermal scan handler Newer laptops and tablets that use ACPI may have thermal sensors and other devices with thermal control capabilities outside the core CPU/SOC, for thermal safety reasons. They are exposed for the OS to use via 1) INT3400 ACPI device object as the master. 2) INT3401 ~ INT340B ACPI device objects as the slaves. This patch introduces a scan handler to enumerate the INT3400 ACPI device object to platform bus, and prevent its slaves from being enumerated before the controller driver being probed. Signed-off-by: Zhang Rui diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 505d4d7..c3b2fcb 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -43,6 +43,7 @@ acpi-y += pci_root.o pci_link.o pci_irq.o acpi-y += acpi_lpss.o acpi-y += acpi_platform.o acpi-y += acpi_pnp.o +acpi-y += int340x_thermal.o acpi-y += power.o acpi-y += event.o acpi-y += sysfs.o diff --git a/drivers/acpi/int340x_thermal.c b/drivers/acpi/int340x_thermal.c new file mode 100644 index 0000000..2103bb6 --- /dev/null +++ b/drivers/acpi/int340x_thermal.c @@ -0,0 +1,51 @@ +/* + * ACPI support for int340x thermal drivers + * + * Copyright (C) 2014, Intel Corporation + * Authors: Zhang Rui + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include "internal.h" + +#define DO_ENUMERATION 0x01 +static const struct acpi_device_id int340x_thermal_device_ids[] = { + {"INT3400", DO_ENUMERATION }, + {"INT3401"}, + {"INT3402"}, + {"INT3403"}, + {"INT3404"}, + {"INT3406"}, + {"INT3407"}, + {"INT3408"}, + {"INT3409"}, + {"INT340A"}, + {"INT340B"}, + {""}, +}; + +static int int340x_thermal_handler_attach(struct acpi_device *adev, + const struct acpi_device_id *id) +{ +#ifdef CONFIG_INT340X_THERMAL + if (id->driver_data == DO_ENUMERATION) + acpi_create_platform_device(adev); +#endif + return 1; +} + +static struct acpi_scan_handler int340x_thermal_handler = { + .ids = int340x_thermal_device_ids, + .attach = int340x_thermal_handler_attach, +}; + +void __init acpi_int340x_thermal_init(void) +{ + acpi_scan_add_handler(&int340x_thermal_handler); +} diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 4c5cf77..de47f9f 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -31,6 +31,7 @@ void acpi_pci_link_init(void); void acpi_processor_init(void); void acpi_platform_init(void); void acpi_pnp_init(void); +void acpi_int340x_thermal_init(void); int acpi_sysfs_init(void); void acpi_container_init(void); void acpi_memory_hotplug_init(void); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 0a817ad..eed9740 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2306,6 +2306,7 @@ int __init acpi_scan_init(void) acpi_container_init(); acpi_memory_hotplug_init(); acpi_pnp_init(); + acpi_int340x_thermal_init(); mutex_lock(&acpi_scan_lock); /* diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 693208e..2ff7416 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -234,6 +234,23 @@ config INTEL_SOC_DTS_THERMAL notification methods.The other trip is a critical trip point, which was set by the driver based on the TJ MAX temperature. +config INT340X_THERMAL + bool + depends on X86 && ACPI + help + Newer laptops and tablets that use ACPI may have thermal sensors and + other devices with thermal control capabilities outside the core + CPU/SOC, for thermal safety reasons. + They are exposed for the OS to use via the INT3400 ACPI device object + as the master, and INT3401~INT340B ACPI device objects as the slaves. + Enable this to expose the temperature information and cooling ability + from these objects to userspace via the normal thermal framework. + This means that a wide range of applications and GUI widgets can show + the information to the user or use this information for making + decisions. For example, the Intel Thermal Daemon can use this + information to allow the user to select his laptop to run without + turning on the fans. + menu "Texas Instruments thermal drivers" source "drivers/thermal/ti-soc-thermal/Kconfig" endmenu -- cgit v0.10.2 From 26bb0e9a1a938ec98ee07aa76533f1a711fba706 Mon Sep 17 00:00:00 2001 From: Lukasz Majewski Date: Wed, 24 Sep 2014 10:27:10 +0200 Subject: thermal: step_wise: fix: Prevent from binary overflow when trend is dropping It turns out that some boards can have instance->lower greater than 0 and when thermal trend is dropping it results with next_target equal to -1. Since the next_target is defined as unsigned long it is interpreted as 0xFFFFFFFF and larger than instance->upper. As a result the next_target is set to instance->upper which ramps up to maximal cooling device target when the temperature is steadily decreasing. Signed-off-by: Lukasz Majewski Signed-off-by: Zhang Rui diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c index f251521..6705a0d 100644 --- a/drivers/thermal/step_wise.c +++ b/drivers/thermal/step_wise.c @@ -76,7 +76,7 @@ static unsigned long get_target_state(struct thermal_instance *instance, next_target = instance->upper; break; case THERMAL_TREND_DROPPING: - if (cur_state == instance->lower) { + if (cur_state <= instance->lower) { if (!throttle) next_target = THERMAL_NO_TARGET; } else { -- cgit v0.10.2 From 083bf668cb70e47b84db64856606e94beac87f01 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 14 Mar 2014 14:06:25 +0800 Subject: ACPI: make acpi_create_platform_device() an external API Signed-off-by: Zhang Rui diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index 2bf9082..a3c89a1 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -113,3 +113,4 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev) kfree(resources); return pdev; } +EXPORT_SYMBOL_GPL(acpi_create_platform_device); diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index de47f9f..f221d1e 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -169,13 +169,6 @@ static inline void suspend_nvs_restore(void) {} #endif /*-------------------------------------------------------------------------- - Platform bus support - -------------------------------------------------------------------------- */ -struct platform_device; - -struct platform_device *acpi_create_platform_device(struct acpi_device *adev); - -/*-------------------------------------------------------------------------- Video -------------------------------------------------------------------------- */ #if defined(CONFIG_ACPI_VIDEO) || defined(CONFIG_ACPI_VIDEO_MODULE) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 807cbc4..2c24c2c 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -432,6 +432,7 @@ static inline bool acpi_driver_match_device(struct device *dev, int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); int acpi_device_modalias(struct device *, char *, int); +struct platform_device *acpi_create_platform_device(struct acpi_device *); #define ACPI_PTR(_ptr) (_ptr) #else /* !CONFIG_ACPI */ -- cgit v0.10.2 From e3ec483a7e24c6ebb5eb763ee56c65c239701066 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Sun, 16 Mar 2014 21:34:16 +0800 Subject: ACPI: add ACPI_TYPE_LOCAL_REFERENCE support to acpi_extract_package() Add ACPI_TYPE_LOCAL_REFERENCE support to acpi_extract_package(), so that we can use this helper for more cases like _ART/_TRT. Signed-off-by: Zhang Rui diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 07c8c5a..1ed7aba9 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -149,6 +149,21 @@ acpi_extract_package(union acpi_object *package, break; } break; + case ACPI_TYPE_LOCAL_REFERENCE: + switch (format_string[i]) { + case 'R': + size_required += sizeof(void *); + tail_offset += sizeof(void *); + break; + default: + printk(KERN_WARNING PREFIX "Invalid package element" + " [%d] got reference," + " expecting [%c]\n", + i, format_string[i]); + return AE_BAD_DATA; + break; + } + break; case ACPI_TYPE_PACKAGE: default: @@ -247,7 +262,18 @@ acpi_extract_package(union acpi_object *package, break; } break; - + case ACPI_TYPE_LOCAL_REFERENCE: + switch (format_string[i]) { + case 'R': + *(void **)head = + (void *)element->reference.handle; + head += sizeof(void *); + break; + default: + /* Should never get here */ + break; + } + break; case ACPI_TYPE_PACKAGE: /* TBD: handle nested packages... */ default: -- cgit v0.10.2 From 816cab931f288c92a3404b1b984576f4822b0445 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 14 Mar 2014 12:45:05 +0800 Subject: Thermal: introduce int3400 thermal driver Introduce int3400 thermal driver. And make INT3400 driver enumerate the other int340x thermal components shown in _ART/_TRT. Signed-off-by: Zhang Rui diff --git a/drivers/acpi/int340x_thermal.c b/drivers/acpi/int340x_thermal.c index 2103bb6..a27d31d 100644 --- a/drivers/acpi/int340x_thermal.c +++ b/drivers/acpi/int340x_thermal.c @@ -33,7 +33,7 @@ static const struct acpi_device_id int340x_thermal_device_ids[] = { static int int340x_thermal_handler_attach(struct acpi_device *adev, const struct acpi_device_id *id) { -#ifdef CONFIG_INT340X_THERMAL +#if defined(CONFIG_INT340X_THERMAL) || defined(CONFIG_INT340X_THERMAL_MODULE) if (id->driver_data == DO_ENUMERATION) acpi_create_platform_device(adev); #endif diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 2ff7416..6f5a87a 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -235,7 +235,7 @@ config INTEL_SOC_DTS_THERMAL was set by the driver based on the TJ MAX temperature. config INT340X_THERMAL - bool + tristate "ACPI INT340X thermal drivers" depends on X86 && ACPI help Newer laptops and tablets that use ACPI may have thermal sensors and diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 31e232f..216503e 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -32,4 +32,5 @@ obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile new file mode 100644 index 0000000..e10a53b --- /dev/null +++ b/drivers/thermal/int340x_thermal/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c new file mode 100644 index 0000000..308c185 --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c @@ -0,0 +1,245 @@ +/* + * INT3400 thermal driver + * + * Copyright (C) 2014, Intel Corporation + * Authors: Zhang Rui + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include + +struct art { + acpi_handle source; + acpi_handle target; + u64 weight; + u64 ac0_max; + u64 ac1_max; + u64 ac2_max; + u64 ac3_max; + u64 ac4_max; + u64 ac5_max; + u64 ac6_max; + u64 ac7_max; + u64 ac8_max; + u64 ac9_max; +}; + +struct trt { + acpi_handle source; + acpi_handle target; + u64 influence; + u64 sampling_period; + u64 reverved1; + u64 reverved2; + u64 reverved3; + u64 reverved4; +}; + +struct int3400_thermal_priv { + struct acpi_device *adev; + int art_count; + struct art *arts; + int trt_count; + struct trt *trts; +}; + +static int parse_art(struct int3400_thermal_priv *priv) +{ + acpi_handle handle = priv->adev->handle; + acpi_status status; + int result = 0; + int i; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer art_format = { + sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; + + if (!acpi_has_method(handle, "_ART")) + return 0; + + status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _ART data\n"); + result = -EFAULT; + goto end; + } + + /* ignore p->package.elements[0], as this is _ART Revision field */ + priv->art_count = p->package.count - 1; + priv->arts = kzalloc(sizeof(struct art) * priv->art_count, GFP_KERNEL); + if (!priv->arts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < priv->art_count; i++) { + struct art *art = &(priv->arts[i]); + + element.length = sizeof(struct art); + element.pointer = art; + + status = acpi_extract_package(&(p->package.elements[i + 1]), + &art_format, &element); + if (ACPI_FAILURE(status)) { + pr_err("Invalid _ART data"); + result = -EFAULT; + kfree(priv->arts); + goto end; + } + result = acpi_bus_get_device(art->source, &adev); + if (!result) + acpi_create_platform_device(adev, NULL); + else + pr_warn("Failed to get source ACPI device\n"); + result = acpi_bus_get_device(art->target, &adev); + if (!result) + acpi_create_platform_device(adev, NULL); + else + pr_warn("Failed to get source ACPI device\n"); + } +end: + kfree(buffer.pointer); + return result; +} + +static int parse_trt(struct int3400_thermal_priv *priv) +{ + acpi_handle handle = priv->adev->handle; + acpi_status status; + int result = 0; + int i; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" }; + + if (!acpi_has_method(handle, "_TRT")) + return 0; + + status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _TRT data\n"); + result = -EFAULT; + goto end; + } + + priv->trt_count = p->package.count; + priv->trts = kzalloc(sizeof(struct trt) * priv->trt_count, GFP_KERNEL); + if (!priv->trts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < priv->trt_count; i++) { + struct trt *trt = &(priv->trts[i]); + + element.length = sizeof(struct trt); + element.pointer = trt; + + status = acpi_extract_package(&(p->package.elements[i]), + &trt_format, &element); + if (ACPI_FAILURE(status)) { + pr_err("Invalid _ART data"); + result = -EFAULT; + kfree(priv->trts); + goto end; + } + + result = acpi_bus_get_device(trt->source, &adev); + if (!result) + acpi_create_platform_device(adev, NULL); + else + pr_warn("Failed to get source ACPI device\n"); + result = acpi_bus_get_device(trt->target, &adev); + if (!result) + acpi_create_platform_device(adev, NULL); + else + pr_warn("Failed to get target ACPI device\n"); + } +end: + kfree(buffer.pointer); + return result; +} + +static int int3400_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3400_thermal_priv *priv; + int result; + + if (!adev) + return -ENODEV; + + priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->adev = adev; + + result = parse_art(priv); + if (result) + goto free_priv; + + result = parse_trt(priv); + if (result) + goto free_art; + + platform_set_drvdata(pdev, priv); + + return 0; +free_art: + kfree(priv->arts); +free_priv: + kfree(priv); + return result; +} + +static int int3400_thermal_remove(struct platform_device *pdev) +{ + struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + + kfree(priv->trts); + kfree(priv->arts); + kfree(priv); + return 0; +} + +static const struct acpi_device_id int3400_thermal_match[] = { + {"INT3400", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); + +static struct platform_driver int3400_thermal_driver = { + .probe = int3400_thermal_probe, + .remove = int3400_thermal_remove, + .driver = { + .name = "int3400 thermal", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(int3400_thermal_match), + }, +}; + +module_platform_driver(int3400_thermal_driver); + +MODULE_DESCRIPTION("INT3400 Thermal driver"); +MODULE_AUTHOR("Zhang Rui "); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From c5738dddc01be49cf6712f886371177e8df36291 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Sun, 23 Mar 2014 23:34:26 +0800 Subject: Thermal: int3400 thermal: add capability to detect supporting UUIDs Signed-off-by: Zhang Rui diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c index 308c185..65c63ba 100644 --- a/drivers/thermal/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c @@ -41,14 +41,79 @@ struct trt { u64 reverved4; }; +enum int3400_thermal_uuid { + INT3400_THERMAL_PASSIVE_1, + INT3400_THERMAL_PASSIVE_2, + INT3400_THERMAL_ACTIVE, + INT3400_THERMAL_CRITICAL, + INT3400_THERMAL_COOLING_MODE, + INT3400_THERMAL_MAXIMUM_UUID, +}; + +static u8 *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { + "42A441D6-AE6A-462b-A84B-4A8CE79027D3", + "9E04115A-AE87-4D1C-9500-0F3E340BFE75", + "3A95C389-E4B8-4629-A526-C52C88626BAE", + "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", + "16CAF1B7-DD38-40ed-B1C1-1B8A1913D531", +}; + struct int3400_thermal_priv { struct acpi_device *adev; int art_count; struct art *arts; int trt_count; struct trt *trts; + u8 uuid_bitmap; }; +static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) +{ + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *obja, *objb; + int i, j; + int result = 0; + acpi_status status; + + status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obja = (union acpi_object *)buf.pointer; + if (obja->type != ACPI_TYPE_PACKAGE) { + result = -EINVAL; + goto end; + } + + for (i = 0; i < obja->package.count; i++) { + objb = &obja->package.elements[i]; + if (objb->type != ACPI_TYPE_BUFFER) { + result = -EINVAL; + goto end; + } + + /* UUID must be 16 bytes */ + if (objb->buffer.length != 16) { + result = -EINVAL; + goto end; + } + + for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { + u8 uuid[16]; + + acpi_str_to_uuid(int3400_thermal_uuids[j], uuid); + if (!strncmp(uuid, objb->buffer.pointer, 16)) { + priv->uuid_bitmap |= (1 << j); + break; + } + } + } + +end: + kfree(buf.pointer); + return result; +} + static int parse_art(struct int3400_thermal_priv *priv) { acpi_handle handle = priv->adev->handle; @@ -193,6 +258,10 @@ static int int3400_thermal_probe(struct platform_device *pdev) priv->adev = adev; + result = int3400_thermal_get_uuids(priv); + if (result) + goto free_priv; + result = parse_art(priv); if (result) goto free_priv; -- cgit v0.10.2 From 0ab15365ff38c8433f4d0cc2d11a1184e2c991cf Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Sun, 23 Mar 2014 23:37:32 +0800 Subject: Thermal: int3400 thermal: register to thermal framework Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 6f5a87a..b34c5f5 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -237,6 +237,7 @@ config INTEL_SOC_DTS_THERMAL config INT340X_THERMAL tristate "ACPI INT340X thermal drivers" depends on X86 && ACPI + select THERMAL_GOV_USER_SPACE help Newer laptops and tablets that use ACPI may have thermal sensors and other devices with thermal control capabilities outside the core diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c index 65c63ba..9104b4f 100644 --- a/drivers/thermal/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c @@ -13,6 +13,7 @@ #include #include #include +#include struct art { acpi_handle source; @@ -60,6 +61,8 @@ static u8 *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { struct int3400_thermal_priv { struct acpi_device *adev; + struct thermal_zone_device *thermal; + int mode; int art_count; struct art *arts; int trt_count; @@ -114,6 +117,36 @@ end: return result; } +static int int3400_thermal_run_osc(acpi_handle handle, + enum int3400_thermal_uuid uuid, bool enable) +{ + u32 ret, buf[2]; + acpi_status status; + int result = 0; + struct acpi_osc_context context = { + .uuid_str = int3400_thermal_uuids[uuid], + .rev = 1, + .cap.length = 8, + }; + + buf[OSC_QUERY_DWORD] = 0; + buf[OSC_SUPPORT_DWORD] = enable; + + context.cap.pointer = buf; + + status = acpi_run_osc(handle, &context); + if (ACPI_SUCCESS(status)) { + ret = *((u32 *)(context.ret.pointer + 4)); + if (ret != enable) + result = -EPERM; + } else + result = -EPERM; + + kfree(context.ret.pointer); + return result; +} + + static int parse_art(struct int3400_thermal_priv *priv) { acpi_handle handle = priv->adev->handle; @@ -243,6 +276,61 @@ end: return result; } +static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + *temp = 20 * 1000; /* faked temp sensor with 20C */ + return 0; +} + +static int int3400_thermal_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct int3400_thermal_priv *priv = thermal->devdata; + + if (!priv) + return -EINVAL; + + *mode = priv->mode; + + return 0; +} + +static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct int3400_thermal_priv *priv = thermal->devdata; + bool enable; + int result = 0; + + if (!priv) + return -EINVAL; + + if (mode == THERMAL_DEVICE_ENABLED) + enable = true; + else if (mode == THERMAL_DEVICE_DISABLED) + enable = false; + else + return -EINVAL; + + if (enable != priv->mode) { + priv->mode = enable; + /* currently, only PASSIVE COOLING is supported */ + result = int3400_thermal_run_osc(priv->adev->handle, + INT3400_THERMAL_PASSIVE_1, enable); + } + return result; +} + +static struct thermal_zone_device_ops int3400_thermal_ops = { + .get_temp = int3400_thermal_get_temp, +}; + +static struct thermal_zone_params int3400_thermal_params = { + .governor_name = "user_space", + .no_hwmon = true, +}; + static int int3400_thermal_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); @@ -272,7 +360,21 @@ static int int3400_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); + if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) { + int3400_thermal_ops.get_mode = int3400_thermal_get_mode; + int3400_thermal_ops.set_mode = int3400_thermal_set_mode; + } + priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, + priv, &int3400_thermal_ops, + &int3400_thermal_params, 0, 0); + if (IS_ERR(priv->thermal)) { + result = PTR_ERR(priv->thermal); + goto free_trt; + } + return 0; +free_trt: + kfree(priv->trts); free_art: kfree(priv->arts); free_priv: @@ -284,6 +386,7 @@ static int int3400_thermal_remove(struct platform_device *pdev) { struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + thermal_zone_device_unregister(priv->thermal); kfree(priv->trts); kfree(priv->arts); kfree(priv); -- cgit v0.10.2 From bd6ad24342c3bc0476ba3f0b647c5ba0a71b45b4 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 19 Nov 2013 14:25:58 +0800 Subject: ACPI / fan: remove unused macro The _COMPONENT, ACPI_MODULE_NAME(name) and ACPI_FAN_FILE_STATE are not used anywhere so remove them. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 8acf53e..562d5f3 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -34,10 +34,6 @@ #define PREFIX "ACPI: " #define ACPI_FAN_CLASS "fan" -#define ACPI_FAN_FILE_STATE "state" - -#define _COMPONENT ACPI_FAN_COMPONENT -ACPI_MODULE_NAME("fan"); MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI Fan Driver"); -- cgit v0.10.2 From 8dd41f78adebb57909cccb0272e74c79e38b5238 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 19 Nov 2013 15:21:24 +0800 Subject: ACPI / fan: remove no need check for device pointer The device pointer will not be NULL in the PM callback and ACPI driver's add/remove callback, so checking NULL for them isn't necessary. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 562d5f3..df861bb 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -131,9 +131,6 @@ static int acpi_fan_add(struct acpi_device *device) int result = 0; struct thermal_cooling_device *cdev; - if (!device) - return -EINVAL; - strcpy(acpi_device_name(device), "Fan"); strcpy(acpi_device_class(device), ACPI_FAN_CLASS); @@ -177,14 +174,7 @@ end: static int acpi_fan_remove(struct acpi_device *device) { - struct thermal_cooling_device *cdev; - - if (!device) - return -EINVAL; - - cdev = acpi_driver_data(device); - if (!cdev) - return -EINVAL; + struct thermal_cooling_device *cdev = acpi_driver_data(device); sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); sysfs_remove_link(&cdev->device.kobj, "device"); @@ -196,9 +186,6 @@ static int acpi_fan_remove(struct acpi_device *device) #ifdef CONFIG_PM_SLEEP static int acpi_fan_suspend(struct device *dev) { - if (!dev) - return -EINVAL; - acpi_bus_set_power(to_acpi_device(dev)->handle, ACPI_STATE_D0); return AE_OK; @@ -208,9 +195,6 @@ static int acpi_fan_resume(struct device *dev) { int result; - if (!dev) - return -EINVAL; - result = acpi_bus_update_power(to_acpi_device(dev)->handle, NULL); if (result) printk(KERN_ERR PREFIX "Error updating fan power state\n"); -- cgit v0.10.2 From 2bb3a2bf9939f3361e25045f4ef7b136b864c3b8 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 19 Nov 2013 15:43:52 +0800 Subject: ACPI / fan: use acpi_device_xxx_power instead of acpi_bus equivelant When we have the acpi_device pointer, there is no need to pass the device's handle to the acpi_bus_xxx_power functions to get/set/update the device's power state, instead, use the acpi_device_xxx_power functions directly. To make this happen for fan module, export acpi_device_update_power. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 67075f8..9177547 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -343,6 +343,7 @@ int acpi_device_update_power(struct acpi_device *device, int *state_p) return 0; } +EXPORT_SYMBOL_GPL(acpi_device_update_power); int acpi_bus_update_power(acpi_handle handle, int *state_p) { diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index df861bb..fff9696 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -92,7 +92,7 @@ static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long if (!device) return -EINVAL; - result = acpi_bus_update_power(device->handle, &acpi_state); + result = acpi_device_update_power(device, &acpi_state); if (result) return result; @@ -110,7 +110,7 @@ fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) if (!device || (state != 0 && state != 1)) return -EINVAL; - result = acpi_bus_set_power(device->handle, + result = acpi_device_set_power(device, state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); return result; @@ -134,7 +134,7 @@ static int acpi_fan_add(struct acpi_device *device) strcpy(acpi_device_name(device), "Fan"); strcpy(acpi_device_class(device), ACPI_FAN_CLASS); - result = acpi_bus_update_power(device->handle, NULL); + result = acpi_device_update_power(device, NULL); if (result) { printk(KERN_ERR PREFIX "Setting initial power state\n"); goto end; @@ -186,7 +186,7 @@ static int acpi_fan_remove(struct acpi_device *device) #ifdef CONFIG_PM_SLEEP static int acpi_fan_suspend(struct device *dev) { - acpi_bus_set_power(to_acpi_device(dev)->handle, ACPI_STATE_D0); + acpi_device_set_power(to_acpi_device(dev), ACPI_STATE_D0); return AE_OK; } @@ -195,7 +195,7 @@ static int acpi_fan_resume(struct device *dev) { int result; - result = acpi_bus_update_power(to_acpi_device(dev)->handle, NULL); + result = acpi_device_update_power(to_acpi_device(dev), NULL); if (result) printk(KERN_ERR PREFIX "Error updating fan power state\n"); diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index f221d1e..447f6d6 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -104,8 +104,6 @@ int acpi_power_get_inferred_state(struct acpi_device *device, int *state); int acpi_power_on_resources(struct acpi_device *device, int state); int acpi_power_transition(struct acpi_device *device, int state); -int acpi_device_update_power(struct acpi_device *device, int *state_p); - int acpi_wakeup_device_init(void); #ifdef CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index bcfd808..6ca3281 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -435,6 +435,7 @@ int acpi_device_set_power(struct acpi_device *device, int state); int acpi_bus_init_power(struct acpi_device *device); int acpi_device_fix_up_power(struct acpi_device *device); int acpi_bus_update_power(acpi_handle handle, int *state_p); +int acpi_device_update_power(struct acpi_device *device, int *state_p); bool acpi_bus_power_manageable(acpi_handle handle); #ifdef CONFIG_PM -- cgit v0.10.2 From 19593a1fb1f6718406afca5b867dab184289d406 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 19 Nov 2013 16:59:20 +0800 Subject: ACPI / fan: convert to platform driver Convert ACPI fan driver to a platform driver for the purpose of phasing out ACPI bus. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index fff9696..8a5b450 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -30,17 +30,14 @@ #include #include #include - -#define PREFIX "ACPI: " - -#define ACPI_FAN_CLASS "fan" +#include MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI Fan Driver"); MODULE_LICENSE("GPL"); -static int acpi_fan_add(struct acpi_device *device); -static int acpi_fan_remove(struct acpi_device *device); +static int acpi_fan_probe(struct platform_device *pdev); +static int acpi_fan_remove(struct platform_device *pdev); static const struct acpi_device_id fan_device_ids[] = { {"PNP0C0B", 0}, @@ -62,15 +59,14 @@ static struct dev_pm_ops acpi_fan_pm = { #define FAN_PM_OPS_PTR NULL #endif -static struct acpi_driver acpi_fan_driver = { - .name = "fan", - .class = ACPI_FAN_CLASS, - .ids = fan_device_ids, - .ops = { - .add = acpi_fan_add, - .remove = acpi_fan_remove, - }, - .drv.pm = FAN_PM_OPS_PTR, +static struct platform_driver acpi_fan_driver = { + .probe = acpi_fan_probe, + .remove = acpi_fan_remove, + .driver = { + .name = "acpi-fan", + .acpi_match_table = fan_device_ids, + .pm = FAN_PM_OPS_PTR, + }, }; /* thermal cooling device callbacks */ @@ -126,17 +122,15 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = { Driver Interface -------------------------------------------------------------------------- */ -static int acpi_fan_add(struct acpi_device *device) +static int acpi_fan_probe(struct platform_device *pdev) { int result = 0; struct thermal_cooling_device *cdev; - - strcpy(acpi_device_name(device), "Fan"); - strcpy(acpi_device_class(device), ACPI_FAN_CLASS); + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); result = acpi_device_update_power(device, NULL); if (result) { - printk(KERN_ERR PREFIX "Setting initial power state\n"); + dev_err(&pdev->dev, "Setting initial power state\n"); goto end; } @@ -147,24 +141,24 @@ static int acpi_fan_add(struct acpi_device *device) goto end; } - dev_dbg(&device->dev, "registered as cooling_device%d\n", cdev->id); + dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); - device->driver_data = cdev; - result = sysfs_create_link(&device->dev.kobj, + platform_set_drvdata(pdev, cdev); + result = sysfs_create_link(&pdev->dev.kobj, &cdev->device.kobj, "thermal_cooling"); if (result) - dev_err(&device->dev, "Failed to create sysfs link " + dev_err(&pdev->dev, "Failed to create sysfs link " "'thermal_cooling'\n"); result = sysfs_create_link(&cdev->device.kobj, - &device->dev.kobj, + &pdev->dev.kobj, "device"); if (result) - dev_err(&device->dev, "Failed to create sysfs link " + dev_err(&pdev->dev, "Failed to create sysfs link " "'device'\n"); - printk(KERN_INFO PREFIX "%s [%s] (%s)\n", + dev_info(&pdev->dev, "%s [%s] (%s)\n", acpi_device_name(device), acpi_device_bid(device), !device->power.state ? "on" : "off"); @@ -172,11 +166,11 @@ end: return result; } -static int acpi_fan_remove(struct acpi_device *device) +static int acpi_fan_remove(struct platform_device *pdev) { - struct thermal_cooling_device *cdev = acpi_driver_data(device); + struct thermal_cooling_device *cdev = platform_get_drvdata(pdev); - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); + sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); sysfs_remove_link(&cdev->device.kobj, "device"); thermal_cooling_device_unregister(cdev); @@ -186,7 +180,7 @@ static int acpi_fan_remove(struct acpi_device *device) #ifdef CONFIG_PM_SLEEP static int acpi_fan_suspend(struct device *dev) { - acpi_device_set_power(to_acpi_device(dev), ACPI_STATE_D0); + acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); return AE_OK; } @@ -195,12 +189,12 @@ static int acpi_fan_resume(struct device *dev) { int result; - result = acpi_device_update_power(to_acpi_device(dev), NULL); + result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); if (result) - printk(KERN_ERR PREFIX "Error updating fan power state\n"); + dev_err(dev, "Error updating fan power state\n"); return result; } #endif -module_acpi_driver(acpi_fan_driver); +module_platform_driver(acpi_fan_driver); -- cgit v0.10.2 From 9519a6356cbf63b1f22a7a208385dc56092c8b7d Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 1 Apr 2014 15:50:27 +0800 Subject: ACPI / Fan: add ACPI 4.0 style fan support This patch adds support for ACPI 4.0 style fan, lacking part is: no support for 'Low Speed Notification Support', 'Fine Grain Control' is not used yet. It's not clear what to do on suspend/resume callback for 4.0 style ACPI fan, so it does nothing for now. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 8a5b450..f7d1c80 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -31,6 +31,7 @@ #include #include #include +#include MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI Fan Driver"); @@ -59,6 +60,29 @@ static struct dev_pm_ops acpi_fan_pm = { #define FAN_PM_OPS_PTR NULL #endif +struct acpi_fan_fps { + u64 control; + u64 trip_point; + u64 speed; + u64 noise_level; + u64 power; +}; + +struct acpi_fan_fif { + u64 revision; + u64 fine_grain_ctrl; + u64 step_size; + u64 low_speed_notification; +}; + +struct acpi_fan { + bool acpi4; + struct acpi_fan_fif fif; + struct acpi_fan_fps *fps; + int fps_count; + struct thermal_cooling_device *cdev; +}; + static struct platform_driver acpi_fan_driver = { .probe = acpi_fan_probe, .remove = acpi_fan_remove, @@ -73,21 +97,62 @@ static struct platform_driver acpi_fan_driver = { static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - /* ACPI fan device only support two states: ON/OFF */ - *state = 1; + struct acpi_device *device = cdev->devdata; + struct acpi_fan *fan = acpi_driver_data(device); + + if (fan->acpi4) + *state = fan->fps_count - 1; + else + *state = 1; return 0; } -static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long - *state) +static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_fan *fan = acpi_driver_data(device); + union acpi_object *obj; + acpi_status status; + int control, i; + + status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "Get fan state failed\n"); + return status; + } + + obj = buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_PACKAGE || + obj->package.count != 3 || + obj->package.elements[1].type != ACPI_TYPE_INTEGER) { + dev_err(&device->dev, "Invalid _FST data\n"); + status = -EINVAL; + goto err; + } + + control = obj->package.elements[1].integer.value; + for (i = 0; i < fan->fps_count; i++) { + if (control == fan->fps[i].control) + break; + } + if (i == fan->fps_count) { + dev_dbg(&device->dev, "Invalid control value returned\n"); + status = -EINVAL; + goto err; + } + + *state = i; + +err: + kfree(obj); + return status; +} + +static int fan_get_state(struct acpi_device *device, unsigned long *state) { - struct acpi_device *device = cdev->devdata; int result; int acpi_state = ACPI_STATE_D0; - if (!device) - return -EINVAL; - result = acpi_device_update_power(device, &acpi_state); if (result) return result; @@ -97,21 +162,57 @@ static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long return 0; } -static int -fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long + *state) { struct acpi_device *device = cdev->devdata; - int result; + struct acpi_fan *fan = acpi_driver_data(device); + + if (fan->acpi4) + return fan_get_state_acpi4(device, state); + else + return fan_get_state(device, state); +} - if (!device || (state != 0 && state != 1)) +static int fan_set_state(struct acpi_device *device, unsigned long state) +{ + if (state != 0 && state != 1) return -EINVAL; - result = acpi_device_set_power(device, - state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); + return acpi_device_set_power(device, + state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); +} - return result; +static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) +{ + struct acpi_fan *fan = acpi_driver_data(device); + acpi_status status; + + if (state >= fan->fps_count) + return -EINVAL; + + status = acpi_execute_simple_method(device->handle, "_FSL", + fan->fps[state].control); + if (ACPI_FAILURE(status)) { + dev_dbg(&device->dev, "Failed to set state by _FSL\n"); + return status; + } + + return 0; } +static int +fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct acpi_device *device = cdev->devdata; + struct acpi_fan *fan = acpi_driver_data(device); + + if (fan->acpi4) + return fan_set_state_acpi4(device, state); + else + return fan_set_state(device, state); + } + static const struct thermal_cooling_device_ops fan_cooling_ops = { .get_max_state = fan_get_max_state, .get_cur_state = fan_get_cur_state, @@ -122,16 +223,125 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = { Driver Interface -------------------------------------------------------------------------- */ +static bool acpi_fan_is_acpi4(struct acpi_device *device) +{ + return acpi_has_method(device->handle, "_FIF") && + acpi_has_method(device->handle, "_FPS") && + acpi_has_method(device->handle, "_FSL") && + acpi_has_method(device->handle, "_FST"); +} + +static int acpi_fan_get_fif(struct acpi_device *device) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; + struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif }; + union acpi_object *obj; + acpi_status status; + + status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); + if (ACPI_FAILURE(status)) + return status; + + obj = buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_PACKAGE) { + dev_err(&device->dev, "Invalid _FIF data\n"); + status = -EINVAL; + goto err; + } + + status = acpi_extract_package(obj, &format, &fif); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "Invalid _FIF element\n"); + status = -EINVAL; + } + +err: + kfree(obj); + return status; +} + +static int acpi_fan_speed_cmp(const void *a, const void *b) +{ + const struct acpi_fan_fps *fps1 = a; + const struct acpi_fan_fps *fps2 = b; + return fps1->speed - fps2->speed; +} + +static int acpi_fan_get_fps(struct acpi_device *device) +{ + struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int i; + + status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); + if (ACPI_FAILURE(status)) + return status; + + obj = buffer.pointer; + if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { + dev_err(&device->dev, "Invalid _FPS data\n"); + status = -EINVAL; + goto err; + } + + fan->fps_count = obj->package.count - 1; /* minus revision field */ + fan->fps = devm_kzalloc(&device->dev, + fan->fps_count * sizeof(struct acpi_fan_fps), + GFP_KERNEL); + if (!fan->fps) { + dev_err(&device->dev, "Not enough memory\n"); + status = -ENOMEM; + goto err; + } + for (i = 0; i < fan->fps_count; i++) { + struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; + struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] }; + status = acpi_extract_package(&obj->package.elements[i + 1], + &format, &fps); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "Invalid _FPS element\n"); + break; + } + } + + /* sort the state array according to fan speed in increase order */ + sort(fan->fps, fan->fps_count, sizeof(*fan->fps), + acpi_fan_speed_cmp, NULL); + +err: + kfree(obj); + return status; +} + static int acpi_fan_probe(struct platform_device *pdev) { int result = 0; struct thermal_cooling_device *cdev; + struct acpi_fan *fan; struct acpi_device *device = ACPI_COMPANION(&pdev->dev); - result = acpi_device_update_power(device, NULL); - if (result) { - dev_err(&pdev->dev, "Setting initial power state\n"); - goto end; + fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); + if (!fan) { + dev_err(&device->dev, "No memory for fan\n"); + return -ENOMEM; + } + device->driver_data = fan; + platform_set_drvdata(pdev, fan); + + if (acpi_fan_is_acpi4(device)) { + if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device)) + goto end; + fan->acpi4 = true; + } else { + result = acpi_device_update_power(device, NULL); + if (result) { + dev_err(&device->dev, "Setting initial power state\n"); + goto end; + } } cdev = thermal_cooling_device_register("Fan", device, @@ -143,7 +353,7 @@ static int acpi_fan_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); - platform_set_drvdata(pdev, cdev); + fan->cdev = cdev; result = sysfs_create_link(&pdev->dev.kobj, &cdev->device.kobj, "thermal_cooling"); @@ -158,21 +368,17 @@ static int acpi_fan_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to create sysfs link " "'device'\n"); - dev_info(&pdev->dev, "%s [%s] (%s)\n", - acpi_device_name(device), acpi_device_bid(device), - !device->power.state ? "on" : "off"); - end: return result; } static int acpi_fan_remove(struct platform_device *pdev) { - struct thermal_cooling_device *cdev = platform_get_drvdata(pdev); + struct acpi_fan *fan = platform_get_drvdata(pdev); sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&cdev->device.kobj, "device"); - thermal_cooling_device_unregister(cdev); + sysfs_remove_link(&fan->cdev->device.kobj, "device"); + thermal_cooling_device_unregister(fan->cdev); return 0; } @@ -180,6 +386,10 @@ static int acpi_fan_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int acpi_fan_suspend(struct device *dev) { + struct acpi_fan *fan = dev_get_drvdata(dev); + if (fan->acpi4) + return 0; + acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0); return AE_OK; @@ -188,6 +398,10 @@ static int acpi_fan_suspend(struct device *dev) static int acpi_fan_resume(struct device *dev) { int result; + struct acpi_fan *fan = dev_get_drvdata(dev); + + if (fan->acpi4) + return 0; result = acpi_device_update_power(ACPI_COMPANION(dev), NULL); if (result) -- cgit v0.10.2 From d806c6e9cdfd6c5663687ec7109e151c0ff66639 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 1 Apr 2014 15:54:05 +0800 Subject: ACPI / Fan: support INT3404 thermal device INT3404 ACPI object follows the ACPI 5.0 fan object definition as described in section 11.3 of the ACPI 5.0 Specification. Thus we can reuse the ACPI fan driver for INT3404 ACPI object. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index f7d1c80..e007c49 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -42,6 +42,7 @@ static int acpi_fan_remove(struct platform_device *pdev); static const struct acpi_device_id fan_device_ids[] = { {"PNP0C0B", 0}, + {"INT3404", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, fan_device_ids); -- cgit v0.10.2 From 7b83fd9d91a411158f72d36958103c708c3b5a86 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Tue, 25 Mar 2014 10:40:09 +0800 Subject: Thermal: move the KELVIN_TO_MILLICELSIUS macro to thermal.h This macro can be used by other component so move it to a common header, but in a slightly different way: define two macros, one macro with an offset and the other doesn't. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 112817e..d24fa19 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -528,7 +528,6 @@ static void acpi_thermal_check(void *data) } /* sys I/F for generic thermal sysfs support */ -#define KELVIN_TO_MILLICELSIUS(t, off) (((t) - (off)) * 100) static int thermal_get_temp(struct thermal_zone_device *thermal, unsigned long *temp) @@ -543,7 +542,8 @@ static int thermal_get_temp(struct thermal_zone_device *thermal, if (result) return result; - *temp = KELVIN_TO_MILLICELSIUS(tz->temperature, tz->kelvin_offset); + *temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(tz->temperature, + tz->kelvin_offset); return 0; } @@ -647,7 +647,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal, if (tz->trips.critical.flags.valid) { if (!trip) { - *temp = KELVIN_TO_MILLICELSIUS( + *temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( tz->trips.critical.temperature, tz->kelvin_offset); return 0; @@ -657,7 +657,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal, if (tz->trips.hot.flags.valid) { if (!trip) { - *temp = KELVIN_TO_MILLICELSIUS( + *temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( tz->trips.hot.temperature, tz->kelvin_offset); return 0; @@ -667,7 +667,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal, if (tz->trips.passive.flags.valid) { if (!trip) { - *temp = KELVIN_TO_MILLICELSIUS( + *temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( tz->trips.passive.temperature, tz->kelvin_offset); return 0; @@ -678,7 +678,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal, for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE && tz->trips.active[i].flags.valid; i++) { if (!trip) { - *temp = KELVIN_TO_MILLICELSIUS( + *temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( tz->trips.active[i].temperature, tz->kelvin_offset); return 0; @@ -694,7 +694,7 @@ static int thermal_get_crit_temp(struct thermal_zone_device *thermal, struct acpi_thermal *tz = thermal->devdata; if (tz->trips.critical.flags.valid) { - *temperature = KELVIN_TO_MILLICELSIUS( + *temperature = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( tz->trips.critical.temperature, tz->kelvin_offset); return 0; @@ -714,8 +714,8 @@ static int thermal_get_trend(struct thermal_zone_device *thermal, if (type == THERMAL_TRIP_ACTIVE) { unsigned long trip_temp; - unsigned long temp = KELVIN_TO_MILLICELSIUS(tz->temperature, - tz->kelvin_offset); + unsigned long temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET( + tz->temperature, tz->kelvin_offset); if (thermal_get_trip_temp(thermal, trip, &trip_temp)) return -EINVAL; diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 0305cde..79ce6b9 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -44,6 +44,8 @@ #define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732 >= 0) ? \ ((long)t-2732+5)/10 : ((long)t-2732-5)/10) #define CELSIUS_TO_KELVIN(t) ((t)*10+2732) +#define DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, off) (((t) - (off)) * 100) +#define DECI_KELVIN_TO_MILLICELSIUS(t) DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, 2732) /* Adding event notification support elements */ #define THERMAL_GENL_FAMILY_NAME "thermal_event" -- cgit v0.10.2 From 77e337c6e23e3b9d22e09ffec202a80f755a54c2 Mon Sep 17 00:00:00 2001 From: Aaron Lu Date: Wed, 3 Sep 2014 15:13:02 +0800 Subject: Thermal: introduce INT3402 thermal driver ACPI INT3402 device object could report temperature for the memory module. To expose such information to user space, a thermal zone device is registered for it so that the thermal sysfs interface can expose such information for userspace to use. Signed-off-by: Aaron Lu Signed-off-by: Zhang Rui diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile index e10a53b..67c98fd 100644 --- a/drivers/thermal/int340x_thermal/Makefile +++ b/drivers/thermal/int340x_thermal/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o diff --git a/drivers/thermal/int340x_thermal/int3402_thermal.c b/drivers/thermal/int340x_thermal/int3402_thermal.c new file mode 100644 index 0000000..a5d08c1 --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3402_thermal.c @@ -0,0 +1,242 @@ +/* + * INT3402 thermal driver for memory temperature reporting + * + * Copyright (C) 2014, Intel Corporation + * Authors: Aaron Lu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#define ACPI_ACTIVE_COOLING_MAX_NR 10 + +struct active_trip { + unsigned long temp; + int id; + bool valid; +}; + +struct int3402_thermal_data { + unsigned long *aux_trips; + int aux_trip_nr; + unsigned long psv_temp; + int psv_trip_id; + unsigned long crt_temp; + int crt_trip_id; + unsigned long hot_temp; + int hot_trip_id; + struct active_trip act_trips[ACPI_ACTIVE_COOLING_MAX_NR]; + acpi_handle *handle; +}; + +static int int3402_thermal_get_zone_temp(struct thermal_zone_device *zone, + unsigned long *temp) +{ + struct int3402_thermal_data *d = zone->devdata; + unsigned long long tmp; + acpi_status status; + + status = acpi_evaluate_integer(d->handle, "_TMP", NULL, &tmp); + if (ACPI_FAILURE(status)) + return -ENODEV; + + /* _TMP returns the temperature in tenths of degrees Kelvin */ + *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp); + + return 0; +} + +static int int3402_thermal_get_trip_temp(struct thermal_zone_device *zone, + int trip, unsigned long *temp) +{ + struct int3402_thermal_data *d = zone->devdata; + int i; + + if (trip < d->aux_trip_nr) + *temp = d->aux_trips[trip]; + else if (trip == d->crt_trip_id) + *temp = d->crt_temp; + else if (trip == d->psv_trip_id) + *temp = d->psv_temp; + else if (trip == d->hot_trip_id) + *temp = d->hot_temp; + else { + for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { + if (d->act_trips[i].valid && + d->act_trips[i].id == trip) { + *temp = d->act_trips[i].temp; + break; + } + } + if (i == ACPI_ACTIVE_COOLING_MAX_NR) + return -EINVAL; + } + return 0; +} + +static int int3402_thermal_get_trip_type(struct thermal_zone_device *zone, + int trip, enum thermal_trip_type *type) +{ + struct int3402_thermal_data *d = zone->devdata; + int i; + + if (trip < d->aux_trip_nr) + *type = THERMAL_TRIP_PASSIVE; + else if (trip == d->crt_trip_id) + *type = THERMAL_TRIP_CRITICAL; + else if (trip == d->hot_trip_id) + *type = THERMAL_TRIP_HOT; + else if (trip == d->psv_trip_id) + *type = THERMAL_TRIP_PASSIVE; + else { + for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { + if (d->act_trips[i].valid && + d->act_trips[i].id == trip) { + *type = THERMAL_TRIP_ACTIVE; + break; + } + } + if (i == ACPI_ACTIVE_COOLING_MAX_NR) + return -EINVAL; + } + return 0; +} + +static int int3402_thermal_set_trip_temp(struct thermal_zone_device *zone, int trip, + unsigned long temp) +{ + struct int3402_thermal_data *d = zone->devdata; + acpi_status status; + char name[10]; + + snprintf(name, sizeof(name), "PAT%d", trip); + status = acpi_execute_simple_method(d->handle, name, + MILLICELSIUS_TO_DECI_KELVIN(temp)); + if (ACPI_FAILURE(status)) + return -EIO; + + d->aux_trips[trip] = temp; + return 0; +} + +static struct thermal_zone_device_ops int3402_thermal_zone_ops = { + .get_temp = int3402_thermal_get_zone_temp, + .get_trip_temp = int3402_thermal_get_trip_temp, + .get_trip_type = int3402_thermal_get_trip_type, + .set_trip_temp = int3402_thermal_set_trip_temp, +}; + +static struct thermal_zone_params int3402_thermal_params = { + .governor_name = "user_space", + .no_hwmon = true, +}; + +static int int3402_thermal_get_temp(acpi_handle handle, char *name, + unsigned long *temp) +{ + unsigned long long r; + acpi_status status; + + status = acpi_evaluate_integer(handle, name, NULL, &r); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLICELSIUS(r); + return 0; +} + +static int int3402_thermal_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3402_thermal_data *d; + struct thermal_zone_device *zone; + acpi_status status; + unsigned long long trip_cnt; + int trip_mask = 0, i; + + if (!acpi_has_method(adev->handle, "_TMP")) + return -ENODEV; + + d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); + if (ACPI_FAILURE(status)) + trip_cnt = 0; + else { + d->aux_trips = devm_kzalloc(&pdev->dev, + sizeof(*d->aux_trips) * trip_cnt, GFP_KERNEL); + if (!d->aux_trips) + return -ENOMEM; + trip_mask = trip_cnt - 1; + d->handle = adev->handle; + d->aux_trip_nr = trip_cnt; + } + + d->crt_trip_id = -1; + if (!int3402_thermal_get_temp(adev->handle, "_CRT", &d->crt_temp)) + d->crt_trip_id = trip_cnt++; + d->hot_trip_id = -1; + if (!int3402_thermal_get_temp(adev->handle, "_HOT", &d->hot_temp)) + d->hot_trip_id = trip_cnt++; + d->psv_trip_id = -1; + if (!int3402_thermal_get_temp(adev->handle, "_PSV", &d->psv_temp)) + d->psv_trip_id = trip_cnt++; + for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { + char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; + if (int3402_thermal_get_temp(adev->handle, name, + &d->act_trips[i].temp)) + break; + d->act_trips[i].id = trip_cnt++; + d->act_trips[i].valid = true; + } + + zone = thermal_zone_device_register(acpi_device_bid(adev), trip_cnt, + trip_mask, d, + &int3402_thermal_zone_ops, + &int3402_thermal_params, + 0, 0); + if (IS_ERR(zone)) + return PTR_ERR(zone); + platform_set_drvdata(pdev, zone); + + return 0; +} + +static int int3402_thermal_remove(struct platform_device *pdev) +{ + struct thermal_zone_device *zone = platform_get_drvdata(pdev); + + thermal_zone_device_unregister(zone); + return 0; +} + +static const struct acpi_device_id int3402_thermal_match[] = { + {"INT3402", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, int3402_thermal_match); + +static struct platform_driver int3402_thermal_driver = { + .probe = int3402_thermal_probe, + .remove = int3402_thermal_remove, + .driver = { + .name = "int3402 thermal", + .owner = THIS_MODULE, + .acpi_match_table = int3402_thermal_match, + }, +}; + +module_platform_driver(int3402_thermal_driver); + +MODULE_DESCRIPTION("INT3402 Thermal driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 79ce6b9..ef90838 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -46,6 +46,8 @@ #define CELSIUS_TO_KELVIN(t) ((t)*10+2732) #define DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, off) (((t) - (off)) * 100) #define DECI_KELVIN_TO_MILLICELSIUS(t) DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, 2732) +#define MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, off) (((t) / 100) + (off)) +#define MILLICELSIUS_TO_DECI_KELVIN(t) MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, 2732) /* Adding event notification support elements */ #define THERMAL_GENL_FAMILY_NAME "thermal_event" -- cgit v0.10.2 From 4384b8fe162d8aa03905d02073707bcf364cc7ce Mon Sep 17 00:00:00 2001 From: Lan Tianyu Date: Wed, 3 Sep 2014 15:13:30 +0800 Subject: Thermal: introduce int3403 thermal driver ACPI INT3403 device object can be used to retrieve temperature date from temperature sensors present in the system, and to expose device' performance control. The previous INT3403 thermal driver supports temperature reporting only, thus remove it and introduce this new & enhanced one. Signed-off-by: Lan Tianyu Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index b34c5f5..6f93e5c 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -207,21 +207,6 @@ config X86_PKG_TEMP_THERMAL two trip points which can be set by user to get notifications via thermal notification methods. -config ACPI_INT3403_THERMAL - tristate "ACPI INT3403 thermal driver" - depends on X86 && ACPI - help - Newer laptops and tablets that use ACPI may have thermal sensors - outside the core CPU/SOC for thermal safety reasons. These - temperature sensors are also exposed for the OS to use via the so - called INT3403 ACPI object. This driver will, on devices that have - such sensors, expose the temperature information from these sensors - to userspace via the normal thermal framework. This means that a wide - range of applications and GUI widgets can show this information to - the user or use this information for making decisions. For example, - the Intel Thermal Daemon can use this information to allow the user - to select his laptop to run without turning on the fans. - config INTEL_SOC_DTS_THERMAL tristate "Intel SoCs DTS thermal driver" depends on X86 && IOSF_MBI diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 216503e..39b85b5 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -31,6 +31,5 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ -obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ diff --git a/drivers/thermal/int3403_thermal.c b/drivers/thermal/int3403_thermal.c deleted file mode 100644 index 17554ee..0000000 --- a/drivers/thermal/int3403_thermal.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * ACPI INT3403 thermal driver - * Copyright (c) 2013, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - */ - -#include -#include -#include -#include -#include -#include - -#define INT3403_TYPE_SENSOR 0x03 -#define INT3403_PERF_CHANGED_EVENT 0x80 -#define INT3403_THERMAL_EVENT 0x90 - -#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100) -#define KELVIN_OFFSET 2732 -#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off)) - -#define ACPI_INT3403_CLASS "int3403" -#define ACPI_INT3403_FILE_STATE "state" - -struct int3403_sensor { - struct thermal_zone_device *tzone; - unsigned long *thresholds; - unsigned long crit_temp; - int crit_trip_id; - unsigned long psv_temp; - int psv_trip_id; -}; - -static int sys_get_curr_temp(struct thermal_zone_device *tzone, - unsigned long *temp) -{ - struct acpi_device *device = tzone->devdata; - unsigned long long tmp; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp); - if (ACPI_FAILURE(status)) - return -EIO; - - *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET); - - return 0; -} - -static int sys_get_trip_hyst(struct thermal_zone_device *tzone, - int trip, unsigned long *temp) -{ - struct acpi_device *device = tzone->devdata; - unsigned long long hyst; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst); - if (ACPI_FAILURE(status)) - return -EIO; - - /* - * Thermal hysteresis represents a temperature difference. - * Kelvin and Celsius have same degree size. So the - * conversion here between tenths of degree Kelvin unit - * and Milli-Celsius unit is just to multiply 100. - */ - *temp = hyst * 100; - - return 0; -} - -static int sys_get_trip_temp(struct thermal_zone_device *tzone, - int trip, unsigned long *temp) -{ - struct acpi_device *device = tzone->devdata; - struct int3403_sensor *obj = acpi_driver_data(device); - - if (trip == obj->crit_trip_id) - *temp = obj->crit_temp; - else if (trip == obj->psv_trip_id) - *temp = obj->psv_temp; - else { - /* - * get_trip_temp is a mandatory callback but - * PATx method doesn't return any value, so return - * cached value, which was last set from user space. - */ - *temp = obj->thresholds[trip]; - } - - return 0; -} - -static int sys_get_trip_type(struct thermal_zone_device *thermal, - int trip, enum thermal_trip_type *type) -{ - struct acpi_device *device = thermal->devdata; - struct int3403_sensor *obj = acpi_driver_data(device); - - /* Mandatory callback, may not mean much here */ - if (trip == obj->crit_trip_id) - *type = THERMAL_TRIP_CRITICAL; - else - *type = THERMAL_TRIP_PASSIVE; - - return 0; -} - -int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip, - unsigned long temp) -{ - struct acpi_device *device = tzone->devdata; - acpi_status status; - char name[10]; - int ret = 0; - struct int3403_sensor *obj = acpi_driver_data(device); - - snprintf(name, sizeof(name), "PAT%d", trip); - if (acpi_has_method(device->handle, name)) { - status = acpi_execute_simple_method(device->handle, name, - MILLI_CELSIUS_TO_DECI_KELVIN(temp, - KELVIN_OFFSET)); - if (ACPI_FAILURE(status)) - ret = -EIO; - else - obj->thresholds[trip] = temp; - } else { - ret = -EIO; - dev_err(&device->dev, "sys_set_trip_temp: method not found\n"); - } - - return ret; -} - -static struct thermal_zone_device_ops tzone_ops = { - .get_temp = sys_get_curr_temp, - .get_trip_temp = sys_get_trip_temp, - .get_trip_type = sys_get_trip_type, - .set_trip_temp = sys_set_trip_temp, - .get_trip_hyst = sys_get_trip_hyst, -}; - -static void acpi_thermal_notify(struct acpi_device *device, u32 event) -{ - struct int3403_sensor *obj; - - if (!device) - return; - - obj = acpi_driver_data(device); - if (!obj) - return; - - switch (event) { - case INT3403_PERF_CHANGED_EVENT: - break; - case INT3403_THERMAL_EVENT: - thermal_zone_device_update(obj->tzone); - break; - default: - dev_err(&device->dev, "Unsupported event [0x%x]\n", event); - break; - } -} - -static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp) -{ - unsigned long long crt; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt); - if (ACPI_FAILURE(status)) - return -EIO; - - *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET); - - return 0; -} - -static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp) -{ - unsigned long long psv; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv); - if (ACPI_FAILURE(status)) - return -EIO; - - *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET); - - return 0; -} - -static int acpi_int3403_add(struct acpi_device *device) -{ - int result = 0; - unsigned long long ptyp; - acpi_status status; - struct int3403_sensor *obj; - unsigned long long trip_cnt; - int trip_mask = 0; - - if (!device) - return -EINVAL; - - status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) - return -EINVAL; - - if (ptyp != INT3403_TYPE_SENSOR) - return -EINVAL; - - obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL); - if (!obj) - return -ENOMEM; - - device->driver_data = obj; - - status = acpi_evaluate_integer(device->handle, "PATC", NULL, - &trip_cnt); - if (ACPI_FAILURE(status)) - trip_cnt = 0; - - if (trip_cnt) { - /* We have to cache, thresholds can't be readback */ - obj->thresholds = devm_kzalloc(&device->dev, - sizeof(*obj->thresholds) * trip_cnt, - GFP_KERNEL); - if (!obj->thresholds) - return -ENOMEM; - trip_mask = BIT(trip_cnt) - 1; - } - - obj->psv_trip_id = -1; - if (!sys_get_trip_psv(device, &obj->psv_temp)) - obj->psv_trip_id = trip_cnt++; - - obj->crit_trip_id = -1; - if (!sys_get_trip_crt(device, &obj->crit_temp)) - obj->crit_trip_id = trip_cnt++; - - obj->tzone = thermal_zone_device_register(acpi_device_bid(device), - trip_cnt, trip_mask, device, &tzone_ops, - NULL, 0, 0); - if (IS_ERR(obj->tzone)) { - result = PTR_ERR(obj->tzone); - return result; - } - - strcpy(acpi_device_name(device), "INT3403"); - strcpy(acpi_device_class(device), ACPI_INT3403_CLASS); - - return 0; -} - -static int acpi_int3403_remove(struct acpi_device *device) -{ - struct int3403_sensor *obj; - - obj = acpi_driver_data(device); - thermal_zone_device_unregister(obj->tzone); - - return 0; -} - -ACPI_MODULE_NAME("int3403"); -static const struct acpi_device_id int3403_device_ids[] = { - {"INT3403", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, int3403_device_ids); - -static struct acpi_driver acpi_int3403_driver = { - .name = "INT3403", - .class = ACPI_INT3403_CLASS, - .ids = int3403_device_ids, - .ops = { - .add = acpi_int3403_add, - .remove = acpi_int3403_remove, - .notify = acpi_thermal_notify, - }, -}; - -module_acpi_driver(acpi_int3403_driver); - -MODULE_AUTHOR("Srinivas Pandruvada "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile index 67c98fd..c4a5c50 100644 --- a/drivers/thermal/int340x_thermal/Makefile +++ b/drivers/thermal/int340x_thermal/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o +obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c new file mode 100644 index 0000000..d20dba9 --- /dev/null +++ b/drivers/thermal/int340x_thermal/int3403_thermal.c @@ -0,0 +1,477 @@ +/* + * ACPI INT3403 thermal driver + * Copyright (c) 2013, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define INT3403_TYPE_SENSOR 0x03 +#define INT3403_TYPE_CHARGER 0x0B +#define INT3403_TYPE_BATTERY 0x0C +#define INT3403_PERF_CHANGED_EVENT 0x80 +#define INT3403_THERMAL_EVENT 0x90 + +#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100) +#define KELVIN_OFFSET 2732 +#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off)) + +struct int3403_sensor { + struct thermal_zone_device *tzone; + unsigned long *thresholds; + unsigned long crit_temp; + int crit_trip_id; + unsigned long psv_temp; + int psv_trip_id; + +}; + +struct int3403_performance_state { + u64 performance; + u64 power; + u64 latency; + u64 linear; + u64 control; + u64 raw_performace; + char *raw_unit; + int reserved; +}; + +struct int3403_cdev { + struct thermal_cooling_device *cdev; + unsigned long max_state; +}; + +struct int3403_priv { + struct platform_device *pdev; + struct acpi_device *adev; + unsigned long long type; + void *priv; +}; + +static int sys_get_curr_temp(struct thermal_zone_device *tzone, + unsigned long *temp) +{ + struct int3403_priv *priv = tzone->devdata; + struct acpi_device *device = priv->adev; + unsigned long long tmp; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET); + + return 0; +} + +static int sys_get_trip_hyst(struct thermal_zone_device *tzone, + int trip, unsigned long *temp) +{ + struct int3403_priv *priv = tzone->devdata; + struct acpi_device *device = priv->adev; + unsigned long long hyst; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLI_CELSIUS(hyst, KELVIN_OFFSET); + + return 0; +} + +static int sys_get_trip_temp(struct thermal_zone_device *tzone, + int trip, unsigned long *temp) +{ + struct int3403_priv *priv = tzone->devdata; + struct int3403_sensor *obj = priv->priv; + + if (priv->type != INT3403_TYPE_SENSOR || !obj) + return -EINVAL; + + if (trip == obj->crit_trip_id) + *temp = obj->crit_temp; + else if (trip == obj->psv_trip_id) + *temp = obj->psv_temp; + else { + /* + * get_trip_temp is a mandatory callback but + * PATx method doesn't return any value, so return + * cached value, which was last set from user space + */ + *temp = obj->thresholds[trip]; + } + + return 0; +} + +static int sys_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct int3403_priv *priv = thermal->devdata; + struct int3403_sensor *obj = priv->priv; + + /* Mandatory callback, may not mean much here */ + if (trip == obj->crit_trip_id) + *type = THERMAL_TRIP_CRITICAL; + else + *type = THERMAL_TRIP_PASSIVE; + + return 0; +} + +int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip, + unsigned long temp) +{ + struct int3403_priv *priv = tzone->devdata; + struct acpi_device *device = priv->adev; + struct int3403_sensor *obj = priv->priv; + acpi_status status; + char name[10]; + int ret = 0; + + snprintf(name, sizeof(name), "PAT%d", trip); + if (acpi_has_method(device->handle, name)) { + status = acpi_execute_simple_method(device->handle, name, + MILLI_CELSIUS_TO_DECI_KELVIN(temp, + KELVIN_OFFSET)); + if (ACPI_FAILURE(status)) + ret = -EIO; + else + obj->thresholds[trip] = temp; + } else { + ret = -EIO; + dev_err(&device->dev, "sys_set_trip_temp: method not found\n"); + } + + return ret; +} + +static struct thermal_zone_device_ops tzone_ops = { + .get_temp = sys_get_curr_temp, + .get_trip_temp = sys_get_trip_temp, + .get_trip_type = sys_get_trip_type, + .set_trip_temp = sys_set_trip_temp, + .get_trip_hyst = sys_get_trip_hyst, +}; + +static struct thermal_zone_params int3403_thermal_params = { + .governor_name = "user_space", + .no_hwmon = true, +}; + +static void int3403_notify(acpi_handle handle, + u32 event, void *data) +{ + struct int3403_priv *priv = data; + struct int3403_sensor *obj; + + if (!priv) + return; + + obj = priv->priv; + if (priv->type != INT3403_TYPE_SENSOR || !obj) + return; + + switch (event) { + case INT3403_PERF_CHANGED_EVENT: + break; + case INT3403_THERMAL_EVENT: + thermal_zone_device_update(obj->tzone); + break; + default: + dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); + break; + } +} + +static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp) +{ + unsigned long long crt; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET); + + return 0; +} + +static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp) +{ + unsigned long long psv; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv); + if (ACPI_FAILURE(status)) + return -EIO; + + *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET); + + return 0; +} + +static int int3403_sensor_add(struct int3403_priv *priv) +{ + int result = 0; + acpi_status status; + struct int3403_sensor *obj; + unsigned long long trip_cnt; + int trip_mask = 0; + + obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); + if (!obj) + return -ENOMEM; + + priv->priv = obj; + + status = acpi_evaluate_integer(priv->adev->handle, "PATC", NULL, + &trip_cnt); + if (ACPI_FAILURE(status)) + trip_cnt = 0; + + if (trip_cnt) { + /* We have to cache, thresholds can't be readback */ + obj->thresholds = devm_kzalloc(&priv->pdev->dev, + sizeof(*obj->thresholds) * trip_cnt, + GFP_KERNEL); + if (!obj->thresholds) { + result = -ENOMEM; + goto err_free_obj; + } + trip_mask = BIT(trip_cnt) - 1; + } + + obj->psv_trip_id = -1; + if (!sys_get_trip_psv(priv->adev, &obj->psv_temp)) + obj->psv_trip_id = trip_cnt++; + + obj->crit_trip_id = -1; + if (!sys_get_trip_crt(priv->adev, &obj->crit_temp)) + obj->crit_trip_id = trip_cnt++; + + obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev), + trip_cnt, trip_mask, priv, &tzone_ops, + &int3403_thermal_params, 0, 0); + if (IS_ERR(obj->tzone)) { + result = PTR_ERR(obj->tzone); + obj->tzone = NULL; + goto err_free_obj; + } + + result = acpi_install_notify_handler(priv->adev->handle, + ACPI_DEVICE_NOTIFY, int3403_notify, + (void *)priv); + if (result) + goto err_free_obj; + + return 0; + + err_free_obj: + if (obj->tzone) + thermal_zone_device_unregister(obj->tzone); + return result; +} + +static int int3403_sensor_remove(struct int3403_priv *priv) +{ + struct int3403_sensor *obj = priv->priv; + + thermal_zone_device_unregister(obj->tzone); + return 0; +} + +/* INT3403 Cooling devices */ +static int int3403_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct int3403_priv *priv = cdev->devdata; + struct int3403_cdev *obj = priv->priv; + + *state = obj->max_state; + return 0; +} + +static int int3403_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct int3403_priv *priv = cdev->devdata; + unsigned long long level; + acpi_status status; + + status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); + if (ACPI_SUCCESS(status)) { + *state = level; + return 0; + } else + return -EINVAL; +} + +static int +int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct int3403_priv *priv = cdev->devdata; + acpi_status status; + + status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); + if (ACPI_SUCCESS(status)) + return 0; + else + return -EINVAL; +} + +static const struct thermal_cooling_device_ops int3403_cooling_ops = { + .get_max_state = int3403_get_max_state, + .get_cur_state = int3403_get_cur_state, + .set_cur_state = int3403_set_cur_state, +}; + +static int int3403_cdev_add(struct int3403_priv *priv) +{ + int result = 0; + acpi_status status; + struct int3403_cdev *obj; + struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *p; + + obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); + if (!obj) + return -ENOMEM; + + status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buf.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + printk(KERN_WARNING "Invalid PPSS data\n"); + return -EFAULT; + } + + obj->max_state = p->package.count - 1; + obj->cdev = + thermal_cooling_device_register(acpi_device_bid(priv->adev), + priv, &int3403_cooling_ops); + if (IS_ERR(obj->cdev)) + result = PTR_ERR(obj->cdev); + + priv->priv = obj; + + /* TODO: add ACPI notification support */ + + return result; +} + +static int int3403_cdev_remove(struct int3403_priv *priv) +{ + struct int3403_cdev *obj = priv->priv; + + thermal_cooling_device_unregister(obj->cdev); + return 0; +} + +static int int3403_add(struct platform_device *pdev) +{ + struct int3403_priv *priv; + int result = 0; + acpi_status status; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdev = pdev; + priv->adev = ACPI_COMPANION(&(pdev->dev)); + if (!priv->adev) { + result = -EINVAL; + goto err; + } + + status = acpi_evaluate_integer(priv->adev->handle, "PTYP", + NULL, &priv->type); + if (ACPI_FAILURE(status)) { + result = -EINVAL; + goto err; + } + + platform_set_drvdata(pdev, priv); + switch (priv->type) { + case INT3403_TYPE_SENSOR: + result = int3403_sensor_add(priv); + break; + case INT3403_TYPE_CHARGER: + case INT3403_TYPE_BATTERY: + result = int3403_cdev_add(priv); + break; + default: + result = -EINVAL; + } + + if (result) + goto err; + return result; + +err: + return result; +} + +static int int3403_remove(struct platform_device *pdev) +{ + struct int3403_priv *priv = platform_get_drvdata(pdev); + + switch (priv->type) { + case INT3403_TYPE_SENSOR: + int3403_sensor_remove(priv); + break; + case INT3403_TYPE_CHARGER: + case INT3403_TYPE_BATTERY: + int3403_cdev_remove(priv); + break; + default: + break; + } + + return 0; +} + +static const struct acpi_device_id int3403_device_ids[] = { + {"INT3403", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, int3403_device_ids); + +static struct platform_driver int3403_driver = { + .probe = int3403_add, + .remove = int3403_remove, + .driver = { + .name = "int3403 thermal", + .owner = THIS_MODULE, + .acpi_match_table = int3403_device_ids, + }, +}; + +module_platform_driver(int3403_driver); + +MODULE_AUTHOR("Srinivas Pandruvada "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); -- cgit v0.10.2 From 52b1c69d7e3cd8bdba0e55bde24093f0779bb29d Mon Sep 17 00:00:00 2001 From: Jacob Pan Date: Wed, 3 Sep 2014 15:14:23 +0800 Subject: Thermal: int340x_thermal: expose acpi thermal relationship tables ACPI 4.0 introduced two thermal relationship tables via _ART (active cooling) and _TRT (passive cooling) objects. These tables contain many to many relationships among thermal sensors and cooling devices. This patch parses _ART and _TRT and makes the result available to the userspace via an misc device interface. At the same time, kernel drivers can also request parsing results from internal kernel APIs. The results include source and target devices, influence, and sampling rate in case of _TRT. For _ART, the result shows source device, target device, and weight percentage. Signed-off-by: Jacob Pan Signed-off-by: Zhang Rui diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 6f93e5c..3a89292 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -223,6 +223,7 @@ config INT340X_THERMAL tristate "ACPI INT340X thermal drivers" depends on X86 && ACPI select THERMAL_GOV_USER_SPACE + select ACPI_THERMAL_REL help Newer laptops and tablets that use ACPI may have thermal sensors and other devices with thermal control capabilities outside the core @@ -237,6 +238,10 @@ config INT340X_THERMAL information to allow the user to select his laptop to run without turning on the fans. +config ACPI_THERMAL_REL + tristate + depends on ACPI + menu "Texas Instruments thermal drivers" source "drivers/thermal/ti-soc-thermal/Kconfig" endmenu diff --git a/drivers/thermal/int340x_thermal/Makefile b/drivers/thermal/int340x_thermal/Makefile index c4a5c50..ffe40bf 100644 --- a/drivers/thermal/int340x_thermal/Makefile +++ b/drivers/thermal/int340x_thermal/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o +obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o diff --git a/drivers/thermal/int340x_thermal/acpi_thermal_rel.c b/drivers/thermal/int340x_thermal/acpi_thermal_rel.c new file mode 100644 index 0000000..0d8db80 --- /dev/null +++ b/drivers/thermal/int340x_thermal/acpi_thermal_rel.c @@ -0,0 +1,400 @@ +/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship + * + * Copyright (c) 2014 Intel Corp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + */ + +/* + * Two functionalities included: + * 1. Export _TRT, _ART, via misc device interface to the userspace. + * 2. Provide parsing result to kernel drivers + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "acpi_thermal_rel.h" + +static acpi_handle acpi_thermal_rel_handle; +static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock); +static int acpi_thermal_rel_chrdev_count; /* #times opened */ +static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */ + +static int acpi_thermal_rel_open(struct inode *inode, struct file *file) +{ + spin_lock(&acpi_thermal_rel_chrdev_lock); + if (acpi_thermal_rel_chrdev_exclu || + (acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) { + spin_unlock(&acpi_thermal_rel_chrdev_lock); + return -EBUSY; + } + + if (file->f_flags & O_EXCL) + acpi_thermal_rel_chrdev_exclu = 1; + acpi_thermal_rel_chrdev_count++; + + spin_unlock(&acpi_thermal_rel_chrdev_lock); + + return nonseekable_open(inode, file); +} + +static int acpi_thermal_rel_release(struct inode *inode, struct file *file) +{ + spin_lock(&acpi_thermal_rel_chrdev_lock); + acpi_thermal_rel_chrdev_count--; + acpi_thermal_rel_chrdev_exclu = 0; + spin_unlock(&acpi_thermal_rel_chrdev_lock); + + return 0; +} + +/** + * acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling + * + * @handle: ACPI handle of the device contains _TRT + * @art_count: the number of valid entries resulted from parsing _TRT + * @artp: pointer to pointer of array of art entries in parsing result + * @create_dev: whether to create platform devices for target and source + * + */ +int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp, + bool create_dev) +{ + acpi_status status; + int result = 0; + int i; + int nr_bad_entries = 0; + struct trt *trts; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" }; + + if (!acpi_has_method(handle, "_TRT")) + return 0; + + status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _TRT data\n"); + result = -EFAULT; + goto end; + } + + *trt_count = p->package.count; + trts = kzalloc(*trt_count * sizeof(struct trt), GFP_KERNEL); + if (!trts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < *trt_count; i++) { + struct trt *trt = &trts[i - nr_bad_entries]; + + element.length = sizeof(struct trt); + element.pointer = trt; + + status = acpi_extract_package(&(p->package.elements[i]), + &trt_format, &element); + if (ACPI_FAILURE(status)) { + nr_bad_entries++; + pr_warn("_TRT package %d is invalid, ignored\n", i); + continue; + } + if (!create_dev) + continue; + + result = acpi_bus_get_device(trt->source, &adev); + if (!result) + acpi_create_platform_device(adev); + else + pr_warn("Failed to get source ACPI device\n"); + + result = acpi_bus_get_device(trt->target, &adev); + if (!result) + acpi_create_platform_device(adev); + else + pr_warn("Failed to get target ACPI device\n"); + } + + *trtp = trts; + /* don't count bad entries */ + *trt_count -= nr_bad_entries; +end: + kfree(buffer.pointer); + return result; +} +EXPORT_SYMBOL(acpi_parse_trt); + +/** + * acpi_parse_art - Parse Active Relationship Table _ART + * + * @handle: ACPI handle of the device contains _ART + * @art_count: the number of valid entries resulted from parsing _ART + * @artp: pointer to pointer of array of art entries in parsing result + * @create_dev: whether to create platform devices for target and source + * + */ +int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp, + bool create_dev) +{ + acpi_status status; + int result = 0; + int i; + int nr_bad_entries = 0; + struct art *arts; + struct acpi_device *adev; + union acpi_object *p; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer element = { 0, NULL }; + struct acpi_buffer art_format = { + sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; + + if (!acpi_has_method(handle, "_ART")) + return 0; + + status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + p = buffer.pointer; + if (!p || (p->type != ACPI_TYPE_PACKAGE)) { + pr_err("Invalid _ART data\n"); + result = -EFAULT; + goto end; + } + + /* ignore p->package.elements[0], as this is _ART Revision field */ + *art_count = p->package.count - 1; + arts = kzalloc(*art_count * sizeof(struct art), GFP_KERNEL); + if (!arts) { + result = -ENOMEM; + goto end; + } + + for (i = 0; i < *art_count; i++) { + struct art *art = &arts[i - nr_bad_entries]; + + element.length = sizeof(struct art); + element.pointer = art; + + status = acpi_extract_package(&(p->package.elements[i + 1]), + &art_format, &element); + if (ACPI_FAILURE(status)) { + pr_warn("_ART package %d is invalid, ignored", i); + nr_bad_entries++; + continue; + } + if (!create_dev) + continue; + + if (art->source) { + result = acpi_bus_get_device(art->source, &adev); + if (!result) + acpi_create_platform_device(adev); + else + pr_warn("Failed to get source ACPI device\n"); + } + if (art->target) { + result = acpi_bus_get_device(art->target, &adev); + if (!result) + acpi_create_platform_device(adev); + else + pr_warn("Failed to get source ACPI device\n"); + } + } + + *artp = arts; + /* don't count bad entries */ + *art_count -= nr_bad_entries; +end: + kfree(buffer.pointer); + return result; +} +EXPORT_SYMBOL(acpi_parse_art); + + +/* get device name from acpi handle */ +static void get_single_name(acpi_handle handle, char *name) +{ + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER}; + + if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer))) + pr_warn("Failed get name from handle\n"); + else { + memcpy(name, buffer.pointer, ACPI_NAME_SIZE); + kfree(buffer.pointer); + } +} + +static int fill_art(char __user *ubuf) +{ + int i; + int ret; + int count; + int art_len; + struct art *arts = NULL; + union art_object *art_user; + + ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false); + if (ret) + goto free_art; + art_len = count * sizeof(union art_object); + art_user = kzalloc(art_len, GFP_KERNEL); + if (!art_user) { + ret = -ENOMEM; + goto free_art; + } + /* now fill in user art data */ + for (i = 0; i < count; i++) { + /* userspace art needs device name instead of acpi reference */ + get_single_name(arts[i].source, art_user[i].source_device); + get_single_name(arts[i].target, art_user[i].target_device); + /* copy the rest int data in addition to source and target */ + memcpy(&art_user[i].weight, &arts[i].weight, + sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2)); + } + + if (copy_to_user(ubuf, art_user, art_len)) + ret = -EFAULT; + kfree(art_user); +free_art: + kfree(arts); + return ret; +} + +static int fill_trt(char __user *ubuf) +{ + int i; + int ret; + int count; + int trt_len; + struct trt *trts = NULL; + union trt_object *trt_user; + + ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false); + if (ret) + goto free_trt; + trt_len = count * sizeof(union trt_object); + trt_user = kzalloc(trt_len, GFP_KERNEL); + if (!trt_user) { + ret = -ENOMEM; + goto free_trt; + } + /* now fill in user trt data */ + for (i = 0; i < count; i++) { + /* userspace trt needs device name instead of acpi reference */ + get_single_name(trts[i].source, trt_user[i].source_device); + get_single_name(trts[i].target, trt_user[i].target_device); + trt_user[i].sample_period = trts[i].sample_period; + trt_user[i].influence = trts[i].influence; + } + + if (copy_to_user(ubuf, trt_user, trt_len)) + ret = -EFAULT; + kfree(trt_user); +free_trt: + kfree(trts); + return ret; +} + +static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd, + unsigned long __arg) +{ + int ret = 0; + unsigned long length = 0; + unsigned long count = 0; + char __user *arg = (void __user *)__arg; + struct trt *trts; + struct art *arts; + + switch (cmd) { + case ACPI_THERMAL_GET_TRT_COUNT: + ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count, + &trts, false); + kfree(trts); + if (!ret) + return put_user(count, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_TRT_LEN: + ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count, + &trts, false); + kfree(trts); + length = count * sizeof(union trt_object); + if (!ret) + return put_user(length, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_TRT: + return fill_trt(arg); + case ACPI_THERMAL_GET_ART_COUNT: + ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count, + &arts, false); + kfree(arts); + if (!ret) + return put_user(count, (unsigned long __user *)__arg); + return ret; + case ACPI_THERMAL_GET_ART_LEN: + ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count, + &arts, false); + kfree(arts); + length = count * sizeof(union art_object); + if (!ret) + return put_user(length, (unsigned long __user *)__arg); + return ret; + + case ACPI_THERMAL_GET_ART: + return fill_art(arg); + + default: + return -ENOTTY; + } +} + +static const struct file_operations acpi_thermal_rel_fops = { + .owner = THIS_MODULE, + .open = acpi_thermal_rel_open, + .release = acpi_thermal_rel_release, + .unlocked_ioctl = acpi_thermal_rel_ioctl, + .llseek = no_llseek, +}; + +static struct miscdevice acpi_thermal_rel_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + "acpi_thermal_rel", + &acpi_thermal_rel_fops +}; + +int acpi_thermal_rel_misc_device_add(acpi_handle handle) +{ + acpi_thermal_rel_handle = handle; + + return misc_register(&acpi_thermal_rel_misc_device); +} +EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add); + +int acpi_thermal_rel_misc_device_remove(acpi_handle handle) +{ + misc_deregister(&acpi_thermal_rel_misc_device); + + return 0; +} +EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove); + +MODULE_AUTHOR("Zhang Rui "); +MODULE_AUTHOR("Jacob Pan + +#define ACPI_THERMAL_MAGIC 's' + +#define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long) +#define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long) +#define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long) +#define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long) + +#define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long) +#define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long) + +struct art { + acpi_handle source; + acpi_handle target; + u64 weight; + u64 ac0_max; + u64 ac1_max; + u64 ac2_max; + u64 ac3_max; + u64 ac4_max; + u64 ac5_max; + u64 ac6_max; + u64 ac7_max; + u64 ac8_max; + u64 ac9_max; +} __packed; + +struct trt { + acpi_handle source; + acpi_handle target; + u64 influence; + u64 sample_period; + u64 reverved1; + u64 reverved2; + u64 reverved3; + u64 reverved4; +} __packed; + +#define ACPI_NR_ART_ELEMENTS 13 +/* for usrspace */ +union art_object { + struct { + char source_device[8]; /* ACPI single name */ + char target_device[8]; /* ACPI single name */ + u64 weight; + u64 ac0_max_level; + u64 ac1_max_level; + u64 ac2_max_level; + u64 ac3_max_level; + u64 ac4_max_level; + u64 ac5_max_level; + u64 ac6_max_level; + u64 ac7_max_level; + u64 ac8_max_level; + u64 ac9_max_level; + }; + u64 __data[ACPI_NR_ART_ELEMENTS]; +}; + +union trt_object { + struct { + char source_device[8]; /* ACPI single name */ + char target_device[8]; /* ACPI single name */ + u64 influence; + u64 sample_period; + u64 reserved[4]; + }; + u64 __data[8]; +}; + +#ifdef __KERNEL__ +int acpi_thermal_rel_misc_device_add(acpi_handle handle); +int acpi_thermal_rel_misc_device_remove(acpi_handle handle); +int acpi_parse_art(acpi_handle handle, int *art_count, struct art **arts, + bool create_dev); +int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trts, + bool create_dev); +#endif + +#endif /* __ACPI_ACPI_THERMAL_H */ -- cgit v0.10.2 From 6306e68a63f26219f3b1f948e96ba387ddccb272 Mon Sep 17 00:00:00 2001 From: Jacob Pan Date: Thu, 12 Jun 2014 08:08:07 -0700 Subject: Thermal: int3400_thermal: use acpi_thermal_rel parsing APIs ACPI _TRT and _ART parsing code has been moved to acpi_thermal_rel such that it can be used by other devices in the future. Use the parsing APIs in acpi_thermal_rel.c instead. Signed-off-by: Jacob Pan Signed-off-by: Zhang Rui diff --git a/drivers/thermal/int340x_thermal/int3400_thermal.c b/drivers/thermal/int340x_thermal/int3400_thermal.c index 9104b4f..edc1cce 100644 --- a/drivers/thermal/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/int340x_thermal/int3400_thermal.c @@ -14,33 +14,7 @@ #include #include #include - -struct art { - acpi_handle source; - acpi_handle target; - u64 weight; - u64 ac0_max; - u64 ac1_max; - u64 ac2_max; - u64 ac3_max; - u64 ac4_max; - u64 ac5_max; - u64 ac6_max; - u64 ac7_max; - u64 ac8_max; - u64 ac9_max; -}; - -struct trt { - acpi_handle source; - acpi_handle target; - u64 influence; - u64 sampling_period; - u64 reverved1; - u64 reverved2; - u64 reverved3; - u64 reverved4; -}; +#include "acpi_thermal_rel.h" enum int3400_thermal_uuid { INT3400_THERMAL_PASSIVE_1, @@ -68,6 +42,7 @@ struct int3400_thermal_priv { int trt_count; struct trt *trts; u8 uuid_bitmap; + int rel_misc_dev_res; }; static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) @@ -146,136 +121,6 @@ static int int3400_thermal_run_osc(acpi_handle handle, return result; } - -static int parse_art(struct int3400_thermal_priv *priv) -{ - acpi_handle handle = priv->adev->handle; - acpi_status status; - int result = 0; - int i; - struct acpi_device *adev; - union acpi_object *p; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer element = { 0, NULL }; - struct acpi_buffer art_format = { - sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" }; - - if (!acpi_has_method(handle, "_ART")) - return 0; - - status = acpi_evaluate_object(handle, "_ART", NULL, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buffer.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - pr_err("Invalid _ART data\n"); - result = -EFAULT; - goto end; - } - - /* ignore p->package.elements[0], as this is _ART Revision field */ - priv->art_count = p->package.count - 1; - priv->arts = kzalloc(sizeof(struct art) * priv->art_count, GFP_KERNEL); - if (!priv->arts) { - result = -ENOMEM; - goto end; - } - - for (i = 0; i < priv->art_count; i++) { - struct art *art = &(priv->arts[i]); - - element.length = sizeof(struct art); - element.pointer = art; - - status = acpi_extract_package(&(p->package.elements[i + 1]), - &art_format, &element); - if (ACPI_FAILURE(status)) { - pr_err("Invalid _ART data"); - result = -EFAULT; - kfree(priv->arts); - goto end; - } - result = acpi_bus_get_device(art->source, &adev); - if (!result) - acpi_create_platform_device(adev, NULL); - else - pr_warn("Failed to get source ACPI device\n"); - result = acpi_bus_get_device(art->target, &adev); - if (!result) - acpi_create_platform_device(adev, NULL); - else - pr_warn("Failed to get source ACPI device\n"); - } -end: - kfree(buffer.pointer); - return result; -} - -static int parse_trt(struct int3400_thermal_priv *priv) -{ - acpi_handle handle = priv->adev->handle; - acpi_status status; - int result = 0; - int i; - struct acpi_device *adev; - union acpi_object *p; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_buffer element = { 0, NULL }; - struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" }; - - if (!acpi_has_method(handle, "_TRT")) - return 0; - - status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer); - if (ACPI_FAILURE(status)) - return -ENODEV; - - p = buffer.pointer; - if (!p || (p->type != ACPI_TYPE_PACKAGE)) { - pr_err("Invalid _TRT data\n"); - result = -EFAULT; - goto end; - } - - priv->trt_count = p->package.count; - priv->trts = kzalloc(sizeof(struct trt) * priv->trt_count, GFP_KERNEL); - if (!priv->trts) { - result = -ENOMEM; - goto end; - } - - for (i = 0; i < priv->trt_count; i++) { - struct trt *trt = &(priv->trts[i]); - - element.length = sizeof(struct trt); - element.pointer = trt; - - status = acpi_extract_package(&(p->package.elements[i]), - &trt_format, &element); - if (ACPI_FAILURE(status)) { - pr_err("Invalid _ART data"); - result = -EFAULT; - kfree(priv->trts); - goto end; - } - - result = acpi_bus_get_device(trt->source, &adev); - if (!result) - acpi_create_platform_device(adev, NULL); - else - pr_warn("Failed to get source ACPI device\n"); - result = acpi_bus_get_device(trt->target, &adev); - if (!result) - acpi_create_platform_device(adev, NULL); - else - pr_warn("Failed to get target ACPI device\n"); - } -end: - kfree(buffer.pointer); - return result; -} - static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, unsigned long *temp) { @@ -350,11 +195,14 @@ static int int3400_thermal_probe(struct platform_device *pdev) if (result) goto free_priv; - result = parse_art(priv); + result = acpi_parse_art(priv->adev->handle, &priv->art_count, + &priv->arts, true); if (result) goto free_priv; - result = parse_trt(priv); + + result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, + &priv->trts, true); if (result) goto free_art; @@ -372,6 +220,9 @@ static int int3400_thermal_probe(struct platform_device *pdev) goto free_trt; } + priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( + priv->adev->handle); + return 0; free_trt: kfree(priv->trts); @@ -386,6 +237,9 @@ static int int3400_thermal_remove(struct platform_device *pdev) { struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); + if (!priv->rel_misc_dev_res) + acpi_thermal_rel_misc_device_remove(priv->adev->handle); + thermal_zone_device_unregister(priv->thermal); kfree(priv->trts); kfree(priv->arts); -- cgit v0.10.2 From d8054749c6795073cb427465a726213d45898f68 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Thu, 19 Jun 2014 15:43:29 +0800 Subject: Thermal: int340x thermal: select ACPI fan driver we share the same driver for both ACPI predefined Fan device and INT3404 Fan device, thus we should select the ACPI Fan driver when int340x thermal drivers are enabeld. Signed-off-by: Zhang Rui diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index d0f3265..b23fe37 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -144,7 +144,7 @@ config ACPI_VIDEO config ACPI_FAN tristate "Fan" - select THERMAL + depends on THERMAL default y help This driver supports ACPI fan devices, allowing user-mode diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3a89292..e65ca2f 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -224,6 +224,7 @@ config INT340X_THERMAL depends on X86 && ACPI select THERMAL_GOV_USER_SPACE select ACPI_THERMAL_REL + select ACPI_FAN help Newer laptops and tablets that use ACPI may have thermal sensors and other devices with thermal control capabilities outside the core -- cgit v0.10.2