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

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


The following commit(s) were added to refs/heads/master by this push:
     new e2ff8be9007 arch/arm/rtl8721dx: add shared Ameba GPIO driver.
e2ff8be9007 is described below

commit e2ff8be9007106c23b0f61e162e57c81adfdca54
Author: dechao_gong <[email protected]>
AuthorDate: Mon Jul 13 15:36:17 2026 +0800

    arch/arm/rtl8721dx: add shared Ameba GPIO driver.
    
    Add a board-agnostic GPIO driver for Realtek Ameba chips, exposing pins
    through the NuttX GPIO (ioexpander) upper half at /dev/gpioN.
    
    - arch/arm/src/common/ameba/ameba_gpio.{c,h}: the shared driver, sitting
      directly on the SDK fwlib register layer.  The fwlib GPIO API it calls
      resolves at link time from the on-chip ROM symbol table, except
      GPIO_INTStatusGet/ClearEdge which are not in ROM and are compiled in
      from fwlib ram_common/ameba_gpio.c (that object also carries GPIO_Init,
      harmlessly overriding the equivalent ROM copy).  Pin interrupts are
      dispatched NuttX-natively: the port's NVIC vector is owned by NuttX
      via irq_attach, and the ISR reads and clears status through the fwlib
      GPIO_INTStatus* helpers.
    - The driver keeps nothing IC-specific: the port count, the per-port
      NVIC vectors and the RCC gate bits come from a per-chip
      <ameba_gpio_chip.h> resolved on the include path, so bringing up a new
      Ameba chip only adds that header, not a change to the shared driver.
    - arch/arm/src/common/ameba/Kconfig: a shared "Ameba Peripheral Support"
      menu with the AMEBA_GPIO option, sourced by each Ameba chip's Kconfig so
      it is reused across ICs (RTL8721Dx, RTL8720F, ...).
    - arch/arm/src/rtl8721dx: provide ameba_gpio_chip.h, wire the driver into
      both the Make/Kconfig and CMake builds, and compile the fwlib
      ameba_gpio.c register layer into libameba_fwlib for the RTL8721Dx.
    - boards/arm/rtl8721dx/pke8721daf: board pin table (rtl8721dx_gpio.c),
      bring-up registration, and a standalone gpio defconfig.
    - Documentation: describe the gpio configuration and pin encoding.
    
    tools/nxstyle: whitelist the Ameba "GPIO_" SDK ROM symbol prefix (alongside
    the existing FLASH_/IPC_/... Ameba prefixes) so the vendor GPIO API's 
mixed-case
    identifiers do not trip nxstyle, matching how the other Ameba SDK prefixes 
are
    handled.
    
    Assisted-by: Claude Code:claude-opus-4-8
    Signed-off-by: dechao_gong <[email protected]>
---
 .../arm/rtl8721dx/boards/pke8721daf/index.rst      |  20 +
 arch/arm/src/common/ameba/Kconfig                  |  25 +
 arch/arm/src/common/ameba/ameba_gpio.c             | 572 +++++++++++++++++++++
 .../arm/src/common/ameba/ameba_gpio.h              |  81 +--
 arch/arm/src/rtl8721dx/CMakeLists.txt              |  10 +
 arch/arm/src/rtl8721dx/Kconfig                     |   5 +
 arch/arm/src/rtl8721dx/Make.defs                   |   4 +
 arch/arm/src/rtl8721dx/ameba_board.mk              |  12 +
 arch/arm/src/rtl8721dx/ameba_gpio_chip.h           |  70 +++
 .../rtl8721dx/pke8721daf/configs/gpio/defconfig    |  51 ++
 boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt |  11 +
 boards/arm/rtl8721dx/pke8721daf/src/Makefile       |   9 +
 .../rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c   |  10 +
 .../{rtl8721dx_pke8721daf.h => rtl8721dx_gpio.c}   |  90 ++--
 .../pke8721daf/src/rtl8721dx_pke8721daf.h          |  13 +
 tools/nxstyle.c                                    |   1 +
 16 files changed, 908 insertions(+), 76 deletions(-)

diff --git a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst 
b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
index e0e317b445b..0570a3afd92 100644
--- a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
+++ b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
@@ -33,6 +33,8 @@ Supported in this NuttX port:
   partition), backing the Wi-Fi key-value store
 * Wi-Fi station and SoftAP through the ``wapi`` tool
 * DHCP client (STA) and DHCP server (SoftAP)
+* GPIO pins exposed as ``/dev/gpioN`` character devices (input, output and
+  interrupt), driven directly on the SDK fwlib register layer
 
 Buttons and LEDs
 ================
@@ -58,6 +60,24 @@ The console is the LOG-UART at 1500000 8N1 (the rate is 
configured by the
 bootloader and inherited by NuttX). The Wi-Fi examples below are available from
 this configuration.
 
+gpio
+----
+
+Minimal NSH with the GPIO driver and the ``gpio`` example enabled (no Wi-Fi).
+The board registers three pins from its pin table (see
+``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_gpio.c``): an output at
+``/dev/gpio0``, an input at ``/dev/gpio1`` and an interrupt pin at
+``/dev/gpio2``. Edit that table to match a board's wiring. Exercise them with
+the example::
+
+    nsh> gpio -o 1 /dev/gpio0     # drive the output high
+    nsh> gpio /dev/gpio1          # read the input
+    nsh> gpio -w 1 /dev/gpio2     # wait for a rising-edge interrupt
+
+Pins are encoded with the ``AMEBA_PA()`` / ``AMEBA_PB()`` helpers from
+``arch/arm/src/common/ameba/ameba_gpio.h`` (port A/B, pin 0-31), matching the
+Ameba SDK ``PinName`` layout.
+
 Wi-Fi
 =====
 
diff --git a/arch/arm/src/common/ameba/Kconfig 
b/arch/arm/src/common/ameba/Kconfig
new file mode 100644
index 00000000000..30d17d5ca06
--- /dev/null
+++ b/arch/arm/src/common/ameba/Kconfig
@@ -0,0 +1,25 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+# Shared Realtek Ameba peripheral drivers (arch/arm/src/common/ameba).  These
+# options are common to every Ameba ARM chip (RTL8721Dx, RTL8720F, ...) and
+# are sourced from each chip's own Kconfig.
+#
+
+menu "Ameba Peripheral Support"
+
+config AMEBA_GPIO
+    bool "GPIO"
+    default n
+    select DEV_GPIO
+    ---help---
+        Expose Ameba GPIO pins through the NuttX GPIO (ioexpander) upper
+        half at /dev/gpioN.  The board selects which pins are registered
+        and their type (input, output or interrupt) in its bring-up code.
+
+        The driver (arch/arm/src/common/ameba/ameba_gpio.c) sits on the SDK
+        fwlib register layer and dispatches pin interrupts through the ROM
+        GPIO interrupt handler.
+
+endmenu # Ameba Peripheral Support
diff --git a/arch/arm/src/common/ameba/ameba_gpio.c 
b/arch/arm/src/common/ameba/ameba_gpio.c
new file mode 100644
index 00000000000..21e06ea5a1f
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_gpio.c
@@ -0,0 +1,572 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_gpio.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 GPIO (ioexpander) lower half for the Realtek Ameba GPIO controller.
+ *
+ * The pad mux, pull resistors and GPIO port registers are all programmed
+ * through the SDK fwlib GPIO API (GPIO_Init(), GPIO_WriteBit(), ...).
+ *
+ * Most of the API has no SDK source and resolves to the on-chip ROM symbol
+ * table (GPIO_WriteBit, GPIO_ReadDataBit, GPIO_INTConfig, GPIO_Direction,
+ * GPIO_INTMode, Pinmux_Config, PAD_PullCtrl, ...).  Only GPIO_INTStatusGet
+ * and GPIO_INTStatusClearEdge are missing from ROM; they live in fwlib
+ * ram_common/ameba_gpio.c, compiled into libameba_fwlib.a to provide them
+ * (see AMEBA_FWLIB_SRCS).  That same source also defines GPIO_Init (which
+ * IS in ROM), so the local object overrides the ROM PROVIDE(GPIO_Init) as a
+ * harmless side effect -- same source, equivalent behaviour.  To keep the
+ * vendor headers out of the NuttX include world, the handful of fwlib
+ * symbols and the GPIO_InitTypeDef layout used here are declared locally
+ * below rather than pulled in from <ameba_gpio.h>.
+ *
+ * Interrupt dispatch stays NuttX-native: the port's NVIC vector is owned by
+ * NuttX (irq_attach), and the ISR reads/clears the controller status through
+ * the fwlib GPIO_INTStatus* helpers before calling each pending pin's
+ * registered callback.
+ */
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/arch.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/spinlock.h>
+#include <nuttx/ioexpander/gpio.h>
+
+#include "arm_internal.h"
+#include "ameba_gpio.h"
+
+/* Per-chip GPIO parameters (port count, per-port NVIC vectors, RCC gate
+ * bits).  Resolved from the configured chip's directory on the include path
+ * (arch/.../chip -> arch/arm/src/<chip>); see <ameba_gpio_chip.h> there.
+ */
+
+#include "ameba_gpio_chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Pin encoding (SDK "PinName": port in bits[7:5], pin num in bits[4:0]). */
+
+#define AMEBA_PIN_PORT(pin)     (((pin) >> 5) & 0x07)
+#define AMEBA_PIN_NUM(pin)      ((pin) & 0x1f)
+
+/* Mirror of the fwlib GPIO_InitTypeDef field values (ameba_gpio.h).
+ * Declared locally so the SDK header need not leak into NuttX includes.
+ */
+
+#define AMEBA_GPIO_MODE_IN      0x0   /* GPIO_Mode_IN                       */
+#define AMEBA_GPIO_MODE_OUT     0x1   /* GPIO_Mode_OUT                      */
+#define AMEBA_GPIO_MODE_INT     0x2   /* GPIO_Mode_INT                      */
+
+#define AMEBA_GPIO_PUPD_NONE    0x0   /* GPIO_PuPd_NOPULL                   */
+#define AMEBA_GPIO_PUPD_DOWN    0x1   /* GPIO_PuPd_DOWN                     */
+#define AMEBA_GPIO_PUPD_UP      0x2   /* GPIO_PuPd_UP                       */
+
+#define AMEBA_GPIO_IT_LEVEL     0x0   /* GPIO_INT_Trigger_LEVEL             */
+#define AMEBA_GPIO_IT_EDGE      0x1   /* GPIO_INT_Trigger_EDGE              */
+#define AMEBA_GPIO_IT_BOTHEDGE  0x2   /* GPIO_INT_Trigger_BOTHEDGE          */
+
+#define AMEBA_GPIO_POL_LOW      0x0   /* GPIO_INT_POLARITY_ACTIVE_LOW       */
+#define AMEBA_GPIO_POL_HIGH     0x1   /* GPIO_INT_POLARITY_ACTIVE_HIGH      */
+
+#define AMEBA_GPIO_DEBOUNCE_OFF 0x0   /* GPIO_INT_DEBOUNCE_DISABLE          */
+
+/* Second argument to GPIO_INTConfig() / state passed to GPIO_WriteBit(). */
+
+#define AMEBA_DISABLE           0x0
+#define AMEBA_ENABLE            0x1
+
+/* AMEBA_APBPERIPH_GPIO (the RCC gate bits), AMEBA_GPIO_NPORTS and the
+ * AMEBA_GPIO_PORT_IRQS vector table come from <ameba_gpio_chip.h>.  The
+ * fwlib GPIO_Init() leaves the peripheral clock to the caller, so the driver
+ * gates AMEBA_APBPERIPH_GPIO on before use (RCC touches the LSYS block).
+ */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Layout-compatible mirror of the fwlib GPIO_InitTypeDef (all u32, same
+ * order); passed by address to GPIO_Init().
+ */
+
+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_gpio_dev_s
+{
+  struct gpio_dev_s gpio;      /* Lower-half GPIO device (must be first) */
+  uint8_t pin;                 /* SDK pin encoding (port[7:5] | num[4:0]) */
+  pin_interrupt_t callback;    /* Interrupt callback (interrupt pins only) */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* SDK fwlib GPIO API used by this driver.  These are plain extern symbols;
+ * the driver does not care where each comes from -- every one is resolved
+ * at link time either by the on-chip ROM symbol table or by a fwlib object
+ * compiled into libameba_fwlib.a.  Which source applies is per-chip:
+ *
+ * RCC_PeriphClockCmd, GPIO_Init, GPIO_WriteBit, GPIO_ReadDataBit and
+ * GPIO_INTConfig are in ROM on every Ameba ARM chip.  (GPIO_Init also
+ * reaches Pinmux_Config, GPIO_Direction, GPIO_INTMode and PAD_PullCtrl
+ * internally, resolved inside ROM, so the driver never names them.)
+ *
+ * GPIO_INTStatusGet and GPIO_INTStatusClearEdge are the gap.  On RTL8721Dx
+ * they come from fwlib ram_common/ameba_gpio.c, compiled in via
+ * AMEBA_FWLIB_SRCS (that object also carries GPIO_Init and harmlessly
+ * overrides the ROM copy).  On amebalite/amebasmart they are in neither ROM
+ * nor any SDK source, so a chip adding those parts MUST supply its own
+ * definition -- a small object writing the port's GPIO_INT_STATUS /
+ * GPIO_INT_EOI -- via its board build.  The shared driver needs no change;
+ * this is the per-chip contract for these two symbols.
+ */
+
+extern void RCC_PeriphClockCmd(uint32_t periph, uint32_t clock,
+                               uint8_t newstate);
+extern void GPIO_Init(struct ameba_gpio_init_s *init);
+extern void GPIO_WriteBit(uint32_t pin, uint32_t state);
+extern uint32_t GPIO_ReadDataBit(uint32_t pin);
+extern void GPIO_INTConfig(uint32_t pin, uint32_t newstate);
+extern uint32_t GPIO_INTStatusGet(uint32_t port);
+extern void GPIO_INTStatusClearEdge(uint32_t port);
+
+/* GPIO lower-half operations */
+
+static int ameba_gpio_read(struct gpio_dev_s *dev, bool *value);
+static int ameba_gpio_write(struct gpio_dev_s *dev, bool value);
+static int ameba_gpio_attach(struct gpio_dev_s *dev,
+                             pin_interrupt_t callback);
+static int ameba_gpio_enable(struct gpio_dev_s *dev, bool enable);
+static int ameba_gpio_setpintype(struct gpio_dev_s *dev,
+                                 enum gpio_pintype_e pintype);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct gpio_operations_s g_ameba_gpio_ops =
+{
+  .go_read       = ameba_gpio_read,
+  .go_write      = ameba_gpio_write,
+  .go_attach     = ameba_gpio_attach,
+  .go_enable     = ameba_gpio_enable,
+  .go_setpintype = ameba_gpio_setpintype,
+};
+
+/* Driver-wide state, kept in one controller instance instead of
+ * scattered file-scope globals.  A single NVIC vector per port drives
+ * up to 32 pins, so the dispatch table, the attach flags and the NVIC
+ * vectors are all per-port, and one spinlock guards the shared table.
+ */
+
+struct ameba_gpio_ctrl_s
+{
+  /* Per-pin device lookup for interrupt dispatch, indexed [port][pin-num];
+   * set once an interrupt pin is registered, read by the port ISR.
+   */
+
+  struct ameba_gpio_dev_s *int_dev[AMEBA_GPIO_NPORTS][32];
+
+  /* Set once a port's NVIC vector has been attached + enabled. */
+
+  bool port_attached[AMEBA_GPIO_NPORTS];
+
+  /* Per-port NVIC vector, from the chip's <ameba_gpio_chip.h>. */
+
+  const int irq[AMEBA_GPIO_NPORTS];
+
+  spinlock_t lock;
+};
+
+static struct ameba_gpio_ctrl_s g_ameba_gpio =
+{
+  .irq  = AMEBA_GPIO_PORT_IRQS,
+  .lock = SP_UNLOCKED,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_gpio_configure
+ *
+ * Description:
+ *   Program the pad mux, direction, pull and (for interrupt pins) trigger
+ *   mode for the given pin type through the fwlib GPIO_Init().  Interrupt
+ *   pins are left masked until the application enables them.
+ *
+ ****************************************************************************/
+
+static void ameba_gpio_configure(struct ameba_gpio_dev_s *priv,
+                                  enum gpio_pintype_e pintype)
+{
+  struct ameba_gpio_init_s init;
+
+  init.pin        = priv->pin;
+  init.pupd       = AMEBA_GPIO_PUPD_NONE;
+  init.ittrigger  = AMEBA_GPIO_IT_EDGE;
+  init.itpolarity = AMEBA_GPIO_POL_LOW;
+  init.itdebounce = AMEBA_GPIO_DEBOUNCE_OFF;
+
+  /* Gate on the GPIO peripheral clock (idempotent). */
+
+  RCC_PeriphClockCmd(AMEBA_APBPERIPH_GPIO, AMEBA_APBPERIPH_GPIO,
+                     AMEBA_ENABLE);
+
+  switch (pintype)
+    {
+      case GPIO_OUTPUT_PIN:
+      case GPIO_OUTPUT_PIN_OPENDRAIN:
+        init.mode = AMEBA_GPIO_MODE_OUT;
+        break;
+
+      case GPIO_INPUT_PIN:
+        init.mode = AMEBA_GPIO_MODE_IN;
+        break;
+
+      case GPIO_INPUT_PIN_PULLUP:
+        init.mode = AMEBA_GPIO_MODE_IN;
+        init.pupd = AMEBA_GPIO_PUPD_UP;
+        break;
+
+      case GPIO_INPUT_PIN_PULLDOWN:
+        init.mode = AMEBA_GPIO_MODE_IN;
+        init.pupd = AMEBA_GPIO_PUPD_DOWN;
+        break;
+
+      case GPIO_INTERRUPT_HIGH_PIN:
+        init.mode       = AMEBA_GPIO_MODE_INT;
+        init.ittrigger  = AMEBA_GPIO_IT_LEVEL;
+        init.itpolarity = AMEBA_GPIO_POL_HIGH;
+        break;
+
+      case GPIO_INTERRUPT_LOW_PIN:
+        init.mode       = AMEBA_GPIO_MODE_INT;
+        init.ittrigger  = AMEBA_GPIO_IT_LEVEL;
+        init.itpolarity = AMEBA_GPIO_POL_LOW;
+        break;
+
+      case GPIO_INTERRUPT_RISING_PIN:
+        init.mode       = AMEBA_GPIO_MODE_INT;
+        init.ittrigger  = AMEBA_GPIO_IT_EDGE;
+        init.itpolarity = AMEBA_GPIO_POL_HIGH;
+        break;
+
+      case GPIO_INTERRUPT_BOTH_PIN:
+        init.mode       = AMEBA_GPIO_MODE_INT;
+        init.ittrigger  = AMEBA_GPIO_IT_BOTHEDGE;
+        init.itpolarity = AMEBA_GPIO_POL_LOW;
+        break;
+
+      case GPIO_INTERRUPT_PIN:
+      case GPIO_INTERRUPT_FALLING_PIN:
+      default:
+        init.mode       = AMEBA_GPIO_MODE_INT;
+        init.ittrigger  = AMEBA_GPIO_IT_EDGE;
+        init.itpolarity = AMEBA_GPIO_POL_LOW;
+        break;
+    }
+
+  GPIO_Init(&init);
+
+  /* GPIO_Init() leaves the interrupt source enabled, and setting up the
+   * trigger can latch a stale edge.  While the pin is still unmasked, clear
+   * that latch, then mask the pin until the application enables it.  Order
+   * matters: GPIO_INTStatusClearEdge() acts through the masked
+   * GPIO_INT_STATUS, so it clears only a currently-unmasked pin --
+   * clearing after masking would miss this pin.  The clear+mask pair runs
+   * under the port lock so the ISR cannot dispatch a stale edge in between;
+   * clearing the whole port's edge latches is safe during registration, as
+   * no interrupt pin is application-enabled yet.
+   */
+
+  if (init.mode == AMEBA_GPIO_MODE_INT)
+    {
+      irqstate_t flags = spin_lock_irqsave(&g_ameba_gpio.lock);
+
+      GPIO_INTStatusClearEdge(AMEBA_PIN_PORT(priv->pin));
+      GPIO_INTConfig(priv->pin, AMEBA_DISABLE);
+
+      spin_unlock_irqrestore(&g_ameba_gpio.lock, flags);
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_interrupt
+ *
+ * Description:
+ *   NuttX-native vector for a GPIO port.  Reads the (masked) interrupt
+ *   status, clears the edge latches and dispatches to each pending pin's
+ *   registered callback.
+ *
+ ****************************************************************************/
+
+static int ameba_gpio_interrupt(int irq, void *context, void *arg)
+{
+  int port = (int)(uintptr_t)arg;
+  uint32_t status = GPIO_INTStatusGet(port);
+
+  /* Clear the port's edge latches.  GPIO_INTStatusClearEdge() re-reads the
+   * status internally and takes no bitmask, so an edge that arrives between
+   * the read above and this clear is acknowledged without being dispatched
+   * this pass -- an inherent limit of the two-call fwlib interface.  It is
+   * harmless for the board's single edge pin, and level-triggered sources
+   * re-assert and fire again regardless.
+   */
+
+  GPIO_INTStatusClearEdge(port);
+
+  while (status != 0)
+    {
+      int num = __builtin_ctz(status);
+      struct ameba_gpio_dev_s *priv = g_ameba_gpio.int_dev[port][num];
+
+      status &= ~((uint32_t)1 << num);
+
+      if (priv != NULL && priv->callback != NULL)
+        {
+          priv->callback(&priv->gpio, num);
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_read
+ ****************************************************************************/
+
+static int ameba_gpio_read(struct gpio_dev_s *dev, bool *value)
+{
+  struct ameba_gpio_dev_s *priv = (struct ameba_gpio_dev_s *)dev;
+
+  DEBUGASSERT(priv != NULL && value != NULL);
+
+  *value = (GPIO_ReadDataBit(priv->pin) != 0);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_write
+ ****************************************************************************/
+
+static int ameba_gpio_write(struct gpio_dev_s *dev, bool value)
+{
+  struct ameba_gpio_dev_s *priv = (struct ameba_gpio_dev_s *)dev;
+
+  DEBUGASSERT(priv != NULL);
+
+  GPIO_WriteBit(priv->pin, value ? AMEBA_ENABLE : AMEBA_DISABLE);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_attach
+ ****************************************************************************/
+
+static int ameba_gpio_attach(struct gpio_dev_s *dev,
+                             pin_interrupt_t callback)
+{
+  struct ameba_gpio_dev_s *priv = (struct ameba_gpio_dev_s *)dev;
+
+  DEBUGASSERT(priv != NULL);
+
+  /* Mask the interrupt while (re)attaching, then record the callback. */
+
+  GPIO_INTConfig(priv->pin, AMEBA_DISABLE);
+  priv->callback = callback;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_enable
+ ****************************************************************************/
+
+static int ameba_gpio_enable(struct gpio_dev_s *dev, bool enable)
+{
+  struct ameba_gpio_dev_s *priv = (struct ameba_gpio_dev_s *)dev;
+
+  DEBUGASSERT(priv != NULL);
+
+  if (enable && priv->callback != NULL)
+    {
+      /* Unmask the pin, then drop any stale edge that unmasking just exposed
+       * (the GPIO_Init arming transient, or an edge latched while masked) so
+       * the first wait does not fire on a past interrupt.  Clearing runs
+       * with the pin unmasked -- GPIO_INTStatusClearEdge() works through the
+       * masked GPIO_INT_STATUS -- and the port lock keeps the ISR from
+       * dispatching that stale edge between the two steps.  Level-triggered
+       * pins do not latch, so a genuinely active level still fires at once.
+       */
+
+      irqstate_t flags = spin_lock_irqsave(&g_ameba_gpio.lock);
+
+      GPIO_INTConfig(priv->pin, AMEBA_ENABLE);
+      GPIO_INTStatusClearEdge(AMEBA_PIN_PORT(priv->pin));
+
+      spin_unlock_irqrestore(&g_ameba_gpio.lock, flags);
+    }
+  else
+    {
+      GPIO_INTConfig(priv->pin, AMEBA_DISABLE);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_setpintype
+ ****************************************************************************/
+
+static int ameba_gpio_setpintype(struct gpio_dev_s *dev,
+                                  enum gpio_pintype_e pintype)
+{
+  struct ameba_gpio_dev_s *priv = (struct ameba_gpio_dev_s *)dev;
+
+  DEBUGASSERT(priv != NULL);
+
+  if (pintype >= GPIO_NPINTYPES)
+    {
+      return -EINVAL;
+    }
+
+  ameba_gpio_configure(priv, pintype);
+  dev->gp_pintype = pintype;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_gpio_port_attach
+ *
+ * Description:
+ *   Attach and enable the NuttX-native interrupt vector for the GPIO port
+ *   that owns the given pin (once per port).
+ *
+ ****************************************************************************/
+
+static void ameba_gpio_port_attach(uint8_t pin)
+{
+  irqstate_t flags;
+  int port = AMEBA_PIN_PORT(pin);
+
+  flags = spin_lock_irqsave(&g_ameba_gpio.lock);
+
+  if (!g_ameba_gpio.port_attached[port])
+    {
+      int irq = g_ameba_gpio.irq[port];
+
+      irq_attach(irq, ameba_gpio_interrupt, (void *)(uintptr_t)port);
+      up_enable_irq(irq);
+      g_ameba_gpio.port_attached[port] = true;
+    }
+
+  spin_unlock_irqrestore(&g_ameba_gpio.lock, flags);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_gpio_register
+ *
+ * Description:
+ *   See ameba_gpio.h.
+ *
+ ****************************************************************************/
+
+int ameba_gpio_register(int minor, uint8_t pin, enum gpio_pintype_e pintype)
+{
+  struct ameba_gpio_dev_s *priv;
+  bool interrupt;
+  int ret;
+
+  if (pintype >= GPIO_NPINTYPES)
+    {
+      return -EINVAL;
+    }
+
+  interrupt = (pintype >= GPIO_INTERRUPT_PIN);
+
+  priv = kmm_zalloc(sizeof(struct ameba_gpio_dev_s));
+  if (priv == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  priv->pin             = pin;
+  priv->gpio.gp_pintype = pintype;
+  priv->gpio.gp_ops     = &g_ameba_gpio_ops;
+
+  /* Apply the pad/pin configuration for this pin type. */
+
+  ameba_gpio_configure(priv, pintype);
+
+  if (interrupt)
+    {
+      /* Publish the device for the port ISR and make sure the owning port's
+       * NuttX vector is live.  The pin stays masked until the application
+       * attaches a callback and enables it.
+       */
+
+      g_ameba_gpio.int_dev[AMEBA_PIN_PORT(pin)][AMEBA_PIN_NUM(pin)] = priv;
+      ameba_gpio_port_attach(pin);
+    }
+
+  ret = gpio_pin_register(&priv->gpio, minor);
+  if (ret < 0)
+    {
+      gpioerr("ERROR: gpio_pin_register(%d) failed: %d\n", minor, ret);
+
+      if (interrupt)
+        {
+          GPIO_INTConfig(pin, AMEBA_DISABLE);
+          g_ameba_gpio.int_dev[AMEBA_PIN_PORT(pin)][AMEBA_PIN_NUM(pin)] =
+            NULL;
+        }
+
+      kmm_free(priv);
+    }
+
+  return ret;
+}
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h 
b/arch/arm/src/common/ameba/ameba_gpio.h
similarity index 52%
copy from boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
copy to arch/arm/src/common/ameba/ameba_gpio.h
index a8f1906edcf..3dc0bd32147 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/arch/arm/src/common/ameba/ameba_gpio.h
@@ -1,5 +1,5 @@
 /****************************************************************************
- * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+ * arch/arm/src/common/ameba/ameba_gpio.h
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -20,8 +20,8 @@
  *
  ****************************************************************************/
 
-#ifndef __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H
-#define __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H
+#ifndef __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_GPIO_H
+#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_GPIO_H
 
 /****************************************************************************
  * Included Files
@@ -29,58 +29,65 @@
 
 #include <nuttx/config.h>
 
-/****************************************************************************
- * Public Function Prototypes
- ****************************************************************************/
+#include <stdint.h>
 
-#ifndef __ASSEMBLY__
+#include <nuttx/ioexpander/gpio.h>
 
 /****************************************************************************
- * Name: rtl8721dx_boardinitialize
- *
- * Description:
- *   Perform board-specific early initialization.
- *
+ * Pre-processor Definitions
  ****************************************************************************/
 
-void rtl8721dx_boardinitialize(void);
+/* A pin is identified with the same encoding the Ameba SDK uses for its
+ * "PinName": bits[7:5] select the GPIO port and bits[4:0] the pin within
+ * the port.  Boards build their pin table from these helpers so the shared
+ * driver never needs to know a board's wiring.
+ */
 
-/****************************************************************************
- * Name: rtl8721dx_bringup
- *
- * Description:
- *   Bring up board features.
- *
- ****************************************************************************/
+#define AMEBA_PORT_A          0
+#define AMEBA_PORT_B          1
+
+#define AMEBA_PIN(port, num)  ((uint8_t)(((port) << 5) | ((num) & 0x1f)))
+
+/* Convenience: the RTL8721Dx / RTL8720F break out ports A and B. */
 
-int rtl8721dx_bringup(void);
+#define AMEBA_PA(num)         AMEBA_PIN(AMEBA_PORT_A, (num))
+#define AMEBA_PB(num)         AMEBA_PIN(AMEBA_PORT_B, (num))
 
-#ifdef CONFIG_RTL8721DX_WIFI
 /****************************************************************************
- * Name: rtl8721dx_wifi_initialize
- *
- * Description:
- *   Bring up the KM4 IPC transport and start the WHC host WiFi stack
- *   (arch/arm/src/rtl8721dx/ameba_wifi_init.c).
- *
+ * Public Function Prototypes
  ****************************************************************************/
 
-int rtl8721dx_wifi_initialize(void);
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
 #endif
 
-#ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
- * Name: ameba_flash_fs_initialize
+ * Name: ameba_gpio_register
  *
  * Description:
- *   Register the on-chip SPI NOR data partition as an MTD device and mount
- *   a littlefs filesystem on it at /data
- *   (arch/arm/src/rtl8721dx/ameba_flash_mtd.c).
+ *   Configure a single Ameba GPIO pin and register it with the NuttX GPIO
+ *   (ioexpander) upper half at /dev/gpioN, where N is the given minor.
+ *
+ * Input Parameters:
+ *   minor   - The /dev/gpioN minor number.
+ *   pin     - The pin, encoded with AMEBA_PIN() / AMEBA_PA() / AMEBA_PB().
+ *   pintype - One of enum gpio_pintype_e (input, output or one of the
+ *             interrupt pin types).
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
  *
  ****************************************************************************/
 
-int ameba_flash_fs_initialize(void);
+int ameba_gpio_register(int minor, uint8_t pin, enum gpio_pintype_e pintype);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
 #endif
 
-#endif /* __ASSEMBLY__ */
-#endif /* __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H */
+#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_GPIO_H */
diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt 
b/arch/arm/src/rtl8721dx/CMakeLists.txt
index 423a80470fe..7bd0ded54f2 100644
--- a/arch/arm/src/rtl8721dx/CMakeLists.txt
+++ b/arch/arm/src/rtl8721dx/CMakeLists.txt
@@ -40,6 +40,10 @@ if(CONFIG_RTL8721DX_FLASH_FS)
   list(APPEND SRCS ${AMEBA_COMMON}/ameba_flash_mtd.c)
 endif()
 
+if(CONFIG_AMEBA_GPIO)
+  list(APPEND SRCS ${AMEBA_COMMON}/ameba_gpio.c)
+endif()
+
 target_include_directories(arch PRIVATE ${AMEBA_COMMON})
 target_sources(arch PRIVATE ${SRCS})
 
@@ -90,6 +94,12 @@ if(CONFIG_RTL8721DX_FLASH_FS)
   list(APPEND AMEBA_FWLIB_SRCS ${AMEBA_SOC}/fwlib/ram_common/ameba_flash_ram.c)
 endif()
 
+if(CONFIG_AMEBA_GPIO)
+  # GPIO_INTStatusGet/ClearEdge are not in the ROM symbol table; compile them 
in
+  # from the fwlib source (mirrors the make build's ameba_board.mk).
+  list(APPEND AMEBA_FWLIB_SRCS ${AMEBA_SOC}/fwlib/ram_common/ameba_gpio.c)
+endif()
+
 # Silence a couple of warnings the vendored SDK sources trip under NuttX's
 # warning set, scoped to this fwlib compile only (never relaxing NuttX's own):
 # -Wno-int-conversion: the SDK passes NULL to irq_register()'s u32 "Data"
diff --git a/arch/arm/src/rtl8721dx/Kconfig b/arch/arm/src/rtl8721dx/Kconfig
index 64efd1cecab..8f3018cbc80 100644
--- a/arch/arm/src/rtl8721dx/Kconfig
+++ b/arch/arm/src/rtl8721dx/Kconfig
@@ -82,4 +82,9 @@ config RTL8721DX_FLASH_FS
 
 endmenu # RTL8721Dx Storage
 
+# Shared Ameba peripheral drivers (GPIO, ...) live in the common IC-agnostic
+# tree and are configured through one Kconfig reused by every Ameba chip.
+
+source "arch/arm/src/common/ameba/Kconfig"
+
 endif # ARCH_CHIP_RTL8721DX
diff --git a/arch/arm/src/rtl8721dx/Make.defs b/arch/arm/src/rtl8721dx/Make.defs
index 44a9f2224b0..02c51ef4c12 100644
--- a/arch/arm/src/rtl8721dx/Make.defs
+++ b/arch/arm/src/rtl8721dx/Make.defs
@@ -50,6 +50,10 @@ ifeq ($(CONFIG_RTL8721DX_FLASH_FS),y)
 CHIP_CSRCS += ameba_flash_mtd.c
 endif
 
+ifeq ($(CONFIG_AMEBA_GPIO),y)
+CHIP_CSRCS += ameba_gpio.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 c326add632e..d7a61fa4a94 100644
--- a/arch/arm/src/rtl8721dx/ameba_board.mk
+++ b/arch/arm/src/rtl8721dx/ameba_board.mk
@@ -116,6 +116,18 @@ AMEBA_FWLIB_SRCS += 
$(TOPDIR)/arch/arm/src/rtl8721dx/ameba_app_start.c \
 ifeq ($(CONFIG_RTL8721DX_FLASH_FS),y)
 AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_flash_ram.c
 endif
+# GPIO register layer.  The GPIO driver (arch/.../common/ameba/ameba_gpio.c)
+# calls the fwlib GPIO API.  GPIO_INTStatusGet/ClearEdge are NOT in the ROM
+# symbol table, so fwlib ram_common/ameba_gpio.c is compiled in to provide
+# them.  That same source also defines GPIO_Init (which IS in ROM), so the
+# local object overrides the ROM PROVIDE(GPIO_Init) as a harmless side effect
+# (same source, equivalent behaviour).  Everything else the driver calls
+# (GPIO_WriteBit/ReadDataBit/Direction/INTMode/INTConfig, Pinmux_Config,
+# PAD_PullCtrl) has no SDK source and resolves to the ROM symbol table.
+ifeq ($(CONFIG_AMEBA_GPIO),y)
+AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_gpio.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_gpio_chip.h 
b/arch/arm/src/rtl8721dx/ameba_gpio_chip.h
new file mode 100644
index 00000000000..280de546489
--- /dev/null
+++ b/arch/arm/src/rtl8721dx/ameba_gpio_chip.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+ * arch/arm/src/rtl8721dx/ameba_gpio_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_GPIO_CHIP_H
+#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_GPIO_CHIP_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+#include <nuttx/irq.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Per-chip GPIO parameters for the shared driver
+ * (arch/arm/src/common/ameba/ameba_gpio.c).  The driver logic, pin encoding
+ * and fwlib API are identical across every Ameba ARM chip, but the port
+ * count, the per-port NVIC vectors and the RCC gate bits are not.  Each chip
+ * supplies its own <ameba_gpio_chip.h> on the include path (arch/.../chip);
+ * the common driver sizes its tables and wires its vectors from the macros
+ * below, so nothing IC-specific is left in common.
+ *
+ * RTL8721Dx (KM4) breaks out two GPIO ports, A and B, each a 32-pin bank
+ * with its own interrupt vector.
+ */
+
+/* Number of GPIO ports (banks) this chip exposes. */
+
+#define AMEBA_GPIO_NPORTS      2
+
+/* NVIC vector for each port, as an initialiser indexed by port number
+ * (0 = A, 1 = B).  Its width must match AMEBA_GPIO_NPORTS.
+ */
+
+#define AMEBA_GPIO_PORT_IRQS   { RTL8721DX_IRQ_GPIOA, RTL8721DX_IRQ_GPIOB }
+
+/* APBPeriph_GPIO / APBPeriph_GPIO_CLOCK (sysreg_lsys.h): the peripheral and
+ * clock bits RCC_PeriphClockCmd() gates for the GPIO block.  On RTL8721Dx
+ * both are ((1 << 30) | (1 << 4)); this value differs on amebasmart, which
+ * is why it lives here rather than in the shared driver.
+ */
+
+#define AMEBA_APBPERIPH_GPIO   (((uint32_t)1 << 30) | ((uint32_t)1 << 4))
+
+#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_GPIO_CHIP_H */
diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/gpio/defconfig 
b/boards/arm/rtl8721dx/pke8721daf/configs/gpio/defconfig
new file mode 100644
index 00000000000..40d05ba350b
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/configs/gpio/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_GPIO=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_GPIO=y
+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_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 170196b66b9..b1cc8e75471 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
+++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
@@ -22,7 +22,18 @@
 
 set(SRCS rtl8721dx_boot.c rtl8721dx_bringup.c)
 
+if(CONFIG_AMEBA_GPIO)
+  list(APPEND SRCS rtl8721dx_gpio.c)
+endif()
+
 target_sources(board PRIVATE ${SRCS})
 
+if(CONFIG_AMEBA_GPIO)
+  # The board pin table pulls in the shared driver's public header from
+  # arch/arm/src/common/ameba/, not on the default board include path.
+  target_include_directories(board
+                             PRIVATE ${NUTTX_DIR}/arch/arm/src/common/ameba)
+endif()
+
 # LD_SCRIPT is not set here: the Ameba image2 linker script is generated
 # (prebuilt/ld.script.gen) and published by the shared ameba_board.cmake.
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/Makefile 
b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
index 957c553ddbe..be0c63a792d 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile
+++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
@@ -24,4 +24,13 @@ include $(TOPDIR)/Make.defs
 
 CSRCS = rtl8721dx_boot.c rtl8721dx_bringup.c
 
+ifeq ($(CONFIG_AMEBA_GPIO),y)
+CSRCS += rtl8721dx_gpio.c
+
+# The board pin 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 e091d1b96fe..2532afcaf8f 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
@@ -108,6 +108,16 @@ int rtl8721dx_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_AMEBA_GPIO
+  /* Register the board's GPIO pins at /dev/gpioN. */
+
+  ret = rtl8721dx_gpio_initialize();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: rtl8721dx_gpio_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_gpio.c
similarity index 55%
copy from boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
copy to boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_gpio.c
index a8f1906edcf..6eb721f158b 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_gpio.c
@@ -1,5 +1,5 @@
 /****************************************************************************
- * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+ * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_gpio.c
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -20,67 +20,79 @@
  *
  ****************************************************************************/
 
-#ifndef __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H
-#define __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H
-
 /****************************************************************************
  * Included Files
  ****************************************************************************/
 
 #include <nuttx/config.h>
 
-/****************************************************************************
- * Public Function Prototypes
- ****************************************************************************/
+#include <sys/param.h>
+#include <syslog.h>
+
+#include <nuttx/ioexpander/gpio.h>
+
+#include "ameba_gpio.h"
+#include "rtl8721dx_pke8721daf.h"
 
-#ifndef __ASSEMBLY__
+#ifdef CONFIG_AMEBA_GPIO
 
 /****************************************************************************
- * Name: rtl8721dx_boardinitialize
- *
- * Description:
- *   Perform board-specific early initialization.
- *
+ * Private Types
  ****************************************************************************/
 
-void rtl8721dx_boardinitialize(void);
+/* One entry per GPIO pin exposed to NuttX.  The pins below are examples used
+ * by the `gpio` (examples/gpio) test -- adjust them to match your board's
+ * wiring.  They are registered in order as /dev/gpio0, /dev/gpio1, ...
+ */
+
+struct rtl8721dx_gpio_s
+{
+  uint8_t pin;                  /* AMEBA_PA()/AMEBA_PB() pin encoding */
+  enum gpio_pintype_e pintype;  /* Input, output or interrupt */
+};
 
 /****************************************************************************
- * Name: rtl8721dx_bringup
- *
- * Description:
- *   Bring up board features.
- *
+ * Private Data
  ****************************************************************************/
 
-int rtl8721dx_bringup(void);
+static const struct rtl8721dx_gpio_s g_gpio_pins[] =
+{
+  { AMEBA_PB(18), GPIO_OUTPUT_PIN    },  /* /dev/gpio0: output    */
+  { AMEBA_PB(19), GPIO_INPUT_PIN     },  /* /dev/gpio1: input     */
+  { AMEBA_PB(20), GPIO_INTERRUPT_PIN },  /* /dev/gpio2: interrupt */
+};
 
-#ifdef CONFIG_RTL8721DX_WIFI
 /****************************************************************************
- * Name: rtl8721dx_wifi_initialize
- *
- * Description:
- *   Bring up the KM4 IPC transport and start the WHC host WiFi stack
- *   (arch/arm/src/rtl8721dx/ameba_wifi_init.c).
- *
+ * Public Functions
  ****************************************************************************/
 
-int rtl8721dx_wifi_initialize(void);
-#endif
-
-#ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
- * Name: ameba_flash_fs_initialize
+ * Name: rtl8721dx_gpio_initialize
  *
  * Description:
- *   Register the on-chip SPI NOR data partition as an MTD device and mount
- *   a littlefs filesystem on it at /data
- *   (arch/arm/src/rtl8721dx/ameba_flash_mtd.c).
+ *   Register the board's GPIO pins with the NuttX GPIO upper half.
  *
  ****************************************************************************/
 
-int ameba_flash_fs_initialize(void);
-#endif
+int rtl8721dx_gpio_initialize(void)
+{
+  int ret;
+  size_t i;
+
+  for (i = 0; i < nitems(g_gpio_pins); i++)
+    {
+      ret = ameba_gpio_register(i, g_gpio_pins[i].pin,
+                                g_gpio_pins[i].pintype);
+      if (ret < 0)
+        {
+          syslog(LOG_ERR,
+                 "ERROR: ameba_gpio_register(/dev/gpio%zu) failed: %d\n",
+                 i, ret);
+          return ret;
+        }
+    }
+
+  return OK;
+}
 
-#endif /* __ASSEMBLY__ */
-#endif /* __BOARDS_ARM_RTL8721DX_PKE8721DAF_SRC_RTL8721DX_PKE8721DAF_H */
+#endif /* CONFIG_AMEBA_GPIO */
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
index a8f1906edcf..85d13543d0e 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
@@ -68,6 +68,19 @@ int rtl8721dx_bringup(void);
 int rtl8721dx_wifi_initialize(void);
 #endif
 
+#ifdef CONFIG_AMEBA_GPIO
+/****************************************************************************
+ * Name: rtl8721dx_gpio_initialize
+ *
+ * Description:
+ *   Register the board's GPIO pins with the NuttX GPIO upper half
+ *   (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_gpio.c).
+ *
+ ****************************************************************************/
+
+int rtl8721dx_gpio_initialize(void);
+#endif
+
 #ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
  * Name: ameba_flash_fs_initialize
diff --git a/tools/nxstyle.c b/tools/nxstyle.c
index d0576f0f655..ae827641677 100644
--- a/tools/nxstyle.c
+++ b/tools/nxstyle.c
@@ -272,6 +272,7 @@ static const char *g_white_prefix[] =
   "DCache_",
   "ChipInfo_",
   "FLASH_",
+  "GPIO_",
   "IPC_",
   "LOGUART_",
   "OSC2M_",


Reply via email to