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 6317f6d59766795ce218e3da936f7832f65db780 Author: Tiago Medicci Serrano <[email protected]> AuthorDate: Thu Nov 3 11:22:27 2022 -0300 esp32s2/i2s: use internal buffer to handle multiple audio formats --- arch/xtensa/src/esp32/esp32_i2s.c | 16 +- arch/xtensa/src/esp32s2/esp32s2_i2s.c | 651 +++++++++++++++++++++++----------- 2 files changed, 449 insertions(+), 218 deletions(-) diff --git a/arch/xtensa/src/esp32/esp32_i2s.c b/arch/xtensa/src/esp32/esp32_i2s.c index 7923e295b9..abb34f0439 100644 --- a/arch/xtensa/src/esp32/esp32_i2s.c +++ b/arch/xtensa/src/esp32/esp32_i2s.c @@ -213,10 +213,6 @@ 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 @@ -714,8 +710,8 @@ static int i2s_txdma_start(struct esp32_i2s_s *priv) ****************************************************************************/ #ifdef I2S_HAVE_TX -static int i2s_txdma_setup(struct esp32_i2s_s *priv, - struct esp32_buffer_s *bfcontainer) +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; @@ -1316,8 +1312,6 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) uint16_t bclk_div; uint32_t sclk; uint32_t mclk_div; - int ma; - int mb; int denominator; int numerator; uint32_t regval; @@ -1363,8 +1357,6 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) freq_diff = abs((int)sclk - (int)(mclk * mclk_div)); - ma = 0; - mb = 0; denominator = 1; numerator = 0; @@ -1387,8 +1379,8 @@ static uint32_t i2s_set_clock(struct esp32_i2s_s *priv) for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) { int b = (int)(a * (freq_diff / (double)mclk) + 0.5); - ma = freq_diff * a; - mb = mclk * b; + int ma = freq_diff * a; + int mb = mclk * b; if (ma == mb) { denominator = a; diff --git a/arch/xtensa/src/esp32s2/esp32s2_i2s.c b/arch/xtensa/src/esp32s2/esp32s2_i2s.c index 24e8753b95..6260e912e3 100644 --- a/arch/xtensa/src/esp32s2/esp32s2_i2s.c +++ b/arch/xtensa/src/esp32s2/esp32s2_i2s.c @@ -93,6 +93,10 @@ # define I2S_RX_ENABLED 0 #endif +#ifndef ALIGN_UP +# define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) +#endif + /* Debug ********************************************************************/ #ifdef CONFIG_DEBUG_I2S_INFO @@ -206,9 +210,23 @@ struct esp32s2_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 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. + */ + +struct esp32s2_buffer_carry_s +{ + uint32_t value; + size_t bytes; +}; + /* This structure describes the state of one receiver or transmitter * transport. */ @@ -219,6 +237,10 @@ struct esp32s2_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 esp32s2_buffer_carry_s carry; }; /* The state of the one I2S peripheral */ @@ -226,10 +248,7 @@ struct esp32s2_transport_s struct esp32s2_i2s_s { struct i2s_dev_s dev; /* Externally visible I2S interface */ - mutex_t lock; /* 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 */ + mutex_t lock; /* Ensures mutually exclusive access */ int cpuint; /* I2S interrupt ID */ uint8_t cpu; /* CPU ID */ @@ -237,12 +256,15 @@ struct esp32s2_i2s_s const struct esp32s2_i2s_config_s *config; -#ifdef I2S_HAVE_TX - struct esp32s2_transport_s tx; /* TX transport state */ + 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 */ - /* Stuff var to fill DMA buffer if not word-aligned */ +#ifdef I2S_HAVE_TX + struct esp32s2_transport_s tx; /* TX transport state */ - uint32_t stuff; + bool tx_started; /* TX channel started */ #endif /* I2S_HAVE_TX */ /* Pre-allocated pool of buffer containers */ @@ -280,24 +302,30 @@ static int i2s_buf_initialize(struct esp32s2_i2s_s *priv); /* DMA support */ #ifdef I2S_HAVE_TX -static int i2s_txdma_setup(struct esp32s2_i2s_s *priv, - struct esp32s2_buffer_s *bfcontainer); -static void i2s_tx_worker(void *arg); -static void i2s_tx_schedule(struct esp32s2_i2s_s *priv, - struct esp32s2_dmadesc_s *outlink); +static int i2s_txdma_setup(struct esp32s2_i2s_s *priv, + struct esp32s2_buffer_s *bfcontainer); +static void i2s_tx_worker(void *arg); +static void i2s_tx_schedule(struct esp32s2_i2s_s *priv, + struct esp32s2_dmadesc_s *outlink); #endif /* I2S_HAVE_TX */ /* I2S methods (and close friends) */ -static uint32_t esp32s2_i2s_mclkfrequency(struct i2s_dev_s *dev, - uint32_t frequency); -static uint32_t esp32s2_i2s_txsamplerate(struct i2s_dev_s *dev, - uint32_t rate); -static uint32_t esp32s2_i2s_txdatawidth(struct i2s_dev_s *dev, int bits); -static int esp32s2_i2s_send(struct i2s_dev_s *dev, - struct ap_buffer_s *apb, - i2s_callback_t callback, void *arg, - uint32_t timeout); +static uint32_t i2s_set_datawidth(struct esp32s2_i2s_s *priv); +static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv); +static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv); +static void i2s_tx_channel_stop(struct esp32s2_i2s_s *priv); +static int i2s_txchannels(struct i2s_dev_s *dev, + uint8_t channels); +static uint32_t i2s_mclkfrequency(struct i2s_dev_s *dev, + uint32_t frequency); +static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, + uint32_t rate); +static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits); +static int i2s_send(struct i2s_dev_s *dev, + struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, + uint32_t timeout); /**************************************************************************** * Private Data @@ -305,10 +333,11 @@ static int esp32s2_i2s_send(struct i2s_dev_s *dev, static const struct i2s_ops_s g_i2sops = { - .i2s_txsamplerate = esp32s2_i2s_txsamplerate, - .i2s_txdatawidth = esp32s2_i2s_txdatawidth, - .i2s_send = esp32s2_i2s_send, - .i2s_mclkfrequency = esp32s2_i2s_mclkfrequency, + .i2s_txchannels = i2s_txchannels, + .i2s_txsamplerate = i2s_txsamplerate, + .i2s_txdatawidth = i2s_txdatawidth, + .i2s_send = i2s_send, + .i2s_mclkfrequency = i2s_mclkfrequency, }; #ifdef CONFIG_ESP32S2_I2S @@ -472,6 +501,8 @@ static void i2s_buf_free(struct esp32s2_i2s_s *priv, flags = enter_critical_section(); bfcontainer->apb = NULL; + bfcontainer->buf = NULL; + bfcontainer->nbytes = 0; bfcontainer->flink = priv->bf_freelist; priv->bf_freelist = bfcontainer; @@ -505,6 +536,9 @@ static int i2s_buf_initialize(struct esp32s2_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); @@ -604,9 +638,14 @@ static int i2s_txdma_setup(struct esp32s2_i2s_s *priv, { struct ap_buffer_s *apb; struct esp32s2_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; + irqstate_t flags; + int ret = OK; DEBUGASSERT(bfcontainer && bfcontainer->apb); @@ -615,27 +654,89 @@ static int i2s_txdma_setup(struct esp32s2_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 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 % bytes_per_sample; + + /* Allocate the current audio buffer considering the remaining bytes + * carried from the last upper half audio buffer. + */ + + bfcontainer->buf = (uint8_t *)calloc(bfcontainer->nbytes, 1); + + data_copied = 0; + buf = bfcontainer->buf; + + /* Copy the remaining bytes from the last audio buffer to the current + * audio buffer. The remaining bytes are part of a sample that was split + * between the last and the current audio buffer. Also, copy the bytes + * from that split sample that are on the current buffer to the internal + * buffer. + */ + + 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, (bytes_per_sample - priv->tx.carry.bytes)); + buf += (bytes_per_sample - priv->tx.carry.bytes); + samp += (bytes_per_sample - priv->tx.carry.bytes); + data_copied += (bytes_per_sample - priv->tx.carry.bytes); + } + + /* Copy the upper half buffer to the internal buffer considering that + * the current upper half buffer may not contain a complete sample at + * the end of the buffer (and those bytes needs to be carried to the + * next audio buffer). + */ + + 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(&priv->tx.carry.value, samp, priv->tx.carry.bytes); + } /* Configure DMA stream */ - bytes_queued = esp32s2_dma_init_with_padding(outlink, I2S_DMADESC_NUM, - (uint8_t *)samp, nbytes, - &priv->stuff); + bytes_queued = esp32s2_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); + i2serr("Failed to enqueue I2S buffer " + "(%" PRIu32 " bytes of %" PRIu32 ")\n", + bytes_queued, bfcontainer->nbytes); return bytes_queued; } + flags = enter_critical_section(); + /* 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); + + leave_critical_section(flags); + + return ret; } #endif /* I2S_HAVE_TX */ @@ -795,7 +896,11 @@ 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 + /* Release the internal buffer used by the DMA outlink */ + + free(bfcontainer->buf); + + /* Release our reference on the audio buffer. This may very likely * cause the audio buffer to be freed. */ @@ -836,9 +941,9 @@ static void i2s_configure(struct esp32s2_i2s_s *priv) if (!(getreg32(I2S_CLKM_CONF_REG) & I2S_CLK_EN)) { i2sinfo("Enabling I2S port clock...\n"); - modifyreg32(I2S_CLKM_CONF_REG, 0, I2S_CLK_EN); modifyreg32(I2S_CLKM_CONF_REG, I2S_CLK_SEL_M, FIELD_TO_VALUE(I2S_CLK_SEL, 2)); + modifyreg32(I2S_CLKM_CONF_REG, 0, I2S_CLK_EN); putreg32(0, I2S_CONF2_REG); } @@ -964,14 +1069,14 @@ static void i2s_configure(struct esp32s2_i2s_s *priv) modifyreg32(I2S_CONF_REG, I2S_TX_SLAVE_MOD, 0); } - /* Congfigure TX chan bit, audio data bit and mono mode. - * On ESP32S2, sample_bit should equals to data_bit + /* Configure TX chan bit, audio data bit and mono mode. + * On ESP32-S2, sample_bit should equals to data_bit */ /* Set TX data width */ - esp32s2_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 */ @@ -1020,16 +1125,179 @@ static void i2s_configure(struct esp32s2_i2s_s *priv) modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_TX_FIFO_MOD_FORCE_EN); - esp32s2_i2s_mclkfrequency((struct i2s_dev_s *)priv, + i2s_mclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate * priv->config->mclk_multiple)); - esp32s2_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_set_datawidth + * + * Description: + * Set the I2S TX data width. + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * Returns the resulting data width + * + ****************************************************************************/ + +static uint32_t i2s_set_datawidth(struct esp32s2_i2s_s *priv) +{ + modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BITS_MOD_M, + FIELD_TO_VALUE(I2S_TX_BITS_MOD, priv->data_width)); + + /* Set TX FIFO operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG, 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 */ + + modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_RIGHT); + + return priv->data_width; +} + +/**************************************************************************** + * Name: i2s_set_clock + * + * Description: + * Set the I2S TX sample rate by adjusting I2S clock. + * + * Input Parameters: + * priv - Initialized I2S device structure. + * + * Returned Value: + * Returns the resulting bitrate + * + ****************************************************************************/ + +static uint32_t i2s_set_clock(struct esp32s2_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; + + /* TODO: provide APLL clock support */ + + sclk = I2S_LL_BASE_CLK; + + /* fmclk = bck_div * fbclk = fsclk / (mclk_div + b / a) + * mclk_div is the I2S clock divider's integral value + * b is the fraction clock divider's numerator value + * a is the fraction clock divider's denominator value + */ + + if (priv->config->role == I2S_ROLE_MASTER) + { + bclk = priv->rate * priv->config->total_slot * priv->data_width; + mclk = priv->mclk_freq; + bclk_div = mclk / bclk; + } + else + { + /* For slave mode, mclk >= bclk * 8, so fix bclk_div to 2 first */ + + bclk_div = 8; + bclk = priv->rate * priv->config->total_slot * priv->data_width; + mclk = bclk * bclk_div; + } + + /* Calculate the nearest integer value of the I2S clock divider */ + + mclk_div = sclk / mclk; + + 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)); + + denominator = 1; + numerator = 0; + + if (freq_diff) + { + float decimal = freq_diff / (float)mclk; + + /* Carry bit if the decimal is greater than + * 1.0 - 1.0 / (63.0 * 2) = 125.0 / 126.0 + */ + + if (decimal > 125.0f / 126.0f) + { + mclk_div++; + } + else + { + uint32_t min = UINT32_MAX; + + for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) + { + int b = (int)(a * (freq_diff / (double)mclk) + 0.5); + int ma = freq_diff * a; + int mb = mclk * b; + if (ma == mb) + { + denominator = a; + numerator = b; + break; + } + + if (abs((mb - ma)) < min) + { + denominator = a; + numerator = b; + min = abs(mb - ma); + } + } + } + } + + i2sinfo("Clock register: [mclk] %" PRIu32 " Hz [numerator] %d " + "[denominator] %d\n", mclk, numerator, denominator); + + regval = getreg32(I2S_CLKM_CONF_REG); + regval &= ~I2S_CLKM_DIV_NUM_M; + regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_NUM, mclk_div); + regval &= ~I2S_CLKM_DIV_B_M; + regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_B, numerator); + regval &= ~I2S_CLKM_DIV_A_M; + regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_A, denominator); + putreg32(regval, I2S_CLKM_CONF_REG); + + /* Set I2S tx bck div num */ + + modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BCK_DIV_NUM_M, + FIELD_TO_VALUE(I2S_TX_BCK_DIV_NUM, bclk_div)); + + /* Returns the actual sample rate */ + + bclk = sclk / (float)((mclk_div + numerator / (float)denominator) * + bclk_div); + rate = bclk / (float)(priv->config->total_slot * priv->data_width); + + return rate; +} + /**************************************************************************** * Name: i2s_tx_channel_start * @@ -1047,6 +1315,12 @@ static void i2s_configure(struct esp32s2_i2s_s *priv) #ifdef I2S_HAVE_TX static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv) { + if (priv->tx_started) + { + i2swarn("TX channel was previously started\n"); + return; + } + /* Reset the TX channel */ modifyreg32(I2S_CONF_REG, 0, I2S_TX_RESET); @@ -1055,14 +1329,18 @@ static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv) /* Reset the DMA operation */ modifyreg32(I2S_LC_CONF_REG, 0, I2S_OUT_RST); + modifyreg32(I2S_LC_CONF_REG, 0, I2S_AHBM_FIFO_RST); + modifyreg32(I2S_LC_CONF_REG, 0, I2S_AHBM_RST); modifyreg32(I2S_LC_CONF_REG, I2S_OUT_RST, 0); + modifyreg32(I2S_LC_CONF_REG, I2S_AHBM_FIFO_RST, 0); + modifyreg32(I2S_LC_CONF_REG, I2S_AHBM_RST, 0); /* Reset TX FIFO */ modifyreg32(I2S_CONF_REG, 0, I2S_TX_FIFO_RESET); modifyreg32(I2S_CONF_REG, I2S_TX_FIFO_RESET, 0); - /* Enable DMA interruption */ + /* Enable DMA interrupt */ up_enable_irq(priv->config->irq); @@ -1076,12 +1354,61 @@ static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv) putreg32(0, I2S_OUT_LINK_REG); + priv->tx_started = true; + i2sinfo("Started TX channel on I2S0\n"); } #endif /* I2S_HAVE_TX */ /**************************************************************************** - * Name: esp32s2_i2s_interrupt + * 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 esp32s2_i2s_s *priv) +{ + if (!priv->tx_started) + { + i2swarn("TX channel was previously stopped\n"); + return; + } + + /* Stop TX channel */ + + modifyreg32(I2S_CONF_REG, I2S_TX_START, 0); + + /* Stop outlink */ + + modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_START, I2S_OUTLINK_STOP); + + /* Disable DMA interrupt */ + + modifyreg32(I2S_INT_ENA_REG, UINT32_MAX, 0); + + /* Disable DMA operation mode */ + + modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0); + + up_disable_irq(priv->config->irq); + + priv->tx_started = false; + + i2sinfo("Stopped TX channel on I2S0\n"); +} +#endif /* I2S_HAVE_TX */ + +/**************************************************************************** + * Name: i2s_interrupt * * Description: * Common I2S DMA interrupt handler @@ -1094,7 +1421,7 @@ static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv) * ****************************************************************************/ -static int esp32s2_i2s_interrupt(int irq, void *context, void *arg) +static int i2s_interrupt(int irq, void *context, void *arg) { struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)arg; struct esp32s2_dmadesc_s *cur = NULL; @@ -1116,7 +1443,7 @@ static int esp32s2_i2s_interrupt(int irq, void *context, void *arg) } /**************************************************************************** - * Name: esp32s2_i2s_mclkfrequency + * Name: i2s_mclkfrequency * * Description: * Set the master clock frequency. Usually, the MCLK is a multiple of the @@ -1132,8 +1459,7 @@ static int esp32s2_i2s_interrupt(int irq, void *context, void *arg) * ****************************************************************************/ -static uint32_t esp32s2_i2s_mclkfrequency(struct i2s_dev_s *dev, - uint32_t frequency) +static uint32_t i2s_mclkfrequency(struct i2s_dev_s *dev, uint32_t frequency) { struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev; @@ -1152,148 +1478,76 @@ static uint32_t esp32s2_i2s_mclkfrequency(struct i2s_dev_s *dev, } /**************************************************************************** - * Name: esp32s2_i2s_txsamplerate + * Name: i2s_txchannels * * 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 number of channels. * * Input Parameters: * dev - Device-specific state data - * rate - The I2S sample rate in samples (not bits) per second + * channels - The I2S numbers of channels * * Returned Value: - * Returns the resulting bitrate + * OK on success; a negated errno value on failure. * ****************************************************************************/ -static uint32_t esp32s2_i2s_txsamplerate(struct i2s_dev_s *dev, - uint32_t rate) +static int i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels) { struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev; - 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; - - /* TODO: provide APLL clock support */ - - /* Disable APLL clock, I2S module will using PLL_D2_CLK(160M) as source - * clock. - */ - - modifyreg32(I2S_CLKM_CONF_REG, I2S_CLK_EN, 0); - sclk = I2S_LL_BASE_CLK; - - /* fmclk = bck_div * fbclk = fsclk / (mclk_div + b / a) - * mclk_div is the I2S clock divider's integral value - * b is the fraction clock divider's numerator value - * a is the fraction clock divider's denominator value - */ - if (priv->config->role == I2S_ROLE_MASTER) - { - bclk = rate * priv->config->total_slot * priv->data_width; - mclk = priv->mclk_freq; - bclk_div = mclk / bclk; - } - else + if (channels != 1 && channels != 2) { - /* 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; - mclk = bclk * bclk_div; + return -EINVAL; } - /* Calculate the nearest integer value of the I2S clock divider */ - - mclk_div = sclk / mclk; - - 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)); - - denominator = 1; - numerator = 0; - - if (freq_diff) - { - float decimal = freq_diff / (float)mclk; - - /* Carry bit if the decimal is greater than - * 1.0 - 1.0 / (63.0 * 2) = 125.0 / 126.0 - */ + i2s_tx_channel_stop(priv); - if (decimal > 125.0f / 126.0f) - { - mclk_div++; - } - else - { - uint32_t min = UINT32_MAX; + priv->channels = channels; - for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) - { - int b = (int)(a * (freq_diff / (double)mclk) + 0.5); - int ma = freq_diff * a; - int mb = mclk * b; - if (ma == mb) - { - denominator = a; - numerator = b; - break; - } + modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_TX_DMA_EQUAL, + priv->channels == 1 ? I2S_TX_DMA_EQUAL : 0); - if (abs((mb - ma)) < min) - { - denominator = a; - numerator = b; - min = abs(mb - ma); - } - } - } - } + i2s_tx_channel_start(priv); - i2sinfo("Clock register: [mclk] %" PRIu32 " Hz [numerator] %d " - "[denominator] %d\n", mclk, numerator, denominator); + return OK; +} - regval = getreg32(I2S_CLKM_CONF_REG); - regval &= ~I2S_CLKM_DIV_NUM_M; - regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_NUM, mclk_div); - regval &= ~I2S_CLKM_DIV_B_M; - regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_B, numerator); - regval &= ~I2S_CLKM_DIV_A_M; - regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_A, denominator); - putreg32(regval, I2S_CLKM_CONF_REG); +/**************************************************************************** + * Name: 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 + * + ****************************************************************************/ - /* Set I2S tx bck div num */ +static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate) +{ + struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev; - modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BCK_DIV_NUM_M, - FIELD_TO_VALUE(I2S_TX_BCK_DIV_NUM, bclk_div)); + i2s_tx_channel_stop(priv); - /* Returns the actual sample rate */ + priv->rate = rate; - bclk = sclk / (float)((mclk_div + numerator / (float)denominator) * - bclk_div); - rate = bclk / (float)(priv->config->total_slot * priv->data_width); + rate = i2s_set_clock(priv); - priv->rate = rate; + i2s_tx_channel_start(priv); return rate; } /**************************************************************************** - * Name: esp32s2_i2s_txdatawidth + * Name: i2s_txdatawidth * * Description: * Set the I2S TX data width. The TX bitrate is determined by @@ -1304,42 +1558,27 @@ static uint32_t esp32s2_i2s_txsamplerate(struct i2s_dev_s *dev, * width - The I2S data with in bits. * * Returned Value: - * Returns the resulting bitrate + * Returns the resulting data width * ****************************************************************************/ -static uint32_t esp32s2_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) +static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits) { struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev; - modifyreg32(I2S_SAMPLE_RATE_CONF_REG, 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 */ - - modifyreg32(I2S_FIFO_CONF_REG, 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_set_datawidth(priv); - if (priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT) - { - modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_RIGHT); - } - else - { - modifyreg32(I2S_CONF_REG, I2S_TX_MSB_RIGHT, 0); - } + i2s_tx_channel_start(priv); return bits; } /**************************************************************************** - * Name: esp32s2_i2s_send + * Name: i2s_send * * Description: * Send a block of data on I2S. @@ -1361,20 +1600,29 @@ static uint32_t esp32s2_i2s_txdatawidth(struct i2s_dev_s *dev, int bits) * ****************************************************************************/ -static int esp32s2_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, - i2s_callback_t callback, void *arg, - uint32_t timeout) +static int i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, + i2s_callback_t callback, void *arg, uint32_t timeout) { struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev; struct esp32s2_buffer_s *bfcontainer; - irqstate_t flags; int ret = OK; + uint32_t nbytes; + + /* Check audio buffer data size from the upper half. If the buffer + * size is not a multiple of the data width, the remaining bytes + * must be sent along with the next audio buffer. + */ + + nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes; - /* Check audio buffer data size */ + nbytes -= (nbytes % (priv->data_width / 8)); - if ((apb->nbytes - apb->curbyte) > - (ESP32S2_DMA_DATALEN_MAX * (I2S_DMADESC_NUM - 1))) + if (nbytes > (ESP32S2_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 - (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM)); return -EFBIG; } @@ -1401,19 +1649,11 @@ static int esp32s2_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, bfcontainer->timeout = timeout; bfcontainer->arg = arg; bfcontainer->apb = apb; + bfcontainer->nbytes = nbytes; bfcontainer->result = -EBUSY; - flags = enter_critical_section(); - 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; @@ -1423,9 +1663,6 @@ static int esp32s2_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb, i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte], apb->nbytes - apb->curbyte); - /* Trigger DMA transfer */ - - leave_critical_section(flags); nxmutex_unlock(&priv->lock); return OK; @@ -1437,7 +1674,7 @@ errout_with_buf: } /**************************************************************************** - * Name: esp32s2_i2sdma_setup + * Name: i2sdma_setup * * Description: * Configure the DMA for the I2S peripheral @@ -1452,7 +1689,7 @@ errout_with_buf: * ****************************************************************************/ -static int esp32s2_i2sdma_setup(struct esp32s2_i2s_s *priv) +static int i2sdma_setup(struct esp32s2_i2s_s *priv) { int ret; @@ -1471,7 +1708,7 @@ static int esp32s2_i2sdma_setup(struct esp32s2_i2s_s *priv) return priv->cpuint; } - ret = irq_attach(priv->config->irq, esp32s2_i2s_interrupt, priv); + ret = irq_attach(priv->config->irq, i2s_interrupt, priv); if (ret != OK) { i2serr("Couldn't attach IRQ to handler.\n"); @@ -1506,6 +1743,8 @@ struct i2s_dev_s *esp32s2_i2sbus_initialize(void) priv = &esp32s2_i2s0_priv; + priv->tx_started = false; + flags = enter_critical_section(); nxmutex_init(&priv->lock); @@ -1520,7 +1759,7 @@ struct i2s_dev_s *esp32s2_i2sbus_initialize(void) goto err; } - ret = esp32s2_i2sdma_setup(priv); + ret = i2sdma_setup(priv); if (ret < 0) { goto err;
