From e7d22fd2f634d570ab036336c7a6e5017244d0e0 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 9 Mar 2017 13:36:00 +0530 Subject: [PATCH 01/25] thermal: ti-soc-thermal: Fetch slope and offset from DT Currently slope and offset values for calculating the hot spot temperature of a thermal zone is being taken directly from driver data. So fetch them from device tree. Signed-off-by: Keerthy Signed-off-by: Eduardo Valentin --- drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 0586bd0f2bab..2054a5c06e86 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -96,8 +96,8 @@ static inline int __ti_thermal_get_temp(void *devdata, int *temp) return ret; /* Default constants */ - slope = s->slope; - constant = s->constant; + slope = thermal_zone_get_slope(data->ti_thermal); + constant = thermal_zone_get_offset(data->ti_thermal); pcb_tz = data->pcb_tz; /* In case pcb zone is available, use the extrapolation rule with it */ From 004f772871315091e671b0979047d9a88c1061fd Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 9 Mar 2017 13:36:01 +0530 Subject: [PATCH 02/25] thermal: ti-soc-thermal: Remove redundant constants Now that slope and offset data are being passed from device tree no need to populate in driver data. Signed-off-by: Keerthy Signed-off-by: Eduardo Valentin --- .../thermal/ti-soc-thermal/dra752-thermal-data.c | 10 ---------- .../thermal/ti-soc-thermal/omap3-thermal-data.c | 4 ---- .../thermal/ti-soc-thermal/omap4-thermal-data.c | 6 ------ .../thermal/ti-soc-thermal/omap5-thermal-data.c | 4 ---- drivers/thermal/ti-soc-thermal/ti-bandgap.h | 4 ---- drivers/thermal/ti-soc-thermal/ti-thermal.h | 16 ---------------- 6 files changed, 44 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c index 118d7d847715..4167373327d9 100644 --- a/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/dra752-thermal-data.c @@ -410,8 +410,6 @@ const struct ti_bandgap_data dra752_data = { .domain = "cpu", .register_cooling = ti_thermal_register_cpu_cooling, .unregister_cooling = ti_thermal_unregister_cpu_cooling, - .slope = DRA752_GRADIENT_SLOPE, - .constant = DRA752_GRADIENT_CONST, .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, .constant_pcb = DRA752_GRADIENT_CONST_W_PCB, }, @@ -419,8 +417,6 @@ const struct ti_bandgap_data dra752_data = { .registers = &dra752_gpu_temp_sensor_registers, .ts_data = &dra752_gpu_temp_sensor_data, .domain = "gpu", - .slope = DRA752_GRADIENT_SLOPE, - .constant = DRA752_GRADIENT_CONST, .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, .constant_pcb = DRA752_GRADIENT_CONST_W_PCB, }, @@ -428,8 +424,6 @@ const struct ti_bandgap_data dra752_data = { .registers = &dra752_core_temp_sensor_registers, .ts_data = &dra752_core_temp_sensor_data, .domain = "core", - .slope = DRA752_GRADIENT_SLOPE, - .constant = DRA752_GRADIENT_CONST, .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, .constant_pcb = DRA752_GRADIENT_CONST_W_PCB, }, @@ -437,8 +431,6 @@ const struct ti_bandgap_data dra752_data = { .registers = &dra752_dspeve_temp_sensor_registers, .ts_data = &dra752_dspeve_temp_sensor_data, .domain = "dspeve", - .slope = DRA752_GRADIENT_SLOPE, - .constant = DRA752_GRADIENT_CONST, .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, .constant_pcb = DRA752_GRADIENT_CONST_W_PCB, }, @@ -446,8 +438,6 @@ const struct ti_bandgap_data dra752_data = { .registers = &dra752_iva_temp_sensor_registers, .ts_data = &dra752_iva_temp_sensor_data, .domain = "iva", - .slope = DRA752_GRADIENT_SLOPE, - .constant = DRA752_GRADIENT_CONST, .slope_pcb = DRA752_GRADIENT_SLOPE_W_PCB, .constant_pcb = DRA752_GRADIENT_CONST_W_PCB, }, diff --git a/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c index 3ee34340edab..c6d217913dd1 100644 --- a/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/omap3-thermal-data.c @@ -91,8 +91,6 @@ const struct ti_bandgap_data omap34xx_data = { .registers = &omap34xx_mpu_temp_sensor_registers, .ts_data = &omap34xx_mpu_temp_sensor_data, .domain = "cpu", - .slope = 0, - .constant = 20000, .slope_pcb = 0, .constant_pcb = 20000, .register_cooling = NULL, @@ -164,8 +162,6 @@ const struct ti_bandgap_data omap36xx_data = { .registers = &omap36xx_mpu_temp_sensor_registers, .ts_data = &omap36xx_mpu_temp_sensor_data, .domain = "cpu", - .slope = 0, - .constant = 20000, .slope_pcb = 0, .constant_pcb = 20000, .register_cooling = NULL, diff --git a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c index d255d33da9eb..fd1113360603 100644 --- a/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/omap4-thermal-data.c @@ -82,8 +82,6 @@ const struct ti_bandgap_data omap4430_data = { .registers = &omap4430_mpu_temp_sensor_registers, .ts_data = &omap4430_mpu_temp_sensor_data, .domain = "cpu", - .slope = OMAP_GRADIENT_SLOPE_4430, - .constant = OMAP_GRADIENT_CONST_4430, .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4430, .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4430, .register_cooling = ti_thermal_register_cpu_cooling, @@ -222,8 +220,6 @@ const struct ti_bandgap_data omap4460_data = { .registers = &omap4460_mpu_temp_sensor_registers, .ts_data = &omap4460_mpu_temp_sensor_data, .domain = "cpu", - .slope = OMAP_GRADIENT_SLOPE_4460, - .constant = OMAP_GRADIENT_CONST_4460, .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4460, .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4460, .register_cooling = ti_thermal_register_cpu_cooling, @@ -255,8 +251,6 @@ const struct ti_bandgap_data omap4470_data = { .registers = &omap4460_mpu_temp_sensor_registers, .ts_data = &omap4460_mpu_temp_sensor_data, .domain = "cpu", - .slope = OMAP_GRADIENT_SLOPE_4470, - .constant = OMAP_GRADIENT_CONST_4470, .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_4470, .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_4470, .register_cooling = ti_thermal_register_cpu_cooling, diff --git a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c index 79ff70c446ba..cd9a304fb571 100644 --- a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c @@ -336,8 +336,6 @@ const struct ti_bandgap_data omap5430_data = { .domain = "cpu", .register_cooling = ti_thermal_register_cpu_cooling, .unregister_cooling = ti_thermal_unregister_cpu_cooling, - .slope = OMAP_GRADIENT_SLOPE_5430_CPU, - .constant = OMAP_GRADIENT_CONST_5430_CPU, .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_CPU, .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_CPU, }, @@ -345,8 +343,6 @@ const struct ti_bandgap_data omap5430_data = { .registers = &omap5430_gpu_temp_sensor_registers, .ts_data = &omap5430_gpu_temp_sensor_data, .domain = "gpu", - .slope = OMAP_GRADIENT_SLOPE_5430_GPU, - .constant = OMAP_GRADIENT_CONST_5430_GPU, .slope_pcb = OMAP_GRADIENT_SLOPE_W_PCB_5430_GPU, .constant_pcb = OMAP_GRADIENT_CONST_W_PCB_5430_GPU, }, diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.h b/drivers/thermal/ti-soc-thermal/ti-bandgap.h index fe0adb898764..209c664c2823 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.h +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.h @@ -254,8 +254,6 @@ struct ti_bandgap { * @ts_data: pointer to struct with thresholds, limits of temperature sensor * @registers: pointer to the list of register offsets and bitfields * @domain: the name of the domain where the sensor is located - * @slope: sensor gradient slope info for hotspot extrapolation equation - * @constant: sensor gradient const info for hotspot extrapolation equation * @slope_pcb: sensor gradient slope info for hotspot extrapolation equation * with no external influence * @constant_pcb: sensor gradient const info for hotspot extrapolation equation @@ -274,8 +272,6 @@ struct ti_temp_sensor { struct temp_sensor_registers *registers; char *domain; /* for hotspot extrapolation */ - const int slope; - const int constant; const int slope_pcb; const int constant_pcb; int (*register_cooling)(struct ti_bandgap *bgp, int id); diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal.h b/drivers/thermal/ti-soc-thermal/ti-thermal.h index f8b7ffea6194..8e85ca973967 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal.h +++ b/drivers/thermal/ti-soc-thermal/ti-thermal.h @@ -25,22 +25,6 @@ #include "ti-bandgap.h" -/* sensors gradient and offsets */ -#define OMAP_GRADIENT_SLOPE_4430 0 -#define OMAP_GRADIENT_CONST_4430 20000 -#define OMAP_GRADIENT_SLOPE_4460 348 -#define OMAP_GRADIENT_CONST_4460 -9301 -#define OMAP_GRADIENT_SLOPE_4470 308 -#define OMAP_GRADIENT_CONST_4470 -7896 - -#define OMAP_GRADIENT_SLOPE_5430_CPU 65 -#define OMAP_GRADIENT_CONST_5430_CPU -1791 -#define OMAP_GRADIENT_SLOPE_5430_GPU 117 -#define OMAP_GRADIENT_CONST_5430_GPU -2992 - -#define DRA752_GRADIENT_SLOPE 0 -#define DRA752_GRADIENT_CONST 2000 - /* PCB sensor calculation constants */ #define OMAP_GRADIENT_SLOPE_W_PCB_4430 0 #define OMAP_GRADIENT_CONST_W_PCB_4430 20000 From b263b473bf62dfd4aa10fc9068b2c78b1b20f202 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Thu, 9 Mar 2017 13:36:02 +0530 Subject: [PATCH 03/25] thermal: ti-soc-thermal: Remove redundant code ti_thermal_expose_sensor always takes the devm_thermal_zone_of_sensor_register call for registration with the device tree nodes present for all the bandgap sensors for omap3/4/5 and dra7 family. There are large chunks of unused code. Removing all of them. Signed-off-by: Keerthy Signed-off-by: Eduardo Valentin --- .../ti-soc-thermal/ti-thermal-common.c | 154 +----------------- 1 file changed, 3 insertions(+), 151 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 2054a5c06e86..02790f69e26c 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -126,119 +126,6 @@ static inline int ti_thermal_get_temp(struct thermal_zone_device *thermal, return __ti_thermal_get_temp(data, temp); } -/* Bind callback functions for thermal zone */ -static int ti_thermal_bind(struct thermal_zone_device *thermal, - struct thermal_cooling_device *cdev) -{ - struct ti_thermal_data *data = thermal->devdata; - int id; - - if (!data || IS_ERR(data)) - return -ENODEV; - - /* check if this is the cooling device we registered */ - if (data->cool_dev != cdev) - return 0; - - id = data->sensor_id; - - /* Simple thing, two trips, one passive another critical */ - return thermal_zone_bind_cooling_device(thermal, 0, cdev, - /* bind with min and max states defined by cpu_cooling */ - THERMAL_NO_LIMIT, - THERMAL_NO_LIMIT, - THERMAL_WEIGHT_DEFAULT); -} - -/* Unbind callback functions for thermal zone */ -static int ti_thermal_unbind(struct thermal_zone_device *thermal, - struct thermal_cooling_device *cdev) -{ - struct ti_thermal_data *data = thermal->devdata; - - if (!data || IS_ERR(data)) - return -ENODEV; - - /* check if this is the cooling device we registered */ - if (data->cool_dev != cdev) - return 0; - - /* Simple thing, two trips, one passive another critical */ - return thermal_zone_unbind_cooling_device(thermal, 0, cdev); -} - -/* Get mode callback functions for thermal zone */ -static int ti_thermal_get_mode(struct thermal_zone_device *thermal, - enum thermal_device_mode *mode) -{ - struct ti_thermal_data *data = thermal->devdata; - - if (data) - *mode = data->mode; - - return 0; -} - -/* Set mode callback functions for thermal zone */ -static int ti_thermal_set_mode(struct thermal_zone_device *thermal, - enum thermal_device_mode mode) -{ - struct ti_thermal_data *data = thermal->devdata; - struct ti_bandgap *bgp; - - bgp = data->bgp; - - if (!data->ti_thermal) { - dev_notice(&thermal->device, "thermal zone not registered\n"); - return 0; - } - - mutex_lock(&data->ti_thermal->lock); - - if (mode == THERMAL_DEVICE_ENABLED) - data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE; - else - data->ti_thermal->polling_delay = 0; - - mutex_unlock(&data->ti_thermal->lock); - - data->mode = mode; - ti_bandgap_write_update_interval(bgp, data->sensor_id, - data->ti_thermal->polling_delay); - thermal_zone_device_update(data->ti_thermal, THERMAL_EVENT_UNSPECIFIED); - dev_dbg(&thermal->device, "thermal polling set for duration=%d msec\n", - data->ti_thermal->polling_delay); - - return 0; -} - -/* Get trip type callback functions for thermal zone */ -static int ti_thermal_get_trip_type(struct thermal_zone_device *thermal, - int trip, enum thermal_trip_type *type) -{ - if (!ti_thermal_is_valid_trip(trip)) - return -EINVAL; - - if (trip + 1 == OMAP_TRIP_NUMBER) - *type = THERMAL_TRIP_CRITICAL; - else - *type = THERMAL_TRIP_PASSIVE; - - return 0; -} - -/* Get trip temperature callback functions for thermal zone */ -static int ti_thermal_get_trip_temp(struct thermal_zone_device *thermal, - int trip, int *temp) -{ - if (!ti_thermal_is_valid_trip(trip)) - return -EINVAL; - - *temp = ti_thermal_get_trip_value(trip); - - return 0; -} - static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend) { struct ti_thermal_data *data = p; @@ -262,38 +149,11 @@ static int __ti_thermal_get_trend(void *p, int trip, enum thermal_trend *trend) return 0; } -/* Get the temperature trend callback functions for thermal zone */ -static int ti_thermal_get_trend(struct thermal_zone_device *thermal, - int trip, enum thermal_trend *trend) -{ - return __ti_thermal_get_trend(thermal->devdata, trip, trend); -} - -/* Get critical temperature callback functions for thermal zone */ -static int ti_thermal_get_crit_temp(struct thermal_zone_device *thermal, - int *temp) -{ - /* shutdown zone */ - return ti_thermal_get_trip_temp(thermal, OMAP_TRIP_NUMBER - 1, temp); -} - static const struct thermal_zone_of_device_ops ti_of_thermal_ops = { .get_temp = __ti_thermal_get_temp, .get_trend = __ti_thermal_get_trend, }; -static struct thermal_zone_device_ops ti_thermal_ops = { - .get_temp = ti_thermal_get_temp, - .get_trend = ti_thermal_get_trend, - .bind = ti_thermal_bind, - .unbind = ti_thermal_unbind, - .get_mode = ti_thermal_get_mode, - .set_mode = ti_thermal_set_mode, - .get_trip_type = ti_thermal_get_trip_type, - .get_trip_temp = ti_thermal_get_trip_temp, - .get_crit_temp = ti_thermal_get_crit_temp, -}; - static struct ti_thermal_data *ti_thermal_build_data(struct ti_bandgap *bgp, int id) { @@ -331,18 +191,10 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, data->ti_thermal = devm_thermal_zone_of_sensor_register(bgp->dev, id, data, &ti_of_thermal_ops); if (IS_ERR(data->ti_thermal)) { - /* Create thermal zone */ - data->ti_thermal = thermal_zone_device_register(domain, - OMAP_TRIP_NUMBER, 0, data, &ti_thermal_ops, - NULL, FAST_TEMP_MONITORING_RATE, - FAST_TEMP_MONITORING_RATE); - if (IS_ERR(data->ti_thermal)) { - dev_err(bgp->dev, "thermal zone device is NULL\n"); - return PTR_ERR(data->ti_thermal); - } - data->ti_thermal->polling_delay = FAST_TEMP_MONITORING_RATE; - data->our_zone = true; + dev_err(bgp->dev, "thermal zone device is NULL\n"); + return PTR_ERR(data->ti_thermal); } + ti_bandgap_set_sensor_data(bgp, id, data); ti_bandgap_write_update_interval(bgp, data->sensor_id, data->ti_thermal->polling_delay); From 78aefd2d5911c4e0b5dc0b0578b3b8c7673be1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:50 +0200 Subject: [PATCH 04/25] thermal: rcar_gen3_thermal: add delay in .thermal_init on r8a7796 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .thermal_init needs to be delayed a short amount of time to allow for the TEMP register to contain something useful. If it's not delayed these warnings are common during boot: thermal thermal_zone0: failed to read out thermal zone (-5) thermal thermal_zone1: failed to read out thermal zone (-5) thermal thermal_zone2: failed to read out thermal zone (-5) The warnings are triggered by the first call to .get_temp() while the TEMP register contains 0 and rcar_gen3_thermal_get_temp() returns -EIO since a TEMP value of 0 will result in a temperature reading which is out of specifications. This should have been done in the initial commit which adds the driver as the same issue was found and corrected for r8a7795. Fixes: 564e73d283af9d4c ("thermal: rcar_gen3_thermal: Add R-Car Gen3 thermal driver") Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index d33c845244b1..ec477d47d0ba 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -222,6 +222,8 @@ static void r8a7796_thermal_init(struct rcar_gen3_thermal_tsc *tsc) reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR); reg_val |= THCTR_THSST; rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val); + + usleep_range(1000, 2000); } static const struct rcar_gen3_thermal_data r8a7795_data = { From 100cfbcf2580b0605f50af32fefd9c8d1d8357fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:51 +0200 Subject: [PATCH 05/25] thermal: rcar_gen3_thermal: remove unneeded mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no point in protecting a register read with a lock. This is most likely a leftover from when the driver was reworked before being submitted for upstream. Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index ec477d47d0ba..cb5c362c0000 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -72,7 +71,6 @@ struct rcar_gen3_thermal_tsc { void __iomem *base; struct thermal_zone_device *zone; struct equation_coefs coef; - struct mutex lock; }; struct rcar_gen3_thermal_priv { @@ -163,16 +161,12 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp) u32 reg; /* Read register and convert to mili Celsius */ - mutex_lock(&tsc->lock); - reg = rcar_gen3_thermal_read(tsc, REG_GEN3_TEMP) & CTEMP_MASK; val1 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b1, tsc->coef.a1); val2 = FIXPT_DIV(FIXPT_INT(reg) - tsc->coef.b2, tsc->coef.a2); mcelsius = FIXPT_TO_MCELSIUS((val1 + val2) / 2); - mutex_unlock(&tsc->lock); - /* Make sure we are inside specifications */ if ((mcelsius < MCELSIUS(-40)) || (mcelsius > MCELSIUS(125))) return -EIO; @@ -299,7 +293,6 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) } priv->tscs[i] = tsc; - mutex_init(&tsc->lock); match_data->thermal_init(tsc); rcar_gen3_thermal_calc_coefs(&tsc->coef, ptat, thcode[i]); From d51546c0db975a4750161d17eef62dfcf9eedc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:52 +0200 Subject: [PATCH 06/25] thermal: rcar_gen3_thermal: check that TSC exists before memory allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the check for a TSC resource before allocating memory for a new TSC. If no TSC is found there is little point in allocating memory for it. Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index cb5c362c0000..9b6bc03dd142 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -276,16 +276,16 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) for (i = 0; i < TSC_MAX_NUM; i++) { struct rcar_gen3_thermal_tsc *tsc; + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + break; + tsc = devm_kzalloc(dev, sizeof(*tsc), GFP_KERNEL); if (!tsc) { ret = -ENOMEM; goto error_unregister; } - res = platform_get_resource(pdev, IORESOURCE_MEM, i); - if (!res) - break; - tsc->base = devm_ioremap_resource(dev, res); if (IS_ERR(tsc->base)) { ret = PTR_ERR(tsc->base); From 97dad1f1d2b3f2a2a77551849357b7ac38b0b6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:53 +0200 Subject: [PATCH 07/25] thermal: rcar_gen3_thermal: record and check number of TSCs found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record how many TSCs are found in struct rcar_gen3_thermal_priv, this is needed to be able to add hardware interrupts for trip points later. Also add a check to make sure at least one TSC is found. Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index 9b6bc03dd142..4a08b35533dc 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -75,6 +75,7 @@ struct rcar_gen3_thermal_tsc { struct rcar_gen3_thermal_priv { struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM]; + unsigned int num_tscs; }; struct rcar_gen3_thermal_data { @@ -307,6 +308,13 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) tsc->zone = zone; } + priv->num_tscs = i; + + if (!priv->num_tscs) { + ret = -ENODEV; + goto error_unregister; + } + return 0; error_unregister: From 7d4b269776ec67c1b7d83c6c727a2771e5f39d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:54 +0200 Subject: [PATCH 08/25] thermal: rcar_gen3_thermal: enable hardware interrupts for trip points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable hardware trip points by implementing the set_trips callback. The thermal core will take care of setting the initial trip point window and to update it once the driver reports a TSC has moved outside it. The interrupt structure for this device is a bit odd. There is not a dedicated IRQ for each TSC, instead the interrupts are shared between all TSCs. IRQn is fired if the temp monitored in IRQTEMPn is reached in any of the TSCs, example IRQ3 is fired if temperature in IRQTEMP3 is reached in either TSC0, TSC1 or TSC2. For this reason the usage of interrupts in this driver is an all-on or all-off design. When an interrupt happens all TSCs are checked and all thermal zones are updated. This could be refined to be more fine grained but the thermal core takes care of only updating the thermal zones that have left their trip point window. Signed-off-by: Niklas Söderlund Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 132 +++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index 4a08b35533dc..d37c7d8f8fcd 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -23,8 +23,11 @@ #include #include #include +#include #include +#include "thermal_core.h" + /* Register offsets */ #define REG_GEN3_IRQSTR 0x04 #define REG_GEN3_IRQMSK 0x08 @@ -40,6 +43,14 @@ #define REG_GEN3_THCODE2 0x54 #define REG_GEN3_THCODE3 0x58 +/* IRQ{STR,MSK,EN} bits */ +#define IRQ_TEMP1 BIT(0) +#define IRQ_TEMP2 BIT(1) +#define IRQ_TEMP3 BIT(2) +#define IRQ_TEMPD1 BIT(3) +#define IRQ_TEMPD2 BIT(4) +#define IRQ_TEMPD3 BIT(5) + /* CTSR bits */ #define CTSR_PONM BIT(8) #define CTSR_AOUT BIT(7) @@ -76,6 +87,7 @@ struct rcar_gen3_thermal_tsc { struct rcar_gen3_thermal_priv { struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM]; unsigned int num_tscs; + spinlock_t lock; /* Protect interrupts on and off */ }; struct rcar_gen3_thermal_data { @@ -113,6 +125,7 @@ static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc, #define FIXPT_SHIFT 7 #define FIXPT_INT(_x) ((_x) << FIXPT_SHIFT) +#define INT_FIXPT(_x) ((_x) >> FIXPT_SHIFT) #define FIXPT_DIV(_a, _b) DIV_ROUND_CLOSEST(((_a) << FIXPT_SHIFT), (_b)) #define FIXPT_TO_MCELSIUS(_x) ((_x) * 1000 >> FIXPT_SHIFT) @@ -178,10 +191,87 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp) return 0; } +static int rcar_gen3_thermal_mcelsius_to_temp(struct rcar_gen3_thermal_tsc *tsc, + int mcelsius) +{ + int celsius, val1, val2; + + celsius = DIV_ROUND_CLOSEST(mcelsius, 1000); + val1 = celsius * tsc->coef.a1 + tsc->coef.b1; + val2 = celsius * tsc->coef.a2 + tsc->coef.b2; + + return INT_FIXPT((val1 + val2) / 2); +} + +static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high) +{ + struct rcar_gen3_thermal_tsc *tsc = devdata; + + low = clamp_val(low, -40000, 125000); + high = clamp_val(high, -40000, 125000); + + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1, + rcar_gen3_thermal_mcelsius_to_temp(tsc, low)); + + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2, + rcar_gen3_thermal_mcelsius_to_temp(tsc, high)); + + return 0; +} + static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = { .get_temp = rcar_gen3_thermal_get_temp, + .set_trips = rcar_gen3_thermal_set_trips, }; +static void rcar_thermal_irq_set(struct rcar_gen3_thermal_priv *priv, bool on) +{ + unsigned int i; + u32 val = on ? IRQ_TEMPD1 | IRQ_TEMP2 : 0; + + for (i = 0; i < priv->num_tscs; i++) + rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQMSK, val); +} + +static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data) +{ + struct rcar_gen3_thermal_priv *priv = data; + u32 status; + int i, ret = IRQ_HANDLED; + + spin_lock(&priv->lock); + for (i = 0; i < priv->num_tscs; i++) { + status = rcar_gen3_thermal_read(priv->tscs[i], REG_GEN3_IRQSTR); + rcar_gen3_thermal_write(priv->tscs[i], REG_GEN3_IRQSTR, 0); + if (status) + ret = IRQ_WAKE_THREAD; + } + + if (ret == IRQ_WAKE_THREAD) + rcar_thermal_irq_set(priv, false); + + spin_unlock(&priv->lock); + + return ret; +} + +static irqreturn_t rcar_gen3_thermal_irq_thread(int irq, void *data) +{ + struct rcar_gen3_thermal_priv *priv = data; + unsigned long flags; + int i; + + for (i = 0; i < priv->num_tscs; i++) + thermal_zone_device_update(priv->tscs[i]->zone, + THERMAL_EVENT_UNSPECIFIED); + + spin_lock_irqsave(&priv->lock, flags); + rcar_thermal_irq_set(priv, true); + spin_unlock_irqrestore(&priv->lock, flags); + + return IRQ_HANDLED; +} + static void r8a7795_thermal_init(struct rcar_gen3_thermal_tsc *tsc) { rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_THBGR); @@ -190,7 +280,11 @@ static void r8a7795_thermal_init(struct rcar_gen3_thermal_tsc *tsc) usleep_range(1000, 2000); rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_PONM); + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F); + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0); + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2); + rcar_gen3_thermal_write(tsc, REG_GEN3_CTSR, CTSR_PONM | CTSR_AOUT | CTSR_THBGR | CTSR_VMEN); @@ -214,6 +308,9 @@ static void r8a7796_thermal_init(struct rcar_gen3_thermal_tsc *tsc) usleep_range(1000, 2000); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQCTL, 0x3F); + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQMSK, 0); + rcar_gen3_thermal_write(tsc, REG_GEN3_IRQEN, IRQ_TEMPD1 | IRQ_TEMP2); + reg_val = rcar_gen3_thermal_read(tsc, REG_GEN3_THCTR); reg_val |= THCTR_THSST; rcar_gen3_thermal_write(tsc, REG_GEN3_THCTR, reg_val); @@ -252,7 +349,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct resource *res; struct thermal_zone_device *zone; - int ret, i; + int ret, irq, i; + char *irqname; const struct rcar_gen3_thermal_data *match_data = of_device_get_match_data(dev); @@ -269,8 +367,32 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + /* + * Request 2 (of the 3 possible) IRQs, the driver only needs to + * to trigger on the low and high trip points of the current + * temp window at this point. + */ + for (i = 0; i < 2; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s:ch%d", + dev_name(dev), i); + if (!irqname) + return -ENOMEM; + + ret = devm_request_threaded_irq(dev, irq, rcar_gen3_thermal_irq, + rcar_gen3_thermal_irq_thread, + IRQF_SHARED, irqname, priv); + if (ret) + return ret; + } + pm_runtime_enable(dev); pm_runtime_get_sync(dev); @@ -306,6 +428,12 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) goto error_unregister; } tsc->zone = zone; + + ret = of_thermal_get_ntrips(tsc->zone); + if (ret < 0) + goto error_unregister; + + dev_info(dev, "TSC%d: Loaded %d trip points\n", i, ret); } priv->num_tscs = i; @@ -315,6 +443,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) goto error_unregister; } + rcar_thermal_irq_set(priv, true); + return 0; error_unregister: From cc4d072b66298716484f5c78d782c64509f4b6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:55 +0200 Subject: [PATCH 09/25] thermal: rcar_gen3_thermal: store device match data in private structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device match data needs to be accessible outside the probe function, store it in the private data structure. Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index d37c7d8f8fcd..f259a995c66c 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -88,6 +88,7 @@ struct rcar_gen3_thermal_priv { struct rcar_gen3_thermal_tsc *tscs[TSC_MAX_NUM]; unsigned int num_tscs; spinlock_t lock; /* Protect interrupts on and off */ + const struct rcar_gen3_thermal_data *data; }; struct rcar_gen3_thermal_data { @@ -351,8 +352,6 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) struct thermal_zone_device *zone; int ret, irq, i; char *irqname; - const struct rcar_gen3_thermal_data *match_data = - of_device_get_match_data(dev); /* default values if FUSEs are missing */ /* TODO: Read values from hardware on supported platforms */ @@ -367,6 +366,8 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->data = of_device_get_match_data(dev); + spin_lock_init(&priv->lock); platform_set_drvdata(pdev, priv); @@ -417,7 +418,7 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) priv->tscs[i] = tsc; - match_data->thermal_init(tsc); + priv->data->thermal_init(tsc); rcar_gen3_thermal_calc_coefs(&tsc->coef, ptat, thcode[i]); zone = devm_thermal_zone_of_sensor_register(dev, i, tsc, From 75f78d6d9eb793d141affaa5a76f20ce1d6ae5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Wed, 29 Mar 2017 20:43:56 +0200 Subject: [PATCH 10/25] thermal: rcar_gen3_thermal: add suspend and resume support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To restore operation it's easiest to reinitialise all TSCs. In order to do this the current trip window needs to be stored in the TSC structure so that it can be restored upon resume. Signed-off-by: Niklas Söderlund Reviewed-by: Wolfram Sang Signed-off-by: Eduardo Valentin --- drivers/thermal/rcar_gen3_thermal.c | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index f259a995c66c..37fcefd06d9f 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -82,6 +82,8 @@ struct rcar_gen3_thermal_tsc { void __iomem *base; struct thermal_zone_device *zone; struct equation_coefs coef; + int low; + int high; }; struct rcar_gen3_thermal_priv { @@ -217,6 +219,9 @@ static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high) rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP2, rcar_gen3_thermal_mcelsius_to_temp(tsc, high)); + tsc->low = low; + tsc->high = high; + return 0; } @@ -454,9 +459,39 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) return ret; } +static int __maybe_unused rcar_gen3_thermal_suspend(struct device *dev) +{ + struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev); + + rcar_thermal_irq_set(priv, false); + + return 0; +} + +static int __maybe_unused rcar_gen3_thermal_resume(struct device *dev) +{ + struct rcar_gen3_thermal_priv *priv = dev_get_drvdata(dev); + unsigned int i; + + for (i = 0; i < priv->num_tscs; i++) { + struct rcar_gen3_thermal_tsc *tsc = priv->tscs[i]; + + priv->data->thermal_init(tsc); + rcar_gen3_thermal_set_trips(tsc, tsc->low, tsc->high); + } + + rcar_thermal_irq_set(priv, true); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(rcar_gen3_thermal_pm_ops, rcar_gen3_thermal_suspend, + rcar_gen3_thermal_resume); + static struct platform_driver rcar_gen3_thermal_driver = { .driver = { .name = "rcar_gen3_thermal", + .pm = &rcar_gen3_thermal_pm_ops, .of_match_table = rcar_gen3_thermal_dt_ids, }, .probe = rcar_gen3_thermal_probe, From 1e2ac9821de6a85d3e8358f238436708d1d46869 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Fri, 31 Mar 2017 20:03:03 +0000 Subject: [PATCH 11/25] dt-bindings: Add thermal zone to bcm2835-thermal example Add a thermal zone in order to make the example complete. Signed-off-by: Stefan Wahren Acked-by: Rob Herring Signed-off-by: Eduardo Valentin --- .../bindings/thermal/brcm,bcm2835-thermal.txt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt b/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt index 474531d2b2c5..da8c5b73ad10 100644 --- a/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt +++ b/Documentation/devicetree/bindings/thermal/brcm,bcm2835-thermal.txt @@ -3,15 +3,39 @@ Binding for Thermal Sensor driver for BCM2835 SoCs. Required parameters: ------------------- -compatible: should be one of: "brcm,bcm2835-thermal", - "brcm,bcm2836-thermal" or "brcm,bcm2837-thermal" -reg: Address range of the thermal registers. -clocks: Phandle of the clock used by the thermal sensor. +compatible: should be one of: "brcm,bcm2835-thermal", + "brcm,bcm2836-thermal" or "brcm,bcm2837-thermal" +reg: Address range of the thermal registers. +clocks: Phandle of the clock used by the thermal sensor. +#thermal-sensor-cells: should be 0 (see thermal.txt) Example: +thermal-zones { + cpu_thermal: cpu-thermal { + polling-delay-passive = <0>; + polling-delay = <1000>; + + thermal-sensors = <&thermal>; + + trips { + cpu-crit { + temperature = <80000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + coefficients = <(-538) 407000>; + + cooling-maps { + }; + }; +}; + thermal: thermal@7e212000 { compatible = "brcm,bcm2835-thermal"; reg = <0x7e212000 0x8>; clocks = <&clocks BCM2835_CLOCK_TSENS>; + #thermal-sensor-cells = <0>; }; From bcb7dd9ef206f7d646ed8dac6fe7772083714253 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Fri, 31 Mar 2017 20:03:06 +0000 Subject: [PATCH 12/25] thermal: bcm2835: add thermal driver for bcm2835 SoC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic thermal driver for bcm2835 SoC. This driver currently make sure that tsense HW block is set up correctly. Tested-by: Rafał Miłecki Signed-off-by: Martin Sperl Signed-off-by: Stefan Wahren Acked-by: Eric Anholt Acked-by: Eduardo Valentin Signed-off-by: Eduardo Valentin --- drivers/thermal/Kconfig | 8 + drivers/thermal/Makefile | 1 + drivers/thermal/bcm2835_thermal.c | 314 ++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 drivers/thermal/bcm2835_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 776b34396144..3bd24063375e 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -453,4 +453,12 @@ config ZX2967_THERMAL the primitive temperature sensor embedded in zx2967 SoCs. This sensor generates the real time die temperature. +config BCM2835_THERMAL + tristate "Thermal sensors on bcm2835 SoC" + depends on ARCH_BCM2835 || COMPILE_TEST + depends on HAS_IOMEM + depends on THERMAL_OF + help + Support for thermal sensors on Broadcom bcm2835 SoCs. + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 7adae2029355..f23cde05dac6 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o +obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o diff --git a/drivers/thermal/bcm2835_thermal.c b/drivers/thermal/bcm2835_thermal.c new file mode 100644 index 000000000000..0ecf80890c84 --- /dev/null +++ b/drivers/thermal/bcm2835_thermal.c @@ -0,0 +1,314 @@ +/* + * Driver for Broadcom BCM2835 SoC temperature sensor + * + * Copyright (C) 2016 Martin Sperl + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BCM2835_TS_TSENSCTL 0x00 +#define BCM2835_TS_TSENSSTAT 0x04 + +#define BCM2835_TS_TSENSCTL_PRWDW BIT(0) +#define BCM2835_TS_TSENSCTL_RSTB BIT(1) + +/* + * bandgap reference voltage in 6 mV increments + * 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV + */ +#define BCM2835_TS_TSENSCTL_CTRL_BITS 3 +#define BCM2835_TS_TSENSCTL_CTRL_SHIFT 2 +#define BCM2835_TS_TSENSCTL_CTRL_MASK \ + GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS + \ + BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \ + BCM2835_TS_TSENSCTL_CTRL_SHIFT) +#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT 1 +#define BCM2835_TS_TSENSCTL_EN_INT BIT(5) +#define BCM2835_TS_TSENSCTL_DIRECT BIT(6) +#define BCM2835_TS_TSENSCTL_CLR_INT BIT(7) +#define BCM2835_TS_TSENSCTL_THOLD_SHIFT 8 +#define BCM2835_TS_TSENSCTL_THOLD_BITS 10 +#define BCM2835_TS_TSENSCTL_THOLD_MASK \ + GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS + \ + BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \ + BCM2835_TS_TSENSCTL_THOLD_SHIFT) +/* + * time how long the block to be asserted in reset + * which based on a clock counter (TSENS clock assumed) + */ +#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT 18 +#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS 8 +#define BCM2835_TS_TSENSCTL_REGULEN BIT(26) + +#define BCM2835_TS_TSENSSTAT_DATA_BITS 10 +#define BCM2835_TS_TSENSSTAT_DATA_SHIFT 0 +#define BCM2835_TS_TSENSSTAT_DATA_MASK \ + GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS + \ + BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \ + BCM2835_TS_TSENSSTAT_DATA_SHIFT) +#define BCM2835_TS_TSENSSTAT_VALID BIT(10) +#define BCM2835_TS_TSENSSTAT_INTERRUPT BIT(11) + +struct bcm2835_thermal_data { + struct thermal_zone_device *tz; + void __iomem *regs; + struct clk *clk; + struct dentry *debugfsdir; +}; + +static int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope) +{ + return offset + slope * adc; +} + +static int bcm2835_thermal_temp2adc(int temp, int offset, int slope) +{ + temp -= offset; + temp /= slope; + + if (temp < 0) + temp = 0; + if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS)) + temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1; + + return temp; +} + +static int bcm2835_thermal_get_temp(void *d, int *temp) +{ + struct bcm2835_thermal_data *data = d; + u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT); + + if (!(val & BCM2835_TS_TSENSSTAT_VALID)) + return -EIO; + + val &= BCM2835_TS_TSENSSTAT_DATA_MASK; + + *temp = bcm2835_thermal_adc2temp( + val, + thermal_zone_get_offset(data->tz), + thermal_zone_get_slope(data->tz)); + + return 0; +} + +static const struct debugfs_reg32 bcm2835_thermal_regs[] = { + { + .name = "ctl", + .offset = 0 + }, + { + .name = "stat", + .offset = 4 + } +}; + +static void bcm2835_thermal_debugfs(struct platform_device *pdev) +{ + struct thermal_zone_device *tz = platform_get_drvdata(pdev); + struct bcm2835_thermal_data *data = tz->devdata; + struct debugfs_regset32 *regset; + + data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL); + if (!data->debugfsdir) + return; + + regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL); + if (!regset) + return; + + regset->regs = bcm2835_thermal_regs; + regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs); + regset->base = data->regs; + + debugfs_create_regset32("regset", 0444, data->debugfsdir, regset); +} + +static struct thermal_zone_of_device_ops bcm2835_thermal_ops = { + .get_temp = bcm2835_thermal_get_temp, +}; + +/* + * Note: as per Raspberry Foundation FAQ + * (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature) + * the recommended temperature range for the SoC -40C to +85C + * so the trip limit is set to 80C. + * this applies to all the BCM283X SoC + */ + +static const struct of_device_id bcm2835_thermal_of_match_table[] = { + { + .compatible = "brcm,bcm2835-thermal", + }, + { + .compatible = "brcm,bcm2836-thermal", + }, + { + .compatible = "brcm,bcm2837-thermal", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table); + +static int bcm2835_thermal_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct thermal_zone_device *tz; + struct bcm2835_thermal_data *data; + struct resource *res; + int err = 0; + u32 val; + unsigned long rate; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + match = of_match_device(bcm2835_thermal_of_match_table, + &pdev->dev); + if (!match) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->regs)) { + err = PTR_ERR(data->regs); + dev_err(&pdev->dev, "Could not get registers: %d\n", err); + return err; + } + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + err = PTR_ERR(data->clk); + if (err != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get clk: %d\n", err); + return err; + } + + err = clk_prepare_enable(data->clk); + if (err) + return err; + + rate = clk_get_rate(data->clk); + if ((rate < 1920000) || (rate > 5000000)) + dev_warn(&pdev->dev, + "Clock %pCn running at %pCr Hz is outside of the recommended range: 1.92 to 5MHz\n", + data->clk, data->clk); + + /* register of thermal sensor and get info from DT */ + tz = thermal_zone_of_sensor_register(&pdev->dev, 0, data, + &bcm2835_thermal_ops); + if (IS_ERR(tz)) { + err = PTR_ERR(tz); + dev_err(&pdev->dev, + "Failed to register the thermal device: %d\n", + err); + goto err_clk; + } + + /* + * right now the FW does set up the HW-block, so we are not + * touching the configuration registers. + * But if the HW is not enabled, then set it up + * using "sane" values used by the firmware right now. + */ + val = readl(data->regs + BCM2835_TS_TSENSCTL); + if (!(val & BCM2835_TS_TSENSCTL_RSTB)) { + int trip_temp, offset, slope; + + slope = thermal_zone_get_slope(tz); + offset = thermal_zone_get_offset(tz); + /* + * For now we deal only with critical, otherwise + * would need to iterate + */ + err = tz->ops->get_trip_temp(tz, 0, &trip_temp); + if (err < 0) { + err = PTR_ERR(tz); + dev_err(&pdev->dev, + "Not able to read trip_temp: %d\n", + err); + goto err_tz; + } + + /* set bandgap reference voltage and enable voltage regulator */ + val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT << + BCM2835_TS_TSENSCTL_CTRL_SHIFT) | + BCM2835_TS_TSENSCTL_REGULEN; + + /* use the recommended reset duration */ + val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT); + + /* trip_adc value from info */ + val |= bcm2835_thermal_temp2adc(trip_temp, + offset, + slope) + << BCM2835_TS_TSENSCTL_THOLD_SHIFT; + + /* write the value back to the register as 2 steps */ + writel(val, data->regs + BCM2835_TS_TSENSCTL); + val |= BCM2835_TS_TSENSCTL_RSTB; + writel(val, data->regs + BCM2835_TS_TSENSCTL); + } + + data->tz = tz; + + platform_set_drvdata(pdev, tz); + + bcm2835_thermal_debugfs(pdev); + + return 0; +err_tz: + thermal_zone_of_sensor_unregister(&pdev->dev, tz); +err_clk: + clk_disable_unprepare(data->clk); + + return err; +} + +static int bcm2835_thermal_remove(struct platform_device *pdev) +{ + struct thermal_zone_device *tz = platform_get_drvdata(pdev); + struct bcm2835_thermal_data *data = tz->devdata; + + debugfs_remove_recursive(data->debugfsdir); + thermal_zone_of_sensor_unregister(&pdev->dev, tz); + clk_disable_unprepare(data->clk); + + return 0; +} + +static struct platform_driver bcm2835_thermal_driver = { + .probe = bcm2835_thermal_probe, + .remove = bcm2835_thermal_remove, + .driver = { + .name = "bcm2835_thermal", + .of_match_table = bcm2835_thermal_of_match_table, + }, +}; +module_platform_driver(bcm2835_thermal_driver); + +MODULE_AUTHOR("Martin Sperl"); +MODULE_DESCRIPTION("Thermal driver for bcm2835 chip"); +MODULE_LICENSE("GPL"); From ee7cdbecb128d8b023c5439004cdea8baa2d0fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Mon, 3 Apr 2017 17:48:28 +0200 Subject: [PATCH 13/25] dt-bindings: thermal: add support for Broadcom's Northstar thermal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit documents binding for thermal used in Northstar family SoCs. There isn't any known Northstar device with active cooling system so DT example has empty cooling-maps node. There is also no support for CPU frequency throttling so I put only a critical trip in the example. Signed-off-by: Rafał Miłecki Signed-off-by: Eduardo Valentin --- .../bindings/thermal/brcm,ns-thermal | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/brcm,ns-thermal diff --git a/Documentation/devicetree/bindings/thermal/brcm,ns-thermal b/Documentation/devicetree/bindings/thermal/brcm,ns-thermal new file mode 100644 index 000000000000..68e047170039 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/brcm,ns-thermal @@ -0,0 +1,37 @@ +* Broadcom Northstar Thermal + +This binding describes thermal sensor that is part of Northstar's DMU (Device +Management Unit). + +Required properties: +- compatible : Must be "brcm,ns-thermal" +- reg : iomem address range of PVTMON registers +- #thermal-sensor-cells : Should be <0> + +Example: + +thermal: thermal@1800c2c0 { + compatible = "brcm,ns-thermal"; + reg = <0x1800c2c0 0x10>; + #thermal-sensor-cells = <0>; +}; + +thermal-zones { + cpu_thermal: cpu-thermal { + polling-delay-passive = <0>; + polling-delay = <1000>; + coefficients = <(-556) 418000>; + thermal-sensors = <&thermal>; + + trips { + cpu-crit { + temperature = <125000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + }; + }; +}; From a94cb7eeecc4104a6874339f90c5d0647359c102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Mon, 3 Apr 2017 17:48:29 +0200 Subject: [PATCH 14/25] thermal: broadcom: add Northstar thermal driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Northstar is a SoC family commonly used in home routers. This commit adds a driver for checking CPU temperature. As Northstar Plus seems to also have this IP block this new symbol gets ARCH_BCM_IPROC dependency. Signed-off-by: Rafał Miłecki Signed-off-by: Jon Mason Signed-off-by: Eduardo Valentin --- drivers/thermal/Kconfig | 5 ++ drivers/thermal/Makefile | 1 + drivers/thermal/broadcom/Kconfig | 8 ++ drivers/thermal/broadcom/Makefile | 1 + drivers/thermal/broadcom/ns-thermal.c | 105 ++++++++++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 drivers/thermal/broadcom/Kconfig create mode 100644 drivers/thermal/broadcom/Makefile create mode 100644 drivers/thermal/broadcom/ns-thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 3bd24063375e..ac7301703d03 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -392,6 +392,11 @@ config MTK_THERMAL Enable this option if you want to have support for thermal management controller present in Mediatek SoCs +menu "Broadcom thermal drivers" +depends on ARCH_BCM || COMPILE_TEST +source "drivers/thermal/broadcom/Kconfig" +endmenu + menu "Texas Instruments thermal drivers" depends on ARCH_HAS_BANDGAP || COMPILE_TEST depends on HAS_IOMEM diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index f23cde05dac6..6b7706b9f27c 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -27,6 +27,7 @@ thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o # platform thermal drivers +obj-y += broadcom/ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig new file mode 100644 index 000000000000..f0dea8a8e002 --- /dev/null +++ b/drivers/thermal/broadcom/Kconfig @@ -0,0 +1,8 @@ +config BCM_NS_THERMAL + tristate "Northstar thermal driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + help + Northstar is a family of SoCs that includes e.g. BCM4708, BCM47081, + BCM4709 and BCM47094. It contains DMU (Device Management Unit) block + with a thermal sensor that allows checking CPU temperature. This + driver provides support for it. diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile new file mode 100644 index 000000000000..059df9a0ed69 --- /dev/null +++ b/drivers/thermal/broadcom/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c new file mode 100644 index 000000000000..80ad32c6b2df --- /dev/null +++ b/drivers/thermal/broadcom/ns-thermal.c @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 Rafał Miłecki + * + * 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 PVTMON_CONTROL0 0x00 +#define PVTMON_CONTROL0_SEL_MASK 0x0000000e +#define PVTMON_CONTROL0_SEL_TEMP_MONITOR 0x00000000 +#define PVTMON_CONTROL0_SEL_TEST_MODE 0x0000000e +#define PVTMON_STATUS 0x08 + +struct ns_thermal { + struct thermal_zone_device *tz; + void __iomem *pvtmon; +}; + +static int ns_thermal_get_temp(void *data, int *temp) +{ + struct ns_thermal *ns_thermal = data; + int offset = thermal_zone_get_offset(ns_thermal->tz); + int slope = thermal_zone_get_slope(ns_thermal->tz); + u32 val; + + val = readl(ns_thermal->pvtmon + PVTMON_CONTROL0); + if ((val & PVTMON_CONTROL0_SEL_MASK) != PVTMON_CONTROL0_SEL_TEMP_MONITOR) { + /* Clear current mode selection */ + val &= ~PVTMON_CONTROL0_SEL_MASK; + + /* Set temp monitor mode (it's the default actually) */ + val |= PVTMON_CONTROL0_SEL_TEMP_MONITOR; + + writel(val, ns_thermal->pvtmon + PVTMON_CONTROL0); + } + + val = readl(ns_thermal->pvtmon + PVTMON_STATUS); + *temp = slope * val + offset; + + return 0; +} + +static const struct thermal_zone_of_device_ops ns_thermal_ops = { + .get_temp = ns_thermal_get_temp, +}; + +static int ns_thermal_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ns_thermal *ns_thermal; + + ns_thermal = devm_kzalloc(dev, sizeof(*ns_thermal), GFP_KERNEL); + if (!ns_thermal) + return -ENOMEM; + + ns_thermal->pvtmon = of_iomap(dev_of_node(dev), 0); + if (WARN_ON(!ns_thermal->pvtmon)) + return -ENOENT; + + ns_thermal->tz = devm_thermal_zone_of_sensor_register(dev, 0, + ns_thermal, + &ns_thermal_ops); + if (IS_ERR(ns_thermal->tz)) { + iounmap(ns_thermal->pvtmon); + return PTR_ERR(ns_thermal->tz); + } + + platform_set_drvdata(pdev, ns_thermal); + + return 0; +} + +static int ns_thermal_remove(struct platform_device *pdev) +{ + struct ns_thermal *ns_thermal = platform_get_drvdata(pdev); + + iounmap(ns_thermal->pvtmon); + + return 0; +} + +static const struct of_device_id ns_thermal_of_match[] = { + { .compatible = "brcm,ns-thermal", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ns_thermal_of_match); + +static struct platform_driver ns_thermal_driver = { + .probe = ns_thermal_probe, + .remove = ns_thermal_remove, + .driver = { + .name = "ns-thermal", + .of_match_table = ns_thermal_of_match, + }, +}; +module_platform_driver(ns_thermal_driver); + +MODULE_DESCRIPTION("Northstar thermal driver"); +MODULE_LICENSE("GPL v2"); From 28c1b08d2a156ef16e912a4c618a529e586e5dbc Mon Sep 17 00:00:00 2001 From: Steve Twiss Date: Tue, 28 Mar 2017 15:43:31 +0100 Subject: [PATCH 15/25] Documentation: devicetree: thermal: da9062/61 TJUNC temperature binding Device tree binding information for DA9062 and DA9061 thermal junction temperature monitor. Binding descriptions for the DA9061 and DA9062 thermal TJUNC supervisor device driver, using a single THERMAL_TRIP_HOT trip-wire and allowing for a configurable polling period for over-temperature polling. This patch also adds two examples, one for DA9062 and one for DA9061. The DA9061 example uses a fall-back compatible string for the DA9062. Acked-by: Rob Herring Signed-off-by: Steve Twiss Signed-off-by: Eduardo Valentin --- .../bindings/thermal/da9062-thermal.txt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/da9062-thermal.txt diff --git a/Documentation/devicetree/bindings/thermal/da9062-thermal.txt b/Documentation/devicetree/bindings/thermal/da9062-thermal.txt new file mode 100644 index 000000000000..e241bb5a5584 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/da9062-thermal.txt @@ -0,0 +1,36 @@ +* Dialog DA9062/61 TJUNC Thermal Module + +This module is part of the DA9061/DA9062. For more details about entire +DA9062 and DA9061 chips see Documentation/devicetree/bindings/mfd/da9062.txt + +Junction temperature thermal module uses an interrupt signal to identify +high THERMAL_TRIP_HOT temperatures for the PMIC device. + +Required properties: + +- compatible: should be one of the following valid compatible string lines: + "dlg,da9061-thermal", "dlg,da9062-thermal" + "dlg,da9062-thermal" + +Optional properties: + +- polling-delay-passive : Specify the polling period, measured in + milliseconds, between thermal zone device update checks. + +Example: DA9062 + + pmic0: da9062@58 { + thermal { + compatible = "dlg,da9062-thermal"; + polling-delay-passive = <3000>; + }; + }; + +Example: DA9061 using a fall-back compatible for the DA9062 onkey driver + + pmic0: da9061@58 { + thermal { + compatible = "dlg,da9061-thermal", "dlg,da9062-thermal"; + polling-delay-passive = <3000>; + }; + }; From 608567aac3206ae886c79688fbb8a62c473b55ef Mon Sep 17 00:00:00 2001 From: Steve Twiss Date: Tue, 28 Mar 2017 15:43:33 +0100 Subject: [PATCH 16/25] thermal: da9062/61: Thermal junction temperature monitoring driver Add junction temperature monitoring supervisor device driver, compatible with the DA9062 and DA9061 PMICs. A MODULE_DEVICE_TABLE() macro is added. If the PMIC's internal junction temperature rises above T_WARN (125 degC) an interrupt is issued. This T_WARN level is defined as the THERMAL_TRIP_HOT trip-wire inside the device driver. The thermal triggering mechanism is interrupt based and happens when the temperature rises above a given threshold level. The component cannot return an exact temperature, it only has knowledge if the temperature is above or below a given threshold value. A status bit must be polled to detect when the temperature falls below that threshold level again. A kernel work queue is configured to repeatedly poll and detect when the temperature falls below this trip-wire, between 1 and 10 second intervals (defaulting at 3 seconds). This scheme is provided as an example. It would be expected that any final implementation will also include a notify() function and any of these settings could be altered to match the application where appropriate. When over-temperature is reached, the interrupt from the DA9061/2 will be repeatedly triggered. The IRQ is therefore disabled when the first over-temperature event happens and the status bit is polled using a work-queue until it becomes false. This strategy is designed to allow the periodic transmission of uevents (HOT trip point) as the first level of temperature supervision method. It is intended for non-invasive temperature control, where the necessary measures for cooling the system down are left to the host software. Once the temperature falls again, the IRQ is re-enabled so a new critical over-temperature event can be detected. Reviewed-by: Lukasz Luba Signed-off-by: Steve Twiss Signed-off-by: Eduardo Valentin --- drivers/thermal/Kconfig | 10 + drivers/thermal/Makefile | 1 + drivers/thermal/da9062-thermal.c | 315 +++++++++++++++++++++++++++++++ 3 files changed, 326 insertions(+) create mode 100644 drivers/thermal/da9062-thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index ac7301703d03..6699843918fa 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -303,6 +303,16 @@ config DB8500_CPUFREQ_COOLING bound cpufreq cooling device turns active to set CPU frequency low to cool down the CPU. +config DA9062_THERMAL + tristate "DA9062/DA9061 Dialog Semiconductor thermal driver" + depends on MFD_DA9062 || COMPILE_TEST + depends on OF + help + Enable this for the Dialog Semiconductor thermal sensor driver. + This will report PMIC junction over-temperature for one thermal trip + zone. + Compatible with the DA9062 and DA9061 PMICs. + config INTEL_POWERCLAMP tristate "Intel PowerClamp idle injection driver" depends on THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 6b7706b9f27c..a1e9b8b4e897 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_MAX77620_THERMAL) += max77620_thermal.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o +obj-$(CONFIG_DA9062_THERMAL) += da9062-thermal.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o diff --git a/drivers/thermal/da9062-thermal.c b/drivers/thermal/da9062-thermal.c new file mode 100644 index 000000000000..dd8dd947b7f0 --- /dev/null +++ b/drivers/thermal/da9062-thermal.c @@ -0,0 +1,315 @@ +/* + * Thermal device driver for DA9062 and DA9061 + * Copyright (C) 2017 Dialog Semiconductor + * + * 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; either version 2 + * of the License, or (at your option) any later version. + * + * 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. + */ + +/* When over-temperature is reached, an interrupt from the device will be + * triggered. Following this event the interrupt will be disabled and + * periodic transmission of uevents (HOT trip point) should define the + * first level of temperature supervision. It is expected that any final + * implementation of the thermal driver will include a .notify() function + * to implement these uevents to userspace. + * + * These uevents are intended to indicate non-invasive temperature control + * of the system, where the necessary measures for cooling are the + * responsibility of the host software. Once the temperature falls again, + * the IRQ is re-enabled so the start of a new over-temperature event can + * be detected without constant software monitoring. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Minimum, maximum and default polling millisecond periods are provided + * here as an example. It is expected that any final implementation to also + * include a modification of these settings to match the required + * application. + */ +#define DA9062_DEFAULT_POLLING_MS_PERIOD 3000 +#define DA9062_MAX_POLLING_MS_PERIOD 10000 +#define DA9062_MIN_POLLING_MS_PERIOD 1000 + +#define DA9062_MILLI_CELSIUS(t) ((t) * 1000) + +struct da9062_thermal_config { + const char *name; +}; + +struct da9062_thermal { + struct da9062 *hw; + struct delayed_work work; + struct thermal_zone_device *zone; + enum thermal_device_mode mode; + struct mutex lock; /* protection for da9062_thermal temperature */ + int temperature; + int irq; + const struct da9062_thermal_config *config; + struct device *dev; +}; + +static void da9062_thermal_poll_on(struct work_struct *work) +{ + struct da9062_thermal *thermal = container_of(work, + struct da9062_thermal, + work.work); + unsigned long delay; + unsigned int val; + int ret; + + /* clear E_TEMP */ + ret = regmap_write(thermal->hw->regmap, + DA9062AA_EVENT_B, + DA9062AA_E_TEMP_MASK); + if (ret < 0) { + dev_err(thermal->dev, + "Cannot clear the TJUNC temperature status\n"); + goto err_enable_irq; + } + + /* Now read E_TEMP again: it is acting like a status bit. + * If over-temperature, then this status will be true. + * If not over-temperature, this status will be false. + */ + ret = regmap_read(thermal->hw->regmap, + DA9062AA_EVENT_B, + &val); + if (ret < 0) { + dev_err(thermal->dev, + "Cannot check the TJUNC temperature status\n"); + goto err_enable_irq; + } + + if (val & DA9062AA_E_TEMP_MASK) { + mutex_lock(&thermal->lock); + thermal->temperature = DA9062_MILLI_CELSIUS(125); + mutex_unlock(&thermal->lock); + thermal_zone_device_update(thermal->zone, + THERMAL_EVENT_UNSPECIFIED); + + delay = msecs_to_jiffies(thermal->zone->passive_delay); + schedule_delayed_work(&thermal->work, delay); + return; + } + + mutex_lock(&thermal->lock); + thermal->temperature = DA9062_MILLI_CELSIUS(0); + mutex_unlock(&thermal->lock); + thermal_zone_device_update(thermal->zone, + THERMAL_EVENT_UNSPECIFIED); + +err_enable_irq: + enable_irq(thermal->irq); +} + +static irqreturn_t da9062_thermal_irq_handler(int irq, void *data) +{ + struct da9062_thermal *thermal = data; + + disable_irq_nosync(thermal->irq); + schedule_delayed_work(&thermal->work, 0); + + return IRQ_HANDLED; +} + +static int da9062_thermal_get_mode(struct thermal_zone_device *z, + enum thermal_device_mode *mode) +{ + struct da9062_thermal *thermal = z->devdata; + *mode = thermal->mode; + return 0; +} + +static int da9062_thermal_get_trip_type(struct thermal_zone_device *z, + int trip, + enum thermal_trip_type *type) +{ + struct da9062_thermal *thermal = z->devdata; + + switch (trip) { + case 0: + *type = THERMAL_TRIP_HOT; + break; + default: + dev_err(thermal->dev, + "Driver does not support more than 1 trip-wire\n"); + return -EINVAL; + } + + return 0; +} + +static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z, + int trip, + int *temp) +{ + struct da9062_thermal *thermal = z->devdata; + + switch (trip) { + case 0: + *temp = DA9062_MILLI_CELSIUS(125); + break; + default: + dev_err(thermal->dev, + "Driver does not support more than 1 trip-wire\n"); + return -EINVAL; + } + + return 0; +} + +static int da9062_thermal_get_temp(struct thermal_zone_device *z, + int *temp) +{ + struct da9062_thermal *thermal = z->devdata; + + mutex_lock(&thermal->lock); + *temp = thermal->temperature; + mutex_unlock(&thermal->lock); + + return 0; +} + +static struct thermal_zone_device_ops da9062_thermal_ops = { + .get_temp = da9062_thermal_get_temp, + .get_mode = da9062_thermal_get_mode, + .get_trip_type = da9062_thermal_get_trip_type, + .get_trip_temp = da9062_thermal_get_trip_temp, +}; + +static const struct da9062_thermal_config da9062_config = { + .name = "da9062-thermal", +}; + +static const struct of_device_id da9062_compatible_reg_id_table[] = { + { .compatible = "dlg,da9062-thermal", .data = &da9062_config }, + { }, +}; + +MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table); + +static int da9062_thermal_probe(struct platform_device *pdev) +{ + struct da9062 *chip = dev_get_drvdata(pdev->dev.parent); + struct da9062_thermal *thermal; + unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; + const struct of_device_id *match; + int ret = 0; + + match = of_match_node(da9062_compatible_reg_id_table, + pdev->dev.of_node); + if (!match) + return -ENXIO; + + if (pdev->dev.of_node) { + if (!of_property_read_u32(pdev->dev.of_node, + "polling-delay-passive", + &pp_tmp)) { + if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD || + pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) { + dev_warn(&pdev->dev, + "Out-of-range polling period %d ms\n", + pp_tmp); + pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; + } + } + } + + thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal), + GFP_KERNEL); + if (!thermal) { + ret = -ENOMEM; + goto err; + } + + thermal->config = match->data; + thermal->hw = chip; + thermal->mode = THERMAL_DEVICE_ENABLED; + thermal->dev = &pdev->dev; + + INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on); + mutex_init(&thermal->lock); + + thermal->zone = thermal_zone_device_register(thermal->config->name, + 1, 0, thermal, + &da9062_thermal_ops, NULL, pp_tmp, + 0); + if (IS_ERR(thermal->zone)) { + dev_err(&pdev->dev, "Cannot register thermal zone device\n"); + ret = PTR_ERR(thermal->zone); + goto err; + } + + dev_dbg(&pdev->dev, + "TJUNC temperature polling period set at %d ms\n", + thermal->zone->passive_delay); + + ret = platform_get_irq_byname(pdev, "THERMAL"); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get platform IRQ.\n"); + goto err_zone; + } + thermal->irq = ret; + + ret = request_threaded_irq(thermal->irq, NULL, + da9062_thermal_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "THERMAL", thermal); + if (ret) { + dev_err(&pdev->dev, + "Failed to request thermal device IRQ.\n"); + goto err_zone; + } + + platform_set_drvdata(pdev, thermal); + return 0; + +err_zone: + thermal_zone_device_unregister(thermal->zone); +err: + return ret; +} + +static int da9062_thermal_remove(struct platform_device *pdev) +{ + struct da9062_thermal *thermal = platform_get_drvdata(pdev); + + free_irq(thermal->irq, thermal); + cancel_delayed_work_sync(&thermal->work); + thermal_zone_device_unregister(thermal->zone); + return 0; +} + +static struct platform_driver da9062_thermal_driver = { + .probe = da9062_thermal_probe, + .remove = da9062_thermal_remove, + .driver = { + .name = "da9062-thermal", + .of_match_table = da9062_compatible_reg_id_table, + }, +}; + +module_platform_driver(da9062_thermal_driver); + +MODULE_AUTHOR("Steve Twiss"); +MODULE_DESCRIPTION("Thermal TJUNC device driver for Dialog DA9062 and DA9061"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9062-thermal"); From cb9b323b5395d0d0cea2922d4f8ad588cf064f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Mon, 17 Apr 2017 23:02:01 +0200 Subject: [PATCH 17/25] thermal: broadcom: ns: specify myself as MODULE_AUTHOR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just in case someone uses modinfo to find (blame) me. Signed-off-by: Rafał Miłecki Signed-off-by: Eduardo Valentin --- drivers/thermal/broadcom/ns-thermal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/broadcom/ns-thermal.c b/drivers/thermal/broadcom/ns-thermal.c index 80ad32c6b2df..322e741a2463 100644 --- a/drivers/thermal/broadcom/ns-thermal.c +++ b/drivers/thermal/broadcom/ns-thermal.c @@ -101,5 +101,6 @@ static struct platform_driver ns_thermal_driver = { }; module_platform_driver(ns_thermal_driver); +MODULE_AUTHOR("Rafał Miłecki "); MODULE_DESCRIPTION("Northstar thermal driver"); MODULE_LICENSE("GPL v2"); From 6892cf07e7337776c0b006db3b96a96bf071acb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mi=C5=82ecki?= Date: Mon, 17 Apr 2017 23:04:16 +0200 Subject: [PATCH 18/25] thermal: bcm2835: move to the broadcom subdirectory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already have 2 Broadcom drivers and at least 1 more is coming. This made us create broadcom subdirectory where bcm2835 should be moves now. Acked-by: Florian Fainelli Signed-off-by: Rafał Miłecki Signed-off-by: Eduardo Valentin --- drivers/thermal/Kconfig | 8 -------- drivers/thermal/Makefile | 1 - drivers/thermal/broadcom/Kconfig | 8 ++++++++ drivers/thermal/broadcom/Makefile | 1 + drivers/thermal/{ => broadcom}/bcm2835_thermal.c | 0 5 files changed, 9 insertions(+), 9 deletions(-) rename drivers/thermal/{ => broadcom}/bcm2835_thermal.c (100%) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 6699843918fa..f786ae433032 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -468,12 +468,4 @@ config ZX2967_THERMAL the primitive temperature sensor embedded in zx2967 SoCs. This sensor generates the real time die temperature. -config BCM2835_THERMAL - tristate "Thermal sensors on bcm2835 SoC" - depends on ARCH_BCM2835 || COMPILE_TEST - depends on HAS_IOMEM - depends on THERMAL_OF - help - Support for thermal sensors on Broadcom bcm2835 SoCs. - endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index a1e9b8b4e897..e6834061da28 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -60,4 +60,3 @@ obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o obj-$(CONFIG_MTK_THERMAL) += mtk_thermal.o obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o -obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o diff --git a/drivers/thermal/broadcom/Kconfig b/drivers/thermal/broadcom/Kconfig index f0dea8a8e002..ab08af4654ef 100644 --- a/drivers/thermal/broadcom/Kconfig +++ b/drivers/thermal/broadcom/Kconfig @@ -1,3 +1,11 @@ +config BCM2835_THERMAL + tristate "Thermal sensors on bcm2835 SoC" + depends on ARCH_BCM2835 || COMPILE_TEST + depends on HAS_IOMEM + depends on THERMAL_OF + help + Support for thermal sensors on Broadcom bcm2835 SoCs. + config BCM_NS_THERMAL tristate "Northstar thermal driver" depends on ARCH_BCM_IPROC || COMPILE_TEST diff --git a/drivers/thermal/broadcom/Makefile b/drivers/thermal/broadcom/Makefile index 059df9a0ed69..c6f62e4fd0ee 100644 --- a/drivers/thermal/broadcom/Makefile +++ b/drivers/thermal/broadcom/Makefile @@ -1 +1,2 @@ +obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o diff --git a/drivers/thermal/bcm2835_thermal.c b/drivers/thermal/broadcom/bcm2835_thermal.c similarity index 100% rename from drivers/thermal/bcm2835_thermal.c rename to drivers/thermal/broadcom/bcm2835_thermal.c From 05d7839aa290901429d8edcd8f7974c9df2bcaa5 Mon Sep 17 00:00:00 2001 From: Dawei Chien Date: Tue, 21 Feb 2017 20:26:52 +0800 Subject: [PATCH 19/25] thermal: mt8173: minor mtk_thermal.c cleanups If thermal bank with 4 sensors, thermal driver should read TEMP_MSR3. However, currently thermal driver would not read TEMP_MSR3 since mt8173 thermal driver only use 3 sensors on each thermal bank at the same time, so this patch would not effect temperature. Only if mt mt8173 thermal driver use 4 sensors on any thermal bank, would read third sensor two times, and lose fourth sensor of vale. cc: stable@vger.kernel.org Fixes: b7cf0053738c ("thermal: Add Mediatek thermal driver for mt2701.") Reviewed-by: Matthias Brugger Signed-off-by: Dawei Chien Signed-off-by: Eduardo Valentin --- drivers/thermal/mtk_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c index 1aff7fde54b1..7737f14846f9 100644 --- a/drivers/thermal/mtk_thermal.c +++ b/drivers/thermal/mtk_thermal.c @@ -191,7 +191,7 @@ static const int mt8173_bank_data[MT8173_NUM_ZONES][3] = { }; static const int mt8173_msr[MT8173_NUM_SENSORS_PER_ZONE] = { - TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR2 + TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR3 }; static const int mt8173_adcpnp[MT8173_NUM_SENSORS_PER_ZONE] = { From e34cab4cd1f98b4daed5bc98fe727e63f8dbf4e4 Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Thu, 4 May 2017 12:34:31 +0100 Subject: [PATCH 20/25] thermal: devfreq_cooling: refactor code and add get_voltage function Move the code which gets the voltage for a given frequency. This code will be resused in few places. Acked-by: Javi Merino Signed-off-by: Lukasz Luba --- drivers/thermal/devfreq_cooling.c | 45 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 4bf4ad58cffd..af9d32837a3a 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -164,27 +164,12 @@ freq_get_state(struct devfreq_cooling_device *dfc, unsigned long freq) return THERMAL_CSTATE_INVALID; } -/** - * get_static_power() - calculate the static power - * @dfc: Pointer to devfreq cooling device - * @freq: Frequency in Hz - * - * Calculate the static power in milliwatts using the supplied - * get_static_power(). The current voltage is calculated using the - * OPP library. If no get_static_power() was supplied, assume the - * static power is negligible. - */ -static unsigned long -get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq) +static unsigned long get_voltage(struct devfreq *df, unsigned long freq) { - struct devfreq *df = dfc->devfreq; struct device *dev = df->dev.parent; unsigned long voltage; struct dev_pm_opp *opp; - if (!dfc->power_ops->get_static_power) - return 0; - opp = dev_pm_opp_find_freq_exact(dev, freq, true); if (PTR_ERR(opp) == -ERANGE) opp = dev_pm_opp_find_freq_exact(dev, freq, false); @@ -202,9 +187,35 @@ get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq) dev_err_ratelimited(dev, "Failed to get voltage for frequency %lu\n", freq); - return 0; } + return voltage; +} + +/** + * get_static_power() - calculate the static power + * @dfc: Pointer to devfreq cooling device + * @freq: Frequency in Hz + * + * Calculate the static power in milliwatts using the supplied + * get_static_power(). The current voltage is calculated using the + * OPP library. If no get_static_power() was supplied, assume the + * static power is negligible. + */ +static unsigned long +get_static_power(struct devfreq_cooling_device *dfc, unsigned long freq) +{ + struct devfreq *df = dfc->devfreq; + unsigned long voltage; + + if (!dfc->power_ops->get_static_power) + return 0; + + voltage = get_voltage(df, freq); + + if (voltage == 0) + return 0; + return dfc->power_ops->get_static_power(df, voltage); } From 2be83da85a64773efaa407639de81bd1377f880e Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Thu, 4 May 2017 12:34:32 +0100 Subject: [PATCH 21/25] thermal: devfreq_cooling: add new interface for direct power read This patch introduces a new interface for device drivers connected to devfreq_cooling in the thermal framework: get_real_power(). Some devices have more sophisticated methods (like power counters) to approximate the actual power that they use. In the previous implementation we had a pre-calculated power table which was then scaled by 'utilization' ('busy_time' and 'total_time' taken from devfreq 'last_status'). With this new interface the driver can provide more precise data regarding actual power to the thermal governor every time the power budget is calculated. We then use this value and calculate the real resource utilization scaling factor. Reviewed-by: Chris Diamand Acked-by: Javi Merino Signed-off-by: Lukasz Luba --- drivers/thermal/devfreq_cooling.c | 105 +++++++++++++++++++++++------- include/linux/devfreq_cooling.h | 19 ++++++ 2 files changed, 101 insertions(+), 23 deletions(-) diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index af9d32837a3a..26c31571a12c 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -28,6 +28,8 @@ #include +#define SCALE_ERROR_MITIGATION 100 + static DEFINE_IDA(devfreq_ida); /** @@ -45,6 +47,12 @@ static DEFINE_IDA(devfreq_ida); * @freq_table_size: Size of the @freq_table and @power_table * @power_ops: Pointer to devfreq_cooling_power, used to generate the * @power_table. + * @res_util: Resource utilization scaling factor for the power. + * It is multiplied by 100 to minimize the error. It is used + * for estimation of the power budget instead of using + * 'utilization' (which is 'busy_time / 'total_time'). + * The 'res_util' range is from 100 to (power_table[state] * 100) + * for the corresponding 'state'. */ struct devfreq_cooling_device { int id; @@ -55,6 +63,8 @@ struct devfreq_cooling_device { u32 *freq_table; size_t freq_table_size; struct devfreq_cooling_power *power_ops; + u32 res_util; + int capped_state; }; /** @@ -250,6 +260,16 @@ get_dynamic_power(struct devfreq_cooling_device *dfc, unsigned long freq, return power; } + +static inline unsigned long get_total_power(struct devfreq_cooling_device *dfc, + unsigned long freq, + unsigned long voltage) +{ + return get_static_power(dfc, freq) + get_dynamic_power(dfc, freq, + voltage); +} + + static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev, struct thermal_zone_device *tz, u32 *power) @@ -259,27 +279,55 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd struct devfreq_dev_status *status = &df->last_status; unsigned long state; unsigned long freq = status->current_frequency; - u32 dyn_power, static_power; + unsigned long voltage; + u32 dyn_power = 0; + u32 static_power = 0; + int res; - /* Get dynamic power for state */ state = freq_get_state(dfc, freq); - if (state == THERMAL_CSTATE_INVALID) - return -EAGAIN; + if (state == THERMAL_CSTATE_INVALID) { + res = -EAGAIN; + goto fail; + } - dyn_power = dfc->power_table[state]; + if (dfc->power_ops->get_real_power) { + voltage = get_voltage(df, freq); + if (voltage == 0) { + res = -EINVAL; + goto fail; + } - /* Scale dynamic power for utilization */ - dyn_power = (dyn_power * status->busy_time) / status->total_time; + res = dfc->power_ops->get_real_power(df, power, freq, voltage); + if (!res) { + state = dfc->capped_state; + dfc->res_util = dfc->power_table[state]; + dfc->res_util *= SCALE_ERROR_MITIGATION; - /* Get static power */ - static_power = get_static_power(dfc, freq); + if (*power > 1) + dfc->res_util /= *power; + } else { + goto fail; + } + } else { + dyn_power = dfc->power_table[state]; + + /* Scale dynamic power for utilization */ + dyn_power *= status->busy_time; + dyn_power /= status->total_time; + /* Get static power */ + static_power = get_static_power(dfc, freq); + + *power = dyn_power + static_power; + } trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power, static_power); - *power = dyn_power + static_power; - return 0; +fail: + /* It is safe to set max in this case */ + dfc->res_util = SCALE_ERROR_MITIGATION; + return res; } static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev, @@ -312,26 +360,34 @@ static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev, unsigned long busy_time; s32 dyn_power; u32 static_power; + s32 est_power; int i; - static_power = get_static_power(dfc, freq); + if (dfc->power_ops->get_real_power) { + /* Scale for resource utilization */ + est_power = power * dfc->res_util; + est_power /= SCALE_ERROR_MITIGATION; + } else { + static_power = get_static_power(dfc, freq); - dyn_power = power - static_power; - dyn_power = dyn_power > 0 ? dyn_power : 0; + dyn_power = power - static_power; + dyn_power = dyn_power > 0 ? dyn_power : 0; - /* Scale dynamic power for utilization */ - busy_time = status->busy_time ?: 1; - dyn_power = (dyn_power * status->total_time) / busy_time; + /* Scale dynamic power for utilization */ + busy_time = status->busy_time ?: 1; + est_power = (dyn_power * status->total_time) / busy_time; + } /* * Find the first cooling state that is within the power * budget for dynamic power. */ for (i = 0; i < dfc->freq_table_size - 1; i++) - if (dyn_power >= dfc->power_table[i]) + if (est_power >= dfc->power_table[i]) break; *state = i; + dfc->capped_state = i; trace_thermal_power_devfreq_limit(cdev, freq, *state, power); return 0; } @@ -387,7 +443,7 @@ static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc) } for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) { - unsigned long power_dyn, voltage; + unsigned long power, voltage; struct dev_pm_opp *opp; opp = dev_pm_opp_find_freq_floor(dev, &freq); @@ -400,12 +456,15 @@ static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc) dev_pm_opp_put(opp); if (dfc->power_ops) { - power_dyn = get_dynamic_power(dfc, freq, voltage); + if (dfc->power_ops->get_real_power) + power = get_total_power(dfc, freq, voltage); + else + power = get_dynamic_power(dfc, freq, voltage); - dev_dbg(dev, "Dynamic power table: %lu MHz @ %lu mV: %lu = %lu mW\n", - freq / 1000000, voltage, power_dyn, power_dyn); + dev_dbg(dev, "Power table: %lu MHz @ %lu mV: %lu = %lu mW\n", + freq / 1000000, voltage, power, power); - power_table[i] = power_dyn; + power_table[i] = power; } freq_table[i] = freq; diff --git a/include/linux/devfreq_cooling.h b/include/linux/devfreq_cooling.h index c35d0c0e0ada..4635f95000a4 100644 --- a/include/linux/devfreq_cooling.h +++ b/include/linux/devfreq_cooling.h @@ -34,6 +34,23 @@ * If get_dynamic_power() is NULL, then the * dynamic power is calculated as * @dyn_power_coeff * frequency * voltage^2 + * @get_real_power: When this is set, the framework uses it to ask the + * device driver for the actual power. + * Some devices have more sophisticated methods + * (like power counters) to approximate the actual power + * that they use. + * This function provides more accurate data to the + * thermal governor. When the driver does not provide + * such function, framework just uses pre-calculated + * table and scale the power by 'utilization' + * (based on 'busy_time' and 'total_time' taken from + * devfreq 'last_status'). + * The value returned by this function must be lower + * or equal than the maximum power value + * for the current state + * (which can be found in power_table[state]). + * When this interface is used, the power_table holds + * max total (static + dynamic) power value for each OPP. */ struct devfreq_cooling_power { unsigned long (*get_static_power)(struct devfreq *devfreq, @@ -41,6 +58,8 @@ struct devfreq_cooling_power { unsigned long (*get_dynamic_power)(struct devfreq *devfreq, unsigned long freq, unsigned long voltage); + int (*get_real_power)(struct devfreq *df, u32 *power, + unsigned long freq, unsigned long voltage); unsigned long dyn_power_coeff; }; From 771ffa14ead18887bed400c09f4bde5bca5bf342 Mon Sep 17 00:00:00 2001 From: Lukasz Luba Date: Thu, 4 May 2017 12:34:33 +0100 Subject: [PATCH 22/25] trace: thermal: add another parameter 'power' to the tracing function This patch adds another parameter to the trace function: trace_thermal_power_devfreq_get_power(). In case when we call directly driver's code for the real power, we do not have static/dynamic_power values. Instead we get total power in the '*power' value. The 'static_power' and 'dynamic_power' are set to 0. Therefore, we have to trace that '*power' value in this scenario. CC: Steven Rostedt CC: Ingo Molnar CC: Zhang Rui CC: Eduardo Valentin Acked-by: Javi Merino Signed-off-by: Lukasz Luba --- drivers/thermal/devfreq_cooling.c | 2 +- include/trace/events/thermal.h | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 26c31571a12c..ef59256887ff 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -321,7 +321,7 @@ static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cd } trace_thermal_power_devfreq_get_power(cdev, status, freq, dyn_power, - static_power); + static_power, *power); return 0; fail: diff --git a/include/trace/events/thermal.h b/include/trace/events/thermal.h index 2b4a8ff72d0d..6cde5b3514c2 100644 --- a/include/trace/events/thermal.h +++ b/include/trace/events/thermal.h @@ -151,9 +151,9 @@ TRACE_EVENT(thermal_power_cpu_limit, TRACE_EVENT(thermal_power_devfreq_get_power, TP_PROTO(struct thermal_cooling_device *cdev, struct devfreq_dev_status *status, unsigned long freq, - u32 dynamic_power, u32 static_power), + u32 dynamic_power, u32 static_power, u32 power), - TP_ARGS(cdev, status, freq, dynamic_power, static_power), + TP_ARGS(cdev, status, freq, dynamic_power, static_power, power), TP_STRUCT__entry( __string(type, cdev->type ) @@ -161,6 +161,7 @@ TRACE_EVENT(thermal_power_devfreq_get_power, __field(u32, load ) __field(u32, dynamic_power ) __field(u32, static_power ) + __field(u32, power) ), TP_fast_assign( @@ -169,11 +170,13 @@ TRACE_EVENT(thermal_power_devfreq_get_power, __entry->load = (100 * status->busy_time) / status->total_time; __entry->dynamic_power = dynamic_power; __entry->static_power = static_power; + __entry->power = power; ), - TP_printk("type=%s freq=%lu load=%u dynamic_power=%u static_power=%u", + TP_printk("type=%s freq=%lu load=%u dynamic_power=%u static_power=%u power=%u", __get_str(type), __entry->freq, - __entry->load, __entry->dynamic_power, __entry->static_power) + __entry->load, __entry->dynamic_power, __entry->static_power, + __entry->power) ); TRACE_EVENT(thermal_power_devfreq_limit, From 68b2440b2a4beaa393d8ed97ac619fd1c94a549e Mon Sep 17 00:00:00 2001 From: Brian Bian Date: Wed, 12 Apr 2017 14:07:17 -0700 Subject: [PATCH 23/25] Thermal: Intel SoC DTS: Change interrupt request behavior The interrupt request call in Intel SoC DTS driver may fail if there is no underlying BIOS support. However, the user space thermal daemon can still use the thermal zones created by the SoC DTS driver in polling mode, therefore, instead of bailing out on interrupt request failures, it is better just to log a warning message and continue the init process. Signed-off-by: Brian Bian Signed-off-by: Zhang Rui --- drivers/thermal/intel_soc_dts_thermal.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/intel_soc_dts_thermal.c b/drivers/thermal/intel_soc_dts_thermal.c index b2bbaa1c60b0..c27868b2c6af 100644 --- a/drivers/thermal/intel_soc_dts_thermal.c +++ b/drivers/thermal/intel_soc_dts_thermal.c @@ -73,8 +73,12 @@ static int __init intel_soc_thermal_init(void) IRQF_TRIGGER_RISING | IRQF_ONESHOT, "soc_dts", soc_dts); if (err) { - pr_err("request_threaded_irq ret %d\n", err); - goto error_irq; + /* + * Do not just error out because the user space thermal + * daemon such as DPTF may use polling instead of being + * interrupt driven. + */ + pr_warn("request_threaded_irq ret %d\n", err); } } @@ -88,7 +92,6 @@ static int __init intel_soc_thermal_init(void) error_trips: if (soc_dts_thres_irq) free_irq(soc_dts_thres_irq, soc_dts); -error_irq: intel_soc_dts_iosf_exit(soc_dts); return err; From e441fd68663e298e99a99e215e0144a0eda6250d Mon Sep 17 00:00:00 2001 From: Keerthy Date: Tue, 18 Apr 2017 09:59:58 +0530 Subject: [PATCH 24/25] thermal: core: Allow orderly_poweroff to be called only once thermal_zone_device_check --> thermal_zone_device_update --> handle_thermal_trip --> handle_critical_trips --> orderly_poweroff The above sequence happens every 250/500 mS based on the configuration. The orderly_poweroff function is getting called every 250/500 mS. With a full fledged file system it takes at least 5-10 Seconds to power off gracefully. In that period due to the thermal_zone_device_check triggering periodically the thermal work queues bombard with orderly_poweroff calls multiple times eventually leading to failures in gracefully powering off the system. Make sure that orderly_poweroff is called only once. Signed-off-by: Keerthy Acked-by: Eduardo Valentin Signed-off-by: Zhang Rui --- drivers/thermal/thermal_core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 11f0675cb7e5..8337c272d3ec 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -45,8 +45,10 @@ static LIST_HEAD(thermal_governor_list); static DEFINE_MUTEX(thermal_list_lock); static DEFINE_MUTEX(thermal_governor_lock); +static DEFINE_MUTEX(poweroff_lock); static atomic_t in_suspend; +static bool power_off_triggered; static struct thermal_governor *def_governor; @@ -342,7 +344,12 @@ static void handle_critical_trips(struct thermal_zone_device *tz, dev_emerg(&tz->device, "critical temperature reached(%d C),shutting down\n", tz->temperature / 1000); - orderly_poweroff(true); + mutex_lock(&poweroff_lock); + if (!power_off_triggered) { + orderly_poweroff(true); + power_off_triggered = true; + } + mutex_unlock(&poweroff_lock); } } @@ -1463,6 +1470,7 @@ static int __init thermal_init(void) { int result; + mutex_init(&poweroff_lock); result = thermal_register_governors(); if (result) goto error; @@ -1497,6 +1505,7 @@ static int __init thermal_init(void) ida_destroy(&thermal_cdev_ida); mutex_destroy(&thermal_list_lock); mutex_destroy(&thermal_governor_lock); + mutex_destroy(&poweroff_lock); return result; } From ef1d87e06ab4d3f9a95f02517ecc50902dc233a7 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Tue, 18 Apr 2017 09:59:59 +0530 Subject: [PATCH 25/25] thermal: core: Add a back up thermal shutdown mechanism orderly_poweroff is triggered when a graceful shutdown of system is desired. This may be used in many critical states of the kernel such as when subsystems detects conditions such as critical temperature conditions. However, in certain conditions in system boot up sequences like those in the middle of driver probes being initiated, userspace will be unable to power off the system in a clean manner and leaves the system in a critical state. In cases like these, the /sbin/poweroff will return success (having forked off to attempt powering off the system. However, the system overall will fail to completely poweroff (since other modules will be probed) and the system is still functional with no userspace (since that would have shut itself off). However, there is no clean way of detecting such failure of userspace powering off the system. In such scenarios, it is necessary for a backup workqueue to be able to force a shutdown of the system when orderly shutdown is not successful after a configurable time period. Reported-by: Nishanth Menon Signed-off-by: Keerthy Acked-by: Eduardo Valentin Signed-off-by: Zhang Rui --- Documentation/thermal/sysfs-api.txt | 21 ++++++++++++ drivers/thermal/Kconfig | 17 +++++++++ drivers/thermal/thermal_core.c | 53 +++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/Documentation/thermal/sysfs-api.txt b/Documentation/thermal/sysfs-api.txt index ef473dc7f55e..bb9a0a53e76b 100644 --- a/Documentation/thermal/sysfs-api.txt +++ b/Documentation/thermal/sysfs-api.txt @@ -582,3 +582,24 @@ platform data is provided, this uses the step_wise throttling policy. This function serves as an arbitrator to set the state of a cooling device. It sets the cooling device to the deepest cooling state if possible. + +6. thermal_emergency_poweroff: + +On an event of critical trip temperature crossing. Thermal framework +allows the system to shutdown gracefully by calling orderly_poweroff(). +In the event of a failure of orderly_poweroff() to shut down the system +we are in danger of keeping the system alive at undesirably high +temperatures. To mitigate this high risk scenario we program a work +queue to fire after a pre-determined number of seconds to start +an emergency shutdown of the device using the kernel_power_off() +function. In case kernel_power_off() fails then finally +emergency_restart() is called in the worst case. + +The delay should be carefully profiled so as to give adequate time for +orderly_poweroff(). In case of failure of an orderly_poweroff() the +emergency poweroff kicks in after the delay has elapsed and shuts down +the system. + +If set to 0 emergency poweroff will not be supported. So a carefully +profiled non-zero positive value is a must for emergerncy poweroff to be +triggered. diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 776b34396144..74ef51dfb816 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -15,6 +15,23 @@ menuconfig THERMAL if THERMAL +config THERMAL_EMERGENCY_POWEROFF_DELAY_MS + int "Emergency poweroff delay in milli-seconds" + depends on THERMAL + default 0 + help + Thermal subsystem will issue a graceful shutdown when + critical temperatures are reached using orderly_poweroff(). In + case of failure of an orderly_poweroff(), the thermal emergency + poweroff kicks in after a delay has elapsed and shuts down the system. + This config is number of milliseconds to delay before emergency + poweroff kicks in. Similarly to the critical trip point, + the delay should be carefully profiled so as to give adequate + time for orderly_poweroff() to finish on regular execution. + If set to 0 emergency poweroff will not be supported. + + In doubt, leave as 0. + config THERMAL_HWMON bool prompt "Expose thermal sensors as hwmon device" diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 8337c272d3ec..b21b9cc2c8d6 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -324,6 +324,54 @@ static void handle_non_critical_trips(struct thermal_zone_device *tz, def_governor->throttle(tz, trip); } +/** + * thermal_emergency_poweroff_func - emergency poweroff work after a known delay + * @work: work_struct associated with the emergency poweroff function + * + * This function is called in very critical situations to force + * a kernel poweroff after a configurable timeout value. + */ +static void thermal_emergency_poweroff_func(struct work_struct *work) +{ + /* + * We have reached here after the emergency thermal shutdown + * Waiting period has expired. This means orderly_poweroff has + * not been able to shut off the system for some reason. + * Try to shut down the system immediately using kernel_power_off + * if populated + */ + WARN(1, "Attempting kernel_power_off: Temperature too high\n"); + kernel_power_off(); + + /* + * Worst of the worst case trigger emergency restart + */ + WARN(1, "Attempting emergency_restart: Temperature too high\n"); + emergency_restart(); +} + +static DECLARE_DELAYED_WORK(thermal_emergency_poweroff_work, + thermal_emergency_poweroff_func); + +/** + * thermal_emergency_poweroff - Trigger an emergency system poweroff + * + * This may be called from any critical situation to trigger a system shutdown + * after a known period of time. By default this is not scheduled. + */ +void thermal_emergency_poweroff(void) +{ + int poweroff_delay_ms = CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS; + /* + * poweroff_delay_ms must be a carefully profiled positive value. + * Its a must for thermal_emergency_poweroff_work to be scheduled + */ + if (poweroff_delay_ms <= 0) + return; + schedule_delayed_work(&thermal_emergency_poweroff_work, + msecs_to_jiffies(poweroff_delay_ms)); +} + static void handle_critical_trips(struct thermal_zone_device *tz, int trip, enum thermal_trip_type trip_type) { @@ -346,6 +394,11 @@ static void handle_critical_trips(struct thermal_zone_device *tz, tz->temperature / 1000); mutex_lock(&poweroff_lock); if (!power_off_triggered) { + /* + * Queue a backup emergency shutdown in the event of + * orderly_poweroff failure + */ + thermal_emergency_poweroff(); orderly_poweroff(true); power_off_triggered = true; }