diff --git a/components/esp_driver_dma/include/esp_private/gdma.h b/components/esp_driver_dma/include/esp_private/gdma.h index 2943914ecf2..219004c083c 100644 --- a/components/esp_driver_dma/include/esp_private/gdma.h +++ b/components/esp_driver_dma/include/esp_private/gdma.h @@ -202,7 +202,7 @@ esp_err_t gdma_disconnect(gdma_channel_handle_t dma_chan); typedef struct { uint32_t max_data_burst_size; /*!< Set the max burst size when DMA read/write the data buffer. Set to 0 means to disable the data burst. - Other value must be power of 2, e.g., 4/8/16/32/64 */ + Other values must be powers of 2 or supported by the selected GDMA bus. */ bool access_ext_mem; /*!< Set this if the DMA transfer will access external memory */ } gdma_transfer_config_t; diff --git a/components/esp_driver_dma/src/gdma.c b/components/esp_driver_dma/src/gdma.c index fcd4be042c0..d555bfa632b 100644 --- a/components/esp_driver_dma/src/gdma.c +++ b/components/esp_driver_dma/src/gdma.c @@ -421,6 +421,9 @@ esp_err_t gdma_config_transfer(gdma_channel_handle_t dma_chan, const gdma_transf return ESP_ERR_INVALID_ARG; } + gdma_pair_t *pair = dma_chan->pair; + gdma_group_t *group = pair->group; + gdma_hal_context_t *hal = &group->hal; uint32_t max_data_burst_size = config->max_data_burst_size; size_t int_mem_alignment = 1; size_t ext_enc_mem_alignment = 1; @@ -443,8 +446,7 @@ esp_err_t gdma_config_transfer(gdma_channel_handle_t dma_chan, const gdma_transf #endif } if (max_data_burst_size) { - // burst size must be power of 2 - ESP_RETURN_ON_FALSE((max_data_burst_size & (max_data_burst_size - 1)) == 0, ESP_ERR_INVALID_ARG, + ESP_RETURN_ON_FALSE(gdma_hal_check_burst_size(hal, max_data_burst_size), ESP_ERR_INVALID_ARG, TAG, "invalid max_data_burst_size: %"PRIu32, max_data_burst_size); } @@ -477,10 +479,6 @@ esp_err_t gdma_config_transfer(gdma_channel_handle_t dma_chan, const gdma_transf ext_no_enc_mem_alignment = BIT(31); } - gdma_pair_t *pair = dma_chan->pair; - gdma_group_t *group = pair->group; - gdma_hal_context_t *hal = &group->hal; - // always enable descriptor burst as the descriptor is always word aligned and is in the internal SRAM bool en_desc_burst = true; gdma_hal_enable_burst(hal, pair->pair_id, dma_chan->direction, en_data_burst, en_desc_burst); diff --git a/components/esp_driver_dma/test_apps/dma/main/test_gdma.c b/components/esp_driver_dma/test_apps/dma/main/test_gdma.c index dcb16822b1b..b08989b9b4b 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_gdma.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_gdma.c @@ -921,3 +921,80 @@ TEST_CASE("GDMA interrupt priority configuration", "[GDMA]") TEST_ESP_OK(gdma_del_channel(tx_chan)); TEST_ESP_OK(gdma_del_channel(rx_chan)); } + +#if SOC_HAS(AHB_GDMA) || SOC_HAS(LP_AHB_GDMA) || SOC_HAS(AXI_GDMA) +typedef esp_err_t (*gdma_new_channel_func_t)(const gdma_channel_alloc_config_t *config, + gdma_channel_handle_t *ret_tx_chan, + gdma_channel_handle_t *ret_rx_chan); + +static void test_gdma_burst_size_validation(gdma_new_channel_func_t new_channel, + size_t unsupported_burst_size, + size_t invalid_psram_burst_size) +{ + gdma_channel_handle_t tx_chan = NULL; + gdma_channel_alloc_config_t channel_config = {}; + TEST_ESP_OK(new_channel(&channel_config, &tx_chan, NULL)); + + gdma_transfer_config_t transfer_config = { + .max_data_burst_size = 16, + .access_ext_mem = false, + }; + TEST_ESP_OK(gdma_config_transfer(tx_chan, &transfer_config)); + + transfer_config.max_data_burst_size = 3; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, gdma_config_transfer(tx_chan, &transfer_config)); + + if (unsupported_burst_size) { + transfer_config.max_data_burst_size = unsupported_burst_size; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, gdma_config_transfer(tx_chan, &transfer_config)); + } + + if (invalid_psram_burst_size) { + transfer_config.access_ext_mem = true; + transfer_config.max_data_burst_size = invalid_psram_burst_size; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, gdma_config_transfer(tx_chan, &transfer_config)); + } + + TEST_ESP_OK(gdma_del_channel(tx_chan)); +} +#endif // SOC_HAS(AHB_GDMA) || SOC_HAS(LP_AHB_GDMA) || SOC_HAS(AXI_GDMA) + +#if SOC_HAS(AHB_GDMA) || SOC_HAS(LP_AHB_GDMA) +TEST_CASE("GDMA rejects invalid AHB burst sizes", "[GDMA]") +{ +#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + const size_t unsupported_burst_size = 8; +#else + const size_t unsupported_burst_size = 0; +#endif + +#if SOC_HAS(AHB_GDMA) +#if GDMA_LL_GET(AHB_PSRAM_CAPABLE) + test_gdma_burst_size_validation(gdma_new_ahb_channel, unsupported_burst_size, + GDMA_LL_MAX_BURST_SIZE_PSRAM * 2); +#else + test_gdma_burst_size_validation(gdma_new_ahb_channel, unsupported_burst_size, 0); +#endif +#endif // SOC_HAS(AHB_GDMA) + +#if SOC_HAS(LP_AHB_GDMA) +#if GDMA_LL_GET(LP_AHB_PSRAM_CAPABLE) + test_gdma_burst_size_validation(gdma_new_lp_ahb_channel, unsupported_burst_size, + GDMA_LL_MAX_BURST_SIZE_PSRAM * 2); +#else + test_gdma_burst_size_validation(gdma_new_lp_ahb_channel, unsupported_burst_size, 0); +#endif +#endif // SOC_HAS(LP_AHB_GDMA) +} +#endif // SOC_HAS(AHB_GDMA) || SOC_HAS(LP_AHB_GDMA) + +#if SOC_HAS(AXI_GDMA) +TEST_CASE("GDMA rejects invalid AXI burst sizes", "[GDMA]") +{ +#if GDMA_LL_GET(AXI_PSRAM_CAPABLE) + test_gdma_burst_size_validation(gdma_new_axi_channel, 4, GDMA_LL_MAX_BURST_SIZE_PSRAM * 2); +#else + test_gdma_burst_size_validation(gdma_new_axi_channel, 4, 0); +#endif // GDMA_LL_GET(AXI_PSRAM_CAPABLE) +} +#endif // SOC_HAS(AXI_GDMA) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 8689dba75b7..7fda0972c20 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -27,10 +27,6 @@ #include "hal/i2s_types.h" #include "hal/hal_utils.h" #include "hal/dma_types.h" -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE -#include "hal/cache_hal.h" -#include "hal/cache_ll.h" -#endif #if I2S_LL_GET(ADC_DAC_CAPABLE) #include "hal/adc_ll.h" @@ -44,6 +40,9 @@ #if SOC_HAS(PAU) #include "esp_private/sleep_retention.h" #endif +#if SOC_GDMA_SUPPORTED +#include "hal/gdma_ll.h" +#endif #include "driver/gpio.h" #include "esp_private/gpio.h" @@ -62,37 +61,68 @@ #include "esp_check.h" #include "esp_attr.h" #include "esp_cache.h" +#include "esp_private/esp_cache_private.h" +#include "esp_private/mspi_mem_barrier.h" #include "esp_rom_gpio.h" #include "esp_memory_utils.h" -/* The actual max size of DMA buffer is 4095 - * Reserve several bytes for alignment, so that the position of the slot data in the buffer will be relatively fixed */ -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE -#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_64B_ALIGNED -#else -#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED -#endif // SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE +#define I2S_DMA_BUFFER_MAX_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE +#define I2S_DMA_DEFAULT_BURST_SIZE 32 static const char *TAG = "i2s_common"; +static uint32_t i2s_resolve_dma_burst_size(size_t requested) +{ + uint32_t burst_size = (uint32_t)requested; + if (burst_size == 0) { + burst_size = I2S_DMA_DEFAULT_BURST_SIZE; + ESP_LOGD(TAG, "dma_burst_size is 0, using default %d", I2S_DMA_DEFAULT_BURST_SIZE); + } + +#if SOC_GDMA_SUPPORTED && !GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE + if (requested != 0) { + ESP_LOGW(TAG, "chip does not support configurable DMA burst size, using default burst size instead"); + } + burst_size = I2S_DMA_DEFAULT_BURST_SIZE; +#endif + + return burst_size; +} + #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC static void s_i2s_channel_update_tx_sync_callback(i2s_chan_handle_t tx_handle, i2s_tx_fifo_sync_callback_t cb, void *user_data); #endif -__attribute__((always_inline)) -inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size) +FORCE_INLINE_ATTR void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size) { - return heap_caps_aligned_calloc(4, num, size, I2S_DMA_ALLOC_CAPS); + uint32_t caps = handle->dma.buffer_in_psram ? + (MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT) : I2S_DMA_ALLOC_CAPS; + return heap_caps_aligned_calloc(handle->dma.buf_alignment, num, size, caps); } -__attribute__((always_inline)) -static inline void i2s_dma_buf_msync(void *addr, size_t size, int flags) +FORCE_INLINE_ATTR void IRAM_ATTR i2s_dma_buf_sync(i2s_chan_handle_t handle, void *buf, size_t size, int flags) { - if (esp_cache_get_line_size_by_addr(addr) > 0) { - esp_cache_msync(addr, size, flags); +#if CONFIG_SPIRAM + if ((flags & ESP_CACHE_MSYNC_FLAG_DIR_M2C) && handle->dma.buffer_in_psram) { + esp_psram_mspi_mb(); + } +#endif + if (esp_cache_get_line_size_by_addr(buf) > 0) { + esp_cache_msync(buf, size, flags); + } +} + +static void i2s_dma_bufs_sync_for_start(i2s_chan_handle_t handle) +{ + if (!handle->dma.bufs) { + return; + } + for (int i = 0; i < handle->dma.desc_num; i++) { + i2s_dma_buf_sync(handle, handle->dma.bufs[i], handle->dma.buf_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); } } @@ -260,12 +290,7 @@ static void i2s_tx_channel_start(i2s_chan_handle_t handle) i2s_hal_tx_reset_fifo(&(handle->controller->hal)); handle->dma.link_index = 0; /* Write back CPU updates (e.g. preload) then drop cache lines before DMA owns the buffers. */ - if (handle->dma.bufs) { - for (int i = 0; i < handle->dma.desc_num; i++) { - i2s_dma_buf_msync(handle->dma.bufs[i], handle->dma.buf_size, - ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); - } - } + i2s_dma_bufs_sync_for_start(handle); #if SOC_GDMA_SUPPORTED if (handle->dma.dma_chan) { gdma_start((handle->dma.dma_chan), gdma_link_get_head_addr(handle->dma.dma_link)); @@ -295,12 +320,7 @@ static void i2s_rx_channel_start(i2s_chan_handle_t handle) i2s_hal_rx_reset_fifo(&(handle->controller->hal)); handle->dma.link_index = 0; /* Flush dirty CPU lines then invalidate so DMA writes cannot be overwritten by later evictions. */ - if (handle->dma.bufs) { - for (int i = 0; i < handle->dma.desc_num; i++) { - i2s_dma_buf_msync(handle->dma.bufs[i], handle->dma.buf_size, - ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); - } - } + i2s_dma_bufs_sync_for_start(handle); #if SOC_GDMA_SUPPORTED if (handle->dma.dma_chan) { gdma_start(handle->dma.dma_chan, gdma_link_get_head_addr(handle->dma.dma_link)); @@ -500,6 +520,7 @@ static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, new_chan->callbacks.on_recv_q_ovf = NULL; new_chan->callbacks.on_sent = NULL; new_chan->callbacks.on_send_q_ovf = NULL; + new_chan->dma.buf_alignment = 4; new_chan->dma.rw_pos = 0; new_chan->dma.curr_ptr = NULL; new_chan->start = NULL; @@ -622,6 +643,11 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 #endif esp_err_t ret = ESP_OK; +#if CONFIG_I2S_ISR_IRAM_SAFE + if (handle->dma.buffer_in_psram && (callbacks->on_recv || callbacks->on_sent)) { + ESP_LOGW(TAG, "DMA buffers are in PSRAM; callbacks must not access them while the cache is disabled"); + } +#endif xSemaphoreTake(handle->mutex, portMAX_DELAY); bool update_dma_cbs = dma_cb_supported && !(sync_only_update && handle->state == I2S_CHAN_STATE_RUNNING); ESP_GOTO_ON_FALSE(!update_dma_cbs || handle->state < I2S_CHAN_STATE_RUNNING, @@ -656,33 +682,28 @@ uint32_t i2s_get_buf_size(i2s_chan_handle_t handle, uint32_t data_bit_width, uin if (bytes_per_frame == 0) { return 0; } - uint32_t bufsize = dma_frame_num * bytes_per_frame; -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - /* bufsize need to align with cache line size */ - uint32_t alignment = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA); + size_t alignment = handle->dma.buf_alignment; + uint32_t max_buf_size = I2S_DMA_BUFFER_MAX_SIZE & ~(alignment - 1); uint32_t aligned_frame_num = dma_frame_num; - /* To make the buffer aligned with the cache line size, search for the ceil aligned size first, - If the buffer size exceed the max DMA buffer size, toggle the sign to search for the floor aligned size */ - for (int sign = 1; bufsize % alignment != 0; aligned_frame_num += sign) { + uint32_t bufsize = aligned_frame_num * bytes_per_frame; + /* Prefer rounding the frame count up. If that would exceed one descriptor, round it down instead. */ + while (bufsize <= max_buf_size && bufsize % alignment != 0) { + aligned_frame_num++; bufsize = aligned_frame_num * bytes_per_frame; - /* If the buffer size exceed the max dma size */ - if (bufsize > I2S_DMA_BUFFER_MAX_SIZE && sign == 1) { - sign = -1; // toggle the search sign - aligned_frame_num = dma_frame_num; // Reset the frame num - bufsize = aligned_frame_num * bytes_per_frame; // Reset the bufsize + } + if (bufsize > max_buf_size) { + aligned_frame_num = dma_frame_num < max_buf_size / bytes_per_frame ? + dma_frame_num : max_buf_size / bytes_per_frame; + bufsize = aligned_frame_num * bytes_per_frame; + while (aligned_frame_num > 0 && bufsize % alignment != 0) { + aligned_frame_num--; + bufsize = aligned_frame_num * bytes_per_frame; } } if (bufsize / bytes_per_frame != dma_frame_num) { - ESP_LOGW(TAG, "dma frame num is adjusted to %"PRIu32" to align the dma buffer with %"PRIu32 + ESP_LOGW(TAG, "dma frame num is adjusted to %"PRIu32" to align the dma buffer with %zu" ", bufsize = %"PRIu32, bufsize / bytes_per_frame, alignment, bufsize); } -#endif - /* Limit DMA buffer size if it is out of range */ - if (bufsize > I2S_DMA_BUFFER_MAX_SIZE) { - uint32_t frame_num = I2S_DMA_BUFFER_MAX_SIZE / bytes_per_frame; - bufsize = frame_num * bytes_per_frame; - ESP_LOGW(TAG, "dma frame num is out of dma buffer size, limited to %"PRIu32, frame_num); - } return bufsize; } @@ -724,7 +745,7 @@ static esp_err_t i2s_mount_dma_link(i2s_chan_handle_t handle, uint32_t bufsize) .num_items = num, .item_alignment = 4, }; - ESP_GOTO_ON_ERROR(gdma_new_link_list(&link_config, &handle->dma.dma_link), err, TAG, "create I2S DMA link failed"); + ESP_RETURN_ON_ERROR(gdma_new_link_list(&link_config, &handle->dma.dma_link), TAG, "create I2S DMA link failed"); for (int i = 0; i < num; i++) { size_t buffer_alignment = 4; @@ -773,7 +794,10 @@ esp_err_t i2s_alloc_dma_resources(i2s_chan_handle_t handle, uint32_t bufsize) { I2S_NULL_POINTER_CHECK(TAG, handle); esp_err_t ret = ESP_OK; - ESP_RETURN_ON_FALSE(bufsize <= I2S_DMA_BUFFER_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "dma buffer can't be bigger than %d", I2S_DMA_BUFFER_MAX_SIZE); + size_t alignment = handle->dma.buf_alignment; + uint32_t max_buf_size = I2S_DMA_BUFFER_MAX_SIZE & ~(alignment - 1); + ESP_RETURN_ON_FALSE(bufsize <= max_buf_size && bufsize % alignment == 0, ESP_ERR_INVALID_ARG, TAG, + "dma buffer must be aligned to %zu and no bigger than %"PRIu32, alignment, max_buf_size); uint32_t num = handle->dma.desc_num; handle->dma.buf_size = bufsize; @@ -857,7 +881,7 @@ static bool i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_ (void)dma_chan; (void)event_data; handle->dma.link_index = (finish_index + 1) % handle->dma.desc_num; - i2s_dma_buf_msync(finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + i2s_dma_buf_sync(handle, finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); i2s_event_data_t evt = { .dma_buf = finish_buf, .size = handle->dma.buf_size, @@ -901,7 +925,7 @@ static bool i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_ } /* Sync buffer after the callback in case users update the buffer in the callback */ if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) { - i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(handle, curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); } if (xQueueIsQueueFullFromISR(handle->msg_queue)) { xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1); @@ -912,7 +936,7 @@ static bool i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_ } if (handle->dma.auto_clear_after_cb) { memset(curr_buf, 0, handle->dma.buf_size); - i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(handle, curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); } xQueueSendFromISR(handle->msg_queue, &curr_buf, &need_yield2); @@ -940,9 +964,9 @@ static void i2s_dma_rx_callback(void *arg) uint32_t finish_index = handle->dma.link_index; void *finish_buf = gdma_link_get_buffer(handle->dma.dma_link, finish_index); handle->dma.link_index = (finish_index + 1) % handle->dma.desc_num; + i2s_dma_buf_sync(handle, finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); evt.dma_buf = finish_buf; evt.size = handle->dma.buf_size; - i2s_dma_buf_msync(finish_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); if (handle->callbacks.on_recv) { user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data); } @@ -991,7 +1015,7 @@ static void i2s_dma_tx_callback(void *arg) } /* Sync buffer after the callback in case users update the buffer in the callback */ if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) { - i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(handle, curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); } if (xQueueIsQueueFullFromISR(handle->msg_queue)) { xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1); @@ -1002,7 +1026,7 @@ static void i2s_dma_tx_callback(void *arg) // Auto clear the dma buffer after data sent if (handle->dma.auto_clear_after_cb) { memset(curr_buf, 0, handle->dma.buf_size); - i2s_dma_buf_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(handle, curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); } xQueueSendFromISR(handle->msg_queue, &curr_buf, &need_yield2); } @@ -1013,6 +1037,69 @@ static void i2s_dma_tx_callback(void *arg) } #endif +esp_err_t i2s_prepare_dma(i2s_chan_handle_t handle) +{ + I2S_NULL_POINTER_CHECK(TAG, handle); + if (handle->dma.dma_chan) { + return ESP_OK; + } + + handle->dma.buf_alignment = 4; +#if SOC_GDMA_SUPPORTED + esp_err_t ret = ESP_OK; + gdma_channel_alloc_config_t dma_cfg = { +#if CONFIG_I2S_ISR_IRAM_SAFE + .flags.isr_cache_safe = true, +#endif // CONFIG_I2S_ISR_IRAM_SAFE + }; + if (handle->dir == I2S_DIR_TX) { + ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan, NULL), + TAG, "register tx dma channel error"); + } else { + ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, NULL, &handle->dma.dma_chan), + TAG, "register rx dma channel error"); + } + + gdma_transfer_config_t transfer_cfg = { + .max_data_burst_size = handle->dma.burst_size, + .access_ext_mem = handle->dma.buffer_in_psram, + }; + ESP_GOTO_ON_ERROR(gdma_config_transfer(handle->dma.dma_chan, &transfer_cfg), + err, TAG, "config dma transfer error"); + gdma_channel_alignment_info_t alignment_info = {}; + ESP_GOTO_ON_ERROR(gdma_get_channel_alignment_constraints(handle->dma.dma_chan, &alignment_info), + err, TAG, "get DMA alignment constraints failed"); + handle->dma.buf_alignment = handle->dma.buffer_in_psram ? + alignment_info.ext_enc_mem_alignment : alignment_info.int_mem_alignment; +#else // I2S dedicated DMA (ESP32 / ESP32-S2) + if (handle->dma.buffer_in_psram) { +#if I2S_LL_SUPPORT(DMA_EXT_MEM) + i2s_ll_dma_set_ext_mem_block_size(handle->controller->hal.dev, I2S_LL_DMA_EXT_MEM_ALIGNMENT); + if (handle->dir == I2S_DIR_TX) { + i2s_ll_dma_tx_enable_burst(handle->controller->hal.dev, true); + } else { + i2s_ll_dma_rx_enable_burst(handle->controller->hal.dev, true); + } + handle->dma.buf_alignment = I2S_LL_DMA_EXT_MEM_ALIGNMENT; +#else // ESP32 cannot use external memory for DMA + return ESP_ERR_NOT_SUPPORTED; +#endif // I2S_LL_SUPPORT(DMA_EXT_MEM) + } +#endif // SOC_GDMA_SUPPORTED + + size_t cache_line_size = 0; + esp_cache_get_alignment(handle->dma.buffer_in_psram ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL, &cache_line_size); + handle->dma.buf_alignment = MAX(handle->dma.buf_alignment, cache_line_size); + return ESP_OK; + +#if SOC_GDMA_SUPPORTED +err: + gdma_del_channel(handle->dma.dma_chan); + handle->dma.dma_chan = NULL; + return ret; +#endif // SOC_GDMA_SUPPORTED +} + #if SOC_GDMA_SUPPORTED /** * @brief I2S DMA interrupt initialization (implemented by I2S dedicated DMA) @@ -1065,54 +1152,24 @@ esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag) return ESP_ERR_NOT_SUPPORTED; } - /* Set GDMA config */ - gdma_channel_alloc_config_t dma_cfg = { -#if CONFIG_I2S_ISR_IRAM_SAFE - .flags.isr_cache_safe = true, -#endif - }; - if (handle->dir == I2S_DIR_TX) { - /* Register a new GDMA tx channel */ - ESP_GOTO_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, &handle->dma.dma_chan, NULL), err, TAG, "Register tx dma channel error"); - } else { - /* Register a new GDMA rx channel */ - ESP_GOTO_ON_ERROR(gdma_new_ahb_channel(&dma_cfg, NULL, &handle->dma.dma_chan), err, TAG, "Register rx dma channel error"); - } - - gdma_transfer_config_t transfer_cfg = { - .max_data_burst_size = 0, - .access_ext_mem = false, - }; - ESP_GOTO_ON_ERROR(gdma_config_transfer(handle->dma.dma_chan, &transfer_cfg), - err_channel, TAG, "Config dma transfer error"); - ESP_GOTO_ON_ERROR(i2s_mount_dma_link(handle, handle->dma.buf_size), - err_channel, TAG, "Mount dma link error"); - ESP_GOTO_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), err_link, TAG, "Connect dma channel error"); + ESP_RETURN_ON_ERROR(gdma_connect(handle->dma.dma_chan, trig), TAG, "connect dma channel error"); if (handle->dir == I2S_DIR_TX) { gdma_tx_event_callbacks_t cb = {.on_trans_eof = i2s_dma_tx_callback}; /* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the callback function */ ESP_GOTO_ON_ERROR(gdma_register_tx_event_callbacks(handle->dma.dma_chan, &cb, handle), - err_disconnect, TAG, "Register tx callback failed"); + err, TAG, "Register tx callback failed"); } else { gdma_rx_event_callbacks_t cb = {.on_recv_eof = i2s_dma_rx_callback}; /* Set callback function for GDMA, the interrupt is triggered by GDMA, then the GDMA ISR will call the callback function */ ESP_GOTO_ON_ERROR(gdma_register_rx_event_callbacks(handle->dma.dma_chan, &cb, handle), - err_disconnect, TAG, "Register rx callback failed"); + err, TAG, "Register rx callback failed"); } + handle->dma.connected = true; return ret; -err_disconnect: - gdma_disconnect(handle->dma.dma_chan); -err_link: - if (handle->dma.dma_link) { - gdma_del_link_list(handle->dma.dma_link); - handle->dma.dma_link = NULL; - } -err_channel: - gdma_del_channel(handle->dma.dma_chan); - handle->dma.dma_chan = NULL; err: + gdma_disconnect(handle->dma.dma_chan); return ret; } #else @@ -1265,6 +1322,37 @@ esp_err_t i2s_check_set_mclk(i2s_chan_handle_t handle, int id, int gpio_num, i2s return ESP_OK; } +static esp_err_t i2s_setup_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, + const i2s_chan_config_t *chan_cfg, uint32_t dma_burst_size, + i2s_chan_handle_t *ret_handle) +{ + i2s_destination_t dest = (dir == I2S_DIR_TX) ? chan_cfg->tx_destination : chan_cfg->rx_destination; + ESP_RETURN_ON_ERROR(i2s_register_channel(i2s_obj, dir, chan_cfg->dma_desc_num, dest == I2S_DESTINATION_DMA), + TAG, "register I2S %s channel failed", dir == I2S_DIR_TX ? "tx" : "rx"); + + i2s_chan_handle_t chan = (dir == I2S_DIR_TX) ? i2s_obj->tx_chan : i2s_obj->rx_chan; + chan->role = chan_cfg->role; + chan->is_port_auto = chan_cfg->id == I2S_NUM_AUTO; + chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; + chan->dma.buffer_in_psram = chan_cfg->dma_buffer_in_psram; + chan->dma.desc_num = chan_cfg->dma_desc_num; + chan->dma.frame_num = chan_cfg->dma_frame_num; + chan->dma.burst_size = dma_burst_size; + chan->destination = dest; + if (dir == I2S_DIR_TX) { + chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb; + chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb; + chan->start = i2s_tx_channel_start; + chan->stop = i2s_tx_channel_stop; + } else { + chan->start = i2s_rx_channel_start; + chan->stop = i2s_rx_channel_stop; + } + *ret_handle = chan; + ESP_LOGD(TAG, "%s channel is registered on I2S%d successfully", dir == I2S_DIR_TX ? "tx" : "rx", i2s_obj->id); + return ESP_OK; +} + /*--------------------------------------------------------------------------- I2S bus Public APIs ---------------------------------------------------------------------------- @@ -1281,6 +1369,17 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_RETURN_ON_FALSE((chan_cfg->id >= 0 && chan_cfg->id < I2S_LL_GET(INST_NUM)) || chan_cfg->id == I2S_NUM_AUTO, ESP_ERR_INVALID_ARG, TAG, "invalid I2S port id"); ESP_RETURN_ON_FALSE(chan_cfg->dma_desc_num >= 2, ESP_ERR_INVALID_ARG, TAG, "there should be at least 2 DMA buffers"); ESP_RETURN_ON_FALSE(chan_cfg->intr_priority >= 0 && chan_cfg->intr_priority <= 7, ESP_ERR_INVALID_ARG, TAG, "intr_priority should be within 0~7"); +#if !(SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM) + // Reject PSRAM DMA buffers when the I2S DMA cannot access PSRAM or PSRAM is not enabled. + ESP_RETURN_ON_FALSE(!chan_cfg->dma_buffer_in_psram, ESP_ERR_NOT_SUPPORTED, TAG, "PSRAM DMA buffer is not supported"); +#endif +#if CONFIG_I2S_ISR_IRAM_SAFE + // Reject auto clear for PSRAM DMA buffers with IRAM-safe ISR + ESP_RETURN_ON_FALSE(!(chan_cfg->dma_buffer_in_psram && + (chan_cfg->auto_clear_before_cb || chan_cfg->auto_clear_after_cb)), + ESP_ERR_NOT_SUPPORTED, TAG, "auto clear is not supported for PSRAM DMA buffers with IRAM-safe ISR"); +#endif + uint32_t dma_burst_size = i2s_resolve_dma_burst_size(chan_cfg->dma_burst_size); #if !SOC_HAS(PAU) ESP_RETURN_ON_FALSE(!chan_cfg->allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported"); #endif @@ -1333,39 +1432,13 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * channel_found = i2s_take_available_channel(i2s_obj, chan_search_mask); } ESP_GOTO_ON_FALSE(channel_found, ESP_ERR_NOT_FOUND, err, TAG, "no available channel found"); - /* Register and specify the tx handle */ if (tx_handle) { - const bool tx_use_dma = chan_cfg->tx_destination == I2S_DESTINATION_DMA; - ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num, tx_use_dma), + ESP_GOTO_ON_ERROR(i2s_setup_channel(i2s_obj, I2S_DIR_TX, chan_cfg, dma_burst_size, tx_handle), err, TAG, "register I2S tx channel failed"); - i2s_obj->tx_chan->role = chan_cfg->role; - i2s_obj->tx_chan->is_port_auto = id == I2S_NUM_AUTO; - i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; - i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb; - i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb; - i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num; - i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num; - i2s_obj->tx_chan->start = i2s_tx_channel_start; - i2s_obj->tx_chan->stop = i2s_tx_channel_stop; - i2s_obj->tx_chan->destination = chan_cfg->tx_destination; - *tx_handle = i2s_obj->tx_chan; - ESP_LOGD(TAG, "tx channel is registered on I2S%d successfully", i2s_obj->id); } - /* Register and specify the rx handle */ if (rx_handle) { - const bool rx_use_dma = chan_cfg->rx_destination == I2S_DESTINATION_DMA; - ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num, rx_use_dma), + ESP_GOTO_ON_ERROR(i2s_setup_channel(i2s_obj, I2S_DIR_RX, chan_cfg, dma_burst_size, rx_handle), err, TAG, "register I2S rx channel failed"); - i2s_obj->rx_chan->role = chan_cfg->role; - i2s_obj->rx_chan->is_port_auto = id == I2S_NUM_AUTO; - i2s_obj->rx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; - i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num; - i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num; - i2s_obj->rx_chan->start = i2s_rx_channel_start; - i2s_obj->rx_chan->stop = i2s_rx_channel_stop; - i2s_obj->rx_chan->destination = chan_cfg->rx_destination; - *rx_handle = i2s_obj->rx_chan; - ESP_LOGD(TAG, "rx channel is registered on I2S%d successfully", i2s_obj->id); } if ((tx_handle != NULL) && (rx_handle != NULL)) { @@ -1481,7 +1554,10 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle) #endif if (handle->dma.dma_chan) { #if SOC_GDMA_SUPPORTED - gdma_disconnect(handle->dma.dma_chan); + if (handle->dma.connected) { + gdma_disconnect(handle->dma.dma_chan); + handle->dma.connected = false; + } gdma_del_channel(handle->dma.dma_chan); #else esp_intr_free(handle->dma.dma_chan); @@ -1667,7 +1743,7 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, } /* Load the data from the last loaded position */ memcpy((uint8_t *)(tx_handle->dma.curr_ptr + tx_handle->dma.rw_pos), data_ptr, bytes_can_load); - i2s_dma_buf_msync(tx_handle->dma.curr_ptr, tx_handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(tx_handle, tx_handle->dma.curr_ptr, tx_handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); data_ptr += bytes_can_load; // Move forward the data pointer total_loaded_bytes += bytes_can_load; // Add to the total loaded bytes remain_bytes -= bytes_can_load; // Update the remaining bytes to be loaded @@ -1719,7 +1795,7 @@ esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t si bytes_can_write = size; } memcpy(data_ptr, src_byte, bytes_can_write); - i2s_dma_buf_msync(handle->dma.curr_ptr, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + i2s_dma_buf_sync(handle, handle->dma.curr_ptr, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); size -= bytes_can_write; src_byte += bytes_can_write; handle->dma.rw_pos += bytes_can_write; diff --git a/components/esp_driver_i2s/i2s_pdm.c b/components/esp_driver_i2s/i2s_pdm.c index 1e5f8eb2ee2..647342c3c26 100644 --- a/components/esp_driver_i2s/i2s_pdm.c +++ b/components/esp_driver_i2s/i2s_pdm.c @@ -232,6 +232,9 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t } handle->mode_info = calloc(1, sizeof(i2s_pdm_tx_config_t)); ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed"); + } /* i2s_set_pdm_tx_slot should be called before i2s_set_pdm_tx_clock and i2s_pdm_tx_set_gpio * while initializing, because clock and gpio is relay on the slot */ ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_slot(handle, &pdm_tx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); @@ -256,6 +259,10 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif @@ -307,6 +314,10 @@ esp_err_t i2s_channel_reconfig_pdm_tx_clock(i2s_chan_handle_t handle, const i2s_ pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); } #endif //CONFIG_PM_ENABLE @@ -594,6 +605,9 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r } handle->mode_info = calloc(1, sizeof(i2s_pdm_rx_config_t)); ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed"); + } /* i2s_set_pdm_rx_slot should be called before i2s_set_pdm_rx_clock and i2s_pdm_rx_set_gpio while initializing, because clock is relay on the slot */ ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_slot(handle, &pdm_rx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_gpio(handle, &pdm_rx_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); @@ -614,6 +628,10 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif @@ -665,6 +683,10 @@ esp_err_t i2s_channel_reconfig_pdm_rx_clock(i2s_chan_handle_t handle, const i2s_ pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); } #endif //CONFIG_PM_ENABLE diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index b562a3a26a6..b6a885a6b15 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -119,7 +119,13 @@ typedef struct { uint32_t link_index; /*!< Index of the next completed DMA link item */ uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ uint32_t frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ + uint32_t burst_size; /*!< Resolved DMA data burst size in bytes */ uint32_t buf_size; /*!< dma buffer size */ + size_t buf_alignment; /*!< DMA buffer alignment required for address and size */ + bool buffer_in_psram; /*!< Whether the DMA buffers are allocated in PSRAM */ +#if SOC_GDMA_SUPPORTED + bool connected; /*!< Whether the GDMA channel is connected to I2S */ +#endif bool auto_clear_after_cb; /*!< Set to auto clear DMA TX descriptor after callback, i2s will always send zero automatically if no data to send */ bool auto_clear_before_cb; /*!< Set to auto clear DMA TX descriptor before callback, i2s will always send zero automatically if no data to send */ uint32_t rw_pos; /*!< reading/writing pointer position */ @@ -294,6 +300,17 @@ extern i2s_platform_t g_i2s; /*!< Global i2s instance for driver internal use * */ esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag); +/** + * @brief Prepare the DMA engine and query the memory alignment constraints + * + * @param handle I2S channel handle + * @return + * - ESP_OK DMA engine prepared + * - ESP_ERR_NOT_SUPPORTED External-memory DMA is unsupported + * - ESP_ERR_NOT_FOUND DMA channel not found + */ +esp_err_t i2s_prepare_dma(i2s_chan_handle_t handle); + #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC /** * @brief Initialize I2S peripheral interrupt diff --git a/components/esp_driver_i2s/i2s_std.c b/components/esp_driver_i2s/i2s_std.c index a8814820e2a..07f019379cc 100644 --- a/components/esp_driver_i2s/i2s_std.c +++ b/components/esp_driver_i2s/i2s_std.c @@ -336,6 +336,10 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf #if SOC_I2S_HW_VERSION_1 ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to constitute full-duplex mode"); #endif + /* DMA alignment must be known before allocating buffers in set_slot */ + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed"); + } /* i2s_set_std_slot should be called before i2s_set_std_clock while initializing, because clock is relay on the slot */ ESP_GOTO_ON_ERROR(i2s_std_set_slot(handle, &std_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); ESP_GOTO_ON_ERROR(i2s_std_set_clock(handle, &std_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); @@ -368,6 +372,10 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif @@ -421,6 +429,10 @@ esp_err_t i2s_channel_reconfig_std_clock(i2s_chan_handle_t handle, const i2s_std pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); } #endif //CONFIG_PM_ENABLE diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index 9af9686ca94..f3ea97c55ee 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -75,7 +75,7 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm clk_info->mclk_div = clk_info->sclk / clk_info->mclk; /* Check if the configuration is correct. Use float for check in case the mclk division might be carried up in the fine division calculation */ - ESP_RETURN_ON_FALSE((float)clk_info->sclk > clk_info->mclk * min_mclk_div, ESP_ERR_INVALID_ARG, TAG, "sample rate is too large"); + ESP_RETURN_ON_FALSE((float)clk_info->sclk > clk_info->mclk * min_mclk_div, ESP_ERR_INVALID_ARG, TAG, "sample rate is too large, sclk: %"PRIu32" Hz, mclk: %"PRIu32" Hz", clk_info->sclk, clk_info->mclk); ESP_RETURN_ON_FALSE(clk_info->mclk_div < I2S_LL_CLK_FRAC_DIV_N_MAX, ESP_ERR_INVALID_ARG, TAG, "sample rate is too small"); return ESP_OK; @@ -316,6 +316,10 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations"); /* Try to constitute full-duplex mode if the TDM configuration is totally same as another channel */ ESP_GOTO_ON_ERROR(s_i2s_channel_try_to_constitute_tdm_duplex(handle, tdm_cfg), err, TAG, "failed to constitute full-duplex mode"); + /* DMA alignment must be known before allocating buffers in set_slot */ + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_prepare_dma(handle), err, TAG, "prepare dma failed"); + } /* i2s_set_tdm_slot should be called before i2s_set_tdm_clock while initializing, because clock is relay on the slot */ ESP_GOTO_ON_ERROR(i2s_tdm_set_slot(handle, &tdm_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); ESP_GOTO_ON_ERROR(i2s_tdm_set_clock(handle, &tdm_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); @@ -348,6 +352,10 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif @@ -400,6 +408,10 @@ esp_err_t i2s_channel_reconfig_tdm_clock(i2s_chan_handle_t handle, const i2s_tdm pm_type = ESP_PM_NO_LIGHT_SLEEP; } #endif // SOC_I2S_SUPPORTS_APLL + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buffer_in_psram) { + // use CPU_MAX lock to ensure PSRAM bandwidth and usability during DFS + pm_type = ESP_PM_CPU_FREQ_MAX; + } ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); } #endif //CONFIG_PM_ENABLE diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index 6523c3c2b77..e7fae39e919 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -24,8 +24,10 @@ extern "C" { .role = i2s_role, \ .dma_desc_num = 6, \ .dma_frame_num = 240, \ + .dma_burst_size = 0, \ .auto_clear_after_cb = false, \ .auto_clear_before_cb = false, \ + .dma_buffer_in_psram = false, \ .allow_pd = false, \ .intr_priority = 0, \ .tx_destination = I2S_DESTINATION_DMA, \ @@ -39,6 +41,8 @@ extern "C" { * @note The callbacks are all running under ISR environment * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. * The variables used in the function should be in the SRAM as well. + * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled and the DMA buffers are in PSRAM, the callback must not access + * the DMA buffer while the cache is disabled. */ typedef struct { i2s_isr_callback_t on_recv; /**< Callback of data received event, only for RX channel @@ -70,6 +74,10 @@ typedef struct { uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots, * it should be the multiple of `3` when the data bit width is 24. */ + size_t dma_burst_size; /*!< DMA data burst size in bytes. Set to 0 to use driver default (32). + * When non-zero, must be a chip-supported power of 2 (see GDMA driver or chip TRM). + * Ignored on chips that do not support configurable burst size. + */ union { bool auto_clear; /*!< Alias of `auto_clear_after_cb` */ bool auto_clear_after_cb; /*!< Set to auto clear DMA TX buffer after `on_sent` callback, I2S will always send zero automatically if no data to send. @@ -86,6 +94,10 @@ typedef struct { int intr_priority; /*!< I2S interrupt priority, range [0, 7], if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */ i2s_destination_t tx_destination; /*!< TX data path: DMA (memory) or Bluetooth (see `i2s_destination_t`). I2S0 only when set to `I2S_DESTINATION_BT`. Immutable after `i2s_new_channel`. */ i2s_destination_t rx_destination; /*!< RX data path: DMA (memory) or Bluetooth. Same constraints as `tx_destination`. */ + bool dma_buffer_in_psram; /*!< Allocate the driver-owned DMA buffers in PSRAM. + * The DMA descriptors are always allocated in internal RAM. + * If PSRAM DMA is unsupported or allocation fails, channel initialization fails without falling back to internal RAM. + */ } i2s_chan_config_t; /** diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index acc65771c96..db32137c5fc 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -5,8 +5,10 @@ */ #include +#include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -17,6 +19,7 @@ #include "esp_private/gpio.h" #include "esp_err.h" #include "esp_attr.h" +#include "esp_memory_utils.h" #include "unity.h" #include "math.h" #include "esp_rom_gpio.h" @@ -45,12 +48,11 @@ #include "../../test_inc/test_i2s.h" +#if I2S_LL_GET(INST_NUM) > 1 && !CONFIG_ESP32P4_SELECTS_REV_LESS_V3 #define I2S_TEST_MODE_SLAVE_TO_MASTER 0 #define I2S_TEST_MODE_MASTER_TO_SLAVE 1 -#define I2S_TEST_MODE_LOOPBACK 2 -// mode: 0, master rx, slave tx. mode: 1, master tx, slave rx. mode: 2, master tx rx loop-back -// Since ESP32-S2 has only one I2S, only loop back test can be tested. +// mode: 0, master rx, slave tx. mode: 1, master tx, slave rx. static void i2s_test_io_config(int mode) { // Connect internal signals using IO matrix. @@ -88,18 +90,13 @@ static void i2s_test_io_config(int mode) } break; #endif - case I2S_TEST_MODE_LOOPBACK: { - esp_rom_gpio_connect_out_signal(DATA_OUT_IO, i2s_periph_signal[0].data_out_sig, 0, 0); - esp_rom_gpio_connect_in_signal(DATA_OUT_IO, i2s_periph_signal[0].data_in_sig, 0); - } - break; - default: { TEST_FAIL_MESSAGE("error: mode not supported"); } break; } } +#endif void i2s_read_write_test(i2s_chan_handle_t tx_chan, i2s_chan_handle_t rx_chan) { @@ -879,41 +876,270 @@ TEST_CASE("I2S_memory_leak_test", "[i2s]") printf("\r\nHeap size after: %"PRIu32"\n", esp_get_free_heap_size()); } -TEST_CASE("I2S_loopback_test", "[i2s]") +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM +static IRAM_ATTR bool i2s_record_external_ram_buf(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) +{ + *((volatile bool *)user_ctx) |= esp_ptr_external_ram(event->dma_buf); + return false; +} +#endif + +/* Re-enable after disable checks that the circular DMA link restarts from its head. */ +static void i2s_std_master_loopback_test(bool dma_buffer_in_psram) { i2s_chan_handle_t tx_handle; i2s_chan_handle_t rx_handle; - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.dma_buffer_in_psram = dma_buffer_in_psram; i2s_std_config_t std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE), .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO), .gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN, }; + std_cfg.gpio_cfg.din = std_cfg.gpio_cfg.dout; +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM + volatile bool tx_in_ext = false; + volatile bool rx_in_ext = false; +#endif + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); - i2s_test_io_config(I2S_TEST_MODE_LOOPBACK); +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM + if (dma_buffer_in_psram) { + i2s_event_callbacks_t tx_cbs = { + .on_sent = i2s_record_external_ram_buf, + }; + i2s_event_callbacks_t rx_cbs = { + .on_recv = i2s_record_external_ram_buf, + }; + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &tx_cbs, (void *)&tx_in_ext)); + TEST_ESP_OK(i2s_channel_register_event_callback(rx_handle, &rx_cbs, (void *)&rx_in_ext)); + } +#endif - TEST_ESP_OK(i2s_channel_enable(tx_handle)); - TEST_ESP_OK(i2s_channel_enable(rx_handle)); - - i2s_read_write_test(tx_handle, rx_handle); - - TEST_ESP_OK(i2s_channel_disable(tx_handle)); - TEST_ESP_OK(i2s_channel_disable(rx_handle)); - - /* Verify the circular DMA link restarts from its head after both channels are re-enabled. */ TEST_ESP_OK(i2s_channel_enable(tx_handle)); TEST_ESP_OK(i2s_channel_enable(rx_handle)); i2s_read_write_test(tx_handle, rx_handle); TEST_ESP_OK(i2s_channel_disable(tx_handle)); TEST_ESP_OK(i2s_channel_disable(rx_handle)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + TEST_ESP_OK(i2s_channel_enable(rx_handle)); + i2s_read_write_test(tx_handle, rx_handle); + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_channel_disable(rx_handle)); + +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM + if (dma_buffer_in_psram) { + TEST_ASSERT_TRUE(tx_in_ext); + TEST_ASSERT_TRUE(rx_in_ext); + } +#endif TEST_ESP_OK(i2s_del_channel(tx_handle)); TEST_ESP_OK(i2s_del_channel(rx_handle)); } +TEST_CASE("I2S_loopback_test", "[i2s]") +{ + i2s_std_master_loopback_test(false); + +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM + i2s_std_master_loopback_test(true); +#endif +} + +#if !(SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM) +TEST_CASE("I2S_psram_dma_buffer_rejected_when_unavailable", "[i2s]") +{ + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + chan_cfg.dma_buffer_in_psram = true; + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL)); +} +#endif + +#if CONFIG_I2S_ISR_IRAM_SAFE && SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM +TEST_CASE("I2S_psram_auto_clear_rejected_with_iram_safe", "[i2s]") +{ + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + chan_cfg.dma_buffer_in_psram = true; + + chan_cfg.auto_clear_before_cb = true; + chan_cfg.auto_clear_after_cb = false; + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + + chan_cfg.auto_clear_before_cb = false; + chan_cfg.auto_clear_after_cb = true; + TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx_handle, NULL)); +} +#endif + +#if SOC_I2S_SUPPORTS_TDM +#define I2S_TDM_INTEGRITY_MAGIC 0xA55A5AA5UL +#define I2S_TDM_INTEGRITY_DESC_NUM 6 +#define I2S_TDM_INTEGRITY_FRAME_NUM 32 +#define I2S_TDM_INTEGRITY_VERIFY_FRAMES 1024 +#define I2S_TDM_INTEGRITY_PREAMBLE_FRAMES 200 +#define I2S_TDM_INTEGRITY_SEND_FRAMES (I2S_TDM_INTEGRITY_PREAMBLE_FRAMES + I2S_TDM_INTEGRITY_VERIFY_FRAMES) + +typedef struct { + uint32_t magic; + uint32_t seq; + uint32_t seq_inv; + uint32_t checksum; +} i2s_tdm_integrity_frame_t; + +_Static_assert(sizeof(i2s_tdm_integrity_frame_t) == 16, "TDM integrity frame must be 16 bytes"); + +static uint32_t i2s_tdm_integrity_checksum(uint32_t magic, uint32_t seq, uint32_t seq_inv) +{ + /* Mix seq before combining with seq_inv so seq ^ ~seq cannot collapse to a constant. */ + uint32_t c = magic ^ (seq * 0x9E3779B1UL); + c = (c << 7) | (c >> 25); + return c ^ seq_inv ^ 0x13579BDFUL; +} + +static void i2s_tdm_integrity_fill_frame(i2s_tdm_integrity_frame_t *frame, uint32_t seq) +{ + frame->magic = I2S_TDM_INTEGRITY_MAGIC; + frame->seq = seq; + frame->seq_inv = ~seq; + frame->checksum = i2s_tdm_integrity_checksum(frame->magic, frame->seq, frame->seq_inv); +} + +static bool i2s_tdm_integrity_frame_valid(const i2s_tdm_integrity_frame_t *frame) +{ + return frame->magic == I2S_TDM_INTEGRITY_MAGIC && + frame->seq_inv == ~frame->seq && + frame->checksum == i2s_tdm_integrity_checksum(frame->magic, frame->seq, frame->seq_inv); +} + +typedef struct { + i2s_chan_handle_t tx_handle; + const uint8_t *buf; + size_t len; + volatile esp_err_t ret; + volatile bool done; +} i2s_tdm_integrity_writer_ctx_t; + +static void i2s_tdm_integrity_writer_task(void *args) +{ + i2s_tdm_integrity_writer_ctx_t *ctx = (i2s_tdm_integrity_writer_ctx_t *)args; + size_t bytes_written = 0; + ctx->ret = i2s_channel_write(ctx->tx_handle, ctx->buf, ctx->len, &bytes_written, 2000); + if (ctx->ret == ESP_OK && bytes_written != ctx->len) { + ctx->ret = ESP_ERR_INVALID_SIZE; + } + ctx->done = true; + vTaskDelete(NULL); +} + +static void i2s_tdm_integrity_loopback_test(bool dma_buffer_in_psram) +{ + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_handle_t rx_handle = NULL; + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.dma_desc_num = I2S_TDM_INTEGRITY_DESC_NUM; + chan_cfg.dma_frame_num = I2S_TDM_INTEGRITY_FRAME_NUM; + chan_cfg.dma_buffer_in_psram = dma_buffer_in_psram; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(SAMPLE_RATE), + .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO, 0x0F), + .gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN, + }; + tdm_cfg.gpio_cfg.din = tdm_cfg.gpio_cfg.dout; + /* Full-duplex RX is forced to slave; default mclk/bclk=3 is below the driver's measured minimum of 4. */ + tdm_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_512; +#if CONFIG_IDF_TARGET_ESP32S31 + // S31 uses XTAL as default clock source, which cannot support high sample rate in this test + tdm_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; +#endif + const size_t send_bytes = I2S_TDM_INTEGRITY_SEND_FRAMES * sizeof(i2s_tdm_integrity_frame_t); + /* Read less than we write so TX is still feeding the bus while RX finishes. */ + const size_t recv_frame_budget = I2S_TDM_INTEGRITY_PREAMBLE_FRAMES + I2S_TDM_INTEGRITY_VERIFY_FRAMES; + const size_t recv_bytes = recv_frame_budget * sizeof(i2s_tdm_integrity_frame_t); + i2s_tdm_integrity_frame_t *send_frames = calloc(I2S_TDM_INTEGRITY_SEND_FRAMES, sizeof(i2s_tdm_integrity_frame_t)); + uint8_t *recv_buf = calloc(1, recv_bytes); + TEST_ASSERT_NOT_NULL(send_frames); + TEST_ASSERT_NOT_NULL(recv_buf); + + for (uint32_t i = 0; i < I2S_TDM_INTEGRITY_SEND_FRAMES; i++) { + i2s_tdm_integrity_fill_frame(&send_frames[i], i); + } + + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); + TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_handle, &tdm_cfg)); + TEST_ESP_OK(i2s_channel_init_tdm_mode(rx_handle, &tdm_cfg)); + + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + TEST_ESP_OK(i2s_channel_enable(rx_handle)); + + i2s_tdm_integrity_writer_ctx_t writer_ctx = { + .tx_handle = tx_handle, + .buf = (const uint8_t *)send_frames, + .len = send_bytes, + .ret = ESP_FAIL, + .done = false, + }; + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(i2s_tdm_integrity_writer_task, "i2s_tdm_wr", 4096, &writer_ctx, 5, NULL)); + + size_t bytes_read = 0; + TEST_ESP_OK(i2s_channel_read(rx_handle, recv_buf, recv_bytes, &bytes_read, 2000)); + TEST_ASSERT_EQUAL(recv_bytes, bytes_read); + + while (!writer_ctx.done) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + /* Writer already called vTaskDelete; yield so idle can reclaim its TCB/stack. */ + vTaskDelay(1); + TEST_ESP_OK(writer_ctx.ret); + + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_channel_disable(rx_handle)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); + TEST_ESP_OK(i2s_del_channel(rx_handle)); + + const i2s_tdm_integrity_frame_t *recv_frames = (const i2s_tdm_integrity_frame_t *)recv_buf; + const size_t recv_frame_count = bytes_read / sizeof(i2s_tdm_integrity_frame_t); + bool synced = false; + uint32_t expected_seq = 0; + uint32_t verified = 0; + + for (size_t i = 0; i + 1 < recv_frame_count && verified < I2S_TDM_INTEGRITY_VERIFY_FRAMES; i++) { + if (!synced) { + if (i2s_tdm_integrity_frame_valid(&recv_frames[i])) { + expected_seq = recv_frames[i].seq; + synced = true; + } else { // skip non-synced preamble frames + continue; + } + } + + TEST_ASSERT_TRUE_MESSAGE(i2s_tdm_integrity_frame_valid(&recv_frames[i]), + "corrupted TDM integrity frame after sync"); + TEST_ASSERT_EQUAL_UINT32(expected_seq, recv_frames[i].seq); + expected_seq++; + verified++; + } + + TEST_ASSERT_TRUE_MESSAGE(synced, "failed to find TDM integrity sync frame\n"); + TEST_ASSERT_EQUAL_UINT32(I2S_TDM_INTEGRITY_VERIFY_FRAMES, verified); + free(send_frames); + free(recv_buf); +} + +TEST_CASE("I2S_tdm_integrity_loopback_test", "[i2s]") +{ + i2s_tdm_integrity_loopback_test(false); +#if SOC_PSRAM_DMA_CAPABLE && CONFIG_SPIRAM + i2s_tdm_integrity_loopback_test(true); +#endif +} +#endif // SOC_I2S_SUPPORTS_TDM + #if I2S_LL_GET(INST_NUM) > 1 && !CONFIG_ESP32P4_SELECTS_REV_LESS_V3 TEST_CASE("I2S_master_write_slave_read_test", "[i2s]") { diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c5 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c5 new file mode 100644 index 00000000000..db575808cf9 --- /dev/null +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c5 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c61 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c61 new file mode 100644 index 00000000000..db575808cf9 --- /dev/null +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32c61 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32h4 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32h4 new file mode 100644 index 00000000000..db575808cf9 --- /dev/null +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32h4 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s2 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s2 new file mode 100644 index 00000000000..db575808cf9 --- /dev/null +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s2 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s31 b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s31 new file mode 100644 index 00000000000..db575808cf9 --- /dev/null +++ b/components/esp_driver_i2s/test_apps/i2s/sdkconfig.defaults.esp32s31 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 diff --git a/components/esp_driver_spi/src/gpspi/spi_common.c b/components/esp_driver_spi/src/gpspi/spi_common.c index f4b7ea637c5..51b8fe91e8e 100644 --- a/components/esp_driver_spi/src/gpspi/spi_common.c +++ b/components/esp_driver_spi/src/gpspi/spi_common.c @@ -267,7 +267,7 @@ static esp_err_t alloc_dma_chan(spi_host_device_t host_id, spi_dma_chan_t dma_ch #define SPI_GDMA_NEW_CHANNEL gdma_new_ahb_channel #endif -static esp_err_t resolve_dma_burst_size(uint32_t requested, uint32_t *out_burst_size) +static uint32_t resolve_dma_burst_size(uint32_t requested) { uint32_t burst_size = requested; if (burst_size == 0) { @@ -280,8 +280,7 @@ static esp_err_t resolve_dma_burst_size(uint32_t requested, uint32_t *out_burst_ } burst_size = SPI_DMA_DEFAULT_BURST_SIZE; #endif - *out_burst_size = burst_size; - return ESP_OK; + return burst_size; } static esp_err_t alloc_dma_chan(spi_host_device_t host_id, spi_dma_chan_t dma_chan, uint32_t dma_burst_size, spi_dma_ctx_t *dma_ctx) @@ -291,8 +290,7 @@ static esp_err_t alloc_dma_chan(spi_host_device_t host_id, spi_dma_chan_t dma_ch esp_err_t ret = ESP_OK; if (dma_chan == SPI_DMA_CH_AUTO) { - uint32_t burst_size = SPI_DMA_DEFAULT_BURST_SIZE; - ESP_RETURN_ON_ERROR(resolve_dma_burst_size(dma_burst_size, &burst_size), SPI_TAG, "invalid dma_burst_size"); + uint32_t burst_size = resolve_dma_burst_size(dma_burst_size); gdma_channel_alloc_config_t alloc_config = { #if CONFIG_SPI_MASTER_ISR_IN_IRAM diff --git a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h index f757652c380..1ea640793b2 100644 --- a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -21,6 +21,11 @@ extern "C" { #define AHB_DMA_LL_GET_HW(id) (((id) == 0) ? (&AHB_DMA) : NULL) +#define AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] #define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15] diff --git a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h index b5ae691b50e..3e936b5f385 100644 --- a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h @@ -21,6 +21,10 @@ extern "C" { #define AHB_DMA_LL_GET_HW(id) (((id) == 0) ? (&AHB_DMA) : NULL) +#define AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32) + #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] #define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15] diff --git a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h index 30a97a2fc10..b5becda79b7 100644 --- a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -21,6 +21,11 @@ extern "C" { #define AHB_DMA_LL_GET_HW(id) (((id) == 0) ? (&AHB_DMA) : NULL) +#define AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] #define AHB_DMA_LL_RX_EVENT_MASK (0x7F) diff --git a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h index b96f2267e9e..49eac86ef6a 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +22,11 @@ extern "C" { #define AHB_DMA_LL_GET_HW(id) (((id) == 0) ? (&AHB_DMA) : NULL) +#define AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + // any "dummy" peripheral ID can be used for M2M mode #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFAC2) #define AHB_DMA_LL_RX_EVENT_MASK (0x1F) diff --git a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h index 7717aad3371..2782c27d9fd 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h @@ -24,6 +24,12 @@ extern "C" { #define AXI_DMA_LL_GET_HW(id) (((id) == 0) ? (&AXI_DMA) : NULL) #define AXI_DMA_LL_SUPPORT(_feat) AXI_DMA_LL_SUPPORT_ ## _feat +#define AXI_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_8 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64 | \ + GDMA_BURST_SIZE_SUPPORT_128) + // any "dummy" peripheral ID can be used for M2M mode #define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0) #define AXI_DMA_LL_RX_EVENT_MASK (0x1F) diff --git a/components/esp_hal_dma/esp32s3/include/hal/gdma_ll.h b/components/esp_hal_dma/esp32s3/include/hal/gdma_ll.h index 43af26638ee..a7f07bdf3da 100644 --- a/components/esp_hal_dma/esp32s3/include/hal/gdma_ll.h +++ b/components/esp_hal_dma/esp32s3/include/hal/gdma_ll.h @@ -19,6 +19,10 @@ #define GDMA_LL_INST_NUM 1 #define GDMA_LL_PAIRS_PER_INST GDMA_LL_AHB_PAIRS_PER_GROUP +#define GDMA_LL_AHB_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + #ifdef __cplusplus extern "C" { #endif diff --git a/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h index 4528aaa3640..a2da783d1eb 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/ahb_dma_ll.h @@ -20,6 +20,16 @@ extern "C" { #define AHB_DMA_LL_GET_HW(id) (((id) == 0) ? (&AHB_DMA) : (((id) == 2) ? ((ahb_dma_dev_t *)&LP_AHB_DMA) : NULL)) +#define AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + +#define LP_AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_4 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64) + // any "dummy" peripheral ID can be used for M2M mode #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0x8200) #define AHB_DMA_LL_RX_EVENT_MASK (0x7F) diff --git a/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h b/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h index a5f8083d09a..8cb5fd3dda0 100644 --- a/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h +++ b/components/esp_hal_dma/esp32s31/include/hal/axi_dma_ll.h @@ -23,6 +23,12 @@ extern "C" { #define AXI_DMA_LL_GET_HW(id) (((id) == 0) ? (&AXI_DMA) : NULL) #define AXI_DMA_LL_SUPPORT(_feat) AXI_DMA_LL_SUPPORT_ ## _feat +#define AXI_DMA_LL_SUPPORTED_BURST_SIZE_MASK (GDMA_BURST_SIZE_SUPPORT_8 | \ + GDMA_BURST_SIZE_SUPPORT_16 | \ + GDMA_BURST_SIZE_SUPPORT_32 | \ + GDMA_BURST_SIZE_SUPPORT_64 | \ + GDMA_BURST_SIZE_SUPPORT_128) + // any "dummy" peripheral ID can be used for M2M mode #define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0) #define AXI_DMA_LL_RX_EVENT_MASK (0x7F) diff --git a/components/esp_hal_dma/gdma_hal_ahb_v1.c b/components/esp_hal_dma/gdma_hal_ahb_v1.c index 10b7c1a668e..650c23604e6 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v1.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v1.c @@ -13,6 +13,9 @@ static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = { .m2m_free_periph_mask = GDMA_LL_M2M_FREE_PERIPH_ID_MASK, .tx_event_mask = GDMA_LL_TX_EVENT_MASK, .rx_event_mask = GDMA_LL_RX_EVENT_MASK, +#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + .supported_burst_size_mask = GDMA_LL_AHB_SUPPORTED_BURST_SIZE_MASK, +#endif }; void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) diff --git a/components/esp_hal_dma/gdma_hal_ahb_v2.c b/components/esp_hal_dma/gdma_hal_ahb_v2.c index 69e1a48323e..0d0706258b8 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v2.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v2.c @@ -14,6 +14,9 @@ static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = { .m2m_free_periph_mask = AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK, .tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK, .rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK, +#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + .supported_burst_size_mask = AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK, +#endif }; void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) @@ -315,6 +318,9 @@ static gdma_hal_priv_data_t gdma_lp_ahb_hal_priv_data = { .m2m_free_periph_mask = LP_AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK, .tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK, .rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK, +#if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + .supported_burst_size_mask = LP_AHB_DMA_LL_SUPPORTED_BURST_SIZE_MASK, +#endif }; void gdma_lp_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) diff --git a/components/esp_hal_dma/gdma_hal_axi.c b/components/esp_hal_dma/gdma_hal_axi.c index d235964be3a..fe76bf33aec 100644 --- a/components/esp_hal_dma/gdma_hal_axi.c +++ b/components/esp_hal_dma/gdma_hal_axi.c @@ -14,6 +14,7 @@ static gdma_hal_priv_data_t gdma_axi_hal_priv_data = { .m2m_free_periph_mask = AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK, .tx_event_mask = AXI_DMA_LL_TX_EVENT_MASK, .rx_event_mask = AXI_DMA_LL_RX_EVENT_MASK, + .supported_burst_size_mask = AXI_DMA_LL_SUPPORTED_BURST_SIZE_MASK, }; void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) diff --git a/components/esp_hal_dma/gdma_hal_top.c b/components/esp_hal_dma/gdma_hal_top.c index 71bc6ef6de6..9d8d08c7ea1 100644 --- a/components/esp_hal_dma/gdma_hal_top.c +++ b/components/esp_hal_dma/gdma_hal_top.c @@ -65,6 +65,19 @@ void gdma_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_ } } +bool gdma_hal_check_burst_size(gdma_hal_context_t *hal, uint32_t burst_sz) +{ + if (burst_sz & (burst_sz - 1)) { + // Not a power of 2 + return false; + } + if (!hal->set_burst_size) { + // When a specific burst size cannot be set (using fixed burst size) + return true; + } + return (hal->priv_data->supported_burst_size_mask & burst_sz) != 0; +} + void gdma_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped) { hal->set_strategy(hal, chan_id, dir, en_owner_check, en_desc_write_back, eof_till_popped); diff --git a/components/esp_hal_dma/include/hal/gdma_hal.h b/components/esp_hal_dma/include/hal/gdma_hal.h index 60fe7b970ed..8432b5eaf8b 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal.h +++ b/components/esp_hal_dma/include/hal/gdma_hal.h @@ -55,6 +55,8 @@ typedef struct { // Supported interrupt events can vary across DMA instances (e.g. AHB vs AXI) uint32_t tx_event_mask; uint32_t rx_event_mask; + // Bitmap of supported configurable data burst sizes, using gdma_burst_size_support_t + uint32_t supported_burst_size_mask; } gdma_hal_priv_data_t; /** @@ -128,6 +130,8 @@ void gdma_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_di void gdma_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz); +bool gdma_hal_check_burst_size(gdma_hal_context_t *hal, uint32_t burst_sz); + void gdma_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped); void gdma_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis); diff --git a/components/esp_hal_dma/include/hal/gdma_types.h b/components/esp_hal_dma/include/hal/gdma_types.h index d2b47cc2986..1ce3bf94c80 100644 --- a/components/esp_hal_dma/include/hal/gdma_types.h +++ b/components/esp_hal_dma/include/hal/gdma_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +22,18 @@ typedef enum { GDMA_CHANNEL_DIRECTION_RX, /*!< GDMA channel direction: RX */ } gdma_channel_direction_t; +/** + * @brief Bit definitions used by the LL layer to describe supported data burst sizes + */ +typedef enum { + GDMA_BURST_SIZE_SUPPORT_4 = 1U << 2, + GDMA_BURST_SIZE_SUPPORT_8 = 1U << 3, + GDMA_BURST_SIZE_SUPPORT_16 = 1U << 4, + GDMA_BURST_SIZE_SUPPORT_32 = 1U << 5, + GDMA_BURST_SIZE_SUPPORT_64 = 1U << 6, + GDMA_BURST_SIZE_SUPPORT_128 = 1U << 7, +} gdma_burst_size_support_t; + /** * @brief GDMA channel events that supported by the ETM module */ diff --git a/components/esp_hal_i2s/esp32s2/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32s2/include/hal/i2s_ll.h index 654f52454e9..bd5dd3cf337 100644 --- a/components/esp_hal_i2s/esp32s2/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32s2/include/hal/i2s_ll.h @@ -27,6 +27,8 @@ #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_BUS_WIDTH 24 #define I2S_LL_INST_NUM 1 +#define I2S_LL_SUPPORT_DMA_EXT_MEM 1 +#define I2S_LL_DMA_EXT_MEM_ALIGNMENT 16 #ifdef __cplusplus extern "C" { @@ -84,6 +86,43 @@ static inline void i2s_ll_dma_enable_eof_on_fifo_empty(i2s_dev_t *hw, bool en) hw->lc_conf.out_eof_mode = en; } +/** + * @brief Set the external-memory block size used by the I2S DMA + * + * @param hw Peripheral I2S hardware instance address + * @param block_size External-memory block size in bytes, valid values are 16, 32 and 64 + */ +static inline void i2s_ll_dma_set_ext_mem_block_size(i2s_dev_t *hw, uint32_t block_size) +{ + HAL_ASSERT(block_size == 16 || block_size == 32 || block_size == 64); + hw->lc_conf.ext_mem_bk_size = __builtin_ctz(block_size) - 4; +} + +/** + * @brief Enable DMA burst access for TX data and descriptors + * + * @param hw Peripheral I2S hardware instance address + * @param en Whether to enable burst access + */ +static inline void i2s_ll_dma_tx_enable_burst(i2s_dev_t *hw, bool en) +{ + hw->lc_conf.out_data_burst_en = en; + hw->lc_conf.outdscr_burst_en = en; +} + +/** + * @brief Enable DMA burst access for RX descriptors + * + * @note ESP32-S2 does not provide a burst-enable bit for RX data. + * + * @param hw Peripheral I2S hardware instance address + * @param en Whether to enable descriptor burst access + */ +static inline void i2s_ll_dma_rx_enable_burst(i2s_dev_t *hw, bool en) +{ + hw->lc_conf.indscr_burst_en = en; +} + /** * @brief Enable the bus clock for I2S module * diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index f4345cf301d..374f3bf6282 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -291,6 +291,12 @@ The data transport of the I2S peripheral, including sending and receiving, is re Both :cpp:func:`i2s_channel_write` and :cpp:func:`i2s_channel_read` are blocking functions. They keeps waiting until the whole source buffer is sent or the whole destination buffer is loaded, unless they exceed the max blocking time, where the error code ``ESP_ERR_TIMEOUT`` returns. To send or receive data asynchronously, callbacks can be registered by :cpp:func:`i2s_channel_register_event_callback`. Users are able to access the DMA buffer directly in the callback function instead of transmitting or receiving by the two blocking functions. However, please be aware that it is an interrupt callback, so do not add complex logic, run floating operation, or call non-reentrant functions in the callback. +:cpp:member:`i2s_chan_config_t::dma_burst_size` sets the DMA burst size in bytes. Set it to ``0`` (the value used by :c:macro:`I2S_CHANNEL_DEFAULT_CONFIG`) to apply the driver default of 32 bytes. A non-zero value must be a power of 2 supported by the chip GDMA. The field is ignored on chips that do not support configurable DMA burst size. + +.. only:: SOC_PSRAM_DMA_CAPABLE + + To reduce internal RAM usage, set :cpp:member:`i2s_chan_config_t::dma_buffer_in_psram` to allocate the driver-owned DMA buffers in PSRAM. DMA descriptors remain in internal RAM. The driver returns an error if PSRAM DMA is unavailable or the allocation fails; it does not fall back to internal RAM. When :ref:`CONFIG_I2S_ISR_IRAM_SAFE` is enabled, TX auto-clear cannot be used with PSRAM DMA buffers, and callbacks must not access the DMA buffer while the cache is disabled. + .. only:: SOC_I2S_SUPPORTS_BT_DEST On {IDF_TARGET_NAME}, when calling :cpp:func:`i2s_new_channel`, you can select the data path for TX and RX separately via :cpp:member:`i2s_chan_config_t::tx_destination` and :cpp:member:`i2s_chan_config_t::rx_destination`. For each direction you can choose either **DMA** or **Bluetooth**: diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 8b3b7ffeac2..2d472aa6a9b 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -291,6 +291,12 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 :cpp:func:`i2s_channel_write` 和 :cpp:func:`i2s_channel_read` 都是阻塞函数,在源缓冲区的数据发送完毕前,或是整个目标缓冲区都被加载数据占用时,它们会一直保持等待状态。在等待时间达到最大阻塞时间时,返回 ``ESP_ERR_TIMEOUT`` 错误。要实现异步发送或接收数据,可以通过 :cpp:func:`i2s_channel_register_event_callback` 注册回调,随即便可在回调函数中直接访问 DMA 缓冲区,无需通过这两个阻塞函数来发送或接收数据。但请注意,该回调是一个中断回调,不要在该回调中添加复杂的逻辑、进行浮点运算或调用不可重入函数。 +:cpp:member:`i2s_chan_config_t::dma_burst_size` 用于设置 DMA 突发传输大小(字节)。设为 ``0`` (:c:macro:`I2S_CHANNEL_DEFAULT_CONFIG` 的默认值)时,驱动使用默认值 32 字节。非 0 值必须是芯片 GDMA 支持的 2 的幂。在不支持配置 DMA 突发大小的芯片上,该字段会被忽略。 + +.. only:: SOC_PSRAM_DMA_CAPABLE + + 为减少内部 RAM 占用,可设置 :cpp:member:`i2s_chan_config_t::dma_buffer_in_psram`,将驱动管理的 DMA 缓冲区分配到 PSRAM 中。DMA 描述符仍分配在内部 RAM 中。如果 PSRAM DMA 不可用或分配失败,驱动将返回错误,而不会回退到内部 RAM。启用 :ref:`CONFIG_I2S_ISR_IRAM_SAFE` 时,TX 自动清零功能不能与 PSRAM DMA 缓冲区同时使用,且在 cache 被禁用期间,回调函数不得访问 DMA 缓冲区。 + .. only:: SOC_I2S_SUPPORTS_BT_DEST 在 {IDF_TARGET_NAME} 上,可在调用 :cpp:func:`i2s_new_channel` 时通过 :cpp:member:`i2s_chan_config_t::tx_destination` 与 :cpp:member:`i2s_chan_config_t::rx_destination` 分别为 TX 与 RX 方向选择数据路径;每个方向可在 **DMA** 与 **Bluetooth** 二者中择一: