xref: /minix3/minix/drivers/clock/readclock/arch/i386/arch_readclock.c (revision f737eea6368426d3302d0075f13f73c4bc646e24)
1*433d6423SLionel Sambuc /* readclock - read the real time clock		Authors: T. Holm & E. Froese
2*433d6423SLionel Sambuc  *
3*433d6423SLionel Sambuc  * Changed to be user-space driver.
4*433d6423SLionel Sambuc  */
5*433d6423SLionel Sambuc 
6*433d6423SLionel Sambuc /************************************************************************/
7*433d6423SLionel Sambuc /*									*/
8*433d6423SLionel Sambuc /*   readclock.c							*/
9*433d6423SLionel Sambuc /*									*/
10*433d6423SLionel Sambuc /*		Read the clock value from the 64 byte CMOS RAM		*/
11*433d6423SLionel Sambuc /*		area, then set system time.				*/
12*433d6423SLionel Sambuc /*									*/
13*433d6423SLionel Sambuc /*		If the machine ID byte is 0xFC or 0xF8, the device	*/
14*433d6423SLionel Sambuc /*		/dev/mem exists and can be opened for reading,		*/
15*433d6423SLionel Sambuc /*		and no errors in the CMOS RAM are reported by the	*/
16*433d6423SLionel Sambuc /*		RTC, then the time is read from the clock RAM		*/
17*433d6423SLionel Sambuc /*		area maintained by the RTC.				*/
18*433d6423SLionel Sambuc /*									*/
19*433d6423SLionel Sambuc /*		The clock RAM values are decoded and fed to mktime	*/
20*433d6423SLionel Sambuc /*		to make a time_t value, then stime(2) is called.	*/
21*433d6423SLionel Sambuc /*									*/
22*433d6423SLionel Sambuc /*		This fails if:						*/
23*433d6423SLionel Sambuc /*									*/
24*433d6423SLionel Sambuc /*		If the machine ID does not match 0xFC or 0xF8 (no	*/
25*433d6423SLionel Sambuc /*		error message.)						*/
26*433d6423SLionel Sambuc /*									*/
27*433d6423SLionel Sambuc /*		If the machine ID is 0xFC or 0xF8 and /dev/mem		*/
28*433d6423SLionel Sambuc /*		is missing, or cannot be accessed.			*/
29*433d6423SLionel Sambuc /*									*/
30*433d6423SLionel Sambuc /*		If the RTC reports errors in the CMOS RAM.		*/
31*433d6423SLionel Sambuc /*									*/
32*433d6423SLionel Sambuc /************************************************************************/
33*433d6423SLionel Sambuc /*    origination          1987-Dec-29              efth                */
34*433d6423SLionel Sambuc /*    robustness	   1990-Oct-06		    C. Sylvain		*/
35*433d6423SLionel Sambuc /* incorp. B. Evans ideas  1991-Jul-06		    C. Sylvain		*/
36*433d6423SLionel Sambuc /*    set time & calibrate 1992-Dec-17		    Kees J. Bot		*/
37*433d6423SLionel Sambuc /*    clock timezone	   1993-Oct-10		    Kees J. Bot		*/
38*433d6423SLionel Sambuc /*    set CMOS clock	   1994-Jun-12		    Kees J. Bot		*/
39*433d6423SLionel Sambuc /************************************************************************/
40*433d6423SLionel Sambuc 
41*433d6423SLionel Sambuc #include <sys/types.h>
42*433d6423SLionel Sambuc #include <stdlib.h>
43*433d6423SLionel Sambuc #include <unistd.h>
44*433d6423SLionel Sambuc #include <stdio.h>
45*433d6423SLionel Sambuc #include <time.h>
46*433d6423SLionel Sambuc #include <errno.h>
47*433d6423SLionel Sambuc #include <minix/type.h>
48*433d6423SLionel Sambuc #include <minix/const.h>
49*433d6423SLionel Sambuc #include <minix/syslib.h>
50*433d6423SLionel Sambuc #include <minix/sysutil.h>
51*433d6423SLionel Sambuc #include <minix/com.h>
52*433d6423SLionel Sambuc #include <minix/log.h>
53*433d6423SLionel Sambuc #include <machine/cmos.h>
54*433d6423SLionel Sambuc 
55*433d6423SLionel Sambuc #include "readclock.h"
56*433d6423SLionel Sambuc 
57*433d6423SLionel Sambuc #define MACH_ID_ADDR	0xFFFFE	/* BIOS Machine ID at FFFF:000E */
58*433d6423SLionel Sambuc 
59*433d6423SLionel Sambuc #define PC_AT		   0xFC	/* Machine ID byte for PC/AT,
60*433d6423SLionel Sambuc 				 * PC/XT286, and PS/2 Models 50, 60 */
61*433d6423SLionel Sambuc #define PS_386		   0xF8	/* Machine ID byte for PS/2 Model 80 */
62*433d6423SLionel Sambuc 
63*433d6423SLionel Sambuc /* Manufacturers usually use the ID value of the IBM model they emulate.
64*433d6423SLionel Sambuc  * However some manufacturers, notably HP and COMPAQ, have had different
65*433d6423SLionel Sambuc  * ideas in the past.
66*433d6423SLionel Sambuc  *
67*433d6423SLionel Sambuc  * Machine ID byte information source:
68*433d6423SLionel Sambuc  *	_The Programmer's PC Sourcebook_ by Thom Hogan,
69*433d6423SLionel Sambuc  *	published by Microsoft Press
70*433d6423SLionel Sambuc  */
71*433d6423SLionel Sambuc 
72*433d6423SLionel Sambuc /* used for logging */
73*433d6423SLionel Sambuc static struct log log = {
74*433d6423SLionel Sambuc 	.name = "cmos_clock",
75*433d6423SLionel Sambuc 	.log_level = LEVEL_INFO,
76*433d6423SLionel Sambuc 	.log_func = default_log
77*433d6423SLionel Sambuc };
78*433d6423SLionel Sambuc 
79*433d6423SLionel Sambuc static int read_register(int reg_addr);
80*433d6423SLionel Sambuc static int write_register(int reg_addr, int value);
81*433d6423SLionel Sambuc 
82*433d6423SLionel Sambuc static int arch_init(void);
83*433d6423SLionel Sambuc static int arch_get_time(struct tm *t, int flags);
84*433d6423SLionel Sambuc static int arch_set_time(struct tm *t, int flags);
85*433d6423SLionel Sambuc static int arch_pwr_off(void);
86*433d6423SLionel Sambuc static void arch_exit(void);
87*433d6423SLionel Sambuc 
88*433d6423SLionel Sambuc int
arch_setup(struct rtc * r)89*433d6423SLionel Sambuc arch_setup(struct rtc *r)
90*433d6423SLionel Sambuc {
91*433d6423SLionel Sambuc 	r->init = arch_init;
92*433d6423SLionel Sambuc 	r->get_time = arch_get_time;
93*433d6423SLionel Sambuc 	r->set_time = arch_set_time;
94*433d6423SLionel Sambuc 	r->pwr_off = arch_pwr_off;
95*433d6423SLionel Sambuc 	r->exit = arch_exit;
96*433d6423SLionel Sambuc 
97*433d6423SLionel Sambuc 	return OK;
98*433d6423SLionel Sambuc }
99*433d6423SLionel Sambuc 
100*433d6423SLionel Sambuc static int
arch_init(void)101*433d6423SLionel Sambuc arch_init(void)
102*433d6423SLionel Sambuc {
103*433d6423SLionel Sambuc 	int s;
104*433d6423SLionel Sambuc 	unsigned char mach_id, cmos_state;
105*433d6423SLionel Sambuc 
106*433d6423SLionel Sambuc 	if ((s = sys_readbios(MACH_ID_ADDR, &mach_id, sizeof(mach_id))) != OK) {
107*433d6423SLionel Sambuc 		log_warn(&log, "sys_readbios failed: %d.\n", s);
108*433d6423SLionel Sambuc 
109*433d6423SLionel Sambuc 		return -1;
110*433d6423SLionel Sambuc 	}
111*433d6423SLionel Sambuc 
112*433d6423SLionel Sambuc 	if (mach_id != PS_386 && mach_id != PC_AT) {
113*433d6423SLionel Sambuc 		log_warn(&log, "Machine ID unknown.");
114*433d6423SLionel Sambuc 		log_warn(&log, "Machine ID byte = %02x\n", mach_id);
115*433d6423SLionel Sambuc 
116*433d6423SLionel Sambuc 		return -1;
117*433d6423SLionel Sambuc 	}
118*433d6423SLionel Sambuc 
119*433d6423SLionel Sambuc 	cmos_state = read_register(CMOS_STATUS);
120*433d6423SLionel Sambuc 
121*433d6423SLionel Sambuc 	if (cmos_state & (CS_LOST_POWER | CS_BAD_CHKSUM | CS_BAD_TIME)) {
122*433d6423SLionel Sambuc 		log_warn(&log, "CMOS RAM error(s) found...");
123*433d6423SLionel Sambuc 		log_warn(&log, "CMOS state = 0x%02x\n", cmos_state);
124*433d6423SLionel Sambuc 
125*433d6423SLionel Sambuc 		if (cmos_state & CS_LOST_POWER)
126*433d6423SLionel Sambuc 			log_warn(&log,
127*433d6423SLionel Sambuc 			    "RTC lost power. Reset CMOS RAM with SETUP.");
128*433d6423SLionel Sambuc 		if (cmos_state & CS_BAD_CHKSUM)
129*433d6423SLionel Sambuc 			log_warn(&log, "CMOS RAM checksum is bad. Run SETUP.");
130*433d6423SLionel Sambuc 		if (cmos_state & CS_BAD_TIME)
131*433d6423SLionel Sambuc 			log_warn(&log,
132*433d6423SLionel Sambuc 			    "Time invalid in CMOS RAM. Reset clock.");
133*433d6423SLionel Sambuc 		return -1;
134*433d6423SLionel Sambuc 	}
135*433d6423SLionel Sambuc 
136*433d6423SLionel Sambuc 	return OK;
137*433d6423SLionel Sambuc }
138*433d6423SLionel Sambuc 
139*433d6423SLionel Sambuc /***********************************************************************/
140*433d6423SLionel Sambuc /*                                                                     */
141*433d6423SLionel Sambuc /*    arch_get_time( time )                                            */
142*433d6423SLionel Sambuc /*                                                                     */
143*433d6423SLionel Sambuc /*    Update the structure pointed to by time with the current time    */
144*433d6423SLionel Sambuc /*    as read from CMOS RAM of the RTC.				       */
145*433d6423SLionel Sambuc /*    If necessary, the time is converted into a binary format before  */
146*433d6423SLionel Sambuc /*    being stored in the structure.                                   */
147*433d6423SLionel Sambuc /*                                                                     */
148*433d6423SLionel Sambuc /***********************************************************************/
149*433d6423SLionel Sambuc 
150*433d6423SLionel Sambuc static int
arch_get_time(struct tm * t,int flags)151*433d6423SLionel Sambuc arch_get_time(struct tm *t, int flags)
152*433d6423SLionel Sambuc {
153*433d6423SLionel Sambuc 	int osec, n;
154*433d6423SLionel Sambuc 
155*433d6423SLionel Sambuc 	do {
156*433d6423SLionel Sambuc 		osec = -1;
157*433d6423SLionel Sambuc 		n = 0;
158*433d6423SLionel Sambuc 		do {
159*433d6423SLionel Sambuc 			/* Clock update in progress? */
160*433d6423SLionel Sambuc 			if (read_register(RTC_REG_A) & RTC_A_UIP)
161*433d6423SLionel Sambuc 				continue;
162*433d6423SLionel Sambuc 
163*433d6423SLionel Sambuc 			t->tm_sec = read_register(RTC_SEC);
164*433d6423SLionel Sambuc 			if (t->tm_sec != osec) {
165*433d6423SLionel Sambuc 				/* Seconds changed.  First from -1, then because the
166*433d6423SLionel Sambuc 				 * clock ticked, which is what we're waiting for to
167*433d6423SLionel Sambuc 				 * get a precise reading.
168*433d6423SLionel Sambuc 				 */
169*433d6423SLionel Sambuc 				osec = t->tm_sec;
170*433d6423SLionel Sambuc 				n++;
171*433d6423SLionel Sambuc 			}
172*433d6423SLionel Sambuc 		} while (n < 2);
173*433d6423SLionel Sambuc 
174*433d6423SLionel Sambuc 		/* Read the other registers. */
175*433d6423SLionel Sambuc 		t->tm_min = read_register(RTC_MIN);
176*433d6423SLionel Sambuc 		t->tm_hour = read_register(RTC_HOUR);
177*433d6423SLionel Sambuc 		t->tm_mday = read_register(RTC_MDAY);
178*433d6423SLionel Sambuc 		t->tm_mon = read_register(RTC_MONTH);
179*433d6423SLionel Sambuc 		t->tm_year = read_register(RTC_YEAR);
180*433d6423SLionel Sambuc 
181*433d6423SLionel Sambuc 		/* Time stable? */
182*433d6423SLionel Sambuc 	} while (read_register(RTC_SEC) != t->tm_sec
183*433d6423SLionel Sambuc 	    || read_register(RTC_MIN) != t->tm_min
184*433d6423SLionel Sambuc 	    || read_register(RTC_HOUR) != t->tm_hour
185*433d6423SLionel Sambuc 	    || read_register(RTC_MDAY) != t->tm_mday
186*433d6423SLionel Sambuc 	    || read_register(RTC_MONTH) != t->tm_mon
187*433d6423SLionel Sambuc 	    || read_register(RTC_YEAR) != t->tm_year);
188*433d6423SLionel Sambuc 
189*433d6423SLionel Sambuc 	if ((read_register(RTC_REG_B) & RTC_B_DM_BCD) == 0) {
190*433d6423SLionel Sambuc 		/* Convert BCD to binary (default RTC mode). */
191*433d6423SLionel Sambuc 		t->tm_year = bcd_to_dec(t->tm_year);
192*433d6423SLionel Sambuc 		t->tm_mon = bcd_to_dec(t->tm_mon);
193*433d6423SLionel Sambuc 		t->tm_mday = bcd_to_dec(t->tm_mday);
194*433d6423SLionel Sambuc 		t->tm_hour = bcd_to_dec(t->tm_hour);
195*433d6423SLionel Sambuc 		t->tm_min = bcd_to_dec(t->tm_min);
196*433d6423SLionel Sambuc 		t->tm_sec = bcd_to_dec(t->tm_sec);
197*433d6423SLionel Sambuc 	}
198*433d6423SLionel Sambuc 	t->tm_mon--;		/* Counts from 0. */
199*433d6423SLionel Sambuc 
200*433d6423SLionel Sambuc 	/* Correct the year, good until 2080. */
201*433d6423SLionel Sambuc 	if (t->tm_year < 80)
202*433d6423SLionel Sambuc 		t->tm_year += 100;
203*433d6423SLionel Sambuc 
204*433d6423SLionel Sambuc 	if ((flags & RTCDEV_Y2KBUG) == RTCDEV_Y2KBUG) {
205*433d6423SLionel Sambuc 		/* Clock with Y2K bug, interpret 1980 as 2000, good until 2020. */
206*433d6423SLionel Sambuc 		if (t->tm_year < 100)
207*433d6423SLionel Sambuc 			t->tm_year += 20;
208*433d6423SLionel Sambuc 	}
209*433d6423SLionel Sambuc 
210*433d6423SLionel Sambuc 	return OK;
211*433d6423SLionel Sambuc }
212*433d6423SLionel Sambuc 
213*433d6423SLionel Sambuc static int
read_register(int reg_addr)214*433d6423SLionel Sambuc read_register(int reg_addr)
215*433d6423SLionel Sambuc {
216*433d6423SLionel Sambuc 	u32_t r;
217*433d6423SLionel Sambuc 
218*433d6423SLionel Sambuc 	if (sys_outb(RTC_INDEX, reg_addr) != OK) {
219*433d6423SLionel Sambuc 		log_warn(&log, "outb failed of %x\n", RTC_INDEX);
220*433d6423SLionel Sambuc 		return -1;
221*433d6423SLionel Sambuc 	}
222*433d6423SLionel Sambuc 	if (sys_inb(RTC_IO, &r) != OK) {
223*433d6423SLionel Sambuc 		log_warn(&log, "inb failed of %x (index %x) failed\n", RTC_IO,
224*433d6423SLionel Sambuc 		    reg_addr);
225*433d6423SLionel Sambuc 		return -1;
226*433d6423SLionel Sambuc 	}
227*433d6423SLionel Sambuc 	return r;
228*433d6423SLionel Sambuc }
229*433d6423SLionel Sambuc 
230*433d6423SLionel Sambuc /***********************************************************************/
231*433d6423SLionel Sambuc /*                                                                     */
232*433d6423SLionel Sambuc /*    arch_set_time( time )                                            */
233*433d6423SLionel Sambuc /*                                                                     */
234*433d6423SLionel Sambuc /*    Set the CMOS RTC to the time found in the structure.             */
235*433d6423SLionel Sambuc /*                                                                     */
236*433d6423SLionel Sambuc /***********************************************************************/
237*433d6423SLionel Sambuc 
238*433d6423SLionel Sambuc static int
arch_set_time(struct tm * t,int flags)239*433d6423SLionel Sambuc arch_set_time(struct tm *t, int flags)
240*433d6423SLionel Sambuc {
241*433d6423SLionel Sambuc 	int regA, regB;
242*433d6423SLionel Sambuc 
243*433d6423SLionel Sambuc 	if ((flags & RTCDEV_CMOSREG) == RTCDEV_CMOSREG) {
244*433d6423SLionel Sambuc 		/* Set A and B registers to their proper values according to the AT
245*433d6423SLionel Sambuc 		 * reference manual.  (For if it gets messed up, but the BIOS doesn't
246*433d6423SLionel Sambuc 		 * repair it.)
247*433d6423SLionel Sambuc 		 */
248*433d6423SLionel Sambuc 		write_register(RTC_REG_A, RTC_A_DV_OK | RTC_A_RS_DEF);
249*433d6423SLionel Sambuc 		write_register(RTC_REG_B, RTC_B_24);
250*433d6423SLionel Sambuc 	}
251*433d6423SLionel Sambuc 
252*433d6423SLionel Sambuc 	/* Inhibit updates. */
253*433d6423SLionel Sambuc 	regB = read_register(RTC_REG_B);
254*433d6423SLionel Sambuc 	write_register(RTC_REG_B, regB | RTC_B_SET);
255*433d6423SLionel Sambuc 
256*433d6423SLionel Sambuc 	t->tm_mon++;		/* Counts from 1. */
257*433d6423SLionel Sambuc 
258*433d6423SLionel Sambuc 	if ((flags & RTCDEV_Y2KBUG) == RTCDEV_Y2KBUG) {
259*433d6423SLionel Sambuc 		/* Set the clock back 20 years to avoid Y2K bug, good until 2020. */
260*433d6423SLionel Sambuc 		if (t->tm_year >= 100)
261*433d6423SLionel Sambuc 			t->tm_year -= 20;
262*433d6423SLionel Sambuc 	}
263*433d6423SLionel Sambuc 
264*433d6423SLionel Sambuc 	if ((regB & 0x04) == 0) {
265*433d6423SLionel Sambuc 		/* Convert binary to BCD (default RTC mode) */
266*433d6423SLionel Sambuc 		t->tm_year = dec_to_bcd(t->tm_year % 100);
267*433d6423SLionel Sambuc 		t->tm_mon = dec_to_bcd(t->tm_mon);
268*433d6423SLionel Sambuc 		t->tm_mday = dec_to_bcd(t->tm_mday);
269*433d6423SLionel Sambuc 		t->tm_hour = dec_to_bcd(t->tm_hour);
270*433d6423SLionel Sambuc 		t->tm_min = dec_to_bcd(t->tm_min);
271*433d6423SLionel Sambuc 		t->tm_sec = dec_to_bcd(t->tm_sec);
272*433d6423SLionel Sambuc 	}
273*433d6423SLionel Sambuc 	write_register(RTC_YEAR, t->tm_year);
274*433d6423SLionel Sambuc 	write_register(RTC_MONTH, t->tm_mon);
275*433d6423SLionel Sambuc 	write_register(RTC_MDAY, t->tm_mday);
276*433d6423SLionel Sambuc 	write_register(RTC_HOUR, t->tm_hour);
277*433d6423SLionel Sambuc 	write_register(RTC_MIN, t->tm_min);
278*433d6423SLionel Sambuc 	write_register(RTC_SEC, t->tm_sec);
279*433d6423SLionel Sambuc 
280*433d6423SLionel Sambuc 	/* Stop the clock. */
281*433d6423SLionel Sambuc 	regA = read_register(RTC_REG_A);
282*433d6423SLionel Sambuc 	write_register(RTC_REG_A, regA | RTC_A_DV_STOP);
283*433d6423SLionel Sambuc 
284*433d6423SLionel Sambuc 	/* Allow updates and restart the clock. */
285*433d6423SLionel Sambuc 	write_register(RTC_REG_B, regB);
286*433d6423SLionel Sambuc 	write_register(RTC_REG_A, regA);
287*433d6423SLionel Sambuc 
288*433d6423SLionel Sambuc 	return OK;
289*433d6423SLionel Sambuc }
290*433d6423SLionel Sambuc 
291*433d6423SLionel Sambuc static int
write_register(int reg_addr,int value)292*433d6423SLionel Sambuc write_register(int reg_addr, int value)
293*433d6423SLionel Sambuc {
294*433d6423SLionel Sambuc 	if (sys_outb(RTC_INDEX, reg_addr) != OK) {
295*433d6423SLionel Sambuc 		log_warn(&log, "outb failed of %x\n", RTC_INDEX);
296*433d6423SLionel Sambuc 		return -1;
297*433d6423SLionel Sambuc 	}
298*433d6423SLionel Sambuc 	if (sys_outb(RTC_IO, value) != OK) {
299*433d6423SLionel Sambuc 		log_warn(&log, "outb failed of %x (index %x)\n", RTC_IO,
300*433d6423SLionel Sambuc 		    reg_addr);
301*433d6423SLionel Sambuc 		return -1;
302*433d6423SLionel Sambuc 	}
303*433d6423SLionel Sambuc 
304*433d6423SLionel Sambuc 	return OK;
305*433d6423SLionel Sambuc }
306*433d6423SLionel Sambuc 
307*433d6423SLionel Sambuc static int
arch_pwr_off(void)308*433d6423SLionel Sambuc arch_pwr_off(void)
309*433d6423SLionel Sambuc {
310*433d6423SLionel Sambuc 	/* Not Implemented */
311*433d6423SLionel Sambuc 	return ENOSYS;
312*433d6423SLionel Sambuc }
313*433d6423SLionel Sambuc 
314*433d6423SLionel Sambuc static void
arch_exit(void)315*433d6423SLionel Sambuc arch_exit(void)
316*433d6423SLionel Sambuc {
317*433d6423SLionel Sambuc 	/* Nothing to clean up here */
318*433d6423SLionel Sambuc 	log_debug(&log, "Exiting...");
319*433d6423SLionel Sambuc }
320*433d6423SLionel Sambuc 
321