diff options
author | Shawn Nematbakhsh <shawnn@chromium.org> | 2017-07-19 14:19:04 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2017-07-21 21:24:12 -0700 |
commit | 94896eaae6791df63eae6e0bfc6161a660d1abe6 (patch) | |
tree | b6383a59d5a92a4e6436eacdbe86c3a03250127b /chip | |
parent | a6c34e183a70aabcef1cd4362303df475afc173e (diff) | |
download | chrome-ec-94896eaae6791df63eae6e0bfc6161a660d1abe6.tar.gz |
g: hwtimer: Improve accuracy of hwtimer and ensure minimum udelay() wait
hwtimer ticks at 8 * 32768 Hz rather than 250 KHz, so adjust our timing
appropriately. Also ensure that udelay() will delay for at least the
requested time, taking into account our timer precision.
BUG=b:63858553
TEST=Generate square wave with 1000us udelay between GPIO edge toggle,
verify period is 1000us + code overhead. Also verify timer behavior on
overflow with 'forcetime' command. Also verify accuracy of system clock
to 0.2% with `timerinfo` and a stopwatch.
BRANCH=None
Change-Id: I5da41bd7250db87de5143cc54ebd0bb750fb7003
Signed-off-by: Shawn Nematbakhsh <shawnn@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/578551
Commit-Ready: Shawn N <shawnn@chromium.org>
Tested-by: Shawn N <shawnn@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
Diffstat (limited to 'chip')
-rw-r--r-- | chip/g/hwtimer.c | 81 |
1 files changed, 68 insertions, 13 deletions
diff --git a/chip/g/hwtimer.c b/chip/g/hwtimer.c index 1bbc3a8703..91dba78c40 100644 --- a/chip/g/hwtimer.c +++ b/chip/g/hwtimer.c @@ -9,27 +9,49 @@ #include "init_chip.h" #include "registers.h" #include "task.h" +#include "timer.h" #include "util.h" -/* The frequency of timerls is 256k so there are about 4usec/tick */ -#define USEC_PER_TICK 4 +#define SOURCE(field) TIMER0_##field +#define EVENT(field) TIMER1_##field + +/* The frequency of timerls is 8 * 32768 Hz. */ +#define TIMER_FREQ_HZ (8 * 32768) + /* - * Scale the maximum number of ticks so that it will only count up to the - * equivalent of 0xffffffff usecs. + * GCD(SECOND, TIMER_FREQ_HZ) = 64. We'll need to use reduced terms to prevent + * overflow of our intermediate uint32_t type in some calculations. */ -#define TIMELS_MAX (0xffffffff / USEC_PER_TICK) +#define GCD 64 +#define TIMER_FREQ_GCD (TIMER_FREQ_HZ / GCD) +#define TIME_GCD (SECOND / GCD) -#define SOURCE(field) TIMER0_##field -#define EVENT(field) TIMER1_##field +/* + * Scale the maximum number of ticks so that it will only count up to the + * equivalent of approximately 0xffffffff usecs. Note that we lose 3us on + * timer wrap due to loss of precision during division. + */ +#define TIMELS_MAX (usecs_to_ticks(0xffffffff)) +/* + * The below calculation is lightweight and can be implemented using + * umull + shift on 32-bit ARM. + */ static inline uint32_t ticks_to_usecs(uint32_t ticks) { - return ticks * USEC_PER_TICK; + return (uint64_t)ticks * SECOND / TIMER_FREQ_HZ; } -static inline uint32_t usec_to_ticks(uint32_t next_evt_us) +/* + * The below calulation is more tricky, this is very inefficient and requires + * 64-bit division: + * return ((uint64_t)(usecs) * TIMER_FREQ_HZ / SECOND); + * Instead use 32 bit vals, divide first, and add back the loss of precision. + */ +static inline uint32_t usecs_to_ticks(uint32_t usecs) { - return next_evt_us / USEC_PER_TICK; + return (usecs / TIME_GCD * TIMER_FREQ_GCD) + + ((usecs % TIME_GCD) * TIMER_FREQ_GCD / TIME_GCD); } uint32_t __hw_clock_event_get(void) @@ -62,8 +84,7 @@ void __hw_clock_event_set(uint32_t deadline) event_time = (deadline - __hw_clock_source_read()); /* Convert event_time to ticks rounding up */ - GREG32(TIMELS, EVENT(LOAD)) = - ((uint64_t)(event_time + USEC_PER_TICK - 1) / USEC_PER_TICK); + GREG32(TIMELS, EVENT(LOAD)) = usecs_to_ticks(event_time) + 1; /* Enable the timer & interrupts */ GWRITE(TIMELS, EVENT(IER), 1); @@ -93,7 +114,7 @@ uint32_t __hw_clock_source_read(void) void __hw_clock_source_set(uint32_t ts) { - GREG32(TIMELS, SOURCE(LOAD)) = (0xffffffff - ts) / USEC_PER_TICK; + GREG32(TIMELS, SOURCE(LOAD)) = usecs_to_ticks(0xffffffff - ts); } /* This handles rollover in the HW timer */ @@ -156,3 +177,37 @@ int __hw_clock_source_init(uint32_t start_t) /* Return the Event timer IRQ number (NOT the HW timer IRQ) */ return GC_IRQNUM_TIMELS0_TIMINT1; } + +#ifdef CONFIG_HW_SPECIFIC_UDELAY +/* + * Custom chip/g udelay(), guaranteed to delay for at least us microseconds. + * + * Lost time during timer wrap is not taken into account since interrupt latency + * and __hw_clock_source_irq() execution time likely exceeds the lost 3us. + */ +void udelay(unsigned us) +{ + unsigned t0 = __hw_clock_source_read(); + + /* + * The timer will tick either 3 us or 4 us, every ~3.8us in realtime. + * To ensure we meet the minimum delay, we must wait out a full + * longest-case timer tick (4 us), since a tick may have occurred + * immediately after sampling t0. + */ + us += ticks_to_usecs(1) + 1; + + /* + * udelay() may be called with interrupts disabled, so we can't rely on + * process_timers() updating the top 32 bits. So handle wraparound + * ourselves rather than calling get_time() and comparing with a + * deadline. + * + * This may fail for delays close to 2^32 us (~4000 sec), because the + * subtraction below can overflow. That's acceptable, because the + * watchdog timer would have tripped long before that anyway. + */ + while (__hw_clock_source_read() - t0 <= us) + ; +} +#endif /* CONFIG_HW_SPECIFIC_UDELAY */ |