Linux-2.6.12-rc2
[linux-2.6.git] / arch / sh / kernel / cpu / rtc.c
1 /*
2  * linux/arch/sh/kernel/rtc.c -- SH3 / SH4 on-chip RTC support
3  *
4  *  Copyright (C) 2000  Philipp Rumpf <prumpf@tux.org>
5  *  Copyright (C) 1999  Tetsuya Okada & Niibe Yutaka
6  */
7
8 #include <linux/init.h>
9 #include <linux/kernel.h>
10 #include <linux/sched.h>
11 #include <linux/time.h>
12
13 #include <asm/io.h>
14 #include <asm/rtc.h>
15
16 #ifndef BCD_TO_BIN
17 #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
18 #endif
19
20 #ifndef BIN_TO_BCD
21 #define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10)
22 #endif
23
24 void sh_rtc_gettimeofday(struct timespec *ts)
25 {
26         unsigned int sec128, sec, sec2, min, hr, wk, day, mon, yr, yr100, cf_bit;
27         unsigned long flags;
28
29  again:
30         do {
31                 local_irq_save(flags);
32                 ctrl_outb(0, RCR1);  /* Clear CF-bit */
33                 sec128 = ctrl_inb(R64CNT);
34                 sec = ctrl_inb(RSECCNT);
35                 min = ctrl_inb(RMINCNT);
36                 hr  = ctrl_inb(RHRCNT);
37                 wk  = ctrl_inb(RWKCNT);
38                 day = ctrl_inb(RDAYCNT);
39                 mon = ctrl_inb(RMONCNT);
40 #if defined(CONFIG_CPU_SH4)
41                 yr  = ctrl_inw(RYRCNT);
42                 yr100 = (yr >> 8);
43                 yr &= 0xff;
44 #else
45                 yr  = ctrl_inb(RYRCNT);
46                 yr100 = (yr == 0x99) ? 0x19 : 0x20;
47 #endif
48                 sec2 = ctrl_inb(R64CNT);
49                 cf_bit = ctrl_inb(RCR1) & RCR1_CF;
50                 local_irq_restore(flags);
51         } while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 0);
52
53         BCD_TO_BIN(yr100);
54         BCD_TO_BIN(yr);
55         BCD_TO_BIN(mon);
56         BCD_TO_BIN(day);
57         BCD_TO_BIN(hr);
58         BCD_TO_BIN(min);
59         BCD_TO_BIN(sec);
60
61         if (yr > 99 || mon < 1 || mon > 12 || day > 31 || day < 1 ||
62             hr > 23 || min > 59 || sec > 59) {
63                 printk(KERN_ERR
64                        "SH RTC: invalid value, resetting to 1 Jan 2000\n");
65                 local_irq_save(flags);
66                 ctrl_outb(RCR2_RESET, RCR2);  /* Reset & Stop */
67                 ctrl_outb(0, RSECCNT);
68                 ctrl_outb(0, RMINCNT);
69                 ctrl_outb(0, RHRCNT);
70                 ctrl_outb(6, RWKCNT);
71                 ctrl_outb(1, RDAYCNT);
72                 ctrl_outb(1, RMONCNT);
73 #if defined(CONFIG_CPU_SH4)
74                 ctrl_outw(0x2000, RYRCNT);
75 #else
76                 ctrl_outb(0, RYRCNT);
77 #endif
78                 ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2);  /* Start */
79                 goto again;
80         }
81
82 #if RTC_BIT_INVERTED != 0
83         if ((sec128 & RTC_BIT_INVERTED))
84                 sec--;
85 #endif
86
87         ts->tv_sec = mktime(yr100 * 100 + yr, mon, day, hr, min, sec);
88         ts->tv_nsec = ((sec128 * 1000000) / 128) * 1000;
89 }
90
91 /*
92  * Changed to only care about tv_sec, and not the full timespec struct
93  * (i.e. tv_nsec).  It can easily be switched to timespec for future cpus
94  * that support setting usec or nsec RTC values.
95  */
96 int sh_rtc_settimeofday(const time_t secs)
97 {
98         int retval = 0;
99         int real_seconds, real_minutes, cmos_minutes;
100         unsigned long flags;
101
102         local_irq_save(flags);
103         ctrl_outb(RCR2_RESET, RCR2);  /* Reset pre-scaler & stop RTC */
104
105         cmos_minutes = ctrl_inb(RMINCNT);
106         BCD_TO_BIN(cmos_minutes);
107
108         /*
109          * since we're only adjusting minutes and seconds,
110          * don't interfere with hour overflow. This avoids
111          * messing with unknown time zones but requires your
112          * RTC not to be off by more than 15 minutes
113          */
114         real_seconds = secs % 60;
115         real_minutes = secs / 60;
116         if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1)
117                 real_minutes += 30;     /* correct for half hour time zone */
118         real_minutes %= 60;
119
120         if (abs(real_minutes - cmos_minutes) < 30) {
121                 BIN_TO_BCD(real_seconds);
122                 BIN_TO_BCD(real_minutes);
123                 ctrl_outb(real_seconds, RSECCNT);
124                 ctrl_outb(real_minutes, RMINCNT);
125         } else {
126                 printk(KERN_WARNING
127                        "set_rtc_time: can't update from %d to %d\n",
128                        cmos_minutes, real_minutes);
129                 retval = -1;
130         }
131
132         ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2);  /* Start RTC */
133         local_irq_restore(flags);
134
135         return retval;
136 }