diff --git a/components/esp_driver_gpio/test_apps/gpio/sdkconfig.defaults.esp32p4 b/components/esp_driver_gpio/test_apps/gpio/sdkconfig.defaults.esp32p4 index d2699b2221b..59573527fda 100644 --- a/components/esp_driver_gpio/test_apps/gpio/sdkconfig.defaults.esp32p4 +++ b/components/esp_driver_gpio/test_apps/gpio/sdkconfig.defaults.esp32p4 @@ -1,3 +1,5 @@ CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_HEX=y CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 +# Disable PSRAM halfsleep feature, otherwise P4 tcm section overflows with COMPILER_OPTIMIZATION_NONE=y +CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED=n diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32p4 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32p4 index d2699b2221b..59573527fda 100644 --- a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32p4 +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32p4 @@ -1,3 +1,5 @@ CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_HEX=y CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 +# Disable PSRAM halfsleep feature, otherwise P4 tcm section overflows with COMPILER_OPTIMIZATION_NONE=y +CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED=n diff --git a/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h b/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h index 11862e21c86..ef69b0b89ef 100644 --- a/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h +++ b/components/esp_hal_mspi/esp32p4/include/hal/psram_ctrlr_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,7 @@ #include "soc/clk_tree_defs.h" #include "soc/hp_system_struct.h" #include "rom/opi_flash.h" +#include "esp_rom_sys.h" #ifdef __cplusplus extern "C" { @@ -40,6 +41,14 @@ extern "C" { #define PSRAM_CTRLR_LL_FIFO_MAX_BYTES 64 +/** + * ESP32-P4 MSPI revision < 3.0 lacks SPIMEM CS force-control (cs_keep_active / + * cs0_dis / cs1_dis) needed to assert CE# and wake PSRAM from half-sleep. + * The device driver must issue a dummy MSPI write instead. + * Revision >= 3.0 can use psram_ctrlr_ll_half_sleep_wakeup(). + */ +#define PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND (HAL_CONFIG(CHIP_SUPPORT_MIN_REV) < 300) + /** * @brief Set PSRAM write cmd * @@ -243,6 +252,20 @@ static inline void psram_ctrlr_ll_enable_hex_data_line_mode(uint32_t mspi_id, bo SPIMEM2.mem_sram_cmd.mem_sdout_hex = en; } +/** + * @brief Get PSRAM hex data line mode enable status + * + * @param mspi_id mspi_id + * + * @return true if hex data line mode is enabled, false otherwise + */ +__attribute__((always_inline)) +static inline bool psram_ctrlr_ll_is_hex_data_line_mode(uint32_t mspi_id) +{ + (void)mspi_id; + return (SPIMEM2.mem_sram_cmd.mem_sdin_hex & SPIMEM2.mem_sram_cmd.mem_sdout_hex); +} + /** * @brief Enable PSRAM AXI master access * @@ -872,6 +895,29 @@ static inline void psram_ctrlr_ll_enable_core_err_resp(void) HP_SYSTEM.core_err_resp_dis.val = 0x0; } +#if !PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND +/** + * @brief Wake PSRAM from half-sleep via MSPI CS controls (P4 MSPI rev >= 3.0). + * + * MSPI2: mem_cs_oe_ctrl drives CS. MSPI3: cs_keep_active + cs0/cs1_dis for CE#. + * Restore MSPI2/MSPI3 cs settings when done. + */ +__attribute__((always_inline)) +static inline void psram_ctrlr_ll_half_sleep_wakeup(void) +{ + bool old_oe_ctrl = SPIMEM2.mem_misc.mem_cs_oe_ctrl; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = 1; + SPIMEM3.misc.cs_keep_active = 1; + SPIMEM3.misc.cs0_dis = 1; + SPIMEM3.misc.cs1_dis = 0; + esp_rom_delay_us(3); + SPIMEM3.misc.cs1_dis = 1; + SPIMEM3.misc.cs0_dis = 0; + SPIMEM3.misc.cs_keep_active = 0; + SPIMEM2.mem_misc.mem_cs_oe_ctrl = old_oe_ctrl; +} +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_hw_support/port/esp32p4/pmu_sleep.c b/components/esp_hw_support/port/esp32p4/pmu_sleep.c index 069e7395121..1dbd9f8a016 100644 --- a/components/esp_hw_support/port/esp32p4/pmu_sleep.c +++ b/components/esp_hw_support/port/esp32p4/pmu_sleep.c @@ -39,6 +39,7 @@ #if CONFIG_SPIRAM #include "hal/ldo_ll.h" #include "hal/mspi_ll.h" +#include "esp_private/esp_psram_impl.h" #endif #if (CONFIG_ESP_REV_MIN_FULL == 300) @@ -191,7 +192,7 @@ const pmu_sleep_config_t* pmu_sleep_config_default( // Get light sleep analog default pmu_sleep_analog_config_t analog_default = PMU_SLEEP_ANALOG_LSLP_CONFIG_DEFAULT(sleep_flags); -#if CONFIG_SPIRAM +#if CONFIG_SPIRAM && CONFIG_ESP32P4_SELECTS_REV_LESS_V3 // Adjust analog parameters to keep EXT_LDO PSRAM channel volt outputting during light-sleep. analog_default.hp_sys.analog.pd_cur = PMU_PD_CUR_SLEEP_ON; analog_default.lp_sys[PMU_MODE_LP_SLEEP].analog.pd_cur = PMU_PD_CUR_SLEEP_ON; @@ -403,11 +404,15 @@ SPM_IRAM_ATTR uint32_t pmu_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, // to be written back so that regdma can get the correct link. // 3. We cannot use the API provided by ROM to invalidate the cache, since it is a function calling that writes data to the stack during // the return process, which results in dirty cachelines in L1 Cache again. - pmu_sleep_cache_sync_items(SMMU_GID_DEFAULT, CACHE_SYNC_WRITEBACK, CACHE_MAP_L1_DCACHE, 0, 0); + pmu_sleep_cache_sync_items(SMMU_GID_DEFAULT, CACHE_SYNC_WRITEBACK, CACHE_MAP_L1_DCACHE, 0, 0); // No PSRAM dirty data after this time write back if (!dslp) { #if CONFIG_SPIRAM psram_ctrlr_ll_wait_all_transaction_done(); +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED + esp_psram_impl_enter_halfsleep_mode(); +#endif + mspi_ll_hold_all_psram_pins(); #endif s_mpll_freq_mhz_before_sleep = rtc_clk_mpll_get_freq(); if (s_mpll_freq_mhz_before_sleep) { @@ -421,10 +426,10 @@ SPM_IRAM_ATTR uint32_t pmu_sleep_start(uint32_t wakeup_opt, uint32_t reject_opt, _psram_ctrlr_ll_enable_core_clock(PSRAM_CTRLR_LL_MSPI_ID_2, false); _psram_ctrlr_ll_enable_module_clock(PSRAM_CTRLR_LL_MSPI_ID_2, false); } - mspi_ll_hold_all_psram_pins(); #endif rtc_clk_mpll_disable(); } + pmu_sleep_cache_sync_items(SMMU_GID_DEFAULT, CACHE_SYNC_WRITEBACK, CACHE_MAP_L1_DCACHE, 0, 0); // No L2 MEM dirty data after this time write back } else { #if CONFIG_P4_REV3_MSPI_CRASH_AFTER_POWER_UP_WORKAROUND if (efuse_hal_chip_revision() == 300) { @@ -488,19 +493,26 @@ SPM_IRAM_ATTR bool pmu_sleep_finish(bool dslp) // Wait eFuse memory update done. while(efuse_ll_get_controller_state() != EFUSE_CONTROLLER_STATE_IDLE); - if (s_mpll_freq_mhz_before_sleep && !dslp) { - rtc_clk_mpll_enable(); - rtc_clk_mpll_configure(clk_hal_xtal_get_freq_mhz(), s_mpll_freq_mhz_before_sleep, true); + if (!dslp) { + if (s_mpll_freq_mhz_before_sleep) { + rtc_clk_mpll_enable(); + rtc_clk_mpll_configure(clk_hal_xtal_get_freq_mhz(), s_mpll_freq_mhz_before_sleep, true); #if CONFIG_SPIRAM - if (!s_pmu_sleep_regdma_backup_enabled) { - // MSPI2 and MSPI3 share the register for core clock. So we only set MSPI2 here. - // If it's a PD_TOP sleep, psram MSPI core clock will be enabled by REGDMA - _psram_ctrlr_ll_enable_core_clock(PSRAM_CTRLR_LL_MSPI_ID_2, true); - _psram_ctrlr_ll_enable_module_clock(PSRAM_CTRLR_LL_MSPI_ID_2, true); + if (!s_pmu_sleep_regdma_backup_enabled) { + // MSPI2 and MSPI3 share the register for core clock. So we only set MSPI2 here. + // If it's a PD_TOP sleep, psram MSPI core clock will be enabled by REGDMA + _psram_ctrlr_ll_enable_core_clock(PSRAM_CTRLR_LL_MSPI_ID_2, true); + _psram_ctrlr_ll_enable_module_clock(PSRAM_CTRLR_LL_MSPI_ID_2, true); + } + _psram_ctrlr_ll_select_clk_source(PSRAM_CTRLR_LL_MSPI_ID_2, PSRAM_CLK_SRC_MPLL); + _psram_ctrlr_ll_select_clk_source(PSRAM_CTRLR_LL_MSPI_ID_3, PSRAM_CLK_SRC_MPLL); +#endif } - _psram_ctrlr_ll_select_clk_source(PSRAM_CTRLR_LL_MSPI_ID_2, PSRAM_CLK_SRC_MPLL); - _psram_ctrlr_ll_select_clk_source(PSRAM_CTRLR_LL_MSPI_ID_3, PSRAM_CLK_SRC_MPLL); +#if CONFIG_SPIRAM mspi_ll_unhold_all_psram_pins(); +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED + esp_psram_impl_exit_halfsleep_mode(); +#endif #endif } diff --git a/components/esp_hw_support/port/esp32p4/rtc_time.c b/components/esp_hw_support/port/esp32p4/rtc_time.c index fec13d07a3a..f52cefd2b8f 100644 --- a/components/esp_hw_support/port/esp32p4/rtc_time.c +++ b/components/esp_hw_support/port/esp32p4/rtc_time.c @@ -15,6 +15,7 @@ #include "soc/timer_group_reg.h" #include "esp_rom_sys.h" #include "esp_private/periph_ctrl.h" +#include "esp_attr.h" ESP_LOG_ATTR_TAG(TAG, "rtc_time"); @@ -211,7 +212,7 @@ uint64_t rtc_time_slowclk_to_us(uint64_t rtc_cycles, uint32_t period) return (rtc_cycles * period) >> RTC_CLK_CAL_FRACT; } -uint64_t rtc_time_get(void) +SPM_IRAM_ATTR uint64_t rtc_time_get(void) { return rtc_timer_hal_get_cycle_count(0); } diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 54d135b0f74..8082a251f54 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -167,6 +167,10 @@ #include "esp_private/sleep_retention.h" #endif +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED +#include "esp_private/esp_psram_impl.h" +#endif + // If light sleep time is less than that, don't power down flash #define FLASH_PD_MIN_SLEEP_TIME_US 2000 @@ -926,6 +930,13 @@ static esp_err_t FORCE_IRAM_ATTR esp_sleep_start_safe(uint32_t sleep_flags, uint #endif #endif // !SOC_MSPI_HAS_INDEPENT_IOMUX } +#endif +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && (CONFIG_SPIRAM_XIP_FROM_PSRAM || !CONFIG_PM_SLP_IRAM_OPT) + // Code outside of esp_sleep_start_safe may be linked to FLASH if CONFIG_PM_SLP_IRAM_OPT is false, + // Code that accesses flash memory may cause cached PSRAM data to be replaced back into PSRAM before + // it has fully resumed. And if CONFIG_SPIRAM_XIP_FROM_PSRAM is enabled, code in Flash will be copied + // to PSRAM for execution. We need to wait here until PSRAM exits half-sleep before returning. + esp_psram_impl_resume_from_halfsleep_mode(s_config.rtc_clk_cal_period); #endif /* Cache Resume 1: Resume cache for continue running*/ resume_cache(); @@ -1580,6 +1591,14 @@ esp_err_t esp_light_sleep_start(void) esp_clk_private_unlock(); esp_timer_private_unlock(); +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED && !CONFIG_SPIRAM_XIP_FROM_PSRAM && CONFIG_PM_SLP_IRAM_OPT + // If CONFIG_SPIRAM_XIP_FROM_PSRAM is not enabled and CONFIG_PM_SLP_IRAM_OPT is enable, + // the sleep-wake process prior to this point does not access the PSRAM, so we can postpone waiting + // for the PSRAM to resume until here, in order to reuse the time overhead + // of the wake-up process as much as possible. + esp_psram_impl_resume_from_halfsleep_mode(s_config.rtc_clk_cal_period); +#endif + #if CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION && CONFIG_PM_SLP_IRAM_OPT /* Cache Resume 0: sleep process done, resume cache for continue running */ if (!(s_config.wakeup_triggers & ignore_check_wakeup_triggers)) { diff --git a/components/esp_pm/Kconfig b/components/esp_pm/Kconfig index 7f6c3623ae9..edb17a8d66c 100644 --- a/components/esp_pm/Kconfig +++ b/components/esp_pm/Kconfig @@ -287,4 +287,28 @@ menu "Power Management" Only enabled when SPI_FLASH_FREQ_LIMIT_C5_240MHZ is enabled. This is an internal configuration, automatically set based on SPI Flash configuration. + config PM_SLP_SPIRAM_HALFSLEEP_ENABLED + bool "Enable halfsleep mode for PSRAM in light sleep" + depends on SPIRAM + default y + help + Enable PSRAM halfsleep mode feature to reduce power consumption during light sleep, if the PSRAM used + support halfsleep mode. + + When enabled, the PSRAM chip will be configured to enter halfsleep mode (if applicable) before entering + light sleep. This reduces power consumption by: + 1. Setting refresh rate to 0.5x + 2. Setting PSRAM to halfsleep mode + + The PSRAM will automatically exit halfsleep mode after waking up from light sleep. + + config PM_SLP_SPIRAM_HALFSLEEP_EXIT_WAIT_DELAY + int "The time to wait for PSRAM to be able to access after exiting halfsleep mode (in us)" + depends on SPIRAM + default 150 + range 50 250 + help + Configure the delay time between waking up PSRAM halfsleep mode and the first access to PSRAM. + Refer to the tXHS parameter in datasheet of the used PSRAM to configure this option. + endmenu # "Power Management" diff --git a/components/esp_psram/CMakeLists.txt b/components/esp_psram/CMakeLists.txt index 55badaefc1e..5d1ee8437b2 100644 --- a/components/esp_psram/CMakeLists.txt +++ b/components/esp_psram/CMakeLists.txt @@ -4,7 +4,7 @@ if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() -set(includes "include") +set(includes "include" "device/include") if(CONFIG_SOC_SPIRAM_XIP_SUPPORTED) list(APPEND includes xip_impl/include) @@ -37,7 +37,6 @@ endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${includes} - PRIV_INCLUDE_DIRS device/include PRIV_REQUIRES ${priv_requires} LDFRAGMENTS linker.lf) diff --git a/components/esp_psram/device/esp_psram_impl_ap_hex.c b/components/esp_psram/device/esp_psram_impl_ap_hex.c index dd910af4cb1..8219f1c528f 100644 --- a/components/esp_psram/device/esp_psram_impl_ap_hex.c +++ b/components/esp_psram/device/esp_psram_impl_ap_hex.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -64,6 +64,8 @@ #define AP_HEX_PSRAM_REF_DATA 0x5a6b7c8d +#define AP_HEX_PSRAM_ULP_MODE_HALFSLEEP 0xF0 + typedef struct { union { struct { @@ -78,7 +80,7 @@ typedef struct { union { struct { uint8_t vendor_id: 5; - uint8_t rsvd0_2: 2; + uint8_t rsvd5_7: 3; uint8_t ulp: 1; }; uint8_t val; @@ -93,9 +95,9 @@ typedef struct { } mr2; union { struct { - uint8_t rsvd3_7: 4; + uint8_t rsvd0_3: 4; uint8_t srf: 2; - uint8_t rsvd0: 1; + uint8_t rsvd6: 1; uint8_t rbx_en: 1; }; uint8_t val; @@ -108,6 +110,13 @@ typedef struct { }; uint8_t val; } mr4; + union { + struct { + uint8_t rsvd0_3: 4; + uint8_t halfsleep: 4; + }; + uint8_t val; + } mr6; union { struct { uint8_t bl: 2; @@ -510,3 +519,87 @@ esp_err_t esp_psram_impl_get_available_size(uint32_t *out_size_bytes) #endif return (s_psram_size ? ESP_OK : ESP_ERR_INVALID_STATE); } + +/******************************* Halfsleep Mode *******************************/ +static PSRAM_HALFSLEEP_DATA_ATTR struct { + uint8_t mr4; + bool is_hex_line_mode; + uint64_t halfsleep_wakeup_tick; +} s_halfsleep_ctx = {0}; + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_enter_halfsleep_mode(void) +{ + // Backup line mode configuration and set to 8-line mode (MR registers R/W not support in hex line mode) + s_halfsleep_ctx.is_hex_line_mode = psram_ctrlr_ll_is_hex_data_line_mode(PSRAM_CTRLR_LL_MSPI_ID_3); + psram_ctrlr_ll_enable_hex_data_line_mode(PSRAM_CTRLR_LL_MSPI_ID_3, false); + // Backup MR4 + psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, + AP_HEX_PSRAM_REG_READ, AP_HEX_PSRAM_RD_CMD_BITLEN, + 0x4, AP_HEX_PSRAM_ADDR_BITLEN, + AP_HEX_PSRAM_RD_REG_DUMMY_BITLEN, + NULL, 0, + &s_halfsleep_ctx.mr4, 16, + false); + // Set refresh rate to 0.5x + hex_psram_mode_reg_t mode_reg; + mode_reg.mr4.val = s_halfsleep_ctx.mr4; + mode_reg.mr4.rf = 3; // (0:4x 1:1x 3:0.5x) + psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, + AP_HEX_PSRAM_REG_WRITE, AP_HEX_PSRAM_RD_CMD_BITLEN, + 0x4, AP_HEX_PSRAM_ADDR_BITLEN, + 0, + &mode_reg.mr4.val, 16, + NULL, 0, + false); + // Set halfsleep mode + uint8_t halfsleep_cmd = AP_HEX_PSRAM_ULP_MODE_HALFSLEEP; + psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, + AP_HEX_PSRAM_REG_WRITE, AP_HEX_PSRAM_RD_CMD_BITLEN, + 0x6, AP_HEX_PSRAM_ADDR_BITLEN, + 0, + &halfsleep_cmd, 16, + NULL, 0, + false); +} + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) +{ + // Record the tick exiting halfsleep mode + s_halfsleep_ctx.halfsleep_wakeup_tick = rtc_time_get(); + +#if PSRAM_CTRLR_LL_MSPI_WAKEUP_WORKAROUND + uint8_t null = 0; + psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, + AP_HEX_PSRAM_REG_WRITE, AP_HEX_PSRAM_WR_CMD_BITLEN, + 0xFF, AP_HEX_PSRAM_ADDR_BITLEN, + 0, + &null, 0, + NULL, 0, + false); +#else + // Set CE# to active to wakeup PSRAM halfsleep + psram_ctrlr_ll_half_sleep_wakeup(); +#endif +} + +PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) +{ + uint64_t halfsleep_exit_tick = s_halfsleep_ctx.halfsleep_wakeup_tick + rtc_time_us_to_slowclk(CONFIG_PM_SLP_SPIRAM_HALFSLEEP_EXIT_WAIT_DELAY, slowclk_period); + while (rtc_time_get() < halfsleep_exit_tick) { + // Busy wait for PSRAM to exit halfsleep mode + } + + // Restore MR4 configuration (restore refresh rate) + psram_ctrlr_ll_common_transaction(PSRAM_CTRLR_LL_MSPI_ID_3, + AP_HEX_PSRAM_REG_WRITE, AP_HEX_PSRAM_WR_CMD_BITLEN, + 0x4, AP_HEX_PSRAM_ADDR_BITLEN, + 0, + &s_halfsleep_ctx.mr4, 16, + NULL, 0, + false); + + // Restore hex line mode if it was enabled before + if (s_halfsleep_ctx.is_hex_line_mode) { + psram_ctrlr_ll_enable_hex_data_line_mode(PSRAM_CTRLR_LL_MSPI_ID_3, true); + } +} diff --git a/components/esp_psram/device/esp_psram_impl_ap_quad.c b/components/esp_psram/device/esp_psram_impl_ap_quad.c index 658920ba3d4..5caae22859d 100644 --- a/components/esp_psram/device/esp_psram_impl_ap_quad.c +++ b/components/esp_psram/device/esp_psram_impl_ap_quad.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -434,6 +434,20 @@ static void config_psram_spi_phases(void) psram_ctrlr_ll_set_cs_pin(PSRAM_CTRLR_LL_MSPI_ID_0, PSRAM_LL_CS_ID_1); } +/******************************* Halfsleep Mode *******************************/ +// This PSRAM device does not support halfsleep mode +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_enter_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) +{ +} + /*--------------------------------------------------------------------------------- * Following APIs are not required to be IRAM-Safe * diff --git a/components/esp_psram/device/include/esp_private/esp_psram_impl.h b/components/esp_psram/device/include/esp_private/esp_psram_impl.h index 4af953527b6..92149a241b5 100644 --- a/components/esp_psram/device/include/esp_private/esp_psram_impl.h +++ b/components/esp_psram/device/include/esp_private/esp_psram_impl.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "esp_err.h" #include "sdkconfig.h" +#include "soc/soc_caps.h" #ifdef __cplusplus extern "C" { @@ -20,6 +21,21 @@ extern "C" { #define PSRAM_SIZE_32MB (32 * 1024 * 1024) #define PSRAM_SIZE_64MB (64 * 1024 * 1024) +#if CONFIG_PM_SLP_SPIRAM_HALFSLEEP_ENABLED +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE +#define PSRAM_HALFSLEEP_DATA_ATTR SPM_DRAM_ATTR +#define PSRAM_HALFSLEEP_SLEEP_CODE_ATTR SPM_IRAM_ATTR +#else +#define PSRAM_HALFSLEEP_DATA_ATTR DRAM_ATTR +#define PSRAM_HALFSLEEP_SLEEP_CODE_ATTR IRAM_ATTR +#endif +#define PSRAM_HALFSLEEP_RESUME_CODE_ATTR IRAM_ATTR +#else +#define PSRAM_HALFSLEEP_DATA_ATTR +#define PSRAM_HALFSLEEP_SLEEP_CODE_ATTR +#define PSRAM_HALFSLEEP_RESUME_CODE_ATTR +#endif + /** * @brief To get the physical psram size in bytes. * @@ -51,6 +67,29 @@ esp_err_t esp_psram_impl_enable(void); */ uint8_t esp_psram_impl_get_cs_io(void); +/** + * @brief Set psram to halfsleep mode to save power consumption + * + * It will also set refresh rate to 0.5x. + */ +void esp_psram_impl_enter_halfsleep_mode(void); + +/** + * @brief Make psram exit halfsleep mode (no wait, wait is done in esp_psram_impl_resume_from_halfsleep_mode) + */ +void esp_psram_impl_exit_halfsleep_mode(void); + +/** + * @brief Resume psram from halfsleep mode (also restore refresh rate) + * + * @param slowclk_period RTC slow clock period, calibration value obtained using rtc_clk_cal + * + * @note PSRAM requires some time (tHSPU) to fully recover from halfsleep mode. + * This function examines the time between exiting halfsleep mode and current time, and busy wait if the time is not long enough. + * This shortens the overall busy loop time if wait is done inside esp_psram_impl_exit_halfsleep_mode. + */ +void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period); + #ifdef __cplusplus } #endif diff --git a/components/esp_psram/esp32/esp_psram_impl_quad.c b/components/esp_psram/esp32/esp_psram_impl_quad.c index 8b5dae69f44..8f002872034 100644 --- a/components/esp_psram/esp32/esp_psram_impl_quad.c +++ b/components/esp_psram/esp32/esp_psram_impl_quad.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2013-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2013-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1175,4 +1175,19 @@ esp_err_t esp_psram_impl_get_available_size(uint32_t *out_size_bytes) { return esp_psram_impl_get_physical_size(out_size_bytes); } + +/******************************* Halfsleep Mode *******************************/ +// This PSRAM device does not support halfsleep mode +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_enter_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) +{ +} + #endif // CONFIG_SPIRAM diff --git a/components/esp_psram/esp32s2/esp_psram_impl_quad.c b/components/esp_psram/esp32s2/esp_psram_impl_quad.c index e8def0feab6..62d53af9957 100644 --- a/components/esp_psram/esp32s2/esp_psram_impl_quad.c +++ b/components/esp_psram/esp32s2/esp_psram_impl_quad.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2013-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2013-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -577,3 +577,17 @@ esp_err_t esp_psram_impl_get_available_size(uint32_t *out_size_bytes) { return esp_psram_impl_get_physical_size(out_size_bytes); } + +/******************************* Halfsleep Mode *******************************/ +// This PSRAM device does not support halfsleep mode +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_enter_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) +{ +} diff --git a/components/esp_psram/esp32s3/esp_psram_impl_octal.c b/components/esp_psram/esp32s3/esp_psram_impl_octal.c index b5580fba09a..a6b42fbd875 100644 --- a/components/esp_psram/esp32s3/esp_psram_impl_octal.c +++ b/components/esp_psram/esp32s3/esp_psram_impl_octal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -456,3 +456,17 @@ esp_err_t esp_psram_impl_get_available_size(uint32_t *out_size_bytes) #endif return (s_psram_size ? ESP_OK : ESP_ERR_INVALID_STATE); } + +/******************************* Halfsleep Mode *******************************/ +// This PSRAM supports halfsleep mode, but not implemented yet +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_enter_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_SLEEP_CODE_ATTR void esp_psram_impl_exit_halfsleep_mode(void) +{ +} + +PSRAM_HALFSLEEP_RESUME_CODE_ATTR void esp_psram_impl_resume_from_halfsleep_mode(uint32_t slowclk_period) +{ +} diff --git a/components/esp_psram/test_apps/psram/main/test_psram.c b/components/esp_psram/test_apps/psram/main/test_psram.c index 9a292d9757b..a40a9ab94e5 100644 --- a/components/esp_psram/test_apps/psram/main/test_psram.c +++ b/components/esp_psram/test_apps/psram/main/test_psram.c @@ -13,26 +13,30 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unity.h" +#include "esp_cpu.h" #include "esp_heap_caps.h" +#include "esp_ipc_isr.h" #include "esp_private/esp_psram_io.h" #include "esp_psram.h" #include "esp_private/esp_psram_extram.h" +#include "esp_private/esp_psram_impl.h" #include "esp_flash.h" #include "esp_partition.h" +#include "soc/rtc.h" __attribute__((unused)) const static char *TAG = "PSRAM"; static void s_test_psram_heap_allocable(void) { size_t largest_size = heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM); - ESP_LOGI(TAG, "largest size is %zu", largest_size); + ESP_EARLY_LOGI(TAG, "largest size is 0x%08x", largest_size); uint32_t *ext_buffer = (uint32_t *)heap_caps_calloc(largest_size, 1, MALLOC_CAP_SPIRAM); TEST_ASSERT(ext_buffer); intptr_t start = (intptr_t)ext_buffer; intptr_t end = (intptr_t)ext_buffer + largest_size; - ESP_LOGI(TAG, "test ext buffer start addr is 0x%"PRIxPTR", end addr is 0x%"PRIxPTR, start, end); + ESP_EARLY_LOGI(TAG, "test ext buffer start addr is %p, end addr is %p", start, end); TEST_ASSERT(esp_psram_check_ptr_addr((void *)start) && esp_psram_check_ptr_addr((void *)end)); for (int i = 0; i < largest_size / sizeof(uint32_t); i++) { @@ -58,6 +62,37 @@ TEST_CASE("stress test psram heap allocable", "[psram][manual][ignore]") } } +#if !CONFIG_SPIRAM_XIP_FROM_PSRAM + +IRAM_ATTR static void s_psram_halfsleep_test(uint32_t rtc_slow_clk_period) +{ + // Stall another core and disable branch predictor to prevent it from accessing PSRAM + esp_ipc_isr_stall_other_cpu(); +#if SOC_BRANCH_PREDICTOR_SUPPORTED + esp_cpu_branch_prediction_disable(); +#endif + esp_psram_impl_enter_halfsleep_mode(); + esp_rom_delay_us(1000); + esp_psram_impl_exit_halfsleep_mode(); + esp_psram_impl_resume_from_halfsleep_mode(rtc_slow_clk_period); +#if SOC_BRANCH_PREDICTOR_SUPPORTED + esp_cpu_branch_prediction_enable(); +#endif + esp_ipc_isr_release_other_cpu(); + + s_test_psram_heap_allocable(); +} + +TEST_CASE("test psram halfsleep mode (if applicable)", "[psram]") +{ + uint32_t rtc_slow_clk_period = rtc_clk_cal(CLK_CAL_RTC_SLOW, CONFIG_RTC_CLK_CAL_CYCLES); + s_test_psram_heap_allocable(); + for (int i = 0; i < 3; i++) { + s_psram_halfsleep_test(rtc_slow_clk_period); + } +} +#endif + #if CONFIG_SPIRAM_XIP_FROM_PSRAM /*--------------------------------------------------------------- SPI1 with XIP diff --git a/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c index edbba1ba4e1..28222227450 100644 --- a/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c +++ b/components/esp_system/test_apps/esp_system_unity_tests/main/test_sleep.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -320,6 +320,24 @@ static void test_psram_accessible_after_lightsleep(void) TEST_ESP_OK(sleep_cpu_configure(true)); #endif + // Verify PSRAM was not corrupted after light sleep + uint8_t *psram_data = (uint8_t *)heap_caps_malloc(1024 * 1024, MALLOC_CAP_SPIRAM); + uint8_t test_pattern = 0x5a; + for (int i = 0; i < 5; i++) { + memset(psram_data, test_pattern, 1024 * 1024); + esp_sleep_enable_timer_wakeup(100 * 1000); + esp_light_sleep_start(); + for (int j = 0; j < (1024 * 1024); j++) { + TEST_ASSERT_EQUAL(test_pattern, psram_data[j]); + } + test_pattern = ~test_pattern; +#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP + TEST_ASSERT_EQUAL(PMU_SLEEP_PD_TOP, sleep_ctx.sleep_flags & PMU_SLEEP_PD_TOP); +#endif + } + free(psram_data); + + // Verify that all addresses in PSRAM are accessible after light sleep esp_sleep_enable_timer_wakeup(100 * 1000); esp_light_sleep_start(); TEST_ASSERT_EQUAL(0, sleep_ctx.sleep_request_result);