blob: 46cd90a166b8d5ee5f1bc411472706fcc21d0798 [file] [log] [blame]
/***************************************************************************//**
* @file em_burtc.c
* @brief Backup Real Time Counter (BURTC) Peripheral API
* @version 5.6.0
*******************************************************************************
* # License
* <b>Copyright 2016 Silicon Laboratories, Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
* 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.
*
* DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
* obligation to support this Software. Silicon Labs is providing the
* Software "AS IS", with no express or implied warranties of any kind,
* including, but not limited to, any implied warranties of merchantability
* or fitness for any particular purpose or warranties against infringement
* of any proprietary rights of a third party.
*
* Silicon Labs will not be liable for any consequential, incidental, or
* special damages, or any other relief, or for any claim by any third party,
* arising from your use of this Software.
*
******************************************************************************/
#include "em_burtc.h"
#if defined(BURTC_PRESENT)
/***************************************************************************//**
* @addtogroup emlib
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup BURTC
* @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 > 0) && (div <= 32768));
/* Count leading zeroes and "reverse" result, Cortex-M3 intrinsic. */
log2 = (31 - __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;
regSync(BURTC_SYNCBUSY_EN);
BURTC->CFG = (presc << _BURTC_CFG_CNTPRESC_SHIFT)
| ((burtcInit->compare0Top ? 1U : 0U) << _BURTC_CFG_COMPTOP_SHIFT)
| ((burtcInit->debugRun ? 1U : 0U) << _BURTC_CFG_DEBUGRUN_SHIFT);
BURTC->EM4WUEN = ((burtcInit->em4comp ? 1U : 0U) << _BURTC_EM4WUEN_COMPEM4WUEN_SHIFT)
| ((burtcInit->em4overflow ? 1U : 0U) << _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)
{
regSync(BURTC_SYNCBUSY_EN);
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;
}
}
#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;
BURTC_SyncWait();
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) */
/** @} (end addtogroup emlib) */
#endif /* BURTC_PRESENT */