blob: f8361f5e788bc81896f3635d268ebf7a40056974 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
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
24void 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 */
96int 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}