From 145bf152deb4c56ebe585624f5b12baf7bb1e104 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Wed, 13 May 2026 16:14:16 +0800 Subject: [PATCH] feat(i2s): add BLE destination config for esp32s31 --- components/esp_driver_i2s/i2s_common.c | 111 ++++++++++++++---- components/esp_driver_i2s/i2s_pdm.c | 42 ++++--- components/esp_driver_i2s/i2s_private.h | 7 ++ components/esp_driver_i2s/i2s_std.c | 13 +- components/esp_driver_i2s/i2s_tdm.c | 19 ++- .../include/driver/i2s_common.h | 12 +- .../test_apps/i2s/main/test_i2s.c | 72 +++++++++++- .../esp_hal_i2s/esp32/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32c3/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32c5/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32c6/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32c61/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32h2/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32h21/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32h4/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32p4/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32s2/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32s3/include/hal/i2s_ll.h | 46 ++++++++ .../esp_hal_i2s/esp32s31/include/hal/i2s_ll.h | 82 +++++++++++++ components/esp_hal_i2s/i2s_hal.c | 3 +- components/esp_hal_i2s/include/hal/i2s_hal.h | 2 +- .../esp_hal_i2s/include/hal/i2s_types.h | 10 +- .../esp32s31/include/soc/Kconfig.soc_caps.in | 4 + .../soc/esp32s31/include/soc/soc_caps.h | 1 + .../soc/esp32s31/register/soc/i2s_reg.h | 4 +- .../soc/esp32s31/register/soc/i2s_struct.h | 4 +- docs/en/api-reference/peripherals/i2s.rst | 9 ++ docs/zh_CN/api-reference/peripherals/i2s.rst | 9 ++ 28 files changed, 850 insertions(+), 60 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index e25ffd33aa0..81b9ae5fe03 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -24,6 +24,7 @@ #include "hal/i2s_periph.h" #include "soc/soc_caps.h" #include "hal/i2s_hal.h" +#include "hal/i2s_types.h" #include "hal/hal_utils.h" #include "hal/dma_types.h" #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE @@ -120,13 +121,17 @@ static void i2s_tx_channel_start(i2s_chan_handle_t handle) { i2s_hal_tx_reset(&(handle->controller->hal)); #if SOC_GDMA_SUPPORTED - gdma_reset((handle->dma.dma_chan)); + if (handle->dma.dma_chan) { + gdma_reset((handle->dma.dma_chan)); + } #else i2s_hal_tx_reset_dma(&(handle->controller->hal)); #endif i2s_hal_tx_reset_fifo(&(handle->controller->hal)); #if SOC_GDMA_SUPPORTED - gdma_start((handle->dma.dma_chan), (uint32_t) handle->dma.desc[0]); + if (handle->dma.dma_chan) { + gdma_start((handle->dma.dma_chan), (uint32_t) handle->dma.desc[0]); + } #else esp_intr_enable(handle->dma.dma_chan); i2s_hal_tx_enable_intr(&(handle->controller->hal)); @@ -142,13 +147,17 @@ static void i2s_rx_channel_start(i2s_chan_handle_t handle) { i2s_hal_rx_reset(&(handle->controller->hal)); #if SOC_GDMA_SUPPORTED - gdma_reset(handle->dma.dma_chan); + if (handle->dma.dma_chan) { + gdma_reset(handle->dma.dma_chan); + } #else i2s_hal_rx_reset_dma(&(handle->controller->hal)); #endif i2s_hal_rx_reset_fifo(&(handle->controller->hal)); #if SOC_GDMA_SUPPORTED - gdma_start(handle->dma.dma_chan, (uint32_t) handle->dma.desc[0]); + if (handle->dma.dma_chan) { + gdma_start(handle->dma.dma_chan, (uint32_t) handle->dma.desc[0]); + } #else esp_intr_enable(handle->dma.dma_chan); i2s_hal_rx_enable_intr(&(handle->controller->hal)); @@ -166,7 +175,9 @@ static void i2s_tx_channel_stop(i2s_chan_handle_t handle) i2s_hal_tx_stop(&(handle->controller->hal)); } #if SOC_GDMA_SUPPORTED - gdma_stop(handle->dma.dma_chan); + if (handle->dma.dma_chan) { + gdma_stop(handle->dma.dma_chan); + } #else i2s_hal_tx_stop_link(&(handle->controller->hal)); i2s_hal_tx_disable_intr(&(handle->controller->hal)); @@ -181,7 +192,9 @@ static void i2s_rx_channel_stop(i2s_chan_handle_t handle) i2s_hal_rx_stop(&(handle->controller->hal)); } #if SOC_GDMA_SUPPORTED - gdma_stop(handle->dma.dma_chan); + if (handle->dma.dma_chan) { + gdma_stop(handle->dma.dma_chan); + } #else i2s_hal_rx_stop_link(&(handle->controller->hal)); i2s_hal_rx_disable_intr(&(handle->controller->hal)); @@ -305,7 +318,7 @@ static inline bool i2s_take_available_channel(i2s_controller_t *i2s_obj, uint8_t return is_available; } -static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, uint32_t desc_num) +static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, uint32_t desc_num, bool use_dma) { I2S_NULL_POINTER_CHECK(TAG, i2s_obj); @@ -322,12 +335,17 @@ static esp_err_t i2s_register_channel(i2s_controller_t *i2s_obj, i2s_dir_t dir, #if CONFIG_PM_ENABLE new_chan->pm_lock = NULL; // Init in i2s_set_clock according to clock source #endif - new_chan->msg_queue = xQueueCreateWithCaps(desc_num - 1, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(new_chan->msg_queue, ESP_ERR_NO_MEM, err, TAG, "No memory for message queue"); + /* The message queue is only used to hand DMA buffer descriptors between the DMA ISR and + * read/write/preload tasks. The binary semaphore is only used to block read/write while the + * channel is disabled. Both are omitted when the data path does not use memory DMA, to save heap. */ + if (use_dma) { + new_chan->msg_queue = xQueueCreateWithCaps(desc_num - 1, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(new_chan->msg_queue, ESP_ERR_NO_MEM, err, TAG, "No memory for message queue"); + new_chan->binary = xSemaphoreCreateBinaryWithCaps(I2S_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(new_chan->binary, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore"); + } new_chan->mutex = xSemaphoreCreateMutexWithCaps(I2S_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(new_chan->mutex, ESP_ERR_NO_MEM, err, TAG, "No memory for mutex semaphore"); - new_chan->binary = xSemaphoreCreateBinaryWithCaps(I2S_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(new_chan->binary, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore"); new_chan->callbacks.on_recv = NULL; new_chan->callbacks.on_recv_q_ovf = NULL; @@ -410,6 +428,9 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 { I2S_NULL_POINTER_CHECK(TAG, handle); I2S_NULL_POINTER_CHECK(TAG, callbacks); + /* on_recv/on_sent are dispatched from the DMA ISR. If this channel does not use the DMA memory path, no such callback fires. */ + ESP_RETURN_ON_FALSE(I2S_CHANNEL_USES_DMA(handle), ESP_ERR_NOT_SUPPORTED, TAG, + "event callbacks require the DMA memory data path on this channel"); esp_err_t ret = ESP_OK; #if CONFIG_I2S_ISR_IRAM_SAFE if (callbacks->on_recv) { @@ -1021,9 +1042,24 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_RETURN_ON_FALSE(!chan_cfg->allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported"); #endif + int id = chan_cfg->id; + if (tx_handle) { + ESP_RETURN_ON_FALSE(chan_cfg->tx_destination == I2S_DESTINATION_DMA || chan_cfg->tx_destination == I2S_DESTINATION_BT, + ESP_ERR_INVALID_ARG, TAG, "invalid tx destination"); + } + if (rx_handle) { + ESP_RETURN_ON_FALSE(chan_cfg->rx_destination == I2S_DESTINATION_DMA || chan_cfg->rx_destination == I2S_DESTINATION_BT, + ESP_ERR_INVALID_ARG, TAG, "invalid rx destination"); + } + if (id != I2S_NUM_AUTO) { + ESP_RETURN_ON_FALSE(!tx_handle || i2s_ll_is_destination_supported(id, chan_cfg->tx_destination), + ESP_ERR_NOT_SUPPORTED, TAG, "tx destination is not supported on selected port"); + ESP_RETURN_ON_FALSE(!rx_handle || i2s_ll_is_destination_supported(id, chan_cfg->rx_destination), + ESP_ERR_NOT_SUPPORTED, TAG, "rx destination is not supported on selected port"); + } + esp_err_t ret = ESP_OK; i2s_controller_t *i2s_obj = NULL; - int id = chan_cfg->id; bool channel_found = false; uint8_t chan_search_mask = 0; chan_search_mask |= tx_handle ? I2S_DIR_TX : 0; @@ -1032,13 +1068,21 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * /* Channel will be registered to one i2s port automatically if id is I2S_NUM_AUTO * Otherwise, the channel will be registered to the specific port. */ if (id == I2S_NUM_AUTO) { + bool has_supported_port = false; for (int i = 0; i < I2S_LL_GET(INST_NUM) && !channel_found; i++) { + bool tx_dest_supported = !tx_handle || i2s_ll_is_destination_supported(i, chan_cfg->tx_destination); + bool rx_dest_supported = !rx_handle || i2s_ll_is_destination_supported(i, chan_cfg->rx_destination); + if (!tx_dest_supported || !rx_dest_supported) { + continue; + } + has_supported_port = true; i2s_obj = i2s_acquire_controller_obj(i); if (!i2s_obj) { continue; } channel_found = i2s_take_available_channel(i2s_obj, chan_search_mask); } + ESP_RETURN_ON_FALSE(has_supported_port, ESP_ERR_NOT_SUPPORTED, TAG, "requested destination is not supported"); ESP_RETURN_ON_FALSE(i2s_obj, ESP_ERR_NOT_FOUND, TAG, "get i2s object failed"); } else { i2s_obj = i2s_acquire_controller_obj(id); @@ -1048,7 +1092,8 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * 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) { - ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_TX, chan_cfg->dma_desc_num), + 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), 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; @@ -1059,12 +1104,14 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * 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) { - ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num), + 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), 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; @@ -1073,6 +1120,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * 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); } @@ -1264,14 +1312,16 @@ esp_err_t i2s_channel_enable(i2s_chan_handle_t handle) #endif handle->start(handle); handle->state = I2S_CHAN_STATE_RUNNING; - if (handle->dir == I2S_DIR_RX) { + if (handle->dir == I2S_DIR_RX && handle->msg_queue) { /* RX queue is reset when the channel is enabled In case legacy data received during disable process */ xQueueReset(handle->msg_queue); } xSemaphoreGive(handle->mutex); /* Give the binary semaphore to enable reading / writing task */ - xSemaphoreGive(handle->binary); + if (handle->binary) { + xSemaphoreGive(handle->binary); + } ESP_LOGD(TAG, "i2s %s channel enabled", handle->dir == I2S_DIR_TX ? "tx" : "rx"); return ret; @@ -1293,12 +1343,14 @@ esp_err_t i2s_channel_disable(i2s_chan_handle_t handle) /* Waiting for reading/wrinting operation quit * It should be acquired before assigning the pointer to NULL, * otherwise may cause NULL pointer panic while reading/writing threads haven't release the lock */ - xSemaphoreTake(handle->binary, portMAX_DELAY); + if (handle->binary) { + xSemaphoreTake(handle->binary, portMAX_DELAY); + } /* Reset the descriptor pointer */ handle->dma.curr_ptr = NULL; handle->dma.rw_pos = 0; handle->stop(handle); - if (handle->dir == I2S_DIR_TX) { + if (handle->dir == I2S_DIR_TX && handle->msg_queue) { /* TX queue is reset when the channel is disabled In case the queue is wrongly reset after preload the data */ xQueueReset(handle->msg_queue); @@ -1319,6 +1371,8 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, { I2S_NULL_POINTER_CHECK(TAG, tx_handle); ESP_RETURN_ON_FALSE(tx_handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "this channel is not tx channel"); + ESP_RETURN_ON_FALSE(I2S_CHANNEL_USES_DMA(tx_handle), ESP_ERR_NOT_SUPPORTED, TAG, + "preload requires the DMA memory data path on this channel"); ESP_RETURN_ON_FALSE(tx_handle->state == I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, TAG, "data can only be preloaded when the channel is READY"); uint8_t *data_ptr = (uint8_t *)src; @@ -1376,6 +1430,8 @@ esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t si { I2S_NULL_POINTER_CHECK(TAG, handle); ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "this channel is not tx channel"); + ESP_RETURN_ON_FALSE(I2S_CHANNEL_USES_DMA(handle), ESP_ERR_NOT_SUPPORTED, TAG, + "i2s_channel_write is unavailable when the DMA memory path is not selected"); esp_err_t ret = ESP_OK; char *data_ptr; @@ -1427,6 +1483,8 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si { I2S_NULL_POINTER_CHECK(TAG, handle); ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_RX, ESP_ERR_INVALID_ARG, TAG, "this channel is not rx channel"); + ESP_RETURN_ON_FALSE(I2S_CHANNEL_USES_DMA(handle), ESP_ERR_NOT_SUPPORTED, TAG, + "i2s_channel_read is unavailable when the DMA memory path is not selected"); esp_err_t ret = ESP_OK; uint8_t *data_ptr; @@ -1549,14 +1607,19 @@ result: if (tune_info) { tune_info->curr_mclk_hz = handle->curr_mclk_hz; tune_info->delta_mclk_hz = (int32_t)handle->curr_mclk_hz - (int32_t)handle->origin_mclk_hz; - uint32_t tot_size = handle->dma.buf_size * handle->dma.desc_num; - uint32_t used_size = 0; - if (handle->dir == I2S_DIR_TX) { - used_size = uxQueueSpacesAvailable(handle->msg_queue) * handle->dma.buf_size + handle->dma.rw_pos; + /* water_mark reflects DMA buffer occupancy. When this channel does not use the DMA memory path (e.g. Bluetooth), skip. */ + if (I2S_CHANNEL_USES_DMA(handle)) { + uint32_t tot_size = handle->dma.buf_size * handle->dma.desc_num; + uint32_t used_size = 0; + if (handle->dir == I2S_DIR_TX) { + used_size = uxQueueSpacesAvailable(handle->msg_queue) * handle->dma.buf_size + handle->dma.rw_pos; + } else { + used_size = uxQueueMessagesWaiting(handle->msg_queue) * handle->dma.buf_size + handle->dma.buf_size - handle->dma.rw_pos; + } + tune_info->water_mark = used_size * 100 / tot_size; } else { - used_size = uxQueueMessagesWaiting(handle->msg_queue) * handle->dma.buf_size + handle->dma.buf_size - handle->dma.rw_pos; + tune_info->water_mark = 0; } - tune_info->water_mark = used_size * 100 / tot_size; } xSemaphoreGive(handle->mutex); return ret; diff --git a/components/esp_driver_i2s/i2s_pdm.c b/components/esp_driver_i2s/i2s_pdm.c index 855aceca888..c7acb795598 100644 --- a/components/esp_driver_i2s/i2s_pdm.c +++ b/components/esp_driver_i2s/i2s_pdm.c @@ -126,9 +126,9 @@ err: static esp_err_t i2s_pdm_tx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_tx_slot_config_t *slot_cfg) { -#if !SOC_I2S_SUPPORTS_PCM2PDM - ESP_RETURN_ON_FALSE(slot_cfg->data_fmt == I2S_PDM_DATA_FMT_RAW, ESP_ERR_NOT_SUPPORTED, TAG, "not support PCM2PDM converter on this target"); -#endif + ESP_RETURN_ON_FALSE(slot_cfg->data_fmt != I2S_PDM_DATA_FMT_PCM || + i2s_ll_is_pcm2pdm_supported(handle->controller->id), + ESP_ERR_NOT_SUPPORTED, TAG, "PCM2PDM converter is not supported on selected port"); /* Update the total slot num and active slot num */ handle->is_raw_pdm = slot_cfg->data_fmt == I2S_PDM_DATA_FMT_RAW; handle->total_slot = 2; @@ -136,8 +136,9 @@ static esp_err_t i2s_pdm_tx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_tx_ uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num); ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width"); - /* The DMA buffer need to re-allocate if the buffer size changed */ - if (handle->dma.buf_size != buf_size) { + /* The DMA buffer need to re-allocate if the buffer size changed. + * Skip when GDMA is not the data path (e.g. Bluetooth destination), since the channel never owns a DMA buffer. */ + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buf_size != buf_size) { ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor"); ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size), TAG, "allocate memory for dma descriptor failed"); @@ -213,7 +214,8 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t #endif I2S_NULL_POINTER_CHECK(TAG, handle); ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "This channel handle is not a TX handle"); - ESP_RETURN_ON_FALSE(handle->controller->id == I2S_NUM_0, ESP_ERR_INVALID_ARG, TAG, "This channel handle is registered on I2S1, but PDM is only supported on I2S0"); + ESP_RETURN_ON_FALSE(i2s_ll_is_pdm_supported(handle->controller->id), + ESP_ERR_NOT_SUPPORTED, TAG, "PDM TX mode is not supported on selected port"); esp_err_t ret = ESP_OK; @@ -231,9 +233,12 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_slot(handle, &pdm_tx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_gpio(handle, &pdm_tx_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_clock(handle, &pdm_tx_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); - ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + } i2s_ll_tx_enable_pdm(handle->controller->hal.dev, pdm_tx_cfg->slot_cfg.data_fmt == I2S_PDM_DATA_FMT_PCM); + i2s_ll_set_destination(handle->controller->hal.dev, handle->dir, handle->destination); #ifdef CONFIG_PM_ENABLE esp_pm_lock_type_t pm_type = ESP_PM_APB_FREQ_MAX; @@ -244,7 +249,7 @@ 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 - ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), TAG, "I2S pm lock create failed"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif /* Initialization finished, mark state as ready */ @@ -470,9 +475,9 @@ err: static esp_err_t i2s_pdm_rx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_rx_slot_config_t *slot_cfg) { -#if !SOC_I2S_SUPPORTS_PDM2PCM - ESP_RETURN_ON_FALSE(slot_cfg->data_fmt == I2S_PDM_DATA_FMT_RAW, ESP_ERR_NOT_SUPPORTED, TAG, "not support PDM2PCM converter on this target"); -#endif + ESP_RETURN_ON_FALSE(slot_cfg->data_fmt != I2S_PDM_DATA_FMT_PCM || + i2s_ll_is_pdm2pcm_supported(handle->controller->id), + ESP_ERR_NOT_SUPPORTED, TAG, "PDM2PCM converter is not supported on selected port"); /* Update the total slot num and active slot num */ handle->is_raw_pdm = slot_cfg->data_fmt == I2S_PDM_DATA_FMT_RAW; handle->total_slot = 2; @@ -480,8 +485,9 @@ static esp_err_t i2s_pdm_rx_set_slot(i2s_chan_handle_t handle, const i2s_pdm_rx_ uint32_t buf_size = i2s_get_buf_size(handle, slot_cfg->data_bit_width, handle->dma.frame_num); ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width"); - /* The DMA buffer need to re-allocate if the buffer size changed */ - if (handle->dma.buf_size != buf_size) { + /* The DMA buffer need to re-allocate if the buffer size changed. + * Skip when GDMA is not the data path (e.g. Bluetooth destination), since the channel never owns a DMA buffer. */ + if (I2S_CHANNEL_USES_DMA(handle) && handle->dma.buf_size != buf_size) { ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor"); ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size), TAG, "allocate memory for dma descriptor failed"); @@ -559,7 +565,8 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r #endif I2S_NULL_POINTER_CHECK(TAG, handle); ESP_RETURN_ON_FALSE(handle->dir == I2S_DIR_RX, ESP_ERR_INVALID_ARG, TAG, "This channel handle is not a RX handle"); - ESP_RETURN_ON_FALSE(handle->controller->id == I2S_NUM_0, ESP_ERR_INVALID_ARG, TAG, "This channel handle is registered on I2S1, but PDM is only supported on I2S0"); + ESP_RETURN_ON_FALSE(i2s_ll_is_pdm_supported(handle->controller->id), + ESP_ERR_NOT_SUPPORTED, TAG, "PDM RX mode is not supported on selected port"); esp_err_t ret = ESP_OK; @@ -576,9 +583,12 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r 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"); ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_clock(handle, &pdm_rx_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); - ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + } i2s_ll_rx_enable_pdm(handle->controller->hal.dev, pdm_rx_cfg->slot_cfg.data_fmt == I2S_PDM_DATA_FMT_PCM); + i2s_ll_set_destination(handle->controller->hal.dev, handle->dir, handle->destination); #ifdef CONFIG_PM_ENABLE esp_pm_lock_type_t pm_type = ESP_PM_APB_FREQ_MAX; @@ -589,7 +599,7 @@ 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 - ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), TAG, "I2S pm lock create failed"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif /* Initialization finished, mark state as ready */ diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 12f9d5572c6..d8cfb331ecd 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -19,6 +19,7 @@ #if SOC_LP_I2S_SUPPORTED #include "hal/lp_i2s_ll.h" #endif +#include "hal/i2s_types.h" #include "driver/i2s_types.h" #if CONFIG_IDF_TARGET_ESP32 #include "esp_clock_output.h" @@ -55,6 +56,11 @@ extern "C" { #define I2S_NULL_POINTER_CHECK(tag, p) ESP_RETURN_ON_FALSE((p), ESP_ERR_INVALID_ARG, tag, "input parameter '"#p"' is NULL") #define MAX(a, b) ((a) > (b) ? (a) : (b)) +/** + * @brief Whether this channel uses the DMA memory data path + */ +#define I2S_CHANNEL_USES_DMA(handle) ((handle)->destination == I2S_DESTINATION_DMA) + /** * @brief i2s channel state for checking if the operation in under right driver state */ @@ -158,6 +164,7 @@ struct i2s_channel_obj_t { }; uint32_t active_slot; /*!< Active slot number */ uint32_t total_slot; /*!< Total slot number */ + i2s_destination_t destination; /*!< Data path destination (DMA memory path vs Bluetooth controller where supported) */ i2s_clock_src_t clk_src; /*!< Clock source */ uint32_t sclk_hz; /*!< Source clock frequency */ uint32_t origin_mclk_hz; /*!< Original mclk frequency */ diff --git a/components/esp_driver_i2s/i2s_std.c b/components/esp_driver_i2s/i2s_std.c index 4a5d73ffa7b..db434ff9b59 100644 --- a/components/esp_driver_i2s/i2s_std.c +++ b/components/esp_driver_i2s/i2s_std.c @@ -17,6 +17,7 @@ #endif #include "hal/i2s_hal.h" +#include "hal/i2s_types.h" #include "driver/gpio.h" #include "driver/i2s_std.h" #include "i2s_private.h" @@ -144,8 +145,9 @@ static esp_err_t i2s_std_set_slot(i2s_chan_handle_t handle, const i2s_std_slot_c uint32_t buf_size = i2s_get_buf_size(handle, norm_slot_cfg.data_bit_width, handle->dma.frame_num); ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width"); - /* The DMA buffer need to re-allocate if the buffer size changed */ - if (handle->dma.buf_size != buf_size) { + /* The DMA buffer need to re-allocate if the buffer size changed. + * Skip when GDMA is not the data path (e.g. Bluetooth destination), since the channel never owns a DMA buffer. */ + if (handle->destination == I2S_DESTINATION_DMA && handle->dma.buf_size != buf_size) { ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor"); ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size), TAG, "allocate memory for dma descriptor failed"); @@ -340,7 +342,9 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf ESP_GOTO_ON_ERROR(i2s_std_set_clock(handle, &std_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); /* i2s_std_set_gpio should be called after i2s_std_set_clock as mclk relies on the clock source */ ESP_GOTO_ON_ERROR(i2s_std_set_gpio(handle, &std_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); - ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + } #if SOC_I2S_HW_VERSION_2 /* Enable clock to start outputting mclk signal. Some codecs will reset once mclk stop */ if (handle->dir == I2S_DIR_TX) { @@ -349,6 +353,7 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf i2s_ll_rx_enable_std(handle->controller->hal.dev); } #endif + i2s_ll_set_destination(handle->controller->hal.dev, handle->dir, handle->destination); #ifdef CONFIG_PM_ENABLE esp_pm_lock_type_t pm_type = ESP_PM_APB_FREQ_MAX; @@ -359,7 +364,7 @@ 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 - ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), TAG, "I2S pm lock create failed"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif /* Initialization finished, mark state as ready */ diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index 2808dae5cdd..d8f4f209997 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -17,6 +17,7 @@ #endif #include "hal/i2s_hal.h" +#include "hal/i2s_types.h" #include "driver/gpio.h" #include "driver/i2s_tdm.h" #include "i2s_private.h" @@ -156,8 +157,9 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c handle->total_slot, slot_bits, (int)I2S_LL_SLOT_FRAME_BIT_MAX); uint32_t buf_size = i2s_get_buf_size(handle, norm_slot_cfg.data_bit_width, handle->dma.frame_num); ESP_RETURN_ON_FALSE(buf_size != 0, ESP_ERR_INVALID_ARG, TAG, "invalid data_bit_width"); - /* The DMA buffer need to re-allocate if the buffer size changed */ - if (handle->dma.buf_size != buf_size) { + /* The DMA buffer need to re-allocate if the buffer size changed. + * Skip when GDMA is not the data path (e.g. Bluetooth destination), since the channel never owns a DMA buffer. */ + if (handle->destination == I2S_DESTINATION_DMA && handle->dma.buf_size != buf_size) { ESP_RETURN_ON_ERROR(i2s_free_dma_desc(handle), TAG, "failed to free the old dma descriptor"); ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size), TAG, "allocate memory for dma descriptor failed"); @@ -322,7 +324,9 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf ESP_GOTO_ON_ERROR(i2s_tdm_set_clock(handle, &tdm_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock"); /* i2s_tdm_set_gpio should be called after i2s_tdm_set_clock as mclk relies on the clock source */ ESP_GOTO_ON_ERROR(i2s_tdm_set_gpio(handle, &tdm_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); - ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + if (I2S_CHANNEL_USES_DMA(handle)) { + ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); + } #if SOC_I2S_HW_VERSION_2 /* Enable clock to start outputting mclk signal. Some codecs will reset once mclk stop */ @@ -332,6 +336,7 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf i2s_ll_rx_enable_tdm(handle->controller->hal.dev); } #endif + i2s_ll_set_destination(handle->controller->hal.dev, handle->dir, handle->destination); #ifdef CONFIG_PM_ENABLE esp_pm_lock_type_t pm_type = ESP_PM_APB_FREQ_MAX; #if SOC_I2S_SUPPORTS_APLL && SOC_I2S_HW_VERSION_2 @@ -341,7 +346,7 @@ 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 - ESP_RETURN_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), TAG, "I2S pm lock create failed"); + ESP_GOTO_ON_ERROR(esp_pm_lock_create(pm_type, 0, "i2s_driver", &handle->pm_lock), err, TAG, "I2S pm lock create failed"); #endif /* Initialization finished, mark state as ready */ @@ -435,8 +440,10 @@ esp_err_t i2s_channel_reconfig_tdm_slot(i2s_chan_handle_t handle, const i2s_tdm_ ESP_GOTO_ON_ERROR(esp_clk_tree_enable_src((soc_module_clk_t)old_clk_src, false), err, TAG, "clock source disable failed"); } - /* Reset queue */ - xQueueReset(handle->msg_queue); + /* Reset queue (skip when no GDMA path is in use) */ + if (handle->msg_queue) { + xQueueReset(handle->msg_queue); + } xSemaphoreGive(handle->mutex); diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index e613308bb45..3d64b77b39d 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -28,6 +28,8 @@ extern "C" { .auto_clear_before_cb = false, \ .allow_pd = false, \ .intr_priority = 0, \ + .tx_destination = I2S_DESTINATION_DMA, \ + .rx_destination = I2S_DESTINATION_DMA, \ } #define I2S_GPIO_UNUSED GPIO_NUM_NC /*!< Used in i2s_gpio_config_t for signals which are not used */ @@ -79,6 +81,8 @@ typedef struct { * This can save power, but at the expense of more RAM being consumed. */ 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`. */ } i2s_chan_config_t; /** @@ -123,13 +127,16 @@ typedef struct { * For ESP32 and ESP32S2, the whole I2S controller (i.e. both RX and TX channel) will be occupied, * even if only one of RX or TX channel is registered. * For the other targets, another channel on this controller will still available. + * @note `tx_destination` / `rx_destination` select the DMA memory path or Bluetooth data path when + * supported. Requesting an unsupported destination or port returns an error. * * @param[in] chan_cfg I2S controller channel configurations * @param[out] ret_tx_handle I2S channel handler used for managing the sending channel(optional) * @param[out] ret_rx_handle I2S channel handler used for managing the receiving channel(optional) * @return * - ESP_OK Allocate new channel(s) success - * - ESP_ERR_NOT_SUPPORTED The communication mode is not supported on the current chip + * - ESP_ERR_NOT_SUPPORTED The communication mode or data path is not supported on the current chip, + * or Bluetooth data path is requested on an unsupported port * - ESP_ERR_INVALID_ARG NULL pointer or illegal parameter in i2s_chan_config_t * - ESP_ERR_NOT_FOUND No available I2S channel found */ @@ -237,6 +244,7 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si * - ESP_OK Set event callbacks successfully * - ESP_ERR_INVALID_ARG Set event callbacks failed because of invalid argument * - ESP_ERR_INVALID_STATE Set event callbacks failed because the current channel state is not REGISTERED or READY + * - ESP_ERR_NOT_SUPPORTED Set event callbacks failed because the channel does not use the DMA data path */ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data); 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 61db3876169..a0086864a96 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 @@ -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 */ @@ -21,7 +21,10 @@ #include "math.h" #include "esp_rom_gpio.h" #include "hal/i2s_periph.h" +#include "hal/i2s_types.h" #include "driver/i2s_std.h" +#include "driver/i2s_common.h" +#include "soc/soc_caps.h" #if SOC_I2S_SUPPORTS_PDM #include "driver/i2s_pdm.h" #endif @@ -1274,3 +1277,70 @@ TEST_CASE("I2S_rate_tunning", "[i2s]") TEST_ESP_OK(i2s_del_channel(tx_handle)); TEST_ESP_OK(i2s_del_channel(rx_handle)); } + +TEST_CASE("i2s_destination_test", "[i2s]") +{ + i2s_chan_handle_t tx = NULL; + i2s_chan_handle_t rx = NULL; + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + + 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, + }; + i2s_event_callbacks_t cbs = { 0 }; + + // DMA destination is the default path and should support normal callback registration for TX. + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx, NULL)); + TEST_ESP_OK(i2s_channel_init_std_mode(tx, &std_cfg)); + TEST_ESP_OK(i2s_channel_register_event_callback(tx, &cbs, NULL)); + TEST_ESP_OK(i2s_del_channel(tx)); + tx = NULL; + + // DMA destination is the default path and should support normal callback registration for RX. + TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx)); + TEST_ESP_OK(i2s_channel_init_std_mode(rx, &std_cfg)); + TEST_ESP_OK(i2s_channel_register_event_callback(rx, &cbs, NULL)); + TEST_ESP_OK(i2s_del_channel(rx)); + rx = NULL; + + chan_cfg.id = I2S_NUM_AUTO; +#if SOC_I2S_SUPPORTS_BT_DEST + + // BT destination should be accepted by auto allocation and bypass DMA-only APIs on TX. + uint8_t buf[16]; + size_t loaded = 0; + size_t written = 0; + size_t read_bytes = 0; + + chan_cfg.tx_destination = I2S_DESTINATION_BT; + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx, NULL)); + TEST_ESP_OK(i2s_channel_init_std_mode(tx, &std_cfg)); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(tx, &cbs, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_preload_data(tx, buf, sizeof(buf), &loaded)); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_write(tx, buf, sizeof(buf), &written, 0)); + TEST_ESP_OK(i2s_del_channel(tx)); + tx = NULL; + chan_cfg.tx_destination = I2S_DESTINATION_DMA; + + // BT destination should be accepted by auto allocation and bypass DMA-only APIs on RX. + chan_cfg.rx_destination = I2S_DESTINATION_BT; + TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx)); + TEST_ESP_OK(i2s_channel_init_std_mode(rx, &std_cfg)); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(rx, &cbs, NULL)); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_read(rx, buf, sizeof(buf), &read_bytes, 0)); + TEST_ESP_OK(i2s_del_channel(rx)); + rx = NULL; + chan_cfg.rx_destination = I2S_DESTINATION_DMA; +#else + // BT destination should be rejected on chips without hardware support. + chan_cfg.tx_destination = I2S_DESTINATION_BT; + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, &tx, NULL)); + chan_cfg.tx_destination = I2S_DESTINATION_DMA; + + chan_cfg.rx_destination = I2S_DESTINATION_BT; + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_new_channel(&chan_cfg, NULL, &rx)); + chan_cfg.rx_destination = I2S_DESTINATION_DMA; +#endif +} diff --git a/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h index 97836b2650b..f5b540a392c 100644 --- a/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32/include/hal/i2s_ll.h @@ -25,6 +25,9 @@ #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_BUS_WIDTH 24 #define I2S_LL_INST_NUM 2 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 +#define I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK (1U << 0) // PDM2PCM is supported on I2S0 #define I2S_LL_TRANS_SIZE_ALIGN_WORD 1 // I2S DMA transfer size must be aligned to word #define I2S_LL_ADC_DAC_CAPABLE 1 // I2S supports to connect to ADC / DAC converters @@ -1150,6 +1153,49 @@ static inline void i2s_ll_enable_builtin_adc_dac(i2s_dev_t *hw, bool enable) hw->conf2.camera_en = 0; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + return (I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32c3/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32c3/include/hal/i2s_ll.h index 655d91917a2..1cf11826cd0 100644 --- a/components/esp_hal_i2s/esp32c3/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32c3/include/hal/i2s_ll.h @@ -23,6 +23,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1195,6 +1197,50 @@ static inline void i2s_ll_tx_pdm_line_mode(i2s_dev_t *hw, i2s_pdm_tx_line_mode_t hw->tx_pcm2pdm_conf.tx_pdm_dac_2out_en = line_mode != I2S_PDM_TX_ONE_LINE_DAC; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32c5/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32c5/include/hal/i2s_ll.h index b0e21bf67ad..e16457d6456 100644 --- a/components/esp_hal_i2s/esp32c5/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32c5/include/hal/i2s_ll.h @@ -25,6 +25,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1369,6 +1371,50 @@ static inline bool i2s_ll_get_etm_rx_threshold_event_status(i2s_dev_t *hw) } } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32c6/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32c6/include/hal/i2s_ll.h index 635a3a8c3ec..72e04482877 100644 --- a/components/esp_hal_i2s/esp32c6/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32c6/include/hal/i2s_ll.h @@ -24,6 +24,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1241,6 +1243,50 @@ static inline void i2s_ll_rx_set_etm_threshold(i2s_dev_t *hw, uint32_t thresh) hw->etm_conf.etm_rx_receive_word_num = thresh; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32c61/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32c61/include/hal/i2s_ll.h index dcdc92860e0..9554fb80394 100644 --- a/components/esp_hal_i2s/esp32c61/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32c61/include/hal/i2s_ll.h @@ -25,6 +25,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1370,6 +1372,50 @@ static inline bool i2s_ll_get_etm_rx_threshold_event_status(i2s_dev_t *hw) } } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32h2/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32h2/include/hal/i2s_ll.h index c2d9fd3311f..b6c0d84bbcf 100644 --- a/components/esp_hal_i2s/esp32h2/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32h2/include/hal/i2s_ll.h @@ -24,6 +24,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1241,6 +1243,50 @@ static inline void i2s_ll_rx_set_etm_threshold(i2s_dev_t *hw, uint32_t thresh) hw->etm_conf.etm_rx_receive_word_num = thresh; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32h21/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32h21/include/hal/i2s_ll.h index 70ac93a0c6c..d92f1138690 100644 --- a/components/esp_hal_i2s/esp32h21/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32h21/include/hal/i2s_ll.h @@ -24,6 +24,7 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1240,6 +1241,51 @@ static inline void i2s_ll_rx_set_etm_threshold(i2s_dev_t *hw, uint32_t thresh) hw->etm_conf.etm_rx_receive_word_num = thresh; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + (void)port_id; + return false; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h index c53f55a10cb..0c9e96de49d 100644 --- a/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h @@ -25,6 +25,8 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1477,6 +1479,50 @@ static inline bool i2s_ll_get_etm_rx_threshold_event_status(i2s_dev_t *hw) } } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32p4/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32p4/include/hal/i2s_ll.h index b8e54de7f2e..560fa268c19 100644 --- a/components/esp_hal_i2s/esp32p4/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32p4/include/hal/i2s_ll.h @@ -27,6 +27,9 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 3 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 +#define I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK (1U << 0) // PDM2PCM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1801,6 +1804,49 @@ static inline bool i2s_ll_get_etm_rx_threshold_event_status(i2s_dev_t *hw) } } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + return (I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + #ifdef __cplusplus } #endif 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 6d78b9ae118..67f511f87c6 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,7 @@ #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_BUS_WIDTH 24 #define I2S_LL_INST_NUM 1 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1015,6 +1016,51 @@ static inline void i2s_ll_tx_stop_on_fifo_empty(i2s_dev_t *hw, bool en) hw->conf1.tx_stop_en = en; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + (void)port_id; + return false; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + (void)port_id; + return false; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32s3/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32s3/include/hal/i2s_ll.h index 92becdc9ccd..ca9758034db 100644 --- a/components/esp_hal_i2s/esp32s3/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32s3/include/hal/i2s_ll.h @@ -23,6 +23,9 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 2 +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 +#define I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK (1U << 0) // PDM2PCM is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -1233,6 +1236,49 @@ static inline void i2s_ll_tx_pdm_line_mode(i2s_dev_t *hw, i2s_pdm_tx_line_mode_t hw->tx_pcm2pdm_conf.tx_dac_2out_en = line_mode != I2S_PDM_TX_ONE_LINE_DAC; } +/** + * @brief Set I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + (void)hw; + (void)dir; + (void)destination; +} + +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + (void)port_id; + return destination == I2S_DESTINATION_DMA; +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + return (I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h index 671671bf8b7..3e3c10a28eb 100644 --- a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h @@ -27,6 +27,10 @@ #define I2S_LL_GET(_attr) I2S_LL_ ## _attr #define I2S_LL_SUPPORT(_feat) I2S_LL_SUPPORT_ ## _feat #define I2S_LL_INST_NUM 2 // ESP32S31 has 2 I2S instances +#define I2S_LL_PDM_SUPPORTED_PORT_MASK (1U << 0) // PDM is supported on I2S0 +#define I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK (1U << 0) // PCM2PDM is supported on I2S0 +#define I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK (1U << 0) // PDM2PCM is supported on I2S0 +#define I2S_LL_BT_DEST_SUPPORTED_PORT_MASK (1U << 0) // Bluetooth destination is supported on I2S0 #ifdef __cplusplus extern "C" { @@ -726,6 +730,45 @@ static inline void i2s_ll_rx_stop(i2s_dev_t *hw) hw->rx_conf.rx_start = 0; } +/** + * @brief Set I2S TX data destination + * + * @param hw Peripheral I2S hardware instance address. + * @param to_bt true: route TX data to Bluetooth; false: route TX data to the DMA memory path (default). + */ +static inline void i2s_ll_tx_set_destination(i2s_dev_t *hw, bool to_bt) +{ + hw->destination.tx_destination = to_bt ? 1 : 0; +} + +/** + * @brief Set I2S RX data destination + * + * @param hw Peripheral I2S hardware instance address. + * @param to_bt true: route RX data to Bluetooth; false: route RX data to the DMA memory path (default). + */ +static inline void i2s_ll_rx_set_destination(i2s_dev_t *hw, bool to_bt) +{ + hw->destination.rx_destination = to_bt ? 1 : 0; +} + +/** + * @brief Set I2S data destination + * + * @param hw Peripheral I2S hardware instance address. + * @param dir I2S channel direction + * @param destination I2S data destination + */ +static inline void i2s_ll_set_destination(i2s_dev_t *hw, i2s_dir_t dir, i2s_destination_t destination) +{ + bool to_bt = (destination == I2S_DESTINATION_BT); + if (dir == I2S_DIR_TX) { + i2s_ll_tx_set_destination(hw, to_bt); + } else { + i2s_ll_rx_set_destination(hw, to_bt); + } +} + /** * @brief Configure TX WS signal width * @@ -1815,6 +1858,45 @@ static inline void i2s_ll_tx_set_hw_sync_suppl_data(i2s_dev_t *hw, uint32_t data hw->hw_sync_data.tx_hw_sync_suppl_data = data; } +/** + * @brief Check whether an I2S data destination is supported on the specified port + */ +static inline bool i2s_ll_is_destination_supported(int port_id, i2s_destination_t destination) +{ + switch (destination) { + case I2S_DESTINATION_DMA: + return true; + case I2S_DESTINATION_BT: + return (I2S_LL_BT_DEST_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; + default: + return false; + } +} + +/** + * @brief Check whether I2S PDM mode is supported on the specified port + */ +static inline bool i2s_ll_is_pdm_supported(int port_id) +{ + return (I2S_LL_PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S TX PCM2PDM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pcm2pdm_supported(int port_id) +{ + return (I2S_LL_PCM2PDM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + +/** + * @brief Check whether I2S RX PDM2PCM converter is supported on the specified port + */ +static inline bool i2s_ll_is_pdm2pcm_supported(int port_id) +{ + return (I2S_LL_PDM2PCM_SUPPORTED_PORT_MASK & (1U << port_id)) != 0; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_i2s/i2s_hal.c b/components/esp_hal_i2s/i2s_hal.c index 8b281126c41..7835fbf347d 100644 --- a/components/esp_hal_i2s/i2s_hal.c +++ b/components/esp_hal_i2s/i2s_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ #include "soc/soc.h" #include "hal/assert.h" #include "hal/i2s_hal.h" +#include "hal/i2s_types.h" #if SOC_I2S_HW_VERSION_2 && (SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX_HP_FILTER) /* PDM tx high pass filter cut-off frequency and coefficients list diff --git a/components/esp_hal_i2s/include/hal/i2s_hal.h b/components/esp_hal_i2s/include/hal/i2s_hal.h index ed9b4581b9f..911eb7df97a 100644 --- a/components/esp_hal_i2s/include/hal/i2s_hal.h +++ b/components/esp_hal_i2s/include/hal/i2s_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/esp_hal_i2s/include/hal/i2s_types.h b/components/esp_hal_i2s/include/hal/i2s_types.h index 4dacccfec38..ba91335bdac 100644 --- a/components/esp_hal_i2s/include/hal/i2s_types.h +++ b/components/esp_hal_i2s/include/hal/i2s_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -70,6 +70,14 @@ typedef soc_periph_i2s_clk_src_t i2s_clock_src_t; /*!< I2S clock source */ typedef int i2s_clock_src_t; /*!< Define a default type to avoid compiling warnings */ #endif +/** + * @brief I2S data path destination + */ +typedef enum { + I2S_DESTINATION_DMA = 0, /*!< Route I2S sample data through DMA to/from memory (default). On chips without GDMA, this is the legacy I2S DMA path. */ + I2S_DESTINATION_BT = 1, /*!< Route I2S data to Bluetooth (I2S0 only; see `SOC_I2S_SUPPORTS_BT_DEST`) */ +} i2s_destination_t; + #if SOC_I2S_SUPPORTS_PCM /** * @brief A/U-law decompress or compress configuration. diff --git a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in index 174d24e7528..5a5a73c9052 100644 --- a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in @@ -1563,6 +1563,10 @@ config SOC_I2S_SUPPORTS_TDM bool default y +config SOC_I2S_SUPPORTS_BT_DEST + bool + default y + config SOC_I2S_PDM_MAX_TX_LINES int default 2 diff --git a/components/soc/esp32s31/include/soc/soc_caps.h b/components/soc/esp32s31/include/soc/soc_caps.h index 8579638b62e..57101d0fff4 100644 --- a/components/soc/esp32s31/include/soc/soc_caps.h +++ b/components/soc/esp32s31/include/soc/soc_caps.h @@ -589,6 +589,7 @@ #define SOC_I2S_SUPPORTS_TX_SYNC_CNT (1) // Support TX synchronization count (ideal_cnt) #define SOC_I2S_SUPPORTS_RX_RECOMB (1) // Support RX recomb for DMA data format reorganization #define SOC_I2S_SUPPORTS_TDM (1) +#define SOC_I2S_SUPPORTS_BT_DEST (1) // Support routing I2S TX/RX data to Bluetooth (Classic sync link / voice path), not via DMA #define SOC_I2S_PDM_MAX_TX_LINES (2) // On I2S0 #define SOC_I2S_PDM_MAX_RX_LINES (4) // On I2S0 diff --git a/components/soc/esp32s31/register/soc/i2s_reg.h b/components/soc/esp32s31/register/soc/i2s_reg.h index e6241612e5c..0831bfd38c8 100644 --- a/components/soc/esp32s31/register/soc/i2s_reg.h +++ b/components/soc/esp32s31/register/soc/i2s_reg.h @@ -1552,14 +1552,14 @@ extern "C" { */ #define I2S_DESTINATION_REG(i) (REG_I2S_BASE(i) + 0xf4) /** I2S_RX_DESTINATION : R/W; bitpos: [0]; default: 0; - * Set this bit to configure the data destination of i2s rx. 1: ble. 0: gdma. + * Set this bit to configure the data destination of i2s rx. 1: bt. 0: gdma. */ #define I2S_RX_DESTINATION (BIT(0)) #define I2S_RX_DESTINATION_M (I2S_RX_DESTINATION_V << I2S_RX_DESTINATION_S) #define I2S_RX_DESTINATION_V 0x00000001U #define I2S_RX_DESTINATION_S 0 /** I2S_TX_DESTINATION : R/W; bitpos: [1]; default: 0; - * Set this bit to configure the data destination of i2s tx. 1: ble. 0: gdma. + * Set this bit to configure the data destination of i2s tx. 1: bt. 0: gdma. */ #define I2S_TX_DESTINATION (BIT(1)) #define I2S_TX_DESTINATION_M (I2S_TX_DESTINATION_V << I2S_TX_DESTINATION_S) diff --git a/components/soc/esp32s31/register/soc/i2s_struct.h b/components/soc/esp32s31/register/soc/i2s_struct.h index 73878564a05..c1461338414 100644 --- a/components/soc/esp32s31/register/soc/i2s_struct.h +++ b/components/soc/esp32s31/register/soc/i2s_struct.h @@ -1027,11 +1027,11 @@ typedef union { typedef union { struct { /** rx_destination : R/W; bitpos: [0]; default: 0; - * Set this bit to configure the data destination of i2s rx. 1: ble. 0: gdma. + * Set this bit to configure the data destination of i2s rx. 1: bt. 0: gdma. */ uint32_t rx_destination:1; /** tx_destination : R/W; bitpos: [1]; default: 0; - * Set this bit to configure the data destination of i2s tx. 1: ble. 0: gdma. + * Set this bit to configure the data destination of i2s tx. 1: bt. 0: gdma. */ uint32_t tx_destination:1; uint32_t reserved_2:30; diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 9cbebf43593..ed5b0b85636 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -291,6 +291,15 @@ 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. +.. 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**: + + - **:cpp:enumerator:`i2s_destination_t::I2S_DESTINATION_DMA` (default)**: Use **DMA** as the TX/RX data path, consistent with the mechanisms described above. + - **:cpp:enumerator:`i2s_destination_t::I2S_DESTINATION_BT`**: Use **Bluetooth** as the TX/RX data path. Common operations that rely on DMA buffering (such as :cpp:func:`i2s_channel_write`, :cpp:func:`i2s_channel_read`, :cpp:func:`i2s_channel_preload_data`, :cpp:func:`i2s_channel_register_event_callback`, etc.) are not available on that direction. Only **I2S0** can select the Bluetooth path. + + For integration with the Bluetooth stack and audio links, see the :doc:`ESP-IDF Bluetooth API Reference <../bluetooth/index>`. + Configuration ^^^^^^^^^^^^^ diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 001185af30a..e8f23e39b1a 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -291,6 +291,15 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 :cpp:func:`i2s_channel_write` 和 :cpp:func:`i2s_channel_read` 都是阻塞函数,在源缓冲区的数据发送完毕前,或是整个目标缓冲区都被加载数据占用时,它们会一直保持等待状态。在等待时间达到最大阻塞时间时,返回 ``ESP_ERR_TIMEOUT`` 错误。要实现异步发送或接收数据,可以通过 :cpp:func:`i2s_channel_register_event_callback` 注册回调,随即便可在回调函数中直接访问 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** 二者中择一: + + - **DMA(默认)**:使用 DMA 作为 TX/RX 数据通路,与前文所述机制一致。 + - **Bluetooth**:使用 **蓝牙** 作为 TX/RX 数据通路。依赖 DMA 缓冲的常用操作(例如 :cpp:func:`i2s_channel_write`、:cpp:func:`i2s_channel_read`、:cpp:func:`i2s_channel_preload_data`、:cpp:func:`i2s_channel_register_event_callback` 等)在该方向上不可用。仅 **I2S0** 可选用 Bluetooth 路径。 + + 与蓝牙协议栈及音频链路的衔接,请参阅 :doc:`ESP-IDF 蓝牙 API 参考 <../bluetooth/index>`。 + 配置 ^^^^