diff options
author | Patrick Wildt <patrick@cvs.openbsd.org> | 2013-09-04 14:38:50 +0000 |
---|---|---|
committer | Patrick Wildt <patrick@cvs.openbsd.org> | 2013-09-04 14:38:50 +0000 |
commit | 6bbb01e146782eed471c94cbe2563ae8b59d1b8b (patch) | |
tree | 367a031eac776c18fc2f1d73bf86cb60576272b1 /sys/arch/armv7/omap/gptimer.c | |
parent | d33bef10c2430729f5df07f3a6eca73a8f3f5d1f (diff) |
In the future, we shouldn't have one port port ARM SoC, that's just
ridiculous. This is the first step for a common and generic ARM port
for ARMv7 SoCs.
Diffstat (limited to 'sys/arch/armv7/omap/gptimer.c')
-rw-r--r-- | sys/arch/armv7/omap/gptimer.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/sys/arch/armv7/omap/gptimer.c b/sys/arch/armv7/omap/gptimer.c new file mode 100644 index 00000000000..6741766e4f8 --- /dev/null +++ b/sys/arch/armv7/omap/gptimer.c @@ -0,0 +1,441 @@ +/* $OpenBSD: gptimer.c,v 1.1 2013/09/04 14:38:30 patrick Exp $ */ +/* + * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/time.h> +#include <sys/evcount.h> +#include <sys/device.h> +#include <sys/timetc.h> +#include <dev/clock_subr.h> +#include <machine/bus.h> +#include <armv7/omap/omapvar.h> +#include <armv7/omap/prcmvar.h> + +#include <machine/intr.h> +#include <arm/cpufunc.h> + +/* 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 omap_attach_args *oa = args; + bus_space_handle_t ioh; + u_int32_t rev; + + gptimer_iot = oa->oa_iot; + if (bus_space_map(gptimer_iot, oa->oa_dev->mem[0].addr, + oa->oa_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 = oa->oa_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%x", + oa->oa_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 ((now - nexttickevent) < (now - nextstatevent)) + 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); +} |