diff --git a/platform/nrf52xxx/timer.c b/platform/nrf52xxx/timer.c index 3345d959..b5989251 100644 --- a/platform/nrf52xxx/timer.c +++ b/platform/nrf52xxx/timer.c @@ -12,40 +12,76 @@ #include #include #include +#include #include -// 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); + } } -