From 998cd5e1f7c0acc5e741bca16d226a47a9c59036 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 4 Jun 2024 14:52:11 +0800 Subject: [PATCH] refactor(gptimer): sleep retention code clean up --- components/esp_driver_gptimer/CMakeLists.txt | 7 +- components/esp_driver_gptimer/src/gptimer.c | 234 +----------------- .../esp_driver_gptimer/src/gptimer_common.c | 228 +++++++++++++++++ .../esp_driver_gptimer/src/gptimer_priv.c | 25 -- .../esp_driver_gptimer/src/gptimer_priv.h | 23 +- .../test_apps/gptimer/main/CMakeLists.txt | 4 + .../test_apps/gptimer/main/test_gptimer.c | 88 ------- .../gptimer/main/test_gptimer_sleep.c | 119 +++++++++ .../test_apps/gptimer/sdkconfig.ci.release | 4 +- .../test_apps/gptimer/sdkconfig.defaults | 3 + components/hal/esp32c6/include/hal/timer_ll.h | 1 + components/hal/esp32h2/include/hal/timer_ll.h | 1 + components/hal/esp32p4/include/hal/timer_ll.h | 1 + docs/en/api-reference/peripherals/gptimer.rst | 17 +- .../api-reference/system/power_management.rst | 6 +- .../api-reference/peripherals/gptimer.rst | 23 +- .../api-reference/system/power_management.rst | 6 +- 17 files changed, 414 insertions(+), 376 deletions(-) create mode 100644 components/esp_driver_gptimer/src/gptimer_common.c delete mode 100644 components/esp_driver_gptimer/src/gptimer_priv.c create mode 100644 components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer_sleep.c diff --git a/components/esp_driver_gptimer/CMakeLists.txt b/components/esp_driver_gptimer/CMakeLists.txt index 9b0871a8f7..fd39190998 100644 --- a/components/esp_driver_gptimer/CMakeLists.txt +++ b/components/esp_driver_gptimer/CMakeLists.txt @@ -1,8 +1,13 @@ +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) diff --git a/components/esp_driver_gptimer/src/gptimer.c b/components/esp_driver_gptimer/src/gptimer.c index 6fd66b9b95..452f4a60f0 100644 --- a/components/esp_driver_gptimer/src/gptimer.c +++ b/components/esp_driver_gptimer/src/gptimer.c @@ -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; diff --git a/components/esp_driver_gptimer/src/gptimer_common.c b/components/esp_driver_gptimer/src/gptimer_common.c new file mode 100644 index 0000000000..ea5bf5e8c1 --- /dev/null +++ b/components/esp_driver_gptimer/src/gptimer_common.c @@ -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; +} diff --git a/components/esp_driver_gptimer/src/gptimer_priv.c b/components/esp_driver_gptimer/src/gptimer_priv.c deleted file mode 100644 index bd834f2014..0000000000 --- a/components/esp_driver_gptimer/src/gptimer_priv.c +++ /dev/null @@ -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; -} diff --git a/components/esp_driver_gptimer/src/gptimer_priv.h b/components/esp_driver_gptimer/src/gptimer_priv.h index fe072c85d4..3c117af44f 100644 --- a/components/esp_driver_gptimer/src/gptimer_priv.h +++ b/components/esp_driver_gptimer/src/gptimer_priv.h @@ -9,13 +9,19 @@ #include #include #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,8 +60,8 @@ 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 - bool retention_link_created; // mark if the retention link is created + 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 diff --git a/components/esp_driver_gptimer/test_apps/gptimer/main/CMakeLists.txt b/components/esp_driver_gptimer/test_apps/gptimer/main/CMakeLists.txt index 2768a1f04e..39337b4ce6 100644 --- a/components/esp_driver_gptimer/test_apps/gptimer/main/CMakeLists.txt +++ b/components/esp_driver_gptimer/test_apps/gptimer/main/CMakeLists.txt @@ -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} diff --git a/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer.c b/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer.c index 4d3e6b4987..757c2f715c 100644 --- a/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer.c +++ b/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer.c @@ -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 diff --git a/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer_sleep.c b/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer_sleep.c new file mode 100644 index 0000000000..ef4f7909e2 --- /dev/null +++ b/components/esp_driver_gptimer/test_apps/gptimer/main/test_gptimer_sleep.c @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 +} diff --git a/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.ci.release b/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.ci.release index 05634936f4..1a87ebbb4a 100644 --- a/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.ci.release +++ b/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.ci.release @@ -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 diff --git a/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.defaults b/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.defaults index 1d96fcebe1..f365803cb0 100644 --- a/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.defaults +++ b/components/esp_driver_gptimer/test_apps/gptimer/sdkconfig.defaults @@ -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 diff --git a/components/hal/esp32c6/include/hal/timer_ll.h b/components/hal/esp32c6/include/hal/timer_ll.h index bc507a3877..a12488a285 100644 --- a/components/hal/esp32c6/include/hal/timer_ll.h +++ b/components/hal/esp32c6/include/hal/timer_ll.h @@ -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]){{{ \ diff --git a/components/hal/esp32h2/include/hal/timer_ll.h b/components/hal/esp32h2/include/hal/timer_ll.h index b62bdb4db8..982d42e500 100644 --- a/components/hal/esp32h2/include/hal/timer_ll.h +++ b/components/hal/esp32h2/include/hal/timer_ll.h @@ -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]){{{ \ diff --git a/components/hal/esp32p4/include/hal/timer_ll.h b/components/hal/esp32p4/include/hal/timer_ll.h index 9dc5f1cef0..e8c678e88e 100644 --- a/components/hal/esp32p4/include/hal/timer_ll.h +++ b/components/hal/esp32p4/include/hal/timer_ll.h @@ -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]){ \ diff --git a/docs/en/api-reference/peripherals/gptimer.rst b/docs/en/api-reference/peripherals/gptimer.rst index b836a05551..65140754bd 100644 --- a/docs/en/api-reference/peripherals/gptimer.rst +++ b/docs/en/api-reference/peripherals/gptimer.rst @@ -43,14 +43,11 @@ 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. - -- 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>`. +- :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: diff --git a/docs/en/api-reference/system/power_management.rst b/docs/en/api-reference/system/power_management.rst index 0d016aae2e..c92d30b7d9 100644 --- a/docs/en/api-reference/system/power_management.rst +++ b/docs/en/api-reference/system/power_management.rst @@ -142,14 +142,13 @@ Light-sleep Peripheral Power Down - INT_MTX - TEE/APM - IO_MUX / GPIO - - UART0 - - TIMG0 + - UART0/1 + - GPTimer - SPI0/1 - SYSTIMER 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. diff --git a/docs/zh_CN/api-reference/peripherals/gptimer.rst b/docs/zh_CN/api-reference/peripherals/gptimer.rst index ecf9cf4cb4..6e23d755dd 100644 --- a/docs/zh_CN/api-reference/peripherals/gptimer.rst +++ b/docs/zh_CN/api-reference/peripherals/gptimer.rst @@ -42,15 +42,12 @@ 要安装一个定时器实例,需要提前提供配置结构体 :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_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::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: diff --git a/docs/zh_CN/api-reference/system/power_management.rst b/docs/zh_CN/api-reference/system/power_management.rst index 05acedc2c1..a3d81f7526 100644 --- a/docs/zh_CN/api-reference/system/power_management.rst +++ b/docs/zh_CN/api-reference/system/power_management.rst @@ -142,14 +142,13 @@ Light-sleep 外设下电 - INT_MTX - TEE/APM - IO_MUX / GPIO - - UART0 - - TIMG0 + - UART0/1 + - GPTimer - SPI0/1 - SYSTIMER 以下外设尚未支持: - 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`` 锁以避免进入休眠导致外设工作上下文丢失。