You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2022/11/21 15:46:54 UTC

[incubator-nuttx] 01/02: esp32s2/i2s: implement I2S receiver module

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 d492a5b09269449f6bfff762f1e62194222eef60
Author: Tiago Medicci Serrano <ti...@espressif.com>
AuthorDate: Tue Nov 15 11:58:51 2022 -0300

    esp32s2/i2s: implement I2S receiver module
    
    - Add ioctl method to enable allocating the apb buffer.
    - Add RX methods to set data width, sample rate, channels and
    for receiving data from the I2S peripheral.
    - Update the i2schar defconfig to enable the I2S receiver.
    - Add nxlooper defconfig to enable testing the RX interface.
    - Add specific bindings on ESP32-S2  bringup to enable nxlooper
    to work without the need of any specific codec.
---
 arch/xtensa/src/esp32s2/Kconfig                    |    2 +-
 arch/xtensa/src/esp32s2/esp32s2_i2s.c              | 1342 +++++++++++++++++---
 .../esp32s2/common/src/esp32s2_board_i2sdev.c      |   82 +-
 .../esp32s2-saola-1/configs/i2schar/defconfig      |    7 +-
 .../configs/{i2schar => nxlooper}/defconfig        |   14 +-
 .../esp32s2/esp32s2-saola-1/src/esp32s2-saola-1.h  |    7 +-
 .../esp32s2/esp32s2-saola-1/src/esp32s2_bringup.c  |   17 +-
 7 files changed, 1254 insertions(+), 217 deletions(-)

diff --git a/arch/xtensa/src/esp32s2/Kconfig b/arch/xtensa/src/esp32s2/Kconfig
index 395442b062..cf7069d09b 100644
--- a/arch/xtensa/src/esp32s2/Kconfig
+++ b/arch/xtensa/src/esp32s2/Kconfig
@@ -301,7 +301,7 @@ config ESP32S2_I2S_WSPIN
 	range 0 46 if ESP32S2_I2S_ROLE_SLAVE
 
 config ESP32S2_I2S_DINPIN
-	int "I2S DOUT pin"
+	int "I2S DIN pin"
 	depends on ESP32S2_I2S_RX
 	default 37
 	range 0 46
diff --git a/arch/xtensa/src/esp32s2/esp32s2_i2s.c b/arch/xtensa/src/esp32s2/esp32s2_i2s.c
index e42103538d..3c1bff0529 100644
--- a/arch/xtensa/src/esp32s2/esp32s2_i2s.c
+++ b/arch/xtensa/src/esp32s2/esp32s2_i2s.c
@@ -89,6 +89,7 @@
 
 #ifdef CONFIG_ESP32S2_I2S_RX
 #  define I2S_RX_ENABLED 1
+#  define I2S_HAVE_RX 1
 #else
 #  define I2S_RX_ENABLED 0
 #endif
@@ -154,7 +155,6 @@ struct esp32s2_i2s_config_s
   uint32_t total_slot;        /* Total slot number */
 
   bool is_apll;               /* Select APLL as the source clock */
-  uint32_t mclk_multiple;     /* The multiple of mclk to the sample rate */
 
   bool tx_en;                 /* Is TX enabled? */
   bool rx_en;                 /* Is RX enabled? */
@@ -202,17 +202,17 @@ struct esp32s2_buffer_s
 {
   struct esp32s2_buffer_s *flink; /* Supports a singly linked list */
 
-  /* The associated DMA outlink */
+  /* The associated DMA in/outlink */
 
-  struct esp32s2_dmadesc_s dma_outlink[I2S_DMADESC_NUM];
+  struct esp32s2_dmadesc_s dma_link[I2S_DMADESC_NUM];
 
-  i2s_callback_t callback;        /* DMA completion callback */
-  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 */
+  i2s_callback_t callback;      /* DMA completion callback */
+  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,
@@ -256,10 +256,11 @@ struct esp32s2_i2s_s
 
   const struct esp32s2_i2s_config_s *config;
 
-  uint32_t          mclk_freq;    /* I2S actual master clock */
-  uint32_t          channels;     /* Audio channels (1:mono or 2:stereo) */
-  uint32_t          rate;         /* I2S actual configured sample-rate */
-  uint32_t          data_width;   /* I2S actual configured data_width */
+  uint32_t    mclk_freq;      /* I2S actual master clock */
+  uint32_t    mclk_multiple;  /* The multiple of mclk to the sample rate */
+  uint32_t    channels;       /* Audio channels (1:mono or 2:stereo) */
+  uint32_t    rate;           /* I2S actual configured sample-rate */
+  uint32_t    data_width;     /* I2S actual configured data_width */
 
 #ifdef I2S_HAVE_TX
   struct esp32s2_transport_s tx;  /* TX transport state */
@@ -267,6 +268,12 @@ struct esp32s2_i2s_s
   bool tx_started;              /* TX channel started */
 #endif /* I2S_HAVE_TX */
 
+#ifdef I2S_HAVE_RX
+  struct esp32s2_transport_s rx;  /* RX transport state */
+
+  bool rx_started;              /* RX channel started */
+#endif /* I2S_HAVE_RX */
+
   /* Pre-allocated pool of buffer containers */
 
   sem_t bufsem;                         /* Buffer wait semaphore */
@@ -304,23 +311,42 @@ static void i2s_tx_schedule(struct esp32s2_i2s_s *priv,
                             struct esp32s2_dmadesc_s *outlink);
 #endif /* I2S_HAVE_TX */
 
+#ifdef I2S_HAVE_RX
+static int  i2s_rxdma_setup(struct esp32s2_i2s_s *priv,
+                            struct esp32s2_buffer_s *bfcontainer);
+static void i2s_rx_worker(void *arg);
+static void i2s_rx_schedule(struct esp32s2_i2s_s *priv,
+                            struct esp32s2_dmadesc_s *outlink);
+#endif /* I2S_HAVE_RX */
+
 /* I2S methods (and close friends) */
 
 static uint32_t i2s_set_datawidth(struct esp32s2_i2s_s *priv);
 static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv);
+static uint32_t i2s_mclkfrequency(struct i2s_dev_s *dev, uint32_t frequency);
+static int      i2s_ioctl(struct i2s_dev_s *dev, int cmd, unsigned long arg);
+
+#ifdef I2S_HAVE_TX
 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 int      i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels);
+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,
+static int      i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
                          i2s_callback_t callback, void *arg,
                          uint32_t timeout);
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+static void     i2s_rx_channel_start(struct esp32s2_i2s_s *priv);
+static void     i2s_rx_channel_stop(struct esp32s2_i2s_s *priv);
+static int      i2s_rxchannels(struct i2s_dev_s *dev, uint8_t channels);
+static uint32_t i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate);
+static uint32_t i2s_rxdatawidth(struct i2s_dev_s *dev, int bits);
+static int      i2s_receive(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
+                            i2s_callback_t callback, void *arg,
+                            uint32_t timeout);
+#endif /* I2S_HAVE_RX */
 
 /****************************************************************************
  * Private Data
@@ -328,10 +354,21 @@ static int      i2s_send(struct i2s_dev_s *dev,
 
 static const struct i2s_ops_s g_i2sops =
 {
+  #ifdef I2S_HAVE_TX
   .i2s_txchannels     = i2s_txchannels,
   .i2s_txsamplerate   = i2s_txsamplerate,
   .i2s_txdatawidth    = i2s_txdatawidth,
   .i2s_send           = i2s_send,
+  #endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  .i2s_rxchannels     = i2s_rxchannels,
+  .i2s_rxsamplerate   = i2s_rxsamplerate,
+  .i2s_rxdatawidth    = i2s_rxdatawidth,
+  .i2s_receive        = i2s_receive,
+#endif /* I2S_HAVE_RX */
+
+  .i2s_ioctl          = i2s_ioctl,
   .i2s_mclkfrequency  = i2s_mclkfrequency,
 };
 
@@ -347,7 +384,6 @@ static const struct esp32s2_i2s_config_s esp32s2_i2s0_config =
   .data_width       = CONFIG_ESP32S2_I2S_DATA_BIT_WIDTH,
   .rate             = CONFIG_ESP32S2_I2S_SAMPLE_RATE,
   .total_slot       = 2,
-  .mclk_multiple    = I2S_MCLK_MULTIPLE_384,
   .tx_en            = I2S_TX_ENABLED,
   .rx_en            = I2S_RX_ENABLED,
 #ifdef CONFIG_ESP32S2_I2S_MCLK
@@ -409,7 +445,7 @@ static struct esp32s2_i2s_s esp32s2_i2s0_priv =
  *   free list
  *
  * Input Parameters:
- *   priv - I2S state instance
+ *   priv - Initialized I2S device structure.
  *
  * Returned Value:
  *   A non-NULL pointer to the allocate buffer container on success; NULL if
@@ -457,7 +493,7 @@ static struct esp32s2_buffer_s *i2s_buf_allocate(struct esp32s2_i2s_s *priv)
  *   Free buffer container by adding it to the head of the free list
  *
  * Input Parameters:
- *   priv - I2S state instance
+ *   priv - Initialized I2S device structure.
  *   bfcontainer - The buffer container to be freed
  *
  * Returned Value:
@@ -498,7 +534,7 @@ static void i2s_buf_free(struct esp32s2_i2s_s *priv,
  *   pre-allocated buffer containers to the free list
  *
  * Input Parameters:
- *   priv - I2S state instance
+ *   priv - Initialized I2S device structure.
  *
  * Returned Value:
  *   OK on success; A negated errno value on failure.
@@ -511,8 +547,10 @@ static void i2s_buf_free(struct esp32s2_i2s_s *priv,
 
 static int i2s_buf_initialize(struct esp32s2_i2s_s *priv)
 {
+#ifdef I2S_HAVE_TX
   priv->tx.carry.bytes = 0;
   priv->tx.carry.value = 0;
+#endif /* I2S_HAVE_TX */
 
   priv->bf_freelist = NULL;
 
@@ -529,10 +567,10 @@ static int i2s_buf_initialize(struct esp32s2_i2s_s *priv)
  *
  * Description:
  *   Initiate the next TX DMA transfer. The DMA outlink was previously bound
- *   so it is safe to start the next DMA transfer at interruption level.
+ *   so it is safe to start the next DMA transfer at interrupt level.
  *
  * Input Parameters:
- *   priv - I2S state instance
+ *   priv - Initialized I2S device structure.
  *
  * Returned Value:
  *   OK on success; a negated errno value on failure
@@ -571,7 +609,7 @@ static int i2s_txdma_start(struct esp32s2_i2s_s *priv)
 
   modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_ADDR_M,
               FIELD_TO_VALUE(I2S_OUTLINK_ADDR,
-              (uintptr_t) bfcontainer->dma_outlink));
+              (uintptr_t) bfcontainer->dma_link));
 
   modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_STOP, I2S_OUTLINK_START);
 
@@ -583,6 +621,68 @@ static int i2s_txdma_start(struct esp32s2_i2s_s *priv)
 }
 #endif /* I2S_HAVE_TX */
 
+/****************************************************************************
+ * Name: i2s_rxdma_start
+ *
+ * Description:
+ *   Initiate the next RX DMA transfer. Assuming the DMA inlink is already
+ *   bound, it's safe to start the next DMA transfer in an interrupt context.
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure
+ *
+ * Assumptions:
+ *   Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static int i2s_rxdma_start(struct esp32s2_i2s_s *priv)
+{
+  struct esp32s2_buffer_s *bfcontainer;
+
+  /* If there is already an active transmission in progress, then bail
+   * returning success.
+   */
+
+  if (!sq_empty(&priv->rx.act))
+    {
+      return OK;
+    }
+
+  /* If there are no pending transfer, then bail returning success */
+
+  if (sq_empty(&priv->rx.pend))
+    {
+      return OK;
+    }
+
+  bfcontainer = (struct esp32s2_buffer_s *)sq_remfirst(&priv->rx.pend);
+
+  /* If there isn't already an active transmission in progress,
+   * then start it.
+   */
+
+  modifyreg32(I2S_RXEOF_NUM_REG, I2S_RX_EOF_NUM_M,
+              FIELD_TO_VALUE(I2S_RX_EOF_NUM, bfcontainer->nbytes));
+
+  modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_ADDR_M,
+              FIELD_TO_VALUE(I2S_INLINK_ADDR,
+              (uintptr_t) bfcontainer->dma_link));
+
+  modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_STOP, I2S_INLINK_START);
+
+  modifyreg32(I2S_CONF_REG, 0, I2S_RX_START);
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
+
+  return OK;
+}
+#endif /* I2S_HAVE_RX */
+
 /****************************************************************************
  * Name: i2s_txdma_setup
  *
@@ -590,7 +690,8 @@ static int i2s_txdma_start(struct esp32s2_i2s_s *priv)
  *   Setup the next TX DMA transfer
  *
  * Input Parameters:
- *   priv - I2S state instance
+ *   priv - Initialized I2S device structure.
+ *   bfcontainer - The buffer container to be set up
  *
  * Returned Value:
  *   OK on success; a negated errno value on failure
@@ -604,21 +705,21 @@ static int i2s_txdma_start(struct esp32s2_i2s_s *priv)
 static int i2s_txdma_setup(struct esp32s2_i2s_s *priv,
                            struct esp32s2_buffer_s *bfcontainer)
 {
-  struct ap_buffer_s *apb;
-  struct esp32s2_dmadesc_s *outlink;
-  uint8_t *samp;
-  apb_samp_t samp_size;
+  int ret = OK;
   size_t carry_size;
   uint32_t bytes_queued;
   uint32_t data_copied;
-  uint8_t *buf;
+  struct ap_buffer_s *apb;
+  struct esp32s2_dmadesc_s *outlink;
+  apb_samp_t samp_size;
   irqstate_t flags;
-  int ret = OK;
+  uint8_t *buf;
+  uint8_t *samp;
 
   DEBUGASSERT(bfcontainer && bfcontainer->apb);
 
   apb = bfcontainer->apb;
-  outlink = bfcontainer->dma_outlink;
+  outlink = bfcontainer->dma_link;
 
   /* Get the transfer information, accounting for any data offset */
 
@@ -679,6 +780,12 @@ static int i2s_txdma_setup(struct esp32s2_i2s_s *priv,
       memcpy(&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 = esp32s2_dma_init(outlink, I2S_DMADESC_NUM,
@@ -708,6 +815,67 @@ static int i2s_txdma_setup(struct esp32s2_i2s_s *priv,
 }
 #endif /* I2S_HAVE_TX */
 
+/****************************************************************************
+ * Name: i2s_rxdma_setup
+ *
+ * Description:
+ *   Setup the next RX DMA transfer
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   bfcontainer - The buffer container to be set up
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure
+ *
+ * Assumptions:
+ *   Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static int i2s_rxdma_setup(struct esp32s2_i2s_s *priv,
+                           struct esp32s2_buffer_s *bfcontainer)
+{
+  int ret = OK;
+  struct esp32s2_dmadesc_s *inlink;
+  uint32_t bytes_queued;
+  irqstate_t flags;
+
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  inlink = bfcontainer->dma_link;
+
+  /* Configure DMA stream */
+
+  bytes_queued = esp32s2_dma_init(inlink, I2S_DMADESC_NUM,
+                                  bfcontainer->apb->samp,
+                                  bfcontainer->nbytes);
+
+  if (bytes_queued != bfcontainer->nbytes)
+    {
+      i2serr("Failed to enqueue I2S buffer "
+             "(%" PRIu32 " bytes of %" PRIu32 ")\n",
+             bytes_queued, bfcontainer->nbytes);
+      return bytes_queued;
+    }
+
+  flags = enter_critical_section();
+
+  /* Add the buffer container to the end of the RX pending queue */
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.pend);
+
+  /* Trigger DMA transfer if no transmission is in progress */
+
+  ret = i2s_rxdma_start(priv);
+
+  leave_critical_section(flags);
+
+  return ret;
+}
+#endif /* I2S_HAVE_RX */
+
 /****************************************************************************
  * Name: i2s_tx_schedule
  *
@@ -716,16 +884,14 @@ static int i2s_txdma_setup(struct esp32s2_i2s_s *priv,
  *   the working thread.
  *
  * Input Parameters:
- *   handle - The DMA handler
- *   arg - A pointer to the chip select struction
- *   result - The result of the DMA transfer
+ *   priv - Initialized I2S device structure.
+ *   outlink - DMA outlink descriptor that triggered the interrupt.
  *
  * Returned Value:
  *   None
  *
  * Assumptions:
  *   - Interrupts are disabled
- *   - The TX timeout has been canceled.
  *
  ****************************************************************************/
 
@@ -758,7 +924,7 @@ static void i2s_tx_schedule(struct esp32s2_i2s_s *priv,
 
       /* Find the last descriptor of the current buffer container */
 
-      bfdesc = bfcontainer->dma_outlink;
+      bfdesc = bfcontainer->dma_link;
       while (!(bfdesc->ctrl & DMA_CTRL_EOF))
         {
           DEBUGASSERT(bfdesc->next);
@@ -809,6 +975,105 @@ static void i2s_tx_schedule(struct esp32s2_i2s_s *priv,
 }
 #endif /* I2S_HAVE_TX */
 
+/****************************************************************************
+ * Name: i2s_rx_schedule
+ *
+ * Description:
+ *   An RX DMA completion has occurred.  Schedule processing on
+ *   the working thread.
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   inlink - DMA inlink descriptor that triggered the interrupt.
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static void i2s_rx_schedule(struct esp32s2_i2s_s *priv,
+                            struct esp32s2_dmadesc_s *inlink)
+{
+  struct esp32s2_buffer_s *bfcontainer;
+  struct esp32s2_dmadesc_s *bfdesc;
+  int ret;
+
+  /* Upon entry, the transfer(s) that just completed are the ones in the
+   * priv->rx.act queue.
+   */
+
+  /* Move all entries from the rx.act queue to the rx.done queue */
+
+  if (!sq_empty(&priv->rx.act))
+    {
+      /* Remove the next buffer container from the rx.act list */
+
+      bfcontainer = (struct esp32s2_buffer_s *)sq_peek(&priv->rx.act);
+
+      /* Check if the DMA descriptor that generated an EOF interrupt is the
+       * last descriptor of the current buffer container's DMA inlink.
+       * REVISIT: what to do if we miss syncronization and the descriptor
+       * that generated the interrupt is different from the expected (the
+       * oldest of the list containing active transmissions)?
+       */
+
+      /* Find the last descriptor of the current buffer container */
+
+      bfdesc = bfcontainer->dma_link;
+      while (!(bfdesc->ctrl & DMA_CTRL_EOF))
+        {
+          DEBUGASSERT(bfdesc->next);
+          bfdesc = bfdesc->next;
+        }
+
+      if (bfdesc == inlink)
+        {
+          sq_remfirst(&priv->rx.act);
+
+          /* Report the result of the transfer */
+
+          bfcontainer->result = OK;
+
+          /* Add the completed buffer container to the tail of the rx.done
+           * queue
+           */
+
+          sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.done);
+
+          /* Check if the DMA is IDLE */
+
+          if (sq_empty(&priv->rx.act))
+            {
+              /* Then start the next DMA. */
+
+              i2s_rxdma_start(priv);
+            }
+        }
+
+      /* If the worker has completed running, then reschedule the working
+       * thread.
+       */
+
+      if (work_available(&priv->rx.work))
+        {
+          /* Schedule the RX DMA done processing to occur on the worker
+           * thread.
+           */
+
+          ret = work_queue(HPWORK, &priv->rx.work, i2s_rx_worker, priv, 0);
+          if (ret != 0)
+            {
+              i2serr("ERROR: Failed to queue RX work: %d\n", ret);
+            }
+        }
+    }
+}
+#endif /* I2S_HAVE_RX */
+
 /****************************************************************************
  * Name: i2s_tx_worker
  *
@@ -868,6 +1133,70 @@ static void i2s_tx_worker(void *arg)
 
       free(bfcontainer->buf);
 
+      /* And release the buffer container */
+
+      i2s_buf_free(priv, bfcontainer);
+    }
+}
+#endif /* I2S_HAVE_TX */
+
+/****************************************************************************
+ * Name: i2s_rx_worker
+ *
+ * Description:
+ *   RX transfer done worker
+ *
+ * Input Parameters:
+ *   arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static void i2s_rx_worker(void *arg)
+{
+  struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)arg;
+  struct esp32s2_buffer_s *bfcontainer;
+  irqstate_t flags;
+
+  DEBUGASSERT(priv);
+
+  /* When the transfer was started, the active buffer containers were removed
+   * from the rx.pend queue and saved in the rx.act queue. We get here when
+   * the DMA is finished.
+   *
+   * In any case, the buffer containers in rx.act will be moved to the end
+   * of the rx.done queue and rx.act will be emptied before this worker is
+   * started.
+   *
+   */
+
+  i2sinfo("rx.act.head=%p rx.done.head=%p\n",
+           priv->rx.act.head, priv->rx.done.head);
+
+  /* Process each buffer in the rx.done queue */
+
+  while (sq_peek(&priv->rx.done) != NULL)
+    {
+      /* Remove the buffer container from the rx.done queue.  NOTE that
+       * interrupts must be disabled to do this because the rx.done queue is
+       * also modified from the interrupt level.
+       */
+
+      flags = enter_critical_section();
+      bfcontainer = (struct esp32s2_buffer_s *)sq_remfirst(&priv->rx.done);
+      leave_critical_section(flags);
+
+      bfcontainer->apb->nbytes = (getreg32(I2S_RXEOF_NUM_REG));
+
+      /* Perform the RX transfer done callback */
+
+      DEBUGASSERT(bfcontainer && bfcontainer->callback);
+      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.
        */
@@ -879,7 +1208,7 @@ static void i2s_tx_worker(void *arg)
       i2s_buf_free(priv, bfcontainer);
     }
 }
-#endif /* I2S_HAVE_TX */
+#endif /* I2S_HAVE_RX */
 
 /****************************************************************************
  * Name: i2s_configure
@@ -929,7 +1258,14 @@ static void i2s_configure(struct esp32s2_i2s_s *priv)
                               priv->config->dout_outsig, 0, 0);
     }
 
-  /* TODO: repeat above function for RX channel */
+  /* Enable RX channel */
+
+  if (priv->config->din_pin != I2S_GPIO_UNUSED)
+    {
+      esp32s2_configgpio(priv->config->din_pin, INPUT_FUNCTION_2);
+      esp32s2_gpio_matrix_in(priv->config->din_pin,
+                             priv->config->din_insig, 0);
+    }
 
   if (priv->config->role == I2S_ROLE_SLAVE)
     {
@@ -981,41 +1317,50 @@ static void i2s_configure(struct esp32s2_i2s_s *priv)
                                   priv->config->mclk_out_sig, 0, 0);
         }
 
-      if (priv->config->rx_en && !priv->config->tx_en)
+      if (priv->config->tx_en && !priv->config->rx_en)
         {
-          /* For "rx + master" mode, select RX signal index for ws and bck */
+          /* For "tx + master" mode, select TX signal index for ws and bck */
 
           esp32s2_gpiowrite(priv->config->ws_pin, 1);
           esp32s2_configgpio(priv->config->ws_pin, OUTPUT_FUNCTION_2);
           esp32s2_gpio_matrix_out(priv->config->ws_pin,
-                                  priv->config->ws_in_outsig, 0, 0);
+                                  priv->config->ws_out_outsig, 0, 0);
 
           esp32s2_gpiowrite(priv->config->bclk_pin, 1);
           esp32s2_configgpio(priv->config->bclk_pin, OUTPUT_FUNCTION_2);
           esp32s2_gpio_matrix_out(priv->config->bclk_pin,
-                                  priv->config->bclk_in_outsig, 0, 0);
+                                  priv->config->bclk_out_outsig, 0, 0);
         }
       else
         {
-          /* For "tx + rx + master" or "tx + master" mode, select TX signal
+          /* For "tx + rx + master" or "rx + master" mode, select RX signal
            * index for ws and bck.
            */
 
           esp32s2_gpiowrite(priv->config->ws_pin, 1);
           esp32s2_configgpio(priv->config->ws_pin, OUTPUT_FUNCTION_2);
           esp32s2_gpio_matrix_out(priv->config->ws_pin,
-                                  priv->config->ws_out_outsig, 0, 0);
+                                  priv->config->ws_in_outsig, 0, 0);
 
           esp32s2_gpiowrite(priv->config->bclk_pin, 1);
           esp32s2_configgpio(priv->config->bclk_pin, OUTPUT_FUNCTION_2);
           esp32s2_gpio_matrix_out(priv->config->bclk_pin,
-                                  priv->config->bclk_out_outsig, 0, 0);
+                                  priv->config->bclk_in_outsig, 0, 0);
         }
     }
 
-  /* TODO: share BCLK and WS if in full-duplex mode */
+  /* Share BCLK and WS if in full-duplex mode */
+
+  if (priv->config->tx_en && priv->config->rx_en)
+    {
+      modifyreg32(I2S_CONF_REG, 0, I2S_SIG_LOOPBACK);
+    }
+  else
+    {
+      modifyreg32(I2S_CONF_REG, I2S_SIG_LOOPBACK, 0);
+    }
 
-  /* Configure the hardware to apply STD format */
+  /* Configure the TX module */
 
   if (priv->config->tx_en)
     {
@@ -1034,11 +1379,22 @@ static void i2s_configure(struct esp32s2_i2s_s *priv)
         }
       else
         {
-          modifyreg32(I2S_CONF_REG, I2S_TX_SLAVE_MOD, 0);
+          /* Since BCLK and WS are shared, only TX or RX can be master. In
+           * this case, force TX as slave to avoid conflict of clock signal.
+           */
+
+          if (priv->config->rx_en)
+            {
+              modifyreg32(I2S_CONF_REG, 0, I2S_TX_SLAVE_MOD);
+            }
+          else
+            {
+              modifyreg32(I2S_CONF_REG, I2S_TX_SLAVE_MOD, 0);
+            }
         }
 
       /* Configure TX chan bit, audio data bit and mono mode.
-       * On ESP32-S2, sample_bit should equals to data_bit
+       * On ESP32-S2, sample_bit should equals to data_bit.
        */
 
       /* Set TX data width */
@@ -1093,22 +1449,138 @@ static void i2s_configure(struct esp32s2_i2s_s *priv)
 
       modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_TX_FIFO_MOD_FORCE_EN);
 
-      i2s_mclkfrequency((struct i2s_dev_s *)priv,
-                                (priv->config->rate *
-                                priv->config->mclk_multiple));
+      /* The default value for the master clock frequency (MCLK frequency)
+       * can be set from the sample rate multiplied by a fixed value, known
+       * as MCLK multiplier. This multiplier, however, should be divisible
+       * by the number of bytes from a sample, i.e, for 24 bits, the
+       * multiplier should be divisible by 3. NOTE: the MCLK frequency can
+       * be adjusted on runtime, so this value remains valid only if the
+       * upper half does not implement the `i2s_mclkfrequency` method.
+       */
+
+      if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT)
+        {
+          priv->mclk_multiple = I2S_MCLK_MULTIPLE_384;
+        }
+      else
+        {
+          priv->mclk_multiple = I2S_MCLK_MULTIPLE_256;
+        }
+
+      i2s_mclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate *
+                        priv->mclk_multiple));
 
       priv->rate = priv->config->rate;
       i2s_set_clock(priv);
     }
 
-  /* TODO: check for rx enabled flag */
+  /* Configure the RX module */
+
+  if (priv->config->rx_en)
+    {
+      /* Reset I2S RX module */
+
+      modifyreg32(I2S_CONF_REG, 0, I2S_RX_RESET);
+      modifyreg32(I2S_CONF_REG, I2S_RX_RESET, 0);
+      modifyreg32(I2S_LC_CONF_REG, 0, I2S_IN_RST);
+      modifyreg32(I2S_LC_CONF_REG, I2S_IN_RST, 0);
+
+      /* Enable/disable I2S RX slave mode */
+
+      if (priv->config->role == I2S_ROLE_SLAVE)
+        {
+          modifyreg32(I2S_CONF_REG, 0, I2S_RX_SLAVE_MOD);
+        }
+      else
+        {
+          modifyreg32(I2S_CONF_REG, I2S_RX_SLAVE_MOD, 0);
+        }
+
+      /* Congfigure RX chan bit, audio data bit and mono mode.
+       * On ESP32-S2, sample_bit should equals to data_bit.
+       */
+
+      /* Set RX data width */
+
+      priv->data_width = priv->config->data_width;
+      i2s_set_datawidth(priv);
+
+      /* Set I2S RX chan mode */
+
+      modifyreg32(I2S_CONF_CHAN_REG, I2S_RX_CHAN_MOD_M, 0);
+
+      /* Enable/disable RX MSB shift, the data will be read at the first
+       * BCK clock.
+       */
+
+      if (priv->config->bit_shift)
+        {
+          modifyreg32(I2S_CONF_REG, 0, I2S_RX_MSB_SHIFT);
+        }
+      else
+        {
+          modifyreg32(I2S_CONF_REG, I2S_RX_MSB_SHIFT, 0);
+        }
+
+      /* Configure RX WS signal width. Set to to enable receiver in PCM
+       * standard mode.
+       */
+
+      if (priv->config->ws_width == 1)
+        {
+          modifyreg32(I2S_CONF_REG, 0, I2S_RX_SHORT_SYNC);
+        }
+      else
+        {
+          modifyreg32(I2S_CONF_REG, I2S_RX_SHORT_SYNC, 0);
+        }
+
+      /* Set I2S RX right channel first */
+
+      if (priv->config->ws_pol == 1)
+        {
+          modifyreg32(I2S_CONF_REG, 0, I2S_RX_RIGHT_FIRST);
+        }
+      else
+        {
+          modifyreg32(I2S_CONF_REG, I2S_RX_RIGHT_FIRST, 0);
+        }
+
+      /* I2S RX fifo module force enable */
+
+      modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_RX_FIFO_MOD_FORCE_EN);
+
+      /* The default value for the master clock frequency (MCLK frequency)
+       * can be set from the sample rate multiplied by a fixed value, known
+       * as MCLK multiplier. This multiplier, however, should be divisible
+       * by the number of bytes from a sample, i.e, for 24 bits, the
+       * multiplier should be divisible by 3. NOTE: the MCLK frequency can
+       * be adjusted on runtime, so this value remains valid only if the
+       * upper half does not implement the `i2s_mclkfrequency` method.
+       */
+
+      if (priv->config->data_width == I2S_DATA_BIT_WIDTH_24BIT)
+        {
+          priv->mclk_multiple = I2S_MCLK_MULTIPLE_384;
+        }
+      else
+        {
+          priv->mclk_multiple = I2S_MCLK_MULTIPLE_256;
+        }
+
+      i2s_mclkfrequency((struct i2s_dev_s *)priv, (priv->config->rate *
+                        priv->mclk_multiple));
+
+      priv->rate = priv->config->rate;
+      i2s_set_clock(priv);
+    }
 }
 
 /****************************************************************************
  * Name: i2s_set_datawidth
  *
  * Description:
- *   Set the I2S TX data width.
+ *   Set the I2S TX/RX data width.
  *
  * Input Parameters:
  *   priv - Initialized I2S device structure.
@@ -1120,19 +1592,47 @@ static void i2s_configure(struct esp32s2_i2s_s *priv)
 
 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));
+#ifdef I2S_HAVE_TX
+  if (priv->config->tx_en)
+    {
+      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 */
 
-  /* Set TX FIFO operation mode */
+      modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_RIGHT);
+    }
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  if (priv->config->rx_en)
+    {
+      modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_RX_BITS_MOD_M,
+                  FIELD_TO_VALUE(I2S_RX_BITS_MOD, priv->data_width));
 
-  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));
+      /* Set RX FIFO operation mode */
 
-  /* I2S TX MSB right enable */
+      modifyreg32(I2S_FIFO_CONF_REG, I2S_RX_FIFO_MOD_M,
+                  priv->data_width <= I2S_DATA_BIT_WIDTH_16BIT ?
+                  FIELD_TO_VALUE(I2S_RX_FIFO_MOD,
+                                 0 + priv->config->mono_en) :
+                  FIELD_TO_VALUE(I2S_RX_FIFO_MOD,
+                                 2 + priv->config->mono_en));
 
-  modifyreg32(I2S_CONF_REG, 0, I2S_TX_MSB_RIGHT);
+      /* I2S RX MSB right enable */
+
+      modifyreg32(I2S_CONF_REG, 0, I2S_RX_MSB_RIGHT);
+    }
+#endif /* I2S_HAVE_RX */
 
   return priv->data_width;
 }
@@ -1156,13 +1656,13 @@ 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;
+  uint16_t bclk_div;
 
   /* TODO: provide APLL clock support */
 
@@ -1252,10 +1752,17 @@ static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv)
   regval |= FIELD_TO_VALUE(I2S_CLKM_DIV_A, denominator);
   putreg32(regval, I2S_CLKM_CONF_REG);
 
-  /* Set I2S tx bck div num */
+  /* Set I2S TX bck div num */
 
+#ifdef I2S_HAVE_TX
   modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_TX_BCK_DIV_NUM_M,
               FIELD_TO_VALUE(I2S_TX_BCK_DIV_NUM, bclk_div));
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  modifyreg32(I2S_SAMPLE_RATE_CONF_REG, I2S_RX_BCK_DIV_NUM_M,
+              FIELD_TO_VALUE(I2S_RX_BCK_DIV_NUM, bclk_div));
+#endif /* I2S_HAVE_RX */
 
   /* Returns the actual sample rate */
 
@@ -1283,51 +1790,111 @@ static uint32_t i2s_set_clock(struct esp32s2_i2s_s *priv)
 #ifdef I2S_HAVE_TX
 static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv)
 {
-  if (priv->tx_started)
+  if (priv->config->tx_en)
     {
-      i2swarn("TX channel was previously started\n");
-      return;
-    }
+      if (priv->tx_started)
+        {
+          i2swarn("TX channel was previously started\n");
+          return;
+        }
 
-  /* Reset the TX channel */
+      /* Reset the TX channel */
 
-  modifyreg32(I2S_CONF_REG, 0, I2S_TX_RESET);
-  modifyreg32(I2S_CONF_REG, I2S_TX_RESET, 0);
+      modifyreg32(I2S_CONF_REG, 0, I2S_TX_RESET);
+      modifyreg32(I2S_CONF_REG, I2S_TX_RESET, 0);
 
-  /* Reset the DMA operation */
+      /* 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);
+      modifyreg32(I2S_LC_CONF_REG, 0, I2S_OUT_RST);
+      modifyreg32(I2S_LC_CONF_REG, I2S_OUT_RST, 0);
 
-  /* Reset TX FIFO */
+      /* Reset TX FIFO */
 
-  modifyreg32(I2S_CONF_REG, 0, I2S_TX_FIFO_RESET);
-  modifyreg32(I2S_CONF_REG, I2S_TX_FIFO_RESET, 0);
+      modifyreg32(I2S_CONF_REG, 0, I2S_TX_FIFO_RESET);
+      modifyreg32(I2S_CONF_REG, I2S_TX_FIFO_RESET, 0);
 
-  /* Enable DMA interrupt */
+      /* Enable DMA interrupt */
 
-  up_enable_irq(priv->config->irq);
+      up_enable_irq(priv->config->irq);
 
-  modifyreg32(I2S_INT_ENA_REG, UINT32_MAX, I2S_OUT_EOF_INT_ENA);
+      modifyreg32(I2S_INT_ENA_REG, 0, I2S_OUT_EOF_INT_ENA);
 
-  /* Enable DMA operation mode */
+      /* Enable DMA operation mode */
 
-  modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_DSCR_EN);
+      modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_DSCR_EN);
 
-  /* Unset the DMA outlink */
+      /* Unset the DMA outlink */
 
-  putreg32(0, I2S_OUT_LINK_REG);
+      putreg32(0, I2S_OUT_LINK_REG);
 
-  priv->tx_started = true;
+      priv->tx_started = true;
 
-  i2sinfo("Started TX channel on I2S0\n");
+      i2sinfo("Started TX channel on I2S0\n");
+    }
 }
 #endif /* I2S_HAVE_TX */
 
+/****************************************************************************
+ * Name: i2s_rx_channel_start
+ *
+ * Description:
+ *   Start RX channel for the I2S port
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static void i2s_rx_channel_start(struct esp32s2_i2s_s *priv)
+{
+  if (priv->config->rx_en)
+    {
+      if (priv->rx_started)
+        {
+          i2swarn("RX channel was previously started\n");
+          return;
+        }
+
+      /* Reset the RX channel */
+
+      modifyreg32(I2S_CONF_REG, 0, I2S_RX_RESET);
+      modifyreg32(I2S_CONF_REG, I2S_RX_RESET, 0);
+
+      /* Reset the DMA operation */
+
+      modifyreg32(I2S_LC_CONF_REG, 0, I2S_IN_RST);
+      modifyreg32(I2S_LC_CONF_REG, I2S_IN_RST, 0);
+
+      /* Reset RX FIFO */
+
+      modifyreg32(I2S_CONF_REG, 0, I2S_RX_FIFO_RESET);
+      modifyreg32(I2S_CONF_REG, I2S_RX_FIFO_RESET, 0);
+
+      /* Enable DMA interrupt */
+
+      up_enable_irq(priv->config->irq);
+
+      modifyreg32(I2S_INT_ENA_REG, 0, I2S_IN_SUC_EOF_INT_ENA);
+
+      /* Enable DMA operation mode */
+
+      modifyreg32(I2S_FIFO_CONF_REG, 0, I2S_DSCR_EN);
+
+      /* Unset the DMA inlink */
+
+      putreg32(0, I2S_IN_LINK_REG);
+
+      priv->rx_started = true;
+
+      i2sinfo("Started RX channel on I2S0\n");
+    }
+}
+#endif /* I2S_HAVE_RX */
+
 /****************************************************************************
  * Name: i2s_tx_channel_stop
  *
@@ -1345,36 +1912,89 @@ static void i2s_tx_channel_start(struct esp32s2_i2s_s *priv)
 #ifdef I2S_HAVE_TX
 static void i2s_tx_channel_stop(struct esp32s2_i2s_s *priv)
 {
-  if (!priv->tx_started)
+  if (priv->config->tx_en)
     {
-      i2swarn("TX channel was previously stopped\n");
-      return;
-    }
+      if (!priv->tx_started)
+        {
+          i2swarn("TX channel was previously stopped\n");
+          return;
+        }
 
-  /* Stop TX channel */
+      /* Stop TX channel */
 
-  modifyreg32(I2S_CONF_REG, I2S_TX_START, 0);
+      modifyreg32(I2S_CONF_REG, I2S_TX_START, 0);
 
-  /* Stop outlink */
+      /* Stop outlink */
 
-  modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_START, I2S_OUTLINK_STOP);
+      modifyreg32(I2S_OUT_LINK_REG, I2S_OUTLINK_START, I2S_OUTLINK_STOP);
 
-  /* Disable DMA interrupt */
+      /* Disable DMA interrupt */
 
-  modifyreg32(I2S_INT_ENA_REG, UINT32_MAX, 0);
+      modifyreg32(I2S_INT_ENA_REG, I2S_OUT_EOF_INT_ENA, 0);
 
-  /* Disable DMA operation mode */
+      /* Disable DMA operation mode */
 
-  modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0);
+      modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0);
 
-  up_disable_irq(priv->config->irq);
+      up_disable_irq(priv->config->irq);
 
-  priv->tx_started = false;
+      priv->tx_started = false;
 
-  i2sinfo("Stopped TX channel on I2S0\n");
+      i2sinfo("Stopped TX channel on I2S0\n");
+    }
 }
 #endif /* I2S_HAVE_TX */
 
+/****************************************************************************
+ * Name: i2s_rx_channel_stop
+ *
+ * Description:
+ *   Stop RX channel for the I2S port
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static void i2s_rx_channel_stop(struct esp32s2_i2s_s *priv)
+{
+  if (priv->config->rx_en)
+    {
+      if (!priv->rx_started)
+        {
+          i2swarn("RX channel was previously stopped\n");
+          return;
+        }
+
+      /* Stop RX channel */
+
+      modifyreg32(I2S_CONF_REG, I2S_RX_START, 0);
+
+      /* Stop outlink */
+
+      modifyreg32(I2S_IN_LINK_REG, I2S_INLINK_START, I2S_INLINK_STOP);
+
+      /* Disable DMA interrupt */
+
+      modifyreg32(I2S_INT_ENA_REG, I2S_IN_SUC_EOF_INT_ENA, 0);
+
+      /* Disable DMA operation mode */
+
+      modifyreg32(I2S_FIFO_CONF_REG, I2S_DSCR_EN, 0);
+
+      up_disable_irq(priv->config->irq);
+
+      priv->rx_started = false;
+
+      i2sinfo("Stopped RX channel on I2S0\n");
+    }
+}
+#endif /* I2S_HAVE_RX */
+
 /****************************************************************************
  * Name: i2s_interrupt
  *
@@ -1382,7 +2002,9 @@ static void i2s_tx_channel_stop(struct esp32s2_i2s_s *priv)
  *   Common I2S DMA interrupt handler
  *
  * Input Parameters:
- *   arg - i2s controller private data
+ *   irq     - Number of the IRQ that generated the interrupt
+ *   context - Interrupt register state save info
+ *   arg     - I2S controller private data
  *
  * Returned Value:
  *   Standard interrupt return value.
@@ -1398,14 +2020,35 @@ static int i2s_interrupt(int irq, void *context, void *arg)
 
   putreg32(UINT32_MAX, I2S_INT_CLR_REG);
 
-  if (status & I2S_OUT_EOF_INT_ST)
+#ifdef I2S_HAVE_TX
+  if (priv->config->tx_en)
     {
-      cur = (struct esp32s2_dmadesc_s *)getreg32(I2S_OUT_EOF_DES_ADDR_REG);
+      if (status & I2S_OUT_EOF_INT_ST)
+        {
+          cur = (struct esp32s2_dmadesc_s *)
+                getreg32(I2S_OUT_EOF_DES_ADDR_REG);
 
-      /* Schedule completion of the transfer to occur on the worker thread */
+          /* Schedule completion of the transfer on the worker thread */
 
-      i2s_tx_schedule(priv, cur);
+          i2s_tx_schedule(priv, cur);
+        }
     }
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  if (priv->config->rx_en)
+    {
+      if (status & I2S_IN_SUC_EOF_INT_ST)
+        {
+          cur = (struct esp32s2_dmadesc_s *)
+                getreg32(I2S_IN_EOF_DES_ADDR_REG);
+
+          /* Schedule completion of the transfer on the worker thread */
+
+          i2s_rx_schedule(priv, cur);
+        }
+    }
+#endif /* I2S_HAVE_RX */
 
   return 0;
 }
@@ -1460,26 +2103,76 @@ static uint32_t i2s_mclkfrequency(struct i2s_dev_s *dev, uint32_t frequency)
  *
  ****************************************************************************/
 
+#ifdef I2S_HAVE_TX
 static int i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels)
 {
   struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
 
-  if (channels != 1 && channels != 2)
+  if (priv->config->tx_en)
     {
-      return -EINVAL;
+      if (channels != 1 && channels != 2)
+        {
+          return -EINVAL;
+        }
+
+      i2s_tx_channel_stop(priv);
+
+      priv->channels = channels;
+
+      modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_TX_DMA_EQUAL,
+                  priv->channels == 1 ? I2S_TX_DMA_EQUAL : 0);
+
+      i2s_tx_channel_start(priv);
+
+      return OK;
     }
 
-  i2s_tx_channel_stop(priv);
+  return -ENOTTY;
+}
+#endif /* I2S_HAVE_TX */
 
-  priv->channels = channels;
+/****************************************************************************
+ * Name: i2s_rxchannels
+ *
+ * Description:
+ *   Set the I2S RX number of channels.
+ *
+ * Input Parameters:
+ *   dev  - Device-specific state data
+ *   channels - The I2S numbers of channels
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
 
-  modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_TX_DMA_EQUAL,
-              priv->channels == 1 ? I2S_TX_DMA_EQUAL : 0);
+#ifdef I2S_HAVE_RX
+static int i2s_rxchannels(struct i2s_dev_s *dev, uint8_t channels)
+{
+  struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
 
-  i2s_tx_channel_start(priv);
+  if (priv->config->rx_en)
+    {
+      if (channels != 1 && channels != 2)
+        {
+          return -EINVAL;
+        }
 
-  return OK;
+      i2s_rx_channel_stop(priv);
+
+      priv->channels = channels;
+
+      modifyreg32(I2S_CONF_REG, priv->channels == 1 ? 0 : I2S_RX_DMA_EQUAL,
+                  priv->channels == 1 ? I2S_RX_DMA_EQUAL : 0);
+
+      i2s_rx_channel_start(priv);
+
+      return OK;
+    }
+
+  return -ENOTTY;
 }
+#endif /* I2S_HAVE_RX */
 
 /****************************************************************************
  * Name: i2s_txsamplerate
@@ -1499,20 +2192,64 @@ static int i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels)
  *
  ****************************************************************************/
 
+#ifdef I2S_HAVE_TX
 static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
 {
   struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
 
-  i2s_tx_channel_stop(priv);
+  if (priv->config->tx_en)
+    {
+      i2s_tx_channel_stop(priv);
 
-  priv->rate = rate;
+      priv->rate = rate;
 
-  rate = i2s_set_clock(priv);
+      rate = i2s_set_clock(priv);
 
-  i2s_tx_channel_start(priv);
+      i2s_tx_channel_start(priv);
 
-  return rate;
+      return rate;
+    }
+
+  return 0;
+}
+#endif /* I2S_HAVE_TX */
+
+/****************************************************************************
+ * Name: i2s_rxsamplerate
+ *
+ * Description:
+ *   Set the I2S RX sample rate.
+ *
+ * Input Parameters:
+ *   dev  - Device-specific state data
+ *   rate - The I2S sample rate in samples (not bits) per second
+ *
+ * Returned Value:
+ *   Returns the resulting bitrate
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static uint32_t i2s_rxsamplerate(struct i2s_dev_s *dev, uint32_t rate)
+{
+  struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
+
+  if (priv->config->rx_en)
+    {
+      i2s_rx_channel_stop(priv);
+
+      priv->rate = rate;
+
+      rate = i2s_set_clock(priv);
+
+      i2s_rx_channel_start(priv);
+
+      return rate;
+    }
+
+  return 0;
 }
+#endif /* I2S_HAVE_RX */
 
 /****************************************************************************
  * Name: i2s_txdatawidth
@@ -1530,20 +2267,65 @@ static uint32_t i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
  *
  ****************************************************************************/
 
+#ifdef I2S_HAVE_TX
 static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
 {
   struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
 
-  i2s_tx_channel_stop(priv);
+  if (priv->config->tx_en)
+    {
+      i2s_tx_channel_stop(priv);
 
-  priv->data_width = bits;
+      priv->data_width = bits;
 
-  i2s_set_datawidth(priv);
+      i2s_set_datawidth(priv);
+
+      i2s_tx_channel_start(priv);
 
-  i2s_tx_channel_start(priv);
+      return bits;
+    }
 
-  return bits;
+  return 0;
 }
+#endif /* I2S_HAVE_TX */
+
+/****************************************************************************
+ * Name: i2s_rxdatawidth
+ *
+ * Description:
+ *   Set the I2S RX data width.  The RX bitrate is determined by
+ *   sample_rate * data_width.
+ *
+ * Input Parameters:
+ *   dev   - Device-specific state data
+ *   width - The I2S data with in bits.
+ *
+ * Returned Value:
+ *   Returns the resulting data width
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_RX
+static uint32_t i2s_rxdatawidth(struct i2s_dev_s *dev, int bits)
+{
+  struct esp32s2_i2s_s *priv = (struct esp32s2_i2s_s *)dev;
+
+  if (priv->config->rx_en)
+    {
+      i2s_rx_channel_stop(priv);
+
+      priv->data_width = bits;
+
+      i2s_set_datawidth(priv);
+
+      i2s_rx_channel_start(priv);
+
+      return bits;
+    }
+
+  return 0;
+}
+#endif /* I2S_HAVE_RX */
 
 /****************************************************************************
  * Name: i2s_send
@@ -1568,81 +2350,257 @@ static uint32_t i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
  *
  ****************************************************************************/
 
+#ifdef I2S_HAVE_TX
 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;
-  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.
-   */
+  if (priv->config->tx_en)
+    {
+      struct esp32s2_buffer_s *bfcontainer;
+      int ret = OK;
+      uint32_t nbytes;
 
-  nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes;
+      /* 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 -= (nbytes % (priv->data_width / 8));
+      nbytes = (apb->nbytes - apb->curbyte) + priv->tx.carry.bytes;
 
-  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;
+      nbytes -= (nbytes % (priv->data_width / 8));
+
+      if (nbytes > (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM))
+        {
+          i2serr("Required buffer size can't fit into DMA outlink "
+                "(exceeds in %" PRIu32 " bytes). Try to increase the "
+                "number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).",
+                nbytes - (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM));
+          return -EFBIG;
+        }
+
+      /* Allocate a buffer container in advance */
+
+      bfcontainer = i2s_buf_allocate(priv);
+      DEBUGASSERT(bfcontainer);
+
+      /* Get exclusive access to the I2S driver data */
+
+      ret = nxmutex_lock(&priv->lock);
+      if (ret < 0)
+        {
+          goto errout_with_buf;
+        }
+
+      /* Add a reference to the audio buffer */
+
+      apb_reference(apb);
+
+      /* Initialize the buffer container structure */
+
+      bfcontainer->callback = callback;
+      bfcontainer->timeout  = timeout;
+      bfcontainer->arg      = arg;
+      bfcontainer->apb      = apb;
+      bfcontainer->nbytes   = nbytes;
+      bfcontainer->result   = -EBUSY;
+
+      ret = i2s_txdma_setup(priv, bfcontainer);
+
+      if (ret != OK)
+        {
+          goto errout_with_buf;
+        }
+
+      i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes);
+      i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte],
+                        apb->nbytes - apb->curbyte);
+
+      nxmutex_unlock(&priv->lock);
+
+      return OK;
+
+errout_with_buf:
+      nxmutex_unlock(&priv->lock);
+      i2s_buf_free(priv, bfcontainer);
+      return ret;
     }
 
-  /* Allocate a buffer container in advance */
+  return -ENOTTY;
+}
+#endif /* I2S_HAVE_TX */
 
-  bfcontainer = i2s_buf_allocate(priv);
-  DEBUGASSERT(bfcontainer);
+/****************************************************************************
+ * Name: i2s_receive
+ *
+ * Description:
+ *   Receive a block of data on I2S.
+ *
+ * Input Parameters:
+ *   dev      - Device-specific state data
+ *   apb      - A pointer to the audio buffer in which to receive data
+ *   callback - A user provided callback function that will be called at
+ *              the completion of the transfer.
+ *   arg      - An opaque argument that will be provided to the callback
+ *              when the transfer complete
+ *   timeout  - The timeout value to use.  The transfer will be cancelled
+ *              and an ETIMEDOUT error will be reported if this timeout
+ *              elapsed without completion of the DMA transfer.  Units
+ *              are system clock ticks.  Zero means no timeout.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
 
-  /* Get exclusive access to the I2S driver data */
+#ifdef I2S_HAVE_RX
+static int i2s_receive(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;
 
-  ret = nxmutex_lock(&priv->lock);
-  if (ret < 0)
+  if (priv->config->rx_en)
     {
-      goto errout_with_buf;
-    }
+      struct esp32s2_buffer_s *bfcontainer;
+      int ret = OK;
+      uint32_t nbytes;
 
-  /* Add a reference to the audio buffer */
+      /* Check max audio buffer data size from the upper half and align the
+       * receiving buffer according to the data width.
+       */
 
-  apb_reference(apb);
+      nbytes = apb->nmaxbytes;
 
-  /* Initialize the buffer container structure */
+      nbytes -= (nbytes % (priv->data_width / 8));
 
-  bfcontainer->callback = callback;
-  bfcontainer->timeout  = timeout;
-  bfcontainer->arg      = arg;
-  bfcontainer->apb      = apb;
-  bfcontainer->nbytes   = nbytes;
-  bfcontainer->result   = -EBUSY;
+      if (nbytes > (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM))
+        {
+          i2serr("Required buffer size can't fit into DMA inlink "
+                "(exceeds in %" PRIu32 " bytes). Try to increase the "
+                "number of the DMA descriptors (CONFIG_I2S_DMADESC_NUM).",
+                nbytes - (ESP32S2_DMA_DATALEN_MAX * I2S_DMADESC_NUM));
+          return -EFBIG;
+        }
 
-  ret = i2s_txdma_setup(priv, bfcontainer);
+      /* Allocate a buffer container in advance */
 
-  if (ret != OK)
-    {
-      goto errout_with_buf;
-    }
+      bfcontainer = i2s_buf_allocate(priv);
+      DEBUGASSERT(bfcontainer);
+
+      /* Get exclusive access to the I2S driver data */
 
-  i2sinfo("Queued %d bytes into DMA buffers\n", apb->nbytes);
-  i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte],
-                    apb->nbytes - apb->curbyte);
+      ret = nxmutex_lock(&priv->lock);
+      if (ret < 0)
+        {
+          goto errout_with_buf;
+        }
 
-  nxmutex_unlock(&priv->lock);
+      /* Add a reference to the audio buffer */
 
-  return OK;
+      apb_reference(apb);
+
+      /* Initialize the buffer container structure */
+
+      bfcontainer->callback = callback;
+      bfcontainer->timeout  = timeout;
+      bfcontainer->arg      = arg;
+      bfcontainer->apb      = apb;
+      bfcontainer->nbytes   = nbytes;
+      bfcontainer->result   = -EBUSY;
+
+      ret = i2s_rxdma_setup(priv, bfcontainer);
+
+      if (ret != OK)
+        {
+          goto errout_with_buf;
+        }
+
+      i2sinfo("Prepared %d bytes to receive DMA buffers\n", apb->nmaxbytes);
+      i2s_dump_buffer("Audio pipeline buffer:", &apb->samp[apb->curbyte],
+                      apb->nbytes - apb->curbyte);
+
+      nxmutex_unlock(&priv->lock);
+
+      return OK;
 
 errout_with_buf:
-  nxmutex_unlock(&priv->lock);
-  i2s_buf_free(priv, bfcontainer);
+      nxmutex_unlock(&priv->lock);
+      i2s_buf_free(priv, bfcontainer);
+      return ret;
+    }
+
+  return -ENOTTY;
+}
+#endif /* I2S_HAVE_RX */
+
+/****************************************************************************
+ * Name: i2s_ioctl
+ *
+ * Description:
+ *   Perform a device ioctl
+ *
+ ****************************************************************************/
+
+static int i2s_ioctl(struct i2s_dev_s *dev, int cmd, unsigned long arg)
+{
+  struct audio_buf_desc_s  *bufdesc;
+  int ret = -ENOTTY;
+
+  switch (cmd)
+    {
+      /* AUDIOIOC_START - Start the audio stream.
+       *
+       *   ioctl argument:  Audio session
+       */
+
+      case AUDIOIOC_START:
+        {
+          i2sinfo("AUDIOIOC_START\n");
+
+          ret = OK;
+        }
+        break;
+
+      /* AUDIOIOC_ALLOCBUFFER - Allocate an audio buffer
+       *
+       *   ioctl argument:  pointer to an audio_buf_desc_s structure
+       */
+
+      case AUDIOIOC_ALLOCBUFFER:
+        {
+          i2sinfo("AUDIOIOC_ALLOCBUFFER\n");
+
+          bufdesc = (struct audio_buf_desc_s *) arg;
+          ret = apb_alloc(bufdesc);
+        }
+        break;
+
+      /* AUDIOIOC_FREEBUFFER - Free an audio buffer
+       *
+       *   ioctl argument:  pointer to an audio_buf_desc_s structure
+       */
+
+      case AUDIOIOC_FREEBUFFER:
+        {
+          i2sinfo("AUDIOIOC_FREEBUFFER\n");
+
+          bufdesc = (struct audio_buf_desc_s *) arg;
+          DEBUGASSERT(bufdesc->u.buffer != NULL);
+          apb_free(bufdesc->u.buffer);
+          ret = sizeof(struct audio_buf_desc_s);
+        }
+        break;
+
+      default:
+        break;
+    }
+
   return ret;
 }
 
 /****************************************************************************
- * Name: i2sdma_setup
+ * Name: i2s_dma_setup
  *
  * Description:
  *   Configure the DMA for the I2S peripheral
@@ -1657,7 +2615,7 @@ errout_with_buf:
  *
  ****************************************************************************/
 
-static int i2sdma_setup(struct esp32s2_i2s_s *priv)
+static int i2s_dma_setup(struct esp32s2_i2s_s *priv)
 {
   int ret;
 
@@ -1711,8 +2669,6 @@ struct i2s_dev_s *esp32s2_i2sbus_initialize(void)
 
   priv = &esp32s2_i2s0_priv;
 
-  priv->tx_started = false;
-
   flags = enter_critical_section();
 
   i2s_configure(priv);
@@ -1725,7 +2681,7 @@ struct i2s_dev_s *esp32s2_i2sbus_initialize(void)
       goto err;
     }
 
-  ret = i2sdma_setup(priv);
+  ret = i2s_dma_setup(priv);
   if (ret < 0)
     {
       goto err;
@@ -1734,9 +2690,23 @@ struct i2s_dev_s *esp32s2_i2sbus_initialize(void)
 #ifdef I2S_HAVE_TX
   /* Start TX channel */
 
-  i2s_tx_channel_start(priv);
+  if (priv->config->tx_en)
+    {
+      priv->tx_started = false;
+      i2s_tx_channel_start(priv);
+    }
 #endif /* I2S_HAVE_TX */
 
+#ifdef I2S_HAVE_RX
+  /* Start RX channel */
+
+  if (priv->config->rx_en)
+    {
+      priv->rx_started = false;
+      i2s_rx_channel_start(priv);
+    }
+#endif /* I2S_HAVE_RX */
+
   leave_critical_section(flags);
 
   /* Success exit */
diff --git a/boards/xtensa/esp32s2/common/src/esp32s2_board_i2sdev.c b/boards/xtensa/esp32s2/common/src/esp32s2_board_i2sdev.c
index 600ae14c1d..52b40d0bae 100644
--- a/boards/xtensa/esp32s2/common/src/esp32s2_board_i2sdev.c
+++ b/boards/xtensa/esp32s2/common/src/esp32s2_board_i2sdev.c
@@ -53,7 +53,8 @@
  *   number.
  *
  * Input Parameters:
- *   None
+ *   enable_tx  - Register device as TX if true
+ *   enable_rx  - Register device as RX if true
  *
  * Returned Value:
  *   Zero is returned on success.  Otherwise, a negated errno value is
@@ -61,12 +62,11 @@
  *
  ****************************************************************************/
 
-int board_i2sdev_initialize(void)
+int board_i2sdev_initialize(bool enable_tx, bool enable_rx)
 {
   struct audio_lowerhalf_s *audio_i2s;
-  struct audio_lowerhalf_s *pcm;
   struct i2s_dev_s *i2s;
-  char devname[5];
+  char devname[8];
   int ret;
 
   ainfo("Initializing I2S\n");
@@ -82,29 +82,71 @@ int board_i2sdev_initialize(void)
     }
 #endif
 
-  audio_i2s = audio_i2s_initialize(i2s, true);
-
-  if (!audio_i2s)
+  if (enable_tx)
     {
-      auderr("ERROR: Failed to initialize the Generic I2S audio driver\n");
-      return -ENODEV;
-    }
+      /* Initialize audio output */
 
-  pcm = pcm_decode_initialize(audio_i2s);
+      audio_i2s = audio_i2s_initialize(i2s, true);
 
-  if (!pcm)
-    {
-      auderr("ERROR: Failed create the PCM decoder\n");
-      return  -ENODEV;
-    }
+      if (audio_i2s == NULL)
+        {
+          auderr("ERROR: Failed to initialize I2S audio output\n");
+          return -ENODEV;
+        }
 
-  snprintf(devname, 5, "pcm0");
+      snprintf(devname, sizeof(devname), "pcm0");
 
-  ret = audio_register(devname, pcm);
+      /* If nxlooper is selected, the playback buffer is not rendered as
+       * a WAV file. Therefore, PCM decode will fail while processing such
+       * output buffer. In such a case, we bypass the PCM decode.
+       */
 
-  if (ret < 0)
+#ifdef CONFIG_SYSTEM_NXLOOPER
+      ret = audio_register(devname, audio_i2s);
+#else
+      struct audio_lowerhalf_s *pcm;
+
+      pcm = pcm_decode_initialize(audio_i2s);
+
+      if (pcm == NULL)
+        {
+          auderr("ERROR: Failed create the PCM decoder\n");
+          return -ENODEV;
+        }
+
+      ret = audio_register(devname, pcm);
+#endif /* CONFIG_SYSTEM_NXLOOPER */
+
+      if (ret < 0)
+        {
+          auderr("ERROR: Failed to register /dev/%s device: %d\n",
+                 devname, ret);
+          return ret;
+        }
+    }
+
+  if (enable_rx)
     {
-      auderr("ERROR: Failed to register /dev/%s device: %d\n", devname, ret);
+      /* Initialize audio input */
+
+      audio_i2s = audio_i2s_initialize(i2s, false);
+
+      if (audio_i2s == NULL)
+        {
+          auderr("ERROR: Failed to initialize I2S audio input\n");
+          return -ENODEV;
+        }
+
+      snprintf(devname, sizeof(devname), "pcm_in0");
+
+      ret = audio_register(devname, audio_i2s);
+
+      if (ret < 0)
+        {
+          auderr("ERROR: Failed to register /dev/%s device: %d\n",
+                 devname, ret);
+          return ret;
+        }
     }
 
   return ret;
diff --git a/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig b/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig
index a9cead0ae9..9a9e766776 100644
--- a/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig
+++ b/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig
@@ -6,7 +6,6 @@
 # modifications.
 #
 # CONFIG_ARCH_LEDS is not set
-# CONFIG_ESP32S2_I2S_RX is not set
 # CONFIG_NDEBUG is not set
 # CONFIG_NSH_ARGCAT is not set
 # CONFIG_NSH_CMDOPT_HEXDUMP is not set
@@ -25,13 +24,15 @@ CONFIG_AUDIO_I2S=y
 CONFIG_AUDIO_I2SCHAR=y
 CONFIG_BOARD_LOOPSPERMSEC=16717
 CONFIG_BUILTIN=y
-CONFIG_DEBUG_FULLOPT=y
-CONFIG_DEBUG_SYMBOLS=y
 CONFIG_DRIVERS_AUDIO=y
 CONFIG_ESP32S2_I2S=y
+CONFIG_ESP32S2_I2S_DATA_BIT_WIDTH_16BIT=y
 CONFIG_ESP32S2_I2S_MCLK=y
 CONFIG_ESP32S2_UART0=y
 CONFIG_EXAMPLES_I2SCHAR=y
+CONFIG_EXAMPLES_I2SCHAR_BUFSIZE=1024
+CONFIG_EXAMPLES_I2SCHAR_RX=y
+CONFIG_EXAMPLES_I2SCHAR_RXSTACKSIZE=4096
 CONFIG_EXAMPLES_I2SCHAR_TX=y
 CONFIG_EXAMPLES_I2SCHAR_TXSTACKSIZE=4096
 CONFIG_FS_PROCFS=y
diff --git a/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig b/boards/xtensa/esp32s2/esp32s2-saola-1/configs/nxlooper/defconfig
similarity index 81%
copy from boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig
copy to boards/xtensa/esp32s2/esp32s2-saola-1/configs/nxlooper/defconfig
index a9cead0ae9..34b98d2e3b 100644
--- a/boards/xtensa/esp32s2/esp32s2-saola-1/configs/i2schar/defconfig
+++ b/boards/xtensa/esp32s2/esp32s2-saola-1/configs/nxlooper/defconfig
@@ -6,7 +6,6 @@
 # modifications.
 #
 # CONFIG_ARCH_LEDS is not set
-# CONFIG_ESP32S2_I2S_RX is not set
 # CONFIG_NDEBUG is not set
 # CONFIG_NSH_ARGCAT is not set
 # CONFIG_NSH_CMDOPT_HEXDUMP is not set
@@ -21,17 +20,25 @@ CONFIG_ARCH_CHIP_ESP32S2WROVER=y
 CONFIG_ARCH_STACKDUMP=y
 CONFIG_ARCH_XTENSA=y
 CONFIG_AUDIO=y
+CONFIG_AUDIO_BUFFER_NUMBYTES=4092
+CONFIG_AUDIO_EXCLUDE_BALANCE=y
+CONFIG_AUDIO_EXCLUDE_FFORWARD=y
+CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME=y
+CONFIG_AUDIO_EXCLUDE_TONE=y
 CONFIG_AUDIO_I2S=y
 CONFIG_AUDIO_I2SCHAR=y
+CONFIG_AUDIO_NUM_BUFFERS=4
 CONFIG_BOARD_LOOPSPERMSEC=16717
 CONFIG_BUILTIN=y
-CONFIG_DEBUG_FULLOPT=y
-CONFIG_DEBUG_SYMBOLS=y
 CONFIG_DRIVERS_AUDIO=y
 CONFIG_ESP32S2_I2S=y
+CONFIG_ESP32S2_I2S_DATA_BIT_WIDTH_16BIT=y
 CONFIG_ESP32S2_I2S_MCLK=y
 CONFIG_ESP32S2_UART0=y
 CONFIG_EXAMPLES_I2SCHAR=y
+CONFIG_EXAMPLES_I2SCHAR_BUFSIZE=1024
+CONFIG_EXAMPLES_I2SCHAR_RX=y
+CONFIG_EXAMPLES_I2SCHAR_RXSTACKSIZE=4096
 CONFIG_EXAMPLES_I2SCHAR_TX=y
 CONFIG_EXAMPLES_I2SCHAR_TXSTACKSIZE=4096
 CONFIG_FS_PROCFS=y
@@ -57,4 +64,5 @@ CONFIG_START_DAY=6
 CONFIG_START_MONTH=12
 CONFIG_START_YEAR=2011
 CONFIG_SYSTEM_NSH=y
+CONFIG_SYSTEM_NXLOOPER=y
 CONFIG_UART0_SERIAL_CONSOLE=y
diff --git a/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2-saola-1.h b/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2-saola-1.h
index be4c69aa25..f98ca7bcb5 100644
--- a/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2-saola-1.h
+++ b/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2-saola-1.h
@@ -33,7 +33,7 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
-/* ESP32S2-DevKitC GPIOs ****************************************************/
+/* ESP32S2-Saola-1 GPIOs ****************************************************/
 
 /* BOOT Button */
 
@@ -162,7 +162,8 @@ int board_bmp180_initialize(int devno, int busno);
  *   number.
  *
  * Input Parameters:
- *   None.
+ *   enable_tx  - Register device as TX if true
+ *   enable_rx  - Register device as RX if true
  *
  * Returned Value:
  *   Zero is returned on success.  Otherwise, a negated errno value is
@@ -171,7 +172,7 @@ int board_bmp180_initialize(int devno, int busno);
  ****************************************************************************/
 
 #if defined(CONFIG_ESP32S2_I2S) && !defined(CONFIG_AUDIO_CS4344)
-int board_i2sdev_initialize(void);
+int board_i2sdev_initialize(bool enable_tx, bool enable_rx);
 #endif
 
 /****************************************************************************
diff --git a/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2_bringup.c b/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2_bringup.c
index 47fbe50649..28c24a5bc9 100644
--- a/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2_bringup.c
+++ b/boards/xtensa/esp32s2/esp32s2-saola-1/src/esp32s2_bringup.c
@@ -242,9 +242,24 @@ int esp32s2_bringup(void)
     }
 #else
 
+  bool i2s_enable_tx;
+  bool i2s_enable_rx;
+
+#ifdef CONFIG_ESP32S2_I2S_TX
+  i2s_enable_tx = true;
+#else
+  i2s_enable_tx = false;
+#endif /* CONFIG_ESP32S2_I2S_TX */
+
+#ifdef CONFIG_ESP32S2_I2S_RX
+    i2s_enable_rx = true;
+#else
+    i2s_enable_rx = false;
+#endif /* CONFIG_ESP32S2_I2S_RX */
+
   /* Configure I2S generic audio on I2S0 */
 
-  ret = board_i2sdev_initialize();
+  ret = board_i2sdev_initialize(i2s_enable_tx, i2s_enable_rx);
   if (ret < 0)
     {
       syslog(LOG_ERR, "Failed to initialize I2S0 driver: %d\n", ret);