Skip to contentSkip to conversation

dt-bindings: thermal: Add support for Airoha EN7581 thermal sensor

PATCH Superseded Christian Marangi 2 participants 6 messages Feb 26, 2025 – May 7, 2025

Subsystems (2)

v5 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

PATCH v5 1/2 dt-bindings: thermal: Add support for Airoha EN7581 thermal sensor

Add support for Airoha EN7581 thermal sensor and monitor. This is a simple sensor for the CPU or SoC Package that provide thermal sensor and trip point for hot low and critical condition to fire interrupt and react on the abnormal state.

---
Changes v2:
- Add Reviewed-by tag

 .../thermal/airoha,en7581-thermal.yaml        | 48 +++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/airoha,en7581-thermal.yaml
Documentation/devicetree/bindings/thermal/airoha,en7581-thermal.yaml new file +48 -0
new file mode 100644
index 000000000000..ca0242ef0378
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/airoha,en7581-thermal.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Airoha EN7581 Thermal Sensor and Monitor
+
+maintainers:
+  - Christian Marangi <[email protected]>
+
+properties:
+  compatible:
+    const: airoha,en7581-thermal
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  airoha,chip-scu:
+    description: phandle to the chip SCU syscon
+    $ref: /schemas/types.yaml#/definitions/phandle
+
+  '#thermal-sensor-cells':
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - airoha,chip-scu
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    thermal-sensor@1efbd800 {
+        compatible = "airoha,en7581-thermal";
+        reg = <0x1efbd000 0xd5c>;
+        interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH>;
+        airoha,chip-scu = <&chip_scu>;
+
+        #thermal-sensor-cells = <0>;
+    };
Signature · 1 line
2.47.1
#1 lore raw

PATCH v5 2/2 thermal: Add support for Airoha EN7581 thermal sensor

Add support for Airoha EN7581 thermal sensor. This provide support for reading the CPU or SoC Package sensor and to setup trip points for hot and critical condition. An interrupt is fired to react on this and doesn't require passive poll to read the temperature.

The thermal regs provide a way to read the ADC value from an external register placed in the Chip SCU regs. Monitor will read this value and fire an interrupt if the trip condition configured is reached.

The Thermal Trip and Interrupt logic is conceptually similar to Mediatek LVTS Thermal but differ in register mapping and actual function/bug workaround. The implementation only share some register names but from functionality observation it's very different and used only for the basic function of periodically poll the temp and trip the interrupt.

---
Changes v5:
- Add additional info on difference from Mediatek LVTS driver Changes v4:
- Handle offset and slope in priv driver
Changes v3:
- Handle thermal_zone_device moved in different header
- Enable interrupt after thermal register
- Use new way to provide slope and offset
Changes v2:
- Add missing Makefile and Kconfig entry (somehow not included in v1)
- Sort include header
- Add missing bitfield.h

 drivers/thermal/Kconfig          |   9 +
 drivers/thermal/Makefile         |   1 +
 drivers/thermal/airoha_thermal.c | 480 +++++++++++++++++++++++++++++++
 3 files changed, 490 insertions(+)
 create mode 100644 drivers/thermal/airoha_thermal.c
drivers/thermal/Kconfig +9 -0
index d3f9686e26e7..801c4c2f8f70 100644
@@ -327,6 +327,15 @@ config QORIQ_THERMAL
 	  cpufreq is used as the cooling device to throttle CPUs when the
 	  passive trip is crossed.
 
+config AIROHA_THERMAL
+	tristate "Airoha thermal sensor driver"
+	depends on ARCH_AIROHA || COMPILE_TEST
+	depends on MFD_SYSCON
+	depends on OF
+	help
+	  Enable this to plug the Airoha thermal sensor driver into the Linux
+	  thermal framework.
+
 config SPEAR_THERMAL
 	tristate "SPEAr thermal sensor driver"
 	depends on PLAT_SPEAR || COMPILE_TEST
drivers/thermal/Makefile +1 -0
index 9abf43a74f2b..d7718978db24 100644
@@ -38,6 +38,7 @@ obj-$(CONFIG_K3_THERMAL)	+= k3_bandgap.o k3_j72xx_bandgap.o
 # platform thermal drivers
 obj-y				+= broadcom/
 obj-$(CONFIG_THERMAL_MMIO)		+= thermal_mmio.o
+obj-$(CONFIG_AIROHA_THERMAL)	+= airoha_thermal.o
 obj-$(CONFIG_SPEAR_THERMAL)	+= spear_thermal.o
 obj-$(CONFIG_SUN8I_THERMAL)     += sun8i_thermal.o
 obj-$(CONFIG_ROCKCHIP_THERMAL)	+= rockchip_thermal.o
drivers/thermal/airoha_thermal.c new file +480 -0

480 lines not on this page · raw message

Signature · 1 line
2.47.1
#2 lore raw

Re: [PATCH v5 2/2] thermal: Add support for Airoha EN7581 thermal sensor

Hi Angelo,

AFAIR, the LVTS driver had issues with the interrupts.

This driver proposed by Ansuel Smith looks very similar to the LVTS and there are some comments regarding errors with the documentation below which may appy to the LVTS driver too.

Would you mind to check ?

See below.

Quoted 17 lines · Christian Marangi

Add support for Airoha EN7581 thermal sensor. This provide support for reading the CPU or SoC Package sensor and to setup trip points for hot and critical condition. An interrupt is fired to react on this and doesn't require passive poll to read the temperature.

The thermal regs provide a way to read the ADC value from an external register placed in the Chip SCU regs. Monitor will read this value and fire an interrupt if the trip condition configured is reached.

The Thermal Trip and Interrupt logic is conceptually similar to Mediatek LVTS Thermal but differ in register mapping and actual function/bug workaround. The implementation only share some register names but from functionality observation it's very different and used only for the basic function of periodically poll the temp and trip the interrupt.

---

[ ... ]

Quoted 18 lines
drivers/thermal/airoha_thermal.c new file +18 -0
+#define   EN7581_HINTEN1			BIT(6)
+#define   EN7581_CINTEN1			BIT(5)
+#define   EN7581_NOHOTINTEN0			BIT(4)
+/* Similar to COLD and HOT also these seems to be swapped in documentation */
+#define   EN7581_LOFSINTEN0			BIT(3) /* In documentation: BIT(2) */
+#define   EN7581_HOFSINTEN0			BIT(2) /* In documentation: BIT(3) */
+/* It seems documentation have these swapped as the HW
+ * - Fire BIT(1) when lower than EN7581_COLD_THRE
+ * - Fire BIT(0) and BIT(5) when higher than EN7581_HOT2NORMAL_THRE or
+ *     EN7581_HOT_THRE
+ */
+#define   EN7581_CINTEN0			BIT(1) /* In documentation: BIT(0) */
+#define   EN7581_HINTEN0			BIT(0) /* In documentation: BIT(1) */
+#define EN7581_TEMPMONINTSTS			0x810
+#define   EN7581_STAGE3_INT_STAT		BIT(31)
+#define   EN7581_STAGE2_INT_STAT		BIT(30)
+#define   EN7581_STAGE1_INT_STAT		BIT(29)
+#define   EN7581_FILTER_INT_STAT_3		BIT(28)

[ ... ]

Quoted 33 lines
drivers/thermal/airoha_thermal.c new file +33 -0
+#define   EN7581_NOHOTINTSTS0			BIT(4)
+/* Similar to COLD and HOT also these seems to be swapped in documentation */
+#define   EN7581_LOFSINTSTS0			BIT(3) /* In documentation: BIT(2) */
+#define   EN7581_HOFSINTSTS0			BIT(2) /* In documentation: BIT(3) */
+/* It seems documentation have these swapped as the HW
+ * - Fire BIT(1) when lower than EN7581_COLD_THRE
+ * - Fire BIT(0) and BIT(5) when higher than EN7581_HOT2NORMAL_THRE or
+ *     EN7581_HOT_THRE
+ *
+ * To clear things, we swap the define but we keep them documented here.
+ */
+#define   EN7581_CINTSTS0			BIT(1) /* In documentation: BIT(0) */
+#define   EN7581_HINTSTS0			BIT(0) /* In documentation: BIT(1)*/
+/* Monitor will take the bigger threshold between HOT2NORMAL and HOT
+ * and will fire both HOT2NORMAL and HOT interrupt when higher than the 2
+ *
+ * It has also been observed that not setting HOT2NORMAL makes the monitor
+ * treat COLD threshold as HOT2NORMAL.
+ */
+#define EN7581_TEMPH2NTHRE			0x824
+/* It seems HOT2NORMAL is actually NORMAL2HOT */
+#define   EN7581_HOT2NORMAL_THRE		GENMASK(11, 0)
+#define EN7581_TEMPHTHRE			0x828
+#define   EN7581_HOT_THRE			GENMASK(11, 0)
+/* Monitor will use this as HOT2NORMAL (fire interrupt when lower than...)*/
+#define EN7581_TEMPCTHRE			0x82c
+#define   EN7581_COLD_THRE			GENMASK(11, 0)
+/* Also LOW and HIGH offset register are swapped */
+#define EN7581_TEMPOFFSETL			0x830 /* In documentation: 0x834 */
+#define   EN7581_LOW_OFFSET			GENMASK(11, 0)
+#define EN7581_TEMPOFFSETH			0x834 /* In documentation: 0x830 */
+#define   EN7581_HIGH_OFFSET			GENMASK(11, 0)
+#define EN7581_TEMPMSRCTL0			0x838

[ ... ]

Signature · 5 lines
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
#3 lore raw

Re: [PATCH v5 2/2] thermal: Add support for Airoha EN7581 thermal sensor

In reply to Christian Marangi
Quoted 19 lines · Christian Marangi

Add support for Airoha EN7581 thermal sensor. This provide support for reading the CPU or SoC Package sensor and to setup trip points for hot and critical condition. An interrupt is fired to react on this and doesn't require passive poll to read the temperature.

The thermal regs provide a way to read the ADC value from an external register placed in the Chip SCU regs. Monitor will read this value and fire an interrupt if the trip condition configured is reached.

The Thermal Trip and Interrupt logic is conceptually similar to Mediatek LVTS Thermal but differ in register mapping and actual function/bug workaround. The implementation only share some register names but from functionality observation it's very different and used only for the basic function of periodically poll the temp and trip the interrupt.

---
Changes v5:
- Add additional info on difference from Mediatek LVTS driver

[ ... ]

Quoted 33 lines
drivers/thermal/airoha_thermal.c new file +33 -0
+static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv)
+{
+	u32 adc_mux, pllrg;
+
+	/* Save PLLRG current value */
+	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);
+	adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1);
+	regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux);
+
+	/* Restore PLLRG value on exit */
+	regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg);
+}
+
+static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+	int min, max, avg_temp, temp_adc;
+	int i;
+
+	/* Get the starting temp */
+	temp_adc = airoha_get_thermal_ADC(priv);
+	min = temp_adc;
+	max = temp_adc;
+	avg_temp = temp_adc;
+
+	/* Make 5 more measurement and average the temp ADC difference */
+	for (i = 0; i < 5; i++) {
+		temp_adc = airoha_get_thermal_ADC(priv);
+		avg_temp += temp_adc;
+		if (temp_adc > max)

drivers/thermal/airoha_thermal.c line 255

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+			max = temp_adc;
+		if (temp_adc < min)
+			min = temp_adc;
+	}
+	avg_temp = avg_temp - max - min;
+	avg_temp /= 4;
+
+	*temp = RAW_TO_TEMP(priv, avg_temp);
+	return 0;
+}

Does this chip support the averaging with the filtered mode which prevent to do this expensive calls when getting the temperature ?

drivers/thermal/airoha_thermal.c line 265

Quoted 9 lines
drivers/thermal/airoha_thermal.c new file +9 -0
+static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low,
+				    int high)
+{
+	struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+
+	if (high != INT_MAX) {
+		/* Validate high and clamp them a sane value */
+		if (high > RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK)))
+			high = 110000;

There are helpers in minmax.h

drivers/thermal/airoha_thermal.c line 275

Quoted 9 lines
drivers/thermal/airoha_thermal.c new file +9 -0
+		/* We offset the high temp of 1°C to trigger correct event */
+		writel(TEMP_TO_RAW(priv, high) >> 4,
+		       priv->base + EN7581_TEMPOFFSETH);
+	}
+
+	if (low != -INT_MAX) {
+		/* Validate low and clamp them to a sane value */
+		if (low < RAW_TO_TEMP(priv, 0))
+			low = -33000;

Same.

drivers/thermal/airoha_thermal.c line 283

Quoted 7 lines
drivers/thermal/airoha_thermal.c new file +7 -0
+		/* We offset the low temp of 1°C to trigger correct event */
+		writel(TEMP_TO_RAW(priv, low) >> 4,
+		       priv->base + EN7581_TEMPOFFSETL);
+	}
+
+	/* Enable sensor 0 monitor */
+	writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0);

The sensor is enabled in this routine but disabled anywhere else. Should it be enabled in the init routine or in the set_mode callbacks ?

Quoted 14 lines
drivers/thermal/airoha_thermal.c new file +14 -0
+	return 0;
+}
+
+static const struct thermal_zone_device_ops thdev_ops = {
+	.get_temp = airoha_thermal_get_temp,
+	.set_trips = airoha_thermal_set_trips,
+};
+
+static irqreturn_t airoha_thermal_irq(int irq, void *data)
+{
+	struct airoha_thermal_priv *priv = data;
+	enum thermal_notify_event event;
+	u32 status;
+

drivers/thermal/airoha_thermal.c line 308

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+	status = readl(priv->base + EN7581_TEMPMONINTSTS);
+	switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) {
+	case EN7581_HOFSINTSTS0:
+		event = THERMAL_TRIP_VIOLATED;
+		break;
+	case EN7581_LOFSINTSTS0:
+		event = THERMAL_EVENT_UNSPECIFIED;
+		break;
+	default:
+		goto exit;

Is the default case possible ?

drivers/thermal/airoha_thermal.c line 315

Quoted 6 lines
drivers/thermal/airoha_thermal.c new file +6 -0
+	}
+	thermal_zone_device_update(priv->tz, event);
+
+exit:
+	/* reset interrupt */
+	writel(status, priv->base + EN7581_TEMPMONINTSTS);

You may want to the ack the interrupt before calling thermal_zone_device_update() as the underlying code can interact with the driver like calling set_trips. I'm not sure if it has on impact or not but IMO it is a good practice.

Quoted 3 lines
drivers/thermal/airoha_thermal.c new file +3 -0
+	return IRQ_HANDLED;
+}
+

[ ... ]

Quoted 44 lines
drivers/thermal/airoha_thermal.c new file +44 -0
+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);
+
+	/*
+	 * Configure ADC valid reading addr
+	 * The AHB temp monitor system doesn't have direct access to the
+	 * thermal sensor. It does instead work by providing all kind of
+	 * address to configure how to access and setup an ADC for the
+	 * sensor. EN7581 supports only one sensor hence the
+	 * implementation is greatly simplified but the AHB supports
+	 * up to 4 different sensor from the same ADC that can be
+	 * switched by tuning the ADC mux or wiriting address.
+	 *
+	 * 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);
+
+	/*
+	 * 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);
+
+	/*
+	 * AHB supports max 12 bytes for ADC voltage. Shift the read
+	 * value 4 bit to the right. Precision lost by this is minimal
+	 * 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);
+
+	/* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */
+	writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3),
+	       priv->base + EN7581_TEMPMONCTL1);
+
+	/*
+	 * filt interval is 1 * 52.715us = 52.715us,

drivers/thermal/airoha_thermal.c line 403

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+	 * sen interval is 379 * 52.715us = 19.97ms
+	 */
+	writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) |
+	       FIELD_PREP(EN7581_FILT_INTERVAL, 379),
+	       priv->base + EN7581_TEMPMONCTL2);
+
+	/* AHB poll is set to 146 * 68.64 = 10.02us */
+	writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146),
+	       priv->base + EN7581_TEMPAHBPOLL);
+}

Thanks for documenting the different steps

Quoted 23 lines
drivers/thermal/airoha_thermal.c new file +23 -0
+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;
+	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);
+
+	chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0);
+	if (!chip_scu_np)
+		return -EINVAL;
+
+	priv->chip_scu = syscon_node_to_regmap(chip_scu_np);
+	if (IS_ERR(priv->chip_scu))
+		return PTR_ERR(priv->chip_scu);
+

drivers/thermal/airoha_thermal.c line 437

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+	of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res);
+	of_node_put(chip_scu_np);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+					airoha_thermal_irq, IRQF_ONESHOT,
+					pdev->name, (void *)priv);

(void *) cast is not needed

Quoted 43 lines
drivers/thermal/airoha_thermal.c new file +43 -0
+	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);
+
+	/* register of thermal sensor and get info from DT */
+	priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops);
+	if (IS_ERR(priv->tz)) {
+		dev_err(dev, "register thermal zone sensor failed\n");
+		return PTR_ERR(priv->tz);
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	/* Enable LOW and HIGH interrupt */
+	writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0,
+	       priv->base + EN7581_TEMPMONINT);
+
+	return 0;
+}
+
+static const struct of_device_id airoha_thermal_match[] = {
+	{ .compatible = "airoha,en7581-thermal" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, airoha_thermal_match);
+
+static struct platform_driver airoha_thermal_driver = {
+	.driver = {
+		.name = "airoha-thermal",
+		.of_match_table = airoha_thermal_match,
+	},
+	.probe = airoha_thermal_probe,
+};
+
+module_platform_driver(airoha_thermal_driver);
+
+MODULE_AUTHOR("Christian Marangi <[email protected]>");
+MODULE_DESCRIPTION("Airoha thermal driver");
+MODULE_LICENSE("GPL");
Signature · 5 lines
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
#4 lore raw

Re: [PATCH v5 2/2] thermal: Add support for Airoha EN7581 thermal sensor

Quoted 61 lines · Daniel Lezcano
Quoted 19 lines · Christian Marangi

Add support for Airoha EN7581 thermal sensor. This provide support for reading the CPU or SoC Package sensor and to setup trip points for hot and critical condition. An interrupt is fired to react on this and doesn't require passive poll to read the temperature.

The thermal regs provide a way to read the ADC value from an external register placed in the Chip SCU regs. Monitor will read this value and fire an interrupt if the trip condition configured is reached.

The Thermal Trip and Interrupt logic is conceptually similar to Mediatek LVTS Thermal but differ in register mapping and actual function/bug workaround. The implementation only share some register names but from functionality observation it's very different and used only for the basic function of periodically poll the temp and trip the interrupt.

---
Changes v5:
- Add additional info on difference from Mediatek LVTS driver

[ ... ]

Quoted 38 lines
drivers/thermal/airoha_thermal.c new file +38 -0
+static void airoha_init_thermal_ADC_mode(struct airoha_thermal_priv *priv)
+{
+	u32 adc_mux, pllrg;
+
+	/* Save PLLRG current value */
+	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);
+	adc_mux = FIELD_PREP(EN7581_MUX_TADC, EN7581_SCU_THERMAL_MUX_DIODE1);
+	regmap_write(priv->chip_scu, EN7581_PWD_TADC, adc_mux);
+
+	/* Restore PLLRG value on exit */
+	regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg);
+}
+
+static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+	int min, max, avg_temp, temp_adc;
+	int i;
+
+	/* Get the starting temp */
+	temp_adc = airoha_get_thermal_ADC(priv);
+	min = temp_adc;
+	max = temp_adc;
+	avg_temp = temp_adc;
+
+	/* Make 5 more measurement and average the temp ADC difference */
+	for (i = 0; i < 5; i++) {
+		temp_adc = airoha_get_thermal_ADC(priv);
+		avg_temp += temp_adc;
+		if (temp_adc > max)
+			max = temp_adc;
+		if (temp_adc < min)
+			min = temp_adc;
+	}
+	avg_temp = avg_temp - max - min;
Quoted 9 lines

drivers/thermal/airoha_thermal.c line 255

Quoted 5 lines
drivers/thermal/airoha_thermal.c new file +5 -0
+	avg_temp /= 4;
+
+	*temp = RAW_TO_TEMP(priv, avg_temp);
+	return 0;
+}

Does this chip support the averaging with the filtered mode which prevent to do this expensive calls when getting the temperature ?

If you notice in this function we read directly from the ADC sensor to have a more precise and realtime temperature.

Yes the thermal module support averaging (and this is what is set for the critical temperature monitor) but since we base everything on interrupt the expensive call is called only when the user poll the temperature with sysfs.

Do you prefer to use the thermal module entirely?

Quoted 12 lines

drivers/thermal/airoha_thermal.c line 265

Quoted 9 lines
drivers/thermal/airoha_thermal.c new file +9 -0
+static int airoha_thermal_set_trips(struct thermal_zone_device *tz, int low,
+				    int high)
+{
+	struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+
+	if (high != INT_MAX) {
+		/* Validate high and clamp them a sane value */
+		if (high > RAW_TO_TEMP(priv, FIELD_MAX(EN7581_DOUT_TADC_MASK)))
+			high = 110000;

There are helpers in minmax.h

Ok will change to use the helper.

Quoted 12 lines

drivers/thermal/airoha_thermal.c line 275

Quoted 9 lines
drivers/thermal/airoha_thermal.c new file +9 -0
+		/* We offset the high temp of 1°C to trigger correct event */
+		writel(TEMP_TO_RAW(priv, high) >> 4,
+		       priv->base + EN7581_TEMPOFFSETH);
+	}
+
+	if (low != -INT_MAX) {
+		/* Validate low and clamp them to a sane value */
+		if (low < RAW_TO_TEMP(priv, 0))
+			low = -33000;

Same.

Quoted 11 lines

drivers/thermal/airoha_thermal.c line 283

Quoted 7 lines
drivers/thermal/airoha_thermal.c new file +7 -0
+		/* We offset the low temp of 1°C to trigger correct event */
+		writel(TEMP_TO_RAW(priv, low) >> 4,
+		       priv->base + EN7581_TEMPOFFSETL);
+	}
+
+	/* Enable sensor 0 monitor */
+	writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0);

The sensor is enabled in this routine but disabled anywhere else. Should it be enabled in the init routine or in the set_mode callbacks ?

It's expected, we enable sensor only when trip are actually set. I observed that enabling before makes the thing fire interrupt with wrong trip point or makes the module stuck in some strange way (due to interrupt trip interval conflicts)

Quoted 18 lines
Quoted 18 lines
drivers/thermal/airoha_thermal.c new file +18 -0
+	return 0;
+}
+
+static const struct thermal_zone_device_ops thdev_ops = {
+	.get_temp = airoha_thermal_get_temp,
+	.set_trips = airoha_thermal_set_trips,
+};
+
+static irqreturn_t airoha_thermal_irq(int irq, void *data)
+{
+	struct airoha_thermal_priv *priv = data;
+	enum thermal_notify_event event;
+	u32 status;
+
+	status = readl(priv->base + EN7581_TEMPMONINTSTS);
+	switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) {
+	case EN7581_HOFSINTSTS0:
+		event = THERMAL_TRIP_VIOLATED;
Quoted 9 lines

drivers/thermal/airoha_thermal.c line 308

Quoted 6 lines
drivers/thermal/airoha_thermal.c new file +6 -0
+		break;
+	case EN7581_LOFSINTSTS0:
+		event = THERMAL_EVENT_UNSPECIFIED;
+		break;
+	default:
+		goto exit;

Is the default case possible ?

No but it's really to make compile happy.

Quoted 12 lines

drivers/thermal/airoha_thermal.c line 315

Quoted 6 lines
drivers/thermal/airoha_thermal.c new file +6 -0
+	}
+	thermal_zone_device_update(priv->tz, event);
+
+exit:
+	/* reset interrupt */
+	writel(status, priv->base + EN7581_TEMPMONINTSTS);

You may want to the ack the interrupt before calling thermal_zone_device_update() as the underlying code can interact with the driver like calling set_trips. I'm not sure if it has on impact or not but IMO it is a good practice.

Ok will move the interrupt reset before thermal_zone_device_update.

Quoted 150 lines
Quoted 3 lines
drivers/thermal/airoha_thermal.c new file +3 -0
+	return IRQ_HANDLED;
+}
+

[ ... ]

Quoted 44 lines
drivers/thermal/airoha_thermal.c new file +44 -0
+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);
+
+	/*
+	 * Configure ADC valid reading addr
+	 * The AHB temp monitor system doesn't have direct access to the
+	 * thermal sensor. It does instead work by providing all kind of
+	 * address to configure how to access and setup an ADC for the
+	 * sensor. EN7581 supports only one sensor hence the
+	 * implementation is greatly simplified but the AHB supports
+	 * up to 4 different sensor from the same ADC that can be
+	 * switched by tuning the ADC mux or wiriting address.
+	 *
+	 * 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);
+
+	/*
+	 * 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);
+
+	/*
+	 * AHB supports max 12 bytes for ADC voltage. Shift the read
+	 * value 4 bit to the right. Precision lost by this is minimal
+	 * 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);
+
+	/* BUS clock is 300MHz counting unit is 3 * 68.64 * 256 = 52.715us */
+	writel(FIELD_PREP(EN7581_PERIOD_UNIT, 3),
+	       priv->base + EN7581_TEMPMONCTL1);
+
+	/*
+	 * filt interval is 1 * 52.715us = 52.715us,

drivers/thermal/airoha_thermal.c line 403

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+	 * sen interval is 379 * 52.715us = 19.97ms
+	 */
+	writel(FIELD_PREP(EN7581_FILT_INTERVAL, 1) |
+	       FIELD_PREP(EN7581_FILT_INTERVAL, 379),
+	       priv->base + EN7581_TEMPMONCTL2);
+
+	/* AHB poll is set to 146 * 68.64 = 10.02us */
+	writel(FIELD_PREP(EN7581_ADC_POLL_INTVL, 146),
+	       priv->base + EN7581_TEMPAHBPOLL);
+}

Thanks for documenting the different steps

Quoted 23 lines
drivers/thermal/airoha_thermal.c new file +23 -0
+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;
+	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);
+
+	chip_scu_np = of_parse_phandle(dev->of_node, "airoha,chip-scu", 0);
+	if (!chip_scu_np)
+		return -EINVAL;
+
+	priv->chip_scu = syscon_node_to_regmap(chip_scu_np);
+	if (IS_ERR(priv->chip_scu))
+		return PTR_ERR(priv->chip_scu);
+

drivers/thermal/airoha_thermal.c line 437

Quoted 10 lines
drivers/thermal/airoha_thermal.c new file +10 -0
+	of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res);
+	of_node_put(chip_scu_np);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+					airoha_thermal_irq, IRQF_ONESHOT,
+					pdev->name, (void *)priv);

(void *) cast is not needed

Quoted 43 lines
drivers/thermal/airoha_thermal.c new file +43 -0
+	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);
+
+	/* register of thermal sensor and get info from DT */
+	priv->tz = devm_thermal_of_zone_register(dev, 0, priv, &thdev_ops);
+	if (IS_ERR(priv->tz)) {
+		dev_err(dev, "register thermal zone sensor failed\n");
+		return PTR_ERR(priv->tz);
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	/* Enable LOW and HIGH interrupt */
+	writel(EN7581_HOFSINTEN0 | EN7581_LOFSINTEN0,
+	       priv->base + EN7581_TEMPMONINT);
+
+	return 0;
+}
+
+static const struct of_device_id airoha_thermal_match[] = {
+	{ .compatible = "airoha,en7581-thermal" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, airoha_thermal_match);
+
+static struct platform_driver airoha_thermal_driver = {
+	.driver = {
+		.name = "airoha-thermal",
+		.of_match_table = airoha_thermal_match,
+	},
+	.probe = airoha_thermal_probe,
+};
+
+module_platform_driver(airoha_thermal_driver);
+
+MODULE_AUTHOR("Christian Marangi <[email protected]>");
+MODULE_DESCRIPTION("Airoha thermal driver");
+MODULE_LICENSE("GPL");
Signature · 5 lines
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog
Signature · 1 line
	Ansuel
#5 lore raw

Re: [PATCH v5 2/2] thermal: Add support for Airoha EN7581 thermal sensor

Quoted 1 line · Christian Marangi

On Fri, May 02, 2025 at 03:30:56PM +0200, Daniel Lezcano wrote:

[ ... ]

Quoted 15 lines
Quoted 15 lines
Quoted 15 lines
drivers/thermal/airoha_thermal.c new file +15 -0
+static int airoha_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz);
+	int min, max, avg_temp, temp_adc;
+	int i;
+
+	/* Get the starting temp */
+	temp_adc = airoha_get_thermal_ADC(priv);
+	min = temp_adc;
+	max = temp_adc;
+	avg_temp = temp_adc;
+
+	/* Make 5 more measurement and average the temp ADC difference */
+	for (i = 0; i < 5; i++) {
+		temp_adc = airoha_get_thermal_ADC(priv);
Quoted 8 lines
Quoted 8 lines

drivers/thermal/airoha_thermal.c line 251

Quoted 8 lines
drivers/thermal/airoha_thermal.c new file +8 -0
+		avg_temp += temp_adc;
+		if (temp_adc > max)
+			max = temp_adc;
+		if (temp_adc < min)
+			min = temp_adc;
+	}
+	avg_temp = avg_temp - max - min;
+	avg_temp /= 4;

Why 4 ? There is 5 samples no ? one before the loop and 4 with the loop

Quoted 1 line
Quoted 1 line

drivers/thermal/airoha_thermal.c line 253

Quoted 1 line
drivers/thermal/airoha_thermal.c new file +1 -0
+	*temp = RAW_TO_TEMP(priv, avg_temp);

Does RAW_TO_TEMP() on a average raw adc value report the same value as an average of the temperature after their conversion ?

Quoted 2 lines
Quoted 2 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+	return 0;
+}

The routine is a bit confusing because of the initial adc read.

avg_value = 0;
min_value = INT_MAX;
max_value = INT_MIN;

for (i = 0; i < AIROHA_MAX_SAMPLES; i++) {
	value = airoha_get_thermal_ADC(priv);
	min_value = min(value, min_value);
	max_value = max(value, max_value);
	avg_value += value;
}

avg_value -= (min_value + max_value);
avg_value /= AIROHA_MAX_SAMPLES;

Quoted 11 lines
Quoted 3 lines

Does this chip support the averaging with the filtered mode which prevent to do this expensive calls when getting the temperature ?

If you notice in this function we read directly from the ADC sensor to have a more precise and realtime temperature.

Yes the thermal module support averaging (and this is what is set for the critical temperature monitor) but since we base everything on interrupt the expensive call is called only when the user poll the temperature with sysfs.

When the temperature reaches the threshold, the interrupt is fired which in turn calls thermal_zone_device_update(). This lead to a polling from the kernel at a higher rate with the governor taking cooling decision.

Quoted 1 line

Do you prefer to use the thermal module entirely?

It is up to you. I would say if the sensor has a hardware assistance to compute the averaging while the CPU does something else it is better. But if the filtered mode has the same issues than the LVTS, then it is probably not worth it.

[ ... ]

Signature · 5 lines
<http://www.linaro.org/> Linaro.org │ Open source software for ARM SoCs

Follow Linaro:  <http://www.facebook.com/pages/Linaro> Facebook |
<http://twitter.com/#!/linaroorg> Twitter |
<http://www.linaro.org/linaro-blog/> Blog