tmedicci commented on code in PR #10465:
URL: https://github.com/apache/nuttx/pull/10465#discussion_r1314918547


##########
arch/xtensa/src/esp32s3/esp32s3_i2s.c:
##########
@@ -0,0 +1,3225 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_i2s.c
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#ifdef CONFIG_ESP32S3_I2S
+
+#include <debug.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+#include <math.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/clock.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/spinlock.h>
+#include <nuttx/mqueue.h>
+#include <nuttx/mm/circbuf.h>
+#include <nuttx/audio/audio.h>
+#include <nuttx/audio/i2s.h>
+
+#include <arch/board/board.h>
+
+#include "esp32s3_i2s.h"
+#include "esp32s3_gpio.h"
+#include "esp32s3_irq.h"
+#include "esp32s3_dma.h"
+
+#include "xtensa.h"
+#include "hardware/esp32s3_gpio_sigmap.h"
+#include "hardware/esp32s3_system.h"
+#include "hardware/esp32s3_i2s.h"
+#include "hardware/esp32s3_soc.h"
+#include "hardware/esp32s3_iomux.h"
+#include "hardware/esp32s3_pinmap.h"
+#include "hardware/esp32s3_dma.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* I2S DMA RX/TX description number */
+
+#define I2S_DMADESC_NUM                 (CONFIG_I2S_DMADESC_NUM)
+
+/* I2S Clock */
+
+#define I2S_LL_BASE_CLK                 (2 * APB_CLK_FREQ)
+#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH   (6)
+#define I2S_LL_MCLK_DIVIDER_MAX         ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) 
- 1)
+
+/* I2S DMA channel number */
+
+#define I2S_DMA_CHANNEL_MAX (2)
+
+#ifdef CONFIG_ESP32S3_I2S0_TX
+#  define I2S0_TX_ENABLED 1
+#  define I2S_HAVE_TX 1
+#else
+#  define I2S0_TX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S0_RX
+#  define I2S0_RX_ENABLED 1
+#  define I2S_HAVE_RX 1
+#else
+#  define I2S0_RX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S1_TX
+#  define I2S1_TX_ENABLED 1
+#  define I2S_HAVE_TX 1
+#else
+#  define I2S1_TX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S1_RX
+#  define I2S1_RX_ENABLED 1
+#  define I2S_HAVE_RX 1
+#else
+#  define I2S1_RX_ENABLED 0
+#endif
+
+#ifndef ALIGN_UP
+#  define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
+#endif
+
+/* Debug ********************************************************************/
+
+#ifdef CONFIG_DEBUG_I2S_INFO
+#  define CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#else
+#  undef CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#endif
+
+#ifndef CONFIG_ESP32S3_I2S_MAXINFLIGHT
+#  define CONFIG_ESP32S3_I2S_MAXINFLIGHT 4
+#endif
+
+#define I2S_GPIO_UNUSED -1      /* For signals which are not used */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Role of the I2S port */
+
+typedef enum
+{
+  I2S_ROLE_MASTER,  /* I2S controller master role, bclk and ws signal will be 
set to output */
+  I2S_ROLE_SLAVE    /* I2S controller slave role, bclk and ws signal will be 
set to input */
+} i2s_role_t;
+
+/* Data width of the I2S channel */
+
+typedef enum
+{
+  I2S_DATA_BIT_WIDTH_8BIT   = 8,    /* I2S channel data bit-width: 8 */
+  I2S_DATA_BIT_WIDTH_16BIT  = 16,   /* I2S channel data bit-width: 16 */
+  I2S_DATA_BIT_WIDTH_24BIT  = 24,   /* I2S channel data bit-width: 24 */
+  I2S_DATA_BIT_WIDTH_32BIT  = 32,   /* I2S channel data bit-width: 32 */
+} i2s_data_bit_width_t;
+
+/* Multiplier of MCLK to sample rate */
+
+typedef enum
+{
+  I2S_MCLK_MULTIPLE_128 = 128,  /* mclk = sample_rate * 128 */
+  I2S_MCLK_MULTIPLE_256 = 256,  /* mclk = sample_rate * 256 */
+  I2S_MCLK_MULTIPLE_384 = 384,  /* mclk = sample_rate * 384 */
+  I2S_MCLK_MULTIPLE_512 = 512,  /* mclk = sample_rate * 512 */
+} i2s_mclk_multiple_t;
+
+/* I2S Clock Source */
+
+typedef enum
+{
+  I2S_XTAL_CLK = 0,
+  I2S_PLL_D2_CLK,
+  I2S_PLL_F160M_CLK,
+  I2S_MCLK_IN,
+} i2s_clk_src_t;
+
+/* I2S Audio Standard Mode */
+
+typedef enum
+{
+  I2S_TDM_PHILIPS = 0,
+  I2S_TDM_MSB,
+  I2S_TDM_PCM,
+  I2S_PDM,
+} i2s_audio_mode_t;
+
+/* Role of the I2S port */
+
+typedef enum
+{
+  I2S_RX,   /* I2S controller receiver (RX) */
+  I2S_TX    /* I2S controller transmitter (TX) */
+} i2s_dir_t;
+
+/* I2S Device hardware configuration */
+
+struct esp32s3_i2s_config_s
+{
+  uint32_t port;              /* I2S port */
+  uint32_t role;              /* I2S port role (master or slave) */
+  uint8_t data_width;         /* I2S sample data width */
+  uint32_t rate;              /* I2S sample-rate */
+  uint32_t total_slot;        /* Total slot number */
+
+  bool tx_en;                 /* Is TX enabled? */
+  bool rx_en;                 /* Is RX enabled? */
+  int8_t mclk_pin;            /* MCLK pin, output */
+
+  int tx_clk_src;             /* Select the I2S TX source clock */
+  int rx_clk_src;             /* Select the I2S TX source clock */
+
+  /* BCLK pin, input in slave role, output in master role */
+
+  int8_t bclk_pin;
+
+  /* WS pin, input in slave role, output in master role */
+
+  int8_t ws_pin;
+
+  int8_t dout_pin;            /* DATA pin, output */
+  int8_t din_pin;             /* DATA pin, input */
+
+  uint32_t bclk_in_insig;     /* RX channel BCK signal (slave mode) index */
+  uint32_t bclk_in_outsig;    /* RX channel BCK signal (master mode) index */
+  uint32_t bclk_out_insig;    /* TX channel BCK signal (slave mode) index */
+  uint32_t bclk_out_outsig;   /* TX channel BCK signal (master mode) index */
+  uint32_t ws_in_insig;       /* RX channel WS signal (slave mode) index */
+  uint32_t ws_in_outsig;      /* RX channel WS signal (master mode) index */
+  uint32_t ws_out_insig;      /* TX channel WS signal (slave mode) index */
+  uint32_t ws_out_outsig;     /* TX channel WS signal (master mode) index */
+  uint32_t din_insig;         /* RX channel Data Input signal index */
+  uint32_t dout_outsig;       /* TX channel Data Output signal index */
+  uint32_t mclk_out_sig;      /* Master clock output index */
+
+  uint8_t  audio_std_mode;    /* Select audio standard (i2s_audio_mode_t) */
+
+  /* WS signal polarity, set true to enable high level first */
+
+  bool ws_pol;
+};
+
+struct esp32s3_buffer_s
+{
+  struct esp32s3_buffer_s *flink; /* Supports a singly linked list */
+
+  /* The associated DMA in/outlink */
+
+  struct esp32s3_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 */
+};
+
+/* 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 esp32s3_buffer_carry_s
+{
+  uint32_t value;
+  size_t bytes;
+};
+
+/* This structure describes the state of one receiver or transmitter
+ * transport.
+ */
+
+struct esp32s3_transport_s
+{
+  sq_queue_t pend;              /* A queue of pending transfers */
+  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 esp32s3_buffer_carry_s carry;
+};
+
+/* The state of the one I2S peripheral */
+
+struct esp32s3_i2s_s
+{
+  struct i2s_dev_s  dev;        /* Externally visible I2S interface */
+  mutex_t           lock;       /* Ensures mutually exclusive access */
+  uint8_t           cpu;        /* CPU ID */
+  spinlock_t        slock;      /* Device specific lock. */
+
+  /* Port configuration */
+
+  const struct esp32s3_i2s_config_s *config;
+
+  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 */
+  uint32_t    dma_channel;    /* I2S DMA channel being used */
+
+#ifdef I2S_HAVE_TX
+  struct esp32s3_transport_s tx;  /* TX transport state */
+
+  int  tx_irq;                    /* TX IRQ */
+  bool tx_started;                /* TX channel started */
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  struct esp32s3_transport_s rx;  /* RX transport state */
+
+  int  rx_irq;                    /* RX IRQ */
+  bool rx_started;                /* RX channel started */
+#endif /* I2S_HAVE_RX */
+
+  bool streaming;                 /* Is I2S peripheral active? */
+
+  /* Pre-allocated pool of buffer containers */
+
+  sem_t bufsem;                         /* Buffer wait semaphore */
+  struct esp32s3_buffer_s *bf_freelist; /* A list a free buffer containers */
+  struct esp32s3_buffer_s containers[CONFIG_ESP32S3_I2S_MAXINFLIGHT];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Register helpers */
+
+#ifdef CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#  define       i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
+#else
+#  define       i2s_dump_buffer(m,b,s)
+#endif
+
+/* Buffer container helpers */
+
+static struct esp32s3_buffer_s *
+                i2s_buf_allocate(struct esp32s3_i2s_s *priv);
+static void     i2s_buf_free(struct esp32s3_i2s_s *priv,
+                             struct esp32s3_buffer_s *bfcontainer);
+static int      i2s_buf_initialize(struct esp32s3_i2s_s *priv);
+
+/* DMA support */
+
+#ifdef I2S_HAVE_TX
+static IRAM_ATTR int  i2s_txdma_setup(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_buffer_s *bfcontainer);
+static void           i2s_tx_worker(void *arg);
+static void           i2s_tx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink);
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+static IRAM_ATTR int  i2s_rxdma_setup(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_buffer_s *bfcontainer);
+static void           i2s_rx_worker(void *arg);
+static void           i2s_rx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink);
+#endif /* I2S_HAVE_RX */
+
+/* I2S methods (and close friends) */
+
+static int32_t  i2s_check_mclkfrequency(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_set_datawidth(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_set_clock(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_getmclkfrequency(struct i2s_dev_s *dev);
+static uint32_t i2s_setmclkfrequency(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 esp32s3_i2s_s *priv);
+static void     i2s_tx_channel_stop(struct esp32s3_i2s_s *priv);
+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,
+                         i2s_callback_t callback, void *arg,
+                         uint32_t timeout);
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+static void     i2s_rx_channel_start(struct esp32s3_i2s_s *priv);
+static void     i2s_rx_channel_stop(struct esp32s3_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
+ ****************************************************************************/
+
+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_getmclkfrequency  = i2s_getmclkfrequency,
+  .i2s_setmclkfrequency  = i2s_setmclkfrequency,
+};
+
+#ifdef CONFIG_ESP32S3_I2S0
+static const struct esp32s3_i2s_config_s esp32s3_i2s0_config =
+{
+  .port             = 0,
+#ifdef CONFIG_ESP32S3_I2S0_ROLE_MASTER
+  .role             = I2S_ROLE_MASTER,
+#else
+  .role             = I2S_ROLE_SLAVE,
+#endif /* CONFIG_ESP32S3_I2S0_ROLE_MASTER */
+  .data_width       = CONFIG_ESP32S3_I2S0_DATA_BIT_WIDTH,
+  .rate             = CONFIG_ESP32S3_I2S0_SAMPLE_RATE,
+  .total_slot       = 2,
+  .tx_en            = I2S0_TX_ENABLED,
+  .rx_en            = I2S0_RX_ENABLED,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+  .rx_clk_src       = I2S_PLL_D2_CLK,
+#ifdef CONFIG_ESP32S3_I2S0_MCLK
+  .mclk_pin         = CONFIG_ESP32S3_I2S0_MCLKPIN,
+#else
+  .mclk_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_MCLK */
+  .bclk_pin         = CONFIG_ESP32S3_I2S0_BCLKPIN,
+  .ws_pin           = CONFIG_ESP32S3_I2S0_WSPIN,
+#ifdef CONFIG_ESP32S3_I2S0_DOUTPIN
+  .dout_pin         = CONFIG_ESP32S3_I2S0_DOUTPIN,
+#else
+  .dout_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_DOUTPIN */
+#ifdef CONFIG_ESP32S3_I2S0_DINPIN
+  .din_pin          = CONFIG_ESP32S3_I2S0_DINPIN,
+#else
+  .din_pin          = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_DINPIN */
+  .bclk_in_insig    = I2S0I_BCK_IN_IDX,
+  .bclk_in_outsig   = I2S0I_BCK_OUT_IDX,
+  .bclk_out_insig   = I2S0O_BCK_IN_IDX,
+  .bclk_out_outsig  = I2S0O_BCK_OUT_IDX,
+  .ws_in_insig      = I2S0I_WS_IN_IDX,
+  .ws_in_outsig     = I2S0I_WS_OUT_IDX,
+  .ws_out_insig     = I2S0O_WS_IN_IDX,
+  .ws_out_outsig    = I2S0O_WS_OUT_IDX,
+  .din_insig        = I2S0I_SD_IN_IDX,
+  .dout_outsig      = I2S0O_SD_OUT_IDX,
+  .mclk_out_sig     = I2S0_MCLK_OUT_IDX,
+  .audio_std_mode   = I2S_TDM_PHILIPS,
+};
+
+static struct esp32s3_i2s_s esp32s3_i2s0_priv =
+{
+  .dev =
+  {
+    .ops = &g_i2sops,
+  },
+  .lock = NXMUTEX_INITIALIZER,
+  .config = &esp32s3_i2s0_config,
+  .bufsem = SEM_INITIALIZER(0),
+};
+#endif /* CONFIG_ESP32S3_I2S0 */
+
+#ifdef CONFIG_ESP32S3_I2S1
+static const struct esp32s3_i2s_config_s esp32s3_i2s1_config =
+{
+  .port             = 1,
+#ifdef CONFIG_ESP32S3_I2S1_ROLE_MASTER
+  .role             = I2S_ROLE_MASTER,
+#else
+  .role             = I2S_ROLE_SLAVE,
+#endif /* CONFIG_ESP32S3_I2S1_ROLE_MASTER */
+  .data_width       = CONFIG_ESP32S3_I2S1_DATA_BIT_WIDTH,
+  .rate             = CONFIG_ESP32S3_I2S1_SAMPLE_RATE,
+  .total_slot       = 2,
+  .tx_en            = I2S1_TX_ENABLED,
+  .rx_en            = I2S1_RX_ENABLED,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+#ifdef CONFIG_ESP32S3_I2S1_MCLK
+  .mclk_pin         = CONFIG_ESP32S3_I2S1_MCLKPIN,
+#else
+  .mclk_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_MCLK */
+  .bclk_pin         = CONFIG_ESP32S3_I2S1_BCLKPIN,
+  .ws_pin           = CONFIG_ESP32S3_I2S1_WSPIN,
+#ifdef CONFIG_ESP32S3_I2S1_DOUTPIN
+  .dout_pin         = CONFIG_ESP32S3_I2S1_DOUTPIN,
+#else
+  .dout_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_DOUTPIN */
+#ifdef CONFIG_ESP32S3_I2S1_DINPIN
+  .din_pin          = CONFIG_ESP32S3_I2S1_DINPIN,
+#else
+  .din_pin          = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_DINPIN */
+  .bclk_in_insig    = I2S1I_BCK_IN_IDX,
+  .bclk_in_outsig   = I2S1I_BCK_OUT_IDX,
+  .bclk_out_insig   = I2S1O_BCK_IN_IDX,
+  .bclk_out_outsig  = I2S1O_BCK_OUT_IDX,
+  .ws_in_insig      = I2S1I_WS_IN_IDX,
+  .ws_in_outsig     = I2S1I_WS_OUT_IDX,
+  .ws_out_insig     = I2S1O_WS_IN_IDX,
+  .ws_out_outsig    = I2S1O_WS_OUT_IDX,
+  .din_insig        = I2S1I_SD_IN_IDX,
+  .dout_outsig      = I2S1O_SD_OUT_IDX,
+  .mclk_out_sig     = I2S1_MCLK_OUT_IDX,
+  .audio_std_mode   = I2S_TDM_PHILIPS,
+};
+
+static struct esp32s3_i2s_s esp32s3_i2s1_priv =
+{
+  .dev =
+  {
+    .ops = &g_i2sops,
+  },
+  .lock = NXMUTEX_INITIALIZER,
+  .config = &esp32s3_i2s1_config,
+  .bufsem = SEM_INITIALIZER(0),
+};
+#endif /* CONFIG_ESP32S3_I2S1 */
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: i2s_buf_allocate
+ *
+ * Description:
+ *   Allocate a buffer container by removing the one at the head of the
+ *   free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   A non-NULL pointer to the allocate buffer container on success; NULL if
+ *   there are no available buffer containers.
+ *
+ * Assumptions:
+ *   The caller does NOT have exclusive access to the I2S state structure.
+ *   That would result in a deadlock!
+ *
+ ****************************************************************************/
+
+static struct esp32s3_buffer_s *i2s_buf_allocate(struct esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  irqstate_t flags;
+  int ret;
+
+  /* Set aside a buffer container.  By doing this, we guarantee that we will
+   * have at least one free buffer container.
+   */
+
+  ret = nxsem_wait_uninterruptible(&priv->bufsem);
+  if (ret < 0)
+    {
+      return NULL;
+    }
+
+  /* Get the buffer from the head of the free list */
+
+  flags = spin_lock_irqsave(&priv->slock);
+  bfcontainer = priv->bf_freelist;
+  DEBUGASSERT(bfcontainer);
+
+  /* Unlink the buffer from the freelist */
+
+  priv->bf_freelist = bfcontainer->flink;
+  spin_unlock_irqrestore(&priv->slock, flags);
+  return bfcontainer;
+}
+
+/****************************************************************************
+ * Name: i2s_buf_free
+ *
+ * Description:
+ *   Free buffer container by adding it to the head of the free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   bfcontainer - The buffer container to be freed
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The caller has exclusive access to the I2S state structure
+ *
+ ****************************************************************************/
+
+static void i2s_buf_free(struct esp32s3_i2s_s *priv,
+                         struct esp32s3_buffer_s *bfcontainer)
+{
+  irqstate_t flags;
+
+  /* Put the buffer container back on the free list (circbuf) */
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  bfcontainer->apb = NULL;
+  bfcontainer->buf = NULL;
+  bfcontainer->nbytes = 0;
+  bfcontainer->flink  = priv->bf_freelist;
+  priv->bf_freelist = bfcontainer;
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  /* Wake up any threads waiting for a buffer container */
+
+  nxsem_post(&priv->bufsem);
+}
+
+/****************************************************************************
+ * Name: i2s_buf_initialize
+ *
+ * Description:
+ *   Initialize the buffer container allocator by adding all of the
+ *   pre-allocated buffer containers to the free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   OK on success; A negated errno value on failure.
+ *
+ * Assumptions:
+ *   Called early in I2S initialization so that there are no issues with
+ *   concurrency.
+ *
+ ****************************************************************************/
+
+static int i2s_buf_initialize(struct esp32s3_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;
+  for (int i = 0; i < CONFIG_ESP32S3_I2S_MAXINFLIGHT; i++)
+    {
+      i2s_buf_free(priv, &priv->containers[i]);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_txdma_start
+ *
+ * Description:
+ *   Initiate the next TX DMA transfer. The DMA outlink was previously bound
+ *   so it is safe to start the next DMA transfer at interrupt level.
+ *
+ * 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_TX
+static int IRAM_ATTR i2s_txdma_start(struct esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+
+  /* If there is already an active transmission in progress, then bail
+   * returning success.
+   */
+
+  if (!sq_empty(&priv->tx.act))
+    {
+      return OK;
+    }
+
+  /* If there are no pending transfer, then bail returning success */
+
+  if (sq_empty(&priv->tx.pend))
+    {
+      return OK;
+    }
+
+  /* Start transmission if no data is already being transmitted */
+
+  bfcontainer = (struct esp32s3_buffer_s *)sq_remfirst(&priv->tx.pend);
+
+  esp32s3_dma_load(bfcontainer->dma_link, priv->dma_channel, I2S_TX);
+  esp32s3_dma_enable(priv->dma_channel, I2S_TX);
+
+  modifyreg32(I2S_TX_CONF_REG(priv->config->port), 0, I2S_TX_START);
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
+
+  return OK;
+}
+#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 esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  size_t eof_nbytes;
+
+  /* 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 esp32s3_buffer_s *)sq_remfirst(&priv->rx.pend);
+
+  /* If there isn't already an active transmission in progress,
+   * then start it.
+   */
+
+  eof_nbytes = MIN(bfcontainer->nbytes, ESP32S3_DMA_BUFLEN_MAX);
+
+  modifyreg32(I2S_RXEOF_NUM_REG(priv->config->port), I2S_RX_EOF_NUM_M,
+              FIELD_TO_VALUE(I2S_RX_EOF_NUM, eof_nbytes));
+
+  esp32s3_dma_load(bfcontainer->dma_link, priv->dma_channel, I2S_RX);
+  esp32s3_dma_enable(priv->dma_channel, I2S_RX);
+
+  modifyreg32(I2S_RX_CONF_REG(priv->config->port), 0, I2S_RX_START);
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
+
+  return OK;
+}
+#endif /* I2S_HAVE_RX */
+
+/****************************************************************************
+ * Name: i2s_txdma_setup
+ *
+ * Description:
+ *   Setup the next TX 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_TX
+static IRAM_ATTR int i2s_txdma_setup(struct esp32s3_i2s_s *priv,
+                                     struct esp32s3_buffer_s *bfcontainer)
+{
+  int ret = OK;
+  size_t carry_size;
+  uint32_t bytes_queued;
+  uint32_t data_copied;
+  struct ap_buffer_s *apb;
+  struct esp32s3_dmadesc_s *outlink;
+  apb_samp_t samp_size;
+  irqstate_t flags;
+  uint8_t *buf;
+  uint8_t padding;
+  uint8_t *samp;
+
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  apb = bfcontainer->apb;
+  outlink = bfcontainer->dma_link;
+
+  /* Get the transfer information, accounting for any data offset */
+
+  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 = calloc(bfcontainer->nbytes, 1);
+  if (bfcontainer->buf == NULL)
+    {
+      i2serr("Failed to allocate the DMA internal buffer "
+             "[%" PRIu32 " bytes]", bfcontainer->nbytes);
+      return -ENOMEM;
+    }
+
+  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((uint8_t *)&priv->tx.carry.value, samp, priv->tx.carry.bytes);
+    }
+
+  /* Release our reference on the audio buffer. This may very likely
+   * cause the audio buffer to be freed.
+   */
+
+  apb_free(bfcontainer->apb);
+
+  /* Configure DMA stream */
+
+  bytes_queued = esp32s3_dma_setup(outlink, I2S_DMADESC_NUM,
+                                   bfcontainer->buf,
+                                   bfcontainer->nbytes, I2S_TX);
+
+  if (bytes_queued != bfcontainer->nbytes)
+    {
+      i2serr("Failed to enqueue I2S buffer "
+             "(%" PRIu32 " bytes of %" PRIu32 ")\n",
+             bytes_queued, bfcontainer->nbytes);
+      return -bytes_queued;
+    }
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  /* Add the buffer container to the end of the TX pending queue */
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
+
+  /* Trigger DMA transfer if no transmission is in progress */
+
+  ret = i2s_txdma_start(priv);
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  return ret;
+}
+#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 esp32s3_i2s_s *priv,
+                           struct esp32s3_buffer_s *bfcontainer)
+{
+  int ret = OK;
+  struct esp32s3_dmadesc_s *inlink;
+  uint32_t bytes_queued;
+  irqstate_t flags;
+
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  inlink = bfcontainer->dma_link;
+
+  /* Configure DMA stream */
+
+  bytes_queued = esp32s3_dma_setup(inlink, I2S_DMADESC_NUM,
+                                   bfcontainer->apb->samp,
+                                   bfcontainer->nbytes, I2S_RX);
+
+  if (bytes_queued != bfcontainer->nbytes)
+    {
+      i2serr("Failed to enqueue I2S buffer "
+             "(%" PRIu32 " bytes of %" PRIu32 ")\n",
+             bytes_queued, bfcontainer->nbytes);
+      return -bytes_queued;
+    }
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  /* 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);
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  return ret;
+}
+#endif /* I2S_HAVE_RX */
+
+/****************************************************************************
+ * Name: i2s_tx_schedule
+ *
+ * Description:
+ *   An TX DMA completion has occurred.  Schedule processing on
+ *   the working thread.
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   outlink - DMA outlink descriptor that triggered the interrupt.
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX
+static void IRAM_ATTR i2s_tx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  struct esp32s3_dmadesc_s *bfdesc;
+  int ret;
+
+  /* Upon entry, the transfer(s) that just completed are the ones in the
+   * priv->tx.act queue.
+   */
+
+  /* Move all entries from the tx.act queue to the tx.done queue */
+
+  if (!sq_empty(&priv->tx.act))
+    {
+      /* Remove the next buffer container from the tx.act list */
+
+      bfcontainer = (struct esp32s3_buffer_s *)sq_peek(&priv->tx.act);
+
+      /* Check if the DMA descriptor that generated an EOF interrupt is the
+       * last descriptor of the current buffer container's DMA outlink.
+       * 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 & ESP32S3_DMA_CTRL_EOF))
+        {
+          DEBUGASSERT(bfdesc->next);
+          bfdesc = bfdesc->next;
+        }
+
+      if (bfdesc == outlink)
+        {
+          sq_remfirst(&priv->tx.act);
+
+          /* Report the result of the transfer */
+
+          bfcontainer->result = OK;
+
+          /* Add the completed buffer container to the tail of the tx.done
+           * queue
+           */
+
+          sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
+
+          /* Check if the DMA is IDLE */
+
+          if (sq_empty(&priv->tx.act))
+            {
+              /* Then start the next DMA. */
+
+              i2s_txdma_start(priv);
+            }
+        }
+
+      /* If the worker has completed running, then reschedule the working
+       * thread.
+       */
+
+      if (work_available(&priv->tx.work))
+        {
+          /* Schedule the TX DMA done processing to occur on the worker
+           * thread.
+           */
+
+          ret = work_queue(HPWORK, &priv->tx.work, i2s_tx_worker, priv, 0);
+          if (ret != 0)
+            {
+              i2serr("ERROR: Failed to queue TX work: %d\n", ret);
+            }
+        }
+    }
+}
+#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 esp32s3_i2s_s *priv,
+                            struct esp32s3_dmadesc_s *inlink)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  struct esp32s3_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 esp32s3_buffer_s *)sq_peek(&priv->rx.act);
+
+      /* Find the last descriptor of the current buffer container */
+
+      bfdesc = bfcontainer->dma_link;
+
+      while (bfdesc->next != NULL &&
+             (bfdesc->next->ctrl & ESP32S3_DMA_CTRL_EOF))
+        {
+          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
+ *
+ * Description:
+ *   TX transfer done worker
+ *
+ * Input Parameters:
+ *   arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX
+static void i2s_tx_worker(void *arg)
+{
+  struct esp32s3_i2s_s *priv = (struct esp32s3_i2s_s *)arg;
+  struct esp32s3_buffer_s *bfcontainer;
+  irqstate_t flags;
+
+  DEBUGASSERT(priv);
+
+  /* When the transfer was started, the active buffer containers were removed
+   * from the tx.pend queue and saved in the tx.act queue.  We get here when
+   * the DMA is finished.
+   *
+   * In any case, the buffer containers in tx.act will be moved to the end
+   * of the tx.done queue and tx.act will be emptied before this worker is
+   * started.
+   *
+   */
+
+  i2sinfo("tx.act.head=%p tx.done.head=%p\n",
+           priv->tx.act.head, priv->tx.done.head);

Review Comment:
   done



##########
arch/xtensa/src/esp32s3/esp32s3_i2s.c:
##########
@@ -0,0 +1,3225 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_i2s.c
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#ifdef CONFIG_ESP32S3_I2S
+
+#include <debug.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+#include <math.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/clock.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/spinlock.h>
+#include <nuttx/mqueue.h>
+#include <nuttx/mm/circbuf.h>
+#include <nuttx/audio/audio.h>
+#include <nuttx/audio/i2s.h>
+
+#include <arch/board/board.h>
+
+#include "esp32s3_i2s.h"
+#include "esp32s3_gpio.h"
+#include "esp32s3_irq.h"
+#include "esp32s3_dma.h"
+
+#include "xtensa.h"
+#include "hardware/esp32s3_gpio_sigmap.h"
+#include "hardware/esp32s3_system.h"
+#include "hardware/esp32s3_i2s.h"
+#include "hardware/esp32s3_soc.h"
+#include "hardware/esp32s3_iomux.h"
+#include "hardware/esp32s3_pinmap.h"
+#include "hardware/esp32s3_dma.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* I2S DMA RX/TX description number */
+
+#define I2S_DMADESC_NUM                 (CONFIG_I2S_DMADESC_NUM)
+
+/* I2S Clock */
+
+#define I2S_LL_BASE_CLK                 (2 * APB_CLK_FREQ)
+#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH   (6)
+#define I2S_LL_MCLK_DIVIDER_MAX         ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) 
- 1)
+
+/* I2S DMA channel number */
+
+#define I2S_DMA_CHANNEL_MAX (2)
+
+#ifdef CONFIG_ESP32S3_I2S0_TX
+#  define I2S0_TX_ENABLED 1
+#  define I2S_HAVE_TX 1
+#else
+#  define I2S0_TX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S0_RX
+#  define I2S0_RX_ENABLED 1
+#  define I2S_HAVE_RX 1
+#else
+#  define I2S0_RX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S1_TX
+#  define I2S1_TX_ENABLED 1
+#  define I2S_HAVE_TX 1
+#else
+#  define I2S1_TX_ENABLED 0
+#endif
+
+#ifdef CONFIG_ESP32S3_I2S1_RX
+#  define I2S1_RX_ENABLED 1
+#  define I2S_HAVE_RX 1
+#else
+#  define I2S1_RX_ENABLED 0
+#endif
+
+#ifndef ALIGN_UP
+#  define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
+#endif
+
+/* Debug ********************************************************************/
+
+#ifdef CONFIG_DEBUG_I2S_INFO
+#  define CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#else
+#  undef CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#endif
+
+#ifndef CONFIG_ESP32S3_I2S_MAXINFLIGHT
+#  define CONFIG_ESP32S3_I2S_MAXINFLIGHT 4
+#endif
+
+#define I2S_GPIO_UNUSED -1      /* For signals which are not used */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Role of the I2S port */
+
+typedef enum
+{
+  I2S_ROLE_MASTER,  /* I2S controller master role, bclk and ws signal will be 
set to output */
+  I2S_ROLE_SLAVE    /* I2S controller slave role, bclk and ws signal will be 
set to input */
+} i2s_role_t;
+
+/* Data width of the I2S channel */
+
+typedef enum
+{
+  I2S_DATA_BIT_WIDTH_8BIT   = 8,    /* I2S channel data bit-width: 8 */
+  I2S_DATA_BIT_WIDTH_16BIT  = 16,   /* I2S channel data bit-width: 16 */
+  I2S_DATA_BIT_WIDTH_24BIT  = 24,   /* I2S channel data bit-width: 24 */
+  I2S_DATA_BIT_WIDTH_32BIT  = 32,   /* I2S channel data bit-width: 32 */
+} i2s_data_bit_width_t;
+
+/* Multiplier of MCLK to sample rate */
+
+typedef enum
+{
+  I2S_MCLK_MULTIPLE_128 = 128,  /* mclk = sample_rate * 128 */
+  I2S_MCLK_MULTIPLE_256 = 256,  /* mclk = sample_rate * 256 */
+  I2S_MCLK_MULTIPLE_384 = 384,  /* mclk = sample_rate * 384 */
+  I2S_MCLK_MULTIPLE_512 = 512,  /* mclk = sample_rate * 512 */
+} i2s_mclk_multiple_t;
+
+/* I2S Clock Source */
+
+typedef enum
+{
+  I2S_XTAL_CLK = 0,
+  I2S_PLL_D2_CLK,
+  I2S_PLL_F160M_CLK,
+  I2S_MCLK_IN,
+} i2s_clk_src_t;
+
+/* I2S Audio Standard Mode */
+
+typedef enum
+{
+  I2S_TDM_PHILIPS = 0,
+  I2S_TDM_MSB,
+  I2S_TDM_PCM,
+  I2S_PDM,
+} i2s_audio_mode_t;
+
+/* Role of the I2S port */
+
+typedef enum
+{
+  I2S_RX,   /* I2S controller receiver (RX) */
+  I2S_TX    /* I2S controller transmitter (TX) */
+} i2s_dir_t;
+
+/* I2S Device hardware configuration */
+
+struct esp32s3_i2s_config_s
+{
+  uint32_t port;              /* I2S port */
+  uint32_t role;              /* I2S port role (master or slave) */
+  uint8_t data_width;         /* I2S sample data width */
+  uint32_t rate;              /* I2S sample-rate */
+  uint32_t total_slot;        /* Total slot number */
+
+  bool tx_en;                 /* Is TX enabled? */
+  bool rx_en;                 /* Is RX enabled? */
+  int8_t mclk_pin;            /* MCLK pin, output */
+
+  int tx_clk_src;             /* Select the I2S TX source clock */
+  int rx_clk_src;             /* Select the I2S TX source clock */
+
+  /* BCLK pin, input in slave role, output in master role */
+
+  int8_t bclk_pin;
+
+  /* WS pin, input in slave role, output in master role */
+
+  int8_t ws_pin;
+
+  int8_t dout_pin;            /* DATA pin, output */
+  int8_t din_pin;             /* DATA pin, input */
+
+  uint32_t bclk_in_insig;     /* RX channel BCK signal (slave mode) index */
+  uint32_t bclk_in_outsig;    /* RX channel BCK signal (master mode) index */
+  uint32_t bclk_out_insig;    /* TX channel BCK signal (slave mode) index */
+  uint32_t bclk_out_outsig;   /* TX channel BCK signal (master mode) index */
+  uint32_t ws_in_insig;       /* RX channel WS signal (slave mode) index */
+  uint32_t ws_in_outsig;      /* RX channel WS signal (master mode) index */
+  uint32_t ws_out_insig;      /* TX channel WS signal (slave mode) index */
+  uint32_t ws_out_outsig;     /* TX channel WS signal (master mode) index */
+  uint32_t din_insig;         /* RX channel Data Input signal index */
+  uint32_t dout_outsig;       /* TX channel Data Output signal index */
+  uint32_t mclk_out_sig;      /* Master clock output index */
+
+  uint8_t  audio_std_mode;    /* Select audio standard (i2s_audio_mode_t) */
+
+  /* WS signal polarity, set true to enable high level first */
+
+  bool ws_pol;
+};
+
+struct esp32s3_buffer_s
+{
+  struct esp32s3_buffer_s *flink; /* Supports a singly linked list */
+
+  /* The associated DMA in/outlink */
+
+  struct esp32s3_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 */
+};
+
+/* 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 esp32s3_buffer_carry_s
+{
+  uint32_t value;
+  size_t bytes;
+};
+
+/* This structure describes the state of one receiver or transmitter
+ * transport.
+ */
+
+struct esp32s3_transport_s
+{
+  sq_queue_t pend;              /* A queue of pending transfers */
+  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 esp32s3_buffer_carry_s carry;
+};
+
+/* The state of the one I2S peripheral */
+
+struct esp32s3_i2s_s
+{
+  struct i2s_dev_s  dev;        /* Externally visible I2S interface */
+  mutex_t           lock;       /* Ensures mutually exclusive access */
+  uint8_t           cpu;        /* CPU ID */
+  spinlock_t        slock;      /* Device specific lock. */
+
+  /* Port configuration */
+
+  const struct esp32s3_i2s_config_s *config;
+
+  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 */
+  uint32_t    dma_channel;    /* I2S DMA channel being used */
+
+#ifdef I2S_HAVE_TX
+  struct esp32s3_transport_s tx;  /* TX transport state */
+
+  int  tx_irq;                    /* TX IRQ */
+  bool tx_started;                /* TX channel started */
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+  struct esp32s3_transport_s rx;  /* RX transport state */
+
+  int  rx_irq;                    /* RX IRQ */
+  bool rx_started;                /* RX channel started */
+#endif /* I2S_HAVE_RX */
+
+  bool streaming;                 /* Is I2S peripheral active? */
+
+  /* Pre-allocated pool of buffer containers */
+
+  sem_t bufsem;                         /* Buffer wait semaphore */
+  struct esp32s3_buffer_s *bf_freelist; /* A list a free buffer containers */
+  struct esp32s3_buffer_s containers[CONFIG_ESP32S3_I2S_MAXINFLIGHT];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Register helpers */
+
+#ifdef CONFIG_ESP32S3_I2S_DUMPBUFFERS
+#  define       i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
+#else
+#  define       i2s_dump_buffer(m,b,s)
+#endif
+
+/* Buffer container helpers */
+
+static struct esp32s3_buffer_s *
+                i2s_buf_allocate(struct esp32s3_i2s_s *priv);
+static void     i2s_buf_free(struct esp32s3_i2s_s *priv,
+                             struct esp32s3_buffer_s *bfcontainer);
+static int      i2s_buf_initialize(struct esp32s3_i2s_s *priv);
+
+/* DMA support */
+
+#ifdef I2S_HAVE_TX
+static IRAM_ATTR int  i2s_txdma_setup(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_buffer_s *bfcontainer);
+static void           i2s_tx_worker(void *arg);
+static void           i2s_tx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink);
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+static IRAM_ATTR int  i2s_rxdma_setup(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_buffer_s *bfcontainer);
+static void           i2s_rx_worker(void *arg);
+static void           i2s_rx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink);
+#endif /* I2S_HAVE_RX */
+
+/* I2S methods (and close friends) */
+
+static int32_t  i2s_check_mclkfrequency(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_set_datawidth(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_set_clock(struct esp32s3_i2s_s *priv);
+static uint32_t i2s_getmclkfrequency(struct i2s_dev_s *dev);
+static uint32_t i2s_setmclkfrequency(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 esp32s3_i2s_s *priv);
+static void     i2s_tx_channel_stop(struct esp32s3_i2s_s *priv);
+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,
+                         i2s_callback_t callback, void *arg,
+                         uint32_t timeout);
+#endif /* I2S_HAVE_TX */
+
+#ifdef I2S_HAVE_RX
+static void     i2s_rx_channel_start(struct esp32s3_i2s_s *priv);
+static void     i2s_rx_channel_stop(struct esp32s3_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
+ ****************************************************************************/
+
+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_getmclkfrequency  = i2s_getmclkfrequency,
+  .i2s_setmclkfrequency  = i2s_setmclkfrequency,
+};
+
+#ifdef CONFIG_ESP32S3_I2S0
+static const struct esp32s3_i2s_config_s esp32s3_i2s0_config =
+{
+  .port             = 0,
+#ifdef CONFIG_ESP32S3_I2S0_ROLE_MASTER
+  .role             = I2S_ROLE_MASTER,
+#else
+  .role             = I2S_ROLE_SLAVE,
+#endif /* CONFIG_ESP32S3_I2S0_ROLE_MASTER */
+  .data_width       = CONFIG_ESP32S3_I2S0_DATA_BIT_WIDTH,
+  .rate             = CONFIG_ESP32S3_I2S0_SAMPLE_RATE,
+  .total_slot       = 2,
+  .tx_en            = I2S0_TX_ENABLED,
+  .rx_en            = I2S0_RX_ENABLED,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+  .rx_clk_src       = I2S_PLL_D2_CLK,
+#ifdef CONFIG_ESP32S3_I2S0_MCLK
+  .mclk_pin         = CONFIG_ESP32S3_I2S0_MCLKPIN,
+#else
+  .mclk_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_MCLK */
+  .bclk_pin         = CONFIG_ESP32S3_I2S0_BCLKPIN,
+  .ws_pin           = CONFIG_ESP32S3_I2S0_WSPIN,
+#ifdef CONFIG_ESP32S3_I2S0_DOUTPIN
+  .dout_pin         = CONFIG_ESP32S3_I2S0_DOUTPIN,
+#else
+  .dout_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_DOUTPIN */
+#ifdef CONFIG_ESP32S3_I2S0_DINPIN
+  .din_pin          = CONFIG_ESP32S3_I2S0_DINPIN,
+#else
+  .din_pin          = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S0_DINPIN */
+  .bclk_in_insig    = I2S0I_BCK_IN_IDX,
+  .bclk_in_outsig   = I2S0I_BCK_OUT_IDX,
+  .bclk_out_insig   = I2S0O_BCK_IN_IDX,
+  .bclk_out_outsig  = I2S0O_BCK_OUT_IDX,
+  .ws_in_insig      = I2S0I_WS_IN_IDX,
+  .ws_in_outsig     = I2S0I_WS_OUT_IDX,
+  .ws_out_insig     = I2S0O_WS_IN_IDX,
+  .ws_out_outsig    = I2S0O_WS_OUT_IDX,
+  .din_insig        = I2S0I_SD_IN_IDX,
+  .dout_outsig      = I2S0O_SD_OUT_IDX,
+  .mclk_out_sig     = I2S0_MCLK_OUT_IDX,
+  .audio_std_mode   = I2S_TDM_PHILIPS,
+};
+
+static struct esp32s3_i2s_s esp32s3_i2s0_priv =
+{
+  .dev =
+  {
+    .ops = &g_i2sops,
+  },
+  .lock = NXMUTEX_INITIALIZER,
+  .config = &esp32s3_i2s0_config,
+  .bufsem = SEM_INITIALIZER(0),
+};
+#endif /* CONFIG_ESP32S3_I2S0 */
+
+#ifdef CONFIG_ESP32S3_I2S1
+static const struct esp32s3_i2s_config_s esp32s3_i2s1_config =
+{
+  .port             = 1,
+#ifdef CONFIG_ESP32S3_I2S1_ROLE_MASTER
+  .role             = I2S_ROLE_MASTER,
+#else
+  .role             = I2S_ROLE_SLAVE,
+#endif /* CONFIG_ESP32S3_I2S1_ROLE_MASTER */
+  .data_width       = CONFIG_ESP32S3_I2S1_DATA_BIT_WIDTH,
+  .rate             = CONFIG_ESP32S3_I2S1_SAMPLE_RATE,
+  .total_slot       = 2,
+  .tx_en            = I2S1_TX_ENABLED,
+  .rx_en            = I2S1_RX_ENABLED,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+  .tx_clk_src       = I2S_PLL_D2_CLK,
+#ifdef CONFIG_ESP32S3_I2S1_MCLK
+  .mclk_pin         = CONFIG_ESP32S3_I2S1_MCLKPIN,
+#else
+  .mclk_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_MCLK */
+  .bclk_pin         = CONFIG_ESP32S3_I2S1_BCLKPIN,
+  .ws_pin           = CONFIG_ESP32S3_I2S1_WSPIN,
+#ifdef CONFIG_ESP32S3_I2S1_DOUTPIN
+  .dout_pin         = CONFIG_ESP32S3_I2S1_DOUTPIN,
+#else
+  .dout_pin         = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_DOUTPIN */
+#ifdef CONFIG_ESP32S3_I2S1_DINPIN
+  .din_pin          = CONFIG_ESP32S3_I2S1_DINPIN,
+#else
+  .din_pin          = I2S_GPIO_UNUSED,
+#endif /* CONFIG_ESP32S3_I2S1_DINPIN */
+  .bclk_in_insig    = I2S1I_BCK_IN_IDX,
+  .bclk_in_outsig   = I2S1I_BCK_OUT_IDX,
+  .bclk_out_insig   = I2S1O_BCK_IN_IDX,
+  .bclk_out_outsig  = I2S1O_BCK_OUT_IDX,
+  .ws_in_insig      = I2S1I_WS_IN_IDX,
+  .ws_in_outsig     = I2S1I_WS_OUT_IDX,
+  .ws_out_insig     = I2S1O_WS_IN_IDX,
+  .ws_out_outsig    = I2S1O_WS_OUT_IDX,
+  .din_insig        = I2S1I_SD_IN_IDX,
+  .dout_outsig      = I2S1O_SD_OUT_IDX,
+  .mclk_out_sig     = I2S1_MCLK_OUT_IDX,
+  .audio_std_mode   = I2S_TDM_PHILIPS,
+};
+
+static struct esp32s3_i2s_s esp32s3_i2s1_priv =
+{
+  .dev =
+  {
+    .ops = &g_i2sops,
+  },
+  .lock = NXMUTEX_INITIALIZER,
+  .config = &esp32s3_i2s1_config,
+  .bufsem = SEM_INITIALIZER(0),
+};
+#endif /* CONFIG_ESP32S3_I2S1 */
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: i2s_buf_allocate
+ *
+ * Description:
+ *   Allocate a buffer container by removing the one at the head of the
+ *   free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   A non-NULL pointer to the allocate buffer container on success; NULL if
+ *   there are no available buffer containers.
+ *
+ * Assumptions:
+ *   The caller does NOT have exclusive access to the I2S state structure.
+ *   That would result in a deadlock!
+ *
+ ****************************************************************************/
+
+static struct esp32s3_buffer_s *i2s_buf_allocate(struct esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  irqstate_t flags;
+  int ret;
+
+  /* Set aside a buffer container.  By doing this, we guarantee that we will
+   * have at least one free buffer container.
+   */
+
+  ret = nxsem_wait_uninterruptible(&priv->bufsem);
+  if (ret < 0)
+    {
+      return NULL;
+    }
+
+  /* Get the buffer from the head of the free list */
+
+  flags = spin_lock_irqsave(&priv->slock);
+  bfcontainer = priv->bf_freelist;
+  DEBUGASSERT(bfcontainer);
+
+  /* Unlink the buffer from the freelist */
+
+  priv->bf_freelist = bfcontainer->flink;
+  spin_unlock_irqrestore(&priv->slock, flags);
+  return bfcontainer;
+}
+
+/****************************************************************************
+ * Name: i2s_buf_free
+ *
+ * Description:
+ *   Free buffer container by adding it to the head of the free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   bfcontainer - The buffer container to be freed
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   The caller has exclusive access to the I2S state structure
+ *
+ ****************************************************************************/
+
+static void i2s_buf_free(struct esp32s3_i2s_s *priv,
+                         struct esp32s3_buffer_s *bfcontainer)
+{
+  irqstate_t flags;
+
+  /* Put the buffer container back on the free list (circbuf) */
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  bfcontainer->apb = NULL;
+  bfcontainer->buf = NULL;
+  bfcontainer->nbytes = 0;
+  bfcontainer->flink  = priv->bf_freelist;
+  priv->bf_freelist = bfcontainer;
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  /* Wake up any threads waiting for a buffer container */
+
+  nxsem_post(&priv->bufsem);
+}
+
+/****************************************************************************
+ * Name: i2s_buf_initialize
+ *
+ * Description:
+ *   Initialize the buffer container allocator by adding all of the
+ *   pre-allocated buffer containers to the free list
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *
+ * Returned Value:
+ *   OK on success; A negated errno value on failure.
+ *
+ * Assumptions:
+ *   Called early in I2S initialization so that there are no issues with
+ *   concurrency.
+ *
+ ****************************************************************************/
+
+static int i2s_buf_initialize(struct esp32s3_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;
+  for (int i = 0; i < CONFIG_ESP32S3_I2S_MAXINFLIGHT; i++)
+    {
+      i2s_buf_free(priv, &priv->containers[i]);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_txdma_start
+ *
+ * Description:
+ *   Initiate the next TX DMA transfer. The DMA outlink was previously bound
+ *   so it is safe to start the next DMA transfer at interrupt level.
+ *
+ * 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_TX
+static int IRAM_ATTR i2s_txdma_start(struct esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+
+  /* If there is already an active transmission in progress, then bail
+   * returning success.
+   */
+
+  if (!sq_empty(&priv->tx.act))
+    {
+      return OK;
+    }
+
+  /* If there are no pending transfer, then bail returning success */
+
+  if (sq_empty(&priv->tx.pend))
+    {
+      return OK;
+    }
+
+  /* Start transmission if no data is already being transmitted */
+
+  bfcontainer = (struct esp32s3_buffer_s *)sq_remfirst(&priv->tx.pend);
+
+  esp32s3_dma_load(bfcontainer->dma_link, priv->dma_channel, I2S_TX);
+  esp32s3_dma_enable(priv->dma_channel, I2S_TX);
+
+  modifyreg32(I2S_TX_CONF_REG(priv->config->port), 0, I2S_TX_START);
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
+
+  return OK;
+}
+#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 esp32s3_i2s_s *priv)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  size_t eof_nbytes;
+
+  /* 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 esp32s3_buffer_s *)sq_remfirst(&priv->rx.pend);
+
+  /* If there isn't already an active transmission in progress,
+   * then start it.
+   */
+
+  eof_nbytes = MIN(bfcontainer->nbytes, ESP32S3_DMA_BUFLEN_MAX);
+
+  modifyreg32(I2S_RXEOF_NUM_REG(priv->config->port), I2S_RX_EOF_NUM_M,
+              FIELD_TO_VALUE(I2S_RX_EOF_NUM, eof_nbytes));
+
+  esp32s3_dma_load(bfcontainer->dma_link, priv->dma_channel, I2S_RX);
+  esp32s3_dma_enable(priv->dma_channel, I2S_RX);
+
+  modifyreg32(I2S_RX_CONF_REG(priv->config->port), 0, I2S_RX_START);
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->rx.act);
+
+  return OK;
+}
+#endif /* I2S_HAVE_RX */
+
+/****************************************************************************
+ * Name: i2s_txdma_setup
+ *
+ * Description:
+ *   Setup the next TX 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_TX
+static IRAM_ATTR int i2s_txdma_setup(struct esp32s3_i2s_s *priv,
+                                     struct esp32s3_buffer_s *bfcontainer)
+{
+  int ret = OK;
+  size_t carry_size;
+  uint32_t bytes_queued;
+  uint32_t data_copied;
+  struct ap_buffer_s *apb;
+  struct esp32s3_dmadesc_s *outlink;
+  apb_samp_t samp_size;
+  irqstate_t flags;
+  uint8_t *buf;
+  uint8_t padding;
+  uint8_t *samp;
+
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  apb = bfcontainer->apb;
+  outlink = bfcontainer->dma_link;
+
+  /* Get the transfer information, accounting for any data offset */
+
+  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 = calloc(bfcontainer->nbytes, 1);
+  if (bfcontainer->buf == NULL)
+    {
+      i2serr("Failed to allocate the DMA internal buffer "
+             "[%" PRIu32 " bytes]", bfcontainer->nbytes);
+      return -ENOMEM;
+    }
+
+  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((uint8_t *)&priv->tx.carry.value, samp, priv->tx.carry.bytes);
+    }
+
+  /* Release our reference on the audio buffer. This may very likely
+   * cause the audio buffer to be freed.
+   */
+
+  apb_free(bfcontainer->apb);
+
+  /* Configure DMA stream */
+
+  bytes_queued = esp32s3_dma_setup(outlink, I2S_DMADESC_NUM,
+                                   bfcontainer->buf,
+                                   bfcontainer->nbytes, I2S_TX);
+
+  if (bytes_queued != bfcontainer->nbytes)
+    {
+      i2serr("Failed to enqueue I2S buffer "
+             "(%" PRIu32 " bytes of %" PRIu32 ")\n",
+             bytes_queued, bfcontainer->nbytes);
+      return -bytes_queued;
+    }
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  /* Add the buffer container to the end of the TX pending queue */
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
+
+  /* Trigger DMA transfer if no transmission is in progress */
+
+  ret = i2s_txdma_start(priv);
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  return ret;
+}
+#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 esp32s3_i2s_s *priv,
+                           struct esp32s3_buffer_s *bfcontainer)
+{
+  int ret = OK;
+  struct esp32s3_dmadesc_s *inlink;
+  uint32_t bytes_queued;
+  irqstate_t flags;
+
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  inlink = bfcontainer->dma_link;
+
+  /* Configure DMA stream */
+
+  bytes_queued = esp32s3_dma_setup(inlink, I2S_DMADESC_NUM,
+                                   bfcontainer->apb->samp,
+                                   bfcontainer->nbytes, I2S_RX);
+
+  if (bytes_queued != bfcontainer->nbytes)
+    {
+      i2serr("Failed to enqueue I2S buffer "
+             "(%" PRIu32 " bytes of %" PRIu32 ")\n",
+             bytes_queued, bfcontainer->nbytes);
+      return -bytes_queued;
+    }
+
+  flags = spin_lock_irqsave(&priv->slock);
+
+  /* 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);
+
+  spin_unlock_irqrestore(&priv->slock, flags);
+
+  return ret;
+}
+#endif /* I2S_HAVE_RX */
+
+/****************************************************************************
+ * Name: i2s_tx_schedule
+ *
+ * Description:
+ *   An TX DMA completion has occurred.  Schedule processing on
+ *   the working thread.
+ *
+ * Input Parameters:
+ *   priv - Initialized I2S device structure.
+ *   outlink - DMA outlink descriptor that triggered the interrupt.
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Interrupts are disabled
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX
+static void IRAM_ATTR i2s_tx_schedule(struct esp32s3_i2s_s *priv,
+                                      struct esp32s3_dmadesc_s *outlink)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  struct esp32s3_dmadesc_s *bfdesc;
+  int ret;
+
+  /* Upon entry, the transfer(s) that just completed are the ones in the
+   * priv->tx.act queue.
+   */
+
+  /* Move all entries from the tx.act queue to the tx.done queue */
+
+  if (!sq_empty(&priv->tx.act))
+    {
+      /* Remove the next buffer container from the tx.act list */
+
+      bfcontainer = (struct esp32s3_buffer_s *)sq_peek(&priv->tx.act);
+
+      /* Check if the DMA descriptor that generated an EOF interrupt is the
+       * last descriptor of the current buffer container's DMA outlink.
+       * 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 & ESP32S3_DMA_CTRL_EOF))
+        {
+          DEBUGASSERT(bfdesc->next);
+          bfdesc = bfdesc->next;
+        }
+
+      if (bfdesc == outlink)
+        {
+          sq_remfirst(&priv->tx.act);
+
+          /* Report the result of the transfer */
+
+          bfcontainer->result = OK;
+
+          /* Add the completed buffer container to the tail of the tx.done
+           * queue
+           */
+
+          sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
+
+          /* Check if the DMA is IDLE */
+
+          if (sq_empty(&priv->tx.act))
+            {
+              /* Then start the next DMA. */
+
+              i2s_txdma_start(priv);
+            }
+        }
+
+      /* If the worker has completed running, then reschedule the working
+       * thread.
+       */
+
+      if (work_available(&priv->tx.work))
+        {
+          /* Schedule the TX DMA done processing to occur on the worker
+           * thread.
+           */
+
+          ret = work_queue(HPWORK, &priv->tx.work, i2s_tx_worker, priv, 0);
+          if (ret != 0)
+            {
+              i2serr("ERROR: Failed to queue TX work: %d\n", ret);
+            }
+        }
+    }
+}
+#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 esp32s3_i2s_s *priv,
+                            struct esp32s3_dmadesc_s *inlink)
+{
+  struct esp32s3_buffer_s *bfcontainer;
+  struct esp32s3_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 esp32s3_buffer_s *)sq_peek(&priv->rx.act);
+
+      /* Find the last descriptor of the current buffer container */
+
+      bfdesc = bfcontainer->dma_link;
+
+      while (bfdesc->next != NULL &&
+             (bfdesc->next->ctrl & ESP32S3_DMA_CTRL_EOF))
+        {
+          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
+ *
+ * Description:
+ *   TX transfer done worker
+ *
+ * Input Parameters:
+ *   arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+#ifdef I2S_HAVE_TX
+static void i2s_tx_worker(void *arg)
+{
+  struct esp32s3_i2s_s *priv = (struct esp32s3_i2s_s *)arg;
+  struct esp32s3_buffer_s *bfcontainer;
+  irqstate_t flags;
+
+  DEBUGASSERT(priv);
+
+  /* When the transfer was started, the active buffer containers were removed
+   * from the tx.pend queue and saved in the tx.act queue.  We get here when
+   * the DMA is finished.
+   *
+   * In any case, the buffer containers in tx.act will be moved to the end
+   * of the tx.done queue and tx.act will be emptied before this worker is
+   * started.
+   *
+   */
+
+  i2sinfo("tx.act.head=%p tx.done.head=%p\n",
+           priv->tx.act.head, priv->tx.done.head);
+
+  /* Process each buffer in the tx.done queue */
+
+  while (sq_peek(&priv->tx.done) != NULL)
+    {
+      /* Remove the buffer container from the tx.done queue.  NOTE that
+       * interrupts must be disabled to do this because the tx.done queue is
+       * also modified from the interrupt level.
+       */
+
+      flags = spin_lock_irqsave(&priv->slock);
+      bfcontainer = (struct esp32s3_buffer_s *)sq_remfirst(&priv->tx.done);
+      spin_unlock_irqrestore(&priv->slock, flags);
+
+      /* Perform the TX transfer done callback */
+
+      DEBUGASSERT(bfcontainer && bfcontainer->callback);
+      bfcontainer->callback(&priv->dev, bfcontainer->apb,
+                            bfcontainer->arg, bfcontainer->result);
+
+      /* Release the internal buffer used by the DMA outlink */
+
+      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 esp32s3_i2s_s *priv = (struct esp32s3_i2s_s *)arg;
+  struct esp32s3_buffer_s *bfcontainer;
+  struct esp32s3_dmadesc_s *dmadesc;
+  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);

Review Comment:
   done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to