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


Reply via email to