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

Huacai Chen <[email protected]> Thu, 16 Jul 2026 11:37:41 +0800
Newsgroups dev.linux.lists.loongarch,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-pm
Message-ID <CAAhV-H5J1mv6gEC9=GJqVV4fRovtO-=ZpzG54Yx=qLM7yX=2ew@mail.gmail.com>
On Thu, Jul 16, 2026 at 10:31 AM Binbin Zhou <[email protected]> wrote:
>
> Hi:
>
> On Fri, Jul 10, 2026 at 10:32 PM Huacai Chen <[email protected]> wrote:
> >
> > 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.
>
> Try communicating in person; there’s no better way. The current
> judgment criteria can only accommodate the standard temperature range.
I suggest
#define LS2K0300_OLD_FUSE_FLAG BIT(2)

Then when you get a temp out of normal range, set this flag. And use
"raw_adc * 569 - 394700" directly for the future if this flag is set.

Huacai
>
> Also, the `old fuse` refers to the very first batch of chips, so I
> don’t think the scope of the issue will be very broad.
> >
> > 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
> > >
>
> --
> Thanks.
> Binbin