/***************************************************************************//**
 * @file em_emu.c
 * @brief Energy Management Unit (EMU) Peripheral API
 * @version 4.2.1
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://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 <limits.h>

#include "em_emu.h"
#if defined( EMU_PRESENT ) && ( EMU_COUNT > 0 )

#include "em_cmu.h"
#include "em_system.h"
#include "em_assert.h"

/***************************************************************************//**
 * @addtogroup EM_Library
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup EMU
 * @brief Energy Management Unit (EMU) Peripheral API
 * @{
 ******************************************************************************/

/* Consistency check, since restoring assumes similar bitpositions in */
/* CMU OSCENCMD and STATUS regs */
#if (CMU_STATUS_AUXHFRCOENS != CMU_OSCENCMD_AUXHFRCOEN)
#error Conflict in AUXHFRCOENS and AUXHFRCOEN bitpositions
#endif
#if (CMU_STATUS_HFXOENS != CMU_OSCENCMD_HFXOEN)
#error Conflict in HFXOENS and HFXOEN bitpositions
#endif
#if (CMU_STATUS_LFRCOENS != CMU_OSCENCMD_LFRCOEN)
#error Conflict in LFRCOENS and LFRCOEN bitpositions
#endif
#if (CMU_STATUS_LFXOENS != CMU_OSCENCMD_LFXOEN)
#error Conflict in LFXOENS and LFXOEN bitpositions
#endif


/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/* Fix for errata EMU_E107 - non-WIC interrupt masks. */
#if defined( _EFM32_GECKO_FAMILY )
#define ERRATA_FIX_EMU_E107_EN
#define NON_WIC_INT_MASK_0    (~(0x0dfc0323U))
#define NON_WIC_INT_MASK_1    (~(0x0U))

#elif defined( _EFM32_TINY_FAMILY )
#define ERRATA_FIX_EMU_E107_EN
#define NON_WIC_INT_MASK_0    (~(0x001be323U))
#define NON_WIC_INT_MASK_1    (~(0x0U))

#elif defined( _EFM32_GIANT_FAMILY )
#define ERRATA_FIX_EMU_E107_EN
#define NON_WIC_INT_MASK_0    (~(0xff020e63U))
#define NON_WIC_INT_MASK_1    (~(0x00000046U))

#elif defined( _EFM32_WONDER_FAMILY )
#define ERRATA_FIX_EMU_E107_EN
#define NON_WIC_INT_MASK_0    (~(0xff020e63U))
#define NON_WIC_INT_MASK_1    (~(0x00000046U))

#else
/* Zero Gecko and future families are not affected by errata EMU_E107 */
#endif

/* Fix for errata EMU_E108 - High Current Consumption on EM4 Entry. */
#if defined( _EFM32_HAPPY_FAMILY )
#define ERRATA_FIX_EMU_E108_EN
#endif
/** @endcond */


#if defined( _EMU_DCDCCTRL_MASK )
/* DCDCTODVDD output range min/max */
#define PWRCFG_DCDCTODVDD_VMIN          1200
#define PWRCFG_DCDCTODVDD_VMAX          3000
typedef enum
{
  errataFixDcdcHsInit,
  errataFixDcdcHsTrimSet,
  errataFixDcdcHsLnWaitDone
} errataFixDcdcHs_TypeDef;
errataFixDcdcHs_TypeDef errataFixDcdcHsState = errataFixDcdcHsInit;
#endif

/*******************************************************************************
 **************************   LOCAL VARIABLES   ********************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/**
 * CMU configured oscillator selection and oscillator enable status. When a
 * user configures oscillators, this varaiable shall shadow the configuration.
 * It is used by the EMU module in order to be able to restore the oscillator
 * config after having been in certain energy modes (since HW may automatically
 * alter config when going into an energy mode). It is the responsibility of
 * the CMU module to keep it up-to-date (or a user if not using the CMU API
 * for oscillator control).
 */
static uint32_t cmuStatus;
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
static uint16_t cmuHfclkStatus;
#endif
#if defined( _EMU_DCDCCTRL_MASK )
static uint16_t dcdcMaxCurrent_mA;
static uint16_t dcdcOutput_mVout;
#endif

/** @endcond */


/*******************************************************************************
 **************************   LOCAL FUNCTIONS   ********************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Restore oscillators and core clock after having been in EM2 or EM3.
 ******************************************************************************/
static void emuRestore(void)
{
  uint32_t oscEnCmd;
  uint32_t cmuLocked;

  /* Although we could use the CMU API for most of the below handling, we */
  /* would like this function to be as efficient as possible. */

  /* CMU registers may be locked */
  cmuLocked = CMU->LOCK & CMU_LOCK_LOCKKEY_LOCKED;
  CMU_Unlock();

  /* AUXHFRCO are automatically disabled (except if using debugger). */
  /* HFRCO, USHFRCO and HFXO are automatically disabled. */
  /* LFRCO/LFXO may be disabled by SW in EM3. */
  /* Restore according to status prior to entering energy mode. */
  oscEnCmd = 0;
  oscEnCmd |= ((cmuStatus & CMU_STATUS_HFRCOENS)    ? CMU_OSCENCMD_HFRCOEN : 0);
  oscEnCmd |= ((cmuStatus & CMU_STATUS_AUXHFRCOENS) ? CMU_OSCENCMD_AUXHFRCOEN : 0);
  oscEnCmd |= ((cmuStatus & CMU_STATUS_LFRCOENS)    ? CMU_OSCENCMD_LFRCOEN : 0);
  oscEnCmd |= ((cmuStatus & CMU_STATUS_HFXOENS)     ? CMU_OSCENCMD_HFXOEN : 0);
  oscEnCmd |= ((cmuStatus & CMU_STATUS_LFXOENS)     ? CMU_OSCENCMD_LFXOEN : 0);
#if defined( _CMU_STATUS_USHFRCOENS_MASK )
  oscEnCmd |= ((cmuStatus & CMU_STATUS_USHFRCOENS)  ? CMU_OSCENCMD_USHFRCOEN : 0);
#endif
  CMU->OSCENCMD = oscEnCmd;


#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  /* Restore oscillator used for clocking core */
  switch (cmuHfclkStatus & _CMU_HFCLKSTATUS_SELECTED_MASK)
  {
    case CMU_HFCLKSTATUS_SELECTED_LFRCO:
      /* HFRCO could only be selected if the autostart HFXO feature is not
       * enabled, otherwise the HFXO would be started and selected automatically.
       * Note: this error hook helps catching erroneous oscillator configurations,
       * when the AUTOSTARTSELEM0EM1 is set in CMU_HFXOCTRL. */
      if (!(CMU->HFXOCTRL & CMU_HFXOCTRL_AUTOSTARTSELEM0EM1))
      {
        /* Wait for LFRCO to stabilize */
        while (!(CMU->STATUS & CMU_STATUS_LFRCORDY))
          ;
        CMU->HFCLKSEL = CMU_HFCLKSEL_HF_LFRCO;
      }
      else
      {
        EFM_ASSERT(0);
      }
      break;

    case CMU_HFCLKSTATUS_SELECTED_LFXO:
      /* Wait for LFXO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_LFXORDY))
        ;
      CMU->HFCLKSEL = CMU_HFCLKSEL_HF_LFXO;
      break;

    case CMU_HFCLKSTATUS_SELECTED_HFXO:
      /* Wait for HFXO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_HFXORDY))
        ;
      CMU->HFCLKSEL = CMU_HFCLKSEL_HF_HFXO;
      break;

    default: /* CMU_HFCLKSTATUS_SELECTED_HFRCO */
      /* If core clock was HFRCO core clock, it is automatically restored to */
      /* state prior to entering energy mode. No need for further action. */
      break;
  }
#else
  switch (cmuStatus & (CMU_STATUS_HFRCOSEL
                      | CMU_STATUS_HFXOSEL
                      | CMU_STATUS_LFRCOSEL
#if defined( CMU_STATUS_USHFRCODIV2SEL )
                      | CMU_STATUS_USHFRCODIV2SEL
#endif
                      | CMU_STATUS_LFXOSEL))
  {
    case CMU_STATUS_LFRCOSEL:
      /* Wait for LFRCO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_LFRCORDY))
        ;
      CMU->CMD = CMU_CMD_HFCLKSEL_LFRCO;
      break;

    case CMU_STATUS_LFXOSEL:
      /* Wait for LFXO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_LFXORDY))
        ;
      CMU->CMD = CMU_CMD_HFCLKSEL_LFXO;
      break;

    case CMU_STATUS_HFXOSEL:
      /* Wait for HFXO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_HFXORDY))
        ;
      CMU->CMD = CMU_CMD_HFCLKSEL_HFXO;
      break;

#if defined( CMU_STATUS_USHFRCODIV2SEL )
    case CMU_STATUS_USHFRCODIV2SEL:
      /* Wait for USHFRCO to stabilize */
      while (!(CMU->STATUS & CMU_STATUS_USHFRCORDY))
        ;
      CMU->CMD = _CMU_CMD_HFCLKSEL_USHFRCODIV2;
      break;
#endif

    default: /* CMU_STATUS_HFRCOSEL */
      /* If core clock was HFRCO core clock, it is automatically restored to */
      /* state prior to entering energy mode. No need for further action. */
      break;
  }

  /* If HFRCO was disabled before entering Energy Mode, turn it off again */
  /* as it is automatically enabled by wake up */
  if ( ! (cmuStatus & CMU_STATUS_HFRCOENS) )
  {
    CMU->OSCENCMD = CMU_OSCENCMD_HFRCODIS;
  }
#endif
  /* Restore CMU register locking */
  if (cmuLocked)
  {
    CMU_Lock();
  }
}


#if defined( ERRATA_FIX_EMU_E107_EN )
/* Get enable conditions for errata EMU_E107 fix. */
static __INLINE bool getErrataFixEmuE107En(void)
{
  /* SYSTEM_ChipRevisionGet could have been used here, but we would like a
   * faster implementation in this case.
   */
  uint16_t majorMinorRev;

  /* CHIP MAJOR bit [3:0] */
  majorMinorRev = ((ROMTABLE->PID0 & _ROMTABLE_PID0_REVMAJOR_MASK)
                   >> _ROMTABLE_PID0_REVMAJOR_SHIFT)
                  << 8;
  /* CHIP MINOR bit [7:4] */
  majorMinorRev |= ((ROMTABLE->PID2 & _ROMTABLE_PID2_REVMINORMSB_MASK)
                    >> _ROMTABLE_PID2_REVMINORMSB_SHIFT)
                   << 4;
  /* CHIP MINOR bit [3:0] */
  majorMinorRev |= (ROMTABLE->PID3 & _ROMTABLE_PID3_REVMINORLSB_MASK)
                   >> _ROMTABLE_PID3_REVMINORLSB_SHIFT;

#if defined( _EFM32_GECKO_FAMILY )
  return (majorMinorRev <= 0x0103);
#elif defined( _EFM32_TINY_FAMILY )
  return (majorMinorRev <= 0x0102);
#elif defined( _EFM32_GIANT_FAMILY )
  return (majorMinorRev <= 0x0103) || (majorMinorRev == 0x0204);
#elif defined( _EFM32_WONDER_FAMILY )
  return (majorMinorRev == 0x0100);
#else
  /* Zero Gecko and future families are not affected by errata EMU_E107 */
  return false;
#endif
}
#endif


#if defined( _EMU_DCDCCTRL_MASK )
/* LP prepare / LN restore P/NFET count */
static void maxCurrentUpdate(void);
#define DCDC_LP_PFET_CNT        7
#define DCDC_LP_NFET_CNT        15
void dcdcFetCntSet(bool lpModeSet)
{
  uint32_t tmp;
  static uint32_t emuDcdcMiscCtrlReg;

  if (lpModeSet)
  {
    emuDcdcMiscCtrlReg = EMU->DCDCMISCCTRL;
    tmp  = EMU->DCDCMISCCTRL
           & ~(_EMU_DCDCMISCCTRL_PFETCNT_MASK | _EMU_DCDCMISCCTRL_NFETCNT_MASK);
    tmp |= (DCDC_LP_PFET_CNT << _EMU_DCDCMISCCTRL_PFETCNT_SHIFT)
            | (DCDC_LP_NFET_CNT << _EMU_DCDCMISCCTRL_NFETCNT_SHIFT);
    EMU->DCDCMISCCTRL = tmp;
    maxCurrentUpdate();
  }
  else
  {
    EMU->DCDCMISCCTRL = emuDcdcMiscCtrlReg;
    maxCurrentUpdate();
  }
}

void dcdcHsFixLnBlock(void)
{
#define EMU_DCDCSTATUS  (* (volatile uint32_t *)(EMU_BASE + 0x7C))
  if (errataFixDcdcHsState == errataFixDcdcHsTrimSet)
  {
    /* Wait for LNRUNNING */
    if ((EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) == EMU_DCDCCTRL_DCDCMODE_LOWNOISE)
    {
      while (!(EMU_DCDCSTATUS & (0x1 << 16)));
    }
    errataFixDcdcHsState = errataFixDcdcHsLnWaitDone;
  }
}
#endif


/** @endcond */


/*******************************************************************************
 **************************   GLOBAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Enter energy mode 2 (EM2).
 *
 * @details
 *   When entering EM2, the high frequency clocks are disabled, ie HFXO, HFRCO
 *   and AUXHFRCO (for AUXHFRCO, see exception note below). When re-entering
 *   EM0, HFRCO is re-enabled and the core will be clocked by the configured
 *   HFRCO band. This ensures a quick wakeup from EM2.
 *
 *   However, prior to entering EM2, the core may have been using another
 *   oscillator than HFRCO. The @p restore parameter gives the user the option
 *   to restore all HF oscillators according to state prior to entering EM2,
 *   as well as the clock used to clock the core. This restore procedure is
 *   handled by SW. However, since handled by SW, it will not be restored
 *   before completing the interrupt function(s) waking up the core!
 *
 * @note
 *   If restoring core clock to use the HFXO oscillator, which has been
 *   disabled during EM2 mode, this function will stall until the oscillator
 *   has stabilized. Stalling time can be reduced by adding interrupt
 *   support detecting stable oscillator, and an asynchronous switch to the
 *   original oscillator. See CMU documentation. Such a feature is however
 *   outside the scope of the implementation in this function.
 * @par
 *   If HFXO is re-enabled by this function, and NOT used to clock the core,
 *   this function will not wait for HFXO to stabilize. This must be considered
 *   by the application if trying to use features relying on that oscillator
 *   upon return.
 * @par
 *   If a debugger is attached, the AUXHFRCO will not be disabled if enabled
 *   upon entering EM2. It will thus remain enabled when returning to EM0
 *   regardless of the @p restore parameter.
 * @par
 *   If HFXO autostart and select is enabled by using CMU_HFXOAutostartEnable(),
 *   the starting and selecting of the core clocks will be identical to the user
 *   independently of the value of the @p restore parameter when waking up on
 *   the wakeup sources corresponding to the autostart and select setting.
 *
 * @param[in] restore
 *   @li true - restore oscillators and clocks, see function details.
 *   @li false - do not restore oscillators and clocks, see function details.
 * @par
 *   The @p restore option should only be used if all clock control is done
 *   via the CMU API.
 ******************************************************************************/
void EMU_EnterEM2(bool restore)
{
#if defined( ERRATA_FIX_EMU_E107_EN )
  bool errataFixEmuE107En;
  uint32_t nonWicIntEn[2];
#endif

  /* Auto-update CMU status just in case before entering energy mode. */
  /* This variable is normally kept up-to-date by the CMU API. */
  cmuStatus = CMU->STATUS;
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  cmuHfclkStatus = (uint16_t)(CMU->HFCLKSTATUS);
#endif

  /* Enter Cortex deep sleep mode */
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  /* Fix for errata EMU_E107 - store non-WIC interrupt enable flags.
     Disable the enabled non-WIC interrupts. */
#if defined( ERRATA_FIX_EMU_E107_EN )
  errataFixEmuE107En = getErrataFixEmuE107En();
  if (errataFixEmuE107En)
  {
    nonWicIntEn[0] = NVIC->ISER[0] & NON_WIC_INT_MASK_0;
    NVIC->ICER[0] = nonWicIntEn[0];
#if (NON_WIC_INT_MASK_1 != (~(0x0U)))
    nonWicIntEn[1] = NVIC->ISER[1] & NON_WIC_INT_MASK_1;
    NVIC->ICER[1] = nonWicIntEn[1];
#endif
  }
#endif

#if defined( _EMU_DCDCCTRL_MASK )
  dcdcFetCntSet(true);
  dcdcHsFixLnBlock();
#endif

  __WFI();

#if defined( _EMU_DCDCCTRL_MASK )
  dcdcFetCntSet(false);
#endif

  /* Fix for errata EMU_E107 - restore state of non-WIC interrupt enable flags. */
#if defined( ERRATA_FIX_EMU_E107_EN )
  if (errataFixEmuE107En)
  {
    NVIC->ISER[0] = nonWicIntEn[0];
#if (NON_WIC_INT_MASK_1 != (~(0x0U)))
    NVIC->ISER[1] = nonWicIntEn[1];
#endif
  }
#endif

  /* Restore oscillators/clocks if specified */
  if (restore)
  {
    emuRestore();
  }
  /* If not restoring, and original clock was not HFRCO, we have to */
  /* update CMSIS core clock variable since core clock has changed */
  /* to using HFRCO. */
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  else if ((cmuHfclkStatus & _CMU_HFCLKSTATUS_SELECTED_MASK)
           != CMU_HFCLKSTATUS_SELECTED_HFRCO)
#else
  else if (!(cmuStatus & CMU_STATUS_HFRCOSEL))
#endif
  {
    SystemCoreClockUpdate();
  }
}


/***************************************************************************//**
 * @brief
 *   Enter energy mode 3 (EM3).
 *
 * @details
 *   When entering EM3, the high frequency clocks are disabled by HW, ie HFXO,
 *   HFRCO and AUXHFRCO (for AUXHFRCO, see exception note below). In addition,
 *   the low frequency clocks, ie LFXO and LFRCO are disabled by SW. When
 *   re-entering EM0, HFRCO is re-enabled and the core will be clocked by the
 *   configured HFRCO band. This ensures a quick wakeup from EM3.
 *
 *   However, prior to entering EM3, the core may have been using another
 *   oscillator than HFRCO. The @p restore parameter gives the user the option
 *   to restore all HF/LF oscillators according to state prior to entering EM3,
 *   as well as the clock used to clock the core. This restore procedure is
 *   handled by SW. However, since handled by SW, it will not be restored
 *   before completing the interrupt function(s) waking up the core!
 *
 * @note
 *   If restoring core clock to use an oscillator other than HFRCO, this
 *   function will stall until the oscillator has stabilized. Stalling time
 *   can be reduced by adding interrupt support detecting stable oscillator,
 *   and an asynchronous switch to the original oscillator. See CMU
 *   documentation. Such a feature is however outside the scope of the
 *   implementation in this function.
 * @par
 *   If HFXO/LFXO/LFRCO are re-enabled by this function, and NOT used to clock
 *   the core, this function will not wait for those oscillators to stabilize.
 *   This must be considered by the application if trying to use features
 *   relying on those oscillators upon return.
 * @par
 *   If a debugger is attached, the AUXHFRCO will not be disabled if enabled
 *   upon entering EM3. It will thus remain enabled when returning to EM0
 *   regardless of the @p restore parameter.
 *
 * @param[in] restore
 *   @li true - restore oscillators and clocks, see function details.
 *   @li false - do not restore oscillators and clocks, see function details.
 * @par
 *   The @p restore option should only be used if all clock control is done
 *   via the CMU API.
 ******************************************************************************/
void EMU_EnterEM3(bool restore)
{
  uint32_t cmuLocked;

#if defined( ERRATA_FIX_EMU_E107_EN )
  bool errataFixEmuE107En;
  uint32_t nonWicIntEn[2];
#endif

  /* Auto-update CMU status just in case before entering energy mode. */
  /* This variable is normally kept up-to-date by the CMU API. */
  cmuStatus = CMU->STATUS;
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  cmuHfclkStatus = (uint16_t)(CMU->HFCLKSTATUS);
#endif

  /* CMU registers may be locked */
  cmuLocked = CMU->LOCK & CMU_LOCK_LOCKKEY_LOCKED;
  CMU_Unlock();

  /* Disable LF oscillators */
  CMU->OSCENCMD = CMU_OSCENCMD_LFXODIS | CMU_OSCENCMD_LFRCODIS;

  /* Restore CMU register locking */
  if (cmuLocked)
  {
    CMU_Lock();
  }

  /* Enter Cortex deep sleep mode */
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  /* Fix for errata EMU_E107 - store non-WIC interrupt enable flags.
     Disable the enabled non-WIC interrupts. */
#if defined( ERRATA_FIX_EMU_E107_EN )
  errataFixEmuE107En = getErrataFixEmuE107En();
  if (errataFixEmuE107En)
  {
    nonWicIntEn[0] = NVIC->ISER[0] & NON_WIC_INT_MASK_0;
    NVIC->ICER[0] = nonWicIntEn[0];
#if (NON_WIC_INT_MASK_1 != (~(0x0U)))
    nonWicIntEn[1] = NVIC->ISER[1] & NON_WIC_INT_MASK_1;
    NVIC->ICER[1] = nonWicIntEn[1];
#endif

  }
#endif

#if defined( _EMU_DCDCCTRL_MASK )
  dcdcFetCntSet(true);
  dcdcHsFixLnBlock();
#endif

  __WFI();

#if defined( _EMU_DCDCCTRL_MASK )
  dcdcFetCntSet(false);
#endif

  /* Fix for errata EMU_E107 - restore state of non-WIC interrupt enable flags. */
#if defined( ERRATA_FIX_EMU_E107_EN )
  if (errataFixEmuE107En)
  {
    NVIC->ISER[0] = nonWicIntEn[0];
#if (NON_WIC_INT_MASK_1 != (~(0x0U)))
    NVIC->ISER[1] = nonWicIntEn[1];
#endif
  }
#endif

  /* Restore oscillators/clocks if specified */
  if (restore)
  {
    emuRestore();
  }
  /* If not restoring, and original clock was not HFRCO, we have to */
  /* update CMSIS core clock variable since core clock has changed */
  /* to using HFRCO. */
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  else if ((cmuHfclkStatus & _CMU_HFCLKSTATUS_SELECTED_MASK)
           != CMU_HFCLKSTATUS_SELECTED_HFRCO)
#else
  else if (!(cmuStatus & CMU_STATUS_HFRCOSEL))
#endif
  {
    SystemCoreClockUpdate();
  }
}


/***************************************************************************//**
 * @brief
 *   Enter energy mode 4 (EM4).
 *
 * @note
 *   Only a power on reset or external reset pin can wake the device from EM4.
 ******************************************************************************/
void EMU_EnterEM4(void)
{
  int i;

#if defined( _EMU_EM4CTRL_EM4ENTRY_SHIFT )
  uint32_t em4seq2 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
                     | (2 << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
  uint32_t em4seq3 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
                     | (3 << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
#else
  uint32_t em4seq2 = (EMU->CTRL & ~_EMU_CTRL_EM4CTRL_MASK)
                     | (2 << _EMU_CTRL_EM4CTRL_SHIFT);
  uint32_t em4seq3 = (EMU->CTRL & ~_EMU_CTRL_EM4CTRL_MASK)
                     | (3 << _EMU_CTRL_EM4CTRL_SHIFT);
#endif

  /* Make sure register write lock is disabled */
  EMU_Unlock();

#if defined( ERRATA_FIX_EMU_E108_EN )
  /* Fix for errata EMU_E108 - High Current Consumption on EM4 Entry. */
  __disable_irq();
  *(volatile uint32_t *)0x400C80E4 = 0;
#endif

#if defined( _EMU_DCDCCTRL_MASK )
  dcdcFetCntSet(true);
  dcdcHsFixLnBlock();
#endif

  for (i = 0; i < 4; i++)
  {
#if defined( _EMU_EM4CTRL_EM4ENTRY_SHIFT )
    EMU->EM4CTRL = em4seq2;
    EMU->EM4CTRL = em4seq3;
  }
  EMU->EM4CTRL = em4seq2;
#else
    EMU->CTRL = em4seq2;
    EMU->CTRL = em4seq3;
  }
  EMU->CTRL = em4seq2;
#endif
}


/***************************************************************************//**
 * @brief
 *   Power down memory block.
 *
 * @param[in] blocks
 *   Specifies a logical OR of bits indicating memory blocks to power down.
 *   Bit 0 selects block 1, bit 1 selects block 2, etc. Memory block 0 cannot
 *   be disabled. Please refer to the reference manual for available
 *   memory blocks for a device.
 *
 * @note
 *   Only a reset can make the specified memory block(s) available for use
 *   after having been powered down. Function will be void for devices not
 *   supporting this feature.
 ******************************************************************************/
void EMU_MemPwrDown(uint32_t blocks)
{
#if defined( _EMU_MEMCTRL_POWERDOWN_MASK )
  EFM_ASSERT(blocks <= (_EMU_MEMCTRL_POWERDOWN_MASK
                        >> _EMU_MEMCTRL_POWERDOWN_SHIFT));
  EMU->MEMCTRL = blocks;

#elif defined( _EMU_MEMCTRL_RAMPOWERDOWN_MASK )       \
      && defined( _EMU_MEMCTRL_RAMHPOWERDOWN_MASK )   \
      && defined( _EMU_MEMCTRL_SEQRAMPOWERDOWN_MASK )
  EFM_ASSERT((blocks & (_EMU_MEMCTRL_RAMPOWERDOWN_MASK
                        | _EMU_MEMCTRL_RAMHPOWERDOWN_MASK
                        | _EMU_MEMCTRL_SEQRAMPOWERDOWN_MASK))
             == blocks);
  EMU->MEMCTRL = blocks;

#elif defined( _EMU_MEMCTRL_RAMPOWERDOWN_MASK )
  EFM_ASSERT((blocks & _EMU_MEMCTRL_RAMPOWERDOWN_MASK) == blocks);
  EMU->MEMCTRL = blocks;

#elif defined( _EMU_RAM0CTRL_RAMPOWERDOWN_MASK )
  EFM_ASSERT((blocks & _EMU_RAM0CTRL_RAMPOWERDOWN_MASK) == blocks);
  EMU->RAM0CTRL = blocks;

#else
  (void)blocks;
#endif
}


/***************************************************************************//**
 * @brief
 *   Update EMU module with CMU oscillator selection/enable status.
 *
 * @details
 *   When entering EM2 and EM3, the HW may change the core clock oscillator
 *   used, as well as disabling some oscillators. The user may optionally select
 *   to restore the oscillators after waking up from EM2 and EM3 through the
 *   SW API.
 *
 *   However, in order to support this in a safe way, the EMU module must
 *   be kept up-to-date on the actual selected configuration. The CMU
 *   module must keep the EMU module up-to-date.
 *
 *   This function is mainly intended for internal use by the CMU module,
 *   but if the applications changes oscillator configurations without
 *   using the CMU API, this function can be used to keep the EMU module
 *   up-to-date.
 ******************************************************************************/
void EMU_UpdateOscConfig(void)
{
  /* Fetch current configuration */
  cmuStatus = CMU->STATUS;
#if defined( _CMU_HFCLKSTATUS_RESETVALUE )
  cmuHfclkStatus = (uint16_t)(CMU->HFCLKSTATUS);
#endif
}


/***************************************************************************//**
 * @brief
 *   Update EMU module with Energy Mode 2 and 3 configuration
 *
 * @param[in] em23Init
 *    Energy Mode 2 and 3 configuration structure
 ******************************************************************************/
void EMU_EM23Init(EMU_EM23Init_TypeDef *em23Init)
{
#if defined( _EMU_CTRL_EMVREG_MASK )
  EMU->CTRL = em23Init->em23VregFullEn ? (EMU->CTRL | EMU_CTRL_EMVREG)
                                         : (EMU->CTRL & ~EMU_CTRL_EMVREG);
#elif defined( _EMU_CTRL_EM23VREG_MASK )
  EMU->CTRL = em23Init->em23VregFullEn ? (EMU->CTRL | EMU_CTRL_EM23VREG)
                                         : (EMU->CTRL & ~EMU_CTRL_EM23VREG);
#else
  (void)em23Init;
#endif
}


#if defined( _EMU_EM4CONF_MASK ) || defined( _EMU_EM4CTRL_MASK )
/***************************************************************************//**
 * @brief
 *   Update EMU module with Energy Mode 4 configuration
 *
 * @param[in] em4Init
 *    Energy Mode 4 configuration structure
 ******************************************************************************/
void EMU_EM4Init(EMU_EM4Init_TypeDef *em4Init)
{
#if defined( _EMU_EM4CONF_MASK )
  /* Init for platforms with EMU->EM4CONF register */
  uint32_t em4conf = EMU->EM4CONF;

  /* Clear fields that will be reconfigured */
  em4conf &= ~(_EMU_EM4CONF_LOCKCONF_MASK
               | _EMU_EM4CONF_OSC_MASK
               | _EMU_EM4CONF_BURTCWU_MASK
               | _EMU_EM4CONF_VREGEN_MASK);

  /* Configure new settings */
  em4conf |= (em4Init->lockConfig << _EMU_EM4CONF_LOCKCONF_SHIFT)
             | (em4Init->osc)
             | (em4Init->buRtcWakeup << _EMU_EM4CONF_BURTCWU_SHIFT)
             | (em4Init->vreg << _EMU_EM4CONF_VREGEN_SHIFT);

  /* Apply configuration. Note that lock can be set after this stage. */
  EMU->EM4CONF = em4conf;

#elif defined( _EMU_EM4CTRL_MASK )
  /* Init for platforms with EMU->EM4CTRL register */

  uint32_t em4ctrl = EMU->EM4CTRL;

  em4ctrl &= ~(_EMU_EM4CTRL_RETAINLFXO_MASK
               | _EMU_EM4CTRL_RETAINLFRCO_MASK
               | _EMU_EM4CTRL_RETAINULFRCO_MASK
               | _EMU_EM4CTRL_EM4STATE_MASK
               | _EMU_EM4CTRL_EM4IORETMODE_MASK);

     em4ctrl |= (em4Init->retainLfxo     ? EMU_EM4CTRL_RETAINLFXO : 0)
                | (em4Init->retainLfrco  ? EMU_EM4CTRL_RETAINLFRCO : 0)
                | (em4Init->retainUlfrco ? EMU_EM4CTRL_RETAINULFRCO : 0)
                | (em4Init->em4State     ? EMU_EM4CTRL_EM4STATE_EM4H : 0)
                | (em4Init->pinRetentionMode);

  EMU->EM4CTRL = em4ctrl;
#endif
}
#endif


#if defined( BU_PRESENT )
/***************************************************************************//**
 * @brief
 *   Configure Backup Power Domain settings
 *
 * @param[in] bupdInit
 *   Backup power domain initialization structure
 ******************************************************************************/
void EMU_BUPDInit(EMU_BUPDInit_TypeDef *bupdInit)
{
  uint32_t reg;

  /* Set power connection configuration */
  reg = EMU->PWRCONF & ~(_EMU_PWRCONF_PWRRES_MASK
                         | _EMU_PWRCONF_VOUTSTRONG_MASK
                         | _EMU_PWRCONF_VOUTMED_MASK
                         | _EMU_PWRCONF_VOUTWEAK_MASK);

  reg |= bupdInit->resistor
         | (bupdInit->voutStrong << _EMU_PWRCONF_VOUTSTRONG_SHIFT)
         | (bupdInit->voutMed    << _EMU_PWRCONF_VOUTMED_SHIFT)
         | (bupdInit->voutWeak   << _EMU_PWRCONF_VOUTWEAK_SHIFT);

  EMU->PWRCONF = reg;

  /* Set backup domain inactive mode configuration */
  reg = EMU->BUINACT & ~(_EMU_BUINACT_PWRCON_MASK);
  reg |= (bupdInit->inactivePower);
  EMU->BUINACT = reg;

  /* Set backup domain active mode configuration */
  reg = EMU->BUACT & ~(_EMU_BUACT_PWRCON_MASK);
  reg |= (bupdInit->activePower);
  EMU->BUACT = reg;

  /* Set power control configuration */
  reg = EMU->BUCTRL & ~(_EMU_BUCTRL_PROBE_MASK
                        | _EMU_BUCTRL_BODCAL_MASK
                        | _EMU_BUCTRL_STATEN_MASK
                        | _EMU_BUCTRL_EN_MASK);

  /* Note use of ->enable to both enable BUPD, use BU_VIN pin input and
     release reset */
  reg |= bupdInit->probe
         | (bupdInit->bodCal          << _EMU_BUCTRL_BODCAL_SHIFT)
         | (bupdInit->statusPinEnable << _EMU_BUCTRL_STATEN_SHIFT)
         | (bupdInit->enable          << _EMU_BUCTRL_EN_SHIFT);

  /* Enable configuration */
  EMU->BUCTRL = reg;

  /* If enable is true, enable BU_VIN input power pin, if not disable it  */
  EMU_BUPinEnable(bupdInit->enable);

  /* If enable is true, release BU reset, if not keep reset asserted */
  BUS_RegBitWrite(&(RMU->CTRL), _RMU_CTRL_BURSTEN_SHIFT, !bupdInit->enable);
}


/***************************************************************************//**
 * @brief
 *   Configure Backup Power Domain BOD Threshold value
 * @note
 *   These values are precalibrated
 * @param[in] mode Active or Inactive mode
 * @param[in] value
 ******************************************************************************/
void EMU_BUThresholdSet(EMU_BODMode_TypeDef mode, uint32_t value)
{
  EFM_ASSERT(value<8);
  EFM_ASSERT(value<=(_EMU_BUACT_BUEXTHRES_MASK>>_EMU_BUACT_BUEXTHRES_SHIFT));

  switch(mode)
  {
    case emuBODMode_Active:
      EMU->BUACT = (EMU->BUACT & ~_EMU_BUACT_BUEXTHRES_MASK)
                   | (value<<_EMU_BUACT_BUEXTHRES_SHIFT);
      break;
    case emuBODMode_Inactive:
      EMU->BUINACT = (EMU->BUINACT & ~_EMU_BUINACT_BUENTHRES_MASK)
                     | (value<<_EMU_BUINACT_BUENTHRES_SHIFT);
      break;
  }
}


/***************************************************************************//**
 * @brief
 *  Configure Backup Power Domain BOD Threshold Range
 * @note
 *  These values are precalibrated
 * @param[in] mode Active or Inactive mode
 * @param[in] value
 ******************************************************************************/
void EMU_BUThresRangeSet(EMU_BODMode_TypeDef mode, uint32_t value)
{
  EFM_ASSERT(value < 4);
  EFM_ASSERT(value<=(_EMU_BUACT_BUEXRANGE_MASK>>_EMU_BUACT_BUEXRANGE_SHIFT));

  switch(mode)
  {
    case emuBODMode_Active:
      EMU->BUACT = (EMU->BUACT & ~_EMU_BUACT_BUEXRANGE_MASK)
                   | (value<<_EMU_BUACT_BUEXRANGE_SHIFT);
      break;
    case emuBODMode_Inactive:
      EMU->BUINACT = (EMU->BUINACT & ~_EMU_BUINACT_BUENRANGE_MASK)
                     | (value<<_EMU_BUINACT_BUENRANGE_SHIFT);
      break;
  }
}
#endif


#if defined( _EMU_DCDCCTRL_MASK )

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Load DCDC calibration constants from DI page. Const means calibration
 *   data that does not change depending on other configuration parameters.
 *
 * @return
 *   False if calibration registers are locked
 ******************************************************************************/
static bool ConstCalibrationLoad(void)
{
  uint32_t val;
  volatile uint32_t *reg;

  /* DI calib data in flash */
  volatile uint32_t* const diCal_EMU_DCDCLNFREQCTRL =  (volatile uint32_t *)(0x0FE08038);
  volatile uint32_t* const diCal_EMU_DCDCLNVCTRL =     (volatile uint32_t *)(0x0FE08040);
  volatile uint32_t* const diCal_EMU_DCDCLPCTRL =      (volatile uint32_t *)(0x0FE08048);
  volatile uint32_t* const diCal_EMU_DCDCLPVCTRL =     (volatile uint32_t *)(0x0FE08050);
  volatile uint32_t* const diCal_EMU_DCDCTRIM0 =       (volatile uint32_t *)(0x0FE08058);
  volatile uint32_t* const diCal_EMU_DCDCTRIM1 =       (volatile uint32_t *)(0x0FE08060);

  if (DEVINFO->DCDCLPVCTRL0 != UINT_MAX)
  {
    val = *(diCal_EMU_DCDCLNFREQCTRL + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCLNFREQCTRL;
    *reg = val;

    val = *(diCal_EMU_DCDCLNVCTRL + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCLNVCTRL;
    *reg = val;

    val = *(diCal_EMU_DCDCLPCTRL + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCLPCTRL;
    *reg = val;

    val = *(diCal_EMU_DCDCLPVCTRL + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCLPVCTRL;
    *reg = val;

    val = *(diCal_EMU_DCDCTRIM0 + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCTRIM0;
    *reg = val;

    val = *(diCal_EMU_DCDCTRIM1 + 1);
    reg = (volatile uint32_t *)*diCal_EMU_DCDCTRIM1;
    *reg = val;

    return true;
  }
  EFM_ASSERT(false);
  /* Return when assertions are disabled */
  return false;
}


/***************************************************************************//**
 * @brief
 *   Set recommended and validated current optimization settings
 *
 ******************************************************************************/
void ValidatedConfigSet(void)
{
#define EMU_DCDCSMCTRL  (* (volatile uint32_t *)(EMU_BASE + 0x44))

  uint32_t dcdcTiming;
  SYSTEM_PartFamily_TypeDef family;
  SYSTEM_ChipRevision_TypeDef rev;

  /* Enable duty cycling of the bias */
  EMU->DCDCLPCTRL |= EMU_DCDCLPCTRL_LPVREFDUTYEN;

  /* Set low-noise RCO for EFM32 and EFR32 */
#if defined( _EFR_DEVICE )
  /* 7MHz is recommended for all EFR32 parts with DCDC */
  EMU->DCDCLNFREQCTRL = (EMU->DCDCLNFREQCTRL & ~_EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                          | (EMU_DcdcLnRcoBand_7MHz << _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT);
#else
  /* 3MHz is recommended for all EFM32 parts with DCDC */
  EMU->DCDCLNFREQCTRL = (EMU->DCDCLNFREQCTRL & ~_EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                          | (EMU_DcdcLnRcoBand_3MHz << _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT);
#endif

  EMU->DCDCTIMING &= ~_EMU_DCDCTIMING_DUTYSCALE_MASK;

  family = SYSTEM_GetFamily();
  SYSTEM_ChipRevisionGet(&rev);
  if ((((family >= systemPartFamilyMighty1P)
         && (family <= systemPartFamilyFlex1V))
       || (family == systemPartFamilyEfm32Pearl1B)
       || (family == systemPartFamilyEfm32Jade1B))
      && ((rev.major == 1) && (rev.minor < 3))
      && (errataFixDcdcHsState == errataFixDcdcHsInit))
  {
    /* LPCMPWAITDIS = 1 */
    EMU_DCDCSMCTRL |= 1;

    dcdcTiming = EMU->DCDCTIMING;
    dcdcTiming &= ~(_EMU_DCDCTIMING_LPINITWAIT_MASK
                    |_EMU_DCDCTIMING_LNWAIT_MASK
                    |_EMU_DCDCTIMING_BYPWAIT_MASK);

    dcdcTiming |= ((180 << _EMU_DCDCTIMING_LPINITWAIT_SHIFT)
                   | (12 << _EMU_DCDCTIMING_LNWAIT_SHIFT)
                   | (180 << _EMU_DCDCTIMING_BYPWAIT_SHIFT));
    EMU->DCDCTIMING = dcdcTiming;

    errataFixDcdcHsState = errataFixDcdcHsTrimSet;
  }
}


/***************************************************************************//**
 * @brief
 *   Calculate and update EMU->DCDCMISCCTRL for maximum DCDC current based
 *   on the slice configuration and user set maximum.
 ******************************************************************************/
static void maxCurrentUpdate(void)
{
  uint32_t lncLimImSel;
  uint32_t lpcLimImSel;
  uint32_t pFetCnt;

  pFetCnt = (EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_PFETCNT_MASK)
             >> _EMU_DCDCMISCCTRL_PFETCNT_SHIFT;

  /* Equation from Reference Manual section 11.5.20, in the register
     field description for LNCLIMILIMSEL and LPCLIMILIMSEL. */
  lncLimImSel = (dcdcMaxCurrent_mA / (5 * (pFetCnt + 1))) - 1;
  /* 80mA as recommended in Application Note AN0948 */
  lpcLimImSel = (80 / (5 * (pFetCnt + 1))) - 1;

  lncLimImSel <<= _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_SHIFT;
  lpcLimImSel <<= _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_SHIFT;
  EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK
                                             | _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_MASK))
                       | (lncLimImSel | lpcLimImSel);
}


/***************************************************************************//**
 * @brief
 *   Set static variable that holds the user set maximum current. Update
 *   DCDC configuration.
 *
 * @param[in] mAmaxCurrent
 *   Maximum allowed current drawn by the DCDC from VREGVDD in mA.
 ******************************************************************************/
static void maxCurrentSet(uint32_t mAmaxCurrent)
{
  dcdcMaxCurrent_mA = mAmaxCurrent;
  maxCurrentUpdate();
}


/***************************************************************************//**
 * @brief
 *   Load EMU_DCDCLPCTRL_LPCMPHYSSEL depending on LP bias, LP feedback
 *   attenuation and DEVINFOREV.
 *
 * @param[in] attSet
 *   LP feedback attenuation.
 * @param[in] lpCmpBias
 *   lpCmpBias selection
 ******************************************************************************/
static bool LpCmpHystCalibrationLoad(bool lpAttenuation, uint32_t lpCmpBias)
{
  uint8_t devinfoRev;
  uint32_t lpcmpHystSel;

  /* Get calib data revision */
  devinfoRev = SYSTEM_GetDevinfoRev();

  /* Load LPATT indexed calibration data */
  if (devinfoRev < 4)
  {
    lpcmpHystSel = DEVINFO->DCDCLPCMPHYSSEL0;

    if (lpAttenuation)
    {
      lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT1_MASK)
                      >> _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT1_SHIFT;
    }
    else
    {
      lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT0_MASK)
                      >> _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT0_SHIFT;
    }
  }
  /* devinfoRev >= 4
     Load LPCMPBIAS indexed calibration data */
  else
  {
    lpcmpHystSel = DEVINFO->DCDCLPCMPHYSSEL1;
    switch (lpCmpBias)
    {
      case _EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS0:
        lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_MASK)
                        >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_SHIFT;
        break;

      case _EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS1:
        lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS1_MASK)
                        >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS1_SHIFT;
        break;

      case _EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS2:
        lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS2_MASK)
                        >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS2_SHIFT;
        break;

      case _EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS3:
        lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS3_MASK)
                        >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS3_SHIFT;
        break;

      default:
        EFM_ASSERT(false);
        /* Return when assertions are disabled */
        return false;
    }
  }

  /* Make sure the sel value is within the field range. */
  lpcmpHystSel <<= _EMU_DCDCLPCTRL_LPCMPHYSSEL_SHIFT;
  if (lpcmpHystSel & ~_EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK)
  {
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }
  EMU->DCDCLPCTRL = (EMU->DCDCLPCTRL & ~_EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK) | lpcmpHystSel;

  return true;
}


/** @endcond */

/***************************************************************************//**
 * @brief
 *   Set DCDC regulator operating mode
 *
 * @param[in] dcdcMode
 *   DCDC mode
 ******************************************************************************/
void EMU_DCDCModeSet(EMU_DcdcMode_TypeDef dcdcMode)
{
  while(EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY);
  BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, dcdcMode == emuDcdcMode_Bypass ? 0 : 1);
  EMU->DCDCCTRL = (EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) | dcdcMode;
}


/***************************************************************************//**
 * @brief
 *   Configure DCDC regulator
 *
 * @note
 *   Use the function EMU_DCDCPowerDown() to if the power circuit is configured
 *   for NODCDC as decribed in Section 11.3.4.3 in the Reference Manual.
 *
 * @param[in] dcdcInit
 *   DCDC initialization structure
 *
 * @return
 *   True if initialization parameters are valid
 ******************************************************************************/
bool EMU_DCDCInit(EMU_DCDCInit_TypeDef *dcdcInit)
{
  uint32_t lpCmpBiasSel;

  /* Set external power configuration. This enables writing to the other
     DCDC registers. */
  EMU->PWRCFG = dcdcInit->powerConfig;

  /* EMU->PWRCFG is write-once and POR reset only. Check that
     we could set the desired power configuration. */
  if ((EMU->PWRCFG & _EMU_PWRCFG_PWRCFG_MASK) != dcdcInit->powerConfig)
  {
    /* If this assert triggers unexpectedly, please power cycle the
       kit to reset the power configuration. */
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }

  /* Load DCDC calibration data from the DI page */
  ConstCalibrationLoad();

  /* Check current parameters */
  EFM_ASSERT(dcdcInit->maxCurrent_mA <= 200);
  EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= dcdcInit->maxCurrent_mA);

  /* DCDC low-noise supports max 200mA */
  if (dcdcInit->dcdcMode == emuDcdcMode_LowNoise)
  {
    EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= 200);
  }

  /* EM2, 3 and 4 current above 100uA is not supported */
  EFM_ASSERT(dcdcInit->em234LoadCurrent_uA <= 100);

  /* Decode LP comparator bias for EM0/1 and EM2/3 */
  lpCmpBiasSel  = EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS1;
  if (dcdcInit->em234LoadCurrent_uA <= 10)
  {
    lpCmpBiasSel  = EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS0;
  }

  /* Set DCDC low-power mode comparator bias selection */
  EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_LPCMPBIAS_MASK
                                             | _EMU_DCDCMISCCTRL_LNFORCECCM_MASK))
                       | ((uint32_t)lpCmpBiasSel
                          | (uint32_t)dcdcInit->lnTransientMode);

  /* Set recommended and validated current optimization settings */
  ValidatedConfigSet();

  /* Set the maximum current that the DCDC can draw from the power source */
  maxCurrentSet(dcdcInit->maxCurrent_mA);

  /* Optimize LN slice based on given load current estimate */
  EMU_DCDCOptimizeSlice(dcdcInit->em01LoadCurrent_mA);

  /* Set DCDC output voltage */
  dcdcOutput_mVout = dcdcInit->mVout;
  if (!EMU_DCDCOutputVoltageSet(dcdcOutput_mVout, true, true))
  {
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }

  /* Set EM0 DCDC operating mode. Output voltage set in EMU_DCDCOutputVoltageSet()
     above takes effect if mode is changed from bypass here. */
  EMU_DCDCModeSet(dcdcInit->dcdcMode);

  /* Select analog peripheral power supply */
  BUS_RegBitWrite(&EMU->PWRCTRL, _EMU_PWRCTRL_ANASW_SHIFT, dcdcInit->anaPeripheralPower ? 1 : 0);

  return true;
}


/***************************************************************************//**
 * @brief
 *   Set DCDC output voltage
 *
 * @param[in] mV
 *   Target DCDC output voltage in mV
 *
 * @return
 *   True if the mV parameter is valid
 ******************************************************************************/
bool EMU_DCDCOutputVoltageSet(uint32_t mV,
                              bool setLpVoltage,
                              bool setLnVoltage)
{
#if defined( _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_MASK )

  bool validOutVoltage;
  uint8_t lnMode;
  bool attSet;
  uint32_t attMask;
  uint32_t vrefLow = 0;
  uint32_t vrefHigh = 0;
  uint32_t vrefVal = 0;
  uint32_t mVlow = 0;
  uint32_t mVhigh = 0;
  uint32_t vrefShift;
  uint32_t lpcmpBias;
  volatile uint32_t* ctrlReg;

  /* Check that the set voltage is within valid range.
     Voltages are obtained from the datasheet. */
  validOutVoltage = false;
  if ((EMU->PWRCFG & _EMU_PWRCFG_PWRCFG_MASK) == EMU_PWRCFG_PWRCFG_DCDCTODVDD)
  {
    validOutVoltage = ((mV >= PWRCFG_DCDCTODVDD_VMIN)
                       && (mV <= PWRCFG_DCDCTODVDD_VMAX));
  }

  if (!validOutVoltage)
  {
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }

  /* Populate both LP and LN registers, set control reg pointer and VREF shift. */
  for (lnMode = 0; lnMode <= 1; lnMode++)
  {
    if (((lnMode == 0) && !setLpVoltage)
        || ((lnMode == 1) && !setLnVoltage))
    {
      continue;
    }

    ctrlReg   = (lnMode ? &EMU->DCDCLNVCTRL : &EMU->DCDCLPVCTRL);
    vrefShift = (lnMode ? _EMU_DCDCLNVCTRL_LNVREF_SHIFT
                        : _EMU_DCDCLPVCTRL_LPVREF_SHIFT);

    /* Set attenuation to use */
    attSet = (mV > 1800);
    if (attSet)
    {
      mVlow = 1800;
      mVhigh = 3000;
      attMask = (lnMode ? EMU_DCDCLNVCTRL_LNATT : EMU_DCDCLPVCTRL_LPATT);
    }
    else
    {
      mVlow = 1200;
      mVhigh = 1800;
      attMask = 0;
    }

    /* Get 2-point calib data from DEVINFO, calculate trimming and set voltege */
    if (lnMode)
    {
      /* Set low-noise DCDC output voltage tuning */
      if (attSet)
      {
        vrefLow  = DEVINFO->DCDCLNVCTRL0;
        vrefHigh = (vrefLow & _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_MASK)
                   >> _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_SHIFT;
        vrefLow  = (vrefLow & _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_MASK)
                   >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_SHIFT;
      }
      else
      {
        vrefLow  = DEVINFO->DCDCLNVCTRL0;
        vrefHigh = (vrefLow & _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_MASK)
                   >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_SHIFT;
        vrefLow  = (vrefLow & _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_MASK)
                   >> _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_SHIFT;
      }
    }
    else
    {
      /* Set low-power DCDC output voltage tuning */

      /* Get LPCMPBIAS and make sure masks are not overlayed */
      lpcmpBias = EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LPCMPBIAS_MASK;
      EFM_ASSERT(!(_EMU_DCDCMISCCTRL_LPCMPBIAS_MASK & attMask));
      switch (attMask | lpcmpBias)
      {
        case EMU_DCDCLPVCTRL_LPATT | EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS0:
          vrefLow  = DEVINFO->DCDCLPVCTRL2;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS0_MASK)
                     >> _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS0_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS0_MASK)
                     >> _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS0_SHIFT;
          break;

        case EMU_DCDCLPVCTRL_LPATT | EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS1:
          vrefLow  = DEVINFO->DCDCLPVCTRL2;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS1_MASK)
                     >> _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS1_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS1_MASK)
                     >> _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS1_SHIFT;
          break;

        case EMU_DCDCLPVCTRL_LPATT | EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS2:
          vrefLow  = DEVINFO->DCDCLPVCTRL3;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS2_MASK)
                     >> _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS2_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS2_MASK)
                     >> _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS2_SHIFT;
          break;

        case EMU_DCDCLPVCTRL_LPATT | EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS3:
          vrefLow  = DEVINFO->DCDCLPVCTRL3;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS3_MASK)
                     >> _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS3_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS3_MASK)
                     >> _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS3_SHIFT;
          break;

        case EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS0:
          vrefLow  = DEVINFO->DCDCLPVCTRL0;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS0_MASK)
                     >> _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS0_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS0_MASK)
                     >> _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS0_SHIFT;
          break;

        case EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS1:
          vrefLow  = DEVINFO->DCDCLPVCTRL0;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS1_MASK)
                     >> _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS1_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS1_MASK)
                     >> _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS1_SHIFT;
          break;

        case EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS2:
          vrefLow  = DEVINFO->DCDCLPVCTRL1;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS2_MASK)
                     >> _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS2_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS2_MASK)
                     >> _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS2_SHIFT;
          break;

        case EMU_DCDCMISCCTRL_LPCMPBIAS_BIAS3:
          vrefLow  = DEVINFO->DCDCLPVCTRL1;
          vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS3_MASK)
                     >> _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS3_SHIFT;
          vrefLow  = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS3_MASK)
                     >> _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS3_SHIFT;
          break;

        default:
          EFM_ASSERT(false);
          break;
      }

      /* Load LP comparator hysteresis calibration */
      if(!(LpCmpHystCalibrationLoad(attSet, lpcmpBias >> _EMU_DCDCMISCCTRL_LPCMPBIAS_SHIFT)))
      {
        EFM_ASSERT(false);
        /* Return when assertions are disabled */
        return false;
      }
    } /* Low-nise / low-power mode */


    /* Check for valid 2-point trim values */
    if ((vrefLow == 0xFF) && (vrefHigh == 0xFF))
    {
      EFM_ASSERT(false);
      /* Return when assertions are disabled */
      return false;
    }

    /* Calculate and set voltage trim */
    vrefVal = ((mV - mVlow) * (vrefHigh - vrefLow))  / (mVhigh - mVlow);
    vrefVal += vrefLow;

    /* Range check */
    if ((vrefVal > vrefHigh) || (vrefVal < vrefLow))
    {
      EFM_ASSERT(false);
      /* Return when assertions are disabled */
      return false;
    }

    /* Update DCDCLNVCTRL/DCDCLPVCTRL */
    *ctrlReg = (vrefVal << vrefShift) | attMask;
  }
#endif
  return true;
}


/***************************************************************************//**
 * @brief
 *   Optimize DCDC slice count based on the estimated average load current
 *   in EM0
 *
 * @param[in] mAEm0LoadCurrent
 *   Estimated average EM0 load current in mA.
 ******************************************************************************/
void EMU_DCDCOptimizeSlice(uint32_t mAEm0LoadCurrent)
{
  uint32_t sliceCount = 0;
  uint32_t rcoBand = (EMU->DCDCLNFREQCTRL & _EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                      >> _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT;

  /* Set recommended slice count */
  if ((EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK) && (rcoBand >= EMU_DcdcLnRcoBand_5MHz))
  {
    if (mAEm0LoadCurrent < 20)
    {
      sliceCount = 4;
    }
    else if ((mAEm0LoadCurrent >= 20) && (mAEm0LoadCurrent < 40))
    {
      sliceCount = 8;
    }
    else
    {
      sliceCount = 16;
    }
  }
  else if ((!(EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK)) && (rcoBand <= EMU_DcdcLnRcoBand_4MHz))
  {
    if (mAEm0LoadCurrent < 10)
    {
      sliceCount = 4;
    }
    else if ((mAEm0LoadCurrent >= 10) && (mAEm0LoadCurrent < 20))
    {
      sliceCount = 8;
    }
    else
    {
      sliceCount = 16;
    }
  }
  else if ((EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK) && (rcoBand <= EMU_DcdcLnRcoBand_4MHz))
  {
    if (mAEm0LoadCurrent < 40)
    {
      sliceCount = 8;
    }
    else
    {
      sliceCount = 16;
    }
  }
  else
  {
    /* This configuration is not recommended. EMU_DCDCInit() applies a recommended
       configuration. */
    EFM_ASSERT(false);
  }

  /* The selected silices are PSLICESEL + 1 */
  sliceCount--;

  /* Apply slice count to both N and P slice */
  sliceCount = (sliceCount << _EMU_DCDCMISCCTRL_PFETCNT_SHIFT
                | sliceCount << _EMU_DCDCMISCCTRL_NFETCNT_SHIFT);
  EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_PFETCNT_MASK
                                             | _EMU_DCDCMISCCTRL_NFETCNT_MASK))
                      | sliceCount;

  /* Update current limit configuration as it depends on the slice configuration. */
  maxCurrentUpdate();
}

/***************************************************************************//**
 * @brief
 *   Set DCDC Low-noise RCO band.
 *
 * @param[in] band
 *   RCO band to set.
 ******************************************************************************/
void EMU_DCDCLnRcoBandSet(EMU_DcdcLnRcoBand_TypeDef band)
{
  EMU->DCDCLNFREQCTRL = (EMU->DCDCLNFREQCTRL & ~_EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                         | (band << _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT);
}

/***************************************************************************//**
 * @brief
 *   Power off the DCDC regulator.
 *
 * @details
 *   This function powers off the DCDC controller. This function should only be
 *   used if the external power circuit is wired for no DCDC. If the external power
 *   circuit is wired for DCDC usage, then use EMU_DCDCInit() and set the
 *   DCDC in bypass mode to disable DCDC.
 *
 * @return
 *   Return false if the DCDC could not be disabled.
 ******************************************************************************/
bool EMU_DCDCPowerOff(void)
{
  /* Set power configuration to hard bypass */
  EMU->PWRCFG = 0xF;
  if ((EMU->PWRCFG & _EMU_PWRCFG_PWRCFG_MASK) != 0xF)
  {
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }

  /* Set DCDC to OFF and disable LP in EM2/3/4 */
  EMU->DCDCCTRL = EMU_DCDCCTRL_DCDCMODE_OFF;
  return true;
}
#endif


#if defined( EMU_STATUS_VMONRDY )
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
__STATIC_INLINE uint32_t vmonMilliVoltToCoarseThreshold(int mV)
{
  return (mV - 1200) / 200;
}

__STATIC_INLINE uint32_t vmonMilliVoltToFineThreshold(int mV, uint32_t coarseThreshold)
{
  return (mV - 1200 - (coarseThreshold * 200)) / 20;
}
/** @endcond */

/***************************************************************************//**
 * @brief
 *   Initialize VMON channel.
 *
 * @details
 *   Initialize a VMON channel without hysteresis. If the channel supports
 *   separate rise and fall triggers, both thresholds will be set to the same
 *   value.
 *
 * @param[in] vmonInit
 *   VMON initialization struct
 ******************************************************************************/
void EMU_VmonInit(EMU_VmonInit_TypeDef *vmonInit)
{
  uint32_t thresholdCoarse, thresholdFine;
  EFM_ASSERT((vmonInit->threshold >= 1200) && (vmonInit->threshold <= 3980));

  thresholdCoarse = vmonMilliVoltToCoarseThreshold(vmonInit->threshold);
  thresholdFine = vmonMilliVoltToFineThreshold(vmonInit->threshold, thresholdCoarse);

  switch(vmonInit->channel)
  {
  case emuVmonChannel_AVDD:
    EMU->VMONAVDDCTRL = (thresholdCoarse << _EMU_VMONAVDDCTRL_RISETHRESCOARSE_SHIFT)
                      | (thresholdFine << _EMU_VMONAVDDCTRL_RISETHRESFINE_SHIFT)
                      | (thresholdCoarse << _EMU_VMONAVDDCTRL_FALLTHRESCOARSE_SHIFT)
                      | (thresholdFine << _EMU_VMONAVDDCTRL_FALLTHRESFINE_SHIFT)
                      | (vmonInit->riseWakeup ? EMU_VMONAVDDCTRL_RISEWU : 0)
                      | (vmonInit->fallWakeup ? EMU_VMONAVDDCTRL_FALLWU : 0)
                      | (vmonInit->enable     ? EMU_VMONAVDDCTRL_EN     : 0);
    break;
  case emuVmonChannel_ALTAVDD:
    EMU->VMONALTAVDDCTRL = (thresholdCoarse << _EMU_VMONALTAVDDCTRL_THRESCOARSE_SHIFT)
                         | (thresholdFine << _EMU_VMONALTAVDDCTRL_THRESFINE_SHIFT)
                         | (vmonInit->riseWakeup ? EMU_VMONALTAVDDCTRL_RISEWU : 0)
                         | (vmonInit->fallWakeup ? EMU_VMONALTAVDDCTRL_FALLWU : 0)
                         | (vmonInit->enable     ? EMU_VMONALTAVDDCTRL_EN     : 0);
    break;
  case emuVmonChannel_DVDD:
    EMU->VMONDVDDCTRL = (thresholdCoarse << _EMU_VMONDVDDCTRL_THRESCOARSE_SHIFT)
                      | (thresholdFine << _EMU_VMONDVDDCTRL_THRESFINE_SHIFT)
                      | (vmonInit->riseWakeup ? EMU_VMONDVDDCTRL_RISEWU : 0)
                      | (vmonInit->fallWakeup ? EMU_VMONDVDDCTRL_FALLWU : 0)
                      | (vmonInit->enable     ? EMU_VMONDVDDCTRL_EN     : 0);
    break;
  case emuVmonChannel_IOVDD0:
    EMU->VMONIO0CTRL = (thresholdCoarse << _EMU_VMONIO0CTRL_THRESCOARSE_SHIFT)
                     | (thresholdFine << _EMU_VMONIO0CTRL_THRESFINE_SHIFT)
                     | (vmonInit->retDisable ? EMU_VMONIO0CTRL_RETDIS : 0)
                     | (vmonInit->riseWakeup ? EMU_VMONIO0CTRL_RISEWU : 0)
                     | (vmonInit->fallWakeup ? EMU_VMONIO0CTRL_FALLWU : 0)
                     | (vmonInit->enable     ? EMU_VMONIO0CTRL_EN     : 0);
    break;
  default:
    EFM_ASSERT(false);
    return;
  }
}

/***************************************************************************//**
 * @brief
 *   Initialize VMON channel with hysteresis (separate rise and fall triggers).
 *
 * @details
 *   Initialize a VMON channel which supports hysteresis. The AVDD channel is
 *   the only channel to support separate rise and fall triggers.
 *
 * @param[in] vmonInit
 *   VMON Hysteresis initialization struct
 ******************************************************************************/
void EMU_VmonHystInit(EMU_VmonHystInit_TypeDef *vmonInit)
{
  uint32_t riseThresholdCoarse, riseThresholdFine, fallThresholdCoarse, fallThresholdFine;
  /* VMON supports voltages between 1200 mV and 3980 mV (inclusive) in 20 mV increments */
  EFM_ASSERT((vmonInit->riseThreshold >= 1200) && (vmonInit->riseThreshold < 4000));
  EFM_ASSERT((vmonInit->fallThreshold >= 1200) && (vmonInit->fallThreshold < 4000));
  /* Fall threshold has to be lower than rise threshold */
  EFM_ASSERT(vmonInit->fallThreshold <= vmonInit->riseThreshold);

  riseThresholdCoarse = vmonMilliVoltToCoarseThreshold(vmonInit->riseThreshold);
  riseThresholdFine = vmonMilliVoltToFineThreshold(vmonInit->riseThreshold, riseThresholdCoarse);
  fallThresholdCoarse = vmonMilliVoltToCoarseThreshold(vmonInit->fallThreshold);
  fallThresholdFine = vmonMilliVoltToFineThreshold(vmonInit->fallThreshold, fallThresholdCoarse);

  switch(vmonInit->channel)
  {
  case emuVmonChannel_AVDD:
    EMU->VMONAVDDCTRL = (riseThresholdCoarse << _EMU_VMONAVDDCTRL_RISETHRESCOARSE_SHIFT)
                      | (riseThresholdFine << _EMU_VMONAVDDCTRL_RISETHRESFINE_SHIFT)
                      | (fallThresholdCoarse << _EMU_VMONAVDDCTRL_FALLTHRESCOARSE_SHIFT)
                      | (fallThresholdFine << _EMU_VMONAVDDCTRL_FALLTHRESFINE_SHIFT)
                      | (vmonInit->riseWakeup ? EMU_VMONAVDDCTRL_RISEWU : 0)
                      | (vmonInit->fallWakeup ? EMU_VMONAVDDCTRL_FALLWU : 0)
                      | (vmonInit->enable     ? EMU_VMONAVDDCTRL_EN     : 0);
    break;
  default:
    EFM_ASSERT(false);
    return;
  }
}

/***************************************************************************//**
 * @brief
 *   Enable or disable a VMON channel
 *
 * @param[in] channel
 *   VMON channel to enable/disable
 *
 * @param[in] enable
 *   Whether to enable or disable
 ******************************************************************************/
void EMU_VmonEnable(EMU_VmonChannel_TypeDef channel, bool enable)
{
  uint32_t volatile * reg;
  uint32_t bit;

  switch(channel)
  {
  case emuVmonChannel_AVDD:
    reg = &(EMU->VMONAVDDCTRL);
    bit = _EMU_VMONAVDDCTRL_EN_SHIFT;
    break;
  case emuVmonChannel_ALTAVDD:
    reg = &(EMU->VMONALTAVDDCTRL);
    bit = _EMU_VMONALTAVDDCTRL_EN_SHIFT;
    break;
  case emuVmonChannel_DVDD:
    reg = &(EMU->VMONDVDDCTRL);
    bit = _EMU_VMONDVDDCTRL_EN_SHIFT;
    break;
  case emuVmonChannel_IOVDD0:
    reg = &(EMU->VMONIO0CTRL);
    bit = _EMU_VMONIO0CTRL_EN_SHIFT;
    break;
  default:
    EFM_ASSERT(false);
    return;
  }

  BUS_RegBitWrite(reg, bit, enable);
}

/***************************************************************************//**
 * @brief
 *   Get the status of a voltage monitor channel.
 *
 * @param[in] channel
 *   VMON channel to get status for
 *
 * @return
 *   Status of the selected VMON channel. True if channel is triggered.
 ******************************************************************************/
bool EMU_VmonChannelStatusGet(EMU_VmonChannel_TypeDef channel)
{
  uint32_t bit;
  switch(channel)
  {
  case emuVmonChannel_AVDD:
    bit = _EMU_STATUS_VMONAVDD_SHIFT;
    break;
  case emuVmonChannel_ALTAVDD:
    bit = _EMU_STATUS_VMONALTAVDD_SHIFT;
    break;
  case emuVmonChannel_DVDD:
    bit = _EMU_STATUS_VMONDVDD_SHIFT;
    break;
  case emuVmonChannel_IOVDD0:
    bit = _EMU_STATUS_VMONIO0_SHIFT;
    break;
  default:
    EFM_ASSERT(false);
    bit = 0;
  }

  return BUS_RegBitRead(&EMU->STATUS, bit);
}
#endif /* EMU_STATUS_VMONRDY */

/** @} (end addtogroup EMU) */
/** @} (end addtogroup EM_Library) */
#endif /* __EM_EMU_H */
