From 7cbf4366da396f415e9c9f1d5b11b45ebdf59ec3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 17 Sep 2026 13:30:02 +0530 Subject: [PATCH 01/36] tools/thermal: Fix typo "binded" in comments "binded" is not a word; the past participle of "bind" is "bound". The tmon.h comment counts cooling devices, so the noun is plural there, matching the wording already used for MAX_NR_CDEV_TRIP. The tui.c clause needs "is" to read as a sentence. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Signed-off-by: Daniel Lezcano Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260917080002.23952-1-hemanth.selam@gmail.com --- tools/thermal/tmon/sysfs.c | 2 +- tools/thermal/tmon/tmon.h | 2 +- tools/thermal/tmon/tui.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c index cb1108bc9249..5d64aff12a86 100644 --- a/tools/thermal/tmon/sysfs.c +++ b/tools/thermal/tmon/sysfs.c @@ -225,7 +225,7 @@ static int find_tzone_cdev(struct dirent *nl, char *tz_name, cdev_name, cdev_name_linked, cdev_id); tzi->cdev_binding |= (1 << cdev_id); - /* find the trip point in which the cdev is binded to + /* find the trip point in which the cdev is bound to * in this tzone */ snprintf(cdev_trip_name, sizeof(cdev_trip_name) - 1, diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h index 44d16d778f04..b7767056f189 100644 --- a/tools/thermal/tmon/tmon.h +++ b/tools/thermal/tmon/tmon.h @@ -77,7 +77,7 @@ struct tz_info { char type[256]; /* e.g. acpitz */ int instance; int passive; /* active zone has passive node to force passive mode */ - int nr_cdev; /* number of cooling device binded */ + int nr_cdev; /* number of cooling devices bound */ int nr_trip_pts; struct trip_point tp[MAX_NR_TRIP]; unsigned long cdev_binding; /* bitmap for attached cdevs */ diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c index 7f5dd2b87f15..dbc3ca95c724 100644 --- a/tools/thermal/tmon/tui.c +++ b/tools/thermal/tmon/tui.c @@ -238,7 +238,7 @@ void show_cooling_device(void) unsigned long trip_binding = ptdata.tzi[i].trip_binding[cdev_inst]; int k = 0; /* per zone trip point id that - * binded to this cdev, one to + * is bound to this cdev, one to * many possible based on the * binding bitmask. */ From 7efceedd23678cd58e98b2c64d4b2b7be03f6cf9 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 15 Sep 2026 20:41:28 +0100 Subject: [PATCH 02/36] thermal/drivers/airoha: Request the EN7581 IRQ after zone registration The EN7581 IRQ is requested before the thermal zone is registered, and the handler passes priv->tz to thermal_zone_device_update(). Unbind leaves the monitor interrupt enabled, so an interrupt pending on the next bind fires while priv->tz is NULL. On a Nokia XG-040G-MD this oopses in thermal_zone_device_update(). On unbind, devres also frees the zone before the IRQ. Request the IRQ in post_probe, after zone registration. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260915194128.42508-1-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 77b3a1af271f..ea3e2649c280 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -528,7 +528,7 @@ static int en7581_thermal_probe(struct platform_device *pdev, struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; - int i, irq, ret; + int i; base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) @@ -567,16 +567,6 @@ static int en7581_thermal_probe(struct platform_device *pdev, of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res); of_node_put(chip_scu_np); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - en7581_thermal_irq, IRQF_ONESHOT, - pdev->name, priv); - if (ret) - return ret; - en7581_thermal_setup_monitor(priv); en7581_thermal_setup_adc_val(dev, priv); @@ -586,6 +576,18 @@ static int en7581_thermal_probe(struct platform_device *pdev, static int en7581_thermal_post_probe(struct platform_device *pdev) { struct airoha_thermal_priv *priv = platform_get_drvdata(pdev); + int irq, ret; + + /* After zone registration: the handler uses priv->tz */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + en7581_thermal_irq, IRQF_ONESHOT, + pdev->name, priv); + if (ret) + return ret; /* Enable LOW and HIGH interrupt (if supported) */ regmap_write(priv->map, EN7581_TEMPMONINT, From e7cfcccd8c38ba674240636d882da879c904296a Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 8 Aug 2026 22:23:33 +0200 Subject: [PATCH 03/36] thermal/drivers/imx9: Constify struct thermal_zone_device_ops 'struct thermal_zone_device_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 8567 2144 0 10711 29d7 drivers/thermal/imx91_thermal.o After: ===== text data bss dec hex filename 8695 2016 0 10711 29d7 drivers/thermal/imx91_thermal.o Signed-off-by: Christophe JAILLET Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/5cce9ea713edcf2aa3c0efd5d31e9d9fd9f40a88.1786220585.git.christophe.jaillet@wanadoo.fr --- drivers/thermal/imx91_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c index 274eee303142..196ee20fce1e 100644 --- a/drivers/thermal/imx91_thermal.c +++ b/drivers/thermal/imx91_thermal.c @@ -225,7 +225,7 @@ static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_de return 0; } -static struct thermal_zone_device_ops tmu_tz_ops = { +static const struct thermal_zone_device_ops tmu_tz_ops = { .get_temp = imx91_tmu_get_temp, .change_mode = imx91_tmu_change_mode, .set_trips = imx91_tmu_set_trips, From 072c3e6ac8cd959b523fb589ad9ddf9d7d2a52b4 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 11 Aug 2026 00:19:28 +0530 Subject: [PATCH 04/36] dt-bindings: thermal: ti,omap-bandgap: Convert to DT schema Convert Texas Instruments OMAP SCM bandgap from legacy text to DT schema Add "ti,dra752-bandgap" to compatible which was missing in text binding but used and matched by same driver. Document the "#thermal-sensor-cells" property, which was missing from the original text binding but is required in practice by all in-tree users via the common thermal-sensor.yaml binding. Signed-off-by: Bhargav Joshi Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260811-ti-bandgap-v2-1-6fec2a71c394@gmail.com --- .../bindings/thermal/ti,omap-bandgap.yaml | 177 ++++++++++++++++++ .../bindings/thermal/ti_soc_thermal.txt | 88 --------- 2 files changed, 177 insertions(+), 88 deletions(-) create mode 100644 Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml delete mode 100644 Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt diff --git a/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml b/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml new file mode 100644 index 000000000000..091b7be28461 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/ti,omap-bandgap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP SCM bandgap + +maintainers: + - Eduardo Valentin + +description: + In the System Control Module, OMAP supplies a voltage reference and a + temperature sensor feature that are gathered in the band gap voltage and + temperature sensor (VBGAPTS) module. The band gap provides current and + voltage reference for its internal circuits and other analog IP blocks. The + analog-to-digital converter (ADC) produces an output value that is + proportional to the silicon temperature. + +properties: + compatible: + enum: + - ti,omap34xx-bandgap + - ti,omap36xx-bandgap + - ti,omap4430-bandgap + - ti,omap4460-bandgap + - ti,omap4470-bandgap + - ti,omap5430-bandgap + - ti,dra752-bandgap + + reg: + minItems: 1 + maxItems: 6 + + interrupts: + maxItems: 1 + + gpios: + maxItems: 1 + description: + used to inform which GPIO line the tshut signal is routed to. The + informed GPIO will be treated as an IRQ; + + "#thermal-sensor-cells": + enum: [0, 1] + +required: + - compatible + - reg + +allOf: + - $ref: thermal-sensor.yaml# + + - if: + properties: + compatible: + contains: + enum: + - ti,omap34xx-bandgap + - ti,omap36xx-bandgap + + then: + properties: + reg: + minItems: 1 + maxItems: 1 + + interrupts: false + + gpios: false + + "#thermal-sensor-cells": + const: 0 + + - if: + properties: + compatible: + contains: + const: ti,omap4430-bandgap + + then: + properties: + reg: + minItems: 2 + maxItems: 2 + + interrupts: false + + "#thermal-sensor-cells": + const: 0 + + required: + - gpios + + - if: + properties: + compatible: + contains: + enum: + - ti,omap4460-bandgap + - ti,omap4470-bandgap + + then: + properties: + reg: + minItems: 3 + maxItems: 3 + + "#thermal-sensor-cells": + const: 0 + + required: + - interrupts + - gpios + + - if: + properties: + compatible: + contains: + const: ti,omap5430-bandgap + + then: + properties: + reg: + minItems: 4 + maxItems: 4 + + gpios: false + + "#thermal-sensor-cells": + const: 1 + + required: + - interrupts + + - if: + properties: + compatible: + contains: + const: ti,dra752-bandgap + + then: + properties: + reg: + minItems: 6 + maxItems: 6 + + gpios: false + + "#thermal-sensor-cells": + const: 1 + + required: + - interrupts + +unevaluatedProperties: false + +examples: + - | + bandgap@48002524 { + compatible = "ti,omap34xx-bandgap"; + reg = <0x48002524 0x4>; + #thermal-sensor-cells = <0>; + }; + + - | + #include + #include + + bandgap@4a0021e0 { + compatible = "ti,dra752-bandgap"; + reg = <0x4a0021e0 0xc>, <0x4a00232c 0xc>, + <0x4a002380 0x2c>, <0x4a0023c0 0x3c>, + <0x4a002564 0x8>, <0x4a002574 0x50>; + interrupts = ; /* talert */ + #thermal-sensor-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt b/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt deleted file mode 100644 index 6299dd8de339..000000000000 --- a/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt +++ /dev/null @@ -1,88 +0,0 @@ -* Texas Instrument OMAP SCM bandgap bindings - -In the System Control Module, OMAP supplies a voltage reference -and a temperature sensor feature that are gathered in the band -gap voltage and temperature sensor (VBGAPTS) module. The band -gap provides current and voltage reference for its internal -circuits and other analog IP blocks. The analog-to-digital -converter (ADC) produces an output value that is proportional -to the silicon temperature. - -Required properties: -- compatible : Should be: - - "ti,omap34xx-bandgap" : for OMAP34xx bandgap - - "ti,omap36xx-bandgap" : for OMAP36xx bandgap - - "ti,omap4430-bandgap" : for OMAP4430 bandgap - - "ti,omap4460-bandgap" : for OMAP4460 bandgap - - "ti,omap4470-bandgap" : for OMAP4470 bandgap - - "ti,omap5430-bandgap" : for OMAP5430 bandgap -- interrupts : this entry should indicate which interrupt line -the talert signal is routed to; -Specific: -- gpios : this entry should be used to inform which GPIO -line the tshut signal is routed to. The informed GPIO will -be treated as an IRQ; -- regs : this entry must also be specified and it is specific -to each bandgap version, because the mapping may change from -soc to soc, apart of depending on available features. - -Example: -OMAP34xx: -bandgap { - reg = <0x48002524 0x4>; - compatible = "ti,omap34xx-bandgap"; -}; - -OMAP36xx: -bandgap { - reg = <0x48002524 0x4>; - compatible = "ti,omap36xx-bandgap"; -}; - -OMAP4430: -bandgap { - reg = <0x4a002260 0x4 0x4a00232C 0x4>; - compatible = "ti,omap4430-bandgap"; -}; - -OMAP4460: -bandgap { - reg = <0x4a002260 0x4 - 0x4a00232C 0x4 - 0x4a002378 0x18>; - compatible = "ti,omap4460-bandgap"; - interrupts = <0 126 4>; /* talert */ - gpios = <&gpio3 22 0>; /* tshut */ -}; - -OMAP4470: -bandgap { - reg = <0x4a002260 0x4 - 0x4a00232C 0x4 - 0x4a002378 0x18>; - compatible = "ti,omap4470-bandgap"; - interrupts = <0 126 4>; /* talert */ - gpios = <&gpio3 22 0>; /* tshut */ -}; - -OMAP5430: -bandgap { - reg = <0x4a0021e0 0xc - 0x4a00232c 0xc - 0x4a002380 0x2c - 0x4a0023C0 0x3c>; - compatible = "ti,omap5430-bandgap"; - interrupts = <0 126 4>; /* talert */ -}; - -DRA752: -bandgap { - reg = <0x4a0021e0 0xc - 0x4a00232c 0xc - 0x4a002380 0x2c - 0x4a0023C0 0x3c - 0x4a002564 0x8 - 0x4a002574 0x50>; - compatible = "ti,dra752-bandgap"; - interrupts = <0 126 4>; /* talert */ -}; From 5e3d33c3fdf3761cc95fcdd815259493b803fa4a Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Sun, 30 Aug 2026 18:10:25 +0800 Subject: [PATCH 05/36] dt-bindings: thermal: qcom,pm8775-mbg-tm: Document PMAU0102 Document MBG temperature monitor on PMAU0102 which is programming-model compatible with the one on PM8775. Signed-off-by: Dhruvin Rajpura Signed-off-by: Shawn Guo Signed-off-by: Daniel Lezcano Reviewed-by: Bartosz Golaszewski Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260830101025.191697-1-shengchao.guo@oss.qualcomm.com --- .../devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml index 2e084d040625..ef8361a55389 100644 --- a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml @@ -21,7 +21,12 @@ description: properties: compatible: - const: qcom,pm8775-mbg-tm + oneOf: + - const: qcom,pm8775-mbg-tm + - items: + - enum: + - qcom,pmau0102-mbg-tm + - const: qcom,pm8775-mbg-tm reg: maxItems: 1 From 84f0e14e6261ef718e6a8583073097ad9ea70e14 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 11:59:59 +0000 Subject: [PATCH 06/36] drivers/thermal/renesas/rzg3e: Fix signed integer overflow in temp_to_code() The thermal core passes -INT_MAX/INT_MAX as low/high trip points, which the driver then converts to code values for programming the registers: numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); Although 'numerator' is defined as s64, the arithmetic is performed as int. Because of this, the multiplication overflows for -INT_MAX and, because temp_d_mc is negative, the subtraction overflows for INT_MAX. To fix this, cast the temperature to s64, so that the arithmetic is performed as s64. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-2-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index c44f5b8858d0..4acee4eaff05 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -194,7 +194,12 @@ static u16 rzg3e_thermal_temp_to_code(struct rzg3e_thermal_priv *priv, int temp_ s64 numerator, denominator; s64 code; - numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); + /* + * Perform the arithmetic in 64 bits so that it cannot overflow for + * -INT_MAX/INT_MAX values passed from the thermal core or when + * userspace writes arbitrary trip point temperatures. + */ + numerator = ((s64)temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); denominator = info->temp_e_mc - info->temp_d_mc; code = div64_s64(numerator, denominator) + priv->trmval0; From aa1bc39e45f29a4f56d7e6be7164e100727df465 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:00 +0000 Subject: [PATCH 07/36] thermal/drivers/renesas/rzg3e: Fix runtime PM handling of compare irq The TSU hw is limited in the sense that lower and upper trip points can be set, but the hw cannot raise interrupts on its own. It can only signal that the thresholds were passed when the temperature is read, either during polling or on a manual read. Therefore, the interrupt enable state configured by set_trips() must remain active for the next read to trigger an event. Currently, runtime PM autosuspend puts the chip to sleep and clears the compare interrupt that was enabled during set_trips(). On power up, the compare interrupt enable is not restored, which means that it won't ever fire. Fix this by caching the contents of the SIER register during runtime power off and restore it during runtime power on. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-3-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 4acee4eaff05..45f67b2e1131 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -93,6 +93,7 @@ struct rzg3e_thermal_info { * @info: chip type specific information * @trmval0: calibration value 0 (b) * @trmval1: calibration value 1 (c) + * @sier: cached interrupt enable register * @lock: protects hardware access during conversions */ struct rzg3e_thermal_priv { @@ -103,6 +104,7 @@ struct rzg3e_thermal_priv { const struct rzg3e_thermal_info *info; u16 trmval0; u16 trmval1; + u32 sier; struct mutex lock; }; @@ -148,12 +150,16 @@ static int rzg3e_thermal_power_on(struct rzg3e_thermal_priv *priv) return ret; } + /* Restore interrupt enable state */ + writel(priv->sier, priv->base + TSU_SIER); + return 0; } static void rzg3e_thermal_power_off(struct rzg3e_thermal_priv *priv) { - /* Disable all interrupts */ + /* Save and disable all interrupts */ + priv->sier = readl(priv->base + TSU_SIER); writel(0, priv->base + TSU_SIER); /* Clear pending interrupts */ From acd9bf8f9047b03f023514bceab6a00e037977ca Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:01 +0000 Subject: [PATCH 08/36] thermal/drivers/renesas/rzg3e: Fix race between set_trips() and get_temp() Datasheet section 7.11.9.2 "Conversion Start Trigger" states that a conversion must only be started while no other conversion is in progress: "Before making the setting to start conversion by software or an ELC trigger, check that the CONV bit of the sensor status register is 0b." set_trips() starts a conversion at the end of the function and returns without waiting for it to finish. A get_temp() call arriving right after that starts a conversion while the CONV bit may still be set, violating the requirement above. Fix this by: - polling for CONV == 0 in get_temp() before starting a conversion - not starting a conversion at the end of set_trips() anymore The conversion started at the end of set_trips() was supposed to check whether the thresholds have been passed, since the hw cannot raise interrupts on its own, but only when the temperature is read. However, the thermal core reads the temperature and computes the trip window from it just before calling into set_trips(), so reading the temperature again straight away is useless. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-4-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 45f67b2e1131..58fdcc66456a 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -225,6 +225,16 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp) guard(mutex)(&priv->lock); + /* Make sure a previous conversion is not in progress */ + ret = readl_poll_timeout(priv->base + TSU_SSR, status, + !(status & TSU_SSR_CONV), + TSU_POLL_DELAY_US, + USEC_PER_MSEC); + if (ret) { + dev_err(priv->dev, "Timeout waiting for conversion\n"); + goto out; + } + /* Clear any previous conversion status */ writel(TSU_SICR_ADCLR, priv->base + TSU_SICR); @@ -304,9 +314,8 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, /* Enable comparison with "out of range" mode (CMPCOND=0) */ writel(TSU_CMSR_CMPEN, priv->base + TSU_CMSR); - /* Unmask compare IRQ and start a conversion to evaluate window */ + /* Unmask compare IRQ */ writel(TSU_SIER_CMPIE, priv->base + TSU_SIER); - writel(TSU_STRGR_ADST, priv->base + TSU_STRGR); pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); From a5f1de6d789484f308ff9b571835b3e2e7385444 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:02 +0000 Subject: [PATCH 09/36] thermal/drivers/renesas/rzg3e: Remove unneeded low/high check in set_trips() The hardware manual doesn't specify that the trip settings must be strictly low < high, just that when the CMPCOND bit is 0, an interrupt will be raised when the acquired temperature data is > ULIM or < LLIM. Also, the thermal core already guarantees low < high. Remove the redundant check. Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-5-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 58fdcc66456a..ffe2b8725031 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -275,10 +275,6 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, u32 val; int ret; - /* Hardware requires low < high */ - if (low >= high) - return -EINVAL; - ret = pm_runtime_resume_and_get(priv->dev); if (ret < 0) return ret; From f5f32c6c48d8951d068815402464f7b6ef1f3d0a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 23 Sep 2026 22:29:13 +0200 Subject: [PATCH 10/36] thermal/cpufreq_cooling: Simplify cpufreq_set_cur_state() Return freq_qos_update_request() errors directly to simplify the code. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260923202911.90464-3-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/cpufreq_cooling.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 768859a7aed0..e89036077bef 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -488,12 +488,12 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, frequency = get_state_freq(cpufreq_cdev, state); ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency); - if (ret >= 0) { - cpufreq_cdev->cpufreq_state = state; - ret = 0; - } + if (ret < 0) + return ret; - return ret; + cpufreq_cdev->cpufreq_state = state; + + return 0; } /** From c337b60bdadc819c39f802d8e6418c320c580e0e Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:03 +0000 Subject: [PATCH 11/36] thermal/drivers/renesas/rzg3e: Remove redundant pm_runtime_mark_last_busy() calls pm_runtime_put_autosuspend() already calls pm_runtime_mark_last_busy() internally, so the explicit calls are not needed. Remove them. Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-6-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index ffe2b8725031..a1ceff79e83e 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -262,7 +262,6 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp) *temp, *temp / 1000, abs(*temp) % 1000, code); out: - pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); return ret; } @@ -313,7 +312,6 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, /* Unmask compare IRQ */ writel(TSU_SIER_CMPIE, priv->base + TSU_SIER); - pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); return 0; @@ -485,7 +483,6 @@ static int rzg3e_thermal_probe(struct platform_device *pdev) if (ret) dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); dev_info(dev, "RZ/G3E thermal sensor registered\n"); From eca1247443d41f695c389e4109c2d7986f8fd95e Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Fri, 8 May 2026 15:37:00 +0530 Subject: [PATCH 12/36] thermal/drivers/qcom/tsens: Widen temperature limits to match hardware range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TSENS v2 software driver currently clamps trip_min_temp and trip_max_temp to -40°C and 120°C respectively. However, the TSENS v2 hardware temperature threshold registers support a wider programmable range from -204°C to +204°C. On newer chipsets using TSENS v2, devices may legitimately operate beyond the existing software limits (for example, up to 130°C). When a trip temperature is programmed outside the software clamped range, it is constrained to 120°C on the upper end or -40°C on the lower end. If the actual temperature continues to exceed this clamped limit, the threshold is immediately violated again, which can result in a continuous interrupt storm. Expand the TSENS v2 software trip temperature limits to match the full hardware supported range (-204°C to +204°C). This avoids repeated threshold reprogramming and ensures correct trip handling on TSENS v2 based platforms. Signed-off-by: Priyansh Jain Signed-off-by: Daniel Lezcano Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260508100700.772985-3-priyansh.jain@oss.qualcomm.com --- drivers/thermal/qcom/tsens-v2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c index 2ee117aa91ba..843dd29fad2e 100644 --- a/drivers/thermal/qcom/tsens-v2.c +++ b/drivers/thermal/qcom/tsens-v2.c @@ -54,8 +54,8 @@ static struct tsens_features tsens_v2_feat = { .adc = 0, .srot_split = 1, .max_sensors = 16, - .trip_min_temp = -40000, - .trip_max_temp = 120000, + .trip_min_temp = -204000, + .trip_max_temp = 204000, }; static struct tsens_features ipq8074_feat = { From 6b7f5abb48cab4ab500ca9fdfcb28aeb0e4d4971 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:15:59 +0100 Subject: [PATCH 13/36] dt-bindings: thermal: r9a08g045-tsu: Document RZ/G3L TSU Add the renesas,r9a08g046-tsu compatible string for the RZ/G3L Thermal Sensor Unit (a.k.a TSU), which is similar to the RZ/G3S TSU but uses different calibration constants. TSU is connected to a dedicated ADC IP (a.k.a ADC1) on RZ/G3L. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Reviewed-by: Claudiu Beznea Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260925061617.127349-2-biju.das.jz@bp.renesas.com --- .../devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml b/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml index 573e2b9d3752..20fc1a8000e3 100644 --- a/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml +++ b/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml @@ -17,7 +17,9 @@ $ref: thermal-sensor.yaml# properties: compatible: - const: renesas,r9a08g045-tsu + enum: + - renesas,r9a08g045-tsu + - renesas,r9a08g046-tsu reg: maxItems: 1 From 91e5a33675e56dbc982703655cdf4637f5e76f23 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:00 +0100 Subject: [PATCH 14/36] thermal/drivers/renesas/rzg3s: Convert calibration defaults to per-SoC match data Move the default (software) calibration values SW_CALIB0_VAL and SW_CALIB1_VAL, used when FUSE values are missing, into a new struct rzg3s_thermal_info retrieved via of_device_get_match_data(). This allows different SoCs sharing this driver to provide their own default calibration constants, which is needed as RZ/G3L uses different values than RZ/G3S. No functional change intended for RZ/G3S. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20260925061617.127349-3-biju.das.jz@bp.renesas.com --- drivers/thermal/renesas/rzg3s_thermal.c | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index 7ced8f76a0ec..146b73a1b751 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,10 +28,6 @@ #define TSU_READ_STEPS 8 -/* Default calibration values, if FUSE values are missing. */ -#define SW_CALIB0_VAL 1297 -#define SW_CALIB1_VAL 751 - #define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE) /** @@ -55,6 +52,16 @@ struct rzg3s_thermal_priv { u16 calib1; }; +/** + * struct rzg3s_thermal_info - RZ/G3S thermal information + * @calib0: calibration value if FUSE value is missing + * @calib1: calibration value if FUSE value is missing + */ +struct rzg3s_thermal_info { + u16 calib0; + u16 calib1; +}; + static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz); @@ -149,7 +156,8 @@ static const struct thermal_zone_device_ops rzg3s_tz_of_ops = { .change_mode = rzg3s_thermal_change_mode, }; -static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) +static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv, + const struct rzg3s_thermal_info *info) { struct device *dev = priv->dev; u32 val; @@ -163,13 +171,13 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) if (val & OTPTSUTRIM_EN_MASK) priv->calib0 = FIELD_GET(OTPTSUTRIM_MASK, val); else - priv->calib0 = SW_CALIB0_VAL; + priv->calib0 = info->calib0; val = readl(priv->base + OTPTSUTRIM_REG(1)); if (val & OTPTSUTRIM_EN_MASK) priv->calib1 = FIELD_GET(OTPTSUTRIM_MASK, val); else - priv->calib1 = SW_CALIB1_VAL; + priv->calib1 = info->calib1; pm_runtime_put_autosuspend(dev); @@ -178,6 +186,7 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) static int rzg3s_thermal_probe(struct platform_device *pdev) { + const struct rzg3s_thermal_info *info = device_get_match_data(&pdev->dev); struct rzg3s_thermal_priv *priv; struct device *dev = &pdev->dev; int ret; @@ -208,7 +217,7 @@ static int rzg3s_thermal_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n"); - ret = rzg3s_thermal_read_calib(priv); + ret = rzg3s_thermal_read_calib(priv, info); if (ret) return dev_err_probe(dev, ret, "Failed to read calibration data!\n"); @@ -251,8 +260,13 @@ static const struct dev_pm_ops rzg3s_thermal_pm_ops = { SYSTEM_SLEEP_PM_OPS(rzg3s_thermal_suspend, rzg3s_thermal_resume) }; +static const struct rzg3s_thermal_info rzg3s_info = { + .calib0 = 1297, + .calib1 = 751, +}; + static const struct of_device_id rzg3s_thermal_dt_ids[] = { - { .compatible = "renesas,r9a08g045-tsu" }, + { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids); From e856ca3013b3074fd07cb81c90b1de332f69153c Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:01 +0100 Subject: [PATCH 15/36] thermal/drivers/renesas/rzg3s: Add RZ/G3L TSU support Add the renesas,r9a08g046-tsu compatible string and its default (software) calibration constants for RZ/G3L, used when FUSE calibration values are missing. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20260925061617.127349-4-biju.das.jz@bp.renesas.com --- drivers/thermal/renesas/rzg3s_thermal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index 146b73a1b751..a8716da7fcd2 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -265,8 +265,14 @@ static const struct rzg3s_thermal_info rzg3s_info = { .calib1 = 751, }; +static const struct rzg3s_thermal_info rzg3l_info = { + .calib0 = 1322, + .calib1 = 772, +}; + static const struct of_device_id rzg3s_thermal_dt_ids[] = { { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info }, + { .compatible = "renesas,r9a08g046-tsu", .data = &rzg3l_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids); From 4f6d1a9ea172c22e6c67e2ec06fb0f20e07e7225 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:50:09 -0500 Subject: [PATCH 16/36] thermal: of: Match trip property helper types The thermal-zone binding defines "temperature" as a signed int32 value and "hysteresis" as an unsigned int32 value. Using helpers with matching types avoids dt_property_check mismatches and preserves the signed interpretation needed for trips below zero. Read "temperature" with the signed helper and keep "hysteresis" on the unsigned helper using separate typed temporaries before storing them in the trip structure. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260612215009.1884665-1-robh@kernel.org --- drivers/thermal/thermal_of.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae..deaae7a995c5 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -63,22 +63,23 @@ static int thermal_of_get_trip_type(struct device_node *np, static int thermal_of_populate_trip(struct device_node *np, struct thermal_trip *trip) { - int prop; + u32 hysteresis; + s32 temperature; int ret; - ret = of_property_read_u32(np, "temperature", &prop); + ret = of_property_read_s32(np, "temperature", &temperature); if (ret < 0) { pr_err("missing temperature property\n"); return ret; } - trip->temperature = prop; + trip->temperature = temperature; - ret = of_property_read_u32(np, "hysteresis", &prop); + ret = of_property_read_u32(np, "hysteresis", &hysteresis); if (ret < 0) { pr_err("missing hysteresis property\n"); return ret; } - trip->hysteresis = prop; + trip->hysteresis = hysteresis; ret = thermal_of_get_trip_type(np, &trip->type); if (ret < 0) { From ddb39deb06e0f816bba0cbcc3a658661df308f8d Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:16 +0000 Subject: [PATCH 17/36] dt-bindings: thermal: Add Google GS101 TMU Document the Thermal Management Unit (TMU) found on the Google GS101 SoC. The GS101 TMU utilizes a hybrid control model shared between the Application Processor (AP) and the ACPM (Alive Clock and Power Manager) firmware. This hybrid ACPM TMU architecture is also present on other Samsung Exynos SoCs (e.g., AutoV920, Exynos850). While the TMU is a standard memory-mapped IP block, on this platform the AP's direct register access is restricted to the interrupt pending (INTPEND) registers for event identification. High-level functional tasks, such as sensor initialization, threshold programming, and temperature reads, are delegated to the ACPM firmware. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260603-acpm-tmu-v8-1-0f1810a356e6@linaro.org --- .../thermal/google,gs101-tmu-top.yaml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml diff --git a/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml new file mode 100644 index 000000000000..75560ebca48d --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/google,gs101-tmu-top.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung Exynos ACPM Thermal Management Unit (TMU) + +maintainers: + - Tudor Ambarus + +description: + The Samsung Exynos ACPM TMU is a thermal sensor block found on Exynos + based platforms (such as Google GS101 and Exynos850). It supports + both direct register-level access and firmware-mediated management + via the ACPM (Alive Clock and Power Manager) firmware. + + On these platforms, the hardware is managed in a hybrid fashion. The + Application Processor (AP) maintains direct memory-mapped access + exclusively to the interrupt pending registers to identify thermal + events. All other functional aspects - including sensor + initialization, threshold configuration, and temperature acquisition + - are handled by the ACPM firmware. The AP coordinates these + operations through the ACPM IPC protocol. + +properties: + compatible: + const: google,gs101-tmu-top + + reg: + maxItems: 1 + + clocks: + items: + - description: APB peripheral clock (PCLK) for TMU register access. + + interrupts: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + + samsung,acpm-ipc: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the ACPM IPC node. + +required: + - compatible + - reg + - clocks + - interrupts + - "#thermal-sensor-cells" + - samsung,acpm-ipc + +additionalProperties: false + +examples: + - | + #include + #include + + thermal-sensor@100a0000 { + compatible = "google,gs101-tmu-top"; + reg = <0x100a0000 0x800>; + clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>; + interrupts = ; + #thermal-sensor-cells = <1>; + samsung,acpm-ipc = <&acpm_ipc>; + }; From 50a2c5d4062a95146f71f974e1feb2ec5c786ccd Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:17 +0000 Subject: [PATCH 18/36] thermal/drivers/samsung: Add Exynos ACPM TMU driver GS101 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add driver for the Thermal Management Unit (TMU) managed via the Alive Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as the Google GS101. The TMU on the GS101 utilizes a hybrid management model shared between the Application Processor (AP) and the ACPM firmware. The driver maintains direct memory-mapped access to the TMU interrupt pending registers to identify thermal events, while delegating functional tasks - such as sensor initialization, threshold configuration, and temperature acquisition, to the ACPM firmware via the ACPM IPC protocol. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Acked-by: Peter Griffin Reviewed-by: Alexey Klimov Reviewed-by: André Draszik Link: https://patch.msgid.link/20260603-acpm-tmu-v8-2-0f1810a356e6@linaro.org --- drivers/thermal/samsung/Kconfig | 19 + drivers/thermal/samsung/Makefile | 2 + drivers/thermal/samsung/acpm-tmu.c | 651 +++++++++++++++++++++++++++++ 3 files changed, 672 insertions(+) create mode 100644 drivers/thermal/samsung/acpm-tmu.c diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index e1e8035d24fb..f63eeae304b7 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -10,3 +10,22 @@ config EXYNOS_THERMAL the TMU, reports temperature and handles cooling action if defined. This driver uses the Exynos core thermal APIs and TMU configuration data from the supported SoCs. + +config EXYNOS_ACPM_THERMAL + tristate "Exynos ACPM thermal management unit driver" + depends on THERMAL_OF + depends on HAS_IOMEM + depends on EXYNOS_ACPM_PROTOCOL + default ARCH_EXYNOS + help + Support for the Thermal Management Unit (TMU) on Samsung Exynos SoCs + utilizing the ACPM IPC protocol, such as the Google GS101. + + The TMU on these platforms is managed through a hybrid architecture. + This driver handles direct register access for thermal interrupt status + monitoring and communicates with the Alive Clock and Power Manager + (ACPM) firmware via the ACPM IPC protocol for functional sensor control + and configuration. + + Select this if you want to monitor device temperature and enable + thermal mitigation on Samsung Exynos ACPM based devices. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index f139407150d2..daed80647c34 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -4,3 +4,5 @@ # obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o exynos_thermal-y := exynos_tmu.o +obj-$(CONFIG_EXYNOS_ACPM_THERMAL) += exynos_acpm_thermal.o +exynos_acpm_thermal-y := acpm-tmu.o diff --git a/drivers/thermal/samsung/acpm-tmu.c b/drivers/thermal/samsung/acpm-tmu.c new file mode 100644 index 000000000000..f9802080acd7 --- /dev/null +++ b/drivers/thermal/samsung/acpm-tmu.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2019 Samsung Electronics Co., Ltd. + * Copyright 2025 Google LLC. + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define EXYNOS_TMU_SENSOR(i) BIT(i) +#define EXYNOS_TMU_SENSORS_MAX_COUNT 16 + +#define GS101_CPUCL2_SENSOR_MASK (EXYNOS_TMU_SENSOR(0) | \ + EXYNOS_TMU_SENSOR(6) | \ + EXYNOS_TMU_SENSOR(7) | \ + EXYNOS_TMU_SENSOR(8) | \ + EXYNOS_TMU_SENSOR(9)) +#define GS101_CPUCL1_SENSOR_MASK (EXYNOS_TMU_SENSOR(4) | \ + EXYNOS_TMU_SENSOR(5)) +#define GS101_CPUCL0_SENSOR_MASK (EXYNOS_TMU_SENSOR(1) | \ + EXYNOS_TMU_SENSOR(2)) + +#define GS101_REG_INTPEND(i) ((i) * 0x50 + 0xf8) + +enum { + P0_INTPEND, + P1_INTPEND, + P2_INTPEND, + P3_INTPEND, + P4_INTPEND, + P5_INTPEND, + P6_INTPEND, + P7_INTPEND, + P8_INTPEND, + P9_INTPEND, + P10_INTPEND, + P11_INTPEND, + P12_INTPEND, + P13_INTPEND, + P14_INTPEND, + P15_INTPEND, + REG_INTPEND_COUNT, +}; + +struct acpm_tmu_sensor_group { + u16 mask; + u8 id; +}; + +struct acpm_tmu_sensor { + const struct acpm_tmu_sensor_group *group; + struct thermal_zone_device *tzd; + struct acpm_tmu_priv *priv; + struct mutex lock; /* protects sensor state */ + bool enabled; +}; + +struct acpm_tmu_priv { + struct regmap_field *regmap_fields[REG_INTPEND_COUNT]; + struct acpm_handle *handle; + struct device *dev; + struct clk *clk; + unsigned int mbox_chan_id; + unsigned int num_sensors; + int irq; + struct acpm_tmu_sensor sensors[] __counted_by(num_sensors); +}; + +struct acpm_tmu_driver_data { + const struct reg_field *reg_fields; + const struct acpm_tmu_sensor_group *sensor_groups; + unsigned int num_sensor_groups; + unsigned int mbox_chan_id; +}; + +#define ACPM_TMU_SENSOR_GROUP(_mask, _id) \ + { \ + .mask = _mask, \ + .id = _id, \ + } + +static const struct acpm_tmu_sensor_group gs101_sensor_groups[] = { + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL2_SENSOR_MASK, 0), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL1_SENSOR_MASK, 1), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL0_SENSOR_MASK, 2), +}; + +static const struct reg_field gs101_reg_fields[REG_INTPEND_COUNT] = { + [P0_INTPEND] = REG_FIELD(GS101_REG_INTPEND(0), 0, 31), + [P1_INTPEND] = REG_FIELD(GS101_REG_INTPEND(1), 0, 31), + [P2_INTPEND] = REG_FIELD(GS101_REG_INTPEND(2), 0, 31), + [P3_INTPEND] = REG_FIELD(GS101_REG_INTPEND(3), 0, 31), + [P4_INTPEND] = REG_FIELD(GS101_REG_INTPEND(4), 0, 31), + [P5_INTPEND] = REG_FIELD(GS101_REG_INTPEND(5), 0, 31), + [P6_INTPEND] = REG_FIELD(GS101_REG_INTPEND(6), 0, 31), + [P7_INTPEND] = REG_FIELD(GS101_REG_INTPEND(7), 0, 31), + [P8_INTPEND] = REG_FIELD(GS101_REG_INTPEND(8), 0, 31), + [P9_INTPEND] = REG_FIELD(GS101_REG_INTPEND(9), 0, 31), + [P10_INTPEND] = REG_FIELD(GS101_REG_INTPEND(10), 0, 31), + [P11_INTPEND] = REG_FIELD(GS101_REG_INTPEND(11), 0, 31), + [P12_INTPEND] = REG_FIELD(GS101_REG_INTPEND(12), 0, 31), + [P13_INTPEND] = REG_FIELD(GS101_REG_INTPEND(13), 0, 31), + [P14_INTPEND] = REG_FIELD(GS101_REG_INTPEND(14), 0, 31), + [P15_INTPEND] = REG_FIELD(GS101_REG_INTPEND(15), 0, 31), +}; + +static const struct regmap_config gs101_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .use_relaxed_mmio = true, + .max_register = GS101_REG_INTPEND(15), +}; + +static const struct acpm_tmu_driver_data acpm_tmu_gs101 = { + .reg_fields = gs101_reg_fields, + .sensor_groups = gs101_sensor_groups, + .num_sensor_groups = ARRAY_SIZE(gs101_sensor_groups), + .mbox_chan_id = 9, +}; + +static int acpm_tmu_op_tz_control(struct acpm_tmu_sensor *sensor, bool on) +{ + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret; + + ret = ops->tz_control(handle, priv->mbox_chan_id, sensor->group->id, + on); + if (ret) + return ret; + + sensor->enabled = on; + + return 0; +} + +static void acpm_tmu_control_rollback(struct acpm_tmu_priv *priv, int start_idx) +{ + int i; + + for (i = start_idx; i >= 0; i--) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + int ret; + + if (!sensor->tzd) + continue; + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, false); + mutex_unlock(&sensor->lock); + if (ret) + dev_err(priv->dev, "Rollback: Failed to disable sensor %d: %d\n", + i, ret); + } +} + +static int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on, bool rollback) +{ + struct device *dev = priv->dev; + int i, ret, err = 0; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + /* Skip sensors that weren't found in DT */ + if (!sensor->tzd) + continue; + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, on); + mutex_unlock(&sensor->lock); + if (ret) { + if (!err) + err = ret; + + /* On enable, stop on first error if rollback is requested */ + if (on && rollback) { + acpm_tmu_control_rollback(priv, i - 1); + break; + } + } + } + + pm_runtime_put_sync(dev); + + return err; +} + +static int acpm_tmu_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + struct device *dev = priv->dev; + int acpm_temp = 0, ret; + + /* + * Fast path: check locklessly if the sensor is enabled to avoid + * expensive runtime PM operations when it is disabled. Any race with + * concurrent disabling is caught by the second check under the lock + * after PM resume. + */ + if (!sensor->enabled) + return -EAGAIN; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + scoped_guard(mutex, &sensor->lock) { + if (!sensor->enabled) { + ret = -EAGAIN; + } else { + ret = ops->read_temp(handle, priv->mbox_chan_id, + sensor->group->id, &acpm_temp); + } + } + + pm_runtime_put_autosuspend(dev); + + if (ret) + return ret; + + *temp = acpm_temp * MILLIDEGREE_PER_DEGREE; + + return 0; +} + +static int acpm_tmu_update_thresholds(struct acpm_tmu_sensor *sensor, + u8 thresholds[2], u8 inten) +{ + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + unsigned int mbox_chan_id = priv->mbox_chan_id; + u8 acpm_sensor_id = sensor->group->id; + bool was_enabled; + int ret, restore_ret; + + guard(mutex)(&sensor->lock); + + was_enabled = sensor->enabled; + + if (was_enabled) { + ret = acpm_tmu_op_tz_control(sensor, false); + if (ret) + return ret; + } + + ret = ops->set_threshold(handle, mbox_chan_id, acpm_sensor_id, + thresholds, 2); + if (!ret) { + ret = ops->set_interrupt_enable(handle, mbox_chan_id, + acpm_sensor_id, inten); + } + + if (was_enabled) { + restore_ret = acpm_tmu_op_tz_control(sensor, true); + if (restore_ret) + dev_err(priv->dev, "Failed to restore sensor state: %d\n", + restore_ret); + if (!ret) + ret = restore_ret; + } + + return ret; +} + +static int acpm_tmu_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + struct device *dev = priv->dev; + u8 thresholds[2] = {}; + u8 inten = 0; + int ret; + + /* If a valid lower bound exists, set the threshold and enable its interrupt */ + if (low > -INT_MAX) { + thresholds[0] = clamp_val(low / MILLIDEGREE_PER_DEGREE, 0, 255); + inten |= BIT(0); + } + + /* If a valid upper bound exists, set the threshold and enable its interrupt */ + if (high < INT_MAX) { + thresholds[1] = clamp_val(high / MILLIDEGREE_PER_DEGREE, 0, 255); + inten |= BIT(1); + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = acpm_tmu_update_thresholds(sensor, thresholds, inten); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const struct thermal_zone_device_ops acpm_tmu_sensor_ops = { + .get_temp = acpm_tmu_get_temp, + .set_trips = acpm_tmu_set_trips, +}; + +static int acpm_tmu_has_pending_irq(struct acpm_tmu_sensor *sensor, + bool *pending_irq) +{ + struct acpm_tmu_priv *priv = sensor->priv; + unsigned long mask = sensor->group->mask; + int i, ret; + u32 val; + + guard(mutex)(&sensor->lock); + + for_each_set_bit(i, &mask, EXYNOS_TMU_SENSORS_MAX_COUNT) { + ret = regmap_field_read(priv->regmap_fields[i], &val); + if (ret) + return ret; + + if (val) { + *pending_irq = true; + break; + } + } + + return 0; +} + +static irqreturn_t acpm_tmu_thread_fn(int irq, void *id) +{ + struct acpm_tmu_priv *priv = id; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + struct device *dev = priv->dev; + bool handled = false; + int i, ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "Failed to resume: %d\n", ret); + return IRQ_NONE; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + bool pending_irq = false; + + if (!sensor->tzd) + continue; + + ret = acpm_tmu_has_pending_irq(sensor, &pending_irq); + if (ret || !pending_irq) + continue; + + handled = true; + + scoped_guard(mutex, &sensor->lock) { + ret = ops->clear_tz_irq(handle, priv->mbox_chan_id, + sensor->group->id); + if (ret) + dev_err(priv->dev, "Sensor %d: failed to clear IRQ (%d)\n", + i, ret); + } + + thermal_zone_device_update(sensor->tzd, + THERMAL_EVENT_UNSPECIFIED); + } + + pm_runtime_put_autosuspend(dev); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static const struct of_device_id acpm_tmu_match[] = { + { .compatible = "google,gs101-tmu-top" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, acpm_tmu_match); + +static int acpm_tmu_probe(struct platform_device *pdev) +{ + const struct acpm_tmu_driver_data *data = &acpm_tmu_gs101; + struct acpm_handle *acpm_handle; + struct device *dev = &pdev->dev; + struct acpm_tmu_priv *priv; + struct regmap *regmap; + void __iomem *base; + int i, ret; + + acpm_handle = devm_acpm_get_by_phandle(dev); + if (IS_ERR(acpm_handle)) + return dev_err_probe(dev, PTR_ERR(acpm_handle), + "Failed to get ACPM handle\n"); + + priv = devm_kzalloc(dev, + struct_size(priv, sensors, data->num_sensor_groups), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->handle = acpm_handle; + priv->mbox_chan_id = data->mbox_chan_id; + priv->num_sensors = data->num_sensor_groups; + + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), "Failed to ioremap resource\n"); + + regmap = devm_regmap_init_mmio(dev, base, &gs101_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->regmap_fields, + data->reg_fields, REG_INTPEND_COUNT); + if (ret) + return dev_err_probe(dev, ret, + "Unable to map syscon registers\n"); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Failed to get the clock\n"); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return dev_err_probe(dev, priv->irq, "Failed to get irq\n"); + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to resume device\n"); + + ret = acpm_handle->ops->tmu.init(acpm_handle, priv->mbox_chan_id); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to init TMU\n"); + goto err_pm_put; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + mutex_init(&sensor->lock); + sensor->group = &data->sensor_groups[i]; + sensor->priv = priv; + + sensor->tzd = devm_thermal_of_zone_register(dev, i, sensor, + &acpm_tmu_sensor_ops); + if (IS_ERR(sensor->tzd)) { + ret = PTR_ERR(sensor->tzd); + if (ret == -ENODEV) { + sensor->tzd = NULL; + dev_dbg(dev, "Sensor %d not used in DT, skipping\n", i); + continue; + } + + ret = dev_err_probe(dev, ret, "Failed to register sensor %d\n", i); + goto err_rollback; + } + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, true); + mutex_unlock(&sensor->lock); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to enable sensor %d\n", i); + goto err_rollback; + } + + thermal_zone_device_update(sensor->tzd, + THERMAL_EVENT_UNSPECIFIED); + + ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs!\n"); + } + + ret = devm_request_threaded_irq(dev, priv->irq, NULL, + acpm_tmu_thread_fn, IRQF_ONESHOT, + dev_name(dev), priv); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to request irq\n"); + goto err_rollback; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_rollback: + acpm_tmu_control_rollback(priv, i - 1); +err_pm_put: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_sync(dev); + return ret; +} + +static void acpm_tmu_remove(struct platform_device *pdev) +{ + struct acpm_tmu_priv *priv = platform_get_drvdata(pdev); + + /* Stop IRQ first to prevent race with thread_fn */ + disable_irq(priv->irq); + + /* + * Disable autosuspend to force the subsequent pm_runtime_put_sync() + * inside acpm_tmu_control() to synchronously suspend the device + * immediately, preventing clock leaks when the driver is removed. + */ + pm_runtime_dont_use_autosuspend(&pdev->dev); + acpm_tmu_control(priv, false, false); +} + +static int acpm_tmu_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret, restore_ret; + + ret = acpm_tmu_control(priv, false, false); + if (ret) + goto err_restore_sensors; + + /* APB clock not required for this specific msg */ + ret = ops->suspend(handle, priv->mbox_chan_id); + if (ret) + goto err_restore_sensors; + + ret = pm_runtime_force_suspend(dev); + if (ret) + goto err_resume_acpm; + + return 0; + +err_resume_acpm: + restore_ret = ops->resume(handle, priv->mbox_chan_id); + if (restore_ret) + dev_err(dev, "Failed to resume ACPM after force suspend failure: %d\n", + restore_ret); + +err_restore_sensors: + restore_ret = acpm_tmu_control(priv, true, false); + if (restore_ret) + dev_err(dev, "Failed to re-enable TMU after suspend failure: %d\n", + restore_ret); + + return ret; +} + +static int acpm_tmu_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret, restore_ret; + + /* APB clock not required for this specific msg */ + ret = ops->resume(handle, priv->mbox_chan_id); + if (ret) + return ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + goto err_suspend_acpm; + + ret = acpm_tmu_control(priv, true, true); + if (ret) + goto err_suspend_pm; + + return 0; + +err_suspend_pm: + restore_ret = pm_runtime_force_suspend(dev); + if (restore_ret) + dev_err(dev, "Failed to force suspend during resume rollback: %d\n", + restore_ret); +err_suspend_acpm: + restore_ret = ops->suspend(handle, priv->mbox_chan_id); + if (restore_ret) + dev_err(dev, "Failed to suspend ACPM during resume rollback: %d\n", + restore_ret); + return ret; +} + +static int acpm_tmu_runtime_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int acpm_tmu_runtime_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + return clk_prepare_enable(priv->clk); +} + +static const struct dev_pm_ops acpm_tmu_pm_ops = { + SYSTEM_SLEEP_PM_OPS(acpm_tmu_suspend, acpm_tmu_resume) + RUNTIME_PM_OPS(acpm_tmu_runtime_suspend, acpm_tmu_runtime_resume, NULL) +}; + +static struct platform_driver acpm_tmu_driver = { + .driver = { + .name = "gs-tmu", + .pm = pm_ptr(&acpm_tmu_pm_ops), + .of_match_table = acpm_tmu_match, + }, + .probe = acpm_tmu_probe, + .remove = acpm_tmu_remove, +}; +module_platform_driver(acpm_tmu_driver); + +MODULE_AUTHOR("Tudor Ambarus "); +MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver"); +MODULE_LICENSE("GPL"); From 05657c7822f2e8f21ccd6c11f2eb207896f5f87a Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:18 +0000 Subject: [PATCH 19/36] MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver Add a MAINTAINERS entry for the Samsung Exynos ACPM thermal driver. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260603-acpm-tmu-v8-3-0f1810a356e6@linaro.org --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c..767bb3e862d1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24298,6 +24298,14 @@ F: drivers/clk/samsung/clk-acpm.c F: drivers/firmware/samsung/exynos-acpm* F: include/linux/firmware/samsung/exynos-acpm-protocol.h +SAMSUNG EXYNOS ACPM THERMAL DRIVER +M: Tudor Ambarus +L: linux-kernel@vger.kernel.org +L: linux-samsung-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml +F: drivers/thermal/samsung/acpm-tmu.c + SAMSUNG EXYNOS MAILBOX DRIVER M: Tudor Ambarus L: linux-samsung-soc@vger.kernel.org From f4428025c8b54d8a6e0fb15589e08b097b7b9a76 Mon Sep 17 00:00:00 2001 From: Liu Jing Date: Thu, 3 Sep 2026 16:28:58 +0800 Subject: [PATCH 20/36] tools/thermal/tmon: Fix signed shift UB in binding display In the thermal zone binding display loop, the expression `(1 << j)` performs a signed integer shift. When j reaches 31, this sets the sign bit of a 32-bit signed integer, which is undefined behavior. Fix it by using `(1U << j)` to perform an unsigned shift. Signed-off-by: Liu Jing Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260903082858.5273-1-liujing@cmss.chinamobile.com --- tools/thermal/tmon/tmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c index 7eb3216a27f4..ef67cd1a4861 100644 --- a/tools/thermal/tmon/tmon.c +++ b/tools/thermal/tmon/tmon.c @@ -171,7 +171,7 @@ static void prepare_logging(void) memset(binding_str, 0, sizeof(binding_str)); for (j = 0; j < 32; j++) - binding_str[j] = (ptdata.tzi[i].cdev_binding & (1 << j)) ? + binding_str[j] = (ptdata.tzi[i].cdev_binding & (1U << j)) ? '1' : '0'; fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", From 481860eba83b3fe27ca9be6f7f5bb4c8febc6572 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 10 Jun 2026 11:20:37 +0200 Subject: [PATCH 21/36] thermal: gov_power_allocator: Use common error handling code in power_allocator_bind() Adjust labels so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/a3dc82f0-7333-465d-85b4-8034b1591442@web.de --- drivers/thermal/gov_power_allocator.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c index 37f2e22a999e..6b9450ffd5de 100644 --- a/drivers/thermal/gov_power_allocator.c +++ b/drivers/thermal/gov_power_allocator.c @@ -707,22 +707,20 @@ static int power_allocator_bind(struct thermal_zone_device *tz) ret = check_power_actors(tz, params); if (ret < 0) { dev_warn(&tz->device, "power_allocator: binding failed\n"); - kfree(params); - return ret; + goto free_params; } ret = allocate_actors_buffer(params, ret); if (ret) { dev_warn(&tz->device, "power_allocator: allocation failed\n"); - kfree(params); - return ret; + goto free_params; } if (!tz->tzp) { tz->tzp = kzalloc_obj(*tz->tzp); if (!tz->tzp) { ret = -ENOMEM; - goto free_params; + goto free_power; } params->allocated_tzp = true; @@ -746,8 +744,9 @@ static int power_allocator_bind(struct thermal_zone_device *tz) return 0; -free_params: +free_power: kfree(params->power); +free_params: kfree(params); return ret; From 51c262cf2aa9dfd97591e570d4e82af493ad1689 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Mon, 28 Sep 2026 16:28:23 +0300 Subject: [PATCH 22/36] thermal/drivers/qcom-spmi-temp-alarm: Fix temp_map indexing Fix the following issue noticed when booting on the db845c-dragonboard: BUG: KASAN: global-out-of-bounds in qpnp_tm_sync_thresholds+0x11c/0x180 [qcom_spmi_temp_alarm] Read of size 24 at addr ffffdd2495f01200 by task (udev-worker)/371 CPU: 3 UID: 0 PID: 371 Comm: (udev-worker) Not tainted 7.3.0-rc3 #116 PREEMPT Hardware name: Thundercomm Dragonboard 845c (DT) Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0xa4/0xf4 print_report+0x1f4/0x5ac kasan_report+0x84/0xcc kasan_check_range+0xf0/0x1a4 memcpy+0x3c/0xa4 qpnp_tm_sync_thresholds+0x11c/0x180 [qcom_spmi_temp_alarm] qpnp_tm_probe+0x3dc/0xae8 [qcom_spmi_temp_alarm] platform_probe+0xc4/0x18c really_probe+0x17c/0x4fc ... The buggy address belongs to the variable: temp_map_gen1+0x60/0xc4 [qcom_spmi_temp_alarm] It looks like temp_map is a pointer to the complete temperature map. Indexing it directly advances the entire map, rather than selecting a threshold row. Fix this by dereferencing the temp_map before indexing it to select the requested threshold row. Fixes: 703f13285a6c ("thermal/drivers/qcom-spmi-temp-alarm: Add temp alarm data struct based on HW subtype") Fixes: 1f835c6a4c84 ("thermal/drivers/qcom-spmi-temp-alarm: Prepare to support additional Temp Alarm subtypes") Signed-off-by: Georgi Djakov Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260928132823.1426513-1-djakov@kernel.org --- drivers/thermal/qcom/qcom-spmi-temp-alarm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c index fb003ca96454..911ebfd0cdeb 100644 --- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c @@ -345,8 +345,8 @@ static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip, } skip: - memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold], - sizeof(chip->temp_thresh_map)); + memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold], + sizeof(chip->temp_thresh_map)); reg |= threshold; if (disable_stage2_shutdown && !chip->require_stage2_shutdown) reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2; @@ -535,8 +535,8 @@ static int qpnp_tm_sync_thresholds(struct qpnp_tm_chip *chip) return ret; threshold = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK; - memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold], - sizeof(chip->temp_thresh_map)); + memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold], + sizeof(chip->temp_thresh_map)); return ret; } From aaa6c8dec49339710f9fd2a2af5646cf4111dbd4 Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 26 Sep 2026 22:49:31 +1000 Subject: [PATCH 23/36] thermal/drivers/ti-soc-thermal: Cancel pending alert work on remove On a threshold crossing, the talert IRQ handler calls ti_thermal_report_sensor_temperature(), which queues the work embedded in the sensor's ti_thermal_data on the system workqueue. Nothing ever cancels or flushes that work. ti_bandgap_remove() frees the talert IRQ last. free_irq() waits for a running handler, but not for the work the handler has queued. When remove returns, devres unregisters and frees the thermal zones and then frees the devm-allocated ti_thermal_data, so a work item that is still pending runs ti_thermal_work() on freed memory: ti_thermal_work() data = container_of(work, struct ti_thermal_data, thermal_wq); thermal_zone_device_update(data->ti_thermal, ...); Free the talert IRQ before removing the sensors, so that no new work can be queued, and cancel the work in ti_thermal_remove_sensor(). This needs an OMAP4460/4470 or OMAP5 SoC (DRA7 has TALERT but no ->report_temperature, so it never queues the work), a threshold crossing just before the driver is unbound or unloaded, and the work still pending when devres frees the data. Found by static analysis tool CodeQL. Fixes: 445eaf871bf9 ("staging: omap-thermal: common code to expose driver to thermal framework") Assisted-by: LLM codeql Signed-off-by: Weigang He Signed-off-by: Daniel Lezcano Acked-by: Keerthy Link: https://patch.msgid.link/20260926124931.3599746-1-geoffreyhe2@gmail.com --- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 7 ++++--- drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index ba43399d0b38..507fe67eaffa 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -1077,6 +1077,10 @@ void ti_bandgap_remove(struct platform_device *pdev) if (!soc_device_match(soc_no_cpu_notifier)) cpu_pm_unregister_notifier(&bgp->nb); + /* Stop the alerts first: they queue work on the sensors' data */ + if (TI_BANDGAP_HAS(bgp, TALERT)) + free_irq(bgp->irq, bgp); + /* Remove sensor interfaces */ for (i = 0; i < bgp->conf->sensor_count; i++) { if (bgp->conf->sensors[i].unregister_cooling) @@ -1093,9 +1097,6 @@ void ti_bandgap_remove(struct platform_device *pdev) clk_put(bgp->fclock); clk_put(bgp->div_clk); - if (TI_BANDGAP_HAS(bgp, TALERT)) - free_irq(bgp->irq, bgp); - if (TI_BANDGAP_HAS(bgp, TSHUT)) free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL); } diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 0cf0826b805a..4bf3b2e0fc68 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -195,6 +195,10 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) data = ti_bandgap_get_sensor_data(bgp, id); + /* Work queued by the talert IRQ must not outlive the data */ + if (!IS_ERR_OR_NULL(data)) + cancel_work_sync(&data->thermal_wq); + if (!IS_ERR_OR_NULL(data) && data->ti_thermal) { if (data->our_zone) thermal_zone_device_unregister(data->ti_thermal); From b17d799ce49bfaad68be5ff3df995b3bd021a9d7 Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 26 Sep 2026 14:27:57 +1000 Subject: [PATCH 24/36] thermal/drivers/sprd: validate the sensor id from the device tree sprd_thm_probe() reads each sensor child node's "reg" property into sen->id and uses it without validation: to compute the sensor's register offsets in sprd_thm_sensor_init(), as the thermal zone id, and finally as an index into thm->sensor[], which has SPRD_THM_MAX_SENSOR (8) entries: ret = of_property_read_u32(sen_child, "reg", &sen->id); ... thm->sensor[sen->id] = sen; Only the number of sensor nodes is checked against SPRD_THM_MAX_SENSOR. A "reg" value of 8 or more, or one that is negative once stored in the int sen->id, makes the register offsets point outside the sensor range and the final store land outside thm->sensor[], overwriting the fields that follow it in struct sprd_thermal_data or other memory. Reject an out-of-range id right after reading it, before it is used. This does not address sparse ids, such as a single sensor with id 7: they leave holes in thm->sensor[] that the later loops over 0..nr_sensors-1 dereference. That needs a separate fix. Found by static analysis tool CodeQL. Fixes: 554fdbaf19b1 ("thermal: sprd: Add Spreadtrum thermal driver support") Assisted-by: LLM codeql Signed-off-by: Weigang He Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260926042757.2109927-1-geoffreyhe2@gmail.com --- drivers/thermal/sprd_thermal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c index d683fcb0f8ab..b57277e2eac0 100644 --- a/drivers/thermal/sprd_thermal.c +++ b/drivers/thermal/sprd_thermal.c @@ -396,6 +396,12 @@ static int sprd_thm_probe(struct platform_device *pdev) goto of_put; } + if (sen->id < 0 || sen->id >= SPRD_THM_MAX_SENSOR) { + dev_err(&pdev->dev, "invalid sensor id %d\n", sen->id); + ret = -EINVAL; + goto of_put; + } + ret = sprd_thm_sensor_calibration(sen_child, thm, sen); if (ret) { dev_err(&pdev->dev, "efuse cal analysis failed"); From 4a0f3193c68eb41882ccf7c298f5cd0b17efb8ab Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:06 +0300 Subject: [PATCH 25/36] thermal/drivers/airoha: Fix AN7583 ADC mux field mapping On AN7583, bits 3:1 of 0x2e4 select the sensor (0 bandgap, 5 GbE, 6 CPU) and bits 3:2 of 0x2a0 select the diode (0 D0, 1 ZERO, 2 D1). The driver names them the other way round: MUX_TADC is 0x2e4, MUX_SENSOR is 0x2a0. v5 of the AN7583 series swapped the airoha_set_thermal_mux() arguments to match these names. The diode index now goes to the sensor selector, so the zone reads mux positions 1, 0 and 2 (PAD_AVS, bandgap, core voltage) instead of the bandgap diodes. On a Nokia XG-040G-MF the result crosses the critical trip and the board powers off during boot. Swap the field definitions. MUX_TADC then selects the diode, as on EN7581, and the existing call order is correct. Fixes: b20d9782756a ("thermal/drivers/airoha: Add support for AN7583 Thermal Sensor") Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-2-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index ea3e2649c280..1b187c41baea 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -18,10 +18,10 @@ #define EN7581_DOUT_TADC 0x2f8 #define EN7581_DOUT_TADC_MASK GENMASK(15, 0) -#define AN7583_MUX_SENSOR 0x2a0 +#define AN7583_MUX_TADC 0x2a0 #define AN7583_LOAD_ADJ GENMASK(3, 2) -#define AN7583_MUX_TADC 0x2e4 -#define AN7583_MUX_TADC_MASK GENMASK(3, 1) +#define AN7583_MUX_SENSOR 0x2e4 +#define AN7583_MUX_SENSOR_MASK GENMASK(3, 1) #define AN7583_DOUT_TADC 0x2f0 /* PTP_THERMAL regs */ @@ -636,8 +636,8 @@ static const struct thermal_zone_device_ops an7583_tz_ops = { static const struct reg_field an7583_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(AN7583_DOUT_TADC, 0, 31), - [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 1, 3), - [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 2, 3), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 2, 3), + [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 1, 3), }; static int an7583_thermal_probe(struct platform_device *pdev, From b1bf55053abed0d72738bcc12297bfa09f45eeb4 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:07 +0300 Subject: [PATCH 26/36] thermal/drivers/airoha: Serialise access to the shared ADC A read selects the sensor and walks three diodes with a 10 ms settle on each. The zone lock serialises reads of one zone. With a zone per AN7583 sensor, added later in this series, reads of different zones interleave and convert samples taken at another mux position. Mux writes are dropped silently unless PLLRG_PROTECT is lifted, and its save and restore do not nest. The cached TADC value also skips a write after another reader has moved the mux. With the AN7583 sensors read as parallel zones during CPU frequency changes, a zone crossed its critical trip on a chip at 55 C and the board shut down. Serialise the sequence, drop the cache, check the selection after writing and after sampling, and return -EAGAIN if it does not hold. Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-3-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 85 +++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 1b187c41baea..0f0e6f3af3cd 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -191,6 +192,7 @@ #define AN7583_NUM_SENSOR 3 #define AIROHA_THERMAL_NO_MUX_SENSOR -1 +#define AIROHA_THERMAL_MUX_TRIES 3 /* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */ #define TEMP_TO_RAW(priv, temp) ((((((temp) / 100) - (priv)->init_temp) * \ @@ -250,7 +252,8 @@ struct airoha_thermal_priv { struct resource scu_adc_res; u32 pllrg_protect; - int current_adc; + /* Serialises mux selection and ADC sampling */ + struct mutex lock; struct thermal_zone_device *tz; int init_temp; @@ -294,9 +297,26 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) return val; } +static bool airoha_thermal_mux_holds(struct airoha_thermal_priv *priv, + int tdac_idx, int sensor_idx) +{ + unsigned int val; + + if (regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], &val) || + val != tdac_idx) + return false; + + if (sensor_idx == AIROHA_THERMAL_NO_MUX_SENSOR) + return true; + + return !regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], &val) && + val == sensor_idx; +} + static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, int tdac_idx, int sensor_idx) { + int tries = AIROHA_THERMAL_MUX_TRIES; u32 pllrg; /* Save PLLRG current value */ @@ -307,19 +327,17 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, priv->pllrg_protect); /* - * Configure Thermal Sensor mux to sensor_idx. + * Configure Thermal Sensor mux to sensor_idx and Thermal ADC mux to + * tdac_idx. Writes can be dropped silently, so read them back. * (if not supported, sensor_idx is AIROHA_THERMAL_NO_MUX_SENSOR) */ - if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR) - regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], - sensor_idx); - - /* Configure Thermal ADC mux to tdac_idx */ - if (priv->current_adc != tdac_idx) { + do { + if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR) + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], + sensor_idx); regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], tdac_idx); - priv->current_adc = tdac_idx; - } + } while (!airoha_thermal_mux_holds(priv, tdac_idx, sensor_idx) && --tries); /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); @@ -596,12 +614,40 @@ static int en7581_thermal_post_probe(struct platform_device *pdev) return 0; } +static int an7583_thermal_read_diode(struct airoha_thermal_priv *priv, + int diode, int sensor_idx, int *val) +{ + airoha_set_thermal_mux(priv, diode, sensor_idx); + *val = airoha_get_thermal_ADC(priv); + + return airoha_thermal_mux_holds(priv, diode, sensor_idx) ? 0 : -EAGAIN; +} + +static int an7583_thermal_read_diodes(struct airoha_thermal_priv *priv, + int sensor_idx, int *zero, int *d0, + int *d1) +{ + int ret; + + ret = an7583_thermal_read_diode(priv, AN7583_ZERO_TADC, sensor_idx, zero); + if (ret) + return ret; + + ret = an7583_thermal_read_diode(priv, AN7583_D0_TADC, sensor_idx, d0); + if (ret) + return ret; + + return an7583_thermal_read_diode(priv, AN7583_D1_TADC, sensor_idx, d1); +} + static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); int sensor_idx; int delta_diode, delta_gain; int coeff, slope, offset; + int tries = AIROHA_THERMAL_MUX_TRIES; + int ret; int diode_zero, diode_d0, diode_d1; @@ -612,12 +658,14 @@ static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) slope = an7583_thermal_slope[sensor_idx]; offset = an7583_thermal_offset[sensor_idx]; - airoha_set_thermal_mux(priv, AN7583_ZERO_TADC, sensor_idx); - diode_zero = airoha_get_thermal_ADC(priv); - airoha_set_thermal_mux(priv, AN7583_D0_TADC, sensor_idx); - diode_d0 = airoha_get_thermal_ADC(priv); - airoha_set_thermal_mux(priv, AN7583_D1_TADC, sensor_idx); - diode_d1 = airoha_get_thermal_ADC(priv); + mutex_lock(&priv->lock); + do { + ret = an7583_thermal_read_diodes(priv, sensor_idx, &diode_zero, + &diode_d0, &diode_d1); + } while (ret == -EAGAIN && --tries); + mutex_unlock(&priv->lock); + if (ret) + return ret; delta_diode = diode_d1 - diode_d0; delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1); @@ -678,7 +726,10 @@ static int airoha_thermal_probe(struct platform_device *pdev) return -ENOMEM; priv->pllrg_protect = soc_data->pllrg_protect; - priv->current_adc = -1; + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; if (!soc_data->probe) return -EINVAL; From 0cc14454eb4a1478ad23644668f8fb19075fd46f Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:08 +0300 Subject: [PATCH 27/36] dt-bindings: arm: airoha: Use one thermal sensor cell for AN7583 AN7583 has three temperature sensors behind the chip SCU: bandgap, GbE and CPU. With no cells a thermal zone can only refer to the bandgap sensor. Use one cell to select the sensor. The AN7583 compatible is new in 7.3-rc1 and no device tree uses the property yet. Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260929165209.69669-4-sochnev.v.74@gmail.com --- .../devicetree/bindings/arm/airoha,en7581-chip-scu.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml index cc564dc7b414..4a10d024f5fa 100644 --- a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml +++ b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml @@ -26,7 +26,8 @@ properties: maxItems: 1 '#thermal-sensor-cells': - const: 0 + description: Sensor index, 0 bandgap, 1 GbE, 2 CPU. + const: 1 required: - compatible From 06a169d840a0d6ce03c230b115cef7d7cebcc2d4 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:09 +0300 Subject: [PATCH 28/36] thermal/drivers/airoha: Register a thermal zone per AN7583 sensor AN7583 has three temperature sensors and the driver has constants for all of them, but it registers one zone and reads only the bandgap sensor. Register a zone per sensor; the thermal sensor cell selects it. A device tree may describe fewer zones than sensors. EN7581 still registers one zone. Idle and with both cores loaded on a Nokia XG-040G-MF: bandgap 56.9 C -> 59.3 C GbE 58.7 C -> 61.5 C CPU 57.0 C -> 61.1 C Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-5-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 64 +++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 0f0e6f3af3cd..7c63a1b44a6b 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -245,6 +245,11 @@ enum airoha_thermal_chip_scu_field { AIROHA_THERMAL_FIELD_MAX, }; +struct airoha_thermal_zone { + struct airoha_thermal_priv *priv; + int sensor; +}; + struct airoha_thermal_priv { struct regmap *map; struct regmap *chip_scu; @@ -263,6 +268,9 @@ struct airoha_thermal_priv { struct airoha_thermal_soc_data { u32 pllrg_protect; + /* Sensor mux value per zone, NULL without a sensor mux */ + const int *sensors; + int num_sensors; const struct thermal_zone_device_ops *thdev_ops; int (*probe)(struct platform_device *pdev, @@ -348,7 +356,8 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; int min_value, max_value, avg_value, value; int i; @@ -374,7 +383,8 @@ static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) static int en7581_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; bool enable_monitor = false; if (high != INT_MAX) { @@ -642,8 +652,9 @@ static int an7583_thermal_read_diodes(struct airoha_thermal_priv *priv, static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); - int sensor_idx; + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; + int sensor_idx = zone->sensor; int delta_diode, delta_gain; int coeff, slope, offset; int tries = AIROHA_THERMAL_MUX_TRIES; @@ -651,9 +662,6 @@ static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) int diode_zero, diode_d0, diode_d1; - /* Always read sensor AN7583_BGP_TEMP_SENSOR */ - sensor_idx = AN7583_BGP_TEMP_SENSOR; - coeff = an7583_thermal_coeff[sensor_idx]; slope = an7583_thermal_slope[sensor_idx]; offset = an7583_thermal_offset[sensor_idx]; @@ -717,7 +725,7 @@ static int airoha_thermal_probe(struct platform_device *pdev) const struct airoha_thermal_soc_data *soc_data; struct airoha_thermal_priv *priv; struct device *dev = &pdev->dev; - int ret; + int ret, i; soc_data = device_get_match_data(dev); @@ -739,11 +747,31 @@ static int airoha_thermal_probe(struct platform_device *pdev) return ret; /* register of thermal sensor and get info from DT */ - priv->tz = devm_thermal_of_zone_register(dev, 0, priv, - soc_data->thdev_ops); - if (IS_ERR(priv->tz)) { - dev_err(dev, "register thermal zone sensor failed\n"); - return PTR_ERR(priv->tz); + for (i = 0; i < soc_data->num_sensors; i++) { + struct airoha_thermal_zone *zone; + struct thermal_zone_device *tz; + + zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); + if (!zone) + return -ENOMEM; + + zone->priv = priv; + zone->sensor = soc_data->sensors ? soc_data->sensors[i] : + AIROHA_THERMAL_NO_MUX_SENSOR; + + tz = devm_thermal_of_zone_register(dev, i, zone, + soc_data->thdev_ops); + if (IS_ERR(tz)) { + /* A DT may describe fewer zones than sensors */ + if (i && PTR_ERR(tz) == -ENODEV) + continue; + + dev_err(dev, "register thermal zone %d failed\n", i); + return PTR_ERR(tz); + } + + if (!i) + priv->tz = tz; } platform_set_drvdata(pdev, priv); @@ -751,7 +779,15 @@ static int airoha_thermal_probe(struct platform_device *pdev) return soc_data->post_probe ? soc_data->post_probe(pdev) : 0; } +/* The bandgap sensor stays zone 0 */ +static const int an7583_zone_sensors[AN7583_NUM_SENSOR] = { + AN7583_BGP_TEMP_SENSOR, + AN7583_GBE_TEMP_SENSOR, + AN7583_CPU_TEMP_SENSOR, +}; + static const struct airoha_thermal_soc_data en7581_data = { + .num_sensors = 1, .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY, .thdev_ops = &en7581_thdev_ops, .probe = &en7581_thermal_probe, @@ -759,6 +795,8 @@ static const struct airoha_thermal_soc_data en7581_data = { }; static const struct airoha_thermal_soc_data an7583_data = { + .sensors = an7583_zone_sensors, + .num_sensors = AN7583_NUM_SENSOR, .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY, .thdev_ops = &an7583_tz_ops, .probe = &an7583_thermal_probe, From 602da762f7a7c494a95cb204ac005965b214e556 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 29 Sep 2026 13:19:51 -0700 Subject: [PATCH 29/36] tools/thermal/tmon: Fix string truncation warning in optarg copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC warns about possible string truncation with -Wstringop-truncation: tmon.c: In function ‘main’: tmon.c:233:25: warning: ‘__builtin_strncpy’ specified bound 20 equals destination size [-Wstringop-truncation] 233 | strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Replace strncpy() with snprintf() to guarantee NUL-termination and silence the compiler warning. Fixes: 94f69966faf8 ("tools/thermal: Introduce tmon, a tool for thermal subsystem") Signed-off-by: Florian Fainelli Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929201951.408456-1-florian.fainelli@broadcom.com --- tools/thermal/tmon/tmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c index ef67cd1a4861..70a2ec50a461 100644 --- a/tools/thermal/tmon/tmon.c +++ b/tools/thermal/tmon/tmon.c @@ -230,7 +230,7 @@ int main(int argc, char **argv) switch (c) { case 'c': no_control = 0; - strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); + snprintf(ctrl_cdev, sizeof(ctrl_cdev), "%s", optarg); break; case 'd': start_daemon_mode(); From 8195b945794ca3a917131b8a5368bb61e687c927 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Wed, 23 Sep 2026 16:37:50 +0200 Subject: [PATCH 30/36] dt-bindings: thermal: amlogic: describe bus clock for t7-thermal The t7 thermal sensor needs a bus clock, in addition to the existing functional clock, to gate register access to the sensor. This bus clock is specific to each sensor, unlike the functional clock which is shared between sensors. The current binding only describes a single unnamed clock and therefore cannot represent this hardware requirement, so add a second clock entry and clock-names for clarity. Make the second clock mandatory for the t7-thermal compatible only, via an additional if/then/else, to avoid breaking other Amlogic SoCs that still use a single clock. Update the t7 example to match. Fixes: b1c8ccdbd4e9 ("dt-bindings: thermal: amlogic: Add support for T7") Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260923-fix-t7-thermal-clock-v2-1-bff7a09eb487@aliel.fr --- .../bindings/thermal/amlogic,thermal.yaml | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml index 8cfa44dcda58..e72cb39ac635 100644 --- a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml @@ -32,7 +32,15 @@ properties: maxItems: 1 clocks: - maxItems: 1 + minItems: 1 + items: + - description: Bus clock, gates register access to the sensor. + - description: Functional clock of the temperature sensor, shared between sensors. + + clock-names: + items: + - const: bus + - const: core power-domains: maxItems: 1 @@ -72,6 +80,22 @@ allOf: else: required: - amlogic,secure-monitor + - if: + properties: + compatible: + contains: + const: amlogic,t7-thermal + then: + properties: + clocks: + minItems: 2 + required: + - clock-names + else: + properties: + clocks: + maxItems: 1 + clock-names: false unevaluatedProperties: false @@ -94,7 +118,8 @@ examples: compatible = "amlogic,t7-thermal"; reg = <0x20000 0x50>; interrupts = ; - clocks = <&clkc_periphs CLKID_TS>; + clocks = <&clkc_periphs CLKID_SYS_TS_A73>, <&clkc_periphs CLKID_TS>; + clock-names = "bus", "core"; #thermal-sensor-cells = <0>; amlogic,secure-monitor = <&sm 1>; }; From 44b39b4fb0f069a3bbe6bfb21a34d2d3730f393a Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Wed, 23 Sep 2026 16:37:51 +0200 Subject: [PATCH 31/36] thermal/drivers/amlogic: Switch to clk_bulk API to support multiple clocks The t7-thermal variant now exposes two clocks (bus and core) in its devicetree binding, but the driver only knows how to get and manage a single struct clk. Using devm_clk_get() would silently ignore the second clock, so switch to devm_clk_bulk_get_all() and the matching clk_bulk_prepare_enable()/clk_bulk_disable_unprepare() calls. This lets the driver transparently handle however many clocks a given compatible declares, without needing per-variant clock-handling code, and keeps working for existing SoCs that only have one. Fixes: 18d65de8157c ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260923-fix-t7-thermal-clock-v2-2-bff7a09eb487@aliel.fr --- drivers/thermal/amlogic_thermal.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c index a0b530624b60..60728de6968a 100644 --- a/drivers/thermal/amlogic_thermal.c +++ b/drivers/thermal/amlogic_thermal.c @@ -100,7 +100,8 @@ struct amlogic_thermal { const struct amlogic_thermal_data *data; struct regmap *regmap; struct regmap *sec_ao_map; - struct clk *clk; + struct clk_bulk_data *clks; + int num_clks; struct thermal_zone_device *tzd; u32 trim_info; struct meson_sm_firmware *sm_fw; @@ -142,7 +143,7 @@ static int amlogic_thermal_enable(struct amlogic_thermal *data) { int ret; - ret = clk_prepare_enable(data->clk); + ret = clk_bulk_prepare_enable(data->num_clks, data->clks); if (ret) return ret; @@ -156,7 +157,7 @@ static void amlogic_thermal_disable(struct amlogic_thermal *data) { regmap_update_bits(data->regmap, TSENSOR_CFG_REG1, TSENSOR_CFG_REG1_ENABLE, 0); - clk_disable_unprepare(data->clk); + clk_bulk_disable_unprepare(data->num_clks, data->clks); } static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp) @@ -323,9 +324,10 @@ static int amlogic_thermal_probe(struct platform_device *pdev) if (IS_ERR(pdata->regmap)) return PTR_ERR(pdata->regmap); - pdata->clk = devm_clk_get(dev, NULL); - if (IS_ERR(pdata->clk)) - return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n"); + ret = devm_clk_bulk_get_all(dev, &pdata->clks); + if (ret <= 0) + return dev_err_probe(dev, ret ?: -ENOENT, "failed to get clocks\n"); + pdata->num_clks = ret; if (pdata->data->use_sm) ret = amlogic_thermal_probe_sm(pdev, pdata); From 200265cfa885553da708a4e677ec10df98d3f68b Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Sun, 11 Oct 2026 19:51:25 +0300 Subject: [PATCH 32/36] dt-bindings: thermal: tsens: Add MSM8952 Add compatible string for tsens block found in MSM8952. Signed-off-by: Muzaffer Kadir Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20261011-msm8952-initial-support-v5-3-1857074992ec@mainlining.org --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index 5a8f7673e730..c78074fbcecf 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -46,6 +46,7 @@ properties: items: - enum: - qcom,msm8937-tsens + - qcom,msm8952-tsens - qcom,msm8956-tsens - qcom,msm8976-tsens - qcom,qcs404-tsens From 8951eb8a781ea4704898ea61bbf52435ff1ee3a4 Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Sun, 11 Oct 2026 19:51:26 +0300 Subject: [PATCH 33/36] thermal/qcom/tsens: Add support for MSM8952 tsens Add support for tsens v1 block that exists on MSM8952 with 11 tsens sensors. Sensor Offsets are taken from downstream. Signed-off-by: Muzaffer Kadir Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://android.googlesource.com/kernel/msm.git/+/refs/tags/android-wear-7.0.0_r0.3/drivers/thermal/msm-tsens.c#1621 Link: https://patch.msgid.link/20261011-msm8952-initial-support-v5-4-1857074992ec@mainlining.org --- drivers/thermal/qcom/tsens-v1.c | 41 +++++++++++++++++++++++++++++++++ drivers/thermal/qcom/tsens.c | 3 +++ drivers/thermal/qcom/tsens.h | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c index faa5d00788ca..7bda8e6087c4 100644 --- a/drivers/thermal/qcom/tsens-v1.c +++ b/drivers/thermal/qcom/tsens-v1.c @@ -145,6 +145,34 @@ static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = { [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0), }; +static int __init init_8952(struct tsens_priv *priv) +{ + priv->sensor[0].p1_calib_offset = 2; + priv->sensor[0].p2_calib_offset = -1; + priv->sensor[1].p1_calib_offset = -4; + priv->sensor[1].p2_calib_offset = -4; + priv->sensor[2].p1_calib_offset = 4; + priv->sensor[2].p2_calib_offset = 5; + priv->sensor[3].p1_calib_offset = 1; + priv->sensor[3].p2_calib_offset = 1; + priv->sensor[4].p1_calib_offset = 2; + priv->sensor[4].p2_calib_offset = 3; + priv->sensor[5].p1_calib_offset = -1; + priv->sensor[5].p2_calib_offset = -1; + priv->sensor[6].p1_calib_offset = 0; + priv->sensor[6].p2_calib_offset = -1; + priv->sensor[7].p1_calib_offset = 3; + priv->sensor[7].p2_calib_offset = 4; + priv->sensor[8].p1_calib_offset = 2; + priv->sensor[8].p2_calib_offset = 4; + priv->sensor[9].p1_calib_offset = 0; + priv->sensor[9].p2_calib_offset = 0; + priv->sensor[10].p1_calib_offset = -3; + priv->sensor[10].p2_calib_offset = -2; + + return init_common(priv); +} + static int __init init_8956(struct tsens_priv *priv) { priv->sensor[0].slope = 3313; priv->sensor[1].slope = 3275; @@ -223,6 +251,19 @@ struct tsens_plat_data data_8937 = { .fields = tsens_v1_regfields, }; +static const struct tsens_ops ops_8952 = { + .init = init_8952, + .calibrate = tsens_calibrate_common, + .get_temp = get_temp_tsens_valid, +}; + +struct tsens_plat_data data_8952 = { + .num_sensors = 11, + .ops = &ops_8952, + .feat = &tsens_v1_feat, + .fields = tsens_v1_regfields, +}; + static const struct tsens_ops ops_8956 = { .init = init_8956, .calibrate = tsens_calibrate_common, diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index b5ec70201e2f..4d7013fc79d6 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1191,6 +1191,9 @@ static const struct of_device_id tsens_table[] = { }, { .compatible = "qcom,msm8939-tsens", .data = &data_8939, + }, { + .compatible = "qcom,msm8952-tsens", + .data = &data_8952, }, { .compatible = "qcom,msm8956-tsens", .data = &data_8956, diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index e8376accdff3..a35b15312961 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -669,7 +669,7 @@ extern struct tsens_plat_data data_8960; extern struct tsens_plat_data data_8226, data_8909, data_8916, data_8939, data_8974, data_9607; /* TSENS v1 targets */ -extern struct tsens_plat_data data_tsens_v1, data_8937, data_8976, data_8956; +extern struct tsens_plat_data data_tsens_v1, data_8937, data_8952, data_8976, data_8956; /* TSENS v1 with no RPM targets */ extern const struct tsens_plat_data data_ipq5018; From 379ae6766d1a7a1f78f1d97b6c72a262f14960e8 Mon Sep 17 00:00:00 2001 From: Aryan Sinha Date: Mon, 12 Oct 2026 20:50:32 +0530 Subject: [PATCH 34/36] thermal/fw: Fix kernel-doc parameter names The kernel-doc for thermal_fwnode_cooling_device_register() still names the old device tree arguments np and of_index, and devm_thermal_fwnode_cooling_device_register() documents fw_index rather than fwn_index. Rename them to match the code and reword the device tree reference as a firmware node one. Signed-off-by: Aryan Sinha Link: https://patch.msgid.link/20261012152037.33461-1-sinha.aryan03@gmail.com Signed-off-by: Daniel Lezcano --- drivers/thermal/thermal_fw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c index e8bd2049cf74..7c28bfada386 100644 --- a/drivers/thermal/thermal_fw.c +++ b/drivers/thermal/thermal_fw.c @@ -11,13 +11,13 @@ /** * thermal_fwnode_cooling_device_register() - register an thermal cooling device - * @np: a pointer to a device tree node. - * @of_index: a cooling device index in the cooling controller + * @fwnode: a pointer to a firmware node. + * @fwn_index: a cooling device index in the cooling controller * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. * - * This function will register a cooling device with device tree node reference. + * This function will register a cooling device with a firmware node reference. * This interface function adds a new thermal cooling device (fan/processor/...) * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself * to all the thermal zone devices registered at the same time. @@ -71,7 +71,7 @@ __devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_ /** * devm_thermal_fwnode_cooling_device_register() - register a thermal cooling device * @dev: a valid struct device pointer of a sensor device. - * @fw_index: a cooling device index in the cooling controller + * @fwn_index: a cooling device index in the cooling controller * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. From 82bb005cd9cf5a9df4cfe4a52db90346ac5fd17a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 08:22:52 +0200 Subject: [PATCH 35/36] thermal: ti-soc-thermal: Fix the return value in ti_bandgap_get_sensor_data() kernel-doc ti_bandgap_get_sensor_data() returns an ERR_PTR() if the bandgap instance or sensor ID is invalid. Otherwise it returns the stored sensor data, which can be NULL if no data has been set or the stored value has been cleared. This mismatch was already present in omap_bandgap_get_sensor_data() when the driver was introduced in staging. Document the validation errors while retaining NULL as a possible stored value. Fixes: 8feaf0ce1a04 ("staging: OMAP4+: thermal: introduce bandgap temperature sensor") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260912062252.42762-1-kmehltretter@gmail.com --- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 507fe67eaffa..815ca0786c54 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -576,7 +576,8 @@ int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data) * @bgp: pointer to bandgap instance * @id: sensor id * - * Return: data stored by set function with sensor id on success or NULL + * Return: the stored sensor data, which may be %NULL, or an ERR_PTR() + * if @bgp or @id is invalid. */ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id) { From 2dc1f296b5754dd2441fc8b6613a362a47f6b98f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 14 Oct 2026 11:18:14 +0800 Subject: [PATCH 36/36] thermal/drivers/rzg3e: Propagate reset assertion failure from suspend rzg3e_thermal_suspend() powers off an active sensor and then ignores failure to assert its reset. System sleep can therefore continue without the clean reset state expected by resume. Return the reset error and, when the sensor had been active, try to restore power before failing suspend. This is best-effort recovery: reset assertion failure may leave partial hardware state, so restoring power is not guaranteed to succeed. Preserve the original reset error. The issue was found by our static-analysis tool and manually reviewed. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Signed-off-by: Daniel Lezcano Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20261014031814.7230-1-hppiscas@163.com --- drivers/thermal/renesas/rzg3e_thermal.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index a1ceff79e83e..e806d130dc53 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -512,15 +512,19 @@ static int rzg3e_thermal_runtime_resume(struct device *dev) static int rzg3e_thermal_suspend(struct device *dev) { struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev); + bool active = pm_runtime_active(dev); + int ret; /* If device is active, power it off */ - if (pm_runtime_active(dev)) + if (active) rzg3e_thermal_power_off(priv); /* Assert reset to ensure clean state after resume */ - reset_control_assert(priv->rstc); + ret = reset_control_assert(priv->rstc); + if (ret && active) + rzg3e_thermal_power_on(priv); - return 0; + return ret; } static int rzg3e_thermal_resume(struct device *dev)