summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorShawn Nematbakhsh <shawnn@chromium.org>2017-07-19 14:19:04 -0700
committerchrome-bot <chrome-bot@chromium.org>2017-07-21 21:24:12 -0700
commit94896eaae6791df63eae6e0bfc6161a660d1abe6 (patch)
treeb6383a59d5a92a4e6436eacdbe86c3a03250127b /chip
parenta6c34e183a70aabcef1cd4362303df475afc173e (diff)
downloadchrome-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.c81
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 */