1 // SPDX-License-Identifier: GPL-2.0+
3 * Author: zhanghongchen <zhanghongchen@loongson.cn>
4 * Yinbo Zhu <zhuyinbo@loongson.cn>
5 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited
8 #include <linux/interrupt.h>
10 #include <linux/minmax.h>
11 #include <linux/mod_devicetable.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/property.h>
15 #include <linux/thermal.h>
16 #include <linux/units.h>
18 #include "thermal_hwmon.h"
20 #define LOONGSON2_MAX_SENSOR_SEL_NUM 3
22 #define LOONGSON2_THSENS_CTRL_HI_REG 0x0
23 #define LOONGSON2_THSENS_CTRL_LOW_REG 0x8
24 #define LOONGSON2_THSENS_STATUS_REG 0x10
25 #define LOONGSON2_THSENS_OUT_REG 0x14
27 #define LOONGSON2_THSENS_INT_LO BIT(0)
28 #define LOONGSON2_THSENS_INT_HIGH BIT(1)
29 #define LOONGSON2_THSENS_INT_EN (LOONGSON2_THSENS_INT_LO | \
30 LOONGSON2_THSENS_INT_HIGH)
31 #define LOONGSON2_THSENS_OUT_MASK 0xFF
34 * This flag is used to indicate the temperature reading
35 * method of the Loongson-2K2000
37 #define LS2K2000_THSENS_OUT_FLAG BIT(0)
39 struct loongson2_thermal_chip_data
{
40 unsigned int thermal_sensor_sel
;
44 struct loongson2_thermal_data
{
45 void __iomem
*ctrl_reg
;
46 void __iomem
*temp_reg
;
47 const struct loongson2_thermal_chip_data
*chip_data
;
50 static void loongson2_set_ctrl_regs(struct loongson2_thermal_data
*data
,
51 int ctrl_data
, bool low
, bool enable
)
54 int reg_off
= data
->chip_data
->thermal_sensor_sel
* 2;
55 int ctrl_reg
= low
? LOONGSON2_THSENS_CTRL_LOW_REG
: LOONGSON2_THSENS_CTRL_HI_REG
;
57 reg_ctrl
= ctrl_data
+ HECTO
;
58 reg_ctrl
|= enable
? 0x100 : 0;
59 writew(reg_ctrl
, data
->ctrl_reg
+ ctrl_reg
+ reg_off
);
62 static int loongson2_thermal_set(struct loongson2_thermal_data
*data
,
63 int low
, int high
, bool enable
)
65 /* Set low temperature threshold */
66 loongson2_set_ctrl_regs(data
, clamp(-40, low
, high
), true, enable
);
68 /* Set high temperature threshold */
69 loongson2_set_ctrl_regs(data
, clamp(125, low
, high
), false, enable
);
74 static int loongson2_2k1000_get_temp(struct thermal_zone_device
*tz
, int *temp
)
77 struct loongson2_thermal_data
*data
= thermal_zone_device_priv(tz
);
79 val
= readl(data
->ctrl_reg
+ LOONGSON2_THSENS_OUT_REG
);
80 *temp
= ((val
& LOONGSON2_THSENS_OUT_MASK
) - HECTO
) * KILO
;
85 static int loongson2_2k2000_get_temp(struct thermal_zone_device
*tz
, int *temp
)
88 struct loongson2_thermal_data
*data
= thermal_zone_device_priv(tz
);
90 val
= readl(data
->temp_reg
);
91 *temp
= ((val
& 0xffff) * 820 / 0x4000 - 311) * KILO
;
96 static irqreturn_t
loongson2_thermal_irq_thread(int irq
, void *dev
)
98 struct thermal_zone_device
*tzd
= dev
;
99 struct loongson2_thermal_data
*data
= thermal_zone_device_priv(tzd
);
101 writeb(LOONGSON2_THSENS_INT_EN
, data
->ctrl_reg
+ LOONGSON2_THSENS_STATUS_REG
);
103 thermal_zone_device_update(tzd
, THERMAL_EVENT_UNSPECIFIED
);
108 static int loongson2_thermal_set_trips(struct thermal_zone_device
*tz
, int low
, int high
)
110 struct loongson2_thermal_data
*data
= thermal_zone_device_priv(tz
);
112 return loongson2_thermal_set(data
, low
/MILLI
, high
/MILLI
, true);
115 static struct thermal_zone_device_ops loongson2_of_thermal_ops
= {
116 .get_temp
= loongson2_2k1000_get_temp
,
117 .set_trips
= loongson2_thermal_set_trips
,
120 static int loongson2_thermal_probe(struct platform_device
*pdev
)
122 struct device
*dev
= &pdev
->dev
;
123 struct loongson2_thermal_data
*data
;
124 struct thermal_zone_device
*tzd
;
127 data
= devm_kzalloc(dev
, sizeof(*data
), GFP_KERNEL
);
131 data
->chip_data
= device_get_match_data(dev
);
133 data
->ctrl_reg
= devm_platform_ioremap_resource(pdev
, 0);
134 if (IS_ERR(data
->ctrl_reg
))
135 return PTR_ERR(data
->ctrl_reg
);
137 /* The temperature output register is separate for Loongson-2K2000 */
138 if (data
->chip_data
->flags
& LS2K2000_THSENS_OUT_FLAG
) {
139 data
->temp_reg
= devm_platform_ioremap_resource(pdev
, 1);
140 if (IS_ERR(data
->temp_reg
))
141 return PTR_ERR(data
->temp_reg
);
143 loongson2_of_thermal_ops
.get_temp
= loongson2_2k2000_get_temp
;
146 irq
= platform_get_irq(pdev
, 0);
150 writeb(LOONGSON2_THSENS_INT_EN
, data
->ctrl_reg
+ LOONGSON2_THSENS_STATUS_REG
);
152 loongson2_thermal_set(data
, 0, 0, false);
154 for (i
= 0; i
<= LOONGSON2_MAX_SENSOR_SEL_NUM
; i
++) {
155 tzd
= devm_thermal_of_zone_register(dev
, i
, data
,
156 &loongson2_of_thermal_ops
);
161 if (PTR_ERR(tzd
) != -ENODEV
)
164 return dev_err_probe(dev
, PTR_ERR(tzd
), "failed to register");
167 ret
= devm_request_threaded_irq(dev
, irq
, NULL
, loongson2_thermal_irq_thread
,
168 IRQF_ONESHOT
, "loongson2_thermal", tzd
);
170 return dev_err_probe(dev
, ret
, "failed to request alarm irq\n");
172 devm_thermal_add_hwmon_sysfs(dev
, tzd
);
177 static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data
= {
178 .thermal_sensor_sel
= 0,
182 static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data
= {
183 .thermal_sensor_sel
= 0,
184 .flags
= LS2K2000_THSENS_OUT_FLAG
,
187 static const struct of_device_id of_loongson2_thermal_match
[] = {
189 .compatible
= "loongson,ls2k1000-thermal",
190 .data
= &loongson2_thermal_ls2k1000_data
,
193 .compatible
= "loongson,ls2k2000-thermal",
194 .data
= &loongson2_thermal_ls2k2000_data
,
198 MODULE_DEVICE_TABLE(of
, of_loongson2_thermal_match
);
200 static struct platform_driver loongson2_thermal_driver
= {
202 .name
= "loongson2_thermal",
203 .of_match_table
= of_loongson2_thermal_match
,
205 .probe
= loongson2_thermal_probe
,
207 module_platform_driver(loongson2_thermal_driver
);
209 MODULE_DESCRIPTION("Loongson2 thermal driver");
210 MODULE_AUTHOR("Loongson Technology Corporation Limited");
211 MODULE_LICENSE("GPL");