[Sound-open-firmware] [PATCH] 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 --- src/arch/xtensa/include/arch/timer.h | 25 +++- src/arch/xtensa/timer.c | 163 ++++++++++++++++++++++++- src/include/reef/timer.h | 13 +- src/include/reef/work.h | 8 +- src/lib/work.c | 18 +-- src/platform/baytrail/include/platform/timer.h | 5 +- src/platform/baytrail/platform.c | 6 +- src/platform/baytrail/timer.c | 137 +++++++++++++++++++-- 8 files changed, 334 insertions(+), 41 deletions(-)
diff --git a/src/arch/xtensa/include/arch/timer.h b/src/arch/xtensa/include/arch/timer.h index 3262ac0..e36df6b 100644 --- a/src/arch/xtensa/include/arch/timer.h +++ b/src/arch/xtensa/include/arch/timer.h @@ -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) { diff --git a/src/arch/xtensa/timer.c b/src/arch/xtensa/timer.c index 9395ca9..82c2481 100644 --- a/src/arch/xtensa/timer.c +++ b/src/arch/xtensa/timer.c @@ -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: + ccompare = xthal_get_ccompare(0); + break; + case TIMER1: + ccompare = xthal_get_ccompare(1); + break; + case TIMER2: + 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: - 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 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; } diff --git a/src/include/reef/timer.h b/src/include/reef/timer.h index d10d5e2..613cb8b 100644 --- a/src/include/reef/timer.h +++ b/src/include/reef/timer.h @@ -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); } diff --git a/src/include/reef/work.h b/src/include/reef/work.h index 6428301..199e46c 100644 --- a/src/include/reef/work.h +++ b/src/include/reef/work.h @@ -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 */ diff --git a/src/lib/work.c b/src/lib/work.c index 3172710..cac3deb 100644 --- a/src/lib/work.c +++ b/src/lib/work.c @@ -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; diff --git a/src/platform/baytrail/include/platform/timer.h b/src/platform/baytrail/include/platform/timer.h index a7d2fc8..8f0aea4 100644 --- a/src/platform/baytrail/include/platform/timer.h +++ b/src/platform/baytrail/include/platform/timer.h @@ -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);
diff --git a/src/platform/baytrail/platform.c b/src/platform/baytrail/platform.c index f97971a..ce8a689 100644 --- a/src/platform/baytrail/platform.c +++ b/src/platform/baytrail/platform.c @@ -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); diff --git a/src/platform/baytrail/timer.c b/src/platform/baytrail/timer.c index 3d08f6f..17c1f0b 100644 --- a/src/platform/baytrail/timer.c +++ b/src/platform/baytrail/timer.c @@ -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; + } +}
participants (1)
-
Liam Girdwood