This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
commit 3b5ab27893a4073b29d01627a82ed1ef8c7fdd99 Author: Tiago Medicci Serrano <tiago.medi...@espressif.com> AuthorDate: Thu Nov 10 16:40:42 2022 -0300 esp32/i2s: implement I2S receiver module - Add ioctl method to enable allocating the apb buffer. - Add RX methods to set data width, sample rate, channels and for receiving data from the I2S peripheral. - Update the i2schar defconfig to enable the I2S receiver. - Add nxlooper defconfig to enable testing the RX interface. - Add specific bindings on ESP32 bringup to enable nxlooper to work without the need of any specific codec. --- arch/xtensa/src/esp32/Kconfig | 17 +- arch/xtensa/src/esp32/esp32_i2s.c | 1603 +++++++++++++++++--- arch/xtensa/src/esp32/esp32_i2s.h | 9 +- .../xtensa/esp32/common/src/esp32_board_i2sdev.c | 89 +- .../esp32/esp32-devkitc/configs/i2schar/defconfig | 8 +- .../esp32/esp32-devkitc/configs/nxlooper/defconfig | 129 ++ .../xtensa/esp32/esp32-devkitc/src/esp32-devkitc.h | 6 +- .../xtensa/esp32/esp32-devkitc/src/esp32_bringup.c | 37 +- 8 files changed, 1603 insertions(+), 295 deletions(-) diff --git a/arch/xtensa/src/esp32/Kconfig b/arch/xtensa/src/esp32/Kconfig index 913f1b8318..8c3ac980d3 100644 --- a/arch/xtensa/src/esp32/Kconfig +++ b/arch/xtensa/src/esp32/Kconfig @@ -369,9 +369,9 @@ config ESP32_I2S0_WSPIN range 0 39 if ESP32_I2S0_ROLE_SLAVE config ESP32_I2S0_DINPIN - int "I2S0 DOUT pin" + int "I2S0 DIN pin" depends on ESP32_I2S0_RX - default 12 + default 19 range 0 39 config ESP32_I2S0_DOUTPIN @@ -471,7 +471,6 @@ endchoice config ESP32_I2S1_DATA_BIT_WIDTH int - default 16 default 8 if ESP32_I2S1_DATA_BIT_WIDTH_8BIT default 16 if ESP32_I2S1_DATA_BIT_WIDTH_16BIT default 24 if ESP32_I2S1_DATA_BIT_WIDTH_24BIT @@ -488,26 +487,26 @@ config ESP32_I2S1_SAMPLE_RATE config ESP32_I2S1_BCLKPIN int "I2S1 BCLK pin" - default 19 + default 22 range 0 33 if ESP32_I2S1_ROLE_MASTER range 0 39 if ESP32_I2S1_ROLE_SLAVE config ESP32_I2S1_WSPIN int "I2S1 WS pin" - default 18 + default 23 range 0 33 if ESP32_I2S1_ROLE_MASTER range 0 39 if ESP32_I2S1_ROLE_SLAVE -config ESP32_I2S1_DOUTPIN - int "I2S1 DOUT pin" +config ESP32_I2S1_DINPIN + int "I2S1 DIN pin" depends on ESP32_I2S1_RX - default 17 + default 26 range 0 39 config ESP32_I2S1_DOUTPIN int "I2S1 DOUT pin" depends on ESP32_I2S1_TX - default 16 + default 25 range 0 33 config ESP32_I2S1_MCLK diff --git a/arch/xtensa/src/esp32/esp32_i2s.c b/arch/xtensa/src/esp32/esp32_i2s.c index 96bc7b8503..064f9b31dd 100644 --- a/arch/xtensa/src/esp32/esp32_i2s.c +++ b/arch/xtensa/src/esp32/esp32_i2s.c @@ -90,6 +90,7 @@ #ifdef CONFIG_ESP32_I2S0_RX # define I2S0_RX_ENABLED 1 +# define I2S_HAVE_RX 1 #else # define I2S0_RX_ENABLED 0 #endif @@ -103,6 +104,7 @@ #ifdef CONFIG_ESP32_I2S1_RX # define I2S1_RX_ENABLED 1 +# define I2S_HAVE_RX 1 #else # define I2S1_RX_ENABLED 0 #endif @@ -172,7 +174,6 @@ struct esp32_i2s_config_s uint32_t total_slot; /* Total slot number */ bool is_apll; /* Select APLL as the source clock */ - uint32_t mclk_multiple; /* The multiple of mclk to the sample rate */ bool tx_en; /* Is TX enabled? */ bool rx_en; /* Is RX enabled? */ @@ -219,9 +220,9 @@ struct esp32_buffer_s { struct esp32_buffer_s *flink; /* Supports a singly linked list */ - /* The associated DMA outlink */ + /* The associated DMA in/outlink */ - struct esp32_dmadesc_s dma_outlink[I2S_DMADESC_NUM]; + struct esp32_dmadesc_s dma_link[I2S_DMADESC_NUM]; i2s_callback_t callback; /* DMA completion callback */ uint32_t timeout; /* Timeout value of the DMA transfers */ @@ -232,7 +233,7 @@ struct esp32_buffer_s int result; /* The result of the transfer */ }; -/* Internal buffer must be aligned to the sample_size. Sometimes, +/* Internal buffer must be aligned to the bytes_per_sample. Sometimes, * however, the audio buffer is not aligned and additional bytes must * be copied to be inserted on the next buffer. This structure keeps * track of the bytes that were not written to the internal buffer yet. @@ -274,10 +275,11 @@ struct esp32_i2s_s const struct esp32_i2s_config_s *config; - uint32_t mclk_freq; /* I2S actual master clock */ - uint32_t channels; /* Audio channels (1:mono or 2:stereo) */ - uint32_t rate; /* I2S actual configured sample-rate */ - uint32_t data_width; /* I2S actual configured data_width */ + uint32_t mclk_freq; /* I2S actual master clock */ + uint32_t mclk_multiple; /* The multiple of mclk to the sample rate */ + uint32_t channels; /* Audio channels (1:mono or 2:stereo) */ + uint32_t rate; /* I2S actual configured sample-rate */ + uint32_t data_width; /* I2S actual configured data_width */ #ifdef I2S_HAVE_TX struct esp32_transport_s tx; /* TX transport state */ @@ -285,6 +287,12 @@ struct esp32_i2s_s bool tx_started; /* TX channel started */ #endif /* I2S_HAVE_TX */ +#ifdef I2S_HAVE_RX + struct esp32_transport_s rx; /* RX transport state */ + + bool rx_started; /* RX channel started */ +#endif /* I2S_HAVE_RX */ + /* Pre-allocated pool of buffer containers */ sem_t bufsem; /* Buffer wait semaphore */ @@ -322,16 +330,28 @@ static void i2s_tx_schedule(struct esp32_i2s_s *priv, struct esp32_dmadesc_s *outlink); #endif /* I2S_HAVE_TX */ +#ifdef I2S_HAVE_RX +static IRAM_ATTR int i2s_rxdma_setup(struct esp32_i2s_s *priv, + struct esp32_buffer_s *bfcontainer); +static void i2s_rx_worker(void *arg); +static void i2s_rx_schedule(struct esp32_i2s_s *priv, + struct esp32_dmadesc_s *outlink); +#endif /* I2S_HAVE_RX */ + /* I2S methods (and close friends) */ static uint32_t i2s_set_datawidth(struct esp32_i2s_s *priv); static uint32_t i2s_set_clock(struct esp32_i2s_s *priv); +static uint32_t esp32_i2s_mclkfrequency(struct i2s_dev_s *dev, + uint32_t frequency); +static int esp32_i2s_ioctl(struct i2s_dev_s *dev, int cmd, + unsigned long arg); + +#ifdef I2S_HAVE_TX static void i2s_tx_channel_start(struct esp32_i2s_s *priv); static void i2s_tx_channel_stop(struct esp32_i2s_s *priv); static int esp32_i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels); -static uint32_t esp32_i2s_mclkfrequency(struct i2s_dev_s *dev, - uint32_t frequency); static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate); static uint32_t esp32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits); @@ -339,6 +359,21 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2s_callback_t callback, void *arg, uint32_t timeout); +#endif /* I2S_HAVE_TX */ + +#ifdef I2S_HAVE_RX +static void i2s_rx_channel_start(struct esp32_i2s_s *priv); +static void i2s_rx_channel_stop(struct esp32_i2s_s *priv); +static int esp32_i2s_rxchannels(struct i2s_dev_s *dev, + uint8_t channels); +static uint32_t esp32_i2s_rxsamplerate(struct i2s_dev_s *dev, + uint32_t rate); +static uint32_t esp32_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits); +static int esp32_i2s_receive(struct i2s_dev_s *dev, + struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, + uint32_t timeout); +#endif /* I2S_HAVE_RX */ /**************************************************************************** * Private Data @@ -346,10 +381,21 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, static const struct i2s_ops_s g_i2sops = { +#ifdef I2S_HAVE_TX .i2s_txchannels = esp32_i2s_txchannels, .i2s_txsamplerate = esp32_i2s_txsamplerate, .i2s_txdatawidth = esp32_i2s_txdatawidth, .i2s_send = esp32_i2s_send, +#endif /* I2S_HAVE_TX */ + +#ifdef I2S_HAVE_RX + .i2s_rxchannels = esp32_i2s_rxchannels, + .i2s_rxsamplerate = esp32_i2s_rxsamplerate, + .i2s_rxdatawidth = esp32_i2s_rxdatawidth, + .i2s_receive = esp32_i2s_receive, +#endif /* I2S_HAVE_RX */ + + .i2s_ioctl = esp32_i2s_ioctl, .i2s_mclkfrequency = esp32_i2s_mclkfrequency, }; @@ -365,7 +411,6 @@ static const struct esp32_i2s_config_s esp32_i2s0_config = .data_width = CONFIG_ESP32_I2S0_DATA_BIT_WIDTH, .rate = CONFIG_ESP32_I2S0_SAMPLE_RATE, .total_slot = 2, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, .tx_en = I2S0_TX_ENABLED, .rx_en = I2S0_RX_ENABLED, #ifdef CONFIG_ESP32_I2S0_MCLK @@ -426,7 +471,6 @@ static const struct esp32_i2s_config_s esp32_i2s1_config = .data_width = CONFIG_ESP32_I2S1_DATA_BIT_WIDTH, .rate = CONFIG_ESP32_I2S1_SAMPLE_RATE, .total_slot = 2, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, .tx_en = I2S1_TX_ENABLED, .rx_en = I2S1_RX_ENABLED, #ifdef CONFIG_ESP32_I2S1_MCLK @@ -487,7 +531,7 @@ static struct esp32_i2s_s esp32_i2s1_priv = * free list * * Input Parameters: - * priv - I2S state instance + * priv - Initialized I2S device structure. * * Returned Value: * A non-NULL pointer to the allocate buffer container on success; NULL if @@ -535,7 +579,7 @@ static struct esp32_buffer_s *i2s_buf_allocate(struct esp32_i2s_s *priv) * Free buffer container by adding it to the head of the free list * * Input Parameters: - * priv - I2S state instance + * priv - Initialized I2S device structure. * bfcontainer - The buffer container to be freed * * Returned Value: @@ -576,7 +620,7 @@ static void i2s_buf_free(struct esp32_i2s_s *priv, * pre-allocated buffer containers to the free list * * Input Parameters: - * priv - I2S state instance + * priv - Initialized I2S device structure. * * Returned Value: * OK on success; A negated errno value on failure. @@ -589,8 +633,10 @@ static void i2s_buf_free(struct esp32_i2s_s *priv, static int i2s_buf_initialize(struct esp32_i2s_s *priv) { +#ifdef I2S_HAVE_TX priv->tx.carry.bytes = 0; priv->tx.carry.value = 0; +#endif /* I2S_HAVE_TX */ priv->bf_freelist = NULL; for (int i = 0; i < CONFIG_ESP32_I2S_MAXINFLIGHT; i++) @@ -606,10 +652,10 @@ static int i2s_buf_initialize(struct esp32_i2s_s *priv) * * Description: * Initiate the next TX DMA transfer. The DMA outlink was previously bound - * so it is safe to start the next DMA transfer at interruption level. + * so it is safe to start the next DMA transfer at interrupt level. * * Input Parameters: - * priv - I2S state instance + * priv - Initialized I2S device structure. * * Returned Value: * OK on success; a negated errno value on failure @@ -648,7 +694,7 @@ static int i2s_txdma_start(struct esp32_i2s_s *priv) modifyreg32(I2S_OUT_LINK_REG(priv->config->port), I2S_OUTLINK_ADDR_M, FIELD_TO_VALUE(I2S_OUTLINK_ADDR, - (uintptr_t) bfcontainer->dma_outlink)); + (uintptr_t) bfcontainer->dma_link)); modifyreg32(I2S_OUT_LINK_REG(priv->config->port), I2S_OUTLINK_STOP, I2S_OUTLINK_START); @@ -661,6 +707,70 @@ static int i2s_txdma_start(struct esp32_i2s_s *priv) } #endif /* I2S_HAVE_TX */ +/**************************************************************************** + * Name: i2s_rxdma_start + * + * Description: + * Initiate the next RX DMA transfer. Assuming the DMA inlink is already + * bound, it's safe to start the next DMA transfer in an interrupt context. + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * OK on success; a negated errno value on failure + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static int i2s_rxdma_start(struct esp32_i2s_s *priv) +{ + struct esp32_buffer_s *bfcontainer; + + /* If there is already an active transmission in progress, then bail + * returning success. + */ + + if (!sq_empty(&priv->rx.act)) + { + return OK; + } + + /* If there are no pending transfer, then bail returning success */ + + if (sq_empty(&priv->rx.pend)) + { + return OK; + } + + bfcontainer = (struct esp32_buffer_s *)sq_remfirst(&priv->rx.pend); + + /* If there isn't already an active transmission in progress, + * then start it. + */ + + modifyreg32(I2S_RXEOF_NUM_REG(priv->config->port), I2S_RX_EOF_NUM_M, + FIELD_TO_VALUE(I2S_RX_EOF_NUM, + (bfcontainer->nbytes / 4))); + + modifyreg32(I2S_IN_LINK_REG(priv->config->port), I2S_INLINK_ADDR_M, + FIELD_TO_VALUE(I2S_INLINK_ADDR, + (uintptr_t) bfcontainer->dma_link)); + + modifyreg32(I2S_IN_LINK_REG(priv->config->port), I2S_INLINK_STOP, + I2S_INLINK_START); + + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_START); + + sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act); + + return OK; +} +#endif /* I2S_HAVE_RX */ + /**************************************************************************** * Name: i2s_txdma_setup * @@ -668,7 +778,8 @@ static int i2s_txdma_start(struct esp32_i2s_s *priv) * Setup the next TX DMA transfer * * Input Parameters: - * priv - I2S state instance + * priv - Initialized I2S device structure. + * bfcontainer - The buffer container to be set up * * Returned Value: * OK on success; a negated errno value on failure @@ -682,34 +793,34 @@ static int i2s_txdma_start(struct esp32_i2s_s *priv) static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, struct esp32_buffer_s *bfcontainer) { - struct ap_buffer_s *apb; - struct esp32_dmadesc_s *outlink; - uint8_t *samp; - apb_samp_t samp_size; + int ret = OK; size_t carry_size; uint32_t bytes_queued; uint32_t data_copied; + struct ap_buffer_s *apb; + struct esp32_dmadesc_s *outlink; + apb_samp_t samp_size; + irqstate_t flags; uint8_t *buf; uint8_t padding; - irqstate_t flags; - int ret = OK; + uint8_t *samp; DEBUGASSERT(bfcontainer && bfcontainer->apb); apb = bfcontainer->apb; - outlink = bfcontainer->dma_outlink; + outlink = bfcontainer->dma_link; /* Get the transfer information, accounting for any data offset */ - const apb_samp_t sample_size = priv->data_width / 8; + const apb_samp_t bytes_per_sample = priv->data_width / 8; samp = &apb->samp[apb->curbyte]; samp_size = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes; - carry_size = samp_size % sample_size; + carry_size = samp_size % bytes_per_sample; /* Padding contains the size (in bytes) to be skipped on the * internal buffer do provide 1) the ability to handle mono * audio files and 2) to correctly fill the buffer to 16 or 32-bits - * aligned positions + * aligned positions. */ padding = priv->channels == 1 ? @@ -721,7 +832,13 @@ static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, /* Copy audio data into internal buffer */ - bfcontainer->buf = (uint8_t *)calloc(bfcontainer->nbytes, 1); + bfcontainer->buf = calloc(bfcontainer->nbytes, 1); + if (bfcontainer->buf == NULL) + { + i2serr("Failed to allocate the DMA internal buffer " + "[%" PRIu32 " bytes]", bfcontainer->nbytes); + return -ENOMEM; + } data_copied = 0; buf = bfcontainer->buf + padding; @@ -731,25 +848,25 @@ static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, memcpy(buf, &priv->tx.carry.value, priv->tx.carry.bytes); buf += priv->tx.carry.bytes; data_copied += priv->tx.carry.bytes; - memcpy(buf, samp, (sample_size - priv->tx.carry.bytes)); - buf += (sample_size - priv->tx.carry.bytes + padding); - samp += (sample_size - priv->tx.carry.bytes); - data_copied += (sample_size - priv->tx.carry.bytes); + memcpy(buf, samp, (bytes_per_sample - priv->tx.carry.bytes)); + buf += (bytes_per_sample - priv->tx.carry.bytes + padding); + samp += (bytes_per_sample - priv->tx.carry.bytes); + data_copied += (bytes_per_sample - priv->tx.carry.bytes); } /* If there is no need to add padding bytes, the memcpy may be done at * once. Otherwise, the operation must add the padding bytes to each - * sample in the internal buffer + * sample in the internal buffer. */ if (padding) { while (data_copied < (samp_size - carry_size)) { - memcpy(buf, samp, sample_size); - buf += (sample_size + padding); - samp += sample_size; - data_copied += sample_size; + memcpy(buf, samp, bytes_per_sample); + buf += (bytes_per_sample + padding); + samp += bytes_per_sample; + data_copied += bytes_per_sample; } } else @@ -787,8 +904,9 @@ static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, if (bytes_queued != bfcontainer->nbytes) { - i2serr("Failed to enqueue I2S buffer (%d bytes of %d)\n", - bytes_queued, (uint32_t)bfcontainer->nbytes); + i2serr("Failed to enqueue I2S buffer " + "(%" PRIu32 " bytes of %" PRIu32 ")\n", + bytes_queued, bfcontainer->nbytes); return bytes_queued; } @@ -808,6 +926,77 @@ static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, } #endif /* I2S_HAVE_TX */ +/**************************************************************************** + * Name: i2s_rxdma_setup + * + * Description: + * Setup the next RX DMA transfer + * + * Input Parameters: + * priv - Initialized I2S device structure. + * bfcontainer - The buffer container to be set up + * + * Returned Value: + * OK on success; a negated errno value on failure + * + * Assumptions: + * Interrupts are disabled + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static int i2s_rxdma_setup(struct esp32_i2s_s *priv, + struct esp32_buffer_s *bfcontainer) +{ + int ret = OK; + struct esp32_dmadesc_s *inlink; + uint32_t bytes_queued; + irqstate_t flags; + + DEBUGASSERT(bfcontainer && bfcontainer->apb); + + inlink = bfcontainer->dma_link; + + /* Allocate the internal buffer for RX */ + + bfcontainer->buf = calloc(bfcontainer->nbytes, 1); + if (bfcontainer->buf == NULL) + { + i2serr("Failed to allocate the DMA internal buffer " + "[%" PRIu32 " bytes]", bfcontainer->nbytes); + return -ENOMEM; + } + + /* Configure DMA stream */ + + bytes_queued = esp32_dma_init(inlink, I2S_DMADESC_NUM, + bfcontainer->buf, + bfcontainer->nbytes); + + if (bytes_queued != bfcontainer->nbytes) + { + i2serr("Failed to enqueue I2S buffer " + "(%" PRIu32 " bytes of %" PRIu32 ")\n", + bytes_queued, bfcontainer->nbytes); + return bytes_queued; + } + + flags = spin_lock_irqsave(&priv->slock); + + /* Add the buffer container to the end of the RX pending queue */ + + sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend); + + /* Trigger DMA transfer if no transmission is in progress */ + + ret = i2s_rxdma_start(priv); + + spin_unlock_irqrestore(&priv->slock, flags); + + return ret; +} +#endif /* I2S_HAVE_RX */ + /**************************************************************************** * Name: i2s_tx_schedule * @@ -816,16 +1005,14 @@ static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, * the working thread. * * Input Parameters: - * handle - The DMA handler - * arg - A pointer to the chip select struction - * result - The result of the DMA transfer + * priv - Initialized I2S device structure. + * outlink - DMA outlink descriptor that triggered the interrupt. * * Returned Value: * None * * Assumptions: * - Interrupts are disabled - * - The TX timeout has been canceled. * ****************************************************************************/ @@ -858,7 +1045,7 @@ static void i2s_tx_schedule(struct esp32_i2s_s *priv, /* Find the last descriptor of the current buffer container */ - bfdesc = bfcontainer->dma_outlink; + bfdesc = bfcontainer->dma_link; while (!(bfdesc->ctrl & DMA_CTRL_EOF)) { DEBUGASSERT(bfdesc->next); @@ -909,6 +1096,105 @@ static void i2s_tx_schedule(struct esp32_i2s_s *priv, } #endif /* I2S_HAVE_TX */ +/**************************************************************************** + * Name: i2s_rx_schedule + * + * Description: + * An RX DMA completion has occurred. Schedule processing on + * the working thread. + * + * Input Parameters: + * priv - Initialized I2S device structure. + * inlink - DMA inlink descriptor that triggered the interrupt. + * + * Returned Value: + * None + * + * Assumptions: + * - Interrupts are disabled + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static void i2s_rx_schedule(struct esp32_i2s_s *priv, + struct esp32_dmadesc_s *inlink) +{ + struct esp32_buffer_s *bfcontainer; + struct esp32_dmadesc_s *bfdesc; + int ret; + + /* Upon entry, the transfer(s) that just completed are the ones in the + * priv->rx.act queue. + */ + + /* Move all entries from the rx.act queue to the rx.done queue */ + + if (!sq_empty(&priv->rx.act)) + { + /* Remove the next buffer container from the rx.act list */ + + bfcontainer = (struct esp32_buffer_s *)sq_peek(&priv->rx.act); + + /* Check if the DMA descriptor that generated an EOF interrupt is the + * last descriptor of the current buffer container's DMA inlink. + * REVISIT: what to do if we miss syncronization and the descriptor + * that generated the interrupt is different from the expected (the + * oldest of the list containing active transmissions)? + */ + + /* Find the last descriptor of the current buffer container */ + + bfdesc = bfcontainer->dma_link; + while (!(bfdesc->ctrl & DMA_CTRL_EOF)) + { + DEBUGASSERT(bfdesc->next); + bfdesc = bfdesc->next; + } + + if (bfdesc == inlink) + { + sq_remfirst(&priv->rx.act); + + /* Report the result of the transfer */ + + bfcontainer->result = OK; + + /* Add the completed buffer container to the tail of the rx.done + * queue + */ + + sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done); + + /* Check if the DMA is IDLE */ + + if (sq_empty(&priv->rx.act)) + { + /* Then start the next DMA. */ + + i2s_rxdma_start(priv); + } + } + + /* If the worker has completed running, then reschedule the working + * thread. + */ + + if (work_available(&priv->rx.work)) + { + /* Schedule the RX DMA done processing to occur on the worker + * thread. + */ + + ret = work_queue(HPWORK, &priv->rx.work, i2s_rx_worker, priv, 0); + if (ret != 0) + { + i2serr("ERROR: Failed to queue RX work: %d\n", ret); + } + } + } +} +#endif /* I2S_HAVE_RX */ + /**************************************************************************** * Name: i2s_tx_worker * @@ -976,62 +1262,219 @@ static void i2s_tx_worker(void *arg) #endif /* I2S_HAVE_TX */ /**************************************************************************** - * Name: i2s_configure + * Name: i2s_rx_worker * * Description: - * Configure I2S + * RX transfer done worker * * Input Parameters: - * priv - Partially initialized I2S device structure. This function - * will complete the I2S specific portions of the initialization + * arg - the I2S device instance cast to void* * * Returned Value: * None * ****************************************************************************/ -static void i2s_configure(struct esp32_i2s_s *priv) +#ifdef I2S_HAVE_RX +static void i2s_rx_worker(void *arg) { - /* Set peripheral clock and clear reset */ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)arg; + struct esp32_buffer_s *bfcontainer; + irqstate_t flags; - modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_I2S0_CLK_EN); - modifyreg32(DPORT_PERIP_RST_EN_REG, 0, DPORT_I2S0_RST); - modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_I2S0_RST, 0); + DEBUGASSERT(priv); - /* I2S module general init, enable I2S clock */ + /* When the transfer was started, the active buffer containers were removed + * from the rx.pend queue and saved in the rx.act queue. We get here when + * the DMA is finished. + * + * In any case, the buffer containers in rx.act will be moved to the end + * of the rx.done queue and rx.act will be emptied before this worker is + * started. + * + */ - if (!(getreg32(I2S_CLKM_CONF_REG(priv->config->port)) & I2S_CLK_ENA)) + i2sinfo("rx.act.head=%p rx.done.head=%p\n", + priv->rx.act.head, priv->rx.done.head); + + /* Process each buffer in the rx.done queue */ + + while (sq_peek(&priv->rx.done) != NULL) { - i2sinfo("Enabling I2S port clock...\n"); - modifyreg32(I2S_CLKM_CONF_REG(priv->config->port), 0, I2S_CLK_ENA); - putreg32(0, I2S_CONF2_REG(priv->config->port)); - } + /* Remove the buffer container from the rx.done queue. NOTE that + * interrupts must be disabled to do this because the rx.done queue is + * also modified from the interrupt level. + */ - /* Configure multiplexed pins as connected on the board */ + flags = spin_lock_irqsave(&priv->slock); + bfcontainer = (struct esp32_buffer_s *)sq_remfirst(&priv->rx.done); + spin_unlock_irqrestore(&priv->slock, flags); - /* TODO: check for loopback mode */ + apb_samp_t samp_size; + uint32_t data_copied; + uint8_t padding; + uint8_t *buf; + uint8_t *samp; - /* Enable TX channel */ + /* Padding contains the size (in bytes) to be skipped on the internal + * buffer - which store data aligned with 2 or 4 bytes - to correctly + * copy and fill the audio buffer according to the actual data width. + */ - if (priv->config->dout_pin != I2S_GPIO_UNUSED) - { - esp32_gpiowrite(priv->config->dout_pin, 1); - esp32_configgpio(priv->config->dout_pin, OUTPUT_FUNCTION_3); - esp32_gpio_matrix_out(priv->config->dout_pin, - priv->config->dout_outsig, 0, 0); - } + padding = priv->data_width != I2S_DATA_BIT_WIDTH_16BIT ? + (priv->data_width != I2S_DATA_BIT_WIDTH_32BIT ? 1 : 0) : 0; - /* TODO: repeat above function for RX channel */ + /* Get the transfer information, accounting for any data offset */ - if (priv->config->role == I2S_ROLE_SLAVE) - { - if (priv->config->tx_en && !priv->config->rx_en) - { - /* For "tx + slave" mode, select TX signal index for ws and bck */ + const apb_samp_t bytes_per_sample = priv->data_width / 8; + samp = &bfcontainer->apb->samp[bfcontainer->apb->curbyte]; + samp_size = (bfcontainer->apb->nbytes - bfcontainer->apb->curbyte); - esp32_gpiowrite(priv->config->ws_pin, 1); - esp32_configgpio(priv->config->ws_pin, INPUT_FUNCTION_3); - esp32_gpio_matrix_in(priv->config->ws_pin, + /* If there is no need to add padding bytes, the memcpy may be done at + * once. Otherwise, the operation must add the padding bytes to each + * sample in the internal buffer. + */ + + data_copied = 0; + buf = bfcontainer->buf + padding; + + if (padding) + { + while (data_copied < samp_size) + { + memcpy(samp, buf, bytes_per_sample); + buf += (bytes_per_sample + padding); + samp += bytes_per_sample; + data_copied += bytes_per_sample; + } + } + else + { + memcpy(samp, buf, samp_size - data_copied); + buf += samp_size - data_copied; + samp += samp_size - data_copied; + data_copied += samp_size - data_copied; + } + + /* Calculate the audio buffer size from the internal buffer size. + * The internal I2S buffer size is calculated from I2S_RXEOF_NUM_REG, + * which returns the size in number of words. + */ + + bfcontainer->apb->nbytes = + (getreg32(I2S_RXEOF_NUM_REG(priv->config->port)) * 4); + + /* The internal buffer size must be divided by either 16 or 32 bits + * as the I2S peripheral aligns each sample to one of these boundaries. + * The result represents the number of samples on the internal buffer. + */ + + bfcontainer->apb->nbytes /= + ((priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + I2S_DATA_BIT_WIDTH_16BIT : I2S_DATA_BIT_WIDTH_32BIT) / 8); + + /* Finally, calculate the number of bytes on the audio buffer */ + + bfcontainer->apb->nbytes *= bytes_per_sample; + + DEBUGASSERT(bfcontainer->apb->nbytes == data_copied); + + /* Perform the RX transfer done callback */ + + DEBUGASSERT(bfcontainer && bfcontainer->callback); + bfcontainer->callback(&priv->dev, bfcontainer->apb, + bfcontainer->arg, bfcontainer->result); + + /* Release the internal buffer used by the DMA inlink */ + + free(bfcontainer->buf); + + /* Release our reference on the audio buffer. This may very likely + * cause the audio buffer to be freed. + */ + + apb_free(bfcontainer->apb); + + /* And release the buffer container */ + + i2s_buf_free(priv, bfcontainer); + } +} +#endif /* I2S_HAVE_RX */ + +/**************************************************************************** + * Name: i2s_configure + * + * Description: + * Configure I2S + * + * Input Parameters: + * priv - Partially initialized I2S device structure. This function + * will complete the I2S specific portions of the initialization + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void i2s_configure(struct esp32_i2s_s *priv) +{ + /* Set peripheral clock and clear reset */ + + if (priv->config->port == ESP32_I2S0) + { + modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_I2S0_CLK_EN); + modifyreg32(DPORT_PERIP_RST_EN_REG, 0, DPORT_I2S0_RST); + modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_I2S0_RST, 0); + } + else + { + modifyreg32(DPORT_PERIP_CLK_EN_REG, 0, DPORT_I2S1_CLK_EN); + modifyreg32(DPORT_PERIP_RST_EN_REG, 0, DPORT_I2S1_RST); + modifyreg32(DPORT_PERIP_RST_EN_REG, DPORT_I2S1_RST, 0); + } + + /* I2S module general init, enable I2S clock */ + + if (!(getreg32(I2S_CLKM_CONF_REG(priv->config->port)) & I2S_CLK_ENA)) + { + i2sinfo("Enabling I2S port clock...\n"); + modifyreg32(I2S_CLKM_CONF_REG(priv->config->port), 0, I2S_CLK_ENA); + putreg32(0, I2S_CONF2_REG(priv->config->port)); + } + + /* Configure multiplexed pins as connected on the board */ + + /* TODO: check for loopback mode */ + + /* Enable TX channel */ + + if (priv->config->dout_pin != I2S_GPIO_UNUSED) + { + esp32_gpiowrite(priv->config->dout_pin, 1); + esp32_configgpio(priv->config->dout_pin, OUTPUT_FUNCTION_3); + esp32_gpio_matrix_out(priv->config->dout_pin, + priv->config->dout_outsig, 0, 0); + } + + /* Enable RX channel */ + + if (priv->config->din_pin != I2S_GPIO_UNUSED) + { + esp32_configgpio(priv->config->din_pin, INPUT_FUNCTION_3); + esp32_gpio_matrix_in(priv->config->din_pin, + priv->config->din_insig, 0); + } + + if (priv->config->role == I2S_ROLE_SLAVE) + { + if (priv->config->tx_en && !priv->config->rx_en) + { + /* For "tx + slave" mode, select TX signal index for ws and bck */ + + esp32_gpiowrite(priv->config->ws_pin, 1); + esp32_configgpio(priv->config->ws_pin, INPUT_FUNCTION_3); + esp32_gpio_matrix_in(priv->config->ws_pin, priv->config->ws_out_insig, 0); esp32_gpiowrite(priv->config->bclk_pin, 1); @@ -1122,9 +1565,18 @@ static void i2s_configure(struct esp32_i2s_s *priv) } } - /* TODO: share BCLK and WS if in full-duplex mode */ + /* Share BCLK and WS if in full-duplex mode */ + + if (priv->config->tx_en && priv->config->rx_en) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_SIG_LOOPBACK); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_SIG_LOOPBACK, 0); + } - /* Configure the hardware to apply STD format */ + /* Configure the TX module */ if (priv->config->tx_en) { @@ -1146,8 +1598,8 @@ static void i2s_configure(struct esp32_i2s_s *priv) modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_SLAVE_MOD, 0); } - /* Congfigure TX chan bit, audio data bit and mono mode. - * On ESP32, sample_bit should equals to data_bit + /* Configure TX chan bit, audio data bit and mono mode. + * On ESP32, sample_bit should equals to data_bit. */ /* Set TX data width */ @@ -1207,22 +1659,157 @@ static void i2s_configure(struct esp32_i2s_s *priv) modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_TX_FIFO_MOD_FORCE_EN); - esp32_i2s_mclkfrequency((struct i2s_dev_s *)priv, - (priv->config->rate * - priv->config->mclk_multiple)); + /* The default value for the master clock frequency (MCLK frequency) + * can be set from the sample rate multiplied by a fixed value, known + * as MCLK multiplier. This multiplier, however, should be divisible + * by the number of bytes from a sample, i.e, for 24 bits, the + * multiplier should be divisible by 3. NOTE: the MCLK frequency can + * be adjusted on runtime, so this value remains valid only if the + * upper half does not implement the `i2s_mclkfrequency` method. + */ + + if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT) + { + priv->mclk_multiple = I2S_MCLK_MULTIPLE_384; + } + else + { + priv->mclk_multiple = I2S_MCLK_MULTIPLE_256; + } + + esp32_i2s_mclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate * + priv->mclk_multiple)); priv->rate = priv->config->rate; i2s_set_clock(priv); } - /* TODO: check for rx enabled flag */ + /* Configure the RX module */ + + if (priv->config->rx_en) + { + /* Reset I2S RX module */ + + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_RESET); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_RESET, 0); + modifyreg32(I2S_LC_CONF_REG(priv->config->port), 0, I2S_IN_RST); + modifyreg32(I2S_LC_CONF_REG(priv->config->port), I2S_IN_RST, 0); + + /* Enable/disable I2S RX slave mode */ + + if (priv->config->role == I2S_ROLE_SLAVE) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_SLAVE_MOD); + } + else + { + /* Since BCLK and WS are shared, only TX or RX can be master. In + * this case, force RX as slave to avoid conflict of clock signal. + */ + + if (priv->config->tx_en) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, + I2S_RX_SLAVE_MOD); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), + I2S_RX_SLAVE_MOD, 0); + } + } + + /* Congfigure RX chan bit, audio data bit and mono mode. + * On ESP32, sample_bit should equals to data_bit. + */ + + /* Set RX data width */ + + priv->data_width = priv->config->data_width; + i2s_set_datawidth(priv); + + /* Set I2S RX chan mode */ + + modifyreg32(I2S_CONF_CHAN_REG(priv->config->port), + I2S_RX_CHAN_MOD_M, 0); + + /* Enable/disable RX MSB shift, the data will be read at the first + * BCK clock. + */ + + if (priv->config->bit_shift) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_MSB_SHIFT); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_MSB_SHIFT, 0); + } + + /* Configure RX WS signal width. Set to to enable receiver in PCM + * standard mode. + */ + + if (priv->config->ws_width == 1) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, + I2S_RX_SHORT_SYNC); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), + I2S_RX_SHORT_SYNC, 0); + } + + /* Set I2S RX right channel first */ + + if (priv->config->ws_pol == 1) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, + I2S_RX_RIGHT_FIRST); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), + I2S_RX_RIGHT_FIRST, 0); + } + + /* I2S RX fifo module force enable */ + + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, + I2S_RX_FIFO_MOD_FORCE_EN); + + /* The default value for the master clock frequency (MCLK frequency) + * can be set from the sample rate multiplied by a fixed value, known + * as MCLK multiplier. This multiplier, however, should be divisible + * by the number of bytes from a sample, i.e, for 24 bits, the + * multiplier should be divisible by 3. NOTE: the MCLK frequency can + * be adjusted on runtime, so this value remains valid only if the + * upper half does not implement the `i2s_mclkfrequency` method. + */ + + if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT) + { + priv->mclk_multiple = I2S_MCLK_MULTIPLE_384; + } + else + { + priv->mclk_multiple = I2S_MCLK_MULTIPLE_256; + } + + esp32_i2s_mclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate * + priv->mclk_multiple)); + + priv->rate = priv->config->rate; + i2s_set_clock(priv); + } } /**************************************************************************** * Name: i2s_set_datawidth * * Description: - * Set the I2S TX data width. + * Set the I2S TX/RX data width. * * Input Parameters: * priv - Initialized I2S device structure. @@ -1234,27 +1821,63 @@ static void i2s_configure(struct esp32_i2s_s *priv) static uint32_t i2s_set_datawidth(struct esp32_i2s_s *priv) { - modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), - I2S_TX_BITS_MOD_M, FIELD_TO_VALUE(I2S_TX_BITS_MOD, - priv->data_width)); +#ifdef I2S_HAVE_TX + if (priv->config->tx_en) + { + modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), + I2S_TX_BITS_MOD_M, FIELD_TO_VALUE(I2S_TX_BITS_MOD, + priv->data_width)); - /* Set TX FIFO operation mode */ + /* Set TX FIFO operation mode */ - modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_TX_FIFO_MOD_M, - priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? - FIELD_TO_VALUE(I2S_TX_FIFO_MOD, 0 + priv->config->mono_en) : - FIELD_TO_VALUE(I2S_TX_FIFO_MOD, 2 + priv->config->mono_en)); + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_TX_FIFO_MOD_M, + priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + FIELD_TO_VALUE(I2S_TX_FIFO_MOD, + 0 + priv->config->mono_en) : + FIELD_TO_VALUE(I2S_TX_FIFO_MOD, + 2 + priv->config->mono_en)); - /* I2S TX MSB right enable */ + /* I2S TX MSB right enable */ - if (priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT) - { - modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_MSB_RIGHT); + if (priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_MSB_RIGHT); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_MSB_RIGHT, 0); + } } - else +#endif /* I2S_HAVE_TX */ + +#ifdef I2S_HAVE_RX + if (priv->config->rx_en) { - modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_MSB_RIGHT, 0); + modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), + I2S_RX_BITS_MOD_M, FIELD_TO_VALUE(I2S_RX_BITS_MOD, + priv->data_width)); + + /* Set RX FIFO operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_RX_FIFO_MOD_M, + priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + FIELD_TO_VALUE(I2S_RX_FIFO_MOD, + 0 + priv->config->mono_en) : + FIELD_TO_VALUE(I2S_RX_FIFO_MOD, + 2 + priv->config->mono_en)); + + /* I2S RX MSB right enable */ + + if (priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT) + { + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_MSB_RIGHT); + } + else + { + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_MSB_RIGHT, 0); + } } +#endif /* I2S_HAVE_RX */ return priv->data_width; } @@ -1278,13 +1901,13 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) uint32_t rate; uint32_t bclk; uint32_t mclk; - uint16_t bclk_div; uint32_t sclk; uint32_t mclk_div; int denominator; int numerator; uint32_t regval; uint32_t freq_diff; + uint16_t bclk_div; /* TODO: provide APLL clock support */ @@ -1320,8 +1943,8 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) mclk_div = sclk / mclk; - i2sinfo("Clock division info: [sclk]%"PRIu32" Hz [mdiv] %d " - "[mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz\n", + i2sinfo("Clock division info: [sclk]%" PRIu32 " Hz [mdiv] %d " + "[mclk] %" PRIu32 " Hz [bdiv] %d [bclk] %" PRIu32 " Hz\n", sclk, mclk_div, mclk, bclk_div, bclk); freq_diff = abs((int)sclk - (int)(mclk * mclk_div)); @@ -1343,7 +1966,7 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) } else { - uint32_t min = ~0; + uint32_t min = UINT32_MAX; for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) { @@ -1367,7 +1990,7 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) } } - i2sinfo("Clock register: [mclk] %"PRIu32" Hz [numerator] %d " + i2sinfo("Clock register: [mclk] %" PRIu32 " Hz [numerator] %d " "[denominator] %d\n", mclk, numerator, denominator); regval = getreg32(I2S_CLKM_CONF_REG(priv->config->port)); @@ -1379,11 +2002,19 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_A, denominator); putreg32(regval, I2S_CLKM_CONF_REG(priv->config->port)); - /* Set I2S tx bck div num */ + /* Set I2S TX bck div num */ +#ifdef I2S_HAVE_TX modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), I2S_TX_BCK_DIV_NUM_M, FIELD_TO_VALUE(I2S_TX_BCK_DIV_NUM, bclk_div)); +#endif /* I2S_HAVE_TX */ + +#ifdef I2S_HAVE_RX + modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), + I2S_RX_BCK_DIV_NUM_M, + FIELD_TO_VALUE(I2S_RX_BCK_DIV_NUM, bclk_div)); +#endif /* I2S_HAVE_RX */ /* Returns the actual sample rate */ @@ -1411,54 +2042,57 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) #ifdef I2S_HAVE_TX static void i2s_tx_channel_start(struct esp32_i2s_s *priv) { - if (priv->tx_started) + if (priv->config->tx_en) { - i2swarn("TX channel of port %d was previously started\n", - priv->config->port); - return; - } + if (priv->tx_started) + { + i2swarn("TX channel of port %d was previously started\n", + priv->config->port); + return; + } - /* Reset the TX channel */ + /* Reset the TX channel */ - modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_RESET); - modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_RESET, 0); + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_RESET); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_RESET, 0); - /* Reset the DMA operation */ + /* Reset the DMA operation */ - modifyreg32(I2S_LC_CONF_REG(priv->config->port), 0, I2S_OUT_RST); - modifyreg32(I2S_LC_CONF_REG(priv->config->port), I2S_OUT_RST, 0); + modifyreg32(I2S_LC_CONF_REG(priv->config->port), 0, I2S_OUT_RST); + modifyreg32(I2S_LC_CONF_REG(priv->config->port), I2S_OUT_RST, 0); - /* Reset TX FIFO */ + /* Reset TX FIFO */ - modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_FIFO_RESET); - modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_FIFO_RESET, 0); + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_FIFO_RESET); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_FIFO_RESET, 0); - /* Enable DMA interrupt */ + /* Enable DMA interrupt */ - up_enable_irq(priv->config->irq); + up_enable_irq(priv->config->irq); - modifyreg32(I2S_INT_ENA_REG(priv->config->port), UINT32_MAX, - I2S_OUT_EOF_INT_ENA); + modifyreg32(I2S_INT_ENA_REG(priv->config->port), 0, + I2S_OUT_EOF_INT_ENA); - /* Enable DMA operation mode */ + /* Enable DMA operation mode */ - modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_DSCR_EN); + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_DSCR_EN); - /* Unset the DMA outlink */ + /* Unset the DMA outlink */ - putreg32(0, I2S_OUT_LINK_REG(priv->config->port)); + putreg32(0, I2S_OUT_LINK_REG(priv->config->port)); - priv->tx_started = true; + priv->tx_started = true; - i2sinfo("Started TX channel of port %d\n", priv->config->port); + i2sinfo("Started TX channel of port %d\n", priv->config->port); + } } #endif /* I2S_HAVE_TX */ /**************************************************************************** - * Name: i2s_tx_channel_stop + * Name: i2s_rx_channel_start * * Description: - * Stop TX channel for the I2S port + * Start RX channel for the I2S port * * Input Parameters: * priv - Initialized I2S device structure. @@ -1468,75 +2102,217 @@ static void i2s_tx_channel_start(struct esp32_i2s_s *priv) * ****************************************************************************/ -#ifdef I2S_HAVE_TX -static void i2s_tx_channel_stop(struct esp32_i2s_s *priv) +#ifdef I2S_HAVE_RX +static void i2s_rx_channel_start(struct esp32_i2s_s *priv) { - if (!priv->tx_started) + if (priv->config->rx_en) { - i2swarn("TX channel of port %d was previously stopped\n", - priv->config->port); - return; - } + if (priv->rx_started) + { + i2swarn("RX channel of port %d was previously started\n", + priv->config->port); + return; + } + + /* Reset the RX channel */ + + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_RESET); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_RESET, 0); + + /* Reset the DMA operation */ - /* Stop TX channel */ + modifyreg32(I2S_LC_CONF_REG(priv->config->port), 0, I2S_IN_RST); + modifyreg32(I2S_LC_CONF_REG(priv->config->port), I2S_IN_RST, 0); - modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_START, 0); + /* Reset RX FIFO */ - /* Stop outlink */ + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_RX_FIFO_RESET); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_FIFO_RESET, 0); - modifyreg32(I2S_OUT_LINK_REG(priv->config->port), I2S_OUTLINK_START, - I2S_OUTLINK_STOP); + /* Enable DMA interrupt */ - /* Disable DMA interrupt */ + up_enable_irq(priv->config->irq); - modifyreg32(I2S_INT_ENA_REG(priv->config->port), UINT32_MAX, 0); + modifyreg32(I2S_INT_ENA_REG(priv->config->port), 0, + I2S_IN_SUC_EOF_INT_ENA); - /* Disable DMA operation mode */ + /* Enable DMA operation mode */ - modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_DSCR_EN, 0); + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_DSCR_EN); - up_disable_irq(priv->config->irq); + /* Unset the DMA outlink */ - priv->tx_started = false; + putreg32(0, I2S_IN_LINK_REG(priv->config->port)); - i2sinfo("Stopped TX channel of port %d\n", priv->config->port); + priv->rx_started = true; + + i2sinfo("Started RX channel of port %d\n", priv->config->port); + } } -#endif /* I2S_HAVE_TX */ +#endif /* I2S_HAVE_RX */ /**************************************************************************** - * Name: esp32_i2s_interrupt + * Name: i2s_tx_channel_stop * * Description: - * Common I2S DMA interrupt handler + * Stop TX channel for the I2S port * * Input Parameters: - * arg - i2s controller private data + * priv - Initialized I2S device structure. * * Returned Value: - * Standard interrupt return value. + * None * ****************************************************************************/ -static int esp32_i2s_interrupt(int irq, void *context, void *arg) +#ifdef I2S_HAVE_TX +static void i2s_tx_channel_stop(struct esp32_i2s_s *priv) { - struct esp32_i2s_s *priv = (struct esp32_i2s_s *)arg; - struct esp32_dmadesc_s *cur = NULL; + if (priv->config->tx_en) + { + if (!priv->tx_started) + { + i2swarn("TX channel of port %d was previously stopped\n", + priv->config->port); + return; + } - uint32_t status = getreg32(I2S_INT_ST_REG(priv->config->port)); + /* Stop TX channel */ - putreg32(UINT32_MAX, I2S_INT_CLR_REG(priv->config->port)); + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_START, 0); - if (status & I2S_OUT_EOF_INT_ST) - { - cur = (struct esp32_dmadesc_s *) - getreg32(I2S_OUT_EOF_DES_ADDR_REG(priv->config->port)); + /* Stop outlink */ - /* Schedule completion of the transfer to occur on the worker thread */ + modifyreg32(I2S_OUT_LINK_REG(priv->config->port), I2S_OUTLINK_START, + I2S_OUTLINK_STOP); - i2s_tx_schedule(priv, cur); - } + /* Disable DMA interrupt */ - return 0; + modifyreg32(I2S_INT_ENA_REG(priv->config->port), + I2S_OUT_EOF_INT_ENA, 0); + + /* Disable DMA operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_DSCR_EN, 0); + + up_disable_irq(priv->config->irq); + + priv->tx_started = false; + + i2sinfo("Stopped TX channel of port %d\n", priv->config->port); + } +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: i2s_rx_channel_stop + * + * Description: + * Stop RX channel for the I2S port + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static void i2s_rx_channel_stop(struct esp32_i2s_s *priv) +{ + if (priv->config->rx_en) + { + if (!priv->rx_started) + { + i2swarn("RX channel of port %d was previously stopped\n", + priv->config->port); + return; + } + + /* Stop RX channel */ + + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_RX_START, 0); + + /* Stop outlink */ + + modifyreg32(I2S_IN_LINK_REG(priv->config->port), I2S_INLINK_START, + I2S_INLINK_STOP); + + /* Disable DMA interrupt */ + + modifyreg32(I2S_INT_ENA_REG(priv->config->port), + I2S_IN_SUC_EOF_INT_ENA, 0); + + /* Disable DMA operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), I2S_DSCR_EN, 0); + + up_disable_irq(priv->config->irq); + + priv->rx_started = false; + + i2sinfo("Stopped RX channel of port %d\n", priv->config->port); + } +} +#endif /* I2S_HAVE_RX */ + +/**************************************************************************** + * Name: esp32_i2s_interrupt + * + * Description: + * Common I2S DMA interrupt handler + * + * Input Parameters: + * irq - Number of the IRQ that generated the interrupt + * context - Interrupt register state save info + * arg - I2S controller private data + * + * Returned Value: + * Standard interrupt return value. + * + ****************************************************************************/ + +static int esp32_i2s_interrupt(int irq, void *context, void *arg) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)arg; + struct esp32_dmadesc_s *cur = NULL; + + uint32_t status = getreg32(I2S_INT_ST_REG(priv->config->port)); + + putreg32(UINT32_MAX, I2S_INT_CLR_REG(priv->config->port)); + +#ifdef I2S_HAVE_TX + if (priv->config->tx_en) + { + if (status & I2S_OUT_EOF_INT_ST) + { + cur = (struct esp32_dmadesc_s *) + getreg32(I2S_OUT_EOF_DES_ADDR_REG(priv->config->port)); + + /* Schedule completion of the transfer on the worker thread */ + + i2s_tx_schedule(priv, cur); + } + } +#endif /* I2S_HAVE_TX */ + +#ifdef I2S_HAVE_RX + if (priv->config->rx_en) + { + if (status & I2S_IN_SUC_EOF_INT_ST) + { + cur = (struct esp32_dmadesc_s *) + getreg32(I2S_IN_EOF_DES_ADDR_REG(priv->config->port)); + + /* Schedule completion of the transfer on the worker thread */ + + i2s_rx_schedule(priv, cur); + } + } +#endif /* I2S_HAVE_RX */ + + return 0; } /**************************************************************************** @@ -1590,18 +2366,60 @@ static uint32_t esp32_i2s_mclkfrequency(struct i2s_dev_s *dev, * ****************************************************************************/ +#ifdef I2S_HAVE_TX static int esp32_i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels) { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - if (channels != 1 && channels != 2) + if (priv->config->tx_en) { - return -EINVAL; + if (channels != 1 && channels != 2) + { + return -EINVAL; + } + + priv->channels = channels; + return OK; } - priv->channels = channels; - return OK; + return -ENOTTY; +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: esp32_i2s_rxchannels + * + * Description: + * Set the I2S RX number of channels. + * + * Input Parameters: + * dev - Device-specific state data + * channels - The I2S numbers of channels + * + * Returned Value: + * OK on success; a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static int esp32_i2s_rxchannels(struct i2s_dev_s *dev, uint8_t channels) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + + if (priv->config->rx_en) + { + if (channels != 1 && channels != 2) + { + return -EINVAL; + } + + priv->channels = channels; + return OK; + } + + return -ENOTTY; } +#endif /* I2S_HAVE_RX */ /**************************************************************************** * Name: esp32_i2s_txsamplerate @@ -1621,20 +2439,64 @@ static int esp32_i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels) * ****************************************************************************/ +#ifdef I2S_HAVE_TX static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - i2s_tx_channel_stop(priv); + if (priv->config->tx_en) + { + i2s_tx_channel_stop(priv); + + priv->rate = rate; - priv->rate = rate; + rate = i2s_set_clock(priv); - rate = i2s_set_clock(priv); + i2s_tx_channel_start(priv); - i2s_tx_channel_start(priv); + return rate; + } - return rate; + return 0; } +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: esp32_i2s_rxsamplerate + * + * Description: + * Set the I2S RX sample rate. + * + * Input Parameters: + * dev - Device-specific state data + * rate - The I2S sample rate in samples (not bits) per second + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static uint32_t esp32_i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + + if (priv->config->rx_en) + { + i2s_rx_channel_stop(priv); + + priv->rate = rate; + + rate = i2s_set_clock(priv); + + i2s_rx_channel_start(priv); + + return rate; + } + + return 0; +} +#endif /* I2S_HAVE_RX */ /**************************************************************************** * Name: esp32_i2s_txdatawidth @@ -1652,20 +2514,65 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) * ****************************************************************************/ +#ifdef I2S_HAVE_TX static uint32_t esp32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - i2s_tx_channel_stop(priv); + if (priv->config->tx_en) + { + i2s_tx_channel_stop(priv); + + priv->data_width = bits; + + i2s_set_datawidth(priv); + + i2s_tx_channel_start(priv); + + return bits; + } + + return 0; +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: esp32_i2s_rxdatawidth + * + * Description: + * Set the I2S RX data width. The RX bitrate is determined by + * sample_rate * data_width. + * + * Input Parameters: + * dev - Device-specific state data + * width - The I2S data with in bits. + * + * Returned Value: + * Returns the resulting data width + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static uint32_t esp32_i2s_rxdatawidth(struct i2s_dev_s *dev, int bits) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + + if (priv->config->rx_en) + { + i2s_rx_channel_stop(priv); + + priv->data_width = bits; - priv->data_width = bits; + i2s_set_datawidth(priv); - i2s_set_datawidth(priv); + i2s_rx_channel_start(priv); - i2s_tx_channel_start(priv); + return bits; + } - return bits; + return 0; } +#endif /* I2S_HAVE_RX */ /**************************************************************************** * Name: esp32_i2s_send @@ -1690,95 +2597,281 @@ static uint32_t esp32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) * ****************************************************************************/ +#ifdef I2S_HAVE_TX static int esp32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, + uint32_t timeout) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + + if (priv->config->tx_en) + { + struct esp32_buffer_s *bfcontainer; + int ret = OK; + uint32_t nbytes; + uint32_t nsamp; + + /* Check audio buffer data size */ + + nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes; + + /* If data width is 8, it is necessary to use a word of 16 bits; + * If data width is 24, it is necessary to use a word of 32 bits; + */ + + nsamp = nbytes / (priv->data_width / 8); + nbytes = nsamp * + ((priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + I2S_DATA_BIT_WIDTH_16BIT : I2S_DATA_BIT_WIDTH_32BIT) / 8); + + /* ESP32's I2S peripheral always consider two channels. If the source + * is mono, it is necessary to copy zero-ed data between each sample. + */ + + nbytes *= priv->channels == 1 ? 2 : 1; + + /* Ensure nbytes is 4-byte aligned */ + + nbytes = ALIGN_UP(nbytes, sizeof(uintptr_t)); + + if (nbytes > (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)) + { + i2serr("Required buffer size can't fit into DMA outlink " + "(exceeds in %" PRIu32 " bytes). Try to increase the " + "number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).", + nbytes - (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)); + return -EFBIG; + } + + /* Allocate a buffer container in advance */ + + bfcontainer = i2s_buf_allocate(priv); + DEBUGASSERT(bfcontainer); + + /* Get exclusive access to the I2S driver data */ + + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + goto errout_with_buf; + } + + /* Add a reference to the audio buffer */ + + apb_reference(apb); + + /* Initialize the buffer container structure */ + + bfcontainer->callback = callback; + bfcontainer->timeout = timeout; + bfcontainer->arg = arg; + bfcontainer->apb = apb; + bfcontainer->nbytes = nbytes; + bfcontainer->result = -EBUSY; + + ret = i2s_txdma_setup(priv, bfcontainer); + + if (ret != OK) + { + goto errout_with_buf; + } + + i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes); + i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte], + apb->nbytes - apb->curbyte); + + nxmutex_unlock(&priv->lock); + + return OK; + +errout_with_buf: + nxmutex_unlock(&priv->lock); + i2s_buf_free(priv, bfcontainer); + return ret; + } + + return -ENOTTY; +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: esp32_i2s_receive + * + * Description: + * Receive a block of data on I2S. + * + * Input Parameters: + * dev - Device-specific state data + * apb - A pointer to the audio buffer in which to receive data + * callback - A user provided callback function that will be called at + * the completion of the transfer. + * arg - An opaque argument that will be provided to the callback + * when the transfer complete + * timeout - The timeout value to use. The transfer will be cancelled + * and an ETIMEDOUT error will be reported if this timeout + * elapsed without completion of the DMA transfer. Units + * are system clock ticks. Zero means no timeout. + * + * Returned Value: + * OK on success; a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef I2S_HAVE_RX +static int esp32_i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2s_callback_t callback, void *arg, uint32_t timeout) { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - struct esp32_buffer_s *bfcontainer; - int ret = OK; - uint32_t nbytes; - uint32_t nsamp; - /* Check audio buffer data size */ + if (priv->config->rx_en) + { + struct esp32_buffer_s *bfcontainer; + int ret = OK; + uint32_t nbytes; + uint32_t nsamp; - nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes; + /* Check max audio buffer data size */ - /* If data width is 8, it is necessary to use a word of 16 bits; - * If data width is 24, it is necessary to a word of 32 bits; - */ + nbytes = apb->nmaxbytes; + + /* If data width is 8, it is necessary to use a word of 16 bits; + * If data width is 24, it is necessary to use a word of 32 bits; + */ - nsamp = nbytes / (priv->data_width / 8); - nbytes = nsamp * - ((priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? - I2S_DATA_BIT_WIDTH_16BIT : I2S_DATA_BIT_WIDTH_32BIT) / 8); + nsamp = nbytes / (priv->data_width / 8); + nbytes = nsamp * + ((priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + I2S_DATA_BIT_WIDTH_16BIT : I2S_DATA_BIT_WIDTH_32BIT) / 8); - /* ESP32's I2S peripheral always consider two channels. If the source is - * mono, it is necessary to copy zero-ed data between each sample. - */ + if (nbytes > (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)) + { + i2serr("Required buffer size can't fit into DMA inlink " + "(exceeds in %" PRIu32 " bytes). Try to increase the " + "number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).", + nbytes - (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)); + return -EFBIG; + } - nbytes *= priv->channels == 1 ? 2 : 1; + /* Allocate a buffer container in advance */ - /* Ensure nbytes is 4-byte aligned */ + bfcontainer = i2s_buf_allocate(priv); + DEBUGASSERT(bfcontainer); - nbytes = ALIGN_UP(nbytes, sizeof(uintptr_t)); + /* Get exclusive access to the I2S driver data */ - if (nbytes > (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)) - { - i2serr("Required buffer size can not be fitted into DMA outlink " - "(exceeds in %" PRIu32 " bytes). Try to increase the " - "number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).", - nbytes - (ESP32_DMA_DATALEN_MAX * I2S_DMADESC_NUM)); - return -EFBIG; + ret = nxmutex_lock(&priv->lock); + if (ret < 0) + { + goto errout_with_buf; + } + + /* Add a reference to the audio buffer */ + + apb_reference(apb); + + /* Initialize the buffer container structure */ + + bfcontainer->callback = callback; + bfcontainer->timeout = timeout; + bfcontainer->arg = arg; + bfcontainer->apb = apb; + bfcontainer->nbytes = nbytes; + bfcontainer->result = -EBUSY; + + ret = i2s_rxdma_setup(priv, bfcontainer); + + if (ret != OK) + { + goto errout_with_buf; + } + + i2sinfo("Prepared %d bytes to receive DMA buffers\n", apb->nmaxbytes); + i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte], + apb->nbytes - apb->curbyte); + + nxmutex_unlock(&priv->lock); + + return OK; + +errout_with_buf: + nxmutex_unlock(&priv->lock); + i2s_buf_free(priv, bfcontainer); + return ret; } - /* Allocate a buffer container in advance */ + return -ENOTTY; +} +#endif /* I2S_HAVE_RX */ - bfcontainer = i2s_buf_allocate(priv); - DEBUGASSERT(bfcontainer); +/**************************************************************************** + * Name: esp32_i2s_ioctl + * + * Description: + * Perform a device ioctl + * + ****************************************************************************/ - /* Get exclusive access to the I2S driver data */ +static int esp32_i2s_ioctl(struct i2s_dev_s *dev, int cmd, + unsigned long arg) +{ + struct audio_buf_desc_s *bufdesc; + int ret = -ENOTTY; - ret = nxmutex_lock(&priv->lock); - if (ret < 0) + switch (cmd) { - goto errout_with_buf; - } + /* AUDIOIOC_START - Start the audio stream. + * + * ioctl argument: Audio session + */ - /* Add a reference to the audio buffer */ + case AUDIOIOC_START: + { + i2sinfo("AUDIOIOC_START\n"); - apb_reference(apb); + ret = OK; + } + break; - /* Initialize the buffer container structure */ + /* AUDIOIOC_ALLOCBUFFER - Allocate an audio buffer + * + * ioctl argument: pointer to an audio_buf_desc_s structure + */ - bfcontainer->callback = callback; - bfcontainer->timeout = timeout; - bfcontainer->arg = arg; - bfcontainer->apb = apb; - bfcontainer->nbytes = nbytes; - bfcontainer->result = -EBUSY; + case AUDIOIOC_ALLOCBUFFER: + { + i2sinfo("AUDIOIOC_ALLOCBUFFER\n"); - ret = i2s_txdma_setup(priv, bfcontainer); - if (ret != OK) - { - goto errout_with_buf; - } + bufdesc = (struct audio_buf_desc_s *) arg; + ret = apb_alloc(bufdesc); + } + break; - i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes); - i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte], - apb->nbytes - apb->curbyte); + /* AUDIOIOC_FREEBUFFER - Free an audio buffer + * + * ioctl argument: pointer to an audio_buf_desc_s structure + */ - nxmutex_unlock(&priv->lock); - return OK; + case AUDIOIOC_FREEBUFFER: + { + i2sinfo("AUDIOIOC_FREEBUFFER\n"); + + bufdesc = (struct audio_buf_desc_s *) arg; + DEBUGASSERT(bufdesc->u.buffer != NULL); + apb_free(bufdesc->u.buffer); + ret = sizeof(struct audio_buf_desc_s); + } + break; + + default: + break; + } -errout_with_buf: - nxmutex_unlock(&priv->lock); - i2s_buf_free(priv, bfcontainer); return ret; } /**************************************************************************** - * Name: esp32_i2sdma_setup + * Name: i2s_dma_setup * * Description: * Configure the DMA for the I2S peripheral @@ -1793,7 +2886,7 @@ errout_with_buf: * ****************************************************************************/ -static int esp32_i2sdma_setup(struct esp32_i2s_s *priv) +static int i2s_dma_setup(struct esp32_i2s_s *priv) { int ret; @@ -1865,8 +2958,6 @@ struct i2s_dev_s *esp32_i2sbus_initialize(int port) return NULL; } - priv->tx_started = false; - flags = spin_lock_irqsave(&priv->slock); i2s_configure(priv); @@ -1879,7 +2970,7 @@ struct i2s_dev_s *esp32_i2sbus_initialize(int port) goto err; } - ret = esp32_i2sdma_setup(priv); + ret = i2s_dma_setup(priv); if (ret < 0) { goto err; @@ -1888,9 +2979,23 @@ struct i2s_dev_s *esp32_i2sbus_initialize(int port) #ifdef I2S_HAVE_TX /* Start TX channel */ - i2s_tx_channel_start(priv); + if (priv->config->tx_en) + { + priv->tx_started = false; + i2s_tx_channel_start(priv); + } #endif /* I2S_HAVE_TX */ +#ifdef I2S_HAVE_RX + /* Start RX channel */ + + if (priv->config->rx_en) + { + priv->rx_started = false; + i2s_rx_channel_start(priv); + } +#endif /* I2S_HAVE_RX */ + spin_unlock_irqrestore(&priv->slock, flags); /* Success exit */ @@ -1902,7 +3007,7 @@ struct i2s_dev_s *esp32_i2sbus_initialize(int port) /* Failure exit */ err: - spin_unlock_irqrestore(&priv->lock, flags); + spin_unlock_irqrestore(&priv->slock, flags); return NULL; } diff --git a/arch/xtensa/src/esp32/esp32_i2s.h b/arch/xtensa/src/esp32/esp32_i2s.h index 6ee13bb279..ab6235f059 100644 --- a/arch/xtensa/src/esp32/esp32_i2s.h +++ b/arch/xtensa/src/esp32/esp32_i2s.h @@ -41,13 +41,8 @@ extern "C" #ifdef CONFIG_ESP32_I2S -#ifdef CONFIG_ESP32_I2S0 - #define ESP32_I2S0 0 -#endif /* CONFIG_ESP32_I2S0 */ - -#ifdef CONFIG_ESP32_I2S1 - #define ESP32_I2S1 1 -#endif /* CONFIG_ESP32_I2S1 */ +#define ESP32_I2S0 0 +#define ESP32_I2S1 1 /**************************************************************************** * Public Function Prototypes diff --git a/boards/xtensa/esp32/common/src/esp32_board_i2sdev.c b/boards/xtensa/esp32/common/src/esp32_board_i2sdev.c index ad5c634f31..c436f950db 100644 --- a/boards/xtensa/esp32/common/src/esp32_board_i2sdev.c +++ b/boards/xtensa/esp32/common/src/esp32_board_i2sdev.c @@ -37,8 +37,8 @@ #include "esp32_i2s.h" -#if defined(CONFIG_ESP32_I2S0) && !defined(CONFIG_AUDIO_CS4344) && \ -!defined(CONFIG_AUDIO_ES8388) || defined(CONFIG_ESP32_I2S1) +#if defined(CONFIG_ESP32_I2S0) && !defined(CONFIG_AUDIO_CS4344) || \ + defined(CONFIG_ESP32_I2S1) /**************************************************************************** * Public Functions @@ -54,7 +54,9 @@ * number. * * Input Parameters: - * port - The I2S port used for the device + * port - The I2S port used for the device + * enable_tx - Register device as TX if true + * enable_rx - Register device as RX if true * * Returned Value: * Zero is returned on success. Otherwise, a negated errno value is @@ -62,12 +64,11 @@ * ****************************************************************************/ -int board_i2sdev_initialize(int port) +int board_i2sdev_initialize(int port, bool enable_tx, bool enable_rx) { struct audio_lowerhalf_s *audio_i2s; - struct audio_lowerhalf_s *pcm; struct i2s_dev_s *i2s; - char devname[12]; + char devname[8]; int ret; ainfo("Initializing I2S\n"); @@ -75,7 +76,7 @@ int board_i2sdev_initialize(int port) i2s = esp32_i2sbus_initialize(port); #ifdef CONFIG_AUDIO_I2SCHAR - ret = i2schar_register(i2s, 0); + ret = i2schar_register(i2s, port); if (ret < 0) { aerr("ERROR: i2schar_register failed: %d\n", ret); @@ -83,29 +84,71 @@ int board_i2sdev_initialize(int port) } #endif - audio_i2s = audio_i2s_initialize(i2s, true); - - if (!audio_i2s) + if (enable_tx) { - auderr("ERROR: Failed to initialize I2S\n"); - return -ENODEV; - } + /* Initialize audio output */ - pcm = pcm_decode_initialize(audio_i2s); + audio_i2s = audio_i2s_initialize(i2s, true); - if (!pcm) - { - auderr("ERROR: Failed create the PCM decoder\n"); - return -ENODEV; - } + if (audio_i2s == NULL) + { + auderr("ERROR: Failed to initialize I2S audio output\n"); + return -ENODEV; + } - snprintf(devname, 12, "pcm%d", port); + snprintf(devname, sizeof(devname), "pcm%d", port); - ret = audio_register(devname, pcm); + /* If nxlooper is selected, the playback buffer is not rendered as + * a WAV file. Therefore, PCM decode will fail while processing such + * output buffer. In such a case, we bypass the PCM decode. + */ - if (ret < 0) +#ifdef CONFIG_SYSTEM_NXLOOPER + ret = audio_register(devname, audio_i2s); +#else + struct audio_lowerhalf_s *pcm; + + pcm = pcm_decode_initialize(audio_i2s); + + if (pcm == NULL) + { + auderr("ERROR: Failed create the PCM decoder\n"); + return -ENODEV; + } + + ret = audio_register(devname, pcm); +#endif /* CONFIG_SYSTEM_NXLOOPER */ + + if (ret < 0) + { + auderr("ERROR: Failed to register /dev/%s device: %d\n", + devname, ret); + return ret; + } + } + + if (enable_rx) { - auderr("ERROR: Failed to register /dev/%s device: %d\n", devname, ret); + /* Initialize audio input */ + + audio_i2s = audio_i2s_initialize(i2s, false); + + if (audio_i2s == NULL) + { + auderr("ERROR: Failed to initialize I2S audio input\n"); + return -ENODEV; + } + + snprintf(devname, sizeof(devname), "pcm_in%d", port); + + ret = audio_register(devname, audio_i2s); + + if (ret < 0) + { + auderr("ERROR: Failed to register /dev/%s device: %d\n", + devname, ret); + return ret; + } } return ret; diff --git a/boards/xtensa/esp32/esp32-devkitc/configs/i2schar/defconfig b/boards/xtensa/esp32/esp32-devkitc/configs/i2schar/defconfig index dcf9c709fa..a4aec245ef 100644 --- a/boards/xtensa/esp32/esp32-devkitc/configs/i2schar/defconfig +++ b/boards/xtensa/esp32/esp32-devkitc/configs/i2schar/defconfig @@ -6,7 +6,6 @@ # modifications. # # CONFIG_ARCH_LEDS is not set -# CONFIG_ESP32_I2S0_RX is not set # CONFIG_NDEBUG is not set # CONFIG_NSH_ARGCAT is not set # CONFIG_NSH_CMDOPT_HEXDUMP is not set @@ -25,13 +24,18 @@ CONFIG_AUDIO_I2S=y CONFIG_AUDIO_I2SCHAR=y CONFIG_BOARD_LOOPSPERMSEC=16717 CONFIG_BUILTIN=y -CONFIG_DEBUG_SYMBOLS=y CONFIG_DRIVERS_AUDIO=y CONFIG_ESP32_I2S0=y +CONFIG_ESP32_I2S0_DATA_BIT_WIDTH_16BIT=y CONFIG_ESP32_I2S0_MCLK=y +CONFIG_ESP32_I2S1=y CONFIG_ESP32_I2S=y CONFIG_ESP32_UART0=y CONFIG_EXAMPLES_I2SCHAR=y +CONFIG_EXAMPLES_I2SCHAR_BUFSIZE=1024 +CONFIG_EXAMPLES_I2SCHAR_RX=y +CONFIG_EXAMPLES_I2SCHAR_RXBUFFERS=2 +CONFIG_EXAMPLES_I2SCHAR_RXSTACKSIZE=4096 CONFIG_EXAMPLES_I2SCHAR_TX=y CONFIG_EXAMPLES_I2SCHAR_TXBUFFERS=2 CONFIG_EXAMPLES_I2SCHAR_TXSTACKSIZE=4096 diff --git a/boards/xtensa/esp32/esp32-devkitc/configs/nxlooper/defconfig b/boards/xtensa/esp32/esp32-devkitc/configs/nxlooper/defconfig new file mode 100644 index 0000000000..e5e60cd57e --- /dev/null +++ b/boards/xtensa/esp32/esp32-devkitc/configs/nxlooper/defconfig @@ -0,0 +1,129 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_ARCH_LEDS is not set +# CONFIG_ESP32_I2S0_RX is not set +# CONFIG_ESP32_I2S1_TX is not set +# CONFIG_NDEBUG is not set +# CONFIG_NSH_ARGCAT is not set +# CONFIG_NSH_CMDOPT_HEXDUMP is not set +# CONFIG_NSH_CMDPARMS is not set +CONFIG_ALLOW_BSD_COMPONENTS=y +CONFIG_ARCH="xtensa" +CONFIG_ARCH_BOARD="esp32-devkitc" +CONFIG_ARCH_BOARD_COMMON=y +CONFIG_ARCH_BOARD_ESP32_DEVKITC=y +CONFIG_ARCH_CHIP="esp32" +CONFIG_ARCH_CHIP_ESP32=y +CONFIG_ARCH_CHIP_ESP32WROVER=y +CONFIG_ARCH_INTERRUPTSTACK=4096 +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARCH_XTENSA=y +CONFIG_AUDIO=y +CONFIG_AUDIOUTILS_MMLPARSER_LIB=y +CONFIG_AUDIO_BUFFER_NUMBYTES=4092 +CONFIG_AUDIO_EXCLUDE_BALANCE=y +CONFIG_AUDIO_EXCLUDE_FFORWARD=y +CONFIG_AUDIO_EXCLUDE_TONE=y +CONFIG_AUDIO_EXCLUDE_VOLUME=y +CONFIG_AUDIO_I2S=y +CONFIG_AUDIO_I2SCHAR=y +CONFIG_AUDIO_NUM_BUFFERS=4 +CONFIG_BOARDCTL_ROMDISK=y +CONFIG_BOARD_LOOPSPERMSEC=16717 +CONFIG_BUILTIN=y +CONFIG_DEFAULT_TASK_STACKSIZE=4096 +CONFIG_DEV_URANDOM=y +CONFIG_DRIVERS_AUDIO=y +CONFIG_DRIVERS_IEEE80211=y +CONFIG_DRIVERS_WIRELESS=y +CONFIG_ESP32_I2S0=y +CONFIG_ESP32_I2S0_DATA_BIT_WIDTH_16BIT=y +CONFIG_ESP32_I2S0_MCLK=y +CONFIG_ESP32_I2S1=y +CONFIG_ESP32_I2S1_DATA_BIT_WIDTH_16BIT=y +CONFIG_ESP32_I2S=y +CONFIG_ESP32_SPIFLASH=y +CONFIG_ESP32_SPIFLASH_SPIFFS=y +CONFIG_ESP32_STORAGE_MTD_SIZE=0x80000 +CONFIG_ESP32_UART0=y +CONFIG_ESP32_WIFI=y +CONFIG_ESP32_WIFI_SAVE_PARAM=y +CONFIG_EXAMPLES_I2SCHAR=y +CONFIG_EXAMPLES_I2SCHAR_BUFSIZE=960 +CONFIG_EXAMPLES_I2SCHAR_RX=y +CONFIG_EXAMPLES_I2SCHAR_RXBUFFERS=2 +CONFIG_EXAMPLES_I2SCHAR_RXSTACKSIZE=2048 +CONFIG_EXAMPLES_I2SCHAR_TX=y +CONFIG_EXAMPLES_I2SCHAR_TXBUFFERS=2 +CONFIG_EXAMPLES_I2SCHAR_TXSTACKSIZE=2048 +CONFIG_EXAMPLES_ROMFS=y +CONFIG_FS_PROCFS=y +CONFIG_FS_ROMFS=y +CONFIG_HAVE_CXX=y +CONFIG_HAVE_CXXINITIALIZE=y +CONFIG_I2S_DMADESC_NUM=4 +CONFIG_IDLETHREAD_STACKSIZE=3072 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INTELHEX_BINARY=y +CONFIG_IOB_NBUFFERS=24 +CONFIG_IOB_THROTTLE=0 +CONFIG_MM_REGIONS=3 +CONFIG_NAME_MAX=48 +CONFIG_NETDB_DNSCLIENT=y +CONFIG_NETDB_DNSCLIENT_NAMESIZE=64 +CONFIG_NETDEV_LATEINIT=y +CONFIG_NETDEV_PHY_IOCTL=y +CONFIG_NETDEV_WIRELESS_IOCTL=y +CONFIG_NETINIT_WAPI_ALG=1 +CONFIG_NETUTILS_IPERF=y +CONFIG_NETUTILS_TELNETD=y +CONFIG_NET_BROADCAST=y +CONFIG_NET_ETH_PKTSIZE=1518 +CONFIG_NET_ICMP=y +CONFIG_NET_ICMP_SOCKET=y +CONFIG_NET_NACTIVESOCKETS=32 +CONFIG_NET_STATISTICS=y +CONFIG_NET_TCP=y +CONFIG_NET_TCP_DELAYED_ACK=y +CONFIG_NET_TCP_WRITE_BUFFERS=y +CONFIG_NET_UDP=y +CONFIG_NSH_ARCHINIT=y +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_LINELEN=300 +CONFIG_NSH_READLINE=y +CONFIG_NXPLAYER_HTTP_STREAMING_SUPPORT=y +CONFIG_NXPLAYER_PLAYTHREAD_STACKSIZE=4096 +CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE=2048 +CONFIG_PREALLOC_TIMERS=4 +CONFIG_PTHREAD_MUTEX_TYPES=y +CONFIG_PTHREAD_STACK_DEFAULT=2048 +CONFIG_RAM_SIZE=114688 +CONFIG_RAM_START=0x20000000 +CONFIG_RR_INTERVAL=200 +CONFIG_SCHED_HPWORK=y +CONFIG_SCHED_HPWORKSTACKSIZE=2048 +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_WAITPID=y +CONFIG_SIG_DEFAULT=y +CONFIG_SPI=y +CONFIG_SPIFFS_NAME_MAX=48 +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_SYSTEM_DHCPC_RENEW=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_NXLOOPER=y +CONFIG_SYSTEM_NXPLAYER=y +CONFIG_SYSTEM_PING=y +CONFIG_TELNET_CHARACTER_MODE=y +CONFIG_TLS_TASK_NELEM=4 +CONFIG_UART0_SERIAL_CONSOLE=y +CONFIG_WIRELESS=y +CONFIG_WIRELESS_WAPI=y +CONFIG_WIRELESS_WAPI_CMDTOOL=y diff --git a/boards/xtensa/esp32/esp32-devkitc/src/esp32-devkitc.h b/boards/xtensa/esp32/esp32-devkitc/src/esp32-devkitc.h index 3360d9ca95..8e5e9d9d08 100644 --- a/boards/xtensa/esp32/esp32-devkitc/src/esp32-devkitc.h +++ b/boards/xtensa/esp32/esp32-devkitc/src/esp32-devkitc.h @@ -165,7 +165,9 @@ int esp32_twai_setup(void); * number. * * Input Parameters: - * port - The I2S port used for the device + * port - The I2S port used for the device + * enable_tx - Register device as TX if true + * enable_rx - Register device as RX if true * * Returned Value: * Zero is returned on success. Otherwise, a negated errno value is @@ -175,7 +177,7 @@ int esp32_twai_setup(void); #if defined(CONFIG_ESP32_I2S0) && !defined(CONFIG_AUDIO_CS4344) || \ defined(CONFIG_ESP32_I2S1) -int board_i2sdev_initialize(int port); +int board_i2sdev_initialize(int port, bool enable_tx, bool enable_rx); #endif /**************************************************************************** diff --git a/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c b/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c index cc3eca312c..cef7858b14 100644 --- a/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c +++ b/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c @@ -488,6 +488,12 @@ int esp32_bringup(void) #ifdef CONFIG_ESP32_I2S +#if defined(CONFIG_ESP32_I2S0) && !defined(CONFIG_AUDIO_CS4344) || \ + defined(CONFIG_ESP32_I2S1) + bool i2s_enable_tx; + bool i2s_enable_rx; +#endif + #ifdef CONFIG_ESP32_I2S0 /* Configure I2S0 */ @@ -503,27 +509,52 @@ int esp32_bringup(void) } #else +#ifdef CONFIG_ESP32_I2S0_TX + i2s_enable_tx = true; +#else + i2s_enable_tx = false; +#endif /* CONFIG_ESP32_I2S0_TX */ + +#ifdef CONFIG_ESP32_I2S0_RX + i2s_enable_rx = true; +#else + i2s_enable_rx = false; +#endif /* CONFIG_ESP32_I2S0_RX */ + /* Configure I2S generic audio on I2S0 */ - ret = board_i2sdev_initialize(ESP32_I2S0); + ret = board_i2sdev_initialize(ESP32_I2S0, i2s_enable_tx, i2s_enable_rx); if (ret < 0) { syslog(LOG_ERR, "Failed to initialize I2S%d driver: %d\n", CONFIG_ESP32_I2S0, ret); } + #endif /* CONFIG_AUDIO_CS4344 */ #endif /* CONFIG_ESP32_I2S0 */ #ifdef CONFIG_ESP32_I2S1 +#ifdef CONFIG_ESP32_I2S1_TX + i2s_enable_tx = true; +#else + i2s_enable_tx = false; +#endif /* CONFIG_ESP32_I2S1_TX */ + +#ifdef CONFIG_ESP32_I2S1_RX + i2s_enable_rx = true; +#else + i2s_enable_rx = false; +#endif /* CONFIG_ESP32_I2S1_RX */ + /* Configure I2S generic audio on I2S1 */ - ret = board_i2sdev_initialize(ESP32_I2S1); + ret = board_i2sdev_initialize(ESP32_I2S1, i2s_enable_tx, i2s_enable_rx); if (ret < 0) { syslog(LOG_ERR, "Failed to initialize I2S%d driver: %d\n", - CONFIG_ESP32_I2S0, ret); + CONFIG_ESP32_I2S1, ret); } #endif /* CONFIG_ESP32_I2S1 */