summaryrefslogtreecommitdiff
path: root/chip/ish/hwtimer.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/ish/hwtimer.c')
-rw-r--r--chip/ish/hwtimer.c234
1 files changed, 151 insertions, 83 deletions
diff --git a/chip/ish/hwtimer.c b/chip/ish/hwtimer.c
index 97a2a4f848..63c9dcde0a 100644
--- a/chip/ish/hwtimer.c
+++ b/chip/ish/hwtimer.c
@@ -3,7 +3,7 @@
* found in the LICENSE file.
*/
-/* Hardware timers driver - HPET */
+/* Hardware timers driver for ISH High Precision Event Timers (HPET) */
#include "console.h"
#include "hpet.h"
@@ -13,44 +13,108 @@
#include "task.h"
#include "util.h"
-#if defined(CHIP_FAMILY_ISH3)
-#define CLOCK_FACTOR 12
-#endif
-
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args)
static uint32_t last_deadline;
-/* TODO: Conform to EC API
- * ISH supports 32KHz and 12MHz clock sources.
- * EC expects timer value in 1MHz.
- * Scale the values and support it.
+/*
+ * Number of ticks for timer0 comparator representing 2^32 us seconds. SECONDS
+ * represents the expected clock frequency of the OS (i.e. 1 Mhz).
*/
+#define ROLLOVER_CMP_VAL (((uint64_t)ISH_HPET_CLK_FREQ << 32) / SECOND)
-void __hw_clock_event_set(uint32_t deadline)
+/*
+ * The ISH hardware needs at least 25 ticks of leeway to arms the timer.
+ * ISH4/5 are the slowest with 32kHz timers, so we wait at least 800us when
+ * scheduling events in the future
+ */
+#define MINIMUM_EVENT_DELAY_US 800
+
+/* Scaling helper methods for different ISH chip variants */
+#ifdef CHIP_FAMILY_ISH3
+#define CLOCK_FACTOR 12
+BUILD_ASSERT(CLOCK_FACTOR * SECOND == ISH_HPET_CLK_FREQ);
+
+static inline uint64_t scale_us2ticks(uint32_t us)
{
- last_deadline = deadline;
-#if defined(CHIP_FAMILY_ISH3)
- HPET_TIMER_COMP(1) = deadline * CLOCK_FACTOR;
-#else
- HPET_TIMER_COMP(1) = deadline;
-#endif
- HPET_TIMER_CONF_CAP(1) |= HPET_Tn_INT_ENB_CNF;
+ return (uint64_t)us * CLOCK_FACTOR;
}
-uint32_t __hw_clock_event_get(void)
+static inline uint32_t scale_ticks2us(uint64_t ticks)
{
- return last_deadline;
+ /*
+ * We drop into asm here since this is on the critical path of reading
+ * the hardware timer for clock result. This needs to be efficient.
+ *
+ * Modulating hi first ensures that the quotient fits in 32-bits due to
+ * the follow math:
+ * Let ticks = (hi << 32) + lo;
+ * Let hi = N*CLOCK_FACTOR + R; where R is hi % CLOCK_FACTOR
+ *
+ * ticks = (N*CLOCK_FACTOR << 32) + (R << 32) + lo
+ *
+ * ticks / CLOCK_FACTOR = ((N*CLOCK_FACTOR << 32) + (R << 32) + lo) /
+ * CLOCK_FACTOR
+ * ticks / CLOCK_FACTOR = (N*CLOCK_FACTOR << 32) / CLOCK_FACTOR +
+ * (R << 32) / CLOCK_FACTOR +
+ * lo / CLOCK_FACTOR
+ * ticks / CLOCK_FACTOR = (N << 32) +
+ * (R << 32) / CLOCK_FACTOR +
+ * lo / CLOCK_FACTOR
+ * If we want to truncate to 32 bits, then the N << 32 can be dropped.
+ * (ticks / CLOCK_FACTOR) & 0xFFFFFFFF = ((R << 32) + lo) / CLOCK_FACTOR
+ */
+ const uint32_t divisor = CLOCK_FACTOR;
+ const uint32_t hi = ((uint32_t)(ticks >> 32)) % divisor;
+ const uint32_t lo = ticks;
+ uint32_t quotient;
+
+ asm("divl %3" : "=a"(quotient) : "d"(hi), "a"(lo), "rm"(divisor));
+ return quotient;
}
-void __hw_clock_event_clear(void)
+#elif defined(CHIP_FAMILY_ISH4) || defined(CHIP_FAMILY_ISH5)
+#define CLOCK_SCALE_BITS 15
+BUILD_ASSERT((1 << CLOCK_SCALE_BITS) == ISH_HPET_CLK_FREQ);
+
+static inline uint32_t scale_us2ticks(uint32_t us)
{
- HPET_TIMER_CONF_CAP(1) &= ~HPET_Tn_INT_ENB_CNF;
+ /*
+ * ticks = us * ISH_HPET_CLK_FREQ / SECOND;
+ *
+ * First multiple us by ISH_HPET_CLK_FREQ via bit shift, then use
+ * 64-bit div into 32-bit result.
+ *
+ * We use asm directly to maintain full 32-bit precision without using
+ * an iterative divide (i.e. 64-bit / 64-bit => 64-bit). We use the
+ * 64-bit / 32-bit => 32-bit asm instruction directly since there is no
+ * way to emitted that instruction via the compiler.
+ *
+ * The intermediate result of (us * ISH_HPET_CLK_FREQ) needs 64-bits of
+ * precision to maintain full 32-bit precision for the end result.
+ */
+ const uint32_t hi = us >> (32 - CLOCK_SCALE_BITS);
+ const uint32_t lo = us << CLOCK_SCALE_BITS;
+ const uint32_t divisor = SECOND;
+ uint32_t ticks;
+
+ asm("divl %3" : "=a"(ticks) : "d"(hi), "a"(lo), "rm"(divisor));
+ return ticks;
}
-#ifdef CHIP_FAMILY_ISH3
+static inline uint32_t scale_ticks2us(uint64_t ticks)
+{
+ /*
+ * us = ticks / ISH_HPET_CLK_FREQ * SECOND;
+ */
+ const uint64_t intermediate = (uint64_t)ticks * SECOND;
+
+ return intermediate >> CLOCK_SCALE_BITS;
+}
+#endif /* CHIP_FAMILY_ISH4 || CHIP_FAMILY_ISH5 */
+
/*
* The 64-bit read on a 32-bit chip can tear during the read. Ensure that the
* value returned for 64-bit didn't rollover while we were reading it.
@@ -68,51 +132,72 @@ static inline uint64_t read_main_timer(void)
return t.val;
}
-#endif
-uint32_t __hw_clock_source_read(void)
+void __hw_clock_event_set(uint32_t deadline)
{
-#if defined(CHIP_FAMILY_ISH3)
- const uint64_t tmp = read_main_timer();
- const uint32_t divisor = CLOCK_FACTOR;
+ uint32_t remaining_us;
+
+ last_deadline = deadline;
+
+ remaining_us = deadline - __hw_clock_source_read();
+
+ /* Ensure HW has enough time to react to new timer value */
+ remaining_us = MAX(remaining_us, MINIMUM_EVENT_DELAY_US);
+
/*
- * Modulating hi first ensures that the quotient fits in 32-bits due to
- * the follow math:
- * Let tmp = (hi << 32) + lo;
- * Let hi = N*CLOCK_FACTOR + R; where R is hi % CLOCK_FACTOR
- *
- * tmp = (N*CLOCK_FACTOR << 32) + (R << 32) + lo
- *
- * tmp / CLOCK_FACTOR = ((N*CLOCK_FACTOR << 32) + (R << 32) + lo) /
- * CLOCK_FACTOR
- * tmp / CLOCK_FACTOR = (N*CLOCK_FACTOR << 32) / CLOCK_FACTOR +
- * (R << 32) / CLOCK_FACTOR +
- * lo / CLOCK_FACTOR
- * tmp / CLOCK_FACTOR = (N << 32) +
- * (R << 32) / CLOCK_FACTOR +
- * lo / CLOCK_FACTOR
- * If we want to truncate to 32 bits, then the N << 32 can be dropped.
- * (tmp / CLOCK_FACTOR) & 0xFFFFFFFF = ((R << 32) + lo) / CLOCK_FACTOR
+ * For ISH3, this assumes that remaining_us is less than 360 seconds
+ * (2^32 us / 12Mhz), otherwise we would need to handle 32-bit rollover
+ * of 12Mhz timer comparator value. Watchdog refresh happens at least
+ * every 10 seconds.
*/
- const uint32_t hi = ((uint32_t)(tmp >> 32)) % divisor;
- const uint32_t lo = tmp;
+ HPET_TIMER_COMP(1) = read_main_timer() + scale_us2ticks(remaining_us);
- register uint32_t quotient;
- asm("divl %3" : "=a"(quotient) : "d"(hi), "a"(lo), "rm"(divisor));
- return quotient;
-#else
- return HPET_MAIN_COUNTER;
+ do {
+ /* Arm timer */
+ HPET_TIMER_CONF_CAP(1) |= HPET_Tn_INT_ENB_CNF;
+
+#if defined(CHIP_FAMILY_ISH4) || defined(CHIP_FAMILY_ISH5)
+ /* Wait for timer settings to settle ~ 150us */
+ while (HPET_CTRL_STATUS & HPET_T1_SETTLING)
+ continue;
#endif
+ /*
+ * TODO(b/124890290): Remove or update while loop that ensures timer is
+ * armed once we have a better hardware understanding.
+ */
+ } while (!(HPET_TIMER_CONF_CAP(1) & HPET_Tn_INT_ENB_CNF));
+}
+
+uint32_t __hw_clock_event_get(void)
+{
+ return last_deadline;
+}
+
+void __hw_clock_event_clear(void)
+{
+ HPET_TIMER_CONF_CAP(1) &= ~HPET_Tn_INT_ENB_CNF;
+}
+
+uint32_t __hw_clock_source_read(void)
+{
+ return scale_ticks2us(read_main_timer());
}
void __hw_clock_source_set(uint32_t ts)
{
+ /* Reset both clock and overflow comparators */
+
HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
-#if defined(CHIP_FAMILY_ISH3)
- HPET_MAIN_COUNTER_64 = (uint64_t)ts * CLOCK_FACTOR;
-#else
- HPET_MAIN_COUNTER = ts;
+
+ HPET_MAIN_COUNTER_64 = scale_us2ticks(ts);
+ HPET_TIMER0_COMP_64 = ROLLOVER_CMP_VAL;
+
+#if defined(CHIP_FAMILY_ISH4) || defined(CHIP_FAMILY_ISH5)
+ /* Wait for timer to settle. required for ISH 4 */
+ while (HPET_CTRL_STATUS & HPET_MAIN_COUNTER_SETTLING)
+ continue;
#endif
+
HPET_GENERAL_CONFIG |= HPET_ENABLE_CNF;
}
@@ -121,7 +206,10 @@ static void __hw_clock_source_irq(int timer_id)
/* Clear interrupt */
HPET_INTR_CLEAR = (1 << timer_id);
- /* If IRQ is from timer 0, 32-bit timer overflowed */
+ /*
+ * If IRQ is from timer 0, 2^32 us have elapsed (i.e. OS timer
+ * overflowed).
+ */
process_timers(timer_id == 0);
}
@@ -152,31 +240,11 @@ int __hw_clock_source_init(uint32_t start_t)
/* Disable HPET */
HPET_GENERAL_CONFIG &= ~HPET_ENABLE_CNF;
-#if defined(CHIP_FAMILY_ISH3)
- HPET_MAIN_COUNTER_64 = (uint64_t)start_t * CLOCK_FACTOR;
-#else
- HPET_MAIN_COUNTER = start_t;
-#endif
+ HPET_MAIN_COUNTER_64 = scale_us2ticks(start_t);
-#if defined(CHIP_FAMILY_ISH3)
- /*
- * Set comparator value. HMC will operate in 64 bit mode.
- * HMC is 12MHz, Hence set COMP to 12x of 1MHz.
- */
- HPET_TIMER_COMP_64(0) = (uint64_t)CLOCK_FACTOR << 32; /*0xC00000000ULL;*/
-#else
- /* Set comparator value */
- HPET_TIMER_COMP(0) = 0XFFFFFFFF;
-#endif
- /* Timer 0 - enable periodic mode */
+ /* Set comparator value for Timer 0 and enable periodic mode */
+ HPET_TIMER0_COMP_64 = ROLLOVER_CMP_VAL;
timer0_config |= HPET_Tn_TYPE_CNF;
-#if defined(CHIP_FAMILY_ISH3)
- /* TIMER0 in 64-bit mode */
- timer0_config &= ~HPET_Tn_32MODE_CNF;
-#else
- /*TIMER0 in 32-bit mode*/
- timer0_config |= HPET_Tn_32MODE_CNF;
-#endif
/* Timer 0 - IRQ routing, no need IRQ set for HPET0 */
timer0_config &= ~HPET_Tn_INT_ROUTE_CNF_MASK;
@@ -192,9 +260,8 @@ int __hw_clock_source_init(uint32_t start_t)
/* Enable interrupt */
timer0_config |= HPET_Tn_INT_ENB_CNF;
- timer1_config |= HPET_Tn_INT_ENB_CNF;
- /* Unask HPET IRQ in IOAPIC */
+ /* Unmask HPET IRQ in IOAPIC */
task_enable_irq(ISH_HPET_TIMER0_IRQ);
task_enable_irq(ISH_HPET_TIMER1_IRQ);
@@ -204,8 +271,8 @@ int __hw_clock_source_init(uint32_t start_t)
#if defined(CHIP_FAMILY_ISH4) || defined(CHIP_FAMILY_ISH5)
/* Wait for timer to settle. required for ISH 4 */
- while (HPET_CTRL_STATUS & HPET_T_CONF_CAP_BIT)
- ;
+ while (HPET_CTRL_STATUS & HPET_MAIN_COUNTER_SETTLING)
+ continue;
#endif
/*
@@ -214,5 +281,6 @@ int __hw_clock_source_init(uint32_t start_t)
*/
HPET_GENERAL_CONFIG |= (HPET_ENABLE_CNF | HPET_LEGACY_RT_CNF);
+ /* Return IRQ value for OS event timer */
return ISH_HPET_TIMER1_IRQ;
}