feat(spi_flash): implement dynamic CPU frequency switching workaround for encrypted writes

This commit implements a workaround that allows ESP32-C5 to run at 240MHz CPU frequency
normally, while automatically reducing CPU frequency during encrypted flash writes to
ensure correct operation. The frequency limit is chip revision dependent:
- v1.2 and above: limited to 160MHz during encrypted writes
- v1.0 and below: limited to 80MHz during encrypted writes

Key implementation details:
- Frequency limiting is triggered automatically when esp_flash_write_encrypted() is called
- Uses start() flags (ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) to integrate with OS layer
- Works with both PM enabled and disabled configurations
- Frequency is automatically restored after encrypted write completes
- For ESP32-C5 with 120MHz flash, Flash clock and timing registers are adjusted when
  CPU frequency is reduced to 80MHz
- SPI1 timing registers are configured during frequency switching since encrypted writes
  use SPI1 and must work correctly at reduced CPU frequencies

Code improvements:
- Use SOC_MSPI_FREQ_AXI_CONSTRAINED capability macro instead of hardcoded chip checks
- Control workaround via Kconfig (CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED) instead of
  hardcoded macros
- Add comprehensive test cases covering various PM configurations and edge cases

This workaround enables ESP32-C5 applications to benefit from 240MHz CPU performance
while maintaining reliable encrypted flash write functionality.
This commit is contained in:
Xiao Xufeng
2025-12-17 01:21:45 +08:00
parent 86f159ec06
commit ae7124abe3
48 changed files with 2109 additions and 129 deletions
+21 -20
View File
@@ -7,6 +7,7 @@
#include <sys/param.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>
#include "sdkconfig.h"
#include "esp_check.h"
#include "esp_log.h"
@@ -17,25 +18,31 @@
#include "esp_private/mspi_timing_tuning.h"
#include "esp_private/esp_clk_utils.h"
// Not directly divide to avoid truncation issue
// DIG-498
#if CONFIG_IDF_TARGET_ESP32P4
#define BELOW_FREQ_THRESHOLD(freq) ((freq) < CONFIG_SPIRAM_SPEED)
#elif CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32H4
#define BELOW_FREQ_THRESHOLD(freq) ((freq) * 8 < CONFIG_SPIRAM_SPEED)
#endif
#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
void esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(uint32_t target_cpu_src_freq, uint32_t target_cpu_freq)
{
#if MSPI_TIMING_LL_FLASH_CPU_CLK_SRC_BINDED
(void) target_cpu_freq;
/* For ESP32S3, the clock source of MSPI is same as the CPU. When CPU use XTAL as clock source, we need to sync the
* MSPI speed mode. */
if (target_cpu_src_freq <= clk_ll_xtal_load_freq_mhz()) {
mspi_timing_change_speed_mode_cache_safe(true);
}
#elif CONFIG_IDF_TARGET_ESP32P4
(void) target_cpu_src_freq;
/**
* Workaround for ESP32P4,
* f_cpu >= f_mspi
#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM
/* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU
* frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow.
*/
if (((target_cpu_freq) < CONFIG_ESPTOOLPY_FLASHFREQ_VAL)
#if CONFIG_SPIRAM
|| ((target_cpu_freq) < CONFIG_SPIRAM_SPEED)
#endif
) {
if (BELOW_FREQ_THRESHOLD(target_cpu_freq)) {
// Before switching to low speed mode, verify CPU frequency meets the constraint
assert(target_cpu_freq >= mspi_timing_get_psram_low_speed_freq_mhz());
mspi_timing_change_speed_mode_cache_safe(true);
}
#else
@@ -51,17 +58,11 @@ void esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(uint32_t target
if (target_cpu_src_freq > clk_ll_xtal_load_freq_mhz()) {
mspi_timing_change_speed_mode_cache_safe(false);
}
#elif CONFIG_IDF_TARGET_ESP32P4
(void) target_cpu_src_freq;
/**
* Workaround for ESP32P4,
* f_cpu >= f_mspi
#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM
/* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU
* frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow.
*/
if (((target_cpu_freq) >= CONFIG_ESPTOOLPY_FLASHFREQ_VAL)
#if CONFIG_SPIRAM
&& ((target_cpu_freq) >= CONFIG_SPIRAM_SPEED)
#endif
) {
if (!BELOW_FREQ_THRESHOLD(target_cpu_freq)) {
mspi_timing_change_speed_mode_cache_safe(false);
}
#else
@@ -24,6 +24,12 @@ extern "C" {
*/
void mspi_timing_enter_low_speed_mode(bool control_spi1);
/**
* @brief Get PSRAM frequency in low speed mode (MHz)
* @return PSRAM frequency in MHz when in low speed mode
*/
uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void);
/**
* @brief Make MSPI work under the frequency as users set, may add certain delays to MSPI RX direction to meet timing requirements.
* @param control_spi1 Select whether to control SPI1. For tuning, we need to use SPI1. After tuning (during startup stage), let the flash driver to control SPI1
@@ -54,7 +60,6 @@ void mspi_timing_psram_tuning(void);
*/
void mspi_timing_set_pin_drive_strength(void);
#ifdef __cplusplus
}
#endif
@@ -481,6 +481,11 @@ void mspi_timing_psram_tuning(void)
/*------------------------------------------------------------------------------
* APIs to make SPI0 (and SPI1) FLASH work for high/low freq
*----------------------------------------------------------------------------*/
uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void)
{
return 20;
}
void mspi_timing_enter_low_speed_mode(bool control_spi1)
{
#if MSPI_TIMING_LL_FLASH_CLK_SRC_CHANGEABLE
@@ -500,14 +505,14 @@ void mspi_timing_enter_low_speed_mode(bool control_spi1)
* Should be extended to other no-timing-tuning chips if needed. e.g.:
* we still need to turn down Flash / PSRAM clock speed at a certain period of time
*/
mspi_timing_config_set_flash_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1);
mspi_timing_config_set_psram_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1);
#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING
uint32_t low_speed_freq_mhz = mspi_timing_get_psram_low_speed_freq_mhz();
mspi_timing_config_set_flash_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1);
mspi_timing_config_set_psram_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1);
#if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING
mspi_timing_flash_config_clear_tuning_regs(control_spi1);
mspi_timing_psram_config_clear_tuning_regs(control_spi1);
#endif //#if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING
#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING
}
/**
@@ -568,10 +573,24 @@ void mspi_timing_change_speed_mode_cache_safe(bool switch_down)
if (switch_down) {
//enter MSPI low speed mode, extra delays should be removed
#if CONFIG_IDF_TARGET_ESP32C5
// ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced.
// Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well
// during runtime frequency switching to ensure proper operation.
mspi_timing_enter_low_speed_mode(true);
#else
mspi_timing_enter_low_speed_mode(false);
#endif
} else {
//enter MSPI high speed mode, extra delays should be considered
#if CONFIG_IDF_TARGET_ESP32C5
// ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced.
// Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well
// during runtime frequency switching to ensure proper operation.
mspi_timing_enter_high_speed_mode(true);
#else
mspi_timing_enter_high_speed_mode(false);
#endif
}
#if SOC_CACHE_FREEZE_SUPPORTED
+13 -2
View File
@@ -1,10 +1,21 @@
idf_build_get_property(target IDF_TARGET)
set(priv_requires esp_system esp_driver_gpio esp_timer)
if(${target} STREQUAL "linux")
return() # This component is not supported by the POSIX/Linux simulator
endif()
idf_component_register(SRCS "pm_locks.c" "pm_trace.c" "pm_impl.c"
set(srcs "pm_locks.c" "pm_trace.c" "pm_impl.c")
if(CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED)
list(APPEND srcs "pm_c5_flash_freq_limit.c")
endif()
if(${target} STREQUAL "esp32c5")
# pm_c5_flash_freq_limit.c needs spi_flash header when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled
list(APPEND priv_requires spi_flash)
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS include
PRIV_REQUIRES esp_system esp_driver_gpio esp_timer
PRIV_REQUIRES "${priv_requires}"
LDFRAGMENTS linker.lf)
+8
View File
@@ -239,4 +239,12 @@ menu "Power Management"
NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in callback and
hence it is highly recommended to keep them as short as possible
config PM_WORKAROUND_FREQ_LIMIT_ENABLED
bool
default y if SPI_FLASH_FREQ_LIMIT_C5_240MHZ
help
Workaround for frequency limit during encrypted flash writes.
Only enabled when SPI_FLASH_FREQ_LIMIT_C5_240MHZ is enabled.
This is an internal configuration, automatically set based on SPI Flash configuration.
endmenu # "Power Management"
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,7 +15,6 @@
#include "soc/rtc.h"
#include "esp_pm.h"
#include "esp_timer.h"
#include "sdkconfig.h"
#ifdef __cplusplus
@@ -26,11 +25,11 @@ extern "C" {
* This is an enum of possible power modes supported by the implementation
*/
typedef enum {
PM_MODE_LIGHT_SLEEP,//!< Light sleep
PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks)
PM_MODE_APB_MAX, //!< Maximum APB frequency mode
PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode
PM_MODE_COUNT //!< Number of items
PM_MODE_LIGHT_SLEEP, //!< Light sleep
PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks)
PM_MODE_APB_MAX, //!< Maximum APB frequency mode
PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode
PM_MODE_COUNT //!< Number of items
} pm_mode_t;
/**
@@ -141,8 +140,18 @@ esp_err_t esp_pm_register_skip_light_sleep_callback(skip_light_sleep_cb_t cb);
*/
esp_err_t esp_pm_unregister_skip_light_sleep_callback(skip_light_sleep_cb_t cb);
/**
* @brief Initialize flash frequency limit
*
* This function initializes the flash frequency limit.
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled.
*/
void esp_pm_flash_freq_limit_init(void);
#ifdef CONFIG_PM_PROFILING
#define WITH_PROFILING
#include "esp_timer.h"
#endif
#ifdef WITH_PROFILING
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
/**
* @brief Initialize and pre-calculate forced CPU_MAX frequency configuration (private function for spi_flash)
*
* This function pre-calculates and stores the forced CPU_MAX frequency configuration
* based on the given frequency limit. The configuration is computed once during
* initialization and reused during runtime for better performance.
*
* @param limit_freq_mhz Frequency limit in MHz
* @note This is a private function, only for use by spi_flash component.
* @note Must be called during initialization before esp_pm_impl_cpu_max_freq_force().
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled.
*/
void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz);
/**
* @brief Force CPU_MAX frequency to pre-configured limit (private function for spi_flash)
*
* This function activates the pre-configured forced CPU_MAX frequency limit.
* When forced, all reads of CPU_MAX frequency will use the pre-configured value
* instead of the configured value.
*
* @note This is a private function, only for use by spi_flash component.
* @note The forced frequency configuration must be pre-calculated during initialization.
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled.
*/
void esp_pm_impl_cpu_max_freq_force(void);
/**
* @brief Unforce CPU_MAX frequency (private function for spi_flash)
*
* This function removes the forced CPU_MAX frequency, allowing the configured
* value to be used again.
* @note This is a private function, only for use by spi_flash component.
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled.
*/
void esp_pm_impl_cpu_max_freq_unforce(void);
#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
#ifdef __cplusplus
}
#endif
+168
View File
@@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
//This file implements esp_flash_freq_limit_cb and esp_flash_freq_unlimit_cb callbacks to limit and unlimit CPU
//frequency when encrypted flash writes are performed.
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_rom_sys.h"
#include "soc/chip_revision.h"
#include "soc/rtc.h"
#include "hal/efuse_hal.h"
#include "esp_private/pm_impl_freq_limit.h"
#include "esp_private/spi_flash_freq_limit_cbs.h"
#include "esp_private/esp_clk_utils.h"
/**
* @brief Get the frequency limit for flash encryption lock based on chip revision
*
* @return uint32_t Frequency limit (MHz)
*
* Logic:
* - v1.2+: limit to 160MHz
* - v1.0: limit to 80MHz
*/
static uint32_t IRAM_ATTR get_encrypt_lock_freq_limit(void)
{
unsigned int chip_revision = efuse_hal_chip_revision();
if (ESP_CHIP_REV_ABOVE(chip_revision, 102)) {
return 160;
} else {
return 80;
}
}
void esp_pm_flash_freq_limit_init(void)
{
uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit();
ESP_EARLY_LOGW("spi_flash", "CPU frequency is set to 240MHz. esp_flash_write_encrypted() will automatically limit CPU frequency to %dMHz during execution.", limit_freq_mhz);
#ifdef CONFIG_PM_ENABLE
/* Pre-calculate and store forced frequency configuration during initialization.
* This is done here to avoid runtime calculation overhead in lock/unlock functions.
*/
esp_pm_impl_cpu_max_freq_force_init(limit_freq_mhz);
#endif
}
#if !CONFIG_PM_ENABLE
/* Saved original frequency for !PM_ENABLE case (0 means no change was made) */
static uint32_t s_saved_freq_mhz = 0;
// Switch CPU frequency with all necessary synchronization (for !PM_ENABLE case)
// Similar to do_switch() but without PM-specific state management
static void IRAM_ATTR esp_pm_impl_switch_cpu_freq(const rtc_cpu_freq_config_t *old_config, const rtc_cpu_freq_config_t *new_config)
{
if (new_config->freq_mhz != old_config->freq_mhz) {
//No need to run on_freq_update for ccount, since that's not supported on C5.
#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
/* Synchronize MSPI speed mode before CPU frequency switching */
esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz);
#endif
//Not taking s_time_update_lock since C5 have fixed divider for systimer
rtc_clk_cpu_freq_set_config_fast(new_config);
#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
/* Synchronize MSPI speed mode after CPU frequency switching */
esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz);
#endif
}
}
/**
* @brief Limit CPU frequency to target frequency (for !PM_ENABLE case only)
*
* This function automatically reads the current CPU frequency, saves it,
* and switches to the target frequency if current frequency is higher.
* The original frequency can be restored by calling unlimit_cpu_freq().
*
* @param target_freq_mhz Target frequency limit in MHz
* @note limit->unlimit cannot be nested, and this function must not be called concurrently
* @note This is a private function, only for use by spi_flash component.
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set.
*/
static void IRAM_ATTR limit_cpu_freq(uint32_t target_freq_mhz)
{
/* PM not enabled, directly switch frequency and save original frequency */
rtc_cpu_freq_config_t old_config, new_config;
rtc_clk_cpu_freq_get_config(&old_config);
if (old_config.freq_mhz > target_freq_mhz) {
s_saved_freq_mhz = old_config.freq_mhz;
if (rtc_clk_cpu_freq_mhz_to_config(target_freq_mhz, &new_config)) {
/* Use PM implementation function to switch frequency with all necessary synchronization */
esp_pm_impl_switch_cpu_freq(&old_config, &new_config);
const unsigned wait_clock_stable_us = 10;
esp_rom_delay_us(wait_clock_stable_us);
}
} else {
s_saved_freq_mhz = 0; /* No change needed */
}
}
/**
* @brief Unlimit CPU frequency, restoring to original frequency (for !PM_ENABLE case only)
*
* This function restores the CPU frequency to the value saved by
* limit_cpu_freq(). If no frequency change was made, this function
* does nothing.
*
* @note limit->unlimit cannot be nested, and this function must not be called concurrently
* @note This is a private function, only for use by spi_flash component.
* @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set.
*/
static void IRAM_ATTR unlimit_cpu_freq(void)
{
/* PM not enabled, restore to original frequency */
if (s_saved_freq_mhz > 0) {
rtc_cpu_freq_config_t old_config, new_config;
rtc_clk_cpu_freq_get_config(&old_config);
if (rtc_clk_cpu_freq_mhz_to_config(s_saved_freq_mhz, &new_config)) {
/* Use PM implementation function to switch frequency with all necessary synchronization */
esp_pm_impl_switch_cpu_freq(&old_config, &new_config);
const unsigned wait_clock_stable_us = 10;
esp_rom_delay_us(wait_clock_stable_us);
}
}
/* If s_saved_freq_mhz == 0, no change was made, so no need to restore */
s_saved_freq_mhz = 0;
}
#endif // !CONFIG_PM_ENABLE
void IRAM_ATTR esp_flash_freq_limit_cb(void)
{
/* Limit the frequency */
#ifdef CONFIG_PM_ENABLE
/* PM enabled, use force mechanism */
esp_pm_impl_cpu_max_freq_force();
#else
/* PM not enabled, use limit mechanism */
uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit();
/* Call PM implementation function to limit CPU frequency.
* This function automatically reads current frequency, saves it, and switches to target frequency.
*/
limit_cpu_freq(limit_freq_mhz);
#endif
}
void IRAM_ATTR esp_flash_freq_unlimit_cb(void)
{
/* Restore the frequency */
#ifdef CONFIG_PM_ENABLE
/* PM enabled, use unforce mechanism */
esp_pm_impl_cpu_max_freq_unforce();
#else
/* PM not enabled, use unlimit mechanism */
/* Call PM implementation function to unlimit CPU frequency.
* This function restores the original frequency saved by limit_cpu_freq().
*/
unlimit_cpu_freq();
#endif
}
+91 -7
View File
@@ -10,6 +10,7 @@
#include <stdint.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <assert.h>
#include "sdkconfig.h"
#include "esp_attr.h"
@@ -33,6 +34,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#if CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
#include "xtensa_timer.h"
#include "xtensa/core-macros.h"
@@ -41,6 +43,7 @@
#include "esp_private/pm_impl.h"
#include "esp_private/pm_trace.h"
#include "esp_private/esp_timer_private.h"
#include "esp_timer.h"
#include "esp_private/esp_clk.h"
#include "esp_private/esp_clk_tree_common.h"
#include "esp_private/sleep_cpu.h"
@@ -50,7 +53,7 @@
#include "esp_private/esp_clk_utils.h"
#include "esp_sleep.h"
#include "esp_memory_utils.h"
#include "esp_rom_sys.h"
#if SOC_PERIPH_CLK_CTRL_SHARED
#define HP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC()
@@ -167,6 +170,16 @@ static esp_pm_lock_handle_t s_rtos_lock_handle[CONFIG_FREERTOS_NUMBER_OF_CORES];
*/
static rtc_cpu_freq_config_t s_cpu_freq_by_mode[PM_MODE_COUNT];
#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
/* Forced CPU_MAX frequency configuration.
* s_cpu_max_freq_forced indicates whether CPU_MAX frequency is forced.
* s_cpu_max_freq_force_config stores the forced frequency configuration.
* Protected by s_switch_lock, same as s_cpu_freq_by_mode.
*/
static bool s_cpu_max_freq_forced = false;
static rtc_cpu_freq_config_t s_cpu_max_freq_force_config;
#endif
/* Whether automatic light sleep is enabled */
static bool s_light_sleep_en = false;
@@ -392,6 +405,32 @@ static esp_err_t esp_pm_sleep_configure(const esp_pm_config_t *config)
return err;
}
/**
* @brief Get frequency configuration for a given mode, considering forced frequency
*
* This function returns a pointer to the frequency configuration for the given mode.
* For PM_MODE_CPU_MAX, if s_cpu_max_freq_forced is true, it returns
* a pointer to s_cpu_max_freq_force_config. Otherwise, it returns a pointer to
* s_cpu_freq_by_mode[mode].
*
* @param mode Power mode to get configuration for
* @note Must be called with s_switch_lock held
* @note Must be in IRAM when called from ISR context (e.g., do_switch)
* @return rtc_cpu_freq_config_t* Pointer to frequency configuration for the given mode
*/
static inline IRAM_ATTR rtc_cpu_freq_config_t *get_cpu_freq_config_by_mode(pm_mode_t mode)
{
#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
if (mode == PM_MODE_CPU_MAX && s_cpu_max_freq_forced) {
return &s_cpu_max_freq_force_config;
} else {
return &s_cpu_freq_by_mode[mode];
}
#else
return &s_cpu_freq_by_mode[mode];
#endif
}
esp_err_t esp_pm_configure(const void* vconfig)
{
#ifndef CONFIG_PM_ENABLE
@@ -641,18 +680,19 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode)
s_is_switching = true;
bool config_changed = s_config_changed;
s_config_changed = false;
portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]);
portEXIT_CRITICAL_ISR(&s_switch_lock);
rtc_cpu_freq_config_t new_config = s_cpu_freq_by_mode[new_mode];
rtc_cpu_freq_config_t new_config = *get_cpu_freq_config_by_mode(new_mode);
rtc_cpu_freq_config_t old_config;
if (!config_changed) {
old_config = s_cpu_freq_by_mode[s_mode];
old_config = *get_cpu_freq_config_by_mode(s_mode);
} else {
rtc_clk_cpu_freq_get_config(&old_config);
}
portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]);
portEXIT_CRITICAL_ISR(&s_switch_lock);
if (new_config.freq_mhz != old_config.freq_mhz) {
uint32_t old_ticks_per_us = old_config.freq_mhz;
uint32_t new_ticks_per_us = new_config.freq_mhz;
@@ -865,6 +905,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime )
void esp_pm_impl_dump_stats(FILE* out)
{
pm_time_t time_in_mode[PM_MODE_COUNT];
uint32_t freq_mhz_by_mode[PM_MODE_COUNT];
portENTER_CRITICAL_ISR(&s_switch_lock);
memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode));
@@ -874,6 +915,10 @@ void esp_pm_impl_dump_stats(FILE* out)
bool light_sleep_en = s_light_sleep_en;
uint32_t light_sleep_counts = s_light_sleep_counts;
uint32_t light_sleep_reject_counts = s_light_sleep_reject_counts;
// Read all frequency configs while holding s_switch_lock
for (int i = 0; i < PM_MODE_COUNT; ++i) {
freq_mhz_by_mode[i] = get_cpu_freq_config_by_mode(i)->freq_mhz;
}
portEXIT_CRITICAL_ISR(&s_switch_lock);
time_in_mode[cur_mode] += now - last_mode_change_time;
@@ -887,7 +932,7 @@ void esp_pm_impl_dump_stats(FILE* out)
}
fprintf(out, "%-8s %-3"PRIu32"M%-7s %-10lld %-2d%%\n",
s_mode_names[i],
s_cpu_freq_by_mode[i].freq_mhz,
freq_mhz_by_mode[i],
"", //Empty space to align columns
time_in_mode[i],
(int) (time_in_mode[i] * 100 / now));
@@ -904,7 +949,7 @@ int esp_pm_impl_get_cpu_freq(pm_mode_t mode)
int freq_mhz;
if (mode >= PM_MODE_LIGHT_SLEEP && mode < PM_MODE_COUNT) {
portENTER_CRITICAL(&s_switch_lock);
freq_mhz = s_cpu_freq_by_mode[mode].freq_mhz;
freq_mhz = get_cpu_freq_config_by_mode(mode)->freq_mhz;
portEXIT_CRITICAL(&s_switch_lock);
} else {
abort();
@@ -1050,3 +1095,42 @@ void esp_pm_impl_waiti(void)
esp_cpu_wait_for_intr();
#endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE
}
#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE
void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz)
{
// Pre-calculate frequency config (done outside critical section)
rtc_cpu_freq_config_t force_config;
bool res = rtc_clk_cpu_freq_mhz_to_config(limit_freq_mhz, &force_config);
assert(res && "Failed to convert forced CPU_MAX frequency to config");
// Store the pre-calculated config in critical section
portENTER_CRITICAL(&s_switch_lock);
s_cpu_max_freq_force_config = force_config;
portEXIT_CRITICAL(&s_switch_lock);
}
void IRAM_ATTR esp_pm_impl_cpu_max_freq_force(void)
{
portENTER_CRITICAL_SAFE(&s_switch_lock);
// Activate pre-configured forced frequency (no calculation needed at runtime)
s_cpu_max_freq_forced = true;
s_config_changed = true;
portEXIT_CRITICAL_SAFE(&s_switch_lock);
do_switch(PM_MODE_CPU_MAX);
const unsigned wait_clock_stable_us = 10;
esp_rom_delay_us(wait_clock_stable_us);
}
void IRAM_ATTR esp_pm_impl_cpu_max_freq_unforce(void)
{
portENTER_CRITICAL_SAFE(&s_switch_lock);
s_cpu_max_freq_forced = false;
s_config_changed = true;
portEXIT_CRITICAL_SAFE(&s_switch_lock);
do_switch(PM_MODE_CPU_MAX);
const unsigned wait_clock_stable_us = 10;
esp_rom_delay_us(wait_clock_stable_us);
}
#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE
@@ -20,6 +20,13 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ
# Please see SoC Errata document for details.
depends on !SECURE_FLASH_ENC_ENABLED
bool "240 MHz"
help
When 240MHz is selected, esp_flash_write_encrypted() will automatically limit CPU frequency during
execution:
- v1.2 and above chips: limited to 160MHz
- v1.0 and below chips: limited to 80MHz
When 160MHz or lower is selected, no frequency limiting occurs during encrypted writes. Please refer to
the ESP32-C5 SoC Errata document for more details.
endchoice
config ESP_DEFAULT_CPU_FREQ_MHZ
+4 -1
View File
@@ -31,7 +31,7 @@
#include "private/esp_coexist_internal.h"
#endif
#if CONFIG_PM_ENABLE
#if CONFIG_PM_ENABLE || CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
#include "esp_pm.h"
#include "esp_private/pm_impl.h"
#endif
@@ -120,6 +120,9 @@ ESP_SYSTEM_INIT_FN(init_flash, CORE, BIT(0), 130)
#endif // CONFIG_SPI_FLASH_BROWNOUT_RESET
// The log library will call the registered callback function to check if the cache is disabled.
esp_log_util_set_cache_enabled_cb(spi_flash_cache_enabled);
#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
esp_pm_flash_freq_limit_init();
#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED
return ESP_OK;
}
#endif // !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
@@ -1119,6 +1119,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR
bool
default y
config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED
bool
default y
config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY
bool
default y
@@ -453,6 +453,7 @@
#define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1)
#define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1)
#define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1)
#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1)
#define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1)
#define SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED 1
@@ -815,6 +815,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR
bool
default y
config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED
bool
default y
config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY
bool
default y
@@ -341,6 +341,7 @@
#define SOC_SPI_MEM_SUPPORT_WRAP (1)
#define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1)
#define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1)
#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1)
#define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1)
#define SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED 1
@@ -715,6 +715,10 @@ config SOC_SPI_MEM_SUPPORT_WRAP
bool
default y
config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED
bool
default y
config SOC_SYSTIMER_COUNTER_NUM
int
default 2
@@ -394,6 +394,7 @@
#define SOC_SPI_MEM_SUPPORT_SW_SUSPEND (1)
#define SOC_SPI_MEM_SUPPORT_CHECK_SUS (1)
#define SOC_SPI_MEM_SUPPORT_WRAP (1)
#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1)
/*-------------------------- SYSTIMER CAPS ----------------------------------*/
#define SOC_SYSTIMER_COUNTER_NUM 2 // Number of counter units
@@ -1499,6 +1499,10 @@ config SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP
bool
default y
config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED
bool
default y
config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR
bool
default y
@@ -572,6 +572,7 @@
#define SOC_MEMSPI_TIMING_TUNING_BY_DQS (1)
#define SOC_MEMSPI_TIMING_TUNING_BY_FLASH_DELAY (1)
#define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1)
#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1)
#define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1)
#define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT (1)
+7
View File
@@ -458,4 +458,11 @@ menu "SPI Flash driver"
application is not using flash encryption feature and is in need of some additional
memory from IRAM region (~1KB) then this config can be disabled.
config SPI_FLASH_FREQ_LIMIT_C5_240MHZ
bool
default y if IDF_TARGET_ESP32C5 && ESP_DEFAULT_CPU_FREQ_MHZ_240
help
Enable frequency limit workaround for encrypted flash writes on ESP32-C5 at 240MHz default CPU frequency.
This is an internal configuration, automatically set based on target chip and CPU frequency.
endmenu
+79 -72
View File
@@ -135,15 +135,6 @@ static ESP_LOG_ATTR const char io_mode_str[][IO_STR_LEN] = {
_Static_assert(sizeof(io_mode_str)/IO_STR_LEN == SPI_FLASH_READ_MODE_MAX, "the io_mode_str should be consistent with the esp_flash_io_mode_t defined in spi_flash_types.h");
esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id);
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
static esp_err_t spiflash_start_default(esp_flash_t *chip);
static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err);
static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip);
static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length);
#endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
typedef struct {
esp_err_t (*start)(esp_flash_t *chip);
esp_err_t (*end)(esp_flash_t *chip, esp_err_t err);
@@ -151,19 +142,70 @@ typedef struct {
esp_err_t (*flash_end_flush_cache)(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length);
} rom_spiflash_api_func_t;
esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id);
#if CONFIG_SPI_FLASH_ROM_IMPL
extern rom_spiflash_api_func_t *esp_flash_api_funcs;
#define rom_spiflash_api_funcs esp_flash_api_funcs
#else
#define rom_spiflash_api_funcs esp_flash_api_funcs_patched_ptr
#endif
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
// API funcs case 1 & 2
static esp_err_t spiflash_start_default(esp_flash_t *chip);
static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err);
static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip);
static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length);
// These functions can be placed in the ROM. For now we use the code in IDF.
DRAM_ATTR static rom_spiflash_api_func_t default_spiflash_rom_api = {
DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched = {
.start = spiflash_start_default,
.end = spiflash_end_default,
.chip_check = check_chip_pointer_default,
.flash_end_flush_cache = flash_end_flush_cache,
};
DRAM_ATTR rom_spiflash_api_func_t *rom_spiflash_api_funcs = &default_spiflash_rom_api;
#else
extern rom_spiflash_api_func_t *esp_flash_api_funcs;
#define rom_spiflash_api_funcs esp_flash_api_funcs
# if !CONFIG_SPI_FLASH_ROM_IMPL
// API funcs case 1: Not using ROM - define our own pointer and all functions
DRAM_ATTR static rom_spiflash_api_func_t *esp_flash_api_funcs_patched_ptr = &esp_flash_api_funcs_patched;
# else // CONFIG_SPI_FLASH_ROM_IMPL
// API funcs case 2: Using ROM APIs but patch all api_funcs by updating esp_flash_api_funcs from ROM
void esp_flash_rom_api_funcs_init(void)
{
// Point esp_flash_api_funcs to our default structure
esp_flash_api_funcs = &esp_flash_api_funcs_patched;
}
# endif // CONFIG_SPI_FLASH_ROM_IMPL
#else // CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
// Using ROM implementation
# if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// API funcs case 3: Using ROM APIs but patch start function to support flags parameter
static esp_err_t spiflash_start_default(esp_flash_t *chip);
DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched;
// Copy ROM structure to RAM and patch start function to support flags
void esp_flash_rom_api_funcs_init(void)
{
rom_spiflash_api_func_t *rom_ptr = esp_flash_api_funcs;
memcpy(&esp_flash_api_funcs_patched, rom_ptr, sizeof(rom_spiflash_api_func_t));
esp_flash_api_funcs_patched.start = spiflash_start_default;
esp_flash_api_funcs = &esp_flash_api_funcs_patched;
}
# else
// API funcs case 4: Using All ROM APIs directly
void esp_flash_rom_api_funcs_init(void)
{
// Do nothing
}
# endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
#endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
/* Static function to notify OS of a new SPI flash operation.
@@ -171,11 +213,13 @@ extern rom_spiflash_api_func_t *esp_flash_api_funcs;
If returns an error result, caller must abort. If returns ESP_OK, caller must
call rom_spiflash_api_funcs->end() before returning.
*/
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
static esp_err_t spiflash_start_default(esp_flash_t *chip)
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
//Avoid constprop issue that place this function into flash.
__attribute__((optimize("O0"))) //IDF-14941
static esp_err_t spiflash_start_core(esp_flash_t *chip, uint32_t flags)
{
if (chip->os_func != NULL && chip->os_func->start != NULL) {
esp_err_t err = chip->os_func->start(chip->os_func_data);
esp_err_t err = chip->os_func->start(chip->os_func_data, flags);
if (err != ESP_OK) {
return err;
}
@@ -184,6 +228,13 @@ static esp_err_t spiflash_start_default(esp_flash_t *chip)
return ESP_OK;
}
static esp_err_t spiflash_start_default(esp_flash_t *chip)
{
return spiflash_start_core(chip, 0);
}
#endif //!CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
/* Static function to notify OS that SPI flash operation is complete.
*/
static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err)
@@ -1235,54 +1286,20 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe)
}
#endif //CONFIG_SPI_FLASH_ROM_IMPL
#if CONFIG_IDF_TARGET_ESP32C5
// Hardware workaround: ESP32-C5 encrypted flash writes require CPU freq ≤ 160 MHz
#include "soc/rtc.h"
static int s_esp32c5_saved_cpu_freq_mhz;
static IRAM_ATTR void esp32c5_freq_limit_acquire(void)
#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// use `esp_flash_write_encrypted` ROM version on chips later than C3, S3
// For ESP32-C5, use IDF implementation when CPU frequency is 240MHz (calling start() with arg is required)
FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip)
{
rtc_cpu_freq_config_t old_config, new_config;
rtc_clk_cpu_freq_get_config(&old_config);
if (old_config.freq_mhz <= 160) {
s_esp32c5_saved_cpu_freq_mhz = 0; // No change needed
return;
}
s_esp32c5_saved_cpu_freq_mhz = old_config.freq_mhz;
if (rtc_clk_cpu_freq_mhz_to_config(160, &new_config)) {
rtc_clk_cpu_freq_set_config_fast(&new_config);
}
}
static IRAM_ATTR void esp32c5_freq_limit_release(void)
{
if (s_esp32c5_saved_cpu_freq_mhz == 0) {
return; // No change was made
}
rtc_cpu_freq_config_t new_config;
if (rtc_clk_cpu_freq_mhz_to_config(s_esp32c5_saved_cpu_freq_mhz, &new_config)) {
rtc_clk_cpu_freq_set_config_fast(&new_config);
}
s_esp32c5_saved_cpu_freq_mhz = 0;
}
#endif // CONFIG_IDF_TARGET_ESP32C5
#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV)
// use `esp_flash_write_encrypted` ROM version on chips later than C3 and S3
FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) {
#if CONFIG_IDF_TARGET_ESP32C5
esp32c5_freq_limit_acquire();
#endif
#if CONFIG_IDF_TARGET_ESP32S2
esp_crypto_dma_lock_acquire();
#endif //CONFIG_IDF_TARGET_ESP32S2
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// Use start_core with LIMIT_CPU_FREQ flag to trigger freq_limit_lock in OS layer
return spiflash_start_core(chip, ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ);
#else
return rom_spiflash_api_funcs->start(chip);
#endif
}
FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) {
@@ -1290,9 +1307,6 @@ FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) {
#if CONFIG_IDF_TARGET_ESP32S2
esp_crypto_dma_lock_release();
#endif //CONFIG_IDF_TARGET_ESP32S2
#if CONFIG_IDF_TARGET_ESP32C5
esp32c5_freq_limit_release();
#endif
return err;
}
@@ -1535,16 +1549,9 @@ esp_err_t IRAM_ATTR esp_flash_write_encrypted(esp_flash_t *chip, uint32_t addres
if (length > chip->size - address) {
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_IDF_TARGET_ESP32C5
esp32c5_freq_limit_acquire();
#endif
err = rom_esp_flash_write_encrypted(chip, address, buffer, length);
#if CONFIG_IDF_TARGET_ESP32C5
esp32c5_freq_limit_release();
#endif
return err;
return rom_esp_flash_write_encrypted(chip, address, buffer, length);
}
#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV)
#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
//init suspend mode cmd, uses internal.
esp_err_t esp_flash_suspend_cmd_init(esp_flash_t* chip)
+25 -1
View File
@@ -145,6 +145,19 @@ esp_flash_t *esp_flash_default_chip = NULL;
#endif //!CONFIG_SPI_FLASH_AUTO_SUSPEND
#endif // Other target
// Dynamic flash configuration is only needed when:
// 1. Frequency limit workaround is enabled (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ)
// 2. Flash frequency requires timing tuning (80MHz or 120MHz, i.e., > 40MHz)
// 3. CPU frequency reduction will trigger MSPI timing tuning to enter low speed mode
// This happens when: SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM &&
// (target_cpu_freq < CONFIG_SPIRAM_SPEED)
// Note: The runtime check for CPU freq < PSRAM speed is done in clk_utils.c,
// which calls mspi_timing_change_speed_mode_cache_safe(true) to enter low speed mode.
// For ESP32-C5, if PSRAM is enabled and CPU freq < PSRAM speed, timing tuning will be disabled.
#define C5_NEEDS_DYNAMIC_CONFIG (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ && CONFIG_SPIRAM && \
(CONFIG_ESPTOOLPY_FLASHFREQ_80M || CONFIG_ESPTOOLPY_FLASHFREQ_120M))
static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_flash_spi_device_config_t *config, bool cs_use_iomux, int cs_id)
{
//Not using spicommon_cs_initialize since we don't want to put the whole
@@ -160,7 +173,7 @@ static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_f
//To avoid the panic caused by flash data line conflicts during cs line
//initialization, disable the cache temporarily
chip->os_func->start(chip->os_func_data);
chip->os_func->start(chip->os_func_data, 0);
gpio_hal_input_enable(&gpio_hal, cs_io_num);
if (cs_use_iomux) {
gpio_hal_func_sel(&gpio_hal, cs_io_num, spics_func);
@@ -554,6 +567,16 @@ esp_err_t esp_flash_init_default_chip(void)
if (err != ESP_OK) {
return err;
}
#if C5_NEEDS_DYNAMIC_CONFIG
err = memspi_host_init_c5_dynamic_config(&esp_flash_default_host);
if (err != ESP_OK) {
return err;
}
#endif
#if CONFIG_SPI_FLASH_ROM_IMPL
esp_flash_rom_api_funcs_init();
#endif // CONFIG_SPI_FLASH_ROM_IMPL
// ROM TODO: account for non-standard default pins in efuse
// ROM TODO: to account for chips which are slow to power on, maybe keep probing in a loop here
@@ -615,6 +638,7 @@ esp_err_t esp_flash_app_init(void)
spi_flash_init_lock();
spi_flash_guard_set(&g_flash_guard_default_ops);
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
esp_flash_reset_counters();
#endif
+7 -1
View File
@@ -38,11 +38,17 @@ typedef struct {
* risk.
*/
typedef struct {
/**
* Flags for start function
*/
/** Limit CPU frequency during flash operations (ESP32-C5 only, 240MHz).
*/
#define ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ BIT(0)
/**
* Called before commencing any flash operation. Does not need to be
* recursive (ie is called at most once for each call to 'end').
*/
esp_err_t (*start)(void *arg);
esp_err_t (*start)(void *arg, uint32_t flags);
/** Called after completing any flash operation. */
esp_err_t (*end)(void *arg);
@@ -117,6 +117,16 @@ esp_err_t esp_flash_app_disable_os_functions(esp_flash_t* chip);
*/
esp_err_t esp_flash_set_dangerous_write_protection(esp_flash_t *chip, const bool protect);
#if CONFIG_SPI_FLASH_ROM_IMPL
/**
* @brief Initialize ROM API functions structure
*
* This function initializes the ROM API functions structure, either by pointing
* to a custom structure or by patching the ROM structure in RAM.
*/
void esp_flash_rom_api_funcs_init(void);
#endif // CONFIG_SPI_FLASH_ROM_IMPL
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "esp_flash.h"
#include "esp_attr.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
/**
* @brief Callback to limit CPU frequency for flash encryption writes (ESP32-C5 only, 240MHz)
*
* This function limits CPU frequency to <= 160MHz during encrypted flash writes.
* It should be called before starting an encrypted flash write operation.
*
* @note This is an internal function, not exposed to users.
* @note This function is placed in IRAM (implementation uses IRAM_ATTR).
* @note This function is called from spi_flash_os_func_app.c::spi1_start() after cache
* and scheduler locks are acquired. As a result, there is no concurrency concern
* and no need for internal locking or reference counting.
*/
void esp_flash_freq_limit_cb(void);
/**
* @brief Callback to unlimit CPU frequency after flash encryption writes (ESP32-C5 only, 240MHz)
*
* This function restores the CPU frequency after an encrypted flash write operation.
* It should be called after completing an encrypted flash write operation.
*
* @note This is an internal function, not exposed to users.
* @note This function is placed in IRAM (implementation uses IRAM_ATTR).
* @note This function is called from spi_flash_os_func_app.c::spi1_end() before cache
* and scheduler locks are released. As a result, there is no concurrency concern
* and no need for internal locking or reference counting.
*/
void esp_flash_freq_unlimit_cb(void);
#endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
#ifdef __cplusplus
}
#endif
@@ -48,6 +48,17 @@ typedef spi_flash_hal_context_t memspi_host_inst_t;
*/
esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_config_t *cfg);
/**
* Initialize the memory SPI host for ESP32-C5 dynamic configuration.
*
* @param host Pointer to the host structure.
*
* @note This function is available only when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is defined.
* @note This function can only be called once at startup, after memspi_host_init_pointers() is called.
* @return always return ESP_OK
*/
esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host);
/*******************************************************************************
* NOTICE
* Rest part of this file are part of the HAL layer
@@ -161,7 +172,7 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address,
/**
* @brief Slicer for write data used in non-encrypted regions. This slicer limit the length to the
* maximum size the host supports, and truncate if the write data lie accross the page boundary
* maximum size the host supports, and truncate if the write data lie across the page boundary
* (256 bytes)
*
* @param address Flash address to write
+5 -2
View File
@@ -45,12 +45,15 @@ entries:
if SPI_FLASH_VERIFY_WRITE = y:
esp_flash_api: s_verify_write (noflash)
if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y:
if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y || SPI_FLASH_FREQ_LIMIT_C5_240MHZ = y:
esp_flash_api: spiflash_start_default (noflash)
esp_flash_api: spiflash_start_core (noflash)
esp_flash_api: esp_flash_write_encrypted (noflash)
if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y:
esp_flash_api: spiflash_end_default (noflash)
esp_flash_api: check_chip_pointer_default (noflash)
esp_flash_api: flash_end_flush_cache (noflash)
esp_flash_api: esp_flash_write_encrypted (noflash)
if SPI_FLASH_ROM_IMPL = n:
esp_flash_api: esp_flash_get_size (noflash)
+54 -3
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "spi_flash_defs.h"
#include "memspi_host_driver.h"
@@ -12,12 +13,13 @@
#include "esp_private/cache_utils.h"
#include "esp_flash_partitions.h"
#include "esp_memory_utils.h"
#include "hal/mspi_ll.h"
#define SPI_FLASH_HAL_MAX_WRITE_BYTES 64
#define SPI_FLASH_HAL_MAX_READ_BYTES 64
DRAM_ATTR static const spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER();
DRAM_ATTR static spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER();
#if SOC_MEMSPI_IS_INDEPENDENT
extern void spi_flash_hal_gpspi_poll_cmd_done(spi_flash_host_inst_t *host);
@@ -71,9 +73,9 @@ esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_
}
#if SOC_MEMSPI_IS_INDEPENDENT
if (cfg->host_id == SPI1_HOST)
if (cfg->host_id == SPI1_HOST) {
host->inst.driver = &esp_flash_default_host;
else {
} else {
host->inst.driver = &esp_flash_gpspi_host;
}
#else
@@ -253,3 +255,52 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address,
}
#endif // CONFIG_SPI_FLASH_ROM_IMPL
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// Dynamic flash configuration based on timing tuning state:
// 1. The timing tuning system automatically sets core clock and timing parameters (including din_mode/num, etc.).
// It also generates configurations such as clock configuration and dummy configuration for driver use.
// Without calling this function, the driver gets these configurations during initialization and uses them forever.
// The timing tuning provides clock configuration (frequency division) and dummy count.
// 2. When dynamic configuration is needed, call this function. The function supports two states:
// - Timing tuned state (high speed): Restore the values generated by the timing tuning system to the driver,
// as if there was no frequency reduction.
// - Without timing tuning state (low speed): Set configuration to maximum frequency (40 MHz on C5), dummy=0.
static uint32_t s_high_speed_clock_reg;
static uint32_t s_high_speed_extra_dummy;
ESP_STATIC_ASSERT(MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT == 80);
#define LOW_SPEED_DIV (MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT/40)
static esp_err_t spi_flash_hal_device_config_c5(spi_flash_host_inst_t *host)
{
spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host;
// Note: SPI0 and SPI1 have separate TIMING_CALI_REG registers, but SPI0's register reflects the timing tuning state
// since SPI0 and SPI1 share din_mode/din_num registers and timing tuning is configured on SPI0
uint32_t timing_cali_reg = REG_READ(SPI_MEM_TIMING_CALI_REG(MSPI_TIMING_LL_MSPI_ID_0));
bool is_low_speed = !(timing_cali_reg & SPI_MEM_TIMING_CALI_M);
if (is_low_speed) {
// Low speed mode: Set to safe division, dummy=0
uint32_t low_speed_clock_reg = mspi_timing_ll_calculate_clock_reg(LOW_SPEED_DIV);
ctx->clock_conf.spimem = low_speed_clock_reg;
ctx->extra_dummy = 0;
} else {
// High speed mode: Restore timing tuning values
ctx->clock_conf.spimem = s_high_speed_clock_reg;
ctx->extra_dummy = s_high_speed_extra_dummy;
}
return spi_flash_hal_device_config(host);
}
esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host)
{
assert(s_high_speed_clock_reg == 0 && s_high_speed_extra_dummy == 0);
spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host;
s_high_speed_clock_reg = ctx->clock_conf.spimem;
s_high_speed_extra_dummy = ctx->extra_dummy;
esp_flash_default_host.dev_config = spi_flash_hal_device_config_c5;
return ESP_OK;
}
#endif
+31 -6
View File
@@ -20,9 +20,12 @@
#include "esp_rom_sys.h"
#include "esp_private/spi_flash_os.h"
#include "esp_private/cache_utils.h"
#include "esp_private/spi_share_hw_ctrl.h"
// For C5 encrypted write workaround
// Functions are only available when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is true
#include "esp_private/spi_flash_freq_limit_cbs.h"
#define SPI_FLASH_CACHE_NO_DISABLE (CONFIG_SPI_FLASH_AUTO_SUSPEND || (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM)
static const char TAG[] = "spi_flash";
@@ -53,6 +56,7 @@ typedef struct {
bool no_protect; //to decide whether to check protected region (for the main chip) or not.
uint32_t acquired_since_us; // Time since last explicit yield()
uint32_t released_since_us; // Time since last end() (implicit yield)
uint32_t start_flags; // Flags passed to start() function, used to determine if freq_limit was called
} app_func_arg_t;
static inline void on_spi_released(app_func_arg_t* ctx);
@@ -90,21 +94,27 @@ static IRAM_ATTR esp_err_t release_spi_bus_lock(void *arg)
return spi_bus_lock_acquire_end(((app_func_arg_t *)arg)->dev_lock);
}
static esp_err_t spi23_start(void *arg){
static esp_err_t spi23_start(void *arg, uint32_t flags)
{
(void)flags;
esp_err_t ret = acquire_spi_bus_lock(arg);
on_spi_acquired((app_func_arg_t*)arg);
return ret;
}
static esp_err_t spi23_end(void *arg){
static esp_err_t spi23_end(void *arg)
{
esp_err_t ret = release_spi_bus_lock(arg);
on_spi_released((app_func_arg_t*)arg);
return ret;
}
static IRAM_ATTR esp_err_t spi1_start(void *arg)
static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags)
{
esp_err_t ret = ESP_OK;
app_func_arg_t* ctx = (app_func_arg_t*)arg;
ctx->start_flags = flags;
/**
* There are three ways for ESP Flash API lock:
* 1. spi bus lock, this is used when SPI1 is shared with GPSPI Master Driver
@@ -136,13 +146,28 @@ static IRAM_ATTR esp_err_t spi1_start(void *arg)
}
#endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
on_spi_acquired((app_func_arg_t*)arg);
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) {
esp_flash_freq_limit_cb();
}
#endif
on_spi_acquired(ctx);
return ret;
}
static IRAM_ATTR esp_err_t spi1_end(void *arg)
{
esp_err_t ret = ESP_OK;
app_func_arg_t* ctx = (app_func_arg_t*)arg;
// Call freq_limit_unlock if needed, before releasing the lock
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
uint32_t flags = ctx->start_flags;
if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) {
esp_flash_freq_unlimit_cb();
}
#endif
/**
* There are three ways for ESP Flash API lock, see `spi1_start`
@@ -166,7 +191,7 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg)
}
#endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
on_spi_released((app_func_arg_t*)arg);
on_spi_released(ctx);
return ret;
}
@@ -15,7 +15,7 @@
#include "hal/cache_ll.h"
#include "soc/soc_caps.h"
static IRAM_ATTR esp_err_t start(void *arg)
static IRAM_ATTR esp_err_t start(void *arg, uint32_t flags)
{
#if SOC_BRANCH_PREDICTOR_SUPPORTED
//branch predictor will start cache request as well
@@ -24,6 +24,15 @@ components/spi_flash/test_apps/esp_flash_blockdev:
depends_components:
- spi_flash
components/spi_flash/test_apps/esp_flash_freq_limit:
enable:
- if: IDF_TARGET == "esp32c5"
depends_components:
- spi_flash
- esp_pm
- esp_driver_gptimer
- esp_hw_support
components/spi_flash/test_apps/esp_flash_stress:
disable:
- if: IDF_TARGET == "esp32h4"
@@ -0,0 +1,17 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.22)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
# "Trim" the build. Include the minimal set of components, main, and anything it depends on. We also depend on
# esptool_py as we set CONFIG_ESPTOOLPY_... options.
set(COMPONENTS main esptool_py)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_esp_flash_freq_limit)
message(STATUS "Checking memspi registers are not read-write by half-word")
include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake)
check_register_rw_half_word(SOC_MODULES "spi_mem*" "spi1_mem*"
HAL_MODULES "spimem_flash")
@@ -0,0 +1,2 @@
| Supported Targets | ESP32-C5 |
| ----------------- | -------- |
@@ -0,0 +1,9 @@
set(srcs "test_app_main.c"
"test_esp_flash_freq_limit.c")
# 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}
PRIV_REQUIRES unity test_utils spi_flash esp_pm bootloader_support freertos esp_timer
esp_driver_gptimer esp_hw_support esp_psram
WHOLE_ARCHIVE)
@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_utils.h"
#define TEST_MEMORY_LEAK_THRESHOLD (700)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD);
}
void app_main(void)
{
unity_run_menu();
}
@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
factory, 0, 0, 0x10000, 1M
flash_test, data, fat, , 700K
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, 0, 0, 0x10000, 1M
5 flash_test, data, fat, , 700K
@@ -0,0 +1,23 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'pm_disabled_240mhz',
'pm_disabled_160mhz',
'pm_enabled_240mhz',
'pm_enabled_160mhz',
'pm_disabled_240mhz_xip_psram',
'pm_enabled_240mhz_xip_psram',
],
indirect=True,
)
@idf_parametrize('target', ['esp32c5'], indirect=['target'])
def test_esp_flash_freq_limit(dut: Dut) -> None:
dut.run_all_single_board_cases(group='esp_flash_freq_limit', timeout=10)
@@ -0,0 +1,2 @@
CONFIG_PM_ENABLE=n
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
@@ -0,0 +1,2 @@
CONFIG_PM_ENABLE=n
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
@@ -0,0 +1,8 @@
CONFIG_PM_ENABLE=n
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_XIP_FROM_PSRAM=y
CONFIG_SPIRAM_SPEED_120M=y
@@ -0,0 +1,6 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_PM_ENABLE=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
@@ -0,0 +1,6 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_PM_ENABLE=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
@@ -0,0 +1,12 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_PM_ENABLE=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_XIP_FROM_PSRAM=y
CONFIG_SPIRAM_SPEED_120M=y
@@ -0,0 +1,13 @@
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_PM_ENABLE=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
CONFIG_SPIRAM=y
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_XIP_FROM_PSRAM=y
CONFIG_SPIRAM_SPEED_120M=y
CONFIG_SPI_FLASH_ROM_IMPL=y
@@ -0,0 +1,2 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"