1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * uniphier_thermal.c - Socionext UniPhier thermal driver |
4 | * Copyright 2014 Panasonic Corporation |
5 | * Copyright 2016-2017 Socionext Inc. |
6 | * Author: |
7 | * Kunihiko Hayashi <hayashi.kunihiko@socionext.com> |
8 | */ |
9 | |
10 | #include <linux/bitops.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/thermal.h> |
18 | |
19 | /* |
20 | * block registers |
21 | * addresses are the offset from .block_base |
22 | */ |
23 | #define PVTCTLEN 0x0000 |
24 | #define PVTCTLEN_EN BIT(0) |
25 | |
26 | #define PVTCTLMODE 0x0004 |
27 | #define PVTCTLMODE_MASK 0xf |
28 | #define PVTCTLMODE_TEMPMON 0x5 |
29 | |
30 | #define EMONREPEAT 0x0040 |
31 | #define EMONREPEAT_ENDLESS BIT(24) |
32 | #define EMONREPEAT_PERIOD GENMASK(3, 0) |
33 | #define EMONREPEAT_PERIOD_1000000 0x9 |
34 | |
35 | /* |
36 | * common registers |
37 | * addresses are the offset from .map_base |
38 | */ |
39 | #define PVTCTLSEL 0x0900 |
40 | #define PVTCTLSEL_MASK GENMASK(2, 0) |
41 | #define PVTCTLSEL_MONITOR 0 |
42 | |
43 | #define SETALERT0 0x0910 |
44 | #define SETALERT1 0x0914 |
45 | #define SETALERT2 0x0918 |
46 | #define SETALERT_TEMP_OVF (GENMASK(7, 0) << 16) |
47 | #define SETALERT_TEMP_OVF_VALUE(val) (((val) & GENMASK(7, 0)) << 16) |
48 | #define SETALERT_EN BIT(0) |
49 | |
50 | #define PMALERTINTCTL 0x0920 |
51 | #define PMALERTINTCTL_CLR(ch) BIT(4 * (ch) + 2) |
52 | #define PMALERTINTCTL_SET(ch) BIT(4 * (ch) + 1) |
53 | #define PMALERTINTCTL_EN(ch) BIT(4 * (ch) + 0) |
54 | #define PMALERTINTCTL_MASK (GENMASK(10, 8) | GENMASK(6, 4) | \ |
55 | GENMASK(2, 0)) |
56 | |
57 | #define TMOD 0x0928 |
58 | #define TMOD_WIDTH 9 |
59 | |
60 | #define TMODCOEF 0x0e5c |
61 | |
62 | #define TMODSETUP0_EN BIT(30) |
63 | #define TMODSETUP0_VAL(val) (((val) & GENMASK(13, 0)) << 16) |
64 | #define TMODSETUP1_EN BIT(15) |
65 | #define TMODSETUP1_VAL(val) ((val) & GENMASK(14, 0)) |
66 | |
67 | /* SoC critical temperature */ |
68 | #define CRITICAL_TEMP_LIMIT (120 * 1000) |
69 | |
70 | /* Max # of alert channels */ |
71 | #define ALERT_CH_NUM 3 |
72 | |
73 | /* SoC specific thermal sensor data */ |
74 | struct uniphier_tm_soc_data { |
75 | u32 map_base; |
76 | u32 block_base; |
77 | u32 tmod_setup_addr; |
78 | }; |
79 | |
80 | struct uniphier_tm_dev { |
81 | struct regmap *regmap; |
82 | struct device *dev; |
83 | bool alert_en[ALERT_CH_NUM]; |
84 | struct thermal_zone_device *tz_dev; |
85 | const struct uniphier_tm_soc_data *data; |
86 | }; |
87 | |
88 | static int uniphier_tm_initialize_sensor(struct uniphier_tm_dev *tdev) |
89 | { |
90 | struct regmap *map = tdev->regmap; |
91 | u32 val; |
92 | u32 tmod_calib[2]; |
93 | int ret; |
94 | |
95 | /* stop PVT */ |
96 | regmap_write_bits(map, reg: tdev->data->block_base + PVTCTLEN, |
97 | PVTCTLEN_EN, val: 0); |
98 | |
99 | /* |
100 | * Since SoC has a calibrated value that was set in advance, |
101 | * TMODCOEF shows non-zero and PVT refers the value internally. |
102 | * |
103 | * If TMODCOEF shows zero, the boards don't have the calibrated |
104 | * value, and the driver has to set default value from DT. |
105 | */ |
106 | ret = regmap_read(map, reg: tdev->data->map_base + TMODCOEF, val: &val); |
107 | if (ret) |
108 | return ret; |
109 | if (!val) { |
110 | /* look for the default values in DT */ |
111 | ret = of_property_read_u32_array(np: tdev->dev->of_node, |
112 | propname: "socionext,tmod-calibration" , |
113 | out_values: tmod_calib, |
114 | ARRAY_SIZE(tmod_calib)); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | regmap_write(map, reg: tdev->data->tmod_setup_addr, |
119 | TMODSETUP0_EN | TMODSETUP0_VAL(tmod_calib[0]) | |
120 | TMODSETUP1_EN | TMODSETUP1_VAL(tmod_calib[1])); |
121 | } |
122 | |
123 | /* select temperature mode */ |
124 | regmap_write_bits(map, reg: tdev->data->block_base + PVTCTLMODE, |
125 | PVTCTLMODE_MASK, PVTCTLMODE_TEMPMON); |
126 | |
127 | /* set monitoring period */ |
128 | regmap_write_bits(map, reg: tdev->data->block_base + EMONREPEAT, |
129 | EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD, |
130 | EMONREPEAT_ENDLESS | EMONREPEAT_PERIOD_1000000); |
131 | |
132 | /* set monitor mode */ |
133 | regmap_write_bits(map, reg: tdev->data->map_base + PVTCTLSEL, |
134 | PVTCTLSEL_MASK, PVTCTLSEL_MONITOR); |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static void uniphier_tm_set_alert(struct uniphier_tm_dev *tdev, u32 ch, |
140 | u32 temp) |
141 | { |
142 | struct regmap *map = tdev->regmap; |
143 | |
144 | /* set alert temperature */ |
145 | regmap_write_bits(map, reg: tdev->data->map_base + SETALERT0 + (ch << 2), |
146 | SETALERT_EN | SETALERT_TEMP_OVF, |
147 | SETALERT_EN | |
148 | SETALERT_TEMP_OVF_VALUE(temp / 1000)); |
149 | } |
150 | |
151 | static void uniphier_tm_enable_sensor(struct uniphier_tm_dev *tdev) |
152 | { |
153 | struct regmap *map = tdev->regmap; |
154 | int i; |
155 | u32 bits = 0; |
156 | |
157 | for (i = 0; i < ALERT_CH_NUM; i++) |
158 | if (tdev->alert_en[i]) |
159 | bits |= PMALERTINTCTL_EN(i); |
160 | |
161 | /* enable alert interrupt */ |
162 | regmap_write_bits(map, reg: tdev->data->map_base + PMALERTINTCTL, |
163 | PMALERTINTCTL_MASK, val: bits); |
164 | |
165 | /* start PVT */ |
166 | regmap_write_bits(map, reg: tdev->data->block_base + PVTCTLEN, |
167 | PVTCTLEN_EN, PVTCTLEN_EN); |
168 | |
169 | usleep_range(min: 700, max: 1500); /* The spec note says at least 700us */ |
170 | } |
171 | |
172 | static void uniphier_tm_disable_sensor(struct uniphier_tm_dev *tdev) |
173 | { |
174 | struct regmap *map = tdev->regmap; |
175 | |
176 | /* disable alert interrupt */ |
177 | regmap_write_bits(map, reg: tdev->data->map_base + PMALERTINTCTL, |
178 | PMALERTINTCTL_MASK, val: 0); |
179 | |
180 | /* stop PVT */ |
181 | regmap_write_bits(map, reg: tdev->data->block_base + PVTCTLEN, |
182 | PVTCTLEN_EN, val: 0); |
183 | |
184 | usleep_range(min: 1000, max: 2000); /* The spec note says at least 1ms */ |
185 | } |
186 | |
187 | static int uniphier_tm_get_temp(struct thermal_zone_device *tz, int *out_temp) |
188 | { |
189 | struct uniphier_tm_dev *tdev = thermal_zone_device_priv(tzd: tz); |
190 | struct regmap *map = tdev->regmap; |
191 | int ret; |
192 | u32 temp; |
193 | |
194 | ret = regmap_read(map, reg: tdev->data->map_base + TMOD, val: &temp); |
195 | if (ret) |
196 | return ret; |
197 | |
198 | /* MSB of the TMOD field is a sign bit */ |
199 | *out_temp = sign_extend32(value: temp, TMOD_WIDTH - 1) * 1000; |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static const struct thermal_zone_device_ops uniphier_of_thermal_ops = { |
205 | .get_temp = uniphier_tm_get_temp, |
206 | }; |
207 | |
208 | static void uniphier_tm_irq_clear(struct uniphier_tm_dev *tdev) |
209 | { |
210 | u32 mask = 0, bits = 0; |
211 | int i; |
212 | |
213 | for (i = 0; i < ALERT_CH_NUM; i++) { |
214 | mask |= (PMALERTINTCTL_CLR(i) | PMALERTINTCTL_SET(i)); |
215 | bits |= PMALERTINTCTL_CLR(i); |
216 | } |
217 | |
218 | /* clear alert interrupt */ |
219 | regmap_write_bits(map: tdev->regmap, |
220 | reg: tdev->data->map_base + PMALERTINTCTL, mask, val: bits); |
221 | } |
222 | |
223 | static irqreturn_t uniphier_tm_alarm_irq(int irq, void *_tdev) |
224 | { |
225 | struct uniphier_tm_dev *tdev = _tdev; |
226 | |
227 | disable_irq_nosync(irq); |
228 | uniphier_tm_irq_clear(tdev); |
229 | |
230 | return IRQ_WAKE_THREAD; |
231 | } |
232 | |
233 | static irqreturn_t uniphier_tm_alarm_irq_thread(int irq, void *_tdev) |
234 | { |
235 | struct uniphier_tm_dev *tdev = _tdev; |
236 | |
237 | thermal_zone_device_update(tdev->tz_dev, THERMAL_EVENT_UNSPECIFIED); |
238 | |
239 | return IRQ_HANDLED; |
240 | } |
241 | |
242 | static int uniphier_tm_probe(struct platform_device *pdev) |
243 | { |
244 | struct device *dev = &pdev->dev; |
245 | struct regmap *regmap; |
246 | struct device_node *parent; |
247 | struct uniphier_tm_dev *tdev; |
248 | int i, ret, irq, crit_temp = INT_MAX; |
249 | |
250 | tdev = devm_kzalloc(dev, size: sizeof(*tdev), GFP_KERNEL); |
251 | if (!tdev) |
252 | return -ENOMEM; |
253 | tdev->dev = dev; |
254 | |
255 | tdev->data = of_device_get_match_data(dev); |
256 | if (WARN_ON(!tdev->data)) |
257 | return -EINVAL; |
258 | |
259 | irq = platform_get_irq(pdev, 0); |
260 | if (irq < 0) |
261 | return irq; |
262 | |
263 | /* get regmap from syscon node */ |
264 | parent = of_get_parent(node: dev->of_node); /* parent should be syscon node */ |
265 | regmap = syscon_node_to_regmap(np: parent); |
266 | of_node_put(node: parent); |
267 | if (IS_ERR(ptr: regmap)) { |
268 | dev_err(dev, "failed to get regmap (error %ld)\n" , |
269 | PTR_ERR(regmap)); |
270 | return PTR_ERR(ptr: regmap); |
271 | } |
272 | tdev->regmap = regmap; |
273 | |
274 | ret = uniphier_tm_initialize_sensor(tdev); |
275 | if (ret) { |
276 | dev_err(dev, "failed to initialize sensor\n" ); |
277 | return ret; |
278 | } |
279 | |
280 | ret = devm_request_threaded_irq(dev, irq, handler: uniphier_tm_alarm_irq, |
281 | thread_fn: uniphier_tm_alarm_irq_thread, |
282 | irqflags: 0, devname: "thermal" , dev_id: tdev); |
283 | if (ret) |
284 | return ret; |
285 | |
286 | platform_set_drvdata(pdev, data: tdev); |
287 | |
288 | tdev->tz_dev = devm_thermal_of_zone_register(dev, id: 0, data: tdev, |
289 | ops: &uniphier_of_thermal_ops); |
290 | if (IS_ERR(ptr: tdev->tz_dev)) { |
291 | dev_err(dev, "failed to register sensor device\n" ); |
292 | return PTR_ERR(ptr: tdev->tz_dev); |
293 | } |
294 | |
295 | /* set alert temperatures */ |
296 | for (i = 0; i < thermal_zone_get_num_trips(tz: tdev->tz_dev); i++) { |
297 | struct thermal_trip trip; |
298 | |
299 | ret = thermal_zone_get_trip(tz: tdev->tz_dev, trip_id: i, trip: &trip); |
300 | if (ret) |
301 | return ret; |
302 | |
303 | if (trip.type == THERMAL_TRIP_CRITICAL && |
304 | trip.temperature < crit_temp) |
305 | crit_temp = trip.temperature; |
306 | uniphier_tm_set_alert(tdev, ch: i, temp: trip.temperature); |
307 | tdev->alert_en[i] = true; |
308 | } |
309 | if (crit_temp > CRITICAL_TEMP_LIMIT) { |
310 | dev_err(dev, "critical trip is over limit(>%d), or not set\n" , |
311 | CRITICAL_TEMP_LIMIT); |
312 | return -EINVAL; |
313 | } |
314 | |
315 | uniphier_tm_enable_sensor(tdev); |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | static void uniphier_tm_remove(struct platform_device *pdev) |
321 | { |
322 | struct uniphier_tm_dev *tdev = platform_get_drvdata(pdev); |
323 | |
324 | /* disable sensor */ |
325 | uniphier_tm_disable_sensor(tdev); |
326 | } |
327 | |
328 | static const struct uniphier_tm_soc_data uniphier_pxs2_tm_data = { |
329 | .map_base = 0xe000, |
330 | .block_base = 0xe000, |
331 | .tmod_setup_addr = 0xe904, |
332 | }; |
333 | |
334 | static const struct uniphier_tm_soc_data uniphier_ld20_tm_data = { |
335 | .map_base = 0xe000, |
336 | .block_base = 0xe800, |
337 | .tmod_setup_addr = 0xe938, |
338 | }; |
339 | |
340 | static const struct of_device_id uniphier_tm_dt_ids[] = { |
341 | { |
342 | .compatible = "socionext,uniphier-pxs2-thermal" , |
343 | .data = &uniphier_pxs2_tm_data, |
344 | }, |
345 | { |
346 | .compatible = "socionext,uniphier-ld20-thermal" , |
347 | .data = &uniphier_ld20_tm_data, |
348 | }, |
349 | { |
350 | .compatible = "socionext,uniphier-pxs3-thermal" , |
351 | .data = &uniphier_ld20_tm_data, |
352 | }, |
353 | { |
354 | .compatible = "socionext,uniphier-nx1-thermal" , |
355 | .data = &uniphier_ld20_tm_data, |
356 | }, |
357 | { /* sentinel */ } |
358 | }; |
359 | MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids); |
360 | |
361 | static struct platform_driver uniphier_tm_driver = { |
362 | .probe = uniphier_tm_probe, |
363 | .remove_new = uniphier_tm_remove, |
364 | .driver = { |
365 | .name = "uniphier-thermal" , |
366 | .of_match_table = uniphier_tm_dt_ids, |
367 | }, |
368 | }; |
369 | module_platform_driver(uniphier_tm_driver); |
370 | |
371 | MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>" ); |
372 | MODULE_DESCRIPTION("UniPhier thermal driver" ); |
373 | MODULE_LICENSE("GPL v2" ); |
374 | |