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

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

commit 7dbf782e5bb978f42e181416f9daf509c1bb5746
Author: dechao_gong <[email protected]>
AuthorDate: Mon Jul 20 11:47:46 2026 +0800

    arch/arm/rtl8721dx: add shared Ameba SPI driver
    
    Add a shared NuttX SPI master lower-half for the Realtek Ameba SPI
    controllers (SPI0/SPI1) in arch/arm/src/common/ameba, driven through the
    SDK fwlib in polling mode with full-duplex exchange and a software chip
    select.  Per-chip wiring (controller count, register bases, clock masks,
    crossbar pad-mux codes and the fwlib SSI_InitTypeDef layout) lives in
    arch/arm/src/rtl8721dx/ameba_spi_chip.h so a port to the other Ameba
    chips only supplies a same-named header.
    
    Each controller registers as /dev/spiN from pke8721daf bring-up through
    the stock SPI character driver; a dedicated `spi` defconfig drives the
    spitool for validation.
    
    Assisted-by: Claude <[email protected]>
    Signed-off-by: dechao_gong <[email protected]>
---
 .../arm/rtl8721dx/boards/pke8721daf/index.rst      |  18 +
 arch/arm/src/common/ameba/Kconfig                  |  16 +
 arch/arm/src/common/ameba/ameba_spi.c              | 648 +++++++++++++++++++++
 arch/arm/src/common/ameba/ameba_spi.h              |  92 +++
 arch/arm/src/rtl8721dx/CMakeLists.txt              |   4 +
 arch/arm/src/rtl8721dx/Make.defs                   |   4 +
 arch/arm/src/rtl8721dx/ameba_board.mk              |   9 +
 arch/arm/src/rtl8721dx/ameba_spi_chip.h            | 113 ++++
 .../arm/rtl8721dx/pke8721daf/configs/spi/defconfig |  51 ++
 boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt |   7 +-
 boards/arm/rtl8721dx/pke8721daf/src/Makefile       |   9 +
 .../rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c   |  10 +
 .../pke8721daf/src/rtl8721dx_pke8721daf.h          |  13 +
 .../arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c   | 103 ++++
 tools/nxstyle.c                                    |   4 +
 15 files changed, 1100 insertions(+), 1 deletion(-)

diff --git a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst 
b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
index 1bbd3cf5c3d..d33d9fafc41 100644
--- a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
+++ b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
@@ -39,6 +39,8 @@ Supported in this NuttX port:
   directly on the SDK fwlib register layer
 * I2C master buses exposed as ``/dev/i2cN`` character devices, driven directly
   on the SDK fwlib register layer
+* SPI master buses exposed as ``/dev/spiN`` character devices, driven directly
+  on the SDK fwlib register layer
 
 Buttons and LEDs
 ================
@@ -116,6 +118,22 @@ external pull-ups on SCL/SDA. Probe a bus with the tool::
 
     nsh> i2c dev -b 0 0x03 0x77     # scan /dev/i2c0 for devices
 
+spi
+---
+
+Minimal NSH with the SPI master driver and the ``spi`` tool
+(``system/spi``) enabled (no Wi-Fi). The board registers two buses from its
+table (see ``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c``): SPI0 at
+``/dev/spi0`` with CLK/MOSI/MISO/CS on PA14/PA15/PA16/PA17 and SPI1 at
+``/dev/spi1`` with CLK/MOSI/MISO on PB18/PB19/PB20 and a software chip-select
+on PB21. Edit that table -- controller, CLK/MOSI/MISO pads and CS pad -- to
+match a board's wiring; the pads use the same ``AMEBA_PA()`` / ``AMEBA_PB()``
+encoding as the GPIO table and are muxed to the SPI function through the SDK
+ROM, while the chip-select is driven as a plain GPIO. Exercise a bus with the
+tool::
+
+    nsh> spi exch -b 1 -x 4 deadbeef     # full-duplex transfer on /dev/spi1
+
 Wi-Fi
 =====
 
diff --git a/arch/arm/src/common/ameba/Kconfig 
b/arch/arm/src/common/ameba/Kconfig
index ab27649ee54..46285e3bc61 100644
--- a/arch/arm/src/common/ameba/Kconfig
+++ b/arch/arm/src/common/ameba/Kconfig
@@ -69,4 +69,20 @@ config AMEBA_I2C
                fwlib register layer and drives the DesignWare I2C block in 
polling
                mode.
 
+config AMEBA_SPI
+       bool "SPI"
+       default n
+       select SPI
+       select SPI_DRIVER
+       select SPI_EXCHANGE
+       ---help---
+               Expose the Ameba SPI controllers (SPI0/SPI1) as NuttX SPI master
+               buses at /dev/spiN.  The board selects which controller is used 
and
+               its CLK/MOSI/MISO/CS pads in its bring-up code; the chip select 
is
+               driven as a plain GPIO (software CS).
+
+               The driver (arch/arm/src/common/ameba/ameba_spi.c) sits on the 
SDK
+               fwlib register layer and drives the DesignWare SSI block in 
polling
+               mode.
+
 endmenu # Ameba Peripheral Support
diff --git a/arch/arm/src/common/ameba/ameba_spi.c 
b/arch/arm/src/common/ameba/ameba_spi.c
new file mode 100644
index 00000000000..755ee2bcd9b
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_spi.c
@@ -0,0 +1,648 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_spi.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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
+ ****************************************************************************/
+
+/* NuttX SPI master lower half for the Realtek Ameba SPI controllers (SPI0
+ * and SPI1).  Each controller is registered from board bring-up and appears
+ * as /dev/spiN through the stock SPI character driver (spi_register()).
+ *
+ * The controller is a Synopsys DesignWare SSI block programmed through the
+ * SDK fwlib SSI API in polling mode (no interrupts): SSI_Init() programs the
+ * role/mode/clock, and each word is moved by spinning on SSI_Writeable() /
+ * SSI_Readable() around SSI_WriteData() / SSI_ReadData().  The block runs in
+ * transmit-and-receive mode so every word written yields one word read,
+ * which is exactly the full-duplex exchange NuttX expects.
+ *
+ * The SSI fwlib routines resolve to the on-chip ROM symbol table (they are
+ * _LONG_CALL_ entries), like the UART fwlib and unlike the I2C fwlib.  The
+ * SSI baud divider is derived from the peripheral clock ip_clk =
+ * PLL_ClkGet() / (HPERI divider + 1), read once at registration.
+ *
+ * The chip select is driven as a plain GPIO (software CS) rather than the
+ * SSI hardware CS: select() toggles the CS pad through GPIO_WriteBit(), so
+ * any pad can serve as CS and the CS timing is not tied to the FIFO state.
+ *
+ * The chip-specific wiring (controller count, register bases, clock masks
+ * and pad-mux codes) lives in the per-chip ameba_spi_chip.h.  To keep the
+ * vendor headers out of the NuttX include world, the few fwlib symbols and
+ * the SSI_InitTypeDef layout used here are declared locally rather than
+ * pulled in from <ameba_spi.h>.
+ */
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <debug.h>
+#include <inttypes.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/mutex.h>
+#include <nuttx/spi/spi.h>
+#include <nuttx/spi/spi_transfer.h>
+
+#include "ameba_spi.h"
+#include "ameba_spi_chip.h"
+#include "ameba_gpio_chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The register bases, peripheral-clock masks, crossbar pad-mux codes and
+ * controller count (AMEBA_NSPI) come from the per-chip ameba_spi_chip.h.
+ * Everything below is common to every current Ameba chip.
+ */
+
+/* fwlib SSI_InitTypeDef field values (SSI_MASTER, TMOD/FRF, SCPOL/SCPH).
+ * These are identical on every Ameba chip audited.
+ */
+
+#define AMEBA_SSI_MASTER        0x1   /* SSI_MASTER (SPI_Role)              */
+#define AMEBA_SSI_TMOD_TR       0x0   /* Transmit & receive (SPI_TransferMode) 
*/
+#define AMEBA_SSI_FRF_SPI       0x0   /* FRF_MOTOROLA_SPI 
(SPI_DataFrameFormat) */
+
+#define AMEBA_SSI_SCPOL_LOW     0x0   /* SCPOL_INACTIVE_IS_LOW  (CPOL=0)    */
+#define AMEBA_SSI_SCPOL_HIGH    0x1   /* SCPOL_INACTIVE_IS_HIGH (CPOL=1)    */
+#define AMEBA_SSI_SCPH_MIDDLE   0x0   /* SCPH_TOGGLES_IN_MIDDLE (CPHA=0)    */
+#define AMEBA_SSI_SCPH_START    0x1   /* SCPH_TOGGLES_AT_START  (CPHA=1)    */
+
+/* GPIO field values for the software chip select (see ameba_gpio.c). */
+
+#define AMEBA_GPIO_MODE_OUT     0x1   /* GPIO_Mode_OUT                      */
+#define AMEBA_GPIO_PUPD_NONE    0x0   /* GPIO_PuPd_NOPULL                   */
+
+/* Second/third argument to fwlib "state" style APIs. */
+
+#define AMEBA_DISABLE           0x0
+#define AMEBA_ENABLE            0x1
+
+/* Bus defaults used until the upper half programs its own. */
+
+#define AMEBA_SPI_DEFAULT_FREQ  1000000
+#define AMEBA_SPI_DEFAULT_BITS  8
+
+/* Bounded spin guarding each FIFO wait so a wedged bus cannot hang. */
+
+#define AMEBA_SPI_TIMEOUT       1000000
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Layout-compatible mirror of the fwlib SSI_InitTypeDef (all u32, same
+ * order); passed by address to SSI_StructInit()/SSI_Init().
+ */
+
+struct ameba_ssi_init_s
+{
+  uint32_t dmarxlvl;           /* SPI_DmaRxDataLevel   */
+  uint32_t dmatxlvl;           /* SPI_DmaTxDataLevel   */
+  uint32_t rxthlvl;            /* SPI_RxThresholdLevel */
+  uint32_t txthlvl;            /* SPI_TxThresholdLevel */
+  uint32_t ssenable;           /* SPI_SlaveSelectEnable */
+  uint32_t divider;            /* SPI_ClockDivider     */
+  uint32_t framenum;           /* SPI_DataFrameNumber  */
+  uint32_t frameformat;        /* SPI_DataFrameFormat  */
+  uint32_t framesize;          /* SPI_DataFrameSize    */
+  uint32_t intmask;            /* SPI_InterruptMask    */
+  uint32_t role;               /* SPI_Role             */
+  uint32_t sclkphase;          /* SPI_SclkPhase        */
+  uint32_t sclkpolarity;       /* SPI_SclkPolarity     */
+  uint32_t transfermode;       /* SPI_TransferMode     */
+};
+
+/* Layout-compatible mirror of the fwlib GPIO_InitTypeDef (see ameba_gpio.c),
+ * used to configure the software chip-select pad as an output.
+ */
+
+struct ameba_gpio_init_s
+{
+  uint32_t mode;               /* GPIO_Mode        */
+  uint32_t pupd;               /* GPIO_PuPd        */
+  uint32_t ittrigger;          /* GPIO_ITTrigger   */
+  uint32_t itpolarity;         /* GPIO_ITPolarity  */
+  uint32_t itdebounce;         /* GPIO_ITDebounce  */
+  uint32_t pin;                /* GPIO_Pin         */
+};
+
+struct ameba_spi_dev_s
+{
+  struct spi_dev_s dev;        /* SPI master lower half (must be first) */
+  uintptr_t base;              /* Non-secure SPI register base address */
+  uint32_t  periph;            /* APBPeriph function mask (RCC arg 1) */
+  uint32_t  clk;               /* APBPeriph clock mask (RCC arg 2) */
+  uint32_t  ipclk;             /* SSI peripheral clock ip_clk (Hz) */
+  uint32_t  frequency;         /* Requested bus frequency (Hz) */
+  uint32_t  actual;            /* Achieved bus frequency (Hz) */
+  uint32_t  divider;           /* SSI clock divider for that frequency */
+  uint8_t   mode;              /* Currently programmed enum spi_mode_e */
+  uint8_t   nbits;             /* Currently programmed bits per word */
+  bool      dirty;             /* SSI needs an SSI_Init() before use */
+  uint8_t   clkpin;            /* SCLK pad (AMEBA_PA()/AMEBA_PB()) */
+  uint8_t   mosipin;           /* MOSI pad */
+  uint8_t   misopin;           /* MISO pad */
+  uint8_t   cspin;             /* Chip-select pad (GPIO output) */
+  uint8_t   clkfid;            /* Pin mux code for the SCLK pad */
+  uint8_t   mosifid;           /* Pin mux code for the MOSI pad */
+  uint8_t   misofid;           /* Pin mux code for the MISO pad */
+  mutex_t   lock;              /* Serializes bus access */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* SDK fwlib SSI/GPIO/pin/clock API, all resolved from the on-chip ROM. */
+
+extern void RCC_PeriphClockCmd(uint32_t periph, uint32_t clock,
+                               uint8_t newstate);
+extern void Pinmux_Config(uint8_t pin, uint32_t func);
+extern void GPIO_Init(struct ameba_gpio_init_s *init);
+extern void GPIO_WriteBit(uint32_t pin, uint32_t state);
+extern void SSI_SetRole(void *spidev, uint32_t role);
+extern void SSI_StructInit(struct ameba_ssi_init_s *init);
+extern void SSI_Init(void *spidev, struct ameba_ssi_init_s *init);
+extern void SSI_Cmd(void *spidev, uint32_t newstate);
+extern uint32_t SSI_Writeable(void *spidev);
+extern uint32_t SSI_Readable(void *spidev);
+extern void SSI_WriteData(void *spidev, uint32_t value);
+extern uint32_t SSI_ReadData(void *spidev);
+
+/* SPI master lower-half operations. */
+
+static int ameba_spi_lock(struct spi_dev_s *dev, bool lock);
+static void ameba_spi_select(struct spi_dev_s *dev, uint32_t devid,
+                             bool selected);
+static uint32_t ameba_spi_setfrequency(struct spi_dev_s *dev,
+                                       uint32_t frequency);
+static void ameba_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode);
+static void ameba_spi_setbits(struct spi_dev_s *dev, int nbits);
+static uint8_t ameba_spi_status(struct spi_dev_s *dev, uint32_t devid);
+static uint32_t ameba_spi_send(struct spi_dev_s *dev, uint32_t wd);
+#ifdef CONFIG_SPI_EXCHANGE
+static void ameba_spi_exchange(struct spi_dev_s *dev,
+                               const void *txbuffer, void *rxbuffer,
+                               size_t nwords);
+#else
+static void ameba_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
+                               size_t nwords);
+static void ameba_spi_recvblock(struct spi_dev_s *dev, void *buffer,
+                                size_t nwords);
+#endif
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct spi_ops_s g_ameba_spi_ops =
+{
+  .lock         = ameba_spi_lock,
+  .select       = ameba_spi_select,
+  .setfrequency = ameba_spi_setfrequency,
+  .setmode      = ameba_spi_setmode,
+  .setbits      = ameba_spi_setbits,
+  .status       = ameba_spi_status,
+  .send         = ameba_spi_send,
+#ifdef CONFIG_SPI_EXCHANGE
+  .exchange     = ameba_spi_exchange,
+#else
+  .sndblock     = ameba_spi_sndblock,
+  .recvblock    = ameba_spi_recvblock,
+#endif
+};
+
+/* Per-controller register base, peripheral function/clock masks and crossbar
+ * pad-mux codes, indexed by controller number and supplied by the per-chip
+ * ameba_spi_chip.h.
+ */
+
+static const uintptr_t g_spi_base[AMEBA_NSPI]   = AMEBA_SPI_BASES;
+static const uint32_t  g_spi_periph[AMEBA_NSPI] = AMEBA_SPI_APBPERIPH;
+static const uint32_t  g_spi_clk[AMEBA_NSPI]    = AMEBA_SPI_APBPERIPH_CLK;
+static const uint8_t   g_spi_clkfid[AMEBA_NSPI]  = AMEBA_SPI_CLKFID;
+static const uint8_t   g_spi_mosifid[AMEBA_NSPI] = AMEBA_SPI_MOSIFID;
+static const uint8_t   g_spi_misofid[AMEBA_NSPI] = AMEBA_SPI_MISOFID;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_spi_ipclk
+ *
+ * Description:
+ *   Return the SSI peripheral clock ip_clk = PLL_ClkGet() / (HPERI + 1),
+ *   the frequency the baud divider divides down to produce SCLK.
+ *
+ ****************************************************************************/
+
+static uint32_t ameba_spi_ipclk(void)
+{
+  /* The register address and HPERI field position are chip-specific and are
+   * expressed by AMEBA_SPI_IPCLK() in the per-chip ameba_spi_chip.h.
+   */
+
+  return AMEBA_SPI_IPCLK();
+}
+
+/****************************************************************************
+ * Name: ameba_spi_configure
+ *
+ * Description:
+ *   (Re)program the SSI block for the currently requested frequency, mode
+ *   and word length, and enable it.  The DesignWare SSI can only be
+ *   configured while disabled, so this runs lazily on the first word after
+ *   any of those parameters changes (priv->dirty).
+ *
+ ****************************************************************************/
+
+static void ameba_spi_configure(struct ameba_spi_dev_s *priv)
+{
+  struct ameba_ssi_init_s init;
+  void *spidev = (void *)priv->base;
+
+  if (!priv->dirty)
+    {
+      return;
+    }
+
+  memset(&init, 0, sizeof(init));
+  SSI_StructInit(&init);
+
+  init.role         = AMEBA_SSI_MASTER;
+  init.transfermode = AMEBA_SSI_TMOD_TR;
+  init.frameformat  = AMEBA_SSI_FRF_SPI;
+  init.ssenable     = 0;      /* Software CS: do not drive the SSI CS line */
+  init.intmask      = 0;      /* Polling: no SSI interrupts */
+  init.divider      = priv->divider;
+  init.framesize    = priv->nbits - 1;
+
+  /* enum spi_mode_e encodes CPOL in bit1 and CPHA in bit0. */
+
+  init.sclkpolarity = (priv->mode & 0x2) ? AMEBA_SSI_SCPOL_HIGH :
+                                           AMEBA_SSI_SCPOL_LOW;
+  init.sclkphase    = (priv->mode & 0x1) ? AMEBA_SSI_SCPH_START :
+                                           AMEBA_SSI_SCPH_MIDDLE;
+
+  SSI_Cmd(spidev, AMEBA_DISABLE);
+  SSI_Init(spidev, &init);
+  SSI_Cmd(spidev, AMEBA_ENABLE);
+
+  priv->dirty = false;
+}
+
+/****************************************************************************
+ * Name: ameba_spi_transfer
+ *
+ * Description:
+ *   Move nwords words full-duplex in polling mode.  Every word written to
+ *   the TX FIFO produces one word in the RX FIFO (transmit-and-receive
+ *   mode); a NULL txbuffer sends 0xffff and a NULL rxbuffer discards the
+ *   received words.
+ *
+ ****************************************************************************/
+
+static void ameba_spi_transfer(struct ameba_spi_dev_s *priv,
+                               const void *txbuffer, void *rxbuffer,
+                               size_t nwords)
+{
+  void *spidev = (void *)priv->base;
+  bool wide = priv->nbits > 8;
+  size_t i;
+  uint32_t to;
+
+  ameba_spi_configure(priv);
+
+  for (i = 0; i < nwords; i++)
+    {
+      uint32_t word = 0xffff;
+      uint32_t rxword;
+
+      if (txbuffer != NULL)
+        {
+          word = wide ? ((const uint16_t *)txbuffer)[i] :
+                        ((const uint8_t *)txbuffer)[i];
+        }
+
+      for (to = AMEBA_SPI_TIMEOUT;
+           to > 0 && SSI_Writeable(spidev) == 0; to--);
+
+      SSI_WriteData(spidev, word);
+
+      for (to = AMEBA_SPI_TIMEOUT;
+           to > 0 && SSI_Readable(spidev) == 0; to--);
+
+      rxword = SSI_ReadData(spidev);
+
+      if (rxbuffer != NULL)
+        {
+          if (wide)
+            {
+              ((uint16_t *)rxbuffer)[i] = (uint16_t)rxword;
+            }
+          else
+            {
+              ((uint8_t *)rxbuffer)[i] = (uint8_t)rxword;
+            }
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_spi_lock
+ ****************************************************************************/
+
+static int ameba_spi_lock(struct spi_dev_s *dev, bool lock)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  return lock ? nxmutex_lock(&priv->lock) : nxmutex_unlock(&priv->lock);
+}
+
+/****************************************************************************
+ * Name: ameba_spi_select
+ *
+ * Description:
+ *   Assert (selected) or de-assert the active-low chip-select GPIO.  A
+ *   single CS pad is driven per bus, so the devid is not used.
+ *
+ ****************************************************************************/
+
+static void ameba_spi_select(struct spi_dev_s *dev, uint32_t devid,
+                             bool selected)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  GPIO_WriteBit(priv->cspin, selected ? AMEBA_DISABLE : AMEBA_ENABLE);
+}
+
+/****************************************************************************
+ * Name: ameba_spi_setfrequency
+ *
+ * Description:
+ *   Choose the largest even SSI divider whose SCLK does not exceed the
+ *   requested frequency (SCLK = ip_clk / divider, even, 2..65534), and
+ *   return the frequency actually achieved.
+ *
+ ****************************************************************************/
+
+static uint32_t ameba_spi_setfrequency(struct spi_dev_s *dev,
+                                       uint32_t frequency)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+  uint32_t divider;
+
+  if (frequency == 0)
+    {
+      return priv->actual;
+    }
+
+  if (frequency == priv->frequency)
+    {
+      return priv->actual;
+    }
+
+  /* Round the divider down to an even value, then bump it up until SCLK no
+   * longer exceeds the request; clamp to the 16-bit even range.
+   */
+
+  divider = (priv->ipclk / frequency / 2) * 2;
+  if (divider < 2)
+    {
+      divider = 2;
+    }
+
+  if ((priv->ipclk / divider) > frequency)
+    {
+      divider += 2;
+    }
+
+  if (divider > 0xfffe)
+    {
+      divider = 0xfffe;
+    }
+
+  priv->divider   = divider;
+  priv->frequency = frequency;
+  priv->actual    = priv->ipclk / divider;
+  priv->dirty     = true;
+
+  spiinfo("frequency=%" PRIu32 " actual=%" PRIu32 " div=%" PRIu32 "\n",
+          frequency, priv->actual, divider);
+  return priv->actual;
+}
+
+/****************************************************************************
+ * Name: ameba_spi_setmode
+ ****************************************************************************/
+
+static void ameba_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  if ((uint8_t)mode != priv->mode)
+    {
+      priv->mode  = (uint8_t)mode;
+      priv->dirty = true;
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_spi_setbits
+ ****************************************************************************/
+
+static void ameba_spi_setbits(struct spi_dev_s *dev, int nbits)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  if (nbits >= 4 && nbits <= 16 && (uint8_t)nbits != priv->nbits)
+    {
+      priv->nbits = (uint8_t)nbits;
+      priv->dirty = true;
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_spi_status
+ ****************************************************************************/
+
+static uint8_t ameba_spi_status(struct spi_dev_s *dev, uint32_t devid)
+{
+  UNUSED(dev);
+  UNUSED(devid);
+  return 0;
+}
+
+/****************************************************************************
+ * Name: ameba_spi_send
+ ****************************************************************************/
+
+static uint32_t ameba_spi_send(struct spi_dev_s *dev, uint32_t wd)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+  uint16_t rxword = 0;
+  uint16_t txword = (uint16_t)wd;
+
+  ameba_spi_transfer(priv, &txword, &rxword, 1);
+  return rxword;
+}
+
+#ifdef CONFIG_SPI_EXCHANGE
+
+/****************************************************************************
+ * Name: ameba_spi_exchange
+ ****************************************************************************/
+
+static void ameba_spi_exchange(struct spi_dev_s *dev,
+                               const void *txbuffer, void *rxbuffer,
+                               size_t nwords)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  ameba_spi_transfer(priv, txbuffer, rxbuffer, nwords);
+}
+
+#else
+
+/****************************************************************************
+ * Name: ameba_spi_sndblock
+ ****************************************************************************/
+
+static void ameba_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
+                               size_t nwords)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  ameba_spi_transfer(priv, buffer, NULL, nwords);
+}
+
+/****************************************************************************
+ * Name: ameba_spi_recvblock
+ ****************************************************************************/
+
+static void ameba_spi_recvblock(struct spi_dev_s *dev, void *buffer,
+                                size_t nwords)
+{
+  struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
+
+  ameba_spi_transfer(priv, NULL, buffer, nwords);
+}
+
+#endif /* CONFIG_SPI_EXCHANGE */
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_spi_register
+ *
+ * Description:
+ *   See ameba_spi.h.
+ *
+ ****************************************************************************/
+
+struct spi_dev_s *ameba_spi_register(int bus, uint8_t clkpin,
+                                     uint8_t mosipin, uint8_t misopin,
+                                     uint8_t cspin)
+{
+  struct ameba_spi_dev_s *priv;
+  struct ameba_gpio_init_s gpio;
+  int ret;
+
+  if (bus < 0 || bus >= AMEBA_NSPI)
+    {
+      return NULL;
+    }
+
+  priv = kmm_zalloc(sizeof(struct ameba_spi_dev_s));
+  if (priv == NULL)
+    {
+      return NULL;
+    }
+
+  priv->dev.ops   = &g_ameba_spi_ops;
+  priv->base      = g_spi_base[bus];
+  priv->periph    = g_spi_periph[bus];
+  priv->clk       = g_spi_clk[bus];
+  priv->clkpin    = clkpin;
+  priv->mosipin   = mosipin;
+  priv->misopin   = misopin;
+  priv->cspin     = cspin;
+  priv->clkfid    = g_spi_clkfid[bus];
+  priv->mosifid   = g_spi_mosifid[bus];
+  priv->misofid   = g_spi_misofid[bus];
+  priv->mode      = SPIDEV_MODE0;
+  priv->nbits     = AMEBA_SPI_DEFAULT_BITS;
+  priv->frequency = 0;
+  priv->dirty     = true;
+  nxmutex_init(&priv->lock);
+
+  /* Gate the SSI peripheral clock and route the CLK/MOSI/MISO pads. */
+
+  RCC_PeriphClockCmd(priv->periph, priv->clk, AMEBA_ENABLE);
+
+  /* The controller powers up as an SPI slave (the LSYS SPIx_MST bit is clear
+   * out of reset), and in slave mode the master-only baud-rate and slave-
+   * enable registers are write-ignored so the block never drives SCLK.
+   * Switch it to master before any SSI_Init() so that configuration sticks.
+   */
+
+  SSI_SetRole((void *)priv->base, AMEBA_SSI_MASTER);
+
+  Pinmux_Config(priv->clkpin, priv->clkfid);
+  Pinmux_Config(priv->mosipin, priv->mosifid);
+  Pinmux_Config(priv->misopin, priv->misofid);
+
+  /* Configure the chip-select pad as a GPIO output, de-asserted (high). */
+
+  RCC_PeriphClockCmd(AMEBA_APBPERIPH_GPIO, AMEBA_APBPERIPH_GPIO,
+                     AMEBA_ENABLE);
+  memset(&gpio, 0, sizeof(gpio));
+  gpio.mode = AMEBA_GPIO_MODE_OUT;
+  gpio.pupd = AMEBA_GPIO_PUPD_NONE;
+  gpio.pin  = priv->cspin;
+  GPIO_Init(&gpio);
+  GPIO_WriteBit(priv->cspin, AMEBA_ENABLE);
+
+  /* Latch the peripheral clock and prime the default bus frequency. */
+
+  priv->ipclk = ameba_spi_ipclk();
+  ameba_spi_setfrequency(&priv->dev, AMEBA_SPI_DEFAULT_FREQ);
+
+  ret = spi_register(&priv->dev, bus);
+  if (ret < 0)
+    {
+      _err("ERROR: spi_register(/dev/spi%d) failed: %d\n", bus, ret);
+      nxmutex_destroy(&priv->lock);
+      kmm_free(priv);
+      return NULL;
+    }
+
+  return &priv->dev;
+}
diff --git a/arch/arm/src/common/ameba/ameba_spi.h 
b/arch/arm/src/common/ameba/ameba_spi.h
new file mode 100644
index 00000000000..92d7f275d50
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_spi.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_spi.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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_COMMON_AMEBA_AMEBA_SPI_H
+#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_SPI_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+#include <nuttx/spi/spi.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The Ameba SPI (DesignWare SSI) controllers exposed to NuttX as SPI master
+ * buses.  The CLK/MOSI/MISO/CS pads are given with the same AMEBA_PA()/
+ * AMEBA_PB() PinName code used by the GPIO driver (see ameba_gpio.h); any
+ * pad can be routed to an SPI bus through the pin mux, and the chip select
+ * is driven as a plain GPIO (software CS) so any pad may serve as CS.
+ */
+
+#define AMEBA_SPI0            0
+#define AMEBA_SPI1            1
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ameba_spi_register
+ *
+ * Description:
+ *   Configure one Ameba SPI (DesignWare SSI) controller as a master bus,
+ *   registering it with the NuttX SPI character driver at /dev/spiN, where N
+ *   is the bus number.  The chip select is driven as a GPIO (software CS).
+ *
+ * Input Parameters:
+ *   bus     - The controller index, AMEBA_SPI0 or AMEBA_SPI1.  Also used as
+ *             the /dev/spiN minor number.
+ *   clkpin  - The SCLK pad, encoded with AMEBA_PA()/AMEBA_PB().
+ *   mosipin - The MOSI pad, encoded with AMEBA_PA()/AMEBA_PB().
+ *   misopin - The MISO pad, encoded with AMEBA_PA()/AMEBA_PB().
+ *   cspin   - The chip-select pad, driven as an active-low GPIO output.
+ *
+ * Returned Value:
+ *   The SPI master lower half on success; NULL on failure.
+ *
+ ****************************************************************************/
+
+struct spi_dev_s *ameba_spi_register(int bus, uint8_t clkpin,
+                                     uint8_t mosipin, uint8_t misopin,
+                                     uint8_t cspin);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_SPI_H */
diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt 
b/arch/arm/src/rtl8721dx/CMakeLists.txt
index bcf9450f9a3..b43237a62a0 100644
--- a/arch/arm/src/rtl8721dx/CMakeLists.txt
+++ b/arch/arm/src/rtl8721dx/CMakeLists.txt
@@ -52,6 +52,10 @@ if(CONFIG_AMEBA_I2C)
   list(APPEND SRCS ${AMEBA_COMMON}/ameba_i2c.c)
 endif()
 
+if(CONFIG_AMEBA_SPI)
+  list(APPEND SRCS ${AMEBA_COMMON}/ameba_spi.c)
+endif()
+
 target_include_directories(arch PRIVATE ${AMEBA_COMMON})
 target_sources(arch PRIVATE ${SRCS})
 
diff --git a/arch/arm/src/rtl8721dx/Make.defs b/arch/arm/src/rtl8721dx/Make.defs
index fbe728e46f4..333db513af8 100644
--- a/arch/arm/src/rtl8721dx/Make.defs
+++ b/arch/arm/src/rtl8721dx/Make.defs
@@ -62,6 +62,10 @@ ifeq ($(CONFIG_AMEBA_I2C),y)
 CHIP_CSRCS += ameba_i2c.c
 endif
 
+ifeq ($(CONFIG_AMEBA_SPI),y)
+CHIP_CSRCS += ameba_spi.c
+endif
+
 ############################################################################
 # Realtek RTL8721Dx SDK integration
 #
diff --git a/arch/arm/src/rtl8721dx/ameba_board.mk 
b/arch/arm/src/rtl8721dx/ameba_board.mk
index 1a59dce5832..24b5d30e26f 100644
--- a/arch/arm/src/rtl8721dx/ameba_board.mk
+++ b/arch/arm/src/rtl8721dx/ameba_board.mk
@@ -146,6 +146,15 @@ ifeq ($(CONFIG_AMEBA_I2C),y)
 AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_i2c.c
 endif
 
+# SPI (DesignWare SSI) register layer.  Like I2C, the fwlib SSI API is NOT in
+# ROM: the SPI driver (arch/.../common/ameba/ameba_spi.c) calls SSI_Init/
+# StructInit/Cmd and SSI_Writeable/Readable/WriteData/ReadData, all compiled
+# from this RAM source and linked in (--gc-sections drops the unused DMA/
+# interrupt helpers).
+ifeq ($(CONFIG_AMEBA_SPI),y)
+AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_spi.c
+endif
+
 # -Wno-int-conversion: the vendored SDK passes NULL to irq_register()'s u32
 # "Data" (interrupt context) argument in many places -- an intentional
 # NULL-as-context idiom.  Silence -Wint-conversion for the SDK fwlib sources
diff --git a/arch/arm/src/rtl8721dx/ameba_spi_chip.h 
b/arch/arm/src/rtl8721dx/ameba_spi_chip.h
new file mode 100644
index 00000000000..ee0f8dfbb40
--- /dev/null
+++ b/arch/arm/src/rtl8721dx/ameba_spi_chip.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+ * arch/arm/src/rtl8721dx/ameba_spi_chip.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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_RTL8721DX_AMEBA_SPI_CHIP_H
+#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_SPI_CHIP_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Per-chip SPI wiring for RTL8721DX (amebadplus).  The shared driver
+ * (arch/arm/src/common/ameba/ameba_spi.c) includes this header to learn how
+ * many SPI (DesignWare SSI) controllers the chip exposes and, for each, its
+ * register base, peripheral-clock masks and crossbar pad-mux codes.
+ *
+ * Contract for the other Ameba chips (amebalite / amebasmart / amebagreen2 /
+ * RTL8720F): supply a same-named header on the chip include path with the
+ * macros below.  What differs per chip, from the fwlib audit:
+ *
+ *   1. Controller count and bases: amebadplus exposes two high-speed SSI
+ *      masters, SPI0 and SPI1, in the PERI_HCLK domain.  The bases below are
+ *      the NON-secure peripheral aliases (0x4012xxxx); the secure aliases
+ *      (0x5012xxxx) must not be used from the non-secure world.
+ *
+ *   2. APBPeriph "function" and "clock" masks are two separate lists because
+ *      RCC_PeriphClockCmd() takes them as distinct arguments.  They are
+ *      equal on this chip.  NOTE the group selector bit30 is 0 here (unlike
+ *      the Ameba UART/I2C blocks where bit30 is 1), so each chip must supply
+ *      its own values.
+ *
+ *   3. Pad mux: SPI0 (master) shares one generic PINMUX_FUNCTION_SPI code
+ *      (8) on all four signals, whereas SPI1 uses per-signal codes
+ *      (CLK/MOSI/MISO/CS).  The codes are therefore supplied as one list per
+ *      signal, indexed by controller.  The chip-select entries are only used
+ *      when hardware CS is selected; this driver drives CS as a plain GPIO
+ *      (software CS), so the CS pad-mux code is informational.
+ */
+
+#define AMEBA_NSPI                2
+
+/* NON-secure SPI register bases (SPI0_REG_BASE / SPI1_REG_BASE). */
+
+#define AMEBA_SPI_BASES           { 0x40124000ul, 0x40125000ul }
+
+/* APBPeriph_SPIx (function) and APBPeriph_SPIx_CLOCK masks.  Equal on this
+ * chip; kept as two lists so chips where they differ can supply both.  The
+ * group selector (bit30) is 0 for the SPI block on amebadplus.
+ */
+
+#define AMEBA_SPI_APBPERIPH       \
+        { (((uint32_t)0 << 30) | ((uint32_t)1 << 14)), \
+          (((uint32_t)0 << 30) | ((uint32_t)1 << 15)) }
+
+#define AMEBA_SPI_APBPERIPH_CLK   \
+        { (((uint32_t)0 << 30) | ((uint32_t)1 << 14)), \
+          (((uint32_t)0 << 30) | ((uint32_t)1 << 15)) }
+
+/* Crossbar pad-mux function codes, one list per signal, indexed by
+ * controller.  SPI0 uses the generic PINMUX_FUNCTION_SPI (8) on every
+ * signal; SPI1 uses its per-signal codes CLK=29 / MISO=30 / MOSI=31 / CS=32.
+ */
+
+#define AMEBA_SPI_CLKFID          { 8, 29 }  /* SPI0 generic, SPI1_CLK  */
+#define AMEBA_SPI_MOSIFID         { 8, 31 }  /* SPI0 generic, SPI1_MOSI */
+#define AMEBA_SPI_MISOFID         { 8, 30 }  /* SPI0 generic, SPI1_MISO */
+#define AMEBA_SPI_CSFID           { 8, 32 }  /* SPI0 generic, SPI1_CS   */
+
+/* SSI peripheral clock: ip_clk = PLL_ClkGet() / (HPERI divider + 1), the
+ * frequency the SPI baud divider divides down to make SCLK.  On amebadplus
+ * the HPERI divider field is bits[10:8] of REG_LSYS_CKD_GRP0 in the system-
+ * control block (non-secure alias 0x41008000 + 0x0220).  BOTH the register
+ * address and the field position are chip-specific -- e.g. on amebalite that
+ * same bit field is not HPERI -- so the whole computation lives here rather
+ * than in the shared driver. PLL_ClkGet() is the amebadplus fwlib PLL query;
+ * each chip header declares the clock-source query its AMEBA_SPI_IPCLK()
+ * uses (the shared driver includes this header before the macro expands).
+ */
+
+extern uint32_t PLL_ClkGet(void);
+
+#define AMEBA_SPI_CKD_GRP0        0x41008220ul
+#define AMEBA_SPI_IPCLK() \
+        (PLL_ClkGet() / \
+         ((((*(volatile uint32_t *)AMEBA_SPI_CKD_GRP0) >> 8) & 0x7) + 1))
+
+#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_SPI_CHIP_H */
diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/spi/defconfig 
b/boards/arm/rtl8721dx/pke8721daf/configs/spi/defconfig
new file mode 100644
index 00000000000..3a4ec21ecae
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/configs/spi/defconfig
@@ -0,0 +1,51 @@
+#
+# This file is autogenerated: PLEASE DO NOT EDIT IT.
+#
+# You can use "make menuconfig" to make any modifications to the installed 
.config file.
+# You can then do "make savedefconfig" to generate a new defconfig file that 
includes your
+# modifications.
+#
+# CONFIG_DEBUG_WARN is not set
+CONFIG_AMEBA_SPI=y
+CONFIG_ARCH="arm"
+CONFIG_ARCH_BOARD="pke8721daf"
+CONFIG_ARCH_BOARD_PKE8721DAF=y
+CONFIG_ARCH_CHIP="rtl8721dx"
+CONFIG_ARCH_CHIP_RTL8721DX=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_ARMV8M_SYSTICK=y
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_ASSERTIONS=y
+CONFIG_DEBUG_FEATURES=y
+CONFIG_DEBUG_FULLOPT=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_DEFAULT_TASK_STACKSIZE=4096
+CONFIG_EXAMPLES_HELLO=y
+CONFIG_FS_PROCFS=y
+CONFIG_FS_TMPFS=y
+CONFIG_IDLETHREAD_STACKSIZE=4096
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_LIBC_MEMFD_ERROR=y
+CONFIG_MM_DEFAULT_ALIGNMENT=32
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_READLINE=y
+CONFIG_PREALLOC_TIMERS=4
+CONFIG_RAM_SIZE=294912
+CONFIG_RAM_START=0x20020000
+CONFIG_RR_INTERVAL=200
+CONFIG_RTL8721DX_FLASH_FS=y
+CONFIG_SCHED_HPWORK=y
+CONFIG_SCHED_HPWORKPRIORITY=192
+CONFIG_SCHED_LPWORK=y
+CONFIG_STACK_COLORATION=y
+CONFIG_START_DAY=16
+CONFIG_START_MONTH=6
+CONFIG_START_YEAR=2026
+CONFIG_SYSTEM_NSH=y
+CONFIG_SYSTEM_NSH_STACKSIZE=2500
+CONFIG_SYSTEM_SPITOOL=y
+CONFIG_TIMER=y
+CONFIG_TIMER_ARCH=y
+CONFIG_USEC_PER_TICK=1000
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt 
b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
index 2ccfdc647a5..34abcac3a23 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
+++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
@@ -34,11 +34,16 @@ if(CONFIG_AMEBA_I2C)
   list(APPEND SRCS rtl8721dx_i2c.c)
 endif()
 
+if(CONFIG_AMEBA_SPI)
+  list(APPEND SRCS rtl8721dx_spi.c)
+endif()
+
 target_sources(board PRIVATE ${SRCS})
 
 if(CONFIG_AMEBA_GPIO
    OR CONFIG_AMEBA_UART
-   OR CONFIG_AMEBA_I2C)
+   OR CONFIG_AMEBA_I2C
+   OR CONFIG_AMEBA_SPI)
   # The board pin/UART tables pull in the shared driver's public headers from
   # arch/arm/src/common/ameba/, not on the default board include path.
   target_include_directories(board
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/Makefile 
b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
index ed80ab1a32e..03eda20df62 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile
+++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
@@ -51,4 +51,13 @@ CSRCS += rtl8721dx_i2c.c
 CFLAGS += 
${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
 endif
 
+ifeq ($(CONFIG_AMEBA_SPI),y)
+CSRCS += rtl8721dx_spi.c
+
+# The board SPI table pulls in the shared driver's public header from
+# arch/arm/src/common/ameba/, which is not on the default board include path.
+
+CFLAGS += 
${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
+endif
+
 include $(TOPDIR)/boards/Board.mk
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
index ab034d08ff7..5ff19f3177f 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
@@ -138,6 +138,16 @@ int rtl8721dx_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_AMEBA_SPI
+  /* Register the board's SPI master buses at /dev/spiN. */
+
+  ret = rtl8721dx_spi_initialize();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: rtl8721dx_spi_initialize failed: %d\n", ret);
+    }
+#endif
+
   /* Install the inter-core HW IPC-semaphore RTOS hooks LAST -- after all the
    * flash / WHC bring-up above, and just before this (board_late_initialize)
    * path returns and nx_start() hands off to the init task.
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
index 52be521d3d9..63a31287359 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
@@ -108,6 +108,19 @@ int rtl8721dx_uart_initialize(void);
 int rtl8721dx_i2c_initialize(void);
 #endif
 
+#ifdef CONFIG_AMEBA_SPI
+/****************************************************************************
+ * Name: rtl8721dx_spi_initialize
+ *
+ * Description:
+ *   Register the board's SPI master buses at /dev/spiN
+ *   (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c).
+ *
+ ****************************************************************************/
+
+int rtl8721dx_spi_initialize(void);
+#endif
+
 #ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
  * Name: ameba_flash_fs_initialize
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c
new file mode 100644
index 00000000000..83c3d42791d
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * 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/param.h>
+#include <syslog.h>
+#include <errno.h>
+
+#include "ameba_gpio.h"
+#include "ameba_spi.h"
+#include "rtl8721dx_pke8721daf.h"
+
+#ifdef CONFIG_AMEBA_SPI
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* One entry per SPI bus exposed to NuttX at /dev/spiN.  The CLK/MOSI/MISO/CS
+ * pads below are examples used by the `spi` config (system/spi spitool);
+ * adjust them to match your board's wiring.
+ */
+
+struct rtl8721dx_spi_s
+{
+  int     bus;                  /* Controller index (AMEBA_SPI0/AMEBA_SPI1) */
+  uint8_t clkpin;               /* SCLK pad (AMEBA_PA()/AMEBA_PB() encoding) */
+  uint8_t mosipin;              /* MOSI pad */
+  uint8_t misopin;              /* MISO pad */
+  uint8_t cspin;                /* Chip-select pad (software CS GPIO) */
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct rtl8721dx_spi_s g_spi_buses[] =
+{
+  {
+    AMEBA_SPI0, AMEBA_PA(14), AMEBA_PA(15), AMEBA_PA(16), AMEBA_PA(17)
+  },
+  {
+    AMEBA_SPI1, AMEBA_PB(18), AMEBA_PB(19), AMEBA_PB(20), AMEBA_PB(21)
+  },
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rtl8721dx_spi_initialize
+ *
+ * Description:
+ *   Register the board's SPI master buses at /dev/spiN.
+ *
+ ****************************************************************************/
+
+int rtl8721dx_spi_initialize(void)
+{
+  int i;
+
+  for (i = 0; i < (int)nitems(g_spi_buses); i++)
+    {
+      if (ameba_spi_register(g_spi_buses[i].bus, g_spi_buses[i].clkpin,
+                             g_spi_buses[i].mosipin, g_spi_buses[i].misopin,
+                             g_spi_buses[i].cspin) == NULL)
+        {
+          syslog(LOG_ERR,
+                 "ERROR: ameba_spi_register(/dev/spi%d) failed\n",
+                 g_spi_buses[i].bus);
+          return -ENODEV;
+        }
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_AMEBA_SPI */
diff --git a/tools/nxstyle.c b/tools/nxstyle.c
index f5ddef85124..ddf8faa59ca 100644
--- a/tools/nxstyle.c
+++ b/tools/nxstyle.c
@@ -275,6 +275,7 @@ static const char *g_white_prefix[] =
   "FLASH_",
   "GPIO_",
   "Get_OSC131_",      /* Get_OSC131_STATE — Ameba SDK RTC accessor */
+  "HPERI_",           /* HPERI_ClkGet — amebagreen2 SPI ip_clk query */
   "I2C_",             /* I2C_Init, I2C_MasterWrite, I2C_InitTypeDef, etc. */
   "IPC_",
   "LOGUART_",
@@ -282,14 +283,17 @@ static const char *g_white_prefix[] =
   "OSC4M_",
   "OSC131K_",
   "PAD_",
+  "PLL_",             /* PLL_ClkGet — amebadplus SPI ip_clk query */
   "Pinmux_",
   "RCC_",
   "RTC_",             /* RTC_InitTypeDef, RTC_Enable, RTC_SetTime, etc. */
   "RTCIO_",
   "SDM32K_",          /* SDM32K_Enable */
+  "SSI_",             /* SSI_Init, SSI_SetRole, SSI_WriteData, etc. */
   "Set_OSC131_",      /* Set_OSC131_STATE — Ameba SDK RTC accessor */
   "SYSCFG_",
   "SYSTIMER_",
+  "SYS_PLL_",         /* SYS_PLL_ClkGet — RTL8720F SPI ip_clk query */
   "UART_",
   "SystemCoreClock",  /* SystemCoreClock, SystemCoreClockUpdate */
   "cmse_",            /* ARM CMSE TrustZone intrinsics (arm_cmse.h) */

Reply via email to