* [PATCH v8 1/7] thermal/drivers/loongson2: Fix negative trip conversion in set_trips
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
@ 2026-11-03 7:23 ` Binbin Zhou
2026-11-03 7:23 ` [PATCH v8 2/7] thermal/drivers/loongson2: Fix clamp() usage for thermal trip points Binbin Zhou
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:23 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou, stable
The `MILLI` is defined as an unsigned long constant. When the
temperatures passed to loongson2_thermal_set_trips() are negative, the
expression `low / MILLI` triggers implicit conversion of the signed
operand to unsigned long, and the division is then carried out in
unsigned arithmetic.
For example, with a low trip of -40000 (milli-degree Celsius):
-40000 / 1000UL = 18446744073709511 /* unsigned long */
After truncation to the 32-bit register field this becomes
1271310379, instead of the expected 60 (i.e. -40 + HECTO). Writing such
bogus value into the trip register leaves most of the upper bits set,
which may hit status/trigger bits and cause spurious thermal interrupts.
Use MILLIDEGREE_PER_DEGREE instead of MILLI so that the division is
performed in signed arithmetic.
Cc: stable@vger.kernel.org
Fixes: e7e3a7c35791 ("thermal/drivers/loongson-2: Add thermal management support")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index 4d40fc706a53..54ddee47cb32 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -108,7 +108,8 @@ static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low,
{
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
- return loongson2_thermal_set(data, low/MILLI, high/MILLI, true);
+ return loongson2_thermal_set(data, low / MILLIDEGREE_PER_DEGREE,
+ high / MILLIDEGREE_PER_DEGREE, true);
}
static const struct thermal_zone_device_ops loongson2_2k1000_of_thermal_ops = {
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v8 2/7] thermal/drivers/loongson2: Fix clamp() usage for thermal trip points
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
2026-11-03 7:23 ` [PATCH v8 1/7] thermal/drivers/loongson2: Fix negative trip conversion in set_trips Binbin Zhou
@ 2026-11-03 7:23 ` Binbin Zhou
2026-11-03 7:24 ` [PATCH v8 3/7] thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming Binbin Zhou
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:23 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou, stable
clamp() takes the value to clamp as its first argument, followed by the
minimum and maximum bounds. The current code passes the -40 and 125
limits as the value instead, so the trip thresholds are not clamped to the
supported sensor range.
Correct the arguments so that low and high are clamped to [-40, 125].
Cc: stable@vger.kernel.org
Fixes: 734b5def91b5 ("thermal/drivers/loongson2: Add Loongson-2K2000 support")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index 54ddee47cb32..9256cd3663e6 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -62,10 +62,10 @@ static int loongson2_thermal_set(struct loongson2_thermal_data *data,
int low, int high, bool enable)
{
/* Set low temperature threshold */
- loongson2_set_ctrl_regs(data, clamp(-40, low, high), true, enable);
+ loongson2_set_ctrl_regs(data, clamp(low, -40, 125), true, enable);
/* Set high temperature threshold */
- loongson2_set_ctrl_regs(data, clamp(125, low, high), false, enable);
+ loongson2_set_ctrl_regs(data, clamp(high, -40, 125), false, enable);
return 0;
}
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v8 3/7] thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
2026-11-03 7:23 ` [PATCH v8 1/7] thermal/drivers/loongson2: Fix negative trip conversion in set_trips Binbin Zhou
2026-11-03 7:23 ` [PATCH v8 2/7] thermal/drivers/loongson2: Fix clamp() usage for thermal trip points Binbin Zhou
@ 2026-11-03 7:24 ` Binbin Zhou
2026-11-03 7:41 ` sashiko-bot
2026-11-03 7:25 ` [PATCH v8 4/7] thermal/drivers/loongson2: Simplify thermal sensor registration Binbin Zhou
` (3 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:24 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou, stable
The Loongson-2K2000 splits its 16-bit gate value across the control
register (low byte) and a dedicated HI_GATE register (high byte), and
derives the gate value from the target temperature with a different
formula than the Loongson-2K1000.
Add a per-SoC set_trips_regs callback and implement the Loongson-2K2000
variant alongside the existing Loongson-2K1000 one.
Cc: stable@vger.kernel.org
Fixes: 734b5def91b5 ("thermal/drivers/loongson2: Add Loongson-2K2000 support")
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 102 +++++++++++++++++++++-------
1 file changed, 76 insertions(+), 26 deletions(-)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index 9256cd3663e6..8be0c6a29efe 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -5,6 +5,7 @@
* Copyright (C) 2022-2023 Loongson Technology Corporation Limited
*/
+#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/minmax.h>
@@ -27,7 +28,24 @@
#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_CTRL_GATE_MASK GENMASK(7, 0)
+#define LOONGSON2_THSENS_CTRL_INTEN BIT(8)
+#define LOONGSON2_THSENS_CTRL_SEL_MASK GENMASK(11, 10)
+
+#define LOONGSON2_THSENS_OUT_8B_MASK GENMASK(7, 0)
+#define LOONGSON2_THSENS_OUT_16B_MASK GENMASK(15, 0)
+
+#define LOONGSON2_LOWEST_VALID_TEMP (-40000)
+#define LOONGSON2_HIGHEST_VALID_TEMP (125000)
+
+/* Loongson-2K2000-Specific Registers */
+#define LS2K2000_THSENS_HI_GATE_HI_REG 0x18
+#define LS2K2000_THSENS_HI_GATE_LOW_REG 0x1c
+
+#define LS2K2000_TEMP_MUL (820)
+#define LS2K2000_TEMP_DIV (0x4000)
+#define LS2K2000_TEMP_OFFSET (311)
/*
* This flag is used to indicate the temperature reading
@@ -35,9 +53,13 @@
*/
#define LS2K2000_THSENS_OUT_FLAG BIT(0)
+struct loongson2_thermal_data;
+
struct loongson2_thermal_chip_data {
unsigned int thermal_sensor_sel;
unsigned int flags;
+ void (*set_trips_regs)(struct loongson2_thermal_data *data,
+ int temp, bool low, bool enable);
};
struct loongson2_thermal_data {
@@ -46,52 +68,77 @@ struct loongson2_thermal_data {
const struct loongson2_thermal_chip_data *chip_data;
};
-static void loongson2_set_ctrl_regs(struct loongson2_thermal_data *data,
- int ctrl_data, bool low, bool enable)
+static void loongson2_thermal_set(struct loongson2_thermal_data *data,
+ int low, int high, bool enable)
{
- int reg_ctrl = 0;
- int reg_off = data->chip_data->thermal_sensor_sel * 2;
- int ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG;
+ low = clamp(low, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
+ high = clamp(high, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
- reg_ctrl = ctrl_data + HECTO;
- reg_ctrl |= enable ? 0x100 : 0;
- writew(reg_ctrl, data->ctrl_reg + ctrl_reg + reg_off);
-}
-
-static int loongson2_thermal_set(struct loongson2_thermal_data *data,
- int low, int high, bool enable)
-{
/* Set low temperature threshold */
- loongson2_set_ctrl_regs(data, clamp(low, -40, 125), true, enable);
+ data->chip_data->set_trips_regs(data, low / MILLIDEGREE_PER_DEGREE, true, enable);
/* Set high temperature threshold */
- loongson2_set_ctrl_regs(data, clamp(high, -40, 125), false, enable);
-
- return 0;
+ data->chip_data->set_trips_regs(data, high / MILLIDEGREE_PER_DEGREE, false, enable);
}
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);
+ int val;
- val = readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG);
- *temp = ((val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO;
+ val = FIELD_GET(LOONGSON2_THSENS_OUT_8B_MASK,
+ readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG));
+ *temp = (val - HECTO) * KILO;
return 0;
}
+static void loongson2_2k1000_set_trips_regs(struct loongson2_thermal_data *data,
+ int temp, bool low, bool enable)
+{
+ u32 sel = data->chip_data->thermal_sensor_sel;
+ int ctrl_reg;
+ u16 val;
+
+ ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG;
+ val = FIELD_PREP(LOONGSON2_THSENS_CTRL_GATE_MASK, temp + HECTO) |
+ FIELD_PREP(LOONGSON2_THSENS_CTRL_INTEN, enable) |
+ FIELD_PREP(LOONGSON2_THSENS_CTRL_SEL_MASK, sel);
+ writew(val, data->ctrl_reg + ctrl_reg + sel * 2);
+}
+
static int loongson2_2k2000_get_temp(struct thermal_zone_device *tz, int *temp)
{
- int val;
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+ int val;
- val = readl(data->temp_reg);
- *temp = ((val & 0xffff) * 820 / 0x4000 - 311) * KILO;
+ val = FIELD_GET(LOONGSON2_THSENS_OUT_16B_MASK, readl(data->temp_reg));
+ *temp = (val * LS2K2000_TEMP_MUL / LS2K2000_TEMP_DIV - LS2K2000_TEMP_OFFSET) * KILO;
return 0;
}
+static void loongson2_2k2000_set_trips_regs(struct loongson2_thermal_data *data,
+ int temp, bool low, bool enable)
+{
+ unsigned int sel = data->chip_data->thermal_sensor_sel;
+ int hi_reg, ctrl_reg, val, gate;
+
+ gate = (temp + LS2K2000_TEMP_OFFSET) * LS2K2000_TEMP_DIV / LS2K2000_TEMP_MUL;
+
+ /* Low byte of the gate value goes to the control register */
+ ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG;
+ val = FIELD_PREP(LOONGSON2_THSENS_CTRL_GATE_MASK, gate) |
+ FIELD_PREP(LOONGSON2_THSENS_CTRL_INTEN, enable) |
+ FIELD_PREP(LOONGSON2_THSENS_CTRL_SEL_MASK, sel);
+ writew(val, data->ctrl_reg + ctrl_reg + sel * 2);
+
+ /* High byte of the gate value goes to the low byte of the HI_GATE register */
+ hi_reg = low ? LS2K2000_THSENS_HI_GATE_LOW_REG : LS2K2000_THSENS_HI_GATE_HI_REG;
+ val = FIELD_GET(GENMASK(15, 8), gate);
+ writeb(val, data->ctrl_reg + hi_reg + sel);
+}
+
static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev)
{
struct thermal_zone_device *tzd = dev;
@@ -108,8 +155,9 @@ static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low,
{
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
- return loongson2_thermal_set(data, low / MILLIDEGREE_PER_DEGREE,
- high / MILLIDEGREE_PER_DEGREE, true);
+ loongson2_thermal_set(data, low, high, true);
+
+ return 0;
}
static const struct thermal_zone_device_ops loongson2_2k1000_of_thermal_ops = {
@@ -184,11 +232,13 @@ static int loongson2_thermal_probe(struct platform_device *pdev)
static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = {
.thermal_sensor_sel = 0,
.flags = 0,
+ .set_trips_regs = loongson2_2k1000_set_trips_regs,
};
static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data = {
.thermal_sensor_sel = 0,
.flags = LS2K2000_THSENS_OUT_FLAG,
+ .set_trips_regs = loongson2_2k2000_set_trips_regs,
};
static const struct of_device_id of_loongson2_thermal_match[] = {
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v8 3/7] thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming
2026-11-03 7:24 ` [PATCH v8 3/7] thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming Binbin Zhou
@ 2026-11-03 7:41 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-11-03 7:41 UTC (permalink / raw)
To: Binbin Zhou; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] thermal: loongson2: torn gate write order in loongson2_2k2000_set_trips_regs()
- [Low] thermal: loongson2: 16-bit gate in 8-bit FIELD_PREP() in set_trips_regs()
--
commit 8811a8d93840088ed85385aa42b6210e17fd97b7
Author: Binbin Zhou <zhoubinbin@loongson.cn>
thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming
This commit introduces a per-SoC set_trips_regs callback in
drivers/thermal/loongson2_thermal.c and implements trip point programming for
the Loongson-2K2000 using its 16-bit gate formula and split control/HI_GATE
registers.
> diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
> index 9256cd3663e60..8be0c6a29efec 100644
> --- a/drivers/thermal/loongson2_thermal.c
> +++ b/drivers/thermal/loongson2_thermal.c
[ ... ]
> @@ -46,52 +68,77 @@ struct loongson2_thermal_data {
[ ... ]
> +static void loongson2_2k2000_set_trips_regs(struct loongson2_thermal_data *data,
> + int temp, bool low, bool enable)
> +{
> + unsigned int sel = data->chip_data->thermal_sensor_sel;
> + int hi_reg, ctrl_reg, val, gate;
> +
> + gate = (temp + LS2K2000_TEMP_OFFSET) * LS2K2000_TEMP_DIV / LS2K2000_TEMP_MUL;
> +
> + /* Low byte of the gate value goes to the control register */
> + ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG;
> + val = FIELD_PREP(LOONGSON2_THSENS_CTRL_GATE_MASK, gate) |
> + FIELD_PREP(LOONGSON2_THSENS_CTRL_INTEN, enable) |
> + FIELD_PREP(LOONGSON2_THSENS_CTRL_SEL_MASK, sel);
[Severity: Low]
As reported by Sashiko on v7 [1], in loongson2_2k2000_set_trips_regs(), can
passing the unmasked 16-bit gate value into
FIELD_PREP(LOONGSON2_THSENS_CTRL_GATE_MASK, gate) trigger a compile-time
BUILD_BUG_ON_MSG failure?
Across the clamped temperature range [-40, 125] C, gate is in the range
[5413, 8709] (0x1525 to 0x2205), so bits 15:8 are always non-zero and never
fit within LOONGSON2_THSENS_CTRL_GATE_MASK (GENMASK(7, 0)).
When loongson2_thermal_probe() calls loongson2_thermal_set(data, 0, 0, false)
with a constant temperature of 0, interprocedural constant propagation or LTO
can resolve loongson2_2k2000_set_trips_regs() with gate = 0x1844 at compile
time, tripping the field overflow check in __BF_FIELD_CHECK_MASK().
Should the low byte be extracted first (for example via
FIELD_GET(GENMASK(7, 0), gate) or lower_8_bits(gate)), matching how the high
byte is extracted with FIELD_GET(GENMASK(15, 8), gate) below?
> + writew(val, data->ctrl_reg + ctrl_reg + sel * 2);
> +
> + /* High byte of the gate value goes to the low byte of the HI_GATE register */
> + hi_reg = low ? LS2K2000_THSENS_HI_GATE_LOW_REG : LS2K2000_THSENS_HI_GATE_HI_REG;
> + val = FIELD_GET(GENMASK(15, 8), gate);
> + writeb(val, data->ctrl_reg + hi_reg + sel);
> +}
[Severity: Medium]
In loongson2_2k2000_set_trips_regs(), could writing the control register
before the HI_GATE register expose a torn 16-bit threshold while the
interrupt is enabled and latch a spurious thermal interrupt?
During loongson2_thermal_probe(), loongson2_thermal_set() first initializes
both thresholds to 0 C (gate = 0x1844) with enable = false:
loongson2_thermal_probe()
loongson2_thermal_set(data, 0, 0, false)
loongson2_2k2000_set_trips_regs()
which leaves 0x18 in LS2K2000_THSENS_HI_GATE_HI_REG and
LS2K2000_THSENS_HI_GATE_LOW_REG.
When devm_thermal_of_zone_register() then programs the initial trip window
via loongson2_thermal_set_trips() -> loongson2_thermal_set() with
enable = true (for example, the 40 C trip point where gate = 0x1b65),
writew() writes the new low byte (0x65) together with
LOONGSON2_THSENS_CTRL_INTEN = 1 while LS2K2000_THSENS_HI_GATE_HI_REG still
holds 0x18. Until writeb() updates HI_GATE_HI_REG to 0x1b, the hardware
comparator is active with a composite threshold of 0x1865 (around 1.6 C),
which is already exceeded at normal operating temperatures. A similar
intermediate {old_hi, new_lo} window occurs during runtime trip updates
whenever the threshold crosses a 256-count boundary with INTEN enabled.
Would it be safer to disable INTEN (or write HI_GATE before enabling INTEN in
the control register) so the hardware comparator never evaluates a half-updated
16-bit gate?
[1] https://lore.kernel.org/r/1375bd12403bffc496261953772a45833f9b5a3e.1791512067.git.zhoubinbin@loongson.cn
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1793690276.git.zhoubinbin@loongson.cn?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v8 4/7] thermal/drivers/loongson2: Simplify thermal sensor registration
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
` (2 preceding siblings ...)
2026-11-03 7:24 ` [PATCH v8 3/7] thermal/drivers/loongson2: Fix Loongson-2K2000 trip point programming Binbin Zhou
@ 2026-11-03 7:25 ` Binbin Zhou
2026-11-03 7:25 ` [PATCH v8 5/7] thermal/drivers/loongson2: Move per-SoC ops and init into chip data Binbin Zhou
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:25 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou
The Loongson-2K SoCs expose a single on-chip thermal sensor, so the
loop over LOONGSON2_MAX_SENSOR_SEL_NUM only ever registers one thermal
zone. The additional iterations never succeed and the surrounding
error handling ends up relying on the value of tzd after the loop,
which is fragile and hard to follow.
Register sensor index 0 directly and fail the probe if that single
registration fails.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index 8be0c6a29efe..19e3f9246cc0 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -17,8 +17,6 @@
#include "thermal_hwmon.h"
-#define LOONGSON2_MAX_SENSOR_SEL_NUM 3
-
#define LOONGSON2_THSENS_CTRL_HI_REG 0x0
#define LOONGSON2_THSENS_CTRL_LOW_REG 0x8
#define LOONGSON2_THSENS_STATUS_REG 0x10
@@ -176,7 +174,7 @@ static int loongson2_thermal_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct loongson2_thermal_data *data;
struct thermal_zone_device *tzd;
- int ret, irq, i;
+ int ret, irq;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -207,17 +205,9 @@ 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);
-
- if (!IS_ERR(tzd))
- break;
-
- if (PTR_ERR(tzd) != -ENODEV)
- continue;
-
- return dev_err_probe(dev, PTR_ERR(tzd), "failed to register");
- }
+ tzd = devm_thermal_of_zone_register(dev, 0, data, thermal_ops);
+ if (IS_ERR(tzd))
+ return dev_err_probe(dev, PTR_ERR(tzd), "failed to register sensor");
ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread,
IRQF_ONESHOT, "loongson2_thermal", tzd);
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v8 5/7] thermal/drivers/loongson2: Move per-SoC ops and init into chip data
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
` (3 preceding siblings ...)
2026-11-03 7:25 ` [PATCH v8 4/7] thermal/drivers/loongson2: Simplify thermal sensor registration Binbin Zhou
@ 2026-11-03 7:25 ` Binbin Zhou
2026-11-03 7:25 ` [PATCH v8 6/7] dt-bindings: thermal: loongson,ls2k-thermal: Add compatible for Loongson-2K0300 Binbin Zhou
2026-11-03 7:25 ` [PATCH v8 7/7] thermal/drivers/loongson2: Add Loongson-2K0300 support Binbin Zhou
6 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:25 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou
Instead of using a flags field to select the thermal_zone_device_ops in
probe and handling the Loongson-2K2000 extended register mapping inline,
store the ops pointer and an optional hw_init() callback in struct
loongson2_thermal_chip_data. Each SoC now provides its own hw_init()
that performs the common initialization and, for Loongson-2K2000, maps
the extended temperature register.
Rename the second mapped resource from temp_reg to extend_reg to better
reflect its purpose. Also rename the per-SoC functions with consistent
ls2k1000/ls2k2000 prefixes and introduce a common set_trips callback.
This makes adding support for new SoCs with different register layouts
easier.
No functional change intended.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 138 +++++++++++++++-------------
1 file changed, 74 insertions(+), 64 deletions(-)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index 19e3f9246cc0..d83aaa588c48 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -45,29 +45,24 @@
#define LS2K2000_TEMP_DIV (0x4000)
#define LS2K2000_TEMP_OFFSET (311)
-/*
- * This flag is used to indicate the temperature reading
- * method of the Loongson-2K2000
- */
-#define LS2K2000_THSENS_OUT_FLAG BIT(0)
-
struct loongson2_thermal_data;
struct loongson2_thermal_chip_data {
unsigned int thermal_sensor_sel;
- unsigned int flags;
+ const struct thermal_zone_device_ops *thermal_ops;
void (*set_trips_regs)(struct loongson2_thermal_data *data,
int temp, bool low, bool enable);
+ int (*hw_init)(struct platform_device *pdev, struct loongson2_thermal_data *data);
};
struct loongson2_thermal_data {
void __iomem *ctrl_reg;
- void __iomem *temp_reg;
+ void __iomem *extend_reg;
const struct loongson2_thermal_chip_data *chip_data;
};
-static void loongson2_thermal_set(struct loongson2_thermal_data *data,
- int low, int high, bool enable)
+static void loongson2_thermal_set_trip_temp(struct loongson2_thermal_data *data,
+ int low, int high, bool enable)
{
low = clamp(low, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
high = clamp(high, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
@@ -79,7 +74,16 @@ static void loongson2_thermal_set(struct loongson2_thermal_data *data,
data->chip_data->set_trips_regs(data, high / MILLIDEGREE_PER_DEGREE, false, enable);
}
-static int loongson2_2k1000_get_temp(struct thermal_zone_device *tz, int *temp)
+static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+
+ loongson2_thermal_set_trip_temp(data, low, high, true);
+
+ return 0;
+}
+
+static int ls2k1000_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
int val;
@@ -91,7 +95,7 @@ static int loongson2_2k1000_get_temp(struct thermal_zone_device *tz, int *temp)
return 0;
}
-static void loongson2_2k1000_set_trips_regs(struct loongson2_thermal_data *data,
+static void ls2k1000_thermal_set_trips_regs(struct loongson2_thermal_data *data,
int temp, bool low, bool enable)
{
u32 sel = data->chip_data->thermal_sensor_sel;
@@ -105,18 +109,27 @@ static void loongson2_2k1000_set_trips_regs(struct loongson2_thermal_data *data,
writew(val, data->ctrl_reg + ctrl_reg + sel * 2);
}
-static int loongson2_2k2000_get_temp(struct thermal_zone_device *tz, int *temp)
+static int ls2k1000_thermal_hw_init(struct platform_device *pdev,
+ struct loongson2_thermal_data *data)
+{
+ writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
+ loongson2_thermal_set_trip_temp(data, 0, 0, false);
+
+ return 0;
+}
+
+static int ls2k2000_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
int val;
- val = FIELD_GET(LOONGSON2_THSENS_OUT_16B_MASK, readl(data->temp_reg));
+ val = FIELD_GET(LOONGSON2_THSENS_OUT_16B_MASK, readl(data->extend_reg));
*temp = (val * LS2K2000_TEMP_MUL / LS2K2000_TEMP_DIV - LS2K2000_TEMP_OFFSET) * KILO;
return 0;
}
-static void loongson2_2k2000_set_trips_regs(struct loongson2_thermal_data *data,
+static void ls2k2000_thermal_set_trips_regs(struct loongson2_thermal_data *data,
int temp, bool low, bool enable)
{
unsigned int sel = data->chip_data->thermal_sensor_sel;
@@ -137,42 +150,60 @@ static void loongson2_2k2000_set_trips_regs(struct loongson2_thermal_data *data,
writeb(val, data->ctrl_reg + hi_reg + sel);
}
-static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev)
+static int ls2k2000_thermal_hw_init(struct platform_device *pdev,
+ struct loongson2_thermal_data *data)
{
- struct thermal_zone_device *tzd = dev;
- struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd);
+ /* The temperature output register is separate for Loongson-2K2000 */
+ data->extend_reg = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(data->extend_reg))
+ return PTR_ERR(data->extend_reg);
writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
-
- thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED);
-
- return IRQ_HANDLED;
-}
-
-static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high)
-{
- struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
-
- loongson2_thermal_set(data, low, high, true);
+ loongson2_thermal_set_trip_temp(data, 0, 0, false);
return 0;
}
-static const struct thermal_zone_device_ops loongson2_2k1000_of_thermal_ops = {
- .get_temp = loongson2_2k1000_get_temp,
+static const struct thermal_zone_device_ops ls2k1000_thermal_zone_ops = {
+ .get_temp = ls2k1000_thermal_get_temp,
.set_trips = loongson2_thermal_set_trips,
};
-static const struct thermal_zone_device_ops loongson2_2k2000_of_thermal_ops = {
- .get_temp = loongson2_2k2000_get_temp,
+static const struct thermal_zone_device_ops ls2k2000_thermal_zone_ops = {
+ .get_temp = ls2k2000_thermal_get_temp,
.set_trips = loongson2_thermal_set_trips,
};
+static const struct loongson2_thermal_chip_data ls2k1000_thermal_chip_data = {
+ .thermal_sensor_sel = 0,
+ .thermal_ops = &ls2k1000_thermal_zone_ops,
+ .set_trips_regs = ls2k1000_thermal_set_trips_regs,
+ .hw_init = ls2k1000_thermal_hw_init,
+};
+
+static const struct loongson2_thermal_chip_data ls2k2000_thermal_chip_data = {
+ .thermal_sensor_sel = 0,
+ .thermal_ops = &ls2k2000_thermal_zone_ops,
+ .set_trips_regs = ls2k2000_thermal_set_trips_regs,
+ .hw_init = ls2k2000_thermal_hw_init,
+};
+
+static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev)
+{
+ struct thermal_zone_device *tzd = dev;
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd);
+
+ writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
+
+ thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
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 device *dev = &pdev->dev;
struct thermal_zone_device *tzd;
int ret, irq;
@@ -181,31 +212,22 @@ static int loongson2_thermal_probe(struct platform_device *pdev)
return -ENOMEM;
data->chip_data = device_get_match_data(dev);
+ if (!data->chip_data)
+ return -EINVAL;
data->ctrl_reg = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->ctrl_reg))
return PTR_ERR(data->ctrl_reg);
- /* The temperature output register is separate for Loongson-2K2000 */
- if (data->chip_data->flags & LS2K2000_THSENS_OUT_FLAG) {
- 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;
- }
-
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
- writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG);
-
- loongson2_thermal_set(data, 0, 0, false);
+ ret = data->chip_data->hw_init(pdev, data);
+ if (ret)
+ return ret;
- tzd = devm_thermal_of_zone_register(dev, 0, data, thermal_ops);
+ tzd = devm_thermal_of_zone_register(dev, 0, data, data->chip_data->thermal_ops);
if (IS_ERR(tzd))
return dev_err_probe(dev, PTR_ERR(tzd), "failed to register sensor");
@@ -219,26 +241,14 @@ static int loongson2_thermal_probe(struct platform_device *pdev)
return 0;
}
-static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = {
- .thermal_sensor_sel = 0,
- .flags = 0,
- .set_trips_regs = loongson2_2k1000_set_trips_regs,
-};
-
-static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data = {
- .thermal_sensor_sel = 0,
- .flags = LS2K2000_THSENS_OUT_FLAG,
- .set_trips_regs = loongson2_2k2000_set_trips_regs,
-};
-
static const struct of_device_id of_loongson2_thermal_match[] = {
{
.compatible = "loongson,ls2k1000-thermal",
- .data = &loongson2_thermal_ls2k1000_data,
+ .data = &ls2k1000_thermal_chip_data,
},
{
.compatible = "loongson,ls2k2000-thermal",
- .data = &loongson2_thermal_ls2k2000_data,
+ .data = &ls2k2000_thermal_chip_data,
},
{ /* end */ }
};
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v8 6/7] dt-bindings: thermal: loongson,ls2k-thermal: Add compatible for Loongson-2K0300
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
` (4 preceding siblings ...)
2026-11-03 7:25 ` [PATCH v8 5/7] thermal/drivers/loongson2: Move per-SoC ops and init into chip data Binbin Zhou
@ 2026-11-03 7:25 ` Binbin Zhou
2026-11-03 7:25 ` [PATCH v8 7/7] thermal/drivers/loongson2: Add Loongson-2K0300 support Binbin Zhou
6 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:25 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou, Conor Dooley
Add a new compatible string `loongson,ls2k0300-thermal` for the thermal
sensor found on the Loongson-2K0300 SoC.
The hardware differs from existing SoCs in that it requires a phandle to
a syscon node that provides the CHIP ID register, used as a compensation
source in the temperature calculation.
Update the binding to make `loongson,chipid` required for this new
compatible, and clarify the register region requirements for each
variant.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../thermal/loongson,ls2k-thermal.yaml | 73 ++++++++++++++-----
1 file changed, 55 insertions(+), 18 deletions(-)
diff --git a/Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml b/Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml
index 79e691b08341..d312e27737a2 100644
--- a/Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml
+++ b/Documentation/devicetree/bindings/thermal/loongson,ls2k-thermal.yaml
@@ -10,13 +10,11 @@ maintainers:
- zhanghongchen <zhanghongchen@loongson.cn>
- Yinbo Zhu <zhuyinbo@loongson.cn>
-allOf:
- - $ref: /schemas/thermal/thermal-sensor.yaml#
-
properties:
compatible:
oneOf:
- enum:
+ - loongson,ls2k0300-thermal
- loongson,ls2k1000-thermal
- loongson,ls2k2000-thermal
- items:
@@ -34,28 +32,56 @@ properties:
'#thermal-sensor-cells':
const: 1
+ loongson,chipid:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the syscon with the Loongson-2K0300 CHIP ID.
+
required:
- compatible
- reg
- interrupts
-if:
- properties:
- compatible:
- contains:
- enum:
- - loongson,ls2k2000-thermal
+allOf:
+ - $ref: /schemas/thermal/thermal-sensor.yaml#
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: loongson,ls2k0300-thermal
+ then:
+ properties:
+ reg:
+ items:
+ - description: Thermal base register region
+ required:
+ - loongson,chipid
-then:
- properties:
- reg:
- minItems: 2
- maxItems: 2
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: loongson,ls2k1000-thermal
+ then:
+ properties:
+ loongson,chipid: false
+ reg:
+ items:
+ - description: Thermal base register region
-else:
- properties:
- reg:
- maxItems: 1
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: loongson,ls2k2000-thermal
+ then:
+ properties:
+ loongson,chipid: false
+ reg:
+ items:
+ - description: Thermal base register region
+ - description: Thermal data output register region
unevaluatedProperties: false
@@ -69,3 +95,14 @@ examples:
interrupts = <7 IRQ_TYPE_LEVEL_LOW>;
#thermal-sensor-cells = <1>;
};
+
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ thermal-sensor@16001500 {
+ compatible = "loongson,ls2k0300-thermal";
+ reg = <0x16001500 0x30>;
+ interrupt-parent = <&liointc1>;
+ interrupts = <20 IRQ_TYPE_LEVEL_HIGH>;
+ loongson,chipid = <&chipid>;
+ #thermal-sensor-cells = <1>;
+ };
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v8 7/7] thermal/drivers/loongson2: Add Loongson-2K0300 support
2026-11-03 7:23 [PATCH v8 0/7] thermal/drivers/loongson2: Fix trip programming and add Loongson-2K0300 support Binbin Zhou
` (5 preceding siblings ...)
2026-11-03 7:25 ` [PATCH v8 6/7] dt-bindings: thermal: loongson,ls2k-thermal: Add compatible for Loongson-2K0300 Binbin Zhou
@ 2026-11-03 7:25 ` Binbin Zhou
2026-11-03 7:43 ` sashiko-bot
6 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2026-11-03 7:25 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Yinbo Zhu, zhanghongchen, Rafael J . Wysocki,
Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-pm,
Binbin Zhou
The Loongson-2K0300 sensor provides an 11-bit output and requires
calibration data from the CHIP ID registers. Read the calibration offset
through the `loongson,chipid` syscon phandle, initialize the sensor
configuration register, and calculate the temperature with the
calibrated slope and offset.
Early Loongson-2K0300 fuse revisions may provide unusable calibration
data. If the calibrated formula reports a temperature outside the valid
sensor range, fall back to the old-fuse slope with a zero calibration
offset.
Trip-point programming for Loongson-2K0300 will be added later.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/thermal/loongson2_thermal.c | 144 ++++++++++++++++++++++++++++
1 file changed, 144 insertions(+)
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
index d83aaa588c48..0a9d039c9ab8 100644
--- a/drivers/thermal/loongson2_thermal.c
+++ b/drivers/thermal/loongson2_thermal.c
@@ -8,10 +8,12 @@
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/mfd/syscon.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/regmap.h>
#include <linux/thermal.h>
#include <linux/units.h>
@@ -32,11 +34,41 @@
#define LOONGSON2_THSENS_CTRL_SEL_MASK GENMASK(11, 10)
#define LOONGSON2_THSENS_OUT_8B_MASK GENMASK(7, 0)
+#define LOONGSON2_THSENS_OUT_11B_MASK GENMASK(10, 0)
#define LOONGSON2_THSENS_OUT_16B_MASK GENMASK(15, 0)
#define LOONGSON2_LOWEST_VALID_TEMP (-40000)
#define LOONGSON2_HIGHEST_VALID_TEMP (125000)
+/* Loongson-2K0300 CHIP ID syscon registers */
+#define LS2K0300_SYSCON_CHIP_ID0 0x10
+#define LS2K0300_SYSCON_CHIP_ID1 0x14
+
+/* Loongson-2K0300 thermal sensor registers */
+#define LS2K0300_THSENS_CFG_REG 0x18
+
+#define LS2K0300_THSENS_INIT_MASK 0xff03
+
+#define LS2K0300_EXTERN_ID BIT(4)
+/* Internal ID: 12-bit unsigned compensation value */
+#define LS2K0300_ID0_VAL_MASK GENMASK(31, 20)
+/* External ID: 16-bit signed compensation value */
+#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_TEMP_SLOPE (570) /* calibrated */
+#define LS2K0300_TEMP_SLOPE_OLD (569) /* old fuse */
+#define LS2K0300_TEMP_OFFSET (394700)
+
+/*
+ * Set when the on-chip calibration data is unusable (early LS2K0300
+ * fuse revision). The driver then uses LS2K0300_TEMP_SLOPE_OLD with
+ * calib_offset = 0 instead of the calibrated formula.
+ */
+#define LS2K0300_UNCALIBRATED_FLAG BIT(0)
+
/* Loongson-2K2000-Specific Registers */
#define LS2K2000_THSENS_HI_GATE_HI_REG 0x18
#define LS2K2000_THSENS_HI_GATE_LOW_REG 0x1c
@@ -58,6 +90,9 @@ struct loongson2_thermal_chip_data {
struct loongson2_thermal_data {
void __iomem *ctrl_reg;
void __iomem *extend_reg;
+ struct regmap *regmap_cfg;
+ unsigned int flags;
+ int calib_offset;
const struct loongson2_thermal_chip_data *chip_data;
};
@@ -83,6 +118,101 @@ static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low,
return 0;
}
+static int ls2k0300_thermal_adc_to_temp_mc(struct loongson2_thermal_data *data, int slope)
+{
+ int raw_adc;
+
+ raw_adc = FIELD_GET(LOONGSON2_THSENS_OUT_11B_MASK,
+ readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG));
+
+ return (raw_adc + data->calib_offset) * slope - LS2K0300_TEMP_OFFSET;
+}
+
+static int ls2k0300_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
+ int slope = LS2K0300_TEMP_SLOPE;
+
+ if (data->flags & LS2K0300_UNCALIBRATED_FLAG)
+ slope = LS2K0300_TEMP_SLOPE_OLD;
+
+ *temp = ls2k0300_thermal_adc_to_temp_mc(data, slope);
+
+ return 0;
+}
+
+static void ls2k0300_thermal_detect_old_fuse(struct device *dev,
+ struct loongson2_thermal_data *data)
+{
+ int temp;
+
+ temp = ls2k0300_thermal_adc_to_temp_mc(data, LS2K0300_TEMP_SLOPE);
+
+ if (temp < LOONGSON2_LOWEST_VALID_TEMP ||
+ temp > LOONGSON2_HIGHEST_VALID_TEMP) {
+ dev_warn(dev, "calibrated temperature %d mC out of range [%d, %d], using old fuse formula",
+ temp, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
+ data->flags = LS2K0300_UNCALIBRATED_FLAG;
+ data->calib_offset = 0;
+ }
+}
+
+static int ls2k0300_thermal_read_calib(struct loongson2_thermal_data *data)
+{
+ int calib_data, calib_offset, ret;
+ u32 chip_id0, chip_id1;
+
+ ret = regmap_read(data->regmap_cfg, LS2K0300_SYSCON_CHIP_ID0, &chip_id0);
+ if (ret)
+ return ret;
+
+ if (chip_id0 & LS2K0300_EXTERN_ID) {
+ ret = regmap_read(data->regmap_cfg, LS2K0300_SYSCON_CHIP_ID1, &chip_id1);
+ if (ret)
+ return ret;
+
+ /*
+ * External ID: the signed compensation value is stored in
+ * CHIP_ID1, with bit 15 as the sign and bits 14:0 as the
+ * magnitude.
+ */
+ 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 {
+ /*
+ * Internal ID: the compensation value is unsigned and is
+ * stored in CHIP_ID0[31:20].
+ */
+ calib_offset = FIELD_GET(LS2K0300_ID0_VAL_MASK, chip_id0);
+ }
+
+ data->calib_offset = calib_offset;
+ return 0;
+}
+
+static int ls2k0300_thermal_hw_init(struct platform_device *pdev,
+ struct loongson2_thermal_data *data)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ 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);
+
+ ret = ls2k0300_thermal_read_calib(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to read calibration data");
+
+ writel(LS2K0300_THSENS_INIT_MASK, data->ctrl_reg + LS2K0300_THSENS_CFG_REG);
+
+ ls2k0300_thermal_detect_old_fuse(dev, data);
+
+ return 0;
+}
+
static int ls2k1000_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
struct loongson2_thermal_data *data = thermal_zone_device_priv(tz);
@@ -164,6 +294,10 @@ static int ls2k2000_thermal_hw_init(struct platform_device *pdev,
return 0;
}
+static const struct thermal_zone_device_ops ls2k0300_thermal_zone_ops = {
+ .get_temp = ls2k0300_thermal_get_temp,
+};
+
static const struct thermal_zone_device_ops ls2k1000_thermal_zone_ops = {
.get_temp = ls2k1000_thermal_get_temp,
.set_trips = loongson2_thermal_set_trips,
@@ -174,6 +308,12 @@ static const struct thermal_zone_device_ops ls2k2000_thermal_zone_ops = {
.set_trips = loongson2_thermal_set_trips,
};
+static const struct loongson2_thermal_chip_data ls2k0300_thermal_chip_data = {
+ .thermal_sensor_sel = 0,
+ .thermal_ops = &ls2k0300_thermal_zone_ops,
+ .hw_init = ls2k0300_thermal_hw_init,
+};
+
static const struct loongson2_thermal_chip_data ls2k1000_thermal_chip_data = {
.thermal_sensor_sel = 0,
.thermal_ops = &ls2k1000_thermal_zone_ops,
@@ -242,6 +382,10 @@ static int loongson2_thermal_probe(struct platform_device *pdev)
}
static const struct of_device_id of_loongson2_thermal_match[] = {
+ {
+ .compatible = "loongson,ls2k0300-thermal",
+ .data = &ls2k0300_thermal_chip_data,
+ },
{
.compatible = "loongson,ls2k1000-thermal",
.data = &ls2k1000_thermal_chip_data,
--
2.52.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v8 7/7] thermal/drivers/loongson2: Add Loongson-2K0300 support
2026-11-03 7:25 ` [PATCH v8 7/7] thermal/drivers/loongson2: Add Loongson-2K0300 support Binbin Zhou
@ 2026-11-03 7:43 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-11-03 7:43 UTC (permalink / raw)
To: Binbin Zhou; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] thermal/drivers/loongson2: missing MFD_SYSCON dependency in Kconfig
- [Medium] thermal: loongson2: missing ADC delay in ls2k0300_thermal_hw_init()
--
commit 10dff186e720aacd669123374d9a7f0a467f16da
Author: Binbin Zhou <zhoubinbin@loongson.cn>
thermal/drivers/loongson2: Add Loongson-2K0300 support
This commit adds thermal sensor support for the Loongson-2K0300 SoC, reading
calibration offsets from the CHIP ID syscon registers and falling back to an
old-fuse slope when the calibrated temperature falls outside the valid range.
> diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
> index d83aaa588c485..0a9d039c9ab8c 100644
> --- a/drivers/thermal/loongson2_thermal.c
> +++ b/drivers/thermal/loongson2_thermal.c
[ ... ]
> @@ -83,6 +118,101 @@ static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low,
> return 0;
> }
>
> +static int ls2k0300_thermal_adc_to_temp_mc(struct loongson2_thermal_data *data, int slope)
> +{
> + int raw_adc;
> +
> + raw_adc = FIELD_GET(LOONGSON2_THSENS_OUT_11B_MASK,
> + readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG));
> +
> + return (raw_adc + data->calib_offset) * slope - LS2K0300_TEMP_OFFSET;
> +}
[ ... ]
> +static void ls2k0300_thermal_detect_old_fuse(struct device *dev,
> + struct loongson2_thermal_data *data)
> +{
> + int temp;
> +
> + temp = ls2k0300_thermal_adc_to_temp_mc(data, LS2K0300_TEMP_SLOPE);
> +
> + if (temp < LOONGSON2_LOWEST_VALID_TEMP ||
> + temp > LOONGSON2_HIGHEST_VALID_TEMP) {
> + dev_warn(dev, "calibrated temperature %d mC out of range [%d, %d], using old fuse formula",
> + temp, LOONGSON2_LOWEST_VALID_TEMP, LOONGSON2_HIGHEST_VALID_TEMP);
> + data->flags = LS2K0300_UNCALIBRATED_FLAG;
> + data->calib_offset = 0;
> + }
> +}
[ ... ]
> +static int ls2k0300_thermal_hw_init(struct platform_device *pdev,
> + struct loongson2_thermal_data *data)
> +{
> + struct device *dev = &pdev->dev;
> + int ret;
> +
> + 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);
[Severity: Medium]
In drivers/thermal/loongson2_thermal.c, ls2k0300_thermal_hw_init()
unconditionally calls syscon_regmap_lookup_by_phandle(). When
CONFIG_MFD_SYSCON is not enabled, the stub in include/linux/mfd/syscon.h
returns ERR_PTR(-ENOTSUPP), causing loongson2_thermal_probe() to fail at
runtime on Loongson-2K0300.
Neither LOONGSON2_THERMAL in drivers/thermal/Kconfig nor LOONGARCH in
arch/loongarch/Kconfig selects or depends on MFD_SYSCON:
drivers/thermal/Kconfig:
config LOONGSON2_THERMAL
tristate "Loongson-2 SoC series thermal driver"
depends on LOONGARCH || COMPILE_TEST
depends on OF
Should LOONGSON2_THERMAL select MFD_SYSCON in Kconfig?
> +
> + ret = ls2k0300_thermal_read_calib(data);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to read calibration data");
> +
> + writel(LS2K0300_THSENS_INIT_MASK, data->ctrl_reg + LS2K0300_THSENS_CFG_REG);
> +
> + ls2k0300_thermal_detect_old_fuse(dev, data);
[Severity: Medium]
As reported by Sashiko on v7 [1], in drivers/thermal/loongson2_thermal.c,
ls2k0300_thermal_hw_init() writes LS2K0300_THSENS_INIT_MASK to
LS2K0300_THSENS_CFG_REG and immediately calls
ls2k0300_thermal_detect_old_fuse() without waiting for the sensor ADC to
finish its initial conversion.
If the sensor was not already enabled prior to probe,
ls2k0300_thermal_detect_old_fuse() -> ls2k0300_thermal_adc_to_temp_mc() can
read a reset or stale value of 0 from LOONGSON2_THSENS_OUT_REG. With
raw_adc == 0, any calibration offset of 622 or less produces a temperature
below LOONGSON2_LOWEST_VALID_TEMP (-40000 mC), permanently setting
LS2K0300_UNCALIBRATED_FLAG and clearing data->calib_offset.
Should there be a delay or readiness check after writing
LS2K0300_THSENS_CFG_REG before reading LOONGSON2_THSENS_OUT_REG?
[1] https://lore.kernel.org/r/93002842dcf01ec651a9711075f5967026b95987.1791512067.git.zhoubinbin@loongson.cn
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1793690276.git.zhoubinbin@loongson.cn?part=7
^ permalink raw reply [flat|nested] 10+ messages in thread