/***************************************************************************//**
* @file
* @brief Power Manager API definition.
*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* SPDX-License-Identifier: Zlib
*
* The licensor of this software is Silicon Laboratories Inc.
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* 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. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 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.
*
******************************************************************************/
#ifndef SL_POWER_MANAGER_H
#define SL_POWER_MANAGER_H
#ifndef SL_POWER_MANAGER_DEBUG
#include "sl_power_manager_config.h"
#endif
#include "sl_slist.h"
#include "sl_status.h"
#include "sl_sleeptimer.h"
#include "sl_enum.h"
#include "em_core_generic.h"
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
/***************************************************************************//**
* @addtogroup power_manager Power Manager
*
* @details Power manager is a platform level software module that manages
* the system's energy modes. Its main purpose is to transition the system to a
* low energy mode when the processor has nothing to execute. The energy mode the
* system will transition to is determined each time the system goes to sleep
* using requirements. These requirements are set by the different software modules
* (drivers, stacks, application code, etc...). Power manager also ensures a
* strict control of some power hungry resources such as the high frequency
* external oscillator (normally called HFXO). Power manager also
* offers a notification mechanism through which any piece of software module can be
* notified of energy mode transitions through callbacks.
*
* @note Sleep Driver is deprecated. Use Power Manager for all sleep-related
* operations. See AN1358:
* Migrating from Sleep Driver to Power Manager for information on how
* to migrate from Sleep Driver to Power Manager.
* @note Emlib EMU functions EMU_EnterEM1()/EMU_EnterEM2()/EMU_EnterEM3() must not
* be used when the Power Manager is present. The Power Manager module must be
* the one deciding at which EM level the device sleeps to ensure the application
* properly works. Using both at the same time could lead to undefined behavior
* in the application.
*
* @details
* ## Initialization
*
* Power manager must be initialized prior to any call to power manager API.
* If sl_system is used, only sl_system_init() must be called, otherwise
* sl_power_manager_init() must be called manually. Note that power manager
* must be initialized after the clock(s), when initialized manually, as the
* power manager check which oscillators are used during the initialization phase.
*
* ## Add and remove requirements
*
* The drivers should add and remove energy mode requirements, at runtime, on the
* lowest energy mode for them depending on their state. When calling
* sl_power_manager_sleep(), the lowest possible Energy mode will be automatically
* selected.
*
* It is possible to add and remove requirements from ISR. If a specific energy mode
* is required in the ISR, but not required to generate the interrupt, a requirement
* on the energy mode can be added from the ISR. It is guaranteed that the associated
* clock will be active once sl_power_manager_add_requirement() returns. The EM
* requirement can be also be removed from an ISR.
*
* ## Subscribe to events
*
* It possible to get notified when the system transition from a power level to
* another power level. This can allow to do some operations depending on which level
* the system goes, such as saving/restoring context.
*
* ## Sleep
*
* When the software has no more operation and only need to wait for an event, the
* software must call sl_power_manager_sleep(). This is automatically done when the
* kernel is present, but it needs to be called from the super loop in a baremetal
* project.
*
* ## Query callback functions
*
* ### Is OK to sleep
*
* Between the time `sl_power_manager_sleep` is called and the MCU is really put
* in a lower Energy mode, it is possible that an ISR occur and require the system
* to resume at that time instead of sleeping. So a callback is called in a critical
* section to validate that the MCU can go to sleep.
*
* In case of an application that runs on an RTOS, the RTOS will take care of determining
* if it is ok to sleep. In case of a baremetal application, the function `sl_power_manager_is_ok_to_sleep()`
* will be generated automatically by Simplicity Studio's wizard.
* The function will look at multiple software modules from the SDK to take a decision.
* The application can contribute to the decision by defining the function `app_is_ok_to_sleep()`.
* If any of the software modules (including the application via `app_is_ok_to_sleep()`) return false,
* the process of entering in sleep will be aborted.
*
* ### Sleep on ISR exit
*
* When the system enters sleep, the only way to wake it up is via an interrupt or
* exception. By default, power manager will assume that when an interrupt
* occurs and the corresponding ISR has been executed, the system must not go back
* to sleep. However, in the case where all the processing related to this interrupt
* is performed in the ISR, it is possible to go back to sleep by using this hook.
*
* In case of an application that runs on an RTOS, the RTOS will take care of determining
* if the system can go back to sleep on ISR exit. Power manager will ensure the system resumes
* its operations as soon as a task is resumed, posted or that its delay expires.
* In case of a baremetal application, the function `sl_power_manager_sleep_on_isr_exit()` will be generated
* automatically by Simplicity Studio's wizard. The function will look at multiple software modules from the SDK
* to take a decision. The application can contribute to the decision by defining the
* function `app_sleep_on_isr_exit()`.
* The generated function will take a decision based on the value returned by the different software modules
* (including the application via `app_sleep_on_isr_exit()`):
*
* `SL_POWER_MANAGER_IGNORE`: if the software module did not cause the system wakeup and/or doesn't want to contribute to the decision.
* `SL_POWER_MANAGER_SLEEP`: if the software module did cause the system wakeup, but the system should go back to sleep.
* `SL_POWER_MANAGER_WAKEUP`: if the software module did cause the system wakeup, and the system should not go back to sleep.
*
* If any software module returned `SL_POWER_MANAGER_SLEEP` and none returned `SL_POWER_MANAGER_WAKEUP`,
* the system will go back to sleep. Any other combination will cause the system not to go back to sleep.
*
* ### Debugging feature
*
* By setting the configuration define SL_POWER_MANAGER_DEBUG to 1, it is possible
* to record the requirements currently set and their owner. It is possible to print
* at any time a table that lists all the added requirements and their owner. This
* table can be printed by caling the function
* sl_power_manager_debug_print_em_requirements().
* Make sure to add the following define
* ```
* #define CURRENT_MODULE_NAME ""
* ```
* to any application code source file that adds and removes requirements.
*
* ## Usage Example
*
* ```
* #define EM_EVENT_MASK_ALL (SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM0 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM0 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM1 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM1 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM2 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM2 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM3 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3)
*
* sl_power_manager_em_transition_event_handle_t event_handle;
* sl_power_manager_em_transition_event_info_t event_info = {
* .event_mask = EM_EVENT_MASK_ALL,
* .on_event = my_events_callback,
* }
*
* void main(void)
* {
* // Initialize power manager; not needed if sl_system_init() is used.
* sl_power_manager_init();
*
* // Limit sleep level to EM1
* sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM1);
*
* // Subscribe to all event types; get notified for every power transition.
* sl_power_manager_subscribe_em_transition_event(&event_handle, &event_info);
* while (1) {
* // Actions
* [...]
* if (completed) {
* // Remove energy mode requirement, can go to EM2 or EM3 now, depending on the configuration
* sl_power_manager_remove_em_requirement(SL_POWER_MANAGER_EM1);
* }
*
* // Sleep to lowest possible energy mode; This call is not needed when using the kernel.
* sl_power_manager_sleep();
* // Will resume after an interrupt or exception
* }
* }
*
* void my_events_callback(sl_power_manager_em_t from,
* sl_power_manager_em_t to)
* {
* printf("Event:%s-%s\r\n", string_lookup_table[from], string_lookup_table[to]);
* }
* ```
*
* @{
******************************************************************************/
// -----------------------------------------------------------------------------
// Defines
// Current module name for debugging features
#ifndef CURRENT_MODULE_NAME
#define CURRENT_MODULE_NAME "Anonymous" ///< current module name
#endif
// Power transition events
#define SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM0 (1 << 0) ///< sl power manager event transition entering em0
#define SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM0 (1 << 1) ///< sl power manager event transition leaving em0
#define SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM1 (1 << 2) ///< sl power manager event transition entering em1
#define SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM1 (1 << 3) ///< sl power manager event transition leaving em1
#define SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM2 (1 << 4) ///< sl power manager event transition entering em2
#define SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM2 (1 << 5) ///< sl power manager event transition leaving em2
#define SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM3 (1 << 6) ///< sl power manager event transition entering em3
#define SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3 (1 << 7) ///< sl power manager event transition leaving em3
// -----------------------------------------------------------------------------
// Data Types
/// @brief Energy modes
typedef enum {
SL_POWER_MANAGER_EM0 = 0, ///< Run Mode (Energy Mode 0)
SL_POWER_MANAGER_EM1, ///< Sleep Mode (Energy Mode 1)
SL_POWER_MANAGER_EM2, ///< Deep Sleep Mode (Energy Mode 2)
SL_POWER_MANAGER_EM3, ///< Stop Mode (Energy Mode 3)
SL_POWER_MANAGER_EM4, ///< Shutoff Mode (Energy Mode 4)
} sl_power_manager_em_t;
/// @brief Mask of all the event(s) to listen to.
typedef uint32_t sl_power_manager_em_transition_event_t;
/***************************************************************************//**
* Typedef for the user supplied callback function which is called when
* an energy mode transition occurs.
*
* @param from Energy mode we are leaving.
* @param to Energy mode we are entering.
******************************************************************************/
typedef void (*sl_power_manager_em_transition_on_event_t)(sl_power_manager_em_t from,
sl_power_manager_em_t to);
/// @brief Struct representing energy mode transition event information
typedef struct {
const sl_power_manager_em_transition_event_t event_mask; ///< Mask of the transitions on which the callback should be called.
const sl_power_manager_em_transition_on_event_t on_event; ///< Function that must be called when the event occurs.
} sl_power_manager_em_transition_event_info_t;
/// @brief Struct representing energy mode transition event handle
typedef struct {
sl_slist_node_t node; ///< List node.
sl_power_manager_em_transition_event_info_t *info; ///< Handle event info.
} sl_power_manager_em_transition_event_handle_t;
/// On ISR Exit Hook answer
SL_ENUM(sl_power_manager_on_isr_exit_t) {
SL_POWER_MANAGER_IGNORE = (1UL << 0UL), ///< The module did not trigger an ISR and it doesn't want to contribute to the decision
SL_POWER_MANAGER_SLEEP = (1UL << 1UL), ///< The module was the one that caused the system wakeup and the system SHOULD go back to sleep
SL_POWER_MANAGER_WAKEUP = (1UL << 2UL), ///< The module was the one that caused the system wakeup and the system MUST NOT go back to sleep
};
// -----------------------------------------------------------------------------
// Internal Prototypes only to be used by Power Manager module
void sli_power_manager_update_em_requirement(sl_power_manager_em_t em,
bool add);
// To make sure that we are able to optimize out the string argument when the
// debug feature is disable, we use a pre-processor macro resulting in a no-op.
// We also make sure to always have a definition for the function regardless if
// the debug feature is enable or not for binary compatibility.
#if (SL_POWER_MANAGER_DEBUG == 1)
void sli_power_manager_debug_log_em_requirement(sl_power_manager_em_t em,
bool add,
const char *name);
#else
#define sli_power_manager_debug_log_em_requirement(em, add, name) /* no-op */
#endif
// -----------------------------------------------------------------------------
// Prototypes
/***************************************************************************//**
* Initialize Power Manager module.
* @return Status code
******************************************************************************/
sl_status_t sl_power_manager_init(void);
/***************************************************************************//**
* Sleep at the lowest allowed energy mode.
*
* @note Must not be called from ISR
* @par
* @note This function will expect and call a callback with the following
* signature: `bool sl_power_manager_is_ok_to_sleep(void)`.
*
* @note This function can be used to cancel a sleep action and handle the
* possible race condition where an ISR that would cause a wakeup is
* triggered right after the decision to call sl_power_manager_sleep()
* has been made.
*
* @note This function must not be called with interrupts disabled.
*
* Usage example:
*
* ```c
* void main(void)
* {
* sl_power_manager_init();
* while (1) {
* tick();
* sl_power_manager_sleep();
* }
* }
* ```
******************************************************************************/
void sl_power_manager_sleep(void);
/***************************************************************************//**
* Adds requirement on given energy mode.
*
* @param em Energy mode to add the requirement to:
* - ::SL_POWER_MANAGER_EM1
* - ::SL_POWER_MANAGER_EM2
******************************************************************************/
__STATIC_INLINE void sl_power_manager_add_em_requirement(sl_power_manager_em_t em)
{
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_CRITICAL();
sli_power_manager_update_em_requirement(em, true);
sli_power_manager_debug_log_em_requirement(em, true, (const char *)CURRENT_MODULE_NAME);
CORE_EXIT_CRITICAL();
}
/***************************************************************************//**
* Removes requirement on given energy mode.
*
* @param em Energy mode to remove the requirement to:
* - ::SL_POWER_MANAGER_EM1
* - ::SL_POWER_MANAGER_EM2
******************************************************************************/
__STATIC_INLINE void sl_power_manager_remove_em_requirement(sl_power_manager_em_t em)
{
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_CRITICAL();
sli_power_manager_update_em_requirement(em, false);
sli_power_manager_debug_log_em_requirement(em, false, (const char *)CURRENT_MODULE_NAME);
CORE_EXIT_CRITICAL();
}
/***************************************************************************//**
* Registers a callback to be called on given Energy Mode transition(s).
*
* @param event_handle Event handle (no initialization needed).
*
* @param event_info Event info structure that contains the event mask and the
* callback that must be called.
*
* @note Adding and removing requirement(s) from a callback on a transition event
* is not supported.
*
* @note The parameters passed must be persistent, meaning that they need to survive
* until the callback fires.
*
* Usage example:
*
* ```c
* #define EM_EVENT_MASK_ALL ( SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM0 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM0 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM1 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM1 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM2 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM2 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM3 \
* | SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3)
*
* sl_power_manager_em_transition_event_handle_t event_handle;
* sl_power_manager_em_transition_event_info_t event_info = {
* .event_mask = EM_EVENT_MASK_ALL,
* .on_event = my_callback,
* };
*
* void my_callback(sl_power_manager_em_t from,
* sl_power_manager_em_t to)
* {
* [...]
* }
*
* void main(void)
* {
* sl_power_manager_init();
* sl_power_manager_subscribe_em_transition_event(&event_handle, &event_info);
* }
* ```
******************************************************************************/
void sl_power_manager_subscribe_em_transition_event(sl_power_manager_em_transition_event_handle_t *event_handle,
const sl_power_manager_em_transition_event_info_t *event_info);
/***************************************************************************//**
* Unregisters an event callback handle on Energy mode transition.
*
* @param event_handle Event handle which must be unregistered (must have been
* registered previously).
*
* @note An EFM_ASSERT is thrown if the handle is not found.
******************************************************************************/
void sl_power_manager_unsubscribe_em_transition_event(sl_power_manager_em_transition_event_handle_t *event_handle);
/***************************************************************************//**
* Get configurable overhead value for early restore time in Sleeptimer ticks
* when a schedule wake-up is set.
*
* @return Current overhead value for early restore time.
*
* @note This function will do nothing when a project contains the
* power_manager_no_deepsleep component, which configures the
* lowest energy mode as EM1.
******************************************************************************/
int32_t sl_power_manager_schedule_wakeup_get_restore_overhead_tick(void);
/***************************************************************************//**
* Set configurable overhead value for early restore time in Sleeptimer ticks
* used for schedule wake-up.
* Must be called after initialization else the value will be overwritten.
*
* @param overhead_tick Overhead value to set for early restore time.
*
* @note The overhead value can also be negative to remove time from the restore
* process.
*
* @note This function will do nothing when a project contains the
* power_manager_no_deepsleep component, which configures the
* lowest energy mode as EM1.
******************************************************************************/
void sl_power_manager_schedule_wakeup_set_restore_overhead_tick(int32_t overhead_tick);
/***************************************************************************//**
* Get configurable minimum off-time value for schedule wake-up in Sleeptimer
* ticks.
*
* @return Current minimum off-time value for schedule wake-up.
*
* @note Turning on external high frequency clock, such as HFXO, requires more
* energy since we must supply higher current for the wake-up.
* Therefore, when an 'external high frequency clock enable' is scheduled
* in 'x' time, there is a threshold 'x' value where turning off the clock
* is not worthwhile since the energy consumed by taking into account the
* wake-up will be greater than if we just keep the clock on until the next
* scheduled clock enabled. This threshold value is what we refer as the
* minimum off-time.
*
* @note This function will do nothing when a project contains the
* power_manager_no_deepsleep component, which configures the
* lowest energy mode as EM1.
******************************************************************************/
uint32_t sl_power_manager_schedule_wakeup_get_minimum_offtime_tick(void);
/***************************************************************************//**
* Set configurable minimum off-time value for schedule wake-up in Sleeptimer
* ticks.
*
* @param minimum_offtime_tick minimum off-time value to set for schedule
* wake-up.
*
* @note Turning on external high frequency clock, such as HFXO, requires more
* energy since we must supply higher current for the wake-up.
* Therefore, when an 'external high frequency clock enable' is scheduled
* in 'x' time, there is a threshold 'x' value where turning off the clock
* is not worthwhile since the energy consumed by taking into account the
* wake-up will be greater than if we just keep the clock on until the next
* scheduled clock enabled. This threshold value is what we refer as the
* minimum off-time.
*
* @note This function will do nothing when a project contains the
* power_manager_no_deepsleep component, which configures the
* lowest energy mode as EM1.
******************************************************************************/
void sl_power_manager_schedule_wakeup_set_minimum_offtime_tick(uint32_t minimum_offtime_tick);
/***************************************************************************//**
* Enable or disable fast wake-up in EM2 and EM3
*
* @param enable True False variable act as a switch for this api
*
* @note Will also update the wake up time from EM2 to EM0.
*
* @note This function will do nothing when a project contains the
* power_manager_no_deepsleep component, which configures the
* lowest energy mode as EM1.
******************************************************************************/
void sl_power_manager_em23_voltage_scaling_enable_fast_wakeup(bool enable);
/**************************************************************************//**
* Determines if the HFXO interrupt was part of the last wake-up and/or if
* the HFXO early wakeup expired during the last ISR
* and if it was the only timer to expire in that period.
*
* @return true if power manager sleep can return to sleep,
* false otherwise.
*
* @note This function will always return false in case a requirement
* is added on SL_POWER_MANAGER_EM1, since we will
* never sleep at a lower level than EM1.
*****************************************************************************/
bool sl_power_manager_is_latest_wakeup_internal(void);
/** @} (end addtogroup power_manager) */
#ifdef __cplusplus
}
#endif
#endif // SL_POWER_MANAGER_H