diff --git a/boards/espressif/esp32_devkitc/esp32_devkitc-pinctrl.dtsi b/boards/espressif/esp32_devkitc/esp32_devkitc-pinctrl.dtsi index fef35fba08880..f0281a4beef3a 100644 --- a/boards/espressif/esp32_devkitc/esp32_devkitc-pinctrl.dtsi +++ b/boards/espressif/esp32_devkitc/esp32_devkitc-pinctrl.dtsi @@ -81,33 +81,19 @@ i2s0_default: i2s0_default { group1 { - pinmux = , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; i2s1_default: i2s1_default { group1 { - pinmux = , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; }; diff --git a/boards/espressif/esp32_devkitc/esp32_devkitc_procpu.dts b/boards/espressif/esp32_devkitc/esp32_devkitc_procpu.dts index 7432a24d48ab1..5cde12b1c6763 100644 --- a/boards/espressif/esp32_devkitc/esp32_devkitc_procpu.dts +++ b/boards/espressif/esp32_devkitc/esp32_devkitc_procpu.dts @@ -93,13 +93,11 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &i2s1 { pinctrl-0 = <&i2s1_default>; pinctrl-names = "default"; - status = "disabled"; }; &spi2 { diff --git a/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit-pinctrl.dtsi b/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit-pinctrl.dtsi index 86bcaa54d5bc2..adcc55d43d158 100644 --- a/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit-pinctrl.dtsi +++ b/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit-pinctrl.dtsi @@ -43,33 +43,19 @@ i2s0_default: i2s0_default { group1 { - pinmux = , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; i2s1_default: i2s1_default { group1 { - pinmux = , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; }; diff --git a/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit_procpu.dts b/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit_procpu.dts index bf2fbbd6d3a7e..f186a895c7e01 100644 --- a/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit_procpu.dts +++ b/boards/espressif/esp32_ethernet_kit/esp32_ethernet_kit_procpu.dts @@ -46,13 +46,11 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &i2s1 { pinctrl-0 = <&i2s1_default>; pinctrl-names = "default"; - status = "disabled"; }; &spi2 { diff --git a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc-pinctrl.dtsi b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc-pinctrl.dtsi index 808df2df0355f..ea23aed2f2054 100644 --- a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc-pinctrl.dtsi +++ b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc-pinctrl.dtsi @@ -44,6 +44,16 @@ }; }; + i2s_default: i2s_default { + group1 { + pinmux = , + , + , + , + ; + }; + }; + twai_default: twai_default { group1 { pinmux = , diff --git a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.dts b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.dts index 627ee822aa669..6208c003f648d 100644 --- a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.dts +++ b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.dts @@ -60,6 +60,11 @@ pinctrl-names = "default"; }; +&i2s { + pinctrl-0 = <&i2s_default>; + pinctrl-names = "default"; +}; + &trng0 { status = "okay"; }; diff --git a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.yaml b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.yaml index f2b98e3e4ed67..20cef2fc49289 100644 --- a/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.yaml +++ b/boards/espressif/esp32c3_devkitc/esp32c3_devkitc.yaml @@ -8,6 +8,7 @@ supported: - adc - gpio - i2c + - i2s - watchdog - uart - dma diff --git a/boards/espressif/esp32c3_devkitm/esp32c3_devkitm-pinctrl.dtsi b/boards/espressif/esp32c3_devkitm/esp32c3_devkitm-pinctrl.dtsi index a3c53becbf441..f85c9f762a8b5 100644 --- a/boards/espressif/esp32c3_devkitm/esp32c3_devkitm-pinctrl.dtsi +++ b/boards/espressif/esp32c3_devkitm/esp32c3_devkitm-pinctrl.dtsi @@ -46,16 +46,11 @@ i2s_default: i2s_default { group1 { - pinmux = , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + , + ; }; }; diff --git a/boards/espressif/esp32c3_devkitm/esp32c3_devkitm.dts b/boards/espressif/esp32c3_devkitm/esp32c3_devkitm.dts index 824a214878dbd..0e30c972d636c 100644 --- a/boards/espressif/esp32c3_devkitm/esp32c3_devkitm.dts +++ b/boards/espressif/esp32c3_devkitm/esp32c3_devkitm.dts @@ -63,7 +63,6 @@ &i2s { pinctrl-0 = <&i2s_default>; pinctrl-names = "default"; - status = "disabled"; }; &trng0 { diff --git a/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore-pinctrl.dtsi b/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore-pinctrl.dtsi index 8371bbc96d82d..4dab8a27f8d29 100644 --- a/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore-pinctrl.dtsi +++ b/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore-pinctrl.dtsi @@ -43,4 +43,14 @@ output-high; }; }; + + i2s_default: i2s_default { + group1 { + pinmux = , + , + , + , + ; + }; + }; }; diff --git a/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore.dts b/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore.dts index 59a271cc8e9ad..c0efdf8ec231a 100644 --- a/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore.dts +++ b/boards/espressif/esp32c6_devkitc/esp32c6_devkitc_hpcore.dts @@ -58,6 +58,11 @@ pinctrl-names = "default"; }; +&i2s { + pinctrl-0 = <&i2s_default>; + pinctrl-names = "default"; +}; + &spi2 { #address-cells = <1>; #size-cells = <0>; diff --git a/boards/espressif/esp32s2_devkitc/esp32s2_devkitc-pinctrl.dtsi b/boards/espressif/esp32s2_devkitc/esp32s2_devkitc-pinctrl.dtsi index 679be933a4621..9163d336faa3c 100644 --- a/boards/espressif/esp32s2_devkitc/esp32s2_devkitc-pinctrl.dtsi +++ b/boards/espressif/esp32s2_devkitc/esp32s2_devkitc-pinctrl.dtsi @@ -72,15 +72,8 @@ pinmux = , , , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + , + ; }; }; }; diff --git a/boards/espressif/esp32s2_devkitc/esp32s2_devkitc.dts b/boards/espressif/esp32s2_devkitc/esp32s2_devkitc.dts index c4a151f12f19b..804bd793541b3 100644 --- a/boards/espressif/esp32s2_devkitc/esp32s2_devkitc.dts +++ b/boards/espressif/esp32s2_devkitc/esp32s2_devkitc.dts @@ -100,7 +100,6 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &trng0 { diff --git a/boards/espressif/esp32s2_saola/esp32s2_saola-pinctrl.dtsi b/boards/espressif/esp32s2_saola/esp32s2_saola-pinctrl.dtsi index 499e756e4b193..27ea89cf89c64 100644 --- a/boards/espressif/esp32s2_saola/esp32s2_saola-pinctrl.dtsi +++ b/boards/espressif/esp32s2_saola/esp32s2_saola-pinctrl.dtsi @@ -72,15 +72,8 @@ pinmux = , , , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + , + ; }; }; }; diff --git a/boards/espressif/esp32s2_saola/esp32s2_saola.dts b/boards/espressif/esp32s2_saola/esp32s2_saola.dts index 005a76a0b7c6e..ddd6e4dc54bf0 100644 --- a/boards/espressif/esp32s2_saola/esp32s2_saola.dts +++ b/boards/espressif/esp32s2_saola/esp32s2_saola.dts @@ -100,7 +100,6 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &trng0 { diff --git a/boards/espressif/esp32s3_devkitc/esp32s3_devkitc-pinctrl.dtsi b/boards/espressif/esp32s3_devkitc/esp32s3_devkitc-pinctrl.dtsi index dee1ce787ecc6..86d02c69e7421 100644 --- a/boards/espressif/esp32s3_devkitc/esp32s3_devkitc-pinctrl.dtsi +++ b/boards/espressif/esp32s3_devkitc/esp32s3_devkitc-pinctrl.dtsi @@ -55,35 +55,21 @@ i2s0_default: i2s0_default { group1 { - pinmux = , - , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + , + ; }; }; i2s1_default: i2s1_default { group1 { - pinmux = , - , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + , + ; }; }; diff --git a/boards/espressif/esp32s3_devkitc/esp32s3_devkitc_procpu.dts b/boards/espressif/esp32s3_devkitc/esp32s3_devkitc_procpu.dts index 33572bbf70746..8c1b137d26d60 100644 --- a/boards/espressif/esp32s3_devkitc/esp32s3_devkitc_procpu.dts +++ b/boards/espressif/esp32s3_devkitc/esp32s3_devkitc_procpu.dts @@ -91,13 +91,11 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &i2s1 { pinctrl-0 = <&i2s1_default>; pinctrl-names = "default"; - status = "disabled"; }; &spi2 { diff --git a/boards/espressif/esp32s3_devkitm/esp32s3_devkitm-pinctrl.dtsi b/boards/espressif/esp32s3_devkitm/esp32s3_devkitm-pinctrl.dtsi index a41bbf59a96d8..f18676e095ca1 100644 --- a/boards/espressif/esp32s3_devkitm/esp32s3_devkitm-pinctrl.dtsi +++ b/boards/espressif/esp32s3_devkitm/esp32s3_devkitm-pinctrl.dtsi @@ -55,35 +55,21 @@ i2s0_default: i2s0_default { group1 { - pinmux = , - , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + , + ; }; }; i2s1_default: i2s1_default { group1 { - pinmux = , - , - , - , - , - ; - output-enable; - }; - - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + , + ; }; }; diff --git a/boards/espressif/esp32s3_devkitm/esp32s3_devkitm_procpu.dts b/boards/espressif/esp32s3_devkitm/esp32s3_devkitm_procpu.dts index f3bdfd37d89eb..28a8f6456eec8 100644 --- a/boards/espressif/esp32s3_devkitm/esp32s3_devkitm_procpu.dts +++ b/boards/espressif/esp32s3_devkitm/esp32s3_devkitm_procpu.dts @@ -87,13 +87,11 @@ &i2s0 { pinctrl-0 = <&i2s0_default>; pinctrl-names = "default"; - status = "disabled"; }; &i2s1 { pinctrl-0 = <&i2s1_default>; pinctrl-names = "default"; - status = "disabled"; }; &spi2 { diff --git a/drivers/i2s/Kconfig.esp32 b/drivers/i2s/Kconfig.esp32 index 8cc13d01dc92e..63f3a8528a6cf 100644 --- a/drivers/i2s/Kconfig.esp32 +++ b/drivers/i2s/Kconfig.esp32 @@ -8,7 +8,7 @@ config I2S_ESP32 depends on DT_HAS_ESPRESSIF_ESP32_I2S_ENABLED select DMA if DT_HAS_ESPRESSIF_ESP32_GDMA_ENABLED help - Enables the ESP32 I2S driver (GDMA SoCs only). + Enables the ESP32 I2S driver. if I2S_ESP32 @@ -31,4 +31,10 @@ config I2S_ESP32_DMA_DESC_NUM_MAX help Max number of link descriptor available for DMA transfers on each I2S channel +config I2S_ESP32_ALLOWED_EMPTY_TX_QUEUE_DEFERRAL_TIME_MS + int "ESP32 I2S empty TX queue processing deferral time" + default 0 + help + Allowed deferral time for processing an empty tx queue in milliseconds + endif diff --git a/drivers/i2s/i2s_esp32.c b/drivers/i2s/i2s_esp32.c index d813b50a8415b..3718ddc46406e 100644 --- a/drivers/i2s/i2s_esp32.c +++ b/drivers/i2s/i2s_esp32.c @@ -38,46 +38,41 @@ LOG_MODULE_REGISTER(i2s_esp32, CONFIG_I2S_LOG_LEVEL); #define I2S_ESP32_CLK_SRC I2S_CLK_SRC_DEFAULT #define I2S_ESP32_DMA_BUFFER_MAX_SIZE 4092 -#define I2S_ESP32_NUM_INST_OK DT_NUM_INST_STATUS_OKAY(espressif_esp32_i2s) +#define I2S_ESP32_NUM_INST_OK DT_NUM_INST_STATUS_OKAY(espressif_esp32_i2s) #define I2S_ESP32_IS_DIR_INST_EN(i, d) DT_INST_DMAS_HAS_NAME(i, d) || DT_INST_IRQ_HAS_NAME(i, d) -#define I2S_ESP32_IS_DIR_EN(d) LISTIFY(I2S_ESP32_NUM_INST_OK, I2S_ESP32_IS_DIR_INST_EN, (||), d) +#define I2S_ESP32_IS_DIR_EN(d) (LISTIFY(I2S_ESP32_NUM_INST_OK, I2S_ESP32_IS_DIR_INST_EN, \ + (||), d)) struct queue_item { void *buffer; size_t size; }; -struct i2s_esp32_stream; - struct i2s_esp32_stream_data { - int32_t state; - bool is_slave; + bool configured; + bool transferring; struct i2s_config i2s_cfg; void *mem_block; size_t mem_block_len; - bool last_block; - bool stop_without_draining; struct k_msgq queue; +#if !SOC_GDMA_SUPPORTED struct intr_handle_data_t *irq_handle; +#endif bool dma_pending; uint8_t chunks_rem; uint8_t chunk_idx; }; struct i2s_esp32_stream_conf { - void (*queue_drop)(const struct i2s_esp32_stream *stream); - int (*start_transfer)(const struct device *dev); - void (*stop_transfer)(const struct device *dev); +#if SOC_GDMA_SUPPORTED const struct device *dma_dev; uint32_t dma_channel; -#if SOC_GDMA_SUPPORTED - void *dma_desc; #else lldesc_t *dma_desc; -#endif int irq_source; int irq_priority; int irq_flags; +#endif }; struct i2s_esp32_stream { @@ -96,7 +91,14 @@ struct i2s_esp32_cfg { }; struct i2s_esp32_data { + int32_t state; + enum i2s_dir active_dir; + bool tx_stop_without_draining; i2s_hal_clock_info_t clk_info; +#if I2S_ESP32_IS_DIR_EN(tx) + struct k_timer tx_deferred_transfer_timer; + const struct device *dev; +#endif }; uint32_t i2s_esp32_get_source_clk_freq(i2s_clock_src_t clk_src) @@ -113,12 +115,10 @@ static esp_err_t i2s_esp32_calculate_clock(const struct i2s_config *i2s_cfg, uin uint16_t mclk_multiple = 256; if (i2s_cfg == NULL) { - LOG_ERR("Input i2s_cfg is NULL"); return ESP_ERR_INVALID_ARG; } if (i2s_hal_clock_info == NULL) { - LOG_ERR("Input hal_clock_info is NULL"); return ESP_ERR_INVALID_ARG; } @@ -142,20 +142,38 @@ static esp_err_t i2s_esp32_calculate_clock(const struct i2s_config *i2s_cfg, uin i2s_hal_clock_info->sclk = i2s_esp32_get_source_clk_freq(I2S_ESP32_CLK_SRC); i2s_hal_clock_info->mclk_div = i2s_hal_clock_info->sclk / i2s_hal_clock_info->mclk; if (i2s_hal_clock_info->mclk_div == 0) { - LOG_ERR("Sample rate is too large for the current clock source"); + LOG_DBG("Sample rate is too large for the current clock source"); return ESP_ERR_INVALID_ARG; } return ESP_OK; } -static void i2s_esp32_queue_drop(const struct i2s_esp32_stream *stream) +static void i2s_esp32_queue_drop(const struct device *dev, enum i2s_dir dir) { + const struct i2s_esp32_cfg *dev_cfg = dev->config; + const struct i2s_esp32_stream *stream; struct queue_item item; - while (k_msgq_get(&stream->data->queue, &item, K_NO_WAIT) == 0) { - k_mem_slab_free(stream->data->i2s_cfg.mem_slab, item.buffer); +#if I2S_ESP32_IS_DIR_EN(rx) + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->rx; + + while (k_msgq_get(&stream->data->queue, &item, K_NO_WAIT) == 0) { + k_mem_slab_free(stream->data->i2s_cfg.mem_slab, item.buffer); + } } +#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + +#if I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->tx; + + while (k_msgq_get(&stream->data->queue, &item, K_NO_WAIT) == 0) { + k_mem_slab_free(stream->data->i2s_cfg.mem_slab, item.buffer); + } + } +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ } static int i2s_esp32_restart_dma(const struct device *dev, enum i2s_dir dir); @@ -163,6 +181,8 @@ static int i2s_esp32_start_dma(const struct device *dev, enum i2s_dir dir); #if I2S_ESP32_IS_DIR_EN(rx) +static void i2s_esp32_rx_stop_transfer(const struct device *dev); + #if SOC_GDMA_SUPPORTED static void i2s_esp32_rx_callback(const struct device *dma_dev, void *arg, uint32_t channel, int status) @@ -171,30 +191,33 @@ static void i2s_esp32_rx_callback(void *arg, int status) #endif /* SOC_GDMA_SUPPORTED */ { const struct device *dev = (const struct device *)arg; + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; const struct i2s_esp32_stream *stream = &dev_cfg->rx; int err; + if (!stream->data->dma_pending) { + return; + } + + stream->data->dma_pending = false; + + if (stream->data->mem_block == NULL) { + LOG_DBG("RX mem_block NULL"); + dev_data->state = I2S_STATE_ERROR; + goto rx_disable; + } + #if SOC_GDMA_SUPPORTED if (status < 0) { #else if (status & I2S_LL_EVENT_RX_DSCR_ERR) { #endif /* SOC_GDMA_SUPPORTED */ - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("RX status bad: %d", status); + dev_data->state = I2S_STATE_ERROR; + LOG_DBG("RX status bad: %d", status); goto rx_disable; } - if (stream->data->mem_block == NULL) { - if (stream->data->state != I2S_STATE_READY) { - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("RX mem_block NULL"); - goto rx_disable; - } else { - return; - } - } - #if SOC_GDMA_SUPPORTED const i2s_hal_context_t *hal = &(dev_cfg->hal); uint16_t chunk_len; @@ -218,7 +241,8 @@ static void i2s_esp32_rx_callback(void *arg, int status) err = dma_reload(stream->conf->dma_dev, stream->conf->dma_channel, (uint32_t)NULL, (uint32_t)dst, chunk_len); if (err < 0) { - LOG_ERR("Failed to reload DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to reload DMA channel: %" PRIu32, + stream->conf->dma_channel); goto rx_disable; } @@ -226,7 +250,7 @@ static void i2s_esp32_rx_callback(void *arg, int status) err = dma_start(stream->conf->dma_dev, stream->conf->dma_channel); if (err < 0) { - LOG_ERR("Failed to start DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to start DMA channel: %" PRIu32, stream->conf->dma_channel); goto rx_disable; } @@ -243,34 +267,41 @@ static void i2s_esp32_rx_callback(void *arg, int status) err = k_msgq_put(&stream->data->queue, &item, K_NO_WAIT); if (err < 0) { - stream->data->state = I2S_STATE_ERROR; + LOG_DBG("RX queue full"); + dev_data->state = I2S_STATE_ERROR; goto rx_disable; } - if (stream->data->state == I2S_STATE_STOPPING) { - stream->data->state = I2S_STATE_READY; - goto rx_disable; + if (dev_data->state == I2S_STATE_STOPPING) { + if (dev_data->active_dir == I2S_DIR_RX || + (dev_data->active_dir == I2S_DIR_BOTH && !dev_cfg->tx.data->transferring)) { + dev_data->state = I2S_STATE_READY; + goto rx_disable; + } } err = k_mem_slab_alloc(stream->data->i2s_cfg.mem_slab, &stream->data->mem_block, K_NO_WAIT); if (err < 0) { - LOG_ERR("RX failed to allocate memory from slab: %i:", err); - stream->data->state = I2S_STATE_ERROR; + LOG_DBG("RX failed to allocate memory from slab: %i:", err); + dev_data->state = I2S_STATE_ERROR; goto rx_disable; } stream->data->mem_block_len = stream->data->i2s_cfg.block_size; err = i2s_esp32_restart_dma(dev, I2S_DIR_RX); if (err < 0) { - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("Failed to restart RX transfer: %d", err); + LOG_DBG("Failed to restart RX transfer: %d", err); + k_mem_slab_free(stream->data->i2s_cfg.mem_slab, stream->data->mem_block); + stream->data->mem_block = NULL; + stream->data->mem_block_len = 0; + dev_data->state = I2S_STATE_ERROR; goto rx_disable; } return; rx_disable: - stream->conf->stop_transfer(dev); + i2s_esp32_rx_stop_transfer(dev); } #if !SOC_GDMA_SUPPORTED @@ -307,9 +338,19 @@ static int i2s_esp32_rx_start_transfer(const struct device *dev) } stream->data->mem_block_len = stream->data->i2s_cfg.block_size; + i2s_hal_rx_stop(hal); + i2s_hal_rx_reset(hal); +#if !SOC_GDMA_SUPPORTED + i2s_hal_rx_reset_dma(hal); +#endif /* !SOC_GDMA_SUPPORTED */ + i2s_hal_rx_reset_fifo(hal); + err = i2s_esp32_start_dma(dev, I2S_DIR_RX); if (err < 0) { - LOG_ERR("Failed to start RX DMA transfer: %d", err); + LOG_DBG("Failed to start RX DMA transfer: %d", err); + k_mem_slab_free(stream->data->i2s_cfg.mem_slab, stream->data->mem_block); + stream->data->mem_block = NULL; + stream->data->mem_block_len = 0; return -EIO; } @@ -319,6 +360,8 @@ static int i2s_esp32_rx_start_transfer(const struct device *dev) esp_intr_enable(stream->data->irq_handle); #endif /* !SOC_GDMA_SUPPORTED */ + stream->data->transferring = true; + return 0; } @@ -339,98 +382,123 @@ static void i2s_esp32_rx_stop_transfer(const struct device *dev) i2s_hal_clear_intr_status(hal, I2S_INTR_MAX); #endif /* SOC_GDMA_SUPPORTED */ - if (stream->data->mem_block != NULL) { - k_mem_slab_free(stream->data->i2s_cfg.mem_slab, stream->data->mem_block); - stream->data->mem_block = NULL; - stream->data->mem_block_len = 0; - } + stream->data->mem_block = NULL; + stream->data->mem_block_len = 0; + + stream->data->transferring = false; } #endif /* I2S_ESP32_IS_DIR_EN(rx) */ #if I2S_ESP32_IS_DIR_EN(tx) -#if SOC_GDMA_SUPPORTED -static void i2s_esp32_tx_callback(const struct device *dma_dev, void *arg, uint32_t channel, - int status) -#else -static void i2s_esp32_tx_callback(void *arg, int status) -#endif /* SOC_GDMA_SUPPORTED */ +static void i2s_esp32_tx_stop_transfer(const struct device *dev); + +void i2s_esp32_tx_compl_transfer(struct k_timer *timer) { - const struct device *dev = (const struct device *)arg; + struct i2s_esp32_data *dev_data = + CONTAINER_OF(timer, struct i2s_esp32_data, tx_deferred_transfer_timer); + const struct device *dev = dev_data->dev; const struct i2s_esp32_cfg *const dev_cfg = dev->config; const struct i2s_esp32_stream *stream = &dev_cfg->tx; struct queue_item item; - void *mem_block_tmp; int err; - if (!stream->data->dma_pending) { - return; - } - - stream->data->dma_pending = false; - -#if SOC_GDMA_SUPPORTED - if (status < 0) { -#else - if (status & I2S_LL_EVENT_TX_DSCR_ERR) { -#endif /* SOC_GDMA_SUPPORTED */ - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("TX bad status: %d", status); + if (dev_data->state == I2S_STATE_ERROR) { goto tx_disable; } - if (stream->data->mem_block == NULL) { - if (stream->data->state != I2S_STATE_READY) { - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("TX mem_block NULL"); - goto tx_disable; - } else { - return; - } - } - - if (stream->data->state == I2S_STATE_STOPPING) { - if (k_msgq_num_used_get(&stream->data->queue) == 0) { - stream->data->state = I2S_STATE_READY; - goto tx_disable; - } else if (stream->data->stop_without_draining == true) { - stream->conf->queue_drop(stream); - stream->data->state = I2S_STATE_READY; + if (dev_data->state == I2S_STATE_STOPPING) { + if (k_msgq_num_used_get(&stream->data->queue) == 0 || + dev_data->tx_stop_without_draining == true) { + if (dev_data->active_dir == I2S_DIR_TX || + (dev_data->active_dir == I2S_DIR_BOTH && + !dev_cfg->rx.data->transferring)) { + dev_data->state = I2S_STATE_READY; + } goto tx_disable; } } - if (stream->data->last_block) { - stream->data->state = I2S_STATE_READY; - goto tx_disable; - } - err = k_msgq_get(&stream->data->queue, &item, K_NO_WAIT); if (err < 0) { - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("TX queue empty: %d", err); + dev_data->state = I2S_STATE_ERROR; + LOG_DBG("TX queue empty: %d", err); goto tx_disable; } - mem_block_tmp = stream->data->mem_block; - stream->data->mem_block = item.buffer; stream->data->mem_block_len = item.size; err = i2s_esp32_restart_dma(dev, I2S_DIR_TX); if (err < 0) { - stream->data->state = I2S_STATE_ERROR; - LOG_ERR("Failed to restart TX transfer: %d", err); + dev_data->state = I2S_STATE_ERROR; + LOG_DBG("Failed to restart TX transfer: %d", err); goto tx_disable; } - k_mem_slab_free(stream->data->i2s_cfg.mem_slab, mem_block_tmp); + return; + +tx_disable: + i2s_esp32_tx_stop_transfer(dev); +} + +#if SOC_GDMA_SUPPORTED +static void i2s_esp32_tx_callback(const struct device *dma_dev, void *arg, uint32_t channel, + int status) +#else +static void i2s_esp32_tx_callback(void *arg, int status) +#endif /* SOC_GDMA_SUPPORTED */ +{ + const struct device *dev = (const struct device *)arg; + struct i2s_esp32_data *dev_data = dev->data; + const struct i2s_esp32_cfg *const dev_cfg = dev->config; + const struct i2s_esp32_stream *stream = &dev_cfg->tx; + + if (dev_data->state == I2S_STATE_ERROR) { + goto tx_disable; + } + + if (!stream->data->dma_pending) { + return; + } + + stream->data->dma_pending = false; + + if (stream->data->mem_block == NULL) { + LOG_DBG("TX mem_block NULL"); + dev_data->state = I2S_STATE_ERROR; + goto tx_disable; + } + + k_mem_slab_free(stream->data->i2s_cfg.mem_slab, stream->data->mem_block); + +#if SOC_GDMA_SUPPORTED + if (status < 0) { +#else + if (status & I2S_LL_EVENT_TX_DSCR_ERR) { +#endif /* SOC_GDMA_SUPPORTED */ + dev_data->state = I2S_STATE_ERROR; + LOG_DBG("TX bad status: %d", status); + goto tx_disable; + } + +#if CONFIG_I2S_ESP32_ALLOWED_EMPTY_TX_QUEUE_DEFERRAL_TIME_MS + if (k_msgq_num_used_get(&stream->data->queue) == 0) { + k_timer_start(&dev_data->tx_deferred_transfer_timer, + K_MSEC(CONFIG_I2S_ESP32_ALLOWED_EMPTY_TX_QUEUE_DEFERRAL_TIME_MS), + K_NO_WAIT); + } else { +#else + { +#endif + i2s_esp32_tx_compl_transfer(&dev_data->tx_deferred_transfer_timer); + } return; tx_disable: - stream->conf->stop_transfer(dev); + i2s_esp32_tx_stop_transfer(dev); } #if !SOC_GDMA_SUPPORTED @@ -470,9 +538,16 @@ static int i2s_esp32_tx_start_transfer(const struct device *dev) stream->data->mem_block = item.buffer; stream->data->mem_block_len = item.size; + i2s_hal_tx_stop(hal); + i2s_hal_tx_reset(hal); +#if !SOC_GDMA_SUPPORTED + i2s_hal_tx_reset_dma(hal); +#endif /* !SOC_GDMA_SUPPORTED */ + i2s_hal_tx_reset_fifo(hal); + err = i2s_esp32_start_dma(dev, I2S_DIR_TX); if (err < 0) { - LOG_ERR("Failed to start TX DMA transfer: %d", err); + LOG_DBG("Failed to start TX DMA transfer: %d", err); return -EIO; } @@ -482,6 +557,8 @@ static int i2s_esp32_tx_start_transfer(const struct device *dev) esp_intr_enable(stream->data->irq_handle); #endif /* !SOC_GDMA_SUPPORTED */ + stream->data->transferring = true; + return 0; } @@ -502,15 +579,104 @@ static void i2s_esp32_tx_stop_transfer(const struct device *dev) i2s_hal_clear_intr_status(hal, I2S_INTR_MAX); #endif /* SOC_GDMA_SUPPORTED */ - if (stream->data->mem_block != NULL) { - k_mem_slab_free(stream->data->i2s_cfg.mem_slab, stream->data->mem_block); - stream->data->mem_block = NULL; - stream->data->mem_block_len = 0; + stream->data->mem_block = NULL; + stream->data->mem_block_len = 0; + + stream->data->transferring = false; +} + +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ + +static int i2s_esp32_start_transfer(const struct device *dev, enum i2s_dir dir) +{ + int err = 0; + +#if I2S_ESP32_IS_DIR_EN(rx) + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + err = i2s_esp32_rx_start_transfer(dev); + if (err < 0) { + LOG_DBG("RX failed to start transfer"); + goto start_transfer_end; + } } +#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + +#if I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + err = i2s_esp32_tx_start_transfer(dev); + if (err < 0) { + LOG_DBG("TX failed to start transfer"); + goto start_transfer_end; + } + } +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ + +start_transfer_end: +#if I2S_ESP32_IS_DIR_EN(rx) && I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_BOTH && err < 0) { + const struct i2s_esp32_cfg *dev_cfg = dev->config; + + if (dev_cfg->rx.data->transferring && !dev_cfg->tx.data->transferring) { + i2s_esp32_rx_stop_transfer(dev); + } + + if (!dev_cfg->rx.data->transferring && dev_cfg->tx.data->transferring) { + i2s_esp32_tx_stop_transfer(dev); + } + } +#endif /* I2S_ESP32_IS_DIR_EN(rx) && I2S_ESP32_IS_DIR_EN(tx) */ + + return err; +} + +static void i2s_esp32_stop_transfer(const struct device *dev, enum i2s_dir dir) +{ +#if I2S_ESP32_IS_DIR_EN(rx) + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + i2s_esp32_rx_stop_transfer(dev); + } +#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + +#if I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + i2s_esp32_tx_stop_transfer(dev); + } +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ } +static bool i2s_esp32_try_stop_transfer(const struct device *dev, enum i2s_dir dir, + enum i2s_trigger_cmd cmd) +{ + const struct i2s_esp32_cfg *dev_cfg = dev->config; + const struct i2s_esp32_stream *stream; + bool at_least_one_dir_with_pending_transfer = false; + +#if I2S_ESP32_IS_DIR_EN(rx) + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->rx; + if (stream->data->dma_pending) { + at_least_one_dir_with_pending_transfer = true; + } else { + i2s_esp32_rx_stop_transfer(dev); + } + } +#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + +#if I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->tx; + if ((cmd == I2S_TRIGGER_DRAIN && k_msgq_num_used_get(&stream->data->queue) > 0) || + stream->data->dma_pending) { + at_least_one_dir_with_pending_transfer = true; + } else { + i2s_esp32_tx_stop_transfer(dev); + } + } #endif /* I2S_ESP32_IS_DIR_EN(tx) */ + return at_least_one_dir_with_pending_transfer; +} + int i2s_esp32_config_dma(const struct device *dev, enum i2s_dir dir, const struct i2s_esp32_stream *stream) { @@ -545,20 +711,19 @@ int i2s_esp32_config_dma(const struct device *dev, enum i2s_dir dir, err = dma_config(stream->conf->dma_dev, stream->conf->dma_channel, &dma_cfg); if (err < 0) { - LOG_ERR("Failed to configure DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to configure DMA channel: %" PRIu32, stream->conf->dma_channel); return -EINVAL; } #else lldesc_t *desc_iter = stream->conf->dma_desc; if (!mem_block) { - LOG_ERR("At least one dma block is required"); + LOG_DBG("At least one dma block is required"); return -EINVAL; } if (!esp_ptr_dma_capable((void *)mem_block)) { - LOG_ERR("Buffer is not in DMA capable memory: %p", - (uint32_t *)mem_block); + LOG_DBG("Buffer is not in DMA capable memory: %p", (uint32_t *)mem_block); return -EINVAL; } @@ -599,7 +764,7 @@ int i2s_esp32_config_dma(const struct device *dev, enum i2s_dir dir, if (desc_iter->empty) { stream->data->dma_pending = false; - LOG_ERR("Run out of descriptors. Increase CONFIG_I2S_ESP32_DMA_DESC_NUM_MAX"); + LOG_DBG("Run out of descriptors. Increase CONFIG_I2S_ESP32_DMA_DESC_NUM_MAX"); return -EINVAL; } #endif /* SOC_GDMA_SUPPORTED */ @@ -623,7 +788,7 @@ static int i2s_esp32_start_dma(const struct device *dev, enum i2s_dir dir) } else if (dir == I2S_DIR_TX) { stream = &dev_cfg->tx; } else { - LOG_ERR("Invalid DMA direction"); + LOG_DBG("Invalid DMA direction"); return -EINVAL; } @@ -631,7 +796,7 @@ static int i2s_esp32_start_dma(const struct device *dev, enum i2s_dir dir) err = i2s_esp32_config_dma(dev, dir, stream); if (err < 0) { - LOG_ERR("Dma configuration failed: %i", err); + LOG_DBG("Dma configuration failed: %i", err); goto unlock; } @@ -660,10 +825,9 @@ static int i2s_esp32_start_dma(const struct device *dev, enum i2s_dir dir) #if SOC_GDMA_SUPPORTED err = dma_start(stream->conf->dma_dev, stream->conf->dma_channel); if (err < 0) { - LOG_ERR("Failed to start DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to start DMA channel: %" PRIu32, stream->conf->dma_channel); goto unlock; } - stream->data->dma_pending = true; #else #if I2S_ESP32_IS_DIR_EN(rx) if (dir == I2S_DIR_RX) { @@ -682,6 +846,8 @@ static int i2s_esp32_start_dma(const struct device *dev, enum i2s_dir dir) #endif /* I2S_ESP32_IS_DIR_EN(tx) */ #endif /* SOC_GDMA_SUPPORTED */ + stream->data->dma_pending = true; + unlock: irq_unlock(key); return err; @@ -702,7 +868,7 @@ static int i2s_esp32_restart_dma(const struct device *dev, enum i2s_dir dir) } else if (dir == I2S_DIR_TX) { stream = &dev_cfg->tx; } else { - LOG_ERR("Invalid DMA direction"); + LOG_DBG("Invalid DMA direction"); return -EINVAL; } @@ -737,7 +903,7 @@ static int i2s_esp32_restart_dma(const struct device *dev, enum i2s_dir dir) err = dma_reload(stream->conf->dma_dev, stream->conf->dma_channel, (uint32_t)src, (uint32_t)dst, chunk_len); if (err < 0) { - LOG_ERR("Failed to reload DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to reload DMA channel: %" PRIu32, stream->conf->dma_channel); return -EIO; } @@ -749,13 +915,13 @@ static int i2s_esp32_restart_dma(const struct device *dev, enum i2s_dir dir) err = dma_start(stream->conf->dma_dev, stream->conf->dma_channel); if (err < 0) { - LOG_ERR("Failed to start DMA channel: %"PRIu32, stream->conf->dma_channel); + LOG_DBG("Failed to start DMA channel: %" PRIu32, stream->conf->dma_channel); return -EIO; } #else err = i2s_esp32_config_dma(dev, dir, stream); if (err < 0) { - LOG_ERR("Failed to configure DMA"); + LOG_DBG("Failed to configure DMA"); return -EIO; } @@ -780,6 +946,7 @@ static int i2s_esp32_restart_dma(const struct device *dev, enum i2s_dir dir) static int i2s_esp32_initialize(const struct device *dev) { + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; const struct device *clk_dev = dev_cfg->clock_dev; const struct i2s_esp32_stream *stream; @@ -790,13 +957,19 @@ static int i2s_esp32_initialize(const struct device *dev) #endif /* !SOC_GDMA_SUPPORTED */ if (!device_is_ready(clk_dev)) { - LOG_ERR("clock control device not ready"); + LOG_DBG("Clock control device not ready"); return -ENODEV; } err = clock_control_on(clk_dev, dev_cfg->clock_subsys); if (err != 0) { - LOG_ERR("Clock control enabling failed: %d", err); + LOG_DBG("Clock control enabling failed: %d", err); + return -EIO; + } + + err = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (err < 0) { + LOG_DBG("Pins setup failed: %d", err); return -EIO; } @@ -805,7 +978,7 @@ static int i2s_esp32_initialize(const struct device *dev) stream = &dev_cfg->rx; #if SOC_GDMA_SUPPORTED if (stream->conf->dma_dev && !device_is_ready(stream->conf->dma_dev)) { - LOG_ERR("%s device not ready", stream->conf->dma_dev->name); + LOG_DBG("%s device not ready", stream->conf->dma_dev->name); return -ENODEV; } #else @@ -819,7 +992,7 @@ static int i2s_esp32_initialize(const struct device *dev) I2S_LL_RX_EVENT_MASK, i2s_esp32_rx_handler, (void *)dev, &(stream->data->irq_handle)); if (err != 0) { - LOG_ERR("Could not allocate rx interrupt (err %d)", err); + LOG_DBG("Could not allocate rx interrupt (err %d)", err); return err; } #endif /* SOC_GDMA_SUPPORTED */ @@ -833,11 +1006,14 @@ static int i2s_esp32_initialize(const struct device *dev) #endif /* I2S_ESP32_IS_DIR_EN(rx) */ #if I2S_ESP32_IS_DIR_EN(tx) + dev_data->dev = dev; + k_timer_init(&dev_data->tx_deferred_transfer_timer, i2s_esp32_tx_compl_transfer, NULL); + if (dev_cfg->tx.data && dev_cfg->tx.conf) { stream = &dev_cfg->tx; #if SOC_GDMA_SUPPORTED if (stream->conf->dma_dev && !device_is_ready(stream->conf->dma_dev)) { - LOG_ERR("%s device not ready", stream->conf->dma_dev->name); + LOG_DBG("%s device not ready", stream->conf->dma_dev->name); return -ENODEV; } #else @@ -851,7 +1027,7 @@ static int i2s_esp32_initialize(const struct device *dev) I2S_LL_TX_EVENT_MASK, i2s_esp32_tx_handler, (void *)dev, &(stream->data->irq_handle)); if (err != 0) { - LOG_ERR("Could not allocate tx interrupt (err %d)", err); + LOG_DBG("Could not allocate tx interrupt (err %d)", err); return err; } #endif /* SOC_GDMA_SUPPORTED */ @@ -868,112 +1044,150 @@ static int i2s_esp32_initialize(const struct device *dev) i2s_ll_clear_intr_status(hal->dev, I2S_INTR_MAX); #endif /* !SOC_GDMA_SUPPORTED */ + dev_data->state = I2S_STATE_NOT_READY; + LOG_DBG("%s initialized", dev->name); return 0; } -static int i2s_esp32_configure_dir(const struct device *dev, enum i2s_dir dir, - const struct i2s_esp32_stream *stream, - const struct i2s_config *i2s_cfg) +static int i2s_esp32_config_check(const struct device *dev, enum i2s_dir dir, + const struct i2s_config *i2s_cfg, uint8_t *data_format) { + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; - uint8_t data_format; - int err; + const struct i2s_esp32_stream *stream; + + if (dir == I2S_DIR_BOTH && + (!dev_cfg->rx.conf || !dev_cfg->rx.data || !dev_cfg->tx.conf || !dev_cfg->tx.data)) { + LOG_DBG("I2S_DIR_BOTH not supported"); + return -ENOSYS; + } - switch (dir) { + if (dev_data->state != I2S_STATE_NOT_READY && dev_data->state != I2S_STATE_READY) { + LOG_DBG("Invalid state: %d", (int)dev_data->state); + return -EINVAL; + } + + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { #if I2S_ESP32_IS_DIR_EN(rx) - case I2S_DIR_RX: - if (stream->conf) { + stream = &dev_cfg->rx; + if (!stream->data || !stream->conf) { + LOG_DBG("RX not enabled"); + return -EINVAL; + } + #if SOC_GDMA_SUPPORTED - if (stream->conf->dma_dev == NULL) { - LOG_ERR("RX DMA controller not available"); + if (stream->conf->dma_dev == NULL) { + LOG_DBG("RX DMA controller not available"); #else - if (stream->conf->irq_source == -1) { - LOG_ERR("RX IRQ source not available"); + if (stream->conf->irq_source == -1) { + LOG_DBG("RX IRQ source not available"); #endif /* SOC_GDMA_SUPPORTED */ - return -EINVAL; - } + return -EINVAL; } - break; +#else + LOG_DBG("RX not enabled"); + return -EINVAL; #endif /* I2S_ESP32_IS_DIR_EN(rx) */ + } + + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { #if I2S_ESP32_IS_DIR_EN(tx) - case I2S_DIR_TX: - if (stream->conf) { + stream = &dev_cfg->tx; + if (!stream->data || !stream->conf) { + LOG_DBG("TX not enabled"); + return -EINVAL; + } + #if SOC_GDMA_SUPPORTED - if (stream->conf->dma_dev == NULL) { - LOG_ERR("TX DMA controller not available"); + if (stream->conf->dma_dev == NULL) { + LOG_DBG("TX DMA controller not available"); #else - if (stream->conf->irq_source == -1) { - LOG_ERR("TX IRQ source not available"); + if (stream->conf->irq_source == -1) { + LOG_DBG("TX IRQ source not available"); #endif /* SOC_GDMA_SUPPORTED */ - return -EINVAL; - } + return -EINVAL; } - break; -#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - default: - LOG_ERR("Invalid direction"); +#else + LOG_DBG("TX not enabled"); return -EINVAL; +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ + } + + if (i2s_cfg->frame_clk_freq == 0U) { + return 0; } - if (stream->data->state != I2S_STATE_NOT_READY && stream->data->state != I2S_STATE_READY) { - LOG_ERR("Invalid state: %d", (int)stream->data->state); + if (i2s_cfg->mem_slab == NULL) { + LOG_DBG("Memory slab is NULL"); return -EINVAL; } - if (i2s_cfg->frame_clk_freq == 0U) { - stream->conf->queue_drop(stream); - memset(&stream->data->i2s_cfg, 0, sizeof(struct i2s_config)); - stream->data->is_slave = false; - stream->data->state = I2S_STATE_NOT_READY; - return 0; + if (i2s_cfg->block_size == 0) { + LOG_DBG("Block size is 0"); + return -EINVAL; } - data_format = i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK; + *data_format = i2s_cfg->format & I2S_FMT_DATA_FORMAT_MASK; + + if (*data_format != I2S_FMT_DATA_FORMAT_I2S && + *data_format != I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED && + *data_format != I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED) { + LOG_DBG("Invalid data format: %u", (unsigned int)*data_format); + return -EINVAL; + } - if (data_format != I2S_FMT_DATA_FORMAT_I2S && - data_format != I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED && - data_format != I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED) { - LOG_ERR("Invalid data format: %u", (unsigned int)data_format); + if (*data_format == I2S_FMT_DATA_FORMAT_I2S && i2s_cfg->format & I2S_FMT_DATA_ORDER_LSB) { + LOG_DBG("Invalid format: %u", (unsigned int)i2s_cfg->format); return -EINVAL; } if (i2s_cfg->word_size != 8 && i2s_cfg->word_size != 16 && i2s_cfg->word_size != 24 && i2s_cfg->word_size != 32) { - LOG_ERR("Word size not supported: %d", (int)i2s_cfg->word_size); + LOG_DBG("Word size not supported: %d", (int)i2s_cfg->word_size); return -EINVAL; } if (i2s_cfg->channels != 2) { - LOG_ERR("Currently only 2 channels are supported"); + LOG_DBG("Currently only 2 channels are supported"); return -EINVAL; } if (i2s_cfg->options & I2S_OPT_LOOPBACK) { - LOG_ERR("For internal loopback: I2S#_O_SD_GPIO = I2S#_I_SD_GPIO"); + LOG_DBG("Unsupported option: I2S_OPT_LOOPBACK"); + LOG_DBG("To enable loopback, use the same SD for TX and RX pinctrl"); return -EINVAL; } if (i2s_cfg->options & I2S_OPT_PINGPONG) { - LOG_ERR("Unsupported option: I2S_OPT_PINGPONG"); + LOG_DBG("Unsupported option: I2S_OPT_PINGPONG"); return -EINVAL; } + return 0; +} + +static int i2s_esp32_config_apply(const struct device *dev, enum i2s_dir dir, + const struct i2s_config *i2s_cfg, uint8_t data_format) +{ + const struct i2s_esp32_cfg *dev_cfg = dev->config; + const struct i2s_esp32_stream *stream; + i2s_hal_slot_config_t slot_cfg = {0}; + bool is_slave; + int err; + if ((i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE) != 0 && (i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) != 0) { - stream->data->is_slave = true; + is_slave = true; } else if ((i2s_cfg->options & I2S_OPT_FRAME_CLK_SLAVE) == 0 && (i2s_cfg->options & I2S_OPT_BIT_CLK_SLAVE) == 0) { - stream->data->is_slave = false; + is_slave = false; } else { - LOG_ERR("I2S_OPT_FRAME_CLK and I2S_OPT_BIT_CLK options must both be" - " MASTER or SLAVE"); + LOG_DBG("I2S_OPT_FRAME_CLK and I2S_OPT_BIT_CLK options are incompatible"); return -EINVAL; } - i2s_hal_slot_config_t slot_cfg = {0}; - slot_cfg.data_bit_width = i2s_cfg->word_size; slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO; slot_cfg.slot_bit_width = i2s_cfg->word_size > 16 ? 32 : 16; @@ -983,19 +1197,21 @@ static int i2s_esp32_configure_dir(const struct device *dev, enum i2s_dir dir, #if SOC_I2S_HW_VERSION_2 slot_cfg.std.left_align = true; #endif /* SOC_I2S_HW_VERSION_2 */ - } else { + } else if (data_format == I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED) { slot_cfg.std.ws_pol = i2s_cfg->format & I2S_FMT_FRAME_CLK_INV ? false : true; slot_cfg.std.bit_shift = false; - if (data_format == I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED) { #if SOC_I2S_HW_VERSION_2 - slot_cfg.std.left_align = true; - } else if (data_format == I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED) { - slot_cfg.std.left_align = false; + slot_cfg.std.left_align = true; + } else if (data_format == I2S_FMT_DATA_FORMAT_RIGHT_JUSTIFIED) { + slot_cfg.std.ws_pol = i2s_cfg->format & I2S_FMT_FRAME_CLK_INV ? false : true; + slot_cfg.std.bit_shift = false; + slot_cfg.std.left_align = false; #endif /* SOC_I2S_HW_VERSION_2 */ - } else { - LOG_ERR("Unsupported data format: %u", (unsigned int)data_format); - } + } else { + LOG_DBG("Unsupported data format: %u", (unsigned int)data_format); + return -EINVAL; } + slot_cfg.std.ws_width = slot_cfg.slot_bit_width; slot_cfg.std.slot_mask = I2S_STD_SLOT_BOTH; #if SOC_I2S_HW_VERSION_1 @@ -1013,65 +1229,43 @@ static int i2s_esp32_configure_dir(const struct device *dev, enum i2s_dir dir, return -EINVAL; } - if (dir == I2S_DIR_TX) { -#if I2S_ESP32_IS_DIR_EN(tx) - i2s_hal_std_enable_tx_channel(hal); - if (dev_cfg->rx.data != NULL && dev_cfg->rx.data->state != I2S_STATE_NOT_READY) { - if (stream->data->is_slave && !dev_cfg->rx.data->is_slave) { /*full duplex*/ - i2s_ll_share_bck_ws(hal->dev, true); - } else { - i2s_ll_share_bck_ws(hal->dev, false); - } - } else { - i2s_ll_share_bck_ws(hal->dev, false); - } - - i2s_hal_std_set_tx_slot(hal, stream->data->is_slave, &slot_cfg); - i2s_hal_set_tx_clock(hal, &i2s_hal_clock_info, I2S_ESP32_CLK_SRC); - -#if SOC_I2S_HW_VERSION_2 - if (dev_cfg->rx.data != NULL && dev_cfg->rx.data->state != I2S_STATE_NOT_READY) { - if (stream->data->is_slave && !dev_cfg->rx.data->is_slave) { /*full duplex*/ - i2s_ll_mclk_bind_to_rx_clk(hal->dev); - } - } -#endif /* SOC_I2S_HW_VERSION_2 */ -#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - } else if (dir == I2S_DIR_RX) { #if I2S_ESP32_IS_DIR_EN(rx) - i2s_hal_std_enable_rx_channel(hal); - if (dev_cfg->tx.data != NULL && dev_cfg->tx.data->state != I2S_STATE_NOT_READY) { - if (stream->data->is_slave && !dev_cfg->tx.data->is_slave) { /*full duplex*/ - i2s_ll_share_bck_ws(hal->dev, true); - } else { - i2s_ll_share_bck_ws(hal->dev, false); - } - } else { - i2s_ll_share_bck_ws(hal->dev, false); + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + bool rx_is_slave; + + rx_is_slave = is_slave; + if (dir == I2S_DIR_BOTH || (dev_cfg->tx.data && dev_cfg->tx.data->configured)) { + rx_is_slave = true; } - i2s_hal_std_set_rx_slot(hal, stream->data->is_slave, &slot_cfg); + i2s_hal_std_set_rx_slot(hal, rx_is_slave, &slot_cfg); i2s_hal_set_rx_clock(hal, &i2s_hal_clock_info, I2S_ESP32_CLK_SRC); + i2s_ll_rx_enable_std(hal->dev); -#if SOC_I2S_HW_VERSION_2 - if (dev_cfg->tx.data != NULL && dev_cfg->tx.data->state != I2S_STATE_NOT_READY) { - if (stream->data->is_slave && !dev_cfg->tx.data->is_slave) { /*full duplex*/ - i2s_ll_mclk_bind_to_tx_clk(hal->dev); - } - } -#endif /* SOC_I2S_HW_VERSION_2 */ -#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + stream = &dev_cfg->rx; + memcpy(&stream->data->i2s_cfg, i2s_cfg, sizeof(struct i2s_config)); + stream->data->configured = true; } +#endif /* I2S_ESP32_IS_DIR_EN(rx) */ - err = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); - if (err < 0) { - LOG_ERR("Pins setup failed: %d", err); - return -EIO; - } +#if I2S_ESP32_IS_DIR_EN(tx) + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + i2s_hal_std_set_tx_slot(hal, is_slave, &slot_cfg); + i2s_hal_set_tx_clock(hal, &i2s_hal_clock_info, I2S_ESP32_CLK_SRC); + i2s_ll_tx_enable_std(hal->dev); - memcpy(&stream->data->i2s_cfg, i2s_cfg, sizeof(struct i2s_config)); + stream = &dev_cfg->tx; + memcpy(&stream->data->i2s_cfg, i2s_cfg, sizeof(struct i2s_config)); + stream->data->configured = true; + } +#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - stream->data->state = I2S_STATE_READY; + if (dev_cfg->rx.data && dev_cfg->rx.data->configured && dev_cfg->tx.data && + dev_cfg->tx.data->configured) { + i2s_ll_share_bck_ws(hal->dev, true); + } else { + i2s_ll_share_bck_ws(hal->dev, false); + } return 0; } @@ -1079,74 +1273,49 @@ static int i2s_esp32_configure_dir(const struct device *dev, enum i2s_dir dir, static int i2s_esp32_configure(const struct device *dev, enum i2s_dir dir, const struct i2s_config *i2s_cfg) { + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; const struct i2s_esp32_stream *stream; + uint8_t data_format; int err; - switch (dir) { - case I2S_DIR_RX: + err = i2s_esp32_config_check(dev, dir, i2s_cfg, &data_format); + if (err < 0) { + return err; + } + + if (i2s_cfg->frame_clk_freq == 0U) { + i2s_esp32_queue_drop(dev, dir); + #if I2S_ESP32_IS_DIR_EN(rx) - stream = &dev_cfg->rx; - if (stream) { - err = i2s_esp32_configure_dir(dev, I2S_DIR_RX, stream, i2s_cfg); - } else { - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->rx; + memset(&stream->data->i2s_cfg, 0, sizeof(struct i2s_config)); + stream->data->configured = false; } -#else - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; #endif /* I2S_ESP32_IS_DIR_EN(rx) */ - break; - case I2S_DIR_TX: -#if I2S_ESP32_IS_DIR_EN(tx) - stream = &dev_cfg->tx; - if (stream) { - err = i2s_esp32_configure_dir(dev, I2S_DIR_TX, stream, i2s_cfg); - } else { - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; - } -#else - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; -#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - break; - case I2S_DIR_BOTH: + #if I2S_ESP32_IS_DIR_EN(tx) - stream = &dev_cfg->tx; - if (stream) { - err = i2s_esp32_configure_dir(dev, I2S_DIR_TX, stream, i2s_cfg); - } else { - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + stream = &dev_cfg->tx; + memset(&stream->data->i2s_cfg, 0, sizeof(struct i2s_config)); + stream->data->configured = false; } -#else - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; #endif /* I2S_ESP32_IS_DIR_EN(tx) */ - if (err < 0) { - break; - } -#if I2S_ESP32_IS_DIR_EN(rx) - stream = &dev_cfg->rx; - if (stream) { - err = i2s_esp32_configure_dir(dev, I2S_DIR_RX, stream, i2s_cfg); - } else { - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; - } -#else - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; -#endif /* I2S_ESP32_IS_DIR_EN(rx) */ - break; - default: - LOG_ERR("Invalid direction: %d", (int)dir); - return -EINVAL; + + dev_data->state = I2S_STATE_NOT_READY; + + return 0; } - return err; + err = i2s_esp32_config_apply(dev, dir, i2s_cfg, data_format); + if (err < 0) { + return err; + } + + dev_data->state = I2S_STATE_READY; + + return 0; } static const struct i2s_config *i2s_esp32_config_get(const struct device *dev, enum i2s_dir dir) @@ -1157,233 +1326,168 @@ static const struct i2s_config *i2s_esp32_config_get(const struct device *dev, e if (dir == I2S_DIR_RX) { #if I2S_ESP32_IS_DIR_EN(rx) stream = &dev_cfg->rx; - if (!stream) { - LOG_ERR("I2S_DIR_RX not enabled"); + if (!stream->data) { + LOG_DBG("RX not enabled"); return NULL; } #else - LOG_ERR("I2S_DIR_RX not enabled"); + LOG_DBG("RX not enabled"); return NULL; #endif /* I2S_ESP32_IS_DIR_EN(rx) */ } else if (dir == I2S_DIR_TX) { #if I2S_ESP32_IS_DIR_EN(tx) stream = &dev_cfg->tx; - if (!stream) { - LOG_ERR("I2S_DIR_TX not enabled"); + if (!stream->data) { + LOG_DBG("TX not enabled"); return NULL; } #else - LOG_ERR("I2S_DIR_TX not enabled"); + LOG_DBG("TX not enabled"); return NULL; #endif /* I2S_ESP32_IS_DIR_EN(tx) */ } else { - LOG_ERR("Invalid direction: %d", (int)dir); + LOG_DBG("Invalid direction: %d", (int)dir); return NULL; } - if (stream->data->state == I2S_STATE_NOT_READY) { + if (!stream->data->configured) { return NULL; } return &stream->data->i2s_cfg; } -static int i2s_esp32_trigger_stream(const struct device *dev, const struct i2s_esp32_stream *stream, - enum i2s_dir dir, enum i2s_trigger_cmd cmd) +static int i2s_esp32_trigger(const struct device *dev, enum i2s_dir dir, enum i2s_trigger_cmd cmd) { + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; - const i2s_hal_context_t *hal = &dev_cfg->hal; + bool configured, cmd_allowed, at_least_one_dir_with_pending_transfer; unsigned int key; int err; - switch (cmd) { - case I2S_TRIGGER_START: - if (stream->data->state != I2S_STATE_READY) { - LOG_ERR("START - Invalid state: %d", (int)stream->data->state); - return -EIO; + if (dir == I2S_DIR_BOTH) { + if (dev_cfg->rx.conf && dev_cfg->rx.data && dev_cfg->tx.conf && dev_cfg->tx.data) { + configured = dev_cfg->rx.data->configured && dev_cfg->tx.data->configured; + } else { + LOG_DBG("I2S_DIR_BOTH not supported"); + return -ENOSYS; } + } else if (dir == I2S_DIR_RX) { + configured = dev_cfg->rx.data->configured; + } else if (dir == I2S_DIR_TX) { + configured = dev_cfg->tx.data->configured; + } else { + LOG_DBG("Invalid dir: %d", dir); + return -EINVAL; + } - key = irq_lock(); - - if (dir == I2S_DIR_RX) { - i2s_hal_rx_stop(hal); - i2s_hal_rx_reset(hal); -#if !SOC_GDMA_SUPPORTED - i2s_hal_rx_reset_dma(hal); -#endif /* !SOC_GDMA_SUPPORTED */ - i2s_hal_rx_reset_fifo(hal); - } else if (dir == I2S_DIR_TX) { - i2s_hal_tx_stop(hal); - i2s_hal_tx_reset(hal); -#if !SOC_GDMA_SUPPORTED - i2s_hal_tx_reset_dma(hal); -#endif /* !SOC_GDMA_SUPPORTED */ - i2s_hal_tx_reset_fifo(hal); - } + if (!configured) { + LOG_DBG("Device is not configured"); + return -EIO; + } - err = stream->conf->start_transfer(dev); - if (err < 0) { - LOG_ERR("START - Transfer start failed: %d", err); - irq_unlock(key); - return -EIO; - } - stream->data->last_block = false; - stream->data->state = I2S_STATE_RUNNING; - irq_unlock(key); + switch (cmd) { + case I2S_TRIGGER_START: + cmd_allowed = (dev_data->state == I2S_STATE_READY); break; - case I2S_TRIGGER_STOP: - key = irq_lock(); - if (stream->data->state != I2S_STATE_RUNNING) { - irq_unlock(key); - LOG_ERR("STOP - Invalid state: %d", (int)stream->data->state); - return -EIO; - } - - if (stream->data->dma_pending) { - stream->data->stop_without_draining = true; - stream->data->state = I2S_STATE_STOPPING; - } else { - stream->conf->stop_transfer(dev); - stream->data->last_block = true; - stream->data->state = I2S_STATE_READY; - } - - irq_unlock(key); - break; - + __fallthrough; case I2S_TRIGGER_DRAIN: - key = irq_lock(); - if (stream->data->state != I2S_STATE_RUNNING) { - irq_unlock(key); - LOG_ERR("DRAIN - Invalid state: %d", (int)stream->data->state); - return -EIO; - } - -#if I2S_ESP32_IS_DIR_EN(tx) - if (dir == I2S_DIR_TX) { - if (k_msgq_num_used_get(&stream->data->queue) > 0 || - stream->data->dma_pending) { - stream->data->stop_without_draining = false; - stream->data->state = I2S_STATE_STOPPING; - } else { - stream->conf->stop_transfer(dev); - stream->data->state = I2S_STATE_READY; - } - } -#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - -#if I2S_ESP32_IS_DIR_EN(rx) - if (dir == I2S_DIR_RX) { - if (stream->data->dma_pending) { - stream->data->stop_without_draining = true; - stream->data->state = I2S_STATE_STOPPING; - } else { - stream->conf->stop_transfer(dev); - stream->data->last_block = true; - stream->data->state = I2S_STATE_READY; - } - } -#endif /* I2S_ESP32_IS_DIR_EN(rx) */ - - irq_unlock(key); + cmd_allowed = (dev_data->state == I2S_STATE_RUNNING); break; - case I2S_TRIGGER_DROP: - if (stream->data->state == I2S_STATE_NOT_READY) { - LOG_ERR("DROP - invalid state: %d", (int)stream->data->state); - return -EIO; - } - stream->conf->stop_transfer(dev); - stream->conf->queue_drop(stream); - stream->data->state = I2S_STATE_READY; + cmd_allowed = configured; break; - case I2S_TRIGGER_PREPARE: - if (stream->data->state != I2S_STATE_ERROR) { - LOG_ERR("PREPARE - invalid state: %d", (int)stream->data->state); - return -EIO; - } - stream->conf->queue_drop(stream); - stream->data->state = I2S_STATE_READY; + cmd_allowed = (dev_data->state == I2S_STATE_ERROR); break; - default: - LOG_ERR("Unsupported trigger command: %d", (int)cmd); + LOG_DBG("Invalid trigger: %d", cmd); return -EINVAL; } - return 0; -} + if (!cmd_allowed) { + switch (cmd) { + case I2S_TRIGGER_START: + LOG_DBG("START - Invalid state: %d", (int)dev_data->state); + break; + case I2S_TRIGGER_STOP: + LOG_DBG("STOP - Invalid state: %d", (int)dev_data->state); + break; + case I2S_TRIGGER_DRAIN: + LOG_DBG("DRAIN - Invalid state: %d", (int)dev_data->state); + break; + case I2S_TRIGGER_DROP: + LOG_DBG("DROP - invalid state: %d", (int)dev_data->state); + break; + case I2S_TRIGGER_PREPARE: + LOG_DBG("PREPARE - invalid state: %d", (int)dev_data->state); + break; + default: + LOG_DBG("Invalid trigger: %d", cmd); + } -static int i2s_esp32_trigger(const struct device *dev, enum i2s_dir dir, enum i2s_trigger_cmd cmd) -{ - const struct i2s_esp32_cfg *dev_cfg = dev->config; - const struct i2s_esp32_stream *stream; - int err; + return -EIO; + } - switch (dir) { - case I2S_DIR_RX: -#if I2S_ESP32_IS_DIR_EN(rx) - stream = &dev_cfg->rx; - if (stream) { - err = i2s_esp32_trigger_stream(dev, stream, I2S_DIR_RX, cmd); - } else { - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; + if (dev_data->state == I2S_STATE_RUNNING && dev_data->active_dir != dir) { + LOG_DBG("Trigger dir (%d) different from active dir (%d)", dir, + dev_data->active_dir); + return -EINVAL; + } + + err = 0; + key = irq_lock(); + switch (cmd) { + case I2S_TRIGGER_START: + dev_data->active_dir = dir; + dev_data->tx_stop_without_draining = true; + err = i2s_esp32_start_transfer(dev, dir); + if (err < 0) { + LOG_DBG("START - Transfer start failed: %d", err); + err = -EIO; + break; } -#else - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; -#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + dev_data->state = I2S_STATE_RUNNING; break; - case I2S_DIR_TX: -#if I2S_ESP32_IS_DIR_EN(tx) - stream = &dev_cfg->tx; - if (stream) { - err = i2s_esp32_trigger_stream(dev, stream, I2S_DIR_TX, cmd); + case I2S_TRIGGER_STOP: + __fallthrough; + case I2S_TRIGGER_DRAIN: + at_least_one_dir_with_pending_transfer = i2s_esp32_try_stop_transfer(dev, dir, cmd); + if (at_least_one_dir_with_pending_transfer) { + dev_data->state = I2S_STATE_STOPPING; } else { - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; + dev_data->state = I2S_STATE_READY; } -#else - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; -#endif /* I2S_ESP32_IS_DIR_EN(tx) */ - break; - case I2S_DIR_BOTH: + #if I2S_ESP32_IS_DIR_EN(tx) - stream = &dev_cfg->tx; - if (stream) { - err = i2s_esp32_trigger_stream(dev, stream, I2S_DIR_TX, cmd); - } else { - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; + if ((dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) && + dev_data->state == I2S_STATE_STOPPING) { + switch (cmd) { + case I2S_TRIGGER_STOP: + dev_data->tx_stop_without_draining = true; + break; + case I2S_TRIGGER_DRAIN: + dev_data->tx_stop_without_draining = false; + default: + } } -#else - LOG_ERR("I2S_DIR_TX not enabled"); - err = -EINVAL; #endif /* I2S_ESP32_IS_DIR_EN(tx) */ - if (err < 0) { - break; - } -#if I2S_ESP32_IS_DIR_EN(rx) - stream = &dev_cfg->rx; - if (stream) { - err = i2s_esp32_trigger_stream(dev, stream, I2S_DIR_RX, cmd); - } else { - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; - } -#else - LOG_ERR("I2S_DIR_RX not enabled"); - err = -EINVAL; -#endif /* I2S_ESP32_IS_DIR_EN(rx) */ + break; + case I2S_TRIGGER_DROP: + i2s_esp32_stop_transfer(dev, dir); + i2s_esp32_queue_drop(dev, dir); + dev_data->state = I2S_STATE_READY; + break; + case I2S_TRIGGER_PREPARE: + i2s_esp32_queue_drop(dev, dir); + dev_data->state = I2S_STATE_READY; break; default: - LOG_ERR("Invalid direction: %d", (int)dir); - err = -EINVAL; + LOG_DBG("Unsupported trigger command: %d", (int)cmd); + err = -EIO; } + irq_unlock(key); return err; } @@ -1391,69 +1495,76 @@ static int i2s_esp32_trigger(const struct device *dev, enum i2s_dir dir, enum i2 static int i2s_esp32_read(const struct device *dev, void **mem_block, size_t *size) { #if I2S_ESP32_IS_DIR_EN(rx) + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; const struct i2s_esp32_stream *stream = &dev_cfg->rx; + enum i2s_state state = dev_data->state; struct queue_item item; int err; - if (!stream) { - *mem_block = NULL; - *size = 0; - - LOG_ERR("I2S_DIR_RX not enabled"); - return -EINVAL; + if (!stream->data || !stream->conf) { + LOG_DBG("RX not enabled"); + return -EIO; } - if (stream->data->state == I2S_STATE_NOT_READY) { - LOG_ERR("RX invalid state: %d", (int)stream->data->state); + if (!stream->data->configured) { + LOG_DBG("RX not configured"); return -EIO; - } else if (stream->data->state == I2S_STATE_ERROR && - k_msgq_num_used_get(&stream->data->queue) == 0) { - LOG_ERR("RX queue empty"); + } + + if (state == I2S_STATE_NOT_READY) { + LOG_DBG("Invalid state: %d", (int)state); return -EIO; } err = k_msgq_get(&stream->data->queue, &item, - K_MSEC(stream->data->i2s_cfg.timeout)); - if (err < 0) { - LOG_ERR("RX queue empty"); - return err; - } + (state == I2S_STATE_ERROR) ? K_NO_WAIT + : K_MSEC(stream->data->i2s_cfg.timeout)); + if (err == 0) { + *mem_block = item.buffer; + *size = item.size; + } else { + LOG_DBG("RX queue empty"); - *mem_block = item.buffer; - *size = item.size; + if (err == -ENOMSG) { + err = -EIO; + } + } - return 0; + return err; #else - *mem_block = NULL; - *size = 0; - - LOG_ERR("I2S_DIR_RX not enabled"); - return -EINVAL; + LOG_DBG("RX not enabled"); + return -EIO; #endif /* I2S_ESP32_IS_DIR_EN(rx) */ } static int i2s_esp32_write(const struct device *dev, void *mem_block, size_t size) { #if I2S_ESP32_IS_DIR_EN(tx) + struct i2s_esp32_data *dev_data = dev->data; const struct i2s_esp32_cfg *dev_cfg = dev->config; const struct i2s_esp32_stream *stream = &dev_cfg->tx; + enum i2s_state state = dev_data->state; int err; - if (!stream) { - LOG_ERR("I2S_DIR_TX not enabled"); - return -EINVAL; + if (!stream->data || !stream->conf) { + LOG_DBG("TX not enabled"); + return -EIO; + } + + if (!stream->data->configured) { + LOG_DBG("TX not configured"); + return -EIO; } - if (stream->data->state != I2S_STATE_RUNNING && - stream->data->state != I2S_STATE_READY) { - LOG_ERR("TX Invalid state: %d", (int)stream->data->state); + if (state != I2S_STATE_RUNNING && state != I2S_STATE_READY) { + LOG_DBG("Invalid state: %d", (int)state); return -EIO; } if (size > stream->data->i2s_cfg.block_size) { - LOG_ERR("Max write size is: %zu", stream->data->i2s_cfg.block_size); - return -EINVAL; + LOG_DBG("Max write size is: %u", stream->data->i2s_cfg.block_size); + return -EIO; } struct queue_item item = {.buffer = mem_block, .size = size}; @@ -1461,14 +1572,13 @@ static int i2s_esp32_write(const struct device *dev, void *mem_block, size_t siz err = k_msgq_put(&stream->data->queue, &item, K_MSEC(stream->data->i2s_cfg.timeout)); if (err < 0) { - LOG_ERR("TX queue full"); - return err; + LOG_DBG("TX queue full"); } - return 0; + return err; #else - LOG_ERR("I2S_DIR_TX not enabled"); - return -EINVAL; + LOG_DBG("TX not enabled"); + return -EIO; #endif /* I2S_ESP32_IS_DIR_EN(tx) */ } @@ -1480,68 +1590,86 @@ static DEVICE_API(i2s, i2s_esp32_driver_api) = { .write = i2s_esp32_write }; -#define I2S_ESP32_STREAM_DECLARE_DMA_DESC(index, dir) \ +#if SOC_GDMA_SUPPORTED + +#define I2S_ESP32_DT_INST_SANITY_CHECK(index) \ + BUILD_ASSERT(DT_INST_NODE_HAS_PROP(index, dmas), "Missing property: dmas"); \ + BUILD_ASSERT(DT_INST_NODE_HAS_PROP(index, dma_names), "Missing property: dma-names"); + +#define I2S_ESP32_STREAM_DECL_DESC(index, rx) + +#define I2S_ESP32_STREAM_DECLARE_DATA(index, dir) + +#define I2S_ESP32_STREAM_DECLARE_CONF(index, dir) \ + .dma_dev = UTIL_AND(DT_INST_DMAS_HAS_NAME(index, dir), \ + DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(index, dir))), \ + .dma_channel = UTIL_AND(DT_INST_DMAS_HAS_NAME(index, dir), \ + DT_INST_DMAS_CELL_BY_NAME(index, dir, channel)) + +#else + +#define I2S_ESP32_DT_INST_SANITY_CHECK(index) \ + BUILD_ASSERT(!DT_INST_NODE_HAS_PROP(index, dmas), "Unexpected property: dmas"); \ + BUILD_ASSERT(!DT_INST_NODE_HAS_PROP(index, dma_names), "Unexpected property: dma-names"); \ + BUILD_ASSERT(DT_INST_NODE_HAS_PROP(index, interrupt_names), \ + "Missing property: interrupt-names") + +#define I2S_ESP32_STREAM_DECL_DESC(index, dir) \ lldesc_t i2s_esp32_stream_##index##_##dir##_dma_desc[CONFIG_I2S_ESP32_DMA_DESC_NUM_MAX] -#define I2S_ESP32_STREAM_DECL_DMA_DESC(index, dir) \ - COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ - (I2S_ESP32_STREAM_DECLARE_DMA_DESC(index, dir)), ()) +#define I2S_ESP32_STREAM_DECLARE_DATA(index, dir) .irq_handle = NULL + +#define I2S_ESP32_STREAM_DECLARE_CONF(index, dir) \ + .irq_source = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ + (DT_INST_IRQ_BY_NAME(index, dir, irq)), (-1)), \ + .irq_priority = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ + (DT_INST_IRQ_BY_NAME(index, dir, priority)), (-1)), \ + .irq_flags = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ + (DT_INST_IRQ_BY_NAME(index, dir, flags)), (-1)), \ + .dma_desc = UTIL_AND(DT_INST_IRQ_HAS_NAME(index, dir), \ + i2s_esp32_stream_##index##_##dir##_dma_desc) + +#endif /* SOC_GDMA_SUPPORTED */ #define I2S_ESP32_STREAM_DECLARE(index, dir) \ struct i2s_esp32_stream_data i2s_esp32_stream_##index##_##dir##_data = { \ - .state = I2S_STATE_NOT_READY, \ - .is_slave = false, \ + .configured = false, \ + .transferring = false, \ .i2s_cfg = {0}, \ .mem_block = NULL, \ .mem_block_len = 0, \ - .last_block = false, \ - .stop_without_draining = false, \ .queue = {}, \ - .irq_handle = NULL, \ - .dma_pending = false \ - }; \ + .dma_pending = false, \ + I2S_ESP32_STREAM_DECLARE_DATA(index, dir)}; \ \ const struct i2s_esp32_stream_conf i2s_esp32_stream_##index##_##dir##_conf = { \ - .queue_drop = i2s_esp32_queue_drop, \ - .start_transfer = i2s_esp32_##dir##_start_transfer, \ - .stop_transfer = i2s_esp32_##dir##_stop_transfer, \ - .dma_dev = UTIL_AND(DT_INST_DMAS_HAS_NAME(index, dir), \ - DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(index, dir))), \ - .dma_channel = UTIL_AND(DT_INST_DMAS_HAS_NAME(index, dir), \ - DT_INST_DMAS_CELL_BY_NAME(index, dir, channel)), \ - .dma_desc = UTIL_AND(DT_INST_IRQ_HAS_NAME(index, dir), \ - i2s_esp32_stream_##index##_##dir##_dma_desc), \ - .irq_source = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ - (DT_INST_IRQ_BY_NAME(index, dir, irq)), (-1)), \ - .irq_priority = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ - (DT_INST_IRQ_BY_NAME(index, dir, priority)), (-1)), \ - .irq_flags = COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), \ - (DT_INST_IRQ_BY_NAME(index, dir, flags)), (-1)) \ - } - -#define I2S_ESP32_STREAM_DECL(index, dir) \ + I2S_ESP32_STREAM_DECLARE_CONF(index, dir)}; + +#define I2S_ESP32_STREAM_COND_DECLARE(index, dir) \ + COND_CODE_1(DT_INST_IRQ_HAS_NAME(index, dir), (I2S_ESP32_STREAM_DECL_DESC(index, dir)), \ + ()); \ COND_CODE_1(UTIL_OR(DT_INST_DMAS_HAS_NAME(index, dir), DT_INST_IRQ_HAS_NAME(index, dir)), \ (I2S_ESP32_STREAM_DECLARE(index, dir)), ()) #define I2S_ESP32_STREAM_INIT(index, dir) \ - .dir = {.conf = UTIL_AND(UTIL_OR(DT_INST_DMAS_HAS_NAME(index, dir), \ - DT_INST_IRQ_HAS_NAME(index, dir)), \ - &i2s_esp32_stream_##index##_##dir##_conf), \ - .data = UTIL_AND(UTIL_OR(DT_INST_DMAS_HAS_NAME(index, dir), \ - DT_INST_IRQ_HAS_NAME(index, dir)), \ - &i2s_esp32_stream_##index##_##dir##_data)} + COND_CODE_1(UTIL_OR(DT_INST_DMAS_HAS_NAME(index, dir), DT_INST_IRQ_HAS_NAME(index, dir)), \ + (.dir = {.conf = &i2s_esp32_stream_##index##_##dir##_conf, \ + .data = &i2s_esp32_stream_##index##_##dir##_data}), \ + (.dir = {.conf = NULL, .data = NULL})) #define I2S_ESP32_INIT(index) \ - PINCTRL_DT_INST_DEFINE(index); \ + I2S_ESP32_DT_INST_SANITY_CHECK(index); \ \ - I2S_ESP32_STREAM_DECL_DMA_DESC(index, rx); \ - I2S_ESP32_STREAM_DECL(index, rx); \ + PINCTRL_DT_INST_DEFINE(index); \ \ - I2S_ESP32_STREAM_DECL_DMA_DESC(index, tx); \ - I2S_ESP32_STREAM_DECL(index, tx); \ + I2S_ESP32_STREAM_COND_DECLARE(index, rx); \ + I2S_ESP32_STREAM_COND_DECLARE(index, tx); \ \ static struct i2s_esp32_data i2s_esp32_data_##index = { \ - .clk_info = {0}}; \ + .state = I2S_STATE_NOT_READY, \ + .tx_stop_without_draining = true, \ + .clk_info = {0}, \ + }; \ \ static const struct i2s_esp32_cfg i2s_esp32_config_##index = { \ .unit = DT_PROP(DT_DRV_INST(index), unit), \ @@ -1549,7 +1677,9 @@ static DEVICE_API(i2s, i2s_esp32_driver_api) = { .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(index)), \ .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(index, offset), \ - I2S_ESP32_STREAM_INIT(index, rx), I2S_ESP32_STREAM_INIT(index, tx)}; \ + I2S_ESP32_STREAM_INIT(index, rx), \ + I2S_ESP32_STREAM_INIT(index, tx), \ + }; \ \ DEVICE_DT_INST_DEFINE(index, &i2s_esp32_initialize, NULL, &i2s_esp32_data_##index, \ &i2s_esp32_config_##index, POST_KERNEL, CONFIG_I2S_INIT_PRIORITY, \ diff --git a/dts/riscv/espressif/esp32c6/esp32c6_common.dtsi b/dts/riscv/espressif/esp32c6/esp32c6_common.dtsi index 34d70c43121ee..482a81aca9a9f 100644 --- a/dts/riscv/espressif/esp32c6/esp32c6_common.dtsi +++ b/dts/riscv/espressif/esp32c6/esp32c6_common.dtsi @@ -312,6 +312,8 @@ interrupts = ; interrupt-parent = <&intc>; clocks = <&clock ESP32_I2S1_MODULE>; + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; unit = <0>; status = "disabled"; }; diff --git a/dts/riscv/espressif/esp32h2/esp32h2_common.dtsi b/dts/riscv/espressif/esp32h2/esp32h2_common.dtsi index 86d6a8a963db3..bef5cc99177a4 100644 --- a/dts/riscv/espressif/esp32h2/esp32h2_common.dtsi +++ b/dts/riscv/espressif/esp32h2/esp32h2_common.dtsi @@ -304,6 +304,8 @@ interrupts = ; interrupt-parent = <&intc>; clocks = <&clock ESP32_I2S1_MODULE>; + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; unit = <0>; status = "disabled"; }; diff --git a/samples/drivers/i2s/echo/boards/esp32_devkitc_wrover_procpu.overlay b/samples/drivers/i2s/echo/boards/esp32_devkitc_procpu.overlay similarity index 65% rename from samples/drivers/i2s/echo/boards/esp32_devkitc_wrover_procpu.overlay rename to samples/drivers/i2s/echo/boards/esp32_devkitc_procpu.overlay index c336dbe8f97f5..7de7be6819a69 100644 --- a/samples/drivers/i2s/echo/boards/esp32_devkitc_wrover_procpu.overlay +++ b/samples/drivers/i2s/echo/boards/esp32_devkitc_procpu.overlay @@ -8,14 +8,8 @@ group1 { pinmux = , , - , - , - ; - output-enable; - }; - group2 { - pinmux = ; - input-enable; + , + ; }; }; @@ -24,5 +18,5 @@ i2s_rxtx: &i2s0 { interrupts = , ; - interrupt-names = "tx", "rx"; + interrupt-names = "rx", "tx"; }; diff --git a/samples/drivers/i2s/echo/boards/esp32s2_devkitc.overlay b/samples/drivers/i2s/echo/boards/esp32s2_devkitc.overlay index 7e81372d2046d..6735e73895e4c 100644 --- a/samples/drivers/i2s/echo/boards/esp32s2_devkitc.overlay +++ b/samples/drivers/i2s/echo/boards/esp32s2_devkitc.overlay @@ -6,16 +6,10 @@ &i2s0_default { group1 { - pinmux = , - , - , - , - ; - output-enable; - }; - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; @@ -24,5 +18,5 @@ i2s_rxtx: &i2s0 { interrupts = , ; - interrupt-names = "tx", "rx"; + interrupt-names = "rx", "tx"; }; diff --git a/samples/drivers/i2s/echo/boards/esp32s3_devkitc_procpu.overlay b/samples/drivers/i2s/echo/boards/esp32s3_devkitc_procpu.overlay index 5c51f7abc42ff..7ef35bfaa5c70 100644 --- a/samples/drivers/i2s/echo/boards/esp32s3_devkitc_procpu.overlay +++ b/samples/drivers/i2s/echo/boards/esp32s3_devkitc_procpu.overlay @@ -6,15 +6,10 @@ &i2s0_default { group1 { - pinmux = , - , - , - ; - output-enable; - }; - group2 { - pinmux = ; - input-enable; + pinmux = , + , + , + ; }; }; diff --git a/samples/drivers/i2s/output/boards/esp32_devkitc_wrover_procpu.overlay b/samples/drivers/i2s/output/boards/esp32_devkitc_procpu.overlay similarity index 72% rename from samples/drivers/i2s/output/boards/esp32_devkitc_wrover_procpu.overlay rename to samples/drivers/i2s/output/boards/esp32_devkitc_procpu.overlay index fd9aa4c1959f2..79dc911ed1ce0 100644 --- a/samples/drivers/i2s/output/boards/esp32_devkitc_wrover_procpu.overlay +++ b/samples/drivers/i2s/output/boards/esp32_devkitc_procpu.overlay @@ -13,13 +13,8 @@ &i2s0_default { group1 { pinmux = , - , - ; - output-enable; - }; - group2 { - pinmux = ; - input-enable; + , + ; }; }; diff --git a/samples/drivers/i2s/output/boards/esp32c6_devkitc_hpcore.overlay b/samples/drivers/i2s/output/boards/esp32c6_devkitc_hpcore.overlay index dec2b95ec30f4..7ca1eadfe048d 100644 --- a/samples/drivers/i2s/output/boards/esp32c6_devkitc_hpcore.overlay +++ b/samples/drivers/i2s/output/boards/esp32c6_devkitc_hpcore.overlay @@ -10,24 +10,16 @@ }; }; -&pinctrl { - i2s_default: i2s_default { - group1 { - pinmux = , - , - , - ; - }; - group2 { - pinmux = ; - }; +&i2s_default { + group1 { + pinmux = , + , + ; }; }; &i2s { status = "okay"; - pinctrl-0 = <&i2s_default>; - pinctrl-names = "default"; dmas = <&dma 3>; dma-names = "tx"; diff --git a/samples/drivers/i2s/output/boards/esp32h2_devkitm.overlay b/samples/drivers/i2s/output/boards/esp32h2_devkitm.overlay new file mode 100644 index 0000000000000..754938f50a612 --- /dev/null +++ b/samples/drivers/i2s/output/boards/esp32h2_devkitm.overlay @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + i2s-tx = &i2s; + }; +}; + +&i2s_default { + group1 { + pinmux = , + , + ; + }; +}; + +&i2s { + status = "okay"; + + dmas = <&dma 3>; + dma-names = "tx"; +}; + +&dma { + status = "okay"; +}; diff --git a/samples/drivers/led/led_strip/boards/esp32c6_devkitc_hpcore.overlay b/samples/drivers/led/led_strip/boards/esp32c6_devkitc_hpcore.overlay index 7ab7ebb2ff50f..6b223b68b6c7c 100644 --- a/samples/drivers/led/led_strip/boards/esp32c6_devkitc_hpcore.overlay +++ b/samples/drivers/led/led_strip/boards/esp32c6_devkitc_hpcore.overlay @@ -12,24 +12,14 @@ }; }; -&pinctrl { - i2s_default: i2s_default { - group1 { - pinmux = , - , - , - ; - }; - group2 { - pinmux = ; - }; +&i2s_default { + group1 { + pinmux = ; }; }; i2s_led: &i2s { status = "okay"; - pinctrl-0 = <&i2s_default>; - pinctrl-names = "default"; dmas = <&dma 3>; dma-names = "tx"; diff --git a/samples/drivers/led/led_strip/boards/esp32h2_devkitm.overlay b/samples/drivers/led/led_strip/boards/esp32h2_devkitm.overlay new file mode 100644 index 0000000000000..6b223b68b6c7c --- /dev/null +++ b/samples/drivers/led/led_strip/boards/esp32h2_devkitm.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + led-strip = &led_strip; + }; +}; + +&i2s_default { + group1 { + pinmux = ; + }; +}; + +i2s_led: &i2s { + status = "okay"; + + dmas = <&dma 3>; + dma-names = "tx"; + + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-i2s"; + + reg = <0>; + chain-length = <1>; + color-mapping = ; + reset-delay = <500>; + }; +}; + +&dma { + status = "okay"; +}; diff --git a/samples/drivers/led/led_strip/boards/esp32s2_devkitc.conf b/samples/drivers/led/led_strip/boards/esp32s2_devkitc.conf deleted file mode 100644 index c1378264b967c..0000000000000 --- a/samples/drivers/led/led_strip/boards/esp32s2_devkitc.conf +++ /dev/null @@ -1 +0,0 @@ -CONFIG_I2S=y diff --git a/samples/drivers/led/led_strip/boards/esp32s2_devkitc.overlay b/samples/drivers/led/led_strip/boards/esp32s2_devkitc.overlay index 9b7c301a3fcd8..0d2a7699c7a76 100644 --- a/samples/drivers/led/led_strip/boards/esp32s2_devkitc.overlay +++ b/samples/drivers/led/led_strip/boards/esp32s2_devkitc.overlay @@ -15,7 +15,6 @@ &i2s0_default { group1 { pinmux = ; - output-enable; }; }; diff --git a/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.conf b/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.conf deleted file mode 100644 index c1378264b967c..0000000000000 --- a/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.conf +++ /dev/null @@ -1 +0,0 @@ -CONFIG_I2S=y diff --git a/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.overlay b/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.overlay index ab31f1d9b7739..518db69ff8794 100644 --- a/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.overlay +++ b/samples/drivers/led/led_strip/boards/esp32s3_devkitc_procpu.overlay @@ -15,7 +15,6 @@ &i2s0_default { group1 { pinmux = ; - output-enable; }; }; diff --git a/tests/boards/espressif/i2s/CMakeLists.txt b/tests/boards/espressif/i2s/CMakeLists.txt new file mode 100644 index 0000000000000..1122589916de8 --- /dev/null +++ b/tests/boards/espressif/i2s/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(i2s_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/boards/espressif/i2s/Kconfig b/tests/boards/espressif/i2s/Kconfig new file mode 100644 index 0000000000000..0aac5b532c3c6 --- /dev/null +++ b/tests/boards/espressif/i2s/Kconfig @@ -0,0 +1,25 @@ +# +# Copyright (c) 2025 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +# + +mainmenu "ESP32 I2S Loopback Test" + +source "Kconfig.zephyr" + +config I2S_TEST_SEPARATE_DEVICES + bool "Use two separate I2S ports for loopback" + help + Use separate I2S ports for transmit and receive. + +config I2S_TEST_USE_GPIO_LOOPBACK + bool "Use GPIO loopback" + help + Use wiring between the data-out and data-in pins for looping back data. + +config I2S_TEST_ALLOWED_DATA_DISCARD + int "Allowed discard in received data" + default 0 + help + Maximum allowed discard between sent and received samples diff --git a/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.conf b/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.conf new file mode 100644 index 0000000000000..fd7b3f71d5059 --- /dev/null +++ b/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD=1 +CONFIG_I2S_ESP32_ALLOWED_EMPTY_TX_QUEUE_DEFERRAL_TIME_MS=1 diff --git a/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.overlay b/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.overlay new file mode 100644 index 0000000000000..58d97874eaba7 --- /dev/null +++ b/tests/boards/espressif/i2s/boards/esp32_devkitc_procpu.overlay @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s0; + }; +}; + +&pinctrl { + i2s0_default: i2s0_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; + }; +}; + +&i2s0 { + status = "okay"; + pinctrl-0 = <&i2s0_default>; + pinctrl-names = "default"; + + interrupts = , + ; + interrupt-names = "tx", "rx"; +}; diff --git a/tests/boards/espressif/i2s/boards/esp32s2_saola.conf b/tests/boards/espressif/i2s/boards/esp32s2_saola.conf new file mode 100644 index 0000000000000..fd7b3f71d5059 --- /dev/null +++ b/tests/boards/espressif/i2s/boards/esp32s2_saola.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD=1 +CONFIG_I2S_ESP32_ALLOWED_EMPTY_TX_QUEUE_DEFERRAL_TIME_MS=1 diff --git a/tests/boards/espressif/i2s/boards/esp32s2_saola.overlay b/tests/boards/espressif/i2s/boards/esp32s2_saola.overlay new file mode 100644 index 0000000000000..cf322a7406b08 --- /dev/null +++ b/tests/boards/espressif/i2s/boards/esp32s2_saola.overlay @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s0; + }; +}; + +&i2s0_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; +}; + +&i2s0 { + status = "okay"; + + interrupts = , + ; + interrupt-names = "rx", "tx"; +}; diff --git a/tests/boards/espressif/i2s/prj.conf b/tests/boards/espressif/i2s/prj.conf new file mode 100644 index 0000000000000..7c088b1ffe467 --- /dev/null +++ b/tests/boards/espressif/i2s/prj.conf @@ -0,0 +1,3 @@ +CONFIG_I2S=y +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y diff --git a/tests/boards/espressif/i2s/src/main.c b/tests/boards/espressif/i2s/src/main.c new file mode 100644 index 0000000000000..2f6e2744970f3 --- /dev/null +++ b/tests/boards/espressif/i2s/src/main.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define SAMPLE_NO 32 +#define TIMEOUT 2000 +#define FRAME_CLK_FREQ 8000 + +#define NUM_RX_BLOCKS 4 +#define NUM_TX_BLOCKS 4 + +#define VAL_L 11 +#define VAL_R 22 + +#define TRANSFER_REPEAT_COUNT 100 + +#define I2S_DEV_NODE_RX DT_ALIAS(i2s_node0) +#ifdef CONFIG_I2S_TEST_SEPARATE_DEVICES +#define I2S_DEV_NODE_TX DT_ALIAS(i2s_node1) +#else +#define I2S_DEV_NODE_TX DT_ALIAS(i2s_node0) +#endif + +ZTEST_DMEM const struct device *dev_i2s_rx = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_RX); +ZTEST_DMEM const struct device *dev_i2s_tx = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_TX); +ZTEST_DMEM const struct device *dev_i2s = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_RX); + +#define BLOCK_SIZE (2 * SAMPLE_NO * sizeof(int16_t)) + +K_MEM_SLAB_DEFINE(rx_mem_slab, BLOCK_SIZE, NUM_RX_BLOCKS, 32); +K_MEM_SLAB_DEFINE(tx_mem_slab, BLOCK_SIZE, NUM_TX_BLOCKS, 32); + +void fill_buf(int16_t *tx_block, int16_t val_l, int16_t val_r) +{ + for (int16_t i = 0; i < SAMPLE_NO; i++) { + tx_block[2 * i] = val_l + i; + tx_block[2 * i + 1] = val_r + i; + } +} + +int verify_buf(int16_t *rx_block, int16_t val_l, int16_t val_r) +{ + int sample_no = SAMPLE_NO; + +#if (CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD > 0) + static ZTEST_DMEM int offset = -1; + + if (offset < 0) { + do { + ++offset; + if (offset > CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD) { + TC_PRINT("Allowed data discard exceeded\n"); + return -TC_FAIL; + } + } while ((rx_block[0] != val_l + offset) || (rx_block[1] != val_r + offset)); + } + + val_l += offset; + val_r += offset; + sample_no -= offset; +#endif + + for (int16_t i = 0; i < sample_no; i++) { + if (rx_block[2 * i] != (val_l + i)) { + TC_PRINT("Error: data_l mismatch at position " + "%d, expected %d, actual %d\n", + i, (int)(val_l + i), (int)rx_block[2 * i]); + return -TC_FAIL; + } + if (rx_block[2 * i + 1] != (val_r + i)) { + TC_PRINT("Error: data_r mismatch at position " + "%d, expected %d, actual %d\n", + i, (int)(val_r + i), (int)rx_block[2 * i + 1]); + return -TC_FAIL; + } + } + + return TC_PASS; +} + +static int tx_block_write_slab(const struct device *i2s_dev, int16_t val_l, int16_t val_r, int err, + struct k_mem_slab *slab) +{ + char tx_block[BLOCK_SIZE]; + int ret; + + fill_buf((uint16_t *)tx_block, val_l, val_r); + ret = i2s_buf_write(i2s_dev, tx_block, BLOCK_SIZE); + if (ret != err) { + TC_PRINT("Error: i2s_write failed expected %d, actual %d\n", err, ret); + return -TC_FAIL; + } + + return TC_PASS; +} + +int tx_block_write(const struct device *i2s_dev, int16_t val_l, int16_t val_r, int err) +{ + return tx_block_write_slab(i2s_dev, val_l, val_r, err, &tx_mem_slab); +} + +static int rx_block_read_slab(const struct device *i2s_dev, int16_t val_l, int16_t val_r, + struct k_mem_slab *slab) +{ + char rx_block[BLOCK_SIZE]; + size_t rx_size; + int ret; + + ret = i2s_buf_read(i2s_dev, rx_block, &rx_size); + if (ret < 0 || rx_size != BLOCK_SIZE) { + TC_PRINT("Error: Read failed\n"); + return -TC_FAIL; + } + ret = verify_buf((uint16_t *)rx_block, val_l, val_r); + if (ret < 0) { + TC_PRINT("Error: Verify failed\n"); + return -TC_FAIL; + } + + return TC_PASS; +} + +int rx_block_read(const struct device *i2s_dev, int16_t val_l, int16_t val_r) +{ + return rx_block_read_slab(i2s_dev, val_l, val_r, &rx_mem_slab); +} + +int configure_stream(const struct device *i2s_dev, enum i2s_dir dir) +{ + int ret; + struct i2s_config i2s_cfg = {0}; + + i2s_cfg.word_size = 16U; + i2s_cfg.channels = 2U; + i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S; + i2s_cfg.frame_clk_freq = FRAME_CLK_FREQ; + i2s_cfg.block_size = BLOCK_SIZE; + i2s_cfg.timeout = TIMEOUT; + + if (dir == I2S_DIR_TX) { + /* Configure the Transmit port as Master */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER; + } else if (dir == I2S_DIR_RX) { + /* Configure the Receive port as Slave */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_SLAVE | I2S_OPT_BIT_CLK_SLAVE; + } else { /* dir == I2S_DIR_BOTH */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER; + } + + if (!IS_ENABLED(CONFIG_I2S_TEST_USE_GPIO_LOOPBACK)) { + i2s_cfg.options |= I2S_OPT_LOOPBACK; + } + + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { + i2s_cfg.mem_slab = &tx_mem_slab; + ret = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg); + if (ret < 0) { + TC_PRINT("Failed to configure I2S TX stream (%d)\n", ret); + return -TC_FAIL; + } + } + + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { + i2s_cfg.mem_slab = &rx_mem_slab; + ret = i2s_configure(i2s_dev, I2S_DIR_RX, &i2s_cfg); + if (ret < 0) { + TC_PRINT("Failed to configure I2S RX stream (%d)\n", ret); + return -TC_FAIL; + } + } + + return TC_PASS; +} + +static void *setup(void) +{ + k_thread_access_grant(k_current_get(), &rx_mem_slab, &tx_mem_slab); + k_object_access_grant(dev_i2s_rx, k_current_get()); + k_object_access_grant(dev_i2s_tx, k_current_get()); + + return NULL; +} + +static void before(void *fixture) +{ + ARG_UNUSED(fixture); + + int ret; + + zassert_not_null(dev_i2s_rx, "RX device not found"); + zassert_true(device_is_ready(dev_i2s_rx), "device %s is not ready", dev_i2s_rx->name); + + zassert_not_null(dev_i2s_tx, "TX device not found"); + zassert_true(device_is_ready(dev_i2s_tx), "device %s is not ready", dev_i2s_tx->name); + + ret = configure_stream(dev_i2s_rx, I2S_DIR_RX); + zassert_equal(ret, TC_PASS); + + ret = configure_stream(dev_i2s_tx, I2S_DIR_TX); + zassert_equal(ret, TC_PASS); +} + +/** @brief I2S transfer. + * + * - START trigger starts both the transmission and reception. + * - sending / receiving a sequence of data returns success. + * - DRAIN trigger empties the transmit queue and stops both streams. + */ +ZTEST_USER(i2s_loopback, test_i2s_transfer) +{ + int ret; + + /* Prefill TX queue */ + ret = tx_block_write(dev_i2s, VAL_L, VAL_R, 0); + zassert_equal(ret, TC_PASS); + + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_START); + zassert_equal(ret, 0, "RX/TX START trigger failed\n"); + + for (int i = 0; i < TRANSFER_REPEAT_COUNT; i++) { + ret = tx_block_write(dev_i2s_tx, VAL_L, VAL_R, 0); + zassert_equal(ret, TC_PASS); + + ret = rx_block_read(dev_i2s_rx, VAL_L, VAL_R); + zassert_equal(ret, TC_PASS); + } + + /* All data written, all but one data block read, flush TX queue + * and stop both streams. + */ + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_DRAIN); + zassert_equal(ret, 0, "RX/TX DRAIN trigger failed"); +} + +ZTEST_SUITE(i2s_loopback, NULL, setup, before, NULL, NULL); diff --git a/tests/boards/espressif/i2s/testcase.yaml b/tests/boards/espressif/i2s/testcase.yaml new file mode 100644 index 0000000000000..4a3e979191e35 --- /dev/null +++ b/tests/boards/espressif/i2s/testcase.yaml @@ -0,0 +1,12 @@ +tests: + boards.esp32.i2s.gpio_loopback: + depends_on: + - i2s + - gpio + tags: + - drivers + - userspace + filter: CONFIG_I2S_TEST_USE_GPIO_LOOPBACK + harness: ztest + harness_config: + fixture: gpio_loopback diff --git a/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.conf b/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.conf new file mode 100644 index 0000000000000..f5a9ecb46a683 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_I2S_DIR_BOTH=y +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_OFFSET=1 diff --git a/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.overlay b/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.overlay new file mode 100644 index 0000000000000..69b3174c14fa8 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32c3_devkitm.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s; + }; +}; + +&i2s_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; +}; + +&i2s { + status = "okay"; + + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; +}; + +&dma { + status = "okay"; +}; diff --git a/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.conf b/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.conf new file mode 100644 index 0000000000000..f5a9ecb46a683 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_I2S_DIR_BOTH=y +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_OFFSET=1 diff --git a/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.overlay b/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.overlay new file mode 100644 index 0000000000000..69b3174c14fa8 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32c6_devkitc_hpcore.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s; + }; +}; + +&i2s_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; +}; + +&i2s { + status = "okay"; + + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; +}; + +&dma { + status = "okay"; +}; diff --git a/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.conf b/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.conf new file mode 100644 index 0000000000000..f5a9ecb46a683 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_I2S_DIR_BOTH=y +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_OFFSET=1 diff --git a/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.overlay b/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.overlay new file mode 100644 index 0000000000000..49f7c8e862c69 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32h2_devkitm.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s; + }; +}; + +&i2s_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; +}; + +&i2s { + status = "okay"; + + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; +}; + +&dma { + status = "okay"; +}; diff --git a/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.conf b/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.conf new file mode 100644 index 0000000000000..f5a9ecb46a683 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_I2S_TEST_USE_I2S_DIR_BOTH=y +CONFIG_I2S_TEST_USE_GPIO_LOOPBACK=y +CONFIG_I2S_TEST_ALLOWED_DATA_OFFSET=1 diff --git a/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.overlay b/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.overlay new file mode 100644 index 0000000000000..29264f14b6c21 --- /dev/null +++ b/tests/drivers/i2s/i2s_api/boards/esp32s3_devkitm_procpu.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* i2s-node0 is the transmitter/receiver */ + +/ { + aliases { + i2s-node0 = &i2s0; + }; +}; + +&i2s0_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + input-enable; + }; + + group3 { + pinmux = ; + output-enable; + }; +}; + +&i2s0 { + status = "okay"; + + dmas = <&dma 2>, <&dma 3>; + dma-names = "rx", "tx"; +}; + +&dma { + status = "okay"; +};