From dd9a5b7719677c40932ccc4c3c01b11769a6e210 Mon Sep 17 00:00:00 2001 From: wanckl Date: Tue, 16 Dec 2025 20:04:10 +0800 Subject: [PATCH] feature(driver_spi): slave driver support psram transfer --- .../include/test_spi_utils.h | 7 +- .../test_driver_utils/test_spi_utils.c | 20 +-- .../esp_driver_spi/include/driver/spi_slave.h | 2 + .../include/esp_private/spi_common_internal.h | 17 +++ .../esp_driver_spi/src/gpspi/spi_common.c | 55 ++++++- .../esp_driver_spi/src/gpspi/spi_master.c | 73 ++------- .../esp_driver_spi/src/gpspi/spi_slave.c | 140 ++++++------------ .../test_apps/master/main/test_spi_master.c | 14 +- .../test_apps/master/main/test_spi_sio.c | 2 +- .../test_apps/slave/main/test_spi_slave.c | 139 +++++++++++++---- .../slave/sdkconfig.ci.release.esp32c5 | 1 + .../slave/sdkconfig.ci.release.esp32c61 | 1 + .../slave/sdkconfig.ci.release.esp32h4 | 1 + .../slave/sdkconfig.ci.release.esp32p4 | 1 + .../slave/sdkconfig.ci.release.esp32s3 | 1 + .../slave_hd/main/test_spi_slave_hd.c | 8 +- .../esp_hal_gpspi/include/hal/spi_slave_hal.h | 17 +++ components/esp_hal_gpspi/spi_slave_hal_iram.c | 12 ++ .../api-reference/peripherals/spi_slave.rst | 8 + .../api-reference/peripherals/spi_slave.rst | 8 + 20 files changed, 313 insertions(+), 214 deletions(-) create mode 100644 components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c5 create mode 100644 components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c61 create mode 100644 components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32h4 create mode 100644 components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32p4 create mode 100644 components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32s3 diff --git a/components/driver/test_apps/components/test_driver_utils/include/test_spi_utils.h b/components/driver/test_apps/components/test_driver_utils/include/test_spi_utils.h index e5da02e8aa2..55d843484d0 100644 --- a/components/driver/test_apps/components/test_driver_utils/include/test_spi_utils.h +++ b/components/driver/test_apps/components/test_driver_utils/include/test_spi_utils.h @@ -266,10 +266,9 @@ void spitest_gpio_output_sel(uint32_t gpio_num, int func, uint32_t signal_idx); //use this function to fix the input source when assign multiple functions to a same pin void spitest_gpio_input_sel(uint32_t gpio_num, int func, uint32_t signal_idx); -//Note this cs_num is the ID of the connected devices' ID, e.g. if 2 devices are connected to the bus, -//then the cs_num of the 1st and 2nd devices are 0 and 1 respectively. -//Enable `soft_master` to connect to soft spi master instead of hardware master. -void same_pin_func_sel(spi_bus_config_t bus, uint8_t cs_pin, uint8_t cs_dev_id, bool soft_master); +// Connect master and slave to the same pin +// master_id and slave_id are the IDs of the master and slave devices, set 0 for each to use soft master/slave. +void same_pin_func_sel(spi_host_device_t master_id, spi_host_device_t slave_id, spi_bus_config_t bus, uint8_t cs_pin); // Soft simulated spi master host for slave testing // `speed_hz` max 500kHz diff --git a/components/driver/test_apps/components/test_driver_utils/test_spi_utils.c b/components/driver/test_apps/components/test_driver_utils/test_spi_utils.c index 6f4e657d013..fcdd17882cf 100644 --- a/components/driver/test_apps/components/test_driver_utils/test_spi_utils.c +++ b/components/driver/test_apps/components/test_driver_utils/test_spi_utils.c @@ -224,20 +224,20 @@ void spitest_gpio_input_sel(uint32_t gpio_num, int func, uint32_t signal_idx) esp_rom_gpio_connect_in_signal(gpio_num, signal_idx, 0); } -void same_pin_func_sel(spi_bus_config_t bus, uint8_t cs_pin, uint8_t cs_dev_id, bool soft_master) +void same_pin_func_sel(spi_host_device_t master_id, spi_host_device_t slave_id, spi_bus_config_t bus, uint8_t cs_pin) { - spitest_gpio_output_sel(bus.mosi_io_num, FUNC_GPIO, soft_master ? SIG_GPIO_OUT_IDX : spi_periph_signal[TEST_SPI_HOST].spid_out); - spitest_gpio_input_sel(bus.mosi_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spid_in); + spitest_gpio_output_sel(bus.mosi_io_num, FUNC_GPIO, (!master_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[master_id].spid_out); + spitest_gpio_input_sel(bus.mosi_io_num, FUNC_GPIO, (!slave_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[slave_id].spid_in); - spitest_gpio_output_sel(bus.miso_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiq_out); - spitest_gpio_input_sel(bus.miso_io_num, FUNC_GPIO, soft_master ? SIG_GPIO_OUT_IDX : spi_periph_signal[TEST_SPI_HOST].spiq_in); + spitest_gpio_input_sel(bus.miso_io_num, FUNC_GPIO, (!master_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[master_id].spiq_in); + spitest_gpio_output_sel(bus.miso_io_num, FUNC_GPIO, (!slave_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[slave_id].spiq_out); - gpio_set_level(cs_pin, 1); //ensure CS is inactive when select to soft_master and before transaction start - spitest_gpio_output_sel(cs_pin, FUNC_GPIO, soft_master ? SIG_GPIO_OUT_IDX : spi_periph_signal[TEST_SPI_HOST].spics_out[cs_dev_id]); - spitest_gpio_input_sel(cs_pin, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spics_in); + gpio_set_level(cs_pin, 1); //ensure CS is inactive when select 0 for soft_master and before transaction start + spitest_gpio_output_sel(cs_pin, FUNC_GPIO, (!master_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[master_id].spics_out[0]); + spitest_gpio_input_sel(cs_pin, FUNC_GPIO, (!slave_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[slave_id].spics_in); - spitest_gpio_output_sel(bus.sclk_io_num, FUNC_GPIO, soft_master ? SIG_GPIO_OUT_IDX : spi_periph_signal[TEST_SPI_HOST].spiclk_out); - spitest_gpio_input_sel(bus.sclk_io_num, FUNC_GPIO, spi_periph_signal[TEST_SLAVE_HOST].spiclk_in); + spitest_gpio_output_sel(bus.sclk_io_num, FUNC_GPIO, (!master_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[master_id].spiclk_out); + spitest_gpio_input_sel(bus.sclk_io_num, FUNC_GPIO, (!slave_id) ? SIG_GPIO_OUT_IDX : spi_periph_signal[slave_id].spiclk_in); } #define GPIO_MAX_FREQ 500*1000 //max of soft spi clock at delay(0) diff --git a/components/esp_driver_spi/include/driver/spi_slave.h b/components/esp_driver_spi/include/driver/spi_slave.h index b22b0078dd6..4a9e8ba3d0c 100644 --- a/components/esp_driver_spi/include/driver/spi_slave.h +++ b/components/esp_driver_spi/include/driver/spi_slave.h @@ -185,6 +185,7 @@ esp_err_t spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transact * @return * - ESP_ERR_INVALID_ARG if parameter is invalid * - ESP_ERR_NOT_SUPPORTED if flag `SPI_SLAVE_NO_RETURN_RESULT` is set + * - ESP_ERR_INVALID_STATE if dma over/underflow error occurs during psram transfer * - ESP_OK on success */ esp_err_t spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, uint32_t ticks_to_wait); @@ -204,6 +205,7 @@ esp_err_t spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transacti * out. * @return * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if dma over/underflow error occurs during psram transfer * - ESP_OK on success */ esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, uint32_t ticks_to_wait); diff --git a/components/esp_driver_spi/include/esp_private/spi_common_internal.h b/components/esp_driver_spi/include/esp_private/spi_common_internal.h index f72d54e9d54..b0c0a6ffae4 100644 --- a/components/esp_driver_spi/include/esp_private/spi_common_internal.h +++ b/components/esp_driver_spi/include/esp_private/spi_common_internal.h @@ -135,6 +135,23 @@ esp_err_t spicommon_dma_desc_alloc(spi_host_device_t host_id, int cfg_max_sz, in */ void spicommon_dma_desc_setup_link(spi_dma_desc_t *dmadesc, const void *data, int len, bool is_rx); +/** + * @brief Setup private buffer for DMA transfer + * + * @param host_id SPI host ID to access the DMA context + * @param buffer buffer to be setup + * @param len length of buffer, in byte + * @param is_tx if buffer is for tx/transmit direction + * @param psram_prefer if psram is preferred + * @param auto_malloc if auto malloc is enabled + * @param ret_buffer return buffer, which is the buffer that is actually used for DMA transfer + * + * @return + * - ESP_OK: On success + * - ESP_ERR_NO_MEM: No enough memory + */ +esp_err_t spicommon_dma_setup_priv_buffer(spi_host_device_t host_id, uint32_t *buffer, uint32_t len, bool is_tx, bool psram_prefer, bool auto_malloc, uint32_t **ret_buffer); + /** * @brief Free DMA for SPI * diff --git a/components/esp_driver_spi/src/gpspi/spi_common.c b/components/esp_driver_spi/src/gpspi/spi_common.c index 04018ed5644..67e2f179b8b 100644 --- a/components/esp_driver_spi/src/gpspi/spi_common.c +++ b/components/esp_driver_spi/src/gpspi/spi_common.c @@ -13,6 +13,7 @@ #include "esp_check.h" #include "esp_cache.h" #include "esp_heap_caps.h" +#include "esp_memory_utils.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "esp_private/gpio.h" @@ -380,6 +381,56 @@ void SPI_COMMON_ISR_ATTR spicommon_dma_desc_setup_link(spi_dma_desc_t *dmadesc, dmadesc[n - 1].next = NULL; } +esp_err_t SPI_COMMON_ISR_ATTR spicommon_dma_setup_priv_buffer(spi_host_device_t host_id, uint32_t *buffer, uint32_t len, bool is_tx, bool psram_prefer, bool auto_malloc, uint32_t **ret_buffer) +{ + spi_bus_attr_t *bus_attr = spi_bus_get_attr(host_id); + spi_dma_ctx_t *dma_ctx = spi_bus_get_dma_ctx(host_id); + assert(bus_attr && dma_ctx); + if ((buffer == NULL) || (len == 0)) { + *ret_buffer = buffer; + return ESP_OK; + } + + bool is_ptr_ext = esp_ptr_external_ram(buffer); + bool use_psram = is_ptr_ext && psram_prefer; +#if SOC_IS(ESP32S2) + ESP_RETURN_ON_FALSE_ISR((host_id != SPI3_HOST) || !use_psram, ESP_ERR_NOT_SUPPORTED, SPI_TAG, "SPI3 does not support external memory"); +#endif + bool need_malloc = is_ptr_ext ? (!use_psram || !esp_ptr_dma_ext_capable(buffer)) : !esp_ptr_dma_capable(buffer); + // If psram is wanted, re-malloc also from psram. + uint32_t mem_cap = MALLOC_CAP_DMA | (use_psram ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL); + uint16_t alignment = 0; + if (is_tx) { + alignment = use_psram ? dma_ctx->dma_align_tx_ext : dma_ctx->dma_align_tx_int; + } else { + // RX cache sync still need consider the cache alignment requirement + if (use_psram) { + alignment = MAX(dma_ctx->dma_align_rx_ext, bus_attr->cache_align_ext); + } else { + alignment = MAX(dma_ctx->dma_align_rx_int, bus_attr->cache_align_int); + } + } + need_malloc |= (((uint32_t)buffer | len) & (alignment - 1)); + uint32_t align_len = (len + alignment - 1) & (~(alignment - 1)); // up align alignment + ESP_EARLY_LOGV(SPI_TAG, "SPI%d %s %p, len %d, is_ptr_ext %d, use_psram: %d, alignment: %d, need_malloc: %d from %s", host_id + 1, is_tx ? "TX" : "RX", buffer, len, is_ptr_ext, use_psram, alignment, need_malloc, (mem_cap & MALLOC_CAP_SPIRAM) ? "psram" : "internal"); + if (need_malloc) { + ESP_RETURN_ON_FALSE_ISR(auto_malloc, ESP_ERR_INVALID_STATE, SPI_TAG, "%s addr&len not align to %d, or not dma_capable, suggest use 'heap_caps_malloc' or enable auto_align", is_tx ? "TX" : "RX", alignment); + uint32_t *temp = heap_caps_aligned_alloc(alignment, align_len, mem_cap); + 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; + } + *ret_buffer = buffer; + uint32_t sync_flags = is_tx ? (ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED) : ESP_CACHE_MSYNC_FLAG_DIR_M2C; + esp_err_t ret = esp_cache_msync((void *)buffer, need_malloc ? align_len : len, sync_flags); + // ESP_ERR_NOT_SUPPORTED stands for not cache sync required, it's allowed here + ESP_RETURN_ON_FALSE_ISR((ret == ESP_OK) || (ret == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_ARG, SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX"); + return ESP_OK; +} + //----------------------------------------------------------free dma periph-------------------------------------------------------// esp_err_t spicommon_dma_chan_free(spi_host_device_t host_id) { @@ -943,7 +994,7 @@ void *spi_bus_dma_memory_alloc(spi_host_device_t host_id, size_t size, uint32_t return heap_caps_aligned_calloc(alignment, 1, size, extra_heap_caps | MALLOC_CAP_DMA); } -spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id) +SPI_COMMON_ISR_ATTR spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id) { if (bus_ctx[host_id] == NULL) { return NULL; @@ -952,7 +1003,7 @@ spi_bus_attr_t* spi_bus_get_attr(spi_host_device_t host_id) return &bus_ctx[host_id]->bus_attr; } -spi_dma_ctx_t* spi_bus_get_dma_ctx(spi_host_device_t host_id) +SPI_COMMON_ISR_ATTR spi_dma_ctx_t* spi_bus_get_dma_ctx(spi_host_device_t host_id) { return spi_dma_ctx[host_id]; } diff --git a/components/esp_driver_spi/src/gpspi/spi_master.c b/components/esp_driver_spi/src/gpspi/spi_master.c index 9ddef9900b2..59e32f9cfb5 100644 --- a/components/esp_driver_spi/src/gpspi/spi_master.c +++ b/components/esp_driver_spi/src/gpspi/spi_master.c @@ -124,10 +124,10 @@ We have two bits to control the interrupt: #include "esp_ipc.h" #include "esp_cache.h" #include "esp_heap_caps.h" +#include "esp_memory_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" -#include "soc/soc_memory_layout.h" #include "driver/gpio.h" #include "hal/spi_hal.h" #include "hal/spi_ll.h" @@ -1170,47 +1170,6 @@ 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(spi_host_t *host, uint32_t *buffer, uint32_t len, bool is_tx, uint32_t flags, uint32_t **ret_buffer) -{ -#if CONFIG_IDF_TARGET_ESP32S2 - ESP_RETURN_ON_FALSE_ISR((host->id != SPI3_HOST) || !(flags & SPI_TRANS_DMA_USE_PSRAM), ESP_ERR_NOT_SUPPORTED, SPI_TAG, "SPI3 does not support external memory"); -#endif - bool is_ptr_ext = esp_ptr_external_ram(buffer); - bool use_psram = is_ptr_ext && (flags & SPI_TRANS_DMA_USE_PSRAM); - bool need_malloc = is_ptr_ext ? (!use_psram || !esp_ptr_dma_ext_capable(buffer)) : !esp_ptr_dma_capable(buffer); - uint16_t alignment = 0; - // If psram is wanted, re-malloc also from psram. - uint32_t mem_cap = MALLOC_CAP_DMA | (use_psram ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL); - if (is_tx) { - alignment = use_psram ? host->dma_ctx->dma_align_tx_ext : host->dma_ctx->dma_align_tx_int; - } else { - // RX cache sync still need consider the cache alignment requirement - if (use_psram) { - alignment = MAX(host->dma_ctx->dma_align_rx_ext, host->bus_attr->cache_align_ext); - } else { - alignment = MAX(host->dma_ctx->dma_align_rx_int, host->bus_attr->cache_align_int); - } - } - need_malloc |= (((uint32_t)buffer | len) & (alignment - 1)); - ESP_EARLY_LOGV(SPI_TAG, "%s %p, len %d, is_ptr_ext %d, use_psram: %d, alignment: %d, need_malloc: %d from %s", is_tx ? "TX" : "RX", buffer, len, is_ptr_ext, use_psram, alignment, need_malloc, (mem_cap & MALLOC_CAP_SPIRAM) ? "psram" : "internal"); - if (need_malloc) { - 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); - len = (len + alignment - 1) & (~(alignment - 1)); // up align alignment - uint32_t *temp = heap_caps_aligned_alloc(alignment, len, mem_cap); - 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; - } - esp_err_t ret = esp_cache_msync((void *)buffer, len, is_tx ? (ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED) : ESP_CACHE_MSYNC_FLAG_DIR_M2C); - // ESP_ERR_NOT_SUPPORTED stands for not cache sync required, it's allowed here - ESP_RETURN_ON_FALSE_ISR((ret == ESP_OK) || (ret == ESP_ERR_NOT_SUPPORTED), ESP_ERR_INVALID_ARG, SPI_TAG, "sync failed for %s buffer", is_tx ? "TX" : "RX"); - *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; @@ -1220,25 +1179,23 @@ static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_host_t *host, spi_trans uint32_t* rcv_ptr = (trans_desc->flags & SPI_TRANS_USE_RXDATA) ? (uint32_t *)trans_desc->rx_data : (uint32_t *)trans_desc->rx_buffer; // tx memory assign uint32_t *send_ptr = (trans_desc->flags & SPI_TRANS_USE_TXDATA) ? (uint32_t *)trans_desc->tx_data : (uint32_t *)trans_desc->tx_buffer; - - esp_err_t ret = ESP_OK; - if (send_ptr && bus_attr->dma_enabled) { - ret = setup_dma_priv_buffer(host, send_ptr, (trans_desc->length + 7) / 8, true, trans_desc->flags, &send_ptr); - if (ret != ESP_OK) { - goto clean_up; - } + if (!bus_attr->dma_enabled) { + priv_desc->buffer_to_send = send_ptr; + priv_desc->buffer_to_rcv = rcv_ptr; + return ESP_OK; } - if (rcv_ptr && bus_attr->dma_enabled) { - ret = setup_dma_priv_buffer(host, rcv_ptr, (trans_desc->rxlength + 7) / 8, false, trans_desc->flags, &rcv_ptr); - if (ret != ESP_OK) { - goto clean_up; - } + bool use_psram = trans_desc->flags & SPI_TRANS_DMA_USE_PSRAM; + bool auto_malloc = !(trans_desc->flags & SPI_TRANS_DMA_BUFFER_ALIGN_MANUAL); + esp_err_t ret = spicommon_dma_setup_priv_buffer(host->id, send_ptr, (trans_desc->length + 7) / 8, true, use_psram, auto_malloc, (void *)&priv_desc->buffer_to_send); + if (ret != ESP_OK) { + goto clean_up; } - - priv_desc->buffer_to_send = send_ptr; - priv_desc->buffer_to_rcv = rcv_ptr; - return ESP_OK; + ret = spicommon_dma_setup_priv_buffer(host->id, rcv_ptr, (trans_desc->rxlength + 7) / 8, false, use_psram, auto_malloc, &priv_desc->buffer_to_rcv); + if (ret != ESP_OK) { + goto clean_up; + } + return ret; clean_up: uninstall_priv_desc(priv_desc); diff --git a/components/esp_driver_spi/src/gpspi/spi_slave.c b/components/esp_driver_spi/src/gpspi/spi_slave.c index 63b5a15b4f7..4664ad342aa 100644 --- a/components/esp_driver_spi/src/gpspi/spi_slave.c +++ b/components/esp_driver_spi/src/gpspi/spi_slave.c @@ -54,8 +54,9 @@ static const char *SPI_TAG = "spi_slave"; /// struct to hold private transaction data (like tx and rx buffer for DMA). typedef struct { spi_slave_transaction_t *trans; //original trans - void *tx_buffer; //actually tx buffer (re-malloced if needed) - void *rx_buffer; //actually rx buffer (re-malloced if needed) + uint32_t *tx_buffer; //actually tx buffer (re-malloced if needed) + uint32_t *rx_buffer; //actually rx buffer (re-malloced if needed) + bool dma_hw_error; //true if DMA hardware over/underflow occurred } spi_slave_trans_priv_t; typedef struct { @@ -73,7 +74,6 @@ typedef struct { QueueHandle_t ret_queue; bool cs_iomux; uint8_t cs_in_signal; - uint16_t internal_mem_align_size; #ifdef CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; #endif @@ -199,14 +199,6 @@ esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *b hal->dmadesc_tx = spihost[host]->dma_ctx->dmadesc_tx; hal->dmadesc_rx = spihost[host]->dma_ctx->dmadesc_rx; hal->dmadesc_n = spihost[host]->dma_ctx->dma_desc_num; - -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - size_t alignment; - esp_cache_get_alignment(MALLOC_CAP_DMA, &alignment); - spihost[host]->internal_mem_align_size = alignment; -#else - spihost[host]->internal_mem_align_size = 4; -#endif } err = spicommon_bus_initialize_io(host, bus_config, SPICOMMON_BUSFLAG_SLAVE | bus_config->flags, NULL); @@ -408,7 +400,6 @@ static void SPI_SLAVE_ISR_ATTR spi_slave_uninstall_priv_trans(spi_host_device_t ESP_EARLY_LOGW(SPI_TAG, "Use DMA but real trans_len is not 4 bytes aligned, slave may loss data"); } #endif -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE if (spihost[host]->bus_attr->dma_enabled) { if (trans->tx_buffer && (trans->tx_buffer != priv_trans->tx_buffer)) { free(priv_trans->tx_buffer); @@ -418,54 +409,28 @@ static void SPI_SLAVE_ISR_ATTR spi_slave_uninstall_priv_trans(spi_host_device_t free(priv_trans->rx_buffer); } } -#endif //SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE } -static esp_err_t SPI_SLAVE_ISR_ATTR spi_slave_setup_priv_trans(spi_host_device_t host, spi_slave_trans_priv_t *priv_trans) +static esp_err_t SPI_SLAVE_ATTR spi_slave_setup_priv_trans(spi_host_device_t host, spi_slave_trans_priv_t *priv_trans) { spi_slave_transaction_t *trans = (spi_slave_transaction_t *)priv_trans->trans; - - priv_trans->tx_buffer = (void *)trans->tx_buffer; - priv_trans->rx_buffer = trans->rx_buffer; - -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - uint16_t alignment = spihost[host]->internal_mem_align_size; - uint32_t buffer_byte_len = (trans->length + 7) / 8; - - if (spihost[host]->bus_attr->dma_enabled && trans->tx_buffer) { - if ((!esp_ptr_dma_capable(trans->tx_buffer) || ((((uint32_t)trans->tx_buffer) | buffer_byte_len) & (alignment - 1)))) { - ESP_RETURN_ON_FALSE_ISR(trans->flags & SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO, ESP_ERR_INVALID_ARG, SPI_TAG, "TX buffer addr&len not align to %d byte, or not dma_capable", alignment); - //if txbuf in the desc not DMA-capable, or not align to "alignment", malloc a new one - ESP_EARLY_LOGD(SPI_TAG, "Allocate TX buffer for DMA"); - buffer_byte_len = (buffer_byte_len + alignment - 1) & (~(alignment - 1)); // up align to "alignment" - uint32_t *temp = heap_caps_aligned_alloc(alignment, buffer_byte_len, MALLOC_CAP_DMA); - if (temp == NULL) { - return ESP_ERR_NO_MEM; - } - - memcpy(temp, trans->tx_buffer, (trans->length + 7) / 8); - priv_trans->tx_buffer = temp; - } - esp_err_t ret = esp_cache_msync((void *)priv_trans->tx_buffer, buffer_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_C2M); - ESP_RETURN_ON_FALSE_ISR(ESP_OK == ret, ESP_ERR_INVALID_STATE, SPI_TAG, "mem sync c2m(writeback) fail"); + if (!spihost[host]->bus_attr->dma_enabled) { + priv_trans->tx_buffer = (uint32_t *)trans->tx_buffer; + priv_trans->rx_buffer = (uint32_t *)trans->rx_buffer; + return ESP_OK; } - if (spihost[host]->bus_attr->dma_enabled && trans->rx_buffer) { - if ((!esp_ptr_dma_capable(trans->rx_buffer) || ((((uint32_t)trans->rx_buffer) | (trans->length + 7) / 8) & (alignment - 1)))) { - ESP_RETURN_ON_FALSE_ISR(trans->flags & SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO, ESP_ERR_INVALID_ARG, SPI_TAG, "RX buffer addr&len not align to %d byte, or not dma_capable", alignment); - //if rxbuf in the desc not DMA-capable, or not align to "alignment", malloc a new one - ESP_EARLY_LOGD(SPI_TAG, "Allocate RX buffer for DMA"); - buffer_byte_len = (buffer_byte_len + alignment - 1) & (~(alignment - 1)); // up align to "alignment" - priv_trans->rx_buffer = heap_caps_aligned_alloc(alignment, buffer_byte_len, MALLOC_CAP_DMA); - if (priv_trans->rx_buffer == NULL) { - free(priv_trans->tx_buffer); - return ESP_ERR_NO_MEM; - } - } - esp_err_t ret = esp_cache_msync((void *)priv_trans->rx_buffer, buffer_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_M2C); - ESP_RETURN_ON_FALSE_ISR(ESP_OK == ret, ESP_ERR_INVALID_STATE, SPI_TAG, "mem sync m2c(invalid) fail"); + + bool auto_malloc = (trans->flags & SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO); + esp_err_t ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->tx_buffer, (trans->length + 7) / 8, true, true, auto_malloc, &priv_trans->tx_buffer); + if (ret != ESP_OK) { + spi_slave_uninstall_priv_trans(host, priv_trans); + return ret; } -#endif //SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - return ESP_OK; + ret = spicommon_dma_setup_priv_buffer(spihost[host]->id, (uint32_t *)trans->rx_buffer, (trans->length + 7) / 8, false, true, auto_malloc, &priv_trans->rx_buffer); + if (ret != ESP_OK) { + spi_slave_uninstall_priv_trans(host, priv_trans); + } + return ret; } esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, uint32_t ticks_to_wait) @@ -473,19 +438,6 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_queue_trans(spi_host_device_t host, const spi BaseType_t r; SPI_CHECK(is_valid_host(host), "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host], "host not slave", ESP_ERR_INVALID_ARG); - SPI_CHECK(spihost[host]->bus_attr->dma_enabled == 0 || trans_desc->tx_buffer == NULL || esp_ptr_dma_capable(trans_desc->tx_buffer), - "txdata not in DMA-capable memory", ESP_ERR_INVALID_ARG); - - // We don't check length WORD alignment for rx when using DMA, seems break DMA requirement, - // however peripheral can also stop DMA from over writing memory even if it not aligned (except esp32). - // ATTENTION!: On esp32, peripheral can NOT stop DMA, if length not WORD aligned, - // remain bytes in last word domain will overwritten by DMA HW, which may cause unexpected issues! - // But driver already used for long time, to avoid breaking changes, we still don't add alignment limit. - SPI_CHECK(spihost[host]->bus_attr->dma_enabled == 0 || trans_desc->rx_buffer == NULL || - (esp_ptr_dma_capable(trans_desc->rx_buffer) && esp_ptr_word_aligned(trans_desc->rx_buffer) && - (trans_desc->length % 8 == 0)), - "rxdata not in DMA-capable memory or not BYTE aligned", ESP_ERR_INVALID_ARG); - SPI_CHECK(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, "data transfer > host maximum", ESP_ERR_INVALID_ARG); spi_slave_trans_priv_t priv_trans = {.trans = (spi_slave_transaction_t *)trans_desc}; @@ -539,29 +491,17 @@ esp_err_t SPI_SLAVE_ISR_ATTR spi_slave_queue_trans_isr(spi_host_device_t host, c ESP_RETURN_ON_FALSE_ISR(is_valid_host(host), ESP_ERR_INVALID_ARG, SPI_TAG, "invalid host"); ESP_RETURN_ON_FALSE_ISR(spihost[host], ESP_ERR_INVALID_ARG, SPI_TAG, "host not slave"); ESP_RETURN_ON_FALSE_ISR(trans_desc->length <= spihost[host]->bus_attr->max_transfer_sz * 8, ESP_ERR_INVALID_ARG, SPI_TAG, "data transfer > host maximum"); - if (spihost[host]->bus_attr->dma_enabled) { - uint16_t alignment = spihost[host]->internal_mem_align_size; - (void) alignment; - -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - // For those targets length and addr alignment is still required from Cache side - uint32_t buffer_byte_len = (trans_desc->length + 7) / 8; - bool tx_aligned = (trans_desc->tx_buffer == NULL) || (esp_ptr_dma_capable(trans_desc->tx_buffer) && ((((uint32_t)trans_desc->tx_buffer | buffer_byte_len) & (alignment - 1)) == 0)); - bool rx_aligned = (trans_desc->rx_buffer == NULL) || (esp_ptr_dma_capable(trans_desc->rx_buffer) && ((((uint32_t)trans_desc->rx_buffer | buffer_byte_len) & (alignment - 1)) == 0)); -#else - bool tx_aligned = (trans_desc->tx_buffer == NULL) || esp_ptr_dma_capable(trans_desc->tx_buffer); - bool rx_aligned = (trans_desc->rx_buffer == NULL) || (esp_ptr_dma_capable(trans_desc->rx_buffer) && esp_ptr_word_aligned(trans_desc->rx_buffer) && (trans_desc->length % 8 == 0)); -#endif - - ESP_RETURN_ON_FALSE_ISR(tx_aligned, ESP_ERR_INVALID_ARG, SPI_TAG, "txdata addr & len not align to %d bytes or not dma_capable", alignment); - ESP_RETURN_ON_FALSE_ISR(rx_aligned, ESP_ERR_INVALID_ARG, SPI_TAG, "rxdata addr & len not align to %d bytes or not dma_capable", alignment); - } spi_slave_trans_priv_t priv_trans = { .trans = (spi_slave_transaction_t *)trans_desc, .tx_buffer = (void *)trans_desc->tx_buffer, .rx_buffer = trans_desc->rx_buffer, }; + if (spihost[host]->bus_attr->dma_enabled) { + // isr api is not allowed to auto_malloc, so don't need to 'uninstall' anything here, return directly + ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->tx_buffer, (trans_desc->length + 7) / 8, true, true, false, &priv_trans.tx_buffer), SPI_TAG, ""); + ESP_RETURN_ON_ERROR_ISR(spicommon_dma_setup_priv_buffer(host, (uint32_t *)trans_desc->rx_buffer, (trans_desc->length + 7) / 8, false, true, false, &priv_trans.rx_buffer), SPI_TAG, ""); + } r = xQueueSendFromISR(spihost[host]->trans_queue, (void *)&priv_trans, &do_yield); if (!r) { return ESP_ERR_NO_MEM; @@ -612,7 +552,7 @@ esp_err_t SPI_SLAVE_ATTR spi_slave_get_trans_result(spi_host_device_t host, spi_ spi_slave_uninstall_priv_trans(host, &priv_trans); *trans_desc = priv_trans.trans; - return ESP_OK; + return priv_trans.dma_hw_error ? ESP_ERR_INVALID_STATE : ESP_OK; } esp_err_t SPI_SLAVE_ATTR spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, uint32_t ticks_to_wait) @@ -683,6 +623,20 @@ static void SPI_SLAVE_ISR_ATTR spi_slave_restart_after_dmareset(void *arg) } #endif //#if CONFIG_IDF_TARGET_ESP32 +static void SPI_SLAVE_ISR_ATTR spi_slave_trans_dma_error_check(spi_slave_t *host) +{ +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM //error checks only for psram dma + if (esp_ptr_external_ram(host->cur_trans.rx_buffer) && spi_slave_hal_get_intr_status(&host->hal, SPI_LL_INTR_IN_FULL)) { + host->cur_trans.dma_hw_error = true; + ESP_DRAM_LOGE(SPI_TAG, "DMA RX overflow detected"); + } + if (esp_ptr_external_ram(host->cur_trans.tx_buffer) && spi_slave_hal_get_intr_status(&host->hal, SPI_LL_INTR_OUT_EMPTY)) { + host->cur_trans.dma_hw_error = true; + ESP_DRAM_LOGE(SPI_TAG, "DMA TX underflow detected"); + } +#endif +} + //This is run in interrupt context and apart from initialization and destruction, this is the only code //touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are //no muxes in this code. @@ -700,6 +654,7 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg) // When DMA is enabled, the slave rx dma suffers from unexpected transactions. Forbid reading until transaction ready. if (use_dma) { freeze_cs(host); + spi_slave_trans_dma_error_check(host); } spi_slave_hal_store_result(hal); @@ -713,17 +668,6 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg) } #endif //#if CONFIG_IDF_TARGET_ESP32 -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE //invalidate here to let user access rx data in post_cb if possible - if (use_dma && host->cur_trans.rx_buffer) { - uint16_t alignment = host->internal_mem_align_size; - uint32_t buffer_byte_len = (host->cur_trans.trans->length + 7) / 8; - buffer_byte_len = (buffer_byte_len + alignment - 1) & (~(alignment - 1)); - // invalidate priv_trans.buffer_to_rcv anyway, only user provide aligned buffer can rcv correct data in post_cb - esp_err_t ret = esp_cache_msync((void *)host->cur_trans.rx_buffer, buffer_byte_len, ESP_CACHE_MSYNC_FLAG_DIR_M2C); - assert(ret == ESP_OK); - (void)ret; - } -#endif if (host->cfg.post_trans_cb) { host->cfg.post_trans_cb(host->cur_trans.trans); } @@ -784,7 +728,9 @@ static void SPI_SLAVE_ISR_ATTR spi_intr(void *arg) if (use_dma) { restore_cs(host); } - +#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE + spi_slave_hal_clear_intr_status(hal, SPI_LL_INTR_IN_FULL | SPI_LL_INTR_OUT_EMPTY); +#endif //Kick off transfer spi_slave_hal_user_start(hal); if (host->cfg.post_setup_cb) { 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 9d01dc32265..ad904a6b622 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 @@ -912,7 +912,7 @@ void test_cmd_addr(spi_slave_task_context_t *slave_context, bool lsb_first) TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &spi)); //connecting pins to two peripherals breaks the output, fix it. - same_pin_func_sel(buscfg, devcfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, buscfg, devcfg.spics_io_num); for (int i = 0; i < 8; i++) { //prepare slave tx data @@ -1097,7 +1097,7 @@ TEST_CASE("SPI master variable dummy test", "[spi]") spi_slave_interface_config_t slave_cfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &bus_cfg, &slave_cfg, SPI_DMA_DISABLED)); - same_pin_func_sel(bus_cfg, dev_cfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, bus_cfg, dev_cfg.spics_io_num); uint8_t data_to_send[] = {0x12, 0x34, 0x56, 0x78}; @@ -1140,7 +1140,7 @@ TEST_CASE("SPI master hd dma TX without RX test", "[spi]") spi_slave_interface_config_t slave_cfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &bus_cfg, &slave_cfg, SPI_DMA_CH_AUTO)); - same_pin_func_sel(bus_cfg, dev_cfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, bus_cfg, dev_cfg.spics_io_num); uint32_t buf_size = 32; uint8_t *mst_send_buf = spi_bus_dma_memory_alloc(TEST_SPI_HOST, buf_size, 0); @@ -2076,11 +2076,11 @@ void test_spi_psram_trans(spi_device_handle_t dev_handle, void *tx, void *rx) // 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; + trans_cfg.override_freq_hz = MIN(80000000, (CONFIG_SPIRAM_SPEED / 2) * 1000 * 1000); printf("%d TX %p RX %p len %d @%ld kHz\n", cnt, trans_cfg.tx_buffer, trans_cfg.rx_buffer, 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, trans_len); + TEST_ASSERT_EQUAL_HEX8_ARRAY(trans_cfg.tx_buffer, trans_cfg.rx_buffer, trans_len); trans_cfg.tx_buffer += trans_len; trans_cfg.rx_buffer += trans_len; trans_len ++; @@ -2125,9 +2125,11 @@ TEST_CASE("SPI_Master: PSRAM buffer transaction via EDMA", "[spi]") TEST_ASSERT(i ? (before - after) < 2 * TEST_EDMA_TRANS_LEN : (before - after) > 2 * TEST_EDMA_TRANS_LEN); spi_device_polling_end(dev_handle, portMAX_DELAY); printf("TX fail: %d, RX fail: %d\n", !!(trans_cfg.flags & SPI_TRANS_DMA_TX_FAIL), !!(trans_cfg.flags & SPI_TRANS_DMA_RX_FAIL)); +#if !SOC_IS(ESP32P4) // P4 can't reach error condition since it has powerful 16bits ddr psram TEST_ASSERT((!!i) == !!(trans_cfg.flags & (SPI_TRANS_DMA_TX_FAIL | SPI_TRANS_DMA_RX_FAIL))); +#endif if (!i) { // data should be correct if using auto malloc - spitest_cmp_or_dump(trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN); + TEST_ASSERT_EQUAL_HEX8_ARRAY(trans_cfg.tx_buffer, trans_cfg.rx_buffer, TEST_EDMA_TRANS_LEN); } } diff --git a/components/esp_driver_spi/test_apps/master/main/test_spi_sio.c b/components/esp_driver_spi/test_apps/master/main/test_spi_sio.c index 0ca09926ada..1b6064a76cb 100644 --- a/components/esp_driver_spi/test_apps/master/main/test_spi_sio.c +++ b/components/esp_driver_spi/test_apps/master/main/test_spi_sio.c @@ -75,7 +75,7 @@ TEST_CASE("SPI Single Board Test SIO", "[spi]") spi_slave_interface_config_t slv_cfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &bus_cfg, &slv_cfg, SPI_DMA_DISABLED)); - same_pin_func_sel(bus_cfg, dev_cfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, bus_cfg, dev_cfg.spics_io_num); inner_connect(bus_cfg); WORD_ALIGNED_ATTR uint8_t master_rx_buffer[320]; diff --git a/components/esp_driver_spi/test_apps/slave/main/test_spi_slave.c b/components/esp_driver_spi/test_apps/slave/main/test_spi_slave.c index db34cd463d5..27717ae0dd8 100644 --- a/components/esp_driver_spi/test_apps/slave/main/test_spi_slave.c +++ b/components/esp_driver_spi/test_apps/slave/main/test_spi_slave.c @@ -8,6 +8,7 @@ */ #include +#include #include "sdkconfig.h" #include "unity.h" #include "test_utils.h" @@ -32,13 +33,9 @@ static WORD_ALIGNED_ATTR uint8_t slave_rxbuf[320]; static const uint8_t master_send[] = { 0x93, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xaa, 0xcc, 0xff, 0xee, 0x55, 0x77, 0x88, 0x43 }; static const uint8_t slave_send[] = { 0xaa, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0 }; -#if (TEST_SPI_PERIPH_NUM >= 2) -//These will only be enabled on chips with 2 or more SPI peripherals -#ifndef CONFIG_SPIRAM -//This test should be removed once the timing test is merged. - +static spi_host_device_t master_slave_ids[2]; static spi_device_handle_t spi; -static void custom_setup(void) +void custom_setup(spi_host_device_t master_id, spi_host_device_t slave_id) { //Initialize buffers memset(master_txbuf, 0, sizeof(master_txbuf)); @@ -46,46 +43,60 @@ static void custom_setup(void) memset(slave_txbuf, 0, sizeof(slave_txbuf)); memset(slave_rxbuf, 0, sizeof(slave_rxbuf)); - //Initialize SPI Master spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG(); buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS; + buscfg.max_transfer_sz = 40960; spi_device_interface_config_t devcfg = { .clock_speed_hz = 4 * 1000 * 1000, //currently only up to 4MHz for internal connect - .mode = 0, //SPI mode 0 .spics_io_num = PIN_NUM_CS, //CS pin .queue_size = 7, //We want to be able to queue 7 transactions at a time - .pre_cb = NULL, .cs_ena_posttrans = 5, .cs_ena_pretrans = 1, }; - //Initialize the SPI bus - TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); - //Attach the device to the SPI bus - TEST_ESP_OK(spi_bus_add_device(TEST_SPI_HOST, &devcfg, &spi)); - //Configuration for the SPI slave interface - spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); - //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected. - gpio_set_pull_mode(PIN_NUM_MOSI, GPIO_PULLUP_ONLY); - gpio_set_pull_mode(PIN_NUM_CLK, GPIO_PULLUP_ONLY); - gpio_set_pull_mode(PIN_NUM_CS, GPIO_PULLUP_ONLY); - //Initialize SPI slave interface - TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO)); + //Initialize SPI Master + if (master_id) { + //Initialize the SPI bus + TEST_ESP_OK(spi_bus_initialize(master_id, &buscfg, SPI_DMA_CH_AUTO)); + //Attach the device to the SPI bus + TEST_ESP_OK(spi_bus_add_device(master_id, &devcfg, &spi)); + } + + if (slave_id) { + //Configuration for the SPI slave interface + spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); + //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected. + slave_pull_up(&buscfg, devcfg.spics_io_num); + //Initialize SPI slave interface + TEST_ESP_OK(spi_slave_initialize(slave_id, &buscfg, &slvcfg, SPI_DMA_CH_AUTO)); + } //Do internal connections - same_pin_func_sel(buscfg, devcfg.spics_io_num, 0, false); + same_pin_func_sel(master_id, slave_id, buscfg, devcfg.spics_io_num); + master_slave_ids[0] = master_id; + master_slave_ids[1] = slave_id; } -static void custom_teardown(void) +void custom_teardown(void) { - TEST_ESP_OK(spi_slave_free(TEST_SLAVE_HOST)); - TEST_ESP_OK(spi_bus_remove_device(spi)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST)); + if (master_slave_ids[1]) { + TEST_ESP_OK(spi_slave_free(master_slave_ids[1])); + master_slave_ids[1] = 0; + } + if (spi != NULL) { + TEST_ESP_OK(spi_bus_remove_device(spi)); + spi = NULL; + } + if (master_slave_ids[0]) { + TEST_ESP_OK(spi_bus_free(master_slave_ids[0])); + master_slave_ids[0] = 0; + } } +#if (TEST_SPI_PERIPH_NUM >= 2) TEST_CASE("test fullduplex slave with only RX direction", "[spi]") { - custom_setup(); + custom_setup(TEST_SPI_HOST, TEST_SLAVE_HOST); memcpy(master_txbuf, master_send, sizeof(master_send)); @@ -132,7 +143,7 @@ TEST_CASE("test fullduplex slave with only RX direction", "[spi]") TEST_CASE("test fullduplex slave with only TX direction", "[spi]") { - custom_setup(); + custom_setup(TEST_SPI_HOST, TEST_SLAVE_HOST); memcpy(slave_txbuf, slave_send, sizeof(slave_send)); @@ -191,7 +202,7 @@ TEST_CASE("Test slave rx no_dma overwrite when length below/over config", "[spi] TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, SPI_DMA_DISABLED)); //initialize master and slave on the same pins break some of the output configs, fix them - same_pin_func_sel(buscfg, devcfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, buscfg, devcfg.spics_io_num); uint8_t master_tx[TEST_SLV_RX_BUF_LEN], slave_rx[TEST_SLV_RX_BUF_LEN]; for (uint8_t i = 0; i < TEST_SLV_RX_BUF_LEN; i++) { @@ -249,16 +260,80 @@ TEST_CASE("Test slave rx no_dma overwrite when length below/over config", "[spi] TEST_ESP_OK(spi_bus_remove_device(spidev0)); TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST)); } -#endif // !CONFIG_SPIRAM #endif // #if (TEST_SPI_PERIPH_NUM >= 2) +#if CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE +#define PSRAM_TRANS_LEN 16000 +TEST_CASE("test slave using external ram", "[spi]") +{ + custom_setup((TEST_SPI_PERIPH_NUM >= 2) ? TEST_SLAVE_HOST : 0, TEST_SPI_HOST); + + uint8_t *slave_ext_tx = heap_caps_aligned_calloc(32, 1, PSRAM_TRANS_LEN, MALLOC_CAP_SPIRAM); + uint8_t *slave_ext_rx = heap_caps_aligned_calloc(32, 1, PSRAM_TRANS_LEN, MALLOC_CAP_SPIRAM); + uint8_t *master_tx = heap_caps_malloc(PSRAM_TRANS_LEN, MALLOC_CAP_DMA); + uint8_t *master_rx = heap_caps_malloc(PSRAM_TRANS_LEN, MALLOC_CAP_DMA); + + spi_slave_transaction_t slave_tans = {}, *out_trans; + slave_tans.length = 8 * PSRAM_TRANS_LEN; + slave_tans.tx_buffer = slave_ext_tx; + slave_tans.rx_buffer = slave_ext_rx; + slave_tans.flags = SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO; + + spi_transaction_t master_tans = {}; + master_tans.override_freq_hz = MIN(60000000, (CONFIG_SPIRAM_SPEED / 2) * 1000 * 1000); + master_tans.tx_buffer = master_tx; + master_tans.rx_buffer = master_rx; + + for (int i = 0; i < 6; i ++) { + test_fill_random_to_buffers_dualboard(7 + i, master_tx, slave_ext_tx, PSRAM_TRANS_LEN); + slave_tans.length -= i * 8; + master_tans.length = slave_tans.length; + master_tans.rxlength = slave_tans.length; + ESP_LOGI(SLAVE_TAG, "Test freq: %ld, tx: %p, rx: %p, len: %d", master_tans.override_freq_hz, slave_tans.tx_buffer, slave_tans.rx_buffer, slave_tans.length / 8); + + uint32_t before = esp_get_free_heap_size(); + TEST_ESP_OK(spi_slave_queue_trans(TEST_SPI_HOST, &slave_tans, portMAX_DELAY)); + uint32_t after = esp_get_free_heap_size(); +#if (TEST_SPI_PERIPH_NUM >= 2) + spi_device_transmit(spi, &master_tans); +#else + spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG(); + spi_master_trans_impl_gpio(buscfg, PIN_NUM_CS, 0, (uint8_t *)master_tans.tx_buffer, master_tans.rx_buffer, master_tans.length / 8, false); +#endif + ESP_LOGI(SLAVE_TAG, "slave malloc: %ld", after - before); + TEST_ASSERT(i ? (before - after) > PSRAM_TRANS_LEN : (before - after) < PSRAM_TRANS_LEN); + TEST_ESP_OK(spi_slave_get_trans_result(TEST_SPI_HOST, &out_trans, portMAX_DELAY)); + + TEST_ASSERT_EQUAL(master_tans.length, slave_tans.trans_len); + TEST_ASSERT_EQUAL_HEX8_ARRAY(slave_tans.tx_buffer, master_tans.rx_buffer, master_tans.length / 8); + TEST_ASSERT_EQUAL_HEX8_ARRAY(master_tans.tx_buffer, slave_tans.rx_buffer, master_tans.length / 8); + ESP_LOGI(SLAVE_TAG, "ok\n"); + } + +#if (TEST_SPI_PERIPH_NUM >= 2) && !SOC_IS(ESP32P4) // P4 can't reach error condition since it has powerful 16bits ddr psram + master_tans.override_freq_hz = 61000000; // real freq will be 40 if just config 60M + ESP_LOGI(SLAVE_TAG, "Testing over freq: %ld", master_tans.override_freq_hz); + TEST_ESP_OK(spi_slave_queue_trans(TEST_SPI_HOST, &slave_tans, portMAX_DELAY)); + spi_device_transmit(spi, (spi_transaction_t *)&master_tans); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, spi_slave_get_trans_result(TEST_SPI_HOST, &out_trans, portMAX_DELAY)); +#endif + + free(slave_ext_tx); + free(slave_ext_rx); + free(master_tx); + free(master_rx); + custom_teardown(); + ESP_LOGI(SLAVE_TAG, "test passed."); +} +#endif // CONFIG_SPIRAM && SOC_PSRAM_DMA_CAPABLE + TEST_CASE("test slave send unaligned", "[spi]") { spi_bus_config_t buscfg = SPI_BUS_TEST_DEFAULT_CONFIG(); buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS; spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO)); - same_pin_func_sel(buscfg, slvcfg.spics_io_num, 0, true); + same_pin_func_sel(0, TEST_SLAVE_HOST, buscfg, slvcfg.spics_io_num); memcpy(master_txbuf, master_send, sizeof(master_send)); memcpy(slave_txbuf, slave_send, sizeof(slave_send)); @@ -678,7 +753,7 @@ TEST_CASE("test_spi_slave_sleep_retention", "[spi]") buscfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS; spi_slave_interface_config_t slvcfg = SPI_SLAVE_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_initialize(TEST_SLAVE_HOST, &buscfg, &slvcfg, SPI_DMA_DISABLED)); - same_pin_func_sel(buscfg, slvcfg.spics_io_num, 0, true); + same_pin_func_sel(0, TEST_SLAVE_HOST, buscfg, slvcfg.spics_io_num); for (uint8_t cnt = 0; cnt < 3; cnt ++) { printf("Going into sleep with power %s ...\n", (buscfg.flags & SPICOMMON_BUSFLAG_SLP_ALLOW_PD) ? "down" : "hold"); diff --git a/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c5 b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c5 new file mode 100644 index 00000000000..cc641ea6033 --- /dev/null +++ b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c5 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c61 b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c61 new file mode 100644 index 00000000000..cc641ea6033 --- /dev/null +++ b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32c61 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32h4 b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32h4 new file mode 100644 index 00000000000..cc641ea6033 --- /dev/null +++ b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32h4 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32p4 b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32p4 new file mode 100644 index 00000000000..cc641ea6033 --- /dev/null +++ b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32p4 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32s3 b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32s3 new file mode 100644 index 00000000000..cc641ea6033 --- /dev/null +++ b/components/esp_driver_spi/test_apps/slave/sdkconfig.ci.release.esp32s3 @@ -0,0 +1 @@ +CONFIG_SPIRAM=y diff --git a/components/esp_driver_spi/test_apps/slave_hd/main/test_spi_slave_hd.c b/components/esp_driver_spi/test_apps/slave_hd/main/test_spi_slave_hd.c index cbeadf8b8c3..4ff0817d02f 100644 --- a/components/esp_driver_spi/test_apps/slave_hd/main/test_spi_slave_hd.c +++ b/components/esp_driver_spi/test_apps/slave_hd/main/test_spi_slave_hd.c @@ -171,7 +171,7 @@ static void test_hd_start(spi_device_handle_t *spi, int freq, const spitest_para //when test with single board via same set of mosi, miso, clk and cs pins. spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG(); spi_slave_hd_slot_config_t slave_hd_cfg = SPI_SLOT_TEST_DEFAULT_CONFIG(); - same_pin_func_sel(bus_cfg, slave_hd_cfg.spics_io_num, 0, false); + same_pin_func_sel(TEST_SPI_HOST, TEST_SLAVE_HOST, bus_cfg, slave_hd_cfg.spics_io_num); wait_wrbuf_sig(ctx, 0); wait_rdbuf_sig(ctx, 0); @@ -529,7 +529,7 @@ TEST_CASE("test spi slave hd segment mode, master too long", "[spi][spi_slv_hd]" //Use GPIO matrix to connect signal of master and slave via same set of pins on one board. spi_bus_config_t bus_cfg = SPI_BUS_TEST_DEFAULT_CONFIG(); spi_slave_hd_slot_config_t slave_hd_cfg = SPI_SLOT_TEST_DEFAULT_CONFIG(); - same_pin_func_sel(bus_cfg, slave_hd_cfg.spics_io_num, 0, true); + same_pin_func_sel(0, TEST_SLAVE_HOST, bus_cfg, slave_hd_cfg.spics_io_num); const int send_buf_size = 1024; WORD_ALIGNED_ATTR uint8_t* slave_send_buf = malloc(send_buf_size * 2); @@ -955,7 +955,7 @@ TEST_CASE("test_spi_slave_hd_sleep_retention", "[spi]") bus_cfg.flags |= SPICOMMON_BUSFLAG_GPIO_PINS; spi_slave_hd_slot_config_t slave_hd_cfg = SPI_SLOT_TEST_DEFAULT_CONFIG(); TEST_ESP_OK(spi_slave_hd_init(TEST_SLAVE_HOST, &bus_cfg, &slave_hd_cfg)); - same_pin_func_sel(bus_cfg, slave_hd_cfg.spics_io_num, 0, true); + same_pin_func_sel(0, TEST_SLAVE_HOST, bus_cfg, slave_hd_cfg.spics_io_num); vTaskDelay(1); for (uint8_t cnt = 0; cnt < 3; cnt ++) { @@ -1033,7 +1033,7 @@ TEST_CASE("test_spi_slave_hd_append_sleep_retention", "[spi]") spi_slave_hd_slot_config_t slave_hd_cfg = SPI_SLOT_TEST_DEFAULT_CONFIG(); slave_hd_cfg.flags |= SPI_SLAVE_HD_APPEND_MODE; TEST_ESP_OK(spi_slave_hd_init(TEST_SLAVE_HOST, &bus_cfg, &slave_hd_cfg)); - same_pin_func_sel(bus_cfg, slave_hd_cfg.spics_io_num, 0, true); + same_pin_func_sel(0, TEST_SLAVE_HOST, bus_cfg, slave_hd_cfg.spics_io_num); vTaskDelay(1); for (uint8_t i = 0; i < 2; i++) { diff --git a/components/esp_hal_gpspi/include/hal/spi_slave_hal.h b/components/esp_hal_gpspi/include/hal/spi_slave_hal.h index 12e3c0a28d5..96effcb09d8 100644 --- a/components/esp_hal_gpspi/include/hal/spi_slave_hal.h +++ b/components/esp_hal_gpspi/include/hal/spi_slave_hal.h @@ -172,6 +172,23 @@ void spi_slave_hal_user_start(const spi_slave_hal_context_t *hal); */ bool spi_slave_hal_usr_is_done(spi_slave_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_slave_hal_get_intr_status(spi_slave_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_slave_hal_clear_intr_status(spi_slave_hal_context_t *hal, uint32_t mask); + /** * Post transaction operations, fetch data from the buffer and recorded the length. * diff --git a/components/esp_hal_gpspi/spi_slave_hal_iram.c b/components/esp_hal_gpspi/spi_slave_hal_iram.c index 1918e53a219..0fa06f60c60 100644 --- a/components/esp_hal_gpspi/spi_slave_hal_iram.c +++ b/components/esp_hal_gpspi/spi_slave_hal_iram.c @@ -99,3 +99,15 @@ bool spi_slave_hal_dma_need_reset(const spi_slave_hal_context_t *hal) #endif // SPI_LL_SLAVE_NEEDS_RESET_WORKAROUND return ret; } + +#if SOC_SPI_SUPPORT_SLAVE_HD_VER2 +bool spi_slave_hal_get_intr_status(spi_slave_hal_context_t *hal, uint32_t mask) +{ + return spi_ll_get_intr(hal->hw, mask); +} + +void spi_slave_hal_clear_intr_status(spi_slave_hal_context_t *hal, uint32_t mask) +{ + spi_ll_clear_intr(hal->hw, mask); +} +#endif diff --git a/docs/en/api-reference/peripherals/spi_slave.rst b/docs/en/api-reference/peripherals/spi_slave.rst index 39e693f5f77..b7a6f6c3a33 100644 --- a/docs/en/api-reference/peripherals/spi_slave.rst +++ b/docs/en/api-reference/peripherals/spi_slave.rst @@ -79,6 +79,14 @@ As not every transaction requires both writing and reading data, you can choose A Host should not start a transaction before its Device is ready for receiving data. It is recommended to use another GPIO pin for a handshake signal to sync the Devices. For more details, see :ref:`transaction_interval`. +.. only:: SOC_PSRAM_DMA_CAPABLE + + Using PSRAM for DMA transfer + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + SPI Slave driver supports using PSRAM for DMA transfer. Directly passing a PSRAM address as :cpp:member:`spi_slave_transaction_t::tx_buffer` or :cpp:member:`spi_slave_transaction_t::rx_buffer` is supported. For the rx_buffer, it has alignment requirements, using :cpp:func:`heap_caps_malloc` to allocate memory can automatically handle the alignment requirements. For the buffers that you can not control, you can also use the :c:macro:`SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO` flag to enable driver to automatically align the buffer from PSRAM. + + Note that this feature shares the MSPI bus bandwidth (bus frequency * bus width), so the transmission bandwidth of the host to this device should be less than the PSRAM bandwidth, otherwise **data may be lost**, and the ``spi_slave_transmit`` function will return the :c:macro:`ESP_ERR_INVALID_STATE` error. Driver Usage ------------ diff --git a/docs/zh_CN/api-reference/peripherals/spi_slave.rst b/docs/zh_CN/api-reference/peripherals/spi_slave.rst index 8975f3c1191..00a18e6f311 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_slave.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_slave.rst @@ -79,6 +79,14 @@ SPI 传输事务 主机应在从机设备准备好接收数据之后再进行传输事务。建议使用另外一个 GPIO 管脚作为握手信号来同步设备。更多细节,请参阅 :ref:`transaction_interval`。 +.. only:: SOC_PSRAM_DMA_CAPABLE + + 使用 PSRAM 的传输 + ^^^^^^^^^^^^^^^^^^ + + SPI Slave 驱动程序支持使用 PSRAM 进行传输。直接传入 PSRAM 地址作为 :cpp:member:`spi_slave_transaction_t::tx_buffer` 或 :cpp:member:`spi_slave_transaction_t::rx_buffer` 即可。对于 rx_buffer ,其地址和传输长度有对齐要求,使用 :cpp:func:`heap_caps_malloc` 分配内存可以自动处理对齐要求。对于不能控制的内存,也可以使用 :c:macro:`SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO` 标志位,驱动会自动从 PSRAM 重新分配满足要求的内存。 + + 请注意该功能共享 MSPI 总线带宽(总线频率 * 总线位宽),因此主机对该设备的传输带宽应小于 PSRAM 带宽,否则 **可能会丢失传输数据**,此时 ``spi_slave_transmit`` 函数将会返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。 使用驱动程序 ------------