Root/package/rtc-rv5c386a/src/rtc.c

1/*
2 * Real Time Clock driver for WL-HDD
3 *
4 * Copyright (C) 2007 Andreas Engel
5 *
6 * Hacked together mostly by copying the relevant code parts from:
7 * drivers/i2c/i2c-bcm5365.c
8 * drivers/i2c/i2c-algo-bit.c
9 * drivers/char/rtc.c
10 *
11 * Note 1:
12 * This module uses the standard char device (10,135), while the Asus module
13 * rtcdrv.o uses (12,0). So, both can coexist which might be handy during
14 * development (but see the comment in rtc_open()).
15 *
16 * Note 2:
17 * You might need to set the clock once after loading the driver the first
18 * time because the driver switches the chip into 24h mode if it is running
19 * in 12h mode.
20 *
21 * Usage:
22 * For compatibility reasons with the original asus driver, the time can be
23 * read and set via the /dev/rtc device entry. The only accepted data format
24 * is "YYYY:MM:DD:W:HH:MM:SS\n". See OpenWrt wiki for a script which handles
25 * this format.
26 *
27 * In addition, this driver supports the standard ioctl() calls for setting
28 * and reading the hardware clock, so the ordinary hwclock utility can also
29 * be used.
30 *
31 * This program is free software; you can redistribute it and/or
32 * modify it under the terms of the GNU General Public License
33 * as published by the Free Software Foundation; either version
34 * 2 of the License, or (at your option) any later version.
35 *
36 * TODO:
37 * - add a /proc/driver/rtc interface?
38 * - make the battery failure bit available through the /proc interface?
39 *
40 * $Id: rtc.c 7 2007-05-25 19:37:01Z ae $
41 */
42
43#include <linux/module.h>
44#include <linux/kmod.h>
45#include <linux/kernel.h>
46#include <linux/types.h>
47#include <linux/miscdevice.h>
48#include <linux/ioport.h>
49#include <linux/fcntl.h>
50#include <linux/init.h>
51#include <linux/spinlock.h>
52#include <linux/rtc.h>
53#include <linux/delay.h>
54#include <linux/version.h>
55#include <linux/smp_lock.h>
56
57#include <asm/uaccess.h>
58#include <asm/system.h>
59
60#include "gpio.h"
61
62#define RTC_IS_OPEN 0x01 /* Means /dev/rtc is in use. */
63
64/* Can be changed via a module parameter. */
65static int rtc_debug = 0;
66
67static unsigned long rtc_status = 0; /* Bitmapped status byte. */
68
69static spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED;
70
71/* These settings are platform dependents. */
72unsigned int sda_index = 0;
73unsigned int scl_index = 0;
74
75#define I2C_READ_MASK 1
76#define I2C_WRITE_MASK 0
77
78#define I2C_ACK 1
79#define I2C_NAK 0
80
81#define RTC_EPOCH 1900
82#define RTC_I2C_ADDRESS (0x32 << 1)
83#define RTC_24HOUR_MODE_MASK 0x20
84#define RTC_PM_MASK 0x20
85#define RTC_VDET_MASK 0x40
86#define RTC_Y2K_MASK 0x80
87
88/*
89 * Delay in microseconds for generating the pulses on the I2C bus. We use
90 * a rather conservative setting here. See datasheet of the RTC chip.
91 */
92#define ADAP_DELAY 50
93
94/* Avoid spurious compiler warnings. */
95#define UNUSED __attribute__((unused))
96
97MODULE_AUTHOR("Andreas Engel");
98MODULE_LICENSE("GPL");
99
100/* Test stolen from switch-adm.c. */
101#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,52)
102module_param(rtc_debug, int, 0);
103#else
104MODULE_PARM(rtc_debug, "i");
105#endif
106
107static inline void sdalo(void)
108{
109    gpio_direction_output(sda_index, 1);
110    udelay(ADAP_DELAY);
111}
112
113static inline void sdahi(void)
114{
115    gpio_direction_input(sda_index);
116    udelay(ADAP_DELAY);
117}
118
119static inline void scllo(void)
120{
121   gpio_direction_output(scl_index, 1);
122    udelay(ADAP_DELAY);
123}
124
125static inline int getscl(void)
126{
127    return (gpio_get_value(scl_index));
128}
129
130static inline int getsda(void)
131{
132    return (gpio_get_value(sda_index));
133}
134
135/*
136 * We shouldn't simply set the SCL pin to high. Like SDA, the SCL line is
137 * bidirectional too. According to the I2C spec, the slave is allowed to
138 * pull down the SCL line to slow down the clock, so we need to check this.
139 * Generally, we'd need a timeout here, but in our case, we just check the
140 * line, assuming the RTC chip behaves well.
141 */
142static int sclhi(void)
143{
144    gpio_direction_input(scl_index);
145    udelay(ADAP_DELAY);
146    if (!getscl()) {
147        printk(KERN_ERR "SCL pin should be low\n");
148        return -ETIMEDOUT;
149    }
150    return 0;
151}
152
153static void i2c_start(void)
154{
155    sdalo();
156    scllo();
157}
158
159static void i2c_stop(void)
160{
161    sdalo();
162    sclhi();
163    sdahi();
164}
165
166static int i2c_outb(int c)
167{
168    int i;
169    int ack;
170
171    /* assert: scl is low */
172    for (i = 7; i >= 0; i--) {
173        if (c & ( 1 << i )) {
174            sdahi();
175        } else {
176            sdalo();
177        }
178        if (sclhi() < 0) { /* timed out */
179            sdahi(); /* we don't want to block the net */
180            return -ETIMEDOUT;
181        };
182        scllo();
183    }
184    sdahi();
185    if (sclhi() < 0) {
186        return -ETIMEDOUT;
187    };
188    /* read ack: SDA should be pulled down by slave */
189    ack = getsda() == 0; /* ack: sda is pulled low ->success. */
190    scllo();
191
192    if (rtc_debug)
193        printk(KERN_DEBUG "i2c_outb(0x%02x) -> %s\n",
194               c, ack ? "ACK": "NAK");
195
196    return ack; /* return 1 if device acked */
197    /* assert: scl is low (sda undef) */
198}
199
200static int i2c_inb(int ack)
201{
202    int i;
203    unsigned int indata = 0;
204
205    /* assert: scl is low */
206
207    sdahi();
208    for (i = 0; i < 8; i++) {
209        if (sclhi() < 0) {
210            return -ETIMEDOUT;
211        };
212        indata *= 2;
213        if (getsda())
214            indata |= 0x01;
215        scllo();
216    }
217    if (ack) {
218        sdalo();
219    } else {
220        sdahi();
221    }
222
223    if (sclhi() < 0) {
224        sdahi();
225        return -ETIMEDOUT;
226    }
227    scllo();
228    sdahi();
229
230    if (rtc_debug)
231        printk(KERN_DEBUG "i2c_inb() -> 0x%02x\n", indata);
232
233    /* assert: scl is low */
234    return indata & 0xff;
235}
236
237static void i2c_init(void)
238{
239    /* no gpio_control for EXTIF */
240    // gpio_control(sda_mask | scl_mask, 0);
241
242   gpio_set_value(sda_index, 0);
243   gpio_set_value(scl_index, 0);
244    sdahi();
245    sclhi();
246}
247
248static int rtc_open(UNUSED struct inode *inode, UNUSED struct file *filp)
249{
250    spin_lock_irq(&rtc_lock);
251
252    if (rtc_status & RTC_IS_OPEN) {
253        spin_unlock_irq(&rtc_lock);
254        return -EBUSY;
255    }
256
257    rtc_status |= RTC_IS_OPEN;
258
259    /*
260     * The following call is only necessary if we use both this driver and
261     * the proprietary one from asus at the same time (which, b.t.w. only
262     * makes sense during development). Otherwise, each access via the asus
263     * driver will make access via this driver impossible.
264     */
265    i2c_init();
266
267    spin_unlock_irq(&rtc_lock);
268
269    return 0;
270}
271
272static int rtc_release(UNUSED struct inode *inode, UNUSED struct file *filp)
273{
274    /* No need for locking here. */
275    rtc_status &= ~RTC_IS_OPEN;
276    return 0;
277}
278
279static int from_bcd(int bcdnum)
280{
281    int fac, num = 0;
282
283    for (fac = 1; bcdnum; fac *= 10) {
284        num += (bcdnum % 16) * fac;
285        bcdnum /= 16;
286    }
287
288    return num;
289}
290
291static int to_bcd(int decnum)
292{
293    int fac, num = 0;
294
295    for (fac = 1; decnum; fac *= 16) {
296        num += (decnum % 10) * fac;
297        decnum /= 10;
298    }
299
300    return num;
301}
302
303static void get_rtc_time(struct rtc_time *rtc_tm)
304{
305    int cr2;
306
307    /*
308     * Read date and time from the RTC. We use read method (3).
309     */
310
311    i2c_start();
312    i2c_outb(RTC_I2C_ADDRESS | I2C_READ_MASK);
313    cr2 = i2c_inb(I2C_ACK);
314    rtc_tm->tm_sec = i2c_inb(I2C_ACK);
315    rtc_tm->tm_min = i2c_inb(I2C_ACK);
316    rtc_tm->tm_hour = i2c_inb(I2C_ACK);
317    rtc_tm->tm_wday = i2c_inb(I2C_ACK);
318    rtc_tm->tm_mday = i2c_inb(I2C_ACK);
319    rtc_tm->tm_mon = i2c_inb(I2C_ACK);
320    rtc_tm->tm_year = i2c_inb(I2C_NAK);
321    i2c_stop();
322
323    if (cr2 & RTC_VDET_MASK) {
324        printk(KERN_WARNING "***RTC BATTERY FAILURE***\n");
325    }
326
327    /* Handle century bit */
328    if (rtc_tm->tm_mon & RTC_Y2K_MASK) {
329        rtc_tm->tm_mon &= ~RTC_Y2K_MASK;
330        rtc_tm->tm_year += 0x100;
331    }
332
333    rtc_tm->tm_sec = from_bcd(rtc_tm->tm_sec);
334    rtc_tm->tm_min = from_bcd(rtc_tm->tm_min);
335    rtc_tm->tm_hour = from_bcd(rtc_tm->tm_hour);
336    rtc_tm->tm_mday = from_bcd(rtc_tm->tm_mday);
337    rtc_tm->tm_mon = from_bcd(rtc_tm->tm_mon) - 1;
338    rtc_tm->tm_year = from_bcd(rtc_tm->tm_year);
339
340    rtc_tm->tm_isdst = -1; /* DST not known */
341}
342
343static void set_rtc_time(struct rtc_time *rtc_tm)
344{
345    rtc_tm->tm_sec = to_bcd(rtc_tm->tm_sec);
346    rtc_tm->tm_min = to_bcd(rtc_tm->tm_min);
347    rtc_tm->tm_hour = to_bcd(rtc_tm->tm_hour);
348    rtc_tm->tm_mday = to_bcd(rtc_tm->tm_mday);
349    rtc_tm->tm_mon = to_bcd(rtc_tm->tm_mon + 1);
350    rtc_tm->tm_year = to_bcd(rtc_tm->tm_year);
351
352    if (rtc_tm->tm_year >= 0x100) {
353        rtc_tm->tm_year -= 0x100;
354        rtc_tm->tm_mon |= RTC_Y2K_MASK;
355    }
356
357    i2c_start();
358    i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK);
359    i2c_outb(0x00); /* set starting register to 0 (=seconds) */
360    i2c_outb(rtc_tm->tm_sec);
361    i2c_outb(rtc_tm->tm_min);
362    i2c_outb(rtc_tm->tm_hour);
363    i2c_outb(rtc_tm->tm_wday);
364    i2c_outb(rtc_tm->tm_mday);
365    i2c_outb(rtc_tm->tm_mon);
366    i2c_outb(rtc_tm->tm_year);
367    i2c_stop();
368}
369
370static ssize_t rtc_write(UNUSED struct file *filp, const char *buf,
371                         size_t count, loff_t *ppos)
372{
373    struct rtc_time rtc_tm;
374    char buffer[23];
375    char *p;
376
377    if (!capable(CAP_SYS_TIME))
378        return -EACCES;
379
380    if (ppos != &filp->f_pos)
381        return -ESPIPE;
382
383    /*
384     * For simplicity, the only acceptable format is:
385     * YYYY:MM:DD:W:HH:MM:SS\n
386     */
387
388    if (count != 22)
389        goto err_out;
390
391    if (copy_from_user(buffer, buf, count))
392        return -EFAULT;
393
394    buffer[sizeof(buffer)-1] = '\0';
395
396    p = &buffer[0];
397
398    rtc_tm.tm_year = simple_strtoul(p, &p, 10);
399    if (*p++ != ':') goto err_out;
400
401    rtc_tm.tm_mon = simple_strtoul(p, &p, 10) - 1;
402    if (*p++ != ':') goto err_out;
403
404    rtc_tm.tm_mday = simple_strtoul(p, &p, 10);
405    if (*p++ != ':') goto err_out;
406
407    rtc_tm.tm_wday = simple_strtoul(p, &p, 10);
408    if (*p++ != ':') goto err_out;
409
410    rtc_tm.tm_hour = simple_strtoul(p, &p, 10);
411    if (*p++ != ':') goto err_out;
412
413    rtc_tm.tm_min = simple_strtoul(p, &p, 10);
414    if (*p++ != ':') goto err_out;
415
416    rtc_tm.tm_sec = simple_strtoul(p, &p, 10);
417    if (*p != '\n') goto err_out;
418
419    rtc_tm.tm_year -= RTC_EPOCH;
420
421    set_rtc_time(&rtc_tm);
422
423    *ppos += count;
424
425    return count;
426
427 err_out:
428    printk(KERN_ERR "invalid format: use YYYY:MM:DD:W:HH:MM:SS\\n\n");
429    return -EINVAL;
430}
431
432
433static ssize_t rtc_read(UNUSED struct file *filp, char *buf, size_t count,
434                        loff_t *ppos)
435{
436    char wbuf[23];
437    struct rtc_time tm;
438    ssize_t len;
439
440    if (count == 0 || *ppos != 0)
441        return 0;
442
443    get_rtc_time(&tm);
444
445    len = sprintf(wbuf, "%04d:%02d:%02d:%d:%02d:%02d:%02d\n",
446              tm.tm_year + RTC_EPOCH,
447              tm.tm_mon + 1,
448              tm.tm_mday,
449              tm.tm_wday,
450              tm.tm_hour,
451              tm.tm_min,
452              tm.tm_sec);
453
454    if (len > (ssize_t)count)
455        len = count;
456
457    if (copy_to_user(buf, wbuf, len))
458        return -EFAULT;
459
460    *ppos += len;
461
462    return len;
463}
464
465static int rtc_do_ioctl(unsigned int cmd, unsigned long arg)
466{
467    struct rtc_time rtc_tm;
468
469    switch (cmd) {
470        case RTC_RD_TIME:
471            memset(&rtc_tm, 0, sizeof(struct rtc_time));
472            get_rtc_time(&rtc_tm);
473            if (copy_to_user((void *)arg, &rtc_tm, sizeof(rtc_tm)))
474                return -EFAULT;
475            break;
476
477        case RTC_SET_TIME:
478            if (!capable(CAP_SYS_TIME))
479                return -EACCES;
480
481            if (copy_from_user(&rtc_tm, (struct rtc_time *)arg,
482                       sizeof(struct rtc_time)))
483                return -EFAULT;
484
485            set_rtc_time(&rtc_tm);
486            break;
487
488        default:
489            return -ENOTTY;
490    }
491
492    return 0;
493}
494
495static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
496{
497    long ret;
498    lock_kernel();
499    ret = rtc_do_ioctl(cmd, arg);
500    unlock_kernel();
501    return ret;
502}
503
504static const struct file_operations rtc_fops = {
505    .owner = THIS_MODULE,
506    .llseek = no_llseek,
507    .read = rtc_read,
508    .write = rtc_write,
509    .unlocked_ioctl = rtc_ioctl,
510    .open = rtc_open,
511    .release = rtc_release,
512};
513
514static struct miscdevice rtc_dev = {
515    .minor = RTC_MINOR,
516    .name = "rtc",
517    .fops = &rtc_fops,
518};
519
520/* Savagely ripped from diag.c. */
521extern char *nvram_get(char *str);
522#define getvar(str) (nvram_get(str)?:"")
523static inline int startswith (char *source, char *cmp)
524{ return !strncmp(source,cmp,strlen(cmp)); }
525static void platform_detect(void)
526{
527    char *buf;
528
529    /* Based on "model_no". */
530    if ((buf = nvram_get("model_no"))) {
531        if (startswith(buf,"WL700")) { /* WL700* */
532            sda_index = 2;
533            scl_index = 5;
534            return;
535        }
536    }
537
538    if (startswith(getvar("hardware_version"), "WL300-")) {
539        /* Either WL-300g or WL-HDD, do more extensive checks */
540        if ((simple_strtoul(getvar("et0phyaddr"), NULL, 0) == 0) &&
541             (simple_strtoul(getvar("et1phyaddr"), NULL, 0) == 1)) {
542            sda_index = 4;
543            scl_index = 5;
544            return;
545        }
546    }
547    /* not found */
548}
549
550static int __init rtc_init(void)
551{
552    int cr1;
553
554    platform_detect();
555
556    if (sda_index == scl_index) {
557        printk(KERN_ERR "RTC-RV5C386A: unrecognized platform!\n");
558        return -ENODEV;
559    }
560
561    i2c_init();
562
563    /*
564     * Switch RTC to 24h mode
565     */
566    i2c_start();
567    i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK);
568    i2c_outb(0xE4); /* start at address 0xE, transmission mode 4 */
569    cr1 = i2c_inb(I2C_NAK);
570    i2c_stop();
571    if ((cr1 & RTC_24HOUR_MODE_MASK) == 0) {
572        /* RTC is running in 12h mode */
573        printk(KERN_INFO "rtc.o: switching to 24h mode\n");
574        i2c_start();
575        i2c_outb(RTC_I2C_ADDRESS | I2C_WRITE_MASK);
576        i2c_outb(0xE0);
577        i2c_outb(cr1 | RTC_24HOUR_MODE_MASK);
578        i2c_stop();
579    }
580
581    misc_register(&rtc_dev);
582
583    printk(KERN_INFO "RV5C386A Real Time Clock Driver loaded\n");
584
585    return 0;
586}
587
588static void __exit rtc_exit (void)
589{
590    misc_deregister(&rtc_dev);
591    printk(KERN_INFO "Successfully removed RTC RV5C386A driver\n");
592}
593
594module_init(rtc_init);
595module_exit(rtc_exit);
596
597/*
598 * Local Variables:
599 * indent-tabs-mode:t
600 * c-basic-offset:8
601 * End:
602 */
603

Archive Download this file



interactive