/***************************************************************************//**
 * @file em_emu.c
 * @brief Energy Management Unit (EMU) Peripheral API
 * @version 5.2.2
 *******************************************************************************
 * # License
 * <b>Copyright 2016 Silicon Laboratories, Inc. 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_common.h"
#include "em_assert.h"

/***************************************************************************//**
 * @addtogroup emlib
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup EMU
 * @brief Energy Management Unit (EMU) Peripheral API
 * @details
 *  This module contains functions to control the EMU peripheral of Silicon
 *  Labs 32-bit MCUs and SoCs. The EMU handles the different low energy modes
 *  in Silicon Labs microcontrollers.
 * @{
 ******************************************************************************/

/* 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 */
#if defined(_SILICON_LABS_32B_SERIES_0)
/* Fix for errata EMU_E107 - non-WIC interrupt masks.
 * Zero Gecko and future families are not affected by errata EMU_E107 */
#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))

#endif
#endif

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

/* Fix for errata EMU_E208 - Occasional Full Reset After Exiting EM4H */
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
#define ERRATA_FIX_EMU_E208_EN
#endif

/* Enable FETCNT tuning errata fix */
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
#define ERRATA_FIX_DCDC_FETCNT_SET_EN
#endif

/* Enable LN handshake errata fix */
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
#define ERRATA_FIX_DCDC_LNHS_BLOCK_EN
typedef enum {
  errataFixDcdcHsInit,
  errataFixDcdcHsTrimSet,
  errataFixDcdcHsBypassLn,
  errataFixDcdcHsLnWaitDone
} errataFixDcdcHs_TypeDef;
static errataFixDcdcHs_TypeDef errataFixDcdcHsState = errataFixDcdcHsInit;
#endif

/* Used to figure out if a memory address is inside or outside of a RAM block.
 * A memory address is inside a RAM block if the address is greater than the
 * RAM block address. */
#define ADDRESS_NOT_IN_BLOCK(addr, block)  ((addr) <= (block))

/* RAM Block layout for various device families. Note that some devices
 * have special layout in RAM0 and some devices have a special RAM block
 * at the end of their block layout. */
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_84)
#define RAM1_BLOCKS            2
#define RAM1_BLOCK_SIZE  0x10000  // 64 kB blocks
#define RAM2_BLOCKS            1
#define RAM2_BLOCK_SIZE    0x800  // 2 kB block
#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_89)
#define RAM0_BLOCKS            2
#define RAM0_BLOCK_SIZE   0x4000
#define RAM1_BLOCKS            2
#define RAM1_BLOCK_SIZE   0x4000  // 16 kB blocks
#define RAM2_BLOCKS            1
#define RAM2_BLOCK_SIZE    0x800  // 2 kB block
#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_95)
#define RAM0_BLOCKS            1
#define RAM0_BLOCK_SIZE   0x4000  // 16 kB block
#define RAM1_BLOCKS            1
#define RAM1_BLOCK_SIZE   0x4000  // 16 kB block
#define RAM2_BLOCKS            1
#define RAM2_BLOCK_SIZE    0x800  // 2 kB block
#elif defined(_SILICON_LABS_32B_SERIES_0) && defined(_EFM32_GIANT_FAMILY)
#define RAM0_BLOCKS            4
#define RAM0_BLOCK_SIZE   0x8000  // 32 kB blocks
#elif defined(_SILICON_LABS_32B_SERIES_0) && defined(_EFM32_GECKO_FAMILY)
#define RAM0_BLOCKS            4
#define RAM0_BLOCK_SIZE   0x1000  //  4 kB blocks
#elif defined(_SILICON_LABS_32B_SERIES_1) && defined(_EFM32_GIANT_FAMILY)
#define RAM0_BLOCKS            8
#define RAM0_BLOCK_SIZE   0x4000  // 16 kB blocks
#define RAM1_BLOCKS            8
#define RAM1_BLOCK_SIZE   0x4000  // 16 kB blocks
#define RAM2_BLOCKS            4
#define RAM2_BLOCK_SIZE  0x10000  // 64 kB blocks
#endif

#if defined(_SILICON_LABS_32B_SERIES_0)
/* RAM_MEM_END on Gecko devices have a value larger than the SRAM_SIZE */
#define RAM0_END    (SRAM_BASE + SRAM_SIZE - 1)
#else
#define RAM0_END    RAM_MEM_END
#endif

#if defined(CMU_STATUS_HFXOSHUNTOPTRDY)
#define HFXO_STATUS_READY_FLAGS  (CMU_STATUS_HFXOPEAKDETRDY | CMU_STATUS_HFXOSHUNTOPTRDY)
#elif defined(CMU_STATUS_HFXOPEAKDETRDY)
#define HFXO_STATUS_READY_FLAGS  (CMU_STATUS_HFXOPEAKDETRDY)
#endif

/** @endcond */

#if defined(_EMU_DCDCCTRL_MASK)
/* DCDCTODVDD output range min/max */
#if !defined(PWRCFG_DCDCTODVDD_VMIN)
#define PWRCFG_DCDCTODVDD_VMIN          1800
#endif
#if !defined(PWRCFG_DCDCTODVDD_VMAX)
#define PWRCFG_DCDCTODVDD_VMAX          3000
#endif
#endif

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/* Static user configuration */
#if defined(_EMU_DCDCCTRL_MASK)
static uint16_t dcdcMaxCurrent_mA;
static uint16_t dcdcEm01LoadCurrent_mA;
static EMU_DcdcLnReverseCurrentControl_TypeDef dcdcReverseCurrentControl;
#endif
#if defined(_EMU_CMD_EM01VSCALE0_MASK)
static EMU_EM01Init_TypeDef vScaleEM01Config = { false };
#endif
/** @endcond */

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

#if defined(_EMU_CMD_EM01VSCALE0_MASK)
/* Convert from level to EM0 and 1 command bit */
__STATIC_INLINE uint32_t vScaleEM01Cmd(EMU_VScaleEM01_TypeDef level)
{
  return EMU_CMD_EM01VSCALE0 << (_EMU_STATUS_VSCALE_VSCALE0 - (uint32_t)level);
}
#endif

/***************************************************************************//**
 * @brief
 *   Save/restore/update oscillator, core clock and voltage scaling configuration on
 *   EM2 or EM3 entry/exit.
 *
 * @details
 *   Hardware may automatically change oscillator and voltage scaling configuration
 *   when going into or out of an energy mode. Static data in this function keeps track of
 *   such configuration bits and is used to restore state if needed.
 *
 ******************************************************************************/
typedef enum {
  emState_Save,         /* Save EMU and CMU state */
  emState_Restore,      /* Restore and unlock     */
} emState_TypeDef;

static void emState(emState_TypeDef action)
{
  uint32_t oscEnCmd;
  uint32_t cmuLocked;
  static uint32_t cmuStatus;
  static CMU_Select_TypeDef hfClock;
#if defined(_EMU_CMD_EM01VSCALE0_MASK)
  static uint8_t vScaleStatus;
#endif

  /* Save or update state */
  if (action == emState_Save) {
    /* Save configuration. */
    cmuStatus = CMU->STATUS;
    hfClock = CMU_ClockSelectGet(cmuClock_HF);
#if defined(_EMU_CMD_EM01VSCALE0_MASK)
    /* Save vscale */
    EMU_VScaleWait();
    vScaleStatus   = (uint8_t)((EMU->STATUS & _EMU_STATUS_VSCALE_MASK)
                               >> _EMU_STATUS_VSCALE_SHIFT);
#endif
  } else if (action == emState_Restore) { /* Restore state */
    /* Apply saved configuration. */
#if defined(_EMU_CMD_EM01VSCALE0_MASK)
    /* Restore EM0 and 1 voltage scaling level. EMU_VScaleWait() is called later,
       just before HF clock select is set. */
    EMU->CMD = vScaleEM01Cmd((EMU_VScaleEM01_TypeDef)vScaleStatus);
#endif

    /* 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(_EMU_STATUS_VSCALE_MASK)
    /* Wait for upscale to complete and then restore selected clock */
    EMU_VScaleWait();
#endif

    if (hfClock != cmuSelect_HFRCO) {
      CMU_ClockSelectSet(cmuClock_HF, hfClock);
    }

    /* 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;
    }

    /* 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

/* LP prepare / LN restore P/NFET count */
#define DCDC_LP_PFET_CNT        7
#define DCDC_LP_NFET_CNT        7
#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN)
static void currentLimitersUpdate(void);
static 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;
    currentLimitersUpdate();
  } else {
    EMU->DCDCMISCCTRL = emuDcdcMiscCtrlReg;
    currentLimitersUpdate();
  }
}
#endif

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

#if defined(_EMU_CTRL_EM23VSCALE_MASK)
/* Configure EMU and CMU for EM2 and 3 voltage downscale */
static void vScaleDownEM23Setup(void)
{
  uint32_t hfSrcClockFrequency;

  EMU_VScaleEM23_TypeDef scaleEM23Voltage =
    (EMU_VScaleEM23_TypeDef)((EMU->CTRL & _EMU_CTRL_EM23VSCALE_MASK)
                             >> _EMU_CTRL_EM23VSCALE_SHIFT);

  EMU_VScaleEM01_TypeDef currentEM01Voltage =
    (EMU_VScaleEM01_TypeDef)((EMU->STATUS & _EMU_STATUS_VSCALE_MASK)
                             >> _EMU_STATUS_VSCALE_SHIFT);

  /* Wait until previous scaling is done. */
  EMU_VScaleWait();

  /* Inverse coding. */
  if ((uint32_t)scaleEM23Voltage > (uint32_t)currentEM01Voltage) {
    /* Set safe clock and wait-states. */
    if (scaleEM23Voltage == emuVScaleEM23_LowPower) {
      hfSrcClockFrequency = CMU_ClockDivGet(cmuClock_HF) * CMU_ClockFreqGet(cmuClock_HF);
      /* Set default low power voltage HFRCO band as HF clock. */
      if (hfSrcClockFrequency > CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX) {
        CMU_HFRCOBandSet(cmuHFRCOFreq_19M0Hz);
      }
      CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFRCO);
    } else {
      /* Other voltage scaling levels are not currently supported. */
      EFM_ASSERT(false);
    }
  } else {
    /* Same voltage or hardware will scale to min(EMU_CTRL_EM23VSCALE, EMU_STATUS_VSCALE)  */
  }
}
#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.
 * @par
 *   If voltage scaling is supported, the restore parameter is true and the EM0
 *   voltage scaling level is set higher than the EM2 level, then the EM0 level is
 *   also restored.
 *
 * @param[in] restore
 *   @li true - save and restore oscillators, clocks and voltage scaling, see
 *   function details.
 *   @li false - do not save and 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

  /* Only save EMU and CMU state if restored on wake-up. */
  if (restore) {
    emState(emState_Save);
  }

#if defined(_EMU_CTRL_EM23VSCALE_MASK)
  vScaleDownEM23Setup();
#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(ERRATA_FIX_DCDC_FETCNT_SET_EN)
  dcdcFetCntSet(true);
#endif
#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN)
  dcdcHsFixLnBlock();
#endif

  __WFI();

#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN)
  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 and voltage scaling if supported. */
  if (restore) {
    emState(emState_Restore);
  } else {
    /* If not restoring, and original clock was not HFRCO, we have to */
    /* update CMSIS core clock variable since HF clock has changed */
    /* to HFRCO. */
    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.
 * @par
 *   If voltage scaling is supported, the restore parameter is true and the EM0
 *   voltage scaling level is set higher than the EM3 level, then the EM0 level is
 *   also restored.
 *
 * @param[in] restore
 *   @li true - save and restore oscillators, clocks and voltage scaling, see
 *   function details.
 *   @li false - do not save and 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

  /* Only save EMU and CMU state if restored on wake-up. */
  if (restore) {
    emState(emState_Save);
  }

#if defined(_EMU_CTRL_EM23VSCALE_MASK)
  vScaleDownEM23Setup();
#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(ERRATA_FIX_DCDC_FETCNT_SET_EN)
  dcdcFetCntSet(true);
#endif
#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN)
  dcdcHsFixLnBlock();
#endif

  __WFI();

#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN)
  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 and voltage scaling if supported. */
  if (restore) {
    emState(emState_Restore);
  } else {
    /* If not restoring, and original clock was not HFRCO, we have to */
    /* update CMSIS core clock variable since HF clock has changed */
    /* to HFRCO. */
    SystemCoreClockUpdate();
  }
}

/***************************************************************************//**
 * @brief
 *   Save CMU HF clock select state, oscillator enable and voltage scaling
 *   (if available) before @ref EMU_EnterEM2() or @ref EMU_EnterEM3() are called
 *   with the restore parameter set to false. Calling this function is
 *   equivalent to calling @ref EMU_EnterEM2() or @ref EMU_EnterEM3() with the
 *   restore parameter set to true, but it allows the state to be saved without
 *   going to sleep. The state can be restored manually by calling
 *   @ref EMU_Restore().
 ******************************************************************************/
void EMU_Save(void)
{
  emState(emState_Save);
}

/***************************************************************************//**
 * @brief
 *   Restore CMU HF clock select state, oscillator enable and voltage scaling
 *   (if available) after @ref EMU_EnterEM2() or @ref EMU_EnterEM3() are called
 *   with the restore parameter set to false. Calling this function is
 *   equivalent to calling @ref EMU_EnterEM2() or @ref EMU_EnterEM3() with the
 *   restore parameter set to true, but it allows the application to evaluate the
 *   wakeup reason before restoring state.
 ******************************************************************************/
void EMU_Restore(void)
{
  emState(emState_Restore);
}

/***************************************************************************//**
 * @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(_EMU_EM4CTRL_MASK)
  if ((EMU->EM4CTRL & _EMU_EM4CTRL_EM4STATE_MASK) == EMU_EM4CTRL_EM4STATE_EM4S) {
    uint32_t dcdcMode = EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK;
    if (dcdcMode == EMU_DCDCCTRL_DCDCMODE_LOWNOISE
        || dcdcMode == EMU_DCDCCTRL_DCDCMODE_LOWPOWER) {
      /* DCDC is not supported in EM4S so we switch DCDC to bypass mode before
       * entering EM4S */
      EMU_DCDCModeSet(emuDcdcMode_Bypass);
    }
  }
#endif

#if defined(_EMU_EM4CTRL_MASK) && defined(ERRATA_FIX_EMU_E208_EN)
  if (EMU->EM4CTRL & EMU_EM4CTRL_EM4STATE_EM4H) {
    /* Fix for errata EMU_E208 - Occasional Full Reset After Exiting EM4H.
     * Full description of errata fix can be found in the errata document. */
    __disable_irq();
    *(volatile uint32_t *)(EMU_BASE + 0x190)  = 0x0000ADE8UL;
    *(volatile uint32_t *)(EMU_BASE + 0x198) |= (0x1UL << 7);
    *(volatile uint32_t *)(EMU_BASE + 0x88)  |= (0x1UL << 8);
  }
#endif

#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(ERRATA_FIX_DCDC_FETCNT_SET_EN)
  dcdcFetCntSet(true);
#endif
#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN)
  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
}

#if defined(_EMU_EM4CTRL_MASK)
/***************************************************************************//**
 * @brief
 *   Enter energy mode 4 hibernate (EM4H).
 *
 * @note
 *   Retention of clocks and GPIO in EM4 can be configured using
 *   @ref EMU_EM4Init before calling this function.
 ******************************************************************************/
void EMU_EnterEM4H(void)
{
  BUS_RegBitWrite(&EMU->EM4CTRL, _EMU_EM4CTRL_EM4STATE_SHIFT, 1);
  EMU_EnterEM4();
}

/***************************************************************************//**
 * @brief
 *   Enter energy mode 4 shutoff (EM4S).
 *
 * @note
 *   Retention of clocks and GPIO in EM4 can be configured using
 *   @ref EMU_EM4Init before calling this function.
 ******************************************************************************/
void EMU_EnterEM4S(void)
{
  BUS_RegBitWrite(&EMU->EM4CTRL, _EMU_EM4CTRL_EM4STATE_SHIFT, 0);
  EMU_EnterEM4();
}
#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 POR reset can power up the specified memory block(s) after powerdown.
 *
 * @deprecated
 *   This function is deprecated, use @ref EMU_RamPowerDown() instead which
 *   maps a user provided memory range into RAM blocks to power down.
 ******************************************************************************/
void EMU_MemPwrDown(uint32_t blocks)
{
#if defined(_EMU_MEMCTRL_MASK)
  EMU->MEMCTRL = blocks & _EMU_MEMCTRL_MASK;
#elif defined(_EMU_RAM0CTRL_MASK)
  EMU->RAM0CTRL = blocks & _EMU_RAM0CTRL_MASK;
#else
  (void)blocks;
#endif
}

/***************************************************************************//**
 * @brief
 *   Power down RAM memory blocks.
 *
 * @details
 *   This function will power down all the RAM blocks that are within a given
 *   range. The RAM block layout is different between device families, so this
 *   function can be used in a generic way to power down a RAM memory region
 *   which is known to be unused.
 *
 *   This function will only power down blocks which are completely enclosed
 *   by the memory range given by [start, end).
 *
 *   Here is an example of how to power down all RAM blocks except the first
 *   one. The first RAM block is special in that it cannot be powered down
 *   by the hardware. The size of this first RAM block is device specific
 *   see the reference manual to find the RAM block sizes.
 *
 * @code
 *   EMU_RamPowerDown(SRAM_BASE, SRAM_BASE + SRAM_SIZE);
 * @endcode
 *
 * @note
 *   Only a POR reset can power up the specified memory block(s) after powerdown.
 *
 * @param[in] start
 *   The start address of the RAM region to power down. This address is
 *   inclusive.
 *
 * @param[in] end
 *   The end address of the RAM region to power down. This address is
 *   exclusive. If this parameter is 0, then all RAM blocks contained in the
 *   region from start to the upper RAM address will be powered down.
 ******************************************************************************/
void EMU_RamPowerDown(uint32_t start, uint32_t end)
{
  uint32_t mask = 0;

  if (end == 0) {
    end = SRAM_BASE + SRAM_SIZE;
  }

  // Check to see if something in RAM0 can be powered down
  if (end > RAM0_END) {
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_84) // EFM32xG12 and EFR32xG12
    // Block 0 is 16 kB and cannot be powered off
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20004000) << 0; // Block 1, 16 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20008000) << 1; // Block 2, 16 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x2000C000) << 2; // Block 3, 16 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20010000) << 3; // Block 4, 64 kB
#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) // EFM32xG1 and EFR32xG1
    // Block 0 is 4 kB and cannot be powered off
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20001000) << 0; // Block 1, 4 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20002000) << 1; // Block 2, 8 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20004000) << 2; // Block 3, 8 kB
    mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20006000) << 3; // Block 4, 7 kB
#elif defined(RAM0_BLOCKS)
    // These platforms have equally sized RAM blocks
    for (int i = 1; i < RAM0_BLOCKS; i++) {
      mask |= ADDRESS_NOT_IN_BLOCK(start, RAM_MEM_BASE + (i * RAM0_BLOCK_SIZE)) << (i - 1);
    }
#endif
  }

  // Power down the selected blocks
#if defined(_EMU_MEMCTRL_MASK)
  EMU->MEMCTRL = EMU->MEMCTRL   | mask;
#elif defined(_EMU_RAM0CTRL_MASK)
  EMU->RAM0CTRL = EMU->RAM0CTRL | mask;
#else
  // These devices are unable to power down RAM blocks
  (void) mask;
  (void) start;
#endif

#if defined(RAM1_MEM_END)
  mask = 0;
  if (end > RAM1_MEM_END) {
    for (int i = 0; i < RAM1_BLOCKS; i++) {
      mask |= ADDRESS_NOT_IN_BLOCK(start, RAM1_MEM_BASE + (i * RAM1_BLOCK_SIZE)) << i;
    }
  }
  EMU->RAM1CTRL |= mask;
#endif

#if defined(RAM2_MEM_END)
  mask = 0;
  if (end > RAM2_MEM_END) {
    for (int i = 0; i < RAM2_BLOCKS; i++) {
      mask |= ADDRESS_NOT_IN_BLOCK(start, RAM2_MEM_BASE + (i * RAM2_BLOCK_SIZE)) << i;
    }
  }
  EMU->RAM2CTRL |= mask;
#endif
}

#if defined(_EMU_EM23PERNORETAINCTRL_MASK)
/***************************************************************************//**
 * @brief
 *   Set EM2 3 peripheral retention control.
 *
 * @param[in] periMask
 *  Peripheral select mask. Use | operator to select multiple peripheral, for example
 *  @ref emuPeripheralRetention_LEUART0 | @ref emuPeripheralRetention_VDAC0.
 * @param[in] enable
 *  Peripheral retention enable (true) or disable (false).
 *
 *
 * @note
 *   Only peripheral retention disable is currently supported. Peripherals are
 *   enabled by default, and can only be disabled.
 ******************************************************************************/
void EMU_PeripheralRetention(EMU_PeripheralRetention_TypeDef periMask, bool enable)
{
  EFM_ASSERT(!enable);
  EMU->EM23PERNORETAINCTRL = periMask & emuPeripheralRetention_ALL;
}
#endif

/***************************************************************************//**
 * @brief
 *   Update EMU module with CMU oscillator selection/enable status.
 *
 * @deprecated
 *   Oscillator status is saved in @ref EMU_EnterEM2() and @ref EMU_EnterEM3().
 ******************************************************************************/
void EMU_UpdateOscConfig(void)
{
  emState(emState_Save);
}

#if defined(_EMU_CMD_EM01VSCALE0_MASK)
/***************************************************************************//**
 * @brief
 *   Voltage scale in EM0 and 1 by clock frequency.
 *
 * @param[in] clockFrequency
 *   Use CMSIS HF clock if 0, or override to custom clock. Providing a
 *   custom clock frequency is required if using a non-standard HFXO
 *   frequency.
 * @param[in] wait
 *   Wait for scaling to complete.
 *
 * @note
 *   This function is primarily needed by the @ref CMU module.
 ******************************************************************************/
void EMU_VScaleEM01ByClock(uint32_t clockFrequency, bool wait)
{
  uint32_t hfSrcClockFrequency;
  uint32_t hfPresc = 1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK)
                           >> _CMU_HFPRESC_PRESC_SHIFT);

  /* VSCALE frequency is HFSRCCLK */
  if (clockFrequency == 0) {
    hfSrcClockFrequency = SystemHFClockGet() * hfPresc;
  } else {
    hfSrcClockFrequency = clockFrequency;
  }

  /* Apply EM0 and 1 voltage scaling command. */
  if (vScaleEM01Config.vScaleEM01LowPowerVoltageEnable
      && (hfSrcClockFrequency < CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX)) {
    EMU_VScaleEM01(emuVScaleEM01_LowPower, wait);
  } else {
    EMU_VScaleEM01(emuVScaleEM01_HighPerformance, wait);
  }
}
#endif

#if defined(_EMU_CMD_EM01VSCALE0_MASK)
/***************************************************************************//**
 * @brief
 *   Force voltage scaling in EM0 and 1 to a specific voltage level.
 *
 * @param[in] voltage
 *   Target VSCALE voltage level.
 * @param[in] wait
 *   Wait for scaling to complate.
 *
 * @note
 *   This function is useful for upscaling before programming Flash from @ref MSC,
 *   and downscaling after programming is done. Flash programming is only supported
 *   at @ref emuVScaleEM01_HighPerformance.
 *
 * @note
 *  This function ignores @ref vScaleEM01LowPowerVoltageEnable set from @ref
 *  EMU_EM01Init().
 ******************************************************************************/
void EMU_VScaleEM01(EMU_VScaleEM01_TypeDef voltage, bool wait)
{
  uint32_t hfSrcClockFrequency;
  uint32_t hfPresc = 1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK)
                           >> _CMU_HFPRESC_PRESC_SHIFT);
  uint32_t hfFreq = SystemHFClockGet();
  EMU_VScaleEM01_TypeDef current = EMU_VScaleGet();

  if (current == voltage) {
    /* Voltage is already at correct level. */
    return;
  }

  hfSrcClockFrequency = hfFreq * hfPresc;

  if (voltage == emuVScaleEM01_LowPower) {
    EFM_ASSERT(hfSrcClockFrequency <= CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX);
    /* Update wait states before scaling down voltage */
    CMU_UpdateWaitStates(hfFreq, emuVScaleEM01_LowPower);
  }

  EMU->CMD = vScaleEM01Cmd(voltage);

  if (voltage == emuVScaleEM01_HighPerformance) {
    /* Update wait states after scaling up voltage */
    CMU_UpdateWaitStates(hfFreq, emuVScaleEM01_HighPerformance);
  }

  if (wait) {
    EMU_VScaleWait();
  }
}
#endif

#if defined(_EMU_CMD_EM01VSCALE0_MASK)
/***************************************************************************//**
 * @brief
 *   Update EMU module with Energy Mode 0 and 1 configuration
 *
 * @param[in] em01Init
 *    Energy Mode 0 and 1 configuration structure
 ******************************************************************************/
void EMU_EM01Init(const EMU_EM01Init_TypeDef *em01Init)
{
  vScaleEM01Config.vScaleEM01LowPowerVoltageEnable =
    em01Init->vScaleEM01LowPowerVoltageEnable;
  EMU_VScaleEM01ByClock(0, true);
}
#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(const 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_CTRL_EM23VSCALE_MASK)
  EMU->CTRL = (EMU->CTRL & ~_EMU_CTRL_EM23VSCALE_MASK)
              | (em23Init->vScaleEM23Voltage << _EMU_CTRL_EM23VSCALE_SHIFT);
#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(const 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

#if defined(_EMU_CTRL_EM4HVSCALE_MASK)
  EMU->CTRL = (EMU->CTRL & ~_EMU_CTRL_EM4HVSCALE_MASK)
              | (em4Init->vScaleEM4HVoltage << _EMU_CTRL_EM4HVSCALE_SHIFT);
#endif
}
#endif

#if defined(BU_PRESENT)
/***************************************************************************//**
 * @brief
 *   Configure Backup Power Domain settings
 *
 * @param[in] bupdInit
 *   Backup power domain initialization structure
 ******************************************************************************/
void EMU_BUPDInit(const 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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if defined(_EMU_DCDCCTRL_MASK)
/* Translate fields with different names across platform generations to common names. */
#if defined(_EMU_DCDCMISCCTRL_LPCMPBIAS_MASK)
#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK      _EMU_DCDCMISCCTRL_LPCMPBIAS_MASK
#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT     _EMU_DCDCMISCCTRL_LPCMPBIAS_SHIFT
#elif defined(_EMU_DCDCMISCCTRL_LPCMPBIASEM234H_MASK)
#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK      _EMU_DCDCMISCCTRL_LPCMPBIASEM234H_MASK
#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT     _EMU_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT
#endif
#if defined(_EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK)
#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK      _EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK
#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT     _EMU_DCDCLPCTRL_LPCMPHYSSEL_SHIFT
#elif defined(_EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK)
#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK      _EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK
#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT     _EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT
#endif

/* Internal DCDC trim modes. */
typedef enum {
  dcdcTrimMode_EM234H_LP = 0,
#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
  dcdcTrimMode_EM01_LP,
#endif
  dcdcTrimMode_LN,
} dcdcTrimMode_TypeDef;

/***************************************************************************//**
 * @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 dcdcConstCalibrationLoad(void)
{
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
  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;

#else
  return true;
#endif
}

/***************************************************************************//**
 * @brief
 *   Set recommended and validated current optimization and timing settings
 *
 ******************************************************************************/
static void dcdcValidatedConfigSet(void)
{
/* Disable LP mode hysterysis in the state machine control */
#define EMU_DCDCMISCCTRL_LPCMPHYSDIS (0x1UL << 1)
/* Comparator threshold on the high side */
#define EMU_DCDCMISCCTRL_LPCMPHYSHI  (0x1UL << 2)
#define EMU_DCDCSMCTRL  (*(volatile uint32_t *)(EMU_BASE + 0x44))

  uint32_t lnForceCcm;

#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
  uint32_t dcdcTiming;
  SYSTEM_ChipRevision_TypeDef rev;
#endif

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

  /* Set low-noise RCO for LNFORCECCM configuration
   * LNFORCECCM is default 1 for EFR32
   * LNFORCECCM is default 0 for EFM32
   */
  lnForceCcm = BUS_RegBitRead(&EMU->DCDCMISCCTRL, _EMU_DCDCMISCCTRL_LNFORCECCM_SHIFT);
  if (lnForceCcm) {
    /* 7MHz is recommended for LNFORCECCM = 1 */
    EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_7MHz);
  } else {
    /* 3MHz is recommended for LNFORCECCM = 0 */
    EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_3MHz);
  }

#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
  EMU->DCDCTIMING &= ~_EMU_DCDCTIMING_DUTYSCALE_MASK;
  EMU->DCDCMISCCTRL |= EMU_DCDCMISCCTRL_LPCMPHYSDIS
                       | EMU_DCDCMISCCTRL_LPCMPHYSHI;

  SYSTEM_ChipRevisionGet(&rev);
  if ((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;
  }
#endif
}

/***************************************************************************//**
 * @brief
 *   Compute current limiters:
 *     LNCLIMILIMSEL: LN current limiter threshold
 *     LPCLIMILIMSEL: LP current limiter threshold
 *     DCDCZDETCTRL:  zero detector limiter threshold
 ******************************************************************************/
static void currentLimitersUpdate(void)
{
  uint32_t lncLimSel;
  uint32_t zdetLimSel;
  uint32_t pFetCnt;
  uint16_t maxReverseCurrent_mA;

  /* 80mA as recommended peak in Application Note AN0948.
     The peak current is the average current plus 50% of the current ripple.
     Hence, a 14mA average current is recommended in LP mode. Since LP PFETCNT is also
     a constant, we get lpcLimImSel = 1. The following calculation is provided
     for documentation only. */
  const uint32_t lpcLim = (((14 + 40) + ((14 + 40) / 2))
                           / (5 * (DCDC_LP_PFET_CNT + 1)))
                          - 1;
  const uint32_t lpcLimSel = lpcLim << _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_SHIFT;

  /* Get enabled PFETs */
  pFetCnt = (EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_PFETCNT_MASK)
            >> _EMU_DCDCMISCCTRL_PFETCNT_SHIFT;

  /* Compute LN current limiter threshold from nominal user input current and
     LN PFETCNT as described in the register description for
     EMU_DCDCMISCCTRL_LNCLIMILIMSEL. */
  lncLimSel = (((dcdcMaxCurrent_mA + 40) + ((dcdcMaxCurrent_mA + 40) / 2))
               / (5 * (pFetCnt + 1)))
              - 1;

  /* Saturate the register field value */
  lncLimSel = SL_MIN(lncLimSel,
                     _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK
                     >> _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_SHIFT);

  lncLimSel <<= _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_SHIFT;

  /* Check for overflow */
  EFM_ASSERT((lncLimSel & ~_EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK) == 0x0);
  EFM_ASSERT((lpcLimSel & ~_EMU_DCDCMISCCTRL_LPCLIMILIMSEL_MASK) == 0x0);

  EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK
                                             | _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_MASK))
                      | (lncLimSel | lpcLimSel);

  /* Compute reverse current limit threshold for the zero detector from user input
     maximum reverse current and LN PFETCNT as described in the register description
     for EMU_DCDCZDETCTRL_ZDETILIMSEL. */
  if (dcdcReverseCurrentControl >= 0) {
    /* If dcdcReverseCurrentControl < 0, then EMU_DCDCZDETCTRL_ZDETILIMSEL is "don't care" */
    maxReverseCurrent_mA = (uint16_t)dcdcReverseCurrentControl;

    zdetLimSel = ( ((maxReverseCurrent_mA + 40) + ((maxReverseCurrent_mA + 40) / 2))
                   / ((2 * (pFetCnt + 1)) + ((pFetCnt + 1) / 2)) );
    /* Saturate the register field value */
    zdetLimSel = SL_MIN(zdetLimSel,
                        _EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK
                        >> _EMU_DCDCZDETCTRL_ZDETILIMSEL_SHIFT);

    zdetLimSel <<= _EMU_DCDCZDETCTRL_ZDETILIMSEL_SHIFT;

    /* Check for overflow */
    EFM_ASSERT((zdetLimSel & ~_EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK) == 0x0);

    EMU->DCDCZDETCTRL = (EMU->DCDCZDETCTRL & ~_EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK)
                        | zdetLimSel;
  }
}

/***************************************************************************//**
 * @brief
 *   Set static variables that hold the user set maximum peak current
 *   and reverse current. Update limiters.
 *
 * @param[in] maxCurrent_mA
 *   Set the maximum peak current that the DCDC can draw from the power source.
 * @param[in] reverseCurrentControl
 *   Reverse current control as defined by
 *   @ref EMU_DcdcLnReverseCurrentControl_TypeDef. Positive values have unit mA.
 ******************************************************************************/
static void userCurrentLimitsSet(uint32_t maxCurrent_mA,
                                 EMU_DcdcLnReverseCurrentControl_TypeDef reverseCurrentControl)
{
  dcdcMaxCurrent_mA = maxCurrent_mA;
  dcdcReverseCurrentControl = reverseCurrentControl;
}

/***************************************************************************//**
 * @brief
 *   Set DCDC low noise compensator control register
 *
 * @param[in] comp
 *   Low-noise mode compensator trim setpoint
 ******************************************************************************/
static void compCtrlSet(EMU_DcdcLnCompCtrl_TypeDef comp)
{
  switch (comp) {
    case emuDcdcLnCompCtrl_1u0F:
      EMU->DCDCLNCOMPCTRL = 0x57204077UL;
      break;

    case emuDcdcLnCompCtrl_4u7F:
      EMU->DCDCLNCOMPCTRL = 0xB7102137UL;
      break;

    default:
      EFM_ASSERT(false);
      break;
  }
}

/***************************************************************************//**
 * @brief
 *   Load EMU_DCDCLPCTRL_LPCMPHYSSEL depending on LP bias, LP feedback
 *   attenuation and DEVINFOREV.
 *
 * @param[in] lpAttenuation
 *   LP feedback attenuation.
 * @param[in] lpCmpBias
 *   lpCmpBias selection.
 * @param[in] trimMode
 *   DCDC trim mode.
 ******************************************************************************/
static bool lpCmpHystCalibrationLoad(bool lpAttenuation,
                                     uint8_t lpCmpBias,
                                     dcdcTrimMode_TypeDef trimMode)
{
  uint32_t lpcmpHystSel;

  /* Get calib data revision */
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
  uint8_t devinfoRev = SYSTEM_GetDevinfoRev();

  /* Load LPATT indexed calibration data */
  if (devinfoRev < 4)
#else
  /* Format change not present of newer families. */
  if (false)
#endif
  {
    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;
    }
  } else {
    /* devinfoRev >= 4: load LPCMPBIAS indexed calibration data */
    lpcmpHystSel = DEVINFO->DCDCLPCMPHYSSEL1;
    switch (lpCmpBias) {
      case 0:
        lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_MASK)
                       >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_SHIFT;
        break;

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

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

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

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

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

#if defined(_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK)
  if (trimMode == dcdcTrimMode_EM01_LP) {
    /* Make sure the sel value is within the field range. */
    lpcmpHystSel <<= _EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_SHIFT;
    if (lpcmpHystSel & ~_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK) {
      EFM_ASSERT(false);
      /* Return when assertions are disabled */
      return false;
    }
    EMU->DCDCLPEM01CFG = (EMU->DCDCLPEM01CFG & ~_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK) | lpcmpHystSel;
  }
#endif

  return true;
}

/***************************************************************************//**
 * @brief
 *   Load LPVREF low and high from DEVINFO.
 *
 * @param[out] vrefL
 *   LPVREF low from DEVINFO.
 * @param[out] vrefH
 *   LPVREF high from DEVINFO.
 * @param[in] lpAttenuation
 *   LP feedback attenuation.
 * @param[in] lpcmpBias
 *   lpcmpBias to lookup in DEVINFO.
 ******************************************************************************/
static void lpGetDevinfoVrefLowHigh(uint32_t *vrefL,
                                    uint32_t *vrefH,
                                    bool lpAttenuation,
                                    uint8_t lpcmpBias)
{
  uint32_t vrefLow = 0;
  uint32_t vrefHigh = 0;

  /* Find VREF high and low in DEVINFO indexed by LPCMPBIAS (lpcmpBias)
     and LPATT (lpAttenuation) */
  uint32_t switchVal = (lpcmpBias << 8) | (lpAttenuation ? 1 : 0);
  switch (switchVal) {
    case ((0 << 8) | 1):
      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 ((1 << 8) | 1):
      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 ((2 << 8) | 1):
      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 ((3 << 8) | 1):
      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 ((0 << 8) | 0):
      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 ((1 << 8) | 0):
      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 ((2 << 8) | 0):
      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 ((3 << 8) | 0):
      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;
  }
  *vrefL = vrefLow;
  *vrefH = vrefHigh;
}

/** @endcond */

/***************************************************************************//**
 * @brief
 *   Set DCDC regulator operating mode
 *
 * @param[in] dcdcMode
 *   DCDC mode
 ******************************************************************************/
void EMU_DCDCModeSet(EMU_DcdcMode_TypeDef dcdcMode)
{
  uint32_t currentDcdcMode;

  /* Wait for any previous write sync to complete and read DCDC mode. */
  while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
  currentDcdcMode = (EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK);

  /* Enable bypass current limiter when not in bypass mode to prevent
     excessive current between VREGVDD and DVDD supplies when reentering bypass mode.  */
  if (currentDcdcMode != EMU_DCDCCTRL_DCDCMODE_BYPASS) {
    BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, 1);
  }

  if ((EMU_DcdcMode_TypeDef)currentDcdcMode == dcdcMode) {
    /* Mode already set. If already in bypass, make sure bypass current limiter
       is disabled. */
    if (dcdcMode == emuDcdcMode_Bypass) {
      BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, 0);
    }
    return;
  }

#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)

  /* Fix for errata DCDC_E203 */
  if ((currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_BYPASS)
      && (dcdcMode == emuDcdcMode_LowNoise)) {
    errataFixDcdcHsState = errataFixDcdcHsBypassLn;
  }

#else

  /* Fix for errata DCDC_E204 */
  if (((currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_OFF) || (currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_BYPASS))
      && ((dcdcMode == emuDcdcMode_LowPower) || (dcdcMode == emuDcdcMode_LowNoise))) {
    /* Always start in LOWNOISE mode and then switch to LOWPOWER mode once LOWNOISE startup is complete. */
    EMU_IntClear(EMU_IFC_DCDCLNRUNNING);
    while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
    EMU->DCDCCTRL = (EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) | EMU_DCDCCTRL_DCDCMODE_LOWNOISE;
    while (!(EMU_IntGet() & EMU_IF_DCDCLNRUNNING)) ;
  }
#endif

  /* Set user requested mode. */
  while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
  EMU->DCDCCTRL = (EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) | dcdcMode;

  /* Disable bypass current limiter after bypass mode is entered.
     Enable the limiter if any other mode is entered. */
  while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
  BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, dcdcMode == emuDcdcMode_Bypass ? 0 : 1);
}

/***************************************************************************//**
 * @brief
 *   Set DCDC LN regulator conduction mode
 *
 * @param[in] conductionMode
 *   DCDC LN conduction mode.
 * @param[in] rcoDefaultSet
 *   The default DCDC RCO band for the conductionMode will be used if true.
 *   Otherwise the current RCO configuration is used.
 ******************************************************************************/
void EMU_DCDCConductionModeSet(EMU_DcdcConductionMode_TypeDef conductionMode, bool rcoDefaultSet)
{
  EMU_DcdcMode_TypeDef currentDcdcMode
    = (EMU_DcdcMode_TypeDef)(EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK);
  EMU_DcdcLnRcoBand_TypeDef rcoBand
    = (EMU_DcdcLnRcoBand_TypeDef)((EMU->DCDCLNFREQCTRL & _EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                                  >> _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT);

  /* Set bypass mode and wait for bypass mode to settle before
     EMU_DCDCMISCCTRL_LNFORCECCM is set. Restore current DCDC mode. */
  EMU_IntClear(EMU_IFC_DCDCINBYPASS);
  EMU_DCDCModeSet(emuDcdcMode_Bypass);
  while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
  while (!(EMU_IntGet() & EMU_IF_DCDCINBYPASS)) ;
  if (conductionMode == emuDcdcConductionMode_DiscontinuousLN) {
    EMU->DCDCMISCCTRL &= ~EMU_DCDCMISCCTRL_LNFORCECCM;
    if (rcoDefaultSet) {
      EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_3MHz);
    } else {
      /* emuDcdcConductionMode_DiscontinuousLN supports up to 4MHz LN RCO. */
      EFM_ASSERT(rcoBand <= emuDcdcLnRcoBand_4MHz);
    }
  } else {
    EMU->DCDCMISCCTRL |= EMU_DCDCMISCCTRL_LNFORCECCM;
    if (rcoDefaultSet) {
      EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_7MHz);
    }
  }
  EMU_DCDCModeSet(currentDcdcMode);
  /* Update slice configuration as it depends on conduction mode and RCO band. */
  EMU_DCDCOptimizeSlice(dcdcEm01LoadCurrent_mA);
}

/***************************************************************************//**
 * @brief
 *   Configure DCDC regulator
 *
 * @note
 * If the power circuit is configured for NODCDC as described in Section
 * 11.3.4.3 of the Reference Manual, do not call this function. Instead call
 * EMU_DCDCPowerOff().
 *
 * @param[in] dcdcInit
 *   DCDC initialization structure
 *
 * @return
 *   True if initialization parameters are valid
 ******************************************************************************/
bool EMU_DCDCInit(const EMU_DCDCInit_TypeDef *dcdcInit)
{
  uint32_t lpCmpBiasSelEM234H;

#if defined(_EMU_PWRCFG_MASK)
  /* Set external power configuration. This enables writing to the other
     DCDC registers. */
  EMU->PWRCFG = EMU_PWRCFG_PWRCFG_DCDCTODVDD;

  /* 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) != EMU_PWRCFG_PWRCFG_DCDCTODVDD) {
    /* 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;
  }
#endif

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

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

  if (dcdcInit->dcdcMode == emuDcdcMode_LowNoise) {
    /* DCDC low-noise supports max 200mA */
    EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= 200);
  }
#if (_SILICON_LABS_GECKO_INTERNAL_SDID != 80)
  else if (dcdcInit->dcdcMode == emuDcdcMode_LowPower) {
    /* Up to 10mA is supported for EM01-LP mode. */
    EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= 10);
  }
#endif

  /* EM2/3/4 current above 10mA is not supported */
  EFM_ASSERT(dcdcInit->em234LoadCurrent_uA <= 10000);

  if (dcdcInit->em234LoadCurrent_uA < 75) {
    lpCmpBiasSelEM234H  = 0;
  } else if (dcdcInit->em234LoadCurrent_uA < 500) {
    lpCmpBiasSelEM234H  = 1 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT;
  } else if (dcdcInit->em234LoadCurrent_uA < 2500) {
    lpCmpBiasSelEM234H  = 2 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT;
  } else {
    lpCmpBiasSelEM234H  = 3 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT;
  }

  /* ==== THESE NEXT STEPS ARE STRONGLY ORDER DEPENDENT ==== */

  /* Set DCDC low-power mode comparator bias selection */

  /* 1. Set DCDC low-power mode comparator bias selection and forced CCM
        => Updates DCDCMISCCTRL_LNFORCECCM */
  EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK
                                             | _EMU_DCDCMISCCTRL_LNFORCECCM_MASK))
                      | ((uint32_t)lpCmpBiasSelEM234H
                         | (dcdcInit->reverseCurrentControl >= 0
                            ? EMU_DCDCMISCCTRL_LNFORCECCM : 0));
#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
  /* Only 10mA EM01-LP current is supported */
  EMU->DCDCLPEM01CFG = (EMU->DCDCLPEM01CFG & ~_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
                       | EMU_DCDCLPEM01CFG_LPCMPBIASEM01_BIAS3;
#endif

  /* 2. Set recommended and validated current optimization settings
        <= Depends on LNFORCECCM
        => Updates DCDCLNFREQCTRL_RCOBAND */
  dcdcValidatedConfigSet();

  /* 3. Updated static currents and limits user data.
        Limiters are updated in EMU_DCDCOptimizeSlice() */
  userCurrentLimitsSet(dcdcInit->maxCurrent_mA,
                       dcdcInit->reverseCurrentControl);
  dcdcEm01LoadCurrent_mA = dcdcInit->em01LoadCurrent_mA;

  /* 4. Optimize LN slice based on given user input load current
        <= Depends on DCDCMISCCTRL_LNFORCECCM and DCDCLNFREQCTRL_RCOBAND
        <= Depends on dcdcInit->maxCurrent_mA and dcdcInit->reverseCurrentControl
        => Updates DCDCMISCCTRL_P/NFETCNT
        => Updates DCDCMISCCTRL_LNCLIMILIMSEL and DCDCMISCCTRL_LPCLIMILIMSEL
        => Updates DCDCZDETCTRL_ZDETILIMSEL */
  EMU_DCDCOptimizeSlice(dcdcInit->em01LoadCurrent_mA);

  /* ======================================================= */

  /* Set DCDC low noise mode compensator control register. */
  compCtrlSet(dcdcInit->dcdcLnCompCtrl);

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

#if (_SILICON_LABS_GECKO_INTERNAL_SDID == 80)
  /* Select analog peripheral power supply. This must be done before
     DCDC mode is set for all EFM32xG1 and EFR32xG1 devices. */
  BUS_RegBitWrite(&EMU->PWRCTRL,
                  _EMU_PWRCTRL_ANASW_SHIFT,
                  dcdcInit->anaPeripheralPower ? 1 : 0);
#endif

#if defined(_EMU_PWRCTRL_REGPWRSEL_MASK)
  /* Select DVDD as input to the digital regulator. The switch to DVDD will take
     effect once the DCDC output is stable. */
  EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD;
#endif

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

#if (_SILICON_LABS_GECKO_INTERNAL_SDID != 80)
  /* Select analog peripheral power supply. This must be done after
     DCDC mode is set for all devices other than EFM32xG1 and EFR32xG1. */
  BUS_RegBitWrite(&EMU->PWRCTRL,
                  _EMU_PWRCTRL_ANASW_SHIFT,
                  dcdcInit->anaPeripheralPower ? 1 : 0);
#endif

  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)

#define DCDC_TRIM_MODES ((uint8_t)dcdcTrimMode_LN + 1)
  bool validOutVoltage;
  bool attenuationSet;
  uint32_t mVlow = 0;
  uint32_t mVhigh = 0;
  uint32_t mVdiff;
  uint32_t vrefVal[DCDC_TRIM_MODES] = { 0 };
  uint32_t vrefLow[DCDC_TRIM_MODES] = { 0 };
  uint32_t vrefHigh[DCDC_TRIM_MODES] = { 0 };
  uint8_t lpcmpBias[DCDC_TRIM_MODES] = { 0 };

  /* Check that the set voltage is within valid range.
     Voltages are obtained from the datasheet. */
  validOutVoltage = ((mV >= PWRCFG_DCDCTODVDD_VMIN)
                     && (mV <= PWRCFG_DCDCTODVDD_VMAX));

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

  /* Set attenuation to use and low/high range. */
  attenuationSet = (mV > 1800);
  if (attenuationSet) {
    mVlow = 1800;
    mVhigh = 3000;
    mVdiff = mVhigh - mVlow;
  } else {
    mVlow = 1200;
    mVhigh = 1800;
    mVdiff = mVhigh - mVlow;
  }

  /* Get 2-point calib data from DEVINFO */

  /* LN mode */
  if (attenuationSet) {
    vrefLow[dcdcTrimMode_LN]  = DEVINFO->DCDCLNVCTRL0;
    vrefHigh[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_MASK)
                                >> _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_SHIFT;
    vrefLow[dcdcTrimMode_LN]  = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_MASK)
                                >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_SHIFT;
  } else {
    vrefLow[dcdcTrimMode_LN]  = DEVINFO->DCDCLNVCTRL0;
    vrefHigh[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_MASK)
                                >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_SHIFT;
    vrefLow[dcdcTrimMode_LN]  = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_MASK)
                                >> _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_SHIFT;
  }

  /* LP EM234H mode */
  lpcmpBias[dcdcTrimMode_EM234H_LP] = (EMU->DCDCMISCCTRL & _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK)
                                      >> _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT;
  lpGetDevinfoVrefLowHigh(&vrefLow[dcdcTrimMode_EM234H_LP],
                          &vrefHigh[dcdcTrimMode_EM234H_LP],
                          attenuationSet,
                          lpcmpBias[dcdcTrimMode_EM234H_LP]);

#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
  /* LP EM01 mode */
  lpcmpBias[dcdcTrimMode_EM01_LP] = (EMU->DCDCLPEM01CFG & _EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
                                    >> _EMU_DCDCLPEM01CFG_LPCMPBIASEM01_SHIFT;
  lpGetDevinfoVrefLowHigh(&vrefLow[dcdcTrimMode_EM01_LP],
                          &vrefHigh[dcdcTrimMode_EM01_LP],
                          attenuationSet,
                          lpcmpBias[dcdcTrimMode_EM01_LP]);
#endif

  /* Calculate output voltage trims */
  vrefVal[dcdcTrimMode_LN]         = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_LN] - vrefLow[dcdcTrimMode_LN]))
                                     / mVdiff;
  vrefVal[dcdcTrimMode_LN]        += vrefLow[dcdcTrimMode_LN];

  vrefVal[dcdcTrimMode_EM234H_LP]  = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_EM234H_LP] - vrefLow[dcdcTrimMode_EM234H_LP]))
                                     / mVdiff;
  vrefVal[dcdcTrimMode_EM234H_LP] += vrefLow[dcdcTrimMode_EM234H_LP];

#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
  vrefVal[dcdcTrimMode_EM01_LP]    = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_EM01_LP] - vrefLow[dcdcTrimMode_EM01_LP]))
                                     / mVdiff;
  vrefVal[dcdcTrimMode_EM01_LP]   += vrefLow[dcdcTrimMode_EM01_LP];
#endif

  /* Range checks */
  if ((vrefVal[dcdcTrimMode_LN] > vrefHigh[dcdcTrimMode_LN])
      || (vrefVal[dcdcTrimMode_LN] < vrefLow[dcdcTrimMode_LN])
#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
      || (vrefVal[dcdcTrimMode_EM01_LP] > vrefHigh[dcdcTrimMode_EM01_LP])
      || (vrefVal[dcdcTrimMode_EM01_LP] < vrefLow[dcdcTrimMode_EM01_LP])
#endif
      || (vrefVal[dcdcTrimMode_EM234H_LP] > vrefHigh[dcdcTrimMode_EM234H_LP])
      || (vrefVal[dcdcTrimMode_EM234H_LP] < vrefLow[dcdcTrimMode_EM234H_LP])) {
    EFM_ASSERT(false);
    /* Return when assertions are disabled */
    return false;
  }

  /* Update output voltage tuning for LN and LP modes. */
  if (setLnVoltage) {
    EMU->DCDCLNVCTRL = (EMU->DCDCLNVCTRL & ~(_EMU_DCDCLNVCTRL_LNVREF_MASK | _EMU_DCDCLNVCTRL_LNATT_MASK))
                       | (vrefVal[dcdcTrimMode_LN] << _EMU_DCDCLNVCTRL_LNVREF_SHIFT)
                       | (attenuationSet ? EMU_DCDCLNVCTRL_LNATT : 0);
  }

  if (setLpVoltage) {
    /* Load LP EM234H comparator hysteresis calibration */
    if (!(lpCmpHystCalibrationLoad(attenuationSet, lpcmpBias[dcdcTrimMode_EM234H_LP], dcdcTrimMode_EM234H_LP))) {
      EFM_ASSERT(false);
      /* Return when assertions are disabled */
      return false;
    }

#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK)
    /* Load LP EM234H comparator hysteresis calibration */
    if (!(lpCmpHystCalibrationLoad(attenuationSet, lpcmpBias[dcdcTrimMode_EM01_LP], dcdcTrimMode_EM01_LP))) {
      EFM_ASSERT(false);
      /* Return when assertions are disabled */
      return false;
    }

    /* LP VREF is that max of trims for EM01 and EM234H. */
    vrefVal[dcdcTrimMode_EM234H_LP] = SL_MAX(vrefVal[dcdcTrimMode_EM234H_LP], vrefVal[dcdcTrimMode_EM01_LP]);
#endif

    /* Don't exceed max available code as specified in the reference manual for EMU_DCDCLPVCTRL. */
    vrefVal[dcdcTrimMode_EM234H_LP] = SL_MIN(vrefVal[dcdcTrimMode_EM234H_LP], 0xE7U);
    EMU->DCDCLPVCTRL = (EMU->DCDCLPVCTRL & ~(_EMU_DCDCLPVCTRL_LPVREF_MASK | _EMU_DCDCLPVCTRL_LPATT_MASK))
                       | (vrefVal[dcdcTrimMode_EM234H_LP] << _EMU_DCDCLPVCTRL_LPVREF_SHIFT)
                       | (attenuationSet ? EMU_DCDCLPVCTRL_LPATT : 0);
  }
#endif
  return true;
}

/***************************************************************************//**
 * @brief
 *   Optimize DCDC slice count based on the estimated average load current
 *   in EM0
 *
 * @param[in] em0LoadCurrent_mA
 *   Estimated average EM0 load current in mA.
 ******************************************************************************/
void EMU_DCDCOptimizeSlice(uint32_t em0LoadCurrent_mA)
{
  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 >= emuDcdcLnRcoBand_5MHz)) {
    if (em0LoadCurrent_mA < 20) {
      sliceCount = 4;
    } else if ((em0LoadCurrent_mA >= 20) && (em0LoadCurrent_mA < 40)) {
      sliceCount = 8;
    } else {
      sliceCount = 16;
    }
  } else if ((!(EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK)) && (rcoBand <= emuDcdcLnRcoBand_4MHz)) {
    if (em0LoadCurrent_mA < 10) {
      sliceCount = 4;
    } else if ((em0LoadCurrent_mA >= 10) && (em0LoadCurrent_mA < 20)) {
      sliceCount = 8;
    } else {
      sliceCount = 16;
    }
  } else if ((EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK) && (rcoBand <= emuDcdcLnRcoBand_4MHz)) {
    if (em0LoadCurrent_mA < 40) {
      sliceCount = 8;
    } else {
      sliceCount = 16;
    }
  } else {
    /* This configuration is not recommended. EMU_DCDCInit() applies a recommended
       configuration. */
    EFM_ASSERT(false);
  }

  /* The selected slices 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 limiters */
  currentLimitersUpdate();
}

/***************************************************************************//**
 * @brief
 *   Set DCDC Low-noise RCO band.
 *
 * @param[in] band
 *   RCO band to set.
 ******************************************************************************/
void EMU_DCDCLnRcoBandSet(EMU_DcdcLnRcoBand_TypeDef band)
{
  uint32_t forcedCcm;
  forcedCcm = BUS_RegBitRead(&EMU->DCDCMISCCTRL, _EMU_DCDCMISCCTRL_LNFORCECCM_SHIFT);

  /* DCM mode supports up to 4MHz LN RCO. */
  EFM_ASSERT((!forcedCcm && band <= emuDcdcLnRcoBand_4MHz) || forcedCcm);

  EMU->DCDCLNFREQCTRL = (EMU->DCDCLNFREQCTRL & ~_EMU_DCDCLNFREQCTRL_RCOBAND_MASK)
                        | (band << _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT);

  /* Update slice configuration as this depends on the RCO band. */
  EMU_DCDCOptimizeSlice(dcdcEm01LoadCurrent_mA);
}

/***************************************************************************//**
 * @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)
{
  bool dcdcModeSet;

#if defined(_EMU_PWRCFG_MASK)
  /* Set DCDCTODVDD only to enable write access to EMU->DCDCCTRL */
  EMU->PWRCFG = EMU_PWRCFG_PWRCFG_DCDCTODVDD;
#endif

  /* Select DVDD as input to the digital regulator */
#if defined(EMU_PWRCTRL_IMMEDIATEPWRSWITCH)
  EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD | EMU_PWRCTRL_IMMEDIATEPWRSWITCH;
#elif defined(EMU_PWRCTRL_REGPWRSEL_DVDD)
  EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD;
#endif

  /* Set DCDC to OFF and disable LP in EM2/3/4. Verify that the required
     mode could be set. */
  while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ;
  EMU->DCDCCTRL = EMU_DCDCCTRL_DCDCMODE_OFF;

  dcdcModeSet = (EMU->DCDCCTRL == EMU_DCDCCTRL_DCDCMODE_OFF);
  EFM_ASSERT(dcdcModeSet);

  return dcdcModeSet;
}
#endif

#if defined(EMU_STATUS_VMONRDY)
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Get calibrated threshold value.
 *
 * @details
 *   All VMON channels have two calibration fields in the DI page that
 *   describes the threshold at 1.86V and 2.98V. This function will convert
 *   the uncalibrated input voltage threshold in millivolts into a calibrated
 *   threshold.
 *
 * @param[in] channel
 *   VMON channel
 *
 * @param[in] threshold
 *   Desired threshold in millivolts.
 *
 * @return
 *   Calibrated threshold value to use. First digit of return value is placed
 *   in the "fine" register fields while the next digits are placed in the
 *   "coarse" register fields.
 ******************************************************************************/
static uint32_t vmonCalibratedThreshold(EMU_VmonChannel_TypeDef channel,
                                        int threshold)
{
  uint32_t tLow;
  uint32_t tHigh;
  uint32_t calReg;

  /* Get calibration values for 1.86V and 2.98V */
  switch (channel) {
    case emuVmonChannel_AVDD:
      calReg = DEVINFO->VMONCAL0;
      tLow = (10 * ((calReg & _DEVINFO_VMONCAL0_AVDD1V86THRESCOARSE_MASK)
                    >> _DEVINFO_VMONCAL0_AVDD1V86THRESCOARSE_SHIFT))
             + ((calReg & _DEVINFO_VMONCAL0_AVDD1V86THRESFINE_MASK)
                >> _DEVINFO_VMONCAL0_AVDD1V86THRESFINE_SHIFT);
      tHigh = (10 * ((calReg & _DEVINFO_VMONCAL0_AVDD2V98THRESCOARSE_MASK)
                     >> _DEVINFO_VMONCAL0_AVDD2V98THRESCOARSE_SHIFT))
              + ((calReg & _DEVINFO_VMONCAL0_AVDD2V98THRESFINE_MASK)
                 >> _DEVINFO_VMONCAL0_AVDD2V98THRESFINE_SHIFT);
      break;
    case emuVmonChannel_ALTAVDD:
      calReg = DEVINFO->VMONCAL0;
      tLow = (10 * ((calReg & _DEVINFO_VMONCAL0_ALTAVDD1V86THRESCOARSE_MASK)
                    >> _DEVINFO_VMONCAL0_ALTAVDD1V86THRESCOARSE_SHIFT))
             + ((calReg & _DEVINFO_VMONCAL0_ALTAVDD1V86THRESFINE_MASK)
                >> _DEVINFO_VMONCAL0_ALTAVDD1V86THRESFINE_SHIFT);
      tHigh = (10 * ((calReg & _DEVINFO_VMONCAL0_ALTAVDD2V98THRESCOARSE_MASK)
                     >> _DEVINFO_VMONCAL0_ALTAVDD2V98THRESCOARSE_SHIFT))
              + ((calReg & _DEVINFO_VMONCAL0_ALTAVDD2V98THRESFINE_MASK)
                 >> _DEVINFO_VMONCAL0_ALTAVDD2V98THRESFINE_SHIFT);
      break;
    case emuVmonChannel_DVDD:
      calReg = DEVINFO->VMONCAL1;
      tLow = (10 * ((calReg & _DEVINFO_VMONCAL1_DVDD1V86THRESCOARSE_MASK)
                    >> _DEVINFO_VMONCAL1_DVDD1V86THRESCOARSE_SHIFT))
             + ((calReg & _DEVINFO_VMONCAL1_DVDD1V86THRESFINE_MASK)
                >> _DEVINFO_VMONCAL1_DVDD1V86THRESFINE_SHIFT);
      tHigh = (10 * ((calReg & _DEVINFO_VMONCAL1_DVDD2V98THRESCOARSE_MASK)
                     >> _DEVINFO_VMONCAL1_DVDD2V98THRESCOARSE_SHIFT))
              + ((calReg & _DEVINFO_VMONCAL1_DVDD2V98THRESFINE_MASK)
                 >> _DEVINFO_VMONCAL1_DVDD2V98THRESFINE_SHIFT);
      break;
    case emuVmonChannel_IOVDD0:
      calReg = DEVINFO->VMONCAL1;
      tLow = (10 * ((calReg & _DEVINFO_VMONCAL1_IO01V86THRESCOARSE_MASK)
                    >> _DEVINFO_VMONCAL1_IO01V86THRESCOARSE_SHIFT))
             + ((calReg & _DEVINFO_VMONCAL1_IO01V86THRESFINE_MASK)
                >> _DEVINFO_VMONCAL1_IO01V86THRESFINE_SHIFT);
      tHigh = (10 * ((calReg & _DEVINFO_VMONCAL1_IO02V98THRESCOARSE_MASK)
                     >> _DEVINFO_VMONCAL1_IO02V98THRESCOARSE_SHIFT))
              + ((calReg & _DEVINFO_VMONCAL1_IO02V98THRESFINE_MASK)
                 >> _DEVINFO_VMONCAL1_IO02V98THRESFINE_SHIFT);
      break;
    default:
      EFM_ASSERT(false);
      return threshold;
  }

  if (tHigh <= tLow) {
    /* Uncalibrated device guard */
    return threshold;
  }

  /* Calculate threshold.
   *
   * Note that volt is used in the reference manual, however we are interested
   * in millivolt results. We also increase the precision of Va and Vb in the
   * calculation instead of using floating points.
   */
  uint32_t va = (1120 * 100) / (tHigh - tLow);
  uint32_t vb = (1860 * 100) - (va * tLow);
  /* Round threshold to nearest integer value. */
  return ((threshold * 100) - vb + (va / 2)) / va;
}

/** @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. The threshold will be converted to a register field value based
 *   on calibration values from the DI page.
 *
 * @param[in] vmonInit
 *   VMON initialization struct
 ******************************************************************************/
void EMU_VmonInit(const EMU_VmonInit_TypeDef *vmonInit)
{
  uint32_t thresholdCoarse, thresholdFine;
  uint32_t threshold;

  EFM_ASSERT((vmonInit->threshold >= 1620) && (vmonInit->threshold <= 3400));

  threshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->threshold);
  thresholdFine = threshold % 10;
  thresholdCoarse = threshold / 10;

  /* Saturate threshold to max values. */
  if (thresholdCoarse > 0xF) {
    thresholdCoarse = 0xF;
    thresholdFine = 9;
  }

  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. The rise and
 *   fall thresholds will be converted to a register field value based on
 *   calibration values from the DI page.
 *
 * @param[in] vmonInit
 *   VMON Hysteresis initialization struct
 ******************************************************************************/
void EMU_VmonHystInit(const EMU_VmonHystInit_TypeDef *vmonInit)
{
  uint32_t riseThreshold;
  uint32_t fallThreshold;

  /* VMON supports voltages between 1620 mV and 3400 mV (inclusive) */
  EFM_ASSERT((vmonInit->riseThreshold >= 1620) && (vmonInit->riseThreshold <= 3400));
  EFM_ASSERT((vmonInit->fallThreshold >= 1620) && (vmonInit->fallThreshold <= 3400));
  /* Fall threshold has to be lower than rise threshold */
  EFM_ASSERT(vmonInit->fallThreshold <= vmonInit->riseThreshold);

  riseThreshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->riseThreshold);
  fallThreshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->fallThreshold);

  switch (vmonInit->channel) {
    case emuVmonChannel_AVDD:
      EMU->VMONAVDDCTRL = ((riseThreshold / 10) << _EMU_VMONAVDDCTRL_RISETHRESCOARSE_SHIFT)
                          | ((riseThreshold % 10) << _EMU_VMONAVDDCTRL_RISETHRESFINE_SHIFT)
                          | ((fallThreshold / 10) << _EMU_VMONAVDDCTRL_FALLTHRESCOARSE_SHIFT)
                          | ((fallThreshold % 10) << _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 */

#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
/***************************************************************************//**
 * @brief
 *   Adjust the bias refresh rate
 *
 * @details
 *   This function is only meant to be used under high-temperature operation on
 *   EFR32xG1 and EFM32xG1 devices. Adjusting the bias mode will
 *   increase the typical current consumption. See application note 1027
 *   and errata documents for further details.
 *
 * @param [in] mode
 *   The new bias refresh rate
 ******************************************************************************/
void EMU_SetBiasMode(EMU_BiasMode_TypeDef mode)
{
#define EMU_TESTLOCK         (*(volatile uint32_t *) (EMU_BASE + 0x190))
#define EMU_BIASCONF         (*(volatile uint32_t *) (EMU_BASE + 0x164))
#define EMU_BIASTESTCTRL     (*(volatile uint32_t *) (EMU_BASE + 0x19C))
#define CMU_ULFRCOCTRL       (*(volatile uint32_t *) (CMU_BASE + 0x03C))

  uint32_t freq = 0x2u;
  bool emuTestLocked = false;

  if (mode == emuBiasMode_1KHz) {
    freq = 0x0u;
  }

  if (EMU_TESTLOCK == 0x1u) {
    emuTestLocked = true;
    EMU_TESTLOCK = 0xADE8u;
  }

  if (mode == emuBiasMode_Continuous) {
    EMU_BIASCONF &= ~0x74u;
  } else {
    EMU_BIASCONF |= 0x74u;
  }

  EMU_BIASTESTCTRL |= 0x8u;
  CMU_ULFRCOCTRL    = (CMU_ULFRCOCTRL & ~0xC00u)
                      | ((freq & 0x3u) << 10u);
  EMU_BIASTESTCTRL &= ~0x8u;

  if (emuTestLocked) {
    EMU_TESTLOCK = 0u;
  }
}
#endif

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