timer: Add support for 64 bit timers.

This patch changes the time and timeouts used by timers from a uint32_t to a
uint64_t. This means clocks can run for years before overflow.

This patch also provides a virtual high 32 bits for HW that only has
32bit timer support. i.e. the high 32 is incremented at every HW timer
overflow.

Finally the patch updates all timer users to use uint64_t timeouts.

Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
This commit is contained in:
Liam Girdwood 2017-09-13 23:17:17 +01:00
parent 45984c98d3
commit 488f02d53b
8 changed files with 334 additions and 41 deletions

View File

@ -39,12 +39,28 @@
struct timer {
uint32_t id;
uint32_t irq;
void *timer_data; /* used by core */
uint32_t hitime; /* high end of 64bit timer */
uint32_t hitimeout;
uint32_t lowtimeout;
};
/* internal API calls */
int timer64_register(struct timer *timer, void(*handler)(void *arg), void *arg);
void timer_64_handler(void *arg);
static inline int arch_timer_register(struct timer *timer,
void(*handler)(void *arg), void *arg)
{
return arch_interrupt_register(timer->id, handler, arg);
uint32_t flags;
int ret;
flags = arch_interrupt_global_disable();
timer64_register(timer, handler, arg);
ret = arch_interrupt_register(timer->id, timer_64_handler, timer);
arch_interrupt_global_enable(flags);
return ret;
}
static inline void arch_timer_unregister(struct timer *timer)
@ -62,12 +78,9 @@ static inline void arch_timer_disable(struct timer *timer)
arch_interrupt_disable_mask(1 << timer->irq);
}
static inline uint32_t arch_timer_get_system(struct timer *timer)
{
return xthal_get_ccount();
}
uint64_t arch_timer_get_system(struct timer *timer);
void arch_timer_set(struct timer *timer, unsigned int ticks);
int arch_timer_set(struct timer *timer, uint64_t ticks);
static inline void arch_timer_clear(struct timer *timer)
{

View File

@ -41,19 +41,174 @@
#include <stdint.h>
#include <errno.h>
void arch_timer_set(struct timer *timer, unsigned int ticks)
struct timer_data {
void (*handler2)(void *arg);
void *arg2;
};
static struct timer_data xtimer[3] = {};
void timer_64_handler(void *arg)
{
struct timer *timer = arg;
struct timer_data *tdata = timer->timer_data;
uint32_t ccompare;
/* get comparator value - will tell us timeout reason */
switch (timer->id) {
case TIMER0:
xthal_set_ccompare(0, ticks);
ccompare = xthal_get_ccompare(0);
break;
case TIMER1:
xthal_set_ccompare(1, ticks);
ccompare = xthal_get_ccompare(1);
break;
case TIMER2:
xthal_set_ccompare(2, ticks);
ccompare = xthal_get_ccompare(2);
break;
default:
return;
}
/* is this a 32 bit rollover ? */
if (ccompare == 1) {
/* roll over the timer */
timer->hitime++;
arch_timer_clear(timer);
} else {
/* no roll over, run the handler */
tdata->handler2(tdata->arg2);
}
/* get next timeout value */
if (timer->hitimeout > 0 && timer->hitimeout == timer->hitime) {
/* timeout is in this 32 bit period */
ccompare = timer->lowtimeout;
} else {
/* timeout is in another 32 bit period */
ccompare = 1;
}
switch (timer->id) {
case TIMER0:
xthal_set_ccompare(0, ccompare);
break;
case TIMER1:
xthal_set_ccompare(1, ccompare);
break;
case TIMER2:
xthal_set_ccompare(2, ccompare);
break;
default:
return;
}
}
int timer64_register(struct timer *timer, void(*handler)(void *arg), void *arg)
{
struct timer_data *tdata;
switch (timer->id) {
case TIMER0:
tdata = &xtimer[0];
break;
case TIMER1:
tdata = &xtimer[1];
break;
case TIMER2:
tdata = &xtimer[2];
break;
default:
return -EINVAL;
}
tdata->handler2 = handler;
tdata->arg2 = arg;
timer->timer_data = tdata;
timer->hitime = 0;
timer->hitimeout = 0;
return 0;
}
uint64_t arch_timer_get_system(struct timer *timer)
{
uint64_t time;
uint32_t flags, low, high, ccompare;
switch (timer->id) {
case TIMER0:
ccompare = xthal_get_ccompare(0);
break;
case TIMER1:
ccompare = xthal_get_ccompare(1);
break;
case TIMER2:
ccompare = xthal_get_ccompare(2);
break;
default:
return 0;
}
flags = arch_interrupt_global_disable();
/* read low 32 bits */
low = xthal_get_ccount();
/* check and see whether 32bit IRQ is pending for timer */
if (arch_interrupt_get_status() & (1 << timer->irq) && ccompare == 1) {
/* yes, overflow has occured but handler has not run */
high = timer->hitime + 1;
} else {
/* no overflow */
high = timer->hitime;
}
time = ((uint64_t)high << 32) | low;
arch_interrupt_global_enable(flags);
return time;
}
int arch_timer_set(struct timer *timer, uint64_t ticks)
{
uint32_t time = 1, hitimeout = ticks >> 32, flags;
/* value of 1 represents rollover */
if ((ticks & 0xffffffff) == 0x1)
ticks++;
flags = arch_interrupt_global_disable();
/* same hi 64 bit context as ticks ? */
if (hitimeout == timer->hitime) {
/* yes, then set the value for next timeout */
time = ticks;
timer->lowtimeout = 0;
timer->hitimeout = 0;
} else if (hitimeout < timer->hitime) {
/* cant be in the past */
arch_interrupt_global_enable(flags);
return -EINVAL;
} else {
/* set for checking at next timeout */
timer->hitimeout = hitimeout;
timer->lowtimeout = ticks;
}
switch (timer->id) {
case TIMER0:
xthal_set_ccompare(0, time);
break;
case TIMER1:
xthal_set_ccompare(1, time);
break;
case TIMER2:
xthal_set_ccompare(2, time);
break;
default:
return -EINVAL;
}
arch_interrupt_global_enable(flags);
return 0;
}

View File

@ -34,11 +34,8 @@
#include <arch/timer.h>
#include <stdint.h>
static inline int timer_register(struct timer *timer,
void(*handler)(void *arg), void *arg)
{
return arch_timer_register(timer, handler, arg);
}
int timer_register(struct timer *timer,
void(*handler)(void *arg), void *arg);
static inline void timer_unregister(struct timer *timer)
{
@ -55,9 +52,9 @@ static inline void timer_disable(struct timer *timer)
arch_timer_disable(timer);
}
static inline void timer_set(struct timer *timer, unsigned int ticks)
static inline int timer_set(struct timer *timer, uint64_t ticks)
{
arch_timer_set(timer, ticks);
return arch_timer_set(timer, ticks);
}
void timer_set_ms(struct timer *timer, unsigned int ms);
@ -71,7 +68,7 @@ unsigned int timer_get_count(struct timer *timer);
unsigned int timer_get_count_delta(struct timer *timer);
static inline uint32_t timer_get_system(struct timer *timer)
static inline uint64_t timer_get_system(struct timer *timer)
{
return arch_timer_get_system(timer);
}

View File

@ -57,9 +57,9 @@ struct work_queue_timesource {
struct timer timer;
int clk;
int notifier;
void (*timer_set)(struct timer *, uint32_t ticks);
int (*timer_set)(struct timer *, uint64_t ticks);
void (*timer_clear)(struct timer *);
uint32_t (*timer_get)(struct timer *);
uint64_t (*timer_get)(struct timer *);
};
/* initialise our work */
@ -69,11 +69,11 @@ struct work_queue_timesource {
(w)->flags = xflags;
/* schedule/cancel work on work queue */
void work_schedule(struct work_queue *queue, struct work *w, uint32_t timeout);
void work_schedule(struct work_queue *queue, struct work *w, uint64_t timeout);
void work_cancel(struct work_queue *queue, struct work *work);
/* schedule/cancel work on default system work queue */
void work_schedule_default(struct work *work, uint32_t timeout);
void work_schedule_default(struct work *work, uint64_t timeout);
void work_cancel_default(struct work *work);
/* create new work queue */

View File

@ -60,22 +60,26 @@
struct work_queue {
struct list_item work; /* list of work */
uint32_t timeout; /* timeout for next queue run */
uint64_t timeout; /* timeout for next queue run */
uint32_t window_size; /* window size for pending work */
spinlock_t lock;
struct notifier notifier; /* notify CPU freq changes */
struct work_queue_timesource *ts; /* time source for work queue */
uint32_t ticks_per_usec; /* ticks per msec */
uint32_t run_ticks; /* ticks when last run */
uint64_t run_ticks; /* ticks when last run */
};
/* generic system work queue */
static struct work_queue *queue_;
static inline void work_set_timer(struct work_queue *queue, uint32_t ticks)
static inline int work_set_timer(struct work_queue *queue, uint64_t ticks)
{
queue->ts->timer_set(&queue->ts->timer, ticks);
int ret;
ret = queue->ts->timer_set(&queue->ts->timer, ticks);
timer_enable(&queue->ts->timer);
return ret;
}
static inline void work_clear_timer(struct work_queue *queue)
@ -84,7 +88,7 @@ static inline void work_clear_timer(struct work_queue *queue)
timer_disable(&queue->ts->timer);
}
static inline uint32_t work_get_timer(struct work_queue *queue)
static inline uint64_t work_get_timer(struct work_queue *queue)
{
return queue->ts->timer_get(&queue->ts->timer);
}
@ -317,7 +321,7 @@ static void work_notify(int message, void *data, void *event_data)
spin_unlock_irq(&queue->lock, flags);
}
void work_schedule(struct work_queue *queue, struct work *w, uint32_t timeout)
void work_schedule(struct work_queue *queue, struct work *w, uint64_t timeout)
{
struct work *work;
struct list_item *wlist;
@ -362,7 +366,7 @@ void work_cancel(struct work_queue *queue, struct work *w)
spin_unlock_irq(&queue->lock, flags);
}
void work_schedule_default(struct work *w, uint32_t timeout)
void work_schedule_default(struct work *w, uint64_t timeout)
{
struct work *work;
struct list_item *wlist;

View File

@ -50,9 +50,10 @@ struct comp_dev;
struct sof_ipc_stream_posn;
extern struct timer *platform_timer;
void platform_timer_set(struct timer *timer, uint32_t ticks);
int platform_timer_set(struct timer *timer, uint64_t ticks);
void platform_timer_clear(struct timer *timer);
uint32_t platform_timer_get(struct timer *timer);
uint64_t platform_timer_get(struct timer *timer);
void platform_timer_start(struct timer *timer);
void platform_timer_stop(struct timer *timer);

View File

@ -172,15 +172,15 @@ int platform_init(struct reef *reef)
platform_ipc_pmc_init();
/* init work queues and clocks */
trace_point(TRACE_BOOT_SYS_WORK);
init_system_workq(&platform_generic_queue);
trace_point(TRACE_BOOT_PLATFORM_TIMER);
platform_timer_start(platform_timer);
trace_point(TRACE_BOOT_PLATFORM_CLOCK);
init_platform_clocks();
trace_point(TRACE_BOOT_SYS_WORK);
init_system_workq(&platform_generic_queue);
/* Set CPU to default frequency for booting */
trace_point(TRACE_BOOT_SYS_CPU_FREQ);
clock_set_freq(CLK_CPU, CLK_MAX_CPU_HZ);

View File

@ -32,15 +32,58 @@
#include <platform/timer.h>
#include <platform/shim.h>
#include <platform/interrupt.h>
#include <reef/debug.h>
#include <reef/audio/component.h>
#include <stdint.h>
struct timer_data {
void (*handler2)(void *arg);
void *arg2;
};
static struct timer_data xtimer[1] = {};
void platform_timer_64_handler(void *arg)
{
struct timer *timer = arg;
struct timer_data *tdata = timer->timer_data;
uint32_t timeout;
/* get timeout value - will tell us timeout reason */
timeout = shim_read(SHIM_EXT_TIMER_CNTLL);
/* we dont use the timer clear bit as we only need to clear the ISR */
shim_write(SHIM_PISR, SHIM_PISR_EXT_TIMER);
/* is this a 32 bit rollover ? */
if (timeout == 1) {
/* roll over the timer */
timer->hitime++;
} else {
/* no roll over, run the handler */
tdata->handler2(tdata->arg2);
}
/* get next timeout value */
if (timer->hitimeout > 0 && timer->hitimeout == timer->hitime) {
/* timeout is in this 32 bit period */
timeout = timer->lowtimeout;
} else {
/* timeout is in another 32 bit period */
timeout = 1;
}
/* set new value and run */
shim_write(SHIM_EXT_TIMER_CNTLH, SHIM_EXT_TIMER_RUN);
shim_write(SHIM_EXT_TIMER_CNTLL, timeout);
}
void platform_timer_start(struct timer *timer)
{
/* run timer */
shim_write(SHIM_EXT_TIMER_CNTLH, SHIM_EXT_TIMER_RUN);
shim_write(SHIM_EXT_TIMER_CNTLL, 0);
shim_write(SHIM_EXT_TIMER_CNTLL, 1);
}
/* this seems to stop rebooting with RTD3 ???? */
@ -51,15 +94,40 @@ void platform_timer_stop(struct timer *timer)
shim_write(SHIM_EXT_TIMER_CNTLH, SHIM_EXT_TIMER_CLEAR);
}
void platform_timer_set(struct timer *timer, uint32_t ticks)
int platform_timer_set(struct timer *timer, uint64_t ticks)
{
uint32_t time = 1, hitimeout = ticks >> 32, flags;
/* a tick value of 0 will not generate an IRQ */
if (ticks == 0)
ticks = 1;
/* value of 1 represents rollover */
if ((ticks & 0xffffffff) < 0x2)
ticks += 2;
flags = arch_interrupt_global_disable();
/* same hi 64 bit context as ticks ? */
if (hitimeout == timer->hitime) {
/* yes, then set the value for next timeout */
time = ticks;
timer->lowtimeout = 0;
timer->hitimeout = 0;
} else if (hitimeout < timer->hitime) {
/* cant be in the past */
arch_interrupt_global_enable(flags);
return -EINVAL;
} else {
/* set for checking at next timeout */
timer->hitimeout = hitimeout;
timer->lowtimeout = ticks;
}
/* set new value and run */
shim_write(SHIM_EXT_TIMER_CNTLH, SHIM_EXT_TIMER_RUN);
shim_write(SHIM_EXT_TIMER_CNTLL, ticks);
shim_write(SHIM_EXT_TIMER_CNTLL, time);
arch_interrupt_global_enable(flags);
return 0;
}
void platform_timer_clear(struct timer *timer)
@ -68,9 +136,31 @@ void platform_timer_clear(struct timer *timer)
shim_write(SHIM_PISR, SHIM_PISR_EXT_TIMER);
}
uint32_t platform_timer_get(struct timer *timer)
uint64_t platform_timer_get(struct timer *timer)
{
return shim_read(SHIM_EXT_TIMER_STAT);
uint64_t time;
uint32_t flags, low, high;
flags = arch_interrupt_global_disable();
/* read low 32 bits */
low = shim_read(SHIM_EXT_TIMER_STAT);
/* check and see whether 32bit IRQ is pending for timer */
if (arch_interrupt_get_status() & IRQ_MASK_EXT_TIMER &&
shim_read(SHIM_EXT_TIMER_CNTLL) == 1) {
/* yes, overflow has occured but handler has not run */
high = timer->hitime + 1;
} else {
/* no overflow */
high = timer->hitime;
}
time = ((uint64_t)high << 32) | low;
arch_interrupt_global_enable(flags);
return time;
}
/* get timestamp for host stream DMA position */
@ -107,3 +197,36 @@ void platform_dai_wallclock(struct comp_dev *dai, uint64_t *wallclock)
/* only 1 wallclock on BYT */
*wallclock = shim_read(SHIM_EXT_TIMER_STAT);
}
static int platform_timer_register(struct timer *timer,
void(*handler)(void *arg), void *arg)
{
struct timer_data *tdata = &xtimer[0];
uint32_t flags;
int ret;
flags = arch_interrupt_global_disable();
tdata->handler2 = handler;
tdata->arg2 = arg;
timer->timer_data = tdata;
timer->hitime = 0;
timer->hitimeout = 0;
ret = arch_interrupt_register(timer->id, platform_timer_64_handler, timer);
arch_interrupt_global_enable(flags);
return ret;
}
int timer_register(struct timer *timer, void(*handler)(void *arg), void *arg)
{
switch (timer->id) {
case TIMER0:
case TIMER1:
case TIMER2:
return arch_timer_register(timer, handler, arg);
case TIMER3:
return platform_timer_register(timer, handler, arg);
default:
return -EINVAL;
}
}