refactor(gptimer): sleep retention code clean up

This commit is contained in:
morris 2024-06-04 14:52:11 +08:00
parent 22a85517ff
commit 3ef9426e2a
No known key found for this signature in database
GPG Key ID: FFB9C5DC0B9DF7A6
18 changed files with 414 additions and 383 deletions

View File

@ -1,24 +1,23 @@
idf_build_get_property(target IDF_TARGET)
if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
set(srcs)
set(public_include "include")
if(CONFIG_SOC_GPTIMER_SUPPORTED)
list(APPEND srcs "src/gptimer.c"
"src/gptimer_priv.c")
"src/gptimer_common.c")
endif()
if(CONFIG_SOC_TIMER_SUPPORT_ETM)
list(APPEND srcs "src/gptimer_etm.c")
endif()
if(${target} STREQUAL "linux")
set(requires "")
else()
set(requires esp_pm)
endif()
set(requires esp_pm)
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${public_include}
REQUIRES "${requires}"
REQUIRES ${requires}
LDFRAGMENTS "linker.lf"
)

View File

@ -17,76 +17,14 @@
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_pm.h"
#include "driver/gptimer.h"
#include "hal/timer_types.h"
#include "hal/timer_hal.h"
#include "hal/timer_ll.h"
#include "soc/timer_periph.h"
#include "esp_memory_utils.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/esp_clk.h"
#include "clk_ctrl_os.h"
#include "esp_clk_tree.h"
#include "gptimer_priv.h"
#if GPTIMER_USE_RETENTION_LINK
#include "esp_private/sleep_retention.h"
#endif
static const char *TAG = "gptimer";
#if SOC_PERIPH_CLK_CTRL_SHARED
#define GPTIMER_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define GPTIMER_CLOCK_SRC_ATOMIC()
#endif
typedef struct gptimer_platform_t {
_lock_t mutex; // platform level mutex lock
gptimer_group_t *groups[SOC_TIMER_GROUPS]; // timer group pool
int group_ref_counts[SOC_TIMER_GROUPS]; // reference count used to protect group install/uninstall
} gptimer_platform_t;
// gptimer driver platform, it's always a singleton
static gptimer_platform_t s_platform;
static gptimer_group_t *gptimer_acquire_group_handle(int group_id);
static void gptimer_release_group_handle(gptimer_group_t *group);
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz);
static void gptimer_default_isr(void *args);
#if GPTIMER_USE_RETENTION_LINK
static esp_err_t sleep_tg_timer_retention_link_cb(void *arg)
{
uint32_t group_id = *(uint32_t *)arg;
esp_err_t err = sleep_retention_entries_create(tg_timer_regs_retention[group_id].link_list,
tg_timer_regs_retention[group_id].link_num,
REGDMA_LINK_PRI_6,
(group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER : SLEEP_RETENTION_MODULE_TG1_TIMER);
if (err == ESP_OK) {
ESP_LOGD(TAG, "Timer group %ld retention initialization", group_id);
}
ESP_RETURN_ON_ERROR(err, TAG, "Failed to create sleep retention linked list for timer group %ld", group_id);
return err;
}
static void gptimer_create_retention_module(gptimer_group_t *group)
{
_lock_acquire(&s_platform.mutex);
int group_id = group->group_id;
if ((group->sleep_retention_initialized == true) && (group->retention_link_created == false)) {
esp_err_t err = sleep_retention_module_allocate((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER : SLEEP_RETENTION_MODULE_TG1_TIMER);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Failed to allocate sleep retention linked list for timer group %d retention, power domain can't turn off", group_id);
} else {
group->retention_link_created = true;
}
}
_lock_release(&s_platform.mutex);
}
#endif
static esp_err_t gptimer_register_to_group(gptimer_t *timer)
{
gptimer_group_t *group = NULL;
@ -156,6 +94,10 @@ esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *re
TAG, "invalid interrupt priority:%d", config->intr_priority);
}
#if !SOC_TIMER_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(config->flags.backup_before_sleep == 0, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
#endif // SOC_TIMER_SUPPORT_SLEEP_RETENTION
timer = heap_caps_calloc(1, sizeof(gptimer_t), GPTIMER_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for gptimer");
// register timer to the group (because one group can have several timers)
@ -392,8 +334,8 @@ esp_err_t gptimer_start(gptimer_handle_t timer)
// the register used by the following LL functions are shared with other API,
// which is possible to run along with this function, so we need to protect
portENTER_CRITICAL_SAFE(&timer->spinlock);
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
timer_ll_enable_alarm(timer->hal.dev, timer->timer_id, timer->flags.alarm_en);
timer_ll_enable_counter(timer->hal.dev, timer->timer_id, true);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
} else {
ESP_RETURN_ON_FALSE_ISR(false, ESP_ERR_INVALID_STATE, TAG, "timer is not enabled yet");
@ -422,172 +364,6 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
return ESP_OK;
}
static gptimer_group_t *gptimer_acquire_group_handle(int group_id)
{
bool new_group = false;
gptimer_group_t *group = NULL;
// prevent install timer group concurrently
_lock_acquire(&s_platform.mutex);
if (!s_platform.groups[group_id]) {
group = heap_caps_calloc(1, sizeof(gptimer_group_t), GPTIMER_MEM_ALLOC_CAPS);
if (group) {
new_group = true;
s_platform.groups[group_id] = group;
// initialize timer group members
group->group_id = group_id;
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
}
} else {
group = s_platform.groups[group_id];
}
if (group) {
// someone acquired the group handle means we have a new object that refer to this group
s_platform.group_ref_counts[group_id]++;
}
if (new_group) {
// !!! HARDWARE SHARED RESOURCE !!!
// the gptimer and watchdog reside in the same the timer group
// we need to increase/decrease the reference count before enable/disable/reset the peripheral
PERIPH_RCC_ACQUIRE_ATOMIC(timer_group_periph_signals.groups[group_id].module, ref_count) {
if (ref_count == 0) {
timer_ll_enable_bus_clock(group_id, true);
timer_ll_reset_register(group_id);
}
}
ESP_LOGD(TAG, "new group (%d) @%p", group_id, group);
#if GPTIMER_USE_RETENTION_LINK
if (group->sleep_retention_initialized != true) {
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = sleep_tg_timer_retention_link_cb,
.arg = &group_id
},
},
.depends = BIT(SLEEP_RETENTION_MODULE_CLOCK_SYSTEM)
};
esp_err_t err = sleep_retention_module_init((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER : SLEEP_RETENTION_MODULE_TG1_TIMER, &init_param);
if (err == ESP_OK) {
group->sleep_retention_initialized = true;
} else {
ESP_LOGW(TAG, "Failed to allocate sleep retention linked list for timer group %d retention", group_id);
}
}
#endif // GPTIMER_USE_RETENTION_LINK
}
_lock_release(&s_platform.mutex);
return group;
}
static void gptimer_release_group_handle(gptimer_group_t *group)
{
int group_id = group->group_id;
bool do_deinitialize = false;
_lock_acquire(&s_platform.mutex);
s_platform.group_ref_counts[group_id]--;
if (s_platform.group_ref_counts[group_id] == 0) {
assert(s_platform.groups[group_id]);
do_deinitialize = true;
s_platform.groups[group_id] = NULL;
}
if (do_deinitialize) {
// disable bus clock for the timer group
PERIPH_RCC_RELEASE_ATOMIC(timer_group_periph_signals.groups[group_id].module, ref_count) {
if (ref_count == 0) {
timer_ll_enable_bus_clock(group_id, false);
}
}
#if GPTIMER_USE_RETENTION_LINK
if (group->retention_link_created) {
sleep_retention_module_free((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER : SLEEP_RETENTION_MODULE_TG1_TIMER);
}
if (group->sleep_retention_initialized) {
sleep_retention_module_deinit((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER : SLEEP_RETENTION_MODULE_TG1_TIMER);
}
#endif
free(group);
ESP_LOGD(TAG, "del group (%d)", group_id);
}
_lock_release(&s_platform.mutex);
}
static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz)
{
uint32_t counter_src_hz = 0;
int timer_id = timer->timer_id;
// TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source
#if SOC_TIMER_GROUP_SUPPORT_RC_FAST
if (src_clk == GPTIMER_CLK_SRC_RC_FAST) {
// RC_FAST clock is not enabled automatically on start up, we enable it here manually.
// Note there's a ref count in the enable/disable function, we must call them in pair in the driver.
periph_rtc_dig_clk8m_enable();
}
#endif // SOC_TIMER_GROUP_SUPPORT_RC_FAST
// get clock source frequency
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)src_clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz),
TAG, "get clock source frequency failed");
#if CONFIG_PM_ENABLE
bool need_pm_lock = true;
// to make the gptimer work reliable, the source clock must stay alive and unchanged
// driver will create different pm lock for that purpose, according to different clock source
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
#if SOC_TIMER_GROUP_SUPPORT_RC_FAST
if (src_clk == GPTIMER_CLK_SRC_RC_FAST) {
// RC_FAST won't be turn off in sleep and won't change its frequency during DFS
need_pm_lock = false;
}
#endif // SOC_TIMER_GROUP_SUPPORT_RC_FAST
#if SOC_TIMER_GROUP_SUPPORT_APB
if (src_clk == GPTIMER_CLK_SRC_APB) {
// APB clock frequency can be changed during DFS
pm_lock_type = ESP_PM_APB_FREQ_MAX;
}
#endif // SOC_TIMER_GROUP_SUPPORT_APB
#if CONFIG_IDF_TARGET_ESP32C2
if (src_clk == GPTIMER_CLK_SRC_PLL_F40M) {
// although PLL_F40M clock is a fixed PLL clock, which is unchangeable
// on ESP32C2, PLL_F40M can be turned off even during DFS (unlike other PLL clocks)
// so we're acquiring a fake "APB" lock here to prevent the system from doing DFS
pm_lock_type = ESP_PM_APB_FREQ_MAX;
}
#endif // CONFIG_IDF_TARGET_ESP32C2
if (need_pm_lock) {
sprintf(timer->pm_lock_name, "gptimer_%d_%d", timer->group->group_id, timer_id); // e.g. gptimer_0_0
ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, timer->pm_lock_name, &timer->pm_lock),
TAG, "create pm lock failed");
}
#endif // CONFIG_PM_ENABLE
// !!! HARDWARE SHARED RESOURCE !!!
// on some ESP chip, different peripheral's clock source setting are mixed in the same register
// so we need to make this done in an atomic way
GPTIMER_CLOCK_SRC_ATOMIC() {
timer_ll_set_clock_source(timer->hal.dev, timer_id, src_clk);
timer_ll_enable_clock(timer->hal.dev, timer_id, true);
}
timer->clk_src = src_clk;
uint32_t prescale = counter_src_hz / resolution_hz; // potential resolution loss here
timer_ll_set_clock_prescale(timer->hal.dev, timer_id, prescale);
timer->resolution_hz = counter_src_hz / prescale; // this is the real resolution
if (timer->resolution_hz != resolution_hz) {
ESP_LOGW(TAG, "resolution lost, expect %"PRIu32", real %"PRIu32, resolution_hz, timer->resolution_hz);
}
return ESP_OK;
}
static void gptimer_default_isr(void *args)
{
bool need_yield = false;

View File

@ -0,0 +1,228 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_clk_tree.h"
#include "esp_private/gptimer.h"
#include "gptimer_priv.h"
static const char *TAG = "gptimer";
typedef struct gptimer_platform_t {
_lock_t mutex; // platform level mutex lock
gptimer_group_t *groups[SOC_TIMER_GROUPS]; // timer group pool
int group_ref_counts[SOC_TIMER_GROUPS]; // reference count used to protect group install/uninstall
} gptimer_platform_t;
// gptimer driver platform, it's always a singleton
static gptimer_platform_t s_platform;
#if GPTIMER_USE_RETENTION_LINK
static esp_err_t gptimer_create_sleep_retention_link_cb(void *arg)
{
gptimer_group_t *group = (gptimer_group_t *)arg;
int group_id = group->group_id;
sleep_retention_module_t module = group->sleep_retention_module;
esp_err_t err = sleep_retention_entries_create(tg_timer_reg_retention_info[group_id].regdma_entry_array,
tg_timer_reg_retention_info[group_id].array_size,
REGDMA_LINK_PRI_GPTIMER, module);
ESP_RETURN_ON_ERROR(err, TAG, "create retention link failed");
return ESP_OK;
}
void gptimer_create_retention_module(gptimer_group_t *group)
{
sleep_retention_module_t module = group->sleep_retention_module;
_lock_acquire(&s_platform.mutex);
if (group->retention_link_created == false) {
if (sleep_retention_module_allocate(module) != ESP_OK) {
// even though the sleep retention module create failed, GPTimer driver should still work, so just warning here
ESP_LOGW(TAG, "create retention module for group %d retention, power domain can't turn off", group->group_id);
} else {
group->retention_link_created = true;
}
}
_lock_release(&s_platform.mutex);
}
#endif // GPTIMER_USE_RETENTION_LINK
gptimer_group_t *gptimer_acquire_group_handle(int group_id)
{
bool new_group = false;
gptimer_group_t *group = NULL;
// prevent install timer group concurrently
_lock_acquire(&s_platform.mutex);
if (!s_platform.groups[group_id]) {
group = heap_caps_calloc(1, sizeof(gptimer_group_t), GPTIMER_MEM_ALLOC_CAPS);
if (group) {
new_group = true;
s_platform.groups[group_id] = group;
// initialize timer group members
group->group_id = group_id;
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
}
} else {
group = s_platform.groups[group_id];
}
if (group) {
// someone acquired the group handle means we have a new object that refer to this group
s_platform.group_ref_counts[group_id]++;
}
_lock_release(&s_platform.mutex);
if (new_group) {
// !!! HARDWARE SHARED RESOURCE !!!
// the gptimer and watchdog reside in the same the timer group
// we need to increase/decrease the reference count before enable/disable/reset the peripheral
PERIPH_RCC_ACQUIRE_ATOMIC(timer_group_periph_signals.groups[group_id].module, ref_count) {
if (ref_count == 0) {
timer_ll_enable_bus_clock(group_id, true);
timer_ll_reset_register(group_id);
}
}
#if GPTIMER_USE_RETENTION_LINK
sleep_retention_module_t module = TIMER_LL_SLEEP_RETENTION_MODULE_ID(group_id);
sleep_retention_module_init_param_t init_param = {
.cbs = {
.create = {
.handle = gptimer_create_sleep_retention_link_cb,
.arg = group
},
},
.depends = BIT(SLEEP_RETENTION_MODULE_CLOCK_SYSTEM)
};
if (sleep_retention_module_init(module, &init_param) == ESP_OK) {
group->sleep_retention_module = module;
} else {
// even though the sleep retention module init failed, RMT driver should still work, so just warning here
ESP_LOGW(TAG, "init sleep retention failed %d, power domain may be turned off during sleep", group_id);
}
#endif // GPTIMER_USE_RETENTION_LINK
ESP_LOGD(TAG, "new group (%d) @%p", group_id, group);
}
return group;
}
void gptimer_release_group_handle(gptimer_group_t *group)
{
int group_id = group->group_id;
bool do_deinitialize = false;
_lock_acquire(&s_platform.mutex);
s_platform.group_ref_counts[group_id]--;
if (s_platform.group_ref_counts[group_id] == 0) {
assert(s_platform.groups[group_id]);
do_deinitialize = true;
s_platform.groups[group_id] = NULL;
}
_lock_release(&s_platform.mutex);
if (do_deinitialize) {
// disable bus clock for the timer group
PERIPH_RCC_RELEASE_ATOMIC(timer_group_periph_signals.groups[group_id].module, ref_count) {
if (ref_count == 0) {
timer_ll_enable_bus_clock(group_id, false);
}
}
#if GPTIMER_USE_RETENTION_LINK
if (group->sleep_retention_module) {
if (group->retention_link_created) {
sleep_retention_module_free(group->sleep_retention_module);
}
sleep_retention_module_deinit(group->sleep_retention_module);
}
#endif
free(group);
ESP_LOGD(TAG, "del group (%d)", group_id);
}
}
esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz)
{
uint32_t counter_src_hz = 0;
int timer_id = timer->timer_id;
// TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source
#if SOC_TIMER_GROUP_SUPPORT_RC_FAST
if (src_clk == GPTIMER_CLK_SRC_RC_FAST) {
// RC_FAST clock is not enabled automatically on start up, we enable it here manually.
// Note there's a ref count in the enable/disable function, we must call them in pair in the driver.
periph_rtc_dig_clk8m_enable();
}
#endif // SOC_TIMER_GROUP_SUPPORT_RC_FAST
// get clock source frequency
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)src_clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz),
TAG, "get clock source frequency failed");
#if CONFIG_PM_ENABLE
bool need_pm_lock = true;
// to make the gptimer work reliable, the source clock must stay alive and unchanged
// driver will create different pm lock for that purpose, according to different clock source
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
#if SOC_TIMER_GROUP_SUPPORT_RC_FAST
if (src_clk == GPTIMER_CLK_SRC_RC_FAST) {
// RC_FAST won't be turn off in sleep and won't change its frequency during DFS
need_pm_lock = false;
}
#endif // SOC_TIMER_GROUP_SUPPORT_RC_FAST
#if SOC_TIMER_GROUP_SUPPORT_APB
if (src_clk == GPTIMER_CLK_SRC_APB) {
// APB clock frequency can be changed during DFS
pm_lock_type = ESP_PM_APB_FREQ_MAX;
}
#endif // SOC_TIMER_GROUP_SUPPORT_APB
#if CONFIG_IDF_TARGET_ESP32C2
if (src_clk == GPTIMER_CLK_SRC_PLL_F40M) {
// although PLL_F40M clock is a fixed PLL clock, which is unchangeable
// on ESP32C2, PLL_F40M can be turned off even during DFS (unlike other PLL clocks)
// so we're acquiring a fake "APB" lock here to prevent the system from doing DFS
pm_lock_type = ESP_PM_APB_FREQ_MAX;
}
#endif // CONFIG_IDF_TARGET_ESP32C2
if (need_pm_lock) {
sprintf(timer->pm_lock_name, "gptimer_%d_%d", timer->group->group_id, timer_id); // e.g. gptimer_0_0
ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_lock_type, 0, timer->pm_lock_name, &timer->pm_lock),
TAG, "create pm lock failed");
}
#endif // CONFIG_PM_ENABLE
// !!! HARDWARE SHARED RESOURCE !!!
// on some ESP chip, different peripheral's clock source setting are mixed in the same register
// so we need to make this done in an atomic way
GPTIMER_CLOCK_SRC_ATOMIC() {
timer_ll_set_clock_source(timer->hal.dev, timer_id, src_clk);
timer_ll_enable_clock(timer->hal.dev, timer_id, true);
}
timer->clk_src = src_clk;
uint32_t prescale = counter_src_hz / resolution_hz; // potential resolution loss here
timer_ll_set_clock_prescale(timer->hal.dev, timer_id, prescale);
timer->resolution_hz = counter_src_hz / prescale; // this is the real resolution
if (timer->resolution_hz != resolution_hz) {
ESP_LOGW(TAG, "resolution lost, expect %"PRIu32", real %"PRIu32, resolution_hz, timer->resolution_hz);
}
return ESP_OK;
}
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
{
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_intr_handle = timer->intr;
return ESP_OK;
}
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
{
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_pm_lock = timer->pm_lock;
return ESP_OK;
}

View File

@ -1,25 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_private/gptimer.h"
#include "gptimer_priv.h"
static const char *TAG = "gptimer";
esp_err_t gptimer_get_intr_handle(gptimer_handle_t timer, intr_handle_t *ret_intr_handle)
{
ESP_RETURN_ON_FALSE(timer && ret_intr_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_intr_handle = timer->intr;
return ESP_OK;
}
esp_err_t gptimer_get_pm_lock(gptimer_handle_t timer, esp_pm_lock_handle_t *ret_pm_lock)
{
ESP_RETURN_ON_FALSE(timer && ret_pm_lock, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
*ret_pm_lock = timer->pm_lock;
return ESP_OK;
}

View File

@ -9,13 +9,19 @@
#include <stdint.h>
#include <stdatomic.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "clk_ctrl_os.h"
#include "esp_pm.h"
#include "soc/soc_caps.h"
#include "soc/timer_periph.h"
#include "hal/timer_types.h"
#include "hal/timer_hal.h"
#include "hal/timer_ll.h"
#include "esp_private/sleep_retention.h"
#include "esp_private/periph_ctrl.h"
#ifdef __cplusplus
extern "C" {
@ -41,6 +47,12 @@ extern "C" {
#define GPTIMER_USE_RETENTION_LINK (SOC_TIMER_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
#if SOC_PERIPH_CLK_CTRL_SHARED
#define GPTIMER_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define GPTIMER_CLOCK_SRC_ATOMIC()
#endif
typedef struct gptimer_t gptimer_t;
typedef struct gptimer_group_t {
@ -48,7 +60,7 @@ typedef struct gptimer_group_t {
portMUX_TYPE spinlock; // to protect per-group register level concurrent access
gptimer_t *timers[SOC_TIMER_GROUP_TIMERS_PER_GROUP];
#if GPTIMER_USE_RETENTION_LINK
bool sleep_retention_initialized; // mark if the retention link is initialized
sleep_retention_module_t sleep_retention_module; // sleep retention module
bool retention_link_created; // mark if the retention link is created
#endif
} gptimer_group_t;
@ -87,6 +99,11 @@ struct gptimer_t {
} flags;
};
gptimer_group_t *gptimer_acquire_group_handle(int group_id);
void gptimer_release_group_handle(gptimer_group_t *group);
esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz);
void gptimer_create_retention_module(gptimer_group_t *group);
#ifdef __cplusplus
}
#endif

View File

@ -5,6 +5,10 @@ if(CONFIG_GPTIMER_ISR_IRAM_SAFE)
list(APPEND srcs "test_gptimer_iram.c")
endif()
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED AND CONFIG_PM_ENABLE)
list(APPEND srcs "test_gptimer_sleep.c")
endif()
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}

View File

@ -13,15 +13,6 @@
#include "soc/soc_caps.h"
#include "esp_attr.h"
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP && SOC_TIMER_SUPPORT_SLEEP_RETENTION
#include "esp_random.h"
#include "esp_rom_uart.h"
#include "esp_sleep.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_pmu.h"
#endif
#if CONFIG_GPTIMER_ISR_IRAM_SAFE
#define TEST_ALARM_CALLBACK_ATTR IRAM_ATTR
#else
@ -605,82 +596,3 @@ TEST_CASE("gptimer_trig_alarm_with_old_count", "[gptimer]")
TEST_ESP_OK(gptimer_disable(timer));
TEST_ESP_OK(gptimer_del_timer(timer));
}
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP && SOC_TIMER_SUPPORT_SLEEP_RETENTION
static gptimer_handle_t timers[SOC_TIMER_GROUP_TOTAL_TIMERS];
static uint32_t timer_resolution_hz[SOC_TIMER_GROUP_TOTAL_TIMERS];
static void test_gptimer_retention(gptimer_config_t *timer_config)
{
for (int i = 0; i < SOC_TIMER_GROUP_TOTAL_TIMERS; i++) {
TEST_ESP_OK(gptimer_new_timer(timer_config, &timers[i]));
TEST_ESP_OK(gptimer_get_resolution(timers[i], &timer_resolution_hz[i]));
unsigned long long count_start_value = 0, count_end_value = 0;
// Let gptimer run for a while
TEST_ESP_OK(gptimer_enable(timers[i]));
TEST_ESP_OK(gptimer_start(timers[i]));
vTaskDelay((esp_random() % 500) / portTICK_PERIOD_MS);
TEST_ESP_OK(gptimer_stop(timers[i]));
TEST_ESP_OK(gptimer_disable(timers[i]));
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &count_start_value));
esp_sleep_context_t sleep_ctx;
esp_sleep_set_sleep_context(&sleep_ctx);
TEST_ESP_OK(sleep_cpu_configure(true));
esp_rom_output_tx_wait_idle(0);
esp_sleep_enable_timer_wakeup(50 * 1000);
esp_light_sleep_start();
if (timer_config->flags.backup_before_sleep) {
TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
} else {
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
}
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
TEST_ESP_OK(gptimer_enable(timers[i]));
TEST_ESP_OK(gptimer_start(timers[i]));
uint32_t random_time_ms = 500 + esp_random() % 2000;
vTaskDelay(random_time_ms / portTICK_PERIOD_MS);
TEST_ESP_OK(gptimer_get_raw_count(timers[i], &count_end_value));
// Error tolerance is 2%
TEST_ASSERT_UINT_WITHIN(random_time_ms / 50, random_time_ms, 1000 * (count_end_value - count_start_value) / (unsigned long long)timer_resolution_hz[i]);
TEST_ESP_OK(gptimer_stop(timers[i]));
TEST_ESP_OK(gptimer_disable(timers[i]));
TEST_ESP_OK(gptimer_del_timer(timers[i]));
TEST_ESP_OK(sleep_cpu_configure(false));
}
}
TEST_CASE("gptimer context kept after peripheral powerdown lightsleep with backup_before_sleep enable", "[gptimer]")
{
printf("install gptimer driver\r\n");
gptimer_config_t timer_config = {
.resolution_hz = 10 * 1000, // 10KHz, 1 tick = 0.1ms
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.flags.backup_before_sleep = true
};
test_gptimer_retention(&timer_config);
}
TEST_CASE("gptimer context kept after peripheral powerdown lightsleep with backup_before_sleep disable", "[gptimer]")
{
printf("install gptimer driver\r\n");
gptimer_config_t timer_config = {
.resolution_hz = 10 * 1000, // 10KHz, 1 tick = 0.1ms
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.flags.backup_before_sleep = false
};
test_gptimer_retention(&timer_config);
}
#endif

View File

@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "driver/gptimer.h"
#include "soc/soc_caps.h"
#include "esp_sleep.h"
#include "esp_private/sleep_cpu.h"
#include "esp_private/esp_sleep_internal.h"
#include "esp_private/esp_pmu.h"
static bool test_gptimer_alarm_stop_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
TaskHandle_t task_handle = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup;
gptimer_stop(timer);
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
/**
* @brief Test the GPTimer driver can still work after light sleep
*
* @param back_up_before_sleep Whether to back up GPTimer registers before sleep
*/
static void test_gptimer_sleep_retention(bool back_up_before_sleep)
{
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
gptimer_config_t timer_config = {
.resolution_hz = 10000, // 10KHz, 1 tick = 0.1ms
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.flags.backup_before_sleep = back_up_before_sleep,
};
gptimer_handle_t timer = NULL;
TEST_ESP_OK(gptimer_new_timer(&timer_config, &timer));
gptimer_event_callbacks_t cbs = {
.on_alarm = test_gptimer_alarm_stop_callback,
};
TEST_ESP_OK(gptimer_register_event_callbacks(timer, &cbs, task_handle));
gptimer_alarm_config_t alarm_config = {
.alarm_count = 10000, // alarm period = 1s
.reload_count = 5000,
.flags.auto_reload_on_alarm = true,
};
TEST_ESP_OK(gptimer_set_alarm_action(timer, &alarm_config));
TEST_ESP_OK(gptimer_enable(timer));
TEST_ESP_OK(gptimer_start(timer));
/// counting from 0 to 10000, it's about 1 second
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1100)));
uint64_t count_value_before_sleep = 0;
TEST_ESP_OK(gptimer_get_raw_count(timer, &count_value_before_sleep));
printf("count value before sleep: %llu\n", count_value_before_sleep);
// the count value should near the reload value
TEST_ASSERT_INT_WITHIN(1, 5000, count_value_before_sleep);
// Note: don't enable the gptimer before going to sleep, ensure no power management lock is acquired by it
TEST_ESP_OK(gptimer_disable(timer));
esp_sleep_context_t sleep_ctx;
esp_sleep_set_sleep_context(&sleep_ctx);
printf("go to light sleep for 2 seconds\r\n");
#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(true));
#endif
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(2 * 1000 * 1000));
TEST_ESP_OK(esp_light_sleep_start());
printf("Waked up! Let's see if GPTimer driver can still work...\r\n");
#if ESP_SLEEP_POWER_DOWN_CPU
TEST_ESP_OK(sleep_cpu_configure(false));
#endif
printf("check if the sleep happened as expected\r\n");
TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);
#if SOC_TIMER_SUPPORT_SLEEP_RETENTION
if (back_up_before_sleep) {
TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP);
}
#endif
uint64_t count_value_after_sleep = 0;
TEST_ESP_OK(gptimer_get_raw_count(timer, &count_value_after_sleep));
printf("count value after sleep wakeup: %llu\n", count_value_after_sleep);
TEST_ASSERT_EQUAL(count_value_before_sleep, count_value_after_sleep);
// re-enable the timer and start it
TEST_ESP_OK(gptimer_enable(timer));
TEST_ESP_OK(gptimer_start(timer));
/// this time, the timer should count from 5000 to 10000, it's about 0.5 second
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(600)));
TEST_ESP_OK(gptimer_get_raw_count(timer, &count_value_after_sleep));
printf("gptimer count value: %llu\n", count_value_after_sleep);
// the count value should near the reload value
TEST_ASSERT_INT_WITHIN(1, 5000, count_value_after_sleep);
TEST_ESP_OK(gptimer_disable(timer));
TEST_ESP_OK(gptimer_del_timer(timer));
}
TEST_CASE("gptimer can work after light sleep", "[gptimer]")
{
test_gptimer_sleep_retention(false);
#if SOC_TIMER_SUPPORT_SLEEP_RETENTION
test_gptimer_sleep_retention(true);
#endif
}

View File

@ -1,9 +1,7 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
CONFIG_PM_DFS_INIT_AUTO=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
# For TASK WDT timer PD_TOP sleep retention test
CONFIG_ESP_SLEEP_DEBUG=y
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y

View File

@ -5,3 +5,6 @@
CONFIG_FREERTOS_HZ=1000
# Disable nano printf, because we need to print the timer count in %llu format
CONFIG_NEWLIB_NANO_FORMAT=n
# primitives for checking sleep internal state
CONFIG_ESP_SLEEP_DEBUG=y

View File

@ -73,7 +73,6 @@ typedef dma_descriptor_align4_t rmt_dma_descriptor_t;
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1))
// Use retention link only when the target supports sleep retention and PM is enabled
#define RMT_USE_RETENTION_LINK (SOC_RMT_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
typedef struct {

View File

@ -24,6 +24,7 @@ extern "C" {
// Get timer group register base address with giving group number
#define TIMER_LL_GET_HW(group_id) ((group_id == 0) ? (&TIMERG0) : (&TIMERG1))
#define TIMER_LL_EVENT_ALARM(timer_id) (1 << (timer_id))
#define TIMER_LL_SLEEP_RETENTION_MODULE_ID(group_id) ((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER: SLEEP_RETENTION_MODULE_TG1_TIMER)
#define TIMER_LL_ETM_TASK_TABLE(group, timer, task) \
(uint32_t [2][1][GPTIMER_ETM_TASK_MAX]){{{ \

View File

@ -24,6 +24,7 @@ extern "C" {
// Get timer group register base address with giving group number
#define TIMER_LL_GET_HW(group_id) ((group_id == 0) ? (&TIMERG0) : (&TIMERG1))
#define TIMER_LL_EVENT_ALARM(timer_id) (1 << (timer_id))
#define TIMER_LL_SLEEP_RETENTION_MODULE_ID(group_id) ((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER: SLEEP_RETENTION_MODULE_TG1_TIMER)
#define TIMER_LL_ETM_TASK_TABLE(group, timer, task) \
(uint32_t [2][1][GPTIMER_ETM_TASK_MAX]){{{ \

View File

@ -24,6 +24,7 @@ extern "C" {
// Get timer group register base address with giving group number
#define TIMER_LL_GET_HW(group_id) ((group_id == 0) ? (&TIMERG0) : (&TIMERG1))
#define TIMER_LL_EVENT_ALARM(timer_id) (1 << (timer_id))
#define TIMER_LL_SLEEP_RETENTION_MODULE_ID(group_id) ((group_id == 0) ? SLEEP_RETENTION_MODULE_TG0_TIMER: SLEEP_RETENTION_MODULE_TG1_TIMER)
#define TIMER_LL_ETM_TASK_TABLE(group, timer, task) \
(uint32_t[2][2][GPTIMER_ETM_TASK_MAX]){ \

View File

@ -43,13 +43,10 @@ A GPTimer instance is represented by :cpp:type:`gptimer_handle_t`. The driver be
To install a timer instance, there is a configuration structure that needs to be given in advance: :cpp:type:`gptimer_config_t`:
- :cpp:member:`gptimer_config_t::clk_src` selects the source clock for the timer. The available clocks are listed in :cpp:type:`gptimer_clock_source_t`, you can only pick one of them. For the effect on power consumption of different clock source, please refer to Section :ref:`gptimer-power-management`.
- :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer, supported directions are listed in :cpp:type:`gptimer_count_direction_t`, you can only pick one of them.
- :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each count step is equivalent to **1 / resolution_hz** seconds.
- :cpp:member:`gptimer_config::intr_priority` sets the priority of the timer interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority.
- :cpp:member:`gptimer_config_t::backup_before_sleep` enables the backup of the GPTimer registers before entering sleep mode. This option implies an balance between power consumption and memory usage. If the power consumption is not a concern, you can disable this option to save memory. But if you want to save more power, you should enable this option to backup the GPTimer registers before entering sleep mode, and restore them after waking up. This feature depends on specific hardware module, if you enable this flag on an unsupported chip, you will get an error message like ``register back up is not supported``.
- Optional :cpp:member:`gptimer_config_t::intr_shared` sets whether or not mark the timer interrupt source as a shared one. For the pros/cons of a shared interrupt, you can refer to :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`.
With all the above configurations set in the structure, the structure can be passed to :cpp:func:`gptimer_new_timer` which will instantiate the timer instance and return a handle of the timer.
@ -284,9 +281,13 @@ Alarm value can be updated dynamically inside the ISR handler callback, by chang
Power Management
^^^^^^^^^^^^^^^^
There are some power management strategies, which might turn off or change the frequency of GPTimer's source clock to save power consumption. For example, during DFS, APB clock will be scaled down. If light-sleep is also enabled, PLL and XTAL clocks will be powered off. Both of them can result in an inaccurate time keeping.
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source before going to sleep. As a result, the time keeping will be inaccurate.
The driver can prevent the above situation from happening by creating different power management lock according to different clock source. The driver increases the reference count of that power management lock in the :cpp:func:`gptimer_enable` and decrease it in the :cpp:func:`gptimer_disable`. So we can ensure the clock source is stable between :cpp:func:`gptimer_enable` and :cpp:func:`gptimer_disable`.
The driver can prevent the above issue by creating a power management lock. The lock type is set based on different clock sources. The driver will acquire the lock in :cpp:func:`gptimer_enable`, and release it in :cpp:func:`gptimer_disable`. So that the timer can work correctly in between these two functions, because the clock source won't be disabled or adjusted its frequency during this time.
.. only:: SOC_TIMER_SUPPORT_SLEEP_RETENTION
Besides the potential changes to the clock source, when the power management is enabled, the system can also power down a domain where GPTimer register located. To ensure the GPTimer driver can continue work after sleep, we can either backup the GPTimer registers to the RAM, or just refuse to power down. You can choose what to do in :cpp:member:`gptimer_config_t::backup_before_sleep`. It's a balance between power saving and memory consumption. Set it based on your application requirements.
.. _gptimer-iram-safe:

View File

@ -142,15 +142,14 @@ Light-sleep Peripheral Power Down
- INT_MTX
- TEE/APM
- IO_MUX / GPIO
- UART0
- TIMG0
- UART0/1
- GPTimer
- SPI0/1
- SYSTIMER
- RMT
The following peripherals are not yet supported:
- ETM
- TIMG1
- ASSIST_DEBUG
- Trace
- Crypto: AES/ECC/HMAC/RSA/SHA/DS/XTA_AES/ECDSA
@ -164,7 +163,6 @@ Light-sleep Peripheral Power Down
- SARADC
- SDIO
- PARL_IO
- UART1
For peripherals that do not support Light-sleep context retention, if the Power management is enabled, the ``ESP_PM_NO_LIGHT_SLEEP`` lock should be held when the peripheral is working to avoid losing the working context of the peripheral when entering sleep.

View File

@ -43,14 +43,11 @@
要安装一个定时器实例,需要提前提供配置结构体 :cpp:type:`gptimer_config_t`
- :cpp:member:`gptimer_config_t::clk_src` 选择定时器的时钟源。:cpp:type:`gptimer_clock_source_t` 中列出多个可用时钟,仅可选择其中一个时钟。了解不同时钟源对功耗的影响,请查看章节 :ref:`gptimer-power-management`
- :cpp:member:`gptimer_config_t::direction` 设置定时器的计数方向,:cpp:type:`gptimer_count_direction_t` 中列出多个支持的方向,仅可选择其中一个方向。
- :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
- :cpp:member:`gptimer_config::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
- 选用 :cpp:member:`gptimer_config_t::intr_shared` 设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`
- :cpp:member:`gptimer_config::backup_before_sleep` 用于使能在进入睡眠模式前备份 GPTimer 寄存器。这个选项需要用户在功耗和内存使用之间取得平衡。如果功耗不是一个问题,可以禁用这个选项来节省内存。但如果想要节省功耗,应该使能这个选项,在进入睡眠模式前备份 GPTimer 寄存器,并在唤醒后恢复它们。这个功能依赖于特定的硬件模块,如果你在不支持的芯片上启用它,你会得到一个错误信息,如 ``register back up is not supported``
- 可选地, :cpp:member:`gptimer_config_t::intr_shared` 设置是否将定时器中断源标记为共享源。了解共享中断的优缺点,请参考 :doc:`Interrupt Handling <../../api-reference/system/intr_alloc>`
完成上述结构配置之后,可以将结构传递给 :cpp:func:`gptimer_new_timer`,用以实例化定时器实例并返回定时器句柄。
@ -284,9 +281,13 @@
电源管理
^^^^^^^^
有些电源管理的策略会在某些时刻关闭时钟源,或者改变时钟源的频率,以求降低功耗。比如在启用 DFS 后APB 时钟源会降低频率。如果浅睡眠 (Light-sleep) 模式也被开启PLL 和 XTAL 时钟都会被默认关闭,从而导致 GPTimer 的计时不准确。
当电源管理 :ref:`CONFIG_PM_ENABLE` 被启用的时候,系统在进入睡眠前可能会调整或禁用时钟源。结果导致 GPTimer 的计时不准确。
驱动程序会根据具体的时钟源选择,通过创建不同的电源锁来避免上述情况的发生。驱动会在 :cpp:func:`gptimer_enable` 函数中增加电源锁的引用计数,并在 :cpp:func:`gptimer_disable` 函数中减少电源锁的引用计数,从而保证了在 :cpp:func:`gptimer_enable`:cpp:func:`gptimer_disable` 之间GPTimer 的时钟源始处于稳定工作的状态。
驱动程序可以通过创建一个电源管理锁来防止上述问题。锁的类型会根据不同的时钟源来设置。驱动程序将在 :cpp:func:`gptimer_enable` 中拿锁,并在 :cpp:func:`gptimer_disable` 中释放锁。这意味着,在这两个函数之间,定时器可以正确工作,因为此时时钟源不会被禁用或改变频率。
.. only:: SOC_TIMER_SUPPORT_SLEEP_RETENTION
除了时钟源的潜在变化外,当启用电源管理时,系统还可以关闭 GPTimer 寄存器所在的电源域。为确保 GPTimer 驱动程序在睡眠后继续工作,用户要么选择将 GPTimer 相关的寄存器备份到 RAM 中,要么拒绝关闭电源域。你可以根据应用需求在 :cpp:member:`gptimer_config_t::backup_before_sleep` 中设置是否需要启用寄存器备份,在功耗和内存使用之间做权衡。
.. _gptimer-iram-safe:

View File

@ -142,15 +142,14 @@ Light-sleep 外设下电
- INT_MTX
- TEE/APM
- IO_MUX / GPIO
- UART0
- TIMG0
- UART0/1
- GPTimer
- SPI0/1
- SYSTIMER
- RMT
以下外设尚未支持:
- ETM
- TIMG1
- ASSIST_DEBUG
- Trace
- Crypto: AES/ECC/HMAC/RSA/SHA/DS/XTA_AES/ECDSA
@ -164,7 +163,6 @@ Light-sleep 外设下电
- SARADC
- SDIO
- PARL_IO
- UART1
对于未支持 Light-sleep 上下文备份的外设,若启用了电源管理功能,应在外设工作时持有 ``ESP_PM_NO_LIGHT_SLEEP`` 锁以避免进入休眠导致外设工作上下文丢失。