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

commit 07fd8cc936a4865838b077f1ac9e6a3e9dfb1fc3
Author: dechao_gong <[email protected]>
AuthorDate: Mon Jul 20 16:34:17 2026 +0800

    arch/arm/rtl8721dx: add shared Ameba ADC driver
    
    Add a NuttX ADC lower-half for the Ameba SoC family, wired for the
    amebadplus/pke8721daf as /dev/adc0.  The driver uses the fwlib ROM
    API and drives on-demand polled conversions via ADC_ReceiveBuf with
    the hardware auto channel-switch FIFO, because amebadplus disables the
    ADC software-trigger path.  Per-chip wiring (channel count, pinmux
    function id, APB clock bits, optional aux clock) lives in a chip
    header so a new IC only supplies its own values without touching the
    shared driver.
    
    Reports raw conversion codes per the NuttX convention (12-bit
    effective, 0..~3876 for 0..3.3V), consistent with the STM32/i.MXRT/
    Tiva ADC drivers.
    
    Verified on hardware: 0V->121, 3.3V->3876 on CH0(PB19) while
    CH1(PB18) held steady, confirming sampling, full-scale and
    multi-channel switch-list isolation.
    
    Signed-off-by: dechao_gong <[email protected]>
    Assisted-by: Claude <[email protected]>
---
 .../arm/rtl8721dx/boards/pke8721daf/index.rst      |  17 +
 arch/arm/src/common/ameba/Kconfig                  |  17 +
 arch/arm/src/common/ameba/ameba_adc.c              | 495 +++++++++++++++++++++
 arch/arm/src/common/ameba/ameba_adc.h              | 100 +++++
 arch/arm/src/rtl8721dx/CMakeLists.txt              |   4 +
 arch/arm/src/rtl8721dx/Make.defs                   |   4 +
 arch/arm/src/rtl8721dx/ameba_adc_chip.h            | 103 +++++
 .../arm/rtl8721dx/pke8721daf/configs/adc/defconfig |  54 +++
 boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt |   7 +-
 boards/arm/rtl8721dx/pke8721daf/src/Makefile       |   9 +
 .../arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.c   |  90 ++++
 .../rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c   |  10 +
 .../pke8721daf/src/rtl8721dx_pke8721daf.h          |  13 +
 13 files changed, 922 insertions(+), 1 deletion(-)

diff --git a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst 
b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
index 14554d6ddae..3861744e7a2 100644
--- a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
+++ b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
@@ -43,6 +43,8 @@ Supported in this NuttX port:
   on the SDK fwlib register layer
 * PWM output exposed as a ``/dev/pwm0`` character device, driven directly on
   the SDK fwlib timer register layer
+* ADC channels exposed as an ``/dev/adc0`` character device, driven directly
+  on the SDK fwlib register layer
 
 Buttons and LEDs
 ================
@@ -153,6 +155,21 @@ example::
 
     nsh> pwm -d 25 -f 1000     # 1 kHz, 25% duty on /dev/pwm0
 
+adc
+---
+
+Minimal NSH with the ADC driver and the ``adc`` example enabled (no Wi-Fi).
+The board registers its channels from a table (see
+``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.c``): ``/dev/adc0`` samples
+CH0 on PB19 and CH1 on PB18. Edit that table -- channel numbers and the analog
+pad each is wired to -- to match a board's wiring; the external channels
+CH0..CH6 map to pads PB19..PB13 and are muxed to the ADC function through the
+SDK ROM, while internal channels carry ``AMEBA_ADC_PIN_NC``. Every listed
+channel is sampled, in order, on each trigger. Read the channels with the
+example::
+
+    nsh> adc -n 1                        # one sweep of /dev/adc0
+
 Wi-Fi
 =====
 
diff --git a/arch/arm/src/common/ameba/Kconfig 
b/arch/arm/src/common/ameba/Kconfig
index 217d4d2232e..64775022448 100644
--- a/arch/arm/src/common/ameba/Kconfig
+++ b/arch/arm/src/common/ameba/Kconfig
@@ -100,4 +100,21 @@ config AMEBA_PWM
                The driver (arch/arm/src/common/ameba/ameba_pwm.c) sits on the 
SDK
                fwlib RTIM register layer and runs in polling task context.
 
+config AMEBA_ADC
+       bool "ADC"
+       default n
+       select ANALOG
+       select ADC
+       ---help---
+               Expose the Ameba SAR ADC as a NuttX ADC input at /dev/adc0.  The
+               converter has one channel-switch list; the board selects which
+               channels are sampled and which analog pad each external channel 
is
+               wired to in its bring-up code.  Every channel is sampled, in 
list
+               order, on each ANIOC_TRIGGER.
+
+               The driver (arch/arm/src/common/ameba/ameba_adc.c) sits on the 
SDK
+               fwlib register layer and reads the auto channel-switch FIFO by
+               polling in task context (the chip does not enable a hardware
+               software-trigger path).
+
 endmenu # Ameba Peripheral Support
diff --git a/arch/arm/src/common/ameba/ameba_adc.c 
b/arch/arm/src/common/ameba/ameba_adc.c
new file mode 100644
index 00000000000..9ba17598e26
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_adc.c
@@ -0,0 +1,495 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_adc.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 ADC lower half for the Realtek Ameba ADC.  The chip has one SAR
+ * converter fed by a channel-switch list; the board says which channels to
+ * sample and which analog pad each external channel is wired to, and this
+ * driver exposes them as one /dev/adc0 device.  Every ANIOC_TRIGGER samples
+ * the whole list, in list order, and hands each raw conversion value to the
+ * ADC upper half tagged with the hardware channel number the sample came
+ * from.
+ *
+ * The converter is driven through the SDK fwlib ADC API.  Those routines are
+ * marked _LONG_CALL_ but are linked from the fwlib RAM source ameba_adc.c
+ * (already compiled into every image for the clock/brown-out calibration),
+ * so no extra source is added to the board build for the ADC.  The fwlib API
+ * takes no register base: it selects the secure or non-secure ADC alias
+ * itself with TrustZone_IsSecure(), so this driver never touches a base
+ * address.
+ *
+ * On amebadplus the SDK deliberately does not enable the ADC hardware
+ * software-trigger path (ADC_SWTrigCmd() is compiled out and only logs a
+ * "not supported" note).  The supported on-demand read is a bounded burst of
+ * the auto channel-switch FIFO: ADC_ReceiveBuf() clears the FIFO, enables
+ * the auto channel switch, reads exactly the requested number of words and
+ * disables it again.  Reading one word per listed channel therefore yields a
+ * single polled sweep of the list -- the on-demand, no-interrupt behaviour a
+ * software trigger would give -- which is what the ANIOC_TRIGGER handler
+ * does.  Everything runs in task context; the ADC_IRQ line is unused.
+ *
+ * The chip-specific wiring (channel count, list depth, pad-mux code, clock
+ * masks) lives in the per-chip ameba_adc_chip.h.  To keep the vendor headers
+ * out of the NuttX include world the few fwlib symbols and constants used
+ * here are declared locally rather than pulled in from <ameba_adc.h>.
+ */
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <debug.h>
+
+#include <nuttx/kmalloc.h>
+#include <nuttx/analog/adc.h>
+#include <nuttx/analog/ioctl.h>
+
+#include "ameba_adc.h"
+#include "ameba_adc_chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Second/third argument to fwlib "state" style APIs. */
+
+#define AMEBA_DISABLE               0x0
+#define AMEBA_ENABLE                0x1
+
+/* fwlib ADC operation mode and pad values (from ameba_adc.h / ameba_gpio.h).
+ * The auto channel-switch mode is the one ADC_ReceiveBuf() drives; the pad
+ * is left un-pulled and its digital input buffer is disabled so the analog
+ * front end sees a clean input.
+ */
+
+#define AMEBA_ADC_AUTO_MODE         0x1  /* ADC_AUTO_MODE       */
+#define AMEBA_ADC_CLKDIV_24         0x3  /* ADC_CLK_DIV_24      */
+#define AMEBA_PAD_NOPULL            0x0  /* GPIO_PuPd_NOPULL    */
+
+/* 16-bit conversion word layout: channel id in [19:16], data in [15:0]. */
+
+#define AMEBA_ADC_GET_CHNO(x)       (((x) >> 16) & 0x0f)
+#define AMEBA_ADC_GET_DATA(x)       ((x) & 0xffff)
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct ameba_adc_dev_s
+{
+  const struct adc_callback_s *cb;   /* Upper-half receive callback */
+  uint8_t   nchan;                   /* Channels in the switch list */
+  bool      enabled;                 /* Converter is powered/running */
+
+  /* Hardware channel number sampled at each list position. */
+
+  uint8_t   chanlist[AMEBA_ADC_MAXLIST];
+
+  /* Analog pad per list position, AMEBA_ADC_PIN_NC for internal channels. */
+
+  uint8_t   pins[AMEBA_ADC_MAXLIST];
+};
+
+/* Layout-compatible mirror of the fwlib ADC_InitTypeDef.  The driver only
+ * ever writes the leading opmode/cvlistlen/cvlist[16] fields (offsets
+ * 0..17), plus -- on amebadplus only -- the ClkDiv byte at offset 18.  That
+ * prefix is identical on every current Ameba chip, so those writes always
+ * land on the right fields.
+ *
+ * Everything past offset 18 is deliberately NOT named here: the tail order
+ * differs per chip (amebadplus is ClkDiv/RxThresholdLevel/SpecialCh/
+ * ChanInType at 28B, amebagreen2 has RxThresholdLevel/SpecialCh/
+ * SamplePeriodUs at 22B, RTL8720F adds an ADC_Chan[8] sub-struct at 52B),
+ * and giving those bytes amebadplus-specific names would only be correct on
+ * one chip and misleading on the others.  The driver never reads or writes
+ * them -- it lets ADC_StructInit() fill the WHOLE per-chip struct with the
+ * correct defaults for whichever fwlib is linked, then overwrites just the
+ * prefix (and ClkDiv).
+ *
+ * The reserved[] tail exists solely so this stack object is at least as
+ * large as the linked chip's real ADC_InitTypeDef -- otherwise
+ * ADC_StructInit(), which writes the whole struct, would overflow the
+ * stack.  Each chip header supplies the real sizeof through
+ * AMEBA_ADC_INIT_SIZE and the static assertion below fails at compile time
+ * if a new port forgets it or under-sizes it.  ClkDiv sits at a fixed
+ * offset 18 on amebadplus, so it stays named; the reserved run starts right
+ * after the named prefix.
+ */
+
+/* Byte span of the named prefix (opmode + cvlistlen + cvlist[16] + clkdiv).
+ * Single source of truth for where the reserved tail begins; the offsetof
+ * assertion below verifies the compiler laid the prefix out with no padding
+ * so this really is the prefix size.
+ */
+
+#define AMEBA_ADC_PREFIX_SIZE  (1 + 1 + 16 + 1)
+
+struct ameba_adc_init_s
+{
+  uint8_t  opmode;                   /* ADC_OpMode                          */
+  uint8_t  cvlistlen;                /* ADC_CvlistLen                       */
+  uint8_t  cvlist[16];               /* ADC_Cvlist[16]                      */
+  uint8_t  clkdiv;                   /* ADC_ClkDiv (amebadplus only, @18)   */
+
+  /* Chip-specific tail (RxThreshold/SpecialCh/... ) -- filled by
+   * ADC_StructInit(), never touched by this driver.  Sized so the whole
+   * mirror is >= the linked chip's ADC_InitTypeDef; ternary keeps it
+   * non-empty when a chip's struct is <= the prefix.
+   */
+
+  uint8_t  reserved[AMEBA_ADC_INIT_SIZE > AMEBA_ADC_PREFIX_SIZE ?
+                    AMEBA_ADC_INIT_SIZE - AMEBA_ADC_PREFIX_SIZE : 1];
+};
+
+static_assert(offsetof(struct ameba_adc_init_s, reserved) ==
+              AMEBA_ADC_PREFIX_SIZE,
+              "ameba_adc_init_s prefix has unexpected padding");
+
+static_assert(sizeof(struct ameba_adc_init_s) >= AMEBA_ADC_INIT_SIZE,
+              "ameba_adc_init_s smaller than the fwlib ADC_InitTypeDef");
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* SDK fwlib ADC / pin / clock API (linked from fwlib RAM ameba_adc.c and the
+ * ROM pinmux table).  None of the ADC calls take a register base -- the
+ * fwlib picks the secure/non-secure alias internally.
+ */
+
+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 PAD_PullCtrl(uint8_t pin, uint8_t pulltype);
+extern void PAD_SleepPullCtrl(uint8_t pin, uint8_t pulltype);
+extern void PAD_InputCtrl(uint8_t pin, uint32_t newstate);
+extern void ADC_StructInit(struct ameba_adc_init_s *init);
+extern void ADC_Init(struct ameba_adc_init_s *init);
+extern void ADC_Cmd(uint32_t newstate);
+extern void ADC_ReceiveBuf(uint32_t *buf, uint32_t len);
+
+/* ADC lower-half operations. */
+
+static int  ameba_adc_bind(struct adc_dev_s *dev,
+                           const struct adc_callback_s *callback);
+static void ameba_adc_reset(struct adc_dev_s *dev);
+static int  ameba_adc_setup(struct adc_dev_s *dev);
+static void ameba_adc_shutdown(struct adc_dev_s *dev);
+static void ameba_adc_rxint(struct adc_dev_s *dev, bool enable);
+static int  ameba_adc_ioctl(struct adc_dev_s *dev, int cmd,
+                            unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct adc_ops_s g_ameba_adc_ops =
+{
+  .ao_bind     = ameba_adc_bind,
+  .ao_reset    = ameba_adc_reset,
+  .ao_setup    = ameba_adc_setup,
+  .ao_shutdown = ameba_adc_shutdown,
+  .ao_rxint    = ameba_adc_rxint,
+  .ao_ioctl    = ameba_adc_ioctl,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_adc_hwinit
+ *
+ * Description:
+ *   Program the converter's channel-switch list for auto channel-switch
+ *   reads.  ADC_Init() itself loads the list from the init struct, so the
+ *   whole board list is applied in one call; the converter is left disabled
+ *   (setup() powers it on).
+ *
+ ****************************************************************************/
+
+static void ameba_adc_hwinit(struct ameba_adc_dev_s *priv)
+{
+  struct ameba_adc_init_s init;
+  int i;
+
+  memset(&init, 0, sizeof(init));
+  ADC_StructInit(&init);
+
+  init.opmode    = AMEBA_ADC_AUTO_MODE;
+  init.cvlistlen = (uint8_t)(priv->nchan - 1);
+
+  /* ClkDiv exists at offset 18 only on amebadplus; on other chips that
+   * byte is RxThresholdLevel, so writing it would corrupt an unrelated
+   * field.  Guard with the chip macro and keep the SDK default elsewhere.
+   */
+
+#ifdef AMEBA_ADC_HAS_CLKDIV
+  init.clkdiv    = AMEBA_ADC_CLKDIV_24;
+#endif
+
+  for (i = 0; i < priv->nchan; i++)
+    {
+      init.cvlist[i] = priv->chanlist[i];
+    }
+
+  ADC_Init(&init);
+}
+
+/****************************************************************************
+ * Name: ameba_adc_bind
+ ****************************************************************************/
+
+static int ameba_adc_bind(struct adc_dev_s *dev,
+                          const struct adc_callback_s *callback)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+
+  priv->cb = callback;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_adc_reset
+ *
+ * Description:
+ *   Reload the channel-switch list; called before setup() and on error
+ *   recovery.  The peripheral clock and pads are latched at registration.
+ *
+ ****************************************************************************/
+
+static void ameba_adc_reset(struct adc_dev_s *dev)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+
+  ameba_adc_hwinit(priv);
+}
+
+/****************************************************************************
+ * Name: ameba_adc_setup
+ *
+ * Description:
+ *   Power the converter on when the device is first opened.
+ *
+ ****************************************************************************/
+
+static int ameba_adc_setup(struct adc_dev_s *dev)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+
+  if (!priv->enabled)
+    {
+      ADC_Cmd(AMEBA_ENABLE);
+      priv->enabled = true;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_adc_shutdown
+ *
+ * Description:
+ *   Power the converter off when the device is closed.
+ *
+ ****************************************************************************/
+
+static void ameba_adc_shutdown(struct adc_dev_s *dev)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+
+  if (priv->enabled)
+    {
+      ADC_Cmd(AMEBA_DISABLE);
+      priv->enabled = false;
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_adc_rxint
+ *
+ * Description:
+ *   No-op: this driver reads by polling the auto channel-switch FIFO on
+ *   demand, so there are no RX interrupts to enable or disable.
+ *
+ ****************************************************************************/
+
+static void ameba_adc_rxint(struct adc_dev_s *dev, bool enable)
+{
+  UNUSED(dev);
+  UNUSED(enable);
+}
+
+/****************************************************************************
+ * Name: ameba_adc_trigger
+ *
+ * Description:
+ *   Sample the whole channel list once and push each raw value to the upper
+ *   half.  ADC_ReceiveBuf() reads one word per listed channel, in list
+ *   order, through the auto channel-switch FIFO; the channel number is taken
+ *   from each word so the sample is tagged with the channel it came from.
+ *
+ ****************************************************************************/
+
+static int ameba_adc_trigger(struct adc_dev_s *dev)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+  uint32_t buf[AMEBA_ADC_MAXLIST];
+  int i;
+
+  if (priv->cb == NULL || priv->cb->au_receive == NULL)
+    {
+      return -EINVAL;
+    }
+
+  ADC_ReceiveBuf(buf, priv->nchan);
+
+  for (i = 0; i < priv->nchan; i++)
+    {
+      uint8_t  ch   = (uint8_t)AMEBA_ADC_GET_CHNO(buf[i]);
+      int32_t  data = (int32_t)AMEBA_ADC_GET_DATA(buf[i]);
+
+      priv->cb->au_receive(dev, ch, data);
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_adc_ioctl
+ ****************************************************************************/
+
+static int ameba_adc_ioctl(struct adc_dev_s *dev, int cmd,
+                           unsigned long arg)
+{
+  struct ameba_adc_dev_s *priv = (struct ameba_adc_dev_s *)dev->ad_priv;
+  int ret;
+
+  switch (cmd)
+    {
+      case ANIOC_TRIGGER:
+        ret = ameba_adc_trigger(dev);
+        break;
+
+      case ANIOC_GET_NCHANNELS:
+        ret = priv->nchan;
+        break;
+
+      default:
+        ret = -ENOTTY;
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_adc_register
+ *
+ * Description:
+ *   See ameba_adc.h.
+ *
+ ****************************************************************************/
+
+int ameba_adc_register(const char *path, const uint8_t *channels,
+                       const uint8_t *pins, unsigned int nchan)
+{
+  struct ameba_adc_dev_s *priv;
+  struct adc_dev_s *dev;
+  unsigned int i;
+  int ret;
+
+  if (nchan == 0)
+    {
+      return -EINVAL;
+    }
+
+  if (nchan > AMEBA_ADC_MAXLIST)
+    {
+      nchan = AMEBA_ADC_MAXLIST;
+    }
+
+  dev = kmm_zalloc(sizeof(struct adc_dev_s) +
+                   sizeof(struct ameba_adc_dev_s));
+  if (dev == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  priv = (struct ameba_adc_dev_s *)(dev + 1);
+  dev->ad_ops  = &g_ameba_adc_ops;
+  dev->ad_priv = priv;
+
+  priv->nchan = (uint8_t)nchan;
+  memset(priv->pins, AMEBA_ADC_PIN_NC, sizeof(priv->pins));
+
+  /* Gate the ADC peripheral clock (plus the cap-touch/CTC clock domain on
+   * the chips whose header defines it) and turn each external channel's pad
+   * into an analog input: route it to the ADC crossbar function, drop its
+   * pulls and disable its digital input buffer.  Internal channels have no
+   * pad and are skipped.
+   */
+
+  RCC_PeriphClockCmd(AMEBA_ADC_APBPERIPH, AMEBA_ADC_APBPERIPH_CLK,
+                     AMEBA_ENABLE);
+#ifdef AMEBA_ADC_AUXCLK_PERIPH
+  RCC_PeriphClockCmd(AMEBA_ADC_AUXCLK_PERIPH, AMEBA_ADC_AUXCLK_CLK,
+                     AMEBA_ENABLE);
+#endif
+
+  for (i = 0; i < nchan; i++)
+    {
+      priv->chanlist[i] = channels[i];
+      priv->pins[i]     = pins[i];
+
+      if (pins[i] != AMEBA_ADC_PIN_NC)
+        {
+          Pinmux_Config(pins[i], AMEBA_ADC_PINMUX_FID);
+          PAD_PullCtrl(pins[i], AMEBA_PAD_NOPULL);
+          PAD_SleepPullCtrl(pins[i], AMEBA_PAD_NOPULL);
+          PAD_InputCtrl(pins[i], AMEBA_DISABLE);
+        }
+    }
+
+  ameba_adc_hwinit(priv);
+
+  ret = adc_register(path, dev);
+  if (ret < 0)
+    {
+      aerr("ERROR: adc_register(%s) failed: %d\n", path, ret);
+      kmm_free(dev);
+      return ret;
+    }
+
+  return OK;
+}
diff --git a/arch/arm/src/common/ameba/ameba_adc.h 
b/arch/arm/src/common/ameba/ameba_adc.h
new file mode 100644
index 00000000000..04352a13f5d
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_adc.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_adc.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_ADC_H
+#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_ADC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The Ameba ADC is a single converter with one channel-switch list: the
+ * board hands ameba_adc_register() a set of channels to sample and, for each
+ * external channel, the analog pad it is wired to.  A single /dev/adc0
+ * device then samples the whole list on every ANIOC_TRIGGER (the SDK does
+ * not enable the hardware software-trigger path on this chip, so the driver
+ * reads one sweep of the list through the auto channel-switch FIFO -- see
+ * ameba_adc.c).
+ *
+ * Channels the board wires to an external pad give that pad here (encoded
+ * with the AMEBA_PA()/AMEBA_PB() PinName codes the GPIO driver uses); the
+ * chip's internal channels (temperature, VBAT, ...) have no pad and carry
+ * AMEBA_ADC_PIN_NC.
+ */
+
+/* Sentinel pad code for an internal ADC channel that has no analog pad. */
+
+#define AMEBA_ADC_PIN_NC      0xff
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ameba_adc_register
+ *
+ * Description:
+ *   Configure the Ameba ADC for a list of channels and register it with the
+ *   NuttX ADC character driver at the given path (typically "/dev/adc0").
+ *   Every channel in the list is sampled, in list order, on each
+ *   ANIOC_TRIGGER; the raw conversion value of each is delivered to the
+ *   upper half tagged with the hardware channel number.
+ *
+ * Input Parameters:
+ *   path     - The device path to register, e.g. "/dev/adc0".
+ *   channels - Array of hardware ADC channel numbers to sample.
+ *   pins     - Array giving, for each channel, the analog pad it is wired to
+ *              (encoded with AMEBA_PA()/AMEBA_PB()), or AMEBA_ADC_PIN_NC for
+ *              an internal channel that has no pad.
+ *   nchan    - Number of entries in channels/pins (clamped to the
+ *              converter's channel-list length).
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int ameba_adc_register(const char *path, const uint8_t *channels,
+                       const uint8_t *pins, unsigned int nchan);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_ADC_H */
diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt 
b/arch/arm/src/rtl8721dx/CMakeLists.txt
index d480f2b1074..71b7f7203a9 100644
--- a/arch/arm/src/rtl8721dx/CMakeLists.txt
+++ b/arch/arm/src/rtl8721dx/CMakeLists.txt
@@ -60,6 +60,10 @@ if(CONFIG_AMEBA_PWM)
   list(APPEND SRCS ${AMEBA_COMMON}/ameba_pwm.c)
 endif()
 
+if(CONFIG_AMEBA_ADC)
+  list(APPEND SRCS ${AMEBA_COMMON}/ameba_adc.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 64adf3980e4..60ebeface1d 100644
--- a/arch/arm/src/rtl8721dx/Make.defs
+++ b/arch/arm/src/rtl8721dx/Make.defs
@@ -70,6 +70,10 @@ ifeq ($(CONFIG_AMEBA_PWM),y)
 CHIP_CSRCS += ameba_pwm.c
 endif
 
+ifeq ($(CONFIG_AMEBA_ADC),y)
+CHIP_CSRCS += ameba_adc.c
+endif
+
 ############################################################################
 # Realtek RTL8721Dx SDK integration
 #
diff --git a/arch/arm/src/rtl8721dx/ameba_adc_chip.h 
b/arch/arm/src/rtl8721dx/ameba_adc_chip.h
new file mode 100644
index 00000000000..47a456f0d64
--- /dev/null
+++ b/arch/arm/src/rtl8721dx/ameba_adc_chip.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+ * arch/arm/src/rtl8721dx/ameba_adc_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_ADC_CHIP_H
+#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_ADC_CHIP_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Per-chip ADC wiring for RTL8721DX (amebadplus).  The shared driver
+ * (arch/arm/src/common/ameba/ameba_adc.c) includes this header to learn how
+ * many channels the converter can list, the crossbar pad-mux code that turns
+ * a pad into an analog input, and the peripheral-clock masks.  A port to
+ * another Ameba chip supplies a same-named header on the chip include path;
+ * the shared driver is never edited -- it reads only the macros below, and
+ * it drives the ADC through the SDK fwlib API, which internally selects the
+ * secure/non-secure register alias (via TrustZone_IsSecure()), so no
+ * register base appears here at all.
+ *
+ * What differs per chip, from the SDK fwlib headers and the vendor
+ * example_adc_ext.h (verified, not guessed):
+ *
+ *   chip         pad-mux code for analog in       extra clock to gate
+ *   -----------  ------------------------------   -------------------
+ *   amebadplus   PINMUX_FUNCTION_ADC       = 6    (none)
+ *   amebalite    PINMUX_FUNCTION_AUXIN            APBPeriph_CTC
+ *   amebasmart   PINMUX_FUNCTION_CAPTOUCH         APBPeriph_CTC
+ *   amebagreen2  PINMUX_FUNCTION_ADC             (none)
+ *   RTL8720F     PINMUX_FUNCTION_ADC             (none)
+ *
+ * Both differences are expressed as macros so a new chip only edits this
+ * header: AMEBA_ADC_PINMUX_FID carries the pad-mux code, and a chip that
+ * needs a second clock domain (amebalite/amebasmart route the ADC through
+ * the cap-touch/CTC block) also defines AMEBA_ADC_AUXCLK_PERIPH / _CLK,
+ * which the driver gates only when present.  amebadplus needs neither, so
+ * it leaves those two undefined -- see the SPI/I2C/PWM chip headers for the
+ * same data-driven, never-computed pattern.
+ *
+ * External channels CH0..CH6 map to pads PB19,PB18,PB17,PB16,PB15,PB14,PB13
+ * (from ameba_adc.h ADC_CHx_PIN); CH7..CH10 are fixed internal channels with
+ * no pad.  The 16-bit conversion word (channel id in [19:16], data in
+ * [15:0]) is identical on every current Ameba chip, so the driver extracts
+ * it directly and no macro is needed here.
+ */
+
+#define AMEBA_ADC_NCHAN           11    /* ADC_CH_NUM: CH0..CH6 ext, 7..10 int 
*/
+#define AMEBA_ADC_MAXLIST         16    /* Channel-switch list depth (Cvlist)  
*/
+#define AMEBA_ADC_PINMUX_FID      6     /* PINMUX_FUNCTION_ADC                 
 */
+#define AMEBA_ADC_IRQ             47    /* ADC_IRQ (unused by this polling 
drv) */
+
+/* sizeof(fwlib ADC_InitTypeDef): OpMode/CvlistLen/Cvlist[16] then ClkDiv/
+ * RxThresholdLevel/SpecialCh/ChanInType(u32) -> 28 bytes.  The shared driver
+ * sizes its stack mirror from this so ADC_StructInit() cannot overflow it.
+ */
+
+#define AMEBA_ADC_INIT_SIZE       28
+
+/* APBPeriph_ADC (function) and APBPeriph_ADC_CLOCK masks.  Equal on this
+ * chip; both set the bit30 group selector and bit23 for the LP ADC block.
+ * amebadplus needs no second clock domain, so AMEBA_ADC_AUXCLK_* are left
+ * undefined (amebalite/amebasmart define them to gate APBPeriph_CTC too).
+ */
+
+#define AMEBA_ADC_APBPERIPH       (((uint32_t)1 << 30) | ((uint32_t)1 << 23))
+#define AMEBA_ADC_APBPERIPH_CLK   (((uint32_t)1 << 30) | ((uint32_t)1 << 23))
+
+/* amebadplus ADC_InitTypeDef has a ClkDiv field at offset 18; the other
+ * Ameba chips (amebagreen2, RTL8720F) do not -- their offset 18 is
+ * RxThresholdLevel.  The shared driver guards the ClkDiv write with this
+ * macro so it only runs on chips that actually have the field.
+ */
+
+#define AMEBA_ADC_HAS_CLKDIV      1
+
+#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_ADC_CHIP_H */
diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/adc/defconfig 
b/boards/arm/rtl8721dx/pke8721daf/configs/adc/defconfig
new file mode 100644
index 00000000000..18b175ba638
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/configs/adc/defconfig
@@ -0,0 +1,54 @@
+#
+# 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_ADC=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_ADC=y
+CONFIG_EXAMPLES_ADC_GROUPSIZE=2
+CONFIG_EXAMPLES_ADC_NSAMPLES=1
+CONFIG_EXAMPLES_ADC_SWTRIG=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 b4722787e6c..44926ad4edb 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
+++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
@@ -42,13 +42,18 @@ if(CONFIG_AMEBA_PWM)
   list(APPEND SRCS rtl8721dx_pwm.c)
 endif()
 
+if(CONFIG_AMEBA_ADC)
+  list(APPEND SRCS rtl8721dx_adc.c)
+endif()
+
 target_sources(board PRIVATE ${SRCS})
 
 if(CONFIG_AMEBA_GPIO
    OR CONFIG_AMEBA_UART
    OR CONFIG_AMEBA_I2C
    OR CONFIG_AMEBA_SPI
-   OR CONFIG_AMEBA_PWM)
+   OR CONFIG_AMEBA_PWM
+   OR CONFIG_AMEBA_ADC)
   # 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 158e7fa6446..c841ac4da3d 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile
+++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
@@ -69,4 +69,13 @@ CSRCS += rtl8721dx_pwm.c
 CFLAGS += 
${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
 endif
 
+ifeq ($(CONFIG_AMEBA_ADC),y)
+CSRCS += rtl8721dx_adc.c
+
+# The board ADC 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_adc.c 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.c
new file mode 100644
index 00000000000..81b3e0ddfd1
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.c
@@ -0,0 +1,90 @@
+/****************************************************************************
+ * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.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_adc.h"
+#include "rtl8721dx_pke8721daf.h"
+
+#ifdef CONFIG_AMEBA_ADC
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Channels sampled on /dev/adc0 and the analog pad each is wired to.  The
+ * external channels CH0..CH6 map to pads PB19..PB13; this board exposes CH0
+ * on PB19 and CH1 on PB18.  Any external channel can be listed here;
+ * internal channels (CH7..CH10: temperature, VBAT) would carry
+ * AMEBA_ADC_PIN_NC.
+ */
+
+static const uint8_t g_adc_channels[] =
+{
+  0,                 /* ADC_CH0 */
+  1,                 /* ADC_CH1 */
+};
+
+static const uint8_t g_adc_pins[] =
+{
+  AMEBA_PB(19),      /* CH0 -> PB19 */
+  AMEBA_PB(18),      /* CH1 -> PB18 */
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rtl8721dx_adc_initialize
+ *
+ * Description:
+ *   Register the board's ADC channels at /dev/adc0.
+ *
+ ****************************************************************************/
+
+int rtl8721dx_adc_initialize(void)
+{
+  int ret;
+
+  ret = ameba_adc_register("/dev/adc0", g_adc_channels, g_adc_pins,
+                           nitems(g_adc_channels));
+  if (ret < 0)
+    {
+      syslog(LOG_ERR,
+             "ERROR: ameba_adc_register(/dev/adc0) failed: %d\n", ret);
+      return ret;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_AMEBA_ADC */
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
index bc4f3847d39..69a45c44ccc 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
@@ -158,6 +158,16 @@ int rtl8721dx_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_AMEBA_ADC
+  /* Register the board's ADC channels at /dev/adc0. */
+
+  ret = rtl8721dx_adc_initialize();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: rtl8721dx_adc_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 f712e50da38..f185ef3ab9a 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
@@ -134,6 +134,19 @@ int rtl8721dx_spi_initialize(void);
 int rtl8721dx_pwm_initialize(void);
 #endif
 
+#ifdef CONFIG_AMEBA_ADC
+/****************************************************************************
+ * Name: rtl8721dx_adc_initialize
+ *
+ * Description:
+ *   Register the board's ADC channels at /dev/adc0
+ *   (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_adc.c).
+ *
+ ****************************************************************************/
+
+int rtl8721dx_adc_initialize(void);
+#endif
+
 #ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
  * Name: ameba_flash_fs_initialize

Reply via email to