diff --git a/components/esp_driver_spi/CMakeLists.txt b/components/esp_driver_spi/CMakeLists.txt index cb1efc7488..945d920b5f 100644 --- a/components/esp_driver_spi/CMakeLists.txt +++ b/components/esp_driver_spi/CMakeLists.txt @@ -27,6 +27,6 @@ idf_component_register( SRCS ${srcs} INCLUDE_DIRS ${public_include} REQUIRES esp_pm - PRIV_REQUIRES esp_timer esp_mm esp_driver_gpio + PRIV_REQUIRES esp_timer esp_mm esp_driver_gpio spi_flash esp_psram #For CONFIG_SPIRAM_SPEED LDFRAGMENTS "linker.lf" ) diff --git a/components/esp_driver_spi/Kconfig b/components/esp_driver_spi/Kconfig index 218f0a67ef..5cccd15f84 100644 --- a/components/esp_driver_spi/Kconfig +++ b/components/esp_driver_spi/Kconfig @@ -44,7 +44,7 @@ menu "ESP-Driver:SPI Configurations" help Normally only the ISR of SPI slave is placed in the IRAM, so that it can work without the flash when interrupt is triggered. - For other functions, there's some possibility that the flash cache + For other functions, there is some possibility that the flash cache miss when running inside and out of SPI functions, which may increase the interval of SPI transactions. Enable this to put ``queue_trans``, ``get_trans_result`` and diff --git a/components/esp_driver_spi/include/driver/spi_master.h b/components/esp_driver_spi/include/driver/spi_master.h index a432e62334..3567c45e9a 100644 --- a/components/esp_driver_spi/include/driver/spi_master.h +++ b/components/esp_driver_spi/include/driver/spi_master.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -104,6 +104,7 @@ typedef struct { */ } spi_device_interface_config_t; +// Input flags #define SPI_TRANS_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode #define SPI_TRANS_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode #define SPI_TRANS_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer. @@ -117,6 +118,11 @@ typedef struct { #define SPI_TRANS_MODE_OCT (1<<10) ///< Transmit/receive data in 8-bit mode #define SPI_TRANS_MULTILINE_ADDR SPI_TRANS_MODE_DIOQIO_ADDR ///< The data lines used at address phase is the same as data phase (otherwise, only one data line is used at address phase) #define SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL (1<<11) ///< By default driver will automatically re-alloc dma buffer if it doesn't meet hardware alignment or dma_capable requirements, this flag is for you to disable this feature, you will need to take care of the alignment otherwise driver will return you error ESP_ERR_INVALID_ARG +#define SPI_TRANS_DMA_USE_PSRAM (1<<12) ///< Use PSRAM for DMA buffer directly, has speed limit, but no temp buffer and save memory + +// Output flags +#define SPI_TRANS_DMA_RX_FAIL (1<<30) ///< RX transaction data lose flag, indicate DMA RX overflow +#define SPI_TRANS_DMA_TX_FAIL (1<<31) ///< TX transaction data lose flag, indicate DMA TX underflow /** * This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes. diff --git a/components/esp_driver_spi/src/gpspi/spi_master.c b/components/esp_driver_spi/src/gpspi/spi_master.c index f78a8dff42..91077d5355 100644 --- a/components/esp_driver_spi/src/gpspi/spi_master.c +++ b/components/esp_driver_spi/src/gpspi/spi_master.c @@ -116,11 +116,14 @@ We have two bits to control the interrupt: #include "esp_private/spi_common_internal.h" #include "esp_private/spi_master_internal.h" #include "esp_private/esp_clk_tree_common.h" +#include "esp_private/cache_utils.h" #include "driver/spi_master.h" #include "clk_ctrl_os.h" #include "esp_log.h" #include "esp_check.h" #include "esp_ipc.h" +#include "esp_cache.h" +#include "esp_heap_caps.h" #include "freertos/task.h" #include "freertos/queue.h" #include "soc/soc_memory_layout.h" @@ -128,10 +131,6 @@ We have two bits to control the interrupt: #include "hal/spi_hal.h" #include "hal/spi_ll.h" #include "hal/hal_utils.h" -#include "esp_heap_caps.h" -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE -#include "esp_cache.h" -#endif #ifdef CONFIG_SPI_MASTER_ISR_IN_IRAM #define SPI_MASTER_ISR_ATTR IRAM_ATTR @@ -159,7 +158,25 @@ We have two bits to control the interrupt: #define SPI_PERIPH_SRC_FREQ_MAX (80*1000*1000) //peripheral hardware limitation for clock source into peripheral -static const char *SPI_TAG = "spi_master"; +/** + * The approx time for dma setup and pop data into peripheral + * This time is theoretically inverse proportion to the PSRAM speed(bandwidth), and direct proportion to the SPI speed, but hard to accurately calculated + * Below is an engineering value based on experience test result, e.g. delay 5us for 20MHz PSRAM speed, 1us for 80M + * Then the formula is: Delay_time = K * (SPI_SPEED / PSRAM_SPEED) + B + * delay + * ▲ + * │ x + * │ x + * │ x + * │ x x + * ─┼─────────────────► + * │ psram speed + */ +#define K_EDMA_SETUP_RATIO 1 / 50000 +#define B_EDMA_SETUP_TIME_US 1 +#define SPI_EDMA_SETUP_TIME_US(spi_speed) ((spi_speed) * K_EDMA_SETUP_RATIO / CONFIG_SPIRAM_SPEED + B_EDMA_SETUP_TIME_US) + +ESP_LOG_ATTR_TAG_DRAM(SPI_TAG, "spi_master"); #define SPI_CHECK(a, str, ret_val, ...) ESP_RETURN_ON_FALSE_ISR(a, ret_val, SPI_TAG, str, ##__VA_ARGS__) typedef struct spi_device_t spi_device_t; @@ -832,6 +849,13 @@ static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_ if (dev->cfg.pre_cb) { dev->cfg.pre_cb(trans); } +#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE + spi_hal_clear_intr_mask(hal, SPI_LL_INTR_IN_FULL | SPI_LL_INTR_OUT_EMPTY); + if (esp_ptr_dma_ext_capable(hal_trans.send_buffer)) { + // ! Delay here is required for EDMA to pass data from PSRAM to GPSPI + esp_rom_delay_us(SPI_EDMA_SETUP_TIME_US(hal_dev->timing_conf.real_freq)); + } +#endif //Kick off transfer spi_hal_user_start(hal); } @@ -929,6 +953,26 @@ static void SPI_MASTER_ISR_ATTR spi_post_sct_trans(spi_host_t *host) } #endif //#if SOC_SPI_SCT_SUPPORTED +static void SPI_MASTER_ISR_ATTR spi_trans_dma_error_check(spi_host_t *host) +{ +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM //error checks only for psram dma + if (!host->sct_mode_enabled) { + if (esp_ptr_external_ram(host->cur_trans_buf.buffer_to_rcv) && spi_hal_get_intr_mask(&host->hal, SPI_LL_INTR_IN_FULL)) { + host->cur_trans_buf.trans->flags |= SPI_TRANS_DMA_RX_FAIL; + ESP_DRAM_LOGE(SPI_TAG, "DMA RX overflow detected"); + } else { + host->cur_trans_buf.trans->flags &= ~SPI_TRANS_DMA_RX_FAIL; + } + if (esp_ptr_external_ram(host->cur_trans_buf.buffer_to_send) && spi_hal_get_intr_mask(&host->hal, SPI_LL_INTR_OUT_EMPTY)) { + host->cur_trans_buf.trans->flags |= SPI_TRANS_DMA_TX_FAIL; + ESP_DRAM_LOGE(SPI_TAG, "DMA TX underflow detected"); + } else { + host->cur_trans_buf.trans->flags &= ~SPI_TRANS_DMA_TX_FAIL; + } + } +#endif +} + // This is run in interrupt context. static void SPI_MASTER_ISR_ATTR spi_intr(void *arg) { @@ -978,6 +1022,7 @@ static void SPI_MASTER_ISR_ATTR spi_intr(void *arg) (void)ret; } #endif + spi_trans_dma_error_check(host); } #if SOC_SPI_SCT_SUPPORTED @@ -1129,7 +1174,9 @@ static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid(spi_device_handle_t handl SPI_CHECK(trans_desc->length <= SPI_LL_CPU_MAX_BIT_LEN, "txdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG); SPI_CHECK(trans_desc->rxlength <= SPI_LL_CPU_MAX_BIT_LEN, "rxdata transfer > hardware max supported len", ESP_ERR_INVALID_ARG); } - + if (esp_ptr_external_ram(trans_desc->tx_buffer) || esp_ptr_external_ram(trans_desc->rx_buffer)){ + SPI_CHECK(spi_flash_cache_enabled(), "Using PSRAM must when cache is enabled", ESP_ERR_INVALID_STATE); + } return ESP_OK; } @@ -1151,6 +1198,45 @@ static SPI_MASTER_ISR_ATTR void uninstall_priv_desc(spi_trans_priv_t* trans_buf) } } +static SPI_MASTER_ISR_ATTR esp_err_t setup_dma_priv_buffer(uint32_t *buffer, uint32_t len, uint32_t alignment, bool is_tx, uint32_t flags, uint32_t **ret_buffer) +{ + bool unaligned = ((((uint32_t)buffer) | len) & (alignment - 1)); +#if !SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + if (!((flags & SPI_TRANS_DMA_USE_PSRAM) && esp_ptr_dma_ext_capable(buffer))) { + // tx don't need align on addr or length on those chips + unaligned = is_tx ? false : (((uint32_t)buffer) & (alignment - 1)); + } +#endif + +#if SOC_PSRAM_DMA_CAPABLE + if ((flags & SPI_TRANS_DMA_USE_PSRAM) && esp_ptr_dma_ext_capable(buffer)) { + // dma psram don't do additional copy, check only + ESP_RETURN_ON_FALSE_ISR(!unaligned, ESP_ERR_INVALID_ARG, SPI_TAG, "%s buffer addr and length should align to %ld Byte to use PSRAM, use heap_caps_aligned_calloc() may helpful", is_tx ? "TX" : "RX", alignment); + ESP_RETURN_ON_ERROR_ISR(esp_cache_msync((void *)buffer, len, is_tx ? ESP_CACHE_MSYNC_FLAG_DIR_C2M : ESP_CACHE_MSYNC_FLAG_DIR_M2C), SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX"); + *ret_buffer = buffer; + return ESP_OK; + } +#endif + if ((!esp_ptr_dma_capable(buffer) || unaligned)) { + ESP_RETURN_ON_FALSE_ISR(!(flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but %s addr&len not align to %d, or not dma_capable", is_tx ? "TX" : "RX", alignment); + //if buf in the desc not DMA-capable, or not bytes aligned to alignment, malloc a new one + ESP_EARLY_LOGD(SPI_TAG, "Allocate %s buffer for DMA", is_tx ? "TX" : "RX"); + len = (len + alignment - 1) & (~(alignment - 1)); // up align alignment + uint32_t *temp = heap_caps_aligned_alloc(alignment, len, MALLOC_CAP_DMA); + ESP_RETURN_ON_FALSE_ISR(temp != NULL, ESP_ERR_NO_MEM, SPI_TAG, "Failed to allocate priv %s buffer", is_tx ? "TX" : "RX"); + + if (is_tx) { + memcpy(temp, buffer, len); + } + buffer = temp; + } +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + ESP_RETURN_ON_ERROR_ISR(esp_cache_msync((void *)buffer, len, is_tx ? ESP_CACHE_MSYNC_FLAG_DIR_C2M : ESP_CACHE_MSYNC_FLAG_DIR_M2C), SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX"); +#endif + *ret_buffer = buffer; + return ESP_OK; +} + static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_host_t *host, spi_trans_priv_t* priv_desc) { spi_transaction_t *trans_desc = priv_desc->trans; @@ -1167,70 +1253,36 @@ static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_host_t *host, spi_trans } // tx memory assign - const uint32_t *send_ptr; + uint32_t *send_ptr; if (trans_desc->flags & SPI_TRANS_USE_TXDATA) { send_ptr = (uint32_t *)&trans_desc->tx_data[0]; } else { //if not use TXDATA neither tx_buffer, tx data assigned to NULL - send_ptr = trans_desc->tx_buffer ; + send_ptr = (uint32_t *)trans_desc->tx_buffer ; } - uint32_t tx_byte_len = (trans_desc->length + 7) / 8; - uint32_t rx_byte_len = (trans_desc->rxlength + 7) / 8; -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - bool tx_unaligned = ((((uint32_t)send_ptr) | tx_byte_len) & (alignment - 1)); - bool rx_unaligned = ((((uint32_t)rcv_ptr) | rx_byte_len) & (alignment - 1)); -#else - bool tx_unaligned = false; //tx don't need align on addr or length, for other chips - bool rx_unaligned = (((uint32_t)rcv_ptr) & (alignment - 1)); -#endif - + esp_err_t ret = ESP_OK; if (send_ptr && bus_attr->dma_enabled) { - if ((!esp_ptr_dma_capable(send_ptr) || tx_unaligned)) { - ESP_RETURN_ON_FALSE(!(trans_desc->flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but TX buffer addr&len not align to %d byte, or not dma_capable", alignment); - //if txbuf in the desc not DMA-capable, or not bytes aligned to alignment, malloc a new one - ESP_EARLY_LOGD(SPI_TAG, "Allocate TX buffer for DMA"); - tx_byte_len = (tx_byte_len + alignment - 1) & (~(alignment - 1)); // up align alignment - uint32_t *temp = heap_caps_aligned_alloc(alignment, tx_byte_len, MALLOC_CAP_DMA); - if (temp == NULL) { - goto clean_up; - } - - memcpy(temp, send_ptr, (trans_desc->length + 7) / 8); - send_ptr = temp; + ret = setup_dma_priv_buffer(send_ptr, (trans_desc->length + 7) / 8, alignment, true, trans_desc->flags, &send_ptr); + if (ret != ESP_OK) { + goto clean_up; } -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - esp_err_t ret = esp_cache_msync((void *)send_ptr, tx_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M); - assert(ret == ESP_OK); - (void)ret; -#endif } if (rcv_ptr && bus_attr->dma_enabled) { - if ((!esp_ptr_dma_capable(rcv_ptr) || rx_unaligned)) { - ESP_RETURN_ON_FALSE(!(trans_desc->flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL), ESP_ERR_INVALID_ARG, SPI_TAG, "Set flag SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL but RX buffer addr&len not align to %d byte, or not dma_capable", alignment); - //if rxbuf in the desc not DMA-capable, or not aligned to alignment, malloc a new one - ESP_EARLY_LOGD(SPI_TAG, "Allocate RX buffer for DMA"); - rx_byte_len = (rx_byte_len + alignment - 1) & (~(alignment - 1)); // up align alignment - rcv_ptr = heap_caps_aligned_alloc(alignment, rx_byte_len, MALLOC_CAP_DMA); - if (rcv_ptr == NULL) { - goto clean_up; - } + ret = setup_dma_priv_buffer(rcv_ptr, (trans_desc->rxlength + 7) / 8, alignment, false, trans_desc->flags, &rcv_ptr); + if (ret != ESP_OK) { + goto clean_up; } -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - // do invalid here to hold on cache status to avoid hardware auto write back during dma transaction - esp_err_t ret = esp_cache_msync((void *)rcv_ptr, rx_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_M2C); - assert(ret == ESP_OK); - (void)ret; -#endif } + priv_desc->buffer_to_send = send_ptr; priv_desc->buffer_to_rcv = rcv_ptr; return ESP_OK; clean_up: uninstall_priv_desc(priv_desc); - return ESP_ERR_NO_MEM; + return ret; } esp_err_t SPI_MASTER_ATTR spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait) @@ -1309,7 +1361,7 @@ esp_err_t SPI_MASTER_ATTR spi_device_get_trans_result(spi_device_handle_t handle } (*trans_desc) = trans_buf.trans; - return ESP_OK; + return (trans_buf.trans->flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)) ? ESP_ERR_INVALID_STATE : ESP_OK; } //Porcelain to do one blocking transmission. @@ -1458,6 +1510,7 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, return ESP_ERR_TIMEOUT; } } + spi_trans_dma_error_check(host); #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE //invalidate here to let user access rx data in post_cb if possible const spi_bus_attr_t *bus_attr = host->bus_attr; @@ -1471,7 +1524,6 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, } } #endif - ESP_LOGV(SPI_TAG, "polling trans done"); //deal with the in-flight transaction spi_post_trans(host); @@ -1487,7 +1539,7 @@ esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end(spi_device_handle_t handle, spi_bus_lock_acquire_end(handle->dev_lock); } - return ESP_OK; + return (host->cur_trans_buf.trans->flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL)) ? ESP_ERR_INVALID_STATE : ESP_OK; } esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc) diff --git a/components/esp_driver_spi/test_apps/master/main/CMakeLists.txt b/components/esp_driver_spi/test_apps/master/main/CMakeLists.txt index f0d824d614..91e5f4d0fc 100644 --- a/components/esp_driver_spi/test_apps/master/main/CMakeLists.txt +++ b/components/esp_driver_spi/test_apps/master/main/CMakeLists.txt @@ -16,6 +16,6 @@ endif() # the component can be registered as WHOLE_ARCHIVE idf_component_register( SRCS ${srcs} - PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio + PRIV_REQUIRES esp_driver_spi spi_flash esp_timer esp_driver_gpio esp_mm WHOLE_ARCHIVE ) diff --git a/components/esp_driver_spi/test_apps/master/main/test_spi_master.c b/components/esp_driver_spi/test_apps/master/main/test_spi_master.c index f3a4daedf8..3f016c1c1c 100644 --- a/components/esp_driver_spi/test_apps/master/main/test_spi_master.c +++ b/components/esp_driver_spi/test_apps/master/main/test_spi_master.c @@ -26,6 +26,7 @@ #include "esp_clk_tree.h" #include "esp_timer.h" #include "esp_log.h" +#include "esp_cache.h" #include "test_utils.h" #include "test_spi_utils.h" #include "spi_performance.h" @@ -776,24 +777,27 @@ TEST_CASE("SPI Master DMA test, TX and RX in different regions", "[spi]") //connect MOSI to two devices breaks the output, fix it. spitest_gpio_output_sel(buscfg.mosi_io_num, FUNC_GPIO, spi_periph_signal[TEST_SPI_HOST].spid_out); -#define TEST_REGION_SIZE 2 +#define TEST_REGION_SIZE 3 static spi_transaction_t trans[TEST_REGION_SIZE]; - int x; memset(trans, 0, sizeof(trans)); - trans[0].length = 320 * 8, - trans[0].tx_buffer = data_malloc + 2; + trans[0].length = 320 * 8; + trans[0].tx_buffer = data_malloc + 2; trans[0].rx_buffer = data_dram; - trans[1].length = 4 * 8, - trans[1].flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA; + trans[1].length = 4 * 8; + trans[1].flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA; uint32_t *ptr = (uint32_t *)trans[1].rx_data; *ptr = 0x54545454; ptr = (uint32_t *)trans[1].tx_data; *ptr = 0xbc124960; + trans[2].length = 64 * 8; + trans[2].tx_buffer = data_drom; + trans[2].rx_buffer = data_malloc; + //Queue all transactions. - for (x = 0; x < TEST_REGION_SIZE; x++) { + for (int x = 0; x < TEST_REGION_SIZE; x++) { ESP_LOGI(TAG, "transmitting %d...", x); ret = spi_device_transmit(spi, &trans[x]); TEST_ASSERT(ret == ESP_OK); @@ -1968,3 +1972,95 @@ TEST_CASE("test_spi_master_auto_sleep_retention", "[spi]") } #endif //CONFIG_PM_ENABLE #endif //SOC_LIGHT_SLEEP_SUPPORTED + +#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE +#define TEST_EDMA_PSRAM_TRANS_NUM 5 +#define TEST_EDMA_TRANS_LEN 20480 //Use PSRAM need a 16/32/64 aligned buffer len +#define TEST_EDMA_BUFFER_SZ (TEST_EDMA_PSRAM_TRANS_NUM * TEST_EDMA_TRANS_LEN) + +void test_spi_psram_trans(spi_device_handle_t dev_handle, void *tx, void *rx) +{ + spi_transaction_t trans_cfg = { + .length = TEST_EDMA_TRANS_LEN * 8, + .flags = SPI_TRANS_DMA_USE_PSRAM, + }; + + for (uint8_t cnt = 0; cnt < TEST_EDMA_PSRAM_TRANS_NUM; cnt ++) { + trans_cfg.tx_buffer = tx + TEST_EDMA_TRANS_LEN * cnt; + trans_cfg.rx_buffer = rx + TEST_EDMA_TRANS_LEN * cnt; + + // To use psram, hardware will pass data through MSPI and GDMA to GPSPI, which need some time + // GPSPI bandwidth(speed * line_num) should always no more than PSRAM bandwidth + trans_cfg.override_freq_hz = (CONFIG_SPIRAM_SPEED / 4) * 1000 * 1000; + printf("%d TX %p RX %p len %d @%ld kHz\n", cnt, trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN, trans_cfg.override_freq_hz / 1000); + TEST_ESP_OK(spi_device_transmit(dev_handle, &trans_cfg)); + TEST_ASSERT(!(trans_cfg.flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL))); + spitest_cmp_or_dump(trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN); + } +} + +TEST_CASE("SPI_Master: PSRAM buffer transaction via EDMA", "[spi]") +{ + spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG(); + buscfg.miso_io_num = buscfg.mosi_io_num; // set spi "self-loopback" + buscfg.max_transfer_sz = TEST_EDMA_BUFFER_SZ; + TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); + + spi_device_handle_t dev_handle; + spi_device_interface_config_t devcfg = SPI_DEVICE_TEST_DEFAULT_CONFIG(); + devcfg.clock_speed_hz = 80 * 1000 * 1000; // Test error case on highest freq first + TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &dev_handle)); + int real_freq_khz; + spi_device_get_actual_freq(dev_handle, &real_freq_khz); + + uint32_t cache_width = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA); + uint8_t *internal_1 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_INTERNAL); + uint8_t *external_1 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_SPIRAM); + uint8_t *external_2 = heap_caps_aligned_calloc(cache_width, 1, TEST_EDMA_BUFFER_SZ, MALLOC_CAP_SPIRAM); + test_fill_random_to_buffers_dualboard(1001, internal_1, external_2, TEST_EDMA_BUFFER_SZ); + + printf("Test error case: High freq @%d kHz\n", real_freq_khz); + spi_transaction_t trans_cfg = { + .length = TEST_EDMA_TRANS_LEN * 8, + .tx_buffer = external_2, + .rx_buffer = external_1, + }; + + for (uint8_t i = 0; i < 2; i++) { + trans_cfg.flags = i ? SPI_TRANS_DMA_USE_PSRAM : 0; + uint32_t before = esp_get_free_heap_size(); + spi_device_polling_start(dev_handle, &trans_cfg, portMAX_DELAY); + uint32_t after = esp_get_free_heap_size(); + printf("\n==== %s ====\n", i ? "EDMA" : "Auto Malloc"); + printf("before: %ld, after: %ld, diff: %ld\n", before, after, after - before); + spi_device_polling_end(dev_handle, portMAX_DELAY); + printf("RX fail: %d, TX fail: %d\n", !!(trans_cfg.flags & SPI_TRANS_DMA_RX_FAIL), !!(trans_cfg.flags & SPI_TRANS_DMA_TX_FAIL)); + TEST_ASSERT(i ? (before - after) < TEST_EDMA_TRANS_LEN : (before - after) > 2 * TEST_EDMA_TRANS_LEN); + TEST_ASSERT((!!i) == !!(trans_cfg.flags & (SPI_TRANS_DMA_RX_FAIL | SPI_TRANS_DMA_TX_FAIL))); + } + + printf("\nTest unaligned tx psram buffer\n"); + trans_cfg.tx_buffer ++; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, spi_device_transmit(dev_handle, &trans_cfg)); + + printf("\nTest trans: internal -> psram\n"); + memset(external_1, 0, TEST_EDMA_BUFFER_SZ); + TEST_ESP_OK(esp_cache_msync((void *)external_1, TEST_EDMA_BUFFER_SZ, ESP_CACHE_MSYNC_FLAG_DIR_C2M)); + test_spi_psram_trans(dev_handle, internal_1, external_1); + + printf("\nTest trans: psram -> psram\n"); + memset(external_2, 0, TEST_EDMA_BUFFER_SZ); + TEST_ESP_OK(esp_cache_msync((void *)external_2, TEST_EDMA_BUFFER_SZ, ESP_CACHE_MSYNC_FLAG_DIR_C2M)); + test_spi_psram_trans(dev_handle, external_1, external_2); + + printf("\nTest trans: psram -> internal\n"); + memset(internal_1, 0, TEST_EDMA_BUFFER_SZ); + test_spi_psram_trans(dev_handle, external_2, internal_1); + + free(internal_1); + free(external_1); + free(external_2); + spi_bus_remove_device(dev_handle); + spi_bus_free(TEST_SPI_HOST); +} +#endif diff --git a/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c5 b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c5 new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c5 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c61 b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c61 new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32c61 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32h4 b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32h4 new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32h4 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32p4 b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32p4 new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32p4 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32s3 b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32s3 new file mode 100644 index 0000000000..cc641ea603 --- /dev/null +++ b/components/esp_driver_spi/test_apps/master/sdkconfig.ci.release.esp32s3 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/hal/esp32c5/include/hal/spi_ll.h b/components/hal/esp32c5/include/hal/spi_ll.h index 42006016b7..5a43252c90 100644 --- a/components/hal/esp32c5/include/hal/spi_ll.h +++ b/components/hal/esp32c5/include/hal/spi_ll.h @@ -55,6 +55,8 @@ typedef spi_dev_t spi_dma_dev_t; // Type definition of all supported interrupts typedef enum { SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done + SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened + SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD. SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD. SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD. @@ -1098,16 +1100,18 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) //helper macros to generate code for each interrupts #define FOR_EACH_ITEM(op, list) do { list(op) } while(0) #define INTR_LIST(item) \ - item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int_ena, dma_int_raw.trans_done_int_raw, dma_int_clr.trans_done_int_clr, dma_int_set.trans_done_int_set) \ - item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int_ena, dma_int_raw.slv_rd_buf_done_int_raw, dma_int_clr.slv_rd_buf_done_int_clr, dma_int_set.slv_rd_buf_done_int_set) \ - item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int_ena, dma_int_raw.slv_wr_buf_done_int_raw, dma_int_clr.slv_wr_buf_done_int_clr, dma_int_set.slv_wr_buf_done_int_set) \ - item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int_ena, dma_int_raw.slv_rd_dma_done_int_raw, dma_int_clr.slv_rd_dma_done_int_clr, dma_int_set.slv_rd_dma_done_int_set) \ - item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int_ena, dma_int_raw.slv_wr_dma_done_int_raw, dma_int_clr.slv_wr_dma_done_int_clr, dma_int_set.slv_wr_dma_done_int_set) \ - item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int_ena, dma_int_raw.dma_seg_trans_done_int_raw, dma_int_clr.dma_seg_trans_done_int_clr, dma_int_set.dma_seg_trans_done_int_set) \ - item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int_ena, dma_int_raw.slv_cmd7_int_raw, dma_int_clr.slv_cmd7_int_clr, dma_int_set.slv_cmd7_int_set) \ - item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int_ena, dma_int_raw.slv_cmd8_int_raw, dma_int_clr.slv_cmd8_int_clr, dma_int_set.slv_cmd8_int_set) \ - item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int_ena, dma_int_raw.slv_cmd9_int_raw, dma_int_clr.slv_cmd9_int_clr, dma_int_set.slv_cmd9_int_set) \ - item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int_ena, dma_int_raw.slv_cmda_int_raw, dma_int_clr.slv_cmda_int_clr, dma_int_set.slv_cmda_int_set) + item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int_ena, dma_int_raw.trans_done_int_raw, dma_int_clr.trans_done_int_clr, dma_int_set.trans_done_int_set) \ + item(SPI_LL_INTR_IN_FULL, dma_int_ena.dma_infifo_full_err_int_ena, dma_int_raw.dma_infifo_full_err_int_raw, dma_int_clr.dma_infifo_full_err_int_clr, dma_int_set.dma_infifo_full_err_int_set) \ + item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.dma_outfifo_empty_err_int_ena, dma_int_raw.dma_outfifo_empty_err_int_raw, dma_int_clr.dma_outfifo_empty_err_int_clr, dma_int_set.dma_outfifo_empty_err_int_set) \ + item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int_ena, dma_int_raw.slv_rd_buf_done_int_raw, dma_int_clr.slv_rd_buf_done_int_clr, dma_int_set.slv_rd_buf_done_int_set) \ + item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int_ena, dma_int_raw.slv_wr_buf_done_int_raw, dma_int_clr.slv_wr_buf_done_int_clr, dma_int_set.slv_wr_buf_done_int_set) \ + item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int_ena, dma_int_raw.slv_rd_dma_done_int_raw, dma_int_clr.slv_rd_dma_done_int_clr, dma_int_set.slv_rd_dma_done_int_set) \ + item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int_ena, dma_int_raw.slv_wr_dma_done_int_raw, dma_int_clr.slv_wr_dma_done_int_clr, dma_int_set.slv_wr_dma_done_int_set) \ + item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int_ena, dma_int_raw.dma_seg_trans_done_int_raw, dma_int_clr.dma_seg_trans_done_int_clr, dma_int_set.dma_seg_trans_done_int_set) \ + item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int_ena, dma_int_raw.slv_cmd7_int_raw, dma_int_clr.slv_cmd7_int_clr, dma_int_set.slv_cmd7_int_set) \ + item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int_ena, dma_int_raw.slv_cmd8_int_raw, dma_int_clr.slv_cmd8_int_clr, dma_int_set.slv_cmd8_int_set) \ + item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int_ena, dma_int_raw.slv_cmd9_int_raw, dma_int_clr.slv_cmd9_int_clr, dma_int_set.slv_cmd9_int_set) \ + item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int_ena, dma_int_raw.slv_cmda_int_raw, dma_int_clr.slv_cmda_int_clr, dma_int_set.slv_cmda_int_set) static inline void spi_ll_enable_intr(spi_dev_t *hw, spi_ll_intr_t intr_mask) diff --git a/components/hal/esp32c61/include/hal/spi_ll.h b/components/hal/esp32c61/include/hal/spi_ll.h index b2304f1b90..46823ff1b4 100644 --- a/components/hal/esp32c61/include/hal/spi_ll.h +++ b/components/hal/esp32c61/include/hal/spi_ll.h @@ -55,6 +55,8 @@ typedef spi_dev_t spi_dma_dev_t; // Type definition of all supported interrupts typedef enum { SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done + SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened + SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD. SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD. SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD. @@ -1098,16 +1100,18 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) //helper macros to generate code for each interrupts #define FOR_EACH_ITEM(op, list) do { list(op) } while(0) #define INTR_LIST(item) \ - item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done, dma_int_raw.trans_done, dma_int_clr.trans_done, dma_int_set.trans_done) \ - item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done, dma_int_raw.slv_rd_buf_done, dma_int_clr.slv_rd_buf_done, dma_int_set.slv_rd_buf_done) \ - item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done, dma_int_raw.slv_wr_buf_done, dma_int_clr.slv_wr_buf_done, dma_int_set.slv_wr_buf_done) \ - item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done, dma_int_raw.slv_rd_dma_done, dma_int_clr.slv_rd_dma_done, dma_int_set.slv_rd_dma_done) \ - item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done, dma_int_raw.slv_wr_dma_done, dma_int_clr.slv_wr_dma_done, dma_int_set.slv_wr_dma_done) \ - item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done, dma_int_raw.dma_seg_trans_done, dma_int_clr.dma_seg_trans_done, dma_int_set.dma_seg_trans_done) \ - item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7, dma_int_raw.slv_cmd7, dma_int_clr.slv_cmd7, dma_int_set.slv_cmd7) \ - item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8, dma_int_raw.slv_cmd8, dma_int_clr.slv_cmd8, dma_int_set.slv_cmd8) \ - item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9, dma_int_raw.slv_cmd9, dma_int_clr.slv_cmd9, dma_int_set.slv_cmd9) \ - item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda, dma_int_raw.slv_cmda, dma_int_clr.slv_cmda, dma_int_set.slv_cmda) + item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done, dma_int_raw.trans_done, dma_int_clr.trans_done, dma_int_set.trans_done) \ + item(SPI_LL_INTR_IN_FULL, dma_int_ena.dma_infifo_full_err, dma_int_raw.dma_infifo_full_err, dma_int_clr.dma_infifo_full_err, dma_int_set.dma_infifo_full_err) \ + item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.dma_outfifo_empty_err, dma_int_raw.dma_outfifo_empty_err, dma_int_clr.dma_outfifo_empty_err, dma_int_set.dma_outfifo_empty_err) \ + item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done, dma_int_raw.slv_rd_buf_done, dma_int_clr.slv_rd_buf_done, dma_int_set.slv_rd_buf_done) \ + item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done, dma_int_raw.slv_wr_buf_done, dma_int_clr.slv_wr_buf_done, dma_int_set.slv_wr_buf_done) \ + item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done, dma_int_raw.slv_rd_dma_done, dma_int_clr.slv_rd_dma_done, dma_int_set.slv_rd_dma_done) \ + item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done, dma_int_raw.slv_wr_dma_done, dma_int_clr.slv_wr_dma_done, dma_int_set.slv_wr_dma_done) \ + item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done, dma_int_raw.dma_seg_trans_done, dma_int_clr.dma_seg_trans_done, dma_int_set.dma_seg_trans_done) \ + item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7, dma_int_raw.slv_cmd7, dma_int_clr.slv_cmd7, dma_int_set.slv_cmd7) \ + item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8, dma_int_raw.slv_cmd8, dma_int_clr.slv_cmd8, dma_int_set.slv_cmd8) \ + item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9, dma_int_raw.slv_cmd9, dma_int_clr.slv_cmd9, dma_int_set.slv_cmd9) \ + item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda, dma_int_raw.slv_cmda, dma_int_clr.slv_cmda, dma_int_set.slv_cmda) static inline void spi_ll_enable_intr(spi_dev_t *hw, spi_ll_intr_t intr_mask) diff --git a/components/hal/esp32h4/include/hal/spi_ll.h b/components/hal/esp32h4/include/hal/spi_ll.h index a198b6342b..a49cd91e19 100644 --- a/components/hal/esp32h4/include/hal/spi_ll.h +++ b/components/hal/esp32h4/include/hal/spi_ll.h @@ -55,6 +55,8 @@ typedef spi_dev_t spi_dma_dev_t; // Type definition of all supported interrupts typedef enum { SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done + SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened + SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD. SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD. SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD. @@ -1120,16 +1122,18 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) //helper macros to generate code for each interrupts #define FOR_EACH_ITEM(op, list) do { list(op) } while(0) #define INTR_LIST(item) \ - item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int_ena, dma_int_raw.trans_done_int_raw, dma_int_clr.trans_done_int_clr, dma_int_set.trans_done_int_set) \ - item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int_ena, dma_int_raw.slv_rd_buf_done_int_raw, dma_int_clr.slv_rd_buf_done_int_clr, dma_int_set.slv_rd_buf_done_int_set) \ - item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int_ena, dma_int_raw.slv_wr_buf_done_int_raw, dma_int_clr.slv_wr_buf_done_int_clr, dma_int_set.slv_wr_buf_done_int_set) \ - item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int_ena, dma_int_raw.slv_rd_dma_done_int_raw, dma_int_clr.slv_rd_dma_done_int_clr, dma_int_set.slv_rd_dma_done_int_set) \ - item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int_ena, dma_int_raw.slv_wr_dma_done_int_raw, dma_int_clr.slv_wr_dma_done_int_clr, dma_int_set.slv_wr_dma_done_int_set) \ - item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int_ena, dma_int_raw.dma_seg_trans_done_int_raw, dma_int_clr.dma_seg_trans_done_int_clr, dma_int_set.dma_seg_trans_done_int_set) \ - item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int_ena, dma_int_raw.slv_cmd7_int_raw, dma_int_clr.slv_cmd7_int_clr, dma_int_set.slv_cmd7_int_set) \ - item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int_ena, dma_int_raw.slv_cmd8_int_raw, dma_int_clr.slv_cmd8_int_clr, dma_int_set.slv_cmd8_int_set) \ - item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int_ena, dma_int_raw.slv_cmd9_int_raw, dma_int_clr.slv_cmd9_int_clr, dma_int_set.slv_cmd9_int_set) \ - item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int_ena, dma_int_raw.slv_cmda_int_raw, dma_int_clr.slv_cmda_int_clr, dma_int_set.slv_cmda_int_set) + item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int_ena, dma_int_raw.trans_done_int_raw, dma_int_clr.trans_done_int_clr, dma_int_set.trans_done_int_set) \ + item(SPI_LL_INTR_IN_FULL, dma_int_ena.dma_infifo_full_err_int_ena, dma_int_raw.dma_infifo_full_err_int_raw, dma_int_clr.dma_infifo_full_err_int_clr, dma_int_set.dma_infifo_full_err_int_set) \ + item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.dma_outfifo_empty_err_int_ena, dma_int_raw.dma_outfifo_empty_err_int_raw, dma_int_clr.dma_outfifo_empty_err_int_clr, dma_int_set.dma_outfifo_empty_err_int_set) \ + item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int_ena, dma_int_raw.slv_rd_buf_done_int_raw, dma_int_clr.slv_rd_buf_done_int_clr, dma_int_set.slv_rd_buf_done_int_set) \ + item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int_ena, dma_int_raw.slv_wr_buf_done_int_raw, dma_int_clr.slv_wr_buf_done_int_clr, dma_int_set.slv_wr_buf_done_int_set) \ + item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int_ena, dma_int_raw.slv_rd_dma_done_int_raw, dma_int_clr.slv_rd_dma_done_int_clr, dma_int_set.slv_rd_dma_done_int_set) \ + item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int_ena, dma_int_raw.slv_wr_dma_done_int_raw, dma_int_clr.slv_wr_dma_done_int_clr, dma_int_set.slv_wr_dma_done_int_set) \ + item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int_ena, dma_int_raw.dma_seg_trans_done_int_raw, dma_int_clr.dma_seg_trans_done_int_clr, dma_int_set.dma_seg_trans_done_int_set) \ + item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int_ena, dma_int_raw.slv_cmd7_int_raw, dma_int_clr.slv_cmd7_int_clr, dma_int_set.slv_cmd7_int_set) \ + item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int_ena, dma_int_raw.slv_cmd8_int_raw, dma_int_clr.slv_cmd8_int_clr, dma_int_set.slv_cmd8_int_set) \ + item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int_ena, dma_int_raw.slv_cmd9_int_raw, dma_int_clr.slv_cmd9_int_clr, dma_int_set.slv_cmd9_int_set) \ + item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int_ena, dma_int_raw.slv_cmda_int_raw, dma_int_clr.slv_cmda_int_clr, dma_int_set.slv_cmda_int_set) static inline void spi_ll_enable_intr(spi_dev_t *hw, spi_ll_intr_t intr_mask) diff --git a/components/hal/esp32p4/include/hal/spi_ll.h b/components/hal/esp32p4/include/hal/spi_ll.h index af6c45b85c..7411d7716f 100644 --- a/components/hal/esp32p4/include/hal/spi_ll.h +++ b/components/hal/esp32p4/include/hal/spi_ll.h @@ -57,6 +57,8 @@ typedef spi_dev_t spi_dma_dev_t; // Type definition of all supported interrupts typedef enum { SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done + SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened + SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD. SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD. SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD. @@ -1171,16 +1173,18 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) //helper macros to generate code for each interrupts #define FOR_EACH_ITEM(op, list) do { list(op) } while(0) #define INTR_LIST(item) \ - item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int, dma_int_raw.trans_done_int, dma_int_clr.trans_done_int, dma_int_set.trans_done_int) \ - item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int, dma_int_raw.slv_rd_buf_done_int, dma_int_clr.slv_rd_buf_done_int, dma_int_set.slv_rd_buf_done_int) \ - item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int, dma_int_raw.slv_wr_buf_done_int, dma_int_clr.slv_wr_buf_done_int, dma_int_set.slv_wr_buf_done_int) \ - item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int, dma_int_raw.slv_rd_dma_done_int, dma_int_clr.slv_rd_dma_done_int, dma_int_set.slv_rd_dma_done_int) \ - item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int, dma_int_raw.slv_wr_dma_done_int, dma_int_clr.slv_wr_dma_done_int, dma_int_set.slv_wr_dma_done_int) \ - item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int, dma_int_raw.dma_seg_trans_done_int, dma_int_clr.dma_seg_trans_done_int, dma_int_set.dma_seg_trans_done_int) \ - item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int, dma_int_raw.slv_cmd7_int, dma_int_clr.slv_cmd7_int, dma_int_set.slv_cmd7_int) \ - item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int, dma_int_raw.slv_cmd8_int, dma_int_clr.slv_cmd8_int, dma_int_set.slv_cmd8_int) \ - item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int, dma_int_raw.slv_cmd9_int, dma_int_clr.slv_cmd9_int, dma_int_set.slv_cmd9_int) \ - item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int, dma_int_raw.slv_cmda_int, dma_int_clr.slv_cmda_int, dma_int_set.slv_cmda_int) + item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done_int, dma_int_raw.trans_done_int, dma_int_clr.trans_done_int, dma_int_set.trans_done_int) \ + item(SPI_LL_INTR_IN_FULL, dma_int_ena.dma_infifo_full_err_int, dma_int_raw.dma_infifo_full_err_int, dma_int_clr.dma_infifo_full_err_int, dma_int_set.dma_infifo_full_err_int) \ + item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.dma_outfifo_empty_err_int, dma_int_raw.dma_outfifo_empty_err_int, dma_int_clr.dma_outfifo_empty_err_int, dma_int_set.dma_outfifo_empty_err_int) \ + item(SPI_LL_INTR_RDBUF, dma_int_ena.slv_rd_buf_done_int, dma_int_raw.slv_rd_buf_done_int, dma_int_clr.slv_rd_buf_done_int, dma_int_set.slv_rd_buf_done_int) \ + item(SPI_LL_INTR_WRBUF, dma_int_ena.slv_wr_buf_done_int, dma_int_raw.slv_wr_buf_done_int, dma_int_clr.slv_wr_buf_done_int, dma_int_set.slv_wr_buf_done_int) \ + item(SPI_LL_INTR_RDDMA, dma_int_ena.slv_rd_dma_done_int, dma_int_raw.slv_rd_dma_done_int, dma_int_clr.slv_rd_dma_done_int, dma_int_set.slv_rd_dma_done_int) \ + item(SPI_LL_INTR_WRDMA, dma_int_ena.slv_wr_dma_done_int, dma_int_raw.slv_wr_dma_done_int, dma_int_clr.slv_wr_dma_done_int, dma_int_set.slv_wr_dma_done_int) \ + item(SPI_LL_INTR_SEG_DONE, dma_int_ena.dma_seg_trans_done_int, dma_int_raw.dma_seg_trans_done_int, dma_int_clr.dma_seg_trans_done_int, dma_int_set.dma_seg_trans_done_int) \ + item(SPI_LL_INTR_CMD7, dma_int_ena.slv_cmd7_int, dma_int_raw.slv_cmd7_int, dma_int_clr.slv_cmd7_int, dma_int_set.slv_cmd7_int) \ + item(SPI_LL_INTR_CMD8, dma_int_ena.slv_cmd8_int, dma_int_raw.slv_cmd8_int, dma_int_clr.slv_cmd8_int, dma_int_set.slv_cmd8_int) \ + item(SPI_LL_INTR_CMD9, dma_int_ena.slv_cmd9_int, dma_int_raw.slv_cmd9_int, dma_int_clr.slv_cmd9_int, dma_int_set.slv_cmd9_int) \ + item(SPI_LL_INTR_CMDA, dma_int_ena.slv_cmda_int, dma_int_raw.slv_cmda_int, dma_int_clr.slv_cmda_int, dma_int_set.slv_cmda_int) static inline void spi_ll_enable_intr(spi_dev_t *hw, spi_ll_intr_t intr_mask) diff --git a/components/hal/esp32s3/include/hal/spi_ll.h b/components/hal/esp32s3/include/hal/spi_ll.h index acc71ba64b..50f83f4d03 100644 --- a/components/hal/esp32s3/include/hal/spi_ll.h +++ b/components/hal/esp32s3/include/hal/spi_ll.h @@ -55,6 +55,8 @@ typedef spi_dev_t spi_dma_dev_t; // Type definition of all supported interrupts typedef enum { SPI_LL_INTR_TRANS_DONE = BIT(0), ///< A transaction has done + SPI_LL_INTR_IN_FULL = BIT(4), ///< DMA in_full error happened + SPI_LL_INTR_OUT_EMPTY = BIT(5), ///< DMA out_empty error happened SPI_LL_INTR_RDBUF = BIT(6), ///< Has received RDBUF command. Only available in slave HD. SPI_LL_INTR_WRBUF = BIT(7), ///< Has received WRBUF command. Only available in slave HD. SPI_LL_INTR_RDDMA = BIT(8), ///< Has received RDDMA command. Only available in slave HD. @@ -1112,6 +1114,8 @@ static inline uint32_t spi_ll_slave_get_rcv_bitlen(spi_dev_t *hw) #define FOR_EACH_ITEM(op, list) do { list(op) } while(0) #define INTR_LIST(item) \ item(SPI_LL_INTR_TRANS_DONE, dma_int_ena.trans_done, dma_int_raw.trans_done, dma_int_clr.trans_done, dma_int_set.trans_done_int_set) \ + item(SPI_LL_INTR_IN_FULL, dma_int_ena.infifo_full_err, dma_int_raw.infifo_full_err, dma_int_clr.infifo_full_err, dma_int_set.infifo_full_err_int_set) \ + item(SPI_LL_INTR_OUT_EMPTY, dma_int_ena.outfifo_empty_err, dma_int_raw.outfifo_empty_err, dma_int_clr.outfifo_empty_err, dma_int_set.outfifo_empty_err_int_set) \ item(SPI_LL_INTR_RDBUF, dma_int_ena.rd_buf_done, dma_int_raw.rd_buf_done, dma_int_clr.rd_buf_done, dma_int_set.rd_buf_done_int_set) \ item(SPI_LL_INTR_WRBUF, dma_int_ena.wr_buf_done, dma_int_raw.wr_buf_done, dma_int_clr.wr_buf_done, dma_int_set.wr_buf_done_int_set) \ item(SPI_LL_INTR_RDDMA, dma_int_ena.rd_dma_done, dma_int_raw.rd_dma_done, dma_int_clr.rd_dma_done, dma_int_set.rd_dma_done_int_set) \ diff --git a/components/hal/include/hal/spi_hal.h b/components/hal/include/hal/spi_hal.h index c9d2e136e8..3dacc434c8 100644 --- a/components/hal/include/hal/spi_hal.h +++ b/components/hal/include/hal/spi_hal.h @@ -240,6 +240,23 @@ void spi_hal_user_start(const spi_hal_context_t *hal); */ bool spi_hal_usr_is_done(const spi_hal_context_t *hal); +/** + * Get SPI interrupt bits status by mask + * + * @param hal Context of the HAL layer. + * @param mask Mask of the interrupt bits to check. + * @return True if the masked interrupts are set, false otherwise. + */ +bool spi_hal_get_intr_mask(spi_hal_context_t *hal, uint32_t mask); + +/** + * Clear SPI interrupt bits by mask + * + * @param hal Context of the HAL layer. + * @param mask Mask of the interrupt bits to clear. + */ +void spi_hal_clear_intr_mask(spi_hal_context_t *hal, uint32_t mask); + /** * Setup transaction operations, write tx buffer to HW registers * @@ -342,16 +359,6 @@ void spi_hal_sct_deinit(spi_hal_context_t *hal); */ void spi_hal_sct_set_conf_bits_len(spi_hal_context_t *hal, uint32_t conf_len); -/** - * Clear SPI interrupt bits by mask - */ -void spi_hal_clear_intr_mask(spi_hal_context_t *hal, uint32_t mask); - -/** - * Get SPI interrupt bits status by mask - */ -bool spi_hal_get_intr_mask(spi_hal_context_t *hal, uint32_t mask); - /** * Set conf_bitslen base to HW for sct, only supported on s2. */ diff --git a/components/hal/spi_hal_iram.c b/components/hal/spi_hal_iram.c index cfde4fd518..2c1dd4e7ea 100644 --- a/components/hal/spi_hal_iram.c +++ b/components/hal/spi_hal_iram.c @@ -234,6 +234,16 @@ bool spi_hal_usr_is_done(const spi_hal_context_t *hal) return spi_ll_usr_is_done(hal->hw); } +#if SOC_SPI_SUPPORT_SLAVE_HD_VER2 +bool spi_hal_get_intr_mask(spi_hal_context_t *hal, uint32_t mask) { + return spi_ll_get_intr(hal->hw, mask); +} + +void spi_hal_clear_intr_mask(spi_hal_context_t *hal, uint32_t mask) { + spi_ll_clear_intr(hal->hw, mask); +} +#endif + void spi_hal_push_tx_buffer(const spi_hal_context_t *hal, const spi_hal_trans_config_t *hal_trans) { if (hal_trans->send_buffer) { @@ -255,15 +265,7 @@ void spi_hal_fetch_result(const spi_hal_context_t *hal) #if SOC_SPI_SCT_SUPPORTED /*------------------------------------------------------------------------------ * Segmented-Configure-Transfer - *----------------------------------------------------------------------------*/ -void spi_hal_clear_intr_mask(spi_hal_context_t *hal, uint32_t mask) { - spi_ll_clear_intr(hal->hw, mask); -} - -bool spi_hal_get_intr_mask(spi_hal_context_t *hal, uint32_t mask) { - return spi_ll_get_intr(hal->hw, mask); -} - +*----------------------------------------------------------------------------*/ void spi_hal_sct_set_conf_bits_len(spi_hal_context_t *hal, uint32_t conf_len) { spi_ll_set_conf_phase_bits_len(hal->hw, conf_len); } diff --git a/docs/en/api-reference/peripherals/spi_master.rst b/docs/en/api-reference/peripherals/spi_master.rst index b0d291a569..c7b75de67c 100644 --- a/docs/en/api-reference/peripherals/spi_master.rst +++ b/docs/en/api-reference/peripherals/spi_master.rst @@ -353,6 +353,15 @@ Driver Usage The example code for the SPI Master driver can be found in the :example:`peripherals/spi_master` directory of ESP-IDF examples. +.. only:: SOC_PSRAM_DMA_CAPABLE + + Transactions with Data on PSRAM + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + {IDF_TARGET_NAME} supports GPSPI Master with DMA transferring data from/to PSRAM directly without extra internal copy process, by adding :c:macro:`SPI_TRANS_DMA_USE_PSRAM` flag to the transaction. Some requirements for PSRAM transactions are: + + 1. The data memory **address** and **transaction length** must both be aligned to cache length, usually the cache length is 16/32/64 bytes. + 2. This feature shares bandwidth with MSPI bus, so GPSPI transfer bandwidth should be less than PSRAM bandwidth, **otherwise transmission data may be lost**. You can check the :c:macro:`SPI_TRANS_DMA_RX_FAIL` and :c:macro:`SPI_TRANS_DMA_TX_FAIL` flags after the transaction is finished to check if error occurs during the transmission. Transactions with Data Not Exceeding 32 Bits ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/zh_CN/api-reference/peripherals/spi_master.rst b/docs/zh_CN/api-reference/peripherals/spi_master.rst index e6856266b4..5fd307fd9e 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_master.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_master.rst @@ -353,6 +353,15 @@ SPI 总线传输事务由五个阶段构成,详见下表(任意阶段均可 SPI 主机驱动程序的示例代码存放在 ESP-IDF 示例项目的 :example:`peripherals/spi_master` 目录下。 +.. only:: SOC_PSRAM_DMA_CAPABLE + + 使用 PSRAM 的传输事务 + ^^^^^^^^^^^^^^^^^^^^^^ + + {IDF_TARGET_NAME} 支持 GPSPI Master 通过 DMA 直接传输 PSRAM 存储的数据而不用内部额外的拷贝过程,在传输配置中添加 :c:macro:`SPI_TRANS_DMA_USE_PSRAM` 标志信号即可使用。使用 PSRAM 传输事务时,请注意以下几点: + + 1. 数据内存 **地址** 和 **传输长度** 都需要与 Cache 长度对齐,通常 Cache 长度为 16/32/64 字节。 + 2. 因为该功能与 MSPI 总线共享带宽,因此 GPSPI 传输带宽应小于 PSRAM 带宽,否则 **可能会丢失传输数据**。可通过在传输结束时检查 :c:macro:`SPI_TRANS_DMA_RX_FAIL` 和 :c:macro:`SPI_TRANS_DMA_TX_FAIL` 标志信号来判断传输是否发生了错误。 传输数据小于 32 位的传输事务 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^