diff --git a/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/api/lp_ticker.c b/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/api/lp_ticker.c index 996bf22f53..3ebc0ce730 100644 --- a/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/api/lp_ticker.c +++ b/targets/TARGET_Freescale/TARGET_MCUXpresso_MCUS/api/lp_ticker.c @@ -1,5 +1,5 @@ /* mbed Microcontroller Library - * Copyright (c) 2016 ARM Limited + * Copyright (c) 2016 - 2018 ARM Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,20 +22,33 @@ #include "cmsis.h" #include "rtc_api.h" -#define MAX_SEC_BITS (12) -#define MAX_SEC_MASK ((1 << MAX_SEC_BITS) - 1) -#define SEC_IN_USEC (1000000) +const ticker_info_t* lp_ticker_get_info() +{ + static const ticker_info_t info = { + 32768, // 32kHz + 32 // 32 bit counter + }; + return &info; +} + +#define SEC_BITS (17) +#define SEC_SHIFT (15) +#define SEC_MASK ((1 << SEC_BITS) - 1) +#define TICKS_BITS (15) +#define TICKS_SHIFT (0) +#define TICKS_MASK ((1 << TICKS_BITS) - 1) + #define OSC32K_CLK_HZ (32768) #define MAX_LPTMR_SLEEP ((1 << 16) - 1) static bool lp_ticker_inited = false; -static int lptmr_schedule = 0; +static timestamp_t lptmr_schedule = 0; static void rtc_isr(void) { uint32_t sr = RTC->SR; if (sr & RTC_SR_TOF_MASK) { - // Reset RTC to 0 so it keeps counting + /* Reset RTC to 0 so it keeps counting. */ RTC_StopTimer(RTC); RTC->TSR = 0; RTC_StartTimer(RTC); @@ -43,14 +56,23 @@ static void rtc_isr(void) RTC_DisableInterrupts(RTC, kRTC_AlarmInterruptEnable); RTC->TAR = 0; /* Write clears the IRQ flag */ - /* Wait subsecond remainder if any */ - if (lptmr_schedule) { - LPTMR_SetTimerPeriod(LPTMR0, lptmr_schedule); + /* Wait subsecond remainder. */ + const uint32_t now_ticks = lp_ticker_read(); + uint32_t delta_ticks = + lptmr_schedule >= now_ticks ? lptmr_schedule - now_ticks : (uint32_t)((uint64_t) lptmr_schedule + 0xFFFFFFFF - now_ticks); + + lptmr_schedule = 0; + + if (delta_ticks == 0) { + lp_ticker_irq_handler(); + } else { + LPTMR_StopTimer(LPTMR0); + LPTMR_ClearStatusFlags(LPTMR0, kLPTMR_TimerCompareFlag); + LPTMR_SetTimerPeriod(LPTMR0, delta_ticks); LPTMR_EnableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable); LPTMR_StartTimer(LPTMR0); - } else { - lp_ticker_irq_handler(); } + } else if (sr & RTC_SR_TIF_MASK) { RTC_DisableInterrupts(RTC, kRTC_TimeOverflowInterruptEnable); } @@ -71,102 +93,124 @@ void lp_ticker_init(void) { lptmr_config_t lptmrConfig; - if (lp_ticker_inited) { - return; - } - lp_ticker_inited = true; + if (!lp_ticker_inited) { - /* Setup low resolution clock - RTC */ - if (!rtc_isenabled()) { - rtc_init(); + /* Setup low resolution clock - RTC */ + if (!rtc_isenabled()) { + rtc_init(); + RTC_StartTimer(RTC); + } + + RTC->TAR = 0; /* Write clears the IRQ flag */ + NVIC_ClearPendingIRQ(RTC_IRQn); RTC_DisableInterrupts(RTC, kRTC_AlarmInterruptEnable | kRTC_SecondsInterruptEnable); - RTC_StartTimer(RTC); + NVIC_SetVector(RTC_IRQn, (uint32_t) rtc_isr); + NVIC_EnableIRQ(RTC_IRQn); + + /* Setup high resolution clock - LPTMR */ + LPTMR_GetDefaultConfig(&lptmrConfig); + + /* Use 32kHz drive */ + CLOCK_SetXtal32Freq(OSC32K_CLK_HZ); + lptmrConfig.prescalerClockSource = kLPTMR_PrescalerClock_2; + LPTMR_Init(LPTMR0, &lptmrConfig); + LPTMR_DisableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable); + NVIC_ClearPendingIRQ(LPTMR0_IRQn); + NVIC_SetVector(LPTMR0_IRQn, (uint32_t) lptmr_isr); + EnableIRQ(LPTMR0_IRQn); + + lptmr_schedule = 0; + + lp_ticker_inited = true; + } else { + /* In case of re-init we need to disable lp ticker interrupt. */ + LPTMR_StopTimer(LPTMR0); + + RTC_DisableInterrupts(RTC, kRTC_AlarmInterruptEnable); + RTC->TAR = 0; /* Write clears the IRQ flag */ + + lptmr_schedule = 0; } - - RTC->TAR = 0; /* Write clears the IRQ flag */ - NVIC_ClearPendingIRQ(RTC_IRQn); - NVIC_SetVector(RTC_IRQn, (uint32_t)rtc_isr); - NVIC_EnableIRQ(RTC_IRQn); - - /* Setup high resolution clock - LPTMR */ - LPTMR_GetDefaultConfig(&lptmrConfig); - /* Use 32kHz drive */ - CLOCK_SetXtal32Freq(OSC32K_CLK_HZ); - lptmrConfig.prescalerClockSource = kLPTMR_PrescalerClock_2; - LPTMR_Init(LPTMR0, &lptmrConfig); - LPTMR_EnableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable); - NVIC_ClearPendingIRQ(LPTMR0_IRQn); - NVIC_SetVector(LPTMR0_IRQn, (uint32_t)lptmr_isr); - EnableIRQ(LPTMR0_IRQn); } /** Read the current counter * - * @return The current timer's counter value in microseconds + * @return The current timer's counter value in ticks */ uint32_t lp_ticker_read(void) { - uint32_t sec, pre; + uint32_t count; + uint32_t last_count; - if (!lp_ticker_inited) { - lp_ticker_init(); - } + /* TPR is increments every 32.768 kHz clock cycle. The TSR increments when + * bit 14 of the TPR transitions from a logic one (32768 ticks - 1 sec). + * After that TPR starts counting from 0. + * + * count value is built as follows: + * count[0 - 14] - ticks (RTC->TPR) + * count[15 - 31] - seconds (RTC->TSR) + */ - sec = RTC->TSR; /* 32b: Seconds */ - pre = RTC->TPR; /* 16b: Increments every 32.768kHz clock cycle (30us) */ + /* Loop until the same tick is read twice since this + * is ripple counter on a different clock domain. + */ + count = ((RTC->TSR << SEC_SHIFT) | (RTC->TPR & TICKS_MASK)); + do { + last_count = count; + count = ((RTC->TSR << SEC_SHIFT) | (RTC->TPR & TICKS_MASK)); + } while (last_count != count); - /* Final value: 11b (4095) for sec and 21b for usec (pre can reach 1,000,000us which is close to 1<<20) */ - uint32_t ret = (((sec & MAX_SEC_MASK) * SEC_IN_USEC) + (((uint64_t)pre * SEC_IN_USEC) / OSC32K_CLK_HZ)); - - return ret; + return count; } /** Set interrupt for specified timestamp * - * @param timestamp The time in microseconds to be set + * @param timestamp The time in ticks to be set */ void lp_ticker_set_interrupt(timestamp_t timestamp) { - uint32_t now_us, delta_us, delta_ticks; - - if (!lp_ticker_inited) { - lp_ticker_init(); - } - lptmr_schedule = 0; - now_us = lp_ticker_read(); - delta_us = timestamp >= now_us ? timestamp - now_us : (uint32_t)((uint64_t)timestamp + 0xFFFFFFFF - now_us); - /* Checking if LPTRM can handle this sleep */ - delta_ticks = USEC_TO_COUNT(delta_us, CLOCK_GetFreq(kCLOCK_Er32kClk)); + /* We get here absolute interrupt time-stamp in ticks which takes into account counter overflow. + * Since we use additional count-down timer to generate interrupt we need to calculate + * load value based on time-stamp. + */ + const uint32_t now_ticks = lp_ticker_read(); + uint32_t delta_ticks = + timestamp >= now_ticks ? timestamp - now_ticks : (uint32_t)((uint64_t) timestamp + 0xFFFFFFFF - now_ticks); + if (delta_ticks == 0) { - /* The requested delay is less than the minimum resolution of this counter */ + /* The requested delay is less than the minimum resolution of this counter. */ delta_ticks = 1; } if (delta_ticks > MAX_LPTMR_SLEEP) { - /* Using RTC if wait time is over 16b (2s @32kHz) */ - uint32_t delta_sec; + /* Using RTC if wait time is over 16b (2s @32kHz). */ + uint32_t delay_sec = delta_ticks >> 15; - delta_us += COUNT_TO_USEC(RTC->TPR, CLOCK_GetFreq(kCLOCK_Er32kClk)); /* Accounting for started second */ - delta_sec = delta_us / SEC_IN_USEC; - delta_us -= delta_sec * SEC_IN_USEC; - - RTC->TAR = RTC->TSR + delta_sec - 1; + RTC->TAR = RTC->TSR + delay_sec - 1; RTC_EnableInterrupts(RTC, kRTC_AlarmInterruptEnable); - /* Set aditional, subsecond, sleep time */ - if (delta_us) { - lptmr_schedule = USEC_TO_COUNT(delta_us, CLOCK_GetFreq(kCLOCK_Er32kClk)); - if (lptmr_schedule == 0) { - /* The requested delay is less than the minimum resolution of this counter */ - lptmr_schedule = 1; - } - - } + /* Store absolute interrupt time-stamp value for further processing in + * RTC interrupt handler (schedule remaining ticks using LPTMR). */ + lptmr_schedule = timestamp; } else { - /* Below RTC resolution using LPTMR */ + /* Below RTC resolution using LPTMR. */ + + /* In case of re-schedule we need to disable RTC interrupt. */ + RTC_DisableInterrupts(RTC, kRTC_AlarmInterruptEnable); + RTC->TAR = 0; /* Write clears the IRQ flag */ + + /* When the LPTMR is enabled, the CMR can be altered only when CSR[TCF] is set. When + * updating the CMR, the CMR must be written and CSR[TCF] must be cleared before the + * LPTMR counter has incremented past the new LPTMR compare value. + * + * When TEN is clear, it resets the LPTMR internal logic, including the CNR and TCF. + * When TEN is set, the LPTMR is enabled. While writing 1 to this field, CSR[5:1] must + * not be altered. + */ + LPTMR_StopTimer(LPTMR0); LPTMR_SetTimerPeriod(LPTMR0, delta_ticks); LPTMR_EnableInterrupts(LPTMR0, kLPTMR_TimerInterruptEnable); LPTMR_StartTimer(LPTMR0); @@ -194,6 +238,7 @@ void lp_ticker_clear_interrupt(void) { RTC->TAR = 0; /* Write clears the IRQ flag */ LPTMR_ClearStatusFlags(LPTMR0, kLPTMR_TimerCompareFlag); + lptmr_schedule = 0; } #endif /* DEVICE_LOWPOWERTIMER */