zephyr/drivers/timer/nrf_rtc_timer.c
Andy Ross 7cbdb6c5c0 drivers/timer: Restore non-tickless tick count behavior
The newer series of timer drivers will compare counters vs. the last
tick boundary to compute a number of ticks to announce to the kernel.
In the case of CONFIG_TICKLESS=n, this actually represents a change of
behavior from our old scheme where "ticks" always reflected the number
of interrupts received.

The distinction only matters when an interrupt is delayed more than a
full tick, of course.  But that actually makes a difference to some
timekeeping code.  Restore the old behavior.

This also has the benefit of further reducing code size when !TICKLESS
and improving performance of the ISR by removing the division
(remember Cortex M0 has no hardware divide!).

Fixes #12409

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2019-02-28 16:11:02 -08:00

204 lines
5.1 KiB
C

/*
* Copyright (c) 2016-2017 Nordic Semiconductor ASA
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <soc.h>
#include <clock_control.h>
#include <drivers/clock_control/nrf_clock_control.h>
#include <system_timer.h>
#include <sys_clock.h>
#include <nrf_rtc.h>
#include <spinlock.h>
#define RTC NRF_RTC1
/*
* Compare values must be set to at least 2 greater than the current
* counter value to ensure that the compare fires. Compare values are
* generally determined by reading the counter, then performing some
* calculations to convert a relative delay to an absolute delay.
* Assume that the counter will not increment more than twice during
* these calculations, allowing for a final check that can replace a
* too-low compare with a value that will guarantee fire.
*/
#define MIN_DELAY 4
#define CYC_PER_TICK (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC \
/ CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#if CYC_PER_TICK < MIN_DELAY
#error Cycles per tick is too small
#endif
#define COUNTER_MAX 0x00ffffffU
#define MAX_TICKS ((COUNTER_MAX - MIN_DELAY) / CYC_PER_TICK)
#define MAX_DELAY (MAX_TICKS * CYC_PER_TICK)
static u32_t last_count;
static inline u32_t counter_sub(u32_t a, u32_t b)
{
return (a - b) & COUNTER_MAX;
}
static inline void set_comparator(u32_t cyc)
{
nrf_rtc_cc_set(RTC, 0, cyc);
}
static inline u32_t counter(void)
{
return nrf_rtc_counter_get(RTC);
}
/* 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/kernel/arm_irq_vector_table) that needs
* to find it to it can set it in a custom vector table. Should
* probably better abstract that at some point (e.g. query and reset
* it by pointer at runtime, maybe?) so we don't have this leaky
* symbol.
*/
void rtc1_nrf_isr(void *arg)
{
ARG_UNUSED(arg);
RTC->EVENTS_COMPARE[0] = 0;
u32_t key = irq_lock();
u32_t t = counter();
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;
if (counter_sub(next, t) < MIN_DELAY) {
next += CYC_PER_TICK;
}
set_comparator(next);
}
irq_unlock(key);
z_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
}
int z_clock_driver_init(struct device *device)
{
struct device *clock;
ARG_UNUSED(device);
clock = device_get_binding(DT_NORDIC_NRF_CLOCK_0_LABEL "_32K");
if (!clock) {
return -1;
}
clock_control_on(clock, (void *)CLOCK_CONTROL_NRF_K32SRC);
/* 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_event_enable(RTC, RTC_EVTENSET_COMPARE0_Msk);
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);
NVIC_ClearPendingIRQ(RTC1_IRQn);
IRQ_CONNECT(RTC1_IRQn, 1, rtc1_nrf_isr, 0, 0);
irq_enable(RTC1_IRQn);
nrf_rtc_task_trigger(RTC, NRF_RTC_TASK_CLEAR);
nrf_rtc_task_trigger(RTC, NRF_RTC_TASK_START);
if (!IS_ENABLED(TICKLESS_KERNEL)) {
set_comparator(counter() + CYC_PER_TICK);
}
return 0;
}
void z_clock_set_timeout(s32_t ticks, bool idle)
{
ARG_UNUSED(idle);
#ifdef CONFIG_TICKLESS_KERNEL
ticks = (ticks == K_FOREVER) ? MAX_TICKS : ticks;
ticks = MAX(MIN(ticks - 1, (s32_t)MAX_TICKS), 0);
/*
* Get the requested delay in tick-aligned cycles. Increase
* by one tick to round up so we don't timeout early due to
* cycles elapsed since the last tick. Cap at the maximum
* tick-aligned delta.
*/
u32_t cyc = MIN((1 + ticks) * CYC_PER_TICK, MAX_DELAY);
u32_t key = irq_lock();
u32_t d = counter_sub(counter(), last_count);
/*
* We've already accounted for anything less than a full tick,
* and assumed we meet the minimum delay for the tick. If
* that's not true, we have to adjust, which may involve a
* rare and expensive integer division.
*/
if (d > (CYC_PER_TICK - MIN_DELAY)) {
if (d >= CYC_PER_TICK) {
/*
* We're late by at least one tick. Adjust
* the compare offset for the missed ones, and
* reduce d to be the portion since the last
* (unseen) tick.
*/
u32_t missed_ticks = d / CYC_PER_TICK;
u32_t missed_cycles = missed_ticks * CYC_PER_TICK;
cyc += missed_cycles;
d -= missed_cycles;
}
if (d > (CYC_PER_TICK - MIN_DELAY)) {
/*
* We're (now) within the tick, but too close
* to meet the minimum delay required to
* guarantee compare firing. Step up to the
* next tick.
*/
cyc += CYC_PER_TICK;
}
if (cyc > MAX_DELAY) {
/* Don't adjust beyond the counter range. */
cyc = MAX_DELAY;
}
}
set_comparator(last_count + cyc);
irq_unlock(key);
#endif
}
u32_t z_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
u32_t key = irq_lock();
u32_t ret = counter_sub(counter(), last_count) / CYC_PER_TICK;
irq_unlock(key);
return ret;
}
u32_t _timer_cycle_get_32(void)
{
u32_t key = irq_lock();
u32_t ret = counter_sub(counter(), last_count) + last_count;
irq_unlock(key);
return ret;
}