Commit | Line | Data |
---|---|---|
1e426ffd KM |
1 | /* |
2 | * R-Car THS/TSC thermal sensor driver | |
3 | * | |
4 | * Copyright (C) 2012 Renesas Solutions Corp. | |
5 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; version 2 of the License. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, write to the Free Software Foundation, Inc., | |
18 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
19 | */ | |
20 | #include <linux/delay.h> | |
21 | #include <linux/err.h> | |
e0a5172e KM |
22 | #include <linux/irq.h> |
23 | #include <linux/interrupt.h> | |
1e426ffd KM |
24 | #include <linux/io.h> |
25 | #include <linux/module.h> | |
26 | #include <linux/platform_device.h> | |
d2a73e22 | 27 | #include <linux/reboot.h> |
1e426ffd KM |
28 | #include <linux/slab.h> |
29 | #include <linux/spinlock.h> | |
30 | #include <linux/thermal.h> | |
31 | ||
d2a73e22 | 32 | #define IDLE_INTERVAL 5000 |
33 | ||
e0a5172e KM |
34 | #define COMMON_STR 0x00 |
35 | #define COMMON_ENR 0x04 | |
36 | #define COMMON_INTMSK 0x0c | |
37 | ||
38 | #define REG_POSNEG 0x20 | |
39 | #define REG_FILONOFF 0x28 | |
e9137a58 KM |
40 | #define REG_THSCR 0x2c |
41 | #define REG_THSSR 0x30 | |
e0a5172e | 42 | #define REG_INTCTRL 0x34 |
1e426ffd KM |
43 | |
44 | /* THSCR */ | |
f8f53e18 | 45 | #define CPCTL (1 << 12) |
1e426ffd KM |
46 | |
47 | /* THSSR */ | |
48 | #define CTEMP 0x3f | |
49 | ||
3676d1dd KM |
50 | struct rcar_thermal_common { |
51 | void __iomem *base; | |
52 | struct device *dev; | |
53 | struct list_head head; | |
e0a5172e | 54 | spinlock_t lock; |
3676d1dd | 55 | }; |
1e426ffd KM |
56 | |
57 | struct rcar_thermal_priv { | |
58 | void __iomem *base; | |
3676d1dd KM |
59 | struct rcar_thermal_common *common; |
60 | struct thermal_zone_device *zone; | |
e0a5172e | 61 | struct delayed_work work; |
b2bbc6a2 | 62 | struct mutex lock; |
3676d1dd | 63 | struct list_head list; |
e0a5172e KM |
64 | int id; |
65 | int ctemp; | |
1e426ffd KM |
66 | }; |
67 | ||
3676d1dd KM |
68 | #define rcar_thermal_for_each_priv(pos, common) \ |
69 | list_for_each_entry(pos, &common->head, list) | |
70 | ||
c499703e | 71 | #define MCELSIUS(temp) ((temp) * 1000) |
9dde8f86 | 72 | #define rcar_zone_to_priv(zone) ((zone)->devdata) |
3676d1dd KM |
73 | #define rcar_priv_to_dev(priv) ((priv)->common->dev) |
74 | #define rcar_has_irq_support(priv) ((priv)->common->base) | |
e0a5172e KM |
75 | #define rcar_id_to_shift(priv) ((priv)->id * 8) |
76 | ||
77 | #ifdef DEBUG | |
78 | # define rcar_force_update_temp(priv) 1 | |
79 | #else | |
80 | # define rcar_force_update_temp(priv) 0 | |
81 | #endif | |
c499703e | 82 | |
1e426ffd KM |
83 | /* |
84 | * basic functions | |
85 | */ | |
e9137a58 KM |
86 | #define rcar_thermal_common_read(c, r) \ |
87 | _rcar_thermal_common_read(c, COMMON_ ##r) | |
88 | static u32 _rcar_thermal_common_read(struct rcar_thermal_common *common, | |
89 | u32 reg) | |
90 | { | |
91 | return ioread32(common->base + reg); | |
92 | } | |
93 | ||
94 | #define rcar_thermal_common_write(c, r, d) \ | |
95 | _rcar_thermal_common_write(c, COMMON_ ##r, d) | |
96 | static void _rcar_thermal_common_write(struct rcar_thermal_common *common, | |
97 | u32 reg, u32 data) | |
98 | { | |
99 | iowrite32(data, common->base + reg); | |
100 | } | |
101 | ||
102 | #define rcar_thermal_common_bset(c, r, m, d) \ | |
103 | _rcar_thermal_common_bset(c, COMMON_ ##r, m, d) | |
104 | static void _rcar_thermal_common_bset(struct rcar_thermal_common *common, | |
105 | u32 reg, u32 mask, u32 data) | |
106 | { | |
107 | u32 val; | |
108 | ||
109 | val = ioread32(common->base + reg); | |
110 | val &= ~mask; | |
111 | val |= (data & mask); | |
112 | iowrite32(val, common->base + reg); | |
113 | } | |
e9137a58 KM |
114 | |
115 | #define rcar_thermal_read(p, r) _rcar_thermal_read(p, REG_ ##r) | |
116 | static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) | |
1e426ffd | 117 | { |
b2bbc6a2 | 118 | return ioread32(priv->base + reg); |
1e426ffd KM |
119 | } |
120 | ||
e9137a58 KM |
121 | #define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, REG_ ##r, d) |
122 | static void _rcar_thermal_write(struct rcar_thermal_priv *priv, | |
123 | u32 reg, u32 data) | |
1e426ffd | 124 | { |
1e426ffd | 125 | iowrite32(data, priv->base + reg); |
1e426ffd | 126 | } |
1e426ffd | 127 | |
e9137a58 KM |
128 | #define rcar_thermal_bset(p, r, m, d) _rcar_thermal_bset(p, REG_ ##r, m, d) |
129 | static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, | |
130 | u32 mask, u32 data) | |
1e426ffd | 131 | { |
1e426ffd KM |
132 | u32 val; |
133 | ||
1e426ffd KM |
134 | val = ioread32(priv->base + reg); |
135 | val &= ~mask; | |
136 | val |= (data & mask); | |
137 | iowrite32(val, priv->base + reg); | |
1e426ffd KM |
138 | } |
139 | ||
140 | /* | |
141 | * zone device functions | |
142 | */ | |
e0a5172e | 143 | static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) |
1e426ffd | 144 | { |
f8f53e18 KM |
145 | struct device *dev = rcar_priv_to_dev(priv); |
146 | int i; | |
147 | int ctemp, old, new; | |
f0e68fc3 | 148 | int ret = -EINVAL; |
f8f53e18 | 149 | |
b2bbc6a2 KM |
150 | mutex_lock(&priv->lock); |
151 | ||
f8f53e18 KM |
152 | /* |
153 | * TSC decides a value of CPTAP automatically, | |
154 | * and this is the conditions which validate interrupt. | |
155 | */ | |
156 | rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL); | |
157 | ||
158 | ctemp = 0; | |
159 | old = ~0; | |
160 | for (i = 0; i < 128; i++) { | |
1e426ffd KM |
161 | /* |
162 | * we need to wait 300us after changing comparator offset | |
163 | * to get stable temperature. | |
164 | * see "Usage Notes" on datasheet | |
165 | */ | |
1e426ffd KM |
166 | udelay(300); |
167 | ||
f8f53e18 KM |
168 | new = rcar_thermal_read(priv, THSSR) & CTEMP; |
169 | if (new == old) { | |
170 | ctemp = new; | |
1e426ffd KM |
171 | break; |
172 | } | |
f8f53e18 | 173 | old = new; |
1e426ffd KM |
174 | } |
175 | ||
f8f53e18 KM |
176 | if (!ctemp) { |
177 | dev_err(dev, "thermal sensor was broken\n"); | |
f0e68fc3 | 178 | goto err_out_unlock; |
f8f53e18 KM |
179 | } |
180 | ||
e0a5172e KM |
181 | /* |
182 | * enable IRQ | |
183 | */ | |
184 | if (rcar_has_irq_support(priv)) { | |
185 | rcar_thermal_write(priv, FILONOFF, 0); | |
186 | ||
187 | /* enable Rising/Falling edge interrupt */ | |
188 | rcar_thermal_write(priv, POSNEG, 0x1); | |
189 | rcar_thermal_write(priv, INTCTRL, (((ctemp - 0) << 8) | | |
190 | ((ctemp - 1) << 0))); | |
191 | } | |
192 | ||
193 | dev_dbg(dev, "thermal%d %d -> %d\n", priv->id, priv->ctemp, ctemp); | |
194 | ||
195 | priv->ctemp = ctemp; | |
f0e68fc3 WY |
196 | ret = 0; |
197 | err_out_unlock: | |
b2bbc6a2 | 198 | mutex_unlock(&priv->lock); |
f0e68fc3 | 199 | return ret; |
1e426ffd KM |
200 | } |
201 | ||
e0a5172e KM |
202 | static int rcar_thermal_get_temp(struct thermal_zone_device *zone, |
203 | unsigned long *temp) | |
204 | { | |
205 | struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); | |
206 | ||
207 | if (!rcar_has_irq_support(priv) || rcar_force_update_temp(priv)) | |
208 | rcar_thermal_update_temp(priv); | |
209 | ||
210 | mutex_lock(&priv->lock); | |
211 | *temp = MCELSIUS((priv->ctemp * 5) - 65); | |
212 | mutex_unlock(&priv->lock); | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
d2a73e22 | 217 | static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone, |
218 | int trip, enum thermal_trip_type *type) | |
219 | { | |
220 | struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); | |
3676d1dd | 221 | struct device *dev = rcar_priv_to_dev(priv); |
d2a73e22 | 222 | |
223 | /* see rcar_thermal_get_temp() */ | |
224 | switch (trip) { | |
225 | case 0: /* +90 <= temp */ | |
226 | *type = THERMAL_TRIP_CRITICAL; | |
227 | break; | |
228 | default: | |
3676d1dd | 229 | dev_err(dev, "rcar driver trip error\n"); |
d2a73e22 | 230 | return -EINVAL; |
231 | } | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone, | |
237 | int trip, unsigned long *temp) | |
238 | { | |
239 | struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); | |
3676d1dd | 240 | struct device *dev = rcar_priv_to_dev(priv); |
d2a73e22 | 241 | |
242 | /* see rcar_thermal_get_temp() */ | |
243 | switch (trip) { | |
244 | case 0: /* +90 <= temp */ | |
245 | *temp = MCELSIUS(90); | |
246 | break; | |
247 | default: | |
3676d1dd | 248 | dev_err(dev, "rcar driver trip error\n"); |
d2a73e22 | 249 | return -EINVAL; |
250 | } | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | static int rcar_thermal_notify(struct thermal_zone_device *zone, | |
256 | int trip, enum thermal_trip_type type) | |
257 | { | |
258 | struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); | |
3676d1dd | 259 | struct device *dev = rcar_priv_to_dev(priv); |
d2a73e22 | 260 | |
261 | switch (type) { | |
262 | case THERMAL_TRIP_CRITICAL: | |
263 | /* FIXME */ | |
3676d1dd | 264 | dev_warn(dev, "Thermal reached to critical temperature\n"); |
d2a73e22 | 265 | break; |
266 | default: | |
267 | break; | |
268 | } | |
269 | ||
270 | return 0; | |
271 | } | |
272 | ||
1e426ffd | 273 | static struct thermal_zone_device_ops rcar_thermal_zone_ops = { |
d2a73e22 | 274 | .get_temp = rcar_thermal_get_temp, |
275 | .get_trip_type = rcar_thermal_get_trip_type, | |
276 | .get_trip_temp = rcar_thermal_get_trip_temp, | |
277 | .notify = rcar_thermal_notify, | |
1e426ffd KM |
278 | }; |
279 | ||
e0a5172e KM |
280 | /* |
281 | * interrupt | |
282 | */ | |
283 | #define rcar_thermal_irq_enable(p) _rcar_thermal_irq_ctrl(p, 1) | |
284 | #define rcar_thermal_irq_disable(p) _rcar_thermal_irq_ctrl(p, 0) | |
285 | static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable) | |
286 | { | |
287 | struct rcar_thermal_common *common = priv->common; | |
288 | unsigned long flags; | |
289 | u32 mask = 0x3 << rcar_id_to_shift(priv); /* enable Rising/Falling */ | |
290 | ||
291 | spin_lock_irqsave(&common->lock, flags); | |
292 | ||
293 | rcar_thermal_common_bset(common, INTMSK, mask, enable ? 0 : mask); | |
294 | ||
295 | spin_unlock_irqrestore(&common->lock, flags); | |
296 | } | |
297 | ||
298 | static void rcar_thermal_work(struct work_struct *work) | |
299 | { | |
300 | struct rcar_thermal_priv *priv; | |
301 | ||
302 | priv = container_of(work, struct rcar_thermal_priv, work.work); | |
303 | ||
304 | rcar_thermal_update_temp(priv); | |
305 | rcar_thermal_irq_enable(priv); | |
306 | thermal_zone_device_update(priv->zone); | |
307 | } | |
308 | ||
309 | static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status) | |
310 | { | |
311 | struct device *dev = rcar_priv_to_dev(priv); | |
312 | ||
313 | status = (status >> rcar_id_to_shift(priv)) & 0x3; | |
314 | ||
315 | if (status & 0x3) { | |
316 | dev_dbg(dev, "thermal%d %s%s\n", | |
317 | priv->id, | |
318 | (status & 0x2) ? "Rising " : "", | |
319 | (status & 0x1) ? "Falling" : ""); | |
320 | } | |
321 | ||
322 | return status; | |
323 | } | |
324 | ||
325 | static irqreturn_t rcar_thermal_irq(int irq, void *data) | |
326 | { | |
327 | struct rcar_thermal_common *common = data; | |
328 | struct rcar_thermal_priv *priv; | |
329 | unsigned long flags; | |
330 | u32 status, mask; | |
331 | ||
332 | spin_lock_irqsave(&common->lock, flags); | |
333 | ||
334 | mask = rcar_thermal_common_read(common, INTMSK); | |
335 | status = rcar_thermal_common_read(common, STR); | |
336 | rcar_thermal_common_write(common, STR, 0x000F0F0F & mask); | |
337 | ||
338 | spin_unlock_irqrestore(&common->lock, flags); | |
339 | ||
340 | status = status & ~mask; | |
341 | ||
342 | /* | |
343 | * check the status | |
344 | */ | |
345 | rcar_thermal_for_each_priv(priv, common) { | |
346 | if (rcar_thermal_had_changed(priv, status)) { | |
347 | rcar_thermal_irq_disable(priv); | |
348 | schedule_delayed_work(&priv->work, | |
349 | msecs_to_jiffies(300)); | |
350 | } | |
351 | } | |
352 | ||
353 | return IRQ_HANDLED; | |
354 | } | |
355 | ||
1e426ffd KM |
356 | /* |
357 | * platform functions | |
358 | */ | |
359 | static int rcar_thermal_probe(struct platform_device *pdev) | |
360 | { | |
3676d1dd | 361 | struct rcar_thermal_common *common; |
1e426ffd | 362 | struct rcar_thermal_priv *priv; |
3676d1dd KM |
363 | struct device *dev = &pdev->dev; |
364 | struct resource *res, *irq; | |
365 | int mres = 0; | |
366 | int i; | |
e0a5172e | 367 | int idle = IDLE_INTERVAL; |
1e426ffd | 368 | |
3676d1dd KM |
369 | common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); |
370 | if (!common) { | |
371 | dev_err(dev, "Could not allocate common\n"); | |
1e426ffd KM |
372 | return -ENOMEM; |
373 | } | |
374 | ||
3676d1dd | 375 | INIT_LIST_HEAD(&common->head); |
e0a5172e | 376 | spin_lock_init(&common->lock); |
3676d1dd KM |
377 | common->dev = dev; |
378 | ||
379 | irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
380 | if (irq) { | |
e0a5172e KM |
381 | int ret; |
382 | ||
3676d1dd KM |
383 | /* |
384 | * platform has IRQ support. | |
385 | * Then, drier use common register | |
386 | */ | |
387 | res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); | |
388 | if (!res) { | |
389 | dev_err(dev, "Could not get platform resource\n"); | |
390 | return -ENODEV; | |
391 | } | |
392 | ||
e0a5172e KM |
393 | ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 0, |
394 | dev_name(dev), common); | |
395 | if (ret) { | |
396 | dev_err(dev, "irq request failed\n "); | |
397 | return ret; | |
398 | } | |
399 | ||
3676d1dd KM |
400 | /* |
401 | * rcar_has_irq_support() will be enabled | |
402 | */ | |
403 | common->base = devm_request_and_ioremap(dev, res); | |
404 | if (!common->base) { | |
405 | dev_err(dev, "Unable to ioremap thermal register\n"); | |
406 | return -ENOMEM; | |
407 | } | |
e0a5172e KM |
408 | |
409 | /* enable temperature comparation */ | |
410 | rcar_thermal_common_write(common, ENR, 0x00030303); | |
411 | ||
412 | idle = 0; /* polling delaye is not needed */ | |
1e426ffd KM |
413 | } |
414 | ||
3676d1dd KM |
415 | for (i = 0;; i++) { |
416 | res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); | |
417 | if (!res) | |
418 | break; | |
419 | ||
420 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
421 | if (!priv) { | |
422 | dev_err(dev, "Could not allocate priv\n"); | |
423 | return -ENOMEM; | |
424 | } | |
425 | ||
426 | priv->base = devm_request_and_ioremap(dev, res); | |
427 | if (!priv->base) { | |
428 | dev_err(dev, "Unable to ioremap priv register\n"); | |
429 | return -ENOMEM; | |
430 | } | |
431 | ||
432 | priv->common = common; | |
e0a5172e | 433 | priv->id = i; |
3676d1dd KM |
434 | mutex_init(&priv->lock); |
435 | INIT_LIST_HEAD(&priv->list); | |
e0a5172e KM |
436 | INIT_DELAYED_WORK(&priv->work, rcar_thermal_work); |
437 | rcar_thermal_update_temp(priv); | |
3676d1dd KM |
438 | |
439 | priv->zone = thermal_zone_device_register("rcar_thermal", | |
440 | 1, 0, priv, | |
441 | &rcar_thermal_zone_ops, NULL, 0, | |
e0a5172e | 442 | idle); |
3676d1dd KM |
443 | if (IS_ERR(priv->zone)) { |
444 | dev_err(dev, "can't register thermal zone\n"); | |
445 | goto error_unregister; | |
446 | } | |
447 | ||
448 | list_move_tail(&priv->list, &common->head); | |
e0a5172e KM |
449 | |
450 | if (rcar_has_irq_support(priv)) | |
451 | rcar_thermal_irq_enable(priv); | |
1e426ffd KM |
452 | } |
453 | ||
3676d1dd | 454 | platform_set_drvdata(pdev, common); |
1e426ffd | 455 | |
3676d1dd | 456 | dev_info(dev, "%d sensor proved\n", i); |
1e426ffd KM |
457 | |
458 | return 0; | |
3676d1dd KM |
459 | |
460 | error_unregister: | |
461 | rcar_thermal_for_each_priv(priv, common) | |
462 | thermal_zone_device_unregister(priv->zone); | |
463 | ||
464 | return -ENODEV; | |
1e426ffd KM |
465 | } |
466 | ||
467 | static int rcar_thermal_remove(struct platform_device *pdev) | |
468 | { | |
3676d1dd KM |
469 | struct rcar_thermal_common *common = platform_get_drvdata(pdev); |
470 | struct rcar_thermal_priv *priv; | |
471 | ||
472 | rcar_thermal_for_each_priv(priv, common) | |
473 | thermal_zone_device_unregister(priv->zone); | |
1e426ffd | 474 | |
1e426ffd KM |
475 | platform_set_drvdata(pdev, NULL); |
476 | ||
1e426ffd KM |
477 | return 0; |
478 | } | |
479 | ||
f5b6d45f | 480 | static const struct of_device_id rcar_thermal_dt_ids[] = { |
76cc1887 KM |
481 | { .compatible = "renesas,rcar-thermal", }, |
482 | {}, | |
483 | }; | |
484 | MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids); | |
485 | ||
1e426ffd KM |
486 | static struct platform_driver rcar_thermal_driver = { |
487 | .driver = { | |
488 | .name = "rcar_thermal", | |
76cc1887 | 489 | .of_match_table = rcar_thermal_dt_ids, |
1e426ffd KM |
490 | }, |
491 | .probe = rcar_thermal_probe, | |
492 | .remove = rcar_thermal_remove, | |
493 | }; | |
494 | module_platform_driver(rcar_thermal_driver); | |
495 | ||
496 | MODULE_LICENSE("GPL"); | |
497 | MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver"); | |
498 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |