diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index df1ae18e4d..72c872c703 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -579,9 +579,20 @@ uint64_t esp_sleep_get_gpio_wakeup_status(void); #endif //SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP /** - * @brief Set power down mode for an RTC power domain in sleep mode + * @brief Configure power domain options for sleep mode * - * If not set set using this API, all power domains default to ESP_PD_OPTION_AUTO. + * This function provides power domain management for sleep mode, allowing users + * to control which power domains remain active during sleep to maintain required + * functionality. The function supports: + * - Automatic power management (ESP_PD_OPTION_AUTO): Power domains are controlled + * automatically based on system requirements and other peripheral usage. + * - Manual power control (ESP_PD_OPTION_ON/ESP_PD_OPTION_OFF): User can force + * specific power domains to stay on or off during sleep. + * + * The management strategy uses reference counting when in manual mode, allowing + * multiple subsystems to request and release power domain resources safely without + * interfering with each other. Power domains will only change state when all + * users have released their requirements. * * @param domain power domain to configure * @param option power down option (ESP_PD_OPTION_OFF, ESP_PD_OPTION_ON, or ESP_PD_OPTION_AUTO) diff --git a/components/esp_hw_support/port/regdma_link.c b/components/esp_hw_support/port/regdma_link.c index dd3dabb41d..9e6fef5bbf 100644 --- a/components/esp_hw_support/port/regdma_link.c +++ b/components/esp_hw_support/port/regdma_link.c @@ -605,37 +605,39 @@ static void regdma_link_update_branch_write_wait_next_wrapper(void *link, regdma void regdma_link_update_next(void *link, int nentry, ...) { + if (!link) { + return; + } + va_list args; va_start(args, nentry); - if (link) { - regdma_entry_buf_t next; - memset(next, 0, sizeof(regdma_entry_buf_t)); - for (int i = 0; i < nentry && i < REGDMA_LINK_ENTRY_NUM; i++) { // Ignore more arguments - next[i] = va_arg(args, void *); - } + regdma_entry_buf_t next; + memset(next, 0, sizeof(regdma_entry_buf_t)); + for (int i = 0; i < nentry && i < REGDMA_LINK_ENTRY_NUM; i++) { // Ignore more arguments + next[i] = va_arg(args, void *); + } - regdma_link_head_t head = REGDMA_LINK_HEAD(link); - if (head.branch) { - typedef void (*update_branch_fn_t)(void *, regdma_entry_buf_t *); - static const update_branch_fn_t updatefn_b[] = { - [0] = regdma_link_update_branch_continuous_next_wrapper, - [1] = regdma_link_update_branch_addr_map_next_wrapper, - [2] = regdma_link_update_branch_write_wait_next_wrapper, - [3] = regdma_link_update_branch_write_wait_next_wrapper - }; - assert((head.mode < ARRAY_SIZE(updatefn_b))); - (*updatefn_b[head.mode])(link, &next); - } else { - typedef void (*update_fn_t)(void *, void *); - static const update_fn_t updatefn[] = { - [0] = regdma_link_update_continuous_next_wrapper, - [1] = regdma_link_update_addr_map_next_wrapper, - [2] = regdma_link_update_write_wait_next_wrapper, - [3] = regdma_link_update_write_wait_next_wrapper - }; - assert((head.mode < ARRAY_SIZE(updatefn))); - (*updatefn[head.mode])(link, next[0]); - } + regdma_link_head_t head = REGDMA_LINK_HEAD(link); + if (head.branch) { + typedef void (*update_branch_fn_t)(void *, regdma_entry_buf_t *); + static const update_branch_fn_t updatefn_b[] = { + [0] = regdma_link_update_branch_continuous_next_wrapper, + [1] = regdma_link_update_branch_addr_map_next_wrapper, + [2] = regdma_link_update_branch_write_wait_next_wrapper, + [3] = regdma_link_update_branch_write_wait_next_wrapper + }; + assert((head.mode < ARRAY_SIZE(updatefn_b))); + (*updatefn_b[head.mode])(link, &next); + } else { + typedef void (*update_fn_t)(void *, void *); + static const update_fn_t updatefn[] = { + [0] = regdma_link_update_continuous_next_wrapper, + [1] = regdma_link_update_addr_map_next_wrapper, + [2] = regdma_link_update_write_wait_next_wrapper, + [3] = regdma_link_update_write_wait_next_wrapper + }; + assert((head.mode < ARRAY_SIZE(updatefn))); + (*updatefn[head.mode])(link, next[0]); } va_end(args); } diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 6543c59482..95d2326b2f 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -2452,19 +2452,37 @@ esp_err_t esp_sleep_pd_config(esp_sleep_pd_domain_t domain, esp_sleep_pd_option_ esp_os_enter_critical_safe(&s_config.lock); int refs = 0; if (s_config.domain[domain].pd_option == ESP_PD_OPTION_AUTO) { + // If domain is currently in auto mode, transition to the new mode directly + // - If option is ESP_PD_OPTION_ON: set refs to 1 + // - If option is ESP_PD_OPTION_OFF: set refs to 0 + // - If option is ESP_PD_OPTION_AUTO: no change in refs s_config.domain[domain].refs = (option == ESP_PD_OPTION_ON) ? 1 : 0; s_config.domain[domain].pd_option = option; } else { if (option == ESP_PD_OPTION_AUTO) { + // If switching from manual to auto mode, reset references and return to auto management s_config.domain[domain].refs = 0; s_config.domain[domain].pd_option = option; } else { - refs = (option == ESP_PD_OPTION_ON) ? s_config.domain[domain].refs++ \ - : (option == ESP_PD_OPTION_OFF) ? --s_config.domain[domain].refs \ - : s_config.domain[domain].refs; + // Manual mode operations (ESP_PD_OPTION_ON/ESP_PD_OPTION_OFF) + // The reference counting implements the following state machine: + // - ON operations increment references, only update pd_option when refs transitions from 0 to 1 + // - OFF operations decrement references, only update pd_option when refs transitions from 1 to 0 + // - This provides symmetric reference counting for resource management + if (option == ESP_PD_OPTION_ON) { + // Get refs value after incrementing: this ensures pd_option is updated when transitioning + // from 0->1 (first request) but not on subsequent increments + refs = s_config.domain[domain].refs++; + } else if (option == ESP_PD_OPTION_OFF) { + // Get refs value after decrementing: this ensures pd_option is updated when transitioning + // from 1->0 (last release) but not on intermediate decrements + refs = --s_config.domain[domain].refs; + } if (refs == 0) { + // Only update pd_option when reference count reaches 0, indicating all users have released s_config.domain[domain].pd_option = option; } else if (refs < 0) { + // Error case: reference count went negative, which indicates unbalanced ON/OFF calls s_config.domain[domain].refs = 0; err = ESP_ERR_INVALID_STATE; }