diff --git a/drivers/timer/nrf_rtc_timer.c b/drivers/timer/nrf_rtc_timer.c index 9035dbcc5a5c2d..a84c3ae3239070 100644 --- a/drivers/timer/nrf_rtc_timer.c +++ b/drivers/timer/nrf_rtc_timer.c @@ -20,7 +20,7 @@ #define COUNTER_HALF_SPAN (COUNTER_SPAN / 2U) #define CYC_PER_TICK (sys_clock_hw_cycles_per_sec() \ / CONFIG_SYS_CLOCK_TICKS_PER_SEC) -#define MAX_TICKS ((COUNTER_MAX - CYC_PER_TICK) / CYC_PER_TICK) +#define MAX_TICKS ((COUNTER_HALF_SPAN - CYC_PER_TICK) / CYC_PER_TICK) #define MAX_CYCLES (MAX_TICKS * CYC_PER_TICK) static struct k_spinlock lock; @@ -37,11 +37,121 @@ static void set_comparator(u32_t cyc) nrf_rtc_cc_set(RTC, 0, cyc & COUNTER_MAX); } +static u32_t get_comparator(void) +{ + return nrf_rtc_cc_get(RTC, 0); +} + +static void event_clear(void) +{ + nrf_rtc_event_clear(RTC, NRF_RTC_EVENT_COMPARE_0); +} + +static void event_enable(void) +{ + nrf_rtc_event_enable(RTC, NRF_RTC_INT_COMPARE0_MASK); +} + +static void int_disable(void) +{ + nrf_rtc_int_disable(RTC, NRF_RTC_INT_COMPARE0_MASK); +} + +static void int_enable(void) +{ + nrf_rtc_int_enable(RTC, NRF_RTC_INT_COMPARE0_MASK); +} + static u32_t counter(void) { return nrf_rtc_counter_get(RTC); } +/* Function ensures that previous CC value will not set event */ +static void prevent_false_prev_evt(void) +{ + u32_t now = counter(); + u32_t prev_val; + + /* First take care of a risk of an event coming from CC being set to + * next tick. Reconfigure CC to future (now tick is the furtherest + * future). If CC was set to next tick we need to wait for up to 15us + * (half of 32k tick) and clean potential event. After that time there + * is no risk of unwanted event. + */ + prev_val = get_comparator(); + event_clear(); + set_comparator(now); + event_enable(); + + if (counter_sub(prev_val, now) == 1) { + k_busy_wait(15); + event_clear(); + } +} + +/* If settings is next tick from now, function attempts to set next tick. If + * counter progresses during that time it means that 1 tick elapsed and + * interrupt is set pending. + */ +static void handle_next_tick_case(u32_t t) +{ + set_comparator(t + 2); + while (t != counter()) { + /* already expired, tick elapsed but event might not be + * generated. Trigger interrupt. + */ + t = counter(); + set_comparator(t + 2); + } +} + +/* Function safely sets absolute alarm. It assumes that provided value is + * less than MAX_TICKS from now. It detects late setting and also handles + * +1 tick case. + */ +static void set_absolute_ticks(u32_t abs_val) +{ + u32_t diff; + u32_t t = counter(); + + diff = counter_sub(abs_val, t); + if (diff == 1) { + handle_next_tick_case(t); + return; + } + + set_comparator(abs_val); + t = counter(); + /* A little trick, subtract 2 to force now and now + 1 case fall into + * negative (> MAX_TICKS). Diff 0 means two ticks from now. + */ + diff = counter_sub(abs_val - 2, t); + if (diff > MAX_TICKS) { + /* Already expired. set for next tick */ + /* It is possible that setting CC was interrupted and CC might + * be set to COUNTER+1 value which will not generate an event. + * In that case, special handling is performed (attempt to set + * CC to COUNTER+2). + */ + handle_next_tick_case(t); + } +} + +/* Sets relative ticks alarm from any context. Function is lockless. It only + * blocks RTC interrupt. + */ +static void set_protected_absolute_ticks(u32_t ticks) +{ + int_disable(); + + prevent_false_prev_evt(); + + set_absolute_ticks(ticks); + + int_enable(); +} + /* Note: this function has public linkage, and MUST have this * particular name. The platform architecture itself doesn't care, * but there is a test (tests/arch/arm_irq_vector_table) that needs @@ -53,27 +163,20 @@ static u32_t counter(void) void rtc1_nrf_isr(void *arg) { ARG_UNUSED(arg); - RTC->EVENTS_COMPARE[0] = 0; + event_clear(); - k_spinlock_key_t key = k_spin_lock(&lock); - u32_t t = counter(); + u32_t t = get_comparator(); u32_t dticks = counter_sub(t, last_count) / CYC_PER_TICK; last_count += dticks * CYC_PER_TICK; if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { - u32_t next = last_count + CYC_PER_TICK; - - /* As below: we're guaranteed to get an interrupt as - * long as it's set two or more cycles in the future + /* protection is not needed because we are in the RTC interrupt + * so it won't get preempted by the interrupt. */ - if (counter_sub(next, t) < 3) { - next += CYC_PER_TICK; - } - set_comparator(next); + set_absolute_ticks(last_count + CYC_PER_TICK); } - k_spin_unlock(&lock, key); z_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1); } @@ -92,12 +195,9 @@ int z_clock_driver_init(struct device *device) /* TODO: replace with counter driver to access RTC */ nrf_rtc_prescaler_set(RTC, 0); - nrf_rtc_cc_set(RTC, 0, CYC_PER_TICK); - nrf_rtc_int_enable(RTC, RTC_INTENSET_COMPARE0_Msk); - - /* Clear the event flag and possible pending interrupt */ - nrf_rtc_event_clear(RTC, NRF_RTC_EVENT_COMPARE_0); + event_clear(); NVIC_ClearPendingIRQ(RTC1_IRQn); + int_enable(); IRQ_CONNECT(RTC1_IRQn, 1, rtc1_nrf_isr, 0, 0); irq_enable(RTC1_IRQn); @@ -115,15 +215,16 @@ int z_clock_driver_init(struct device *device) void z_clock_set_timeout(s32_t ticks, bool idle) { ARG_UNUSED(idle); + u32_t cyc; + + if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) { + return; + } -#ifdef CONFIG_TICKLESS_KERNEL ticks = (ticks == K_TICKS_FOREVER) ? MAX_TICKS : ticks; ticks = MAX(MIN(ticks - 1, (s32_t)MAX_TICKS), 0); - k_spinlock_key_t key = k_spin_lock(&lock); - u32_t cyc, dt, t = counter(); - u32_t unannounced = counter_sub(t, last_count); - bool zli_fixup = IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS); + u32_t unannounced = counter_sub(counter(), last_count); /* If we haven't announced for more than half the 24-bit wrap * duration, then force an announce to avoid loss of a wrap @@ -149,74 +250,7 @@ void z_clock_set_timeout(s32_t ticks, bool idle) } cyc += last_count; - - /* Per NRF docs, the RTC is guaranteed to trigger a compare - * event if the comparator value to be set is at least two - * cycles later than the current value of the counter. So if - * we're three or more cycles out, we can set it blindly. If - * not, check the time again immediately after setting: it's - * possible we "just missed it" and can flag an immediate - * interrupt. Or it could be exactly two cycles out, which - * will have worked. Otherwise, there's no way to get an - * interrupt at the right time and we have to slip the event - * by one clock cycle (or we could spin, but this is a slow - * clock and spinning for a whole cycle can be thousands of - * instructions!) - * - * You might ask: why not set the comparator first and then - * check the timer synchronously to see if we missed it, which - * would avoid the need for a slipped cycle. That doesn't - * work, the states overlap inside the counter hardware. It's - * possible to set a comparator value of "N", issue a DSB - * instruction to flush the pipeline, and then immediately - * read a counter value of "N-1" (i.e. the comparator is still - * in the future), and yet still not receive an interrupt at - * least on nRF52. Some experimentation on nrf52840 shows - * that you need to be early by about 400 processor cycles - * (about 1/5th of a RTC cycle) in order to reliably get the - * interrupt. The docs say two cycles, they mean two cycles. - */ - if (counter_sub(cyc, t) > 2) { - set_comparator(cyc); - } else { - set_comparator(cyc); - dt = counter_sub(cyc, counter()); - if (dt == 0 || dt > 0x7fffff) { - /* Missed it! */ - NVIC_SetPendingIRQ(RTC1_IRQn); - if (IS_ENABLED(CONFIG_ZERO_LATENCY_IRQS)) { - zli_fixup = false; - } - } else if (dt == 1) { - /* Too soon, interrupt won't arrive. */ - set_comparator(cyc + 2); - } - /* Otherwise it was two cycles out, we're fine */ - } - -#ifdef CONFIG_ZERO_LATENCY_IRQS - /* Failsafe. ZLIs can preempt us even though interrupts are - * masked, blowing up the sensitive timing above. If the - * feature is enabled and we haven't recorded the presence of - * a pending interrupt then we need a final check (in a loop! - * because this too can be interrupted) to confirm that the - * comparator is still in the future. Don't bother being - * fancy with cycle counting here, just set an interrupt - * "soon" that we know will get the timer back to a known - * state. This handles (via some hairy modular expressions) - * the wraparound cases where we are preempted for as much as - * half the counter space. - */ - if (zli_fixup && counter_sub(cyc, counter()) <= 0x7fffff) { - while (counter_sub(cyc, counter() + 2) > 0x7fffff) { - cyc = counter() + 3; - set_comparator(cyc); - } - } -#endif - - k_spin_unlock(&lock, key); -#endif /* CONFIG_TICKLESS_KERNEL */ + set_protected_absolute_ticks(cyc); } u32_t z_clock_elapsed(void)