Initial commit of firmware

This commit is contained in:
2025-04-12 13:30:57 +01:00
commit 264a3462e0
374 changed files with 332649 additions and 0 deletions

View File

@@ -0,0 +1,591 @@
/***************************************************************************//**
* @file
* @brief Power Manager API definition.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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 "sl_core.h"
#include "sl_code_classification.h"
#include <stdbool.h>
#include <stdint.h>
#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 <a href="https://www.silabs.com/documents/
* public/application-notes/
* an1358-migrating-from-sleep-driver-to-power-manager.pdf">AN1358:
* Migrating from Sleep Driver to Power Manager</a> 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.
*
* Requirements should not be removed if it was not previously added.
*
* ## 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 "<Module printable name here>"
* ```
* 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 (DEPRECATED)
#define SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3 (1 << 7) ///< sl power manager event transition leaving em3 (DEPRECATED)
// -----------------------------------------------------------------------------
// 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.
const 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
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
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)
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
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. This means
* both BASEPRI and PRIMASK MUST have a value of 0 when invoking this
* function.
*
* Usage example:
*
* ```c
* void main(void)
* {
* sl_power_manager_init();
* while (1) {
* tick();
* sl_power_manager_sleep();
* }
* }
* ```
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
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 (DEPRECATED)
*
* @note Adding EM requirements on SL_POWER_MANAGER_EM2 is now DEPRECATED.
* The calls can simply be removed since the system will go to deepsleep
* (EM2/EM3) in the absence of EM1 requirements.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__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 (DEPRECATED)
*
* @note Removing EM requirements on SL_POWER_MANAGER_EM2 is now DEPRECATED.
* The calls can simply be removed since the system will go to deepsleep
* (EM2/EM3) in the absence of EM1 requirements.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__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.
*
* @note SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM3 and
* SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3 are now DEPRECATED and should
* not be used in the event_info argument.
*
* 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_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.
*****************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
bool sl_power_manager_is_latest_wakeup_internal(void);
/***************************************************************************//**
* Enter energy mode 4 (EM4).
*
* @note You should not expect to return from this function. Once the device
* enters EM4, only a power on reset or external reset pin can wake the
* device.
*
* @note On xG22 devices, this function re-configures the IADC if EM4 entry
* is possible.
******************************************************************************/
void sl_power_manager_enter_em4(void);
/***************************************************************************//**
* When EM4 pin retention is set to power_manager_pin_retention_latch,
* then pins are retained through EM4 entry and wakeup. The pin state is
* released by calling this function. The feature allows peripherals or
* GPIO to be re-initialized after EM4 exit (reset), and when
* initialization is done, this function can release pins and return
* control to the peripherals or GPIO.
*
* @note When the EM4 Pin Retention feature is not available on a device,
* calling this function will do nothing.
******************************************************************************/
void sl_power_manager_em4_unlatch_pin_retention(void);
/***************************************************************************//**
* Energy mode 4 pre-sleep hook function.
*
* @note This function is called by @ref sl_power_manager_enter_em4 just
* prior to the sequence of writes to put the device in EM4. The
* function implementation does not perform anything, but it is
* SL_WEAK so that it can be re-implemented in application code if
* actions are needed.
******************************************************************************/
void sl_power_manager_em4_presleep_hook(void);
/** @} (end addtogroup power_manager) */
#ifdef __cplusplus
}
#endif
#endif // SL_POWER_MANAGER_H

View File

@@ -0,0 +1,60 @@
/***************************************************************************//**
* @file
* @brief Power Manager API definition.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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_DEBUG_H
#define SL_POWER_MANAGER_DEBUG_H
#include "sl_power_manager.h"
#ifdef __cplusplus
extern "C" {
#endif
/***************************************************************************//**
* @addtogroup power_manager
* @{
******************************************************************************/
// -----------------------------------------------------------------------------
// Prototypes
/***************************************************************************//**
* Print a table that describes the current requirements on each energy
* mode and their owner.
******************************************************************************/
void sl_power_manager_debug_print_em_requirements(void);
/** @} (end addtogroup power_manager) */
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,172 @@
/***************************************************************************//**
* @file
* @brief Power Manager Private API definition.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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 SLI_POWER_MANAGER_H
#define SLI_POWER_MANAGER_H
#include "sl_power_manager.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*******************************************************************************
****************************** HOOK REFERENCES ****************************
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
bool sl_power_manager_sleep_on_isr_exit(void);
// Callback to application after wakeup but before restoring interrupts.
// For internal Silicon Labs use only
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__WEAK void sli_power_manager_on_wakeup(void);
// Hook that can be used by the log outputer to suspend transmission of logs
// in case it would require energy mode changes while in the sleep loop.
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__WEAK void sli_power_manager_suspend_log_transmission(void);
// Hook that can be used by the log outputer to resume transmission of logs.
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__WEAK void sli_power_manager_resume_log_transmission(void);
// Callback to notify possible transition from EM1P to EM2.
// For internal Silicon Labs use only
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
__WEAK void sli_power_manager_em1p_to_em2_notification(void);
/***************************************************************************//**
* Mandatory callback that allows to cancel sleeping action.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
bool sl_power_manager_is_ok_to_sleep(void);
/*******************************************************************************
***************************** PROTOTYPES **********************************
******************************************************************************/
void sli_power_manager_update_hf_clock_settings_preservation_requirement(bool add);
/***************************************************************************//**
* Adds requirement on the preservation of the High Frequency Clocks settings.
*
* @note FOR INTERNAL USE ONLY.
*
* @note Must be used together with adding an EM2 requirement.
******************************************************************************/
void sli_power_manager_add_hf_clock_settings_preservation_requirement(void);
/***************************************************************************//**
* Removes requirement on the preservation of the High Frequency Clocks settings.
*
* @note FOR INTERNAL USE ONLY.
*
* @note Must be used together with removing an EM2 requirement.
******************************************************************************/
void sli_power_manager_remove_hf_clock_settings_preservation_requirement(void);
/***************************************************************************//**
* Informs the power manager module that the high accuracy/high frequency clock
* is used.
*
* @note FOR INTERNAL USE ONLY.
*
* @note Must be called by RAIL initialization in case radio clock settings
* are not set before the Power Manager initialization.
******************************************************************************/
__WEAK void sli_power_manager_set_high_accuracy_hf_clock_as_used(void);
/***************************************************************************//**
* Gets the wake-up restore process time.
* If we are not in the context of a deepsleep and therefore don't need to
* do a restore, the return value is 0.
*
*
* @return Wake-up restore process time.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
uint32_t sli_power_manager_get_restore_delay(void);
/***************************************************************************//**
* Initiates the wake-up restore process.
******************************************************************************/
void sli_power_manager_initiate_restore(void);
/***************************************************************************//**
* Performs pre sleep operations.
*
* @note Must only be called by the RTOS integration code.
******************************************************************************/
void sli_power_manager_pre_sleep(void);
/***************************************************************************//**
* Fetches current energy mode
*
* @return Returns current energy mode
******************************************************************************/
sl_power_manager_em_t sli_power_manager_get_current_em(void);
/***************************************************************************//**
* Update Energy Mode 4 configurations.
******************************************************************************/
void sli_power_manager_init_em4(void);
/***************************************************************************//**
* Enable or disable fast wake-up in EM2 and EM3
*
* @note Will also update the wake up time from EM2 to EM0.
******************************************************************************/
void sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(bool enable);
/***************************************************************************//**
* Initializes energy mode transition list.
******************************************************************************/
void sli_power_manager_em_transition_event_list_init(void);
/***************************************************************************//**
* Notify subscribers about energy mode transition.
*
* @param from Energy mode from which CPU comes from.
*
* @param to Energy mode to which CPU is going to.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_notify_em_transition(sl_power_manager_em_t from,
sl_power_manager_em_t to);
#ifdef __cplusplus
}
#endif
#endif /* SLI_POWER_MANAGER_H */

View File

@@ -0,0 +1,213 @@
/***************************************************************************//**
* @file
* @brief Power Manager common API implementation.
*******************************************************************************
* # License
* <b>Copyright 2024 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
*
******************************************************************************/
#include "sl_power_manager.h"
#include "sl_power_manager_config.h"
#include "sli_power_manager.h"
#include "sli_clock_manager.h"
#include "sl_assert.h"
#include "sl_atomic.h"
#include "sl_clock_manager.h"
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#include "em_device.h"
#if !defined(_SILICON_LABS_32B_SERIES_3)
#include "em_emu.h"
#else
#include "sli_power_manager_execution_modes_private.h"
#endif
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
/*******************************************************************************
*************************** LOCAL VARIABLES ********************************
******************************************************************************/
// Events subscribers lists
static sl_slist_node_t *power_manager_em_transition_event_list = NULL;
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* Last-chance check before sleep.
*
* @return True, if the system should actually sleep.
* False, if not.
*
* @note This is the fallback implementation of the callback, it can be
* overridden by the application or other components.
******************************************************************************/
__WEAK bool sl_power_manager_is_ok_to_sleep(void)
{
return true;
}
/***************************************************************************//**
* Check if the MCU can sleep after an interrupt.
*
* @return True, if the system can sleep after the interrupt.
* False, otherwise.
*
* @note This is the fallback implementation of the callback, it can be
* overridden by the application or other components.
******************************************************************************/
__WEAK bool sl_power_manager_sleep_on_isr_exit(void)
{
return false;
}
/***************************************************************************//**
* Enable or disable fast wake-up in EM2 and EM3
*
* @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)
{
#if (defined(EMU_VSCALE_PRESENT) && !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT))
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_CRITICAL();
sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(enable);
CORE_EXIT_CRITICAL();
#else
(void)enable;
#endif
}
/***************************************************************************//**
* Registers a callback to be called on given Energy Mode transition(s).
*
* @note Adding/Removing requirement(s) from the callback is not supported.
******************************************************************************/
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)
{
CORE_DECLARE_IRQ_STATE;
event_handle->info = event_info;
CORE_ENTER_CRITICAL();
sl_slist_push(&power_manager_em_transition_event_list, &event_handle->node);
CORE_EXIT_CRITICAL();
}
/***************************************************************************//**
* Unregisters an event callback handle on Energy mode transition.
******************************************************************************/
void sl_power_manager_unsubscribe_em_transition_event(sl_power_manager_em_transition_event_handle_t *event_handle)
{
CORE_DECLARE_IRQ_STATE;
CORE_ENTER_CRITICAL();
sl_slist_remove(&power_manager_em_transition_event_list, &event_handle->node);
CORE_EXIT_CRITICAL();
}
/***************************************************************************//**
* Initializes energy mode transition list.
******************************************************************************/
void sli_power_manager_em_transition_event_list_init(void)
{
sl_slist_init(&power_manager_em_transition_event_list);
}
/***************************************************************************//**
* Notify subscribers about energy mode transition.
******************************************************************************/
void sli_power_manager_notify_em_transition(sl_power_manager_em_t from,
sl_power_manager_em_t to)
{
sl_power_manager_em_transition_event_handle_t *handle;
sl_power_manager_em_transition_event_t transition = 0;
switch (to) {
case SL_POWER_MANAGER_EM0:
transition = SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM0;
break;
case SL_POWER_MANAGER_EM1:
transition = SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM1;
break;
case SL_POWER_MANAGER_EM2:
transition = SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM2;
break;
#if !defined(SL_CATALOG_POWER_MANAGER_ARM_SLEEP_ON_EXIT)
case SL_POWER_MANAGER_EM3:
transition = SL_POWER_MANAGER_EVENT_TRANSITION_ENTERING_EM3;
break;
#endif
default:
EFM_ASSERT(0);
}
switch (from) {
case SL_POWER_MANAGER_EM0:
transition |= SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM0;
break;
case SL_POWER_MANAGER_EM1:
transition |= SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM1;
break;
case SL_POWER_MANAGER_EM2:
transition |= SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM2;
break;
#if !defined(SL_CATALOG_POWER_MANAGER_ARM_SLEEP_ON_EXIT)
case SL_POWER_MANAGER_EM3:
transition |= SL_POWER_MANAGER_EVENT_TRANSITION_LEAVING_EM3;
break;
#endif
default:
EFM_ASSERT(0);
}
SL_SLIST_FOR_EACH_ENTRY(power_manager_em_transition_event_list, handle, sl_power_manager_em_transition_event_handle_t, node) {
if ((handle->info->event_mask & transition) > 0) {
handle->info->on_event(from, to);
}
}
}

View File

@@ -0,0 +1,330 @@
/***************************************************************************//**
* @file
* @brief Power Manager EM4 API implementation.
*******************************************************************************
* # License
* <b>Copyright 2024 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
*
******************************************************************************/
#include "sl_power_manager.h"
#include "sl_power_manager_config.h"
#include "sli_power_manager.h"
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#include "em_device.h"
#if defined(_SILICON_LABS_32B_SERIES_2)
#include "em_emu.h"
#include "em_cmu.h"
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)
#include "em_iadc.h"
#endif
#endif
#include <stdlib.h>
#include <stdint.h>
/*******************************************************************************
****************************** DEFINES ************************************
******************************************************************************/
#if defined(WDOG_PRESENT)
// Macros to determine if WDOG instances are clocked or not
#if defined(CMU_CLKEN0_WDOG0)
#define WDOG0_CLOCK_ENABLED_BIT (CMU->CLKEN0 & CMU_CLKEN0_WDOG0)
#else
// There's no CMU->CLKEN1 so assume the WDOG0 is clocked
#define WDOG0_CLOCK_ENABLED_BIT 1
#endif
#if defined(CMU_CLKEN1_WDOG1)
#define WDOG1_CLOCK_ENABLED_BIT (CMU->CLKEN1 & CMU_CLKEN1_WDOG1)
#else
// There's no CMU->CLKEN1 so assume the WDOG1 is clocked
#define WDOG1_CLOCK_ENABLED_BIT 1
#endif
#endif
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
static bool is_em4_blocked(void);
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
static void ramp_dvdd_and_switch_to_dcdc_bypass_mode(void);
#endif
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* update energy mode 4 configurations.
******************************************************************************/
void sli_power_manager_init_em4(void)
{
#if !defined(_SILICON_LABS_32B_SERIES_2)
EMU->EM4CTRL = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4IORETMODE_MASK)
| (uint32_t)SL_POWER_MANAGER_INIT_EMU_EM4_PIN_RETENTION_MODE;
#else
EMU_EM4Init_TypeDef em4_init = EMU_EM4INIT_DEFAULT;
em4_init.pinRetentionMode = (EMU_EM4PinRetention_TypeDef)SL_POWER_MANAGER_INIT_EMU_EM4_PIN_RETENTION_MODE;
EMU_EM4Init(&em4_init);
#endif
}
/******************************************************************************
* Event called before entering EM4 sleep.
*****************************************************************************/
SL_WEAK void sl_power_manager_em4_presleep_hook(void)
{
// This implementation is empty, but this function can be redefined as it's a weak implementation.
}
/***************************************************************************//**
* Enter energy mode 4 (EM4).
*
* @note You should not expect to return from this function. Once the device
* enters EM4, only a power on reset or external reset pin can wake the
* device.
*
* @note On xG22 devices, this function re-configures the IADC if EM4 entry
* is possible.
******************************************************************************/
__NO_RETURN void sl_power_manager_enter_em4(void)
{
/* Device with Boost DC-DC cannot enter EM4 because Boost DC-DC module does not
* have BYPASS switch so DC-DC converter can not be set to bypass mode. */
#if (defined(_SILICON_LABS_DCDC_FEATURE) \
&& (_SILICON_LABS_DCDC_FEATURE == _SILICON_LABS_DCDC_FEATURE_DCDC_BOOST))
EFM_ASSERT(false);
#endif
// Make sure that we are not interrupted while we are entering em4
CORE_CRITICAL_IRQ_DISABLE();
EFM_ASSERT(is_em4_blocked() == false);
#if defined(SL_CATALOG_METRIC_EM4_WAKE_PRESENT)
sli_metric_em4_wake_init();
#endif
uint32_t em4seq2 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
| (2U << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
uint32_t em4seq3 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK)
| (3U << _EMU_EM4CTRL_EM4ENTRY_SHIFT);
// Make sure that the register write lock is disabled.
EMU->LOCK = EMU_LOCK_LOCKKEY_UNLOCK;
#if defined(_DCDC_IF_EM4ERR_MASK)
// Workaround for bug that may cause a Hard Fault on EM4 entry
CMU_CLOCK_SELECT_SET(SYSCLK, FSRCO);
// The buck DC-DC is available in all energy modes except for EM4.
// The DC-DC converter must first be turned off and switched over to bypass mode.
#if (defined(EMU_SERIES2_DCDC_BUCK_PRESENT) \
|| defined(EMU_SERIES2_DCDC_BOOST_PRESENT))
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
ramp_dvdd_and_switch_to_dcdc_bypass_mode();
#else
EMU_DCDCModeSet(emuDcdcMode_Bypass);
#endif
#endif
#endif
sl_power_manager_em4_presleep_hook();
for (uint8_t i = 0; i < 4; i++) {
EMU->EM4CTRL = em4seq2;
EMU->EM4CTRL = em4seq3;
}
EMU->EM4CTRL = em4seq2;
__WFI();
for (;; ) {
// __NO_RETURN
}
}
/***************************************************************************//**
* When EM4 pin retention is set to power_manager_pin_retention_latch,
* then pins are retained through EM4 entry and wakeup. The pin state is
* released by calling this function. The feature allows peripherals or
* GPIO to be re-initialized after EM4 exit (reset), and when
* initialization is done, this function can release pins and return
* control to the peripherals or GPIO.
******************************************************************************/
void sl_power_manager_em4_unlatch_pin_retention(void)
{
#if defined(_EMU_EM4CTRL_EM4IORETMODE_MASK)
EMU->CMD = EMU_CMD_EM4UNLATCH;
#endif
}
/***************************************************************************//**
* Returns true if em4 entry is blocked by a watchdog peripheral.
******************************************************************************/
static bool is_em4_blocked(void)
{
#if defined(WDOG_PRESENT)
#if WDOG_COUNT > 0
if ( WDOG0_CLOCK_ENABLED_BIT && (WDOG0->CFG & WDOG_CFG_EM4BLOCK) && (WDOG0->EN & WDOG_EN_EN) ) {
return true;
}
#endif
#if WDOG_COUNT > 1
if ( WDOG1_CLOCK_ENABLED_BIT && (WDOG1->CFG & WDOG_CFG_EM4BLOCK) && (WDOG1->EN & WDOG_EN_EN) ) {
return true;
}
#endif
#endif
return false;
}
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)
/* The following section provides an optimization to improve peak current
* consumption on xG22 devices.
*/
// ADC clock frequency (source and after prescale)
#define CLK_SRC_ADC_FREQ 9600000 // CLK_SRC_ADC
#define CLK_ADC_FREQ 1000000 // CLK_ADC
extern void sli_delay_loop(uint32_t cycles);
uint32_t * dcdc_test_addr = (uint32_t *)(DCDC_NS_BASE + 0x80);
uint32_t ipkval = 7;
uint32_t ipktimeout = 1;
/* Pulse generation sequence TOCTRIG (bit 3) TOCMODE (bit 2)*/
uint32_t cmd[2] = { (1 << 2) | (1 << 3), (1 << 2) };
/***************************************************************************//**
* The voltage of Dvdd is ramped up to VMCU by sending pulses to a DCDC test
* register. These pulses are delayed, and ipkval and ipktimeout are tuned
* such that the voltage at Dvdd increases gradually to the voltage level of
* VMCU. Using the IADC, once Dvdd has gotten sufficiently close to VMCU,
* the DCDC peripheral is then switched into bypass mode. The IADC is used to
* detect this by sampling the voltage of Dvdd periodically, and calculating the
* difference between samples, when the difference is within some margin of zero
* then we know that the ramp sequence has reached a plateau.
******************************************************************************/
static void ramp_dvdd_and_switch_to_dcdc_bypass_mode(void)
{
// Initialize the IADC for the purposes of detecting when the Dvdd ramp
// reaches a plateau.
IADC_Init_t init = IADC_INIT_DEFAULT;
IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
CMU_ClockEnable(cmuClock_IADC0, true);
init.srcClkPrescale = IADC_calcSrcClkPrescale(IADC0, CLK_SRC_ADC_FREQ, 0);
initAllConfigs.configs[0].reference = iadcCfgReferenceInt1V2;
initAllConfigs.configs[0].vRef = 1210;
initAllConfigs.configs[0].analogGain = iadcCfgAnalogGain1x;
initAllConfigs.configs[0].digAvg = iadcDigitalAverage1;
initAllConfigs.configs[0].adcClkPrescale =
IADC_calcAdcClkPrescale(IADC0,
CLK_ADC_FREQ,
0,
iadcCfgModeNormal,
init.srcClkPrescale);
init.warmup = iadcWarmupKeepWarm;
IADC_reset(IADC0);
CMU_ClockSelectSet(cmuClock_IADCCLK, cmuSelect_EM01GRPACLK);
initSingle.triggerAction = iadcTriggerActionContinuous;
initSingle.alignment = iadcAlignRight12;
initSingleInput.compare = false; // Disable Window CMP
initSingleInput.posInput = iadcPosInputDvdd;
IADC_init(IADC0, &init, &initAllConfigs);
IADC_initSingle(IADC0, &initSingle, &initSingleInput);
// Start capturing
IADC_command(IADC0, iadcCmdStartSingle);
// Initialize DCDC peak current value and timeout to reach peak current value
DCDC->EM01CTRL0 = (DCDC->EM01CTRL0 & ~_DCDC_EM01CTRL0_IPKVAL_MASK) | (ipkval << 0);
DCDC->CTRL = (DCDC->CTRL & ~_DCDC_CTRL_IPKTMAXCTRL_MASK) | (ipktimeout << 4);
/* Generate pulses */
uint32_t iter = 1U;
IADC_Result_t prev_result;
volatile IADC_Result_t current_result = IADC_readSingleResult(IADC0);
while (true) {
// If the algorithm doesn't converge after 500 pulses, switch to dcdc
// bypass anyways.
if (iter >= 500) {
DCDC->CTRL_CLR = DCDC_CTRL_MODE;
EFM_ASSERT(false);
return;
}
/* Pulse generation sequence TOCTRIG (bit 3) TOCMODE (bit 2)*/
*dcdc_test_addr = cmd[0];
*dcdc_test_addr = cmd[1];
// In DCDC mode, MCU input voltage VREGVDD cannot be directly measured, so
// we can't know what the target DVDD voltage is. Instead, since DVDD
// ramp-up should follow a RC charge curve, measure DVDD and keep charging
// until the delta between measures is smaller than the set tolerance.
if (iter % 20U == 0U) {
prev_result = current_result;
current_result = IADC_readSingleResult(IADC0);
if ( abs((int32_t)(current_result.data - prev_result.data)) < SL_POWER_MANAGER_RAMP_DVDD_TOLERANCE ) {
DCDC->CTRL_CLR = DCDC_CTRL_MODE;
return;
}
}
if (DCDC->IF & DCDC_IF_TMAX) {
if (ipkval) {
ipkval--; // DCDC peak current value
}
if (ipktimeout < 7) {
ipktimeout++; // Timeout to reach peak current value
}
DCDC->EM01CTRL0 = (DCDC->EM01CTRL0 & ~_DCDC_EM01CTRL0_IPKVAL_MASK) | (ipkval << 0);
DCDC->CTRL = (DCDC->CTRL & ~_DCDC_CTRL_IPKTMAXCTRL_MASK) | (ipktimeout << 4);
DCDC->IF_CLR = DCDC_IF_TMAX;
}
/* delay for 8 clock cycles */
sli_delay_loop(8);
iter++;
}
}
#endif // defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) && (SL_POWER_MANAGER_RAMP_DVDD_EN == 1)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
/***************************************************************************//**
* @file
* @brief Power Manager Debug API implementation.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
*
******************************************************************************/
#include "sl_power_manager.h"
#include "sl_power_manager_config.h"
#include "sl_power_manager_debug.h"
#include "sli_power_manager_private.h"
#if (SL_POWER_MANAGER_DEBUG == 1)
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
static sl_slist_node_t *power_manager_debug_requirement_em_table[SLI_POWER_MANAGER_EM_TABLE_SIZE];
static sli_power_debug_requirement_entry_t power_debug_entry_table[SL_POWER_MANAGER_DEBUG_POOL_SIZE];
static sl_slist_node_t *power_debug_free_entry_list = NULL;
static bool power_debug_ran_out_of_entry = false;
static void power_manager_log_add_requirement(sl_slist_node_t **p_list,
bool add,
const char *name);
/***************************************************************************//**
* Print a fancy table that describes the current requirements on each energy
* mode and their owner.
******************************************************************************/
void sl_power_manager_debug_print_em_requirements(void)
{
uint8_t i;
sli_power_debug_requirement_entry_t *entry;
if (power_debug_ran_out_of_entry) {
printf("WARNING: The system ran out of Debug Entry; This report is likely to be incomplete. Increase SL_POWER_MANAGER_DEBUG_POOL_SIZE\n\n");
}
printf("------------------------------------------\n");
printf("| EM requirements\n");
printf("------------------------------------------\n");
for (i = 0; i < SLI_POWER_MANAGER_EM_TABLE_SIZE; i++) {
if (power_manager_debug_requirement_em_table[i] != NULL) {
printf("| EM%d requirement module owners:\n", i + 1);
}
SL_SLIST_FOR_EACH_ENTRY(power_manager_debug_requirement_em_table[i], entry, sli_power_debug_requirement_entry_t, node) {
printf("| %s\n", entry->module_name);
}
if (power_manager_debug_requirement_em_table[i] != NULL) {
printf("------------------------------------------\n");
}
}
}
/***************************************************************************//**
* Initialize debugging feature.
******************************************************************************/
void sli_power_manager_debug_init(void)
{
uint32_t i;
for (i = 0; i < SL_POWER_MANAGER_DEBUG_POOL_SIZE; i++) {
sli_power_debug_requirement_entry_t *entry = &power_debug_entry_table[i];
sl_slist_push(&power_debug_free_entry_list, &entry->node);
}
}
/***************************************************************************//**
* Log requirement to a list
*
* @param p_list List where to push or remove the requirement.
*
* @param add Add (true) or remove (false) the requirement.
*
* @param name Module name that acquired or remove the requirement.
******************************************************************************/
static void power_manager_log_add_requirement(sl_slist_node_t **p_list,
bool add,
const char *name)
{
sl_slist_node_t *node;
sli_power_debug_requirement_entry_t *entry;
if (add == true) {
// Get entry from free list
node = sl_slist_pop(&power_debug_free_entry_list);
if (node == NULL) {
power_debug_ran_out_of_entry = true;
return;
}
// Push entry to the EMx requirement debug list
entry = SL_SLIST_ENTRY(node, sli_power_debug_requirement_entry_t, node);
entry->module_name = name;
sl_slist_push(p_list, &entry->node);
} else {
sli_power_debug_requirement_entry_t *entry_remove = NULL;
// Search in the EMx requirement debug list
SL_SLIST_FOR_EACH_ENTRY(*p_list, entry, sli_power_debug_requirement_entry_t, node) {
// Current module name and entry module name
if (strcmp(entry->module_name, name) == 0) {
entry_remove = entry;
break;
}
}
if (entry_remove == NULL) {
return;
}
sl_slist_remove(p_list, &entry_remove->node);
sl_slist_push(&power_debug_free_entry_list, &entry_remove->node);
}
}
#endif // SL_POWER_MANAGER_DEBUG
#undef sli_power_manager_debug_log_em_requirement
/***************************************************************************//**
* Log energy mode (EM) requirement
*
* @param em Energy mode added or removed.
*
* @param add Add (true) or remove (false) the requirement.
*
* @param name Module name that adds or removes the requirement.
******************************************************************************/
void sli_power_manager_debug_log_em_requirement(sl_power_manager_em_t em,
bool add,
const char *name)
{
#if (SL_POWER_MANAGER_DEBUG == 1)
if (em != SL_POWER_MANAGER_EM0) {
power_manager_log_add_requirement(&power_manager_debug_requirement_em_table[em - 1], add, name);
}
#else
(void)em;
(void)add;
(void)name;
#endif
}

View File

@@ -0,0 +1,706 @@
/***************************************************************************//**
* @file
* @brief Power Manager HAL API implementation.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
*
******************************************************************************/
#include "em_device.h"
#if defined(_SILICON_LABS_32B_SERIES_2)
#include "em_emu.h"
#include "em_cmu.h"
#include "sl_assert.h"
#include "sl_power_manager.h"
#include "sli_power_manager.h"
#include "sli_power_manager_private.h"
#include "sl_sleeptimer.h"
#include "sli_sleeptimer.h"
#include "sl_power_manager_config.h"
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)
#include "em_iadc.h"
#include <stdlib.h>
#endif
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT) \
&& !defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
#include "sli_hfxo_manager.h"
#endif
#include <stdbool.h>
/*******************************************************************************
********************************* DEFINES *********************************
******************************************************************************/
// Time required by the hardware to come out of EM2 in microseconds.
// This value includes HW startup, emlib and sleepdrv execution time.
// Voltage scaling, HFXO startup and HFXO steady times are excluded from
// this because they are handled separately. RTCCSYNC time is also
// excluded and it is handled by RTCCSYNC code itself.
#if (_SILICON_LABS_32B_SERIES_2_CONFIG == 1)
#define EM2_WAKEUP_PROCESS_TIME_OVERHEAD_US (31u)
#else // (_SILICON_LABS_32B_SERIES_2_CONFIG == 2),
#define EM2_WAKEUP_PROCESS_TIME_OVERHEAD_US (31u)
#endif
// DPLL Locking delay related defines
#define DPLL_COARSECOUNT_VALUE (5u)
// Time it takes to upscale voltage after EM2 in microseconds.
// This value represents the time for scaling from VSCALE0 to VSCALE2.
#define EM2_WAKEUP_VSCALE_OVERHEAD_US (64u)
// Default time value in microseconds required to wake-up the hfxo oscillator.
#define HFXO_WAKE_UP_TIME_DEFAULT_VALUE_US (400u)
// high frequency oscillator wake-up time margin for possible variation
// A shift by 3 will be like a division by 8, so a percentage of 12.5%.
#define HFXO_START_UP_TIME_OVERHEAD_LOG2 3
// Default time value in microseconds for the HFXO minimum off time.
#define HFXO_MINIMUM_OFFTIME_DEFAULT_VALUE_US (400u)
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
// Table size of HFXO wake-up time measurement
#define HFXO_WAKE_UP_TIME_TABLE_SIZE 10
#endif
// Defines for hidden HFXO0 DBGSTATUS register and STARTUPDONE flag
#define HFXO0_DBGSTATUS (*(volatile uint32_t *)(HFXO0_BASE + 0x05C))
#define HFXO_DBGSTATUS_STARTUPDONE (0x1UL << 1) /**< Startup Done Status */
#define _HFXO_DBGSTATUS_STARTUPDONE_SHIFT 1 /**< Shift value for HFXO_STARTUPDONE */
#define _HFXO_DBGSTATUS_STARTUPDONE_MASK 0x2UL /**< Bit mask for HFXO_STARTUPDONE */
/*******************************************************************************
******************************* MACROS *************************************
******************************************************************************/
/*******************************************************************************
* DPLL lock time can be approximately calculated by the equation:
* COARSECOUNT * (M + 1) * Tref
* Where
* - COARSECOUNT is calibration value in a hidden register. Its default value
* is 5 and should not change with calibration.
* - M is one the DPLL configuration parameter.
* - Tref is the reference clock period.
*******************************************************************************/
#define DPLL_LOCKING_DELAY_US_FUNCTION(M, freq_ref) \
(uint32_t)(((uint64_t)(DPLL_COARSECOUNT_VALUE * ((M) +1)) * 1000000 + ((freq_ref) - 1)) / (freq_ref))
/*******************************************************************************
*************************** LOCAL VARIABLES ********************************
******************************************************************************/
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
// Variables to save the relevant clock registers.
uint32_t cmu_em01_grpA_clock_register;
#if defined(_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK)
uint32_t cmu_em01_grpB_clock_register;
#endif
#if defined(_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK)
uint32_t cmu_em01_grpC_clock_register;
#endif
#if defined(_CMU_DPLLREFCLKCTRL_CLKSEL_MASK)
uint32_t cmu_dpll_ref_clock_register;
#endif
uint32_t cmu_sys_clock_register;
// Time in ticks required for the general wake-up process.
static uint32_t process_wakeup_overhead_tick = 0;
#if defined(EMU_VSCALE_PRESENT)
static bool is_fast_wakeup_enabled = true;
#endif
static bool is_hf_x_oscillator_used = false;
static bool is_dpll_used = false;
static bool is_entering_deepsleep = false;
static bool is_hf_x_oscillator_already_started = false;
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
static uint32_t hf_x_oscillator_wakeup_time_tc_inital = 0;
static uint32_t hfxo_wakeup_time_table[HFXO_WAKE_UP_TIME_TABLE_SIZE];
static uint8_t hfxo_wakeup_time_table_index = 0;
static uint32_t hfxo_wakeup_time_sum_average = 0;
// Time in ticks required for HFXO start-up after wake-up from sleep.
static uint32_t hfxo_wakeup_time_tick = 0;
#endif
#endif
/***************************************************************************//**
* Do some hardware initialization if necessary.
******************************************************************************/
void sli_power_manager_init_hardware(void)
{
// Initializes EMU (voltage scaling in EM2/3)
#if defined(EMU_VSCALE_EM01_PRESENT)
EMU_EM01Init_TypeDef em01_init = EMU_EM01INIT_DEFAULT;
EMU_EM01Init(&em01_init);
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
#if defined(EMU_VSCALE_PRESENT)
#if defined(SL_POWER_MANAGER_CONFIG_VOLTAGE_SCALING_FAST_WAKEUP)
#if (SL_POWER_MANAGER_CONFIG_VOLTAGE_SCALING_FAST_WAKEUP == 0)
sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(false);
#else
sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(true);
#endif
#else
sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(false);
#endif
#endif
// Get the current HF oscillator for the SYSCLK
cmu_sys_clock_register = CMU->SYSCLKCTRL & _CMU_SYSCLKCTRL_CLKSEL_MASK;
#if defined(_CMU_DPLLREFCLKCTRL_CLKSEL_MASK)
cmu_dpll_ref_clock_register = CMU->DPLLREFCLKCTRL & _CMU_DPLLREFCLKCTRL_CLKSEL_MASK;
#endif
#if defined(CMU_CLKEN0_DPLL0)
CMU->CLKEN0_SET = CMU_CLKEN0_HFXO0;
CMU->CLKEN0_SET = CMU_CLKEN0_DPLL0;
#endif
is_dpll_used = ((DPLL0->STATUS & _DPLL_STATUS_ENS_MASK) != 0);
is_hf_x_oscillator_used = ((cmu_sys_clock_register == CMU_SYSCLKCTRL_CLKSEL_HFXO)
|| ((CMU->EM01GRPACLKCTRL & _CMU_EM01GRPACLKCTRL_CLKSEL_MASK) == CMU_EM01GRPACLKCTRL_CLKSEL_HFXO));
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
is_hf_x_oscillator_used = is_hf_x_oscillator_used || ((CMU->RADIOCLKCTRL & _CMU_RADIOCLKCTRL_EN_MASK) != 0);
#endif
#if defined(CMU_EM01GRPBCLKCTRL_CLKSEL_HFXO)
is_hf_x_oscillator_used = is_hf_x_oscillator_used || ((CMU->EM01GRPBCLKCTRL & _CMU_EM01GRPBCLKCTRL_CLKSEL_MASK) == CMU_EM01GRPBCLKCTRL_CLKSEL_HFXO);
#endif
#if defined(CMU_EM01GRPCCLKCTRL_CLKSEL_HFXO)
is_hf_x_oscillator_used = is_hf_x_oscillator_used || ((CMU->EM01GRPCCLKCTRL & _CMU_EM01GRPCCLKCTRL_CLKSEL_MASK) == CMU_EM01GRPCCLKCTRL_CLKSEL_HFXO);
#endif
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
// Set HFXO wakeup time to conservative default value
hfxo_wakeup_time_tick = sli_power_manager_convert_delay_us_to_tick(HFXO_WAKE_UP_TIME_DEFAULT_VALUE_US);
for (uint8_t i = 0; i < HFXO_WAKE_UP_TIME_TABLE_SIZE; i++) {
hfxo_wakeup_time_table[i] = hfxo_wakeup_time_tick;
hfxo_wakeup_time_sum_average += hfxo_wakeup_time_tick;
}
#endif
if (is_dpll_used && !is_hf_x_oscillator_used) {
is_hf_x_oscillator_used = is_hf_x_oscillator_used || ((CMU->DPLLREFCLKCTRL & _CMU_DPLLREFCLKCTRL_CLKSEL_MASK) == _CMU_DPLLREFCLKCTRL_CLKSEL_HFXO);
}
// Calculate DPLL locking delay from its configuration
if (is_dpll_used) {
uint32_t freq = 0;
switch (CMU->DPLLREFCLKCTRL & _CMU_DPLLREFCLKCTRL_CLKSEL_MASK) {
case _CMU_DPLLREFCLKCTRL_CLKSEL_HFXO:
freq = SystemHFXOClockGet();
break;
case _CMU_DPLLREFCLKCTRL_CLKSEL_LFXO:
freq = SystemLFXOClockGet();
break;
case _CMU_DPLLREFCLKCTRL_CLKSEL_CLKIN0:
freq = SystemCLKIN0Get();
break;
default:
EFM_ASSERT(false);
break;
}
if (freq > 0) { // Avoid division by 0
// Add DPLL Locking delay
process_wakeup_overhead_tick += sli_power_manager_convert_delay_us_to_tick(DPLL_LOCKING_DELAY_US_FUNCTION((DPLL0->CFG1 & _DPLL_CFG1_M_MASK) >> _DPLL_CFG1_M_SHIFT, freq));
}
}
process_wakeup_overhead_tick += sli_power_manager_convert_delay_us_to_tick(EM2_WAKEUP_PROCESS_TIME_OVERHEAD_US);
#endif
}
/***************************************************************************//**
* Enable or disable fast wake-up in EM2 and EM3.
******************************************************************************/
void sli_power_manager_em23_voltage_scaling_enable_fast_wakeup(bool enable)
{
#if (defined(EMU_VSCALE_PRESENT) && !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT))
if (enable == is_fast_wakeup_enabled) {
return;
}
EMU_EM23Init_TypeDef em23_init = EMU_EM23INIT_DEFAULT;
// Enable/disable EMU voltage scaling in EM2/3
if (enable) {
em23_init.vScaleEM23Voltage = emuVScaleEM23_FastWakeup;
} else {
em23_init.vScaleEM23Voltage = emuVScaleEM23_LowPower;
}
EMU_EM23Init(&em23_init);
// Calculate and add voltage scaling wake-up delays in ticks
if (enable) {
// Remove voltage scaling delay if it was added before
process_wakeup_overhead_tick -= sli_power_manager_convert_delay_us_to_tick(EM2_WAKEUP_VSCALE_OVERHEAD_US);
} else {
// Add voltage scaling delay if it was not added before
process_wakeup_overhead_tick += sli_power_manager_convert_delay_us_to_tick(EM2_WAKEUP_VSCALE_OVERHEAD_US);
}
is_fast_wakeup_enabled = enable;
#else
(void)enable;
#endif
}
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Save the CMU HF clock select state, oscillator enable, and voltage scaling.
******************************************************************************/
void sli_power_manager_save_states(void)
{
// Save HF clock sources
cmu_em01_grpA_clock_register = CMU->EM01GRPACLKCTRL & _CMU_EM01GRPACLKCTRL_CLKSEL_MASK;
#if defined(_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK)
cmu_em01_grpB_clock_register = CMU->EM01GRPBCLKCTRL & _CMU_EM01GRPBCLKCTRL_CLKSEL_MASK;
#endif
#if defined(_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK)
cmu_em01_grpC_clock_register = CMU->EM01GRPCCLKCTRL & _CMU_EM01GRPCCLKCTRL_CLKSEL_MASK;
#endif
EMU_Save();
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Handle pre-sleep operations if any are necessary, like manually disabling
* oscillators, change clock settings, etc.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void EMU_EM23PresleepHook(void)
{
// Change the HF Clocks to be on FSRCO before sleep
if (is_entering_deepsleep) {
is_entering_deepsleep = false;
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) | _CMU_SYSCLKCTRL_CLKSEL_FSRCO;
// Switch the HF Clocks oscillator's to FSRCO before deepsleep
CMU->EM01GRPACLKCTRL = (CMU->EM01GRPACLKCTRL & ~_CMU_EM01GRPACLKCTRL_CLKSEL_MASK) | _CMU_EM01GRPACLKCTRL_CLKSEL_FSRCO;
#if defined(_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK)
CMU->EM01GRPBCLKCTRL = (CMU->EM01GRPBCLKCTRL & ~_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK) | _CMU_EM01GRPBCLKCTRL_CLKSEL_FSRCO;
#endif
#if defined(_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK)
CMU->EM01GRPCCLKCTRL = (CMU->EM01GRPCCLKCTRL & ~_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK) | _CMU_EM01GRPCCLKCTRL_CLKSEL_FSRCO;
#endif
// Disable DPLL before deepsleep
#if (_DPLL_IPVERSION_IPVERSION_DEFAULT >= 1)
#if defined(_CMU_DPLLREFCLKCTRL_CLKSEL_MASK)
if (is_dpll_used) {
DPLL0->EN_CLR = DPLL_EN_EN;
while ((DPLL0->EN & _DPLL_EN_DISABLING_MASK) != 0) {
// Wait for DPLL to be disabled.
}
}
#endif
#endif
SystemCoreClockUpdate();
}
// Clear HFXO IEN RDY before entering sleep to prevent HFXO HW requests from waking up the system
HFXO0->IEN_CLR = HFXO_IEN_RDY;
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Handle post-sleep operations. The idea is to start HFXO ASAP when we know we
* will need it.
*
* @note In case HFXO is already started when we wake-up (ENS flag is up),
* the hidden flag STARTUPDONE is check to see if the HFXO was just
* enabled or not. If HFXO is enabled automatically following the wake-up,
* the STARTUPDONE flag will not yet be up, and it's an indication that
* we can still process to the HFXO restore time measurement.
******************************************************************************/
void EMU_EM23PostsleepHook(void)
{
// Re enable HFXO IEN RDY since it was disabled in EMU_EM23PresleepHook
HFXO0->IEN_SET = HFXO_IEN_RDY;
// Poke sleeptimer to determine if power manager's timer expired before the
// ISR handler executes.
// Also, check if HFXO is used.
if (is_hf_x_oscillator_used
&& sli_sleeptimer_hal_is_int_status_set(SLEEPTIMER_EVENT_COMP)
&& sli_sleeptimer_is_power_manager_timer_next_to_expire()) {
// Check if HFXO is already running and has finished its startup.
// If yes, don't do the HFXO restore time measurement.
if (((HFXO0->STATUS & _HFXO_STATUS_ENS_MASK) != 0
&& (HFXO0_DBGSTATUS & _HFXO_DBGSTATUS_STARTUPDONE_MASK) != 0)
|| (HFXO0->STATUS & _HFXO_STATUS_RDY_MASK) != 0) {
#if !defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
// Force-enable HFXO in case the HFXO on-demand request would be removed
// before we finish the restore process.
HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
#endif
return;
}
// Start measure HFXO restore time.
is_hf_x_oscillator_already_started = true;
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
hf_x_oscillator_wakeup_time_tc_inital = sl_sleeptimer_get_tick_count();
// Switch SYSCLK to HFXO to measure restore time
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) | cmuSelect_HFXO;
SystemCoreClockUpdate();
#else
sli_hfxo_manager_begin_startup_measurement();
// Force enable HFXO to measure restore time
HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
#endif
}
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Handle pre-deepsleep operations if any are necessary, like manually disabling
* oscillators, change clock settings, etc.
******************************************************************************/
void sli_power_manager_handle_pre_deepsleep_operations(void)
{
is_entering_deepsleep = true;
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Handle post-sleep operations if any are necessary, like manually enabling
* oscillators, change clock settings, etc.
******************************************************************************/
void sli_power_manager_restore_high_freq_accuracy_clk(void)
{
if (!is_hf_x_oscillator_used) {
return;
}
// For the cases where it's not started from an early wake up
// And if HFXO is not already running.
if (!is_hf_x_oscillator_already_started) {
// Check if HFXO is already running and has finished its startup.
// If yes, don't do the HFXO restore time measurement.
if (((HFXO0->STATUS & _HFXO_STATUS_ENS_MASK) != 0
&& (HFXO0_DBGSTATUS & _HFXO_DBGSTATUS_STARTUPDONE_MASK) != 0)
|| (HFXO0->STATUS & _HFXO_STATUS_RDY_MASK) != 0) {
#if !defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
// Force-enable HFXO in case the HFXO on-demand request would be removed
// before we finish the restore process.
HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
#endif
return;
}
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
hf_x_oscillator_wakeup_time_tc_inital = sl_sleeptimer_get_tick_count();
// Switch SYSCLK to HFXO to measure restore time
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) | cmuSelect_HFXO;
SystemCoreClockUpdate();
#else
// Start measure HFXO restore time
sli_hfxo_manager_begin_startup_measurement();
// Force enable HFXO to measure restore time
HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
#endif
}
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
uint32_t current_time = sl_sleeptimer_get_tick_count() - hf_x_oscillator_wakeup_time_tc_inital;
// Calculate average for HFXO restore time
hfxo_wakeup_time_sum_average -= (int32_t)hfxo_wakeup_time_table[hfxo_wakeup_time_table_index] - (int32_t)current_time;
hfxo_wakeup_time_table[hfxo_wakeup_time_table_index] = current_time;
hfxo_wakeup_time_tick = ((hfxo_wakeup_time_sum_average + (HFXO_WAKE_UP_TIME_TABLE_SIZE - 1) ) / HFXO_WAKE_UP_TIME_TABLE_SIZE);
// Update index of wakeup time table
hfxo_wakeup_time_table_index++;
hfxo_wakeup_time_table_index %= HFXO_WAKE_UP_TIME_TABLE_SIZE;
#endif
is_hf_x_oscillator_already_started = false;
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Checks if HF accuracy clocks is fully restored and, if needed, waits for it.
*
* @param wait True, to wait for HF accuracy clocks to be ready
* False, otherwise.
*
* @return True, if HFXO ready.
* False, otherwise.
******************************************************************************/
bool sli_power_manager_is_high_freq_accuracy_clk_ready(bool wait)
{
if (!is_hf_x_oscillator_used) {
return true;
}
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
(void)wait;
return true;
#else
return sli_hfxo_manager_is_hfxo_ready(wait);
#endif
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Restore CMU HF clock select state, oscillator enable, and voltage scaling.
******************************************************************************/
void sli_power_manager_restore_states(void)
{
// Restore specific EMU saved contexts
EMU_Restore();
// Restore DPLL after deepsleep
#if (_DPLL_IPVERSION_IPVERSION_DEFAULT >= 1)
#if defined(_CMU_DPLLREFCLKCTRL_CLKSEL_MASK)
if (is_dpll_used) {
DPLL0->EN_SET = DPLL_EN_EN;
while ((DPLL0->STATUS & _DPLL_STATUS_RDY_MASK) == 0U) {
// Wait for DPLL to be ready.
}
}
#endif
#endif
// Restore SYSCLK to what it was before the deepsleep
CMU->SYSCLKCTRL = (CMU->SYSCLKCTRL & ~_CMU_SYSCLKCTRL_CLKSEL_MASK) | cmu_sys_clock_register;
// Restore the HF Clocks to what they were before deepsleep
CMU->EM01GRPACLKCTRL = (CMU->EM01GRPACLKCTRL & ~_CMU_EM01GRPACLKCTRL_CLKSEL_MASK) | cmu_em01_grpA_clock_register;
#if defined(_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK)
CMU->EM01GRPBCLKCTRL = (CMU->EM01GRPBCLKCTRL & ~_CMU_EM01GRPBCLKCTRL_CLKSEL_MASK) | cmu_em01_grpB_clock_register;
#endif
#if defined(_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK)
CMU->EM01GRPCCLKCTRL = (CMU->EM01GRPCCLKCTRL & ~_CMU_EM01GRPCCLKCTRL_CLKSEL_MASK) | cmu_em01_grpC_clock_register;
#endif
// Remove FORCEEN on HFXO
if (is_hf_x_oscillator_used) {
HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
}
SystemCoreClockUpdate();
}
#endif
/***************************************************************************//**
* Applies energy mode.
*
* @param em Energy mode to apply:
* SL_POWER_MANAGER_EM0
* SL_POWER_MANAGER_EM1
* SL_POWER_MANAGER_EM2
*
* @note EMU_EnterEM2() and EMU_EnterEM3() has the parameter 'restore' set to
* true in the Power Manager. When set to true, the parameter 'restore'
* allows the EMU driver to save and restore oscillators, clocks and
* voltage scaling. When the processor returns from EM2 or EM3, its
* execution resumes in a clean and stable state.
******************************************************************************/
void sli_power_manager_apply_em(sl_power_manager_em_t em)
{
#if defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
// Perform required actions according to energy mode
switch (em) {
case SL_POWER_MANAGER_EM1:
case SL_POWER_MANAGER_EM2:
case SL_POWER_MANAGER_EM3:
#if (SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING == 1)
// when measuring interrupt disabled time, we don't
// want to count the time spent in sleep
sl_cycle_counter_pause();
#endif
EMU_EnterEM1();
#if (SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING == 1)
sl_cycle_counter_resume();
#endif
break;
default:
EFM_ASSERT(false);
break;
}
#else
// Perform required actions according to energy mode
switch (em) {
case SL_POWER_MANAGER_EM1:
#if (SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING == 1)
// when measuring interrupt disabled time, we don't
// want to count the time spent in sleep
sl_cycle_counter_pause();
#endif
EMU_EnterEM1();
#if (SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING == 1)
sl_cycle_counter_resume();
#endif
break;
case SL_POWER_MANAGER_EM2:
EMU_EnterEM2(false);
break;
case SL_POWER_MANAGER_EM3:
EMU_EnterEM3(false);
break;
default:
EFM_ASSERT(false);
break;
}
#endif // SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT
}
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/*******************************************************************************
* Returns the default minimum offtime for HFXO.
******************************************************************************/
uint32_t sli_power_manager_get_default_high_frequency_minimum_offtime(void)
{
return sli_power_manager_convert_delay_us_to_tick(HFXO_MINIMUM_OFFTIME_DEFAULT_VALUE_US);
}
#endif
/*******************************************************************************
* Gets the delay associated the wake-up process from EM23.
*
* @return Delay for the complete wake-up process with full restore.
******************************************************************************/
uint32_t sli_power_manager_get_wakeup_process_time_overhead(void)
{
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
uint32_t delay = 0;
// Add HFXO start-up delay if applicable
if (is_hf_x_oscillator_used) {
#if defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT)
delay = hfxo_wakeup_time_tick;
#else
delay = sli_hfxo_manager_get_startup_time();
#endif
delay += delay >> HFXO_START_UP_TIME_OVERHEAD_LOG2;
}
// Add all additional overhead wake-up delays (DPLL, VSCALE, general wake-up process)
delay += process_wakeup_overhead_tick;
return delay;
#else
return 0;
#endif
}
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/***************************************************************************//**
* Informs the power manager module that the high accuracy/high frequency clock
* is used.
******************************************************************************/
void sli_power_manager_set_high_accuracy_hf_clock_as_used(void)
{
is_hf_x_oscillator_used = true;
}
#endif
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/*******************************************************************************
* Restores the Low Frequency clocks according to what LF oscillators are used.
*
* @note On series 2, the on-demand will enable automatically the oscillators
* used when coming from sleep.
******************************************************************************/
void sli_power_manager_low_frequency_restore(void)
{
// Nothing to do as on-demand feature will enable the LF oscillators automatically.
}
/***************************************************************************//**
* Informs the power manager if the high accuracy/high frequency clock
* is used, prior to scheduling an early clock restore.
*
* @return true if HFXO is used, else false.
******************************************************************************/
bool sli_power_manager_is_high_freq_accuracy_clk_used(void)
{
return is_hf_x_oscillator_used;
}
#endif
#if defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/*******************************************************************************
* HAL hook function for pre EM1HCLKDIV sleep.
******************************************************************************/
void sli_power_manager_em1hclkdiv_presleep_operations(void)
{
// No operations to do before EM1HCLKDIV sleep on series 2 devices
}
/*******************************************************************************
* HAL hook function for post EM1HCLKDIV sleep.
******************************************************************************/
void sli_power_manager_em1hclkdiv_postsleep_operations(void)
{
// No operations to do before EM1HCLKDIV sleep on series 2 devices
}
#endif
#endif

View File

@@ -0,0 +1,164 @@
/***************************************************************************//**
* @file
* @brief Power Manager Internal API definition.
*******************************************************************************
* # License
* <b>Copyright 2019 Silicon Laboratories Inc. www.silabs.com</b>
*******************************************************************************
*
* 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.
*
******************************************************************************/
#include "sl_power_manager.h"
#include "sl_slist.h"
#include "sl_code_classification.h"
#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif
#if defined(SL_CATALOG_EMLIB_CORE_DEBUG_CONFIG_PRESENT)
#include "emlib_core_debug_config.h"
#endif
#if !defined(SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING)
#define SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING 0
#endif
#if (SL_EMLIB_CORE_ENABLE_INTERRUPT_DISABLED_TIMING == 1)
#include "sl_cycle_counter.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
#define SLI_POWER_MANAGER_EM_TABLE_SIZE 2
#define SLI_POWER_MANAGER_EM4_ENTRY_WAIT_LOOPS 200
/*******************************************************************************
***************************** DATA TYPES *********************************
******************************************************************************/
// Debug entry
typedef struct {
sl_slist_node_t node;
const char *module_name;
} sli_power_debug_requirement_entry_t;
/*******************************************************************************
***************************** PROTOTYPES **********************************
******************************************************************************/
void sli_power_manager_init_hardware(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_apply_em(sl_power_manager_em_t em);
void sli_power_manager_debug_init(void);
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_save_states(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_handle_pre_deepsleep_operations(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_restore_high_freq_accuracy_clk(void);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
bool sli_power_manager_is_high_freq_accuracy_clk_ready(bool wait);
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_restore_states(void);
/*******************************************************************************
* Converts microseconds time in sleeptimer ticks.
******************************************************************************/
uint32_t sli_power_manager_convert_delay_us_to_tick(uint32_t time_us);
/*******************************************************************************
* Returns the default minimum offtime for xtal high frequency oscillator.
******************************************************************************/
uint32_t sli_power_manager_get_default_high_frequency_minimum_offtime(void);
/*******************************************************************************
* Restores the Low Frequency clocks according to which LF oscillators are used.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_low_frequency_restore(void);
/***************************************************************************//**
* Informs the power manager if the high accuracy/high frequency clock
* is used, prior to scheduling an early clock restore.
*
* @return true if HFXO is used, else false.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
bool sli_power_manager_is_high_freq_accuracy_clk_used(void);
#endif
/*******************************************************************************
* Gets the delay associated the wake-up process from EM23.
*
* @return Delay for the complete wake-up process with full restore.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
uint32_t sli_power_manager_get_wakeup_process_time_overhead(void);
#if !defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/*******************************************************************************
* Gets the status of power manager variable is_sleeping_waiting_for_clock_restore.
*
* @return true if Power Manager is sleeping waiting for clock restore, else false.
*
* @note FOR INTERNAL USE ONLY.
******************************************************************************/
bool sli_power_manager_get_clock_restore_status(void);
#endif
#if defined(SL_CATALOG_POWER_MANAGER_NO_DEEPSLEEP_PRESENT)
/*******************************************************************************
* HAL hook function for pre EM1HCLKDIV sleep.
*
* @note FOR INTERNAL USE ONLY.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_em1hclkdiv_presleep_operations(void);
/*******************************************************************************
* HAL hook function for post EM1HCLKDIV sleep.
*
* @note FOR INTERNAL USE ONLY.
******************************************************************************/
SL_CODE_CLASSIFY(SL_CODE_COMPONENT_POWER_MANAGER, SL_CODE_CLASS_TIME_CRITICAL)
void sli_power_manager_em1hclkdiv_postsleep_operations(void);
#endif
#ifdef __cplusplus
}
#endif