--- /dev/null
+From 42de37f40e1bc818df216dfa0918c114cfb5941d Mon Sep 17 00:00:00 2001
+Date: Sun, 11 May 2025 20:49:55 +0200
+Subject: [PATCH] thermal/drivers: 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.
+
+---
+ drivers/thermal/Kconfig | 9 +
+ drivers/thermal/Makefile | 1 +
+ drivers/thermal/airoha_thermal.c | 489 +++++++++++++++++++++++++++++++
+ 3 files changed, 499 insertions(+)
+ create mode 100644 drivers/thermal/airoha_thermal.c
+
+--- a/drivers/thermal/Kconfig
++++ b/drivers/thermal/Kconfig
+@@ -317,6 +317,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
+--- a/drivers/thermal/Makefile
++++ b/drivers/thermal/Makefile
+@@ -34,6 +34,7 @@ obj-$(CONFIG_K3_THERMAL) += k3_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
+--- /dev/null
++++ b/drivers/thermal/airoha_thermal.c
+@@ -0,0 +1,489 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/module.h>
++#include <linux/bitfield.h>
++#include <linux/delay.h>
++#include <linux/interrupt.h>
++#include <linux/mfd/syscon.h>
++#include <linux/of.h>
++#include <linux/of_address.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++#include <linux/thermal.h>
++
++/* SCU regs */
++#define EN7581_PLLRG_PROTECT 0x268
++#define EN7581_PWD_TADC 0x2ec
++#define EN7581_MUX_TADC GENMASK(3, 1)
++#define EN7581_DOUT_TADC 0x2f8
++#define EN7581_DOUT_TADC_MASK GENMASK(15, 0)
++
++/* PTP_THERMAL regs */
++#define EN7581_TEMPMONCTL0 0x800
++#define EN7581_SENSE3_EN BIT(3)
++#define EN7581_SENSE2_EN BIT(2)
++#define EN7581_SENSE1_EN BIT(1)
++#define EN7581_SENSE0_EN BIT(0)
++#define EN7581_TEMPMONCTL1 0x804
++/* period unit calculated in BUS clock * 256 scaling-up */
++#define EN7581_PERIOD_UNIT GENMASK(9, 0)
++#define EN7581_TEMPMONCTL2 0x808
++#define EN7581_FILT_INTERVAL GENMASK(25, 16)
++#define EN7581_SEN_INTERVAL GENMASK(9, 0)
++#define EN7581_TEMPMONINT 0x80C
++#define EN7581_STAGE3_INT_EN BIT(31)
++#define EN7581_STAGE2_INT_EN BIT(30)
++#define EN7581_STAGE1_INT_EN BIT(29)
++#define EN7581_FILTER_INT_EN_3 BIT(28)
++#define EN7581_IMMD_INT_EN3 BIT(27)
++#define EN7581_NOHOTINTEN3 BIT(26)
++#define EN7581_HOFSINTEN3 BIT(25)
++#define EN7581_LOFSINTEN3 BIT(24)
++#define EN7581_HINTEN3 BIT(23)
++#define EN7581_CINTEN3 BIT(22)
++#define EN7581_FILTER_INT_EN_2 BIT(21)
++#define EN7581_FILTER_INT_EN_1 BIT(20)
++#define EN7581_FILTER_INT_EN_0 BIT(19)
++#define EN7581_IMMD_INT_EN2 BIT(18)
++#define EN7581_IMMD_INT_EN1 BIT(17)
++#define EN7581_IMMD_INT_EN0 BIT(16)
++#define EN7581_TIME_OUT_INT_EN BIT(15)
++#define EN7581_NOHOTINTEN2 BIT(14)
++#define EN7581_HOFSINTEN2 BIT(13)
++#define EN7581_LOFSINTEN2 BIT(12)
++#define EN7581_HINTEN2 BIT(11)
++#define EN7581_CINTEN2 BIT(10)
++#define EN7581_NOHOTINTEN1 BIT(9)
++#define EN7581_HOFSINTEN1 BIT(8)
++#define EN7581_LOFSINTEN1 BIT(7)
++#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)
++#define EN7581_IMMD_INT_STS3 BIT(27)
++#define EN7581_NOHOTINTSTS3 BIT(26)
++#define EN7581_HOFSINTSTS3 BIT(25)
++#define EN7581_LOFSINTSTS3 BIT(24)
++#define EN7581_HINTSTS3 BIT(23)
++#define EN7581_CINTSTS3 BIT(22)
++#define EN7581_FILTER_INT_STAT_2 BIT(21)
++#define EN7581_FILTER_INT_STAT_1 BIT(20)
++#define EN7581_FILTER_INT_STAT_0 BIT(19)
++#define EN7581_IMMD_INT_STS2 BIT(18)
++#define EN7581_IMMD_INT_STS1 BIT(17)
++#define EN7581_IMMD_INT_STS0 BIT(16)
++#define EN7581_TIME_OUT_INT_STAT BIT(15)
++#define EN7581_NOHOTINTSTS2 BIT(14)
++#define EN7581_HOFSINTSTS2 BIT(13)
++#define EN7581_LOFSINTSTS2 BIT(12)
++#define EN7581_HINTSTS2 BIT(11)
++#define EN7581_CINTSTS2 BIT(10)
++#define EN7581_NOHOTINTSTS1 BIT(9)
++#define EN7581_HOFSINTSTS1 BIT(8)
++#define EN7581_LOFSINTSTS1 BIT(7)
++#define EN7581_HINTSTS1 BIT(6)
++#define EN7581_CINTSTS1 BIT(5)
++#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
++#define EN7581_MSRCTL3 GENMASK(11, 9)
++#define EN7581_MSRCTL2 GENMASK(8, 6)
++#define EN7581_MSRCTL1 GENMASK(5, 3)
++#define EN7581_MSRCTL0 GENMASK(2, 0)
++#define EN7581_TEMPADCVALIDADDR 0x878
++#define EN7581_ADC_VALID_ADDR GENMASK(31, 0)
++#define EN7581_TEMPADCVOLTADDR 0x87c
++#define EN7581_ADC_VOLT_ADDR GENMASK(31, 0)
++#define EN7581_TEMPRDCTRL 0x880
++/*
++ * NOTICE: AHB have this set to 0 by default. Means that
++ * the same addr is used for ADC volt and valid reading.
++ * In such case, VALID ADDR is used and volt addr is ignored.
++ */
++#define EN7581_RD_CTRL_DIFF BIT(0)
++#define EN7581_TEMPADCVALIDMASK 0x884
++#define EN7581_ADV_RD_VALID_POLARITY BIT(5)
++#define EN7581_ADV_RD_VALID_POS GENMASK(4, 0)
++#define EN7581_TEMPADCVOLTAGESHIFT 0x888
++#define EN7581_ADC_VOLTAGE_SHIFT GENMASK(4, 0)
++/*
++ * Same values for each CTL.
++ * Can operate in:
++ * - 1 sample
++ * - 2 sample and make average of them
++ * - 4,6,10,16 sample, drop max and min and make avgerage of them
++ */
++#define EN7581_MSRCTL_1SAMPLE 0x0
++#define EN7581_MSRCTL_AVG2SAMPLE 0x1
++#define EN7581_MSRCTL_4SAMPLE_MAX_MIX_AVG2 0x2
++#define EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4 0x3
++#define EN7581_MSRCTL_10SAMPLE_MAX_MIX_AVG8 0x4
++#define EN7581_MSRCTL_18SAMPLE_MAX_MIX_AVG16 0x5
++#define EN7581_TEMPAHBPOLL 0x840
++#define EN7581_ADC_POLL_INTVL GENMASK(31, 0)
++/* PTPSPARE0,2 reg are used to store efuse info for calibrated temp offset */
++#define EN7581_EFUSE_TEMP_OFFSET_REG 0xf20 /* PTPSPARE0 */
++#define EN7581_EFUSE_TEMP_OFFSET GENMASK(31, 16)
++#define EN7581_PTPSPARE1 0xf24 /* PTPSPARE1 */
++#define EN7581_EFUSE_TEMP_CPU_SENSOR_REG 0xf28 /* PTPSPARE2 */
++
++#define EN7581_SLOPE_X100_DIO_DEFAULT 5645
++#define EN7581_SLOPE_X100_DIO_AVS 5645
++
++#define EN7581_INIT_TEMP_CPK_X10 300
++#define EN7581_INIT_TEMP_FTK_X10 620
++#define EN7581_INIT_TEMP_NONK_X10 550
++
++#define EN7581_SCU_THERMAL_PROTECT_KEY 0x12
++#define EN7581_SCU_THERMAL_MUX_DIODE1 0x7
++
++/* 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) + \
++ (priv)->default_offset)
++
++/* Convert raw to temp ((((temp - offset) * 1000) / slope + init) * 100) */
++#define RAW_TO_TEMP(priv, raw) (((((raw) - (priv)->default_offset) * 1000) / \
++ (priv)->default_slope + \
++ (priv)->init_temp) * 100)
++
++#define AIROHA_MAX_SAMPLES 6
++
++struct airoha_thermal_priv {
++ void __iomem *base;
++ struct regmap *chip_scu;
++ struct resource scu_adc_res;
++
++ struct thermal_zone_device *tz;
++ int init_temp;
++ int default_slope;
++ int default_offset;
++};
++
++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);
++}
++
++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_value, max_value, avg_value, value;
++ int i;
++
++ 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;
++ }
++
++ /* Drop min and max and average for the remaining sample */
++ avg_value -= (min_value + max_value);
++ avg_value /= AIROHA_MAX_SAMPLES - 2;
++
++ *temp = RAW_TO_TEMP(priv, avg_value);
++ return 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);
++ bool enable_monitor = false;
++
++ if (high != INT_MAX) {
++ /* Validate high and clamp it a supported value */
++ high = clamp_t(int, high, RAW_TO_TEMP(priv, 0),
++ 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);
++
++ enable_monitor = true;
++ }
++
++ if (low != -INT_MAX) {
++ /* Validate low and clamp it to a supported value */
++ low = clamp_t(int, high, 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 */
++ writel(TEMP_TO_RAW(priv, low) >> 4,
++ priv->base + EN7581_TEMPOFFSETL);
++
++ enable_monitor = true;
++ }
++
++ /* Enable sensor 0 monitor after trip are set */
++ if (enable_monitor)
++ writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0);
++
++ 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;
++ bool update = false;
++ u32 status;
++
++ status = readl(priv->base + EN7581_TEMPMONINTSTS);
++ switch (status & (EN7581_HOFSINTSTS0 | EN7581_LOFSINTSTS0)) {
++ case EN7581_HOFSINTSTS0:
++ event = THERMAL_TRIP_VIOLATED;
++ update = true;
++ break;
++ case EN7581_LOFSINTSTS0:
++ event = THERMAL_EVENT_UNSPECIFIED;
++ update = true;
++ break;
++ default:
++ /* Should be impossible as we enable only these Interrupt */
++ break;
++ }
++
++ /* Reset Interrupt */
++ writel(status, priv->base + EN7581_TEMPMONINTSTS);
++
++ if (update)
++ thermal_zone_device_update(priv->tz, event);
++
++ return IRQ_HANDLED;
++}
++
++static void airoha_thermal_setup_adc_val(struct device *dev,
++ struct airoha_thermal_priv *priv)
++{
++ u32 efuse_calib_info, cpu_sensor;
++
++ /* 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);
++ 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);
++ if (cpu_sensor) {
++ priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT;
++ priv->init_temp = EN7581_INIT_TEMP_FTK_X10;
++ } else {
++ priv->default_slope = EN7581_SLOPE_X100_DIO_AVS;
++ priv->init_temp = EN7581_INIT_TEMP_CPK_X10;
++ }
++ } else {
++ priv->default_offset = airoha_get_thermal_ADC(priv);
++ priv->default_slope = EN7581_SLOPE_X100_DIO_DEFAULT;
++ priv->init_temp = EN7581_INIT_TEMP_NONK_X10;
++ dev_info(dev, "missing thermal calibrarion EFUSE, using non calibrated value\n");
++ }
++}
++
++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,
++ * 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);
++}
++
++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);
++
++ 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, 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);
++
++ /* 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_DESCRIPTION("Airoha thermal driver");
++MODULE_LICENSE("GPL");
--- /dev/null
+From e23cba0ab49a9cf95e9bc3a86cfbf336b0e285f6 Mon Sep 17 00:00:00 2001
+Date: Wed, 14 May 2025 23:39:12 +0200
+Subject: [PATCH] thermal/drivers/airoha: Fix spelling mistake
+
+Fix various spelling mistake in airoha_thermal_setup_monitor() and
+define.
+
+---
+ drivers/thermal/airoha_thermal.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/thermal/airoha_thermal.c
++++ b/drivers/thermal/airoha_thermal.c
+@@ -155,7 +155,7 @@
+ * Can operate in:
+ * - 1 sample
+ * - 2 sample and make average of them
+- * - 4,6,10,16 sample, drop max and min and make avgerage of them
++ * - 4,6,10,16 sample, drop max and min and make average of them
+ */
+ #define EN7581_MSRCTL_1SAMPLE 0x0
+ #define EN7581_MSRCTL_AVG2SAMPLE 0x1
+@@ -365,12 +365,12 @@ static void airoha_thermal_setup_monitor
+ /*
+ * 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
++ * thermal sensor. It does instead work by providing various
++ * addresses 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.
++ * up to 4 different sensors from the same ADC that can be
++ * switched by tuning the ADC mux or writing address.
+ *
+ * We set valid instead of volt as we don't enable valid/volt
+ * split reading and AHB read valid addr in such case.
+++ /dev/null
-From 1f194995c3648e20da53137d4c9110b39e779f41 Mon Sep 17 00:00:00 2001
-Date: Fri, 18 Oct 2024 11:34:35 +0200
-Subject: [PATCH 2/3] thermal: of: Add
- devm_thermal_of_zone_register_with_params() variant
-
-Commit b1ae92dcfa8e ("thermal: core: Make struct thermal_zone_device
-definition internal") moved the thermal_zone_device struct from global
-thermal.h to internal thermal_core.h making the internal variables of
-the struct not accessible from the user drivers (without inclusing
-thermal_core.h).
-
-One case where the internal variables might be needed is for the
-thermal_zone_params in the context of OF probe.
-
-In such case a thermal driver might have default params that can only be
-parsed at runtime (example present in EFUSE or derived from other values)
-and wants to update the values in the thermal_zone_params for the
-thermal device. (to use the helper like get_slope() or get_offset())
-
-To account for this scenario, introduce a variant of
-devm_thermal_of_zone_register(),
-devm_thermal_of_zone_register_with_params(), that takes and additional
-variable and permits to register the thermal device with default
-thermal_zone_params.
-
-To follow OF implementation, these params are only treated as default
-params and are ignored if a related one is defined in DT. (example a
-slope or offset value defined in DT have priority to the default one
-passed in a thermal_device_params struct)
-
-This permits to support both implementation, use the helpers and expose
-these values in sysfs.
-
----
- drivers/thermal/thermal_of.c | 67 +++++++++++++++++++++++++++++-------
- include/linux/thermal.h | 13 +++++++
- 2 files changed, 68 insertions(+), 12 deletions(-)
-
---- a/drivers/thermal/thermal_of.c
-+++ b/drivers/thermal/thermal_of.c
-@@ -246,7 +246,7 @@ static void thermal_of_parameters_init(s
- {
- int coef[2];
- int ncoef = ARRAY_SIZE(coef);
-- int prop, ret;
-+ int prop;
-
- tzp->no_hwmon = true;
-
-@@ -258,14 +258,11 @@ static void thermal_of_parameters_init(s
- * thermal zone. Thus, we are considering only the first two
- * values as slope and offset.
- */
-- ret = of_property_read_u32_array(np, "coefficients", coef, ncoef);
-- if (ret) {
-- coef[0] = 1;
-- coef[1] = 0;
-+ if (!of_property_read_u32_array(np, "coefficients", coef, ncoef)) {
-+ tzp->slope = coef[0];
-+ tzp->offset = coef[1];
- }
-
-- tzp->slope = coef[0];
-- tzp->offset = coef[1];
- }
-
- static struct device_node *thermal_of_zone_get_by_name(struct thermal_zone_device *tz)
-@@ -459,10 +456,15 @@ static void thermal_of_zone_unregister(s
- * zone properties and registers new thermal zone with those
- * properties.
- *
-+ * The passed thermal zone params are treated as default values and ignored if
-+ * the related property is found in DT. (DT params have priority to
-+ * default values)
-+ *
- * @sensor: A device node pointer corresponding to the sensor in the device tree
- * @id: An integer as sensor identifier
- * @data: A private data to be stored in the thermal zone dedicated private area
- * @ops: A set of thermal sensor ops
-+ * @tzp: a pointer to the default thermal zone params structure associated with the sensor
- *
- * Return: a valid thermal zone structure pointer on success.
- * - EINVAL: if the device tree thermal description is malformed
-@@ -470,11 +472,11 @@ static void thermal_of_zone_unregister(s
- * - Other negative errors are returned by the underlying called functions
- */
- static struct thermal_zone_device *thermal_of_zone_register(struct device_node *sensor, int id, void *data,
-- const struct thermal_zone_device_ops *ops)
-+ const struct thermal_zone_device_ops *ops,
-+ struct thermal_zone_params *tzp)
- {
- struct thermal_zone_device *tz;
- struct thermal_trip *trips;
-- struct thermal_zone_params tzp = {};
- struct thermal_zone_device_ops *of_ops;
- struct device_node *np;
- int delay, pdelay;
-@@ -509,7 +511,7 @@ static struct thermal_zone_device *therm
- goto out_kfree_trips;
- }
-
-- thermal_of_parameters_init(np, &tzp);
-+ thermal_of_parameters_init(np, tzp);
-
- of_ops->bind = thermal_of_bind;
- of_ops->unbind = thermal_of_unbind;
-@@ -517,7 +519,7 @@ static struct thermal_zone_device *therm
- mask = GENMASK_ULL((ntrips) - 1, 0);
-
- tz = thermal_zone_device_register_with_trips(np->name, trips, ntrips,
-- mask, data, of_ops, &tzp,
-+ mask, data, of_ops, tzp,
- pdelay, delay);
- if (IS_ERR(tz)) {
- ret = PTR_ERR(tz);
-@@ -572,6 +574,7 @@ static int devm_thermal_of_zone_match(st
- struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, int sensor_id, void *data,
- const struct thermal_zone_device_ops *ops)
- {
-+ struct thermal_zone_params tzp = { .slope = 1 };
- struct thermal_zone_device **ptr, *tzd;
-
- ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
-@@ -579,7 +582,7 @@ struct thermal_zone_device *devm_thermal
- if (!ptr)
- return ERR_PTR(-ENOMEM);
-
-- tzd = thermal_of_zone_register(dev->of_node, sensor_id, data, ops);
-+ tzd = thermal_of_zone_register(dev->of_node, sensor_id, data, ops, &tzp);
- if (IS_ERR(tzd)) {
- devres_free(ptr);
- return tzd;
-@@ -593,6 +596,46 @@ struct thermal_zone_device *devm_thermal
- EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register);
-
- /**
-+ * devm_thermal_of_zone_register_with_params - register a thermal tied with the sensor life cycle
-+ * with default params
-+ *
-+ * This function is the device version of the thermal_of_zone_register() function.
-+ *
-+ * @dev: a device structure pointer to sensor to be tied with the thermal zone OF life cycle
-+ * @sensor_id: the sensor identifier
-+ * @data: a pointer to a private data to be stored in the thermal zone 'devdata' field
-+ * @ops: a pointer to the ops structure associated with the sensor
-+ * @tzp: a pointer to the default thermal zone params structure associated with the sensor
-+ *
-+ * The thermal zone params are treated as default values and ignored if the related property is
-+ * found in DT. (DT params have priority to default values)
-+ */
-+struct thermal_zone_device *devm_thermal_of_zone_register_with_params(struct device *dev, int sensor_id,
-+ void *data,
-+ const struct thermal_zone_device_ops *ops,
-+ struct thermal_zone_params *tzp)
-+{
-+ struct thermal_zone_device **ptr, *tzd;
-+
-+ ptr = devres_alloc(devm_thermal_of_zone_release, sizeof(*ptr),
-+ GFP_KERNEL);
-+ if (!ptr)
-+ return ERR_PTR(-ENOMEM);
-+
-+ tzd = thermal_of_zone_register(dev->of_node, sensor_id, data, ops, tzp);
-+ if (IS_ERR(tzd)) {
-+ devres_free(ptr);
-+ return tzd;
-+ }
-+
-+ *ptr = tzd;
-+ devres_add(dev, ptr);
-+
-+ return tzd;
-+}
-+EXPORT_SYMBOL_GPL(devm_thermal_of_zone_register_with_params);
-+
-+/**
- * devm_thermal_of_zone_unregister - Resource managed version of
- * thermal_of_zone_unregister().
- * @dev: Device for which which resource was allocated.
---- a/include/linux/thermal.h
-+++ b/include/linux/thermal.h
-@@ -263,6 +263,10 @@ struct thermal_zone_params {
- #ifdef CONFIG_THERMAL_OF
- struct thermal_zone_device *devm_thermal_of_zone_register(struct device *dev, int id, void *data,
- const struct thermal_zone_device_ops *ops);
-+struct thermal_zone_device *devm_thermal_of_zone_register_with_params(struct device *dev, int id,
-+ void *data,
-+ const struct thermal_zone_device_ops *ops,
-+ struct thermal_zone_params *tzp);
-
- void devm_thermal_of_zone_unregister(struct device *dev, struct thermal_zone_device *tz);
-
-@@ -274,6 +278,15 @@ struct thermal_zone_device *devm_thermal
- {
- return ERR_PTR(-ENOTSUPP);
- }
-+
-+static inline
-+struct thermal_zone_device *devm_thermal_of_zone_register_with_params(struct device *dev, int id,
-+ void *data,
-+ const struct thermal_zone_device_ops *ops,
-+ struct thermal_zone_params *tzp)
-+{
-+ return ERR_PTR(-ENOTSUPP);
-+}
-
- static inline void devm_thermal_of_zone_unregister(struct device *dev,
- struct thermal_zone_device *tz)
+++ /dev/null
-From bc6a6a4ec6c28467683121cc165e5681b4acdf8d Mon Sep 17 00:00:00 2001
-Date: Tue, 27 Aug 2024 23:04:53 +0200
-Subject: [PATCH 3/3] 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.
-
----
- drivers/thermal/Kconfig | 9 +
- drivers/thermal/Makefile | 1 +
- drivers/thermal/airoha_thermal.c | 482 +++++++++++++++++++++++++++++++
- 3 files changed, 492 insertions(+)
- create mode 100644 drivers/thermal/airoha_thermal.c
-
---- a/drivers/thermal/Kconfig
-+++ b/drivers/thermal/Kconfig
-@@ -317,6 +317,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
---- a/drivers/thermal/Makefile
-+++ b/drivers/thermal/Makefile
-@@ -34,6 +34,7 @@ obj-$(CONFIG_K3_THERMAL) += k3_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
---- /dev/null
-+++ b/drivers/thermal/airoha_thermal.c
-@@ -0,0 +1,482 @@
-+// SPDX-License-Identifier: GPL-2.0-or-later
-+
-+#include <linux/module.h>
-+#include <linux/bitfield.h>
-+#include <linux/delay.h>
-+#include <linux/interrupt.h>
-+#include <linux/mfd/syscon.h>
-+#include <linux/of.h>
-+#include <linux/of_address.h>
-+#include <linux/platform_device.h>
-+#include <linux/regmap.h>
-+#include <linux/thermal.h>
-+
-+/* SCU regs */
-+#define EN7581_PLLRG_PROTECT 0x268
-+#define EN7581_PWD_TADC 0x2ec
-+#define EN7581_MUX_TADC GENMASK(3, 1)
-+#define EN7581_DOUT_TADC 0x2f8
-+#define EN7581_DOUT_TADC_MASK GENMASK(15, 0)
-+
-+/* PTP_THERMAL regs */
-+#define EN7581_TEMPMONCTL0 0x800
-+#define EN7581_SENSE3_EN BIT(3)
-+#define EN7581_SENSE2_EN BIT(2)
-+#define EN7581_SENSE1_EN BIT(1)
-+#define EN7581_SENSE0_EN BIT(0)
-+#define EN7581_TEMPMONCTL1 0x804
-+/* period unit calculated in BUS clock * 256 scaling-up */
-+#define EN7581_PERIOD_UNIT GENMASK(9, 0)
-+#define EN7581_TEMPMONCTL2 0x808
-+#define EN7581_FILT_INTERVAL GENMASK(25, 16)
-+#define EN7581_SEN_INTERVAL GENMASK(9, 0)
-+#define EN7581_TEMPMONINT 0x80C
-+#define EN7581_STAGE3_INT_EN BIT(31)
-+#define EN7581_STAGE2_INT_EN BIT(30)
-+#define EN7581_STAGE1_INT_EN BIT(29)
-+#define EN7581_FILTER_INT_EN_3 BIT(28)
-+#define EN7581_IMMD_INT_EN3 BIT(27)
-+#define EN7581_NOHOTINTEN3 BIT(26)
-+#define EN7581_HOFSINTEN3 BIT(25)
-+#define EN7581_LOFSINTEN3 BIT(24)
-+#define EN7581_HINTEN3 BIT(23)
-+#define EN7581_CINTEN3 BIT(22)
-+#define EN7581_FILTER_INT_EN_2 BIT(21)
-+#define EN7581_FILTER_INT_EN_1 BIT(20)
-+#define EN7581_FILTER_INT_EN_0 BIT(19)
-+#define EN7581_IMMD_INT_EN2 BIT(18)
-+#define EN7581_IMMD_INT_EN1 BIT(17)
-+#define EN7581_IMMD_INT_EN0 BIT(16)
-+#define EN7581_TIME_OUT_INT_EN BIT(15)
-+#define EN7581_NOHOTINTEN2 BIT(14)
-+#define EN7581_HOFSINTEN2 BIT(13)
-+#define EN7581_LOFSINTEN2 BIT(12)
-+#define EN7581_HINTEN2 BIT(11)
-+#define EN7581_CINTEN2 BIT(10)
-+#define EN7581_NOHOTINTEN1 BIT(9)
-+#define EN7581_HOFSINTEN1 BIT(8)
-+#define EN7581_LOFSINTEN1 BIT(7)
-+#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)
-+#define EN7581_IMMD_INT_STS3 BIT(27)
-+#define EN7581_NOHOTINTSTS3 BIT(26)
-+#define EN7581_HOFSINTSTS3 BIT(25)
-+#define EN7581_LOFSINTSTS3 BIT(24)
-+#define EN7581_HINTSTS3 BIT(23)
-+#define EN7581_CINTSTS3 BIT(22)
-+#define EN7581_FILTER_INT_STAT_2 BIT(21)
-+#define EN7581_FILTER_INT_STAT_1 BIT(20)
-+#define EN7581_FILTER_INT_STAT_0 BIT(19)
-+#define EN7581_IMMD_INT_STS2 BIT(18)
-+#define EN7581_IMMD_INT_STS1 BIT(17)
-+#define EN7581_IMMD_INT_STS0 BIT(16)
-+#define EN7581_TIME_OUT_INT_STAT BIT(15)
-+#define EN7581_NOHOTINTSTS2 BIT(14)
-+#define EN7581_HOFSINTSTS2 BIT(13)
-+#define EN7581_LOFSINTSTS2 BIT(12)
-+#define EN7581_HINTSTS2 BIT(11)
-+#define EN7581_CINTSTS2 BIT(10)
-+#define EN7581_NOHOTINTSTS1 BIT(9)
-+#define EN7581_HOFSINTSTS1 BIT(8)
-+#define EN7581_LOFSINTSTS1 BIT(7)
-+#define EN7581_HINTSTS1 BIT(6)
-+#define EN7581_CINTSTS1 BIT(5)
-+#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
-+#define EN7581_MSRCTL3 GENMASK(11, 9)
-+#define EN7581_MSRCTL2 GENMASK(8, 6)
-+#define EN7581_MSRCTL1 GENMASK(5, 3)
-+#define EN7581_MSRCTL0 GENMASK(2, 0)
-+#define EN7581_TEMPADCVALIDADDR 0x878
-+#define EN7581_ADC_VALID_ADDR GENMASK(31, 0)
-+#define EN7581_TEMPADCVOLTADDR 0x87c
-+#define EN7581_ADC_VOLT_ADDR GENMASK(31, 0)
-+#define EN7581_TEMPRDCTRL 0x880
-+/*
-+ * NOTICE: AHB have this set to 0 by default. Means that
-+ * the same addr is used for ADC volt and valid reading.
-+ * In such case, VALID ADDR is used and volt addr is ignored.
-+ */
-+#define EN7581_RD_CTRL_DIFF BIT(0)
-+#define EN7581_TEMPADCVALIDMASK 0x884
-+#define EN7581_ADV_RD_VALID_POLARITY BIT(5)
-+#define EN7581_ADV_RD_VALID_POS GENMASK(4, 0)
-+#define EN7581_TEMPADCVOLTAGESHIFT 0x888
-+#define EN7581_ADC_VOLTAGE_SHIFT GENMASK(4, 0)
-+/*
-+ * Same values for each CTL.
-+ * Can operate in:
-+ * - 1 sample
-+ * - 2 sample and make average of them
-+ * - 4,6,10,16 sample, drop max and min and make avgerage of them
-+ */
-+#define EN7581_MSRCTL_1SAMPLE 0x0
-+#define EN7581_MSRCTL_AVG2SAMPLE 0x1
-+#define EN7581_MSRCTL_4SAMPLE_MAX_MIX_AVG2 0x2
-+#define EN7581_MSRCTL_6SAMPLE_MAX_MIX_AVG4 0x3
-+#define EN7581_MSRCTL_10SAMPLE_MAX_MIX_AVG8 0x4
-+#define EN7581_MSRCTL_18SAMPLE_MAX_MIX_AVG16 0x5
-+#define EN7581_TEMPAHBPOLL 0x840
-+#define EN7581_ADC_POLL_INTVL GENMASK(31, 0)
-+/* PTPSPARE0,2 reg are used to store efuse info for calibrated temp offset */
-+#define EN7581_EFUSE_TEMP_OFFSET_REG 0xf20 /* PTPSPARE0 */
-+#define EN7581_EFUSE_TEMP_OFFSET GENMASK(31, 16)
-+#define EN7581_PTPSPARE1 0xf24 /* PTPSPARE1 */
-+#define EN7581_EFUSE_TEMP_CPU_SENSOR_REG 0xf28 /* PTPSPARE2 */
-+
-+#define EN7581_SLOPE_X100_DIO_DEFAULT 5645
-+#define EN7581_SLOPE_X100_DIO_AVS 5645
-+
-+#define EN7581_INIT_TEMP_CPK_X10 300
-+#define EN7581_INIT_TEMP_FTK_X10 620
-+#define EN7581_INIT_TEMP_NONK_X10 550
-+
-+#define EN7581_SCU_THERMAL_PROTECT_KEY 0x12
-+#define EN7581_SCU_THERMAL_MUX_DIODE1 0x7
-+
-+/* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */
-+#define TEMP_TO_RAW(priv, tz, temp) ((((((temp) / 100) - (priv)->init_temp) * \
-+ thermal_zone_get_slope(tz)) / 1000) + \
-+ thermal_zone_get_offset(tz))
-+
-+/* Convert raw to temp ((((temp - offset) * 1000) / slope + init) * 100) */
-+#define RAW_TO_TEMP(priv, tz, raw) (((((raw) - thermal_zone_get_offset(tz)) * 1000) / \
-+ thermal_zone_get_slope(tz) + \
-+ (priv)->init_temp) * 100)
-+
-+struct airoha_thermal_priv {
-+ void __iomem *base;
-+ struct regmap *chip_scu;
-+ struct resource scu_adc_res;
-+
-+ struct thermal_zone_device *tz;
-+ int init_temp;
-+};
-+
-+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);
-+}
-+
-+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;
-+ avg_temp /= 4;
-+
-+ *temp = RAW_TO_TEMP(priv, tz, avg_temp);
-+ return 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, tz, FIELD_MAX(EN7581_DOUT_TADC_MASK)))
-+ high = 110000;
-+
-+ /* We offset the high temp of 1°C to trigger correct event */
-+ writel(TEMP_TO_RAW(priv, tz, 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, tz, 0))
-+ low = -33000;
-+
-+ /* We offset the low temp of 1°C to trigger correct event */
-+ writel(TEMP_TO_RAW(priv, tz, low) >> 4,
-+ priv->base + EN7581_TEMPOFFSETL);
-+ }
-+
-+ /* Enable sensor 0 monitor */
-+ writel(EN7581_SENSE0_EN, priv->base + EN7581_TEMPMONCTL0);
-+
-+ 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;
-+ break;
-+ case EN7581_LOFSINTSTS0:
-+ event = THERMAL_EVENT_UNSPECIFIED;
-+ break;
-+ default:
-+ goto exit;
-+ }
-+
-+ thermal_zone_device_update(priv->tz, event);
-+
-+exit:
-+ /* reset interrupt */
-+ writel(status, priv->base + EN7581_TEMPMONINTSTS);
-+
-+ return IRQ_HANDLED;
-+}
-+
-+static void airoha_thermal_setup_adc_val(struct device *dev,
-+ struct airoha_thermal_priv *priv,
-+ struct thermal_zone_params *tzp)
-+{
-+ u32 efuse_calib_info, cpu_sensor;
-+
-+ /* 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);
-+ if (efuse_calib_info) {
-+ tzp->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);
-+ if (cpu_sensor) {
-+ tzp->slope = EN7581_SLOPE_X100_DIO_DEFAULT;
-+ priv->init_temp = EN7581_INIT_TEMP_FTK_X10;
-+ } else {
-+ tzp->slope = EN7581_SLOPE_X100_DIO_AVS;
-+ priv->init_temp = EN7581_INIT_TEMP_CPK_X10;
-+ }
-+ } else {
-+ tzp->offset = airoha_get_thermal_ADC(priv);
-+ tzp->slope = EN7581_SLOPE_X100_DIO_DEFAULT;
-+ priv->init_temp = EN7581_INIT_TEMP_NONK_X10;
-+ dev_info(dev, "missing thermal calibrarion EFUSE, using non calibrated value\n");
-+ }
-+}
-+
-+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,
-+ * 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);
-+}
-+
-+static int airoha_thermal_probe(struct platform_device *pdev)
-+{
-+ struct thermal_zone_params tzp = { };
-+ 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);
-+
-+ 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);
-+ 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, &tzp);
-+
-+ /* register of thermal sensor and get info from DT */
-+ priv->tz = devm_thermal_of_zone_register_with_params(dev, 0, priv,
-+ &thdev_ops,
-+ &tzp);
-+ 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_DESCRIPTION("Airoha thermal driver");
-+MODULE_LICENSE("GPL");