Commit | Line | Data |
---|---|---|
9a5238a9 | 1 | /* |
2 | * Hisilicon thermal sensor driver | |
3 | * | |
4 | * Copyright (c) 2014-2015 Hisilicon Limited. | |
5 | * Copyright (c) 2014-2015 Linaro Limited. | |
6 | * | |
7 | * Xinwei Kong <kong.kongxinwei@hisilicon.com> | |
8 | * Leo Yan <leo.yan@linaro.org> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
15 | * kind, whether express or implied; without even the implied warranty | |
16 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | */ | |
19 | ||
20 | #include <linux/cpufreq.h> | |
21 | #include <linux/delay.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/io.h> | |
a160a465 | 26 | #include <linux/of_device.h> |
9a5238a9 | 27 | |
28 | #include "thermal_core.h" | |
29 | ||
5ed82b79 KW |
30 | #define HI6220_TEMP0_LAG (0x0) |
31 | #define HI6220_TEMP0_TH (0x4) | |
32 | #define HI6220_TEMP0_RST_TH (0x8) | |
33 | #define HI6220_TEMP0_CFG (0xC) | |
a160a465 | 34 | #define HI6220_TEMP0_CFG_SS_MSK (0xF000) |
5ed82b79 KW |
35 | #define HI6220_TEMP0_CFG_HDAK_MSK (0x30) |
36 | #define HI6220_TEMP0_EN (0x10) | |
37 | #define HI6220_TEMP0_INT_EN (0x14) | |
38 | #define HI6220_TEMP0_INT_CLR (0x18) | |
39 | #define HI6220_TEMP0_RST_MSK (0x1C) | |
40 | #define HI6220_TEMP0_VALUE (0x28) | |
41 | ||
42 | #define HI6220_TEMP_BASE (-60000) | |
43 | #define HI6220_TEMP_RESET (100000) | |
44 | #define HI6220_TEMP_STEP (785) | |
a160a465 | 45 | #define HI6220_TEMP_LAG (3500) |
5ed82b79 KW |
46 | |
47 | #define HI6220_DEFAULT_SENSOR 2 | |
9a5238a9 | 48 | |
49 | struct hisi_thermal_sensor { | |
9a5238a9 | 50 | struct thermal_zone_device *tzd; |
9a5238a9 | 51 | uint32_t id; |
52 | uint32_t thres_temp; | |
53 | }; | |
54 | ||
55 | struct hisi_thermal_data { | |
a160a465 KW |
56 | int (*get_temp)(struct hisi_thermal_data *data); |
57 | int (*enable_sensor)(struct hisi_thermal_data *data); | |
58 | int (*disable_sensor)(struct hisi_thermal_data *data); | |
59 | int (*irq_handler)(struct hisi_thermal_data *data); | |
9a5238a9 | 60 | struct platform_device *pdev; |
61 | struct clk *clk; | |
609f26dc | 62 | struct hisi_thermal_sensor sensor; |
9a5238a9 | 63 | void __iomem *regs; |
609f26dc | 64 | int irq; |
9a5238a9 | 65 | }; |
66 | ||
48880b97 DL |
67 | /* |
68 | * The temperature computation on the tsensor is as follow: | |
69 | * Unit: millidegree Celsius | |
e42bbe11 | 70 | * Step: 200/255 (0.7843) |
48880b97 DL |
71 | * Temperature base: -60°C |
72 | * | |
e42bbe11 | 73 | * The register is programmed in temperature steps, every step is 785 |
48880b97 DL |
74 | * millidegree and begins at -60 000 m°C |
75 | * | |
76 | * The temperature from the steps: | |
77 | * | |
e42bbe11 | 78 | * Temp = TempBase + (steps x 785) |
48880b97 DL |
79 | * |
80 | * and the steps from the temperature: | |
81 | * | |
e42bbe11 | 82 | * steps = (Temp - TempBase) / 785 |
48880b97 DL |
83 | * |
84 | */ | |
5ed82b79 | 85 | static inline int hi6220_thermal_step_to_temp(int step) |
9a5238a9 | 86 | { |
5ed82b79 | 87 | return HI6220_TEMP_BASE + (step * HI6220_TEMP_STEP); |
9a5238a9 | 88 | } |
89 | ||
5ed82b79 | 90 | static inline int hi6220_thermal_temp_to_step(int temp) |
9a5238a9 | 91 | { |
5ed82b79 | 92 | return DIV_ROUND_UP(temp - HI6220_TEMP_BASE, HI6220_TEMP_STEP); |
db2b0332 DL |
93 | } |
94 | ||
10d7e9a9 DL |
95 | /* |
96 | * The lag register contains 5 bits encoding the temperature in steps. | |
97 | * | |
98 | * Each time the temperature crosses the threshold boundary, an | |
99 | * interrupt is raised. It could be when the temperature is going | |
100 | * above the threshold or below. However, if the temperature is | |
101 | * fluctuating around this value due to the load, we can receive | |
102 | * several interrupts which may not desired. | |
103 | * | |
104 | * We can setup a temperature representing the delta between the | |
105 | * threshold and the current temperature when the temperature is | |
106 | * decreasing. | |
107 | * | |
108 | * For instance: the lag register is 5°C, the threshold is 65°C, when | |
109 | * the temperature reaches 65°C an interrupt is raised and when the | |
110 | * temperature decrease to 65°C - 5°C another interrupt is raised. | |
111 | * | |
112 | * A very short lag can lead to an interrupt storm, a long lag | |
113 | * increase the latency to react to the temperature changes. In our | |
114 | * case, that is not really a problem as we are polling the | |
115 | * temperature. | |
116 | * | |
117 | * [0:4] : lag register | |
118 | * | |
5ed82b79 | 119 | * The temperature is coded in steps, cf. HI6220_TEMP_STEP. |
10d7e9a9 DL |
120 | * |
121 | * Min : 0x00 : 0.0 °C | |
122 | * Max : 0x1F : 24.3 °C | |
123 | * | |
124 | * The 'value' parameter is in milliCelsius. | |
125 | */ | |
5ed82b79 | 126 | static inline void hi6220_thermal_set_lag(void __iomem *addr, int value) |
1e11b014 | 127 | { |
5ed82b79 KW |
128 | writel(DIV_ROUND_UP(value, HI6220_TEMP_STEP) & 0x1F, |
129 | addr + HI6220_TEMP0_LAG); | |
1e11b014 DL |
130 | } |
131 | ||
5ed82b79 | 132 | static inline void hi6220_thermal_alarm_clear(void __iomem *addr, int value) |
1e11b014 | 133 | { |
5ed82b79 | 134 | writel(value, addr + HI6220_TEMP0_INT_CLR); |
1e11b014 DL |
135 | } |
136 | ||
5ed82b79 | 137 | static inline void hi6220_thermal_alarm_enable(void __iomem *addr, int value) |
1e11b014 | 138 | { |
5ed82b79 | 139 | writel(value, addr + HI6220_TEMP0_INT_EN); |
1e11b014 DL |
140 | } |
141 | ||
5ed82b79 | 142 | static inline void hi6220_thermal_alarm_set(void __iomem *addr, int temp) |
1e11b014 | 143 | { |
5ed82b79 KW |
144 | writel(hi6220_thermal_temp_to_step(temp) | 0x0FFFFFF00, |
145 | addr + HI6220_TEMP0_TH); | |
1e11b014 DL |
146 | } |
147 | ||
5ed82b79 | 148 | static inline void hi6220_thermal_reset_set(void __iomem *addr, int temp) |
1e11b014 | 149 | { |
5ed82b79 | 150 | writel(hi6220_thermal_temp_to_step(temp), addr + HI6220_TEMP0_RST_TH); |
1e11b014 DL |
151 | } |
152 | ||
5ed82b79 | 153 | static inline void hi6220_thermal_reset_enable(void __iomem *addr, int value) |
1e11b014 | 154 | { |
5ed82b79 | 155 | writel(value, addr + HI6220_TEMP0_RST_MSK); |
1e11b014 DL |
156 | } |
157 | ||
5ed82b79 | 158 | static inline void hi6220_thermal_enable(void __iomem *addr, int value) |
1e11b014 | 159 | { |
5ed82b79 | 160 | writel(value, addr + HI6220_TEMP0_EN); |
1e11b014 DL |
161 | } |
162 | ||
5ed82b79 | 163 | static inline int hi6220_thermal_get_temperature(void __iomem *addr) |
1e11b014 | 164 | { |
5ed82b79 | 165 | return hi6220_thermal_step_to_temp(readl(addr + HI6220_TEMP0_VALUE)); |
1e11b014 DL |
166 | } |
167 | ||
b424315a DL |
168 | /* |
169 | * Temperature configuration register - Sensor selection | |
170 | * | |
171 | * Bits [19:12] | |
172 | * | |
173 | * 0x0: local sensor (default) | |
174 | * 0x1: remote sensor 1 (ACPU cluster 1) | |
175 | * 0x2: remote sensor 2 (ACPU cluster 0) | |
176 | * 0x3: remote sensor 3 (G3D) | |
177 | */ | |
5ed82b79 | 178 | static inline void hi6220_thermal_sensor_select(void __iomem *addr, int sensor) |
1e11b014 | 179 | { |
5ed82b79 KW |
180 | writel((readl(addr + HI6220_TEMP0_CFG) & ~HI6220_TEMP0_CFG_SS_MSK) | |
181 | (sensor << 12), addr + HI6220_TEMP0_CFG); | |
1e11b014 DL |
182 | } |
183 | ||
b424315a DL |
184 | /* |
185 | * Temperature configuration register - Hdak conversion polling interval | |
186 | * | |
187 | * Bits [5:4] | |
188 | * | |
189 | * 0x0 : 0.768 ms | |
190 | * 0x1 : 6.144 ms | |
191 | * 0x2 : 49.152 ms | |
192 | * 0x3 : 393.216 ms | |
193 | */ | |
5ed82b79 | 194 | static inline void hi6220_thermal_hdak_set(void __iomem *addr, int value) |
1e11b014 | 195 | { |
5ed82b79 KW |
196 | writel((readl(addr + HI6220_TEMP0_CFG) & ~HI6220_TEMP0_CFG_HDAK_MSK) | |
197 | (value << 4), addr + HI6220_TEMP0_CFG); | |
1e11b014 DL |
198 | } |
199 | ||
a160a465 KW |
200 | static int hi6220_thermal_irq_handler(struct hisi_thermal_data *data) |
201 | { | |
202 | hi6220_thermal_alarm_clear(data->regs, 1); | |
203 | return 0; | |
204 | } | |
205 | ||
206 | static int hi6220_thermal_get_temp(struct hisi_thermal_data *data) | |
207 | { | |
208 | return hi6220_thermal_get_temperature(data->regs); | |
209 | } | |
210 | ||
211 | static int hi6220_thermal_disable_sensor(struct hisi_thermal_data *data) | |
9a5238a9 | 212 | { |
9a5238a9 | 213 | /* disable sensor module */ |
5ed82b79 KW |
214 | hi6220_thermal_enable(data->regs, 0); |
215 | hi6220_thermal_alarm_enable(data->regs, 0); | |
216 | hi6220_thermal_reset_enable(data->regs, 0); | |
943c0f6a KW |
217 | |
218 | clk_disable_unprepare(data->clk); | |
a160a465 KW |
219 | |
220 | return 0; | |
9a5238a9 | 221 | } |
222 | ||
5ed82b79 | 223 | static int hi6220_thermal_enable_sensor(struct hisi_thermal_data *data) |
a0678da8 KW |
224 | { |
225 | struct hisi_thermal_sensor *sensor = &data->sensor; | |
226 | int ret; | |
227 | ||
228 | /* enable clock for tsensor */ | |
229 | ret = clk_prepare_enable(data->clk); | |
230 | if (ret) | |
231 | return ret; | |
232 | ||
233 | /* disable module firstly */ | |
5ed82b79 KW |
234 | hi6220_thermal_reset_enable(data->regs, 0); |
235 | hi6220_thermal_enable(data->regs, 0); | |
a0678da8 KW |
236 | |
237 | /* select sensor id */ | |
5ed82b79 | 238 | hi6220_thermal_sensor_select(data->regs, sensor->id); |
a0678da8 KW |
239 | |
240 | /* setting the hdak time */ | |
5ed82b79 | 241 | hi6220_thermal_hdak_set(data->regs, 0); |
a0678da8 KW |
242 | |
243 | /* setting lag value between current temp and the threshold */ | |
5ed82b79 | 244 | hi6220_thermal_set_lag(data->regs, HI6220_TEMP_LAG); |
a0678da8 KW |
245 | |
246 | /* enable for interrupt */ | |
5ed82b79 | 247 | hi6220_thermal_alarm_set(data->regs, sensor->thres_temp); |
a0678da8 | 248 | |
5ed82b79 | 249 | hi6220_thermal_reset_set(data->regs, HI6220_TEMP_RESET); |
a0678da8 KW |
250 | |
251 | /* enable module */ | |
5ed82b79 KW |
252 | hi6220_thermal_reset_enable(data->regs, 1); |
253 | hi6220_thermal_enable(data->regs, 1); | |
a0678da8 | 254 | |
5ed82b79 KW |
255 | hi6220_thermal_alarm_clear(data->regs, 0); |
256 | hi6220_thermal_alarm_enable(data->regs, 1); | |
a0678da8 KW |
257 | |
258 | return 0; | |
259 | } | |
260 | ||
a160a465 KW |
261 | static int hi6220_thermal_probe(struct hisi_thermal_data *data) |
262 | { | |
263 | struct platform_device *pdev = data->pdev; | |
264 | struct device *dev = &pdev->dev; | |
265 | struct resource *res; | |
266 | int ret; | |
267 | ||
268 | data->get_temp = hi6220_thermal_get_temp; | |
269 | data->enable_sensor = hi6220_thermal_enable_sensor; | |
270 | data->disable_sensor = hi6220_thermal_disable_sensor; | |
271 | data->irq_handler = hi6220_thermal_irq_handler; | |
272 | ||
273 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
274 | data->regs = devm_ioremap_resource(dev, res); | |
275 | if (IS_ERR(data->regs)) { | |
276 | dev_err(dev, "failed to get io address\n"); | |
277 | return PTR_ERR(data->regs); | |
278 | } | |
279 | ||
280 | data->clk = devm_clk_get(dev, "thermal_clk"); | |
281 | if (IS_ERR(data->clk)) { | |
282 | ret = PTR_ERR(data->clk); | |
283 | if (ret != -EPROBE_DEFER) | |
284 | dev_err(dev, "failed to get thermal clk: %d\n", ret); | |
285 | return ret; | |
286 | } | |
287 | ||
288 | data->irq = platform_get_irq(pdev, 0); | |
289 | if (data->irq < 0) | |
290 | return data->irq; | |
291 | ||
292 | data->sensor.id = HI6220_DEFAULT_SENSOR; | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
81d7cb79 | 297 | static int hisi_thermal_get_temp(void *__data, int *temp) |
9a5238a9 | 298 | { |
81d7cb79 DL |
299 | struct hisi_thermal_data *data = __data; |
300 | struct hisi_thermal_sensor *sensor = &data->sensor; | |
9a5238a9 | 301 | |
a160a465 | 302 | *temp = data->get_temp(data); |
9a5238a9 | 303 | |
10d7e9a9 DL |
304 | dev_dbg(&data->pdev->dev, "id=%d, temp=%d, thres=%d\n", |
305 | sensor->id, *temp, sensor->thres_temp); | |
9a5238a9 | 306 | |
307 | return 0; | |
308 | } | |
309 | ||
3fe156f1 | 310 | static const struct thermal_zone_of_device_ops hisi_of_thermal_ops = { |
9a5238a9 | 311 | .get_temp = hisi_thermal_get_temp, |
312 | }; | |
313 | ||
10d7e9a9 | 314 | static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev) |
9a5238a9 | 315 | { |
316 | struct hisi_thermal_data *data = dev; | |
609f26dc | 317 | struct hisi_thermal_sensor *sensor = &data->sensor; |
5ed82b79 | 318 | int temp = 0; |
9a5238a9 | 319 | |
a160a465 | 320 | data->irq_handler(data); |
9a5238a9 | 321 | |
5ed82b79 | 322 | hisi_thermal_get_temp(data, &temp); |
9a5238a9 | 323 | |
10d7e9a9 DL |
324 | if (temp >= sensor->thres_temp) { |
325 | dev_crit(&data->pdev->dev, "THERMAL ALARM: %d > %d\n", | |
326 | temp, sensor->thres_temp); | |
9a5238a9 | 327 | |
609f26dc | 328 | thermal_zone_device_update(data->sensor.tzd, |
10d7e9a9 | 329 | THERMAL_EVENT_UNSPECIFIED); |
9a5238a9 | 330 | |
5ed82b79 | 331 | } else { |
10d7e9a9 DL |
332 | dev_crit(&data->pdev->dev, "THERMAL ALARM stopped: %d < %d\n", |
333 | temp, sensor->thres_temp); | |
334 | } | |
9a5238a9 | 335 | |
336 | return IRQ_HANDLED; | |
337 | } | |
338 | ||
339 | static int hisi_thermal_register_sensor(struct platform_device *pdev, | |
340 | struct hisi_thermal_data *data, | |
a160a465 | 341 | struct hisi_thermal_sensor *sensor) |
9a5238a9 | 342 | { |
343 | int ret, i; | |
344 | const struct thermal_trip *trip; | |
345 | ||
44a520d8 | 346 | sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, |
81d7cb79 DL |
347 | sensor->id, data, |
348 | &hisi_of_thermal_ops); | |
9a5238a9 | 349 | if (IS_ERR(sensor->tzd)) { |
350 | ret = PTR_ERR(sensor->tzd); | |
439dc968 | 351 | sensor->tzd = NULL; |
9a5238a9 | 352 | dev_err(&pdev->dev, "failed to register sensor id %d: %d\n", |
353 | sensor->id, ret); | |
354 | return ret; | |
355 | } | |
356 | ||
357 | trip = of_thermal_get_trip_points(sensor->tzd); | |
358 | ||
359 | for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) { | |
360 | if (trip[i].type == THERMAL_TRIP_PASSIVE) { | |
e42bbe11 | 361 | sensor->thres_temp = trip[i].temperature; |
9a5238a9 | 362 | break; |
363 | } | |
364 | } | |
365 | ||
366 | return 0; | |
367 | } | |
368 | ||
369 | static const struct of_device_id of_hisi_thermal_match[] = { | |
a160a465 | 370 | { .compatible = "hisilicon,tsensor", .data = hi6220_thermal_probe }, |
9a5238a9 | 371 | { /* end */ } |
372 | }; | |
373 | MODULE_DEVICE_TABLE(of, of_hisi_thermal_match); | |
374 | ||
375 | static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor, | |
376 | bool on) | |
377 | { | |
378 | struct thermal_zone_device *tzd = sensor->tzd; | |
379 | ||
380 | tzd->ops->set_mode(tzd, | |
381 | on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); | |
382 | } | |
383 | ||
384 | static int hisi_thermal_probe(struct platform_device *pdev) | |
385 | { | |
386 | struct hisi_thermal_data *data; | |
a160a465 KW |
387 | int const (*platform_probe)(struct hisi_thermal_data *); |
388 | struct device *dev = &pdev->dev; | |
9a5238a9 | 389 | int ret; |
390 | ||
a160a465 | 391 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
9a5238a9 | 392 | if (!data) |
393 | return -ENOMEM; | |
394 | ||
9a5238a9 | 395 | data->pdev = pdev; |
a160a465 | 396 | platform_set_drvdata(pdev, data); |
9a5238a9 | 397 | |
a160a465 KW |
398 | platform_probe = of_device_get_match_data(dev); |
399 | if (!platform_probe) { | |
400 | dev_err(dev, "failed to get probe func\n"); | |
401 | return -EINVAL; | |
9a5238a9 | 402 | } |
403 | ||
a160a465 KW |
404 | ret = platform_probe(data); |
405 | if (ret) | |
9a5238a9 | 406 | return ret; |
9a5238a9 | 407 | |
ff4ec299 | 408 | ret = hisi_thermal_register_sensor(pdev, data, |
a160a465 | 409 | &data->sensor); |
ff4ec299 | 410 | if (ret) { |
a160a465 | 411 | dev_err(dev, "failed to register thermal sensor: %d\n", ret); |
ff4ec299 | 412 | return ret; |
9a5238a9 | 413 | } |
414 | ||
a160a465 | 415 | ret = data->enable_sensor(data); |
10d7e9a9 | 416 | if (ret) { |
a160a465 | 417 | dev_err(dev, "Failed to setup the sensor: %d\n", ret); |
10d7e9a9 DL |
418 | return ret; |
419 | } | |
ff4ec299 | 420 | |
a160a465 KW |
421 | if (data->irq) { |
422 | ret = devm_request_threaded_irq(dev, data->irq, NULL, | |
423 | hisi_thermal_alarm_irq_thread, | |
424 | IRQF_ONESHOT, "hisi_thermal", data); | |
425 | if (ret < 0) { | |
426 | dev_err(dev, "failed to request alarm irq: %d\n", ret); | |
427 | return ret; | |
428 | } | |
2cb4de78 DL |
429 | } |
430 | ||
609f26dc | 431 | hisi_thermal_toggle_sensor(&data->sensor, true); |
c176b10b | 432 | |
9a5238a9 | 433 | return 0; |
9a5238a9 | 434 | } |
435 | ||
436 | static int hisi_thermal_remove(struct platform_device *pdev) | |
437 | { | |
438 | struct hisi_thermal_data *data = platform_get_drvdata(pdev); | |
609f26dc | 439 | struct hisi_thermal_sensor *sensor = &data->sensor; |
9a5238a9 | 440 | |
ff4ec299 | 441 | hisi_thermal_toggle_sensor(sensor, false); |
a160a465 KW |
442 | |
443 | data->disable_sensor(data); | |
9a5238a9 | 444 | |
445 | return 0; | |
446 | } | |
447 | ||
448 | #ifdef CONFIG_PM_SLEEP | |
449 | static int hisi_thermal_suspend(struct device *dev) | |
450 | { | |
451 | struct hisi_thermal_data *data = dev_get_drvdata(dev); | |
452 | ||
a160a465 | 453 | data->disable_sensor(data); |
9a5238a9 | 454 | |
9a5238a9 | 455 | return 0; |
456 | } | |
457 | ||
458 | static int hisi_thermal_resume(struct device *dev) | |
459 | { | |
460 | struct hisi_thermal_data *data = dev_get_drvdata(dev); | |
461 | ||
a160a465 | 462 | return data->enable_sensor(data); |
9a5238a9 | 463 | } |
464 | #endif | |
465 | ||
466 | static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops, | |
467 | hisi_thermal_suspend, hisi_thermal_resume); | |
468 | ||
469 | static struct platform_driver hisi_thermal_driver = { | |
470 | .driver = { | |
471 | .name = "hisi_thermal", | |
9a5238a9 | 472 | .pm = &hisi_thermal_pm_ops, |
473 | .of_match_table = of_hisi_thermal_match, | |
474 | }, | |
475 | .probe = hisi_thermal_probe, | |
476 | .remove = hisi_thermal_remove, | |
477 | }; | |
478 | ||
479 | module_platform_driver(hisi_thermal_driver); | |
480 | ||
481 | MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>"); | |
482 | MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>"); | |
483 | MODULE_DESCRIPTION("Hisilicon thermal driver"); | |
484 | MODULE_LICENSE("GPL v2"); |