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-11-25 15:03:25 +00: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"
@@ -303,6 +303,7 @@ SPI Flash Driver
- Deprecated API ``spi_flash_dump_counters`` has been removed. Please use :cpp:func:`esp_flash_dump_counters` instead.
- Deprecated API ``spi_flash_get_counters`` has been removed. Please use :cpp:func:`esp_flash_get_counters` instead.
- Deprecated API ``spi_flash_reset_counters`` has been removed. Please use :cpp:func:`esp_flash_reset_counters` instead.
- New argument ``flags`` is added to ``esp_flash_os_functions_t::start``. Caller and implementer should handle this argument properly.
- Kconfig option ``CONFIG_SPI_FLASH_ROM_DRIVER_PATCH`` has been removed. Considering that this option is unlikely to be widely used by users and may cause serious issues if misused, it has been decided to remove it.
.. note::
@@ -303,6 +303,7 @@ SPI flash 驱动
- 已弃用的 API ``spi_flash_dump_counters`` 已被移除。请改用 :cpp:func:`esp_flash_dump_counters`
- 已弃用的 API ``spi_flash_get_counters`` 已被移除。请改用 :cpp:func:`esp_flash_get_counters`
- 已弃用的 API ``spi_flash_reset_counters`` 已被移除。请改用 :cpp:func:`esp_flash_reset_counters`
- ``esp_flash_os_functions_t::start`` 新增了一个参数 ``flags``。调用者和实现者应正确处理此参数。
- Kconfig 选项 ``CONFIG_SPI_FLASH_ROM_DRIVER_PATCH`` 已被移除,考虑到这个选项不会被广泛被用户使用,且有因误用而导致出现严重的问题,遂决定移除。
.. note::