Re: [PATCH 2/2] thermal/drivers/loongson2: Add thermal driver support for Loongson-2K0300

Huacai Chen <[email protected]>
Newsgroups dev.linux.lists.loongarch,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-pm
Message-ID <CAAhV-H6Ae=J9a6y6C9WE-sfUJnrypWpc8e01VHJ3ndVt-F+RXA@mail.gmail.com>
Hi, Binbin,

On Fri, Jul 10, 2026 at 4:25 PM Binbin Zhou <[email protected]> wrote:
>
> 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)
The naming is a little strange, maybe use LOONGSON2_THSENS_OUT_8B_MASK
and LOONGSON2_THSENS_OUT_10B_MASK?

> +
> +#define LS2K0300_CHIP_ID1              0x4
Also define LS2K0300_CHIP_ID0 here?

> +#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) {
Is there a better way to detect the old fuse? Because I think temp_mc
between LS2K0300_LOWEST_VALID_TEMP and  LS2K0300_HIGHEST_VALID_TEMP is
also not valid for the old fuse.

Huacai

> +               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
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.