/* $OpenBSD: i80321_clock.c,v 1.8 2008/02/14 19:53:22 drahn Exp $ */ /* * Copyright (c) 2006 Dale Rahn * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMER_FREQUENCY 200000000 /* 200MHz */ static struct evcount clk_count; static struct evcount stat_count; static int clk_irq = 129; /* XXX */ static int stat_irq = 130; /* XXX */ uint32_t nextstatevent; uint32_t nexttickevent; uint32_t ticks_per_intr; uint32_t ticks_per_second; uint32_t lastnow; uint32_t statvar, statmin; int i80321_timer_inited; static inline u_int32_t tmr0_read(void); static inline void tmr0_write(u_int32_t val); static inline u_int32_t tcr0_read(void); static inline void tcr0_write(u_int32_t val); static inline u_int32_t trr0_read(void); static inline void trr0_write(u_int32_t val); static inline u_int32_t tmr1_read(void); static inline void tmr1_write(u_int32_t val); static inline u_int32_t tcr1_read(void); static inline void tcr1_write(u_int32_t val); static inline u_int32_t trr1_read(void); static inline void trr1_write(u_int32_t val); static inline u_int32_t tisr_read(void); static inline void tisr_write(u_int32_t val); int i80321_intr(void *frame); u_int tcr1_get_timecount(struct timecounter *tc); static struct timecounter tcr1_timecounter = { tcr1_get_timecount, NULL, 0xffffffff, 0, "tcr1", 0, NULL }; todr_chip_handle_t todr_handle; /* * TMR0 is used in non-reload mode as it is used for both the clock * timer and sched timer. * * The counters on 80321 are count down interrupt on 0, not match * register based, so it is not possible to find out how much * many interrupts passed while irqs were blocked. * also it is not possible to atomically add to the register * get get it to precisely fire at a non-fixed interval. * * To work around this both timers are used, TMR1 is used as a reference * clock set to auto reload with 0xffffffff, however we just ignore the * interrupt it would generate. NOTE: does this drop one tick * ever wrap? Could the reference timer be used in non-reload mode, * where it would just keep counting, and not stop at 0 ? * * Internally this keeps track of when the next timer should fire * and based on that time and the current value of the reference * clock a number is written into the timer count register to schedule * the next event. */ static inline u_int32_t tmr0_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c0, c1, 0" : "=r" (ret)); return ret; } static inline void tmr0_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c0, c1, 0" :: "r" (val)); } static inline u_int32_t tcr0_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c2, c1, 0" : "=r" (ret)); return ret; } static inline void tcr0_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c2, c1, 0" :: "r" (val)); } static inline u_int32_t trr0_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c4, c1, 0" : "=r" (ret)); return ret; } static inline void trr0_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c4, c1, 0" :: "r" (val)); } static inline u_int32_t tmr1_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c1, c1, 0" : "=r" (ret)); return ret; } static inline void tmr1_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c1, c1, 0" :: "r" (val)); } inline u_int32_t tcr1_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c3, c1, 0" : "=r" (ret)); return ret; } static inline void tcr1_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c3, c1, 0" :: "r" (val)); } static inline u_int32_t trr1_read(void) { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c5, c1, 0" : "=r" (ret)); return ret; } static inline void trr1_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c5, c1, 0" :: "r" (val)); } static inline u_int32_t tisr_read() { u_int32_t ret; __asm volatile ("mrc p6, 0, %0, c6, c1, 0" : "=r" (ret)); return ret; } static inline void tisr_write(u_int32_t val) { __asm volatile ("mcr p6, 0, %0, c6, c1, 0" :: "r" (val)); } /* counter counts down not up, so reverse the results by subtracting. */ u_int tcr1_get_timecount(struct timecounter *tc) { return UINT_MAX - tcr1_read(); } /* * timer 1 is running a timebase counter, * ie reload 0xffffffff, reload, interrupt ignored * timer 0 will be programmed with the delay until the next * event. this is not set for reload */ int i80321_intr(void *frame) { uint32_t now, r; uint32_t nextevent; tisr_write(TISR_TMR0); now = tcr1_read(); #if 0 if (lastnow < now) { /* rollover, remove the missing 'tick'; 1-0xffffffff, not 0- */ nextstatevent -=1; nexttickevent -=1; } #endif while ((int32_t) (now - nexttickevent) < 0) { nexttickevent -= ticks_per_intr; /* XXX - correct nexttickevent? */ clk_count.ec_count++; hardclock(frame); } while ((int32_t) (now - nextstatevent) < 0) { do { r = random() & (statvar -1); } while (r == 0); /* random == 0 not allowed */ nextstatevent -= statmin + r; /* XXX - correct nextstatevent? */ stat_count.ec_count++; statclock(frame); } if ((now - nexttickevent) < (now - nextstatevent)) nextevent = now - nexttickevent; else nextevent = now - nextstatevent; if (nextevent < 10 /* XXX */) nextevent = 10; if (nextevent > ticks_per_intr) { /* * If interrupts are blocked too long, like during * the root prompt or ddb, the timer can roll over, * this will allow the system to continue to run * even if time is lost. */ nextevent = ticks_per_intr; nexttickevent = now; nextstatevent = now; } tcr0_write(nextevent); tmr0_write(TMRx_ENABLE|TMRx_PRIV|TMRx_CSEL_CORE); lastnow = now; return 1; } void cpu_initclocks() { uint32_t now; struct timeval rtctime; time_t first_sec, sec; u_int32_t first_tb, second_tb; /* would it make sense to have this be 100/1000 to round nicely? */ /* 100/1000 or 128/1024 ? */ stathz = 100; profhz = 1000; ticks_per_second = 200 * 1000000; /* 200 MHz */ if (todr_handle != NULL && todr_gettime(todr_handle, &rtctime) == 0) { int psw; int new_tps; int tps_diff; psw = disable_interrupts(I32_bit); first_sec = rtctime.tv_sec; do { first_tb = tcr1_read(); todr_gettime(todr_handle, &rtctime); sec = rtctime.tv_sec; } while (sec == first_sec); first_sec = sec; do { second_tb = tcr1_read(); todr_gettime(todr_handle, &rtctime); sec = rtctime.tv_sec; } while (sec == first_sec); new_tps = first_tb - second_tb; tps_diff = ticks_per_second - new_tps; if (tps_diff < 0) tps_diff = -tps_diff; /* * only if the calculated time is more than 0.1% use the * new calculate time. Otherwise system with accurate clocks * can be penalized. (error in measurement) */ if (tps_diff > ticks_per_second/1000) ticks_per_second = new_tps; restore_interrupts(psw); } setstatclockrate(stathz); ticks_per_intr = ticks_per_second / hz; evcount_attach(&clk_count, "clock", (void *)&clk_irq, &evcount_intr); evcount_attach(&stat_count, "stat", (void *)&stat_irq, &evcount_intr); (void) i80321_intr_establish(ICU_INT_TMR0, IPL_CLOCK, i80321_intr, NULL, NULL); now = 0xffffffff; nextstatevent = now - ticks_per_intr; nexttickevent = now - ticks_per_intr; tcr1_write(now); trr1_write(now); tmr1_write(TMRx_ENABLE|TMRx_RELOAD|TMRx_PRIV|TMRx_CSEL_CORE); tcr0_write(now); /* known big value */ tmr0_write(TMRx_ENABLE|TMRx_PRIV|TMRx_CSEL_CORE); tcr0_write(ticks_per_intr); tcr1_timecounter.tc_frequency = ticks_per_second; tc_init(&tcr1_timecounter); i80321_timer_inited = 1; } void delay(u_int usecs) { u_int32_t clock, oclock, delta, delaycnt; volatile int j; int csec, usec; csec = usecs / 10000; usec = usecs % 10000; delaycnt = (TIMER_FREQUENCY / 100) * csec + (TIMER_FREQUENCY / 100) * usec / 10000; if (delaycnt <= 1) /* delay too short spin for a bit */ for (j = 100; j > 0; j--) ; if (i80321_timer_inited == 0) { /* clock isn't initialized yet */ for (; usecs > 0; usecs--) for (j = 100; j > 0; j--) ; return; } oclock = tcr1_read(); while(1) { clock = tcr1_read(); /* timer counts down, not up so old - new */ delta = oclock - clock; if (delta > delaycnt) break; } } void setstatclockrate(int newhz) { int minint, statint; int s; s = splclock(); statint = ticks_per_second / newhz; /* calculate largest 2^n which is smaller that just over half statint */ statvar = 0x40000000; /* really big power of two */ minint = statint / 2 + 100; while (statvar > minint) statvar >>= 1; statmin = statint - (statvar >> 1); splx(s); /* * XXX this allows the next stat timer to occur then it switches * to the new frequency. Rather than switching instantly. */ } void i80321_calibrate_delay(void) { tmr1_write(0); /* stop timer */ tisr_write(TISR_TMR1); /* clear interrupt */ trr1_write(0xffffffff); /* reload value */ tcr1_write(0xffffffff); /* current value */ tmr1_write(TMRx_ENABLE|TMRx_RELOAD|TMRx_PRIV|TMRx_CSEL_CORE); } /* * inittodr: * * Initialize time from the time-of-day register. */ #define MINYEAR 2003 /* minimum plausible year */ void inittodr(time_t base) { time_t deltat; struct timeval rtctime; struct timespec ts; int badbase; if (base < (MINYEAR - 1970) * SECYR) { printf("WARNING: preposterous time in file system\n"); /* read the system clock anyway */ base = (MINYEAR - 1970) * SECYR; badbase = 1; } else badbase = 0; if (todr_handle == NULL || todr_gettime(todr_handle, &rtctime) != 0 || rtctime.tv_sec == 0) { /* * Believe the time in the file system for lack of * anything better, resetting the TODR. */ rtctime.tv_sec = base; rtctime.tv_usec = 0; if (todr_handle != NULL && !badbase) { printf("WARNING: preposterous clock chip time\n"); resettodr(); } goto bad; } ts.tv_sec = rtctime.tv_sec; ts.tv_nsec = rtctime.tv_usec * 1000; tc_setclock(&ts); if (!badbase) { /* * See if we gained/lost two or more days; if * so, assume something is amiss. */ deltat = rtctime.tv_sec - base; if (deltat < 0) deltat = -deltat; if (deltat < 2 * SECDAY) return; /* all is well */ printf("WARNING: clock %s %ld days\n", rtctime.tv_sec < base ? "lost" : "gained", (long)deltat / SECDAY); } bad: printf("WARNING: CHECK AND RESET THE DATE!\n"); } /* * resettodr: * * Reset the time-of-day register with the current time. */ void resettodr(void) { struct timeval rtctime; if (time_second == 0) return; microtime(&rtctime); if (todr_handle != NULL && todr_settime(todr_handle, &rtctime) != 0) printf("resettodr: failed to set time\n"); }