summaryrefslogtreecommitdiffstats
path: root/include/asm-arm/arch-rpc/time.h
blob: b28666b37501d668f977fd45c333c004f11f266b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * linux/include/asm-arm/arch-rpc/time.h
 *
 * Copyright (c) 1996 Russell King.
 *
 * Changelog:
 *  24-Sep-1996	RMK	Created
 *  10-Oct-1996	RMK	Brought up to date with arch-sa110eval
 *  04-Dec-1997	RMK	Updated for new arch/arm/time.c
 */
#include <asm/iomd.h>

static long last_rtc_update = 0;	/* last time the cmos clock got updated */

extern __inline__ unsigned long gettimeoffset (void)
{
	unsigned long offset = 0;
	unsigned int count1, count2, status1, status2;

	status1 = IOMD_IRQREQA;
	barrier ();
	outb(0, IOMD_T0LATCH);
	barrier ();
	count1 = inb(IOMD_T0CNTL) | (inb(IOMD_T0CNTH) << 8);
	barrier ();
	status2 = inb(IOMD_IRQREQA);
	barrier ();
	outb(0, IOMD_T0LATCH);
	barrier ();
	count2 = inb(IOMD_T0CNTL) | (inb(IOMD_T0CNTH) << 8);

	if (count2 < count1) {
		/*
		 * This means that we haven't just had an interrupt
		 * while reading into status2.
		 */
		if (status2 & (1 << 5))
			offset = tick;
		count1 = count2;
	} else if (count2 > count1) {
		/*
		 * We have just had another interrupt while reading
		 * status2.
		 */
		offset += tick;
		count1 = count2;
	}

	count1 = LATCH - count1;
	/*
	 * count1 = number of clock ticks since last interrupt
	 */
	offset += count1 * tick / LATCH;
	return offset;
}

extern int iic_control(unsigned char, int, char *, int);

static int set_rtc_time(unsigned long nowtime)
{
	char buf[5], ctrl;

	if (iic_control(0xa1, 0, &ctrl, 1) != 0)
		printk("RTC: failed to read control reg\n");

	/*
	 * Reset divider
	 */
	ctrl |= 0x80;

	if (iic_control(0xa0, 0, &ctrl, 1) != 0)
		printk("RTC: failed to stop the clock\n");

	/*
	 * We only set the time - we don't set the date.
	 * This means that there is the possibility once
	 * a day for the correction to disrupt the date.
	 * We really ought to write the time and date, or
	 * nothing at all.
	 */
	buf[0] = 0;
	buf[1] = nowtime % 60;		nowtime /= 60;
	buf[2] = nowtime % 60;		nowtime /= 60;
	buf[3] = nowtime % 24;

	BIN_TO_BCD(buf[1]);
	BIN_TO_BCD(buf[2]);
	BIN_TO_BCD(buf[3]);

	if (iic_control(0xa0, 1, buf, 4) != 0)
		printk("RTC: Failed to set the time\n");

	/*
	 * Re-enable divider
	 */
	ctrl &= ~0x80;

	if (iic_control(0xa0, 0, &ctrl, 1) != 0)
		printk("RTC: failed to start the clock\n");

	return 0;
}

extern __inline__ unsigned long get_rtc_time(void)
{
	unsigned int year, i;
	char buf[8];

	/*
	 * The year is not part of the RTC counter
	 * registers, and is stored in RAM.  This
	 * means that it will not be automatically
	 * updated.
	 */
	if (iic_control(0xa1, 0xc0, buf, 1) != 0)
		printk("RTC: failed to read the year\n");

	/*
	 * If the year is before 1970, then the year
	 * is actually 100 in advance.  This gives us
	 * a year 2070 bug...
	 */
	year = 1900 + buf[0];
	if (year < 1970)
		year += 100;

	/*
	 * Read the time and date in one go - this
	 * will ensure that we don't get any effects
	 * due to carry (the RTC latches the counters
	 * during a read).
	 */
	if (iic_control(0xa1, 2, buf, 5) != 0) {
		printk("RTC: failed to read the time and date\n");
		memset(buf, 0, sizeof(buf));
	}

	/*FIXME:
	 * This doesn't seem to work.  Does RISC OS
	 * actually use the RTC year?  It doesn't
	 * seem to.  In that case, how does it update
	 * the CMOS year?
	 */
	/*year += (buf[3] >> 6) & 3;*/

	/*
	 * The RTC combines years with date and weekday
	 * with month.  We need to mask off this extra
	 * information before converting the date to
	 * binary.
	 */
	buf[4] &= 0x1f;
	buf[3] &= 0x3f;
printk("Year %4d mon %02X day %02X hour %02X min %02X sec %02X\n", year, buf[4], buf[3], buf[2], buf[1], buf[0]);
	for (i = 0; i < 5; i++)
		BCD_TO_BIN(buf[i]);

	return mktime(year, buf[4], buf[3], buf[2], buf[1], buf[0]);
}

static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	do_timer(regs);

	/* If we have an externally synchronized linux clock, then update
	 * CMOS clock accordingly every ~11 minutes.  Set_rtc_mmss() has to be
	 * called as close as possible to 500 ms before the new second starts.
	 */
	if ((time_status & STA_UNSYNC) == 0 &&
	    xtime.tv_sec > last_rtc_update + 660 &&
	    xtime.tv_usec >= 50000 - (tick >> 1) &&
	    xtime.tv_usec < 50000 + (tick >> 1)) {
		if (set_rtc_time(xtime.tv_sec) == 0)
			last_rtc_update = xtime.tv_sec;
		else
			last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
	}
}

static struct irqaction timerirq = {
	timer_interrupt,
	0,
	0,
	"timer",
	NULL,
	NULL
};

/*
 * Set up timer interrupt, and return the current time in seconds.
 */
extern __inline__ void setup_timer(void)
{
	outb(LATCH & 255, IOMD_T0LTCHL);
	outb(LATCH >> 8, IOMD_T0LTCHH);
	outb(0, IOMD_T0GO);

	xtime.tv_sec = get_rtc_time();

	setup_arm_irq(IRQ_TIMER, &timerirq);
}