/***************************************************************************//** * @file * @brief Energy Management Unit (EMU) Peripheral API for EFM32 * @author Energy Micro AS * @version 2.3.2 ******************************************************************************* * @section License * (C) Copyright 2010 Energy Micro AS, http://www.energymicro.com ******************************************************************************* * * This source code is the property of Energy Micro AS. The source and compiled * code may only be used on Energy Micro "EFM32" microcontrollers. * * This copyright notice may not be removed from the source code nor changed. * * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Energy Micro AS has no * obligation to support this Software. Energy Micro AS 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. * * Energy Micro AS 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 "efm32_emu.h" #include "efm32_cmu.h" #include "efm32_assert.h" /***************************************************************************//** * @addtogroup EFM32_Library * @{ ******************************************************************************/ /***************************************************************************//** * @addtogroup EMU * @brief Energy Management Unit (EMU) Peripheral API for EFM32 * @{ ******************************************************************************/ /* 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 /******************************************************************************* ************************** LOCAL VARIABLES ******************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /** * CMU configured oscillator selection and oscillator enable status. When a * user configures oscillators, this varaiable shall shadow the configuration. * It is used by the EMU module in order to be able to restore the oscillator * config after having been in certain energy modes (since HW may automatically * alter config when going into an energy mode). It is the responsibility of * the CMU module to keep it up-to-date (or a user if not using the CMU API * for oscillator control). */ static uint16_t cmuStatus; /** @endcond */ /******************************************************************************* ************************** LOCAL FUNCTIONS ******************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /***************************************************************************//** * @brief * Restore oscillators and core clock after having been in EM2 or EM3. ******************************************************************************/ static void EMU_Restore(void) { uint32_t cmuLocked; /* Although we could use the CMU API for most of the below handling, we */ /* would like this function to be as efficient as possible. */ /* CMU registers may be locked */ cmuLocked = CMU->LOCK & CMU_LOCK_LOCKKEY_LOCKED; CMU_Unlock(); /* AUXHFRCO was automatically disabled (except if using debugger). */ /* HFXO was automatically disabled. */ /* LFRCO/LFXO were possibly disabled by SW in EM3. */ /* Restore according to status prior to entering EM. */ CMU->OSCENCMD = cmuStatus & (CMU_STATUS_AUXHFRCOENS | CMU_STATUS_HFXOENS | CMU_STATUS_LFRCOENS | CMU_STATUS_LFXOENS); /* Restore oscillator used for clocking core */ switch (cmuStatus & (CMU_STATUS_HFXOSEL | CMU_STATUS_HFRCOSEL | CMU_STATUS_LFXOSEL | CMU_STATUS_LFRCOSEL)) { case CMU_STATUS_LFRCOSEL: /* Wait for LFRCO to stabilize */ while (!(CMU->STATUS & CMU_STATUS_LFRCORDY)) ; CMU->CMD = CMU_CMD_HFCLKSEL_LFRCO; break; case CMU_STATUS_LFXOSEL: /* Wait for LFXO to stabilize */ while (!(CMU->STATUS & CMU_STATUS_LFXORDY)) ; CMU->CMD = CMU_CMD_HFCLKSEL_LFXO; break; case CMU_STATUS_HFXOSEL: /* Wait for HFXO to stabilize */ while (!(CMU->STATUS & CMU_STATUS_HFXORDY)) ; CMU->CMD = CMU_CMD_HFCLKSEL_HFXO; break; default: /* CMU_STATUS_HFRCOSEL */ /* If core clock was HFRCO core clock, it is automatically restored to */ /* state prior to entering energy mode. No need for further action. */ break; } /* If HFRCO was disabled before entering Energy Mode, turn it off again */ /* as it is automatically enabled by wake up */ if ( ! (cmuStatus & CMU_STATUS_HFRCOENS) ) { CMU->OSCENCMD = CMU_OSCENCMD_HFRCODIS; } /* Restore CMU register locking */ if (cmuLocked) { CMU_Lock(); } } /** @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. * * @param[in] restore * @li true - restore oscillators and clocks, see function details. * @li false - do not restore oscillators and clocks, see function details. * @par * The @p restore option should only be used if all clock control is done * via the CMU API. ******************************************************************************/ void EMU_EnterEM2(bool restore) { /* Auto-update CMU status just in case before entering energy mode. */ /* This variable is normally kept up-to-date by the CMU API. */ cmuStatus = (uint16_t)(CMU->STATUS); /* Enter Cortex-M3 deep sleep mode */ SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __WFI(); /* Restore oscillators/clocks if specified */ if (restore) { EMU_Restore(); } /* If not restoring, and original clock was not HFRCO, we have to */ /* update CMSIS core clock variable since core clock has changed */ /* to using HFRCO. */ else if (!(cmuStatus & CMU_STATUS_HFRCOSEL)) { SystemCoreClockUpdate(); } } /***************************************************************************//** * @brief * Enter energy mode 3 (EM3). * * @details * When entering EM3, the high frequency clocks are disabled by HW, ie HFXO, * HFRCO and AUXHFRCO (for AUXHFRCO, see exception note below). In addition, * the low frequency clocks, ie LFXO and LFRCO are disabled by SW. When * re-entering EM0, HFRCO is re-enabled and the core will be clocked by the * configured HFRCO band. This ensures a quick wakeup from EM3. * * However, prior to entering EM3, the core may have been using another * oscillator than HFRCO. The @p restore parameter gives the user the option * to restore all HF/LF oscillators according to state prior to entering EM3, * as well as the clock used to clock the core. This restore procedure is * handled by SW. However, since handled by SW, it will not be restored * before completing the interrupt function(s) waking up the core! * * @note * If restoring core clock to use an oscillator other than HFRCO, this * function will stall until the oscillator has stabilized. Stalling time * can be reduced by adding interrupt support detecting stable oscillator, * and an asynchronous switch to the original oscillator. See CMU * documentation. Such a feature is however outside the scope of the * implementation in this function. * @par * If HFXO/LFXO/LFRCO are re-enabled by this function, and NOT used to clock * the core, this function will not wait for those oscillators to stabilize. * This must be considered by the application if trying to use features * relying on those oscillators upon return. * @par * If a debugger is attached, the AUXHFRCO will not be disabled if enabled * upon entering EM3. It will thus remain enabled when returning to EM0 * regardless of the @p restore parameter. * * @param[in] restore * @li true - restore oscillators and clocks, see function details. * @li false - do not restore oscillators and clocks, see function details. * @par * The @p restore option should only be used if all clock control is done * via the CMU API. ******************************************************************************/ void EMU_EnterEM3(bool restore) { uint32_t cmuLocked; /* Auto-update CMU status just in case before entering energy mode. */ /* This variable is normally kept up-to-date by the CMU API. */ cmuStatus = (uint16_t)(CMU->STATUS); /* 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-M3 deep sleep mode */ SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __WFI(); /* Restore oscillators/clocks if specified */ if (restore) { EMU_Restore(); } /* If not restoring, and original clock was not HFRCO, we have to */ /* update CMSIS core clock variable since core clock has changed */ /* to using HFRCO. */ else if (!(cmuStatus & CMU_STATUS_HFRCOSEL)) { SystemCoreClockUpdate(); } } /***************************************************************************//** * @brief * Enter energy mode 4 (EM4). * * @note * Only a power on reset or external reset pin can wake the device from EM4. ******************************************************************************/ void EMU_EnterEM4(void) { int i; /* Make sure register write lock is disabled */ EMU->LOCK = EMU_LOCK_LOCKKEY_UNLOCK; for (i = 0; i < 4; i++) { EMU->CTRL = (2 << _EMU_CTRL_EM4CTRL_SHIFT); EMU->CTRL = (3 << _EMU_CTRL_EM4CTRL_SHIFT); } EMU->CTRL = (2 << _EMU_CTRL_EM4CTRL_SHIFT); } /***************************************************************************//** * @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 EFM32 reference manual for available * memory blocks for a device. * * @note * Only a reset can make the specified memory block(s) available for use * after having been powered down. Function will be void for devices not * supporting this feature. ******************************************************************************/ void EMU_MemPwrDown(uint32_t blocks) { #if defined(_EMU_MEMCTRL_RESETVALUE) EFM_ASSERT(blocks <= _EMU_MEMCTRL_MASK); EMU->MEMCTRL = blocks; #else (void)blocks; #endif } /***************************************************************************//** * @brief * Update EMU module with CMU oscillator selection/enable status. * * @details * When entering EM2 and EM3, the HW may change the core clock oscillator * used, as well as disabling some oscillators. The user may optionally select * to restore the oscillators after waking up from EM2 and EM3 through the * SW API. * * However, in order to support this in a safe way, the EMU module must * be kept up-to-date on the actual selected configuration. The CMU * module must keep the EMU module up-to-date. * * This function is mainly intended for internal use by the CMU module, * but if the applications changes oscillator configurations without * using the CMU API, this function can be used to keep the EMU module * up-to-date. ******************************************************************************/ void EMU_UpdateOscConfig(void) { /* Fetch current configuration */ cmuStatus = (uint16_t)(CMU->STATUS); } #if defined(_EFM32_GIANT_FAMILY) /***************************************************************************//** * @brief * Update EMU module with Energy Mode 4 configuration * * @param[in] em4init * Energy Mode 4 configuration structure ******************************************************************************/ void EMU_EM4Init(EMU_EM4Init_TypeDef *em4init) { 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; } /***************************************************************************//** * @brief * Configure BackUp Power Domain settings * * @note * stig note to self: Touches RMU->CTRL BUPD? * * @param[in] bupdInit * Backup power domain initialization structure ******************************************************************************/ void EMU_BUPDInit(EMU_BUPDInit_TypeDef *bupdInit) { uint32_t reg; EFM_ASSERT(bupdInit->inactiveThresRange < 4); EFM_ASSERT(bupdInit->inactiveThreshold < 4); EFM_ASSERT(bupdInit->activeThresRange < 4); EFM_ASSERT(bupdInit->activeThreshold < 4); /* 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| _EMU_BUINACT_BUENRANGE_MASK| _EMU_BUINACT_BUENTHRES_MASK); reg |= (bupdInit->inactivePower| (bupdInit->inactiveThresRange << _EMU_BUINACT_BUENRANGE_SHIFT)| (bupdInit->inactiveThreshold << _EMU_BUINACT_BUENTHRES_SHIFT)); EMU->BUINACT = reg; /* Set backup domain active mode configuration */ reg = EMU->BUACT & ~( _EMU_BUACT_PWRCON_MASK| _EMU_BUACT_BUEXRANGE_MASK| _EMU_BUACT_BUEXTHRES_MASK); reg |= (bupdInit->activePower| (bupdInit->activeThresRange << _EMU_BUACT_BUEXRANGE_SHIFT)| (bupdInit->activeThreshold << _EMU_BUACT_BUEXTHRES_SHIFT)); 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 */ BITBAND_Peripheral(&(RMU->CTRL), _RMU_CTRL_BURSTEN_SHIFT, !bupdInit->enable); } #endif /** @} (end addtogroup EMU) */ /** @} (end addtogroup EFM32_Library) */