xref: /illumos-gate/usr/src/uts/i86pc/io/todpc_subr.c (revision 4e5b757f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1990, 1991 UNIX System Laboratories, Inc.	*/
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 /*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
32 /*	  All Rights Reserved	*/
33 
34 #pragma ident	"%Z%%M%	%I%	%E% SMI"
35 
36 #include <sys/param.h>
37 #include <sys/time.h>
38 #include <sys/systm.h>
39 
40 #include <sys/cpuvar.h>
41 #include <sys/clock.h>
42 #include <sys/debug.h>
43 #include <sys/rtc.h>
44 #include <sys/archsystm.h>
45 #include <sys/sysmacros.h>
46 #include <sys/lockstat.h>
47 #include <sys/stat.h>
48 #include <sys/sunddi.h>
49 
50 static int todpc_rtcget(unsigned char *buf);
51 static void todpc_rtcput(unsigned char *buf);
52 
53 /*
54  * Machine-dependent clock routines.
55  */
56 
57 /*
58  * Write the specified time into the clock chip.
59  * Must be called with tod_lock held.
60  */
61 /*ARGSUSED*/
62 static void
63 todpc_set(tod_ops_t *top, timestruc_t ts)
64 {
65 	todinfo_t tod = utc_to_tod(ts.tv_sec - ggmtl());
66 	struct rtc_t rtc;
67 
68 	ASSERT(MUTEX_HELD(&tod_lock));
69 
70 	if (todpc_rtcget((unsigned char *)&rtc))
71 		return;
72 
73 	/*
74 	 * rtc bytes are in binary-coded decimal, so we have to convert.
75 	 * We assume that we wrap the rtc year back to zero at 2000.
76 	 */
77 	/* LINTED: YRBASE = 0 for x86 */
78 	tod.tod_year -= YRBASE;
79 	if (tod.tod_year >= 100) {
80 		tod.tod_year -= 100;
81 		rtc.rtc_century = BYTE_TO_BCD(20); /* 20xx year */
82 	} else
83 		rtc.rtc_century = BYTE_TO_BCD(19); /* 19xx year */
84 	rtc.rtc_yr	= BYTE_TO_BCD(tod.tod_year);
85 	rtc.rtc_mon	= BYTE_TO_BCD(tod.tod_month);
86 	rtc.rtc_dom	= BYTE_TO_BCD(tod.tod_day);
87 	/* dow < 10, so no conversion */
88 	rtc.rtc_dow	= (unsigned char)tod.tod_dow;
89 	rtc.rtc_hr	= BYTE_TO_BCD(tod.tod_hour);
90 	rtc.rtc_min	= BYTE_TO_BCD(tod.tod_min);
91 	rtc.rtc_sec	= BYTE_TO_BCD(tod.tod_sec);
92 
93 	todpc_rtcput((unsigned char *)&rtc);
94 }
95 
96 /*
97  * Read the current time from the clock chip and convert to UNIX form.
98  * Assumes that the year in the clock chip is valid.
99  * Must be called with tod_lock held.
100  */
101 /*ARGSUSED*/
102 static timestruc_t
103 todpc_get(tod_ops_t *top)
104 {
105 	timestruc_t ts;
106 	todinfo_t tod;
107 	struct rtc_t rtc;
108 	int compute_century;
109 	static int century_warn = 1; /* only warn once, not each time called */
110 	static int range_warn = 1;
111 
112 	ASSERT(MUTEX_HELD(&tod_lock));
113 
114 	if (todpc_rtcget((unsigned char *)&rtc)) {
115 		ts.tv_sec = 0;
116 		ts.tv_nsec = 0;
117 		tod_fault_reset();
118 		return (ts);
119 	}
120 
121 	/* assume that we wrap the rtc year back to zero at 2000 */
122 	tod.tod_year	= BCD_TO_BYTE(rtc.rtc_yr);
123 	if (tod.tod_year < 69) {
124 		if (range_warn && tod.tod_year > 38) {
125 			cmn_err(CE_WARN, "hardware real-time clock is out "
126 				"of range -- time needs to be reset");
127 			range_warn = 0;
128 		}
129 		tod.tod_year += 100 + YRBASE; /* 20xx year */
130 		compute_century = 20;
131 	} else {
132 		/* LINTED: YRBASE = 0 for x86 */
133 		tod.tod_year += YRBASE; /* 19xx year */
134 		compute_century = 19;
135 	}
136 	if (century_warn && BCD_TO_BYTE(rtc.rtc_century) != compute_century) {
137 		cmn_err(CE_NOTE,
138 			"The hardware real-time clock appears to have the "
139 			"wrong century: %d.\nSolaris will still operate "
140 			"correctly, but other OS's/firmware agents may "
141 			"not.\nUse date(1) to set the date to the current "
142 			"time to correct the RTC.",
143 			BCD_TO_BYTE(rtc.rtc_century));
144 		century_warn = 0;
145 	}
146 	tod.tod_month	= BCD_TO_BYTE(rtc.rtc_mon);
147 	tod.tod_day	= BCD_TO_BYTE(rtc.rtc_dom);
148 	tod.tod_dow	= rtc.rtc_dow;	/* dow < 10, so no conversion needed */
149 	tod.tod_hour	= BCD_TO_BYTE(rtc.rtc_hr);
150 	tod.tod_min	= BCD_TO_BYTE(rtc.rtc_min);
151 	tod.tod_sec	= BCD_TO_BYTE(rtc.rtc_sec);
152 
153 	ts.tv_sec = tod_to_utc(tod) + ggmtl();
154 	ts.tv_nsec = 0;
155 
156 	return (ts);
157 }
158 
159 /*
160  * Routine to read contents of real time clock to the specified buffer.
161  * Returns ENXIO if clock not valid, or EAGAIN if clock data cannot be read
162  * else 0.
163  * The routine will busy wait for the Update-In-Progress flag to clear.
164  * On completion of the reads the Seconds register is re-read and the
165  * UIP flag is rechecked to confirm that an clock update did not occur
166  * during the accesses.  Routine will error exit after 256 attempts.
167  * (See bugid 1158298.)
168  * Routine returns RTC_NREG (which is 15) bytes of data, as given in the
169  * technical reference.  This data includes both time and status registers.
170  */
171 
172 static int
173 todpc_rtcget(unsigned char *buf)
174 {
175 	unsigned char	reg;
176 	int		i;
177 	int		retries = 256;
178 	unsigned char	*rawp;
179 
180 	ASSERT(MUTEX_HELD(&tod_lock));
181 
182 	outb(RTC_ADDR, RTC_D);		/* check if clock valid */
183 	reg = inb(RTC_DATA);
184 	if ((reg & RTC_VRT) == 0)
185 		return (ENXIO);
186 
187 checkuip:
188 	if (retries-- < 0)
189 		return (EAGAIN);
190 	outb(RTC_ADDR, RTC_A);		/* check if update in progress */
191 	reg = inb(RTC_DATA);
192 	if (reg & RTC_UIP) {
193 		tenmicrosec();
194 		goto checkuip;
195 	}
196 
197 	for (i = 0, rawp = buf; i < RTC_NREG; i++) {
198 		outb(RTC_ADDR, i);
199 		*rawp++ = inb(RTC_DATA);
200 	}
201 	outb(RTC_ADDR, RTC_CENTURY); /* do century */
202 	((struct rtc_t *)buf)->rtc_century = inb(RTC_DATA);
203 	outb(RTC_ADDR, 0);		/* re-read Seconds register */
204 	reg = inb(RTC_DATA);
205 	if (reg != ((struct rtc_t *)buf)->rtc_sec ||
206 	    (((struct rtc_t *)buf)->rtc_statusa & RTC_UIP))
207 		/* update occured during reads */
208 		goto checkuip;
209 
210 	return (0);
211 }
212 
213 /*
214  * This routine writes the contents of the given buffer to the real time
215  * clock.  It is given RTC_NREGP bytes of data, which are the 10 bytes used
216  * to write the time and set the alarm.  It should be called with the priority
217  * raised to 5.
218  */
219 static void
220 todpc_rtcput(unsigned char *buf)
221 {
222 	unsigned char	reg;
223 	int		i;
224 
225 	outb(RTC_ADDR, RTC_B);
226 	reg = inb(RTC_DATA);
227 	outb(RTC_ADDR, RTC_B);
228 	outb(RTC_DATA, reg | RTC_SET);	/* allow time set now */
229 	for (i = 0; i < RTC_NREGP; i++) { /* set the time */
230 		outb(RTC_ADDR, i);
231 		outb(RTC_DATA, buf[i]);
232 	}
233 	outb(RTC_ADDR, RTC_CENTURY); /* do century */
234 	outb(RTC_DATA, ((struct rtc_t *)buf)->rtc_century);
235 	outb(RTC_ADDR, RTC_B);
236 	outb(RTC_DATA, reg & ~RTC_SET);	/* allow time update */
237 }
238 
239 static tod_ops_t todpc_ops = {
240 	TOD_OPS_VERSION,
241 	todpc_get,
242 	todpc_set,
243 	NULL
244 };
245 
246 /*
247  * Initialize for the default TOD ops vector for use on hardware.
248  */
249 
250 tod_ops_t *tod_ops = &todpc_ops;
251