blob: 556ec6ba22fd37d61dae21c52662e66a3a7325de [file] [log] [blame]
/***************************************************************************//**
* @file sleep.c
* @brief Energy Modes management driver.
* @version 4.2.1
* @details
* This is a energy modes management module consisting of sleep.c and sleep.h
* source files. The main purpose of the module is to ease energy
* optimization with a simple API. The module allows the system to always sleep
* in the lowest possible energy mode. Users could set up callbacks that are
* being called before and after each and every sleep. A counting semaphore is
* available for each low energy mode (EM1/EM2/EM3) to protect certain system
* states from being corrupted. This semaphore has limit set to maximum 255 locks.
*
* The module provides the following public API to the users:
* SLEEP_Init()
* SLEEP_Sleep()
* SLEEP_SleepBlockBegin()
* SLEEP_SleepBlockEnd()
* SLEEP_ForceSleepInEM4()
*
*******************************************************************************
* @section License
* <b>(C) Copyright 2014 Silicon Labs, http://www.silabs.com</b>
*******************************************************************************
*
* This file is licensed under the Silabs License Agreement. See the file
* "Silabs_License_Agreement.txt" for details. Before using this software for
* any purpose, you must agree to the terms of that agreement.
*
******************************************************************************/
/* Chip specific header file(s). */
#include "em_device.h"
#include "em_assert.h"
#include "em_int.h"
#include "em_rmu.h"
#include "em_emu.h"
/* Module header file(s). */
#include "sleep.h"
/* stdlib is needed for NULL definition */
#include <stdlib.h>
/***************************************************************************//**
* @addtogroup EM_Drivers
* @{
******************************************************************************/
/***************************************************************************//**
* @addtogroup SLEEP
* @brief Energy Modes management driver.
* @details
* This is a energy modes management module consisting of sleep.c and sleep.h
* source files. The main purpose of the module is to ease energy
* optimization with a simple API. The module allows the system to always sleep
* in the lowest possible energy mode. Users could set up callbacks that are
* being called before and after each and every sleep. A counting semaphore is
* available for each low energy mode (EM1/EM2/EM3) to protect certain system
* states from being corrupted. This semaphore has limit set to maximum 255 locks.
* @{
******************************************************************************/
/*******************************************************************************
******************************* MACROS ************************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/* Number of low energy modes (EM1, EM2, EM3). Note: EM4 sleep/wakeup is handled
* differently therefore it is not part of the list! */
#define SLEEP_NUMOF_LOW_ENERGY_MODES 3U
/*******************************************************************************
****************************** TYPEDEFS ***********************************
******************************************************************************/
/*******************************************************************************
****************************** CONSTANTS **********************************
******************************************************************************/
/*******************************************************************************
******************************* STATICS ***********************************
******************************************************************************/
/* Callback functions to call before and after sleep. */
static SLEEP_CbFuncPtr_t sleepCallback = NULL;
static SLEEP_CbFuncPtr_t wakeUpCallback = NULL;
/* Sleep block counter array representing the nested sleep blocks for the low
* energy modes (EM1/EM2/EM3). Array index 0 corresponds to EM1, 1 to EM2 and 2
* to EM3 accordingly.
*
* Note:
* - EM4 sleep/wakeup is handled differently therefore it is not part of the
* list!
* - Max. number of sleep block nesting is 255. */
static uint8_t sleepBlockCnt[SLEEP_NUMOF_LOW_ENERGY_MODES];
/*******************************************************************************
****************************** PROTOTYPES *********************************
******************************************************************************/
static void SLEEP_EnterEMx(SLEEP_EnergyMode_t eMode);
//static SLEEP_EnergyMode_t SLEEP_LowestEnergyModeGet(void);
/** @endcond */
/*******************************************************************************
*************************** GLOBAL FUNCTIONS ******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Initialize the Sleep module.
*
* @details
* Use this function to initialize the Sleep module, should be called
* only once! Pointers to sleep and wake-up callback functions shall be
* provided when calling this function.
* If SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to true, this function checks
* for the cause of the reset that implicitly called it and calls the wakeup
* callback if the reset was a wakeup from EM4 (does not work on Gecko MCU).
*
* @param[in] pSleepCb
* Pointer to the callback function that is being called before the device is
* going to sleep.
*
* @param[in] pWakeUpCb
* Pointer to the callback function that is being called after wake up.
******************************************************************************/
void SLEEP_Init(SLEEP_CbFuncPtr_t pSleepCb, SLEEP_CbFuncPtr_t pWakeUpCb)
{
/* Initialize callback functions. */
sleepCallback = pSleepCb;
wakeUpCallback = pWakeUpCb;
/* Reset sleep block counters. Note: not using for() saves code! */
sleepBlockCnt[0U] = 0U;
sleepBlockCnt[1U] = 0U;
sleepBlockCnt[2U] = 0U;
#if (SLEEP_EM4_WAKEUP_CALLBACK_ENABLED == true) && defined(RMU_RSTCAUSE_EM4WURST)
/* Check if the Init() happened after an EM4 reset. */
if (RMU_ResetCauseGet() & RMU_RSTCAUSE_EM4WURST)
{
/* Clear the cause of the reset. */
RMU_ResetCauseClear();
/* Call wakeup callback with EM4 parameter. */
if (NULL != wakeUpCallback)
{
wakeUpCallback(sleepEM4);
}
}
#endif
}
/***************************************************************************//**
* @brief
* Sets the system to sleep into the lowest possible energy mode.
*
* @details
* This function takes care of the system states protected by the sleep block
* provided by SLEEP_SleepBlockBegin() / SLEEP_SleepBlockEnd(). It allows
* the system to go into the lowest possible energy mode that the device can
* be set into at the time of the call of this function.
* This function will not go lower than EM3 because leaving EM4 requires
* resetting MCU. To enter into EM4 call SLEEP_ForceSleepInEM4().
*
* @return
* Energy Mode that was entered. Possible values:
* @li sleepEM0
* @li sleepEM1
* @li sleepEM2
* @li sleepEM3
******************************************************************************/
SLEEP_EnergyMode_t SLEEP_Sleep(void)
{
SLEEP_EnergyMode_t allowedEM;
INT_Disable();
allowedEM = SLEEP_LowestEnergyModeGet();
if ((allowedEM >= sleepEM1) && (allowedEM <= sleepEM3))
{
SLEEP_EnterEMx(allowedEM);
}
else
{
allowedEM = sleepEM0;
}
INT_Enable();
return(allowedEM);
}
/***************************************************************************//**
* @brief
* Force the device to go to EM4 without doing any checks.
*
* @details
* This function unblocks the low energy sleep block then goes to EM4.
*
* @note
* Regular RAM is not retained in EM4 and the wake up causes a reset.
* If the configuration option SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to
* true, the SLEEP_Init() function checks for the reset cause and calls the
* EM4 wakeup callback.
******************************************************************************/
void SLEEP_ForceSleepInEM4(void)
{
#if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
/* Unblock the EM2/EM3/EM4 block in the EMU. */
EMU_EM2UnBlock();
#endif
/* Request entering to EM4. */
SLEEP_EnterEMx(sleepEM4);
}
/***************************************************************************//**
* @brief
* Begin sleep block in the requested energy mode.
*
* @details
* Blocking a critical system state from a certain energy mode makes sure that
* the system is not set to that energy mode while the block is not being
* released.
* Every SLEEP_SleepBlockBegin() increases the corresponding counter and
* every SLEEP_SleepBlockEnd() decreases it.
*
* Example:\code
* SLEEP_SleepBlockBegin(sleepEM2); // do not allow EM2 or higher
* // do some stuff that requires EM1 at least, like ADC sampling
* SLEEP_SleepBlockEnd(sleepEM2); // remove restriction for EM2\endcode
*
* @note
* Be aware that there is limit of maximum blocks nesting to 255.
*
* @param[in] eMode
* Energy mode to begin to block. Possible values:
* @li sleepEM1 - Begin to block the system from being set to EM1 (and EM2..4).
* @li sleepEM2 - Begin to block the system from being set to EM2 (and EM3/EM4).
* @li sleepEM3 - Begin to block the system from being set to EM3 (and EM4).
******************************************************************************/
void SLEEP_SleepBlockBegin(SLEEP_EnergyMode_t eMode)
{
EFM_ASSERT((eMode >= sleepEM1) && (eMode < sleepEM4));
EFM_ASSERT((sleepBlockCnt[(uint8_t) eMode - 1U]) < 255U);
/* Increase the sleep block counter of the selected energy mode. */
sleepBlockCnt[(uint8_t) eMode - 1U]++;
#if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
/* Block EM2/EM3 sleep if the EM2 block begins. */
if (eMode == sleepEM2)
{
EMU_EM2Block();
}
#endif
}
/***************************************************************************//**
* @brief
* End sleep block in the requested energy mode.
*
* @details
* Release restriction for entering certain energy mode. Every call of this
* function reduce blocking counter by 1. Once the counter for specific energy
* mode is 0 and all counters for lower energy modes are 0 as well, using
* particular energy mode is allowed.
* Every SLEEP_SleepBlockBegin() increases the corresponding counter and
* every SLEEP_SleepBlockEnd() decreases it.
*
* Example:\code
* // at start all energy modes are allowed
* SLEEP_SleepBlockBegin(sleepEM2); // EM2, EM3, EM4 are blocked
* SLEEP_SleepBlockBegin(sleepEM1); // EM1, EM2, EM3, EM4 are blocked
* SLEEP_SleepBlockBegin(sleepEM1); // EM1, EM2, EM3, EM4 are blocked
* SLEEP_SleepBlockEnd(sleepEM2); // still EM1, EM2, EM3, EM4 are blocked
* SLEEP_SleepBlockEnd(sleepEM1); // still EM1, EM2, EM3, EM4 are blocked
* SLEEP_SleepBlockEnd(sleepEM1); // all energy modes are allowed now\endcode
*
* @param[in] eMode
* Energy mode to end to block. Possible values:
* @li sleepEM1 - End to block the system from being set to EM1 (and EM2..4).
* @li sleepEM2 - End to block the system from being set to EM2 (and EM3/EM4).
* @li sleepEM3 - End to block the system from being set to EM3 (and EM4).
******************************************************************************/
void SLEEP_SleepBlockEnd(SLEEP_EnergyMode_t eMode)
{
EFM_ASSERT((eMode >= sleepEM1) && (eMode < sleepEM4));
/* Decrease the sleep block counter of the selected energy mode. */
if (sleepBlockCnt[(uint8_t) eMode - 1U] > 0U)
{
sleepBlockCnt[(uint8_t) eMode - 1U]--;
}
#if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
/* Check if the EM2/EM3 block should be unblocked in the EMU. */
if (0U == sleepBlockCnt[(uint8_t) sleepEM2 - 1U])
{
EMU_EM2UnBlock();
}
#endif
}
/***************************************************************************//**
* @brief
* Gets the lowest energy mode that the system is allowed to be set to.
*
* @details
* This function uses the low energy mode block counters to determine the
* lowest possible that the system is allowed to be set to.
*
* @return
* Lowest energy mode that the system can be set to. Possible values:
* @li sleepEM0
* @li sleepEM1
* @li sleepEM2
* @li sleepEM3
******************************************************************************/
SLEEP_EnergyMode_t SLEEP_LowestEnergyModeGet(void)
{
SLEEP_EnergyMode_t tmpLowestEM = sleepEM0;
/* Check which is the lowest energy mode that the system can be set to. */
if (0U == sleepBlockCnt[(uint8_t) sleepEM1 - 1U])
{
tmpLowestEM = sleepEM1;
if (0U == sleepBlockCnt[(uint8_t) sleepEM2 - 1U])
{
tmpLowestEM = sleepEM2;
if (0U == sleepBlockCnt[(uint8_t) sleepEM3 - 1U])
{
tmpLowestEM = sleepEM3;
}
}
}
/* Compare with the default lowest energy mode setting. */
if (SLEEP_LOWEST_ENERGY_MODE_DEFAULT < tmpLowestEM)
{
tmpLowestEM = SLEEP_LOWEST_ENERGY_MODE_DEFAULT;
}
return tmpLowestEM;
}
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief
* Call the callbacks and enter the requested energy mode.
*
* @details
* This function is not part of the API, therefore it shall not be called by
* the user directly as it doesn not have any checks if the system is ready
* for sleep!
*
* @note
* The EM4 wakeup callback is not being called from this function because
* waking up from EM4 causes a reset.
* If SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to true, SLEEP_Init() function
* checks for the cause of the reset and calls the wakeup callback if the
* reset was a wakeup from EM4.
******************************************************************************/
static void SLEEP_EnterEMx(SLEEP_EnergyMode_t eMode)
{
EFM_ASSERT((eMode > sleepEM0) && (eMode <= sleepEM4));
/* Call sleepCallback() before going to sleep. */
if (NULL != sleepCallback)
{
/* Call the callback before going to sleep. */
sleepCallback(eMode);
}
/* Enter the requested energy mode. */
switch (eMode)
{
case sleepEM1:
{
EMU_EnterEM1();
} break;
case sleepEM2:
{
EMU_EnterEM2(true);
} break;
case sleepEM3:
{
EMU_EnterEM3(true);
} break;
case sleepEM4:
{
EMU_EnterEM4();
} break;
default:
{
/* Don't do anything, stay in EM0. */
} break;
}
/* Call the callback after waking up from sleep. */
if (NULL != wakeUpCallback)
{
wakeUpCallback(eMode);
}
}
/** @endcond */
/** @} (end addtogroup SLEEP */
/** @} (end addtogroup EM_Drivers) */