[nrf52][tick] improve RTC use for LK tick

Using the RTC1 module for the LK tick (instead of arm systick)
Use the nrf_hal library for peripheral manipulation instead of
direct register access.  Fixed race condition in current_time_hires
which would result in non monotonic readings.

Tested with clock_tests on:
  nrf52-pca10040-test  (nrf52832)
  nrf52-pca10056-test  (nrf52840)
This commit is contained in:
Eric Holland
2020-10-14 16:06:38 -04:00
parent 945cd5ecdb
commit fbac7e3f8f

View File

@@ -12,40 +12,76 @@
#include <platform/timer.h>
#include <platform/clock.h>
#include <nrfx_clock.h>
#include <hal/nrf_rtc.h>
#include <sys/types.h>
// TODO - integrate nrfx rtc driver instead of direct register manipulation here.
/*
* This uses the RTC1 module for the LK system tick instead of using the ARM
* systick since systick is not available in all low power states. This is also
* not utilizing the nrfx rtc driver as it adds unneeded functionality and thus
* overhead to the irq processing. It is however using convenience functions
* in the nrf hal library (nrf hal should not be confused with nrfx driver library)
*/
//base counter is total number of clock cycles elapsed
// compare channel used by our timer.
#define LK_RTC_CC_CHAN (0)
#define MULT_BY_32768(x) ((x) << 15)
#define DIV_BY_32768(x) ((x) >> 15)
/*
* base_counter is total number of ms elapsed in 49.15 format. By choosing this
* format we take advantage of the fact our oscillator is 32768Hz, allowing the
* recurring math to be done with shift by 15. This preserves some precision in
* the base_counter for use in current_time_hires.
*
* base_counter will overflow every 17,851 years. Plan accordingly.
*/
static volatile uint64_t base_counter = 0;
static uint32_t clock_rate = 0;
//cycles of our clock per tick interval
static uint32_t cycles_per_tick = 0;
static uint32_t rtc_counts_per_tick = 0;
static volatile uint32_t last_cc = 0;
//delta to be added to base counter (ms in 49.15 format) each lk tick
static uint32_t ms_scale = 0;
static platform_timer_callback cb;
static void *cb_args;
typedef enum handler_return (*platform_timer_callback)(void *arg, lk_time_t now);
status_t platform_set_periodic_timer(platform_timer_callback callback, void *arg, lk_time_t interval) {
ASSERT(clock_rate > 0);
ASSERT(nrfx_clock_lfclk_is_running());
cb = callback;
cb_args = arg;
// RTC clock frequency is always 32768Hz, so we take advantage of shifts vs. multiplicaiton
rtc_counts_per_tick = MULT_BY_32768(interval) / 1000 ;
ms_scale = rtc_counts_per_tick * 1000;
cycles_per_tick = clock_rate * interval / 1000 ;
// Stop the counter in case it was already running
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_STOP);
NRF_RTC1->CC[0] = 0x00ffffff & (base_counter + cycles_per_tick);
// Clear the counter
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_CLEAR);
NRF_RTC1->PRESCALER = 0;
// Set the first timer interval
nrf_rtc_cc_set(NRF_RTC1, LK_RTC_CC_CHAN, rtc_counts_per_tick);
NRF_RTC1->INTENSET = RTC_INTENSET_COMPARE0_Enabled << RTC_INTENSET_COMPARE0_Pos;
// Run at full 32768Hz rate, no prescale
nrf_rtc_prescaler_set(NRF_RTC1, 0);
// Allow the CC channel to create interrupts when flagged
nrf_rtc_int_enable(NRF_RTC1, RTC_CHANNEL_INT_MASK(LK_RTC_CC_CHAN));
// In order for this event to wake the device from low power/sleep state, the
// event must be enabled to do so.
nrf_rtc_event_enable(NRF_RTC1, RTC_CHANNEL_INT_MASK(LK_RTC_CC_CHAN));
// Clear the event state prior to enabling the irq to prevent spurious irq
nrf_rtc_event_clear(NRF_RTC1, nrf_rtc_compare_event_get(LK_RTC_CC_CHAN));
// Start the RTC counter.
nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_START);
NRF_RTC1->EVENTS_TICK = 0;
NRF_RTC1->EVENTS_COMPARE[0] = 0;
NRF_RTC1->TASKS_START = 1;
NVIC_EnableIRQ(RTC1_IRQn);
return NO_ERROR;
@@ -59,36 +95,37 @@ lk_time_t current_time(void) {
t = base_counter;
} while (base_counter != t);
return t * 1000 / clock_rate;
return DIV_BY_32768(t);
}
// time in usec
lk_bigtime_t current_time_hires(void) {
uint64_t t;
volatile uint64_t t;
volatile uint32_t delta;
do {
// order of operations below is critical to catch if timer is updated
// during the calculations.
t = base_counter;
} while (base_counter != t);
delta = (nrf_rtc_counter_get(NRF_RTC1) - last_cc);
} while ((t != base_counter));
// base_counter only gets updated once every tick, regain extra
// precision by adding in elapsed counts since last tick interrupt
// Note: counter is only 24 bits
t = t + ((NRF_RTC1->COUNTER - (t & 0x00ffffff)) & 0x00ffffff);
return (t * 1000000 / clock_rate);
return DIV_BY_32768((t + delta * 1000) * 1000);
}
void nrf52_RTC1_IRQ(void) {
// update to this point in time
base_counter += cycles_per_tick;
base_counter += ms_scale;
arm_cm_irq_entry();
NRF_RTC1->EVENTS_COMPARE[0] = 0;
nrf_rtc_event_clear(NRF_RTC1, nrf_rtc_compare_event_get(LK_RTC_CC_CHAN));
// calculate time of next interrupt
NRF_RTC1->CC[0] = 0x00ffffff & (base_counter + cycles_per_tick);
last_cc = nrf_rtc_cc_get(NRF_RTC1, LK_RTC_CC_CHAN);
nrf_rtc_cc_set(NRF_RTC1, LK_RTC_CC_CHAN, last_cc + rtc_counts_per_tick);
bool resched = false;
if (cb) {
lk_time_t now = current_time();
lk_time_t now = DIV_BY_32768(base_counter);
if (cb(cb_args, now) == INT_RESCHEDULE)
resched = true;
}
@@ -97,8 +134,11 @@ void nrf52_RTC1_IRQ(void) {
void arm_cm_systick_init(uint32_t hz) {
ASSERT(hz == 32768);
clock_rate = hz;
//Enable the LF xtal oscillator
nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_Xtal);
//Try to enable the LF xtal oscillator
status_t status = nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_Xtal);
if (status != NO_ERROR) {
// If xtal won't start (might not be present on target) then
// use the internal RC oscillator which is always present.
nrf52_clock_lfclk_enable(NRF_CLOCK_LFCLK_RC);
}
}