/* $OpenBSD: clock.c,v 1.13 2005/12/03 21:36:17 brad Exp $ */ /* $NetBSD: clock.c,v 1.20 1997/04/27 20:43:38 thorpej Exp $ */ /* * Copyright (c) 1988 University of Utah. * Copyright (c) 1982, 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * the Systems Programming Group of the University of Utah Computer * Science Department. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * from: Utah $Hdr: clock.c 1.18 91/01/21$ * * @(#)clock.c 8.2 (Berkeley) 1/12/94 */ /* * HPs use the MC6840 PTM with the following arrangement: * Timers 1 and 3 are externally driver from a 25MHz source. * Output from timer 3 is tied to the input of timer 2. * The latter makes it possible to use timers 3 and 2 together to get * a 32-bit countdown timer. */ #include #include #include #include #include #include #include #include #include /* for BBC */ #include #ifdef GPROF #include #endif int clkstd[1]; static int clkint; /* clock interval, as loaded */ /* * Statistics clock interval and variance, in usec. Variance must be a * power of two. Since this gives us an even number, not an odd number, * we discard one case and compensate. That is, a variance of 1024 would * give us offsets in [0..1023]. Instead, we take offsets in [1..1023]. * This is symmetric about the point 512, or statvar/2, and thus averages * to that value (assuming uniform random numbers). */ static int statvar = 1024 / 4; /* {stat,prof}clock variance */ static int statmin; /* statclock interval - variance/2 */ static int profmin; /* profclock interval - variance/2 */ static int timer3min; /* current, from above choices */ static int statprev; /* previous value in stat timer */ static int month_days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; u_char bbc_registers[13]; volatile u_int8_t *bbcaddr = NULL; void clockintr(struct clockframe *); void statintr(struct clockframe *); void hp300_calibrate_delay(void); struct bbc_tm *gmt_to_bbc(long); int bbc_to_gmt(u_long *); void read_bbc(void); u_char read_bbc_reg(int); void send_clock_cmd(volatile u_int8_t *, u_int8_t, u_int8_t *, u_int8_t, u_int8_t *); u_char write_bbc_reg(int, u_int); static int clock_ipl = IPL_CLOCK; static int stat_ipl = IPL_STATCLOCK; struct evcount clockcnt; struct evcount statcnt; /* * Machine-dependent clock routines. * * A note on the real-time clock: * We actually load the clock with interval-1 instead of interval. * This is because the counter decrements to zero after N+1 enabled clock * periods where N is the value loaded into the counter. * * The frequencies of the HP300 clocks must be a multiple of four * microseconds (since the clock counts in 4 us units). */ #define COUNTS_PER_SEC (1000000 / CLK_RESOLUTION) /* * Calibrate the delay constant, based on Chuck Cranor's * mvme68k delay calibration algorithm. */ void hp300_calibrate_delay() { extern int delay_divisor; volatile struct clkreg *clk; volatile u_char csr; int intvl; clkstd[0] = IIOV(0x5F8000); /* XXX yuck */ clk = (volatile struct clkreg *)clkstd[0]; /* * Calibrate delay() using the 4 usec counter. * We adjust delay_divisor until we get the result we want. * We assume we've been called at splhigh(). */ for (delay_divisor = 140; delay_divisor > 1; delay_divisor--) { /* Reset clock chip */ clk->clk_cr2 = CLK_CR1; clk->clk_cr1 = CLK_RESET; /* * Prime the timer. We're looking for * 10,000 usec (10ms). See interval comment * above. */ intvl = (10000 / CLK_RESOLUTION) - 1; asm volatile(" movpw %0,%1@(5)" : : "d" (intvl), "a" (clk)); /* Enable the timer */ clk->clk_cr2 = CLK_CR1; clk->clk_cr1 = CLK_IENAB; delay(10000); /* Timer1 interrupt flag high? */ csr = clk->clk_sr; if (csr & CLK_INT1) { /* * Got it. Clear interrupt and get outta here. */ asm volatile(" movpw %0@(5),%1" : : "a" (clk), "d" (intvl)); break; } /* * Nope. Poll for completion of the interval, * clear interrupt, and try again. */ do { csr = clk->clk_sr; } while ((csr & CLK_INT1) == 0); asm volatile(" movpw %0@(5),%1" : : "a" (clk), "d" (intvl)); } /* * Make sure the clock interrupt is disabled. Otherwise, * we can end up calling hardclock() before proc0 is set up, * causing a bad pointer deref. */ clk->clk_cr2 = CLK_CR1; clk->clk_cr1 = CLK_RESET; /* * Sanity check the delay_divisor value. If we totally lost, * assume a 50MHz CPU; */ if (delay_divisor == 0) delay_divisor = 2048 / 50; /* Calculate CPU speed. */ cpuspeed = 2048 / delay_divisor; } /* * Set up the real-time and statistics clocks. Leave stathz 0 only if * no alternative timer is available. */ void cpu_initclocks() { volatile struct clkreg *clk; int intvl, statint, profint, minint; clkstd[0] = IIOV(0x5F8000); /* XXX grot */ clk = (volatile struct clkreg *)clkstd[0]; if (COUNTS_PER_SEC % hz) { printf("cannot get %d Hz clock; using 100 Hz\n", hz); hz = 100; } /* * Clock has several counters, so we can always use separate * statclock. */ if (stathz == 0) /* XXX should be set in param.c */ stathz = hz; else if (COUNTS_PER_SEC % stathz) { printf("cannot get %d Hz statclock; using 100 Hz\n", stathz); stathz = 100; } if (profhz == 0) /* XXX should be set in param.c */ profhz = stathz * 5; else if (profhz < stathz || COUNTS_PER_SEC % profhz) { printf("cannot get %d Hz profclock; using %d Hz\n", profhz, stathz); profhz = stathz; } intvl = COUNTS_PER_SEC / hz; statint = COUNTS_PER_SEC / stathz; profint = COUNTS_PER_SEC / profhz; minint = statint / 2 + 100; while (statvar > minint) statvar >>= 1; tick = intvl * CLK_RESOLUTION; /* adjust interval counts, per note above */ intvl--; statint--; profint--; /* calculate base reload values */ clkint = intvl; statmin = statint - (statvar >> 1); profmin = profint - (statvar >> 1); timer3min = statmin; statprev = statint; evcount_attach(&statcnt, "stat", &stat_ipl, &evcount_intr); evcount_attach(&clockcnt, "clock", &clock_ipl, &evcount_intr); /* finally, load hardware */ clk->clk_cr2 = CLK_CR1; clk->clk_cr1 = CLK_RESET; asm volatile(" movpw %0,%1@(5)" : : "d" (intvl), "a" (clk)); asm volatile(" movpw %0,%1@(9)" : : "d" (0), "a" (clk)); asm volatile(" movpw %0,%1@(13)" : : "d" (statint), "a" (clk)); clk->clk_cr2 = CLK_CR1; clk->clk_cr1 = CLK_IENAB; clk->clk_cr2 = CLK_CR3; clk->clk_cr3 = CLK_IENAB; } /* * We assume newhz is either stathz or profhz, and that neither will * change after being set up above. Could recalculate intervals here * but that would be a drag. */ void setstatclockrate(newhz) int newhz; { if (newhz == stathz) timer3min = statmin; else timer3min = profmin; } /* * Timer clock interrupt. */ void clockintr(fp) struct clockframe *fp; { clockcnt.ec_count++; hardclock(fp); } /* * Statistics/profiling clock interrupt. Compute a new interval. * Interrupt has already been cleared. */ void statintr(fp) struct clockframe *fp; { volatile struct clkreg *clk; int newint, r, var; clk = (volatile struct clkreg *)clkstd[0]; var = statvar; do { r = random() & (var - 1); } while (r == 0); newint = timer3min + r; /* * The timer was automatically reloaded with the previous latch * value at the time of the interrupt. Compensate now for the * amount of time that has run off since then (minimum of 2-12 * timer ticks depending on CPU type) plus one tick roundoff. * This should keep us closer to the mean. */ asm volatile(" clrl %0; movpw %1@(13),%0" : "=d" (r) : "a" (clk)); newint -= (statprev - r + 1); asm volatile(" movpw %0,%1@(13)" : : "d" (newint), "a" (clk)); statprev = newint; statcnt.ec_count++; statclock(fp); } /* * Return the best possible estimate of the current time. */ void microtime(tvp) struct timeval *tvp; { volatile struct clkreg *clk; int s, u, t, u2, s2; /* * Read registers from slowest-changing to fastest-changing, * then re-read out to slowest. If the values read before the * innermost match those read after, the innermost value is * consistent with the outer values. If not, it may not be and * we must retry. Typically this loop runs only once; occasionally * it runs twice, and only rarely does it run longer. * * (Using this loop avoids the need to block interrupts.) */ clk = (volatile struct clkreg *)clkstd[0]; do { s = time.tv_sec; u = time.tv_usec; asm volatile (" clrl %0; movpw %1@(5),%0" : "=d" (t) : "a" (clk)); u2 = time.tv_usec; s2 = time.tv_sec; } while (u != u2 || s != s2); u += (clkint - t) * CLK_RESOLUTION; if (u >= 1000000) { /* normalize */ s++; u -= 1000000; } tvp->tv_sec = s; tvp->tv_usec = u; } /* * Initialize the time of day register, based on the time base which is, e.g. * from a filesystem. */ void inittodr(base) time_t base; { u_long timbuf = base; /* assume no battery clock exists */ static int bbcinited = 0; /* XXX */ if (!bbcinited) { if (machineid == HP_425 && mmuid == MMUID_425_E) bbcaddr = NULL; else { if (badbaddr((caddr_t)(BBCADDR + HILP_STAT))) printf("WARNING: no battery clock\n"); else bbcaddr = BBCADDR; } bbcinited = 1; } /* * bbc_to_gmt converts and stores the gmt in timbuf. * If an error is detected in bbc_to_gmt, or if the filesystem * time is more recent than the gmt time in the clock, * then use the filesystem time and warn the user. */ if (bbcaddr != NULL) { if (!bbc_to_gmt(&timbuf) || timbuf < base) { printf("WARNING: bad date in battery clock\n"); timbuf = base; } } if (base < 5*SECYR) { printf("WARNING: preposterous time in file system"); timbuf = 6*SECYR + 186*SECDAY + SECDAY/2; printf(" -- CHECK AND RESET THE DATE!\n"); } /* Battery clock does not store usec's, so forget about it. */ time.tv_sec = timbuf; } /* * Restore the time of day hardware after a time change. */ void resettodr() { int i; struct bbc_tm *tmptr; if (bbcaddr == NULL) return; tmptr = gmt_to_bbc(time.tv_sec); decimal_to_bbc(0, 1, tmptr->tm_sec); decimal_to_bbc(2, 3, tmptr->tm_min); decimal_to_bbc(4, 5, tmptr->tm_hour); decimal_to_bbc(7, 8, tmptr->tm_mday); decimal_to_bbc(9, 10, tmptr->tm_mon); decimal_to_bbc(11, 12, tmptr->tm_year); /* Some bogusness to deal with seemingly broken hardware. Nonsense */ bbc_registers[5] = ((tmptr->tm_hour / 10) & 0x03) + 8; write_bbc_reg(15, 13); /* reset prescalar */ for (i = 0; i <= NUM_BBC_REGS; i++) if (bbc_registers[i] != write_bbc_reg(i, bbc_registers[i])) { printf("Cannot set battery backed clock\n"); break; } } struct bbc_tm * gmt_to_bbc(tim) long tim; { int i; long hms, day; static struct bbc_tm rt; day = tim / SECDAY; hms = tim % SECDAY; /* Hours, minutes, seconds are easy */ rt.tm_hour = hms / 3600; rt.tm_min = (hms % 3600) / 60; rt.tm_sec = (hms % 3600) % 60; /* Number of years in days */ for (i = STARTOFTIME - 1900; day >= days_in_year(i); i++) day -= days_in_year(i); rt.tm_year = i; /* Number of months in days left */ if (leapyear(rt.tm_year)) days_in_month(FEBRUARY) = 29; for (i = 1; day >= days_in_month(i); i++) day -= days_in_month(i); days_in_month(FEBRUARY) = 28; rt.tm_mon = i; /* Days are what is left over (+1) from all that. */ rt.tm_mday = day + 1; return(&rt); } int bbc_to_gmt(timbuf) u_long *timbuf; { int i; u_long tmp; int year, month, day, hour, min, sec; read_bbc(); sec = bbc_to_decimal(1, 0); min = bbc_to_decimal(3, 2); /* * Hours are different for some reason. Makes no sense really. */ hour = ((bbc_registers[5] & 0x03) * 10) + bbc_registers[4]; day = bbc_to_decimal(8, 7); month = bbc_to_decimal(10, 9); year = bbc_to_decimal(12, 11) + 1900; range_test(hour, 0, 23); range_test(day, 1, 31); range_test(month, 1, 12); range_test(year, STARTOFTIME, 2038); /* 2038 is the end of time. */ tmp = 0; for (i = STARTOFTIME; i < year; i++) tmp += days_in_year(i); if (leapyear(year) && month > FEBRUARY) tmp++; for (i = 1; i < month; i++) tmp += days_in_month(i); tmp += (day - 1); tmp = ((tmp * 24 + hour) * 60 + min) * 60 + sec; *timbuf = tmp; return(1); } void read_bbc() { int i, read_okay; read_okay = 0; while (!read_okay) { read_okay = 1; for (i = 0; i <= NUM_BBC_REGS; i++) bbc_registers[i] = read_bbc_reg(i); for (i = 0; i <= NUM_BBC_REGS; i++) if (bbc_registers[i] != read_bbc_reg(i)) read_okay = 0; } } u_char read_bbc_reg(reg) int reg; { u_char data = reg; if (bbcaddr != NULL) { send_clock_cmd(bbcaddr, BBC_SET_REG, &data, 1, NULL); send_clock_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &data); } return(data); } u_char write_bbc_reg(reg, data) int reg; u_int data; { u_char tmp; tmp = (u_char) ((data << HIL_SSHIFT) | reg); if (bbcaddr != NULL) { send_clock_cmd(bbcaddr, BBC_SET_REG, &tmp, 1, NULL); send_clock_cmd(bbcaddr, BBC_WRITE_REG, NULL, 0, NULL); send_clock_cmd(bbcaddr, BBC_READ_REG, NULL, 0, &tmp); } return(tmp); } /* * Battery-backed clock command interface. * The BBC appears to have an HIL-like command interface, but can not attach * as a complete HIL device to an HIL controller driver. * The following routine is a simplified command loop. */ void send_clock_cmd(volatile u_int8_t *address, u_int8_t cmd, u_int8_t *data, u_int8_t dlen, u_int8_t *rdata) { u_int8_t status; int s; s = splvm(); while ((address[HILP_STAT] & HIL_BUSY) != 0) DELAY(1); address[HILP_CMD] = cmd; while (dlen--) { while ((address[HILP_STAT] & HIL_BUSY) != 0) DELAY(1); address[HILP_DATA] = *data++; DELAY(1); } if (rdata != NULL) { do { while ((address[HILP_STAT] & HIL_DATA_RDY) == 0) DELAY(1); status = address[HILP_STAT]; *rdata = address[HILP_DATA]; DELAY(1); } while (((status >> HIL_SSHIFT) & HIL_SMASK) != HIL_68K); } splx(s); }