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;

Reply via email to