1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Author: zhanghongchen <zhanghongchen@loongson.cn> |
4 | * Yinbo Zhu <zhuyinbo@loongson.cn> |
5 | * Copyright (C) 2022-2023 Loongson Technology Corporation Limited |
6 | */ |
7 | |
8 | #include <linux/interrupt.h> |
9 | #include <linux/io.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> |
17 | #include "thermal_hwmon.h" |
18 | |
19 | #define LOONGSON2_MAX_SENSOR_SEL_NUM 3 |
20 | |
21 | #define LOONGSON2_THSENS_CTRL_HI_REG 0x0 |
22 | #define LOONGSON2_THSENS_CTRL_LOW_REG 0x8 |
23 | #define LOONGSON2_THSENS_STATUS_REG 0x10 |
24 | #define LOONGSON2_THSENS_OUT_REG 0x14 |
25 | |
26 | #define LOONGSON2_THSENS_INT_LO BIT(0) |
27 | #define LOONGSON2_THSENS_INT_HIGH BIT(1) |
28 | #define LOONGSON2_THSENS_OUT_MASK 0xFF |
29 | |
30 | struct loongson2_thermal_chip_data { |
31 | unsigned int thermal_sensor_sel; |
32 | }; |
33 | |
34 | struct loongson2_thermal_data { |
35 | void __iomem *regs; |
36 | const struct loongson2_thermal_chip_data *chip_data; |
37 | }; |
38 | |
39 | static int loongson2_thermal_set(struct loongson2_thermal_data *data, |
40 | int low, int high, bool enable) |
41 | { |
42 | u64 reg_ctrl = 0; |
43 | int reg_off = data->chip_data->thermal_sensor_sel * 2; |
44 | |
45 | low = clamp(-40, low, high); |
46 | high = clamp(125, low, high); |
47 | |
48 | low += HECTO; |
49 | high += HECTO; |
50 | |
51 | reg_ctrl = low; |
52 | reg_ctrl |= enable ? 0x100 : 0; |
53 | writew(val: reg_ctrl, addr: data->regs + LOONGSON2_THSENS_CTRL_LOW_REG + reg_off); |
54 | |
55 | reg_ctrl = high; |
56 | reg_ctrl |= enable ? 0x100 : 0; |
57 | writew(val: reg_ctrl, addr: data->regs + LOONGSON2_THSENS_CTRL_HI_REG + reg_off); |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | static int loongson2_thermal_get_temp(struct thermal_zone_device *tz, int *temp) |
63 | { |
64 | u32 reg_val; |
65 | struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd: tz); |
66 | |
67 | reg_val = readl(addr: data->regs + LOONGSON2_THSENS_OUT_REG); |
68 | *temp = ((reg_val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO; |
69 | |
70 | return 0; |
71 | } |
72 | |
73 | static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev) |
74 | { |
75 | struct thermal_zone_device *tzd = dev; |
76 | struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd); |
77 | |
78 | writeb(LOONGSON2_THSENS_INT_LO | LOONGSON2_THSENS_INT_HIGH, addr: data->regs + |
79 | LOONGSON2_THSENS_STATUS_REG); |
80 | |
81 | thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED); |
82 | |
83 | return IRQ_HANDLED; |
84 | } |
85 | |
86 | static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) |
87 | { |
88 | struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd: tz); |
89 | |
90 | return loongson2_thermal_set(data, low: low/MILLI, high: high/MILLI, enable: true); |
91 | } |
92 | |
93 | static const struct thermal_zone_device_ops loongson2_of_thermal_ops = { |
94 | .get_temp = loongson2_thermal_get_temp, |
95 | .set_trips = loongson2_thermal_set_trips, |
96 | }; |
97 | |
98 | static int loongson2_thermal_probe(struct platform_device *pdev) |
99 | { |
100 | struct device *dev = &pdev->dev; |
101 | struct loongson2_thermal_data *data; |
102 | struct thermal_zone_device *tzd; |
103 | int ret, irq, i; |
104 | |
105 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
106 | if (!data) |
107 | return -ENOMEM; |
108 | |
109 | data->chip_data = device_get_match_data(dev); |
110 | |
111 | data->regs = devm_platform_ioremap_resource(pdev, index: 0); |
112 | if (IS_ERR(ptr: data->regs)) |
113 | return PTR_ERR(ptr: data->regs); |
114 | |
115 | irq = platform_get_irq(pdev, 0); |
116 | if (irq < 0) |
117 | return irq; |
118 | |
119 | writeb(LOONGSON2_THSENS_INT_LO | LOONGSON2_THSENS_INT_HIGH, addr: data->regs + |
120 | LOONGSON2_THSENS_STATUS_REG); |
121 | |
122 | loongson2_thermal_set(data, low: 0, high: 0, enable: false); |
123 | |
124 | for (i = 0; i <= LOONGSON2_MAX_SENSOR_SEL_NUM; i++) { |
125 | tzd = devm_thermal_of_zone_register(dev, id: i, data, |
126 | ops: &loongson2_of_thermal_ops); |
127 | |
128 | if (!IS_ERR(ptr: tzd)) |
129 | break; |
130 | |
131 | if (PTR_ERR(ptr: tzd) != -ENODEV) |
132 | continue; |
133 | |
134 | return dev_err_probe(dev, err: PTR_ERR(ptr: tzd), fmt: "failed to register" ); |
135 | } |
136 | |
137 | ret = devm_request_threaded_irq(dev, irq, NULL, thread_fn: loongson2_thermal_irq_thread, |
138 | IRQF_ONESHOT, devname: "loongson2_thermal" , dev_id: tzd); |
139 | if (ret < 0) |
140 | return dev_err_probe(dev, err: ret, fmt: "failed to request alarm irq\n" ); |
141 | |
142 | devm_thermal_add_hwmon_sysfs(dev, tz: tzd); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = { |
148 | .thermal_sensor_sel = 0, |
149 | }; |
150 | |
151 | static const struct of_device_id of_loongson2_thermal_match[] = { |
152 | { |
153 | .compatible = "loongson,ls2k1000-thermal" , |
154 | .data = &loongson2_thermal_ls2k1000_data, |
155 | }, |
156 | { /* end */ } |
157 | }; |
158 | MODULE_DEVICE_TABLE(of, of_loongson2_thermal_match); |
159 | |
160 | static struct platform_driver loongson2_thermal_driver = { |
161 | .driver = { |
162 | .name = "loongson2_thermal" , |
163 | .of_match_table = of_loongson2_thermal_match, |
164 | }, |
165 | .probe = loongson2_thermal_probe, |
166 | }; |
167 | module_platform_driver(loongson2_thermal_driver); |
168 | |
169 | MODULE_DESCRIPTION("Loongson2 thermal driver" ); |
170 | MODULE_LICENSE("GPL" ); |
171 | |