Fix timer to support oneshot and periodic mode

Use 64 bit high and low counter for timecounter and delay

Reviewed by: mav@, ian@
Approved by: gonzo@
This commit is contained in:
Ganbold Tsagaankhuu 2013-01-24 09:36:50 +00:00
parent abd1548c8d
commit e62a1d7908

View File

@ -62,12 +62,18 @@ __FBSDID("$FreeBSD$");
#define SW_TIMER0_INT_VALUE_REG 0x14
#define SW_TIMER0_CUR_VALUE_REG 0x18
#define SYS_TIMER_SCAL 16 /* timer clock source pre-divsion */
#define SYS_TIMER_CLKSRC 24000000 /* timer clock source */
#define TMR_INTER_VAL SYS_TIMER_CLKSRC/(SYS_TIMER_SCAL * 1000)
#define SW_COUNTER64LO_REG 0xa4
#define SW_COUNTER64HI_REG 0xa8
#define CNT64_CTRL_REG 0xa0
#define CLOCK_TICK_RATE TMR_INTER_VAL
#define INITIAL_TIMECOUNTER (0xffffffff)
#define CNT64_RL_EN 0x02 /* read latch enable */
#define TIMER_ENABLE (1<<0)
#define TIMER_AUTORELOAD (1<<1)
#define TIMER_OSC24M (1<<2) /* oscillator = 24mhz */
#define TIMER_PRESCALAR (4<<4) /* prescalar = 16 */
#define SYS_TIMER_CLKSRC 24000000 /* clock source */
struct a10_timer_softc {
device_t sc_dev;
@ -76,7 +82,7 @@ struct a10_timer_softc {
bus_space_handle_t sc_bsh;
void *sc_ih; /* interrupt handler */
uint32_t sc_period;
uint32_t clkfreq;
uint32_t timer0_freq;
struct eventtimer et;
};
@ -92,8 +98,10 @@ static int a10_timer_timer_start(struct eventtimer *,
struct bintime *, struct bintime *);
static int a10_timer_timer_stop(struct eventtimer *);
static uint64_t timer_read_counter64(void);
static int a10_timer_initialized = 0;
static int a10_timer_intr(void *);
static int a10_timer_hardclock(void *);
static int a10_timer_probe(device_t);
static int a10_timer_attach(device_t);
@ -113,6 +121,22 @@ static struct resource_spec a10_timer_spec[] = {
{ -1, 0 }
};
static uint64_t
timer_read_counter64(void)
{
uint32_t lo, hi;
/* Latch counter, wait for it to be ready to read. */
timer_write_4(a10_timer_sc, CNT64_CTRL_REG, CNT64_RL_EN);
while (timer_read_4(a10_timer_sc, CNT64_CTRL_REG) & CNT64_RL_EN)
continue;
hi = timer_read_4(a10_timer_sc, SW_COUNTER64HI_REG);
lo = timer_read_4(a10_timer_sc, SW_COUNTER64LO_REG);
return (((uint64_t)hi << 32) | lo);
}
static int
a10_timer_probe(device_t dev)
{
@ -130,7 +154,6 @@ a10_timer_attach(device_t dev)
struct a10_timer_softc *sc;
int err;
uint32_t val;
uint32_t freq;
sc = device_get_softc(dev);
@ -143,28 +166,8 @@ a10_timer_attach(device_t dev)
sc->sc_bst = rman_get_bustag(sc->res[0]);
sc->sc_bsh = rman_get_bushandle(sc->res[0]);
/* set interval */
timer_write_4(sc, SW_TIMER0_INT_VALUE_REG, TMR_INTER_VAL);
/* set clock source to HOSC, 16 pre-division */
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
val &= ~(0x07<<4);
val &= ~(0x03<<2);
val |= (4<<4) | (1<<2);
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
/* set mode to auto reload */
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
val |= (1<<1);
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
/* Enable timer0 */
val = timer_read_4(sc, SW_TIMER_IRQ_EN_REG);
val |= (1<<0);
timer_write_4(sc, SW_TIMER_IRQ_EN_REG, val);
/* Setup and enable the timer interrupt */
err = bus_setup_intr(dev, sc->res[1], INTR_TYPE_CLK, a10_timer_intr,
err = bus_setup_intr(dev, sc->res[1], INTR_TYPE_CLK, a10_timer_hardclock,
NULL, sc, &sc->sc_ih);
if (err != 0) {
bus_release_resources(dev, a10_timer_spec, sc->res);
@ -172,17 +175,27 @@ a10_timer_attach(device_t dev)
"err = %d\n", err);
return (ENXIO);
}
freq = SYS_TIMER_CLKSRC;
/* Set desired frequency in event timer and timecounter */
sc->et.et_frequency = (uint64_t)freq;
sc->clkfreq = (uint64_t)freq;
/* Set clock source to OSC24M, 16 pre-division */
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
val |= TIMER_PRESCALAR | TIMER_OSC24M;
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
/* Enable timer0 */
val = timer_read_4(sc, SW_TIMER_IRQ_EN_REG);
val |= TIMER_ENABLE;
timer_write_4(sc, SW_TIMER_IRQ_EN_REG, val);
sc->timer0_freq = SYS_TIMER_CLKSRC;
/* Set desired frequency in event timer and timecounter */
sc->et.et_frequency = sc->timer0_freq;
sc->et.et_name = "a10_timer Eventtimer";
sc->et.et_flags = ET_FLAGS_ONESHOT | ET_FLAGS_PERIODIC;
sc->et.et_quality = 1000;
sc->et.et_min_period.sec = 0;
sc->et.et_min_period.frac =
((0x00000002LLU << 32) / sc->et.et_frequency) << 32;
((0x00000005LLU << 32) / sc->et.et_frequency) << 32;
sc->et.et_max_period.sec = 0xfffffff0U / sc->et.et_frequency;
sc->et.et_max_period.frac =
((0xfffffffeLLU << 32) / sc->et.et_frequency) << 32;
@ -194,15 +207,20 @@ a10_timer_attach(device_t dev)
if (device_get_unit(dev) == 0)
a10_timer_sc = sc;
a10_timer_timecounter.tc_frequency = (uint64_t)freq;
a10_timer_timecounter.tc_frequency = sc->timer0_freq;
tc_init(&a10_timer_timecounter);
printf("clock: hz=%d stathz = %d\n", hz, stathz);
if (bootverbose) {
device_printf(sc->sc_dev, "clock: hz=%d stathz = %d\n", hz, stathz);
device_printf(sc->sc_dev, "timer clock frequency %d\n", sc->clkfreq);
device_printf(sc->sc_dev, "event timer clock frequency %u\n",
sc->timer0_freq);
device_printf(sc->sc_dev, "timecounter clock frequency %lld\n",
a10_timer_timecounter.tc_frequency);
}
a10_timer_initialized = 1;
return (0);
}
@ -211,25 +229,42 @@ a10_timer_timer_start(struct eventtimer *et, struct bintime *first,
struct bintime *period)
{
struct a10_timer_softc *sc;
uint32_t clo, count;
uint32_t count;
uint32_t val;
sc = (struct a10_timer_softc *)et->et_priv;
if (first != NULL) {
sc->sc_period = 0;
if (period != NULL) {
sc->sc_period = (sc->et.et_frequency * (period->frac >> 32)) >> 32;
sc->sc_period += sc->et.et_frequency * period->sec;
}
if (first == NULL)
count = sc->sc_period;
else {
count = (sc->et.et_frequency * (first->frac >> 32)) >> 32;
if (first->sec != 0)
count += sc->et.et_frequency * first->sec;
/* clear */
timer_write_4(sc, SW_TIMER0_CUR_VALUE_REG, 0);
clo = timer_read_4(sc, SW_TIMER0_CUR_VALUE_REG);
clo += count;
timer_write_4(sc, SW_TIMER0_CUR_VALUE_REG, clo);
return (0);
}
return (EINVAL);
/* Update timer values */
timer_write_4(sc, SW_TIMER0_INT_VALUE_REG, sc->sc_period);
timer_write_4(sc, SW_TIMER0_CUR_VALUE_REG, count);
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
if (first == NULL) {
/* periodic */
val |= TIMER_AUTORELOAD;
} else {
/* oneshot */
val &= ~TIMER_AUTORELOAD;
}
/* Enable timer0 */
val |= TIMER_ENABLE;
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
return (0);
}
static int
@ -240,12 +275,9 @@ a10_timer_timer_stop(struct eventtimer *et)
sc = (struct a10_timer_softc *)et->et_priv;
/* clear */
timer_write_4(sc, SW_TIMER0_CUR_VALUE_REG, 0);
/* disable */
/* Disable timer0 */
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
val &= ~(1<<0); /* Disable timer0 */
val &= ~TIMER_ENABLE;
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
sc->sc_period = 0;
@ -256,8 +288,7 @@ a10_timer_timer_stop(struct eventtimer *et)
int
a10_timer_get_timerfreq(struct a10_timer_softc *sc)
{
return (sc->clkfreq);
return (sc->timer0_freq);
}
void
@ -267,18 +298,35 @@ cpu_initclocks(void)
}
static int
a10_timer_intr(void *arg)
a10_timer_hardclock(void *arg)
{
struct a10_timer_softc *sc;
uint32_t val;
sc = (struct a10_timer_softc *)arg;
/* Clear interrupt pending bit. */
timer_write_4(sc, SW_TIMER_IRQ_STA_REG, 0x1);
val = timer_read_4(sc, SW_TIMER0_CTRL_REG);
/*
* Disabled autoreload and sc_period > 0 means
* timer_start was called with non NULL first value.
* Now we will set periodic timer with the given period
* value.
*/
if ((val & (1<<1)) == 0 && sc->sc_period > 0) {
/* Update timer */
timer_write_4(sc, SW_TIMER0_CUR_VALUE_REG, sc->sc_period);
/* Make periodic and enable */
val |= TIMER_AUTORELOAD | TIMER_ENABLE;
timer_write_4(sc, SW_TIMER0_CTRL_REG, val);
}
if (sc->et.et_active)
sc->et.et_event_cb(&sc->et, sc->et.et_arg);
/* pending */
timer_write_4(sc, SW_TIMER_IRQ_STA_REG, 0x1);
return (FILTER_HANDLED);
}
@ -289,7 +337,7 @@ a10_timer_get_timecount(struct timecounter *tc)
if (a10_timer_sc == NULL)
return (0);
return (timer_read_4(a10_timer_sc, SW_TIMER0_CUR_VALUE_REG));
return ((u_int)timer_read_counter64());
}
static device_method_t a10_timer_methods[] = {
@ -313,29 +361,19 @@ void
DELAY(int usec)
{
uint32_t counter;
uint32_t val, val_temp;
int32_t nticks;
uint64_t end, now;
/* Timer is not initialized yet */
if (!a10_timer_initialized) {
for (; usec > 0; usec--)
for (counter = 200; counter > 0; counter--)
/* Prevent optimizing out the loop */
for (counter = 50; counter > 0; counter--)
cpufunc_nullop();
return;
}
val = timer_read_4(a10_timer_sc, SW_TIMER0_CUR_VALUE_REG);
nticks = ((a10_timer_sc->clkfreq / 1000000 + 1) * usec);
now = timer_read_counter64();
end = now + (a10_timer_sc->timer0_freq / 1000000) * (usec + 1);
while (nticks > 0) {
val_temp = timer_read_4(a10_timer_sc, SW_TIMER0_CUR_VALUE_REG);
if (val > val_temp)
nticks -= (val - val_temp);
else
nticks -= (val + (INITIAL_TIMECOUNTER - val_temp));
val = val_temp;
}
while (now < end)
now = timer_read_counter64();
}