/***************************************************************************//** * @file * @brief Backup Real Time Counter (BURTC) Peripheral API ******************************************************************************* * # License * Copyright 2018 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * SPDX-License-Identifier: Zlib * * The licensor of this software is Silicon Laboratories Inc. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * ******************************************************************************/ #include "em_burtc.h" #if defined(BURTC_PRESENT) /***************************************************************************//** * @addtogroup burtc BURTC - Backup RTC * @brief Backup Real Time Counter (BURTC) Peripheral API * @details * This module contains functions to control the BURTC peripheral of Silicon * Labs 32-bit MCUs. The Backup Real Time Counter allows timekeeping in all * energy modes. The Backup RTC is also available when the system is in backup * mode. * @{ ******************************************************************************/ /******************************************************************************* ******************************* DEFINES *********************************** ******************************************************************************/ /******************************************************************************* ************************** LOCAL FUNCTIONS ******************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /***************************************************************************//** * @brief Convert dividend to a prescaler logarithmic value. Only works for even * numbers equal to 2^n. * @param[in] div Unscaled dividend, * @return Base 2 logarithm of input, as used by fixed prescalers. ******************************************************************************/ __STATIC_INLINE uint32_t divToLog2(uint32_t div) { uint32_t log2; /* Prescaler accepts an argument of 128 or less, valid values being 2^n. */ EFM_ASSERT((div > 0UL) && (div <= 32768UL)); /* Count leading zeroes and "reverse" result, Cortex-M3 intrinsic. */ log2 = (31UL - __CLZ(div)); return log2; } /***************************************************************************//** * @brief * Wait for an ongoing sync of register(s) to low frequency domain to complete. * * @param[in] mask * A bitmask corresponding to SYNCBUSY register defined bits, indicating * registers that must complete any ongoing synchronization. ******************************************************************************/ __STATIC_INLINE void regSync(uint32_t mask) { #if defined(_BURTC_FREEZE_MASK) /* Avoid deadlock if modifying the same register twice when freeze mode is activated or when a clock is not selected for the BURTC. If a clock is not selected, then the sync is done once the clock source is set. */ if ((BURTC->FREEZE & BURTC_FREEZE_REGFREEZE) || ((BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK) == BURTC_CTRL_CLKSEL_NONE) || ((BURTC->CTRL & _BURTC_CTRL_RSTEN_MASK) == BURTC_CTRL_RSTEN)) { return; } #endif /* Wait for any pending previous write operation to complete */ /* in low frequency domain. This is only required for the Gecko Family. */ while ((BURTC->SYNCBUSY & mask) != 0U) { } } /** @endcond */ /******************************************************************************* ************************** GLOBAL FUNCTIONS ******************************* ******************************************************************************/ /***************************************************************************//** * @brief Initialize BURTC. * * @details * Configures the BURTC peripheral. * * @note * Before initialization, BURTC module must first be enabled by clearing the * reset bit in the RMU, i.e., * @verbatim * RMU_ResetControl(rmuResetBU, rmuResetModeClear); * @endverbatim * Compare channel 0 must be configured outside this function, before * initialization if enable is set to true. The counter will always be reset. * * @param[in] burtcInit * A pointer to the BURTC initialization structure. ******************************************************************************/ void BURTC_Init(const BURTC_Init_TypeDef *burtcInit) { #if defined(_SILICON_LABS_32B_SERIES_0) uint32_t ctrl; uint32_t presc; /* Check initializer structure integrity. */ EFM_ASSERT(burtcInit != (BURTC_Init_TypeDef *) 0); /* Clock divider must be between 1 and 128, really on the form 2^n. */ EFM_ASSERT((burtcInit->clkDiv >= 1) && (burtcInit->clkDiv <= 128)); /* Ignored compare bits during low power operation must be less than 7. */ /* Note! Giant Gecko revision C errata, do NOT use LPCOMP=7. */ EFM_ASSERT(burtcInit->lowPowerComp <= 6); /* You cannot enable the BURTC if mode is set to disabled. */ EFM_ASSERT((burtcInit->enable == false) || ((burtcInit->enable == true) && (burtcInit->mode != burtcModeDisable))); /* Low power mode is only available with LFRCO or LFXO as clock source. */ EFM_ASSERT((burtcInit->clkSel != burtcClkSelULFRCO) || ((burtcInit->clkSel == burtcClkSelULFRCO) && (burtcInit->lowPowerMode == burtcLPDisable))); /* Calculate a prescaler value from the clock divider input. */ /* Note! If clock select (clkSel) is ULFRCO, a clock divisor (clkDiv) of value 1 will select a 2 kHz ULFRCO clock, while any other value will select a 1 kHz ULFRCO clock source. */ presc = divToLog2(burtcInit->clkDiv); /* Make sure all registers are updated simultaneously. */ if (burtcInit->enable) { BURTC_FreezeEnable(true); } /* Modification of LPMODE register requires sync with potential ongoing * register updates in LF domain. */ regSync(BURTC_SYNCBUSY_LPMODE); /* Configure low power mode. */ BURTC->LPMODE = (uint32_t) (burtcInit->lowPowerMode); /* New configuration. */ ctrl = (BURTC_CTRL_RSTEN | (burtcInit->mode) | (burtcInit->debugRun << _BURTC_CTRL_DEBUGRUN_SHIFT) | (burtcInit->compare0Top << _BURTC_CTRL_COMP0TOP_SHIFT) | (burtcInit->lowPowerComp << _BURTC_CTRL_LPCOMP_SHIFT) | (presc << _BURTC_CTRL_PRESC_SHIFT) | (burtcInit->clkSel) | (burtcInit->timeStamp << _BURTC_CTRL_BUMODETSEN_SHIFT)); /* Clear interrupts. */ BURTC_IntClear(0xFFFFFFFF); /* Set the new configuration. */ BURTC->CTRL = ctrl; /* Enable BURTC and counter. */ if (burtcInit->enable) { /* To enable BURTC counter, disable reset. */ BURTC_Enable(true); /* Clear freeze. */ BURTC_FreezeEnable(false); } #elif defined(_SILICON_LABS_32B_SERIES_2) uint32_t presc; presc = divToLog2(burtcInit->clkDiv); if (BURTC->EN != 0U) { BURTC_SyncWait(); } BURTC->EN_CLR = BURTC_EN_EN; #if defined(_BURTC_SYNCBUSY_EN_MASK) regSync(BURTC_SYNCBUSY_EN); #elif defined(_BURTC_EN_DISABLING_MASK) while (BURTC->EN & _BURTC_EN_DISABLING_MASK) { /* Wait for disabling to finish */ } #endif BURTC->CFG = (presc << _BURTC_CFG_CNTPRESC_SHIFT) | ((burtcInit->compare0Top ? 1UL : 0UL) << _BURTC_CFG_COMPTOP_SHIFT) | ((burtcInit->debugRun ? 1UL : 0UL) << _BURTC_CFG_DEBUGRUN_SHIFT); BURTC->EM4WUEN = ((burtcInit->em4comp ? 1UL : 0UL) << _BURTC_EM4WUEN_COMPEM4WUEN_SHIFT) | ((burtcInit->em4overflow ? 1UL : 0UL) << _BURTC_EM4WUEN_OFEM4WUEN_SHIFT); BURTC->EN_SET = BURTC_EN_EN; if (burtcInit->start) { BURTC_Start(); } #endif } #if defined(_SILICON_LABS_32B_SERIES_2) /***************************************************************************//** * @brief * Enable or Disable BURTC peripheral. * * @param[in] enable * true to enable, false to disable. ******************************************************************************/ void BURTC_Enable(bool enable) { #if defined(_BURTC_SYNCBUSY_EN_MASK) regSync(BURTC_SYNCBUSY_EN); #endif if ((BURTC->EN == 0U) && !enable) { /* Trying to disable BURTC when it's already disabled */ return; } if (BURTC->EN != 0U) { /* Modifying the enable bit while synchronization is active will BusFault */ BURTC_SyncWait(); } if (enable) { BURTC->EN_SET = BURTC_EN_EN; } else { BURTC_Stop(); BURTC_SyncWait(); /* Wait for the stop to synchronize */ BURTC->EN_CLR = BURTC_EN_EN; #if defined(_BURTC_SYNCBUSY_EN_MASK) regSync(BURTC_SYNCBUSY_EN); #elif defined(_BURTC_EN_DISABLING_MASK) while (BURTC->EN & _BURTC_EN_DISABLING_MASK) { /* Wait for disabling to finish */ } #endif } } #elif defined(_SILICON_LABS_32B_SERIES_0) /***************************************************************************//** * @brief * Enable or Disable BURTC peripheral reset and start counter * @param[in] enable * If true; asserts reset to BURTC, halts counter, if false; deassert reset ******************************************************************************/ void BURTC_Enable(bool enable) { /* Note! If mode is disabled, BURTC counter will not start */ EFM_ASSERT(((enable == true) && ((BURTC->CTRL & _BURTC_CTRL_MODE_MASK) != BURTC_CTRL_MODE_DISABLE)) || (enable == false)); BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, (uint32_t) !enable); } #endif /***************************************************************************//** * @brief Set BURTC compare channel. * * @param[in] comp Compare the channel index, must be 0 for current devices. * * @param[in] value New compare value. ******************************************************************************/ void BURTC_CompareSet(unsigned int comp, uint32_t value) { (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */ EFM_ASSERT(comp == 0U); #if defined(_BURTC_COMP0_MASK) /* Modification of COMP0 register requires sync with potential ongoing * register updates in LF domain. */ regSync(BURTC_SYNCBUSY_COMP0); /* Configure compare channel 0/. */ BURTC->COMP0 = value; #else /* Wait for last potential write to complete. */ regSync(BURTC_SYNCBUSY_COMP); /* Configure compare channel 0 */ BURTC->COMP = value; regSync(BURTC_SYNCBUSY_COMP); #endif } /***************************************************************************//** * @brief Get the BURTC compare value. * * @param[in] comp Compare the channel index value, must be 0 for Giant/Leopard Gecko. * * @return The currently configured value for this compare channel. ******************************************************************************/ uint32_t BURTC_CompareGet(unsigned int comp) { (void) comp; /* Unused parameter when EFM_ASSERT is undefined. */ EFM_ASSERT(comp == 0U); #if defined(_BURTC_COMP0_MASK) return BURTC->COMP0; #else return BURTC->COMP; #endif } /***************************************************************************//** * @brief Reset counter ******************************************************************************/ void BURTC_CounterReset(void) { #if defined(_BURTC_CTRL_MASK) /* Set and clear reset bit */ BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 1U); BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 0U); #else BURTC_Stop(); BURTC->CNT = 0U; BURTC_Start(); #endif } /***************************************************************************//** * @brief * Restore BURTC to reset state. * @note * Before accessing the BURTC, BURSTEN in RMU->CTRL must be cleared. * LOCK will not be reset to default value, as this will disable access * to core BURTC registers. ******************************************************************************/ void BURTC_Reset(void) { #if defined(_SILICON_LABS_32B_SERIES_0) bool buResetState; /* Read reset state, set reset, and restore state. */ buResetState = BUS_RegBitRead(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT); BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, 1); BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, buResetState); #elif defined(_SILICON_LABS_32B_SERIES_2) if (BURTC->EN != 0U) { BURTC_SyncWait(); } BURTC->EN_SET = BURTC_EN_EN; BURTC_Stop(); BURTC->CNT = 0x0; BURTC->PRECNT = 0x0; BURTC->COMP = 0x0; BURTC->EM4WUEN = _BURTC_EM4WUEN_RESETVALUE; BURTC->IEN = _BURTC_IEN_RESETVALUE; BURTC->IF_CLR = _BURTC_IF_MASK; /* Wait for all values to synchronize. BusFaults can happen if we don't * do this before the enable bit is cleared. */ BURTC_SyncWait(); BURTC->EN_CLR = BURTC_EN_EN; #if defined(_BURTC_SYNCBUSY_EN_MASK) while (BURTC->SYNCBUSY != 0U) { // Wait for the EN=0 to synchronize } #elif defined(_BURTC_EN_DISABLING_MASK) while (BURTC->EN & _BURTC_EN_DISABLING_MASK) { /* Wait for disabling to finish */ } #endif BURTC->CFG = _BURTC_CFG_RESETVALUE; #endif } #if defined(_BURTC_CTRL_MASK) /***************************************************************************//** * @brief * Get the clock frequency of the BURTC. * * @return * The current frequency in Hz. ******************************************************************************/ uint32_t BURTC_ClockFreqGet(void) { uint32_t clkSel; uint32_t clkDiv; uint32_t frequency; clkSel = BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK; clkDiv = (BURTC->CTRL & _BURTC_CTRL_PRESC_MASK) >> _BURTC_CTRL_PRESC_SHIFT; switch (clkSel) { /** Ultra-low frequency (1 kHz) clock. */ case BURTC_CTRL_CLKSEL_ULFRCO: if (_BURTC_CTRL_PRESC_DIV1 == clkDiv) { frequency = 2000; /* 2 kHz when clock divisor is 1. */ } else { frequency = SystemULFRCOClockGet(); /* 1 kHz when divisor is different from 1. */ } break; /** Low-frequency RC oscillator. */ case BURTC_CTRL_CLKSEL_LFRCO: frequency = SystemLFRCOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */ break; /** Low-frequency crystal oscillator. */ case BURTC_CTRL_CLKSEL_LFXO: frequency = SystemLFXOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */ break; default: /* No clock selected for BURTC. */ frequency = 0; } return frequency; } #endif /** @} (end addtogroup burtc) */ #endif /* BURTC_PRESENT */