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> | |
24 | #include <asm/io.h> | |
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 | ||
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) | |
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 | ||
7d12e780 | 76 | static irqreturn_t sh_rtc_interrupt(int irq, void *id) |
317a6104 PM |
77 | { |
78 | struct platform_device *pdev = id; | |
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 | ||
7d12e780 | 100 | static irqreturn_t sh_rtc_periodic(int irq, void *id) |
317a6104 PM |
101 | { |
102 | struct sh_rtc *rtc = dev_get_drvdata(id); | |
103 | ||
104 | spin_lock(&rtc->lock); | |
105 | ||
106 | rtc_update_irq(&rtc->rtc_dev->class_dev, 1, RTC_PF | RTC_IRQF); | |
107 | ||
108 | spin_unlock(&rtc->lock); | |
109 | ||
110 | return IRQ_HANDLED; | |
111 | } | |
112 | ||
113 | static inline void sh_rtc_setpie(struct device *dev, unsigned int enable) | |
114 | { | |
115 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
116 | unsigned int tmp; | |
117 | ||
118 | spin_lock_irq(&rtc->lock); | |
119 | ||
120 | tmp = readb(rtc->regbase + RCR2); | |
121 | ||
122 | if (enable) { | |
123 | tmp &= ~RCR2_PESMASK; | |
124 | tmp |= RCR2_PEF | (2 << 4); | |
125 | } else | |
126 | tmp &= ~(RCR2_PESMASK | RCR2_PEF); | |
127 | ||
128 | writeb(tmp, rtc->regbase + RCR2); | |
129 | ||
130 | spin_unlock_irq(&rtc->lock); | |
131 | } | |
132 | ||
133 | static inline void sh_rtc_setaie(struct device *dev, unsigned int enable) | |
134 | { | |
135 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
136 | unsigned int tmp; | |
137 | ||
138 | spin_lock_irq(&rtc->lock); | |
139 | ||
140 | tmp = readb(rtc->regbase + RCR1); | |
141 | ||
142 | if (enable) | |
143 | tmp |= RCR1_AIE; | |
144 | else | |
145 | tmp &= ~RCR1_AIE; | |
146 | ||
147 | writeb(tmp, rtc->regbase + RCR1); | |
148 | ||
149 | spin_unlock_irq(&rtc->lock); | |
150 | } | |
151 | ||
152 | static int sh_rtc_open(struct device *dev) | |
153 | { | |
154 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
155 | unsigned int tmp; | |
156 | int ret; | |
157 | ||
158 | tmp = readb(rtc->regbase + RCR1); | |
159 | tmp &= ~RCR1_CF; | |
160 | tmp |= RCR1_CIE; | |
161 | writeb(tmp, rtc->regbase + RCR1); | |
162 | ||
35f3c518 | 163 | ret = request_irq(rtc->periodic_irq, sh_rtc_periodic, IRQF_DISABLED, |
317a6104 PM |
164 | "sh-rtc period", dev); |
165 | if (unlikely(ret)) { | |
166 | dev_err(dev, "request period IRQ failed with %d, IRQ %d\n", | |
167 | ret, rtc->periodic_irq); | |
168 | return ret; | |
169 | } | |
170 | ||
35f3c518 | 171 | ret = request_irq(rtc->carry_irq, sh_rtc_interrupt, IRQF_DISABLED, |
317a6104 PM |
172 | "sh-rtc carry", dev); |
173 | if (unlikely(ret)) { | |
174 | dev_err(dev, "request carry IRQ failed with %d, IRQ %d\n", | |
175 | ret, rtc->carry_irq); | |
176 | free_irq(rtc->periodic_irq, dev); | |
177 | goto err_bad_carry; | |
178 | } | |
179 | ||
35f3c518 | 180 | ret = request_irq(rtc->alarm_irq, sh_rtc_interrupt, IRQF_DISABLED, |
317a6104 PM |
181 | "sh-rtc alarm", dev); |
182 | if (unlikely(ret)) { | |
183 | dev_err(dev, "request alarm IRQ failed with %d, IRQ %d\n", | |
184 | ret, rtc->alarm_irq); | |
185 | goto err_bad_alarm; | |
186 | } | |
187 | ||
188 | return 0; | |
189 | ||
190 | err_bad_alarm: | |
191 | free_irq(rtc->carry_irq, dev); | |
192 | err_bad_carry: | |
193 | free_irq(rtc->periodic_irq, dev); | |
194 | ||
195 | return ret; | |
196 | } | |
197 | ||
198 | static void sh_rtc_release(struct device *dev) | |
199 | { | |
200 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
201 | ||
202 | sh_rtc_setpie(dev, 0); | |
203 | ||
204 | free_irq(rtc->periodic_irq, dev); | |
205 | free_irq(rtc->carry_irq, dev); | |
206 | free_irq(rtc->alarm_irq, dev); | |
207 | } | |
208 | ||
209 | static int sh_rtc_proc(struct device *dev, struct seq_file *seq) | |
210 | { | |
211 | struct sh_rtc *rtc = dev_get_drvdata(dev); | |
212 | unsigned int tmp; | |
213 | ||
214 | tmp = readb(rtc->regbase + RCR1); | |
215 | seq_printf(seq, "alarm_IRQ\t: %s\n", | |
216 | (tmp & RCR1_AIE) ? "yes" : "no"); | |
217 | seq_printf(seq, "carry_IRQ\t: %s\n", | |
218 | (tmp & RCR1_CIE) ? "yes" : "no"); | |
219 | ||
220 | tmp = readb(rtc->regbase + RCR2); | |
221 | seq_printf(seq, "periodic_IRQ\t: %s\n", | |
222 | (tmp & RCR2_PEF) ? "yes" : "no"); | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
227 | static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) | |
228 | { | |
229 | unsigned int ret = -ENOIOCTLCMD; | |
230 | ||
231 | switch (cmd) { | |
232 | case RTC_PIE_OFF: | |
233 | case RTC_PIE_ON: | |
234 | sh_rtc_setpie(dev, cmd == RTC_PIE_ON); | |
235 | ret = 0; | |
236 | break; | |
237 | case RTC_AIE_OFF: | |
238 | case RTC_AIE_ON: | |
239 | sh_rtc_setaie(dev, cmd == RTC_AIE_ON); | |
240 | ret = 0; | |
241 | break; | |
242 | } | |
243 | ||
244 | return ret; | |
245 | } | |
246 | ||
247 | static int sh_rtc_read_time(struct device *dev, struct rtc_time *tm) | |
248 | { | |
249 | struct platform_device *pdev = to_platform_device(dev); | |
250 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
251 | unsigned int sec128, sec2, yr, yr100, cf_bit; | |
252 | ||
253 | do { | |
254 | unsigned int tmp; | |
255 | ||
256 | spin_lock_irq(&rtc->lock); | |
257 | ||
258 | tmp = readb(rtc->regbase + RCR1); | |
259 | tmp &= ~RCR1_CF; /* Clear CF-bit */ | |
260 | tmp |= RCR1_CIE; | |
261 | writeb(tmp, rtc->regbase + RCR1); | |
262 | ||
263 | sec128 = readb(rtc->regbase + R64CNT); | |
264 | ||
265 | tm->tm_sec = BCD2BIN(readb(rtc->regbase + RSECCNT)); | |
266 | tm->tm_min = BCD2BIN(readb(rtc->regbase + RMINCNT)); | |
267 | tm->tm_hour = BCD2BIN(readb(rtc->regbase + RHRCNT)); | |
268 | tm->tm_wday = BCD2BIN(readb(rtc->regbase + RWKCNT)); | |
269 | tm->tm_mday = BCD2BIN(readb(rtc->regbase + RDAYCNT)); | |
270 | tm->tm_mon = BCD2BIN(readb(rtc->regbase + RMONCNT)); | |
271 | ||
272 | #if defined(CONFIG_CPU_SH4) | |
273 | yr = readw(rtc->regbase + RYRCNT); | |
274 | yr100 = BCD2BIN(yr >> 8); | |
275 | yr &= 0xff; | |
276 | #else | |
277 | yr = readb(rtc->regbase + RYRCNT); | |
278 | yr100 = BCD2BIN((yr == 0x99) ? 0x19 : 0x20); | |
279 | #endif | |
280 | ||
281 | tm->tm_year = (yr100 * 100 + BCD2BIN(yr)) - 1900; | |
282 | ||
283 | sec2 = readb(rtc->regbase + R64CNT); | |
284 | cf_bit = readb(rtc->regbase + RCR1) & RCR1_CF; | |
285 | ||
286 | spin_unlock_irq(&rtc->lock); | |
287 | } while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 0); | |
288 | ||
289 | #if RTC_BIT_INVERTED != 0 | |
290 | if ((sec128 & RTC_BIT_INVERTED)) | |
291 | tm->tm_sec--; | |
292 | #endif | |
293 | ||
294 | dev_dbg(&dev, "%s: tm is secs=%d, mins=%d, hours=%d, " | |
295 | "mday=%d, mon=%d, year=%d, wday=%d\n", | |
296 | __FUNCTION__, | |
297 | tm->tm_sec, tm->tm_min, tm->tm_hour, | |
298 | tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); | |
299 | ||
300 | if (rtc_valid_tm(tm) < 0) | |
301 | dev_err(dev, "invalid date\n"); | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
306 | static int sh_rtc_set_time(struct device *dev, struct rtc_time *tm) | |
307 | { | |
308 | struct platform_device *pdev = to_platform_device(dev); | |
309 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
310 | unsigned int tmp; | |
311 | int year; | |
312 | ||
313 | spin_lock_irq(&rtc->lock); | |
314 | ||
315 | /* Reset pre-scaler & stop RTC */ | |
316 | tmp = readb(rtc->regbase + RCR2); | |
317 | tmp |= RCR2_RESET; | |
318 | writeb(tmp, rtc->regbase + RCR2); | |
319 | ||
320 | writeb(BIN2BCD(tm->tm_sec), rtc->regbase + RSECCNT); | |
321 | writeb(BIN2BCD(tm->tm_min), rtc->regbase + RMINCNT); | |
322 | writeb(BIN2BCD(tm->tm_hour), rtc->regbase + RHRCNT); | |
323 | writeb(BIN2BCD(tm->tm_wday), rtc->regbase + RWKCNT); | |
324 | writeb(BIN2BCD(tm->tm_mday), rtc->regbase + RDAYCNT); | |
325 | writeb(BIN2BCD(tm->tm_mon), rtc->regbase + RMONCNT); | |
326 | ||
327 | #ifdef CONFIG_CPU_SH3 | |
328 | year = tm->tm_year % 100; | |
329 | writeb(BIN2BCD(year), rtc->regbase + RYRCNT); | |
330 | #else | |
331 | year = (BIN2BCD((tm->tm_year + 1900) / 100) << 8) | | |
332 | BIN2BCD(tm->tm_year % 100); | |
333 | writew(year, rtc->regbase + RYRCNT); | |
334 | #endif | |
335 | ||
336 | /* Start RTC */ | |
337 | tmp = readb(rtc->regbase + RCR2); | |
338 | tmp &= ~RCR2_RESET; | |
339 | tmp |= RCR2_RTCEN | RCR2_START; | |
340 | writeb(tmp, rtc->regbase + RCR2); | |
341 | ||
342 | spin_unlock_irq(&rtc->lock); | |
343 | ||
344 | return 0; | |
345 | } | |
346 | ||
347 | static struct rtc_class_ops sh_rtc_ops = { | |
348 | .open = sh_rtc_open, | |
349 | .release = sh_rtc_release, | |
350 | .ioctl = sh_rtc_ioctl, | |
351 | .read_time = sh_rtc_read_time, | |
352 | .set_time = sh_rtc_set_time, | |
353 | .proc = sh_rtc_proc, | |
354 | }; | |
355 | ||
356 | static int __devinit sh_rtc_probe(struct platform_device *pdev) | |
357 | { | |
358 | struct sh_rtc *rtc; | |
359 | struct resource *res; | |
360 | int ret = -ENOENT; | |
361 | ||
362 | rtc = kzalloc(sizeof(struct sh_rtc), GFP_KERNEL); | |
363 | if (unlikely(!rtc)) | |
364 | return -ENOMEM; | |
365 | ||
366 | spin_lock_init(&rtc->lock); | |
367 | ||
368 | rtc->periodic_irq = platform_get_irq(pdev, 0); | |
369 | if (unlikely(rtc->periodic_irq < 0)) { | |
370 | dev_err(&pdev->dev, "No IRQ for period\n"); | |
371 | goto err_badres; | |
372 | } | |
373 | ||
374 | rtc->carry_irq = platform_get_irq(pdev, 1); | |
375 | if (unlikely(rtc->carry_irq < 0)) { | |
376 | dev_err(&pdev->dev, "No IRQ for carry\n"); | |
377 | goto err_badres; | |
378 | } | |
379 | ||
380 | rtc->alarm_irq = platform_get_irq(pdev, 2); | |
381 | if (unlikely(rtc->alarm_irq < 0)) { | |
382 | dev_err(&pdev->dev, "No IRQ for alarm\n"); | |
383 | goto err_badres; | |
384 | } | |
385 | ||
386 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
387 | if (unlikely(res == NULL)) { | |
388 | dev_err(&pdev->dev, "No IO resource\n"); | |
389 | goto err_badres; | |
390 | } | |
391 | ||
392 | rtc->regsize = res->end - res->start + 1; | |
393 | ||
394 | rtc->res = request_mem_region(res->start, rtc->regsize, pdev->name); | |
395 | if (unlikely(!rtc->res)) { | |
396 | ret = -EBUSY; | |
397 | goto err_badres; | |
398 | } | |
399 | ||
400 | rtc->regbase = (void __iomem *)rtc->res->start; | |
401 | if (unlikely(!rtc->regbase)) { | |
402 | ret = -EINVAL; | |
403 | goto err_badmap; | |
404 | } | |
405 | ||
406 | rtc->rtc_dev = rtc_device_register("sh", &pdev->dev, | |
407 | &sh_rtc_ops, THIS_MODULE); | |
408 | if (IS_ERR(rtc)) { | |
409 | ret = PTR_ERR(rtc->rtc_dev); | |
410 | goto err_badmap; | |
411 | } | |
412 | ||
413 | platform_set_drvdata(pdev, rtc); | |
414 | ||
415 | return 0; | |
416 | ||
417 | err_badmap: | |
418 | release_resource(rtc->res); | |
419 | err_badres: | |
420 | kfree(rtc); | |
421 | ||
422 | return ret; | |
423 | } | |
424 | ||
425 | static int __devexit sh_rtc_remove(struct platform_device *pdev) | |
426 | { | |
427 | struct sh_rtc *rtc = platform_get_drvdata(pdev); | |
428 | ||
429 | if (likely(rtc->rtc_dev)) | |
430 | rtc_device_unregister(rtc->rtc_dev); | |
431 | ||
432 | sh_rtc_setpie(&pdev->dev, 0); | |
433 | sh_rtc_setaie(&pdev->dev, 0); | |
434 | ||
435 | release_resource(rtc->res); | |
436 | ||
437 | platform_set_drvdata(pdev, NULL); | |
438 | ||
439 | kfree(rtc); | |
440 | ||
441 | return 0; | |
442 | } | |
443 | static struct platform_driver sh_rtc_platform_driver = { | |
444 | .driver = { | |
445 | .name = "sh-rtc", | |
446 | .owner = THIS_MODULE, | |
447 | }, | |
448 | .probe = sh_rtc_probe, | |
449 | .remove = __devexit_p(sh_rtc_remove), | |
450 | }; | |
451 | ||
452 | static int __init sh_rtc_init(void) | |
453 | { | |
454 | return platform_driver_register(&sh_rtc_platform_driver); | |
455 | } | |
456 | ||
457 | static void __exit sh_rtc_exit(void) | |
458 | { | |
459 | platform_driver_unregister(&sh_rtc_platform_driver); | |
460 | } | |
461 | ||
462 | module_init(sh_rtc_init); | |
463 | module_exit(sh_rtc_exit); | |
464 | ||
465 | MODULE_DESCRIPTION("SuperH on-chip RTC driver"); | |
466 | MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); | |
467 | MODULE_LICENSE("GPL"); |