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

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

commit a15117f0a0f876e1be59c3cfef1f307aa76e8f34
Author: Jorge Guzman <[email protected]>
AuthorDate: Sun Oct 25 01:50:48 2026 -0300

    arch/arm/gd32w51x: read the QSPI through the DMA
    
    Add CONFIG_GD32W51X_QSPI_DMA: the data phase of the indirect reads of
    CONFIG_GD32W51X_QSPI_DMATHRESHOLD bytes or more is moved by DMA1
    channel 0, request 5 (QSPI_STAT DMAEN), and the thread sleeps until the
    end of the transfer instead of polling the FIFO.  The user manual also
    lists DMA1 channel 1 for the QSPI requests, but that channel gets none
    on the GD32W515.
    
    The littlefs configuration of the GD-xD-W515-EVAL enables it: data read
    through the DMA matches the polled reads, and reading a 256 KB file
    from /flash takes 60 ms instead of 80 ms.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Jorge Guzman <[email protected]>
---
 .../arm/gd32w51x/boards/gd-xd-w515-eval/index.rst  |   3 +-
 arch/arm/src/gd32w51x/Kconfig                      |  17 +++
 arch/arm/src/gd32w51x/gd32w51x_qspi.c              | 114 ++++++++++++++++++++-
 arch/arm/src/gd32w51x/hardware/gd32w51x_dma.h      |   7 ++
 arch/arm/src/gd32w51x/hardware/gd32w51x_qspi.h     |   1 +
 .../gd-xd-w515-eval/configs/littlefs/defconfig     |   2 +
 6 files changed, 138 insertions(+), 6 deletions(-)

diff --git 
a/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst 
b/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
index cadd028a6e7..338fd3bd06e 100644
--- a/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
+++ b/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
@@ -250,7 +250,8 @@ reboot:
 
 - ``/data``: the last 128 KB of the internal flash (``/dev/gd32flash``).
 - ``/flash``: the 16 MB QSPI NOR flash (``/dev/qspiflash``, GD25 driver in
-  QSPI mode).
+  QSPI mode).  Reads of 64 bytes or more go through DMA1 channel 0
+  (``CONFIG_GD32W51X_QSPI_DMA``): 256 KB in 60 ms instead of 80 ms.
 
 ::
 
diff --git a/arch/arm/src/gd32w51x/Kconfig b/arch/arm/src/gd32w51x/Kconfig
index 0614fbf3ed3..7f11e7707fc 100644
--- a/arch/arm/src/gd32w51x/Kconfig
+++ b/arch/arm/src/gd32w51x/Kconfig
@@ -123,6 +123,23 @@ config GD32W51X_QSPI
                is a secure peripheral: its pins are marked secure when it is
                initialized.
 
+config GD32W51X_QSPI_DMA
+       bool "QSPI DMA reads"
+       default n
+       depends on GD32W51X_QSPI && GD32W51X_DMA
+       ---help---
+               Move the data of the longer reads with DMA1 channel 0 instead of
+               polling the FIFO.  The calling thread sleeps until the transfer
+               ends.
+
+config GD32W51X_QSPI_DMATHRESHOLD
+       int "QSPI DMA threshold (bytes)"
+       default 64
+       depends on GD32W51X_QSPI_DMA
+       ---help---
+               Reads shorter than this are done by polling: setting up DMA 
costs
+               more than moving a few bytes.
+
 config GD32W51X_TSI
        bool "TSI"
        default n
diff --git a/arch/arm/src/gd32w51x/gd32w51x_qspi.c 
b/arch/arm/src/gd32w51x/gd32w51x_qspi.c
index 2e5f3c96502..7dfd034edd1 100644
--- a/arch/arm/src/gd32w51x/gd32w51x_qspi.c
+++ b/arch/arm/src/gd32w51x/gd32w51x_qspi.c
@@ -35,6 +35,7 @@
 #include <nuttx/arch.h>
 #include <nuttx/kmalloc.h>
 #include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
 #include <nuttx/spi/qspi.h>
 
 #include <arch/board/board.h>
@@ -43,6 +44,7 @@
 #include "chip.h"
 #include "gd32w51x.h"
 #include "gd32w51x_clockconfig.h"
+#include "gd32w51x_dma.h"
 #include "gd32w51x_gpio.h"
 #include "gd32w51x_qspi.h"
 #include "hardware/gd32w51x_qspi.h"
@@ -69,6 +71,10 @@
 
 #define QSPI_TIMEOUT        0x00ffffff
 
+/* Longest wait for a DMA read: 64 KB at the slowest useful clock */
+
+#define QSPI_DMA_TIMEOUT    MSEC2TICK(1000)
+
 /****************************************************************************
  * Private Types
  ****************************************************************************/
@@ -81,6 +87,11 @@ struct gd32_qspidev_s
   uint32_t actual;              /* Actual clock frequency */
   uint8_t mode;                 /* Mode 0 or 3 */
   bool initialized;
+#ifdef CONFIG_GD32W51X_QSPI_DMA
+  DMA_HANDLE rxdma;             /* DMA channel of the reads */
+  sem_t dmawait;                /* Wait for the end of a DMA read */
+  volatile uint16_t dmaresult;  /* DMA status, non-zero when done */
+#endif
 };
 
 /****************************************************************************
@@ -122,6 +133,9 @@ static struct gd32_qspidev_s g_qspi0dev =
     .ops = &g_qspi_ops,
   },
   .lock = NXMUTEX_INITIALIZER,
+#ifdef CONFIG_GD32W51X_QSPI_DMA
+  .dmawait = SEM_INITIALIZER(0),
+#endif
 };
 
 /****************************************************************************
@@ -183,6 +197,73 @@ static int qspi_wait_status(uint32_t flags)
   return OK;
 }
 
+#ifdef CONFIG_GD32W51X_QSPI_DMA
+/****************************************************************************
+ * Name: qspi_dmacallback
+ ****************************************************************************/
+
+static void qspi_dmacallback(DMA_HANDLE handle, uint16_t isr, void *arg)
+{
+  struct gd32_qspidev_s *priv = (struct gd32_qspidev_s *)arg;
+
+  /* The status is OR'ed with 0x0080 to assure that it is non-zero */
+
+  priv->dmaresult = isr | 0x0080;
+  nxsem_post(&priv->dmawait);
+}
+
+/****************************************************************************
+ * Name: qspi_dmaread
+ *
+ * Description:
+ *   Move the data phase of an indirect read with the DMA.  TCFG has been
+ *   written; the transfer starts when the address is written.
+ *
+ ****************************************************************************/
+
+static int qspi_dmaread(struct gd32_qspidev_s *priv, uint32_t addr,
+                        uint8_t *buffer, size_t buflen)
+{
+  struct gd32_dma_config_s cfg;
+  int ret;
+
+  cfg.periph_addr         = GD32W51X_QSPI_DATA;
+  cfg.memory0_addr        = (uint32_t)buffer;
+  cfg.number              = buflen;
+  cfg.periph_inc          = DMA_PERIPH_INCREASE_DISABLE;
+  cfg.memory_inc          = DMA_MEMORY_INCREASE_ENABLE;
+  cfg.periph_memory_width = DMA_WIDTH_8BITS_SELECT;
+  cfg.circular_mode       = DMA_CIRCULAR_MODE_DISABLE;
+  cfg.direction           = DMA_PERIPH_TO_MEMORY;
+  cfg.priority            = DMA_PRIO_HIGH_SELECT;
+
+  priv->dmaresult = 0;
+  gd32_dma_setup(priv->rxdma, &cfg, GD32_DMA_SINGLE_DATA_MODE);
+  gd32_dma_start(priv->rxdma, qspi_dmacallback, priv,
+                 DMA_CHXCTL_FTFIE | DMA_CHXCTL_TAEIE);
+
+  modifyreg32(GD32W51X_QSPI_STAT, 0, QSPI_STAT_DMAEN);
+  putreg32(addr, GD32W51X_QSPI_ADDR);
+
+  do
+    {
+      ret = nxsem_tickwait_uninterruptible(&priv->dmawait,
+                                           QSPI_DMA_TIMEOUT);
+    }
+  while (ret == OK && priv->dmaresult == 0);
+
+  modifyreg32(GD32W51X_QSPI_STAT, QSPI_STAT_DMAEN, 0);
+  gd32_dma_stop(priv->rxdma);
+
+  if (ret == OK && (priv->dmaresult & DMA_INTF_TAEIF) != 0)
+    {
+      ret = -EIO;
+    }
+
+  return ret;
+}
+#endif
+
 /****************************************************************************
  * Name: qspi_transfer
  *
@@ -193,8 +274,9 @@ static int qspi_wait_status(uint32_t flags)
  *
  ****************************************************************************/
 
-static int qspi_transfer(uint32_t tcfg, bool hasaddr, uint32_t addr,
-                         uint8_t *buffer, size_t buflen, bool read)
+static int qspi_transfer(struct gd32_qspidev_s *priv, uint32_t tcfg,
+                         bool hasaddr, uint32_t addr, uint8_t *buffer,
+                         size_t buflen, bool read)
 {
   size_t i;
   int ret;
@@ -217,6 +299,21 @@ static int qspi_transfer(uint32_t tcfg, bool hasaddr, 
uint32_t addr,
           QSPI_TCFG_FMOD_SHIFT;
   putreg32(tcfg, GD32W51X_QSPI_TCFG);
 
+#ifdef CONFIG_GD32W51X_QSPI_DMA
+  if (read && hasaddr && buflen >= CONFIG_GD32W51X_QSPI_DMATHRESHOLD &&
+      !up_interrupt_context())
+    {
+      ret = qspi_dmaread(priv, addr, buffer, buflen);
+      if (ret < 0)
+        {
+          goto errout;
+        }
+
+      hasaddr = false;
+      buflen  = 0;
+    }
+#endif
+
   if (hasaddr)
     {
       putreg32(addr, GD32W51X_QSPI_ADDR);
@@ -375,7 +472,8 @@ static int qspi_command(struct qspi_dev_s *dev,
       tcfg |= lines << QSPI_TCFG_DATAMOD_SHIFT;
     }
 
-  return qspi_transfer(tcfg, QSPICMD_ISADDRESS(cmdinfo->flags),
+  return qspi_transfer((struct gd32_qspidev_s *)dev, tcfg,
+                       QSPICMD_ISADDRESS(cmdinfo->flags),
                        cmdinfo->addr, cmdinfo->buffer,
                        QSPICMD_ISDATA(cmdinfo->flags) ? cmdinfo->buflen : 0,
                        !QSPICMD_ISWRITE(cmdinfo->flags));
@@ -425,8 +523,9 @@ static int qspi_memory(struct qspi_dev_s *dev,
           QSPI_TCFG_DUMYC_MASK) |
          (dlines << QSPI_TCFG_DATAMOD_SHIFT);
 
-  return qspi_transfer(tcfg, true, meminfo->addr, meminfo->buffer,
-                       meminfo->buflen, QSPIMEM_ISREAD(meminfo->flags));
+  return qspi_transfer((struct gd32_qspidev_s *)dev, tcfg, true,
+                       meminfo->addr, meminfo->buffer, meminfo->buflen,
+                       QSPIMEM_ISREAD(meminfo->flags));
 }
 
 /****************************************************************************
@@ -505,6 +604,11 @@ struct qspi_dev_s *gd32_qspi_initialize(int intf)
                (QSPI_FMSZ << QSPI_DCFG_FMSZ_SHIFT), GD32W51X_QSPI_DCFG);
       modifyreg32(GD32W51X_QSPI_CTL, 0, QSPI_CTL_QSPIEN);
 
+#ifdef CONFIG_GD32W51X_QSPI_DMA
+      priv->rxdma = gd32_dma_channel_alloc(DMA_REQ_QSPI);
+      DEBUGASSERT(priv->rxdma != NULL);
+#endif
+
       priv->frequency   = 0;
       priv->mode        = QSPIDEV_MODE0;
       priv->initialized = true;
diff --git a/arch/arm/src/gd32w51x/hardware/gd32w51x_dma.h 
b/arch/arm/src/gd32w51x/hardware/gd32w51x_dma.h
index 96ee112e158..4dca259dec1 100644
--- a/arch/arm/src/gd32w51x/hardware/gd32w51x_dma.h
+++ b/arch/arm/src/gd32w51x/hardware/gd32w51x_dma.h
@@ -428,4 +428,11 @@
 #define DMA_REQ_SPI1_TX       GD32_DMA_REQUEST(GD32_DMA_SUBPERI0, \
                                                GD32_DMA_CH4)
 
+/* The user manual also lists DMA1 channel 1 for the QSPI, but that channel
+ * gets no request on the GD32W515.
+ */
+
+#define DMA_REQ_QSPI          GD32_DMA_REQUEST(GD32_DMA_SUBPERI5, \
+                                               GD32_DMA1_CH(0))
+
 #endif /* __ARCH_ARM_SRC_GD32W51X_HARDWARE_GD32W51X_DMA_H */
diff --git a/arch/arm/src/gd32w51x/hardware/gd32w51x_qspi.h 
b/arch/arm/src/gd32w51x/hardware/gd32w51x_qspi.h
index 1cdc3382a48..fe9a0214892 100644
--- a/arch/arm/src/gd32w51x/hardware/gd32w51x_qspi.h
+++ b/arch/arm/src/gd32w51x/hardware/gd32w51x_qspi.h
@@ -86,6 +86,7 @@
 #define QSPI_STAT_SM                 (1 << 3)   /* Status match */
 #define QSPI_STAT_TMOUT              (1 << 4)   /* Timeout */
 #define QSPI_STAT_WS                 (1 << 5)   /* Wrong start sequence */
+#define QSPI_STAT_DMAEN              (1 << 16)  /* DMA enable */
 
 #define QSPI_STATC_TERRC             (1 << 0)
 #define QSPI_STATC_TCC               (1 << 1)
diff --git a/boards/arm/gd32w51x/gd-xd-w515-eval/configs/littlefs/defconfig 
b/boards/arm/gd32w51x/gd-xd-w515-eval/configs/littlefs/defconfig
index 6f179fa7cc1..a7657712596 100644
--- a/boards/arm/gd32w51x/gd-xd-w515-eval/configs/littlefs/defconfig
+++ b/boards/arm/gd32w51x/gd-xd-w515-eval/configs/littlefs/defconfig
@@ -24,8 +24,10 @@ CONFIG_FS_LITTLEFS=y
 CONFIG_FS_LITTLEFS_PROGRAM_SIZE_FACTOR=1
 CONFIG_FS_PROCFS=y
 CONFIG_GD25_QSPI=y
+CONFIG_GD32W51X_DMA=y
 CONFIG_GD32W51X_PROGMEM=y
 CONFIG_GD32W51X_QSPI=y
+CONFIG_GD32W51X_QSPI_DMA=y
 CONFIG_GD32W51X_RNG=y
 CONFIG_GD32W51X_UART2=y
 CONFIG_IDLETHREAD_STACKSIZE=2048

Reply via email to