From dcff5220a7a402f876f3f2f908748d90910ce63f Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Thu, 19 Oct 2023 11:37:20 +0800 Subject: [PATCH] feat(spi_flash): Support configurable tSUS in flash suspend --- components/hal/include/hal/spi_flash_hal.h | 4 +- components/hal/spi_flash_hal.c | 1 + components/hal/spi_flash_hal_iram.c | 7 +-- components/spi_flash/Kconfig | 8 +++ components/spi_flash/esp_flash_spi_init.c | 12 +++- components/spi_flash/spi_flash_chip_generic.c | 13 ++-- .../flash_suspend/main/test_flash_suspend.c | 60 ++++++++++++++++--- .../peripherals/spi_flash/auto_suspend.inc | 2 + .../spi_flash/spi_flash_optional_feature.rst | 1 + .../peripherals/spi_flash/auto_suspend.inc | 2 + 10 files changed, 93 insertions(+), 17 deletions(-) diff --git a/components/hal/include/hal/spi_flash_hal.h b/components/hal/include/hal/spi_flash_hal.h index 99192071f3..593bb5b1a1 100644 --- a/components/hal/include/hal/spi_flash_hal.h +++ b/components/hal/include/hal/spi_flash_hal.h @@ -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; /** diff --git a/components/hal/spi_flash_hal.c b/components/hal/spi_flash_hal.c index 3c7e225802..75414367ef 100644 --- a/components/hal/spi_flash_hal.c +++ b/components/hal/spi_flash_hal.c @@ -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 diff --git a/components/hal/spi_flash_hal_iram.c b/components/hal/spi_flash_hal_iram.c index 3adb6404bd..edcbe9e7ad 100644 --- a/components/hal/spi_flash_hal_iram.c +++ b/components/hal/spi_flash_hal_iram.c @@ -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); diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index a2cbc91215..5c4cb52d62 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -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 diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index f0404e8704..65407c70c3 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -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) { diff --git a/components/spi_flash/spi_flash_chip_generic.c b/components/spi_flash/spi_flash_chip_generic.c index d63bb73afc..045615cb76 100644 --- a/components/spi_flash/spi_flash_chip_generic.c +++ b/components/spi_flash/spi_flash_chip_generic.c @@ -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; } diff --git a/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c b/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c index cde14fe00a..2a9e4be993 100644 --- a/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c +++ b/components/spi_flash/test_apps/flash_suspend/main/test_flash_suspend.c @@ -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)); diff --git a/docs/en/api-reference/peripherals/spi_flash/auto_suspend.inc b/docs/en/api-reference/peripherals/spi_flash/auto_suspend.inc index 070e4c8eba..beb78d5c53 100644 --- a/docs/en/api-reference/peripherals/spi_flash/auto_suspend.inc +++ b/docs/en/api-reference/peripherals/spi_flash/auto_suspend.inc @@ -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. diff --git a/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst b/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst index f4414266d9..ebcebaf8ef 100644 --- a/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst +++ b/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst @@ -40,6 +40,7 @@ The support for ESP32-P4 may be added in the future. 1. XM25QxxC series. 2. GD25QxxE series. + 3. FM25Q32 .. attention:: diff --git a/docs/zh_CN/api-reference/peripherals/spi_flash/auto_suspend.inc b/docs/zh_CN/api-reference/peripherals/spi_flash/auto_suspend.inc index 17668c2307..1f465725d2 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_flash/auto_suspend.inc +++ b/docs/zh_CN/api-reference/peripherals/spi_flash/auto_suspend.inc @@ -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 仲裁。