/*$NetBSD: epclk.c,v 1.4 2005/02/26 12:00:52 simonb Exp $*/ /* * Agere Vx115 clock functions * Copyright (c) 2006, Jon Sevy * * Based on epclk.c * Copyright (c) 2004 Jesse Off * All rights reserved. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: *This product includes software developed by the NetBSD *Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Driver for the Vx115 clock tick. * We use Timer 1 for the system clock */ #include __KERNEL_RCSID(0, "$NetBSD: vx115_clk.c,v 1.4 2005/02/26 12:00:52 simonb Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include /* for HZ */ #define DEBUG_CLK #ifdef DEBUG_CLK #define DPRINTF(fmt...) printf(fmt) #else #define DPRINTF(fmt...) #endif static int vx115_clk_match(struct device *, struct cfdata *, void *); static void vx115_clk_attach(struct device *, struct device *, void *); void rtcinit(void); /* callback functions for intr_functions */ static int vx115_clk_intr(void* arg); struct vx115_clk_softc { struct device device; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_intr; }; static struct vx115_clk_softc *vx115_clk_sc = NULL; static struct timeval lasttv; /* Match value for clock timer; running at 16kHz, want HZ ticks per second */ /* BTW, we use HZ == 64 or HZ == 128 so have a nice divisor */ /* NOTE: don't change there without visiting the functions below which */ /* convert between timer counts and microseconds */ #define VX115_CLK_SOURCE (32768/2) #define VX115_TIMER_LATCH (VX115_CLK_SOURCE / HZ) #define VX115_USEC_PER_TICK (1000000 / HZ) static uint32_t vx115_timer_count_to_usec(uint32_t count) { uint32_t result; /* convert specified number of ticks to usec, and round up */ /* note that with 16 kHz tick rate, maximum count will be */ /* 256 (for HZ = 64), so we won't have overflow issues */ result = (1000000 * count) / (VX115_CLK_SOURCE); if ((result*VX115_CLK_SOURCE) != (count*1000000)) { /* round up */ result += 1; } return result; } /* This may only be called when overflow is avoided; typically, */ /* it will be used when usec < VX115_USEC_PER_TICK */ static uint32_t vx115_usec_to_timer_count(uint32_t usec) { uint32_t result; /* convert specified number of usec to timer ticks, and round up */ result = (VX115_CLK_SOURCE * usec) / 1000000; if ((result*1000000) != (usec*VX115_CLK_SOURCE)) { /* round up */ result += 1; } return result; } /* macros to simplify writing to the timer controller */ #define vx115_read_clk(offset) bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset) #define vx115_write_clk(offset, value) bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, value) CFATTACH_DECL(vx115_clk, sizeof(struct vx115_clk_softc), vx115_clk_match, vx115_clk_attach, NULL, NULL); static int vx115_clk_match(struct device *parent, struct cfdata *match, void *aux) { DPRINTF("vx115_clk_match\n"); return 2; } static void vx115_clk_attach(struct device *parent, struct device *self, void *aux) { struct vx115_clk_softc *sc = (struct vx115_clk_softc*) self; struct vx115_attach_args *sa = (struct vx115_attach_args*) aux; DPRINTF("vx115_clk_attach\n"); sc->sc_iot = sa->sa_iot; sc->sc_intr = sa->sa_intr; if (vx115_clk_sc == NULL) vx115_clk_sc = sc; /* map bus space and get handle */ if (bus_space_map(sc->sc_iot, sa->sa_addr, sa->sa_size, 0, &sc->sc_ioh) != 0) panic("%s: Cannot map registers", self->dv_xname); } /* * vx115_clk_intr: * *Handle the hardclock interrupt. */ static int vx115_clk_intr(void *arg) { struct vx115_clk_softc *sc = vx115_clk_sc; /* make sure it's the kernel timer that generated the interupt */ /* need to do this since the interrupt line is shared by the */ /* other interval and PWM timers */ if (vx115_read_clk(TIM_TMRSR) & TIM_TMRSR_I1S) { /* clear the interrupt bit in the timer */ vx115_write_clk(TIM_TMRSR, TIM_TMRSR_I1S); /* call the kernel timer handler */ hardclock((struct clockframe*) arg); #if 0 if (hardclock_ticks % HZ == 0) printf("time %i sec\n", hardclock_ticks/HZ); #endif return 1; } else { /* it's one of the other timers; just pass it on */ return 0; } } /* * setstatclockrate: * *Set the rate of the statistics clock. * *We assume that hz is either stathz or profhz, and that neither *will change after being set by cpu_initclocks(). We could *recalculate the intervals here, but that would be a pain. */ void setstatclockrate(int hz) { /* use hardclock */ } /* * cpu_initclocks: * *Initialize the clock and get it going. */ void cpu_initclocks(void) { struct vx115_clk_softc *sc = vx115_clk_sc; stathz = profhz = 0; /* set up and enable interval timer 1 as kernel timer, */ /* using 32kHz clock source */ /* Note that since clock rate divisor is 2 for TMRCNTRATE = 0 */ /* the actual clock rate is 16kHz */ vx115_write_clk(TIM_TMRCR, (TIM_TMRCR_ITE1 | TIM_TMRCR_ITC1_32K)); /* register interrupt handler */ vx115_intr_establish(sc->sc_intr, IPL_CLOCK, vx115_clk_intr, NULL); vx115_configure_irq(sc->sc_intr, VX115_INT_SENSE_LEVEL, VX115_INT_POLARITY_HIGH); vx115_enable_irq(sc->sc_intr); /* Enable interrupts from timer 1 */ vx115_write_clk(TIM_TMRIE, TIM_TMRIE_I1E); /* load the max register; when this happens, we're running */ vx115_write_clk(TIM_ITMAXC1, VX115_TIMER_LATCH); } /* * microtime: * *Fill in the specified timeval struct with the current time *accurate to the microsecond. */ void microtime(register struct timeval *tvp) { struct vx115_clk_softc *sc = vx115_clk_sc; u_int oldirqstate; u_int current_count; #ifdef DEBUG if (vx115_clk_sc == NULL) { printf("microtime: called before initialize vx115_clk\n"); tvp->tv_sec = 0; tvp->tv_usec = 0; return; } #endif oldirqstate = disable_interrupts(I32_bit); /* get current timer count */ current_count = vx115_read_clk(TIM_ITCNT1); /* Fill in the timeval struct. */ *tvp = time; /* Refine the usec field using current timer count */ tvp->tv_usec += vx115_timer_count_to_usec(VX115_TIMER_LATCH - current_count); /* Make sure microseconds doesn't overflow. */ while (__predict_false(tvp->tv_usec >= 1000000)) { tvp->tv_usec -= 1000000; tvp->tv_sec++; } /* Make sure the time has advanced. */ if (__predict_false(tvp->tv_sec == lasttv.tv_sec && tvp->tv_usec <= lasttv.tv_usec)) { tvp->tv_usec = lasttv.tv_usec + 1; if (tvp->tv_usec >= 1000000) { tvp->tv_usec -= 1000000; tvp->tv_sec++; } } lasttv = *tvp; restore_interrupts(oldirqstate); } extern int hardclock_ticks; static void tdelay(unsigned int ticks) { u_int32_t start, end, current; current = hardclock_ticks; start = current; end = start + ticks; /* just loop for the specified number of ticks */ while (current < end) current = hardclock_ticks; } static void udelay(unsigned int usec) { struct vx115_clk_softc *sc = vx115_clk_sc; u_int32_t start, end, current; current = vx115_read_clk(TIM_ITCNT1); start = current; end = start - vx115_usec_to_timer_count(usec); if (end <= 0) { /* need for counter to wrap; adjust end value, and */ /* wait for current to be below end but above start */ end += VX115_TIMER_LATCH; while (!((current <= end) && (current > start))) current = vx115_read_clk(TIM_ITCNT1); } else { /* just wait until count value is at or below end value */ while (current > end) current = vx115_read_clk(TIM_ITCNT1); } } /* * delay: * *Delay for at least N microseconds. Note that due to our coarse clock, * our resolution is 61 us. But we round up so we'll wait at least as * long as requested. */ void delay(unsigned int usec) { #ifdef DEBUG if (vx115_clk_sc == NULL) { printf("delay: called before start vx115_clk\n"); return; } #endif if (usec >= VX115_USEC_PER_TICK) { printf("delay: called with large value (requested delay = %i us)\n", usec); /* have more than 1 tick; just do in ticks */ unsigned int ticks = usec/VX115_USEC_PER_TICK; if (ticks*VX115_USEC_PER_TICK != usec) ticks += 1; tdelay(ticks); } else { /* less than 1 tick; can do as usec */ udelay(usec); } } todr_chip_handle_t todr_handle; /* * todr_attach: * *Set the specified time-of-day register as the system real-time clock. */ void todr_attach(todr_chip_handle_t todr) { if (todr_handle) panic("todr_attach: rtc already configured"); todr_handle = todr; } /* * inittodr: * *Initialize time from the time-of-day register. */ #define MINYEAR 2003 /* minimum plausible year */ void inittodr(time_t base) { time_t deltat; 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, (struct timeval *)&time) != 0 || time.tv_sec == 0) { /* * Believe the time in the file system for lack of * anything better, resetting the TODR. */ time.tv_sec = base; time.tv_usec = 0; if (todr_handle != NULL && !badbase) { printf("WARNING: preposterous clock chip time\n"); resettodr(); } goto bad; } if (!badbase) { /* * See if we gained/lost two or more days; if * so, assume something is amiss. */ deltat = time.tv_sec - base; if (deltat < 0) deltat = -deltat; if (deltat < 2 * SECDAY) return;/* all is well */ printf("WARNING: clock %s %ld days\n", time.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) { if (time.tv_sec == 0) return; if ((todr_handle != NULL) && (todr_settime(todr_handle, (struct timeval *)&time) != 0)) printf("resettodr: failed to set time\n"); }