diff options
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 4 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-vr41xx.c | 471 |
3 files changed, 476 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 929dd8090578..b4a252b6cdcc 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -147,6 +147,10 @@ config RTC_DRV_SA1100 | |||
147 | To compile this driver as a module, choose M here: the | 147 | To compile this driver as a module, choose M here: the |
148 | module will be called rtc-sa1100. | 148 | module will be called rtc-sa1100. |
149 | 149 | ||
150 | config RTC_DRV_VR41XX | ||
151 | tristate "NEC VR4100 series RTC" | ||
152 | depends on RTC_CLASS && CPU_VR41XX | ||
153 | |||
150 | config RTC_DRV_TEST | 154 | config RTC_DRV_TEST |
151 | tristate "Test driver/device" | 155 | tristate "Test driver/device" |
152 | depends on RTC_CLASS | 156 | depends on RTC_CLASS |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 8d4c7fe88d58..a9ca0f171686 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -19,3 +19,4 @@ obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o | |||
19 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o | 19 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o |
20 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o | 20 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o |
21 | obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o | 21 | obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o |
22 | obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o | ||
diff --git a/drivers/rtc/rtc-vr41xx.c b/drivers/rtc/rtc-vr41xx.c new file mode 100644 index 000000000000..4d49fd501198 --- /dev/null +++ b/drivers/rtc/rtc-vr41xx.c | |||
@@ -0,0 +1,471 @@ | |||
1 | /* | ||
2 | * Driver for NEC VR4100 series Real Time Clock unit. | ||
3 | * | ||
4 | * Copyright (C) 2003-2006 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
19 | */ | ||
20 | #include <linux/fs.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/ioport.h> | ||
23 | #include <linux/irq.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/rtc.h> | ||
27 | #include <linux/spinlock.h> | ||
28 | #include <linux/types.h> | ||
29 | |||
30 | #include <asm/div64.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/uaccess.h> | ||
33 | #include <asm/vr41xx/vr41xx.h> | ||
34 | |||
35 | MODULE_AUTHOR("Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp>"); | ||
36 | MODULE_DESCRIPTION("NEC VR4100 series RTC driver"); | ||
37 | MODULE_LICENSE("GPL"); | ||
38 | |||
39 | #define RTC1_TYPE1_START 0x0b0000c0UL | ||
40 | #define RTC1_TYPE1_END 0x0b0000dfUL | ||
41 | #define RTC2_TYPE1_START 0x0b0001c0UL | ||
42 | #define RTC2_TYPE1_END 0x0b0001dfUL | ||
43 | |||
44 | #define RTC1_TYPE2_START 0x0f000100UL | ||
45 | #define RTC1_TYPE2_END 0x0f00011fUL | ||
46 | #define RTC2_TYPE2_START 0x0f000120UL | ||
47 | #define RTC2_TYPE2_END 0x0f00013fUL | ||
48 | |||
49 | #define RTC1_SIZE 0x20 | ||
50 | #define RTC2_SIZE 0x20 | ||
51 | |||
52 | /* RTC 1 registers */ | ||
53 | #define ETIMELREG 0x00 | ||
54 | #define ETIMEMREG 0x02 | ||
55 | #define ETIMEHREG 0x04 | ||
56 | /* RFU */ | ||
57 | #define ECMPLREG 0x08 | ||
58 | #define ECMPMREG 0x0a | ||
59 | #define ECMPHREG 0x0c | ||
60 | /* RFU */ | ||
61 | #define RTCL1LREG 0x10 | ||
62 | #define RTCL1HREG 0x12 | ||
63 | #define RTCL1CNTLREG 0x14 | ||
64 | #define RTCL1CNTHREG 0x16 | ||
65 | #define RTCL2LREG 0x18 | ||
66 | #define RTCL2HREG 0x1a | ||
67 | #define RTCL2CNTLREG 0x1c | ||
68 | #define RTCL2CNTHREG 0x1e | ||
69 | |||
70 | /* RTC 2 registers */ | ||
71 | #define TCLKLREG 0x00 | ||
72 | #define TCLKHREG 0x02 | ||
73 | #define TCLKCNTLREG 0x04 | ||
74 | #define TCLKCNTHREG 0x06 | ||
75 | /* RFU */ | ||
76 | #define RTCINTREG 0x1e | ||
77 | #define TCLOCK_INT 0x08 | ||
78 | #define RTCLONG2_INT 0x04 | ||
79 | #define RTCLONG1_INT 0x02 | ||
80 | #define ELAPSEDTIME_INT 0x01 | ||
81 | |||
82 | #define RTC_FREQUENCY 32768 | ||
83 | #define MAX_PERIODIC_RATE 6553 | ||
84 | #define MAX_USER_PERIODIC_RATE 64 | ||
85 | |||
86 | static void __iomem *rtc1_base; | ||
87 | static void __iomem *rtc2_base; | ||
88 | |||
89 | #define rtc1_read(offset) readw(rtc1_base + (offset)) | ||
90 | #define rtc1_write(offset, value) writew((value), rtc1_base + (offset)) | ||
91 | |||
92 | #define rtc2_read(offset) readw(rtc2_base + (offset)) | ||
93 | #define rtc2_write(offset, value) writew((value), rtc2_base + (offset)) | ||
94 | |||
95 | static unsigned long epoch = 1970; /* Jan 1 1970 00:00:00 */ | ||
96 | |||
97 | static spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; | ||
98 | static char rtc_name[] = "RTC"; | ||
99 | static unsigned long periodic_frequency; | ||
100 | static unsigned long periodic_count; | ||
101 | |||
102 | struct resource rtc_resource[2] = { | ||
103 | { .name = rtc_name, | ||
104 | .flags = IORESOURCE_MEM, }, | ||
105 | { .name = rtc_name, | ||
106 | .flags = IORESOURCE_MEM, }, | ||
107 | }; | ||
108 | |||
109 | static inline unsigned long read_elapsed_second(void) | ||
110 | { | ||
111 | |||
112 | unsigned long first_low, first_mid, first_high; | ||
113 | |||
114 | unsigned long second_low, second_mid, second_high; | ||
115 | |||
116 | do { | ||
117 | first_low = rtc1_read(ETIMELREG); | ||
118 | first_mid = rtc1_read(ETIMEMREG); | ||
119 | first_high = rtc1_read(ETIMEHREG); | ||
120 | second_low = rtc1_read(ETIMELREG); | ||
121 | second_mid = rtc1_read(ETIMEMREG); | ||
122 | second_high = rtc1_read(ETIMEHREG); | ||
123 | } while (first_low != second_low || first_mid != second_mid || | ||
124 | first_high != second_high); | ||
125 | |||
126 | return (first_high << 17) | (first_mid << 1) | (first_low >> 15); | ||
127 | } | ||
128 | |||
129 | static inline void write_elapsed_second(unsigned long sec) | ||
130 | { | ||
131 | spin_lock_irq(&rtc_lock); | ||
132 | |||
133 | rtc1_write(ETIMELREG, (uint16_t)(sec << 15)); | ||
134 | rtc1_write(ETIMEMREG, (uint16_t)(sec >> 1)); | ||
135 | rtc1_write(ETIMEHREG, (uint16_t)(sec >> 17)); | ||
136 | |||
137 | spin_unlock_irq(&rtc_lock); | ||
138 | } | ||
139 | |||
140 | static void vr41xx_rtc_release(struct device *dev) | ||
141 | { | ||
142 | |||
143 | spin_lock_irq(&rtc_lock); | ||
144 | |||
145 | rtc1_write(ECMPLREG, 0); | ||
146 | rtc1_write(ECMPMREG, 0); | ||
147 | rtc1_write(ECMPHREG, 0); | ||
148 | rtc1_write(RTCL1LREG, 0); | ||
149 | rtc1_write(RTCL1HREG, 0); | ||
150 | |||
151 | spin_unlock_irq(&rtc_lock); | ||
152 | |||
153 | disable_irq(ELAPSEDTIME_IRQ); | ||
154 | disable_irq(RTCLONG1_IRQ); | ||
155 | } | ||
156 | |||
157 | static int vr41xx_rtc_read_time(struct device *dev, struct rtc_time *time) | ||
158 | { | ||
159 | unsigned long epoch_sec, elapsed_sec; | ||
160 | |||
161 | epoch_sec = mktime(epoch, 1, 1, 0, 0, 0); | ||
162 | elapsed_sec = read_elapsed_second(); | ||
163 | |||
164 | rtc_time_to_tm(epoch_sec + elapsed_sec, time); | ||
165 | |||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | static int vr41xx_rtc_set_time(struct device *dev, struct rtc_time *time) | ||
170 | { | ||
171 | unsigned long epoch_sec, current_sec; | ||
172 | |||
173 | epoch_sec = mktime(epoch, 1, 1, 0, 0, 0); | ||
174 | current_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, | ||
175 | time->tm_hour, time->tm_min, time->tm_sec); | ||
176 | |||
177 | write_elapsed_second(current_sec - epoch_sec); | ||
178 | |||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static int vr41xx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) | ||
183 | { | ||
184 | unsigned long low, mid, high; | ||
185 | struct rtc_time *time = &wkalrm->time; | ||
186 | |||
187 | spin_lock_irq(&rtc_lock); | ||
188 | |||
189 | low = rtc1_read(ECMPLREG); | ||
190 | mid = rtc1_read(ECMPMREG); | ||
191 | high = rtc1_read(ECMPHREG); | ||
192 | |||
193 | spin_unlock_irq(&rtc_lock); | ||
194 | |||
195 | rtc_time_to_tm((high << 17) | (mid << 1) | (low >> 15), time); | ||
196 | |||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | static int vr41xx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) | ||
201 | { | ||
202 | unsigned long alarm_sec; | ||
203 | struct rtc_time *time = &wkalrm->time; | ||
204 | |||
205 | alarm_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, | ||
206 | time->tm_hour, time->tm_min, time->tm_sec); | ||
207 | |||
208 | spin_lock_irq(&rtc_lock); | ||
209 | |||
210 | rtc1_write(ECMPLREG, (uint16_t)(alarm_sec << 15)); | ||
211 | rtc1_write(ECMPMREG, (uint16_t)(alarm_sec >> 1)); | ||
212 | rtc1_write(ECMPHREG, (uint16_t)(alarm_sec >> 17)); | ||
213 | |||
214 | spin_unlock_irq(&rtc_lock); | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static int vr41xx_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) | ||
220 | { | ||
221 | unsigned long count; | ||
222 | |||
223 | switch (cmd) { | ||
224 | case RTC_AIE_ON: | ||
225 | enable_irq(ELAPSEDTIME_IRQ); | ||
226 | break; | ||
227 | case RTC_AIE_OFF: | ||
228 | disable_irq(ELAPSEDTIME_IRQ); | ||
229 | break; | ||
230 | case RTC_PIE_ON: | ||
231 | enable_irq(RTCLONG1_IRQ); | ||
232 | break; | ||
233 | case RTC_PIE_OFF: | ||
234 | disable_irq(RTCLONG1_IRQ); | ||
235 | break; | ||
236 | case RTC_IRQP_READ: | ||
237 | return put_user(periodic_frequency, (unsigned long __user *)arg); | ||
238 | break; | ||
239 | case RTC_IRQP_SET: | ||
240 | if (arg > MAX_PERIODIC_RATE) | ||
241 | return -EINVAL; | ||
242 | |||
243 | if (arg > MAX_USER_PERIODIC_RATE && capable(CAP_SYS_RESOURCE) == 0) | ||
244 | return -EACCES; | ||
245 | |||
246 | periodic_frequency = arg; | ||
247 | |||
248 | count = RTC_FREQUENCY; | ||
249 | do_div(count, arg); | ||
250 | |||
251 | periodic_count = count; | ||
252 | |||
253 | spin_lock_irq(&rtc_lock); | ||
254 | |||
255 | rtc1_write(RTCL1LREG, count); | ||
256 | rtc1_write(RTCL1HREG, count >> 16); | ||
257 | |||
258 | spin_unlock_irq(&rtc_lock); | ||
259 | break; | ||
260 | case RTC_EPOCH_READ: | ||
261 | return put_user(epoch, (unsigned long __user *)arg); | ||
262 | case RTC_EPOCH_SET: | ||
263 | /* Doesn't support before 1900 */ | ||
264 | if (arg < 1900) | ||
265 | return -EINVAL; | ||
266 | |||
267 | if (capable(CAP_SYS_TIME) == 0) | ||
268 | return -EACCES; | ||
269 | |||
270 | epoch = arg; | ||
271 | break; | ||
272 | default: | ||
273 | return -EINVAL; | ||
274 | } | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static irqreturn_t elapsedtime_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
280 | { | ||
281 | struct platform_device *pdev = (struct platform_device *)dev_id; | ||
282 | struct rtc_device *rtc = platform_get_drvdata(pdev); | ||
283 | |||
284 | rtc2_write(RTCINTREG, ELAPSEDTIME_INT); | ||
285 | |||
286 | rtc_update_irq(&rtc->class_dev, 1, RTC_AF); | ||
287 | |||
288 | return IRQ_HANDLED; | ||
289 | } | ||
290 | |||
291 | static irqreturn_t rtclong1_interrupt(int irq, void *dev_id, struct pt_regs *regs) | ||
292 | { | ||
293 | struct platform_device *pdev = (struct platform_device *)dev_id; | ||
294 | struct rtc_device *rtc = platform_get_drvdata(pdev); | ||
295 | unsigned long count = periodic_count; | ||
296 | |||
297 | rtc2_write(RTCINTREG, RTCLONG1_INT); | ||
298 | |||
299 | rtc1_write(RTCL1LREG, count); | ||
300 | rtc1_write(RTCL1HREG, count >> 16); | ||
301 | |||
302 | rtc_update_irq(&rtc->class_dev, 1, RTC_PF); | ||
303 | |||
304 | return IRQ_HANDLED; | ||
305 | } | ||
306 | |||
307 | static struct rtc_class_ops vr41xx_rtc_ops = { | ||
308 | .release = vr41xx_rtc_release, | ||
309 | .ioctl = vr41xx_rtc_ioctl, | ||
310 | .read_time = vr41xx_rtc_read_time, | ||
311 | .set_time = vr41xx_rtc_set_time, | ||
312 | .read_alarm = vr41xx_rtc_read_alarm, | ||
313 | .set_alarm = vr41xx_rtc_set_alarm, | ||
314 | }; | ||
315 | |||
316 | static int __devinit rtc_probe(struct platform_device *pdev) | ||
317 | { | ||
318 | struct rtc_device *rtc; | ||
319 | unsigned int irq; | ||
320 | int retval; | ||
321 | |||
322 | if (pdev->num_resources != 2) | ||
323 | return -EBUSY; | ||
324 | |||
325 | rtc1_base = ioremap(pdev->resource[0].start, RTC1_SIZE); | ||
326 | if (rtc1_base == NULL) | ||
327 | return -EBUSY; | ||
328 | |||
329 | rtc2_base = ioremap(pdev->resource[1].start, RTC2_SIZE); | ||
330 | if (rtc2_base == NULL) { | ||
331 | iounmap(rtc1_base); | ||
332 | rtc1_base = NULL; | ||
333 | return -EBUSY; | ||
334 | } | ||
335 | |||
336 | rtc = rtc_device_register(rtc_name, &pdev->dev, &vr41xx_rtc_ops, THIS_MODULE); | ||
337 | if (IS_ERR(rtc)) { | ||
338 | iounmap(rtc1_base); | ||
339 | iounmap(rtc2_base); | ||
340 | rtc1_base = NULL; | ||
341 | rtc2_base = NULL; | ||
342 | return PTR_ERR(rtc); | ||
343 | } | ||
344 | |||
345 | spin_lock_irq(&rtc_lock); | ||
346 | |||
347 | rtc1_write(ECMPLREG, 0); | ||
348 | rtc1_write(ECMPMREG, 0); | ||
349 | rtc1_write(ECMPHREG, 0); | ||
350 | rtc1_write(RTCL1LREG, 0); | ||
351 | rtc1_write(RTCL1HREG, 0); | ||
352 | |||
353 | spin_unlock_irq(&rtc_lock); | ||
354 | |||
355 | irq = ELAPSEDTIME_IRQ; | ||
356 | retval = request_irq(irq, elapsedtime_interrupt, SA_INTERRUPT, | ||
357 | "elapsed_time", pdev); | ||
358 | if (retval == 0) { | ||
359 | irq = RTCLONG1_IRQ; | ||
360 | retval = request_irq(irq, rtclong1_interrupt, SA_INTERRUPT, | ||
361 | "rtclong1", pdev); | ||
362 | } | ||
363 | |||
364 | if (retval < 0) { | ||
365 | printk(KERN_ERR "rtc: IRQ%d is busy\n", irq); | ||
366 | rtc_device_unregister(rtc); | ||
367 | if (irq == RTCLONG1_IRQ) | ||
368 | free_irq(ELAPSEDTIME_IRQ, NULL); | ||
369 | iounmap(rtc1_base); | ||
370 | iounmap(rtc2_base); | ||
371 | rtc1_base = NULL; | ||
372 | rtc2_base = NULL; | ||
373 | return retval; | ||
374 | } | ||
375 | |||
376 | platform_set_drvdata(pdev, rtc); | ||
377 | |||
378 | disable_irq(ELAPSEDTIME_IRQ); | ||
379 | disable_irq(RTCLONG1_IRQ); | ||
380 | |||
381 | printk(KERN_INFO "rtc: Real Time Clock of NEC VR4100 series\n"); | ||
382 | |||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | static int __devexit rtc_remove(struct platform_device *pdev) | ||
387 | { | ||
388 | struct rtc_device *rtc; | ||
389 | |||
390 | rtc = platform_get_drvdata(pdev); | ||
391 | if (rtc != NULL) | ||
392 | rtc_device_unregister(rtc); | ||
393 | |||
394 | platform_set_drvdata(pdev, NULL); | ||
395 | |||
396 | free_irq(ELAPSEDTIME_IRQ, NULL); | ||
397 | free_irq(RTCLONG1_IRQ, NULL); | ||
398 | if (rtc1_base != NULL) | ||
399 | iounmap(rtc1_base); | ||
400 | if (rtc2_base != NULL) | ||
401 | iounmap(rtc2_base); | ||
402 | |||
403 | return 0; | ||
404 | } | ||
405 | |||
406 | static struct platform_device *rtc_platform_device; | ||
407 | |||
408 | static struct platform_driver rtc_platform_driver = { | ||
409 | .probe = rtc_probe, | ||
410 | .remove = __devexit_p(rtc_remove), | ||
411 | .driver = { | ||
412 | .name = rtc_name, | ||
413 | .owner = THIS_MODULE, | ||
414 | }, | ||
415 | }; | ||
416 | |||
417 | static int __init vr41xx_rtc_init(void) | ||
418 | { | ||
419 | int retval; | ||
420 | |||
421 | switch (current_cpu_data.cputype) { | ||
422 | case CPU_VR4111: | ||
423 | case CPU_VR4121: | ||
424 | rtc_resource[0].start = RTC1_TYPE1_START; | ||
425 | rtc_resource[0].end = RTC1_TYPE1_END; | ||
426 | rtc_resource[1].start = RTC2_TYPE1_START; | ||
427 | rtc_resource[1].end = RTC2_TYPE1_END; | ||
428 | break; | ||
429 | case CPU_VR4122: | ||
430 | case CPU_VR4131: | ||
431 | case CPU_VR4133: | ||
432 | rtc_resource[0].start = RTC1_TYPE2_START; | ||
433 | rtc_resource[0].end = RTC1_TYPE2_END; | ||
434 | rtc_resource[1].start = RTC2_TYPE2_START; | ||
435 | rtc_resource[1].end = RTC2_TYPE2_END; | ||
436 | break; | ||
437 | default: | ||
438 | return -ENODEV; | ||
439 | break; | ||
440 | } | ||
441 | |||
442 | rtc_platform_device = platform_device_alloc("RTC", -1); | ||
443 | if (rtc_platform_device == NULL) | ||
444 | return -ENOMEM; | ||
445 | |||
446 | retval = platform_device_add_resources(rtc_platform_device, | ||
447 | rtc_resource, ARRAY_SIZE(rtc_resource)); | ||
448 | |||
449 | if (retval == 0) | ||
450 | retval = platform_device_add(rtc_platform_device); | ||
451 | |||
452 | if (retval < 0) { | ||
453 | platform_device_put(rtc_platform_device); | ||
454 | return retval; | ||
455 | } | ||
456 | |||
457 | retval = platform_driver_register(&rtc_platform_driver); | ||
458 | if (retval < 0) | ||
459 | platform_device_unregister(rtc_platform_device); | ||
460 | |||
461 | return retval; | ||
462 | } | ||
463 | |||
464 | static void __exit vr41xx_rtc_exit(void) | ||
465 | { | ||
466 | platform_driver_unregister(&rtc_platform_driver); | ||
467 | platform_device_unregister(rtc_platform_device); | ||
468 | } | ||
469 | |||
470 | module_init(vr41xx_rtc_init); | ||
471 | module_exit(vr41xx_rtc_exit); | ||