Merge branch 'bugfix/esp_flash_write_performance_regression' into 'master'
esp_flash: fix the write performance regression See merge request espressif/esp-idf!8658
This commit is contained in:
commit
fd3e28ef0d
17 changed files with 516 additions and 87 deletions
|
@ -90,6 +90,71 @@
|
||||||
#define IDF_PERFORMANCE_MIN_SDIO_THROUGHPUT_KBSEC_FRHOST_SPI 1000
|
#define IDF_PERFORMANCE_MIN_SDIO_THROUGHPUT_KBSEC_FRHOST_SPI 1000
|
||||||
#endif
|
#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)
|
//time to perform the task selection plus context switch (from task)
|
||||||
#ifndef IDF_PERFORMANCE_MAX_SCHEDULING_TIME
|
#ifndef IDF_PERFORMANCE_MAX_SCHEDULING_TIME
|
||||||
#define IDF_PERFORMANCE_MAX_SCHEDULING_TIME 2000
|
#define IDF_PERFORMANCE_MAX_SCHEDULING_TIME 2000
|
||||||
|
|
|
@ -60,8 +60,8 @@
|
||||||
//#define SOC_SPI_SUPPORT_CD_SIG
|
//#define SOC_SPI_SUPPORT_CD_SIG
|
||||||
|
|
||||||
// Peripheral supports DIO, DOUT, QIO, or QOUT
|
// 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"
|
// 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;})
|
||||||
|
|
||||||
|
|
|
@ -47,20 +47,11 @@
|
||||||
#define SOC_SPI_SUPPORT_CONTINUOUS_TRANS 1
|
#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
|
// 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"
|
// 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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
#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
|
/// 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.
|
/// Empty function to be compatible with new version chips.
|
||||||
#define spi_flash_ll_set_dummy_out(dev, out_en, out_lev)
|
#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.
|
* Set the data to be written in the data buffer.
|
||||||
*
|
*
|
||||||
* @param dev Beginning address of the peripheral registers.
|
* @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.
|
* @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
|
// Load data registers, word at a time
|
||||||
int num_words = (length + 3) >> 2;
|
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.
|
* Get the address length that is set in register, in bits.
|
||||||
*
|
*
|
||||||
* @param dev Beginning address of the peripheral registers.
|
* @param dev Beginning address of the peripheral registers.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static inline int spi_flash_ll_get_addr_bitlen(spi_dev_t *dev)
|
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;
|
return dev->user.usr_addr ? dev->user1.usr_addr_bitlen + 1 : 0;
|
||||||
|
|
|
@ -34,9 +34,14 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define gpspi_flash_ll_get_hw(host_id) (((host_id)==SPI2_HOST ? &GPSPI2 \
|
#define gpspi_flash_ll_get_hw(host_id) ( ((host_id)==SPI2_HOST) ? &GPSPI2 : (\
|
||||||
: ((host_id)==SPI3_HOST ? &GPSPI3 \
|
((host_id)==SPI3_HOST) ? &GPSPI3 : (\
|
||||||
: ({abort();(spi_dev_t*)0;}))))
|
{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;
|
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.
|
* Get the address length that is set in register, in bits.
|
||||||
*
|
*
|
||||||
* @param dev Beginning address of the peripheral registers.
|
* @param dev Beginning address of the peripheral registers.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static inline int gpspi_flash_ll_get_addr_bitlen(spi_dev_t *dev)
|
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;
|
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 dev Beginning address of the peripheral registers.
|
||||||
* @param out_en whether to enable IO output for dummy phase
|
* @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)
|
static inline void gpspi_flash_ll_set_dummy_out(spi_dev_t *dev, uint32_t out_en, uint32_t out_lev)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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) \
|
#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)))
|
: 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 {
|
typedef union {
|
||||||
gpspi_flash_ll_clock_reg_t gpspi;
|
gpspi_flash_ll_clock_reg_t gpspi;
|
||||||
|
|
|
@ -35,7 +35,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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;
|
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.
|
* Get the address length that is set in register, in bits.
|
||||||
*
|
*
|
||||||
* @param dev Beginning address of the peripheral registers.
|
* @param dev Beginning address of the peripheral registers.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static inline int spimem_flash_ll_get_addr_bitlen(spi_mem_dev_t *dev)
|
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;
|
return dev->user.usr_addr ? dev->user1.usr_addr_bitlen + 1 : 0;
|
||||||
|
|
|
@ -51,11 +51,12 @@ esp_err_t spi_flash_hal_configure_host_io_mode(
|
||||||
esp_flash_io_mode_t io_mode)
|
esp_flash_io_mode_t io_mode)
|
||||||
{
|
{
|
||||||
spi_dev_t *dev = get_spi_dev(host);
|
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;
|
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 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.
|
* the address field, to avoid the flash going into continuous read mode.
|
||||||
|
|
|
@ -145,7 +145,7 @@ esp_err_t spi_bus_add_flash_device(esp_flash_t **out_chip, const esp_flash_spi_d
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dev_id;
|
int dev_id = -1;
|
||||||
esp_err_t err = esp_flash_init_os_functions(chip, config->host_id, &dev_id);
|
esp_err_t err = esp_flash_init_os_functions(chip, config->host_id, &dev_id);
|
||||||
if (err == ESP_ERR_NOT_SUPPORTED) {
|
if (err == ESP_ERR_NOT_SUPPORTED) {
|
||||||
ESP_LOGE(TAG, "Init os functions failed! No free CS.");
|
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;
|
ret = err;
|
||||||
goto fail;
|
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);
|
assert(dev_id < SOC_SPI_PERIPH_CS_NUM(config->host_id) && dev_id >= 0);
|
||||||
|
|
||||||
bool use_iomux = spicommon_bus_using_iomux(config->host_id);
|
bool use_iomux = spicommon_bus_using_iomux(config->host_id);
|
||||||
|
|
|
@ -48,8 +48,8 @@ typedef struct {
|
||||||
/** Called before any erase/write operations to check whether the region is limited by the OS */
|
/** 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);
|
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'. */
|
/** Delay for at least 'us' microseconds. Called in between 'start' and 'end'. */
|
||||||
esp_err_t (*delay_ms)(void *arg, unsigned ms);
|
esp_err_t (*delay_us)(void *arg, unsigned us);
|
||||||
} esp_flash_os_functions_t;
|
} esp_flash_os_functions_t;
|
||||||
|
|
||||||
/** @brief Structure to describe a SPI flash chip connected to the system.
|
/** @brief Structure to describe a SPI flash chip connected to the system.
|
||||||
|
|
|
@ -28,8 +28,12 @@ typedef struct {
|
||||||
esp_flash_io_mode_t io_mode; ///< IO mode to read from the Flash
|
esp_flash_io_mode_t io_mode; ///< IO mode to read from the Flash
|
||||||
esp_flash_speed_t speed; ///< Speed of the Flash clock
|
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 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;
|
} esp_flash_spi_device_config_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
* progress bit) to be cleared.
|
||||||
*
|
*
|
||||||
* @param chip Pointer to SPI flash chip to use. If NULL, esp_flash_default_chip is substituted.
|
* @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
|
* @return
|
||||||
* - ESP_OK if success
|
* - ESP_OK if success
|
||||||
* - ESP_ERR_TIMEOUT if not idle before timeout
|
* - ESP_ERR_TIMEOUT if not idle before timeout
|
||||||
* - or other error passed from the ``wait_idle`` or ``read_status`` function of host driver
|
* - 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
|
* @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
|
* spi_flash_chip_generic_wait_idle() and may be useful when implementing
|
||||||
* alternative drivers.
|
* 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.
|
* @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
|
* - ESP_ERR_TIMEOUT if not idle before timeout
|
||||||
* - or other error passed from the ``set_write_protect`` or ``common_command`` function of host driver
|
* - 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.
|
/// 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);
|
typedef esp_err_t (*esp_flash_rdsr_func_t)(esp_flash_t* chip, uint32_t* out_sr);
|
||||||
|
|
|
@ -20,12 +20,15 @@
|
||||||
|
|
||||||
static const char TAG[] = "chip_generic";
|
static const char TAG[] = "chip_generic";
|
||||||
|
|
||||||
#define SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT 4000
|
#define SPI_FLASH_DEFAULT_IDLE_TIMEOUT_MS 200
|
||||||
#define SPI_FLASH_GENERIC_SECTOR_ERASE_TIMEOUT 500 //according to GD25Q127 + 100ms
|
#define SPI_FLASH_GENERIC_CHIP_ERASE_TIMEOUT_MS 4000
|
||||||
#define SPI_FLASH_GENERIC_BLOCK_ERASE_TIMEOUT 1300 //according to GD25Q127 + 100ms
|
#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)
|
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;
|
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;
|
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);
|
err = chip->chip_drv->set_chip_write_protect(chip, false);
|
||||||
if (err == ESP_OK) {
|
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) {
|
if (err == ESP_OK) {
|
||||||
chip->host->erase_chip(chip->host);
|
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;
|
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;
|
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);
|
esp_err_t err = chip->chip_drv->set_chip_write_protect(chip, false);
|
||||||
if (err == ESP_OK) {
|
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) {
|
if (err == ESP_OK) {
|
||||||
chip->host->erase_sector(chip->host, start_address);
|
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;
|
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;
|
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);
|
esp_err_t err = chip->chip_drv->set_chip_write_protect(chip, false);
|
||||||
if (err == ESP_OK) {
|
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) {
|
if (err == ESP_OK) {
|
||||||
chip->host->erase_block(chip->host, start_address);
|
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;
|
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;
|
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;
|
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) {
|
if (err == ESP_OK) {
|
||||||
// Perform the actual Page Program command
|
// Perform the actual Page Program command
|
||||||
chip->host->program_page(chip->host, buffer, address, length);
|
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;
|
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;
|
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) {
|
if (err == ESP_OK) {
|
||||||
chip->host->set_write_protect(chip->host, write_protect);
|
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;
|
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) {
|
while (chip->host->host_idle(chip->host) && *timeout_us > 0) {
|
||||||
if (*timeout_ms > 1) {
|
#if HOST_DELAY_INTERVAL_US > 0
|
||||||
chip->os_func->delay_ms(chip->os_func_data, 1);
|
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;
|
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) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
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) {
|
if ((status & SR_WIP) == 0) {
|
||||||
break; // Write in progress is complete
|
break; // Write in progress is complete
|
||||||
}
|
}
|
||||||
if (timeout_ms > 1) {
|
if (timeout_us > 0 && interval > 0) {
|
||||||
chip->os_func->delay_ms(chip->os_func_data, 1);
|
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)
|
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;
|
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) {
|
if (ret != ESP_OK) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,9 +95,9 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg)
|
||||||
#endif
|
#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;
|
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 = {
|
static const DRAM_ATTR esp_flash_os_functions_t esp_flash_spi1_default_os_functions = {
|
||||||
.start = spi1_start,
|
.start = spi1_start,
|
||||||
.end = spi1_end,
|
.end = spi1_end,
|
||||||
.delay_ms = delay_ms,
|
.delay_us = delay_us,
|
||||||
.region_protected = main_flash_region_protected,
|
.region_protected = main_flash_region_protected,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = {
|
static const esp_flash_os_functions_t esp_flash_spi23_default_os_functions = {
|
||||||
.start = spi_start,
|
.start = spi_start,
|
||||||
.end = spi_end,
|
.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_handle_t lock = spi_bus_lock_get_by_id(host_id);
|
||||||
spi_bus_lock_dev_handle_t dev_handle;
|
spi_bus_lock_dev_handle_t dev_handle;
|
||||||
spi_bus_lock_dev_config_t config = {.flags = SPI_BUS_LOCK_DEV_FLAG_CS_REQUIRED};
|
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);
|
esp_err_t err = spi_bus_lock_register_dev(lock, &config, &dev_handle);
|
||||||
if (err != ESP_OK) {
|
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) {
|
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;
|
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;
|
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)
|
esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip)
|
||||||
{
|
{
|
||||||
if (chip->os_func_data) {
|
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);
|
free(chip->os_func_data);
|
||||||
}
|
}
|
||||||
chip->os_func = NULL;
|
chip->os_func = NULL;
|
||||||
|
|
|
@ -65,16 +65,16 @@ static IRAM_ATTR esp_err_t end(void *arg)
|
||||||
return ESP_OK;
|
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;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DRAM_ATTR esp_flash_os_functions_t esp_flash_noos_functions = {
|
const DRAM_ATTR esp_flash_os_functions_t esp_flash_noos_functions = {
|
||||||
.start = start,
|
.start = start,
|
||||||
.end = end,
|
.end = end,
|
||||||
.delay_ms = delay_ms,
|
.delay_us = delay_us,
|
||||||
.region_protected = NULL,
|
.region_protected = NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
#include "hal/spi_flash_hal.h"
|
#include "hal/spi_flash_hal.h"
|
||||||
|
#include "ccomp_timer.h"
|
||||||
|
|
||||||
#define FUNC_SPI 1
|
#define FUNC_SPI 1
|
||||||
|
|
||||||
|
@ -165,6 +166,26 @@ flashtest_config_t config_list[] = {
|
||||||
};
|
};
|
||||||
#endif
|
#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)
|
static void setup_bus(spi_host_device_t host_id)
|
||||||
{
|
{
|
||||||
if (host_id == SPI_HOST) {
|
if (host_id == SPI_HOST) {
|
||||||
|
@ -615,8 +636,11 @@ void test_permutations(flashtest_config_t* config)
|
||||||
cfg->speed = speed;
|
cfg->speed = speed;
|
||||||
setup_new_chip(cfg, &chip);
|
setup_new_chip(cfg, &chip);
|
||||||
|
|
||||||
|
spi_host_device_t host_id;
|
||||||
|
get_chip_host(chip, &host_id, NULL);
|
||||||
|
|
||||||
if (io_mode > SPI_FLASH_FASTRD
|
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;
|
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);
|
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
|
#ifdef CONFIG_SPIRAM_USE_MALLOC
|
||||||
|
|
||||||
/* Utility: Read into a small internal RAM buffer using esp_flash_read() and compare what
|
/* 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);
|
FLASH_TEST_CASE("esp_flash_read large PSRAM buffer low memory", test_flash_read_large_psram_buffer_low_internal_mem);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <sys/param.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
|
@ -9,6 +10,8 @@
|
||||||
#include "driver/timer.h"
|
#include "driver/timer.h"
|
||||||
#include "esp_intr_alloc.h"
|
#include "esp_intr_alloc.h"
|
||||||
#include "test_utils.h"
|
#include "test_utils.h"
|
||||||
|
#include "ccomp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
struct flash_test_ctx {
|
struct flash_test_ctx {
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
|
@ -16,6 +19,8 @@ struct flash_test_ctx {
|
||||||
SemaphoreHandle_t done;
|
SemaphoreHandle_t done;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char TAG[] = "test_spi_flash";
|
||||||
|
|
||||||
/* Base offset in flash for tests. */
|
/* Base offset in flash for tests. */
|
||||||
static size_t start;
|
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);
|
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
|
#if portNUM_PROCESSORS > 1
|
||||||
TEST_CASE("spi_flash deadlock with high priority busy-waiting task", "[spi_flash][esp_flash]")
|
TEST_CASE("spi_flash deadlock with high priority busy-waiting task", "[spi_flash][esp_flash]")
|
||||||
|
|
Loading…
Reference in a new issue