[PATCH v4 5/5] thermal/drivers/loongson2: Add support for Loongson-2K0300
Binbin Zhou <[email protected]> Tue, 4 Aug 2026 20:43:58 +0800
| Newsgroups | dev.linux.lists.mfd,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <0c2af3251d520ab04d18c14fce0a825e7b171ca5.1785829933.git.zhoubinbin@loongson.cn> |
Add support for the thermal sensor found on the Loongson-2K0300 SoC. The Loongson-2K0300 thermal sensor uses a 10-bit ADC and requires per-chip calibration. The calibration offset is stored in the CHIP ID registers, which are accessed via syscon. The driver reads this offset and applies it when converting the raw ADC value to millicelsius. To handle old fuse versions that cannot be calibrated correctly, the driver includes a fallback formula and a validity check. Once invalid data is detected, the driver falls back to the old formula for future reads and warns the user. Signed-off-by: Binbin Zhou <[email protected]> --- drivers/thermal/loongson2_thermal.c | 94 ++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c index 1a8ca2cd06f1..51b29b60372a 100644 --- a/drivers/thermal/loongson2_thermal.c +++ b/drivers/thermal/loongson2_thermal.c @@ -2,9 +2,11 @@ /* * Author: zhanghongchen <[email protected]> * Yinbo Zhu <[email protected]> + * Binbin Zhou <[email protected]> * Copyright (C) 2022-2023 Loongson Technology Corporation Limited */ +#include <linux/bitfield.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/minmax.h> @@ -14,6 +16,9 @@ #include <linux/property.h> #include <linux/thermal.h> #include <linux/units.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/syscore_ops.h> #include "thermal_hwmon.h" @@ -23,18 +28,34 @@ #define LOONGSON2_THSENS_CTRL_LOW_REG 0x8 #define LOONGSON2_THSENS_STATUS_REG 0x10 #define LOONGSON2_THSENS_OUT_REG 0x14 +#define LOONGSON2_THSENS_CFG_REG 0x18 #define LOONGSON2_THSENS_INT_LO BIT(0) #define LOONGSON2_THSENS_INT_HIGH BIT(1) #define LOONGSON2_THSENS_INT_EN (LOONGSON2_THSENS_INT_LO | \ LOONGSON2_THSENS_INT_HIGH) -#define LOONGSON2_THSENS_OUT_MASK 0xFF +#define LOONGSON2_THSENS_OUT_8B_MASK 0xFF +#define LOONGSON2_THSENS_OUT_11B_MASK GENMASK(10, 0) + +#define LS2K0300_CHIP_ID0 0x10 +#define LS2K0300_CHIP_ID1 0x14 +#define LS2K0300_EXTERN_ID BIT(4) +#define LS2K0300_ID0_VAL_MASK GENMASK(31, 20) +#define LS2K0300_ID1_VAL_MASK GENMASK(15, 0) + +#define LS2K0300_COMP_VAL_MASK GENMASK(14, 0) +#define LS2K0300_COMP_SIGN_BIT BIT(15) + +#define LS2K0300_LOWEST_VALID_TEMP (-55000) +#define LS2K0300_HIGHEST_VALID_TEMP (125000) /* * This flag is used to indicate the temperature reading * method of the Loongson-2K2000 */ #define LS2K2000_THSENS_OUT_FLAG BIT(0) +#define LS2K0300_CHIP_ID_FLAG BIT(1) +#define LS2K0300_OLD_FUSE_FLAG BIT(2) struct loongson2_thermal_chip_data { unsigned int thermal_sensor_sel; @@ -43,8 +64,11 @@ struct loongson2_thermal_chip_data { }; struct loongson2_thermal_data { + struct device *dev; void __iomem *ctrl_reg; void __iomem *temp_reg; + struct regmap *regmap_cfg; + u32 flags; const struct loongson2_thermal_chip_data *chip_data; }; @@ -72,13 +96,54 @@ static int loongson2_thermal_set(struct loongson2_thermal_data *data, return 0; } +static int loongson2_2k0300_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct loongson2_thermal_data *tdata = thermal_zone_device_priv(tz); + int calib_data, calib_offset, temp_mc, raw_adc; + u32 chip_id0 = 0, chip_id1 = 0; + + writel(0xff03, tdata->ctrl_reg + LOONGSON2_THSENS_CFG_REG); + raw_adc = FIELD_GET(LOONGSON2_THSENS_OUT_11B_MASK, + readl(tdata->ctrl_reg + LOONGSON2_THSENS_OUT_REG)); + + if (tdata->flags & LS2K0300_OLD_FUSE_FLAG) { + *temp = raw_adc * 569 - 394700; + return 0; + } + + regmap_read(tdata->regmap_cfg, LS2K0300_CHIP_ID0, &chip_id0); + regmap_read(tdata->regmap_cfg, LS2K0300_CHIP_ID1, &chip_id1); + + if (chip_id0 & LS2K0300_EXTERN_ID) { + calib_data = FIELD_GET(LS2K0300_ID1_VAL_MASK, chip_id1); + calib_offset = FIELD_GET(LS2K0300_COMP_VAL_MASK, calib_data); + if (calib_data & LS2K0300_COMP_SIGN_BIT) + calib_offset = -calib_offset; + } else { + calib_data = FIELD_GET(LS2K0300_ID0_VAL_MASK, chip_id0); + calib_offset = FIELD_GET(LS2K0300_COMP_VAL_MASK, calib_data); + } + + temp_mc = (raw_adc + calib_offset) * 570 - 394700; + + /* For old fuse which can not read right thermal data */ + if (temp_mc < LS2K0300_LOWEST_VALID_TEMP || temp_mc > LS2K0300_HIGHEST_VALID_TEMP) { + dev_warn_once(tdata->dev, "It's an old fuse, thermal %d is not right\n", temp_mc); + tdata->flags |= LS2K0300_OLD_FUSE_FLAG; + temp_mc = raw_adc * 569 - 394700; + } + *temp = temp_mc; + + return 0; +} + static int loongson2_2k1000_get_temp(struct thermal_zone_device *tz, int *temp) { int val; struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); val = readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG); - *temp = ((val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO; + *temp = ((val & LOONGSON2_THSENS_OUT_8B_MASK) - HECTO) * KILO; return 0; } @@ -113,6 +178,11 @@ static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, return loongson2_thermal_set(data, low/MILLI, high/MILLI, true); } +static const struct thermal_zone_device_ops loongson2_2k0300_of_thermal_ops = { + .get_temp = loongson2_2k0300_get_temp, + .set_trips = loongson2_thermal_set_trips, +}; + static const struct thermal_zone_device_ops loongson2_2k1000_of_thermal_ops = { .get_temp = loongson2_2k1000_get_temp, .set_trips = loongson2_thermal_set_trips, @@ -135,6 +205,8 @@ static int loongson2_thermal_probe(struct platform_device *pdev) return -ENOMEM; data->chip_data = device_get_match_data(dev); + data->flags = data->chip_data->flags; + data->dev = dev; data->ctrl_reg = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->ctrl_reg)) @@ -147,6 +219,14 @@ static int loongson2_thermal_probe(struct platform_device *pdev) return PTR_ERR(data->temp_reg); } + /* The chip id register is needed for Loongson-2K0300 */ + if (data->chip_data->flags & LS2K0300_CHIP_ID_FLAG) { + data->regmap_cfg = + syscon_regmap_lookup_by_phandle(dev->of_node, "loongson,chipid"); + if (IS_ERR(data->regmap_cfg)) + return PTR_ERR(data->regmap_cfg); + } + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; @@ -179,6 +259,12 @@ static int loongson2_thermal_probe(struct platform_device *pdev) return 0; } +static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k0300_data = { + .thermal_sensor_sel = 0, + .flags = LS2K0300_CHIP_ID_FLAG, + .thermal_ops = &loongson2_2k0300_of_thermal_ops, +}; + static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = { .thermal_sensor_sel = 0, .flags = 0, @@ -192,6 +278,10 @@ static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data }; static const struct of_device_id of_loongson2_thermal_match[] = { + { + .compatible = "loongson,ls2k0300-thermal", + .data = &loongson2_thermal_ls2k0300_data, + }, { .compatible = "loongson,ls2k1000-thermal", .data = &loongson2_thermal_ls2k1000_data, -- 2.52.0