mspi: make cpu clock source switch safe

For some of the MSPI high frequency setting (e.g. 80M DDR mode Flash or PSRAM), timing tuning is required.
Certain delays will be added to the MSPI RX direction. When system clock switches down, the delays should be
cleared. When system clock switches up, the delays should be restored.
This commit is contained in:
Armando 2021-10-19 12:25:08 +08:00
parent 7c3a37977f
commit c331c85318
8 changed files with 81 additions and 70 deletions

View File

@ -21,18 +21,7 @@
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_efuse.h" #include "esp_efuse.h"
#include "esp_efuse_table.h" #include "esp_efuse_table.h"
#ifndef BOOTLOADER_BUILD
/**
* TODO: IDF-3204
* Temporarily solution. Depends on MSPI
* Final solution: the rtc should not depend on MSPI. We should do rtc related before Flash init
*/
#include "esp_private/spi_flash_os.h" #include "esp_private/spi_flash_os.h"
#include "esp32s3/rom/cache.h"
#include "freertos/portmacro.h"
portMUX_TYPE rtc_init_spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
#endif
#define RTC_CNTL_MEM_FORCE_NOISO (RTC_CNTL_SLOWMEM_FORCE_NOISO | RTC_CNTL_FASTMEM_FORCE_NOISO) #define RTC_CNTL_MEM_FORCE_NOISO (RTC_CNTL_SLOWMEM_FORCE_NOISO | RTC_CNTL_FASTMEM_FORCE_NOISO)
@ -248,39 +237,23 @@ static void set_ocode_by_efuse(int calib_version)
REGI2C_WRITE_MASK(I2C_ULP, I2C_ULP_IR_FORCE_CODE, 1); REGI2C_WRITE_MASK(I2C_ULP, I2C_ULP_IR_FORCE_CODE, 1);
} }
#ifndef BOOTLOADER_BUILD /**
//TODO: IDF-3204 * TODO: IDF-4141
//Temporary solution, these 2 functions should be defined elsewhere, because similar operations are also needed elsewhere * 1. This function will change the system clock source to XTAL. Under lower frequency (e.g. XTAL), MSPI timing tuning configures should be modified accordingly.
//Final solution: the rtc should not depend on MSPI. We should do rtc related before Flash init * 2. RTC related should be done before SPI0 initialisation
static void IRAM_ATTR enter_mspi_low_speed_mode_safe(void) */
{ static void calibrate_ocode(void)
portENTER_CRITICAL(&rtc_init_spinlock);
Cache_Freeze_ICache_Enable(1);
Cache_Freeze_DCache_Enable(1);
spi_timing_enter_mspi_low_speed_mode(false);
Cache_Freeze_DCache_Disable();
Cache_Freeze_ICache_Disable();
portEXIT_CRITICAL(&rtc_init_spinlock);
}
static void IRAM_ATTR enter_mspi_high_speed_mode_safe(void)
{
portENTER_CRITICAL(&rtc_init_spinlock);
Cache_Freeze_ICache_Enable(1);
Cache_Freeze_DCache_Enable(1);
spi_timing_enter_mspi_high_speed_mode(false);
Cache_Freeze_DCache_Disable();
Cache_Freeze_ICache_Disable();
portEXIT_CRITICAL(&rtc_init_spinlock);
}
#endif
//TODO: IDF-3204
//This function will change the system clock source to XTAL. Under lower frequency (e.g. XTAL), MSPI timing tuning configures should be modified accordingly.
static void IRAM_ATTR calibrate_ocode(void)
{ {
#ifndef BOOTLOADER_BUILD #ifndef BOOTLOADER_BUILD
enter_mspi_low_speed_mode_safe(); /**
* Background:
* 1. Following code will switch the system clock to XTAL first, to self-calibrate the OCode.
* 2. For some of the MSPI high frequency setting (e.g. 80M DDR mode Flash or PSRAM), timing tuning is required.
* Certain delay will be added to the MSPI RX direction.
*
* When CPU clock switches down, the delay should be cleared. Therefore here we call this function to remove the delays.
*/
spi_timing_change_speed_mode_cache_safe(true);
#endif #endif
/* /*
Bandgap output voltage is not precise when calibrate o-code by hardware sometimes, so need software o-code calibration (must turn off PLL). Bandgap output voltage is not precise when calibrate o-code by hardware sometimes, so need software o-code calibration (must turn off PLL).
@ -330,6 +303,7 @@ static void IRAM_ATTR calibrate_ocode(void)
} }
rtc_clk_cpu_freq_set_config(&old_config); rtc_clk_cpu_freq_set_config(&old_config);
#ifndef BOOTLOADER_BUILD #ifndef BOOTLOADER_BUILD
enter_mspi_high_speed_mode_safe(); //System clock is switched back to PLL. Here we switch to the MSPI high speed mode, add the delays back
spi_timing_change_speed_mode_cache_safe(false);
#endif #endif
} }

View File

@ -359,25 +359,20 @@ void spi_timing_config_psram_tune_dummy(uint8_t extra_dummy)
#endif //#if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING #endif //#if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING
static bool spi_timing_config_cs_setup_enable(void)
/*-------------------------------------------------------------------------------------------------
* To let upper lay (spi_flash_timing_tuning.c) to know the necessary timing registers
*-------------------------------------------------------------------------------------------------*/
static bool s_get_cs_setup_enable(void)
{ {
return REG_GET_BIT(SPI_MEM_USER_REG(0), SPI_MEM_CS_SETUP); return REG_GET_BIT(SPI_MEM_USER_REG(0), SPI_MEM_CS_SETUP);
} }
static bool spi_timing_config_cs_hold_enable(void) static bool s_get_cs_hold_enable(void)
{ {
return REG_GET_BIT(SPI_MEM_USER_REG(0), SPI_MEM_CS_HOLD); return REG_GET_BIT(SPI_MEM_USER_REG(0), SPI_MEM_CS_HOLD);
} }
bool spi_timine_config_flash_is_tuned(void)
{
#if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING
return true;
#else
return false;
#endif
}
/** /**
* Get the SPI1 Flash CS timing setting. The setup time and hold time are both realistic cycles. * Get the SPI1 Flash CS timing setting. The setup time and hold time are both realistic cycles.
* @note On ESP32-S3, SPI0/1 share the Flash CS timing registers. Therefore, we should not change these values. * @note On ESP32-S3, SPI0/1 share the Flash CS timing registers. Therefore, we should not change these values.
@ -392,12 +387,12 @@ void spi_timing_config_get_cs_timing(uint8_t *setup_time, uint32_t *hold_time)
* The logic here is, if setup_en / hold_en is false, then we return the realistic cycle number, * The logic here is, if setup_en / hold_en is false, then we return the realistic cycle number,
* which is 0. If true, then the realistic cycle number is (reg_value + 1) * which is 0. If true, then the realistic cycle number is (reg_value + 1)
*/ */
if (spi_timing_config_cs_setup_enable()) { if (s_get_cs_setup_enable()) {
*setup_time += 1; *setup_time += 1;
} else { } else {
*setup_time = 0; *setup_time = 0;
} }
if (spi_timing_config_cs_hold_enable()) { if (s_get_cs_hold_enable()) {
*hold_time += 1; *hold_time += 1;
} else { } else {
*hold_time = 0; *hold_time = 0;

View File

@ -245,7 +245,7 @@ void spi_timing_config_psram_tune_dummy(uint8_t extra_dummy);
/** /**
* SPI1 register info get APIs. These APIs inform `spi_flash_timing_tuning.c` (driver layer) of the SPI1 flash settings. * SPI1 register info get APIs. These APIs inform `spi_flash_timing_tuning.c` (driver layer) of the SPI1 flash settings.
* In this way, other components (e.g.: esp_flash driver) can get the info from it. * In this way, other components (e.g.: esp_flash driver) can get the info from it (`spi_flash_timing_tuning.c`).
*/ */
void spi_timing_config_get_cs_timing(uint8_t *setup_time, uint32_t *hold_time); void spi_timing_config_get_cs_timing(uint8_t *setup_time, uint32_t *hold_time);
uint32_t spi_timing_config_get_flash_clock_reg(void); uint32_t spi_timing_config_get_flash_clock_reg(void);

View File

@ -296,7 +296,7 @@ esp_err_t esp_flash_init_default_chip(void)
// For chips need time tuning, get value directely from system here. // For chips need time tuning, get value directely from system here.
#if SOC_SPI_MEM_SUPPORT_TIME_TUNING #if SOC_SPI_MEM_SUPPORT_TIME_TUNING
if (spi_timine_config_flash_is_tuned()) { if (spi_timing_is_tuned()) {
cfg.using_timing_tuning = 1; cfg.using_timing_tuning = 1;
spi_timing_get_flash_timing_param(&cfg.timing_reg); spi_timing_get_flash_timing_param(&cfg.timing_reg);
} }

View File

@ -45,17 +45,26 @@ extern "C" {
esp_err_t spi_flash_init_chip_state(void); esp_err_t spi_flash_init_chip_state(void);
/** /**
* @brief Make MSPI work under 20Mhz * @brief Make MSPI work under 20Mhz, remove the timing tuning required delays.
* @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 * @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
*/ */
void spi_timing_enter_mspi_low_speed_mode(bool control_spi1); void spi_timing_enter_mspi_low_speed_mode(bool control_spi1);
/** /**
* @brief Make MSPI work under the frequency as users set * @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 * @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
*/ */
void spi_timing_enter_mspi_high_speed_mode(bool control_spi1); void spi_timing_enter_mspi_high_speed_mode(bool control_spi1);
/**
* @brief Switch MSPI into low speed mode / high speed mode.
* @note This API is cache safe, it will freeze both D$ and I$ and restore them after MSPI is switched
* @note For some of the MSPI high frequency settings (e.g. 80M DDR mode Flash or PSRAM), timing tuning is required.
* Certain delays will be added to the MSPI RX direction. When CPU clock switches from PLL to XTAL, should call
* this API first to enter MSPI low speed mode to remove the delays, and vice versa.
*/
void spi_timing_change_speed_mode_cache_safe(bool switch_down);
/** /**
* @brief Tune MSPI flash timing to make it work under high frequency * @brief Tune MSPI flash timing to make it work under high frequency
*/ */
@ -90,9 +99,9 @@ esp_err_t esp_flash_init_main(esp_flash_t *chip);
void spi_timing_get_flash_timing_param(spi_flash_hal_timing_config_t *out_timing_config); void spi_timing_get_flash_timing_param(spi_flash_hal_timing_config_t *out_timing_config);
/** /**
* @brief Judge if the flash in tuned * @brief Get the knowledge if the MSPI timing is tuned or not
*/ */
bool spi_timine_config_flash_is_tuned(void); bool spi_timing_is_tuned(void);
/** /**
* @brief Set Flash chip specifically required MSPI register settings here * @brief Set Flash chip specifically required MSPI register settings here

View File

@ -17,6 +17,7 @@
#include "soc/soc.h" #include "soc/soc.h"
#if CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32S3
#include "esp32s3/spi_timing_config.h" #include "esp32s3/spi_timing_config.h"
#include "esp32s3/rom/cache.h"
#endif #endif
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*(arr))) #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*(arr)))
@ -377,6 +378,7 @@ void spi_timing_psram_tuning(void)
} }
#endif //SPI_TIMING_PSRAM_NEEDS_TUNING #endif //SPI_TIMING_PSRAM_NEEDS_TUNING
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* APIs to make SPI0 (and SPI1) FLASH work for high/low freq * APIs to make SPI0 (and SPI1) FLASH work for high/low freq
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
@ -460,9 +462,33 @@ void spi_timing_enter_mspi_high_speed_mode(bool control_spi1)
#endif #endif
} }
/** void spi_timing_change_speed_mode_cache_safe(bool switch_down)
* Should be only used by SPI1 Flash driver to know the necessary timing registers {
*/ Cache_Freeze_ICache_Enable(1);
Cache_Freeze_DCache_Enable(1);
if (switch_down) {
//enter MSPI low speed mode, extra delays should be removed
spi_timing_enter_mspi_low_speed_mode(false);
} else {
//enter MSPI high speed mode, extra delays should be considered
spi_timing_enter_mspi_high_speed_mode(false);
}
Cache_Freeze_DCache_Disable();
Cache_Freeze_ICache_Disable();
}
/*------------------------------------------------------------------------------
* APIs to inform SPI1 Flash driver of necessary timing configurations
*----------------------------------------------------------------------------*/
bool spi_timing_is_tuned(void)
{
#if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING
return true;
#else
return false;
#endif
}
#if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING #if SPI_TIMING_FLASH_NEEDS_TUNING || SPI_TIMING_PSRAM_NEEDS_TUNING
void spi_timing_get_flash_timing_param(spi_flash_hal_timing_config_t *out_timing_config) void spi_timing_get_flash_timing_param(spi_flash_hal_timing_config_t *out_timing_config)
{ {

View File

@ -17,7 +17,6 @@
#include "esp32s3/rom/opi_flash.h" #include "esp32s3/rom/opi_flash.h"
#endif #endif
const static char *TAG = "SPI0";
//-----------------------------------------SPI0 PSRAM TEST-----------------------------------------------// //-----------------------------------------SPI0 PSRAM TEST-----------------------------------------------//
#if CONFIG_SPIRAM #if CONFIG_SPIRAM
@ -80,8 +79,7 @@ extern void spi_flash_enable_interrupts_caches_and_other_cpu(void);
static DRAM_ATTR uint8_t rd_buf[SPI1_FLASH_TEST_LEN]; static DRAM_ATTR uint8_t rd_buf[SPI1_FLASH_TEST_LEN];
static DRAM_ATTR uint8_t wr_buf[SPI1_FLASH_TEST_LEN]; static DRAM_ATTR uint8_t wr_buf[SPI1_FLASH_TEST_LEN];
static NOINLINE_ATTR IRAM_ATTR esp_err_t spi1_flash_test(void)
static IRAM_ATTR esp_err_t spi1_flash_test(void)
{ {
printf(DRAM_STR("----------SPI1 Flash Test----------\n")); printf(DRAM_STR("----------SPI1 Flash Test----------\n"));
@ -124,7 +122,7 @@ static IRAM_ATTR esp_err_t spi1_flash_test(void)
#define SPI0_FLASH_TEST_BUF {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, \ #define SPI0_FLASH_TEST_BUF {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, \
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F} 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}
uint8_t flash_rd_buf[SPI0_FLASH_TEST_LEN] __attribute__((section (".flash.rodata"))) = SPI0_FLASH_TEST_BUF; static const uint8_t flash_rd_buf[SPI0_FLASH_TEST_LEN] = SPI0_FLASH_TEST_BUF;
extern int _flash_rodata_start; extern int _flash_rodata_start;
extern int _rodata_reserved_end; extern int _rodata_reserved_end;
@ -133,7 +131,7 @@ static IRAM_ATTR esp_err_t spi0_flash_test(void)
{ {
printf("----------SPI0 Flash Test----------\n"); printf("----------SPI0 Flash Test----------\n");
//Check if the flash_rd_buf is in .rodata //Check if the flash_rd_buf is in .rodata
ESP_RETURN_ON_ERROR(((intptr_t)flash_rd_buf >= (intptr_t)_flash_rodata_start) && ((intptr_t)flash_rd_buf < (intptr_t)_rodata_reserved_end), TAG, "psram_rd_buf not in rodata"); assert(((intptr_t)flash_rd_buf >= (intptr_t)&_flash_rodata_start) && ((intptr_t)flash_rd_buf < (intptr_t)&_rodata_reserved_end));
uint8_t cmp_buf[SPI0_FLASH_TEST_LEN] = SPI0_FLASH_TEST_BUF; uint8_t cmp_buf[SPI0_FLASH_TEST_LEN] = SPI0_FLASH_TEST_BUF;

View File

@ -0,0 +1,9 @@
# Legacy, F4R4, Flash 120M SDR, PSRAM disable, compiler -Os and silent
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
CONFIG_SPI_FLASH_USE_LEGACY_IMPL=y
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y