xref: /openbsd/sys/arch/i386/isa/clock.c (revision 3bef86f7)
1 /*	$OpenBSD: clock.c,v 1.68 2023/09/17 14:50:51 cheloha Exp $	*/
2 /*	$NetBSD: clock.c,v 1.39 1996/05/12 23:11:54 mycroft Exp $	*/
3 
4 /*-
5  * Copyright (c) 1993, 1994 Charles Hannum.
6  * Copyright (c) 1990 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * William Jolitz and Don Ahn.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  *	@(#)clock.c	7.2 (Berkeley) 5/12/91
37  */
38 /*
39  * Mach Operating System
40  * Copyright (c) 1991,1990,1989 Carnegie Mellon University
41  * All Rights Reserved.
42  *
43  * Permission to use, copy, modify and distribute this software and its
44  * documentation is hereby granted, provided that both the copyright
45  * notice and this permission notice appear in all copies of the
46  * software, derivative works or modified versions, and any portions
47  * thereof, and that both notices appear in supporting documentation.
48  *
49  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
50  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
51  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
52  *
53  * Carnegie Mellon requests users of this software to return to
54  *
55  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
56  *  School of Computer Science
57  *  Carnegie Mellon University
58  *  Pittsburgh PA 15213-3890
59  *
60  * any improvements or extensions that they make and grant Carnegie Mellon
61  * the rights to redistribute these changes.
62  */
63 /*
64   Copyright 1988, 1989 by Intel Corporation, Santa Clara, California.
65 
66 		All Rights Reserved
67 
68 Permission to use, copy, modify, and distribute this software and
69 its documentation for any purpose and without fee is hereby
70 granted, provided that the above copyright notice appears in all
71 copies and that both the copyright notice and this permission notice
72 appear in supporting documentation, and that the name of Intel
73 not be used in advertising or publicity pertaining to distribution
74 of the software without specific, written prior permission.
75 
76 INTEL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
77 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
78 IN NO EVENT SHALL INTEL BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
79 CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
80 LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
81 NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
82 WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
83 */
84 
85 /*
86  * Primitive clock interrupt routines.
87  */
88 #include <sys/param.h>
89 #include <sys/systm.h>
90 #include <sys/time.h>
91 #include <sys/kernel.h>
92 #include <sys/clockintr.h>
93 #include <sys/timeout.h>
94 #include <sys/timetc.h>
95 #include <sys/mutex.h>
96 
97 #include <machine/intr.h>
98 #include <machine/pio.h>
99 #include <machine/cpufunc.h>
100 
101 #include <dev/clock_subr.h>
102 #include <dev/isa/isareg.h>
103 #include <dev/isa/isavar.h>
104 #include <dev/ic/mc146818reg.h>
105 #include <dev/ic/i8253reg.h>
106 #include <i386/isa/nvram.h>
107 
108 void	spinwait(int);
109 int	clockintr(void *);
110 int	gettick(void);
111 int	rtcget(mc_todregs *);
112 void	rtcput(mc_todregs *);
113 int	rtcintr(void *);
114 void	rtcdrain(void *);
115 int	calibrate_cyclecounter_ctr(void);
116 
117 u_int mc146818_read(void *, u_int);
118 void mc146818_write(void *, u_int, u_int);
119 
120 int cpuspeed;
121 int clock_broken_latch;
122 
123 /* Timecounter on the i8254 */
124 uint32_t i8254_lastcount;
125 uint32_t i8254_offset;
126 int i8254_ticked;
127 u_int i8254_get_timecount(struct timecounter *tc);
128 u_int i8254_simple_get_timecount(struct timecounter *tc);
129 
130 static struct timecounter i8254_timecounter = {
131 	.tc_get_timecount = i8254_get_timecount,
132 	.tc_counter_mask = ~0u,
133 	.tc_frequency = TIMER_FREQ,
134 	.tc_name = "i8254",
135 	.tc_quality = 0,
136 	.tc_priv = NULL,
137 	.tc_user = 0,
138 };
139 struct mutex timer_mutex = MUTEX_INITIALIZER(IPL_HIGH);
140 u_long rtclock_tval;
141 
142 #define	SECMIN	((unsigned)60)			/* seconds per minute */
143 #define	SECHOUR	((unsigned)(60*SECMIN))		/* seconds per hour */
144 
145 u_int
146 mc146818_read(void *sc, u_int reg)
147 {
148 	int s;
149 	u_char v;
150 
151 	s = splhigh();
152 	outb(IO_RTC, reg);
153 	DELAY(1);
154 	v = inb(IO_RTC+1);
155 	DELAY(1);
156 	splx(s);
157 	return (v);
158 }
159 
160 void
161 mc146818_write(void *sc, u_int reg, u_int datum)
162 {
163 	int s;
164 
165 	s = splhigh();
166 	outb(IO_RTC, reg);
167 	DELAY(1);
168 	outb(IO_RTC+1, datum);
169 	DELAY(1);
170 	splx(s);
171 }
172 
173 void
174 startclocks(void)
175 {
176 	int s;
177 
178 	mtx_enter(&timer_mutex);
179 	rtclock_tval = TIMER_DIV(hz);
180 	i8254_startclock();
181 	mtx_leave(&timer_mutex);
182 
183 	/* Check diagnostic status */
184 	if ((s = mc146818_read(NULL, NVRAM_DIAG)) != 0)	/* XXX softc */
185 		printf("RTC BIOS diagnostic error %b\n", (unsigned int) s,
186 		    NVRAM_DIAG_BITS);
187 }
188 
189 void
190 rtcdrain(void *v)
191 {
192 	struct timeout *to = (struct timeout *)v;
193 
194 	if (to != NULL)
195 		timeout_del(to);
196 
197 	/* Drain any un-acknowledged RTC interrupts. */
198 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF)
199 		; /* Nothing. */
200 }
201 
202 int
203 clockintr(void *frame)
204 {
205 	if (timecounter->tc_get_timecount == i8254_get_timecount) {
206 		if (i8254_ticked) {
207 			i8254_ticked = 0;
208 		} else {
209 			i8254_offset += rtclock_tval;
210 			i8254_lastcount = 0;
211 		}
212 	}
213 	clockintr_dispatch(frame);
214 	return (1);
215 }
216 
217 int
218 rtcintr(void *frame)
219 {
220 	u_int stat = 0;
221 
222 	/*
223 	 * If rtcintr is 'late', next intr may happen immediately.
224 	 * Get them all. (Also, see comment in cpu_initclocks().)
225 	 */
226 	while (mc146818_read(NULL, MC_REGC) & MC_REGC_PF)
227 		stat = 1;
228 
229 	/* XXX Can rtcintr() run before i8254_initclocks() is complete? */
230 	if (stathz != 0 && stat)
231 		clockintr_dispatch(frame);
232 
233 	return (stat);
234 }
235 
236 int
237 gettick(void)
238 {
239 	u_long s;
240 
241 	if (clock_broken_latch) {
242 		int v1, v2, v3;
243 		int w1, w2, w3;
244 
245 		/*
246 		 * Don't lock the mutex in this case, clock_broken_latch
247 		 * CPUs don't do MP anyway.
248 		 */
249 
250 		s = intr_disable();
251 
252 		v1 = inb(IO_TIMER1 + TIMER_CNTR0);
253 		v1 |= inb(IO_TIMER1 + TIMER_CNTR0) << 8;
254 		v2 = inb(IO_TIMER1 + TIMER_CNTR0);
255 		v2 |= inb(IO_TIMER1 + TIMER_CNTR0) << 8;
256 		v3 = inb(IO_TIMER1 + TIMER_CNTR0);
257 		v3 |= inb(IO_TIMER1 + TIMER_CNTR0) << 8;
258 
259 		intr_restore(s);
260 
261 		if (v1 >= v2 && v2 >= v3 && v1 - v3 < 0x200)
262 			return (v2);
263 
264 #define _swap_val(a, b) do { \
265 	int c = a; \
266 	a = b; \
267 	b = c; \
268 } while (0)
269 
270 		/* sort v1 v2 v3 */
271 		if (v1 < v2)
272 			_swap_val(v1, v2);
273 		if (v2 < v3)
274 			_swap_val(v2, v3);
275 		if (v1 < v2)
276 			_swap_val(v1, v2);
277 
278 		/* compute the middle value */
279 		if (v1 - v3 < 0x200)
280 			return (v2);
281 		w1 = v2 - v3;
282 		w2 = v3 - v1 + TIMER_DIV(hz);
283 		w3 = v1 - v2;
284 		if (w1 >= w2) {
285 			if (w1 >= w3)
286 				return (v1);
287 		} else {
288 			if (w2 >= w3)
289 				return (v2);
290 		}
291 		return (v3);
292 	} else {
293 		u_char lo, hi;
294 
295 		mtx_enter(&timer_mutex);
296 		s = intr_disable();
297 		/* Select counter 0 and latch it. */
298 		outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
299 		lo = inb(IO_TIMER1 + TIMER_CNTR0);
300 		hi = inb(IO_TIMER1 + TIMER_CNTR0);
301 
302 		intr_restore(s);
303 		mtx_leave(&timer_mutex);
304 		return ((hi << 8) | lo);
305 	}
306 }
307 
308 /*
309  * Wait "n" microseconds.
310  * Relies on timer 1 counting down from (TIMER_FREQ / hz) at TIMER_FREQ Hz.
311  * Note: timer had better have been programmed before this is first used!
312  * (Note that we use `rate generator' mode, which counts at 1:1; `square
313  * wave' mode counts at 2:1).
314  */
315 void
316 i8254_delay(int n)
317 {
318 	int limit, tick, otick;
319 
320 	/*
321 	 * Read the counter first, so that the rest of the setup overhead is
322 	 * counted.
323 	 */
324 	otick = gettick();
325 
326 #ifdef __GNUC__
327 	/*
328 	 * Calculate ((n * TIMER_FREQ) / 1e6) using explicit assembler code so
329 	 * we can take advantage of the intermediate 64-bit quantity to prevent
330 	 * loss of significance.
331 	 */
332 	n -= 5;
333 	if (n < 0)
334 		return;
335 	__asm volatile("mul %2\n\tdiv %3"
336 			 : "=a" (n)
337 			 : "0" (n), "r" (TIMER_FREQ), "r" (1000000)
338 			 : "%edx", "cc");
339 #else
340 	/*
341 	 * Calculate ((n * TIMER_FREQ) / 1e6) without using floating point and
342 	 * without any avoidable overflows.
343 	 */
344 	n -= 20;
345 	{
346 		int sec = n / 1000000,
347 		    usec = n % 1000000;
348 		n = sec * TIMER_FREQ +
349 		    usec * (TIMER_FREQ / 1000000) +
350 		    usec * ((TIMER_FREQ % 1000000) / 1000) / 1000 +
351 		    usec * (TIMER_FREQ % 1000) / 1000000;
352 	}
353 #endif
354 
355 	limit = TIMER_FREQ / hz;
356 
357 	while (n > 0) {
358 		tick = gettick();
359 		if (tick > otick)
360 			n -= limit - (tick - otick);
361 		else
362 			n -= otick - tick;
363 		otick = tick;
364 	}
365 }
366 
367 int
368 calibrate_cyclecounter_ctr(void)
369 {
370 	struct cpu_info *ci = curcpu();
371 	unsigned long long count, last_count, msr;
372 
373 	if ((ci->ci_flags & CPUF_CONST_TSC) == 0 ||
374 	    (cpu_perf_eax & CPUIDEAX_VERID) <= 1 ||
375 	    CPUIDEDX_NUM_FC(cpu_perf_edx) <= 1)
376 		return (-1);
377 
378 	msr = rdmsr(MSR_PERF_FIXED_CTR_CTRL);
379 	if (msr & MSR_PERF_FIXED_CTR_FC(1, MSR_PERF_FIXED_CTR_FC_MASK)) {
380 		/* some hypervisor is dicking us around */
381 		return (-1);
382 	}
383 
384 	msr |= MSR_PERF_FIXED_CTR_FC(1, MSR_PERF_FIXED_CTR_FC_1);
385 	wrmsr(MSR_PERF_FIXED_CTR_CTRL, msr);
386 
387 	msr = rdmsr(MSR_PERF_GLOBAL_CTRL) | MSR_PERF_GLOBAL_CTR1_EN;
388 	wrmsr(MSR_PERF_GLOBAL_CTRL, msr);
389 
390 	last_count = rdmsr(MSR_PERF_FIXED_CTR1);
391 	delay(1000000);
392 	count = rdmsr(MSR_PERF_FIXED_CTR1);
393 
394 	msr = rdmsr(MSR_PERF_FIXED_CTR_CTRL);
395 	msr &= MSR_PERF_FIXED_CTR_FC(1, MSR_PERF_FIXED_CTR_FC_MASK);
396 	wrmsr(MSR_PERF_FIXED_CTR_CTRL, msr);
397 
398 	msr = rdmsr(MSR_PERF_GLOBAL_CTRL);
399 	msr &= ~MSR_PERF_GLOBAL_CTR1_EN;
400 	wrmsr(MSR_PERF_GLOBAL_CTRL, msr);
401 
402 	cpuspeed = ((count - last_count) + 999999) / 1000000;
403 
404 	return (cpuspeed == 0 ? -1 : 0);
405 }
406 
407 void
408 calibrate_cyclecounter(void)
409 {
410 	unsigned long long count, last_count;
411 
412 	if (calibrate_cyclecounter_ctr() == 0)
413 		return;
414 
415 	__asm volatile("rdtsc" : "=A" (last_count));
416 	delay(1000000);
417 	__asm volatile("rdtsc" : "=A" (count));
418 
419 	cpuspeed = ((count - last_count) + 999999) / 1000000;
420 }
421 
422 void
423 i8254_initclocks(void)
424 {
425 	i8254_inittimecounter();	/* hook the interrupt-based i8254 tc */
426 
427 	stathz = 128;
428 	profhz = 1024;		/* XXX does not divide into 1 billion */
429 }
430 
431 void
432 i8254_start_both_clocks(void)
433 {
434 	clockintr_cpu_init(NULL);
435 
436 	/*
437 	 * When using i8254 for clock, we also use the rtc for profclock.
438 	 *
439 	 * These IRQs are not MP-safe, but it is harmless to lie about it
440 	 * because we cannot reach this point unless we are only booting
441 	 * a single CPU.
442 	 */
443 	(void)isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK | IPL_MPSAFE,
444 	    clockintr, 0, "clock");
445 	(void)isa_intr_establish(NULL, 8, IST_PULSE, IPL_STATCLOCK | IPL_MPSAFE,
446 	    rtcintr, 0, "rtc");
447 
448 	rtcstart();			/* start the mc146818 clock */
449 }
450 
451 void
452 rtcstart(void)
453 {
454 	static struct timeout rtcdrain_timeout;
455 
456 	mc146818_write(NULL, MC_REGA, MC_BASE_32_KHz | MC_RATE_128_Hz);
457 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR | MC_REGB_PIE);
458 
459 	/*
460 	 * On a number of i386 systems, the rtc will fail to start when booting
461 	 * the system. This is due to us missing to acknowledge an interrupt
462 	 * during early stages of the boot process. If we do not acknowledge
463 	 * the interrupt, the rtc clock will not generate further interrupts.
464 	 * To solve this, once interrupts are enabled, use a timeout (once)
465 	 * to drain any un-acknowledged rtc interrupt(s).
466 	 */
467 
468 	timeout_set(&rtcdrain_timeout, rtcdrain, (void *)&rtcdrain_timeout);
469 	timeout_add(&rtcdrain_timeout, 1);
470 }
471 
472 void
473 rtcstop(void)
474 {
475 	mc146818_write(NULL, MC_REGB, MC_REGB_24HR);
476 }
477 
478 int
479 rtcget(mc_todregs *regs)
480 {
481 	if ((mc146818_read(NULL, MC_REGD) & MC_REGD_VRT) == 0) /* XXX softc */
482 		return (-1);
483 	MC146818_GETTOD(NULL, regs);			/* XXX softc */
484 	return (0);
485 }
486 
487 void
488 rtcput(mc_todregs *regs)
489 {
490 	MC146818_PUTTOD(NULL, regs);			/* XXX softc */
491 }
492 
493 int
494 bcdtobin(int n)
495 {
496 	return (((n >> 4) & 0x0f) * 10 + (n & 0x0f));
497 }
498 
499 int
500 bintobcd(int n)
501 {
502 	return ((u_char)(((n / 10) << 4) & 0xf0) | ((n % 10) & 0x0f));
503 }
504 
505 /*
506  * check whether the CMOS layout is "standard"-like (ie, not PS/2-like),
507  * to be called at splclock()
508  */
509 int cmoscheck(void);
510 int
511 cmoscheck(void)
512 {
513 	int i;
514 	unsigned short cksum = 0;
515 
516 	for (i = 0x10; i <= 0x2d; i++)
517 		cksum += mc146818_read(NULL, i); /* XXX softc */
518 
519 	return (cksum == (mc146818_read(NULL, 0x2e) << 8)
520 			  + mc146818_read(NULL, 0x2f));
521 }
522 
523 /*
524  * patchable to control century byte handling:
525  * 1: always update
526  * -1: never touch
527  * 0: try to figure out itself
528  */
529 int rtc_update_century = 0;
530 
531 /*
532  * Expand a two-digit year as read from the clock chip
533  * into full width.
534  * Being here, deal with the CMOS century byte.
535  */
536 int clock_expandyear(int);
537 int
538 clock_expandyear(int clockyear)
539 {
540 	int s, clockcentury, cmoscentury;
541 
542 	clockcentury = (clockyear < 70) ? 20 : 19;
543 	clockyear += 100 * clockcentury;
544 
545 	if (rtc_update_century < 0)
546 		return (clockyear);
547 
548 	s = splclock();
549 	if (cmoscheck())
550 		cmoscentury = mc146818_read(NULL, NVRAM_CENTURY);
551 	else
552 		cmoscentury = 0;
553 	splx(s);
554 	if (!cmoscentury) {
555 #ifdef DIAGNOSTIC
556 		printf("clock: unknown CMOS layout\n");
557 #endif
558 		return (clockyear);
559 	}
560 	cmoscentury = bcdtobin(cmoscentury);
561 
562 	if (cmoscentury != clockcentury) {
563 		/* XXX note: saying "century is 20" might confuse the naive. */
564 		printf("WARNING: NVRAM century is %d but RTC year is %d\n",
565 		       cmoscentury, clockyear);
566 
567 		/* Kludge to roll over century. */
568 		if ((rtc_update_century > 0) ||
569 		    ((cmoscentury == 19) && (clockcentury == 20) &&
570 		     (clockyear == 2000))) {
571 			printf("WARNING: Setting NVRAM century to %d\n",
572 			       clockcentury);
573 			s = splclock();
574 			mc146818_write(NULL, NVRAM_CENTURY,
575 				       bintobcd(clockcentury));
576 			splx(s);
577 		}
578 	} else if (cmoscentury == 19 && rtc_update_century == 0)
579 		rtc_update_century = 1; /* will update later in resettodr() */
580 
581 	return (clockyear);
582 }
583 
584 int
585 rtcgettime(struct todr_chip_handle *handle, struct timeval *tv)
586 {
587 	mc_todregs rtclk;
588 	struct clock_ymdhms dt;
589 	int s;
590 
591 	s = splclock();
592 	if (rtcget(&rtclk)) {
593 		splx(s);
594 		return EINVAL;
595 	}
596 	splx(s);
597 
598 #ifdef CLOCK_DEBUG
599 	printf("readclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR],
600 	    rtclk[MC_MONTH], rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN],
601 	    rtclk[MC_SEC]);
602 #endif
603 
604 	dt.dt_sec = bcdtobin(rtclk[MC_SEC]);
605 	dt.dt_min = bcdtobin(rtclk[MC_MIN]);
606 	dt.dt_hour = bcdtobin(rtclk[MC_HOUR]);
607 	dt.dt_day = bcdtobin(rtclk[MC_DOM]);
608 	dt.dt_mon = bcdtobin(rtclk[MC_MONTH]);
609 	dt.dt_year = clock_expandyear(bcdtobin(rtclk[MC_YEAR]));
610 
611 	tv->tv_sec = clock_ymdhms_to_secs(&dt) - utc_offset;
612 	tv->tv_usec = 0;
613 	return 0;
614 }
615 
616 int
617 rtcsettime(struct todr_chip_handle *handle, struct timeval *tv)
618 {
619 	mc_todregs rtclk;
620 	struct clock_ymdhms dt;
621 	int century, s;
622 
623 	s = splclock();
624 	if (rtcget(&rtclk))
625 		memset(&rtclk, 0, sizeof(rtclk));
626 	splx(s);
627 
628 	clock_secs_to_ymdhms(tv->tv_sec + utc_offset, &dt);
629 
630 	rtclk[MC_SEC] = bintobcd(dt.dt_sec);
631 	rtclk[MC_MIN] = bintobcd(dt.dt_min);
632 	rtclk[MC_HOUR] = bintobcd(dt.dt_hour);
633 	rtclk[MC_DOW] = dt.dt_wday + 1;
634 	rtclk[MC_YEAR] = bintobcd(dt.dt_year % 100);
635 	rtclk[MC_MONTH] = bintobcd(dt.dt_mon);
636 	rtclk[MC_DOM] = bintobcd(dt.dt_day);
637 
638 #ifdef CLOCK_DEBUG
639 	printf("setclock: %x/%x/%x %x:%x:%x\n", rtclk[MC_YEAR], rtclk[MC_MONTH],
640 	   rtclk[MC_DOM], rtclk[MC_HOUR], rtclk[MC_MIN], rtclk[MC_SEC]);
641 #endif
642 
643 	s = splclock();
644 	rtcput(&rtclk);
645 	if (rtc_update_century > 0) {
646 		century = bintobcd(dt.dt_year / 100);
647 		mc146818_write(NULL, NVRAM_CENTURY, century); /* XXX softc */
648 	}
649 	splx(s);
650 	return 0;
651 }
652 
653 struct todr_chip_handle rtc_todr;
654 
655 void
656 rtcinit(void)
657 {
658 	rtc_todr.todr_gettime = rtcgettime;
659 	rtc_todr.todr_settime = rtcsettime;
660 	rtc_todr.todr_quality = 0;
661 	todr_attach(&rtc_todr);
662 }
663 
664 void
665 setstatclockrate(int arg)
666 {
667 	if (initclock_func == i8254_initclocks) {
668 		if (arg == stathz)
669 			mc146818_write(NULL, MC_REGA,
670 			    MC_BASE_32_KHz | MC_RATE_128_Hz);
671 		else
672 			mc146818_write(NULL, MC_REGA,
673 			    MC_BASE_32_KHz | MC_RATE_1024_Hz);
674 	}
675 }
676 
677 void
678 i8254_inittimecounter(void)
679 {
680 	tc_init(&i8254_timecounter);
681 }
682 
683 /*
684  * If we're using lapic to drive hardclock, we can use a simpler
685  * algorithm for the i8254 timecounters.
686  */
687 void
688 i8254_inittimecounter_simple(void)
689 {
690 	i8254_timecounter.tc_get_timecount = i8254_simple_get_timecount;
691 	i8254_timecounter.tc_counter_mask = 0x7fff;
692 	i8254_timecounter.tc_frequency = TIMER_FREQ;
693 
694 	mtx_enter(&timer_mutex);
695 	rtclock_tval = 0x8000;
696 	i8254_startclock();
697 	mtx_leave(&timer_mutex);
698 
699 	tc_init(&i8254_timecounter);
700 }
701 
702 void
703 i8254_startclock(void)
704 {
705 	u_long tval = rtclock_tval;
706 
707 	outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
708 	outb(IO_TIMER1 + TIMER_CNTR0, tval & 0xff);
709 	outb(IO_TIMER1 + TIMER_CNTR0, tval >> 8);
710 }
711 
712 u_int
713 i8254_simple_get_timecount(struct timecounter *tc)
714 {
715 	return (rtclock_tval - gettick());
716 }
717 
718 u_int
719 i8254_get_timecount(struct timecounter *tc)
720 {
721 	u_char hi, lo;
722 	u_int count;
723 	u_long s;
724 
725 	s = intr_disable();
726 
727 	outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL0 | TIMER_LATCH);
728 	lo = inb(IO_TIMER1 + TIMER_CNTR0);
729 	hi = inb(IO_TIMER1 + TIMER_CNTR0);
730 
731 	count = rtclock_tval - ((hi << 8) | lo);
732 
733 	if (count < i8254_lastcount) {
734 		i8254_ticked = 1;
735 		i8254_offset += rtclock_tval;
736 	}
737 	i8254_lastcount = count;
738 	count += i8254_offset;
739 
740 	intr_restore(s);
741 
742 	return (count);
743 }
744