From dd04ad1cdabcad51e34b74b4e91b9aeb7180d05d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 26 Jun 2026 13:38:22 +0300 Subject: [PATCH 01/32] thermal/drivers/rcar: Fix error checking in probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code accidentally calls thermal_zone_device_enable() before checking whether thermal_zone_device_register_with_trips() failed. Move the call until later to avoid an error pointer dereference of "priv->zone". The driver works differently depending on if we are using OF thermal or not. We use thermal_add_hwmon_sysfs() if we are using OF thermal and call thermal_zone_device_enable() if not. We can share same error check for if either of these fail. Moving the thermal_zone_device_enable() call is a bit cleaner as well. The original code used a three step process to cleanup: 1. Call thermal_zone_device_unregister() to cleanup. 2. Set priv->zone to an error pointer to preserve the error code. 3. Set priv->zone to NULL to avoid a second call to thermal_zone_device_unregister() in the rcar_thermal_remove() function. Now we can just do a direct goto error_unregister and rcar_thermal_remove() handles the cleanup properly. Fixes: bbcf90c0646a ("thermal: Explicitly enable non-changing thermal zone devices") Reviewed-by: Geert Uytterhoeven Reviewed-by: Niklas Söderlund Signed-off-by: Dan Carpenter Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/aj5WnseULiwgmlWv@stanley.mountain --- drivers/thermal/renesas/rcar_thermal.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c index 6e5dcac5d47a..fd686da9252e 100644 --- a/drivers/thermal/renesas/rcar_thermal.c +++ b/drivers/thermal/renesas/rcar_thermal.c @@ -492,12 +492,6 @@ static int rcar_thermal_probe(struct platform_device *pdev) "rcar_thermal", trips, ARRAY_SIZE(trips), priv, &rcar_thermal_zone_ops, NULL, 0, idle); - - ret = thermal_zone_device_enable(priv->zone); - if (ret) { - thermal_zone_device_unregister(priv->zone); - priv->zone = ERR_PTR(ret); - } } if (IS_ERR(priv->zone)) { dev_err(dev, "can't register thermal zone\n"); @@ -506,11 +500,12 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; } - if (chip->use_of_thermal) { + if (chip->use_of_thermal) ret = thermal_add_hwmon_sysfs(priv->zone); - if (ret) - goto error_unregister; - } + else + ret = thermal_zone_device_enable(priv->zone); + if (ret) + goto error_unregister; rcar_thermal_irq_enable(priv); From cbe31d5ce49873a2a5a3ca5decb4935672396e77 Mon Sep 17 00:00:00 2001 From: "Bryan B. Lima" Date: Tue, 30 Jun 2026 22:07:18 -0300 Subject: [PATCH 02/32] thermal/drivers/armada: Use bitfield and bitmask macros Replace manual bitfield manipulations with FIELD_MODIFY() and define constants with BIT() and GENMASK() to make code more readable. Also, remove offset and shift constants for clarity in use of bitfield macros. Signed-off-by: Bryan B. Lima Co-developed-by: Gustavo S. Correa Signed-off-by: Gustavo S. Correa Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260701010802.99029-1-bblima@usp.br --- drivers/thermal/armada_thermal.c | 98 ++++++++++++++------------------ 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index c2fbdb534f61..be6240984002 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -20,41 +20,35 @@ #include /* Thermal Manager Control and Status Register */ -#define PMU_TDC0_SW_RST_MASK (0x1 << 1) -#define PMU_TM_DISABLE_OFFS 0 -#define PMU_TM_DISABLE_MASK (0x1 << PMU_TM_DISABLE_OFFS) -#define PMU_TDC0_REF_CAL_CNT_OFFS 11 -#define PMU_TDC0_REF_CAL_CNT_MASK (0x1ff << PMU_TDC0_REF_CAL_CNT_OFFS) -#define PMU_TDC0_OTF_CAL_MASK (0x1 << 30) -#define PMU_TDC0_START_CAL_MASK (0x1 << 25) +#define PMU_TDC0_SW_RST_MASK BIT(1) +#define PMU_TM_DISABLE_MASK BIT(0) +#define PMU_TDC0_REF_CAL_CNT_MASK GENMASK(19, 11) +#define PMU_TDC0_OTF_CAL_MASK BIT(30) +#define PMU_TDC0_START_CAL_MASK BIT(25) -#define A375_UNIT_CONTROL_SHIFT 27 -#define A375_UNIT_CONTROL_MASK 0x7 +#define A375_UNIT_CONTROL_MASK GENMASK(29, 27) #define A375_READOUT_INVERT BIT(15) #define A375_HW_RESETn BIT(8) /* Errata fields */ -#define CONTROL0_TSEN_TC_TRIM_MASK 0x7 +#define CONTROL0_TSEN_TC_TRIM_MASK GENMASK(2, 0) #define CONTROL0_TSEN_TC_TRIM_VAL 0x3 #define CONTROL0_TSEN_START BIT(0) #define CONTROL0_TSEN_RESET BIT(1) #define CONTROL0_TSEN_ENABLE BIT(2) #define CONTROL0_TSEN_AVG_BYPASS BIT(6) -#define CONTROL0_TSEN_CHAN_SHIFT 13 -#define CONTROL0_TSEN_CHAN_MASK 0xF -#define CONTROL0_TSEN_OSR_SHIFT 24 -#define CONTROL0_TSEN_OSR_MAX 0x3 -#define CONTROL0_TSEN_MODE_SHIFT 30 +#define CONTROL0_TSEN_CHAN_MASK GENMASK(16, 13) +#define CONTROL0_TSEN_OSR_MASK GENMASK(25, 24) +#define CONTROL0_TSEN_OSR_MAX FIELD_MAX(CONTROL0_TSEN_OSR_MASK) +#define CONTROL0_TSEN_MODE_MASK GENMASK(31, 30) #define CONTROL0_TSEN_MODE_EXTERNAL 0x2 -#define CONTROL0_TSEN_MODE_MASK 0x3 -#define CONTROL1_TSEN_AVG_MASK 0x7 +#define CONTROL1_TSEN_AVG_MASK GENMASK(2, 0) #define CONTROL1_EXT_TSEN_SW_RESET BIT(7) #define CONTROL1_EXT_TSEN_HW_RESETn BIT(8) #define CONTROL1_TSEN_INT_EN BIT(25) -#define CONTROL1_TSEN_SELECT_OFF 21 -#define CONTROL1_TSEN_SELECT_MASK 0x3 +#define CONTROL1_TSEN_SELECT_MASK GENMASK(22, 21) #define STATUS_POLL_PERIOD_US 1000 #define STATUS_POLL_TIMEOUT_US 100000 @@ -140,23 +134,21 @@ static void armadaxp_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg |= PMU_TDC0_SW_RST_MASK; - + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); - reg &= ~PMU_TDC0_SW_RST_MASK; + FIELD_MODIFY(PMU_TDC0_SW_RST_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Enable the sensor */ regmap_read(priv->syscon, data->syscon_status_off, ®); - reg &= ~PMU_TM_DISABLE_MASK; + FIELD_MODIFY(PMU_TM_DISABLE_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_status_off, reg); } @@ -167,14 +159,13 @@ static void armada370_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= PMU_TDC0_OTF_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_OTF_CAL_MASK, ®, 1); /* Reference calibration value */ - reg &= ~PMU_TDC0_REF_CAL_CNT_MASK; - reg |= (0xf1 << PMU_TDC0_REF_CAL_CNT_OFFS); + FIELD_MODIFY(PMU_TDC0_REF_CAL_CNT_MASK, ®, 0xf1); /* Reset the sensor */ - reg &= ~PMU_TDC0_START_CAL_MASK; + FIELD_MODIFY(PMU_TDC0_START_CAL_MASK, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); @@ -188,14 +179,14 @@ static void armada375_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~(A375_UNIT_CONTROL_MASK << A375_UNIT_CONTROL_SHIFT); - reg &= ~A375_READOUT_INVERT; - reg &= ~A375_HW_RESETn; + FIELD_MODIFY(A375_UNIT_CONTROL_MASK, ®, 0); + FIELD_MODIFY(A375_READOUT_INVERT, ®, 0); + FIELD_MODIFY(A375_HW_RESETn, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(20); - reg |= A375_HW_RESETn; + FIELD_MODIFY(A375_HW_RESETn, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); msleep(50); @@ -220,14 +211,13 @@ static void armada380_init(struct platform_device *pdev, /* Disable the HW/SW reset */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg |= CONTROL1_EXT_TSEN_HW_RESETn; - reg &= ~CONTROL1_EXT_TSEN_SW_RESET; + FIELD_MODIFY(CONTROL1_EXT_TSEN_HW_RESETn, ®, 1); + FIELD_MODIFY(CONTROL1_EXT_TSEN_SW_RESET, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); /* Set Tsen Tc Trim to correct default value (errata #132698) */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_TC_TRIM_MASK; - reg |= CONTROL0_TSEN_TC_TRIM_VAL; + FIELD_MODIFY(CONTROL0_TSEN_TC_TRIM_MASK, ®, CONTROL0_TSEN_TC_TRIM_VAL); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -238,14 +228,15 @@ static void armada_ap80x_init(struct platform_device *pdev, u32 reg; regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg &= ~CONTROL0_TSEN_RESET; - reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_ENABLE; + FIELD_MODIFY(CONTROL0_TSEN_RESET, ®, 0); + FIELD_MODIFY(CONTROL0_TSEN_START, ®, 1); + FIELD_MODIFY(CONTROL0_TSEN_ENABLE, ®, 1); /* Sample every ~2ms */ - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); /* Enable average (2 samples by default) */ - reg &= ~CONTROL0_TSEN_AVG_BYPASS; + FIELD_MODIFY(CONTROL0_TSEN_AVG_BYPASS, ®, 0); regmap_write(priv->syscon, data->syscon_control0_off, reg); } @@ -260,13 +251,12 @@ static void armada_cp110_init(struct platform_device *pdev, /* Sample every ~2ms */ regmap_read(priv->syscon, data->syscon_control0_off, ®); - reg |= CONTROL0_TSEN_OSR_MAX << CONTROL0_TSEN_OSR_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_OSR_MASK, ®, CONTROL0_TSEN_OSR_MAX); regmap_write(priv->syscon, data->syscon_control0_off, reg); /* Average the output value over 2^1 = 2 samples */ regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_AVG_MASK; - reg |= 1; + FIELD_MODIFY(CONTROL1_TSEN_AVG_MASK, ®, 1); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -313,7 +303,7 @@ armada_disable_overheat_interrupt(struct armada_thermal_priv *priv) u32 reg; regmap_read(priv->syscon, data->syscon_control1_off, ®); - reg &= ~CONTROL1_TSEN_INT_EN; + FIELD_MODIFY(CONTROL1_TSEN_INT_EN, ®, 0); regmap_write(priv->syscon, data->syscon_control1_off, reg); } @@ -331,20 +321,18 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) /* Stop the measurements */ regmap_read(priv->syscon, data->syscon_control0_off, &ctrl0); - ctrl0 &= ~CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 0); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); - /* Reset the mode, internal sensor will be automatically selected */ - ctrl0 &= ~(CONTROL0_TSEN_MODE_MASK << CONTROL0_TSEN_MODE_SHIFT); - /* Other channels are external and should be selected accordingly */ if (channel) { /* Change the mode to external */ - ctrl0 |= CONTROL0_TSEN_MODE_EXTERNAL << - CONTROL0_TSEN_MODE_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, CONTROL0_TSEN_MODE_EXTERNAL); /* Select the sensor */ - ctrl0 &= ~(CONTROL0_TSEN_CHAN_MASK << CONTROL0_TSEN_CHAN_SHIFT); - ctrl0 |= (channel - 1) << CONTROL0_TSEN_CHAN_SHIFT; + FIELD_MODIFY(CONTROL0_TSEN_CHAN_MASK, &ctrl0, channel - 1); + } else { + /* Reset the mode, internal sensor will be automatically selected */ + FIELD_MODIFY(CONTROL0_TSEN_MODE_MASK, &ctrl0, 0); } /* Actually set the mode/channel */ @@ -352,7 +340,7 @@ static int armada_select_channel(struct armada_thermal_priv *priv, int channel) priv->current_channel = channel; /* Re-start the measurements */ - ctrl0 |= CONTROL0_TSEN_START; + FIELD_MODIFY(CONTROL0_TSEN_START, &ctrl0, 1); regmap_write(priv->syscon, data->syscon_control0_off, ctrl0); /* From b08e97bee0e1438a7d3283f901b51121aa2640d5 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sun, 5 Jul 2026 18:43:12 +0200 Subject: [PATCH 03/32] thermal: Remove unneeded 'fast_io' parameter in regmap_config When using MMIO with regmap, fast_io is implied. No need to set it again. Signed-off-by: Wolfram Sang Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260705164311.2273-2-wsa+renesas@sang-engineering.com --- drivers/thermal/armada_thermal.c | 1 - drivers/thermal/sun8i_thermal.c | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index be6240984002..f64e46fcf0be 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -710,7 +710,6 @@ static const struct regmap_config armada_thermal_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, - .fast_io = true, }; static int armada_thermal_probe_legacy(struct platform_device *pdev, diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c index 22674790629a..284684137c43 100644 --- a/drivers/thermal/sun8i_thermal.c +++ b/drivers/thermal/sun8i_thermal.c @@ -149,7 +149,6 @@ static const struct regmap_config config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, - .fast_io = true, .max_register = 0xfc, }; From 251621813fb4275e24431f9a0690aec9b15823e7 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:29 +0200 Subject: [PATCH 04/32] thermal/drivers/airoha: Fix copy paste error on clamp_t low temp In airoha_thermal_set_trips, there is a copy paste error on clamping the value for the low trip temp point. Fix it to the correct value and actually clamp for the low variable. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Wayen Yan Link: https://patch.msgid.link/20260702094846.17325-2-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index b9fd6bfc88e5..439aa011b75c 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -273,7 +273,7 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, if (low != -INT_MAX) { /* Validate low and clamp it to a supported value */ - low = clamp_t(int, high, RAW_TO_TEMP(priv, 0), + low = clamp_t(int, low, RAW_TO_TEMP(priv, 0), RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the low temp of 1°C to trigger correct event */ From 6791265d609549be55bb35b747c9648d0b570c12 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:30 +0200 Subject: [PATCH 05/32] thermal/drivers/airoha: Fix copy paste error for sen internal In airoha_thermal_setup_monitor there is a copy paste error on configuring the internval for temp monitor. Fix the error and use the correct mask for the sen interval for the EN7581_TEMPMONCTL2 register. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Wayen Yan Link: https://patch.msgid.link/20260702094846.17325-3-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 439aa011b75c..829a7327fc40 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -403,7 +403,7 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * sen interval is 379 * 52.715us = 19.97ms */ writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) | - FIELD_PREP(EN7581_FILT_INTERVAL, 379), + FIELD_PREP(EN7581_SEN_INTERVAL, 379), priv->base + EN7581_TEMPMONCTL2); /* AHB poll is set to 146 * 68.64 = 10.02us */ From a3137f1aa44cd431e495afb6a97ccc498b9ebe75 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:31 +0200 Subject: [PATCH 06/32] thermal/drivers/airoha: Convert to regmap API In preparation for support of Airoha AN7583, convert the driver to regmap API. This is needed as Airoha AN7583 will be based on syscon regmap. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-4-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 77 +++++++++++++++++++------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 829a7327fc40..b63893a8997a 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -194,7 +194,7 @@ #define AIROHA_MAX_SAMPLES 6 struct airoha_thermal_priv { - void __iomem *base; + struct regmap *map; struct regmap *chip_scu; struct resource scu_adc_res; @@ -265,8 +265,8 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the high temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, high) >> 4, - priv->base + EN7581_TEMPOFFSETH); + regmap_write(priv->map, EN7581_TEMPOFFSETH, + TEMP_TO_RAW(priv, high) >> 4); enable_monitor = true; } @@ -277,15 +277,15 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK))); /* We offset the low temp of 1°C to trigger correct event */ - writel(TEMP_TO_RAW(priv, low) >> 4, - priv->base + EN7581_TEMPOFFSETL); + regmap_write(priv->map, EN7581_TEMPOFFSETL, + TEMP_TO_RAW(priv, low) >> 4); enable_monitor = true; } /* Enable sensor 0 monitor after trip are set */ if (enable_monitor) - writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0); + regmap_write(priv->map, EN7581_TEMPMONCTL0, EN7581_SENSE0_EN); return 0; } @@ -300,9 +300,9 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) struct airoha_thermal_priv *priv = data; enum thermal_notify_event event; bool update = false; - u32 status; + u32 status = 0; - status = readl(priv->base + EN7581_TEMPMONINTSTS); + regmap_read(priv->map, EN7581_TEMPMONINTSTS, &status); switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) { case EN7581_HOFSINTSTS0: event = THERMAL_TRIP_VIOLATED; @@ -318,7 +318,7 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) } /* Reset Interrupt */ - writel(status, priv->base + EN7581_TEMPMONINTSTS); + regmap_write(priv->map, EN7581_TEMPMONINTSTS, status); if (update) thermal_zone_device_update(priv->tz, event); @@ -329,18 +329,19 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) static void airoha_thermal_setup_adc_val(struct device *dev, struct airoha_thermal_priv *priv) { - u32 efuse_calib_info, cpu_sensor; + u32 efuse_calib_info = 0; + u32 cpu_sensor = 0; /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */ airoha_init_thermal_ADC_mode(priv); /* sleep 10 ms for ADC to enable */ usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); - efuse_calib_info = readl(priv->base + EN7581_EFUSE_TEMP_OFFSET_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { priv->default_offset = FIELD_GET(EN7581_EFUSE_TEMP_OFFSET, efuse_calib_info); /* Different slope are applied if the sensor is used for CPU or for package */ - cpu_sensor = readl(priv->base + EN7581_EFUSE_TEMP_CPU_SENSOR_REG); + regmap_read(priv->map, EN7581_EFUSE_TEMP_CPU_SENSOR_REG, &cpu_sensor); if (cpu_sensor) { priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT; priv->init_temp = EN7581_INIT_TEMP_FTK_X10; @@ -359,8 +360,8 @@ static void airoha_thermal_setup_adc_val(struct device *dev, static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) { /* Set measure mode */ - writel(FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4), - priv->base + EN7581_TEMPMSRCTL0); + regmap_write(priv->map, EN7581_TEMPMSRCTL0, + FIELD_PREP(EN7581_MSRCTL0, EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4)); /* * Configure ADC valid reading addr @@ -375,15 +376,15 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * We set valid instead of volt as we don't enable valid/volt * split reading and AHB read valid addr in such case. */ - writel(priv->scu_adc_res.start + EN7581_DOUT_TADC, - priv->base + EN7581_TEMPADCVALIDADDR); + regmap_write(priv->map, EN7581_TEMPADCVALIDADDR, + priv->scu_adc_res.start + EN7581_DOUT_TADC); /* * Configure valid bit on a fake value of bit 16. The ADC outputs * max of 2 bytes for voltage. */ - writel(FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16), - priv->base + EN7581_TEMPADCVALIDMASK); + regmap_write(priv->map, EN7581_TEMPADCVALIDMASK, + FIELD_PREP(EN7581_ADV_RD_VALID_POS, 16)); /* * AHB supports max 12 bytes for ADC voltage. Shift the read @@ -391,40 +392,52 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) * in the order of half a °C and is acceptable in the context * of triggering interrupt in critical condition. */ - writel(FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4), - priv->base + EN7581_TEMPADCVOLTAGESHIFT); + regmap_write(priv->map, EN7581_TEMPADCVOLTAGESHIFT, + FIELD_PREP(EN7581_ADC_VOLTAGE_SHIFT, 4)); /* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */ - writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3), - priv->base + EN7581_TEMPMONCTL1); + regmap_write(priv->map, EN7581_TEMPMONCTL1, + FIELD_PREP(EN7581_PERIOD_UNIT, 3)); /* * filt interval is 1 * 52.715us = 52.715us, * sen interval is 379 * 52.715us = 19.97ms */ - writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) | - FIELD_PREP(EN7581_SEN_INTERVAL, 379), - priv->base + EN7581_TEMPMONCTL2); + regmap_write(priv->map, EN7581_TEMPMONCTL2, + FIELD_PREP(EN7581_FILT_INTERVAL, 1) | + FIELD_PREP(EN7581_SEN_INTERVAL, 379)); /* AHB poll is set to 146 * 68.64 = 10.02us */ - writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146), - priv->base + EN7581_TEMPAHBPOLL); + regmap_write(priv->map, EN7581_TEMPAHBPOLL, + FIELD_PREP(EN7581_ADC_POLL_INTVL, 146)); } +static const struct regmap_config airoha_thermal_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + static int airoha_thermal_probe(struct platform_device *pdev) { struct airoha_thermal_priv *priv; struct device_node *chip_scu_np; struct device *dev = &pdev->dev; + void __iomem *base; int irq, ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->map = devm_regmap_init_mmio(dev, base, + &airoha_thermal_regmap_config); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0); if (!chip_scu_np) @@ -462,8 +475,8 @@ static int airoha_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); /* Enable LOW and HIGH interrupt */ - writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0, - priv->base + EN7581_TEMPMONINT); + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); return 0; } From 62f6a66189b161d475dbdb31055b3ce8963cc418 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:32 +0200 Subject: [PATCH 07/32] thermal/drivers/airoha: Generalize probe function In preparation for support of Airoha AN7583, generalize the probe function to address for the 2 SoC difference. Implement a match_data struct where it's possible to define a more specific probe and post_probe function and specific thermal ops and pllrg protect value. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-5-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 102 +++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 27 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index b63893a8997a..ebb47ae5f2ce 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -198,12 +198,23 @@ struct airoha_thermal_priv { struct regmap *chip_scu; struct resource scu_adc_res; + u32 pllrg_protect; + struct thermal_zone_device *tz; int init_temp; int default_slope; int default_offset; }; +struct airoha_thermal_soc_data { + u32 pllrg_protect; + + const struct thermal_zone_device_ops *thdev_ops; + int (*probe)(struct platform_device *pdev, + struct airoha_thermal_priv *priv); + int (*post_probe)(struct platform_device *pdev); +}; + static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; @@ -220,7 +231,8 @@ static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg); /* Give access to thermal regs */ - regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, EN7581_SCU_THERMAL_PROTECT_KEY); + regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, + priv->pllrg_protect); adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1); regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux); @@ -228,7 +240,7 @@ static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); } -static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) +static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); int min_value, max_value, avg_value, value; @@ -253,7 +265,7 @@ static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp) return 0; } -static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, +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); @@ -290,12 +302,12 @@ static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low, return 0; } -static const struct thermal_zone_device_ops thdev_ops = { - .get_temp = airoha_thermal_get_temp, - .set_trips = airoha_thermal_set_trips, +static const struct thermal_zone_device_ops en7581_thdev_ops = { + .get_temp = en7581_thermal_get_temp, + .set_trips = en7581_thermal_set_trips, }; -static irqreturn_t airoha_thermal_irq(int irq, void *data) +static irqreturn_t en7581_thermal_irq(int irq, void *data) { struct airoha_thermal_priv *priv = data; enum thermal_notify_event event; @@ -326,7 +338,7 @@ static irqreturn_t airoha_thermal_irq(int irq, void *data) return IRQ_HANDLED; } -static void airoha_thermal_setup_adc_val(struct device *dev, +static void en7581_thermal_setup_adc_val(struct device *dev, struct airoha_thermal_priv *priv) { u32 efuse_calib_info = 0; @@ -357,7 +369,7 @@ static void airoha_thermal_setup_adc_val(struct device *dev, } } -static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) +static void en7581_thermal_setup_monitor(struct airoha_thermal_priv *priv) { /* Set measure mode */ regmap_write(priv->map, EN7581_TEMPMSRCTL0, @@ -412,30 +424,26 @@ static void airoha_thermal_setup_monitor(struct airoha_thermal_priv *priv) FIELD_PREP(EN7581_ADC_POLL_INTVL, 146)); } -static const struct regmap_config airoha_thermal_regmap_config = { +static const struct regmap_config en7581_thermal_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, }; -static int airoha_thermal_probe(struct platform_device *pdev) +static int en7581_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) { - struct airoha_thermal_priv *priv; struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; int irq, ret; - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); priv->map = devm_regmap_init_mmio(dev, base, - &airoha_thermal_regmap_config); + &en7581_thermal_regmap_config); if (IS_ERR(priv->map)) return PTR_ERR(priv->map); @@ -455,18 +463,55 @@ static int airoha_thermal_probe(struct platform_device *pdev) return irq; ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - airoha_thermal_irq, IRQF_ONESHOT, + en7581_thermal_irq, IRQF_ONESHOT, pdev->name, priv); if (ret) { dev_err(dev, "Can't get interrupt working.\n"); return ret; } - airoha_thermal_setup_monitor(priv); - airoha_thermal_setup_adc_val(dev, priv); + 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); + + /* Enable LOW and HIGH interrupt (if supported) */ + regmap_write(priv->map, EN7581_TEMPMONINT, + EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); + + return 0; +} + +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; + + soc_data = device_get_match_data(dev); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pllrg_protect = soc_data->pllrg_protect; + + if (!soc_data->probe) + return -EINVAL; + + ret = soc_data->probe(pdev, priv); + if (ret) + return ret; /* register of thermal sensor and get info from DT */ - priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops); + 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); @@ -474,15 +519,18 @@ static int airoha_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - /* Enable LOW and HIGH interrupt */ - regmap_write(priv->map, EN7581_TEMPMONINT, - EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0); - - return 0; + return soc_data->post_probe ? soc_data->post_probe(pdev) : 0; } +static const struct airoha_thermal_soc_data en7581_data = { + .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &en7581_thdev_ops, + .probe = &en7581_thermal_probe, + .post_probe = &en7581_thermal_post_probe, +}; + static const struct of_device_id airoha_thermal_match[] = { - { .compatible = "airoha,en7581-thermal" }, + { .compatible = "airoha,en7581-thermal", .data = &en7581_data }, {}, }; MODULE_DEVICE_TABLE(of, airoha_thermal_match); From 86741c9128629a3059a0a759b665442958dedea0 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:33 +0200 Subject: [PATCH 08/32] thermal/drivers/airoha: Generalize get_thermal_ADC and set_mux function In preparation for support of Airoha AN7583, generalize get_thermal_ADC() and set_thermal_mux() with the use of reg_field API. This is to take into account the same logic between the current supported SoC and the new one but with different register address. While at it also further improve some comments and move sleep inside the set_thermal_mux function. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-6-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 56 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index ebb47ae5f2ce..249abbbd46bc 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -193,9 +193,18 @@ #define AIROHA_MAX_SAMPLES 6 +enum airoha_thermal_chip_scu_field { + AIROHA_THERMAL_DOUT_TADC, + AIROHA_THERMAL_MUX_TADC, + + /* keep last */ + AIROHA_THERMAL_FIELD_MAX, +}; + struct airoha_thermal_priv { struct regmap *map; struct regmap *chip_scu; + struct regmap_field *chip_scu_fields[AIROHA_THERMAL_FIELD_MAX]; struct resource scu_adc_res; u32 pllrg_protect; @@ -219,25 +228,32 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; - regmap_read(priv->chip_scu, EN7581_DOUT_TADC, &val); - return FIELD_GET(EN7581_DOUT_TADC_MASK, val); + regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_DOUT_TADC], + &val); + return val; } -static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv) +static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, + int tdac_idx) { - u32 adc_mux, pllrg; + u32 pllrg; /* Save PLLRG current value */ regmap_read(priv->chip_scu, EN7581_PLLRG_PROTECT, &pllrg); - /* Give access to thermal regs */ + /* Give access to Thermal regs */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, priv->pllrg_protect); - adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1); - regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux); + + /* Configure Thermal ADC mux to tdac_idx */ + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], + tdac_idx); /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); + + /* Sleep 10 ms for Thermal ADC to enable */ + usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); } static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) @@ -344,10 +360,8 @@ static void en7581_thermal_setup_adc_val(struct device *dev, u32 efuse_calib_info = 0; u32 cpu_sensor = 0; - /* Setup thermal sensor to ADC mode and setup the mux to DIODE1 */ - airoha_init_thermal_ADC_mode(priv); - /* sleep 10 ms for ADC to enable */ - usleep_range(10 * USEC_PER_MSEC, 11 * USEC_PER_MSEC); + /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */ + airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1); regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { @@ -430,13 +444,18 @@ static const struct regmap_config en7581_thermal_regmap_config = { .val_bits = 32, }; +static const struct reg_field en7581_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { + [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(EN7581_DOUT_TADC, 0, 15), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(EN7581_PWD_TADC, 1, 3), +}; + static int en7581_thermal_probe(struct platform_device *pdev, struct airoha_thermal_priv *priv) { struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; - int irq, ret; + int i, irq, ret; base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) @@ -455,6 +474,19 @@ static int en7581_thermal_probe(struct platform_device *pdev, if (IS_ERR(priv->chip_scu)) return PTR_ERR(priv->chip_scu); + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + en7581_chip_scu_fields[i]); + if (IS_ERR(field)) { + of_node_put(chip_scu_np); + return PTR_ERR(field); + } + + priv->chip_scu_fields[i] = field; + } + of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res); of_node_put(chip_scu_np); From fff997256c652d3405c71dd107fe5756c8295963 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:34 +0200 Subject: [PATCH 09/32] dt-bindings: arm: airoha: Add the chip-scu node for AN7583 SoC Document support for Airoha AN7583 chip-scu node. This is similar to Airoha EN7581 with the addition of the presence of thermal sensor in addition to controlling HW PIN and other miscellaneous pheriperals. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260702094846.17325-7-ansuelsmth@gmail.com --- .../bindings/arm/airoha,en7581-chip-scu.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml index 67c449d804c2..cc564dc7b414 100644 --- a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml +++ b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml @@ -19,15 +19,29 @@ properties: items: - enum: - airoha,en7581-chip-scu + - airoha,an7583-chip-scu - const: syscon reg: maxItems: 1 + '#thermal-sensor-cells': + const: 0 + required: - compatible - reg +if: + properties: + compatible: + contains: + const: airoha,en7581-chip-scu + +then: + properties: + '#thermal-sensor-cells': false + additionalProperties: false examples: From b20d9782756a05ddae69512d79ffa0353c488b38 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Jul 2026 11:48:35 +0200 Subject: [PATCH 10/32] thermal/drivers/airoha: Add support for AN7583 Thermal Sensor Add support for Airoha AN7583 Thermal driver. This apply similar logic on how to read the temperature but totally drop support for the PTP_THERMAL subsystem. PTP_THERMAL subsystem was a way to trigger trip point from hardware by configuring how to read the temperature internally. This subsystem has been totally removed from Airoha AN7583 permitting only to read the temperature. The SoC support up to 3 sensor but the original driver always read the BGA sensor hence it's currently implemented reading only this specific sensor. Reference and values for the other 2 sensor are defined for further implementation if confirmed working. set_thermal_mux() is extended to also address muxing the sensor as AN7583 use a different way to read the temperature from 3 different diode. The EN7581 code is updated to account for these changes. Signed-off-by: Christian Marangi Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260702094846.17325-8-ansuelsmth@gmail.com --- drivers/thermal/airoha_thermal.c | 161 ++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 249abbbd46bc..c84b5c36e880 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -18,6 +18,12 @@ #define EN7581_DOUT_TADC 0x2f8 #define EN7581_DOUT_TADC_MASK GENMASK(15, 0) +#define AN7583_MUX_SENSOR 0x2a0 +#define AN7583_LOAD_ADJ GENMASK(3, 2) +#define AN7583_MUX_TADC 0x2e4 +#define AN7583_MUX_TADC_MASK GENMASK(3, 1) +#define AN7583_DOUT_TADC 0x2f0 + /* PTP_THERMAL regs */ #define EN7581_TEMPMONCTL0 0x800 #define EN7581_SENSE3_EN BIT(3) @@ -181,6 +187,11 @@ #define EN7581_SCU_THERMAL_PROTECT_KEY 0x12 #define EN7581_SCU_THERMAL_MUX_DIODE1 0x7 +#define AN7583_SCU_THERMAL_PROTECT_KEY 0x80 +#define AN7583_NUM_SENSOR 3 + +#define AIROHA_THERMAL_NO_MUX_SENSOR -1 + /* 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) * \ (priv)->default_slope) / 1000) + \ @@ -193,8 +204,39 @@ #define AIROHA_MAX_SAMPLES 6 +/* + * AN7583 supports all these ADC mux but the original driver + * always checked temp with the AN7583_BGP_TEMP_SENSOR. + * Assume using the other sensor temperature is invalid and + * always read from AN7583_BGP_TEMP_SENSOR. + * + * On top of this it's defined that AN7583 supports 3 + * sensor: AN7583_BGP_TEMP_SENSOR, AN7583_GBE_TEMP_SENSOR, + * AN7583_CPU_TEMP_SENSOR. + * + * Provide the ADC mux for reference. + */ +enum an7583_thermal_adc_mux { + AN7583_BGP_TEMP_SENSOR, + AN7583_PAD_AVS, + AN7583_CORE_POWER, + AN7583_AVSDAC_OUT, + AN7583_VCM, + AN7583_GBE_TEMP_SENSOR, + AN7583_CPU_TEMP_SENSOR, + + AN7583_ADC_MUX_MAX, +}; + +enum an7583_thermal_diode_mux { + AN7583_D0_TADC, + AN7583_ZERO_TADC, + AN7583_D1_TADC, +}; + enum airoha_thermal_chip_scu_field { AIROHA_THERMAL_DOUT_TADC, + AIROHA_THERMAL_MUX_SENSOR, AIROHA_THERMAL_MUX_TADC, /* keep last */ @@ -208,6 +250,7 @@ struct airoha_thermal_priv { struct resource scu_adc_res; u32 pllrg_protect; + int current_adc; struct thermal_zone_device *tz; int init_temp; @@ -224,6 +267,24 @@ struct airoha_thermal_soc_data { int (*post_probe)(struct platform_device *pdev); }; +static const unsigned int an7583_thermal_coeff[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 973, + [AN7583_GBE_TEMP_SENSOR] = 995, + [AN7583_CPU_TEMP_SENSOR] = 1035, +}; + +static const unsigned int an7583_thermal_slope[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 7440, + [AN7583_GBE_TEMP_SENSOR] = 7620, + [AN7583_CPU_TEMP_SENSOR] = 8390, +}; + +static const unsigned int an7583_thermal_offset[AN7583_ADC_MUX_MAX] = { + [AN7583_BGP_TEMP_SENSOR] = 294, + [AN7583_GBE_TEMP_SENSOR] = 298, + [AN7583_CPU_TEMP_SENSOR] = 344, +}; + static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) { u32 val; @@ -234,7 +295,7 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) } static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, - int tdac_idx) + int tdac_idx, int sensor_idx) { u32 pllrg; @@ -245,9 +306,20 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, priv->pllrg_protect); + /* + * Configure Thermal Sensor mux to sensor_idx. + * (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 */ - regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], - tdac_idx); + if (priv->current_adc != tdac_idx) { + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], + tdac_idx); + priv->current_adc = tdac_idx; + } /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); @@ -361,7 +433,8 @@ static void en7581_thermal_setup_adc_val(struct device *dev, u32 cpu_sensor = 0; /* Setup Thermal Sensor to ADC mode and setup the mux to DIODE1 */ - airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1); + airoha_set_thermal_mux(priv, EN7581_SCU_THERMAL_MUX_DIODE1, + AIROHA_THERMAL_NO_MUX_SENSOR); regmap_read(priv->map, EN7581_EFUSE_TEMP_OFFSET_REG, &efuse_calib_info); if (efuse_calib_info) { @@ -477,6 +550,10 @@ static int en7581_thermal_probe(struct platform_device *pdev, for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { struct regmap_field *field; + /* Skip registering MUX_SENSOR field as not supported */ + if (i == AIROHA_THERMAL_MUX_SENSOR) + continue; + field = devm_regmap_field_alloc(dev, priv->chip_scu, en7581_chip_scu_fields[i]); if (IS_ERR(field)) { @@ -519,6 +596,74 @@ static int en7581_thermal_post_probe(struct platform_device *pdev) return 0; } +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 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); + + delta_diode = diode_d1 - diode_d0; + delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1); + if (!delta_gain) + return -EINVAL; + + *temp = (slope * delta_diode * 10) / delta_gain - offset * 10; + *temp *= 100; + + return 0; +} + +static const struct thermal_zone_device_ops an7583_tz_ops = { + .get_temp = an7583_thermal_get_temp, +}; + +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), +}; + +static int an7583_thermal_probe(struct platform_device *pdev, + struct airoha_thermal_priv *priv) +{ + struct device *dev = &pdev->dev; + int i; + + priv->chip_scu = device_node_to_regmap(dev->of_node); + if (IS_ERR(priv->chip_scu)) + return PTR_ERR(priv->chip_scu); + + for (i = 0; i < AIROHA_THERMAL_FIELD_MAX; i++) { + struct regmap_field *field; + + field = devm_regmap_field_alloc(dev, priv->chip_scu, + an7583_chip_scu_fields[i]); + if (IS_ERR(field)) + return PTR_ERR(field); + + priv->chip_scu_fields[i] = field; + } + + return 0; +} + static int airoha_thermal_probe(struct platform_device *pdev) { const struct airoha_thermal_soc_data *soc_data; @@ -533,6 +678,7 @@ static int airoha_thermal_probe(struct platform_device *pdev) return -ENOMEM; priv->pllrg_protect = soc_data->pllrg_protect; + priv->current_adc = -1; if (!soc_data->probe) return -EINVAL; @@ -561,8 +707,15 @@ static const struct airoha_thermal_soc_data en7581_data = { .post_probe = &en7581_thermal_post_probe, }; +static const struct airoha_thermal_soc_data an7583_data = { + .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY, + .thdev_ops = &an7583_tz_ops, + .probe = &an7583_thermal_probe, +}; + static const struct of_device_id airoha_thermal_match[] = { { .compatible = "airoha,en7581-thermal", .data = &en7581_data }, + { .compatible = "airoha,an7583-chip-scu", .data = &an7583_data }, {}, }; MODULE_DEVICE_TABLE(of, airoha_thermal_match); From 0c569e22020f53ddfac0099b0aa193907bfbcd6f Mon Sep 17 00:00:00 2001 From: Rakesh Kota Date: Fri, 24 Jul 2026 16:30:24 +0530 Subject: [PATCH 11/32] thermal/drivers/qcom-spmi-adc-tm5: Drop IIO_VAL_INT check in adc_tm5_get_temp Commit bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") fixed the iio_read_channel_processed_scale to return 0 on success instead of IIO_VAL_INT (1). The existing check in adc_tm5_get_temp() treated a successful return as an error because it expected IIO_VAL_INT. Drop the redundant `ret != IIO_VAL_INT` condition and rely solely on the negative error check. Fixes: bb21ee31f575 ("iio: Fix iio_multiply_value use in iio_read_channel_processed_scale") Signed-off-by: Rakesh Kota Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260724-adc-tm5-drop-iio-val-int-check-v1-1-0b85a0895dd7@oss.qualcomm.com --- drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index d7f2e6ca92c2..d1b086737bcd 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -369,9 +369,6 @@ static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp) if (ret < 0) return ret; - if (ret != IIO_VAL_INT) - return -EINVAL; - return 0; } From bcc6d886e5006a4656901d2d7fb6a215c96068a0 Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 22 Jul 2026 16:49:09 +0800 Subject: [PATCH 12/32] thermal/drivers/imx: Disable clock on runtime resume failure imx_thermal_runtime_resume() enables the thermal clock before powering up the sensor and enabling measurements. If either regmap_write() fails, the function returns with the clock still enabled. This leaves the clock enable count unbalanced after a failed runtime resume. Disable the clock on those failure paths before returning the error. Fixes: 4cf2ddf16e17 ("thermal/drivers/imx: Implement runtime PM support") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/20260722084909.463437-1-pengcan@kylinos.cn --- drivers/thermal/imx_thermal.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index 5aaacbc53478..b0e88b6a9352 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -832,12 +832,12 @@ static int imx_thermal_runtime_resume(struct device *dev) ret = regmap_write(map, socdata->sensor_ctrl + REG_CLR, socdata->power_down_mask); if (ret) - return ret; + goto disable_clk; ret = regmap_write(map, socdata->sensor_ctrl + REG_SET, socdata->measure_temp_mask); if (ret) - return ret; + goto disable_clk; /* * According to the temp sensor designers, it may require up to ~17us @@ -846,6 +846,11 @@ static int imx_thermal_runtime_resume(struct device *dev) usleep_range(20, 50); return 0; + +disable_clk: + clk_disable_unprepare(data->thermal_clk); + + return ret; } static const struct dev_pm_ops imx_thermal_pm_ops = { From 5d901d7d1002a60cf03266d13fe76ce303cc254c Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Wed, 22 Jul 2026 16:14:41 +0530 Subject: [PATCH 13/32] dt-bindings: thermal: Add Qualcomm MBG thermal monitor support Add bindings for the Qualcomm MBG (Master Bandgap) temperature alarm peripheral found on the PM8775 PMIC. Unlike the existing SPMI temp alarm peripheral, the MBG peripheral supports both hot and cold threshold monitoring across two programmable levels (LVL1 and LVL2), with interrupt status reported via a fault status register over SPMI. Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260722-spmi-mbg-driver-v3-1-ef73064f2789@oss.qualcomm.com --- .../bindings/mfd/qcom,spmi-pmic.yaml | 4 ++ .../bindings/thermal/qcom,pm8775-mbg-tm.yaml | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml diff --git a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml index 644c42b5e2e5..11db2b3f9a6b 100644 --- a/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml +++ b/Documentation/devicetree/bindings/mfd/qcom,spmi-pmic.yaml @@ -193,6 +193,10 @@ patternProperties: type: object $ref: /schemas/thermal/qcom,spmi-temp-alarm.yaml# + "^temperature-sensor@[0-9a-f]+$": + type: object + $ref: /schemas/thermal/qcom,pm8775-mbg-tm.yaml# + "^typec@[0-9a-f]+$": type: object $ref: /schemas/usb/qcom,pmic-typec.yaml# diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml new file mode 100644 index 000000000000..2e084d040625 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/qcom,pm8775-mbg-tm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm's SPMI PMIC MBG Thermal Monitoring + +maintainers: + - Jishnu Prakash + - Kamal Wadhwa + +description: + Qualcomm's MBG(Master Bandgap) temperature alarm monitors the die + temperature and generates an interrupt if the PMIC die temperature is + over a set of programmable temperature thresholds. It allows monitoring + for both hot and cold, LVL1 and LVL2 thresholds, which makes it different + from the existing temp alarm peripheral. The interrupt comes over SPMI + and the MBG's fault status register gives details to understand whether + it is a hot/cold and LVL1/LVL2 violation. + +properties: + compatible: + const: qcom,pm8775-mbg-tm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + io-channels: + items: + - description: ADC channel, which reports chip die temperature. + + io-channel-names: + items: + - const: thermal + + '#thermal-sensor-cells': + const: 0 + +required: + - compatible + - reg + - interrupts + - io-channels + - io-channel-names + +allOf: + - $ref: thermal-sensor.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + + pmic { + #address-cells = <1>; + #size-cells = <0>; + + temperature-sensor@d700 { + compatible = "qcom,pm8775-mbg-tm"; + reg = <0xd700>; + interrupts = <0x1 0xd7 0x0 IRQ_TYPE_EDGE_RISING>; + io-channels = <&pm8775_adc 0x3>; + io-channel-names = "thermal"; + #thermal-sensor-cells = <0>; + }; + }; +... From c3dce117333c0e2fbdb35228d4960442d2b1ef54 Mon Sep 17 00:00:00 2001 From: Satya Priya Kakitapalli Date: Wed, 22 Jul 2026 16:14:42 +0530 Subject: [PATCH 14/32] thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring Add a driver for the Qualcomm MBG (Master Bandgap) thermal monitoring device. It monitors PMIC die temperature in hardware and raises an interrupt when a programmed threshold is crossed. MBG hardware supports two upper-threshold levels. Currently, only the level-1 threshold is supported by the driver. Temperature is read from the associated ADC channel, and when a level-1 upper-threshold violation occurs, the hardware generates an interrupt over SPMI. The driver notifies the thermal framework accordingly. Signed-off-by: Satya Priya Kakitapalli Co-developed-by: Sachin Gupta Signed-off-by: Sachin Gupta Signed-off-by: Daniel Lezcano Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260722-spmi-mbg-driver-v3-2-ef73064f2789@oss.qualcomm.com --- drivers/thermal/qcom/Kconfig | 14 ++ drivers/thermal/qcom/Makefile | 1 + drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 256 ++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-mbg-tm.c diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index a6bb01082ec6..ecfd1d166e53 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -21,6 +21,20 @@ config QCOM_SPMI_ADC_TM5 Thermal client sets threshold temperature for both warm and cool and gets updated when a threshold is reached. +config QCOM_SPMI_MBG_TM + tristate "Qualcomm SPMI PMIC MBG Temperature monitor" + depends on QCOM_SPMI_ADC5_GEN3 + select REGMAP_SPMI + help + This enables Qualcomm PMIC MBG (Master Bandgap) thermal monitor. + + The MBG block monitors PMIC die temperature in hardware and raises an + interrupt when the programmed threshold is crossed. + + Temperature is read from the associated ADC channel, and threshold + interrupts are forwarded to the thermal framework as trip-violation + events. Current support handles a single LVL1 upper (hot) trip. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 0fa2512042e7..1bec2746b98d 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c new file mode 100644 index 000000000000..fa2f10002253 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MBG_TEMP_MON2_FAULT_STATUS 0x50 + +#define MON_FAULT_STATUS_MASK GENMASK(7, 4) +#define MON_FAULT_LVL1_UPR 0x5 + +#define MON2_LVL1_UP_THRESH 0x59 + +#define MBG_TEMP_MON2_MISC_CFG 0x5f +#define MON2_UP_THRESH_EN BIT(1) + +#define MBG_TEMP_STEP_MV 8 +#define MBG_TEMP_DEFAULT_TEMP_MV 600 +#define MBG_TEMP_CONSTANT 1000 +#define MBG_MIN_TRIP_TEMP 25000 +#define MBG_MAX_SUPPORTED_TEMP 160000 + +/** + * struct mbg_tm_chip - MBG thermal monitor device data. + * @map: regmap for accessing MBG thermal registers. + * @dev: mbg_tm_chip device. + * @tz_dev: thermal zone device registered with the thermal framework. + * @lock: mbg_tm_chip lock for set trip temperature. + * @base: base register offset for this MBG instance + * @irq: interrupt line used to signal threshold events + * @last_temp: last measured temperature. + * @last_thres_crossed: indicates whether the last interrupt crossed a threshold + * @adc: IIO ADC channel used for temperature sensing + */ +struct mbg_tm_chip { + struct regmap *map; + struct device *dev; + struct thermal_zone_device *tz_dev; + struct mutex lock; + unsigned int base; + int irq; + int last_temp; + bool last_thres_crossed; + struct iio_channel *adc; +}; + +/** + * struct mbg_map_table - temperature to voltage mapping entry + * @min_temp: minimum temperature supported by this mapping entry + * @vtemp0: reference voltage or ADC code corresponding to the temperature + * @tc: temperature coefficient used for conversion calculations + */ +struct mbg_map_table { + int min_temp; + int vtemp0; + int tc; +}; + +static const struct mbg_map_table map_table[] = { + { -60000, 4337, 1967 }, + { -40000, 4731, 1964 }, + { -20000, 5124, 1957 }, + { 0, 5515, 1949 }, + { 20000, 5905, 1940 }, + { 40000, 6293, 1930 }, + { 60000, 6679, 1921 }, + { 80000, 7064, 1910 }, + { 100000, 7446, 1896 }, + { 120000, 7825, 1878 }, + { 140000, 8201, 1859 }, +}; + +static int mbg_tm_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret, milli_celsius; + + scoped_guard(mutex, &chip->lock) { + if (chip->last_thres_crossed) { + dev_dbg(chip->dev, "last_temp: %d\n", chip->last_temp); + chip->last_thres_crossed = false; + *temp = chip->last_temp; + return 0; + } + } + + ret = iio_read_channel_processed(chip->adc, &milli_celsius); + if (ret < 0) { + dev_err(chip->dev, "Failed to read iio channel with %d\n", ret); + return ret; + } + + *temp = milli_celsius; + + return 0; +} + +static int temp_to_vtemp_mv(int temp) +{ + int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0; + + for (idx = 0; idx < ARRAY_SIZE(map_table); idx++) + if (temp >= map_table[idx].min_temp && + temp < (map_table[idx].min_temp + 20000)) { + tc = map_table[idx].tc; + t0 = map_table[idx].min_temp; + vtemp0 = map_table[idx].vtemp0; + break; + } + + /* + * Formula to calculate vtemp(mV) from a given temp + * vtemp = (temp - minT) * tc + vtemp0 + * tc, t0 and vtemp0 values are mentioned in the map_table array. + */ + vtemp = ((temp - t0) * tc + vtemp0 * 100000) / 1000000; + + /* step size is 8mV */ + return abs(vtemp - MBG_TEMP_DEFAULT_TEMP_MV) / MBG_TEMP_STEP_MV; +} + +static int mbg_tm_set_trip_temp(struct thermal_zone_device *tz, int low_temp, + int temp) +{ + struct mbg_tm_chip *chip = thermal_zone_device_priv(tz); + int ret = 0; + + guard(mutex)(&chip->lock); + + /* The HW has a limitation that the trip set must be above 25C */ + if (temp > MBG_MIN_TRIP_TEMP && temp < MBG_MAX_SUPPORTED_TEMP) { + ret = regmap_write(chip->map, chip->base + MON2_LVL1_UP_THRESH, + temp_to_vtemp_mv(temp)); + if (ret < 0) + return ret; + + ret = regmap_set_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + if (ret < 0) + return ret; + } else { + dev_err(chip->dev, "Set trip b/w 25C and 160C\n"); + ret = regmap_clear_bits(chip->map, chip->base + MBG_TEMP_MON2_MISC_CFG, + MON2_UP_THRESH_EN); + return -ERANGE; + } + + /* + * Configure the last_temp one degree higher, to ensure the + * violated temp is returned to thermal framework when it reads + * temperature for the first time after the violation happens. + * This is needed to account for the inaccuracy in the conversion + * formula used which leads to the thermal framework setting back + * the same thresholds in case the temperature it reads does not + * show violation. + */ + chip->last_temp = temp + MBG_TEMP_CONSTANT; + + return ret; +} + +static const struct thermal_zone_device_ops mbg_tm_ops = { + .get_temp = mbg_tm_get_temp, + .set_trips = mbg_tm_set_trip_temp, +}; + +static irqreturn_t mbg_tm_isr(int irq, void *data) +{ + struct mbg_tm_chip *chip = data; + int ret, val; + + scoped_guard(mutex, &chip->lock) { + ret = regmap_read(chip->map, chip->base + MBG_TEMP_MON2_FAULT_STATUS, &val); + if (ret < 0) + return IRQ_HANDLED; + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) + chip->last_thres_crossed = true; + } + + if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) { + dev_dbg(chip->dev, "Notifying Thermal, fault status=%d\n", val); + thermal_zone_device_update(chip->tz_dev, THERMAL_TRIP_VIOLATED); + } else { + dev_dbg(chip->dev, "Lvl1 upper threshold not violated, ignoring interrupt\n"); + } + + return IRQ_HANDLED; +} + +static int mbg_tm_probe(struct platform_device *pdev) +{ + struct mbg_tm_chip *chip; + struct device_node *node = pdev->dev.of_node; + u32 res; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + mutex_init(&chip->lock); + + chip->map = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->map) + return -ENXIO; + + ret = device_property_read_u32(chip->dev, "reg", &res); + if (ret < 0) + return dev_err_probe(chip->dev, ret, "Couldn't read reg property\n"); + + chip->base = res; + + chip->irq = platform_get_irq(pdev, 0); + if (chip->irq < 0) + return dev_err_probe(chip->dev, chip->irq, "Failed to get irq\n"); + + chip->adc = devm_iio_channel_get(&pdev->dev, "thermal"); + if (IS_ERR(chip->adc)) + return dev_err_probe(chip->dev, PTR_ERR(chip->adc), "Failed to get adc channel\n"); + + chip->tz_dev = devm_thermal_of_zone_register(chip->dev, 0, chip, &mbg_tm_ops); + if (IS_ERR(chip->tz_dev)) + return dev_err_probe(chip->dev, PTR_ERR(chip->tz_dev), + "Failed to register sensor\n"); + + return devm_request_threaded_irq(&pdev->dev, chip->irq, NULL, mbg_tm_isr, IRQF_ONESHOT, + node->name, chip); +} + +static const struct of_device_id mbg_tm_match_table[] = { + { .compatible = "qcom,pm8775-mbg-tm" }, + { } +}; +MODULE_DEVICE_TABLE(of, mbg_tm_match_table); + +static struct platform_driver mbg_tm_driver = { + .driver = { + .name = "qcom-spmi-mbg-tm", + .of_match_table = mbg_tm_match_table, + }, + .probe = mbg_tm_probe, +}; +module_platform_driver(mbg_tm_driver); + +MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); +MODULE_LICENSE("GPL"); From 83f98a48f22c36bdbae0f5e07e07b26be44816eb Mon Sep 17 00:00:00 2001 From: Haritha S K Date: Fri, 19 Jun 2026 15:50:59 +0530 Subject: [PATCH 15/32] dt-bindings: thermal: qcom-tsens: Document the Maili Temperature Sensor Document the Temperature Sensor (TSENS) on the Qualcomm Maili SoC. Acked-by: Krzysztof Kozlowski Signed-off-by: Haritha S K Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260619-b4-maili-upstream-3-v2-1-e54516c37022@oss.qualcomm.com --- 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 f0efaa8349ee..5a8f7673e730 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -58,6 +58,7 @@ properties: - qcom,glymur-tsens - qcom,hawi-tsens - qcom,kaanapali-tsens + - qcom,maili-tsens - qcom,milos-tsens - qcom,nord-tsens - qcom,msm8953-tsens From 9cbf780428ca1102377a228febd5fc4164ea6512 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Thu, 16 Jul 2026 15:04:19 +0800 Subject: [PATCH 16/32] thermal/drivers/spacemit/k1: Add shutdown action and reorder registration order Add a devm action to clean hardware interrupts, sampling, and control registers on driver unbind, mirroring what k1_tsensor_init() sets up. Reorder the registration order within probe(): register the thermal zones first, then request the IRQ, and register the shutdown action last. On removal, the hardware interrupt is disabled first, then the IRQ is released, and finally the thermal zones are released. This avoids the IRQ thread accessing an already unregistered thermal zone during devres cleanup. Signed-off-by: Pei Xiao Signed-off-by: Daniel Lezcano Reviewed-by: Troy Mitchell Link: https://patch.msgid.link/1967d2bcc8fede6fbd25fc8eee07f2873fb41472.1784184867.git.xiaopei01@kylinos.cn --- drivers/thermal/spacemit/k1_tsensor.c | 75 ++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c index 79222d233129..ab12e2ec8ae4 100644 --- a/drivers/thermal/spacemit/k1_tsensor.c +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -199,6 +199,39 @@ static irqreturn_t k1_tsensor_irq_thread(int irq, void *data) return IRQ_HANDLED; } +static void k1_tsensor_shutdown(struct k1_tsensor *ts) +{ + u32 val; + + /* Disable all interrupts */ + writel(0xffffffff, ts->base + K1_TSENSOR_INT_EN_REG); + + /* Disable all sensors */ + val = readl(ts->base + K1_TSENSOR_EN_REG); + val &= ~K1_TSENSOR_EN_ALL; + writel(val, ts->base + K1_TSENSOR_EN_REG); + + /* Clear the sampling configuration set by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_TIME_REG); + val &= ~(K1_TSENSOR_TIME_FILTER_PERIOD | + K1_TSENSOR_TIME_ADC_CNT_RST | + K1_TSENSOR_TIME_WAIT_REF_CNT); + writel(val, ts->base + K1_TSENSOR_TIME_REG); + + /* Clear the control bits configured by k1_tsensor_init() */ + val = readl(ts->base + K1_TSENSOR_PCTRL_REG); + val &= ~(K1_TSENSOR_PCTRL_RAW_SEL | + K1_TSENSOR_PCTRL_TEMP_MODE | + K1_TSENSOR_PCTRL_HW_AUTO_MODE | + K1_TSENSOR_PCTRL_ENABLE); + writel(val, ts->base + K1_TSENSOR_PCTRL_REG); +} + +static void k1_tsensor_shutdown_action(void *data) +{ + k1_tsensor_shutdown(data); +} + static int k1_tsensor_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -229,34 +262,48 @@ static int k1_tsensor_probe(struct platform_device *pdev) k1_tsensor_init(ts); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(dev, irq, NULL, - k1_tsensor_irq_thread, - IRQF_ONESHOT, "k1_tsensor", ts); - if (ret < 0) - return ret; - for (i = 0; i < MAX_SENSOR_NUMBER; ++i) { ts->ch[i].id = i; ts->ch[i].ts = ts; ts->ch[i].tzd = devm_thermal_of_zone_register(dev, i, ts->ch + i, &k1_tsensor_ops); - if (IS_ERR(ts->ch[i].tzd)) - return PTR_ERR(ts->ch[i].tzd); + if (IS_ERR(ts->ch[i].tzd)) { + ret = PTR_ERR(ts->ch[i].tzd); + goto err_shutdown; + } /* Attach sysfs hwmon attributes for userspace monitoring */ ret = devm_thermal_add_hwmon_sysfs(dev, ts->ch[i].tzd); if (ret) dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); - - k1_tsensor_enable_irq(ts->ch + i); } + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_shutdown; + } + + ret = devm_request_threaded_irq(dev, irq, NULL, + k1_tsensor_irq_thread, + IRQF_ONESHOT, "k1_tsensor", ts); + if (ret < 0) + goto err_shutdown; + + ret = devm_add_action_or_reset(dev, k1_tsensor_shutdown_action, ts); + if (ret) + return ret; + + /* Enable interrupts only after all zones and the handler are ready */ + for (i = 0; i < MAX_SENSOR_NUMBER; ++i) + k1_tsensor_enable_irq(ts->ch + i); + platform_set_drvdata(pdev, ts); return 0; + +err_shutdown: + k1_tsensor_shutdown(ts); + return ret; } static const struct of_device_id k1_tsensor_dt_ids[] = { From c4c40502363510d4179088babc327e4c904e3f79 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 4 Aug 2026 23:37:15 +0200 Subject: [PATCH 17/32] thermal/of: Fix trivial enabled typo Fix trivial typo, s@enabled@enable@. No functional change. Signed-off-by: Marek Vasut Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260804213738.287818-1-marek.vasut+renesas@mailbox.org --- drivers/thermal/thermal_of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 100fd8a0c8ce..0217a49b08ae 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -444,7 +444,7 @@ static struct thermal_zone_device *thermal_of_zone_register(struct device_node * ret = thermal_zone_device_enable(tz); if (ret) { - pr_err("Failed to enabled thermal zone '%s', id=%d: %d\n", + pr_err("Failed to enable thermal zone '%s', id=%d: %d\n", tz->type, tz->id, ret); thermal_of_zone_unregister(tz); return ERR_PTR(ret); From 7680af9184f1c9b8b22182c34c9d5282eebf3a97 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:37 +0530 Subject: [PATCH 18/32] iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print devm_request_threaded_irq() internally prints an error message using dev_err_probe() in case of any errors. Remove the error print in the devm_request_threaded_irq() failure path as it is not needed. Suggested-by: Andy Shevchenko Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Andy Shevchenko Reviewed-by: Jonathan Cameron Reviewed-by: Maxwell Doose Reviewed-by: Joshua Crofts Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-1-011981f756c8@oss.qualcomm.com --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index c56b650fd8c0..2eaebf6b34e8 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -821,9 +821,7 @@ static int adc5_gen3_probe(struct platform_device *pdev) adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, adc); if (ret) - return dev_err_probe(dev, ret, - "Failed to request SDAM%d irq\n", - ADC5_GEN3_VADC_SDAM); + return ret; ret = adc5_get_fw_data(adc); if (ret) From b39f9b4b21df871005a59e2aea814644896d0dd3 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:38 +0530 Subject: [PATCH 19/32] iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver The SDAM0 IRQ can be triggered for both EOC (end of conversion) events for immediate ADC reads done in this driver and for threshold violation events, based on ADC_TM thresholds configured from the auxiliary ADC_TM driver on TM channels on the first SDAM. At present, this interrupt is handled only in the ISR in the main ADC driver. When the ISR is triggered for an ADC_TM event, this driver notifies the ADC_TM driver by calling a notifier callback exposed from it for this purpose. To simplify the interrupt handling in both drivers, share the interrupt between the drivers. With this, ADC_TM interrupts on SDAM0 will be handled directly in the ADC_TM driver, so remove the notifier callback and all TM interrupt handling in the main ADC ISR. Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-2-011981f756c8@oss.qualcomm.com --- drivers/iio/adc/qcom-spmi-adc5-gen3.c | 65 +++++-------------- include/linux/iio/adc/qcom-adc5-gen3-common.h | 2 - 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c index 2eaebf6b34e8..c68c6c5f6aca 100644 --- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c @@ -55,9 +55,6 @@ struct adc5_channel_prop { * requests from multiple clients. * @data: software configuration data. * @n_tm_channels: number of ADC channels used for TM measurements. - * @handler: TM callback to be called for threshold violation interrupt - * on first SDAM. - * @tm_aux: pointer to auxiliary TM device. */ struct adc5_chip { struct device *dev; @@ -69,8 +66,6 @@ struct adc5_chip { struct mutex lock; const struct adc5_data *data; unsigned int n_tm_channels; - void (*handler)(struct auxiliary_device *tm_aux); - struct auxiliary_device *tm_aux; }; int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index, @@ -286,23 +281,21 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) { struct adc5_chip *adc = dev_id; struct device *dev = adc->dev; - struct auxiliary_device *adev; u8 status, eoc_status, val; - u8 tm_status[2]; int ret; ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_STATUS1, &status, sizeof(status)); if (ret) { dev_err(dev, "adc read status1 failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status)); if (ret) { dev_err(dev, "adc read eoc status failed with %d\n", ret); - return IRQ_HANDLED; + return IRQ_NONE; } if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { @@ -315,30 +308,13 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id) return IRQ_HANDLED; } + dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x\n", status, eoc_status); + /* CHAN0 is the preconfigured channel for immediate conversion */ - if (eoc_status & ADC5_GEN3_EOC_CHAN_0) - complete(&adc->complete); - - ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, - ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status)); - if (ret) { - dev_err(dev, "adc read TM status failed with %d\n", ret); - return IRQ_HANDLED; - } - - dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n", - status, eoc_status, tm_status[0], tm_status[1]); - - if (tm_status[0] || tm_status[1]) { - adev = adc->tm_aux; - if (!adev || !adev->dev.driver) { - dev_err(dev, "adc_tm auxiliary device not initialized\n"); - return IRQ_HANDLED; - } - - adc->handler(adev); - } + if (!(eoc_status & ADC5_GEN3_EOC_CHAN_0)) + return IRQ_NONE; + complete(&adc->complete); return IRQ_HANDLED; } @@ -683,8 +659,6 @@ static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc) if (ret) return ret; - adc->tm_aux = &aux_device->aux_dev; - return 0; } @@ -740,16 +714,6 @@ int adc5_gen3_therm_code_to_temp(struct device *dev, } EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3"); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)) -{ - struct iio_dev *indio_dev = dev_get_drvdata(dev->parent); - struct adc5_chip *adc = iio_priv(indio_dev); - - adc->handler = handler; -} -EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3"); - static int adc5_gen3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -816,10 +780,17 @@ static int adc5_gen3_probe(struct platform_device *pdev) return -ENOMEM; } - ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, - adc5_gen3_isr, 0, - adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, - adc); + /* + * This interrupt is shared with the ADC_TM auxiliary driver, which + * is threaded and uses IRQF_ONESHOT. Since shared interrupts need + * to agree on IRQF_ONESHOT configuration and there is a kernel + * warning for using IRQF_ONESHOT with non-threaded interrupts, + * make this also a threaded IRQ. + */ + ret = devm_request_threaded_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq, + NULL, adc5_gen3_isr, IRQF_ONESHOT | IRQF_SHARED, + adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name, + adc); if (ret) return ret; diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h index 6303eaa6640b..39cbfcbdb101 100644 --- a/include/linux/iio/adc/qcom-adc5-gen3-common.h +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h @@ -205,7 +205,5 @@ int adc5_gen3_get_scaled_reading(struct device *dev, int adc5_gen3_therm_code_to_temp(struct device *dev, struct adc5_channel_common_prop *common_props, u16 code, int *val); -void adc5_gen3_register_tm_event_notifier(struct device *dev, - void (*handler)(struct auxiliary_device *)); #endif /* QCOM_ADC5_GEN3_COMMON_H */ From 948ee3a74f35644c16b6c100f19ff0799cebe0b5 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 22 Jul 2026 16:56:39 +0530 Subject: [PATCH 20/32] thermal/drivers/qcom: add support for PMIC5 Gen3 ADC thermal monitoring Add support for ADC_TM part of PMIC5 Gen3 in an auxiliary driver under the Gen3 ADC driver. Its functionality is similar to that of PMIC5 Gen2 ADC_TM, which implements the threshold setting and interrupt generating functions, used to support thermal trip points. In Gen3 ADC, the register interface is implemented on one or more SDAM (Shared Direct Access Memory) peripherals instead of dedicated ADC peripherals. Each ADC SDAM has eight channels which can be configured for either immediate reads (main ADC driver's functionality) or ADC_TM reads. By convention, the first channel of the first ADC SDAM is reserved for all immediate reads and remaining channels across all SDAMs are used for ADC_TM functionality. On the first SDAM, the interrupt line and configuration registers are shared between the main ADC and auxiliary ADC_TM drivers. Access to the registers is protected through a mutex shared between the drivers. The ADC_TM driver accesses this mutex and some other functions shared from the main driver (like adc5_gen3_get_scaled_reading() for immediate channel reads in the .get_temp() callback) through APIs exported into a shared namespace. Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Acked-by: Andy Shevchenko Link: https://patch.msgid.link/20260722-gen3_adc_tm-v4-3-011981f756c8@oss.qualcomm.com --- drivers/thermal/qcom/Kconfig | 9 +++++++++ drivers/thermal/qcom/Makefile | 1 + 2 files changed, 10 insertions(+) diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index ecfd1d166e53..a8cf7e258201 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -35,6 +35,15 @@ config QCOM_SPMI_MBG_TM interrupts are forwarded to the thermal framework as trip-violation events. Current support handles a single LVL1 upper (hot) trip. +config QCOM_SPMI_ADC_TM5_GEN3 + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3" + depends on QCOM_SPMI_ADC5_GEN3 + help + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal + monitoring device. It shows up as a thermal zone with multiple trip points. + Thermal client sets threshold temperature for both warm and cool and + gets updated when a threshold is reached. + config QCOM_SPMI_TEMP_ALARM tristate "Qualcomm SPMI PMIC Temperature Alarm" depends on OF && SPMI && IIO diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 1bec2746b98d..937ba0fe2801 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ tsens-8960.o obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o obj-$(CONFIG_QCOM_SPMI_MBG_TM) += qcom-spmi-mbg-tm.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_QCOM_LMH) += lmh.o From 5f0d4a79439120650a858b9ccb345d08929df990 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 12:52:29 +0200 Subject: [PATCH 21/32] dt-bindings: thermal: mediatek: Make resets optional for MT8196 Both LVTS-AP and LVTS-MCU may be shared with SoC-internal MCUs running some sort of firmware that checks thermals in order to scale frequency, or to take action for critical SoC thermal protection - and this is seen on most MT8196 boards. Make resets optional, as doing a HW reset on such boards will result in either an immediate thermal protect shutdown or in a rather important and usually permanent system slowdown. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260721105230.101906-2-angelogioacchino.delregno@collabora.com --- .../bindings/thermal/mediatek,lvts-thermal.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml b/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml index 975235130670..29f431fcdcd5 100644 --- a/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/mediatek,lvts-thermal.yaml @@ -94,12 +94,23 @@ allOf: nvmem-cell-names: minItems: 2 + - if: + properties: + compatible: + not: + contains: + enum: + - mediatek,mt8196-lvts-ap + - mediatek,mt8196-lvts-mcu + then: + required: + - resets + required: - compatible - reg - interrupts - clocks - - resets - nvmem-cells - nvmem-cell-names From 6b73e78462ad420a61ef0bede534c0fecb0d7ba6 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Tue, 21 Jul 2026 12:52:30 +0200 Subject: [PATCH 22/32] thermal/drivers/mediatek/lvts_thermal: Make reset optional for MT8196 Depending on the SoC+Firmware combination, the LVTS hardware may be may be actively used by one or even multiple concurrent MCUs! In this case, resetting it may produce either a severe slowdown of the entire system, or even a thermal protection AP reset, as some MCU(s) may be reading a very high or very low temperature while the LVTS is being reset. On those, don't fail if no reset is found as that may be omitted on purpose, but still check if there's one, because some board(s) may be running on a different bootchain with reduced firmwares or using firmwares with reduced functionality. So, use devm_reset_control_get_optional_exclusive() instead, as the LVTS controller always had only one reset and retrieving that by index, specifically, always made little sense anyway. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Daniel Lezcano Reviewed-by: Philipp Zabel Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260721105230.101906-3-angelogioacchino.delregno@collabora.com --- drivers/thermal/mediatek/lvts_thermal.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c index a9617d5e0077..d5dbfc3d128e 100644 --- a/drivers/thermal/mediatek/lvts_thermal.c +++ b/drivers/thermal/mediatek/lvts_thermal.c @@ -1470,7 +1470,20 @@ static int lvts_probe(struct platform_device *pdev) if (IS_ERR(lvts_td->base)) return dev_err_probe(dev, PTR_ERR(lvts_td->base), "Failed to map io resource\n"); - lvts_td->reset = devm_reset_control_get_by_index(dev, 0); + /* + * Depending on the SoC+Firmware combination, the LVTS hardware may be + * may be actively used by one or even multiple concurrent MCUs! + * In this case, resetting it may produce either a severe slowdown of + * the entire system, or even a thermal protection AP reset, as some + * MCU(s) may be reading a very high or very low temperature while the + * LVTS is being reset. + * + * On those, don't fail if no reset is found as that may be omitted on + * purpose, but still check if there's one, because some board(s) may + * be running on a different bootchain with reduced firmwares or using + * firmwares with reduced functionality. + */ + lvts_td->reset = devm_reset_control_get_optional_exclusive(dev, NULL); if (IS_ERR(lvts_td->reset)) return dev_err_probe(dev, PTR_ERR(lvts_td->reset), "Failed to get reset control\n"); From fcbf9964b67a6d6704c50ed28daa24c3b164f01c Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 22 Jul 2026 15:56:25 +0800 Subject: [PATCH 23/32] thermal/drivers/qoriq: Disable clock on resume failure qoriq_tmu_resume() enables the TMU clock before clearing the power-down bit and enabling monitoring. If either register update fails, the function returns with the clock still enabled. This leaves the clock enable count unbalanced after a failed resume. Disable the clock on those failure paths before returning the error. Fixes: 51904045d4aa ("thermal: qoriq: Add clock operations") Cc: stable@vger.kernel.org Signed-off-by: Can Peng Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260722075625.452684-1-pengcan@kylinos.cn --- drivers/thermal/qoriq_thermal.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index 35439ec5f8bc..297724e81593 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -415,11 +415,20 @@ static int qoriq_tmu_resume(struct device *dev) if (data->ver > TMU_VER1) { ret = regmap_clear_bits(data->regmap, REGS_TMR, TMR_CMD); if (ret) - return ret; + goto disable_clk; } /* Enable monitoring */ - return regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME); + if (ret) + goto disable_clk; + + return 0; + +disable_clk: + clk_disable_unprepare(data->clk); + + return ret; } static DEFINE_SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops, From cb0ec27efac2aaf92393ad563a35fb6f00d0a6cd Mon Sep 17 00:00:00 2001 From: Andreas Haufler Date: Tue, 21 Jul 2026 10:32:13 +0200 Subject: [PATCH 24/32] tools/lib/thermal: Fix misplaced extern "C" closing brace The public libthermal header opens the C++ 'extern "C" {' block inside the __LIBTHERMAL_H include guard, but places the closing brace after the guard has already ended: #endif /* __LIBTHERMAL_H */ #ifdef __cplusplus } #endif On a single inclusion the braces still balance, so the problem is invisible. On the second inclusion of the header in the same C++ translation unit the include guard skips the opening 'extern "C" {', while the closing '}' lives outside the guard and is emitted anyway. This leaves a stray '}' and breaks compilation for any C++ consumer that includes the header more than once. Move the closing block inside the include guard so both halves of the 'extern "C"' declaration are guarded consistently. Signed-off-by: Andreas Haufler Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260721083230.91246-1-andreas@haufler.info --- tools/lib/thermal/include/thermal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/thermal/include/thermal.h b/tools/lib/thermal/include/thermal.h index 818ecdfb46e5..d9097271d9fa 100644 --- a/tools/lib/thermal/include/thermal.h +++ b/tools/lib/thermal/include/thermal.h @@ -175,8 +175,8 @@ LIBTHERMAL_API thermal_error_t thermal_sampling_handle(struct thermal_handler *t LIBTHERMAL_API int thermal_sampling_fd(struct thermal_handler *th); -#endif /* __LIBTHERMAL_H */ - #ifdef __cplusplus } #endif + +#endif /* __LIBTHERMAL_H */ From 68b78ad1e3533c1330dc9b7517947491c1a909c3 Mon Sep 17 00:00:00 2001 From: surendra Date: Mon, 20 Jul 2026 16:06:06 +0530 Subject: [PATCH 25/32] thermal/drivers/spacemit: Validate clamped trip thresholds k1_tsensor_set_trips() checks the requested trip temperatures before converting them to the sensor register representation. Distinct out-of-range temperatures can clamp to the same hardware value, leaving the sensor with an invalid low/high threshold pair. Validate the ordering after conversion and clamping. Fixes: 296a977f2bac ("thermal/drivers/spacemit/k1: Add thermal sensor support") Signed-off-by: surendra Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260720103606.93924-1-kr494167@gmail.com --- drivers/thermal/spacemit/k1_tsensor.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/spacemit/k1_tsensor.c b/drivers/thermal/spacemit/k1_tsensor.c index ab12e2ec8ae4..ef91453e5538 100644 --- a/drivers/thermal/spacemit/k1_tsensor.c +++ b/drivers/thermal/spacemit/k1_tsensor.c @@ -156,13 +156,12 @@ static int k1_tsensor_set_trips(struct thermal_zone_device *tz, int low, int hig struct k1_tsensor *ts = ch->ts; u32 val; - if (low >= high) - return -EINVAL; - low = clamp_val(low / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, FIELD_MAX(K1_TSENSOR_THRSH_LOW_MASK)); high = clamp_val(high / 1000 + TEMPERATURE_OFFSET, TEMPERATURE_OFFSET, FIELD_MAX(K1_TSENSOR_THRSH_HIGH_MASK)); + if (low >= high) + return -EINVAL; val = readl(ts->base + K1_TSENSOR_THRSH_REG(ch->id)); From 55c16e15ff9f91bc994a20124b83faf27f0e3690 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Fri, 17 Jul 2026 10:47:10 +0800 Subject: [PATCH 26/32] thermal/drivers: Remove redundant error messages on IRQ request failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() and devm_request_threaded_irq() automatically log detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Reviewed-by: Andy Shevchenko Reviewed-by: Geert Uytterhoeven Reviewed-by: Niklas Söderlund Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260717024733.374913-1-panchuang@vivo.com --- drivers/thermal/airoha_thermal.c | 4 +- drivers/thermal/armada_thermal.c | 5 +- drivers/thermal/broadcom/brcmstb_thermal.c | 3 +- drivers/thermal/db8500_thermal.c | 8 +- drivers/thermal/hisi_thermal.c | 4 +- drivers/thermal/imx91_thermal.c | 2 +- drivers/thermal/imx_thermal.c | 4 +- .../processor_thermal_device_pci.c | 8 +- .../thermal/intel/intel_bxt_pmic_thermal.c | 4 +- drivers/thermal/loongson2_thermal.c | 2 +- drivers/thermal/max77620_thermal.c | 8 +- drivers/thermal/mediatek/lvts_thermal.c | 2 +- drivers/thermal/qcom/lmh.c | 1 - drivers/thermal/qcom/tsens.c | 5 +- drivers/thermal/renesas/rcar_thermal.c | 4 +- drivers/thermal/renesas/rzg3e_thermal.c | 4 +- drivers/thermal/rockchip_thermal.c | 3 +- drivers/thermal/samsung/exynos_tmu.c | 4 +- drivers/thermal/st/st_thermal_memmap.c | 4 +- drivers/thermal/st/stm_thermal.c | 5 +- drivers/thermal/tegra/soctherm.c | 8 +- drivers/thermal/tegra/tegra30-tsensor.c | 3 +- drivers/thermal/thermal-fw.c | 12 +++ drivers/thermal/thermal_fw.c | 97 +++++++++++++++++++ 24 files changed, 134 insertions(+), 70 deletions(-) create mode 100644 drivers/thermal/thermal-fw.c create mode 100644 drivers/thermal/thermal_fw.c diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index c84b5c36e880..77b3a1af271f 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -574,10 +574,8 @@ static int en7581_thermal_probe(struct platform_device *pdev, ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, en7581_thermal_irq, IRQF_ONESHOT, pdev->name, priv); - if (ret) { - dev_err(dev, "Can't get interrupt working.\n"); + if (ret) return ret; - } en7581_thermal_setup_monitor(priv); en7581_thermal_setup_adc_val(dev, priv); diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index f64e46fcf0be..912c68a44bdc 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -900,11 +900,8 @@ static int armada_thermal_probe(struct platform_device *pdev) armada_overheat_isr, armada_overheat_isr_thread, 0, NULL, priv); - if (ret) { - dev_err(&pdev->dev, "Cannot request threaded IRQ %d\n", - irq); + if (ret) return ret; - } } /* diff --git a/drivers/thermal/broadcom/brcmstb_thermal.c b/drivers/thermal/broadcom/brcmstb_thermal.c index a9ffa596f7c0..5e23f9e00847 100644 --- a/drivers/thermal/broadcom/brcmstb_thermal.c +++ b/drivers/thermal/broadcom/brcmstb_thermal.c @@ -356,8 +356,7 @@ static int brcmstb_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, DRV_NAME, priv); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "could not request IRQ\n"); + return ret; } dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n"); diff --git a/drivers/thermal/db8500_thermal.c b/drivers/thermal/db8500_thermal.c index 576f88b6a1b3..c47aa9974c1e 100644 --- a/drivers/thermal/db8500_thermal.c +++ b/drivers/thermal/db8500_thermal.c @@ -167,10 +167,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, low_irq, NULL, prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_low", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp low irq\n"); + if (ret < 0) return ret; - } high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); if (high_irq < 0) @@ -179,10 +177,8 @@ static int db8500_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, high_irq, NULL, prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, "dbx500_temp_high", th); - if (ret < 0) { - dev_err(dev, "failed to allocate temp high irq\n"); + if (ret < 0) return ret; - } /* register of thermal sensor and get info from DT */ th->tz = devm_thermal_of_zone_register(dev, 0, th, &thdev_ops); diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c index 4307161533a7..17ed0c5b7767 100644 --- a/drivers/thermal/hisi_thermal.c +++ b/drivers/thermal/hisi_thermal.c @@ -578,10 +578,8 @@ static int hisi_thermal_probe(struct platform_device *pdev) hisi_thermal_alarm_irq_thread, IRQF_ONESHOT, sensor->irq_name, sensor); - if (ret < 0) { - dev_err(dev, "Failed to request alarm irq: %d\n", ret); + if (ret < 0) return ret; - } ret = data->ops->enable_sensor(sensor); if (ret) { diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c index 25915bb702be..274eee303142 100644 --- a/drivers/thermal/imx91_thermal.c +++ b/drivers/thermal/imx91_thermal.c @@ -331,7 +331,7 @@ static int imx91_tmu_probe(struct platform_device *pdev) IRQF_ONESHOT, "imx91_thermal", tmu); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; pm_runtime_put(dev); diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index b0e88b6a9352..887d381541a3 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -732,10 +732,8 @@ static int imx_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, data->irq, imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread, 0, "imx_thermal", data); - if (ret < 0) { - dev_err(dev, "failed to request alarm irq: %d\n", ret); + if (ret < 0) goto thermal_zone_unregister; - } pm_runtime_put(data->dev); diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c index c693d934103a..c5131423ec9b 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device_pci.c @@ -308,10 +308,8 @@ static int proc_thermal_setup_msi(struct pci_dev *pdev, struct proc_thermal_pci ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, 0, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", irq); + if (ret) goto err_free_msi_vectors; - } proc_thermal_msi_map[i] = irq; } @@ -394,10 +392,8 @@ static int proc_thermal_pci_probe(struct pci_dev *pdev, const struct pci_device_ ret = devm_request_threaded_irq(&pdev->dev, irq, proc_thermal_irq_handler, proc_thermal_irq_thread_handler, irq_flag, KBUILD_MODNAME, pci_info); - if (ret) { - dev_err(&pdev->dev, "Request IRQ %d failed\n", pdev->irq); + if (ret) goto err_ret_tzone; - } } ret = thermal_zone_device_enable(pci_info->tzone); diff --git a/drivers/thermal/intel/intel_bxt_pmic_thermal.c b/drivers/thermal/intel/intel_bxt_pmic_thermal.c index 6312c6ba081f..aeaefbbd5d8f 100644 --- a/drivers/thermal/intel/intel_bxt_pmic_thermal.c +++ b/drivers/thermal/intel/intel_bxt_pmic_thermal.c @@ -245,10 +245,8 @@ static int pmic_thermal_probe(struct platform_device *pdev) NULL, pmic_thermal_irq_handler, IRQF_ONESHOT, "pmic_thermal", pdev); - if (ret) { - dev_err(dev, "request irq(%d) failed: %d\n", virq, ret); + if (ret) return ret; - } pmic_irq_count++; } diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c index 88f87badfdf6..4d40fc706a53 100644 --- a/drivers/thermal/loongson2_thermal.c +++ b/drivers/thermal/loongson2_thermal.c @@ -173,7 +173,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread, IRQF_ONESHOT, "loongson2_thermal", tzd); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request alarm irq\n"); + return ret; devm_thermal_add_hwmon_sysfs(dev, tzd); diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index 85a12e98d6dc..f4a1535f4806 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -121,19 +121,15 @@ static int max77620_thermal_probe(struct platform_device *pdev) max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq1: %d\n", ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, mtherm->irq_tjalarm2, NULL, max77620_thermal_irq, IRQF_ONESHOT | IRQF_SHARED, dev_name(&pdev->dev), mtherm); - if (ret < 0) { - dev_err(&pdev->dev, "Failed to request irq2: %d\n", ret); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c index d5dbfc3d128e..52f99f3e6450 100644 --- a/drivers/thermal/mediatek/lvts_thermal.c +++ b/drivers/thermal/mediatek/lvts_thermal.c @@ -1504,7 +1504,7 @@ static int lvts_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, lvts_irq_handler, IRQF_ONESHOT, dev_name(dev), lvts_td); if (ret) - return dev_err_probe(dev, ret, "Failed to request interrupt\n"); + return ret; platform_set_drvdata(pdev, lvts_td); diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c index 3d072b7a4a6d..99396b93eff5 100644 --- a/drivers/thermal/qcom/lmh.c +++ b/drivers/thermal/qcom/lmh.c @@ -223,7 +223,6 @@ static int lmh_probe(struct platform_device *pdev) IRQF_NO_THREAD | IRQF_NO_SUSPEND, "lmh-irq", lmh_data); if (ret) { - dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq); irq_domain_remove(lmh_data->domain); return ret; } diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 6e3714ecab1d..b5ec70201e2f 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1258,10 +1258,7 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, dev_name(&pdev->dev), priv); - if (ret) - dev_err(&pdev->dev, "%s: failed to get irq\n", - __func__); - else + if (!ret) *irq_num = irq; } diff --git a/drivers/thermal/renesas/rcar_thermal.c b/drivers/thermal/renesas/rcar_thermal.c index fd686da9252e..9a9038671b04 100644 --- a/drivers/thermal/renesas/rcar_thermal.c +++ b/drivers/thermal/renesas/rcar_thermal.c @@ -446,10 +446,8 @@ static int rcar_thermal_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, rcar_thermal_irq, IRQF_SHARED, dev_name(dev), common); - if (ret) { - dev_err(dev, "irq request failed\n"); + if (ret) goto error_unregister; - } /* update ENR bits */ if (chip->irq_per_ch) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index f0e29fe633db..c44f5b8858d0 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -461,10 +461,8 @@ static int rzg3e_thermal_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, rzg3e_thermal_irq, rzg3e_thermal_irq_thread, IRQF_ONESHOT, "rzg3e_thermal", priv); - if (ret) { - dev_err(dev, "Failed to request IRQ: %d\n", ret); + if (ret) goto err_pm_put; - } /* Add hwmon sysfs interface */ ret = devm_thermal_add_hwmon_sysfs(dev, priv->zone); diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index c49ddf70f86e..08891608baa6 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1773,8 +1773,7 @@ static int rockchip_thermal_probe(struct platform_device *pdev) IRQF_ONESHOT, "rockchip_thermal", thermal); if (error) - return dev_err_probe(&pdev->dev, error, - "failed to request tsadc irq.\n"); + return error; thermal->chip->control(thermal->regs, true); diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index 47a99b3c5395..56717bb50d60 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -1102,10 +1102,8 @@ static int exynos_tmu_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_ONESHOT, dev_name(dev), data); - if (ret) { - dev_err(dev, "Failed to request irq: %d\n", data->irq); + if (ret) goto err_sclk; - } exynos_tmu_control(pdev, true); return 0; diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c index 8f76e50ea567..e3dbe4df80cb 100644 --- a/drivers/thermal/st/st_thermal_memmap.c +++ b/drivers/thermal/st/st_thermal_memmap.c @@ -101,10 +101,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor) NULL, st_mmap_thermal_trip_handler, IRQF_TRIGGER_RISING | IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "failed to register IRQ %d\n", sensor->irq); + if (ret) return ret; - } return st_mmap_enable_irq(sensor); } diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c index 5d8170bfb382..3290da7ab607 100644 --- a/drivers/thermal/st/stm_thermal.c +++ b/drivers/thermal/st/stm_thermal.c @@ -390,11 +390,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor) stm_thermal_irq_handler, IRQF_ONESHOT, dev->driver->name, sensor); - if (ret) { - dev_err(dev, "%s: Failed to register IRQ %d\n", __func__, - sensor->irq); + if (ret) return ret; - } dev_dbg(dev, "%s: thermal IRQ registered", __func__); diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index d8e988a0d43e..f74acf13b24d 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -2007,10 +2007,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, dev_name(&pdev->dev), tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'thermal_irq' failed.\n"); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, tegra->edp_irq, @@ -2019,10 +2017,8 @@ static int soctherm_interrupts_init(struct platform_device *pdev, IRQF_ONESHOT, "soctherm_edp", tegra); - if (ret < 0) { - dev_err(&pdev->dev, "request_irq 'edp_irq' failed.\n"); + if (ret < 0) return ret; - } return 0; } diff --git a/drivers/thermal/tegra/tegra30-tsensor.c b/drivers/thermal/tegra/tegra30-tsensor.c index 6245f6b97f43..10a5ab1fe1b9 100644 --- a/drivers/thermal/tegra/tegra30-tsensor.c +++ b/drivers/thermal/tegra/tegra30-tsensor.c @@ -602,8 +602,7 @@ static int tegra_tsensor_probe(struct platform_device *pdev) tegra_tsensor_isr, IRQF_ONESHOT, "tegra_tsensor", ts); if (err) - return dev_err_probe(&pdev->dev, err, - "failed to request interrupt\n"); + return err; return 0; } diff --git a/drivers/thermal/thermal-fw.c b/drivers/thermal/thermal-fw.c new file mode 100644 index 000000000000..47c557e3244d --- /dev/null +++ b/drivers/thermal/thermal-fw.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * of-thermal-testing.c - Generic Thermal Management device tree testing support + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include + + + diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c new file mode 100644 index 000000000000..e8bd2049cf74 --- /dev/null +++ b/drivers/thermal/thermal_fw.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * thermal-fw.c - Thermal components creation from firmware description + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +#include +#include +#include +#include + +/** + * 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 + * @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 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. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +thermal_fwnode_cooling_device_register(struct fwnode_handle *fwnode, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device *cdev; + + cdev = __thermal_cooling_device_register(type, devdata, ops); + if (IS_ERR(cdev)) + return cdev; + + cdev->np = (struct device_node *)fwnode; + cdev->of_index = fwn_index; + thermal_cooling_device_init_complete(cdev); + + return cdev; +} +EXPORT_SYMBOL_GPL(thermal_fwnode_cooling_device_register); + +static struct thermal_cooling_device * +__devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_handle *fwnode, + int fwn_index, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + struct thermal_cooling_device **ptr, *tcd; + + ptr = devres_alloc(thermal_cooling_device_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + tcd = thermal_fwnode_cooling_device_register(fwnode, fwn_index, type, devdata, ops); + if (IS_ERR(tcd)) { + devres_free(ptr); + return tcd; + } + + *ptr = tcd; + devres_add(dev, ptr); + + return tcd; +} + +/** + * 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 + * @type: the thermal cooling device type. + * @devdata: device private data. + * @ops: standard thermal cooling devices callbacks. + * + * 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. + * + * Return: a pointer to the created struct thermal_cooling_device or an + * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + */ +struct thermal_cooling_device * +devm_thermal_fwnode_cooling_device_register(struct device *dev, int fwn_index, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return __devm_thermal_fwnode_cooling_device_register(dev, dev_fwnode(dev), fwn_index, + type, devdata, ops); +} +EXPORT_SYMBOL_GPL(devm_thermal_fwnode_cooling_device_register); + + From cebe359188013aa54e3afd06cc6461c6ebbfb6d3 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Tue, 11 Aug 2026 16:54:26 +0200 Subject: [PATCH 27/32] thermal/drivers/qcom: Fix missing spmi adc tm5 gen3 file Add missing file resulting from a manual application of the change below after fixing a conflict in the Makefile. Fixes: 948ee3a74f35 ("thermal/drivers/qcom: add support for PMIC5 Gen3 ADC thermal monitoring") Signed-off-by: Jishnu Prakash Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260811145427.3089426-1-daniel.lezcano@kernel.org --- drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 ++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c new file mode 100644 index 000000000000..9cf552d36868 --- /dev/null +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define ADC_TM5_GEN3_CONFIG_REGS 12 + +struct device; +struct adc_tm5_gen3_chip; + +/** + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure + * @common_props: structure with common ADC channel properties. + * @chip: ADC TM device. + * @tzd: pointer to thermal device corresponding to TM channel. + * @sdam_index: SDAM on which this TM channel lies. + * @timer: time period of recurring TM measurement. + * @tm_chan_index: TM channel number used. + * @high_thr_en: TM high threshold crossing detection enabled. + * @low_thr_en: TM low threshold crossing detection enabled. + */ +struct adc_tm5_gen3_channel_props { + struct adc5_channel_common_prop common_props; + struct adc_tm5_gen3_chip *chip; + struct thermal_zone_device *tzd; + unsigned int sdam_index; + unsigned int timer; + unsigned int tm_chan_index; + bool high_thr_en; + bool low_thr_en; +}; + +/** + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure + * @dev_data: Top-level ADC device data. + * @chan_props: Array of ADC_TM channel structures. + * @dev: SPMI ADC5 Gen3 device. + * @nchannels: number of TM channels allocated + */ +struct adc_tm5_gen3_chip { + struct adc5_device_data *dev_data; + struct adc_tm5_gen3_channel_props *chan_props; + struct device *dev; + unsigned int nchannels; +}; + +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *, + adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev)) + +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq) +{ + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + if (adc_tm5->dev_data->base[i].irq == irq) + return i; + } + return -ENOENT; +} + +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + int ret, sdam_num; + u8 tm_status[2]; + u8 status, val; + + sdam_num = get_sdam_from_irq(adc_tm5, irq); + if (sdam_num < 0) + return IRQ_NONE; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1, + &status, sizeof(status)); + if (ret) + return IRQ_NONE; + + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) { + val = ADC5_GEN3_CONV_ERR_CLR_REQ; + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num, + ADC5_GEN3_CONV_ERR_CLR, &val, 1); + return IRQ_HANDLED; + } + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + if (tm_status[0] || tm_status[1]) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id) +{ + struct adc_tm5_gen3_chip *adc_tm5 = dev_id; + u8 tm_status[2]; + int sdam_index; + + sdam_index = get_sdam_from_irq(adc_tm5, irq); + if (sdam_index < 0) + return IRQ_NONE; + + scoped_guard(adc5_gen3, adc_tm5) { + int ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS, + tm_status, sizeof(tm_status)); + if (ret) + return IRQ_NONE; + + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, tm_status, + sizeof(tm_status)); + if (ret) + return IRQ_NONE; + } + + for (int i = 0; i < adc_tm5->nchannels; i++) { + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i]; + int offset = chan_prop->tm_chan_index; + bool upper_set, lower_set; + + if (chan_prop->sdam_index != sdam_index) + continue; + + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en); + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en); + + if (!(upper_set || lower_set)) + continue; + + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED); + } + + return IRQ_HANDLED; +} + +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp); +} + +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + int ret; + u8 val; + + prop->high_thr_en = false; + prop->low_thr_en = false; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret) + return ret; + + val = BIT(prop->tm_chan_index); + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val)); + if (ret) + return ret; + + val = MEAS_INT_DISABLE; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_TIMER_SEL, &val, sizeof(val)); + if (ret) + return ret; + + /* To indicate there is an actual conversion request */ + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_PERPH_CH, &val, sizeof(val)); + if (ret) + return ret; + + val = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &val, sizeof(val)); +} + +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip; + u8 buf[ADC_TM5_GEN3_CONFIG_REGS]; + u8 conv_req; + u16 adc_code; + int ret; + + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index); + if (ret < 0) + return ret; + + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_SID, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* Write SID */ + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid); + + /* Select TM channel and indicate there is an actual conversion request */ + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index; + + buf[2] = prop->timer; + + /* Digital param selection */ + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]); + + /* Update fast average sample value */ + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK, + prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN; + + /* Select ADC channel */ + buf[5] = prop->common_props.channel; + + /* Select HW settle delay for channel */ + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK, + prop->common_props.hw_settle_time_us); + + buf[7] = 0; + + /* High temperature corresponds to low voltage threshold */ + prop->low_thr_en = (high_temp != INT_MAX); + if (prop->low_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp); + put_unaligned_le16(adc_code, &buf[8]); + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN; + } + + /* Low temperature corresponds to high voltage threshold */ + prop->high_thr_en = (low_temp != -INT_MAX); + if (prop->high_thr_en) { + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp); + put_unaligned_le16(adc_code, &buf[10]); + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN; + } + + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID, + buf, sizeof(buf)); + if (ret < 0) + return ret; + + conv_req = ADC5_GEN3_CONV_REQ_REQ; + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req)); +} + +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz, + int low_temp, int high_temp) +{ + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz); + struct adc_tm5_gen3_chip *adc_tm5; + + if (!prop || !prop->chip) + return -EINVAL; + + adc_tm5 = prop->chip; + + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n", + prop->common_props.label, low_temp, high_temp); + + guard(adc5_gen3)(adc_tm5); + + return adc_tm5_gen3_configure(prop, low_temp, high_temp); +} + +static const struct thermal_zone_device_ops adc_tm_ops = { + .get_temp = adc_tm5_gen3_get_temp, + .set_trips = adc_tm5_gen3_set_trip_temp, +}; + +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5) +{ + struct thermal_zone_device *tzd; + unsigned int channel; + int ret; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props); + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel, + &adc_tm5->chan_props[i], + &adc_tm_ops); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) == -ENODEV) { + dev_dbg(adc_tm5->dev, + "thermal sensor on channel %d is not used\n", + channel); + continue; + } + return PTR_ERR(tzd); + } + adc_tm5->chan_props[i].tzd = tzd; + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd); + if (ret) + return ret; + } + + return 0; +} + +static void adc5_gen3_disable(void *data) +{ + struct adc_tm5_gen3_chip *adc_tm5 = data; + + guard(adc5_gen3)(adc_tm5); + + /* Disable all available TM channels */ + for (int i = 0; i < adc_tm5->nchannels; i++) + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]); +} + +static int adc_tm5_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *id) +{ + struct adc_tm5_gen3_chip *adc_tm5; + struct tm5_aux_dev_wrapper *aux_dev_wrapper; + struct device *dev = &aux_dev->dev; + int ret; + + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL); + if (!adc_tm5) + return -ENOMEM; + + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev); + + adc_tm5->dev = dev; + adc_tm5->dev_data = aux_dev_wrapper->dev_data; + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels; + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels, + sizeof(*adc_tm5->chan_props), GFP_KERNEL); + if (!adc_tm5->chan_props) + return -ENOMEM; + + for (int i = 0; i < adc_tm5->nchannels; i++) { + /* + * Since the first channel of the first SDAM is reserved for + * immediate ADC conversions, TM channel count must start from + * the channel just after it. The variable tm_count is used to + * calculate SDAM and TM channel index on that SDAM correctly + * for each TM channel. + */ + int tm_count = i + 1; + + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i]; + adc_tm5->chan_props[i].timer = MEAS_INT_1S; + adc_tm5->chan_props[i].sdam_index = tm_count / 8; + adc_tm5->chan_props[i].tm_chan_index = tm_count % 8; + adc_tm5->chan_props[i].chip = adc_tm5; + } + + /* + * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as + * part of the set_trips calls during thermal zone registration. This + * action is to disable them all in case of probe failure. + */ + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5); + if (ret) + return ret; + + ret = adc_tm5_register_tzd(adc_tm5); + if (ret) + return ret; + + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) { + u32 irq_flags = IRQF_ONESHOT; + + /* + * First SDAM's interrupt is shared between main ADC driver and + * auxiliary TM driver, so its flags must include IRQF_SHARED. + * This is not needed for other SDAMs as they will be used only + * for TM functionality. + */ + if (i == 0) + irq_flags |= IRQF_SHARED; + + ret = devm_request_threaded_irq(dev, + adc_tm5->dev_data->base[i].irq, + adctm5_gen3_isr, + adctm5_gen3_isr_thread, + irq_flags, + adc_tm5->dev_data->base[i].irq_name, + adc_tm5); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = { + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table); + +static struct auxiliary_driver adctm5gen3_auxiliary_driver = { + .id_table = adctm5_auxiliary_id_table, + .probe = adc_tm5_probe, +}; +module_auxiliary_driver(adctm5gen3_auxiliary_driver); + +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3"); From c5f7c0d35c552cbaf1f0630a9731a04f7fe03948 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 11 Aug 2026 11:49:35 +0200 Subject: [PATCH 28/32] thermal/drivers/qcom/spm mbg tm: Fix missing bitfield header Add missing bitfield header leading to the error: >> drivers/thermal/qcom/qcom-spmi-mbg-tm.c:184:21: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration] 184 | if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) | ^~~~~~~~~ Fixes: c3dce117333c ("thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608080800.RfxKb9uR-lkp@intel.com/ Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://patch.msgid.link/20260811094935.2941313-1-daniel.lezcano@kernel.org --- drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c index fa2f10002253..0492d5eeca01 100644 --- a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -2,7 +2,7 @@ /* * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ - +#include #include #include #include From b115930d716defc3a7daa2bc2ae2465d864b7114 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 11 Aug 2026 11:47:47 +0200 Subject: [PATCH 29/32] thermal/drivers/armada: Fix missing bitfields include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing include leading to the error: error: implicit declaration of function ‘FIELD_GET’ [-Werror=implicit-function-declaration] 184 | if (FIELD_GET(MON_FAULT_STATUS_MASK, val) == MON_FAULT_LVL1_UPR) | ^~~~~~~~~ cc1: all warnings being treated as errors Fixes: cbe31d5ce498 ("thermal/drivers/armada: Use bitfield and bitmask macros") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608082242.drjXuzsN-lkp@intel.com/ Signed-off-by: Daniel Lezcano Reviewed-by: Miquel Raynal Link: https://patch.msgid.link/20260811094747.2940616-1-daniel.lezcano@kernel.org --- drivers/thermal/armada_thermal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/armada_thermal.c b/drivers/thermal/armada_thermal.c index 912c68a44bdc..ff63b4f2b977 100644 --- a/drivers/thermal/armada_thermal.c +++ b/drivers/thermal/armada_thermal.c @@ -4,6 +4,7 @@ * * Copyright (C) 2013 Marvell */ +#include #include #include #include From 1087c29d9c5f0ca6793ad5b117943b45e000364b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 24 Aug 2026 19:52:08 +0200 Subject: [PATCH 30/32] Revert "thermal/core: Use the thermal class pointer as init guard" This reverts commit 499274d078d0 ("thermal/core: Use the thermal class pointer as init guard") because it depends on another commit that needs to be reverted. Signed-off-by: Rafael J. Wysocki Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/6301222.lOV4Wx5bFT@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 28a20d4b475c..2d9d8740491f 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -890,6 +890,7 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, } static struct class *thermal_class __ro_after_init; +static bool thermal_class_unavailable __ro_after_init = true; static inline void print_bind_err_msg(struct thermal_zone_device *tz, @@ -973,7 +974,7 @@ thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_devi !ops->set_cur_state) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); cdev = kzalloc_obj(*cdev); @@ -1447,7 +1448,7 @@ thermal_zone_device_register_with_trips(const char *type, if (polling_delay && passive_delay > polling_delay) return ERR_PTR(-EINVAL); - if (!thermal_class) + if (thermal_class_unavailable) return ERR_PTR(-ENODEV); tz = kzalloc_flex(*tz, trips, num_trips); @@ -1745,7 +1746,7 @@ static void __thermal_pm_prepare(void) void thermal_pm_prepare(void) { - if (!thermal_class) + if (thermal_class_unavailable) return; __thermal_pm_prepare(); @@ -1776,7 +1777,7 @@ void thermal_pm_complete(void) { struct thermal_zone_device *tz; - if (!thermal_class) + if (thermal_class_unavailable) return; guard(mutex)(&thermal_list_lock); @@ -1815,6 +1816,8 @@ static int __init thermal_init(void) } thermal_class = tc; + thermal_class_unavailable = false; + return 0; unregister_governors: From aa4174127fe63f3b6521529d0f1d66470ae4d8ad Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 24 Aug 2026 19:53:00 +0200 Subject: [PATCH 31/32] Revert "thermal/core: Allocate the thermal class dynamically" This reverts commit 34f54003643e ("thermal/core: Allocate the thermal class dynamically") that went against driver core changes aiming at the elimination of class_create() [1]. No intentional functional impact. Link: https://lore.kernel.org/linux-pm/2026082411-flask-rewire-434f@gregkh/ Signed-off-by: Rafael J. Wysocki Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/4761117.LvFx2qVVIh@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 2d9d8740491f..82e2f0d8a26d 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -889,7 +889,9 @@ static void thermal_unbind_cdev_from_trip(struct thermal_zone_device *tz, kfree(pos); } -static struct class *thermal_class __ro_after_init; +static const struct class thermal_class = { + .name = "thermal", +}; static bool thermal_class_unavailable __ro_after_init = true; static inline @@ -1011,7 +1013,7 @@ int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdat mutex_init(&cdev->lock); INIT_LIST_HEAD(&cdev->thermal_instances); cdev->updated = false; - cdev->device.class = thermal_class; + cdev->device.class = &thermal_class; cdev->device.release = thermal_cdev_release; device_initialize(&cdev->device); cdev->devdata = devdata; @@ -1484,7 +1486,7 @@ thermal_zone_device_register_with_trips(const char *type, if (!tz->ops.critical) tz->ops.critical = thermal_zone_device_critical; - tz->device.class = thermal_class; + tz->device.class = &thermal_class; tz->device.release = thermal_zone_device_release; tz->devdata = devdata; tz->num_trips = num_trips; @@ -1790,7 +1792,6 @@ void thermal_pm_complete(void) static int __init thermal_init(void) { - struct class *tc; int result; thermal_debug_init(); @@ -1809,13 +1810,10 @@ static int __init thermal_init(void) if (result) goto unregister_governors; - tc = class_create("thermal"); - if (IS_ERR(tc)) { - result = PTR_ERR(tc); + result = class_register(&thermal_class); + if (result) goto unregister_governors; - } - thermal_class = tc; thermal_class_unavailable = false; return 0; From 79a57e48822a88f082a4dadb366abaf3c0988b5d Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 12 Aug 2026 18:17:22 -0700 Subject: [PATCH 32/32] thermal/drivers/qcom-spmi-mbg-tm: Add module namespace import for IIO_CONSUMER Commit ebf1d03dab96 ("iio: inkern: Use namespaced exports") in the iio tree restricts certain exported core functions that a driver added in commit c3dce117333c ("thermal/drivers/qcom: Add support for Qualcomm MBG thermal monitoring") from the thermal tree uses, causing modpost to warn (or error without CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS): ERROR: modpost: drivers/thermal/qcom/qcom-spmi-mbg-tm.ko: module uses symbol 'devm_iio_channel_get' from namespace 'IIO_CONSUMER', but does not import it. ERROR: modpost: drivers/thermal/qcom/qcom-spmi-mbg-tm.ko: module uses symbol 'iio_read_channel_processed' from namespace 'IIO_CONSUMER', but does not import it. Add the IIO_CONSUMER namespace import to clear up the error. Signed-off-by: Nathan Chancellor Acked-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://patch.msgid.link/20260812-qcom-spmi-mbg-tm-ns-modpost-error-v1-1-d849390d2714@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/qcom/qcom-spmi-mbg-tm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c index 0492d5eeca01..a05bb444627a 100644 --- a/drivers/thermal/qcom/qcom-spmi-mbg-tm.c +++ b/drivers/thermal/qcom/qcom-spmi-mbg-tm.c @@ -254,3 +254,4 @@ module_platform_driver(mbg_tm_driver); MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_CONSUMER");