File: /usr/src/linux/arch/sh/kernel/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 timeval *tv)
25     {
26     	unsigned int sec128, sec, min, hr, wk, day, mon, yr, yr100;
27     
28      again:
29     	do {
30     		ctrl_outb(0, RCR1);  /* Clear CF-bit */
31     		sec128 = ctrl_inb(R64CNT);
32     		sec = ctrl_inb(RSECCNT);
33     		min = ctrl_inb(RMINCNT);
34     		hr  = ctrl_inb(RHRCNT);
35     		wk  = ctrl_inb(RWKCNT);
36     		day = ctrl_inb(RDAYCNT);
37     		mon = ctrl_inb(RMONCNT);
38     #if defined(__SH4__)
39     		yr  = ctrl_inw(RYRCNT);
40     		yr100 = (yr >> 8);
41     		yr &= 0xff;
42     #else
43     		yr  = ctrl_inb(RYRCNT);
44     		yr100 = (yr == 0x99) ? 0x19 : 0x20;
45     #endif
46     	} while ((ctrl_inb(RCR1) & RCR1_CF) != 0);
47     
48     #if RTC_BIT_INVERTED != 0
49     	/* Work around to avoid reading correct value. */
50     	if (sec128 == RTC_BIT_INVERTED) {
51     		schedule_timeout(1);
52     		goto again;
53     	}
54     #endif
55     
56     	BCD_TO_BIN(yr100);
57     	BCD_TO_BIN(yr);
58     	BCD_TO_BIN(mon);
59     	BCD_TO_BIN(day);
60     	BCD_TO_BIN(hr);
61     	BCD_TO_BIN(min);
62     	BCD_TO_BIN(sec);
63     
64     	if (yr > 99 || mon < 1 || mon > 12 || day > 31 || day < 1 ||
65     	    hr > 23 || min > 59 || sec > 59) {
66     		printk(KERN_ERR
67     		       "SH RTC: invalid value, resetting to 1 Jan 2000\n");
68     		ctrl_outb(RCR2_RESET, RCR2);  /* Reset & Stop */
69     		ctrl_outb(0, RSECCNT);
70     		ctrl_outb(0, RMINCNT);
71     		ctrl_outb(0, RHRCNT);
72     		ctrl_outb(6, RWKCNT);
73     		ctrl_outb(1, RDAYCNT);
74     		ctrl_outb(1, RMONCNT);
75     #if defined(__SH4__)
76     		ctrl_outw(0x2000, RYRCNT);
77     #else
78     		ctrl_outb(0, RYRCNT);
79     #endif
80     		ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2);  /* Start */
81     		goto again;
82     	}
83     
84     	tv->tv_sec = mktime(yr100 * 100 + yr, mon, day, hr, min, sec);
85     	tv->tv_usec = ((sec128 ^ RTC_BIT_INVERTED) * 1000000) / 128;
86     }
87     
88     static int set_rtc_time(unsigned long nowtime)
89     {
90     	int retval = 0;
91     	int real_seconds, real_minutes, cmos_minutes;
92     
93     	ctrl_outb(RCR2_RESET, RCR2);  /* Reset pre-scaler & stop RTC */
94     
95     	cmos_minutes = ctrl_inb(RMINCNT);
96     	BCD_TO_BIN(cmos_minutes);
97     
98     	/*
99     	 * since we're only adjusting minutes and seconds,
100     	 * don't interfere with hour overflow. This avoids
101     	 * messing with unknown time zones but requires your
102     	 * RTC not to be off by more than 15 minutes
103     	 */
104     	real_seconds = nowtime % 60;
105     	real_minutes = nowtime / 60;
106     	if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1)
107     		real_minutes += 30;	/* correct for half hour time zone */
108     	real_minutes %= 60;
109     
110     	if (abs(real_minutes - cmos_minutes) < 30) {
111     		BIN_TO_BCD(real_seconds);
112     		BIN_TO_BCD(real_minutes);
113     		ctrl_outb(real_seconds, RSECCNT);
114     		ctrl_outb(real_minutes, RMINCNT);
115     	} else {
116     		printk(KERN_WARNING
117     		       "set_rtc_time: can't update from %d to %d\n",
118     		       cmos_minutes, real_minutes);
119     		retval = -1;
120     	}
121     
122     	ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2);  /* Start RTC */
123     
124     	return retval;
125     }
126     
127     int sh_rtc_settimeofday(const struct timeval *tv)
128     {
129     #if RTC_BIT_INVERTED != 0
130     	/* This is not accurate, but better than nothing. */
131     	schedule_timeout(HZ/2);
132     #endif
133     	return set_rtc_time(tv->tv_sec);
134     }
135