Commit | Line | Data |
---|---|---|
317a6104 PM |
1 | /* |
2 | * SuperH On-Chip RTC Support | |
3 | * | |
4 | * Copyright (C) 2006 Paul Mundt | |
5 | * | |
6 | * Based on the old arch/sh/kernel/cpu/rtc.c by: | |
7 | * | |
8 | * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> | |
9 | * Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka | |
10 | * | |
11 | * This file is subject to the terms and conditions of the GNU General Public | |
12 | * License. See the file "COPYING" in the main directory of this archive | |
13 | * for more details. | |
14 | */ | |
15 | #include <linux/module.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/bcd.h> | |
18 | #include <linux/rtc.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/seq_file.h> | |
22 | #include <linux/interrupt.h> | |
23 | #include <linux/spinlock.h> | |
31ccb081 | 24 | #include <linux/io.h> |
317a6104 PM |
25 | |
26 | #ifdef CONFIG_CPU_SH3 | |
27 | #define rtc_reg_size sizeof(u16) | |
28 | #define RTC_BIT_INVERTED 0 /* No bug on SH7708, SH7709A */ | |
29 | #elif defined(CONFIG_CPU_SH4) | |
30 | #define rtc_reg_size sizeof(u32) | |
31 | #define RTC_BIT_INVERTED 0x40 /* bug on SH7750, SH7750S */ | |
32 | #endif | |
33 | ||
34 | #define RTC_REG(r) ((r) * rtc_reg_size) | |
35 | ||
31ccb081 JL |
36 | #define R64CNT RTC_REG(0) |
37 | #define RSECCNT RTC_REG(1) | |
38 | #define RMINCNT RTC_REG(2) | |
39 | #define RHRCNT RTC_REG(3) | |
40 | #define RWKCNT RTC_REG(4) | |
41 | #define RDAYCNT RTC_REG(5) | |
42 | #define RMONCNT RTC_REG(6) | |
43 | #define RYRCNT RTC_REG(7) | |
44 | #define RSECAR RTC_REG(8) | |
45 | #define RMINAR RTC_REG(9) | |
46 | #define RHRAR RTC_REG(10) | |
47 | #define RWKAR RTC_REG(11) | |
48 | #define RDAYAR RTC_REG(12) | |
49 | #define RMONAR RTC_REG(13) | |
50 | #define RCR1 RTC_REG(14) | |
51 | #define RCR2 RTC_REG(15) | |
317a6104 PM |
52 | |
53 | /* RCR1 Bits */ | |
54 | #define RCR1_CF 0x80 /* Carry Flag */ | |
55 | #define RCR1_CIE 0x10 /* Carry Interrupt Enable */ | |
56 | #define RCR1_AIE 0x08 /* Alarm Interrupt Enable */ | |
57 | #define RCR1_AF 0x01 /* Alarm Flag */ | |
58 | ||
59 | /* RCR2 Bits */ | |
60 | #define RCR2_PEF 0x80 /* PEriodic interrupt Flag */ | |
61 | #define RCR2_PESMASK 0x70 /* Periodic interrupt Set */ | |
62 | #define RCR2_RTCEN 0x08 /* ENable RTC */ | |
63 | #define RCR2_ADJ 0x04 /* ADJustment (30-second) */ | |
64 | #define RCR2_RESET 0x02 /* Reset bit */ | |
65 | #define RCR2_START 0x01 /* Start bit */ | |
66 | ||
67 | struct sh_rtc { | |
68 | void __iomem *regbase; | |
69 | unsigned long regsize; | |
70 | struct resource *res; | |
71 | unsigned int alarm_irq, periodic_irq, carry_irq; | |
72 | struct rtc_device *rtc_dev; | |
73 | spinlock_t lock; | |
74 | }; | |
75 | ||
31ccb081 | 76 | static irqreturn_t sh_rtc_interrupt(int irq, void *dev_id) |
317a6104 | 77 | { |
31ccb081 | 78 | struct platform_device *pdev = to_platform_device(dev_id); |
317a6104 PM |
79 | struct sh_rtc *rtc = platform_get_drvdata(pdev); |
80 | unsigned int tmp, events = 0; | |
81 | ||
82 | spin_lock(&rtc->lock); | |
83 | ||
84 | tmp = readb(rtc->regbase + RCR1); | |
85 | ||
86 | if (tmp & RCR1_AF) | |
87 | events |= RTC_AF | RTC_IRQF; | |
88 | ||
89 | tmp &= ~(RCR1_CF | RCR1_AF); | |
90 | ||
91 | writeb(tmp, rtc->regbase + RCR1); | |
92 | ||
93 | rtc_update_irq(&rtc->rtc_dev->class_dev, 1, events); | |
94 | ||
95 | spin_unlock(&rtc->lock); | |
96 | ||
97 | return IRQ_HANDLED; | |
98 | } | |
99 | ||
31ccb081 | 100 | static irqreturn_t sh_rtc_periodic(int irq, void *dev_id) |
317a6104 | 101 | { |
31ccb081 JL |
102 | struct platform_device *pdev = to_platform_device(dev_id); |
103 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
317a6104 PM |
104 | |
105 | spin_lock(&rtc->lock); | |
106 | ||
107 | rtc_update_irq(&rtc->rtc_dev->class_dev, 1, RTC_PF | RTC_IRQF); | |
108 | ||
109 | spin_unlock(&rtc->lock); | |
110 | ||
111 | return IRQ_HANDLED; | |
112 | } | |
113 | ||
114 | static inline void sh_rtc_setpie(struct device *dev, unsigned int enable) | |
115 | { | |
116 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
117 | unsigned int tmp; | |
118 | ||
119 | spin_lock_irq(&rtc->lock); | |
120 | ||
121 | tmp = readb(rtc->regbase + RCR2); | |
122 | ||
123 | if (enable) { | |
124 | tmp &= ~RCR2_PESMASK; | |
125 | tmp |= RCR2_PEF | (2 << 4); | |
126 | } else | |
127 | tmp &= ~(RCR2_PESMASK | RCR2_PEF); | |
128 | ||
129 | writeb(tmp, rtc->regbase + RCR2); | |
130 | ||
131 | spin_unlock_irq(&rtc->lock); | |
132 | } | |
133 | ||
134 | static inline void sh_rtc_setaie(struct device *dev, unsigned int enable) | |
135 | { | |
136 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
137 | unsigned int tmp; | |
138 | ||
139 | spin_lock_irq(&rtc->lock); | |
140 | ||
141 | tmp = readb(rtc->regbase + RCR1); | |
142 | ||
143 | if (enable) | |
144 | tmp |= RCR1_AIE; | |
145 | else | |
146 | tmp &= ~RCR1_AIE; | |
147 | ||
148 | writeb(tmp, rtc->regbase + RCR1); | |
149 | ||
150 | spin_unlock_irq(&rtc->lock); | |
151 | } | |
152 | ||
153 | static int sh_rtc_open(struct device *dev) | |
154 | { | |
155 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
156 | unsigned int tmp; | |
157 | int ret; | |
158 | ||
159 | tmp = readb(rtc->regbase + RCR1); | |
160 | tmp &= ~RCR1_CF; | |
161 | tmp |= RCR1_CIE; | |
162 | writeb(tmp, rtc->regbase + RCR1); | |
163 | ||
35f3c518 | 164 | ret = request_irq(rtc->periodic_irq, sh_rtc_periodic, IRQF_DISABLED, |
317a6104 PM |
165 | "sh-rtc period", dev); |
166 | if (unlikely(ret)) { | |
167 | dev_err(dev, "request period IRQ failed with %d, IRQ %d\n", | |
168 | ret, rtc->periodic_irq); | |
169 | return ret; | |
170 | } | |
171 | ||
35f3c518 | 172 | ret = request_irq(rtc->carry_irq, sh_rtc_interrupt, IRQF_DISABLED, |
317a6104 PM |
173 | "sh-rtc carry", dev); |
174 | if (unlikely(ret)) { | |
175 | dev_err(dev, "request carry IRQ failed with %d, IRQ %d\n", | |
176 | ret, rtc->carry_irq); | |
177 | free_irq(rtc->periodic_irq, dev); | |
178 | goto err_bad_carry; | |
179 | } | |
180 | ||
35f3c518 | 181 | ret = request_irq(rtc->alarm_irq, sh_rtc_interrupt, IRQF_DISABLED, |
317a6104 PM |
182 | "sh-rtc alarm", dev); |
183 | if (unlikely(ret)) { | |
184 | dev_err(dev, "request alarm IRQ failed with %d, IRQ %d\n", | |
185 | ret, rtc->alarm_irq); | |
186 | goto err_bad_alarm; | |
187 | } | |
188 | ||
189 | return 0; | |
190 | ||
191 | err_bad_alarm: | |
192 | free_irq(rtc->carry_irq, dev); | |
193 | err_bad_carry: | |
194 | free_irq(rtc->periodic_irq, dev); | |
195 | ||
196 | return ret; | |
197 | } | |
198 | ||
199 | static void sh_rtc_release(struct device *dev) | |
200 | { | |
201 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
202 | ||
203 | sh_rtc_setpie(dev, 0); | |
204 | ||
205 | free_irq(rtc->periodic_irq, dev); | |
206 | free_irq(rtc->carry_irq, dev); | |
207 | free_irq(rtc->alarm_irq, dev); | |
208 | } | |
209 | ||
210 | static int sh_rtc_proc(struct device *dev, struct seq_file *seq) | |
211 | { | |
212 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
213 | unsigned int tmp; | |
214 | ||
215 | tmp = readb(rtc->regbase + RCR1); | |
216 | seq_printf(seq, "alarm_IRQ\t: %s\n", | |
217 | (tmp & RCR1_AIE) ? "yes" : "no"); | |
218 | seq_printf(seq, "carry_IRQ\t: %s\n", | |
219 | (tmp & RCR1_CIE) ? "yes" : "no"); | |
220 | ||
221 | tmp = readb(rtc->regbase + RCR2); | |
222 | seq_printf(seq, "periodic_IRQ\t: %s\n", | |
223 | (tmp & RCR2_PEF) ? "yes" : "no"); | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) | |
229 | { | |
230 | unsigned int ret = -ENOIOCTLCMD; | |
231 | ||
232 | switch (cmd) { | |
233 | case RTC_PIE_OFF: | |
234 | case RTC_PIE_ON: | |
235 | sh_rtc_setpie(dev, cmd == RTC_PIE_ON); | |
236 | ret = 0; | |
237 | break; | |
238 | case RTC_AIE_OFF: | |
239 | case RTC_AIE_ON: | |
240 | sh_rtc_setaie(dev, cmd == RTC_AIE_ON); | |
241 | ret = 0; | |
242 | break; | |
243 | } | |
244 | ||
245 | return ret; | |
246 | } | |
247 | ||
248 | static int sh_rtc_read_time(struct device *dev, struct rtc_time *tm) | |
249 | { | |
250 | struct platform_device *pdev = to_platform_device(dev); | |
251 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
252 | unsigned int sec128, sec2, yr, yr100, cf_bit; | |
253 | ||
254 | do { | |
255 | unsigned int tmp; | |
256 | ||
257 | spin_lock_irq(&rtc->lock); | |
258 | ||
259 | tmp = readb(rtc->regbase + RCR1); | |
260 | tmp &= ~RCR1_CF; /* Clear CF-bit */ | |
261 | tmp |= RCR1_CIE; | |
262 | writeb(tmp, rtc->regbase + RCR1); | |
263 | ||
264 | sec128 = readb(rtc->regbase + R64CNT); | |
265 | ||
266 | tm->tm_sec = BCD2BIN(readb(rtc->regbase + RSECCNT)); | |
267 | tm->tm_min = BCD2BIN(readb(rtc->regbase + RMINCNT)); | |
268 | tm->tm_hour = BCD2BIN(readb(rtc->regbase + RHRCNT)); | |
269 | tm->tm_wday = BCD2BIN(readb(rtc->regbase + RWKCNT)); | |
270 | tm->tm_mday = BCD2BIN(readb(rtc->regbase + RDAYCNT)); | |
271 | tm->tm_mon = BCD2BIN(readb(rtc->regbase + RMONCNT)); | |
272 | ||
273 | #if defined(CONFIG_CPU_SH4) | |
274 | yr = readw(rtc->regbase + RYRCNT); | |
275 | yr100 = BCD2BIN(yr >> 8); | |
276 | yr &= 0xff; | |
277 | #else | |
278 | yr = readb(rtc->regbase + RYRCNT); | |
279 | yr100 = BCD2BIN((yr == 0x99) ? 0x19 : 0x20); | |
280 | #endif | |
281 | ||
282 | tm->tm_year = (yr100 * 100 + BCD2BIN(yr)) - 1900; | |
283 | ||
284 | sec2 = readb(rtc->regbase + R64CNT); | |
285 | cf_bit = readb(rtc->regbase + RCR1) & RCR1_CF; | |
286 | ||
287 | spin_unlock_irq(&rtc->lock); | |
288 | } while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 0); | |
289 | ||
290 | #if RTC_BIT_INVERTED != 0 | |
291 | if ((sec128 & RTC_BIT_INVERTED)) | |
292 | tm->tm_sec--; | |
293 | #endif | |
294 | ||
295 | dev_dbg(&dev, "%s: tm is secs=%d, mins=%d, hours=%d, " | |
296 | "mday=%d, mon=%d, year=%d, wday=%d\n", | |
297 | __FUNCTION__, | |
298 | tm->tm_sec, tm->tm_min, tm->tm_hour, | |
299 | tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); | |
300 | ||
301 | if (rtc_valid_tm(tm) < 0) | |
302 | dev_err(dev, "invalid date\n"); | |
303 | ||
304 | return 0; | |
305 | } | |
306 | ||
307 | static int sh_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
308 | { | |
309 | struct platform_device *pdev = to_platform_device(dev); | |
310 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
311 | unsigned int tmp; | |
312 | int year; | |
313 | ||
314 | spin_lock_irq(&rtc->lock); | |
315 | ||
316 | /* Reset pre-scaler & stop RTC */ | |
317 | tmp = readb(rtc->regbase + RCR2); | |
318 | tmp |= RCR2_RESET; | |
319 | writeb(tmp, rtc->regbase + RCR2); | |
320 | ||
321 | writeb(BIN2BCD(tm->tm_sec), rtc->regbase + RSECCNT); | |
322 | writeb(BIN2BCD(tm->tm_min), rtc->regbase + RMINCNT); | |
323 | writeb(BIN2BCD(tm->tm_hour), rtc->regbase + RHRCNT); | |
324 | writeb(BIN2BCD(tm->tm_wday), rtc->regbase + RWKCNT); | |
325 | writeb(BIN2BCD(tm->tm_mday), rtc->regbase + RDAYCNT); | |
326 | writeb(BIN2BCD(tm->tm_mon), rtc->regbase + RMONCNT); | |
327 | ||
328 | #ifdef CONFIG_CPU_SH3 | |
329 | year = tm->tm_year % 100; | |
330 | writeb(BIN2BCD(year), rtc->regbase + RYRCNT); | |
331 | #else | |
332 | year = (BIN2BCD((tm->tm_year + 1900) / 100) << 8) | | |
333 | BIN2BCD(tm->tm_year % 100); | |
334 | writew(year, rtc->regbase + RYRCNT); | |
335 | #endif | |
336 | ||
337 | /* Start RTC */ | |
338 | tmp = readb(rtc->regbase + RCR2); | |
339 | tmp &= ~RCR2_RESET; | |
340 | tmp |= RCR2_RTCEN | RCR2_START; | |
341 | writeb(tmp, rtc->regbase + RCR2); | |
342 | ||
343 | spin_unlock_irq(&rtc->lock); | |
344 | ||
345 | return 0; | |
346 | } | |
347 | ||
348 | static struct rtc_class_ops sh_rtc_ops = { | |
349 | .open = sh_rtc_open, | |
350 | .release = sh_rtc_release, | |
351 | .ioctl = sh_rtc_ioctl, | |
352 | .read_time = sh_rtc_read_time, | |
353 | .set_time = sh_rtc_set_time, | |
354 | .proc = sh_rtc_proc, | |
355 | }; | |
356 | ||
357 | static int __devinit sh_rtc_probe(struct platform_device *pdev) | |
358 | { | |
359 | struct sh_rtc *rtc; | |
360 | struct resource *res; | |
361 | int ret = -ENOENT; | |
362 | ||
363 | rtc = kzalloc(sizeof(struct sh_rtc), GFP_KERNEL); | |
364 | if (unlikely(!rtc)) | |
365 | return -ENOMEM; | |
366 | ||
367 | spin_lock_init(&rtc->lock); | |
368 | ||
369 | rtc->periodic_irq = platform_get_irq(pdev, 0); | |
370 | if (unlikely(rtc->periodic_irq < 0)) { | |
371 | dev_err(&pdev->dev, "No IRQ for period\n"); | |
372 | goto err_badres; | |
373 | } | |
374 | ||
375 | rtc->carry_irq = platform_get_irq(pdev, 1); | |
376 | if (unlikely(rtc->carry_irq < 0)) { | |
377 | dev_err(&pdev->dev, "No IRQ for carry\n"); | |
378 | goto err_badres; | |
379 | } | |
380 | ||
381 | rtc->alarm_irq = platform_get_irq(pdev, 2); | |
382 | if (unlikely(rtc->alarm_irq < 0)) { | |
383 | dev_err(&pdev->dev, "No IRQ for alarm\n"); | |
384 | goto err_badres; | |
385 | } | |
386 | ||
387 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
388 | if (unlikely(res == NULL)) { | |
389 | dev_err(&pdev->dev, "No IO resource\n"); | |
390 | goto err_badres; | |
391 | } | |
392 | ||
393 | rtc->regsize = res->end - res->start + 1; | |
394 | ||
395 | rtc->res = request_mem_region(res->start, rtc->regsize, pdev->name); | |
396 | if (unlikely(!rtc->res)) { | |
397 | ret = -EBUSY; | |
398 | goto err_badres; | |
399 | } | |
400 | ||
401 | rtc->regbase = (void __iomem *)rtc->res->start; | |
402 | if (unlikely(!rtc->regbase)) { | |
403 | ret = -EINVAL; | |
404 | goto err_badmap; | |
405 | } | |
406 | ||
407 | rtc->rtc_dev = rtc_device_register("sh", &pdev->dev, | |
408 | &sh_rtc_ops, THIS_MODULE); | |
409 | if (IS_ERR(rtc)) { | |
410 | ret = PTR_ERR(rtc->rtc_dev); | |
411 | goto err_badmap; | |
412 | } | |
413 | ||
414 | platform_set_drvdata(pdev, rtc); | |
415 | ||
416 | return 0; | |
417 | ||
418 | err_badmap: | |
419 | release_resource(rtc->res); | |
420 | err_badres: | |
421 | kfree(rtc); | |
422 | ||
423 | return ret; | |
424 | } | |
425 | ||
426 | static int __devexit sh_rtc_remove(struct platform_device *pdev) | |
427 | { | |
428 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
429 | ||
430 | if (likely(rtc->rtc_dev)) | |
431 | rtc_device_unregister(rtc->rtc_dev); | |
432 | ||
433 | sh_rtc_setpie(&pdev->dev, 0); | |
434 | sh_rtc_setaie(&pdev->dev, 0); | |
435 | ||
436 | release_resource(rtc->res); | |
437 | ||
438 | platform_set_drvdata(pdev, NULL); | |
439 | ||
440 | kfree(rtc); | |
441 | ||
442 | return 0; | |
443 | } | |
444 | static struct platform_driver sh_rtc_platform_driver = { | |
445 | .driver = { | |
446 | .name = "sh-rtc", | |
447 | .owner = THIS_MODULE, | |
448 | }, | |
449 | .probe = sh_rtc_probe, | |
450 | .remove = __devexit_p(sh_rtc_remove), | |
451 | }; | |
452 | ||
453 | static int __init sh_rtc_init(void) | |
454 | { | |
455 | return platform_driver_register(&sh_rtc_platform_driver); | |
456 | } | |
457 | ||
458 | static void __exit sh_rtc_exit(void) | |
459 | { | |
460 | platform_driver_unregister(&sh_rtc_platform_driver); | |
461 | } | |
462 | ||
463 | module_init(sh_rtc_init); | |
464 | module_exit(sh_rtc_exit); | |
465 | ||
466 | MODULE_DESCRIPTION("SuperH on-chip RTC driver"); | |
467 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); | |
468 | MODULE_LICENSE("GPL"); |