xref: /netbsd-src/sys/arch/sun3/sun3/clock.c (revision 07bae7edddbb1ce4c926b2e8db425804589074c9)
1 /*	$NetBSD: clock.c,v 1.24 1995/04/07 04:30:13 gwr Exp $	*/
2 
3 /*
4  * Copyright (c) 1994 Gordon W. Ross
5  * Copyright (c) 1993 Adam Glass
6  * Copyright (c) 1988 University of Utah.
7  * Copyright (c) 1982, 1990, 1993
8  *	The Regents of the University of California.  All rights reserved.
9  *
10  * This code is derived from software contributed to Berkeley by
11  * the Systems Programming Group of the University of Utah Computer
12  * Science Department.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. All advertising materials mentioning features or use of this software
23  *    must display the following acknowledgement:
24  *	This product includes software developed by the University of
25  *	California, Berkeley and its contributors.
26  * 4. Neither the name of the University nor the names of its contributors
27  *    may be used to endorse or promote products derived from this software
28  *    without specific prior written permission.
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40  * SUCH DAMAGE.
41  *
42  *	from: Utah Hdr: clock.c 1.18 91/01/21$
43  *	from: @(#)clock.c	8.2 (Berkeley) 1/12/94
44  */
45 
46 /*
47  * Machine-dependent clock routines for the Intersil 7170:
48  * Original by Adam Glass;  partially rewritten by Gordon Ross.
49  */
50 
51 #include <sys/param.h>
52 #include <sys/systm.h>
53 #include <sys/time.h>
54 #include <sys/kernel.h>
55 #include <sys/device.h>
56 
57 #include <machine/autoconf.h>
58 #include <machine/psl.h>
59 #include <machine/cpu.h>
60 
61 #include <machine/mon.h>
62 #include <machine/obio.h>
63 #include <machine/control.h>
64 
65 #include "intersil7170.h"
66 #include "interreg.h"
67 
68 extern volatile u_char *interrupt_reg;
69 volatile char *clock_va;
70 
71 #define intersil_clock ((volatile struct intersil7170 *) clock_va)
72 
73 #define intersil_command(run, interrupt) \
74 	(run | interrupt | INTERSIL_CMD_FREQ_32K | INTERSIL_CMD_24HR_MODE | \
75 	 INTERSIL_CMD_NORMAL_MODE)
76 
77 #define intersil_clear() (void)intersil_clock->clk_intr_reg
78 
79 int clockmatch __P((struct device *, void *vcf, void *args));
80 void clockattach __P((struct device *, struct device *, void *));
81 
82 struct cfdriver clockcd = {
83 	NULL, "clock", clockmatch, clockattach,
84 	DV_DULL, sizeof(struct device), 0 };
85 
86 int clockmatch(parent, vcf, args)
87     struct device *parent;
88     void *vcf, *args;
89 {
90     struct cfdata *cf = vcf;
91 	struct confargs *ca = args;
92 
93 	/* This driver only supports one unit. */
94 	if (cf->cf_unit != 0)
95 		return (0);
96 	if (ca->ca_paddr == -1)
97 		ca->ca_paddr = OBIO_CLOCK;
98 	if (ca->ca_intpri == -1)
99 		ca->ca_intpri = 5;
100 	return (1);
101 }
102 
103 extern void level5intr_clock();
104 void clockattach(parent, self, args)
105 	struct device *parent;
106 	struct device *self;
107 	void *args;
108 {
109 	struct confargs *ca = args;
110 
111 	printf("\n");
112 	if (ca->ca_intpri != 5)
113 		panic("clock: level != 5");
114 	/*
115 	 * Can not hook up the ISR until cpu_initclock()
116 	 * because hardclock is not ready until then.
117 	 */
118 }
119 
120 /*
121  * Set and/or clear the desired clock bits in the interrupt
122  * register.  We have to be extremely careful that we do it
123  * in such a manner that we don't get ourselves lost.
124  */
125 set_clk_mode(on, off, enable)
126 	u_char on, off;
127 	int enable;
128 {
129 	register u_char interreg;
130 	register int s;
131 
132 	s = getsr();
133 	if ((s & PSL_IPL) < PSL_IPL7)
134 		panic("set_clk_mode: ipl");
135 
136 	if (!intersil_clock)
137 		panic("set_clk_mode: map");
138 
139 	/*
140 	 * make sure that we are only playing w/
141 	 * clock interrupt register bits
142 	 */
143 	on &= (IREG_CLOCK_ENAB_7 | IREG_CLOCK_ENAB_5);
144 	off &= (IREG_CLOCK_ENAB_7 | IREG_CLOCK_ENAB_5);
145 
146 	/*
147 	 * Get a copy of current interrupt register,
148 	 * turning off any undesired bits (aka `off')
149 	 */
150 	interreg = *interrupt_reg & ~(off | IREG_ALL_ENAB);
151 	*interrupt_reg &= ~IREG_ALL_ENAB;
152 
153 	/*
154 	 * Next we turns off the CLK5 and CLK7 bits to clear
155 	 * the flip-flops, then we disable clock interrupts.
156 	 * Now we can read the clock's interrupt register
157 	 * to clear any pending signals there.
158 	 */
159 	*interrupt_reg &= ~(IREG_CLOCK_ENAB_7 | IREG_CLOCK_ENAB_5);
160 	intersil_clock->clk_cmd_reg =
161 		intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IDISABLE);
162 	intersil_clear();
163 
164 	/*
165 	 * Now we set all the desired bits
166 	 * in the interrupt register, then
167 	 * we turn the clock back on and
168 	 * finally we can enable all interrupts.
169 	 */
170 	*interrupt_reg |= (interreg | on);		/* enable flip-flops */
171 
172 	if (enable)
173 		intersil_clock->clk_cmd_reg =
174 			intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IENABLE);
175 
176 	*interrupt_reg |= IREG_ALL_ENAB;		/* enable interrupts */
177 }
178 
179 /* Called very early by internal_configure. */
180 void clock_init()
181 {
182 	clock_va = obio_find_mapping(OBIO_CLOCK, OBIO_CLOCK_SIZE);
183 
184 	if (!clock_va)
185 		mon_panic("clock_init: clock_va\n");
186 	if (!interrupt_reg)
187 		mon_panic("clock_init: interrupt_reg\n");
188 
189 	/* Turn off clock interrupts until cpu_initclocks() */
190 	/* isr_init() already set the interrupt reg to zero. */
191 	intersil_clock->clk_cmd_reg =
192 		intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IDISABLE);
193 	intersil_clear();
194 }
195 
196 #ifdef	DIAGNOSTIC
197 static int clk_intr_ready;
198 #endif
199 
200 /*
201  * Set up the real-time clock (enable clock interrupts).
202  * Leave stathz 0 since there is no secondary clock available.
203  * Note that clock interrupts MUST STAY DISABLED until here.
204  */
205 void
206 cpu_initclocks(void)
207 {
208 	int s;
209 
210 	if (!intersil_clock)
211 		panic("cpu_initclocks");
212 	s = splhigh();
213 
214 	isr_add_custom(5, level5intr_clock);
215 #ifdef	DIAGNOSTIC
216 	clk_intr_ready = 1;
217 #endif
218 
219 	/* Set the clock to interrupt 100 time per second. */
220 	intersil_clock->clk_intr_reg = INTERSIL_INTER_CSECONDS;
221 
222 	*interrupt_reg |= IREG_CLOCK_ENAB_5;	/* enable clock */
223 	intersil_clock->clk_cmd_reg =
224 		intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IENABLE);
225 	*interrupt_reg |= IREG_ALL_ENAB;		/* enable interrupts */
226 	splx(s);
227 }
228 
229 /*
230  * This doesn't need to do anything, as we have only one timer and
231  * profhz==stathz==hz.
232  */
233 void
234 setstatclockrate(newhz)
235 	int newhz;
236 {
237 	/* nothing */
238 }
239 
240 /*
241  * This is is called by the "custom" interrupt handler
242  * after it has reset the pending bit in the clock.
243  */
244 int clock_count = 0;
245 void clock_intr(frame)
246 	struct clockframe *frame;
247 {
248 	static unsigned char led_pattern = 0xFE;
249 
250 #ifdef	DIAGNOSTIC
251 	if (!clk_intr_ready)
252 		panic("clock_intr");
253 #endif
254 
255 	/* XXX - Move this LED frobbing to the idle loop? */
256 	clock_count++;
257 	if ((clock_count & 7) == 0) {
258 		led_pattern = (led_pattern << 1) | 1;
259 		if (led_pattern == 0xFF)
260 			led_pattern = 0xFE;
261 		set_control_byte((char *) DIAG_REG, led_pattern);
262 	}
263 	hardclock(frame);
264 }
265 
266 
267 /*
268  * Return the best possible estimate of the time in the timeval
269  * to which tvp points.  We do this by returning the current time
270  * plus the amount of time since the last clock interrupt.
271  *
272  * Check that this time is no less than any previously-reported time,
273  * which could happen around the time of a clock adjustment.  Just for
274  * fun, we guarantee that the time will be greater than the value
275  * obtained by a previous call.
276  */
277 void
278 microtime(tvp)
279 	register struct timeval *tvp;
280 {
281 	int s = splhigh();
282 	static struct timeval lasttime;
283 
284 	*tvp = time;
285 	tvp->tv_usec;
286 	while (tvp->tv_usec > 1000000) {
287 		tvp->tv_sec++;
288 		tvp->tv_usec -= 1000000;
289 	}
290 	if (tvp->tv_sec == lasttime.tv_sec &&
291 		tvp->tv_usec <= lasttime.tv_usec &&
292 		(tvp->tv_usec = lasttime.tv_usec + 1) > 1000000) {
293 		tvp->tv_sec++;
294 		tvp->tv_usec -= 1000000;
295 	}
296 	lasttime = *tvp;
297 	splx(s);
298 }
299 
300 
301 /*
302  * Machine-dependent clock routines.
303  *
304  * Inittodr initializes the time of day hardware which provides
305  * date functions.
306  *
307  * Resettodr restores the time of day hardware after a time change.
308  */
309 #define SECDAY		86400L
310 #define SECYR		(SECDAY * 365)
311 
312 static long clk_get_secs(void);
313 static void clk_set_secs(long);
314 
315 /*
316  * Initialize the time of day register, based on the time base
317  * which is, e.g. from a filesystem.
318  */
319 void inittodr(fs_time)
320 	time_t fs_time;
321 {
322 	long diff, clk_time;
323 	long long_ago = (5 * SECYR);
324 	int clk_bad = 0;
325 
326 	/*
327 	 * Sanity check time from file system.
328 	 * If it is zero,assume filesystem time is just unknown
329 	 * instead of preposterous.  Don't bark.
330 	 */
331 	if (fs_time < long_ago) {
332 		/*
333 		 * If fs_time is zero, assume filesystem time is just
334 		 * unknown instead of preposterous.  Don't bark.
335 		 */
336 		if (fs_time != 0)
337 			printf("WARNING: preposterous time in file system\n");
338 		/* 1991/07/01  12:00:00 */
339 		fs_time = 21*SECYR + 186*SECDAY + SECDAY/2;
340 	}
341 
342 	clk_time = clk_get_secs();
343 
344 	/* Sanity check time from clock. */
345 	if (clk_time < long_ago) {
346 		printf("WARNING: bad date in battery clock");
347 		clk_bad = 1;
348 		clk_time = fs_time;
349 	} else {
350 		/* Does the clock time jive with the file system? */
351 		diff = clk_time - fs_time;
352 		if (diff < 0)
353 			diff = -diff;
354 		if (diff >= (SECDAY*2)) {
355 			printf("WARNING: clock %s %d days",
356 				   (clk_time < fs_time) ? "lost" : "gained",
357 				   diff / SECDAY);
358 			clk_bad = 1;
359 		}
360 	}
361 	if (clk_bad)
362 		printf(" -- CHECK AND RESET THE DATE!\n");
363 	time.tv_sec = clk_time;
364 }
365 
366 /*
367  * Resettodr restores the time of day hardware after a time change.
368  */
369 void resettodr()
370 {
371 	clk_set_secs(time.tv_sec);
372 }
373 
374 /*
375  * Machine dependent base year:
376  * Note: must be < 1970
377  */
378 #define	CLOCK_BASE_YEAR	1968
379 
380 
381 /*
382  * Routine to copy state into and out of the clock.
383  * The clock registers have to be read or written
384  * in sequential order (or so it appears). -gwr
385  */
386 static void clk_get_dt(struct date_time *dt)
387 {
388 	int s;
389 	register volatile char *src, *dst;
390 
391 	src = (char *) &intersil_clock->counters;
392 
393 	s = splhigh();
394 	intersil_clock->clk_cmd_reg =
395 		intersil_command(INTERSIL_CMD_STOP, INTERSIL_CMD_IENABLE);
396 
397 	dst = (char *) dt;
398 	dt++;	/* end marker */
399 	do {
400 		*dst++ = *src++;
401 	} while (dst < (char*)dt);
402 
403 	intersil_clock->clk_cmd_reg =
404 		intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IENABLE);
405 	splx(s);
406 }
407 
408 static void clk_set_dt(struct date_time *dt)
409 {
410 	int s;
411 	register volatile char *src, *dst;
412 
413 	dst = (char *) &intersil_clock->counters;
414 
415 	s = splhigh();
416 	intersil_clock->clk_cmd_reg =
417 		intersil_command(INTERSIL_CMD_STOP, INTERSIL_CMD_IENABLE);
418 
419 	src = (char *) dt;
420 	dt++;	/* end marker */
421 	do {
422 		*dst++ = *src++;
423 	} while (src < (char *)dt);
424 
425 	intersil_clock->clk_cmd_reg =
426 		intersil_command(INTERSIL_CMD_RUN, INTERSIL_CMD_IENABLE);
427 	splx(s);
428 }
429 
430 
431 
432 /*
433  * Generic routines to convert to or from a POSIX date
434  * (seconds since 1/1/1970) and  yr/mo/day/hr/min/sec
435  *
436  * These are organized this way mostly to so the code
437  * can easily be tested in an independent user program.
438  * (These are derived from the hp300 code.)
439  */
440 
441 /* Traditional UNIX base year */
442 #define	POSIX_BASE_YEAR	1970
443 #define FEBRUARY	2
444 
445 #define	leapyear(year)		((year) % 4 == 0)
446 #define	days_in_year(a) 	(leapyear(a) ? 366 : 365)
447 #define	days_in_month(a) 	(month_days[(a) - 1])
448 
449 static int month_days[12] = {
450 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
451 };
452 
453 void gmt_to_dt(long *tp, struct date_time *dt)
454 {
455 	register int i;
456 	register long days, secs;
457 
458 	days = *tp / SECDAY;
459 	secs = *tp % SECDAY;
460 
461 	/* Hours, minutes, seconds are easy */
462 	dt->dt_hour = secs / 3600;
463 	secs = secs % 3600;
464 	dt->dt_min  = secs / 60;
465 	secs = secs % 60;
466 	dt->dt_sec  = secs;
467 
468 	/* Day of week (Note: 1/1/1970 was a Thursday) */
469 	dt->dt_dow = (days + 4) % 7;
470 
471 	/* Number of years in days */
472 	i = POSIX_BASE_YEAR;
473 	while (days >= days_in_year(i)) {
474 		days -= days_in_year(i);
475 		i++;
476 	}
477 	dt->dt_year = i - CLOCK_BASE_YEAR;
478 
479 	/* Number of months in days left */
480 	if (leapyear(i))
481 		days_in_month(FEBRUARY) = 29;
482 	for (i = 1; days >= days_in_month(i); i++)
483 		days -= days_in_month(i);
484 	days_in_month(FEBRUARY) = 28;
485 	dt->dt_month = i;
486 
487 	/* Days are what is left over (+1) from all that. */
488 	dt->dt_day = days + 1;
489 }
490 
491 void dt_to_gmt(struct date_time *dt, long *tp)
492 {
493 	register int i;
494 	register long tmp;
495 	int year;
496 
497 	/*
498 	 * Hours are different for some reason. Makes no sense really.
499 	 */
500 
501 	tmp = 0;
502 
503 	if (dt->dt_hour >= 24) goto out;
504 	if (dt->dt_day  >  31) goto out;
505 	if (dt->dt_month > 12) goto out;
506 
507 	year = dt->dt_year + CLOCK_BASE_YEAR;
508 
509 	/*
510 	 * Compute days since start of time
511 	 * First from years, then from months.
512 	 */
513 	for (i = POSIX_BASE_YEAR; i < year; i++)
514 		tmp += days_in_year(i);
515 	if (leapyear(year) && dt->dt_month > FEBRUARY)
516 		tmp++;
517 
518 	/* Months */
519 	for (i = 1; i < dt->dt_month; i++)
520 	  	tmp += days_in_month(i);
521 	tmp += (dt->dt_day - 1);
522 
523 	/* Now do hours */
524 	tmp = tmp * 24 + dt->dt_hour;
525 
526 	/* Now do minutes */
527 	tmp = tmp * 60 + dt->dt_min;
528 
529 	/* Now do seconds */
530 	tmp = tmp * 60 + dt->dt_sec;
531 
532  out:
533 	*tp = tmp;
534 }
535 
536 /*
537  * Now routines to get and set clock as POSIX time.
538  */
539 
540 static long clk_get_secs()
541 {
542 	struct date_time dt;
543 	long gmt;
544 
545 	clk_get_dt(&dt);
546 	dt_to_gmt(&dt, &gmt);
547 	return (gmt);
548 }
549 
550 static void clk_set_secs(long secs)
551 {
552 	struct date_time dt;
553 	long gmt;
554 
555 	gmt = secs;
556 	gmt_to_dt(&gmt, &dt);
557 	clk_set_dt(&dt);
558 }
559 
560 
561 #ifdef	DEBUG
562 /* Call this from DDB or whatever... */
563 int clkdebug()
564 {
565 	struct date_time dt;
566 	long gmt;
567 	long *lp;
568 
569 	bzero((char*)&dt, sizeof(dt));
570 	clk_get_dt(&dt);
571 	lp = (long*)&dt;
572 	printf("clkdebug: dt=[%x,%x]\n", lp[0], lp[1]);
573 
574 	dt_to_gmt(&dt, &gmt);
575 	printf("clkdebug: gmt=%x\n", gmt);
576 }
577 #endif
578