[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
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.