diff --git a/components/idf_test/include/idf_performance.h b/components/idf_test/include/idf_performance.h index 85f824f0b..57d35ff39 100644 --- a/components/idf_test/include/idf_performance.h +++ b/components/idf_test/include/idf_performance.h @@ -90,6 +90,71 @@ #define IDF_PERFORMANCE_MIN_SDIO_THROUGHPUT_KBSEC_FRHOST_SPI 1000 #endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_WR_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_WR_4B 22200 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_RD_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_RD_4B 53400 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_WR_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_WR_2KB (701*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_RD_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_RD_2KB (7088*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_ERASE +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_LEGACY_ERASE 52200 +#endif + +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_WR_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_WR_4B 27400 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_RD_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_RD_4B 53600 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_WR_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_WR_2KB (1015*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_RD_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_RD_2KB (7797*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_ERASE +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_ERASE 44300 +#endif + +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_WR_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_WR_4B 24400 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_RD_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_RD_4B 50100 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_WR_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_WR_2KB (618*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_RD_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_RD_2KB (1601*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_ERASE +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_SPI1_ERASE 59800 +#endif + +// Some performance value based on the test against GD chip with single_core config. +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_WR_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_WR_4B 68900 +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_RD_4B +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_RD_4B (359*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_WR_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_WR_2KB (475*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_RD_2KB +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_RD_2KB (1697*1000) +#endif +#ifndef IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_ERASE +#define IDF_PERFORMANCE_MIN_FLASH_SPEED_BYTE_PER_SEC_EXT_ERASE 81300 +#endif + //time to perform the task selection plus context switch (from task) #ifndef IDF_PERFORMANCE_MAX_SCHEDULING_TIME #define IDF_PERFORMANCE_MAX_SCHEDULING_TIME 2000 diff --git a/components/soc/soc/esp32/include/soc/spi_caps.h b/components/soc/soc/esp32/include/soc/spi_caps.h index 722ae5735..fe7461a45 100644 --- a/components/soc/soc/esp32/include/soc/spi_caps.h +++ b/components/soc/soc/esp32/include/soc/spi_caps.h @@ -60,8 +60,8 @@ //#define SOC_SPI_SUPPORT_CD_SIG // Peripheral supports DIO, DOUT, QIO, or QOUT -#define SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(spi_dev) 1 +#define SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(spi_host) ({(void)spi_host; 1;}) // Peripheral doesn't support output given level during its "dummy phase" -#define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(spi_dev) 0 +#define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(spi_host) ({(void)spi_host; 0;}) diff --git a/components/soc/soc/esp32s2/include/soc/spi_caps.h b/components/soc/soc/esp32s2/include/soc/spi_caps.h index e09e6e4dc..a34e55fdd 100644 --- a/components/soc/soc/esp32s2/include/soc/spi_caps.h +++ b/components/soc/soc/esp32s2/include/soc/spi_caps.h @@ -47,20 +47,11 @@ #define SOC_SPI_SUPPORT_CONTINUOUS_TRANS 1 -#ifdef __cplusplus -extern "C" { -#endif -struct spi_dev_s; -extern volatile struct spi_dev_s GPSPI3; -struct spi_mem_dev_s; -extern volatile struct spi_mem_dev_s SPIMEM1; -#ifdef __cplusplus -} -#endif - // Peripheral supports DIO, DOUT, QIO, or QOUT -#define SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(spi_dev) (!((void*)spi_dev == (void*)&GPSPI3)) +// VSPI (SPI3) only support 1-bit mode +#define SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(host_id) ((host_id) != 2) // Peripheral supports output given level during its "dummy phase" -#define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(spi_dev) ((void*)spi_dev == (void*)&SPIMEM1) +// Only SPI1 supports this feature +#define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(host_id) ((host_id) == 0) diff --git a/components/soc/src/esp32/include/hal/spi_flash_ll.h b/components/soc/src/esp32/include/hal/spi_flash_ll.h index d47396f54..74f37db4b 100644 --- a/components/soc/src/esp32/include/hal/spi_flash_ll.h +++ b/components/soc/src/esp32/include/hal/spi_flash_ll.h @@ -43,7 +43,16 @@ extern "C" { #define SPI_FLASH_LL_CLKREG_VAL_80MHZ ((spi_flash_ll_clock_reg_t){.val=0x80000000}) ///< Clock set to 80 MHz /// Get the start address of SPI peripheral registers by the host ID -#define spi_flash_ll_get_hw(host_id) ((host_id)==SPI1_HOST? &SPI1:((host_id)==SPI2_HOST?&SPI2:((host_id)==SPI3_HOST?&SPI3:({abort();(spi_dev_t*)0;})))) +#define spi_flash_ll_get_hw(host_id) ( ((host_id)==SPI1_HOST) ? &SPI1 :(\ + ((host_id)==SPI2_HOST) ? &SPI2 :(\ + ((host_id)==SPI3_HOST) ? &SPI3 :(\ + {abort();(spi_dev_t*)0;}\ + ))) ) +#define spi_flash_ll_hw_get_id(dev) ( ((dev) == &SPI1) ? SPI1_HOST :(\ + ((dev) == &SPI2) ? SPI2_HOST :(\ + ((dev) == &SPI3) ? SPI3_HOST :(\ + -1\ + ))) ) /// Empty function to be compatible with new version chips. #define spi_flash_ll_set_dummy_out(dev, out_en, out_lev) @@ -161,12 +170,12 @@ static inline void spi_flash_ll_write_word(spi_dev_t *dev, uint32_t word) /** * Set the data to be written in the data buffer. - * + * * @param dev Beginning address of the peripheral registers. - * @param buffer Buffer holding the data + * @param buffer Buffer holding the data * @param length Length of data in bytes. */ -static inline void spi_flash_ll_set_buffer_data(spi_dev_t *dev, const void *buffer, uint32_t length) +static inline void spi_flash_ll_set_buffer_data(spi_dev_t *dev, const void *buffer, uint32_t length) { // Load data registers, word at a time int num_words = (length + 3) >> 2; @@ -324,10 +333,10 @@ static inline void spi_flash_ll_set_command8(spi_dev_t *dev, uint8_t command) /** * Get the address length that is set in register, in bits. - * + * * @param dev Beginning address of the peripheral registers. - * - */ + * + */ static inline int spi_flash_ll_get_addr_bitlen(spi_dev_t *dev) { return dev->user.usr_addr ? dev->user1.usr_addr_bitlen + 1 : 0; diff --git a/components/soc/src/esp32s2/include/hal/gpspi_flash_ll.h b/components/soc/src/esp32s2/include/hal/gpspi_flash_ll.h index 993370dc4..b53b8eabd 100644 --- a/components/soc/src/esp32s2/include/hal/gpspi_flash_ll.h +++ b/components/soc/src/esp32s2/include/hal/gpspi_flash_ll.h @@ -34,9 +34,14 @@ extern "C" { #endif -#define gpspi_flash_ll_get_hw(host_id) (((host_id)==SPI2_HOST ? &GPSPI2 \ - : ((host_id)==SPI3_HOST ? &GPSPI3 \ - : ({abort();(spi_dev_t*)0;})))) +#define gpspi_flash_ll_get_hw(host_id) ( ((host_id)==SPI2_HOST) ? &GPSPI2 : (\ + ((host_id)==SPI3_HOST) ? &GPSPI3 : (\ + {abort();(spi_dev_t*)0;}\ + )) ) +#define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : (\ + ((dev) == (void*)&GPSPI3) ? SPI3_HOST : (\ + -1 \ + )) ) typedef typeof(GPSPI2.clock) gpspi_flash_ll_clock_reg_t; @@ -286,10 +291,10 @@ static inline void gpspi_flash_ll_set_command8(spi_dev_t *dev, uint8_t command) /** * Get the address length that is set in register, in bits. - * + * * @param dev Beginning address of the peripheral registers. - * - */ + * + */ static inline int gpspi_flash_ll_get_addr_bitlen(spi_dev_t *dev) { return dev->user.usr_addr ? dev->user1.usr_addr_bitlen + 1 : 0; @@ -346,7 +351,7 @@ static inline void gpspi_flash_ll_set_dummy(spi_dev_t *dev, uint32_t dummy_n) * * @param dev Beginning address of the peripheral registers. * @param out_en whether to enable IO output for dummy phase - * @param out_level dummy output level + * @param out_level dummy output level */ static inline void gpspi_flash_ll_set_dummy_out(spi_dev_t *dev, uint32_t out_en, uint32_t out_lev) { diff --git a/components/soc/src/esp32s2/include/hal/spi_flash_ll.h b/components/soc/src/esp32s2/include/hal/spi_flash_ll.h index 383c8275a..d31bf2a50 100644 --- a/components/soc/src/esp32s2/include/hal/spi_flash_ll.h +++ b/components/soc/src/esp32s2/include/hal/spi_flash_ll.h @@ -41,6 +41,12 @@ extern "C" { #define spi_flash_ll_get_hw(host_id) (((host_id)<=SPI1_HOST ? (spi_dev_t*) spimem_flash_ll_get_hw(host_id) \ : gpspi_flash_ll_get_hw(host_id))) +#define spi_flash_ll_hw_get_id(dev) ({int dev_id = spimem_flash_ll_hw_get_id(dev); \ + if (dev_id < 0) {\ + dev_id = gpspi_flash_ll_hw_get_id(dev);\ + }\ + dev_id; \ + }) typedef union { gpspi_flash_ll_clock_reg_t gpspi; diff --git a/components/soc/src/esp32s2/include/hal/spimem_flash_ll.h b/components/soc/src/esp32s2/include/hal/spimem_flash_ll.h index dc8b39611..cb257d7f3 100644 --- a/components/soc/src/esp32s2/include/hal/spimem_flash_ll.h +++ b/components/soc/src/esp32s2/include/hal/spimem_flash_ll.h @@ -35,7 +35,8 @@ extern "C" { #endif -#define spimem_flash_ll_get_hw(host_id) (((host_id)==SPI1_HOST ? &SPIMEM1 : NULL )) +#define spimem_flash_ll_get_hw(host_id) (((host_id)==SPI1_HOST ? &SPIMEM1 : NULL )) +#define spimem_flash_ll_hw_get_id(dev) ((dev) == (void*)&SPIMEM1? SPI1_HOST: -1) typedef typeof(SPIMEM1.clock) spimem_flash_ll_clock_reg_t; @@ -324,10 +325,10 @@ static inline void spimem_flash_ll_set_command8(spi_mem_dev_t *dev, uint8_t comm /** * Get the address length that is set in register, in bits. - * + * * @param dev Beginning address of the peripheral registers. - * - */ + * + */ static inline int spimem_flash_ll_get_addr_bitlen(spi_mem_dev_t *dev) { return dev->user.usr_addr ? dev->user1.usr_addr_bitlen + 1 : 0; diff --git a/components/soc/src/hal/spi_flash_hal_common.inc b/components/soc/src/hal/spi_flash_hal_common.inc index f42a1f6ba..b84c6181d 100644 --- a/components/soc/src/hal/spi_flash_hal_common.inc +++ b/components/soc/src/hal/spi_flash_hal_common.inc @@ -51,11 +51,12 @@ esp_err_t spi_flash_hal_configure_host_io_mode( esp_flash_io_mode_t io_mode) { spi_dev_t *dev = get_spi_dev(host); + int host_id = spi_flash_ll_hw_get_id(dev); - if (!SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(dev) && io_mode > SPI_FLASH_FASTRD) { + if (!SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(host_id) && io_mode > SPI_FLASH_FASTRD) { return ESP_ERR_NOT_SUPPORTED; } - if (addr_bitlen > 24 && SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(dev)) { + if (addr_bitlen > 24 && SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUTPUT(host_id)) { /* * The extra address bits (24-addr_bitlen) are used to control the M7-M0 bits right after * the address field, to avoid the flash going into continuous read mode. diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index d72a38866..69a516a09 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -145,7 +145,7 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d goto fail; } - int dev_id; + int dev_id = -1; esp_err_t err = esp_flash_init_os_functions(chip, config->host_id, &dev_id); if (err == ESP_ERR_NOT_SUPPORTED) { ESP_LOGE(TAG, "Init os functions failed! No free CS."); @@ -156,6 +156,12 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d ret = err; goto fail; } + // When `CONFIG_SPI_FLASH_SHARE_SPI1_BUS` is not enabled on SPI1 bus, the + // `esp_flash_init_os_functions` will not be able to assign a new device ID. In this case, we + // use the `cs_id` in the config structure. + if (dev_id == -1 && config->host_id == SPI_HOST) { + dev_id = config->cs_id; + } assert(dev_id < SOC_SPI_PERIPH_CS_NUM(config->host_id) && dev_id >= 0); bool use_iomux = spicommon_bus_using_iomux(config->host_id); diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index ec196deff..a60bfe437 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -48,8 +48,8 @@ typedef struct { /** Called before any erase/write operations to check whether the region is limited by the OS */ esp_err_t (*region_protected)(void* arg, size_t start_addr, size_t size); - /** Delay for at least 'ms' milliseconds. Called in between 'start' and 'end'. */ - esp_err_t (*delay_ms)(void *arg, unsigned ms); + /** Delay for at least 'us' microseconds. Called in between 'start' and 'end'. */ + esp_err_t (*delay_us)(void *arg, unsigned us); } esp_flash_os_functions_t; /** @brief Structure to describe a SPI flash chip connected to the system. diff --git a/components/spi_flash/include/esp_flash_spi_init.h b/components/spi_flash/include/esp_flash_spi_init.h index f3ac315ee..85334d3ea 100644 --- a/components/spi_flash/include/esp_flash_spi_init.h +++ b/components/spi_flash/include/esp_flash_spi_init.h @@ -28,8 +28,12 @@ typedef struct { esp_flash_io_mode_t io_mode; ///< IO mode to read from the Flash esp_flash_speed_t speed; ///< Speed of the Flash clock int input_delay_ns; ///< Input delay of the data pins, in ns. Set to 0 if unknown. - - int cs_id; ///< @deprecated CS pin (signal) to use + /** + * CS line ID, ignored when not `host_id` is not SPI1_HOST, or + * `CONFIG_SPI_FLASH_SHARE_SPI1_BUS` is enabled. In this case, the CS line used is + * automatically assigned by the SPI bus lock. + */ + int cs_id; } esp_flash_spi_device_config_t; /** diff --git a/components/spi_flash/include/spi_flash_chip_generic.h b/components/spi_flash/include/spi_flash_chip_generic.h index 36dd3cb89..15ff8f7b1 100644 --- a/components/spi_flash/include/spi_flash_chip_generic.h +++ b/components/spi_flash/include/spi_flash_chip_generic.h @@ -193,14 +193,14 @@ esp_err_t spi_flash_chip_generic_get_write_protect(esp_flash_t *chip, bool *out_ * progress bit) to be cleared. * * @param chip Pointer to SPI flash chip to use. If NULL, esp_flash_default_chip is substituted. - * @param timeout_ms Time to wait before timeout, in ms. + * @param timeout_us Time to wait before timeout, in us. * * @return * - ESP_OK if success * - ESP_ERR_TIMEOUT if not idle before timeout * - or other error passed from the ``wait_idle`` or ``read_status`` function of host driver */ -esp_err_t spi_flash_chip_generic_wait_idle(esp_flash_t *chip, uint32_t timeout_ms); +esp_err_t spi_flash_chip_generic_wait_idle(esp_flash_t *chip, uint32_t timeout_us); /** * @brief Set the specified SPI read mode according to the data in the chip @@ -247,7 +247,7 @@ extern const spi_flash_chip_t esp_flash_chip_generic; * spi_flash_chip_generic_wait_idle() and may be useful when implementing * alternative drivers. * - * timeout_ms will be decremented if the function needs to wait until the host hardware is idle. + * timeout_us will be decremented if the function needs to wait until the host hardware is idle. * * @param chip Pointer to SPI flash chip to use. If NULL, esp_flash_default_chip is substituted. * @@ -256,7 +256,7 @@ extern const spi_flash_chip_t esp_flash_chip_generic; * - ESP_ERR_TIMEOUT if not idle before timeout * - or other error passed from the ``set_write_protect`` or ``common_command`` function of host driver */ -esp_err_t spi_flash_generic_wait_host_idle(esp_flash_t *chip, uint32_t *timeout_ms); +esp_err_t spi_flash_generic_wait_host_idle(esp_flash_t *chip, uint32_t *timeout_us); /// Function pointer type for reading status register with QE bit. typedef esp_err_t (*esp_flash_rdsr_func_t)(esp_flash_t* chip, uint32_t* out_sr); diff --git a/components/spi_flash/spi_flash_chip_generic.c b/components/spi_flash/spi_flash_chip_generic.c index 539e11665..e0c3badd5 100644 --- a/components/spi_flash/spi_flash_chip_generic.c +++ b/components/spi_flash/spi_flash_chip_generic.c @@ -20,12 +20,15 @@ static const char TAG[] = "chip_generic"; -#define SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT 4000 -#define SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT 500 //according to GD25Q127 + 100ms -#define SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT 1300 //according to GD25Q127 + 100ms +#define SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS 200 +#define SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT_MS 4000 +#define SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT_MS 500 //according to GD25Q127 + 100ms +#define SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT_MS 1300 //according to GD25Q127 + 100ms +#define SPI_FLASH_GENERIC_PAGE_PROGRAM_TIMEOUT_MS 500 + +#define HOST_DELAY_INTERVAL_US 1 +#define CHIP_WAIT_IDLE_INTERVAL_US 20 -#define DEFAULT_IDLE_TIMEOUT 200 -#define DEFAULT_PAGE_PROGRAM_TIMEOUT 500 esp_err_t spi_flash_chip_generic_probe(esp_flash_t *chip, uint32_t flash_id) { @@ -54,7 +57,7 @@ esp_err_t spi_flash_chip_generic_reset(esp_flash_t *chip) return err; } - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); return err; } @@ -80,7 +83,7 @@ esp_err_t spi_flash_chip_generic_erase_chip(esp_flash_t *chip) err = chip->chip_drv->set_chip_write_protect(chip, false); if (err == ESP_OK) { - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { chip->host->erase_chip(chip->host); @@ -91,7 +94,7 @@ esp_err_t spi_flash_chip_generic_erase_chip(esp_flash_t *chip) return err; } } - err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT_MS * 1000); } return err; } @@ -100,7 +103,7 @@ esp_err_t spi_flash_chip_generic_erase_sector(esp_flash_t *chip, uint32_t start_ { esp_err_t err = chip->chip_drv->set_chip_write_protect(chip, false); if (err == ESP_OK) { - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { chip->host->erase_sector(chip->host, start_address); @@ -111,7 +114,7 @@ esp_err_t spi_flash_chip_generic_erase_sector(esp_flash_t *chip, uint32_t start_ return err; } } - err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT_MS * 1000); } return err; } @@ -120,7 +123,7 @@ esp_err_t spi_flash_chip_generic_erase_block(esp_flash_t *chip, uint32_t start_a { esp_err_t err = chip->chip_drv->set_chip_write_protect(chip, false); if (err == ESP_OK) { - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); } if (err == ESP_OK) { chip->host->erase_block(chip->host, start_address); @@ -131,7 +134,7 @@ esp_err_t spi_flash_chip_generic_erase_block(esp_flash_t *chip, uint32_t start_a return err; } } - err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT_MS * 1000); } return err; } @@ -163,13 +166,13 @@ esp_err_t spi_flash_chip_generic_page_program(esp_flash_t *chip, const void *buf { esp_err_t err; - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); if (err == ESP_OK) { // Perform the actual Page Program command chip->host->program_page(chip->host, buffer, address, length); - err = chip->chip_drv->wait_idle(chip, DEFAULT_PAGE_PROGRAM_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_GENERIC_PAGE_PROGRAM_TIMEOUT_MS * 1000); } return err; } @@ -210,7 +213,7 @@ esp_err_t spi_flash_chip_generic_set_write_protect(esp_flash_t *chip, bool write { esp_err_t err = ESP_OK; - err = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + err = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); if (err == ESP_OK) { chip->host->set_write_protect(chip->host, write_protect); @@ -239,25 +242,31 @@ esp_err_t spi_flash_chip_generic_get_write_protect(esp_flash_t *chip, bool *out_ return err; } -esp_err_t spi_flash_generic_wait_host_idle(esp_flash_t *chip, uint32_t *timeout_ms) +esp_err_t spi_flash_generic_wait_host_idle(esp_flash_t *chip, uint32_t *timeout_us) { - while (chip->host->host_idle(chip->host) && *timeout_ms > 0) { - if (*timeout_ms > 1) { - chip->os_func->delay_ms(chip->os_func_data, 1); + while (chip->host->host_idle(chip->host) && *timeout_us > 0) { +#if HOST_DELAY_INTERVAL_US > 0 + if (*timeout_us > 1) { + int delay = MIN(HOST_DELAY_INTERVAL_US, *timeout_us); + chip->os_func->delay_us(chip->os_func_data, delay); + *timeout_us -= delay; + } else { + return ESP_ERR_TIMEOUT; } - (*timeout_ms)--; +#endif } - return (*timeout_ms > 0) ? ESP_OK : ESP_ERR_TIMEOUT; + return ESP_OK; } -esp_err_t spi_flash_chip_generic_wait_idle(esp_flash_t *chip, uint32_t timeout_ms) +esp_err_t spi_flash_chip_generic_wait_idle(esp_flash_t *chip, uint32_t timeout_us) { - timeout_ms++; // allow at least one pass before timeout, last one has no sleep cycle + timeout_us++; // allow at least one pass before timeout, last one has no sleep cycle uint8_t status = 0; - while (timeout_ms > 0) { + const int interval = CHIP_WAIT_IDLE_INTERVAL_US; + while (timeout_us > 0) { - esp_err_t err = spi_flash_generic_wait_host_idle(chip, &timeout_ms); + esp_err_t err = spi_flash_generic_wait_host_idle(chip, & timeout_us); if (err != ESP_OK) { return err; } @@ -269,13 +278,14 @@ esp_err_t spi_flash_chip_generic_wait_idle(esp_flash_t *chip, uint32_t timeout_m if ((status & SR_WIP) == 0) { break; // Write in progress is complete } - if (timeout_ms > 1) { - chip->os_func->delay_ms(chip->os_func_data, 1); + if (timeout_us > 0 && interval > 0) { + int delay = MIN(interval, timeout_us); + chip->os_func->delay_us(chip->os_func_data, delay); + timeout_us -= delay; } - timeout_ms--; } - return (timeout_ms > 0) ? ESP_OK : ESP_ERR_TIMEOUT; + return (timeout_us > 0) ? ESP_OK : ESP_ERR_TIMEOUT; } esp_err_t spi_flash_chip_generic_config_host_io_mode(esp_flash_t *chip) @@ -495,7 +505,7 @@ esp_err_t spi_flash_common_set_io_mode(esp_flash_t *chip, esp_flash_wrsr_func_t return ret; } - ret = chip->chip_drv->wait_idle(chip, DEFAULT_IDLE_TIMEOUT); + ret = chip->chip_drv->wait_idle(chip, SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS * 1000); if (ret != ESP_OK) { return ret; } diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index 07b8778aa..af1ee8aa1 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -95,9 +95,9 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg) #endif } -static IRAM_ATTR esp_err_t delay_ms(void *arg, unsigned ms) +static IRAM_ATTR esp_err_t delay_us(void *arg, unsigned us) { - ets_delay_us(1000 * ms); + ets_delay_us(us); return ESP_OK; } @@ -117,24 +117,40 @@ static DRAM_ATTR spi1_app_func_arg_t main_flash_arg = {}; static const DRAM_ATTR esp_flash_os_functions_t esp_flash_spi1_default_os_functions = { .start = spi1_start, .end = spi1_end, - .delay_ms = delay_ms, + .delay_us = delay_us, .region_protected = main_flash_region_protected, }; static const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = { .start = spi_start, .end = spi_end, - .delay_ms = delay_ms, + .delay_us = delay_us, }; -esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int* out_dev_id) +static spi_bus_lock_dev_handle_t register_dev(int host_id) { spi_bus_lock_handle_t lock = spi_bus_lock_get_by_id(host_id); spi_bus_lock_dev_handle_t dev_handle; spi_bus_lock_dev_config_t config = {.flags = SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED}; esp_err_t err = spi_bus_lock_register_dev(lock, &config, &dev_handle); if (err != ESP_OK) { - return err; + return NULL; + } + return dev_handle; +} + +esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int* out_dev_id) +{ + spi_bus_lock_dev_handle_t dev_handle = NULL; + + // Skip initializing the bus lock when the bus is SPI1 and the bus is not shared with SPI Master + // driver, leaving dev_handle = NULL + bool skip_register_dev = (host_id == SPI_HOST); +#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS + skip_register_dev = false; +#endif + if (!skip_register_dev) { + dev_handle = register_dev(host_id); } if (host_id == SPI1_HOST) { @@ -166,7 +182,10 @@ esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int* out_d return ESP_ERR_INVALID_ARG; } - *out_dev_id = spi_bus_lock_get_dev_id(dev_handle); + // Bus lock not initialized, the device ID should be directly given by application. + if (dev_handle) { + *out_dev_id = spi_bus_lock_get_dev_id(dev_handle); + } return ESP_OK; } @@ -174,7 +193,11 @@ esp_err_t esp_flash_init_os_functions(esp_flash_t *chip, int host_id, int* out_d esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip) { if (chip->os_func_data) { - spi_bus_lock_unregister_dev(((app_func_arg_t*)chip->os_func_data)->dev_lock); + spi_bus_lock_dev_handle_t dev_lock = ((app_func_arg_t*)chip->os_func_data)->dev_lock; + // SPI bus lock is possible not used on SPI1 bus + if (dev_lock) { + spi_bus_lock_unregister_dev(dev_lock); + } free(chip->os_func_data); } chip->os_func = NULL; diff --git a/components/spi_flash/spi_flash_os_func_noos.c b/components/spi_flash/spi_flash_os_func_noos.c index b42a64edd..851a2d6ab 100644 --- a/components/spi_flash/spi_flash_os_func_noos.c +++ b/components/spi_flash/spi_flash_os_func_noos.c @@ -65,16 +65,16 @@ static IRAM_ATTR esp_err_t end(void *arg) return ESP_OK; } -static IRAM_ATTR esp_err_t delay_ms(void *arg, unsigned ms) +static IRAM_ATTR esp_err_t delay_us(void *arg, unsigned us) { - ets_delay_us(1000 * ms); + ets_delay_us(us); return ESP_OK; } const DRAM_ATTR esp_flash_os_functions_t esp_flash_noos_functions = { .start = start, .end = end, - .delay_ms = delay_ms, + .delay_us = delay_us, .region_protected = NULL, }; diff --git a/components/spi_flash/test/test_esp_flash.c b/components/spi_flash/test/test_esp_flash.c index f431362a8..0e3eebe25 100644 --- a/components/spi_flash/test/test_esp_flash.c +++ b/components/spi_flash/test/test_esp_flash.c @@ -19,6 +19,7 @@ #include "sdkconfig.h" #include "hal/spi_flash_hal.h" +#include "ccomp_timer.h" #define FUNC_SPI 1 @@ -165,6 +166,26 @@ flashtest_config_t config_list[] = { }; #endif +static void get_chip_host(esp_flash_t* chip, spi_host_device_t* out_host_id, int* out_cs_id) +{ + spi_host_device_t host_id; + int cs_id; + if (chip == NULL) { + host_id = SPI_HOST; + cs_id = 0; + } else { + spi_flash_memspi_data_t* driver_data = (spi_flash_memspi_data_t*)chip->host->driver_data; + host_id = spi_flash_ll_hw_get_id(driver_data->spi); + cs_id = driver_data->cs_num; + } + if (out_host_id) { + *out_host_id = host_id; + } + if (out_cs_id) { + *out_cs_id = cs_id; + } +} + static void setup_bus(spi_host_device_t host_id) { if (host_id == SPI_HOST) { @@ -615,8 +636,11 @@ void test_permutations(flashtest_config_t* config) cfg->speed = speed; setup_new_chip(cfg, &chip); + spi_host_device_t host_id; + get_chip_host(chip, &host_id, NULL); + if (io_mode > SPI_FLASH_FASTRD - && !SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(((spi_flash_memspi_data_t *)chip->host->driver_data)->spi)) { + && !SOC_SPI_PERIPH_SUPPORT_MULTILINE_MODE(host_id)) { continue; } @@ -719,6 +743,157 @@ static void test_write_large_buffer(esp_flash_t *chip, const uint8_t *source, si read_and_check(chip, part, source, length); } +typedef struct { + uint32_t us_start; + size_t len; + const char* name; +} time_meas_ctx_t; + +static void time_measure_start(time_meas_ctx_t* ctx) +{ + ctx->us_start = esp_timer_get_time(); + ccomp_timer_start(); +} + +static uint32_t time_measure_end(time_meas_ctx_t* ctx) +{ + uint32_t c_time_us = ccomp_timer_stop(); + uint32_t time_us = esp_timer_get_time() - ctx->us_start; + + ESP_LOGI(TAG, "%s: compensated: %.2lf kB/s, typical: %.2lf kB/s", ctx->name, ctx->len / (c_time_us / 1000.), ctx->len / (time_us/1000.)); + return ctx->len * 1000 / (c_time_us / 1000); +} + +#define TEST_TIMES 20 +#define TEST_SECTORS 4 + +static uint32_t measure_erase(const esp_partition_t* part) +{ + const int total_len = SPI_FLASH_SEC_SIZE * TEST_SECTORS; + time_meas_ctx_t time_ctx = {.name = "erase", .len = total_len}; + + time_measure_start(&time_ctx); + esp_err_t err = esp_flash_erase_region(part->flash_chip, part->address, total_len); + TEST_ESP_OK(err); + return time_measure_end(&time_ctx); +} + +// should called after measure_erase +static uint32_t measure_write(const char* name, const esp_partition_t* part, const uint8_t* data_to_write, int seg_len) +{ + const int total_len = SPI_FLASH_SEC_SIZE; + time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES}; + + time_measure_start(&time_ctx); + for (int i = 0; i < TEST_TIMES; i ++) { + // Erase one time, but write 100 times the same data + size_t len = total_len; + int offset = 0; + + while (len) { + int len_write = MIN(seg_len, len); + esp_err_t err = esp_flash_write(part->flash_chip, data_to_write + offset, part->address + offset, len_write); + TEST_ESP_OK(err); + + offset += len_write; + len -= len_write; + } + } + return time_measure_end(&time_ctx); +} + +static uint32_t measure_read(const char* name, const esp_partition_t* part, uint8_t* data_read, int seg_len) +{ + const int total_len = SPI_FLASH_SEC_SIZE; + time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES}; + + time_measure_start(&time_ctx); + for (int i = 0; i < TEST_TIMES; i ++) { + size_t len = total_len; + int offset = 0; + + while (len) { + int len_read = MIN(seg_len, len); + esp_err_t err = esp_flash_read(part->flash_chip, data_read + offset, part->address + offset, len_read); + TEST_ESP_OK(err); + + offset += len_read; + len -= len_read; + } + } + return time_measure_end(&time_ctx); +} + +#define MEAS_WRITE(n) (measure_write("write in "#n"-byte chunks", &test_part, data_to_write, n)) +#define MEAS_READ(n) (measure_read("read in "#n"-byte chunks", &test_part, data_read, n)) + +static void test_flash_read_write_performance(esp_flash_t* chip) +{ + const esp_partition_t *part = get_test_data_partition(); + // Copy to new partition variable and replace the chip member + // Actually there's no "partition" in the external flash on runners. We just don't bother creating a new partition variable. + esp_partition_t test_part; + memcpy(&test_part, part, sizeof(esp_partition_t)); + test_part.flash_chip = chip; + + const int total_len = SPI_FLASH_SEC_SIZE; + uint8_t *data_to_write = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + uint8_t *data_read = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + + srand(777); + for (int i = 0; i < total_len; i++) { + data_to_write[i] = rand(); + } + + uint32_t erase_1 = measure_erase(&test_part); + uint32_t speed_WR_4B = MEAS_WRITE(4); + uint32_t speed_RD_4B = MEAS_READ(4); + uint32_t erase_2 = measure_erase(&test_part); + uint32_t speed_WR_2KB = MEAS_WRITE(2048); + uint32_t speed_RD_2KB = MEAS_READ(2048); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(data_to_write, data_read, total_len); + +#if !CONFIG_SPIRAM_SUPPORT && !CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE +# define CHECK_DATA(bus, suffix) TEST_PERFORMANCE_GREATER_THAN(FLASH_SPEED_BYTE_PER_SEC_##bus##suffix, "%d", speed_##suffix) +# define CHECK_ERASE(bus, var) TEST_PERFORMANCE_GREATER_THAN(FLASH_SPEED_BYTE_PER_SEC_##bus##ERASE, "%d", var) +#else +# define CHECK_DATA(bus, suffix) ((void)speed_##suffix) +# define CHECK_ERASE(bus, var) ((void)var) +#endif + +// Erase time may vary a lot, can increase threshold if this fails with a reasonable speed +#define CHECK_PERFORMANCE(bus) do {\ + CHECK_DATA(bus, WR_4B); \ + CHECK_DATA(bus, RD_4B); \ + CHECK_DATA(bus, WR_2KB); \ + CHECK_DATA(bus, RD_2KB); \ + CHECK_ERASE(bus, erase_1); \ + CHECK_ERASE(bus, erase_2); \ + } while (0) + + spi_host_device_t host_id; + int cs_id; + + get_chip_host(chip, &host_id, &cs_id); + if (host_id != SPI_HOST) { + // Chips on other SPI buses + CHECK_PERFORMANCE(EXT_); + } else if (cs_id == 0) { + // Main flash + CHECK_PERFORMANCE(); + } else { + // Other cs pins on SPI1 + CHECK_PERFORMANCE(SPI1_); + } + free(data_to_write); + free(data_read); +} + +FLASH_TEST_CASE("Test esp_flash read/write performance", test_flash_read_write_performance); +FLASH_TEST_CASE_3("Test esp_flash read/write performance", test_flash_read_write_performance); + + #ifdef CONFIG_SPIRAM_USE_MALLOC /* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what @@ -785,5 +960,4 @@ static void test_flash_read_large_psram_buffer_low_internal_mem(esp_flash_t *chi FLASH_TEST_CASE("esp_flash_read large PSRAM buffer low memory", test_flash_read_large_psram_buffer_low_internal_mem); - #endif diff --git a/components/spi_flash/test/test_spi_flash.c b/components/spi_flash/test/test_spi_flash.c index 7b85839e5..c1275f91c 100644 --- a/components/spi_flash/test/test_spi_flash.c +++ b/components/spi_flash/test/test_spi_flash.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -9,6 +10,8 @@ #include "driver/timer.h" #include "esp_intr_alloc.h" #include "test_utils.h" +#include "ccomp_timer.h" +#include "esp_log.h" struct flash_test_ctx { uint32_t offset; @@ -16,6 +19,8 @@ struct flash_test_ctx { SemaphoreHandle_t done; }; +static const char TAG[] = "test_spi_flash"; + /* Base offset in flash for tests. */ static size_t start; @@ -186,6 +191,135 @@ TEST_CASE("spi flash functions can run along with IRAM interrupts", "[spi_flash] free(read_arg.buf); } +typedef struct { + uint32_t us_start; + size_t len; + const char* name; +} time_meas_ctx_t; + +static void time_measure_start(time_meas_ctx_t* ctx) +{ + ctx->us_start = esp_timer_get_time(); + ccomp_timer_start(); +} + +static uint32_t time_measure_end(time_meas_ctx_t* ctx) +{ + uint32_t c_time_us = ccomp_timer_stop(); + uint32_t time_us = esp_timer_get_time() - ctx->us_start; + + ESP_LOGI(TAG, "%s: compensated: %.2lf kB/s, typical: %.2lf kB/s", ctx->name, ctx->len / (c_time_us/1000.), ctx->len / (time_us/1000.)); + return ctx->len * 1000 / (c_time_us / 1000); +} + +#define TEST_TIMES 20 +#define TEST_SECTORS 4 + +static uint32_t measure_erase(const esp_partition_t* part) +{ + const int total_len = SPI_FLASH_SEC_SIZE * TEST_SECTORS; + time_meas_ctx_t time_ctx = {.name = "erase", .len = total_len}; + + time_measure_start(&time_ctx); + esp_err_t err = spi_flash_erase_range(part->address, total_len); + TEST_ESP_OK(err); + return time_measure_end(&time_ctx); +} + +// should called after measure_erase +static uint32_t measure_write(const char* name, const esp_partition_t* part, const uint8_t* data_to_write, int seg_len) +{ + const int total_len = SPI_FLASH_SEC_SIZE; + time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES}; + + time_measure_start(&time_ctx); + for (int i = 0; i < TEST_TIMES; i ++) { + // Erase one time, but write 100 times the same data + size_t len = total_len; + int offset = 0; + + while (len) { + int len_write = MIN(seg_len, len); + esp_err_t err = spi_flash_write(part->address + offset, data_to_write + offset, len_write); + TEST_ESP_OK(err); + + offset += len_write; + len -= len_write; + } + } + return time_measure_end(&time_ctx); +} + +static uint32_t measure_read(const char* name, const esp_partition_t* part, uint8_t* data_read, int seg_len) +{ + const int total_len = SPI_FLASH_SEC_SIZE; + time_meas_ctx_t time_ctx = {.name = name, .len = total_len * TEST_TIMES}; + + time_measure_start(&time_ctx); + for (int i = 0; i < TEST_TIMES; i ++) { + size_t len = total_len; + int offset = 0; + + while (len) { + int len_read = MIN(seg_len, len); + esp_err_t err = spi_flash_read(part->address + offset, data_read + offset, len_read); + TEST_ESP_OK(err); + + offset += len_read; + len -= len_read; + } + } + return time_measure_end(&time_ctx); +} + +#define MEAS_WRITE(n) (measure_write("write in "#n"-byte chunks", part, data_to_write, n)) +#define MEAS_READ(n) (measure_read("read in "#n"-byte chunks", part, data_read, n)) + +TEST_CASE("Test spi_flash read/write performance", "[spi_flash]") +{ + const esp_partition_t *part = get_test_data_partition(); + + const int total_len = SPI_FLASH_SEC_SIZE; + uint8_t *data_to_write = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + uint8_t *data_read = heap_caps_malloc(total_len, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + + srand(777); + for (int i = 0; i < total_len; i++) { + data_to_write[i] = rand(); + } + + uint32_t erase_1 = measure_erase(part); + uint32_t speed_WR_4B = MEAS_WRITE(4); + uint32_t speed_RD_4B = MEAS_READ(4); + uint32_t erase_2 = measure_erase(part); + uint32_t speed_WR_2KB = MEAS_WRITE(2048); + uint32_t speed_RD_2KB = MEAS_READ(2048); + + TEST_ASSERT_EQUAL_HEX8_ARRAY(data_to_write, data_read, total_len); + +// Data checks are disabled when PSRAM is used or in Freertos compliance check test +#if !CONFIG_SPIRAM_SUPPORT && !CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE +# define CHECK_DATA(suffix) TEST_PERFORMANCE_GREATER_THAN(FLASH_SPEED_BYTE_PER_SEC_LEGACY_##suffix, "%d", speed_##suffix) +# define CHECK_ERASE(var) TEST_PERFORMANCE_GREATER_THAN(FLASH_SPEED_BYTE_PER_SEC_LEGACY_ERASE, "%d", var) +#else +# define CHECK_DATA(suffix) ((void)speed_##suffix) +# define CHECK_ERASE(var) ((void)var) +#endif + + CHECK_DATA(WR_4B); + CHECK_DATA(RD_4B); + CHECK_DATA(WR_2KB); + CHECK_DATA(RD_2KB); + + // Erase time may vary a lot, can increase threshold if this fails with a reasonable speed + CHECK_ERASE(erase_1); + CHECK_ERASE(erase_2); + + free(data_to_write); + free(data_read); +} + + #if portNUM_PROCESSORS > 1 TEST_CASE("spi_flash deadlock with high priority busy-waiting task", "[spi_flash][esp_flash]")