You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ma...@apache.org on 2021/04/19 00:40:04 UTC

[incubator-nuttx] 02/03: arm/rp2040: Add RP2040 I2S driver

This is an automated email from the ASF dual-hosted git repository.

masayuki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git

commit 048802bcd283f48bc48da2219caf3e134a0f38d6
Author: Yuichi Nakamura <y....@gmail.com>
AuthorDate: Sun Apr 18 19:05:37 2021 +0900

    arm/rp2040: Add RP2040 I2S driver
---
 arch/arm/src/rp2040/Kconfig          |   43 ++
 arch/arm/src/rp2040/Make.defs        |    5 +
 arch/arm/src/rp2040/rp2040_i2s.c     | 1389 ++++++++++++++++++++++++++++++++++
 arch/arm/src/rp2040/rp2040_i2s.h     |   74 ++
 arch/arm/src/rp2040/rp2040_i2s_pio.c |  382 ++++++++++
 arch/arm/src/rp2040/rp2040_i2s_pio.h |  105 +++
 6 files changed, 1998 insertions(+)

diff --git a/arch/arm/src/rp2040/Kconfig b/arch/arm/src/rp2040/Kconfig
index 14c8cfd..cd0c35c 100644
--- a/arch/arm/src/rp2040/Kconfig
+++ b/arch/arm/src/rp2040/Kconfig
@@ -141,6 +141,49 @@ config RP2040_I2C_DRIVER
 
 endif
 
+config RP2040_I2S
+	bool "I2S"
+	select I2S
+
+if RP2040_I2S
+
+config RP2040_I2S_MAXINFLIGHT
+	int "I2S queue size"
+	default 16
+	---help---
+		This is the total number of transfers that can be enqueue before
+		the caller is required to wait.  This setting determines the number
+		certain queue data structures that will be pre-allocated.
+
+config RP2040_I2S_DATALEN
+	int "Data width (bits)"
+	default 16
+	---help---
+		Data width in bits.  This is a default value and may be change
+		via the I2S interface
+
+config RP2040_I2S_DATA
+	int "I2S DATA GPIO pin assign (0-29)"
+	default 9
+	range 0 29
+
+config RP2040_I2S_CLOCK
+	int "I2S CLOCk GPIO pin assign (0-29)"
+	default 10
+	range 0 29
+
+config RP2040_I2S_PIO
+	int "RP2040 PIO number used for I2S (0-1)"
+	default 0
+	range 0 1
+
+config RP2040_I2S_PIO_SM
+	int "RP2040 PIO state machine number used for I2S (0-3)"
+	default 0
+	range 0 3
+
+endif
+
 menuconfig RP2040_SPISD
 	bool "SPI SD Card"
 	default n
diff --git a/arch/arm/src/rp2040/Make.defs b/arch/arm/src/rp2040/Make.defs
index f15c01a..a640d43 100644
--- a/arch/arm/src/rp2040/Make.defs
+++ b/arch/arm/src/rp2040/Make.defs
@@ -80,6 +80,11 @@ ifeq ($(CONFIG_RP2040_I2C),y)
 CHIP_CSRCS += rp2040_i2c.c
 endif
 
+ifeq ($(CONFIG_RP2040_I2S),y)
+CHIP_CSRCS += rp2040_i2s.c
+CHIP_CSRCS += rp2040_i2s_pio.c
+endif
+
 ifeq ($(CONFIG_RP2040_FLASH_BOOT),y)
 ifneq ($(PICO_SDK_PATH),)
 include chip/boot2/Make.defs
diff --git a/arch/arm/src/rp2040/rp2040_i2s.c b/arch/arm/src/rp2040/rp2040_i2s.c
new file mode 100644
index 0000000..89eaa24
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_i2s.c
@@ -0,0 +1,1389 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_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>
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <queue.h>
+#include <debug.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/wdog.h>
+#include <nuttx/wqueue.h>
+#include <nuttx/spi/spi.h>
+#include <nuttx/audio/audio.h>
+#include <nuttx/audio/i2s.h>
+
+#include <arch/board/board.h>
+
+#include "arm_internal.h"
+#include "arm_arch.h"
+
+#include "rp2040_gpio.h"
+#include "rp2040_dmac.h"
+#include "rp2040_i2s_pio.h"
+
+#ifdef CONFIG_RP2040_I2S
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+#ifndef CONFIG_SCHED_WORKQUEUE
+#  error Work queue support is required (CONFIG_SCHED_WORKQUEUE)
+#endif
+
+#ifndef CONFIG_AUDIO
+#  error CONFIG_AUDIO required by this driver
+#endif
+
+#ifndef CONFIG_RP2040_I2S_MAXINFLIGHT
+#  define CONFIG_RP2040_I2S_MAXINFLIGHT 16
+#endif
+
+/* Debug ********************************************************************/
+
+/* Check if SSC debug is enabled (non-standard.. no support in
+ * include/debug.h
+ */
+
+#ifndef CONFIG_DEBUG_I2S_INFO
+#  undef CONFIG_RP2040_I2S_DUMPBUFFERS
+#endif
+
+/* The I2S can handle most any bit width from 8 to 32.  However, the DMA
+ * logic here is constrained to byte, half-word, and word sizes.
+ */
+
+#ifndef CONFIG_RP2040_I2S_DATALEN
+#  define CONFIG_RP2040_I2S_DATALEN 16
+#endif
+
+#if CONFIG_RP2040_I2S_DATALEN == 8
+#  define RP2040_I2S_DATAMASK  0
+#elif CONFIG_RP2040_I2S_DATALEN == 16
+#  define RP2040_I2S_DATAMASK  1
+#elif  CONFIG_RP2040_I2S_DATALEN < 8 || CONFIG_RP2040_I2S_DATALEN > 16
+#  error Invalid value for CONFIG_RP2040_I2S_DATALEN
+#else
+#  error Valid but supported value for CONFIG_RP2040_I2S_DATALEN
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* I2S buffer container */
+
+struct rp2040_buffer_s
+{
+  struct rp2040_buffer_s *flink; /* Supports a singly linked list */
+  i2s_callback_t callback;       /* Function to call when the transfer
+                                  * completes */
+  uint32_t timeout;              /* The timeout value to use with DMA
+                                  * transfers */
+  void *arg;                     /* The argument to be returned with the
+                                  * callback */
+  struct ap_buffer_s *apb;       /* The audio buffer */
+  int result;                    /* The result of the transfer */
+};
+
+/* This structure describes the state of one receiver or transmitter
+ * transport.
+ */
+
+struct rp2040_transport_s
+{
+  DMA_HANDLE dma;               /* I2S DMA handle */
+  struct wdog_s dog;            /* Watchdog that handles DMA timeouts */
+  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 */
+  uint32_t timeout;             /* Current DMA timeout value */
+};
+
+/* The state of the one I2S peripheral */
+
+struct rp2040_i2s_s
+{
+  struct i2s_dev_s  dev;            /* Externally visible I2S interface */
+  sem_t             exclsem;        /* Assures mutually exclusive access to I2S */
+  bool              initialized;    /* Has I2S interface been initialized */
+  uint8_t           datalen;        /* Data width (8 or 16) */
+#ifdef CONFIG_DEBUG_FEATURES
+  uint8_t           align;          /* Log2 of data width (0 or 1) */
+#endif
+  uint32_t          samplerate;     /* Data sample rate */
+  uint32_t          channels;       /* Audio channels (1:mono or 2:stereo) */
+  dma_config_t      txconfig;       /* TX DMA configuration */
+  struct rp2040_transport_s tx;     /* TX transport state */
+
+  /* Pre-allocated pool of buffer containers */
+
+  sem_t bufsem;                     /* Buffer wait semaphore */
+  struct rp2040_buffer_s *freelist; /* A list a free buffer containers */
+  struct rp2040_buffer_s containers[CONFIG_RP2040_I2S_MAXINFLIGHT];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Register helpers */
+
+#ifdef CONFIG_RP2040_I2S_DUMPBUFFERS
+#  define       i2s_dump_buffer(m,b,s) lib_dumpbuffer(m,b,s)
+#else
+#  define       i2s_dump_buffer(m,b,s)
+#endif
+
+/* Semaphore helpers */
+
+static int      i2s_exclsem_take(struct rp2040_i2s_s *priv);
+#define         i2s_exclsem_give(priv) nxsem_post(&priv->exclsem)
+
+static int      i2s_bufsem_take(struct rp2040_i2s_s *priv);
+#define         i2s_bufsem_give(priv) nxsem_post(&priv->bufsem)
+
+/* Buffer container helpers */
+
+static struct rp2040_buffer_s *
+                i2s_buf_allocate(struct rp2040_i2s_s *priv);
+static void     i2s_buf_free(struct rp2040_i2s_s *priv,
+                             struct rp2040_buffer_s *bfcontainer);
+static void     i2s_buf_initialize(struct rp2040_i2s_s *priv);
+
+/* DMA support */
+
+static void     i2s_txdma_timeout(wdparm_t arg);
+static int      i2s_txdma_setup(struct rp2040_i2s_s *priv);
+static void     i2s_tx_worker(void *arg);
+static void     i2s_tx_schedule(struct rp2040_i2s_s *priv, int result);
+static void     i2s_txdma_callback(DMA_HANDLE handle, uint8_t result,
+                                   void *arg);
+
+/* I2S methods (and close friends) */
+
+static int      i2s_checkwidth(struct rp2040_i2s_s *priv, int bits);
+
+static int      rp2040_i2s_txchannels(struct i2s_dev_s *dev,
+                                      uint8_t channels);
+static uint32_t rp2040_i2s_txsamplerate(struct i2s_dev_s *dev,
+                                        uint32_t rate);
+static uint32_t rp2040_i2s_txdatawidth(struct i2s_dev_s *dev, int bits);
+static int      rp2040_i2s_send(struct i2s_dev_s *dev,
+                                struct ap_buffer_s *apb,
+                                i2s_callback_t callback, void *arg,
+                                uint32_t timeout);
+static int      rp2040_i2s_ioctl(struct i2s_dev_s *dev, int cmd,
+                                 unsigned long arg);
+
+/* Initialization */
+
+static int      i2s_dma_flags(struct rp2040_i2s_s *priv);
+static int      i2s_dma_allocate(struct rp2040_i2s_s *priv);
+static void     i2s_dma_free(struct rp2040_i2s_s *priv);
+static void     i2s_configure(struct rp2040_i2s_s *priv);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* I2S device operations */
+
+static const struct i2s_ops_s g_i2sops =
+{
+  .i2s_txchannels   = rp2040_i2s_txchannels,
+  .i2s_txsamplerate = rp2040_i2s_txsamplerate,
+  .i2s_txdatawidth  = rp2040_i2s_txdatawidth,
+  .i2s_send         = rp2040_i2s_send,
+  .i2s_ioctl        = rp2040_i2s_ioctl,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: i2s_exclsem_take
+ *
+ * Description:
+ *   Take the exclusive access semaphore handling any exceptional conditions
+ *
+ * Input Parameters:
+ *   priv - A reference to the i2s peripheral state
+ *
+ * Returned Value:
+ *   Normally OK, but may return -ECANCELED in the rare event that the task
+ *   has been canceled.
+ *
+ ****************************************************************************/
+
+static int i2s_exclsem_take(struct rp2040_i2s_s *priv)
+{
+  return nxsem_wait_uninterruptible(&priv->exclsem);
+}
+
+/****************************************************************************
+ * Name: i2s_bufsem_take
+ *
+ * Description:
+ *   Take the buffer semaphore handling any exceptional conditions
+ *
+ * Input Parameters:
+ *   priv - A reference to the i2s peripheral state
+ *
+ * Returned Value:
+ *   Normally OK, but may return -ECANCELED in the rare event that the task
+ *   has been canceled.
+ *
+ ****************************************************************************/
+
+static int i2s_bufsem_take(struct rp2040_i2s_s *priv)
+{
+  return nxsem_wait_uninterruptible(&priv->bufsem);
+}
+
+/****************************************************************************
+ * Name: i2s_buf_allocate
+ *
+ * Description:
+ *   Allocate a buffer container by removing the one at the head of the
+ *   free list
+ *
+ * Input Parameters:
+ *   priv - I2S state instance
+ *
+ * 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 rp2040_buffer_s *i2s_buf_allocate(struct rp2040_i2s_s *priv)
+{
+  struct rp2040_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 = i2s_bufsem_take(priv);
+  if (ret < 0)
+    {
+      return NULL;
+    }
+
+  /* Get the buffer from the head of the free list */
+
+  flags = enter_critical_section();
+  bfcontainer = priv->freelist;
+  DEBUGASSERT(bfcontainer);
+
+  /* Unlink the buffer from the freelist */
+
+  priv->freelist = bfcontainer->flink;
+  leave_critical_section(flags);
+  return bfcontainer;
+}
+
+/****************************************************************************
+ * Name: i2s_buf_free
+ *
+ * Description:
+ *   Free buffer container by adding it to the head of the free list
+ *
+ * Input Parameters:
+ *   priv - I2S state instance
+ *   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 rp2040_i2s_s *priv,
+                         struct rp2040_buffer_s *bfcontainer)
+{
+  irqstate_t flags;
+
+  /* Put the buffer container back on the free list */
+
+  flags = enter_critical_section();
+  bfcontainer->flink  = priv->freelist;
+  priv->freelist = bfcontainer;
+  leave_critical_section(flags);
+
+  /* Wake up any threads waiting for a buffer container */
+
+  i2s_bufsem_give(priv);
+}
+
+/****************************************************************************
+ * 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 - I2S state instance
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Called early in I2S initialization so that there are no issues with
+ *   concurrency.
+ *
+ ****************************************************************************/
+
+static void i2s_buf_initialize(struct rp2040_i2s_s *priv)
+{
+  int i;
+
+  priv->freelist = NULL;
+  nxsem_init(&priv->bufsem, 0, CONFIG_RP2040_I2S_MAXINFLIGHT);
+
+  for (i = 0; i < CONFIG_RP2040_I2S_MAXINFLIGHT; i++)
+    {
+      i2s_buf_free(priv, &priv->containers[i]);
+    }
+}
+
+/****************************************************************************
+ * Name: i2s_txdma_timeout
+ *
+ * Description:
+ *   The TX watchdog timeout without completion of the TX DMA.
+ *
+ * Input Parameters:
+ *   arg - The argument
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   Always called from the interrupt level with interrupts disabled.
+ *
+ ****************************************************************************/
+
+static void i2s_txdma_timeout(wdparm_t arg)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)arg;
+  DEBUGASSERT(priv != NULL);
+
+  /* Cancel the DMA */
+
+  rp2040_dmastop(priv->tx.dma);
+
+  /* Then schedule completion of the transfer to occur on the worker thread.
+   */
+
+  i2s_tx_schedule(priv, -ETIMEDOUT);
+}
+
+/****************************************************************************
+ * Name: i2s_txdma_setup
+ *
+ * Description:
+ *   Setup and initiate the next TX DMA transfer
+ *
+ * Input Parameters:
+ *   priv - I2S state instance
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure
+ *
+ * Assumptions:
+ *   Interrupts are disabled
+ *
+ ****************************************************************************/
+
+static int i2s_txdma_setup(struct rp2040_i2s_s *priv)
+{
+  struct rp2040_buffer_s *bfcontainer;
+  struct ap_buffer_s *apb;
+  uintptr_t samp;
+  uint32_t timeout;
+  apb_samp_t nbytes;
+  int ret;
+
+  /* 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;
+    }
+
+  /* Adding the pending DMA */
+
+  /* Remove the pending TX transfer at the head of the TX pending
+   * queue.
+   */
+
+  bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.pend);
+  DEBUGASSERT(bfcontainer && bfcontainer->apb);
+
+  apb = bfcontainer->apb;
+
+  /* Get the transfer information, accounting for any data offset */
+
+  samp   = (uintptr_t)&apb->samp[apb->curbyte];
+  nbytes = apb->nbytes - apb->curbyte;
+#ifdef CONFIG_DEBUG_FEATURES
+  DEBUGASSERT((samp & priv->align) == 0 && (nbytes & priv->align) == 0);
+#endif
+
+  /* Configure DMA stream */
+
+  rp2040_txdmasetup(priv->tx.dma,
+                    rp2040_i2s_pio_getdmaaddr(),
+                    (uint32_t)samp, nbytes,
+                    priv->txconfig);
+
+  timeout = bfcontainer->timeout;
+
+  /* Add the container to the list of active DMAs */
+
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.act);
+
+  /* Start the DMA, saving the container as the current active transfer */
+
+  rp2040_dmastart(priv->tx.dma, i2s_txdma_callback, priv);
+  rp2040_i2s_pio_enable(true);
+
+  /* Start a watchdog to catch DMA timeouts */
+
+  if (timeout > 0)
+    {
+      ret = wd_start(&priv->tx.dog, timeout,
+                     i2s_txdma_timeout, (wdparm_t)priv);
+
+      priv->tx.timeout = timeout;
+
+      /* Check if we have successfully started the watchdog timer.  Note
+       * that we do nothing in the case of failure to start the timer.  We
+       * are already committed to the DMA anyway.  Let's just hope that the
+       * DMA does not hang.
+       */
+
+      if (ret < 0)
+        {
+          i2serr("ERROR: wd_start failed: %d\n", ret);
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_tx_worker
+ *
+ * Description:
+ *   TX transfer done worker
+ *
+ * Input Parameters:
+ *   arg - the I2S device instance cast to void*
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void i2s_tx_worker(void *arg)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)arg;
+  struct rp2040_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... either successfully, with a DMA error, or with a
+   * DMA timeout.
+   *
+   * 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);
+
+  /* Check if the DMA is IDLE */
+
+  if (sq_empty(&priv->tx.act))
+    {
+      /* Then start the next DMA.  This must be done with interrupts
+       * disabled.
+       */
+
+      flags = enter_critical_section();
+      i2s_txdma_setup(priv);
+      leave_critical_section(flags);
+    }
+
+  /* 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 enabled to do this because the tx.done queue is
+       * also modified from the interrupt level.
+       */
+
+      flags = enter_critical_section();
+      bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.done);
+      leave_critical_section(flags);
+
+      /* Perform the TX 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.
+       */
+
+      apb_free(bfcontainer->apb);
+
+      /* And release the buffer container */
+
+      i2s_buf_free(priv, bfcontainer);
+    }
+}
+
+/****************************************************************************
+ * Name: i2s_tx_schedule
+ *
+ * Description:
+ *   An TX DMA completion or timeout has occurred.  Schedule processing on
+ *   the working thread.
+ *
+ * Input Parameters:
+ *   priv - I2S state instance
+ *   result - The result of the DMA transfer
+ *
+ * Returned Value:
+ *   None
+ *
+ * Assumptions:
+ *   - Interrupts are disabled
+ *   - The TX timeout has been canceled.
+ *
+ ****************************************************************************/
+
+static void i2s_tx_schedule(struct rp2040_i2s_s *priv, int result)
+{
+  struct rp2040_buffer_s *bfcontainer;
+  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 */
+
+  while (!sq_empty(&priv->tx.act))
+    {
+      /* Remove the next buffer container from the tx.act list */
+
+      bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.act);
+
+      /* Report the result of the transfer */
+
+      bfcontainer->result = result;
+
+      /* Add the completed buffer container to the tail of the tx.done
+       * queue
+       */
+
+      sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.done);
+    }
+
+  /* If the worker has completed running, then reschedule the working thread.
+   * REVISIT:  There may be a race condition here.  So we do nothing is the
+   * worker is not available.
+   */
+
+  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);
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: i2s_txdma_callback
+ *
+ * Description:
+ *   This callback function is invoked at the completion of the I2S TX DMA.
+ *
+ * Input Parameters:
+ *   handle - The DMA handler
+ *   result - The result of the DMA transfer
+ *   arg - A pointer to the chip select struction
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void i2s_txdma_callback(DMA_HANDLE handle, uint8_t result, void *arg)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)arg;
+  DEBUGASSERT(priv != NULL);
+
+  /* Cancel the watchdog timeout */
+
+  if (priv->tx.timeout > 0)
+    {
+      wd_cancel(&priv->tx.dog);
+    }
+
+  /* Then schedule completion of the transfer to occur on the worker thread */
+
+  i2s_tx_schedule(priv, result);
+}
+
+/****************************************************************************
+ * Name: i2s_checkwidth
+ *
+ * Description:
+ *   Check for a valid bit width.  The I2S is capable of handling most any
+ *   bit width from 8 to 16, but the DMA logic in this driver is constrained
+ *   to 8- and 16-bit data widths
+ *
+ * Input Parameters:
+ *   dev  - Device-specific state data
+ *   bits - The I2S data with in bits.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int i2s_checkwidth(struct rp2040_i2s_s *priv, int bits)
+{
+  /* The I2S can handle most any bit width from 8 to 32.  However, the DMA
+   * logic here is constrained to byte, half-word, and word sizes.
+   */
+
+  switch (bits)
+    {
+    case 8:
+#ifdef CONFIG_DEBUG
+      priv->align = 0;
+#endif
+      break;
+
+    case 16:
+#ifdef CONFIG_DEBUG
+      priv->align = 1;
+#endif
+      break;
+
+    default:
+      i2serr("ERROR: Unsupported or invalid data width: %d\n", bits);
+      return (bits < 8 || bits > 16) ? -EINVAL : -ENOSYS;
+    }
+
+  /* Save the new data width */
+
+  priv->datalen = bits;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_txchannels
+ *
+ * Description:
+ *   Set the I2S TX number of channels.
+ *
+ * Input Parameters:
+ *   dev  - Device-specific state data
+ *   channels - The I2S numbers of channels
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int rp2040_i2s_txchannels(struct i2s_dev_s *dev, uint8_t channels)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)dev;
+
+  if (channels != 1 && channels != 2)
+    {
+      return -EINVAL;
+    }
+
+  priv->channels = channels;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_txsamplerate
+ *
+ * Description:
+ *   Set the I2S TX sample rate.
+ *
+ * Input Parameters:
+ *   dev  - Device-specific state data
+ *   rate - The I2S sample rate in samples (not bits) per second
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static uint32_t rp2040_i2s_txsamplerate(struct i2s_dev_s *dev, uint32_t rate)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)dev;
+
+  DEBUGASSERT(priv && priv->samplerate >= 0 && rate > 0);
+
+  if (rate < 8000)
+    {
+      return -EINVAL;
+    }
+
+  priv->samplerate = rate;
+  return 0;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_txdatawidth
+ *
+ * Description:
+ *   Set the I2S TX data width.  The TX bitrate is determined by
+ *   sample_rate * data_width.
+ *
+ * Input Parameters:
+ *   dev   - Device-specific state data
+ *   bits - The I2S data with in bits.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static uint32_t rp2040_i2s_txdatawidth(struct i2s_dev_s *dev, int bits)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)dev;
+  int ret;
+
+  i2sinfo("Data width bits of tx = %d\n", bits);
+  DEBUGASSERT(priv && bits > 1);
+
+  /* Check if this is a bit width that we are configured to handle */
+
+  ret = i2s_checkwidth(priv, bits);
+  if (ret < 0)
+    {
+      i2serr("ERROR: i2s_checkwidth failed: %d\n", ret);
+      return 0;
+    }
+
+  /* Update the DMA flags */
+
+  ret = i2s_dma_flags(priv);
+  if (ret < 0)
+    {
+      i2serr("ERROR: i2s_dma_flags failed: %d\n", ret);
+      return 0;
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_send
+ *
+ * Description:
+ *   Send a block of data on I2S.
+ *
+ * Input Parameters:
+ *   dev      - Device-specific state data
+ *   apb      - A pointer to the audio buffer from which to send data
+ *   callback - A user provided callback function that will be called at
+ *              the completion of the transfer.  The callback will be
+ *              performed in the context of the worker thread.
+ *   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 canceled
+ *              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.  NOTE:  This function
+ *   only enqueues the transfer and returns immediately.  Success here only
+ *   means that the transfer was enqueued correctly.
+ *
+ *   When the transfer is complete, a 'result' value will be provided as
+ *   an argument to the callback function that will indicate if the transfer
+ *   failed.
+ *
+ ****************************************************************************/
+
+static int rp2040_i2s_send(struct i2s_dev_s *dev, struct ap_buffer_s *apb,
+                    i2s_callback_t callback, void *arg, uint32_t timeout)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)dev;
+  struct rp2040_buffer_s *bfcontainer;
+  irqstate_t flags;
+  int ret;
+
+  /* Make sure that we have valid pointers that that the data has uint32_t
+   * alignment.
+   */
+
+  DEBUGASSERT(priv && apb);
+  i2sinfo("apb=%p nbytes=%d arg=%p timeout=%" PRId32 "\n",
+          apb, apb->nbytes - apb->curbyte, arg, timeout);
+
+  i2s_dump_buffer("Sending", &apb->samp[apb->curbyte],
+                  apb->nbytes - apb->curbyte);
+#ifdef CONFIG_DEBUG_FEATURES
+  DEBUGASSERT(((uintptr_t)&apb->samp[apb->curbyte] & priv->align) == 0);
+#endif
+
+  /* Allocate a buffer container in advance */
+
+  bfcontainer = i2s_buf_allocate(priv);
+  DEBUGASSERT(bfcontainer);
+
+  /* Get exclusive access to the I2S driver data */
+
+  ret = i2s_exclsem_take(priv);
+  if (ret < 0)
+    {
+      goto errout_with_buf;
+    }
+
+  /* Add a reference to the audio buffer */
+
+  apb_reference(apb);
+
+  /* Initialize the buffer container structure */
+
+  bfcontainer->callback = (void *)callback;
+  bfcontainer->timeout  = timeout;
+  bfcontainer->arg      = arg;
+  bfcontainer->apb      = apb;
+  bfcontainer->result   = -EBUSY;
+
+  /* Add the buffer container to the end of the TX pending queue */
+
+  flags = enter_critical_section();
+  sq_addlast((sq_entry_t *)bfcontainer, &priv->tx.pend);
+
+  leave_critical_section(flags);
+  i2s_exclsem_give(priv);
+  return OK;
+
+errout_with_buf:
+  i2s_buf_free(priv, bfcontainer);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_cleanup_queues
+ *
+ * Description:
+ *   Clean up the all buffers in the queues.
+ *
+ * Input Parameters:
+ *   priv - I2S state instance
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void i2s_cleanup_queues(struct rp2040_i2s_s *priv)
+{
+  irqstate_t flags;
+  struct rp2040_buffer_s *bfcontainer;
+
+  while (sq_peek(&priv->tx.done) != NULL)
+    {
+      flags = enter_critical_section();
+      bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.done);
+      leave_critical_section(flags);
+      bfcontainer->callback(&priv->dev, bfcontainer->apb,
+                            bfcontainer->arg, OK);
+      apb_free(bfcontainer->apb);
+      i2s_buf_free(priv, bfcontainer);
+    }
+
+  while (sq_peek(&priv->tx.act) != NULL)
+    {
+      flags = enter_critical_section();
+      bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.act);
+      leave_critical_section(flags);
+      bfcontainer->callback(&priv->dev, bfcontainer->apb,
+                            bfcontainer->arg, OK);
+      apb_free(bfcontainer->apb);
+      i2s_buf_free(priv, bfcontainer);
+    }
+
+  while (sq_peek(&priv->tx.pend) != NULL)
+    {
+      flags = enter_critical_section();
+      bfcontainer = (struct rp2040_buffer_s *)sq_remfirst(&priv->tx.pend);
+      leave_critical_section(flags);
+      bfcontainer->apb->flags |= AUDIO_APB_FINAL;
+      bfcontainer->callback(&priv->dev, bfcontainer->apb,
+                            bfcontainer->arg, OK);
+      apb_free(bfcontainer->apb);
+      i2s_buf_free(priv, bfcontainer);
+    }
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_ioctl
+ *
+ * Description:
+ *   Perform a device ioctl
+ *
+ ****************************************************************************/
+
+static int rp2040_i2s_ioctl(struct i2s_dev_s *dev, int cmd,
+                            unsigned long arg)
+{
+  struct rp2040_i2s_s *priv = (struct rp2040_i2s_s *)dev;
+  FAR struct audio_buf_desc_s  *bufdesc;
+  int ret = -ENOTTY;
+
+  switch (cmd)
+    {
+      /* AUDIOIOC_START - Start the audio stream.
+       *
+       *   ioctl argument:  Audio session
+       */
+
+      case AUDIOIOC_START:
+        {
+          irqstate_t flags;
+          int mode;
+
+          i2sinfo("AUDIOIOC_START\n");
+
+          if (priv->channels == 1)
+            {
+              if (priv->datalen == 16)
+                mode = RP2040_I2S_PIO_16BIT_MONO;
+              else
+                mode = RP2040_I2S_PIO_8BIT_MONO;
+            }
+          else
+            {
+              if (priv->datalen == 16)
+                mode = RP2040_I2S_PIO_16BIT_STEREO;
+              else
+                mode = RP2040_I2S_PIO_8BIT_STEREO;
+           }
+
+          rp2040_i2s_pio_configure(mode, priv->samplerate);
+
+          flags = enter_critical_section();
+          ret = i2s_txdma_setup(priv);
+          leave_critical_section(flags);
+        }
+        break;
+
+      /* AUDIOIOC_STOP - Stop the audio stream.
+       *
+       *   ioctl argument:  Audio session
+       */
+
+#ifndef CONFIG_AUDIO_EXCLUDE_STOP
+      case AUDIOIOC_STOP:
+        {
+          irqstate_t flags;
+
+          i2sinfo("AUDIOIOC_STOP\n");
+
+          flags = enter_critical_section();
+          if (priv->tx.timeout > 0)
+            {
+              wd_cancel(&priv->tx.dog);
+            }
+
+          rp2040_dmastop(priv->tx.dma);
+          leave_critical_section(flags);
+
+          i2s_cleanup_queues(priv);
+
+          ret = 0;
+        }
+        break;
+#endif /* CONFIG_AUDIO_EXCLUDE_STOP */
+
+      /* AUDIOIOC_PAUSE - Pause the audio stream.
+       *
+       *   ioctl argument:  Audio session
+       */
+
+#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
+
+      case AUDIOIOC_PAUSE:
+        {
+          irqstate_t flags;
+
+          i2sinfo("AUDIOIOC_PAUSE\n");
+
+          flags = enter_critical_section();
+          if (priv->tx.timeout > 0)
+            {
+              priv->tx.timeout = wd_gettime(&priv->tx.dog);
+              wd_cancel(&priv->tx.dog);
+            }
+
+          rp2040_i2s_pio_enable(false);
+          leave_critical_section(flags);
+
+          ret = 0;
+        }
+        break;
+
+      /* AUDIOIOC_RESUME - Resume the audio stream.
+       *
+       *   ioctl argument:  Audio session
+       */
+
+      case AUDIOIOC_RESUME:
+        {
+          irqstate_t flags;
+
+          i2sinfo("AUDIOIOC_RESUME\n");
+
+          flags = enter_critical_section();
+          if (priv->tx.timeout > 0)
+            {
+              wd_start(&priv->tx.dog, priv->tx.timeout,
+                       i2s_txdma_timeout, (wdparm_t)priv);
+            }
+
+          rp2040_i2s_pio_enable(true);
+          leave_critical_section(flags);
+
+          ret = 0;
+        }
+        break;
+
+#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */
+
+      /* AUDIOIOC_ALLOCBUFFER - Allocate an audio buffer
+       *
+       *   ioctl argument:  pointer to an audio_buf_desc_s structure
+       */
+
+      case AUDIOIOC_ALLOCBUFFER:
+        {
+          i2sinfo("AUDIOIOC_ALLOCBUFFER\n");
+
+          bufdesc = (FAR 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 = (FAR 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: i2s_dma_flags
+ *
+ * Description:
+ *   Determine DMA FLAGS based on PID and data width
+ *
+ * Input Parameters:
+ *   priv - Partially initialized I2S device structure.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure
+ *
+ ****************************************************************************/
+
+static int i2s_dma_flags(struct rp2040_i2s_s *priv)
+{
+  switch (priv->datalen)
+    {
+    case 8:
+      priv->txconfig.size = RP2040_DMA_SIZE_BYTE;
+      break;
+
+    case 16:
+      priv->txconfig.size = RP2040_DMA_SIZE_HALFWORD;
+      break;
+
+    default:
+      i2serr("ERROR: Unsupported data width: %d\n", priv->datalen);
+      return -ENOSYS;
+    }
+
+  priv->txconfig.noincr = false;
+  priv->txconfig.dreq = rp2040_i2s_pio_getdreq();
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: i2s_dma_allocate
+ *
+ * Description:
+ *   Allocate I2S DMA channels
+ *
+ * Input Parameters:
+ *   priv - Partially initialized I2S device structure.  This function
+ *          will complete the DMA specific portions of the initialization
+ *
+ * Returned Value:
+ *   OK on success; A negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int i2s_dma_allocate(struct rp2040_i2s_s *priv)
+{
+  /* Allocate a TX DMA channel */
+
+  priv->tx.dma = rp2040_dmachannel();
+  if (!priv->tx.dma)
+    {
+      i2serr("ERROR: Failed to allocate the TX DMA channel\n");
+      goto errout;
+    }
+
+  /* Success exit */
+
+  return OK;
+
+  /* Error exit */
+
+errout:
+  i2s_dma_free(priv);
+  return -ENOMEM;
+}
+
+/****************************************************************************
+ * Name: i2s_dma_free
+ *
+ * Description:
+ *   Release DMA-related resources allocated by i2s_dma_allocate()
+ *
+ * Input Parameters:
+ *   priv - Partially initialized I2S device structure.
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void i2s_dma_free(struct rp2040_i2s_s *priv)
+{
+  if (priv->tx.timeout > 0)
+    {
+      wd_cancel(&priv->tx.dog);
+    }
+
+  if (priv->tx.dma)
+    {
+      rp2040_dmafree(priv->tx.dma);
+    }
+}
+
+/****************************************************************************
+ * Name: i2s_configure
+ *
+ * Description:
+ *   Configure I2S
+ *
+ * Input Parameters:
+ *   priv - Partially initialized I2S device structure.  These functions
+ *          will complete the I2S specific portions of the initialization
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void i2s_configure(struct rp2040_i2s_s *priv)
+{
+  /* Only configure if the port is not already configured */
+
+  if (!priv->initialized)
+    {
+      rp2040_gpio_set_function(CONFIG_RP2040_I2S_DATA,
+                               RP2040_GPIO_FUNC_PIO0);
+      rp2040_gpio_set_function(CONFIG_RP2040_I2S_CLOCK,
+                               RP2040_GPIO_FUNC_PIO0);
+      rp2040_gpio_set_function(CONFIG_RP2040_I2S_CLOCK + 1,
+                               RP2040_GPIO_FUNC_PIO0);
+
+      priv->initialized = true;
+    }
+
+  /* Configure driver state specific to this I2S peripheral */
+
+  priv->channels = 2;
+  priv->samplerate = 44100;
+  priv->datalen = CONFIG_RP2040_I2S_DATALEN;
+#ifdef CONFIG_DEBUG
+  priv->align   = RP2040_I2S_DATAMASK;
+#endif
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_i2sbus_initialize
+ *
+ * Description:
+ *   Initialize the selected i2S port
+ *
+ * Input Parameters:
+ *   Port number (for hardware that has multiple I2S interfaces)
+ *
+ * Returned Value:
+ *   Valid I2S device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+FAR struct i2s_dev_s *rp2040_i2sbus_initialize(int port)
+{
+  FAR struct rp2040_i2s_s *priv = NULL;
+  irqstate_t flags;
+  int ret;
+
+  i2sinfo("port: %d\n", port);
+
+  priv = (struct rp2040_i2s_s *)kmm_zalloc(sizeof(struct rp2040_i2s_s));
+  if (!priv)
+    {
+      i2serr("ERROR: Failed to allocate a chip select structure\n");
+      return NULL;
+    }
+
+  /* Set up the initial state for this chip select structure.  Other fields
+   * were zeroed by kmm_zalloc().
+   */
+
+  /* Initialize the common parts for the I2S device structure */
+
+  nxsem_init(&priv->exclsem, 0, 1);
+  priv->dev.ops = &g_i2sops;
+
+  /* Initialize buffering */
+
+  i2s_buf_initialize(priv);
+
+  flags = enter_critical_section();
+
+  i2s_configure(priv);
+
+  /* Allocate DMA channels */
+
+  ret = i2s_dma_allocate(priv);
+  if (ret < 0)
+    {
+      goto errout_with_alloc;
+    }
+
+  leave_critical_section(flags);
+
+  /* Success exit */
+
+  return &priv->dev;
+
+  /* Failure exits */
+
+errout_with_alloc:
+  nxsem_destroy(&priv->exclsem);
+  kmm_free(priv);
+  return NULL;
+}
+
+#endif /* CONFIG_RP2040_I2S */
diff --git a/arch/arm/src/rp2040/rp2040_i2s.h b/arch/arm/src/rp2040/rp2040_i2s.h
new file mode 100644
index 0000000..ca7cd2362
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_i2s.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_i2s.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_RP2040_RP2040_I2S_H
+#define __ARCH_ARM_SRC_RP2040_RP2040_I2S_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/audio/i2s.h>
+
+#include "chip.h"
+
+#ifndef __ASSEMBLY__
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_i2sbus_initialize
+ *
+ * Description:
+ *   Initialize the selected I2S port
+ *
+ * Input Parameters:
+ *   Port number (for hardware that has multiple I2S interfaces)
+ *
+ * Returned Value:
+ *   Valid I2S device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+FAR struct i2s_dev_s *rp2040_i2sbus_initialize(int port);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ARCH_ARM_SRC_RP2040_RP2040_I2S_H */
diff --git a/arch/arm/src/rp2040/rp2040_i2s_pio.c b/arch/arm/src/rp2040/rp2040_i2s_pio.c
new file mode 100644
index 0000000..bdce734
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_i2s_pio.c
@@ -0,0 +1,382 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_i2s_pio.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>
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <arch/board/board.h>
+
+#include "rp2040_i2s_pio.h"
+#include "rp2040_pio.h"
+#include "rp2040_pio_instructions.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Configuration ************************************************************/
+
+#ifndef CONFIG_RP2040_I2S_PIO
+  #define CONFIG_RP2040_I2S_PIO     0
+#endif
+
+#ifndef  CONFIG_RP2040_I2S_PIO_SM
+  #define CONFIG_RP2040_I2S_PIO_SM  0
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct rp2040_i2s_pio_config
+{
+  const rp2040_pio_program_t program;
+  uint32_t entry;
+  uint32_t wrap_target;
+  uint32_t wrap;
+  bool autopull;
+  uint32_t clocks;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* PIO program for 16bit stereo I2S transfer */
+
+static const uint16_t pio_program_i2s_16s[] =
+  {
+            /*     .wrap_target                       */
+
+    0x6870, /*  0: out    null, 16        side 1      */
+    0x6001, /*  1: out    pins, 1         side 0      */
+    0xe82d, /*  2: set    x, 13           side 1      */
+    0x6001, /*  3: out    pins, 1         side 0      */
+    0x0843, /*  4: jmp    x--, 3          side 1      */
+    0x7001, /*  5: out    pins, 1         side 2      */
+    0x7870, /*  6: out    null, 16        side 3      */
+    0x7001, /*  7: out    pins, 1         side 2      */
+    0xf82d, /*  8: set    x, 13           side 3      */
+    0x7001, /*  9: out    pins, 1         side 2      */
+    0x1849, /* 10: jmp    x--, 9          side 3      */
+    0x6001, /* 11: out    pins, 1         side 0      */
+
+            /*     .wrap                              */
+  };
+
+/* PIO program for 16bit mono I2S transfer */
+
+static const uint16_t pio_program_i2s_16m[] =
+  {
+            /*     .wrap_target                       */
+
+    0x80a0, /*  0: pull   block           side 0      */
+    0x6870, /*  1: out    null, 16        side 1      */
+    0xa847, /*  2: mov    y, osr          side 1      */
+    0x6101, /*  3: out    pins, 1         side 0 [1]  */
+    0xe92d, /*  4: set    x, 13           side 1 [1]  */
+    0x6101, /*  5: out    pins, 1         side 0 [1]  */
+    0x0945, /*  6: jmp    x--, 5          side 1 [1]  */
+    0x7101, /*  7: out    pins, 1         side 2 [1]  */
+    0xb9e2, /*  8: mov    osr, y          side 3 [1]  */
+    0x7101, /*  9: out    pins, 1         side 2 [1]  */
+    0xf92d, /* 10: set    x, 13           side 3 [1]  */
+    0x7101, /* 11: out    pins, 1         side 2 [1]  */
+    0x194b, /* 12: jmp    x--, 11         side 3 [1]  */
+    0x6001, /* 13: out    pins, 1         side 0      */
+
+            /*     .wrap                              */
+  };
+
+/* PIO program for 8bit stereo I2S transfer */
+
+static const uint16_t pio_program_i2s_8s[] =
+  {
+            /*     .wrap_target                       */
+
+    0x80a0, /*  0: pull   block           side 0      */
+    0x6078, /*  1: out    null, 24        side 0      */
+    0xa9ef, /*  2: mov    osr, !osr       side 1 [1]  */
+    0x6101, /*  3: out    pins, 1         side 0 [1]  */
+    0xa8ef, /*  4: mov    osr, !osr       side 1      */
+    0xe826, /*  5: set    x, 6            side 1      */
+    0x6101, /*  6: out    pins, 1         side 0 [1]  */
+    0x0946, /*  7: jmp    x--, 6          side 1 [1]  */
+    0xe100, /*  8: set    pins, 0         side 0 [1]  */
+    0xe925, /*  9: set    x, 5            side 1 [1]  */
+    0xa142, /* 10: nop                    side 0 [1]  */
+    0x094a, /* 11: jmp    x--, 10         side 1 [1]  */
+    0x90a0, /* 12: pull   block           side 2      */
+    0x7078, /* 13: out    null, 24        side 2      */
+    0xb9ef, /* 14: mov    osr, !osr       side 3 [1]  */
+    0x7101, /* 15: out    pins, 1         side 2 [1]  */
+    0xb8ef, /* 16: mov    osr, !osr       side 3      */
+    0xf826, /* 17: set    x, 6            side 3      */
+    0x7101, /* 18: out    pins, 1         side 2 [1]  */
+    0x1952, /* 19: jmp    x--, 18         side 3 [1]  */
+    0xf100, /* 20: set    pins, 0         side 2 [1]  */
+    0xf925, /* 21: set    x, 5            side 3 [1]  */
+    0xb142, /* 22: nop                    side 2 [1]  */
+    0x1956, /* 23: jmp    x--, 22         side 3 [1]  */
+
+            /*     .wrap                              */
+  };
+
+/* PIO program for 8bit mono I2S transfer */
+
+static const uint16_t pio_program_i2s_8m[] =
+  {
+            /*     .wrap_target                       */
+
+    0x80a0, /*  0: pull   block           side 0      */
+    0x6078, /*  1: out    null, 24        side 0      */
+    0xa8ef, /*  2: mov    osr, !osr       side 1      */
+    0xa847, /*  3: mov    y, osr          side 1      */
+    0x6101, /*  4: out    pins, 1         side 0 [1]  */
+    0xa8ef, /*  5: mov    osr, !osr       side 1      */
+    0xe826, /*  6: set    x, 6            side 1      */
+    0x6101, /*  7: out    pins, 1         side 0 [1]  */
+    0x0947, /*  8: jmp    x--, 7          side 1 [1]  */
+    0xe100, /*  9: set    pins, 0         side 0 [1]  */
+    0xe925, /* 10: set    x, 5            side 1 [1]  */
+    0xa142, /* 11: nop                    side 0 [1]  */
+    0x094b, /* 12: jmp    x--, 11         side 1 [1]  */
+    0xb142, /* 13: nop                    side 2 [1]  */
+    0xb9e2, /* 14: mov    osr, y          side 3 [1]  */
+    0x7101, /* 15: out    pins, 1         side 2 [1]  */
+    0xb8ef, /* 16: mov    osr, !osr       side 3      */
+    0xf826, /* 17: set    x, 6            side 3      */
+    0x7101, /* 18: out    pins, 1         side 2 [1]  */
+    0x1952, /* 19: jmp    x--, 18         side 3 [1]  */
+    0xf100, /* 20: set    pins, 0         side 2 [1]  */
+    0xf925, /* 21: set    x, 5            side 3 [1]  */
+    0xb142, /* 22: nop                    side 2 [1]  */
+    0x1956, /* 23: jmp    x--, 22         side 3 [1]  */
+
+            /*     .wrap                              */
+  };
+
+/* PIO configuration table */
+
+static const struct rp2040_i2s_pio_config g_pio_i2s_configs[] =
+  {
+    [RP2040_I2S_PIO_16BIT_STEREO] =
+      {
+        {
+          pio_program_i2s_16s,
+          sizeof(pio_program_i2s_16s) / sizeof(uint16_t),
+          -1
+        },
+        0, 0, 11,
+        true, 16 * 2 * 2
+      },
+
+    [RP2040_I2S_PIO_16BIT_MONO] =
+      {
+        {
+          pio_program_i2s_16m,
+          sizeof(pio_program_i2s_16m) / sizeof(uint16_t),
+          -1
+        },
+        0, 0, 13,
+        false, 16 * 2 * 4
+      },
+
+    [RP2040_I2S_PIO_8BIT_STEREO] =
+      {
+        {
+          pio_program_i2s_8s,
+          sizeof(pio_program_i2s_8s) / sizeof(uint16_t),
+          -1
+        },
+        0, 0, 23,
+        false, 16 * 2 * 4
+      },
+
+    [RP2040_I2S_PIO_8BIT_MONO] =
+      {
+        {
+          pio_program_i2s_8m,
+          sizeof(pio_program_i2s_8m) / sizeof(uint16_t),
+          -1
+        },
+        0, 0, 23,
+        false, 16 * 2 * 4
+      }
+  };
+
+static const uint32_t g_i2s_pio = CONFIG_RP2040_I2S_PIO;
+static const uint32_t g_i2s_pio_sm = CONFIG_RP2040_I2S_PIO_SM;
+
+/* PIO I2S status */
+
+static int g_pio_current_mode = -1;
+static uint32_t g_pio_current_samplerate;
+static uint32_t g_pio_current_offset;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static float get_clkdiv(int mode, uint32_t samplerate)
+{
+  float div = (float)BOARD_SYS_FREQ /
+              (samplerate * g_pio_i2s_configs[mode].clocks);
+
+  return div;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_configure
+ *
+ * Description:
+ *   Configure RP2040 PIO for I2S
+ *
+ ****************************************************************************/
+
+int rp2040_i2s_pio_configure(int mode, uint32_t samplerate)
+{
+  const struct rp2040_i2s_pio_config *conf;
+  rp2040_pio_sm_config sm_config;
+
+  uint32_t data_pin = CONFIG_RP2040_I2S_DATA;
+  uint32_t clock_pin_base = CONFIG_RP2040_I2S_CLOCK;
+  uint32_t pin_mask = (1u << data_pin) | (3u << clock_pin_base);
+
+  /* Check parameters */
+
+  if (mode < 0 || mode >= RP2040_I2S_PIO_MAX_MODE ||
+      samplerate == 0)
+    {
+      return -1;
+    }
+
+  if (mode == g_pio_current_mode)
+    {
+      if (samplerate == g_pio_current_samplerate)
+        {
+          return 0;
+        }
+      else
+        {
+          /* Only changing the sampling rate */
+
+          rp2040_pio_sm_set_clkdiv(g_i2s_pio, g_i2s_pio_sm,
+                                   get_clkdiv(mode, samplerate));
+          rp2040_pio_sm_clkdiv_restart(g_i2s_pio, g_i2s_pio_sm);
+          return 0;
+        }
+    }
+
+  if (g_pio_current_mode < 0)
+    {
+      /* Claim to use PIO state machine for I2S */
+
+      rp2040_pio_sm_claim(g_i2s_pio, g_i2s_pio_sm);
+    }
+  else
+    {
+      /* Remove existing PIO program to change the I2S mode */
+
+      rp2040_pio_remove_program(CONFIG_RP2040_I2S_PIO,
+                          &g_pio_i2s_configs[g_pio_current_mode].program,
+                          g_pio_current_offset);
+    }
+
+  /* Program the PIO */
+
+  conf = &g_pio_i2s_configs[mode];
+  g_pio_current_offset = rp2040_pio_add_program(CONFIG_RP2040_I2S_PIO,
+                          &conf->program);
+  g_pio_current_mode = mode;
+
+  /* Configure the state machine */
+
+  sm_config = rp2040_pio_get_default_sm_config();
+  rp2040_sm_config_set_wrap(&sm_config,
+                            g_pio_current_offset + conf->wrap_target,
+                            g_pio_current_offset + conf->wrap);
+  rp2040_sm_config_set_sideset(&sm_config, 2, false, false);
+
+  rp2040_sm_config_set_out_pins(&sm_config, data_pin, 1);
+  rp2040_sm_config_set_sideset_pins(&sm_config, clock_pin_base);
+  rp2040_sm_config_set_out_shift(&sm_config, false, conf->autopull, 32);
+  rp2040_sm_config_set_set_pins(&sm_config, data_pin, 1);
+  rp2040_sm_config_set_clkdiv(&sm_config, get_clkdiv(mode, samplerate));
+  rp2040_pio_sm_init(g_i2s_pio, g_i2s_pio_sm,
+                     g_pio_current_offset, &sm_config);
+
+  rp2040_pio_sm_set_pindirs_with_mask(g_i2s_pio, g_i2s_pio_sm,
+                                      pin_mask, pin_mask);
+  rp2040_pio_sm_set_pins(g_i2s_pio, g_i2s_pio_sm, 1); /* clear pins */
+  rp2040_pio_sm_exec(g_i2s_pio, g_i2s_pio_sm,
+                     pio_encode_jmp(g_pio_current_offset + conf->entry));
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_enable
+ *
+ * Description:
+ *   Set enable I2S transfer
+ *
+ ****************************************************************************/
+
+void rp2040_i2s_pio_enable(bool enable)
+{
+  rp2040_pio_sm_set_enabled(g_i2s_pio, g_i2s_pio_sm, enable);
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_getdmaaddr
+ *
+ * Description:
+ *   Get DMA peripheral address for I2S transfer
+ *
+ ****************************************************************************/
+
+uintptr_t rp2040_i2s_pio_getdmaaddr(void)
+{
+  return RP2040_PIO_TXF(g_i2s_pio, g_i2s_pio_sm);
+}
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_getdmaaddr
+ *
+ * Description:
+ *   Get DMA peripheral address for I2S transfer
+ *
+ ****************************************************************************/
+
+uint8_t rp2040_i2s_pio_getdreq(void)
+{
+  return RP2040_DMA_DREQ_PIO0_TX0 + g_i2s_pio_sm + g_i2s_pio * 8;
+}
diff --git a/arch/arm/src/rp2040/rp2040_i2s_pio.h b/arch/arm/src/rp2040/rp2040_i2s_pio.h
new file mode 100644
index 0000000..d30f170
--- /dev/null
+++ b/arch/arm/src/rp2040/rp2040_i2s_pio.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+ * arch/arm/src/rp2040/rp2040_i2s_pio.h
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_RP2040_RP2040_I2S_PIO_H
+#define __ARCH_ARM_SRC_RP2040_RP2040_I2S_PIO_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define RP2040_I2S_PIO_16BIT_STEREO         0
+#define RP2040_I2S_PIO_16BIT_MONO           1
+#define RP2040_I2S_PIO_8BIT_STEREO          2
+#define RP2040_I2S_PIO_8BIT_MONO            3
+#define RP2040_I2S_PIO_MAX_MODE             4
+
+#ifndef __ASSEMBLY__
+
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_configure
+ *
+ * Description:
+ *   Configure RP2040 PIO for I2S
+ *
+ ****************************************************************************/
+
+int rp2040_i2s_pio_configure(int mode, uint32_t samplerate);
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_enable
+ *
+ * Description:
+ *   Set enable I2S transfer
+ *
+ ****************************************************************************/
+
+void rp2040_i2s_pio_enable(bool enable);
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_getdmaaddr
+ *
+ * Description:
+ *   Get DMA peripheral address for I2S transfer
+ *
+ ****************************************************************************/
+
+uintptr_t rp2040_i2s_pio_getdmaaddr(void);
+
+/****************************************************************************
+ * Name: rp2040_i2s_pio_getdmaaddr
+ *
+ * Description:
+ *   Get DREQ number for I2S transfer
+ *
+ ****************************************************************************/
+
+uint8_t rp2040_i2s_pio_getdreq(void);
+
+#undef EXTERN
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ARCH_ARM_SRC_RP2040_RP2040_I2S_PIO_H */