From 9b5944b795b31ff495eeeb81c410af1abfeab2e1 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Mon, 24 Feb 2025 14:37:24 +0800 Subject: [PATCH] feat(esp_hw_support): support switch to VBAT power supply in deepsleep on esp32p4 --- components/esp_hw_support/CMakeLists.txt | 2 +- .../include/esp_private/esp_pmu.h | 8 ++ .../include/esp_private/esp_sleep_internal.h | 1 + components/esp_hw_support/include/esp_sleep.h | 12 ++ components/esp_hw_support/linker.lf | 5 + .../esp_hw_support/port/esp32h2/pmu_sleep.c | 13 +- .../esp32h2/private_include/pmu_bit_defs.h | 1 + .../port/esp32h2/private_include/pmu_param.h | 4 +- .../esp_hw_support/port/esp32p4/pmu_sleep.c | 5 + .../esp32p4/private_include/pmu_bit_defs.h | 2 +- .../power_supply/include/esp_vbat.h | 49 +++++++ .../power_supply/port/esp32h2/Kconfig.power | 15 +++ .../power_supply/port/esp32p4/Kconfig.power | 15 +++ components/esp_hw_support/power_supply/vbat.c | 120 +++++++++++++++--- components/esp_hw_support/sleep_modes.c | 71 ++++++++++- components/esp_system/startup_funcs.c | 6 +- components/hal/esp32h2/include/hal/vbat_ll.h | 1 + components/hal/esp32p4/include/hal/vbat_ll.h | 4 + components/hal/include/hal/vbat_hal.h | 4 +- components/hal/vbat_hal.c | 12 +- .../soc/esp32p4/register/soc/pmu_struct.h | 8 +- 21 files changed, 317 insertions(+), 41 deletions(-) create mode 100644 components/esp_hw_support/power_supply/include/esp_vbat.h diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index 0b6edcfb02..4fe375d1bf 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -157,7 +157,7 @@ if(NOT non_os_build) list(APPEND srcs "power_supply/brownout.c") endif() - if(CONFIG_ESP_VBAT_INIT_AUTO) + if(CONFIG_SOC_VBAT_SUPPORTED) list(APPEND srcs "power_supply/vbat.c") endif() diff --git a/components/esp_hw_support/include/esp_private/esp_pmu.h b/components/esp_hw_support/include/esp_private/esp_pmu.h index 5e798e4a63..c416b3adf3 100644 --- a/components/esp_hw_support/include/esp_private/esp_pmu.h +++ b/components/esp_hw_support/include/esp_private/esp_pmu.h @@ -47,6 +47,7 @@ typedef enum { #define RTC_SLEEP_PD_MODEM PMU_SLEEP_PD_MODEM //!< Power down modem(include wifi, ble and 15.4) //These flags are not power domains, but will affect some sleep parameters +#define RTC_SLEEP_POWER_BY_VBAT BIT(26) #define RTC_SLEEP_DIG_USE_8M BIT(27) #define RTC_SLEEP_USE_ADC_TESEN_MONITOR BIT(28) #define RTC_SLEEP_NO_ULTRA_LOW BIT(29) //!< Avoid using ultra low power in deep sleep, in which RTCIO cannot be used as input, and RTCMEM can't work under high temperature @@ -123,6 +124,12 @@ typedef enum { #define RTC_LP_VAD_TRIG_EN 0 #endif //SOC_LP_VAD_SUPPORTED +#if SOC_VBAT_SUPPORTED +#define RTC_VBAT_UNDER_VOLT_TRIG_EN PMU_VBAT_UNDERVOLT_WAKEUP_EN //!< VBAT under voltage wakeup +#else +#define RTC_VBAT_UNDER_VOLT_TRIG_EN 0 +#endif //SOC_VBAT_SUPPORTED + #define RTC_XTAL32K_DEAD_TRIG_EN 0 // TODO #define RTC_BROWNOUT_DET_TRIG_EN 0 // TODO @@ -143,6 +150,7 @@ typedef enum { RTC_XTAL32K_DEAD_TRIG_EN | \ RTC_USB_TRIG_EN | \ RTC_LP_VAD_TRIG_EN | \ + RTC_VBAT_UNDER_VOLT_TRIG_EN | \ RTC_BROWNOUT_DET_TRIG_EN) diff --git a/components/esp_hw_support/include/esp_private/esp_sleep_internal.h b/components/esp_hw_support/include/esp_private/esp_sleep_internal.h index 3bc486ad1c..dc2d2f87fe 100644 --- a/components/esp_hw_support/include/esp_private/esp_sleep_internal.h +++ b/components/esp_hw_support/include/esp_private/esp_sleep_internal.h @@ -39,6 +39,7 @@ typedef enum { ESP_SLEEP_RTC_FAST_USE_XTAL_MODE, //!< The mode in which the crystal is used as the RTC_FAST clock source, need keep XTAL on in HP_SLEEP mode when ULP is working. ESP_SLEEP_DIG_USE_XTAL_MODE, //!< The mode requested by digital peripherals to keep XTAL clock on during sleep (both HP_SLEEP and LP_SLEEP mode). (!!! Only valid for lightsleep, will override the XTAL domain config by esp_sleep_pd_config) ESP_SLEEP_LP_USE_XTAL_MODE, //!< The mode requested by lp peripherals to keep XTAL clock on during sleep. Only valid for lightsleep. + ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE, //!< The mode to switch power supply to VBAT during deep sleep. ESP_SLEEP_MODE_MAX, } esp_sleep_sub_mode_t; diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index 38d1f8cd48..a0e40763fc 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -119,6 +119,7 @@ typedef enum { ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG, //!< Wakeup caused by COCPU crash ESP_SLEEP_WAKEUP_BT, //!< Wakeup caused by BT (light sleep only) ESP_SLEEP_WAKEUP_VAD, //!< Wakeup caused by VAD + ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT, //!< Wakeup caused by VDD_BAT under voltage. } esp_sleep_source_t; /** @@ -194,6 +195,16 @@ esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us); esp_err_t esp_sleep_enable_vad_wakeup(void); #endif +#if SOC_VBAT_SUPPORTED +/** + * Wakeup chip is VBAT power voltage is lower than the configured brownout_threshold value. + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_sleep_enable_vbat_under_volt_wakeup(void); +#endif + #if SOC_TOUCH_SENSOR_SUPPORTED /** * @brief Enable wakeup by touch sensor @@ -591,6 +602,7 @@ esp_err_t esp_sleep_pd_config(esp_sleep_pd_domain_t domain, * * @return * - No return - If the sleep is not rejected. + * - ESP_ERR_INVALID_STATE VBAT power does not meet the requirements for entering deepsleep * - ESP_ERR_SLEEP_REJECT sleep request is rejected(wakeup source set before the sleep request) */ esp_err_t esp_deep_sleep_try_to_start(void); diff --git a/components/esp_hw_support/linker.lf b/components/esp_hw_support/linker.lf index c87f73229b..430cdb8ae1 100644 --- a/components/esp_hw_support/linker.lf +++ b/components/esp_hw_support/linker.lf @@ -68,6 +68,11 @@ entries: sar_periph_ctrl (noflash) elif PM_SLP_IRAM_OPT = y: sar_periph_ctrl: sar_periph_ctrl_power_enable (noflash) + if ESP_VBAT_ISR_CACHE_SAFE = y: + sleep_modes: esp_sleep_disable_wakeup_source (noflash) + sleep_modes: esp_sleep_enable_vbat_under_volt_wakeup (noflash) + sleep_modes: esp_sleep_sub_mode_config (noflash) + sleep_modes: esp_sleep_sub_mode_force_disable (noflash) [mapping:soc_pm] archive: libsoc.a diff --git a/components/esp_hw_support/port/esp32h2/pmu_sleep.c b/components/esp_hw_support/port/esp32h2/pmu_sleep.c index b0c6c7d434..bea1ec2eb8 100644 --- a/components/esp_hw_support/port/esp32h2/pmu_sleep.c +++ b/components/esp_hw_support/port/esp32h2/pmu_sleep.c @@ -140,11 +140,6 @@ const pmu_sleep_config_t* pmu_sleep_config_default( ) { pmu_sleep_power_config_t power_default = PMU_SLEEP_POWER_CONFIG_DEFAULT(sleep_flags); - config->power = power_default; - - pmu_sleep_param_config_t param_default = PMU_SLEEP_PARAM_CONFIG_DEFAULT(sleep_flags); - config->param = *pmu_sleep_param_config_default(¶m_default, &power_default, sleep_flags, adjustment, slowclk_period, fastclk_period); - if (dslp) { pmu_sleep_digital_config_t digital_default = PMU_SLEEP_DIGITAL_DSLP_CONFIG_DEFAULT(sleep_flags, clk_flags); config->digital = digital_default; @@ -152,6 +147,11 @@ const pmu_sleep_config_t* pmu_sleep_config_default( pmu_sleep_analog_config_t analog_default = PMU_SLEEP_ANALOG_DSLP_CONFIG_DEFAULT(sleep_flags); analog_default.lp_sys[LP(SLEEP)].analog.dbias = get_slp_lp_dbias(); config->analog = analog_default; + + if (sleep_flags & RTC_SLEEP_POWER_BY_VBAT) { + power_default.lp_sys[PMU_MODE_LP_SLEEP].dig_power.vddbat_mode = 1; + power_default.lp_sys[PMU_MODE_LP_SLEEP].dig_power.bod_source_sel = 1; + } } else { pmu_sleep_digital_config_t digital_default = PMU_SLEEP_DIGITAL_LSLP_CONFIG_DEFAULT(sleep_flags, clk_flags); config->digital = digital_default; @@ -174,6 +174,9 @@ const pmu_sleep_config_t* pmu_sleep_config_default( } config->analog = analog_default; } + config->power = power_default; + pmu_sleep_param_config_t param_default = PMU_SLEEP_PARAM_CONFIG_DEFAULT(sleep_flags); + config->param = *pmu_sleep_param_config_default(¶m_default, &power_default, sleep_flags, adjustment, slowclk_period, fastclk_period); return config; } diff --git a/components/esp_hw_support/port/esp32h2/private_include/pmu_bit_defs.h b/components/esp_hw_support/port/esp32h2/private_include/pmu_bit_defs.h index aa71249f27..fa4d3f515b 100644 --- a/components/esp_hw_support/port/esp32h2/private_include/pmu_bit_defs.h +++ b/components/esp_hw_support/port/esp32h2/private_include/pmu_bit_defs.h @@ -18,6 +18,7 @@ extern "C" { #define PMU_UART1_WAKEUP_EN BIT(7) #define PMU_BLE_SOC_WAKEUP_EN BIT(10) #define PMU_USB_WAKEUP_EN BIT(14) +#define PMU_VBAT_UNDERVOLT_WAKEUP_EN BIT(15) #ifdef __cplusplus } diff --git a/components/esp_hw_support/port/esp32h2/private_include/pmu_param.h b/components/esp_hw_support/port/esp32h2/private_include/pmu_param.h index 23926878a3..1cd4f2590b 100644 --- a/components/esp_hw_support/port/esp32h2/private_include/pmu_param.h +++ b/components/esp_hw_support/port/esp32h2/private_include/pmu_param.h @@ -139,7 +139,9 @@ typedef union { typedef union { struct { - uint32_t reserved0 : 30; + uint32_t reserved0 : 27; + uint32_t bod_source_sel : 1; + uint32_t vddbat_mode : 2; uint32_t mem_dslp : 1; uint32_t peri_pd_en: 1; }; diff --git a/components/esp_hw_support/port/esp32p4/pmu_sleep.c b/components/esp_hw_support/port/esp32p4/pmu_sleep.c index a2351d85ee..f0e4152825 100644 --- a/components/esp_hw_support/port/esp32p4/pmu_sleep.c +++ b/components/esp_hw_support/port/esp32p4/pmu_sleep.c @@ -169,6 +169,11 @@ const pmu_sleep_config_t* pmu_sleep_config_default( pmu_sleep_analog_config_t analog_default = PMU_SLEEP_ANALOG_DSLP_CONFIG_DEFAULT(sleep_flags); analog_default.hp_sys.analog.xpd_0p1a = 0; config->analog = analog_default; + + if (sleep_flags & RTC_SLEEP_POWER_BY_VBAT) { + power_default.lp_sys[PMU_MODE_LP_SLEEP].dig_power.vddbat_mode = 1; + power_default.lp_sys[PMU_MODE_LP_SLEEP].dig_power.bod_source_sel = 1; + } } else { // Get light sleep digital_default pmu_sleep_digital_config_t digital_default = PMU_SLEEP_DIGITAL_LSLP_CONFIG_DEFAULT(sleep_flags); diff --git a/components/esp_hw_support/port/esp32p4/private_include/pmu_bit_defs.h b/components/esp_hw_support/port/esp32p4/private_include/pmu_bit_defs.h index aff80ee74a..a32668ec48 100644 --- a/components/esp_hw_support/port/esp32p4/private_include/pmu_bit_defs.h +++ b/components/esp_hw_support/port/esp32p4/private_include/pmu_bit_defs.h @@ -25,7 +25,7 @@ extern "C" { #define PMU_EXT1_WAKEUP_EN BIT(12) #define PMU_LP_TIMER_WAKEUP_EN BIT(13) #define PMU_BOD_WAKEUP_EN BIT(14) -#define PMU_VDDBAT_UNDERVOLT_WAKEUP_EN BIT(15) +#define PMU_VBAT_UNDERVOLT_WAKEUP_EN BIT(15) #define PMU_LP_CORE_TRAP_WAKEUP_EN BIT(16) #define PMU_ETM_WAKEUP_EN BIT(17) #define PMU_LP_TIMER1_WAKEUP_EN BIT(18) diff --git a/components/esp_hw_support/power_supply/include/esp_vbat.h b/components/esp_hw_support/power_supply/include/esp_vbat.h new file mode 100644 index 0000000000..0bf511697e --- /dev/null +++ b/components/esp_hw_support/power_supply/include/esp_vbat.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ESP_VBAT_STATE_NORMAL, /*!< Normal working state, not charging */ + ESP_VBAT_STATE_CHARGING, /*!< Battery is charging by VDDA, only for chargeable battery. */ + ESP_VBAT_STATE_LOWBATTERY, /*!< Battery is under voltage, battery replacement required */ +} esp_vbat_state_t; + +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY +/** + * @brief Wait until battery charging done. + * + * @note This function is not allowed to be called in ISR context. + * + * @param checking_period Time in ticks to wait until timeout or succeed + * @return + * - ESP_OK: Battery charging completed + * - ESP_FAIL: Called in ISR context + * - ESP_ERR_TIMEOUT: Timeout waiting for battery charging to complete + */ +esp_err_t esp_vbat_wait_battery_charge_done(TickType_t checking_period); +#endif + +/** + * @brief Get battery status. + * @return + * - DISCHARGING Battery is discharging. + * - CHARGING Battery is charing (ESP_VBAT_USE_RECHARGEABLE_BATTERY only). + * - LOWBATTERY Battery is under voltage. + */ +esp_vbat_state_t esp_vbat_get_battery_state(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hw_support/power_supply/port/esp32h2/Kconfig.power b/components/esp_hw_support/power_supply/port/esp32h2/Kconfig.power index 579ae69d19..b2319dba52 100644 --- a/components/esp_hw_support/power_supply/port/esp32h2/Kconfig.power +++ b/components/esp_hw_support/power_supply/port/esp32h2/Kconfig.power @@ -78,6 +78,14 @@ menu "Power Supplier" during deep sleep. - When this option is disabled, the RTC battery input (VBAT) must not be left floating. + config ESP_VBAT_ISR_CACHE_SAFE + bool "VBAT ISR IRAM-Safe" + default y + depends on SOC_VBAT_SUPPORTED && ESP_VBAT_INIT_AUTO + help + Ensure the VBAT interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + config ESP_VBAT_USE_RECHARGEABLE_BATTERY bool "The battery for RTC battery is a rechargeable battery" default n @@ -167,6 +175,13 @@ menu "Power Supplier" bool "2.57V" endchoice + config ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT + bool "Wake up chip in deepsleep if VBAT brownout" + depends on !ESP_VBAT_USE_RECHARGEABLE_BATTERY + default n + help + Wake up the chip if the vbat voltage drops below the brownout voltage during deepsleep. + config ESP_VBAT_DET_LVL_LOW int depends on ESP_VBAT_USE_RECHARGEABLE_BATTERY diff --git a/components/esp_hw_support/power_supply/port/esp32p4/Kconfig.power b/components/esp_hw_support/power_supply/port/esp32p4/Kconfig.power index 2e308de598..acdd8b8648 100644 --- a/components/esp_hw_support/power_supply/port/esp32p4/Kconfig.power +++ b/components/esp_hw_support/power_supply/port/esp32p4/Kconfig.power @@ -67,6 +67,14 @@ menu "Power Supplier" during deep sleep. - When this option is disabled, the RTC battery input (VBAT) must not be left floating. + config ESP_VBAT_ISR_CACHE_SAFE + bool "VBAT ISR IRAM-Safe" + default y + depends on SOC_VBAT_SUPPORTED && ESP_VBAT_INIT_AUTO + help + Ensure the VBAT interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + config ESP_VBAT_USE_RECHARGEABLE_BATTERY bool "The battery for RTC battery is a rechargeable battery" default n @@ -126,6 +134,13 @@ menu "Power Supplier" bool "2.42V" endchoice + config ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT + bool "Wake up chip in deepsleep if VBAT brownout" + depends on !ESP_VBAT_USE_RECHARGEABLE_BATTERY + default n + help + Wake up the chip if the vbat voltage drops below the brownout voltage during deepsleep. + config ESP_VBAT_DET_LVL_LOW int depends on ESP_VBAT_USE_RECHARGEABLE_BATTERY diff --git a/components/esp_hw_support/power_supply/vbat.c b/components/esp_hw_support/power_supply/vbat.c index 6b360a58fc..5e750160f4 100644 --- a/components/esp_hw_support/power_supply/vbat.c +++ b/components/esp_hw_support/power_supply/vbat.c @@ -15,17 +15,38 @@ #include "hal/vbat_hal.h" #include "freertos/FreeRTOS.h" #include "sdkconfig.h" +#include "esp_private/esp_sleep_internal.h" #include "esp_private/startup_internal.h" +#include "esp_private/rtc_ctrl.h" +#include "esp_sleep.h" +#include "esp_vbat.h" #include "esp_check.h" +#include "soc/rtc.h" +#include "soc/clk_tree_defs.h" #include "soc/power_supply_periph.h" +#if CONFIG_ESP_VBAT_INIT_AUTO +#if CONFIG_ESP_VBAT_ISR_CACHE_SAFE +#define VBAT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED) +#else +#define VBAT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED) +#endif // CONFIG_ESP_VBAT_ISR_CACHE_SAFE + #define VBAT_BROWNOUT_DET_LVL CONFIG_ESP_VBAT_BROWNOUT_DET_LVL #if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY -#define VBAT_CHARGE_DET_LVL_LOW CONFIG_ESP_VBAT_DET_LVL_LOW -#define VBAT_CHARGE_DET_LVL_HIGH CONFIG_ESP_VBAT_DET_LVL_HIGH -#define VBAT_CHARGER_RESISTOR_VALUE CONFIG_ESP_VBAT_CHARGER_CIRCUIT_RESISTOR_VAL +#define VBAT_CHARGE_DET_LVL_LOW CONFIG_ESP_VBAT_DET_LVL_LOW +#define VBAT_CHARGE_DET_LVL_HIGH CONFIG_ESP_VBAT_DET_LVL_HIGH +#define VBAT_CHARGER_RESISTOR_VALUE CONFIG_ESP_VBAT_CHARGER_CIRCUIT_RESISTOR_VAL +#endif +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY || CONFIG_ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT +#define VBAT_CHARGER_FILTER_TIME_US 50 +#define VBAT_CHARGER_HYSTERESIS_THRESHOLD_LOW 100 +#define VBAT_CHARGER_HYSTERESIS_THRESHOLD_HIGH (VBAT_CHARGER_HYSTERESIS_THRESHOLD_LOW + (VBAT_CHARGER_FILTER_TIME_US * SOC_CLK_RC_FAST_FREQ_APPROX) / MHZ) +#endif + +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY #if (VBAT_CHARGER_RESISTOR_VALUE < 1000 || VBAT_CHARGER_RESISTOR_VALUE > 4500 || VBAT_CHARGER_RESISTOR_VALUE % 500 != 0) #error "vbat charger resistor (ESP_VBAT_CHARGER_CIRCUIT_RESISTOR_VAL) must be between 1000 and 4500 ohms and must be a multiple of 500." #endif @@ -33,60 +54,123 @@ #if (VBAT_BROWNOUT_DET_LVL >= VBAT_CHARGE_DET_LVL_LOW) #error "vbat charger low threshold is equal or lower than vbat brownout threshold, please put vbat brownout threshold lower than vbat charger low threshold" #endif - #endif static const char TAG[] = "VBAT"; +#endif + +static struct { + esp_vbat_state_t state; +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY + SemaphoreHandle_t charging_done_sem; +#endif +} vbat_status = { + .state = ESP_VBAT_STATE_NORMAL +}; + +#if CONFIG_ESP_VBAT_INIT_AUTO +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY +static StaticSemaphore_t charging_done_semphr_buffer; +#endif IRAM_ATTR static void vbat_isr_handler(void *arg) { + portBASE_TYPE HPTaskAwoken = pdFALSE; uint32_t int_status; vbat_ll_get_interrupt_status(&int_status); - vbat_ll_clear_intr_mask(int_status); - - if (int_status & VBAT_LL_CHARGER_UNDERVOLTAGE_INTR) { - ESP_DRAM_LOGW(TAG, "RTC battery voltage low, start charging..."); - vbat_ll_start_battery_charge(true); - } +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY if (int_status & VBAT_LL_CHARGER_UPVOLTAGE_INTR) { ESP_DRAM_LOGW(TAG, "RTC battery voltage reaches high limit , stop charging..."); + vbat_status.state = ESP_VBAT_STATE_NORMAL; vbat_ll_start_battery_charge(false); + vbat_ll_enable_intr_mask(VBAT_LL_CHARGER_UNDERVOLTAGE_INTR | VBAT_LL_BROWNOUT_INTR, true); + vbat_ll_enable_intr_mask(VBAT_LL_CHARGER_UPVOLTAGE_INTR, false); + vbat_ll_clear_intr_mask(VBAT_LL_CHARGER_UPVOLTAGE_INTR); + esp_sleep_enable_vbat_under_volt_wakeup(); + esp_sleep_sub_mode_config(ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE, true); + xSemaphoreGiveFromISR(vbat_status.charging_done_sem, &HPTaskAwoken); + } +#endif + + if (int_status & (VBAT_LL_CHARGER_UNDERVOLTAGE_INTR)) { +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY + vbat_status.state = ESP_VBAT_STATE_CHARGING; + ESP_DRAM_LOGW(TAG, "RTC battery voltage low, start charging..."); + vbat_ll_start_battery_charge(true); + vbat_ll_enable_intr_mask(VBAT_LL_CHARGER_UPVOLTAGE_INTR, true); + vbat_ll_enable_intr_mask(VBAT_LL_CHARGER_UNDERVOLTAGE_INTR, false); +#endif + vbat_ll_clear_intr_mask(VBAT_LL_CHARGER_UNDERVOLTAGE_INTR); + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT); + esp_sleep_sub_mode_config(ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE, false); } if (int_status & VBAT_LL_BROWNOUT_INTR) { // TODO: A callback may needed here to inform an under voltage event. + vbat_status.state = ESP_VBAT_STATE_LOWBATTERY; + esp_sleep_sub_mode_force_disable(ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE); ESP_DRAM_LOGW(TAG, "RTC battery voltage low, please change battery..."); + vbat_ll_clear_intr_mask(VBAT_LL_BROWNOUT_INTR); } + if (HPTaskAwoken) { + portYIELD_FROM_ISR(); + } } +#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY +esp_err_t esp_vbat_wait_battery_charge_done(TickType_t checking_period) +{ + BaseType_t ret; + if (!xPortInIsrContext()) { + ret = xSemaphoreTake(vbat_status.charging_done_sem, checking_period); + } else { + return ESP_FAIL; + } + return (ret == pdPASS) ? ESP_OK : ESP_ERR_TIMEOUT; +} +#endif +#endif + +esp_vbat_state_t esp_vbat_get_battery_state(void) +{ + return vbat_status.state; +} + +#if CONFIG_ESP_VBAT_INIT_AUTO esp_err_t esp_vbat_init(void) { intr_handle_t vbat_intr; #if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY + vbat_status.charging_done_sem = xSemaphoreCreateBinaryStatic(&charging_done_semphr_buffer); vbat_hal_config_t vbat_cfg = { .enable_vbat_charger = true, .charger_resistor_value = VBAT_CHARGER_RESISTOR_VALUE, .low_threshold = VBAT_CHARGE_DET_LVL_LOW, .high_threshold = VBAT_CHARGE_DET_LVL_HIGH, .brownout_threshold = VBAT_BROWNOUT_DET_LVL, - .undervoltage_filter_time = 20, - .upvoltage_filter_time = 10, + .undervoltage_filter_time = VBAT_CHARGER_HYSTERESIS_THRESHOLD_HIGH, + .upvoltage_filter_time = VBAT_CHARGER_HYSTERESIS_THRESHOLD_LOW, .interrupt_mask = (VBAT_LL_CHARGER_MASK | VBAT_LL_DETECT_MASK), }; - #else vbat_hal_config_t vbat_cfg = { - .enable_vbat_charger = false, +#if CONFIG_ESP_VBAT_WAKEUP_CHIP_ON_VBAT_BROWNOUT + .enable_vbat_charger = true, + .low_threshold = VBAT_BROWNOUT_DET_LVL, + .undervoltage_filter_time = VBAT_CHARGER_HYSTERESIS_THRESHOLD_HIGH, + .upvoltage_filter_time = VBAT_CHARGER_HYSTERESIS_THRESHOLD_LOW, +#endif .brownout_threshold = VBAT_BROWNOUT_DET_LVL, .interrupt_mask = VBAT_LL_DETECT_MASK, }; #endif - vbat_hal_config(&vbat_cfg); - - ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(power_supply_periph_signal.irq, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, (uint32_t)brownout_ll_intr_get_status_reg(), VBAT_LL_CHARGER_MASK | VBAT_LL_DETECT_MASK, &vbat_isr_handler, NULL, &vbat_intr), TAG, "Allocate vbat isr failed"); - + ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(power_supply_periph_signal.irq, VBAT_INTR_ALLOC_FLAG, (uint32_t)brownout_ll_intr_get_status_reg(), vbat_cfg.interrupt_mask, &vbat_isr_handler, NULL, &vbat_intr), TAG, "Allocate vbat isr failed"); + esp_sleep_sub_mode_force_disable(ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE); + esp_sleep_sub_mode_config(ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE, true); + esp_sleep_enable_vbat_under_volt_wakeup(); return ESP_OK; } +#endif diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 280c3f35d7..c90ca42df1 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -127,6 +127,11 @@ #include "hal/lp_timer_hal.h" #endif +#if SOC_VBAT_SUPPORTED +#include "esp_vbat.h" +#include "hal/vbat_ll.h" +#endif + #if SOC_PMU_SUPPORTED #include "esp_private/esp_pmu.h" #include "esp_private/sleep_sys_periph.h" @@ -339,6 +344,9 @@ static esp_err_t timer_wakeup_prepare(int64_t sleep_duration); #if SOC_TOUCH_SENSOR_SUPPORTED static void touch_wakeup_prepare(void); #endif +#if SOC_VBAT_SUPPORTED +static void vbat_under_volt_wakeup_prepare(void); +#endif #if SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP && SOC_DEEP_SLEEP_SUPPORTED static void gpio_deep_sleep_wakeup_prepare(void); #endif @@ -1044,13 +1052,6 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl * The configuration change will change the reading of the sleep pad, which will cause the touch wake-up sensor to trigger falsely. */ keep_rtc_power_on = true; -#elif CONFIG_IDF_TARGET_ESP32P4 - /* Due to esp32p4 eco0 hardware bug, if LP peripheral power domain is powerdowned in sleep, there will be a possibility of - * triggering the EFUSE_CRC reset, so disable the power-down of this power domain on lightsleep for ECO0 version. - */ - if (!ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 1)) { - keep_rtc_power_on = true; - } #endif } } else { @@ -1065,6 +1066,12 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl } #endif +#if SOC_VBAT_SUPPORTED + if (deep_sleep && (s_config.wakeup_triggers & RTC_VBAT_UNDER_VOLT_TRIG_EN)) { + vbat_under_volt_wakeup_prepare(); + } +#endif + uint32_t reject_triggers = allow_sleep_rejection ? (s_config.wakeup_triggers & RTC_SLEEP_REJECT_MASK) : 0; if (!deep_sleep) { @@ -1191,6 +1198,20 @@ inline static uint32_t IRAM_ATTR call_rtc_sleep_start(uint32_t reject_triggers, static esp_err_t IRAM_ATTR deep_sleep_start(bool allow_sleep_rejection) { +#if SOC_VBAT_SUPPORTED + if (s_sleep_sub_mode_ref_cnt[ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE]) { + esp_vbat_state_t battery_state = esp_vbat_get_battery_state(); + if (battery_state == ESP_VBAT_STATE_CHARGING) { + ESP_LOGW(TAG, "Battery is charging, should wait until charging is complete before entering deep sleep!"); + } else if (battery_state == ESP_VBAT_STATE_LOWBATTERY) { + ESP_LOGE(TAG, "Battery is in low battery state, chip may lose power during deepsleep!"); + } + if ((battery_state != ESP_VBAT_STATE_NORMAL) && allow_sleep_rejection) { + return ESP_ERR_INVALID_STATE; + } + } +#endif + #if CONFIG_IDF_TARGET_ESP32S2 /* Due to hardware limitations, on S2 the brownout detector sometimes trigger during deep sleep to circumvent this we disable the brownout detector before sleeping */ @@ -1667,6 +1688,16 @@ esp_err_t esp_sleep_disable_wakeup_source(esp_sleep_source_t source) else if (CHECK_SOURCE(source, ESP_SLEEP_WAKEUP_ULP, RTC_ULP_TRIG_EN)) { s_config.wakeup_triggers &= ~RTC_ULP_TRIG_EN; } +#endif +#if SOC_LP_VAD_SUPPORTED + else if (CHECK_SOURCE(source, ESP_SLEEP_WAKEUP_VAD, RTC_LP_VAD_TRIG_EN)) { + s_config.wakeup_triggers &= ~RTC_LP_VAD_TRIG_EN; + } +#endif +#if SOC_VBAT_SUPPORTED + else if (CHECK_SOURCE(source, ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT, RTC_VBAT_UNDER_VOLT_TRIG_EN)) { + s_config.wakeup_triggers &= ~RTC_VBAT_UNDER_VOLT_TRIG_EN; + } #endif else { ESP_LOGE(TAG, "Incorrect wakeup source (%d) to disable.", (int) source); @@ -1748,6 +1779,14 @@ esp_err_t esp_sleep_enable_vad_wakeup(void) } #endif +#if SOC_VBAT_SUPPORTED +esp_err_t esp_sleep_enable_vbat_under_volt_wakeup(void) +{ + s_config.wakeup_triggers |= RTC_VBAT_UNDER_VOLT_TRIG_EN; + return ESP_OK; +} +#endif + static SLEEP_FN_ATTR esp_err_t timer_wakeup_prepare(int64_t sleep_duration) { if (sleep_duration < 0) { @@ -1775,6 +1814,13 @@ static SLEEP_FN_ATTR esp_err_t timer_wakeup_prepare(int64_t sleep_duration) return ESP_OK; } +#if SOC_VBAT_SUPPORTED +static void vbat_under_volt_wakeup_prepare(void) +{ + vbat_ll_clear_intr_mask(VBAT_LL_CHARGER_UNDERVOLTAGE_INTR); +} +#endif + #if SOC_TOUCH_SENSOR_SUPPORTED static void touch_wakeup_prepare(void) { @@ -2274,6 +2320,10 @@ esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause(void) #if SOC_LP_VAD_SUPPORTED } else if (wakeup_cause & RTC_LP_VAD_TRIG_EN) { return ESP_SLEEP_WAKEUP_VAD; +#endif +#if SOC_VBAT_SUPPORTED + } else if (wakeup_cause & RTC_VBAT_UNDER_VOLT_TRIG_EN) { + return ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT; #endif } else { return ESP_SLEEP_WAKEUP_UNDEFINED; @@ -2339,6 +2389,7 @@ int32_t* esp_sleep_sub_mode_dump_config(FILE *stream) { [ESP_SLEEP_RTC_FAST_USE_XTAL_MODE] = "ESP_SLEEP_RTC_FAST_USE_XTAL_MODE", [ESP_SLEEP_DIG_USE_XTAL_MODE] = "ESP_SLEEP_DIG_USE_XTAL_MODE", [ESP_SLEEP_LP_USE_XTAL_MODE] = "ESP_SLEEP_LP_USE_XTAL_MODE", + [ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE] = "ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE", }[mode], s_sleep_sub_mode_ref_cnt[mode] ? "ENABLED" : "DISABLED", s_sleep_sub_mode_ref_cnt[mode]); @@ -2607,6 +2658,12 @@ static SLEEP_FN_ATTR uint32_t get_sleep_flags(uint32_t sleep_flags, bool deepsle } #endif +#if SOC_VBAT_SUPPORTED + if (s_sleep_sub_mode_ref_cnt[ESP_SLEEP_VBAT_POWER_DEEPSLEEP_MODE] && deepsleep) { + sleep_flags |= RTC_SLEEP_POWER_BY_VBAT; + } +#endif + #ifdef CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND if (!deepsleep) { sleep_flags &= ~RTC_SLEEP_PD_RTC_PERIPH; diff --git a/components/esp_system/startup_funcs.c b/components/esp_system/startup_funcs.c index 2855dc325a..2c1279a87b 100644 --- a/components/esp_system/startup_funcs.c +++ b/components/esp_system/startup_funcs.c @@ -80,6 +80,10 @@ ESP_SYSTEM_INIT_FN(init_brownout, CORE, BIT(0), 104) // [refactor-todo] leads to call chain rtc_is_register (driver) -> esp_intr_alloc (esp32/esp32s2) -> // malloc (newlib) -> heap_caps_malloc (heap), so heap must be at least initialized esp_err_t ret = ESP_OK; + // BOD and VBAT share the same interrupt number. To avoid blocking the system in an intermediate state + // where an interrupt occurs and the interrupt number is enabled, but the ISR is not configured, enable + // the interrupt after configuring both ISRs. + portDISABLE_INTERRUPTS(); #if CONFIG_ESP_BROWNOUT_DET esp_brownout_init(); #else @@ -91,7 +95,7 @@ ESP_SYSTEM_INIT_FN(init_brownout, CORE, BIT(0), 104) #if CONFIG_ESP_VBAT_INIT_AUTO ret = esp_vbat_init(); #endif - + portENABLE_INTERRUPTS(); return ret; } #endif diff --git a/components/hal/esp32h2/include/hal/vbat_ll.h b/components/hal/esp32h2/include/hal/vbat_ll.h index f637ee4db0..ec68f2b224 100644 --- a/components/hal/esp32h2/include/hal/vbat_ll.h +++ b/components/hal/esp32h2/include/hal/vbat_ll.h @@ -102,6 +102,7 @@ static inline void vbat_ll_set_charger_resistor(uint32_t resistor) */ static inline void vbat_ll_start_battery_charge(bool start) { + LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_charger = start; LP_ANA_PERI.vddbat_bod_cntl.vddbat_charger = start; } diff --git a/components/hal/esp32p4/include/hal/vbat_ll.h b/components/hal/esp32p4/include/hal/vbat_ll.h index be742e38ae..3747f83ff3 100644 --- a/components/hal/esp32p4/include/hal/vbat_ll.h +++ b/components/hal/esp32p4/include/hal/vbat_ll.h @@ -15,6 +15,7 @@ #include #include "esp_bit_defs.h" #include "soc/lp_analog_peri_struct.h" +#include "hal/assert.h" #include "hal/regi2c_ctrl.h" #include "soc/regi2c_brownout.h" @@ -72,6 +73,7 @@ static inline void vbat_ll_enable_charger_comparator(bool enable) */ static inline void vbat_ll_set_undervoltage_filter_time(uint32_t time_tick) { + HAL_ASSERT(time_tick < (2<<10)); LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_undervoltage_target = time_tick; } @@ -82,6 +84,7 @@ static inline void vbat_ll_set_undervoltage_filter_time(uint32_t time_tick) */ static inline void vbat_ll_set_upvoltage_filter_time(uint32_t time_tick) { + HAL_ASSERT(time_tick < (2<<10)); LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_upvoltage_target = time_tick; } @@ -102,6 +105,7 @@ static inline void vbat_ll_set_charger_resistor(uint32_t resistor) */ static inline void vbat_ll_start_battery_charge(bool start) { + LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_charger = start; LP_ANA_PERI.vddbat_bod_cntl.vddbat_charger = start; } diff --git a/components/hal/include/hal/vbat_hal.h b/components/hal/include/hal/vbat_hal.h index e450ed7127..473919d069 100644 --- a/components/hal/include/hal/vbat_hal.h +++ b/components/hal/include/hal/vbat_hal.h @@ -21,8 +21,8 @@ typedef struct { uint8_t low_threshold; // low voltage threshold uint8_t high_threshold; // high voltage threshold uint8_t brownout_threshold; // brownout threshold - uint8_t undervoltage_filter_time; // under voltage filter time - uint8_t upvoltage_filter_time; // up voltage filter time + uint16_t undervoltage_filter_time; // under voltage filter time + uint16_t upvoltage_filter_time; // up voltage filter time } vbat_hal_config_t; /** diff --git a/components/hal/vbat_hal.c b/components/hal/vbat_hal.c index b37e1e8081..1e9a6683e9 100644 --- a/components/hal/vbat_hal.c +++ b/components/hal/vbat_hal.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ - +#include "hal/assert.h" #include "hal/vbat_ll.h" #include "hal/vbat_hal.h" @@ -15,9 +15,19 @@ void vbat_hal_config(const vbat_hal_config_t *cfg) uint8_t resistor_reg = (cfg->charger_resistor_value - 1000) / 500; vbat_ll_set_charger_resistor(resistor_reg); vbat_ll_set_charger_threshold(cfg->low_threshold, cfg->high_threshold); + HAL_ASSERT(cfg->undervoltage_filter_time > cfg->upvoltage_filter_time); vbat_ll_set_undervoltage_filter_time(cfg->undervoltage_filter_time); vbat_ll_set_upvoltage_filter_time(cfg->upvoltage_filter_time); } vbat_ll_set_brownout_threshold(cfg->brownout_threshold); + + uint32_t int_status; + vbat_ll_get_interrupt_status(&int_status); + // TODO: When the chip wakes up from vbat under voltage, the upvoltage interrupt will + // be triggered at the same time, workaround by software here. + if (int_status & VBAT_LL_CHARGER_UNDERVOLTAGE_INTR) { + vbat_ll_clear_intr_mask(VBAT_LL_CHARGER_UPVOLTAGE_INTR); + } + vbat_ll_enable_intr_mask(cfg->interrupt_mask, true); } diff --git a/components/soc/esp32p4/register/soc/pmu_struct.h b/components/soc/esp32p4/register/soc/pmu_struct.h index d8936af001..b62a05199b 100644 --- a/components/soc/esp32p4/register/soc/pmu_struct.h +++ b/components/soc/esp32p4/register/soc/pmu_struct.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -769,9 +769,9 @@ typedef union { typedef union { struct { - uint32_t module : 2; - uint32_t reserved1 : 29; - uint32_t sw_update : 1; + uint32_t ana_vddbat_mode : 2; + uint32_t reserved1 : 29; + uint32_t sw_update : 1; }; uint32_t val; } pmu_vddbat_cfg_t;