]>
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> | |
26 | ||
27 | #include "thermal_core.h" | |
28 | ||
1e11b014 | 29 | #define TEMP0_LAG (0x0) |
9a5238a9 | 30 | #define TEMP0_TH (0x4) |
31 | #define TEMP0_RST_TH (0x8) | |
32 | #define TEMP0_CFG (0xC) | |
b424315a DL |
33 | #define TEMP0_CFG_SS_MSK (0xF000) |
34 | #define TEMP0_CFG_HDAK_MSK (0x30) | |
9a5238a9 | 35 | #define TEMP0_EN (0x10) |
36 | #define TEMP0_INT_EN (0x14) | |
37 | #define TEMP0_INT_CLR (0x18) | |
38 | #define TEMP0_RST_MSK (0x1C) | |
39 | #define TEMP0_VALUE (0x28) | |
40 | ||
48880b97 | 41 | #define HISI_TEMP_BASE (-60000) |
9a5238a9 | 42 | #define HISI_TEMP_RESET (100000) |
e42bbe11 | 43 | #define HISI_TEMP_STEP (785) |
10d7e9a9 | 44 | #define HISI_TEMP_LAG (3500) |
9a5238a9 | 45 | |
46 | #define HISI_MAX_SENSORS 4 | |
ff4ec299 | 47 | #define HISI_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 { | |
9a5238a9 | 56 | struct platform_device *pdev; |
57 | struct clk *clk; | |
609f26dc | 58 | struct hisi_thermal_sensor sensor; |
9a5238a9 | 59 | void __iomem *regs; |
609f26dc | 60 | int irq; |
9a5238a9 | 61 | }; |
62 | ||
48880b97 DL |
63 | /* |
64 | * The temperature computation on the tsensor is as follow: | |
65 | * Unit: millidegree Celsius | |
e42bbe11 | 66 | * Step: 200/255 (0.7843) |
48880b97 DL |
67 | * Temperature base: -60°C |
68 | * | |
e42bbe11 | 69 | * The register is programmed in temperature steps, every step is 785 |
48880b97 DL |
70 | * millidegree and begins at -60 000 m°C |
71 | * | |
72 | * The temperature from the steps: | |
73 | * | |
e42bbe11 | 74 | * Temp = TempBase + (steps x 785) |
48880b97 DL |
75 | * |
76 | * and the steps from the temperature: | |
77 | * | |
e42bbe11 | 78 | * steps = (Temp - TempBase) / 785 |
48880b97 DL |
79 | * |
80 | */ | |
81 | static inline int hisi_thermal_step_to_temp(int step) | |
9a5238a9 | 82 | { |
48880b97 | 83 | return HISI_TEMP_BASE + (step * HISI_TEMP_STEP); |
9a5238a9 | 84 | } |
85 | ||
bc02ef6d | 86 | static inline int hisi_thermal_temp_to_step(int temp) |
9a5238a9 | 87 | { |
e42bbe11 | 88 | return DIV_ROUND_UP(temp - HISI_TEMP_BASE, HISI_TEMP_STEP); |
db2b0332 DL |
89 | } |
90 | ||
10d7e9a9 DL |
91 | /* |
92 | * The lag register contains 5 bits encoding the temperature in steps. | |
93 | * | |
94 | * Each time the temperature crosses the threshold boundary, an | |
95 | * interrupt is raised. It could be when the temperature is going | |
96 | * above the threshold or below. However, if the temperature is | |
97 | * fluctuating around this value due to the load, we can receive | |
98 | * several interrupts which may not desired. | |
99 | * | |
100 | * We can setup a temperature representing the delta between the | |
101 | * threshold and the current temperature when the temperature is | |
102 | * decreasing. | |
103 | * | |
104 | * For instance: the lag register is 5°C, the threshold is 65°C, when | |
105 | * the temperature reaches 65°C an interrupt is raised and when the | |
106 | * temperature decrease to 65°C - 5°C another interrupt is raised. | |
107 | * | |
108 | * A very short lag can lead to an interrupt storm, a long lag | |
109 | * increase the latency to react to the temperature changes. In our | |
110 | * case, that is not really a problem as we are polling the | |
111 | * temperature. | |
112 | * | |
113 | * [0:4] : lag register | |
114 | * | |
115 | * The temperature is coded in steps, cf. HISI_TEMP_STEP. | |
116 | * | |
117 | * Min : 0x00 : 0.0 °C | |
118 | * Max : 0x1F : 24.3 °C | |
119 | * | |
120 | * The 'value' parameter is in milliCelsius. | |
121 | */ | |
1e11b014 DL |
122 | static inline void hisi_thermal_set_lag(void __iomem *addr, int value) |
123 | { | |
e42bbe11 | 124 | writel(DIV_ROUND_UP(value, HISI_TEMP_STEP) & 0x1F, addr + TEMP0_LAG); |
1e11b014 DL |
125 | } |
126 | ||
127 | static inline void hisi_thermal_alarm_clear(void __iomem *addr, int value) | |
128 | { | |
129 | writel(value, addr + TEMP0_INT_CLR); | |
130 | } | |
131 | ||
132 | static inline void hisi_thermal_alarm_enable(void __iomem *addr, int value) | |
133 | { | |
134 | writel(value, addr + TEMP0_INT_EN); | |
135 | } | |
136 | ||
137 | static inline void hisi_thermal_alarm_set(void __iomem *addr, int temp) | |
138 | { | |
139 | writel(hisi_thermal_temp_to_step(temp) | 0x0FFFFFF00, addr + TEMP0_TH); | |
140 | } | |
141 | ||
142 | static inline void hisi_thermal_reset_set(void __iomem *addr, int temp) | |
143 | { | |
144 | writel(hisi_thermal_temp_to_step(temp), addr + TEMP0_RST_TH); | |
145 | } | |
146 | ||
147 | static inline void hisi_thermal_reset_enable(void __iomem *addr, int value) | |
148 | { | |
149 | writel(value, addr + TEMP0_RST_MSK); | |
150 | } | |
151 | ||
152 | static inline void hisi_thermal_enable(void __iomem *addr, int value) | |
153 | { | |
154 | writel(value, addr + TEMP0_EN); | |
155 | } | |
156 | ||
b424315a | 157 | static inline int hisi_thermal_get_temperature(void __iomem *addr) |
1e11b014 | 158 | { |
b424315a | 159 | return hisi_thermal_step_to_temp(readl(addr + TEMP0_VALUE)); |
1e11b014 DL |
160 | } |
161 | ||
b424315a DL |
162 | /* |
163 | * Temperature configuration register - Sensor selection | |
164 | * | |
165 | * Bits [19:12] | |
166 | * | |
167 | * 0x0: local sensor (default) | |
168 | * 0x1: remote sensor 1 (ACPU cluster 1) | |
169 | * 0x2: remote sensor 2 (ACPU cluster 0) | |
170 | * 0x3: remote sensor 3 (G3D) | |
171 | */ | |
172 | static inline void hisi_thermal_sensor_select(void __iomem *addr, int sensor) | |
1e11b014 | 173 | { |
b424315a DL |
174 | writel((readl(addr + TEMP0_CFG) & ~TEMP0_CFG_SS_MSK) | |
175 | (sensor << 12), addr + TEMP0_CFG); | |
1e11b014 DL |
176 | } |
177 | ||
b424315a DL |
178 | /* |
179 | * Temperature configuration register - Hdak conversion polling interval | |
180 | * | |
181 | * Bits [5:4] | |
182 | * | |
183 | * 0x0 : 0.768 ms | |
184 | * 0x1 : 6.144 ms | |
185 | * 0x2 : 49.152 ms | |
186 | * 0x3 : 393.216 ms | |
187 | */ | |
1e11b014 DL |
188 | static inline void hisi_thermal_hdak_set(void __iomem *addr, int value) |
189 | { | |
b424315a DL |
190 | writel((readl(addr + TEMP0_CFG) & ~TEMP0_CFG_HDAK_MSK) | |
191 | (value << 4), addr + TEMP0_CFG); | |
1e11b014 DL |
192 | } |
193 | ||
9a5238a9 | 194 | static void hisi_thermal_disable_sensor(struct hisi_thermal_data *data) |
195 | { | |
9a5238a9 | 196 | /* disable sensor module */ |
1e11b014 DL |
197 | hisi_thermal_enable(data->regs, 0); |
198 | hisi_thermal_alarm_enable(data->regs, 0); | |
199 | hisi_thermal_reset_enable(data->regs, 0); | |
943c0f6a KW |
200 | |
201 | clk_disable_unprepare(data->clk); | |
9a5238a9 | 202 | } |
203 | ||
81d7cb79 | 204 | static int hisi_thermal_get_temp(void *__data, int *temp) |
9a5238a9 | 205 | { |
81d7cb79 DL |
206 | struct hisi_thermal_data *data = __data; |
207 | struct hisi_thermal_sensor *sensor = &data->sensor; | |
9a5238a9 | 208 | |
10d7e9a9 | 209 | *temp = hisi_thermal_get_temperature(data->regs); |
9a5238a9 | 210 | |
10d7e9a9 DL |
211 | dev_dbg(&data->pdev->dev, "id=%d, temp=%d, thres=%d\n", |
212 | sensor->id, *temp, sensor->thres_temp); | |
9a5238a9 | 213 | |
214 | return 0; | |
215 | } | |
216 | ||
3fe156f1 | 217 | static const struct thermal_zone_of_device_ops hisi_of_thermal_ops = { |
9a5238a9 | 218 | .get_temp = hisi_thermal_get_temp, |
219 | }; | |
220 | ||
10d7e9a9 | 221 | static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev) |
9a5238a9 | 222 | { |
223 | struct hisi_thermal_data *data = dev; | |
609f26dc | 224 | struct hisi_thermal_sensor *sensor = &data->sensor; |
10d7e9a9 | 225 | int temp; |
9a5238a9 | 226 | |
10d7e9a9 | 227 | hisi_thermal_alarm_clear(data->regs, 1); |
9a5238a9 | 228 | |
10d7e9a9 | 229 | temp = hisi_thermal_get_temperature(data->regs); |
9a5238a9 | 230 | |
10d7e9a9 DL |
231 | if (temp >= sensor->thres_temp) { |
232 | dev_crit(&data->pdev->dev, "THERMAL ALARM: %d > %d\n", | |
233 | temp, sensor->thres_temp); | |
9a5238a9 | 234 | |
609f26dc | 235 | thermal_zone_device_update(data->sensor.tzd, |
10d7e9a9 | 236 | THERMAL_EVENT_UNSPECIFIED); |
9a5238a9 | 237 | |
10d7e9a9 DL |
238 | } else if (temp < sensor->thres_temp) { |
239 | dev_crit(&data->pdev->dev, "THERMAL ALARM stopped: %d < %d\n", | |
240 | temp, sensor->thres_temp); | |
241 | } | |
9a5238a9 | 242 | |
243 | return IRQ_HANDLED; | |
244 | } | |
245 | ||
246 | static int hisi_thermal_register_sensor(struct platform_device *pdev, | |
247 | struct hisi_thermal_data *data, | |
248 | struct hisi_thermal_sensor *sensor, | |
249 | int index) | |
250 | { | |
251 | int ret, i; | |
252 | const struct thermal_trip *trip; | |
253 | ||
254 | sensor->id = index; | |
9a5238a9 | 255 | |
44a520d8 | 256 | sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, |
81d7cb79 DL |
257 | sensor->id, data, |
258 | &hisi_of_thermal_ops); | |
9a5238a9 | 259 | if (IS_ERR(sensor->tzd)) { |
260 | ret = PTR_ERR(sensor->tzd); | |
439dc968 | 261 | sensor->tzd = NULL; |
9a5238a9 | 262 | dev_err(&pdev->dev, "failed to register sensor id %d: %d\n", |
263 | sensor->id, ret); | |
264 | return ret; | |
265 | } | |
266 | ||
267 | trip = of_thermal_get_trip_points(sensor->tzd); | |
268 | ||
269 | for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) { | |
270 | if (trip[i].type == THERMAL_TRIP_PASSIVE) { | |
e42bbe11 | 271 | sensor->thres_temp = trip[i].temperature; |
9a5238a9 | 272 | break; |
273 | } | |
274 | } | |
275 | ||
276 | return 0; | |
277 | } | |
278 | ||
279 | static const struct of_device_id of_hisi_thermal_match[] = { | |
280 | { .compatible = "hisilicon,tsensor" }, | |
281 | { /* end */ } | |
282 | }; | |
283 | MODULE_DEVICE_TABLE(of, of_hisi_thermal_match); | |
284 | ||
285 | static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor, | |
286 | bool on) | |
287 | { | |
288 | struct thermal_zone_device *tzd = sensor->tzd; | |
289 | ||
290 | tzd->ops->set_mode(tzd, | |
291 | on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); | |
292 | } | |
293 | ||
10d7e9a9 DL |
294 | static int hisi_thermal_setup(struct hisi_thermal_data *data) |
295 | { | |
943c0f6a KW |
296 | struct hisi_thermal_sensor *sensor = &data->sensor; |
297 | int ret; | |
10d7e9a9 | 298 | |
943c0f6a KW |
299 | /* enable clock for tsensor */ |
300 | ret = clk_prepare_enable(data->clk); | |
301 | if (ret) | |
302 | return ret; | |
10d7e9a9 DL |
303 | |
304 | /* disable module firstly */ | |
305 | hisi_thermal_reset_enable(data->regs, 0); | |
306 | hisi_thermal_enable(data->regs, 0); | |
307 | ||
308 | /* select sensor id */ | |
309 | hisi_thermal_sensor_select(data->regs, sensor->id); | |
310 | ||
311 | /* setting the hdak time */ | |
312 | hisi_thermal_hdak_set(data->regs, 0); | |
313 | ||
314 | /* setting lag value between current temp and the threshold */ | |
315 | hisi_thermal_set_lag(data->regs, HISI_TEMP_LAG); | |
316 | ||
317 | /* enable for interrupt */ | |
318 | hisi_thermal_alarm_set(data->regs, sensor->thres_temp); | |
319 | ||
320 | hisi_thermal_reset_set(data->regs, HISI_TEMP_RESET); | |
321 | ||
322 | /* enable module */ | |
323 | hisi_thermal_reset_enable(data->regs, 1); | |
324 | hisi_thermal_enable(data->regs, 1); | |
325 | ||
326 | hisi_thermal_alarm_clear(data->regs, 0); | |
327 | hisi_thermal_alarm_enable(data->regs, 1); | |
328 | ||
329 | return 0; | |
330 | } | |
331 | ||
9a5238a9 | 332 | static int hisi_thermal_probe(struct platform_device *pdev) |
333 | { | |
334 | struct hisi_thermal_data *data; | |
335 | struct resource *res; | |
9a5238a9 | 336 | int ret; |
337 | ||
338 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | |
339 | if (!data) | |
340 | return -ENOMEM; | |
341 | ||
9a5238a9 | 342 | data->pdev = pdev; |
343 | ||
344 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
345 | data->regs = devm_ioremap_resource(&pdev->dev, res); | |
346 | if (IS_ERR(data->regs)) { | |
347 | dev_err(&pdev->dev, "failed to get io address\n"); | |
348 | return PTR_ERR(data->regs); | |
349 | } | |
350 | ||
351 | data->irq = platform_get_irq(pdev, 0); | |
352 | if (data->irq < 0) | |
353 | return data->irq; | |
354 | ||
9a5238a9 | 355 | platform_set_drvdata(pdev, data); |
356 | ||
357 | data->clk = devm_clk_get(&pdev->dev, "thermal_clk"); | |
358 | if (IS_ERR(data->clk)) { | |
359 | ret = PTR_ERR(data->clk); | |
360 | if (ret != -EPROBE_DEFER) | |
361 | dev_err(&pdev->dev, | |
362 | "failed to get thermal clk: %d\n", ret); | |
363 | return ret; | |
364 | } | |
365 | ||
ff4ec299 | 366 | ret = hisi_thermal_register_sensor(pdev, data, |
609f26dc | 367 | &data->sensor, |
ff4ec299 DL |
368 | HISI_DEFAULT_SENSOR); |
369 | if (ret) { | |
370 | dev_err(&pdev->dev, "failed to register thermal sensor: %d\n", | |
371 | ret); | |
372 | return ret; | |
9a5238a9 | 373 | } |
374 | ||
10d7e9a9 DL |
375 | ret = hisi_thermal_setup(data); |
376 | if (ret) { | |
377 | dev_err(&pdev->dev, "Failed to setup the sensor: %d\n", ret); | |
378 | return ret; | |
379 | } | |
ff4ec299 | 380 | |
10d7e9a9 | 381 | ret = devm_request_threaded_irq(&pdev->dev, data->irq, NULL, |
2cb4de78 | 382 | hisi_thermal_alarm_irq_thread, |
10d7e9a9 | 383 | IRQF_ONESHOT, "hisi_thermal", data); |
2cb4de78 DL |
384 | if (ret < 0) { |
385 | dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); | |
386 | return ret; | |
387 | } | |
388 | ||
609f26dc | 389 | hisi_thermal_toggle_sensor(&data->sensor, true); |
c176b10b | 390 | |
9a5238a9 | 391 | return 0; |
9a5238a9 | 392 | } |
393 | ||
394 | static int hisi_thermal_remove(struct platform_device *pdev) | |
395 | { | |
396 | struct hisi_thermal_data *data = platform_get_drvdata(pdev); | |
609f26dc | 397 | struct hisi_thermal_sensor *sensor = &data->sensor; |
9a5238a9 | 398 | |
ff4ec299 | 399 | hisi_thermal_toggle_sensor(sensor, false); |
9a5238a9 | 400 | hisi_thermal_disable_sensor(data); |
9a5238a9 | 401 | |
402 | return 0; | |
403 | } | |
404 | ||
405 | #ifdef CONFIG_PM_SLEEP | |
406 | static int hisi_thermal_suspend(struct device *dev) | |
407 | { | |
408 | struct hisi_thermal_data *data = dev_get_drvdata(dev); | |
409 | ||
410 | hisi_thermal_disable_sensor(data); | |
9a5238a9 | 411 | |
9a5238a9 | 412 | return 0; |
413 | } | |
414 | ||
415 | static int hisi_thermal_resume(struct device *dev) | |
416 | { | |
417 | struct hisi_thermal_data *data = dev_get_drvdata(dev); | |
418 | ||
943c0f6a | 419 | return hisi_thermal_setup(data); |
9a5238a9 | 420 | } |
421 | #endif | |
422 | ||
423 | static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops, | |
424 | hisi_thermal_suspend, hisi_thermal_resume); | |
425 | ||
426 | static struct platform_driver hisi_thermal_driver = { | |
427 | .driver = { | |
428 | .name = "hisi_thermal", | |
9a5238a9 | 429 | .pm = &hisi_thermal_pm_ops, |
430 | .of_match_table = of_hisi_thermal_match, | |
431 | }, | |
432 | .probe = hisi_thermal_probe, | |
433 | .remove = hisi_thermal_remove, | |
434 | }; | |
435 | ||
436 | module_platform_driver(hisi_thermal_driver); | |
437 | ||
438 | MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>"); | |
439 | MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>"); | |
440 | MODULE_DESCRIPTION("Hisilicon thermal driver"); | |
441 | MODULE_LICENSE("GPL v2"); |