[PATCH 2/2] thermal/drivers/loongson2: Add thermal driver support for Loongson-2K0300
Binbin Zhou <[email protected]>
| Newsgroups | dev.linux.lists.loongarch,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <e5a574b2ae0e5e0ec32fe81c0ccd03eb3ad5a164.1783670011.git.zhoubinbin@loongson.cn> |
The Loongson-2K0300 SoC uses a new thermal sensor that requires reading a separate CPU ID register to obtain hardware version information. This version info is used as a correction factor (fix_data) in the temperature calculation formula. Its thermal sensor requires the following hardware-specific handling: - Read chip ID register (offset 0x0 and 0x4) to get the compensation value (comp_val). The value is stored in either bits [31:20] of the ID0 register or bits [15:0] of the ID1 register, depending on the EXTERN_ID bit. - The compensation value is a signed 15-bit field; extract the value and apply sign accordingly. Additionally, some early Loongson-2K0300 chips may have an old fuse that yields invalid temperature readings outside the -55 to 125 range. In such cases, the driver falls back to a simplified formula (raw * 569 - 394700) and logs a warning, ensuring the system can still function without crashing. Signed-off-by: Binbin Zhou <[email protected]> --- drivers/thermal/loongson2_thermal.c | 83 ++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c index ea4dd2fb1f47..a7eb87070aa9 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]> - * Copyright (C) 2022-2023 Loongson Technology Corporation Limited + * Binbin Zhou <[email protected]> + * Copyright (C) 2022-2026 Loongson Technology Corporation Limited */ +#include <linux/bitfield.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/minmax.h> @@ -23,27 +25,44 @@ #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 LS2K0300_THSENS_OUT_MASK GENMASK(10, 0) + +#define LS2K0300_CHIP_ID1 0x4 +#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) struct loongson2_thermal_chip_data { unsigned int thermal_sensor_sel; unsigned int flags; + const struct thermal_zone_device_ops *thermal_ops; }; struct loongson2_thermal_data { + struct device *dev; void __iomem *ctrl_reg; void __iomem *temp_reg; + void __iomem *id_reg; const struct loongson2_thermal_chip_data *chip_data; }; @@ -71,6 +90,38 @@ 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, chip_id1; + + raw_adc = FIELD_GET(LS2K0300_THSENS_OUT_MASK, + readl(tdata->ctrl_reg + LOONGSON2_THSENS_OUT_REG)); + chip_id0 = readl(tdata->id_reg); + chip_id1 = readl(tdata->id_reg + LS2K0300_CHIP_ID1); + + if (chip_id0 & LS2K0300_EXTERN_ID) + calib_data = FIELD_GET(LS2K0300_ID1_VAL_MASK, chip_id1); + else + calib_data = FIELD_GET(LS2K0300_ID0_VAL_MASK, chip_id0); + + calib_offset = FIELD_GET(LS2K0300_COMP_VAL_MASK, calib_data); + if (calib_data & LS2K0300_COMP_SIGN_BIT) + calib_offset = -calib_offset; + + 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); + 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; @@ -112,6 +163,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, @@ -124,7 +180,6 @@ static const struct thermal_zone_device_ops loongson2_2k2000_of_thermal_ops = { static int loongson2_thermal_probe(struct platform_device *pdev) { - const struct thermal_zone_device_ops *thermal_ops; struct device *dev = &pdev->dev; struct loongson2_thermal_data *data; struct thermal_zone_device *tzd; @@ -134,6 +189,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev) if (!data) return -ENOMEM; + data->dev = dev; data->chip_data = device_get_match_data(dev); data->ctrl_reg = devm_platform_ioremap_resource(pdev, 0); @@ -145,10 +201,13 @@ static int loongson2_thermal_probe(struct platform_device *pdev) data->temp_reg = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(data->temp_reg)) return PTR_ERR(data->temp_reg); + } - thermal_ops = &loongson2_2k2000_of_thermal_ops; - } else { - thermal_ops = &loongson2_2k1000_of_thermal_ops; + /* The chip id register is needed for Loongson-2K0300 */ + if (data->chip_data->flags & LS2K0300_CHIP_ID_FLAG) { + data->id_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(data->id_reg)) + return PTR_ERR(data->id_reg); } irq = platform_get_irq(pdev, 0); @@ -160,7 +219,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev) loongson2_thermal_set(data, 0, 0, false); for (i = 0; i <= LOONGSON2_MAX_SENSOR_SEL_NUM; i++) { - tzd = devm_thermal_of_zone_register(dev, i, data, thermal_ops); + tzd = devm_thermal_of_zone_register(dev, i, data, data->chip_data->thermal_ops); if (!IS_ERR(tzd)) break; @@ -181,17 +240,29 @@ 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, + .thermal_ops = &loongson2_2k1000_of_thermal_ops, }; static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data = { .thermal_sensor_sel = 0, .flags = LS2K2000_THSENS_OUT_FLAG, + .thermal_ops = &loongson2_2k2000_of_thermal_ops, }; 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