/***************************************************************************//**
 * @file
 * @brief Backup Real Time Counter (BURTC) Peripheral API
 *******************************************************************************
 * # License
 * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * 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 */
