diff options
42 files changed, 1331 insertions, 287 deletions
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 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 = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>; - 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>; }; 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 <tudor.ambarus@linaro.org> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/clock/google,gs101.h> + + thermal-sensor@100a0000 { + compatible = "google,gs101-tmu-top"; + reg = <0x100a0000 0x800>; + clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>; + interrupts = <GIC_SPI 769 IRQ_TYPE_LEVEL_HIGH 0>; + #thermal-sensor-cells = <1>; + samsung,acpm-ipc = <&acpm_ipc>; + }; 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 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 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 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 <edubezval@gmail.com> + +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 <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + + bandgap@4a0021e0 { + compatible = "ti,dra752-bandgap"; + reg = <0x4a0021e0 0xc>, <0x4a00232c 0xc>, + <0x4a002380 0x2c>, <0x4a0023c0 0x3c>, + <0x4a002564 0x8>, <0x4a002574 0x50>; + interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>; /* 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 */ -}; diff --git a/Documentation/driver-api/thermal/cpu-idle-cooling.rst b/Documentation/driver-api/thermal/cpu-idle-cooling.rst index c2a7ca676853..b9d4c91a7f15 100644 --- a/Documentation/driver-api/thermal/cpu-idle-cooling.rst +++ b/Documentation/driver-api/thermal/cpu-idle-cooling.rst @@ -161,7 +161,7 @@ tree. So with the idle injection mechanism, we want an average power specific OPP and idle another amount of time. That could be put in a equation:: - P(opp)target = ((Trunning x (P(opp)running) + (Tidle x P(opp)idle)) / + P(opp)target = (Trunning x (P(opp)running) + (Tidle x P(opp)idle)) / (Trunning + Tidle) ... diff --git a/MAINTAINERS b/MAINTAINERS index b3e79e617b9c..8d2fe0f815b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24378,6 +24378,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 <tudor.ambarus@linaro.org> +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 <tudor.ambarus@linaro.org> L: linux-samsung-soc@vger.kernel.org diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 77b3a1af271f..7c63a1b44a6b 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -5,6 +5,7 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/mfd/syscon.h> +#include <linux/mutex.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> @@ -18,10 +19,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 */ @@ -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) * \ @@ -243,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; @@ -250,7 +257,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; @@ -260,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, @@ -294,9 +305,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 +335,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); @@ -330,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; @@ -356,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) { @@ -528,7 +556,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,6 +595,18 @@ 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); + en7581_thermal_setup_monitor(priv); + en7581_thermal_setup_adc_val(dev, priv); + + return 0; +} + +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; @@ -577,45 +617,63 @@ static int en7581_thermal_probe(struct platform_device *pdev, if (ret) return ret; - en7581_thermal_setup_monitor(priv); - en7581_thermal_setup_adc_val(dev, priv); + /* Enable LOW and HIGH interrupt (if supported) */ + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); return 0; } -static int en7581_thermal_post_probe(struct platform_device *pdev) +static int an7583_thermal_read_diode(struct airoha_thermal_priv *priv, + int diode, int sensor_idx, int *val) { - struct airoha_thermal_priv *priv = platform_get_drvdata(pdev); + airoha_set_thermal_mux(priv, diode, sensor_idx); + *val = airoha_get_thermal_ADC(priv); - /* Enable LOW and HIGH interrupt (if supported) */ - regmap_write(priv->map, EN7581_TEMPMONINT, - EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); + return airoha_thermal_mux_holds(priv, diode, sensor_idx) ? 0 : -EAGAIN; +} - return 0; +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; + 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; + int ret; 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]; - 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); @@ -634,8 +692,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, @@ -667,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); @@ -676,7 +734,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; @@ -686,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); @@ -698,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, @@ -706,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, 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); diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 768859a7aed0..d75b42fb955e 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; + + cpufreq_cdev->cpufreq_state = state; - return ret; + return 0; } /** @@ -661,8 +661,8 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy) cdev = __cpufreq_cooling_register(np, policy, em); if (IS_ERR(cdev)) { - pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %ld\n", - policy->cpu, PTR_ERR(cdev)); + pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %pe\n", + policy->cpu, cdev); cdev = NULL; } } diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 0330a8112832..9ada52cbf7dd 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -156,8 +156,8 @@ static unsigned long get_voltage(struct devfreq *df, unsigned long freq) opp = dev_pm_opp_find_freq_exact(dev, freq, false); if (IS_ERR(opp)) { - dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n", - freq, PTR_ERR(opp)); + dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %pe\n", + freq, opp); return 0; } diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c index 37f2e22a999e..1cc9605a738f 100644 --- a/drivers/thermal/gov_power_allocator.c +++ b/drivers/thermal/gov_power_allocator.c @@ -660,10 +660,15 @@ static void power_allocator_update_tz(struct thermal_zone_device *tz, enum thermal_notify_event reason) { struct power_allocator_params *params = tz->governor_data; - const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max); + const struct thermal_trip_desc *td; struct thermal_instance *instance; int num_actors = 0; + if (!params->trip_max) + return; + + td = trip_to_trip_desc(params->trip_max); + switch (reason) { case THERMAL_TZ_BIND_CDEV: case THERMAL_TZ_UNBIND_CDEV: @@ -707,22 +712,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 +749,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; 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, diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c index f80dbe2ca7e4..850bfe7c1ede 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c @@ -179,17 +179,21 @@ static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, { int cpu; int curr_temp, ret; - - *temp = 0; + bool temp_valid = false; for_each_online_cpu(cpu) { ret = intel_tcc_get_temp(cpu, &curr_temp, false); if (ret < 0) return ret; - if (!*temp || curr_temp > *temp) + if (!temp_valid || curr_temp > *temp) { *temp = curr_temp; + temp_valid = true; + } } + if (!temp_valid) + return -ENODATA; + *temp *= 1000; return 0; @@ -291,10 +295,8 @@ int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv) } proc_priv->int340x_zone = int340x_thermal_zone_add(adev, get_temp); - if (IS_ERR(proc_priv->int340x_zone)) { + if (IS_ERR(proc_priv->int340x_zone)) return PTR_ERR(proc_priv->int340x_zone); - } else - ret = 0; ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, proc_thermal_notify, diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c index 91f291627132..e161b95828e8 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c @@ -66,15 +66,16 @@ static int slider_def_balance_set(const char *arg, const struct kernel_param *kp guard(mutex)(&slider_param_lock); ret = kstrtou8(arg, 16, &slider_val); - if (!ret) { - if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] || - slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE]) - return -EINVAL; + if (ret) + return ret; - slider_balanced_param = slider_val; - } + if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] || + slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE]) + return -EINVAL; - return ret; + slider_balanced_param = slider_val; + + return 0; } static int slider_def_balance_get(char *buf, const struct kernel_param *kp) @@ -101,14 +102,15 @@ static int slider_def_offset_set(const char *arg, const struct kernel_param *kp) guard(mutex)(&slider_param_lock); ret = kstrtou8(arg, 16, &offset); - if (!ret) { - if (offset > SOC_SLIDER_VALUE_MAXIMUM) - return -EINVAL; + if (ret) + return ret; - slider_offset = offset; - } + if (offset > SOC_SLIDER_VALUE_MAXIMUM) + return -EINVAL; - return ret; + slider_offset = offset; + + return 0; } static int slider_def_offset_get(char *buf, const struct kernel_param *kp) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index bd7fd98dc310..1ffcb0a699e5 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -94,7 +94,7 @@ static int duration_set(const char *arg, const struct kernel_param *kp) } mutex_lock(&powerclamp_lock); - duration = clamp(new_duration, 6ul, 25ul) * 1000; + duration = new_duration * 1000; mutex_unlock(&powerclamp_lock); exit: @@ -103,13 +103,9 @@ exit: static int duration_get(char *buf, const struct kernel_param *kp) { - int ret; - - mutex_lock(&powerclamp_lock); - ret = sysfs_emit(buf, "%d\n", duration / 1000); - mutex_unlock(&powerclamp_lock); + guard(mutex)(&powerclamp_lock); - return ret; + return sysfs_emit(buf, "%u\n", duration / 1000); } static const struct kernel_param_ops duration_ops = { @@ -143,12 +139,9 @@ copy_mask: } /* Return true if the cpumask and idle percent combination is invalid */ -static bool check_invalid(cpumask_var_t mask, u8 idle) +static bool check_invalid(const struct cpumask *mask, u8 idle) { - if (cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE) - return true; - - return false; + return cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE; } static int cpumask_set(const char *arg, const struct kernel_param *kp) @@ -214,41 +207,32 @@ MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection."); static int max_idle_set(const char *arg, const struct kernel_param *kp) { u8 new_max_idle; - int ret = 0; + int ret; - mutex_lock(&powerclamp_lock); + guard(mutex)(&powerclamp_lock); /* Can't set mask when cooling device is in use */ - if (powerclamp_data.clamping) { - ret = -EAGAIN; - goto skip_limit_set; - } + if (powerclamp_data.clamping) + return -EAGAIN; ret = kstrtou8(arg, 10, &new_max_idle); if (ret) - goto skip_limit_set; + return ret; - if (new_max_idle > MAX_TARGET_RATIO) { - ret = -EINVAL; - goto skip_limit_set; - } + if (new_max_idle > MAX_TARGET_RATIO) + return -EINVAL; if (!cpumask_available(idle_injection_cpu_mask)) { ret = allocate_copy_idle_injection_mask(cpu_present_mask); if (ret) - goto skip_limit_set; + return ret; } - if (check_invalid(idle_injection_cpu_mask, new_max_idle)) { - ret = -EINVAL; - goto skip_limit_set; - } + if (check_invalid(idle_injection_cpu_mask, new_max_idle)) + return -EINVAL; max_idle = new_max_idle; -skip_limit_set: - mutex_unlock(&powerclamp_lock); - return ret; } @@ -289,9 +273,10 @@ static int window_size_set(const char *arg, const struct kernel_param *kp) pr_err("Out of recommended window size %lu, between 2-10\n", new_window_size); ret = -EINVAL; + goto exit_win; } - window_size = clamp(new_window_size, 2ul, 10ul); + window_size = new_window_size; smp_mb(); exit_win: @@ -536,23 +521,17 @@ static struct idle_inject_device *ii_dev; */ static bool idle_inject_update(void) { - bool update = false; - /* We can't sleep in this callback */ if (!mutex_trylock(&powerclamp_lock)) return true; if (!(powerclamp_data.count % powerclamp_data.window_size_now)) { + unsigned int runtime; should_skip = powerclamp_adjust_controls(powerclamp_data.target_ratio, powerclamp_data.guard, powerclamp_data.window_size_now); - update = true; - } - - if (update) { - unsigned int runtime = get_run_time(); - + runtime = get_run_time(); idle_inject_set_duration(ii_dev, runtime, duration); } @@ -560,10 +539,7 @@ static bool idle_inject_update(void) mutex_unlock(&powerclamp_lock); - if (should_skip) - return false; - - return true; + return !should_skip; } /* This function starts idle injection by calling idle_inject_start() */ diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c index 52ea4f7fc9f9..75f6fbec2530 100644 --- a/drivers/thermal/intel/intel_tcc_cooling.c +++ b/drivers/thermal/intel/intel_tcc_cooling.c @@ -69,6 +69,7 @@ static const struct x86_cpu_id tcc_ids[] __initconst = { X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL), X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL), diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index af72db6299cd..4554a6fa55d1 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -680,8 +680,8 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm) continue; } - dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n", - adc_tm->channels[i].channel, PTR_ERR(tzd)); + dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %pe\n", + adc_tm->channels[i].channel, tzd); return PTR_ERR(tzd); } adc_tm->channels[i].tzd = tzd; 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; } 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-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 = { 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 @@ -1192,6 +1192,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; diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index c44f5b8858d0..e806d130dc53 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 */ @@ -194,7 +200,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; @@ -214,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); @@ -241,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; } @@ -254,10 +274,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; @@ -293,11 +309,9 @@ 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); return 0; @@ -469,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"); @@ -499,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) diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index 7ced8f76a0ec..a8716da7fcd2 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/property.h> #include <linux/reset.h> #include <linux/thermal.h> #include <linux/units.h> @@ -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,19 @@ 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 rzg3s_thermal_info rzg3l_info = { + .calib0 = 1322, + .calib1 = 772, +}; + static const struct of_device_id rzg3s_thermal_dt_ids[] = { - { .compatible = "renesas,r9a08g045-tsu" }, + { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info }, + { .compatible = "renesas,r9a08g046-tsu", .data = &rzg3l_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids); 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 <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/device/devres.h> +#include <linux/err.h> +#include <linux/firmware/samsung/exynos-acpm-protocol.h> +#include <linux/interrupt.h> +#include <linux/minmax.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/thermal.h> +#include <linux/units.h> + +#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 <tudor.ambarus@linaro.org>"); +MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver"); +MODULE_LICENSE("GPL"); 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"); diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index ac928c199829..e8d4da0aeac3 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1666,7 +1666,7 @@ EXPORT_SYMBOL_GPL(thermal_zone_device_unregister); * * Return: On success returns a reference to an unique thermal zone with * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid - * paramenters, -ENODEV for not found and -EEXIST for multiple matches). + * parameters, -ENODEV for not found and -EEXIST for multiple matches). */ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name) { 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. diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 14dfac9359b8..9e96fb1b033e 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) { @@ -349,10 +350,10 @@ out: } /** - * thermal_of_zone_unregister - Cleanup the specific allocated ressources + * thermal_of_zone_unregister - Cleanup the specific allocated resources * * This function disables the thermal zone and frees the different - * ressources allocated specific to the thermal OF. + * resources allocated specific to the thermal OF. * * @tz: a pointer to the thermal zone structure */ diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index ba43399d0b38..457639be2b32 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -332,7 +332,7 @@ static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id) * ti_bandgap_read_counter() - read the sensor counter * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds */ static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id, int *interval) @@ -352,7 +352,7 @@ static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id, * ti_bandgap_read_counter_delay() - read the sensor counter delay * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds */ static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id, int *interval) @@ -394,7 +394,7 @@ static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id, * ti_bandgap_read_update_interval() - read the sensor update interval * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds * * Return: 0 on success or the proper error code */ @@ -427,7 +427,7 @@ exit: * ti_bandgap_write_counter_delay() - set the counter_delay * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds * * Return: 0 on success or the proper error code */ @@ -471,7 +471,7 @@ static int ti_bandgap_write_counter_delay(struct ti_bandgap *bgp, int id, * ti_bandgap_write_counter() - set the bandgap sensor counter * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds */ static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id, u32 interval) @@ -486,7 +486,7 @@ static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id, * ti_bandgap_write_update_interval() - set the update interval * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds * * Return: 0 on success or the proper error code */ @@ -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) { @@ -1077,6 +1078,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 +1098,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); diff --git a/include/uapi/linux/thermal.h b/include/uapi/linux/thermal.h index 46a2633d33aa..36f3da2d6a7c 100644 --- a/include/uapi/linux/thermal.h +++ b/include/uapi/linux/thermal.h @@ -81,11 +81,11 @@ enum thermal_genl_event { THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, /* Cdev state updated */ THERMAL_GENL_EVENT_TZ_GOV_CHANGE, /* Governor policy changed */ THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, /* CPU capability changed */ - THERMAL_GENL_EVENT_THRESHOLD_ADD, /* A thresold has been added */ - THERMAL_GENL_EVENT_THRESHOLD_DELETE, /* A thresold has been deleted */ + THERMAL_GENL_EVENT_THRESHOLD_ADD, /* A threshold has been added */ + THERMAL_GENL_EVENT_THRESHOLD_DELETE, /* A threshold has been deleted */ THERMAL_GENL_EVENT_THRESHOLD_FLUSH, /* All thresolds have been deleted */ - THERMAL_GENL_EVENT_THRESHOLD_UP, /* A thresold has been crossed the way up */ - THERMAL_GENL_EVENT_THRESHOLD_DOWN, /* A thresold has been crossed the way down */ + THERMAL_GENL_EVENT_THRESHOLD_UP, /* A threshold has been crossed the way up */ + THERMAL_GENL_EVENT_THRESHOLD_DOWN, /* A threshold has been crossed the way down */ __THERMAL_GENL_EVENT_MAX, }; #define THERMAL_GENL_EVENT_MAX (__THERMAL_GENL_EVENT_MAX - 1) 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.c b/tools/thermal/tmon/tmon.c index 7eb3216a27f4..70a2ec50a461 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", @@ -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(); 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. */ |
