feat(spi_flash): Support configurable tSUS in flash suspend

This commit is contained in:
Cao Sen Miao 2023-10-19 11:37:20 +08:00
parent 8768c9231c
commit dcff5220a7
10 changed files with 93 additions and 17 deletions

View File

@ -52,8 +52,9 @@ typedef struct {
uint32_t slicer_flags; /// Slicer flags for configuring how to slice data correctly while reading or writing.
#define SPI_FLASH_HOST_CONTEXT_SLICER_FLAG_DTR BIT(0) ///< Slice data according to DTR mode, the address and length must be even (A0=0).
int freq_mhz; /// Flash clock frequency.
uint8_t tsus_val; ///< Tsus value of suspend (us)
} spi_flash_hal_context_t;
ESP_STATIC_ASSERT(sizeof(spi_flash_hal_context_t) == 44, "size of spi_flash_hal_context_t incorrect. Please check data compatibility with the ROM");
ESP_STATIC_ASSERT(sizeof(spi_flash_hal_context_t) == 48, "size of spi_flash_hal_context_t incorrect. Please check data compatibility with the ROM");
/// This struct provide MSPI Flash necessary timing related config, should be consistent with that in union in `spi_flash_hal_config_t`.
typedef struct {
@ -85,6 +86,7 @@ typedef struct {
esp_flash_io_mode_t default_io_mode; ///< Default flash io mode.
int freq_mhz; ///< SPI flash clock speed (MHZ).
int clock_src_freq; ///< SPI flash clock source (MHZ).
uint8_t tsus_val; ///< Tsus value of suspend (us)
} spi_flash_hal_config_t;
/**

View File

@ -125,6 +125,7 @@ esp_err_t spi_flash_hal_init(spi_flash_hal_context_t *data_out, const spi_flash_
if (cfg->auto_sus_en) {
data_out->flags |= SPI_FLASH_HOST_CONTEXT_FLAG_AUTO_SUSPEND;
data_out->flags |= SPI_FLASH_HOST_CONTEXT_FLAG_AUTO_RESUME;
data_out->tsus_val = cfg->tsus_val;
}
#if SOC_SPI_MEM_SUPPORT_OPI_MODE

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -12,7 +12,6 @@ void spi_flash_hal_setup_auto_suspend_mode(spi_flash_host_inst_t *host);
void spi_flash_hal_disable_auto_resume_mode(spi_flash_host_inst_t *host);
void spi_flash_hal_disable_auto_suspend_mode(spi_flash_host_inst_t *host);
void spi_flash_hal_setup_auto_resume_mode(spi_flash_host_inst_t *host);
#define SPI_FLASH_TSUS_SAFE_VAL_US (30)
#define SPI_FLASH_TSHSL2_SAFE_VAL_NS (30)
#endif //SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
@ -148,8 +147,8 @@ void spi_flash_hal_setup_auto_suspend_mode(spi_flash_host_inst_t *host)
spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host;
spimem_flash_ll_auto_wait_idle_init(dev, true);
spimem_flash_ll_auto_suspend_init(dev, true);
// tsus = ceil(SPI_FLASH_TSUS_SAFE_VAL_US * ctx->freq_mhz / spimem_flash_ll_get_tsus_unit_in_cycles);
uint32_t tsus = (SPI_FLASH_TSUS_SAFE_VAL_US * ctx->freq_mhz / spimem_flash_ll_get_tsus_unit_in_cycles(dev)) + ((SPI_FLASH_TSUS_SAFE_VAL_US * ctx->freq_mhz) % spimem_flash_ll_get_tsus_unit_in_cycles(dev) != 0);
// tsus = ceil(ctx->tsus_val * ctx->freq_mhz / spimem_flash_ll_get_tsus_unit_in_cycles);
uint32_t tsus = (ctx->tsus_val * ctx->freq_mhz / spimem_flash_ll_get_tsus_unit_in_cycles(dev)) + ((ctx->tsus_val * ctx->freq_mhz) % spimem_flash_ll_get_tsus_unit_in_cycles(dev) != 0);
spimem_flash_ll_set_sus_delay(dev, tsus);
// tshsl2 = ceil(SPI_FLASH_TSHSL2_SAFE_VAL_NS * spimem_flash_ll_get_source_freq_mhz() * 0.001);
uint32_t tshsl2 = (SPI_FLASH_TSHSL2_SAFE_VAL_NS * spimem_flash_ll_get_source_freq_mhz() / 1000) + ((SPI_FLASH_TSHSL2_SAFE_VAL_NS * spimem_flash_ll_get_source_freq_mhz()) % 1000 != 0);

View File

@ -108,6 +108,14 @@ menu "Main Flash configuration"
Also refer to `Concurrency Constraints for Flash on SPI1` > `Flash Auto Suspend Feature`
before enabling this option.
config SPI_FLASH_SUSPEND_TSUS_VAL_US
int "SPI flash tSUS value (refer to chapter AC CHARACTERISTICS)"
default 50
range 20 100
help
This config is used for setting Tsus parameter. Tsus means CS# high to next command after
suspend. You can refer to the chapter of AC CHARACTERISTICS of flash datasheet.
endmenu
endmenu

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -117,6 +117,7 @@ esp_flash_t *esp_flash_default_chip = NULL;
.auto_sus_en = true,\
.cs_setup = 1,\
}
#define TSUS_VAL_SUSPEND CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US
#endif //!CONFIG_SPI_FLASH_AUTO_SUSPEND
#endif // Other target
@ -356,6 +357,15 @@ esp_err_t esp_flash_init_default_chip(void)
cfg.clock_src_freq = spi_flash_ll_get_source_clock_freq_mhz(cfg.host_id);
#if CONFIG_SPI_FLASH_AUTO_SUSPEND
if (TSUS_VAL_SUSPEND > 400 || TSUS_VAL_SUSPEND < 20) {
// Assume that the tsus value cannot larger than 400 (because the performance might be really bad)
// And value cannot smaller than 20 (never see that small tsus value, might be wrong)
return ESP_ERR_INVALID_ARG;
}
cfg.tsus_val = TSUS_VAL_SUSPEND;
#endif // CONFIG_SPI_FLASH_AUTO_SUSPEND
//the host is already initialized, only do init for the data and load it to the host
esp_err_t err = memspi_host_init_pointers(&esp_flash_default_host, &cfg);
if (err != ESP_OK) {

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -596,10 +596,15 @@ spi_flash_caps_t spi_flash_chip_generic_get_caps(esp_flash_t *chip)
// 32M-bits address support
// flash suspend support
// Only `XMC` support suspend for now.
// XMC support suspend
if (chip->chip_id >> 16 == 0x20) {
caps_flags |= SPI_FLASH_CHIP_CAP_SUSPEND;
}
// FM support suspend
if (chip->chip_id >> 16 == 0xa1) {
caps_flags |= SPI_FLASH_CHIP_CAP_SUSPEND;
}
// flash read unique id.
caps_flags |= SPI_FLASH_CHIP_CAP_UNIQUE_ID;
return caps_flags;
@ -790,8 +795,8 @@ esp_err_t spi_flash_common_set_io_mode(esp_flash_t *chip, esp_flash_wrsr_func_t
esp_err_t spi_flash_chip_generic_suspend_cmd_conf(esp_flash_t *chip)
{
// Only XMC support auto-suspend
if (chip->chip_id >> 16 != 0x20) {
// chips which support auto-suspend
if (chip->chip_id >> 16 != 0x20 && chip->chip_id >> 16 != 0xa1) {
ESP_EARLY_LOGE(TAG, "The flash you use doesn't support auto suspend, only \'XMC\' is supported");
return ESP_ERR_NOT_SUPPORTED;
}

View File

@ -121,14 +121,10 @@ TEST_CASE("flash suspend test", "[spi_flash][suspend]")
};
TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer));
/**
set alarm_count is 2500.
So, in this case, ISR duration time is around 2240 and ISR interval time is around 2470
so ISR_interval - ISR_duration is around 230us. Just a little bit larger than `tsus` value.
*/
// For pre-test, set alarm_count as a larger value.
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = 2500,
.alarm_count = 10000,
.flags.auto_reload_on_alarm = true,
};
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
@ -151,11 +147,61 @@ TEST_CASE("flash suspend test", "[spi_flash][suspend]")
RECORD_TIME_END(erase_time);
TEST_ESP_OK(gptimer_stop(gptimer));
TEST_ESP_OK(gptimer_disable(gptimer));
uint32_t isr_duration_time = s_isr_time / times;
ESP_LOGI(TAG, "--------------------Pre Test...--------------------");
ESP_LOGI(TAG, "Flash Driver Erase Operation finishes, duration:\n\t\t%0.2f us", GET_US_BY_CCOUNT(erase_time));
ESP_LOGI(TAG, "During Erase, ISR callback function(in flash) response time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_flash_func_time / times));
ESP_LOGI(TAG, "During Erase, ISR duration time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(isr_duration_time));
ESP_LOGI(TAG, "During Erase, ISR interval time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_isr_interval_time / (times - 1)));
// init all time parameters
s_flash_func_t1 = 0;
s_flash_func_t2 = 0;
s_flash_func_time = 0;
s_isr_t1 = 0;
s_isr_t2 = 0;
s_isr_time = 0;
s_isr_interval_t1 = 0;
s_isr_interval_t2 = 0;
s_isr_interval_time = 0;
times = 0;
// run test again for validate the tSUS value
/**
set alarm_count is duration_time + Tsus value.
So, in this case, ISR duration time is around 2217 and ISR interval time is around 2259(tested value, can change each time)
so ISR_interval - ISR_duration is around 42us. Just a little bit larger than `tsus` value.
*/
alarm_config.alarm_count = GET_US_BY_CCOUNT(isr_duration_time) + CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US,
TEST_ESP_OK(gptimer_set_alarm_action(gptimer, &alarm_config));
TEST_ESP_OK(gptimer_register_event_callbacks(gptimer, &cbs, &is_flash));
TEST_ESP_OK(gptimer_enable(gptimer));
TEST_ESP_OK(gptimer_start(gptimer));
erase_time = 0;
RECORD_TIME_START();
TEST_ESP_OK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
TEST_ESP_OK(esp_flash_write(part->flash_chip, large_const_buffer, part->address, sizeof(large_const_buffer)));
RECORD_TIME_END(erase_time);
TEST_ESP_OK(gptimer_stop(gptimer));
isr_duration_time = GET_US_BY_CCOUNT(s_isr_time / times);
uint32_t isr_interval_time = GET_US_BY_CCOUNT(s_isr_interval_time / (times - 1));
ESP_LOGI(TAG, "--------------------Formal Test...--------------------");
ESP_LOGI(TAG, "Flash Driver Erase Operation finishes, duration:\n\t\t%0.2f us", GET_US_BY_CCOUNT(erase_time));
ESP_LOGI(TAG, "During Erase, ISR callback function(in flash) response time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_flash_func_time / times));
ESP_LOGI(TAG, "During Erase, ISR duration time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_isr_time / times));
ESP_LOGI(TAG, "During Erase, ISR interval time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_isr_interval_time / (times - 1)));
ESP_LOGI(TAG, "The tsus value which passes the test is:\n\t\t%ld us", isr_interval_time - isr_duration_time);
// 15 stands for threshold. We allow the interval time minus duration time is little bit larger than TSUS value
TEST_ASSERT_LESS_THAN(CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US + 15, isr_interval_time - isr_duration_time);
ESP_LOGI(TAG, "Reasonable value!");
ESP_LOGI(TAG, "Finish");
TEST_ESP_OK(gptimer_disable(gptimer));

View File

@ -47,4 +47,6 @@ Regarding the flash suspend feature usage and corresponding response time delay,
2. ISR interval: ISR cannot be triggered very often. The most important time is the **ISR interval minus ISR time** (from point b to point c in the diagram). During this time, SPI1 will send resume command to restart the operation. However, it needs a time ``tsus`` for preparation, and the typical value of ``tsus`` is about **40 us**. If SPI1 cannot resume the operation but another suspend command comes, it will cause CPU starve and ``TWDT`` may be triggered.
The ``tsus`` time mentioned in point 2 can be found by looking through the flash datasheets, usually in the AC CHARACTERISTICS section. Users needs to make sure that the ``tsus`` value obtained from the datasheets is not greater than the :ref:`CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US` value in Kconfig.
Furthermore, the flash suspend might be delayed. If both the CPU and the cache access the flash via SPI0 frequently and SPI1 sends the suspend command frequently as well, the efficiency of MSPI data transfer will be influenced. So, we have a **lock** inside to prevent this. When SPI1 sends the suspend command, SPI0 will take over memory SPI bus and take the lock. After SPI0 finishes sending data, it will retain control of the memory SPI bus until the lock delay period time finishes. During this lock delay period, if there is any other SPI0 transaction, then the SPI0 transaction will be proceeded and a new lock delay period will start. Otherwise, SPI0 will release the memory bus and start SPI0/1 arbitration.

View File

@ -40,6 +40,7 @@ The support for ESP32-P4 may be added in the future.
1. XM25QxxC series.
2. GD25QxxE series.
3. FM25Q32
.. attention::

View File

@ -47,4 +47,6 @@ flash 自动暂停功能
2. ISR 间隔时间 (ISR interval):由于不能频繁触发 ISR需格外注意 **ISR 间隔时间减去 ISR 时间后的剩余时间** (图中 b 点至 c 点的距离。在此期间SPI1 会发送恢复命令重新启动操作,所需准备时间 ``tsus`` 的典型值约为 **40 us**。如果在 SPI1 完成恢复操作前接收到了新的暂停命令,可能导致 CPU 饥饿,触发 ``TWDT``
对于第 2 点中所提到的 ``tsus`` 时间可以通过翻阅 flash datasheets 查找,通常在 AC CHARACTERISTICS 章节中。用户需要保证从 datasheets 获得的 ``tsus`` 值不大于 :ref:`CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US` 值。
此外flash 暂停可能延迟。CPU 和缓存通过 SPI0 频繁访问 flash SPI1 频繁发送暂停命令时,会导致 MSPI 数据传输效率下降。可以通过在内部使用 **** 来避免此种情况。当 SPI1 发送暂停命令时SPI0 将接管内存 SPI 总线并启用锁。SPI0 完成数据传输后,在锁延迟时间结束前,都将保有对内存 SPI 总线的控制权。在此锁延迟期间,如果接收到其他 SPI0 事务,则该 SPI0 事务将正常进行,并开启新一轮锁延迟周期。如无其他 SPI0 事务,则 SPI0 释放内存总线并启动 SPI0/1 仲裁。