/* $OpenBSD: gptimer.c,v 1.4 2014/06/20 14:08:11 rapha Exp $ */ /* * Copyright (c) 2007,2009 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. */ /* * WARNING - this timer initializion has not been checked * to see if it will do _ANYTHING_ sane if the omap enters * low power mode. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* registers */ #define GP_TIDR 0x000 #define GP_TIDR_REV 0xff #define GP_TIOCP_CFG 0x010 #define GP_TIOCP_CFG_CLKA 0x000000300 #define GP_TIOCP_CFG_EMUFREE 0x000000020 #define GP_TIOCP_CFG_IDLEMODE 0x000000018 #define GP_TIOCP_CFG_ENAPWAKEUP 0x000000004 #define GP_TIOCP_CFG_SOFTRESET 0x000000002 #define GP_TIOCP_CFG_AUTOIDLE 0x000000001 #define GP_TISTAT 0x014 #define GP_TISTAT_RESETDONE 0x000000001 #define GP_TISR 0x018 #define GP_TISTAT_TCAR 0x00000004 #define GP_TISTAT_OVF 0x00000002 #define GP_TISTAT_MATCH 0x00000001 #define GP_TIER 0x1c #define GP_TIER_TCAR_EN 0x4 #define GP_TIER_OVF_EN 0x2 #define GP_TIER_MAT_EN 0x1 #define GP_TWER 0x020 #define GP_TWER_TCAR_EN 0x00000004 #define GP_TWER_OVF_EN 0x00000002 #define GP_TWER_MAT_EN 0x00000001 #define GP_TCLR 0x024 #define GP_TCLR_GPO (1<<14) #define GP_TCLR_CAPT (1<<13) #define GP_TCLR_PT (1<<12) #define GP_TCLR_TRG (3<<10) #define GP_TCLR_TRG_O (1<<10) #define GP_TCLR_TRG_OM (2<<10) #define GP_TCLR_TCM (3<<8) #define GP_TCLR_TCM_RISE (1<<8) #define GP_TCLR_TCM_FALL (2<<8) #define GP_TCLR_TCM_BOTH (3<<8) #define GP_TCLR_SCPWM (1<<7) #define GP_TCLR_CE (1<<6) #define GP_TCLR_PRE (1<<5) #define GP_TCLR_PTV (7<<2) #define GP_TCLR_AR (1<<1) #define GP_TCLR_ST (1<<0) #define GP_TCRR 0x028 /* counter */ #define GP_TLDR 0x02c /* reload */ #define GP_TTGR 0x030 #define GP_TWPS 0x034 #define GP_TWPS_TCLR 0x01 #define GP_TWPS_TCRR 0x02 #define GP_TWPS_TLDR 0x04 #define GP_TWPS_TTGR 0x08 #define GP_TWPS_TMAR 0x10 #define GP_TWPS_ALL 0x1f #define GP_TMAR 0x038 #define GP_TCAR 0x03C #define GP_TSICR 0x040 #define GP_TSICR_POSTED 0x00000002 #define GP_TSICR_SFT 0x00000001 #define GP_TCAR2 0x044 #define TIMER_FREQUENCY 32768 /* 32kHz is used, selectable */ static struct evcount clk_count; static struct evcount stat_count; #define GPT1_IRQ 38 #define GPTIMER0_IRQ 38 //static int clk_irq = GPT1_IRQ; /* XXX 37 */ void gptimer_attach(struct device *parent, struct device *self, void *args); int gptimer_intr(void *frame); void gptimer_wait(int reg); void gptimer_cpu_initclocks(void); void gptimer_delay(u_int); void gptimer_setstatclockrate(int newhz); bus_space_tag_t gptimer_iot; bus_space_handle_t gptimer_ioh0, gptimer_ioh1; int gptimer_irq = 0; u_int gptimer_get_timecount(struct timecounter *); static struct timecounter gptimer_timecounter = { gptimer_get_timecount, NULL, 0x7fffffff, 0, "gptimer", 0, NULL }; volatile u_int32_t nexttickevent; volatile u_int32_t nextstatevent; u_int32_t ticks_per_second; u_int32_t ticks_per_intr; u_int32_t ticks_err_cnt; u_int32_t ticks_err_sum; u_int32_t statvar, statmin; struct cfattach gptimer_ca = { sizeof (struct device), NULL, gptimer_attach }; struct cfdriver gptimer_cd = { NULL, "gptimer", DV_DULL }; void gptimer_attach(struct device *parent, struct device *self, void *args) { struct armv7_attach_args *aa = args; bus_space_handle_t ioh; u_int32_t rev; gptimer_iot = aa->aa_iot; if (bus_space_map(gptimer_iot, aa->aa_dev->mem[0].addr, aa->aa_dev->mem[0].size, 0, &ioh)) panic("gptimer_attach: bus_space_map failed!"); rev = bus_space_read_4(gptimer_iot, ioh, GP_TIDR); printf(" rev %d.%d\n", rev >> 4 & 0xf, rev & 0xf); if (self->dv_unit == 0) { gptimer_ioh0 = ioh; gptimer_irq = aa->aa_dev->irq[0]; bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TCLR, 0); } else if (self->dv_unit == 1) { /* start timer because it is used in delay */ gptimer_ioh1 = ioh; bus_space_write_4(gptimer_iot, gptimer_ioh1, GP_TCRR, 0); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh1, GP_TLDR, 0); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh1, GP_TCLR, GP_TCLR_AR | GP_TCLR_ST); gptimer_wait(GP_TWPS_ALL); gptimer_timecounter.tc_frequency = TIMER_FREQUENCY; tc_init(&gptimer_timecounter); } else panic("attaching too many gptimers at 0x%lx", aa->aa_dev->mem[0].addr); arm_clock_register(gptimer_cpu_initclocks, gptimer_delay, gptimer_setstatclockrate, NULL); } /* * See comment in arm/xscale/i80321_clock.c * * counter is count up, but with autoreload timers it is not possible * to detect how many interrupts passed while interrupts 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 two timers are used, GPT1 is used as a reference * clock without reload , however we just ignore the interrupt it * would (may?) generate. * * 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. */ int gptimer_intr(void *frame) { u_int32_t now, r; u_int32_t nextevent, duration; /* clear interrupt */ now = bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); while ((int32_t) (nexttickevent - now) < 0) { nexttickevent += ticks_per_intr; ticks_err_sum += ticks_err_cnt; #if 0 if (ticks_err_sum > hz) { u_int32_t match_error; match_error = ticks_err_sum / hz ticks_err_sum -= (match_error * hz); } #else /* looping a few times is faster than divide */ while (ticks_err_sum > hz) { nexttickevent += 1; ticks_err_sum -= hz; } #endif clk_count.ec_count++; hardclock(frame); } while ((int32_t) (nextstatevent - now) < 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 ((nexttickevent - now) < (nextstatevent - now)) nextevent = nexttickevent; else nextevent = nextstatevent; /* XXX */ duration = nextevent - bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); #if 0 printf("duration 0x%x %x %x\n", nextevent - bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR), bus_space_read_4(gptimer_iot, gptimer_ioh0, GP_TCRR), bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR)); #endif if (duration <= 0) duration = 1; /* trigger immediately. */ if (duration > 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. */ duration = ticks_per_intr; nexttickevent = now; nextstatevent = now; } gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TISR, bus_space_read_4(gptimer_iot, gptimer_ioh0, GP_TISR)); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TCRR, -duration); return 1; } /* * would be interesting to play with trigger mode while having one timer * in 32KHz mode, and the other timer running in sysclk mode and use * the high resolution speeds (matters more for delay than tick timer */ void gptimer_cpu_initclocks() { // u_int32_t now; stathz = 128; profhz = 1024; ticks_per_second = TIMER_FREQUENCY; setstatclockrate(stathz); ticks_per_intr = ticks_per_second / hz; ticks_err_cnt = ticks_per_second % hz; ticks_err_sum = 0;; prcm_setclock(1, PRCM_CLK_SPEED_32); prcm_setclock(2, PRCM_CLK_SPEED_32); /* establish interrupts */ arm_intr_establish(gptimer_irq, IPL_CLOCK, gptimer_intr, NULL, "tick"); /* setup timer 0 (hardware timer 2) */ /* reset? - XXX */ bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TLDR, 0); nexttickevent = nextstatevent = bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR) + ticks_per_intr; gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TIER, GP_TIER_OVF_EN); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TWER, GP_TWER_OVF_EN); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TCLR, GP_TCLR_AR | GP_TCLR_ST); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TISR, bus_space_read_4(gptimer_iot, gptimer_ioh0, GP_TISR)); gptimer_wait(GP_TWPS_ALL); bus_space_write_4(gptimer_iot, gptimer_ioh0, GP_TCRR, -ticks_per_intr); gptimer_wait(GP_TWPS_ALL); } void gptimer_wait(int reg) { while (bus_space_read_4(gptimer_iot, gptimer_ioh0, GP_TWPS) & reg) ; } #if 0 void microtime(struct timeval *tvp) { int s; int deltacnt; u_int32_t counter, expected; s = splhigh(); if (1) { /* not inited */ tvp->tv_sec = 0; tvp->tv_usec = 0; return; } s = splhigh(); counter = bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); expected = nexttickevent; *tvp = time; splx(s); deltacnt = counter - expected + ticks_per_intr; #if 1 /* low frequency timer algorithm */ tvp->tv_usec += deltacnt * 1000000ULL / TIMER_FREQUENCY; #else /* high frequency timer algorithm - XXX */ tvp->tv_usec += deltacnt / (TIMER_FREQUENCY / 1000000ULL); #endif while (tvp->tv_usec >= 1000000) { tvp->tv_sec++; tvp->tv_usec -= 1000000; } } #endif void gptimer_delay(u_int usecs) { u_int32_t clock, oclock, delta, delaycnt; volatile int j; int csec, usec; if (usecs > (0x80000000 / (TIMER_FREQUENCY))) { csec = usecs / 10000; usec = usecs % 10000; delaycnt = (TIMER_FREQUENCY / 100) * csec + (TIMER_FREQUENCY / 100) * usec / 10000; } else { delaycnt = TIMER_FREQUENCY * usecs / 1000000; } if (delaycnt <= 1) for (j = 100; j > 0; j--) ; if (gptimer_ioh1 == 0) { /* BAH */ for (; usecs > 0; usecs--) for (j = 100; j > 0; j--) ; return; } oclock = bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); while (1) { for (j = 100; j > 0; j--) ; clock = bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); delta = clock - oclock; if (delta > delaycnt) break; } } void gptimer_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. */ } u_int gptimer_get_timecount(struct timecounter *tc) { return bus_space_read_4(gptimer_iot, gptimer_ioh1, GP_TCRR); }