This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git
commit 9ecc345c02599d1ea8eff65eaf8b7df7c2336dd9 Author: Tiago Medicci Serrano <[email protected]> AuthorDate: Tue Oct 25 14:25:15 2022 -0300 esp32/i2s: use internal buffer to handle multiple audio formats --- arch/xtensa/src/esp32/esp32_i2s.c | 695 +++++++++++++++------ .../esp32/esp32-devkitc/configs/audio/defconfig | 3 +- 2 files changed, 492 insertions(+), 206 deletions(-) diff --git a/arch/xtensa/src/esp32/esp32_i2s.c b/arch/xtensa/src/esp32/esp32_i2s.c index 830b25b728..59ceea1a6a 100644 --- a/arch/xtensa/src/esp32/esp32_i2s.c +++ b/arch/xtensa/src/esp32/esp32_i2s.c @@ -82,29 +82,37 @@ #define I2S_DMA_CHANNEL_MAX (2) #ifdef CONFIG_ESP32_I2S0_TX - #define I2S0_TX_ENABLED 1 - #define I2S_HAVE_TX 1 +# define I2S0_TX_ENABLED 1 +# define I2S_HAVE_TX 1 #else - #define I2S0_TX_ENABLED 0 +# define I2S0_TX_ENABLED 0 #endif #ifdef CONFIG_ESP32_I2S0_RX - #define I2S0_RX_ENABLED 1 +# define I2S0_RX_ENABLED 1 #else - #define I2S0_RX_ENABLED 0 +# define I2S0_RX_ENABLED 0 #endif #ifdef CONFIG_ESP32_I2S1_TX - #define I2S1_TX_ENABLED 1 - #define I2S_HAVE_TX 1 +# define I2S1_TX_ENABLED 1 +# define I2S_HAVE_TX 1 #else - #define I2S1_TX_ENABLED 0 +# define I2S1_TX_ENABLED 0 #endif #ifdef CONFIG_ESP32_I2S1_RX - #define I2S1_RX_ENABLED 1 +# define I2S1_RX_ENABLED 1 #else - #define I2S1_RX_ENABLED 0 +# define I2S1_RX_ENABLED 0 +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ALIGN_UP +# define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) #endif /* Debug ********************************************************************/ @@ -205,6 +213,10 @@ struct esp32_i2s_config_s /* WS signal polarity, set true to enable high lever first */ bool ws_pol; + + /* The associated DMA outlink */ + + struct esp32_dmadesc_s dma_outlink[I2S_DMADESC_NUM]; }; struct esp32_buffer_s @@ -219,9 +231,23 @@ struct esp32_buffer_s uint32_t timeout; /* Timeout value of the DMA transfers */ void *arg; /* Callback's argument */ struct ap_buffer_s *apb; /* The audio buffer */ + uint8_t *buf; /* The DMA's descriptor buffer */ + uint32_t nbytes; /* The DMA's descriptor buffer size */ int result; /* The result of the transfer */ }; +/* Internal buffer must be aligned to the sample_size. 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. + */ + +struct esp32_buffer_carry_s +{ + uint32_t value; + size_t bytes; +}; + /* This structure describes the state of one receiver or transmitter * transport. */ @@ -232,31 +258,35 @@ struct esp32_transport_s sq_queue_t act; /* A queue of active transfers */ sq_queue_t done; /* A queue of completed transfers */ struct work_s work; /* Supports worker thread operations */ + + /* Bytes to be written at the beginning of the next DMA buffer */ + + struct esp32_buffer_carry_s carry; }; /* The state of the one I2S peripheral */ struct esp32_i2s_s { - struct i2s_dev_s dev; /* Externally visible I2S interface */ - sem_t exclsem; /* Assures mutually exclusive access */ - 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 */ - int cpuint; /* I2S interrupt ID */ - uint8_t cpu; /* CPU ID */ - spinlock_t lock; /* Device specific lock. */ + struct i2s_dev_s dev; /* Externally visible I2S interface */ + sem_t exclsem; /* Assures mutually exclusive access */ + int cpuint; /* I2S interrupt ID */ + uint8_t cpu; /* CPU ID */ + spinlock_t lock; /* Device specific lock. */ /* Port configuration */ 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 */ + #ifdef I2S_HAVE_TX struct esp32_transport_s tx; /* TX transport state */ - /* Stuff var to fill DMA buffer if not word-aligned */ - - uint32_t stuff; + bool tx_started; /* TX channel started */ #endif /* I2S_HAVE_TX */ /* Pre-allocated pool of buffer containers */ @@ -291,21 +321,27 @@ static int i2s_bufsem_take(struct esp32_i2s_s *priv); static struct esp32_buffer_s * i2s_buf_allocate(struct esp32_i2s_s *priv); static void i2s_buf_free(struct esp32_i2s_s *priv, - struct esp32_buffer_s *bfcontainer); + struct esp32_buffer_s *bfcontainer); static int i2s_buf_initialize(struct esp32_i2s_s *priv); /* DMA support */ #ifdef I2S_HAVE_TX -static int i2s_txdma_setup(struct esp32_i2s_s *priv, - struct esp32_buffer_s *bfcontainer); -static void i2s_tx_worker(void *arg); -static void i2s_tx_schedule(struct esp32_i2s_s *priv, - struct esp32_dmadesc_s *outlink); +static IRAM_ATTR int i2s_txdma_setup(struct esp32_i2s_s *priv, + struct esp32_buffer_s *bfcontainer); +static void i2s_tx_worker(void *arg); +static void i2s_tx_schedule(struct esp32_i2s_s *priv, + struct esp32_dmadesc_s *outlink); #endif /* I2S_HAVE_TX */ /* 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 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, @@ -322,6 +358,7 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, static const struct i2s_ops_s g_i2sops = { + .i2s_txchannels = esp32_i2s_txchannels, .i2s_txsamplerate = esp32_i2s_txsamplerate, .i2s_txdatawidth = esp32_i2s_txdatawidth, .i2s_send = esp32_i2s_send, @@ -567,6 +604,8 @@ static void i2s_buf_free(struct esp32_i2s_s *priv, flags = spin_lock_irqsave(&priv->lock); bfcontainer->apb = NULL; + bfcontainer->buf = NULL; + bfcontainer->nbytes = 0; bfcontainer->flink = priv->bf_freelist; priv->bf_freelist = bfcontainer; @@ -600,6 +639,9 @@ static int i2s_buf_initialize(struct esp32_i2s_s *priv) { int ret; + priv->tx.carry.bytes = 0; + priv->tx.carry.value = 0; + priv->bf_freelist = NULL; ret = nxsem_init(&priv->bufsem, 0, 0); @@ -700,9 +742,15 @@ static int i2s_txdma_setup(struct esp32_i2s_s *priv, { struct ap_buffer_s *apb; struct esp32_dmadesc_s *outlink; - uintptr_t samp; - apb_samp_t nbytes; + uint8_t *samp; + apb_samp_t samp_size; + size_t carry_size; uint32_t bytes_queued; + uint32_t data_copied; + uint8_t *buf; + uint8_t padding; + irqstate_t flags; + int ret = OK; DEBUGASSERT(bfcontainer && bfcontainer->apb); @@ -711,27 +759,110 @@ static int i2s_txdma_setup(struct esp32_i2s_s *priv, /* Get the transfer information, accounting for any data offset */ - samp = (uintptr_t)&apb->samp[apb->curbyte]; - nbytes = apb->nbytes - apb->curbyte; + const apb_samp_t sample_size = 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; + + /* 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 + */ + + padding = priv->channels == 1 ? + ((priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ? + I2S_DATA_BIT_WIDTH_16BIT : I2S_DATA_BIT_WIDTH_32BIT) / 8) : 0; + + padding += priv->data_width != I2S_DATA_BIT_WIDTH_16BIT ? + (priv->data_width != I2S_DATA_BIT_WIDTH_32BIT ? 1 : 0) : 0; + + /* Copy audio data into internal buffer */ + + bfcontainer->buf = (uint8_t *)calloc(bfcontainer->nbytes, 1); + + data_copied = 0; + buf = bfcontainer->buf + padding; + + if (priv->tx.carry.bytes) + { + 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); + } + + /* 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 + */ + + 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; + } + } + else + { + memcpy(buf, samp, samp_size - (data_copied + carry_size)); + buf += samp_size - (data_copied + carry_size); + samp += samp_size - (data_copied + carry_size); + data_copied += samp_size - (data_copied + carry_size); + } + + /* If the audio buffer's size is not a multiple of the sample size, + * it's necessary to carry the remaining bytes that are part of what + * would be the last sample on this buffer. These bytes will then be + * saved and inserted at the beginning of the next DMA buffer to + * rebuild the sample correctly. + */ + + priv->tx.carry.bytes = carry_size; + + if (priv->tx.carry.bytes) + { + memcpy((uint8_t *)&priv->tx.carry.value, samp, priv->tx.carry.bytes); + } + + /* Release our reference on the audio buffer. This may very likely + * cause the audio buffer to be freed. + */ + + apb_free(bfcontainer->apb); /* Configure DMA stream */ - bytes_queued = esp32_dma_init_with_padding(outlink, I2S_DMADESC_NUM, - (uint8_t *)samp, nbytes, - &priv->stuff); + bytes_queued = esp32_dma_init(outlink, I2S_DMADESC_NUM, + bfcontainer->buf, bfcontainer->nbytes); - if (bytes_queued != nbytes) + if (bytes_queued != bfcontainer->nbytes) { i2serr("Failed to enqueue I2S buffer (%d bytes of %d)\n", - bytes_queued, (uint32_t)nbytes); + bytes_queued, (uint32_t)bfcontainer->nbytes); return bytes_queued; } + flags = spin_lock_irqsave(&priv->lock); + /* Add the buffer container to the end of the TX pending queue */ sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend); - return OK; + /* Trigger DMA transfer if no transmission is in progress */ + + ret = i2s_txdma_start(priv); + + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; } #endif /* I2S_HAVE_TX */ @@ -891,11 +1022,9 @@ static void i2s_tx_worker(void *arg) bfcontainer->callback(&priv->dev, bfcontainer->apb, bfcontainer->arg, bfcontainer->result); - /* Release our reference on the audio buffer. This may very likely - * cause the audio buffer to be freed. - */ + /* Release the internal buffer used by the DMA outlink */ - apb_free(bfcontainer->apb); + free(bfcontainer->buf); /* And release the buffer container */ @@ -947,7 +1076,7 @@ static void i2s_configure(struct esp32_i2s_s *priv) 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); + priv->config->dout_outsig, 0, 0); } /* TODO: repeat above function for RX channel */ @@ -961,7 +1090,7 @@ static void i2s_configure(struct esp32_i2s_s *priv) esp32_gpiowrite(priv->config->ws_pin, 1); esp32_configgpio(priv->config->ws_pin, INPUT_FUNCTION_3); esp32_gpio_matrix_out(priv->config->ws_pin, - priv->config->ws_out_insig, 0, 0); + priv->config->ws_out_insig, 0, 0); esp32_gpiowrite(priv->config->bclk_pin, 1); esp32_configgpio(priv->config->bclk_pin, INPUT_FUNCTION_3); @@ -977,12 +1106,12 @@ static void i2s_configure(struct esp32_i2s_s *priv) esp32_gpiowrite(priv->config->ws_pin, 1); esp32_configgpio(priv->config->ws_pin, INPUT_FUNCTION_3); esp32_gpio_matrix_out(priv->config->ws_pin, - priv->config->ws_in_insig, 0, 0); + priv->config->ws_in_insig, 0, 0); esp32_gpiowrite(priv->config->bclk_pin, 1); esp32_configgpio(priv->config->bclk_pin, INPUT_FUNCTION_3); esp32_gpio_matrix_out(priv->config->bclk_pin, - priv->config->bclk_in_insig, 0, 0); + priv->config->bclk_in_insig, 0, 0); } } else @@ -1000,22 +1129,22 @@ static void i2s_configure(struct esp32_i2s_s *priv) esp32_configgpio(priv->config->mclk_pin, OUTPUT_FUNCTION_2); esp32_gpio_matrix_out(priv->config->mclk_pin, - SIG_GPIO_OUT_IDX, 0, 0); + SIG_GPIO_OUT_IDX, 0, 0); if (priv->config->mclk_pin == 0) { putreg32(priv->config->is_apll ? - 0xfff6 : (is_i2s0 ? 0xfff0 : 0xffff), PIN_CTRL); + 0xfff6 : (is_i2s0 ? 0xfff0 : 0xffff), PIN_CTRL); } else if (priv->config->mclk_pin == 1) { putreg32(priv->config->is_apll ? - 0xf6f6 : (is_i2s0 ? 0xf0f0 : 0xf0ff), PIN_CTRL); + 0xf6f6 : (is_i2s0 ? 0xf0f0 : 0xf0ff), PIN_CTRL); } else { putreg32(priv->config->is_apll ? - 0xff66 : (is_i2s0 ? 0xff00 : 0xff0f), PIN_CTRL); + 0xff66 : (is_i2s0 ? 0xff00 : 0xff0f), PIN_CTRL); } } @@ -1026,12 +1155,12 @@ static void i2s_configure(struct esp32_i2s_s *priv) esp32_gpiowrite(priv->config->ws_pin, 1); esp32_configgpio(priv->config->ws_pin, OUTPUT_FUNCTION_3); esp32_gpio_matrix_out(priv->config->ws_pin, - priv->config->ws_in_outsig, 0, 0); + priv->config->ws_in_outsig, 0, 0); esp32_gpiowrite(priv->config->bclk_pin, 1); esp32_configgpio(priv->config->bclk_pin, OUTPUT_FUNCTION_3); esp32_gpio_matrix_out(priv->config->bclk_pin, - priv->config->bclk_in_outsig, 0, 0); + priv->config->bclk_in_outsig, 0, 0); } else { @@ -1081,8 +1210,8 @@ static void i2s_configure(struct esp32_i2s_s *priv) /* Set TX data width */ - esp32_i2s_txdatawidth((struct i2s_dev_s *)priv, - priv->config->data_width); + priv->data_width = priv->config->data_width; + i2s_set_datawidth(priv); /* Set I2S tx chan mode */ @@ -1140,156 +1269,71 @@ static void i2s_configure(struct esp32_i2s_s *priv) (priv->config->rate * priv->config->mclk_multiple)); - esp32_i2s_txsamplerate((struct i2s_dev_s *)priv, priv->config->rate); + priv->rate = priv->config->rate; + i2s_set_clock(priv); } /* TODO: check for rx enabled flag */ } /**************************************************************************** - * Name: i2s_tx_channel_start + * Name: i2s_set_datawidth * * Description: - * Start TX channel for the I2S port + * Set the I2S TX data width. * * Input Parameters: * priv - Initialized I2S device structure. * * Returned Value: - * None + * Returns the resulting data width * ****************************************************************************/ -#ifdef I2S_HAVE_TX -static void i2s_tx_channel_start(struct esp32_i2s_s *priv) +static uint32_t i2s_set_datawidth(struct esp32_i2s_s *priv) { - /* 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); - - /* 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); - - /* 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); - - /* Enable DMA interruption */ - - up_enable_irq(priv->config->irq); - - modifyreg32(I2S_INT_ENA_REG(priv->config->port), UINT32_MAX, - I2S_OUT_EOF_INT_ENA); - - /* Enable DMA operation mode */ - - modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_DSCR_EN); - - /* Unset the DMA outlink */ - - putreg32(0, I2S_OUT_LINK_REG(priv->config->port)); - - i2sinfo("Started TX channel of port %d\n", priv->config->port); -} -#endif /* I2S_HAVE_TX */ - -/**************************************************************************** - * Name: esp32_i2s_interrupt - * - * Description: - * Common I2S DMA interrupt handler - * - * Input Parameters: - * arg - i2s controller private data - * - * Returned Value: - * Standard interrupt return value. - * - ****************************************************************************/ + modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), + I2S_TX_BITS_MOD_M, FIELD_TO_VALUE(I2S_TX_BITS_MOD, + priv->data_width)); -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; + /* Set TX FIFO operation mode */ - uint32_t status = getreg32(I2S_INT_ST_REG(priv->config->port)); + 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)); - putreg32(UINT32_MAX, I2S_INT_CLR_REG(priv->config->port)); + /* I2S TX MSB right enable */ - if (status & I2S_OUT_EOF_INT_ST) + if (priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT) { - cur = (struct esp32_dmadesc_s *) - getreg32(I2S_OUT_EOF_DES_ADDR_REG(priv->config->port)); - - /* Schedule completion of the transfer to occur on the worker thread */ - - i2s_tx_schedule(priv, cur); + modifyreg32(I2S_CONF_REG(priv->config->port), 0, I2S_TX_MSB_RIGHT); } - - return 0; -} - -/**************************************************************************** - * Name: esp32_i2s_mclkfrequency - * - * Description: - * Set the master clock frequency. Usually, the MCLK is a multiple of the - * sample rate. Most of the audio codecs require setting specific MCLK - * frequency according to the sample rate. - * - * Input Parameters: - * dev - Device-specific state data - * frequency - The I2S master clock's frequency - * - * Returned Value: - * Returns the resulting master clock or a negated errno value on failure. - * - ****************************************************************************/ - -static uint32_t esp32_i2s_mclkfrequency(struct i2s_dev_s *dev, - uint32_t frequency) -{ - struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - - /* Check if the master clock frequency is beyond the highest possible - * value and return an error. - */ - - if (frequency >= (I2S_LL_BASE_CLK / 2)) + else { - return -EINVAL; + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_MSB_RIGHT, 0); } - priv->mclk_freq = frequency; - - return frequency; + return priv->data_width; } /**************************************************************************** - * Name: esp32_i2s_txsamplerate + * Name: i2s_set_clock * * Description: - * Set the I2S TX sample rate. NOTE: This will have no effect if (1) the - * driver does not support an I2S transmitter or if (2) the sample rate is - * driven by the I2S frame clock. This may also have unexpected side- - * effects of the TX sample is coupled with the RX sample rate. + * Set the I2S TX sample rate by adjusting I2S clock. * * Input Parameters: - * dev - Device-specific state data - * rate - The I2S sample rate in samples (not bits) per second + * priv - Initialized I2S device structure. * * Returned Value: * Returns the resulting bitrate * ****************************************************************************/ -static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) +static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) { - struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + uint32_t rate; uint32_t bclk; uint32_t mclk; uint16_t bclk_div; @@ -1319,7 +1363,7 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) if (priv->config->role == I2S_ROLE_MASTER) { - bclk = rate * priv->config->total_slot * priv->data_width; + bclk = priv->rate * priv->config->total_slot * priv->data_width; mclk = priv->mclk_freq; bclk_div = mclk / bclk; } @@ -1328,7 +1372,7 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) /* For slave mode, mclk >= bclk * 8, so fix bclk_div to 2 first */ bclk_div = 8; - bclk = rate * priv->config->total_slot * priv->data_width; + bclk = priv->rate * priv->config->total_slot * priv->data_width; mclk = bclk * bclk_div; } @@ -1355,7 +1399,7 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) * 1.0 - 1.0 / (63.0 * 2) = 125.0 / 126.0 */ - if (decimal > 125.0 / 126.0) + if (decimal > 125.0f / 126.0f) { mclk_div++; } @@ -1409,8 +1453,248 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) bclk_div); rate = bclk / (float)(priv->config->total_slot * priv->data_width); + return rate; +} + +/**************************************************************************** + * Name: i2s_tx_channel_start + * + * Description: + * Start TX channel for the I2S port + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef I2S_HAVE_TX +static void i2s_tx_channel_start(struct esp32_i2s_s *priv) +{ + if (priv->tx_started) + { + i2swarn("TX channel of port %d was previously started\n", + priv->config->port); + return; + } + + /* 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); + + /* 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); + + /* 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); + + /* Enable DMA interrupt */ + + up_enable_irq(priv->config->irq); + + modifyreg32(I2S_INT_ENA_REG(priv->config->port), UINT32_MAX, + I2S_OUT_EOF_INT_ENA); + + /* Enable DMA operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG(priv->config->port), 0, I2S_DSCR_EN); + + /* Unset the DMA outlink */ + + putreg32(0, I2S_OUT_LINK_REG(priv->config->port)); + + priv->tx_started = true; + + i2sinfo("Started TX channel of port %d\n", priv->config->port); +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: i2s_tx_channel_stop + * + * Description: + * Stop TX channel for the I2S port + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef I2S_HAVE_TX +static void i2s_tx_channel_stop(struct esp32_i2s_s *priv) +{ + if (!priv->tx_started) + { + i2swarn("TX channel of port %d was previously stopped\n", + priv->config->port); + return; + } + + /* Stop TX channel */ + + modifyreg32(I2S_CONF_REG(priv->config->port), I2S_TX_START, 0); + + /* Stop outlink */ + + modifyreg32(I2S_OUT_LINK_REG(priv->config->port), I2S_OUTLINK_START, + I2S_OUTLINK_STOP); + + /* Disable DMA interrupt */ + + modifyreg32(I2S_INT_ENA_REG(priv->config->port), UINT32_MAX, 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: esp32_i2s_interrupt + * + * Description: + * Common I2S DMA interrupt handler + * + * Input Parameters: + * 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)); + + 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 to occur on the worker thread */ + + i2s_tx_schedule(priv, cur); + } + + return 0; +} + +/**************************************************************************** + * Name: esp32_i2s_mclkfrequency + * + * Description: + * Set the master clock frequency. Usually, the MCLK is a multiple of the + * sample rate. Most of the audio codecs require setting specific MCLK + * frequency according to the sample rate. + * + * Input Parameters: + * dev - Device-specific state data + * frequency - The I2S master clock's frequency + * + * Returned Value: + * Returns the resulting master clock or a negated errno value on failure. + * + ****************************************************************************/ + +static uint32_t esp32_i2s_mclkfrequency(struct i2s_dev_s *dev, + uint32_t frequency) +{ + struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; + + /* Check if the master clock frequency is beyond the highest possible + * value and return an error. + */ + + if (frequency >= (I2S_LL_BASE_CLK / 2)) + { + return -EINVAL; + } + + priv->mclk_freq = frequency; + + return frequency; +} + +/**************************************************************************** + * Name: esp32_i2s_txchannels + * + * Description: + * Set the I2S TX 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. + * + ****************************************************************************/ + +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) + { + return -EINVAL; + } + + priv->channels = channels; + return OK; +} + +/**************************************************************************** + * Name: esp32_i2s_txsamplerate + * + * Description: + * Set the I2S TX sample rate. NOTE: This will have no effect if (1) the + * driver does not support an I2S transmitter or if (2) the sample rate is + * driven by the I2S frame clock. This may also have unexpected side- + * effects of the TX sample is coupled with the 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 + * + ****************************************************************************/ + +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); + priv->rate = rate; + rate = i2s_set_clock(priv); + + i2s_tx_channel_start(priv); + return rate; } @@ -1426,7 +1710,7 @@ static uint32_t esp32_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) * width - The I2S data with in bits. * * Returned Value: - * Returns the resulting bitrate + * Returns the resulting data width * ****************************************************************************/ @@ -1434,28 +1718,13 @@ static uint32_t esp32_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; - modifyreg32(I2S_SAMPLE_RATE_CONF_REG(priv->config->port), - I2S_TX_BITS_MOD_M, FIELD_TO_VALUE(I2S_TX_BITS_MOD, bits)); + i2s_tx_channel_stop(priv); priv->data_width = bits; - /* Set TX FIFO operation mode */ + i2s_set_datawidth(priv); - 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 */ - - 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); - } + i2s_tx_channel_start(priv); return bits; } @@ -1489,14 +1758,39 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, { struct esp32_i2s_s *priv = (struct esp32_i2s_s *)dev; struct esp32_buffer_s *bfcontainer; - irqstate_t flags; int ret = OK; + uint32_t nbytes; + uint32_t nsamp; /* Check audio buffer data size */ - if ((apb->nbytes - apb->curbyte) > - (ESP32_DMA_DATALEN_MAX * (I2S_DMADESC_NUM - 1))) + 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 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 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; } @@ -1519,23 +1813,15 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, /* Initialize the buffer container structure */ - bfcontainer->callback = (void *)callback; + bfcontainer->callback = callback; bfcontainer->timeout = timeout; bfcontainer->arg = arg; bfcontainer->apb = apb; + bfcontainer->nbytes = nbytes; bfcontainer->result = -EBUSY; - flags = spin_lock_irqsave(&priv->lock); - ret = i2s_txdma_setup(priv, bfcontainer); - if (ret != OK) - { - goto errout_with_buf; - } - - ret = i2s_txdma_start(priv); - if (ret != OK) { goto errout_with_buf; @@ -1543,11 +1829,8 @@ static int esp32_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes); i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte], - apb->nbytes - apb->curbyte); + apb->nbytes - apb->curbyte); - /* Trigger DMA transfer */ - - spin_unlock_irqrestore(&priv->lock, flags); i2s_exclsem_give(priv); return OK; @@ -1586,7 +1869,7 @@ static int esp32_i2sdma_setup(struct esp32_i2s_s *priv) priv->cpu = up_cpu_index(); priv->cpuint = esp32_setup_irq(priv->cpu, priv->config->periph, - 1, ESP32_CPUINT_LEVEL); + 1, ESP32_CPUINT_LEVEL); if (priv->cpuint < 0) { i2serr("Failed to allocate a CPU interrupt.\n"); @@ -1598,8 +1881,8 @@ static int esp32_i2sdma_setup(struct esp32_i2s_s *priv) { i2serr("Couldn't attach IRQ to handler.\n"); esp32_teardown_irq(priv->cpu, - priv->config->periph, - priv->cpuint); + priv->config->periph, + priv->cpuint); return ret; } @@ -1646,6 +1929,8 @@ struct i2s_dev_s *esp32_i2sbus_initialize(int port) return NULL; } + priv->tx_started = false; + flags = spin_lock_irqsave(&priv->lock); nxsem_init(&priv->exclsem, 0, 1); diff --git a/boards/xtensa/esp32/esp32-devkitc/configs/audio/defconfig b/boards/xtensa/esp32/esp32-devkitc/configs/audio/defconfig index 0d6fa51d51..d9d634bd01 100644 --- a/boards/xtensa/esp32/esp32-devkitc/configs/audio/defconfig +++ b/boards/xtensa/esp32/esp32-devkitc/configs/audio/defconfig @@ -24,6 +24,7 @@ CONFIG_ARCH_STACKDUMP=y CONFIG_ARCH_XTENSA=y CONFIG_AUDIO=y CONFIG_AUDIOUTILS_MMLPARSER_LIB=y +CONFIG_AUDIO_BUFFER_NUMBYTES=4092 CONFIG_AUDIO_CS4344=y CONFIG_AUDIO_EXCLUDE_BALANCE=y CONFIG_AUDIO_EXCLUDE_FFORWARD=y @@ -31,7 +32,7 @@ CONFIG_AUDIO_EXCLUDE_TONE=y CONFIG_AUDIO_EXCLUDE_VOLUME=y CONFIG_AUDIO_I2S=y CONFIG_AUDIO_I2SCHAR=y -CONFIG_AUDIO_NUM_BUFFERS=4 +CONFIG_AUDIO_NUM_BUFFERS=6 CONFIG_BOARDCTL_ROMDISK=y CONFIG_BOARD_LOOPSPERMSEC=16717 CONFIG_BUILTIN=y
