diff mbox series

[v8,1/2] thermal: loongson-2: add thermal management support

Message ID 20221028013532.10361-1-zhuyinbo@loongson.cn (mailing list archive)
State Not Applicable, archived
Headers show
Series [v8,1/2] thermal: loongson-2: add thermal management support | expand

Commit Message

Yinbo Zhu Oct. 28, 2022, 1:35 a.m. UTC
This patch adds the support for Loongson-2 thermal sensor controller,
which can support maximum 4 sensors.

It's based on thermal of framework:
 - Trip points defined in device tree.
 - Cpufreq as cooling device registered in Loongson-2 cpufreq driver.
 - Pwm fan as cooling device registered in hwmon pwm-fan driver.

Signed-off-by: zhanghongchen <zhanghongchen@loongson.cn>
Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
---
Change in v8:
		1. Replace string loongson2/Loongson2/LOONGSON2 with loongson-2/
                   Loongson-2/LOONGSON-2 in Kconfig and commit log and MAINTAINERS
		   files.

 MAINTAINERS                         |   7 +
 drivers/thermal/Kconfig             |  10 ++
 drivers/thermal/Makefile            |   1 +
 drivers/thermal/loongson2_thermal.c | 264 ++++++++++++++++++++++++++++
 4 files changed, 282 insertions(+)
 create mode 100644 drivers/thermal/loongson2_thermal.c

Comments

Yinbo Zhu Oct. 28, 2022, 6:57 a.m. UTC | #1
在 2022/10/28 下午1:55, kernel test robot 写道:
> Hi Yinbo,
> 
> I love your patch! Perhaps something to improve:
> 
> [auto build test WARNING on rafael-pm/thermal]
> [also build test WARNING on linus/master v6.1-rc2 next-20221027]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
> 
> url:    https://github.com/intel-lab-lkp/linux/commits/Yinbo-Zhu/thermal-loongson-2-add-thermal-management-support/20221028-093603
> base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal
> patch link:    https://lore.kernel.org/r/20221028013532.10361-1-zhuyinbo%40loongson.cn
> patch subject: [PATCH v8 1/2] thermal: loongson-2: add thermal management support
> config: sparc-allyesconfig
> compiler: sparc64-linux-gcc (GCC) 12.1.0
> reproduce (this is a W=1 build):
>          wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
>          chmod +x ~/bin/make.cross
>          # https://github.com/intel-lab-lkp/linux/commit/d94e01f4aefe4c0ab05babe101f0a0ce53165456
>          git remote add linux-review https://github.com/intel-lab-lkp/linux
>          git fetch --no-tags linux-review Yinbo-Zhu/thermal-loongson-2-add-thermal-management-support/20221028-093603
>          git checkout d94e01f4aefe4c0ab05babe101f0a0ce53165456
>          # save the config file
>          mkdir build_dir && cp config build_dir/.config
>          COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc SHELL=/bin/bash drivers/thermal/
> 
> If you fix the issue, kindly add following tag where applicable
> | Reported-by: kernel test robot <lkp@intel.com>
> 
> All warnings (new ones prefixed by >>):
> 
>     drivers/thermal/loongson2_thermal.c:136:21: error: variable 'loongson2_of_thermal_ops' has initializer but incomplete type
>       136 | static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~
>     drivers/thermal/loongson2_thermal.c:137:10: error: 'const struct thermal_zone_of_device_ops' has no member named 'get_temp'
>       137 |         .get_temp = loongson2_thermal_get_temp,
>           |          ^~~~~~~~
>>> drivers/thermal/loongson2_thermal.c:137:21: warning: excess elements in struct initializer
>       137 |         .get_temp = loongson2_thermal_get_temp,
>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~
>     drivers/thermal/loongson2_thermal.c:137:21: note: (near initialization for 'loongson2_of_thermal_ops')
>     drivers/thermal/loongson2_thermal.c:138:10: error: 'const struct thermal_zone_of_device_ops' has no member named 'set_trips'
>       138 |         .set_trips = loongson2_thermal_set_trips,
>           |          ^~~~~~~~~
>     drivers/thermal/loongson2_thermal.c:138:22: warning: excess elements in struct initializer
>       138 |         .set_trips = loongson2_thermal_set_trips,
>           |                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>     drivers/thermal/loongson2_thermal.c:138:22: note: (near initialization for 'loongson2_of_thermal_ops')
>     drivers/thermal/loongson2_thermal.c: In function 'loongson2_thermal_probe':
>     drivers/thermal/loongson2_thermal.c:177:21: error: implicit declaration of function 'devm_thermal_zone_of_sensor_register'; did you mean 'devm_thermal_of_zone_register'? [-Werror=implicit-function-declaration]
>       177 |         data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>           |                     devm_thermal_of_zone_register
>>> drivers/thermal/loongson2_thermal.c:177:19: warning: assignment to 'struct thermal_zone_device *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
>       177 |         data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>           |                   ^
>     drivers/thermal/loongson2_thermal.c: At top level:
>     drivers/thermal/loongson2_thermal.c:136:48: error: storage size of 'loongson2_of_thermal_ops' isn't known
>       136 | static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>           |                                                ^~~~~~~~~~~~~~~~~~~~~~~~
>     cc1: some warnings being treated as errors
> 
> 
> vim +137 drivers/thermal/loongson2_thermal.c
> 
>     135	
>     136	static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>   > 137		.get_temp = loongson2_thermal_get_temp,
>     138		.set_trips = loongson2_thermal_set_trips,
>     139	};
>     140	
>     141	static int loongson2_thermal_probe(struct platform_device *pdev)
>     142	{
>     143		struct device *dev = &pdev->dev;
>     144		struct resource *res;
>     145		struct loongson2_thermal_data *data;
>     146		int ret;
>     147	
>     148		data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>     149		if (!data)
>     150			return -ENOMEM;
>     151	
>     152		data->pdev = pdev;
>     153		platform_set_drvdata(pdev, data);
>     154	
>     155		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>     156		data->regs = devm_ioremap(dev, res->start, resource_size(res));
>     157		if (IS_ERR(data->regs))
>     158			return PTR_ERR(data->regs);
>     159	
>     160		/* get irq */
>     161		data->irq = platform_get_irq(pdev, 0);
>     162		if (data->irq < 0)
>     163			return data->irq;
>     164	
>     165		/* get id */
>     166		data->id = loongson2_thermal_get_sensor_id();
>     167		if (data->id > LOONGSON2_SOC_MAX_SENSOR_NUM - 1 || data->id < 0) {
>     168			dev_err(dev, "sensor id error,must be in <0 ~ %d>\n",
>     169					LOONGSON2_SOC_MAX_SENSOR_NUM - 1);
>     170			return -EINVAL;
>     171		}
>     172	
>     173		writeb(0xff, data->regs + LOONGSON2_TSENSOR_STATUS);
>     174	
>     175		loongson2_thermal_set(data, 0, 0, false);
>     176	
>   > 177		data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>     178								   data->id, data,
>     179								   &loongson2_of_thermal_ops);
>     180		if (IS_ERR(data->tzd)) {
>     181			ret = PTR_ERR(data->tzd);
>     182			data->tzd = NULL;
>     183			dev_err(&pdev->dev, "failed to register %d\n", ret);
>     184			return ret;
>     185		}
>     186	
>     187		ret = devm_request_threaded_irq(dev, data->irq,
>     188				loongson2_thermal_alarm_irq, loongson2_thermal_irq_thread,
>     189				IRQF_ONESHOT, "loongson2_thermal", data);
>     190		if (ret < 0) {
>     191			dev_err(dev, "failed to request alarm irq: %d\n", ret);
>     192			return ret;
>     193		}
>     194	
>     195		/*
>     196		 * Thermal_zone doesn't enable hwmon as default,
>     197		 * enable it here
>     198		 */
>     199		data->tzd->tzp->no_hwmon = false;
>     200		ret = thermal_add_hwmon_sysfs(data->tzd);
>     201		if (ret) {
>     202			dev_err(dev, "failed to add hwmon sysfs interface %d\n", ret);
>     203			return ret;
>     204		}
>     205	
>     206		return 0;
>     207	}Hi

I use W=1 to compile it and doesn't reproduce that warning, and I added
the change that don't touch .c code. I think it shoud be error report.

Tks
Yinbo
>     208	
>
Krzysztof Kozlowski Oct. 28, 2022, 11:44 a.m. UTC | #2
On 28/10/2022 02:57, Yinbo Zhu wrote:
> 
> 
> 在 2022/10/28 下午1:55, kernel test robot 写道:
>> Hi Yinbo,
>>
>> I love your patch! Perhaps something to improve:
>>
>> [auto build test WARNING on rafael-pm/thermal]
>> [also build test WARNING on linus/master v6.1-rc2 next-20221027]
>> [If your patch is applied to the wrong git tree, kindly drop us a note.
>> And when submitting patch, we suggest to use '--base' as documented in
>> https://git-scm.com/docs/git-format-patch#_base_tree_information]
>>
>> url:    https://github.com/intel-lab-lkp/linux/commits/Yinbo-Zhu/thermal-loongson-2-add-thermal-management-support/20221028-093603
>> base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git thermal
>> patch link:    https://lore.kernel.org/r/20221028013532.10361-1-zhuyinbo%40loongson.cn
>> patch subject: [PATCH v8 1/2] thermal: loongson-2: add thermal management support
>> config: sparc-allyesconfig
>> compiler: sparc64-linux-gcc (GCC) 12.1.0
>> reproduce (this is a W=1 build):
>>          wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
>>          chmod +x ~/bin/make.cross
>>          # https://github.com/intel-lab-lkp/linux/commit/d94e01f4aefe4c0ab05babe101f0a0ce53165456
>>          git remote add linux-review https://github.com/intel-lab-lkp/linux
>>          git fetch --no-tags linux-review Yinbo-Zhu/thermal-loongson-2-add-thermal-management-support/20221028-093603
>>          git checkout d94e01f4aefe4c0ab05babe101f0a0ce53165456
>>          # save the config file
>>          mkdir build_dir && cp config build_dir/.config
>>          COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc SHELL=/bin/bash drivers/thermal/
>>
>> If you fix the issue, kindly add following tag where applicable
>> | Reported-by: kernel test robot <lkp@intel.com>
>>
>> All warnings (new ones prefixed by >>):
>>
>>     drivers/thermal/loongson2_thermal.c:136:21: error: variable 'loongson2_of_thermal_ops' has initializer but incomplete type
>>       136 | static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~
>>     drivers/thermal/loongson2_thermal.c:137:10: error: 'const struct thermal_zone_of_device_ops' has no member named 'get_temp'
>>       137 |         .get_temp = loongson2_thermal_get_temp,
>>           |          ^~~~~~~~
>>>> drivers/thermal/loongson2_thermal.c:137:21: warning: excess elements in struct initializer
>>       137 |         .get_temp = loongson2_thermal_get_temp,
>>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~
>>     drivers/thermal/loongson2_thermal.c:137:21: note: (near initialization for 'loongson2_of_thermal_ops')
>>     drivers/thermal/loongson2_thermal.c:138:10: error: 'const struct thermal_zone_of_device_ops' has no member named 'set_trips'
>>       138 |         .set_trips = loongson2_thermal_set_trips,
>>           |          ^~~~~~~~~
>>     drivers/thermal/loongson2_thermal.c:138:22: warning: excess elements in struct initializer
>>       138 |         .set_trips = loongson2_thermal_set_trips,
>>           |                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~
>>     drivers/thermal/loongson2_thermal.c:138:22: note: (near initialization for 'loongson2_of_thermal_ops')
>>     drivers/thermal/loongson2_thermal.c: In function 'loongson2_thermal_probe':
>>     drivers/thermal/loongson2_thermal.c:177:21: error: implicit declaration of function 'devm_thermal_zone_of_sensor_register'; did you mean 'devm_thermal_of_zone_register'? [-Werror=implicit-function-declaration]
>>       177 |         data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>>           |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>           |                     devm_thermal_of_zone_register
>>>> drivers/thermal/loongson2_thermal.c:177:19: warning: assignment to 'struct thermal_zone_device *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
>>       177 |         data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>>           |                   ^
>>     drivers/thermal/loongson2_thermal.c: At top level:
>>     drivers/thermal/loongson2_thermal.c:136:48: error: storage size of 'loongson2_of_thermal_ops' isn't known
>>       136 | static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>>           |                                                ^~~~~~~~~~~~~~~~~~~~~~~~
>>     cc1: some warnings being treated as errors
>>
>>
>> vim +137 drivers/thermal/loongson2_thermal.c
>>
>>     135	
>>     136	static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
>>   > 137		.get_temp = loongson2_thermal_get_temp,
>>     138		.set_trips = loongson2_thermal_set_trips,
>>     139	};
>>     140	
>>     141	static int loongson2_thermal_probe(struct platform_device *pdev)
>>     142	{
>>     143		struct device *dev = &pdev->dev;
>>     144		struct resource *res;
>>     145		struct loongson2_thermal_data *data;
>>     146		int ret;
>>     147	
>>     148		data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
>>     149		if (!data)
>>     150			return -ENOMEM;
>>     151	
>>     152		data->pdev = pdev;
>>     153		platform_set_drvdata(pdev, data);
>>     154	
>>     155		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>     156		data->regs = devm_ioremap(dev, res->start, resource_size(res));
>>     157		if (IS_ERR(data->regs))
>>     158			return PTR_ERR(data->regs);
>>     159	
>>     160		/* get irq */
>>     161		data->irq = platform_get_irq(pdev, 0);
>>     162		if (data->irq < 0)
>>     163			return data->irq;
>>     164	
>>     165		/* get id */
>>     166		data->id = loongson2_thermal_get_sensor_id();
>>     167		if (data->id > LOONGSON2_SOC_MAX_SENSOR_NUM - 1 || data->id < 0) {
>>     168			dev_err(dev, "sensor id error,must be in <0 ~ %d>\n",
>>     169					LOONGSON2_SOC_MAX_SENSOR_NUM - 1);
>>     170			return -EINVAL;
>>     171		}
>>     172	
>>     173		writeb(0xff, data->regs + LOONGSON2_TSENSOR_STATUS);
>>     174	
>>     175		loongson2_thermal_set(data, 0, 0, false);
>>     176	
>>   > 177		data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
>>     178								   data->id, data,
>>     179								   &loongson2_of_thermal_ops);
>>     180		if (IS_ERR(data->tzd)) {
>>     181			ret = PTR_ERR(data->tzd);
>>     182			data->tzd = NULL;
>>     183			dev_err(&pdev->dev, "failed to register %d\n", ret);
>>     184			return ret;
>>     185		}
>>     186	
>>     187		ret = devm_request_threaded_irq(dev, data->irq,
>>     188				loongson2_thermal_alarm_irq, loongson2_thermal_irq_thread,
>>     189				IRQF_ONESHOT, "loongson2_thermal", data);
>>     190		if (ret < 0) {
>>     191			dev_err(dev, "failed to request alarm irq: %d\n", ret);
>>     192			return ret;
>>     193		}
>>     194	
>>     195		/*
>>     196		 * Thermal_zone doesn't enable hwmon as default,
>>     197		 * enable it here
>>     198		 */
>>     199		data->tzd->tzp->no_hwmon = false;
>>     200		ret = thermal_add_hwmon_sysfs(data->tzd);
>>     201		if (ret) {
>>     202			dev_err(dev, "failed to add hwmon sysfs interface %d\n", ret);
>>     203			return ret;
>>     204		}
>>     205	
>>     206		return 0;
>>     207	}Hi
> 
> I use W=1 to compile it and doesn't reproduce that warning, and I added
> the change that don't touch .c code. I think it shoud be error report.

More likely is your code is not correct.

Best regards,
Krzysztof
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 589517372408..cb6a59213006 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11899,6 +11899,13 @@  F:	drivers/*/*loongarch*
 F:	Documentation/loongarch/
 F:	Documentation/translations/zh_CN/loongarch/
 
+LOONGSON-2 SOC SERIES THERMAL DRIVER
+M:	zhanghongchen <zhanghongchen@loongson.cn>
+M:	Yinbo Zhu <zhuyinbo@loongson.cn>
+L:	linux-pm@vger.kernel.org
+S:	Maintained
+F:	drivers/thermal/loongson2_thermal.c
+
 LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
 M:	Sathya Prakash <sathya.prakash@broadcom.com>
 M:	Sreekanth Reddy <sreekanth.reddy@broadcom.com>
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index e052dae614eb..734ba7238d96 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -504,4 +504,14 @@  config KHADAS_MCU_FAN_THERMAL
 	  If you say yes here you get support for the FAN controlled
 	  by the Microcontroller found on the Khadas VIM boards.
 
+config LOONGSON2_THERMAL
+	tristate "Loongson-2 SoC series thermal driver"
+	depends on OF
+	default y
+	help
+	  Support for Thermal driver found on Loongson-2 SoC series platforms.
+	  It supports one critical trip point and one passive trip point. The
+	  cpufreq and the pwm fan is used as the cooling device to throttle
+	  CPUs when the passive trip is crossed.
+
 endif
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index def8e1a0399c..e99f839126fa 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -61,3 +61,4 @@  obj-$(CONFIG_UNIPHIER_THERMAL)	+= uniphier_thermal.o
 obj-$(CONFIG_AMLOGIC_THERMAL)     += amlogic_thermal.o
 obj-$(CONFIG_SPRD_THERMAL)	+= sprd_thermal.o
 obj-$(CONFIG_KHADAS_MCU_FAN_THERMAL)	+= khadas_mcu_fan.o
+obj-$(CONFIG_LOONGSON2_THERMAL)	+= loongson2_thermal.o
diff --git a/drivers/thermal/loongson2_thermal.c b/drivers/thermal/loongson2_thermal.c
new file mode 100644
index 000000000000..082f9dd0b2a2
--- /dev/null
+++ b/drivers/thermal/loongson2_thermal.c
@@ -0,0 +1,264 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: zhanghongchen <zhanghongchen@loongson.cn>
+ *         Yinbo Zhu <zhuyinbo@loongson.cn>
+ * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
+ */
+
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include "thermal_hwmon.h"
+
+#define LOONGSON2_SOC_MAX_SENSOR_NUM			4
+
+#define LOONGSON2_TSENSOR_CTRL_HI			0x0
+#define LOONGSON2_TSENSOR_CTRL_LO			0x8
+#define LOONGSON2_TSENSOR_STATUS			0x10
+#define LOONGSON2_TSENSOR_OUT				0x14
+
+struct loongson2_thermal_data {
+	struct thermal_zone_device *tzd;
+	int irq;
+	int id;
+	void __iomem *regs;
+	struct platform_device *pdev;
+	u16 ctrl_low_val;
+	u16 ctrl_hi_val;
+};
+
+static int loongson2_thermal_set(struct loongson2_thermal_data *data,
+					int low, int high, bool enable)
+{
+	u64 reg_ctrl = 0;
+	int reg_off = data->id * 2;
+
+	if (low > high)
+		return -EINVAL;
+
+	low = low < -100 ? -100 : low;
+	high = high > 155 ? 155 : high;
+
+	low += 100;
+	high += 100;
+
+	reg_ctrl |= low;
+	reg_ctrl |= enable ? 0x100 : 0;
+	writew(reg_ctrl, data->regs + LOONGSON2_TSENSOR_CTRL_LO + reg_off);
+
+	reg_ctrl = 0;
+	reg_ctrl |= high;
+	reg_ctrl |= enable ? 0x100 : 0;
+	writew(reg_ctrl, data->regs + LOONGSON2_TSENSOR_CTRL_HI + reg_off);
+
+	return 0;
+}
+
+static int loongson2_thermal_get_temp(void *__data, int *temp)
+{
+	struct loongson2_thermal_data *data = __data;
+	u32 reg_val;
+
+	reg_val = readl(data->regs + LOONGSON2_TSENSOR_OUT);
+	*temp = ((reg_val & 0xff) - 100) * 1000;
+
+	return 0;
+}
+
+static int loongson2_thermal_get_sensor_id(void)
+{
+	int ret, id;
+	struct of_phandle_args sensor_specs;
+	struct device_node *np, *sensor_np;
+
+	np = of_find_node_by_name(NULL, "thermal-zones");
+	if (!np)
+		return -ENODEV;
+
+	sensor_np = of_get_next_child(np, NULL);
+	ret = of_parse_phandle_with_args(sensor_np, "thermal-sensors",
+			"#thermal-sensor-cells",
+			0, &sensor_specs);
+	if (ret) {
+		of_node_put(np);
+		of_node_put(sensor_np);
+		return ret;
+	}
+
+	if (sensor_specs.args_count >= 1) {
+		id = sensor_specs.args[0];
+		WARN(sensor_specs.args_count > 1,
+				"%s: too many cells in sensor specifier %d\n",
+				sensor_specs.np->name, sensor_specs.args_count);
+	} else {
+		id = 0;
+	}
+
+	of_node_put(np);
+	of_node_put(sensor_np);
+
+	return id;
+}
+
+static irqreturn_t loongson2_thermal_alarm_irq(int irq, void *dev)
+{
+	struct loongson2_thermal_data *data = dev;
+
+	/* clear interrupt */
+	writeb(0x3, data->regs + LOONGSON2_TSENSOR_STATUS);
+
+	disable_irq_nosync(irq);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev)
+{
+	struct loongson2_thermal_data *data = dev;
+
+	thermal_zone_device_update(data->tzd,
+				   THERMAL_EVENT_UNSPECIFIED);
+	enable_irq(data->irq);
+
+	return IRQ_HANDLED;
+}
+
+static int loongson2_thermal_set_trips(void *data, int low, int high)
+{
+	return loongson2_thermal_set(data, low/1000, high/1000, true);
+}
+
+static const struct thermal_zone_of_device_ops loongson2_of_thermal_ops = {
+	.get_temp = loongson2_thermal_get_temp,
+	.set_trips = loongson2_thermal_set_trips,
+};
+
+static int loongson2_thermal_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct loongson2_thermal_data *data;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->pdev = pdev;
+	platform_set_drvdata(pdev, data);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->regs = devm_ioremap(dev, res->start, resource_size(res));
+	if (IS_ERR(data->regs))
+		return PTR_ERR(data->regs);
+
+	/* get irq */
+	data->irq = platform_get_irq(pdev, 0);
+	if (data->irq < 0)
+		return data->irq;
+
+	/* get id */
+	data->id = loongson2_thermal_get_sensor_id();
+	if (data->id > LOONGSON2_SOC_MAX_SENSOR_NUM - 1 || data->id < 0) {
+		dev_err(dev, "sensor id error,must be in <0 ~ %d>\n",
+				LOONGSON2_SOC_MAX_SENSOR_NUM - 1);
+		return -EINVAL;
+	}
+
+	writeb(0xff, data->regs + LOONGSON2_TSENSOR_STATUS);
+
+	loongson2_thermal_set(data, 0, 0, false);
+
+	data->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev,
+							   data->id, data,
+							   &loongson2_of_thermal_ops);
+	if (IS_ERR(data->tzd)) {
+		ret = PTR_ERR(data->tzd);
+		data->tzd = NULL;
+		dev_err(&pdev->dev, "failed to register %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_request_threaded_irq(dev, data->irq,
+			loongson2_thermal_alarm_irq, loongson2_thermal_irq_thread,
+			IRQF_ONESHOT, "loongson2_thermal", data);
+	if (ret < 0) {
+		dev_err(dev, "failed to request alarm irq: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Thermal_zone doesn't enable hwmon as default,
+	 * enable it here
+	 */
+	data->tzd->tzp->no_hwmon = false;
+	ret = thermal_add_hwmon_sysfs(data->tzd);
+	if (ret) {
+		dev_err(dev, "failed to add hwmon sysfs interface %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int loongson2_thermal_remove(struct platform_device *pdev)
+{
+	struct loongson2_thermal_data *data = platform_get_drvdata(pdev);
+	int reg_off = data->id * 2;
+
+	/* disable interrupt */
+	writew(0, data->regs + LOONGSON2_TSENSOR_CTRL_LO + reg_off);
+	writew(0, data->regs + LOONGSON2_TSENSOR_CTRL_HI + reg_off);
+
+	return 0;
+}
+
+static const struct of_device_id of_loongson2_thermal_match[] = {
+	{ .compatible = "loongson,ls2k-thermal",},
+	{ /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_loongson2_thermal_match);
+
+static int __maybe_unused loongson2_thermal_suspend(struct device *dev)
+{
+	struct loongson2_thermal_data *data = dev_get_drvdata(dev);
+	int reg_off = data->id * 2;
+
+	data->ctrl_low_val = readw(data->regs + LOONGSON2_TSENSOR_CTRL_LO + reg_off);
+	data->ctrl_hi_val = readw(data->regs + LOONGSON2_TSENSOR_CTRL_HI + reg_off);
+
+	writew(0, data->regs + LOONGSON2_TSENSOR_CTRL_LO + reg_off);
+	writew(0, data->regs + LOONGSON2_TSENSOR_CTRL_HI + reg_off);
+
+	return 0;
+}
+
+static int __maybe_unused loongson2_thermal_resume(struct device *dev)
+{
+	struct loongson2_thermal_data *data = dev_get_drvdata(dev);
+	int reg_off = data->id * 2;
+
+	writew(data->ctrl_low_val, data->regs + LOONGSON2_TSENSOR_CTRL_LO + reg_off);
+	writew(data->ctrl_hi_val, data->regs + LOONGSON2_TSENSOR_CTRL_HI + reg_off);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(loongson2_thermal_pm_ops,
+			 loongson2_thermal_suspend, loongson2_thermal_resume);
+
+static struct platform_driver loongson2_thermal_driver = {
+	.driver = {
+		.name		= "loongson2_thermal",
+		.pm = &loongson2_thermal_pm_ops,
+		.of_match_table = of_loongson2_thermal_match,
+	},
+	.probe	= loongson2_thermal_probe,
+	.remove	= loongson2_thermal_remove,
+};
+module_platform_driver(loongson2_thermal_driver);