1 /***************************************************************************//**
\r
3 * @brief Energy Modes management driver.
\r
6 * This is a energy modes management module consisting of sleep.c and sleep.h
\r
7 * source files. The main purpose of the module is to ease energy
\r
8 * optimization with a simple API. The module allows the system to always sleep
\r
9 * in the lowest possible energy mode. Users could set up callbacks that are
\r
10 * being called before and after each and every sleep. A counting semaphore is
\r
11 * available for each low energy mode (EM1/EM2/EM3) to protect certain system
\r
12 * states from being corrupted. This semaphore has limit set to maximum 255 locks.
\r
14 * The module provides the following public API to the users:
\r
17 * SLEEP_SleepBlockBegin()
\r
18 * SLEEP_SleepBlockEnd()
\r
19 * SLEEP_ForceSleepInEM4()
\r
21 *******************************************************************************
\r
23 * <b>(C) Copyright 2014 Silicon Labs, http://www.silabs.com</b>
\r
24 *******************************************************************************
\r
26 * This file is licensed under the Silabs License Agreement. See the file
\r
27 * "Silabs_License_Agreement.txt" for details. Before using this software for
\r
28 * any purpose, you must agree to the terms of that agreement.
\r
30 ******************************************************************************/
\r
33 /* Chip specific header file(s). */
\r
34 #include "em_device.h"
\r
35 #include "em_assert.h"
\r
40 /* Module header file(s). */
\r
43 /* stdlib is needed for NULL definition */
\r
46 /***************************************************************************//**
\r
47 * @addtogroup EM_Drivers
\r
49 ******************************************************************************/
\r
51 /***************************************************************************//**
\r
53 * @brief Energy Modes management driver.
\r
55 * This is a energy modes management module consisting of sleep.c and sleep.h
\r
56 * source files. The main purpose of the module is to ease energy
\r
57 * optimization with a simple API. The module allows the system to always sleep
\r
58 * in the lowest possible energy mode. Users could set up callbacks that are
\r
59 * being called before and after each and every sleep. A counting semaphore is
\r
60 * available for each low energy mode (EM1/EM2/EM3) to protect certain system
\r
61 * states from being corrupted. This semaphore has limit set to maximum 255 locks.
\r
63 ******************************************************************************/
\r
65 /*******************************************************************************
\r
66 ******************************* MACROS ************************************
\r
67 ******************************************************************************/
\r
69 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
\r
71 /* Number of low energy modes (EM1, EM2, EM3). Note: EM4 sleep/wakeup is handled
\r
72 * differently therefore it is not part of the list! */
\r
73 #define SLEEP_NUMOF_LOW_ENERGY_MODES 3U
\r
77 /*******************************************************************************
\r
78 ****************************** TYPEDEFS ***********************************
\r
79 ******************************************************************************/
\r
82 /*******************************************************************************
\r
83 ****************************** CONSTANTS **********************************
\r
84 ******************************************************************************/
\r
87 /*******************************************************************************
\r
88 ******************************* STATICS ***********************************
\r
89 ******************************************************************************/
\r
91 /* Callback functions to call before and after sleep. */
\r
92 static SLEEP_CbFuncPtr_t sleepCallback = NULL;
\r
93 static SLEEP_CbFuncPtr_t wakeUpCallback = NULL;
\r
95 /* Sleep block counter array representing the nested sleep blocks for the low
\r
96 * energy modes (EM1/EM2/EM3). Array index 0 corresponds to EM1, 1 to EM2 and 2
\r
97 * to EM3 accordingly.
\r
100 * - EM4 sleep/wakeup is handled differently therefore it is not part of the
\r
102 * - Max. number of sleep block nesting is 255. */
\r
103 static uint8_t sleepBlockCnt[SLEEP_NUMOF_LOW_ENERGY_MODES];
\r
105 /*******************************************************************************
\r
106 ****************************** PROTOTYPES *********************************
\r
107 ******************************************************************************/
\r
109 static void SLEEP_EnterEMx(SLEEP_EnergyMode_t eMode);
\r
110 //static SLEEP_EnergyMode_t SLEEP_LowestEnergyModeGet(void);
\r
114 /*******************************************************************************
\r
115 *************************** GLOBAL FUNCTIONS ******************************
\r
116 ******************************************************************************/
\r
118 /***************************************************************************//**
\r
120 * Initialize the Sleep module.
\r
123 * Use this function to initialize the Sleep module, should be called
\r
124 * only once! Pointers to sleep and wake-up callback functions shall be
\r
125 * provided when calling this function.
\r
126 * If SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to true, this function checks
\r
127 * for the cause of the reset that implicitly called it and calls the wakeup
\r
128 * callback if the reset was a wakeup from EM4 (does not work on Gecko MCU).
\r
130 * @param[in] pSleepCb
\r
131 * Pointer to the callback function that is being called before the device is
\r
134 * @param[in] pWakeUpCb
\r
135 * Pointer to the callback function that is being called after wake up.
\r
136 ******************************************************************************/
\r
137 void SLEEP_Init(SLEEP_CbFuncPtr_t pSleepCb, SLEEP_CbFuncPtr_t pWakeUpCb)
\r
139 /* Initialize callback functions. */
\r
140 sleepCallback = pSleepCb;
\r
141 wakeUpCallback = pWakeUpCb;
\r
143 /* Reset sleep block counters. Note: not using for() saves code! */
\r
144 sleepBlockCnt[0U] = 0U;
\r
145 sleepBlockCnt[1U] = 0U;
\r
146 sleepBlockCnt[2U] = 0U;
\r
148 #if (SLEEP_EM4_WAKEUP_CALLBACK_ENABLED == true) && defined(RMU_RSTCAUSE_EM4WURST)
\r
149 /* Check if the Init() happened after an EM4 reset. */
\r
150 if (RMU_ResetCauseGet() & RMU_RSTCAUSE_EM4WURST)
\r
152 /* Clear the cause of the reset. */
\r
153 RMU_ResetCauseClear();
\r
154 /* Call wakeup callback with EM4 parameter. */
\r
155 if (NULL != wakeUpCallback)
\r
157 wakeUpCallback(sleepEM4);
\r
164 /***************************************************************************//**
\r
166 * Sets the system to sleep into the lowest possible energy mode.
\r
169 * This function takes care of the system states protected by the sleep block
\r
170 * provided by SLEEP_SleepBlockBegin() / SLEEP_SleepBlockEnd(). It allows
\r
171 * the system to go into the lowest possible energy mode that the device can
\r
172 * be set into at the time of the call of this function.
\r
173 * This function will not go lower than EM3 because leaving EM4 requires
\r
174 * resetting MCU. To enter into EM4 call SLEEP_ForceSleepInEM4().
\r
177 * Energy Mode that was entered. Possible values:
\r
182 ******************************************************************************/
\r
183 SLEEP_EnergyMode_t SLEEP_Sleep(void)
\r
185 SLEEP_EnergyMode_t allowedEM;
\r
189 allowedEM = SLEEP_LowestEnergyModeGet();
\r
191 if ((allowedEM >= sleepEM1) && (allowedEM <= sleepEM3))
\r
193 SLEEP_EnterEMx(allowedEM);
\r
197 allowedEM = sleepEM0;
\r
206 /***************************************************************************//**
\r
208 * Force the device to go to EM4 without doing any checks.
\r
211 * This function unblocks the low energy sleep block then goes to EM4.
\r
214 * Regular RAM is not retained in EM4 and the wake up causes a reset.
\r
215 * If the configuration option SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to
\r
216 * true, the SLEEP_Init() function checks for the reset cause and calls the
\r
217 * EM4 wakeup callback.
\r
218 ******************************************************************************/
\r
219 void SLEEP_ForceSleepInEM4(void)
\r
221 #if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
\r
222 /* Unblock the EM2/EM3/EM4 block in the EMU. */
\r
226 /* Request entering to EM4. */
\r
227 SLEEP_EnterEMx(sleepEM4);
\r
230 /***************************************************************************//**
\r
232 * Begin sleep block in the requested energy mode.
\r
235 * Blocking a critical system state from a certain energy mode makes sure that
\r
236 * the system is not set to that energy mode while the block is not being
\r
238 * Every SLEEP_SleepBlockBegin() increases the corresponding counter and
\r
239 * every SLEEP_SleepBlockEnd() decreases it.
\r
242 * SLEEP_SleepBlockBegin(sleepEM2); // do not allow EM2 or higher
\r
243 * // do some stuff that requires EM1 at least, like ADC sampling
\r
244 * SLEEP_SleepBlockEnd(sleepEM2); // remove restriction for EM2\endcode
\r
247 * Be aware that there is limit of maximum blocks nesting to 255.
\r
250 * Energy mode to begin to block. Possible values:
\r
251 * @li sleepEM1 - Begin to block the system from being set to EM1 (and EM2..4).
\r
252 * @li sleepEM2 - Begin to block the system from being set to EM2 (and EM3/EM4).
\r
253 * @li sleepEM3 - Begin to block the system from being set to EM3 (and EM4).
\r
254 ******************************************************************************/
\r
255 void SLEEP_SleepBlockBegin(SLEEP_EnergyMode_t eMode)
\r
257 EFM_ASSERT((eMode >= sleepEM1) && (eMode < sleepEM4));
\r
258 EFM_ASSERT((sleepBlockCnt[(uint8_t) eMode - 1U]) < 255U);
\r
260 /* Increase the sleep block counter of the selected energy mode. */
\r
261 sleepBlockCnt[(uint8_t) eMode - 1U]++;
\r
263 #if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
\r
264 /* Block EM2/EM3 sleep if the EM2 block begins. */
\r
265 if (eMode == sleepEM2)
\r
272 /***************************************************************************//**
\r
274 * End sleep block in the requested energy mode.
\r
277 * Release restriction for entering certain energy mode. Every call of this
\r
278 * function reduce blocking counter by 1. Once the counter for specific energy
\r
279 * mode is 0 and all counters for lower energy modes are 0 as well, using
\r
280 * particular energy mode is allowed.
\r
281 * Every SLEEP_SleepBlockBegin() increases the corresponding counter and
\r
282 * every SLEEP_SleepBlockEnd() decreases it.
\r
285 * // at start all energy modes are allowed
\r
286 * SLEEP_SleepBlockBegin(sleepEM2); // EM2, EM3, EM4 are blocked
\r
287 * SLEEP_SleepBlockBegin(sleepEM1); // EM1, EM2, EM3, EM4 are blocked
\r
288 * SLEEP_SleepBlockBegin(sleepEM1); // EM1, EM2, EM3, EM4 are blocked
\r
289 * SLEEP_SleepBlockEnd(sleepEM2); // still EM1, EM2, EM3, EM4 are blocked
\r
290 * SLEEP_SleepBlockEnd(sleepEM1); // still EM1, EM2, EM3, EM4 are blocked
\r
291 * SLEEP_SleepBlockEnd(sleepEM1); // all energy modes are allowed now\endcode
\r
294 * Energy mode to end to block. Possible values:
\r
295 * @li sleepEM1 - End to block the system from being set to EM1 (and EM2..4).
\r
296 * @li sleepEM2 - End to block the system from being set to EM2 (and EM3/EM4).
\r
297 * @li sleepEM3 - End to block the system from being set to EM3 (and EM4).
\r
298 ******************************************************************************/
\r
299 void SLEEP_SleepBlockEnd(SLEEP_EnergyMode_t eMode)
\r
301 EFM_ASSERT((eMode >= sleepEM1) && (eMode < sleepEM4));
\r
303 /* Decrease the sleep block counter of the selected energy mode. */
\r
304 if (sleepBlockCnt[(uint8_t) eMode - 1U] > 0U)
\r
306 sleepBlockCnt[(uint8_t) eMode - 1U]--;
\r
309 #if (SLEEP_HW_LOW_ENERGY_BLOCK_ENABLED == true)
\r
310 /* Check if the EM2/EM3 block should be unblocked in the EMU. */
\r
311 if (0U == sleepBlockCnt[(uint8_t) sleepEM2 - 1U])
\r
318 /***************************************************************************//**
\r
320 * Gets the lowest energy mode that the system is allowed to be set to.
\r
323 * This function uses the low energy mode block counters to determine the
\r
324 * lowest possible that the system is allowed to be set to.
\r
327 * Lowest energy mode that the system can be set to. Possible values:
\r
332 ******************************************************************************/
\r
333 SLEEP_EnergyMode_t SLEEP_LowestEnergyModeGet(void)
\r
335 SLEEP_EnergyMode_t tmpLowestEM = sleepEM0;
\r
337 /* Check which is the lowest energy mode that the system can be set to. */
\r
338 if (0U == sleepBlockCnt[(uint8_t) sleepEM1 - 1U])
\r
340 tmpLowestEM = sleepEM1;
\r
341 if (0U == sleepBlockCnt[(uint8_t) sleepEM2 - 1U])
\r
343 tmpLowestEM = sleepEM2;
\r
344 if (0U == sleepBlockCnt[(uint8_t) sleepEM3 - 1U])
\r
346 tmpLowestEM = sleepEM3;
\r
351 /* Compare with the default lowest energy mode setting. */
\r
352 if (SLEEP_LOWEST_ENERGY_MODE_DEFAULT < tmpLowestEM)
\r
354 tmpLowestEM = SLEEP_LOWEST_ENERGY_MODE_DEFAULT;
\r
357 return tmpLowestEM;
\r
360 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
\r
362 /***************************************************************************//**
\r
364 * Call the callbacks and enter the requested energy mode.
\r
367 * This function is not part of the API, therefore it shall not be called by
\r
368 * the user directly as it doesn not have any checks if the system is ready
\r
372 * The EM4 wakeup callback is not being called from this function because
\r
373 * waking up from EM4 causes a reset.
\r
374 * If SLEEP_EM4_WAKEUP_CALLBACK_ENABLED is set to true, SLEEP_Init() function
\r
375 * checks for the cause of the reset and calls the wakeup callback if the
\r
376 * reset was a wakeup from EM4.
\r
377 ******************************************************************************/
\r
378 static void SLEEP_EnterEMx(SLEEP_EnergyMode_t eMode)
\r
380 EFM_ASSERT((eMode > sleepEM0) && (eMode <= sleepEM4));
\r
382 /* Call sleepCallback() before going to sleep. */
\r
383 if (NULL != sleepCallback)
\r
385 /* Call the callback before going to sleep. */
\r
386 sleepCallback(eMode);
\r
389 /* Enter the requested energy mode. */
\r
399 EMU_EnterEM2(true);
\r
404 EMU_EnterEM3(true);
\r
414 /* Don't do anything, stay in EM0. */
\r
418 /* Call the callback after waking up from sleep. */
\r
419 if (NULL != wakeUpCallback)
\r
421 wakeUpCallback(eMode);
\r
426 /** @} (end addtogroup SLEEP */
\r
427 /** @} (end addtogroup EM_Drivers) */
\r