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