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 6f2e17c113a17c1e26f4399edbe4ed5aaf18989a
Author: dechao_gong <[email protected]>
AuthorDate: Mon Jul 20 15:11:08 2026 +0800

    arch/arm/rtl8721dx: add shared Ameba PWM driver
    
    Add a shared NuttX PWM lower-half for the Realtek Ameba PWM timer in
    arch/arm/src/common/ameba, driven through the SDK fwlib.  TIM8 provides a
    single time base feeding eight compare channels (CCR0..CCR7) that share one
    frequency while each carries its own duty, so a single /dev/pwm0 exposes the
    multichannel output via CONFIG_PWM_NCHANNELS.  The fwlib PWM routines are
    split ROM/RAM: the time-base calls resolve from on-chip ROM, while the
    capture/compare calls live in fwlib ram_common/ameba_tim.c, which the build
    pulls into AMEBA_FWLIB_SRCS when CONFIG_AMEBA_PWM is set.
    
    Per-chip wiring (timer index, channel count, register base, input clock,
    IRQ, clock masks and the crossbar pad-mux code table) lives in
    arch/arm/src/rtl8721dx/ameba_pwm_chip.h so a port to another Ameba chip only
    supplies a same-named header; the pad-mux codes are a per-channel table
    (AMEBA_PWM_PINMUX_FIDS) rather than a computed base, so chips with a single
    shared code or codes grouped per timer are expressed by the header alone.
    
    The timer registers as /dev/pwm0 from pke8721daf bring-up through the stock
    PWM character driver; a dedicated `pwm` defconfig drives examples/pwm for
    validation.
    
    Signed-off-by: dechao_gong <[email protected]>
    Assisted-by: Claude <[email protected]>
---
 .../arm/rtl8721dx/boards/pke8721daf/index.rst      |  19 +
 arch/arm/src/common/ameba/Kconfig                  |  15 +
 arch/arm/src/common/ameba/ameba_pwm.c              | 558 +++++++++++++++++++++
 arch/arm/src/common/ameba/ameba_pwm.h              |  95 ++++
 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_pwm_chip.h            |  97 ++++
 .../arm/rtl8721dx/pke8721daf/configs/pwm/defconfig |  52 ++
 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_pwm.c   |  89 ++++
 tools/nxstyle.c                                    |   1 +
 15 files changed, 981 insertions(+), 1 deletion(-)

diff --git a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst 
b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
index d33d9fafc41..14554d6ddae 100644
--- a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
+++ b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
@@ -41,6 +41,8 @@ Supported in this NuttX port:
   on the SDK fwlib register layer
 * SPI master buses exposed as ``/dev/spiN`` character devices, driven directly
   on the SDK fwlib register layer
+* PWM output exposed as a ``/dev/pwm0`` character device, driven directly on
+  the SDK fwlib timer register layer
 
 Buttons and LEDs
 ================
@@ -134,6 +136,23 @@ tool::
 
     nsh> spi exch -b 1 -x 4 deadbeef     # full-duplex transfer on /dev/spi1
 
+pwm
+---
+
+Minimal NSH with the PWM driver and the ``pwm`` example
+(``examples/pwm``) enabled (no Wi-Fi). The board registers one timer at
+``/dev/pwm0`` (see ``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.c``):
+TIM8 drives up to eight compare channels off one shared time base, so every
+channel shares one frequency and each carries its own duty cycle. The example
+table routes channel 1 to PB18 and channel 2 to PB19; edit it -- one pad per
+channel, ``AMEBA_PWM_PIN_NC`` for the unused ones -- 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 PWM function through the crossbar. Set
+``CONFIG_PWM_NCHANNELS`` to the number of channels used. Exercise it with the
+example::
+
+    nsh> pwm -d 25 -f 1000     # 1 kHz, 25% duty on /dev/pwm0
+
 Wi-Fi
 =====
 
diff --git a/arch/arm/src/common/ameba/Kconfig 
b/arch/arm/src/common/ameba/Kconfig
index 46285e3bc61..217d4d2232e 100644
--- a/arch/arm/src/common/ameba/Kconfig
+++ b/arch/arm/src/common/ameba/Kconfig
@@ -85,4 +85,19 @@ config AMEBA_SPI
                fwlib register layer and drives the DesignWare SSI block in 
polling
                mode.
 
+config AMEBA_PWM
+       bool "PWM"
+       default n
+       select PWM
+       ---help---
+               Expose the Ameba PWM timer as a NuttX PWM output at /dev/pwm0.  
The
+               chip has a single PWM-capable timer whose one time base feeds 
several
+               compare channels, so every channel shares one frequency and each
+               carries its own duty cycle (set CONFIG_PWM_NCHANNELS to the 
number of
+               channels used).  The board selects which channel is routed to 
which
+               pad in its bring-up code.
+
+               The driver (arch/arm/src/common/ameba/ameba_pwm.c) sits on the 
SDK
+               fwlib RTIM register layer and runs in polling task context.
+
 endmenu # Ameba Peripheral Support
diff --git a/arch/arm/src/common/ameba/ameba_pwm.c 
b/arch/arm/src/common/ameba/ameba_pwm.c
new file mode 100644
index 00000000000..a54f8cdbb87
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_pwm.c
@@ -0,0 +1,558 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_pwm.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 PWM lower half for the Realtek Ameba PWM timer.  The chip has a
+ * single PWM-capable timer (TIM8 on amebadplus) whose one time base feeds
+ * several compare channels (CCR0..CCR7).  Because the time base is shared,
+ * every channel runs at the same frequency and each carries its own duty
+ * cycle; the driver therefore exposes one /dev/pwm0 device that programs the
+ * frequency once and walks info->channels[] for the per-channel duties (the
+ * CONFIG_PWM_NCHANNELS multi-channel form of the upper half).
+ *
+ * The timer is driven through the SDK fwlib RTIM API.  The time-base setup
+ * (RTIM_TimeBaseStructInit/RTIM_TimeBaseInit/RTIM_Cmd) resolves to the
+ * on-chip ROM symbol table (those are _LONG_CALL_ entries), while the
+ * capture/compare and period helpers (RTIM_CCStructInit/RTIM_CCxInit/
+ * RTIM_CCRxSet/RTIM_CCxCmd/RTIM_ChangePeriod/RTIM_PrescalerConfig) are
+ * compiled from the fwlib RAM source ameba_tim.c and linked in.  Everything
+ * runs in task context (RTIM_CCxInit busy-waits on the update-generated
+ * flag), never from an ISR.
+ *
+ * The frequency divider chain is f_out = CLKFREQ / ((PSC + 1) * (ARR + 1))
+ * with 16-bit PSC and ARR; the driver picks the smallest prescaler that lets
+ * the auto-reload value fit 16 bits, maximising duty resolution.  The duty
+ * is the upper half's ub16_t fraction: CCR = duty * (ARR + 1) with the SDK
+ * convention CCR = 0 -> 0 %, CCR >= ARR -> 100 %.
+ *
+ * The chip-specific wiring (timer index, channel count, register base, input
+ * clock, clock masks and pad-mux base) lives in the per-chip
+ * ameba_pwm_chip.h.  To keep the vendor headers out of the NuttX include
+ * world, the few fwlib symbols and the two RTIM init-struct layouts used
+ * here are declared locally rather than pulled in from <ameba_pwmtimer.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/timers/pwm.h>
+
+#include "ameba_pwm.h"
+#include "ameba_pwm_chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The timer index, channel count, register base, input clock, peripheral-
+ * clock masks, IRQ and pad-mux base come from the per-chip ameba_pwm_chip.h.
+ * Everything below is common to every current Ameba chip.
+ */
+
+/* Second/third argument to fwlib "state" style APIs. */
+
+#define AMEBA_DISABLE               0x0
+#define AMEBA_ENABLE                0x1
+
+/* fwlib RTIM capture/compare field values (from ameba_pwmtimer.h). */
+
+#define AMEBA_TIM_CCMODE_PWM        0x00000000  /* TIM_CCMode_PWM          */
+#define AMEBA_TIM_CCPOL_HIGH        0x00000000  /* TIM_CCPolarity_High     */
+#define AMEBA_TIM_CCPOL_LOW         0x04000000  /* TIM_CCPolarity_Low      */
+#define AMEBA_TIM_OCPRELOAD_ENABLE  0x02000000  /* TIM_OCPreload_Enable    */
+#define AMEBA_TIM_CCX_ENABLE        0x01000000  /* TIM_CCx_Enable          */
+#define AMEBA_TIM_CCX_DISABLE       0x00000000  /* TIM_CCx_Disable         */
+#define AMEBA_TIM_PSCRELOAD_UPDATE  0x00000000  /* TIM_PSCReloadMode_Update */
+
+/* PSC and ARR are 16-bit on the PWM timer. */
+
+#define AMEBA_TIM_MAX16             0xffff
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Layout-compatible mirror of the fwlib RTIM_TimeBaseInitTypeDef (same field
+ * order and types); passed by address to RTIM_TimeBaseStructInit()/
+ * RTIM_TimeBaseInit().
+ */
+
+struct ameba_tim_timebase_s
+{
+  uint32_t prescaler;          /* TIM_Prescaler    */
+  uint32_t period;             /* TIM_Period       */
+  uint32_t updateevent;        /* TIM_UpdateEvent  */
+  uint32_t updatesource;       /* TIM_UpdateSource */
+  uint32_t arrprotection;      /* TIM_ARRProtection */
+  uint8_t  idx;                /* TIM_Idx          */
+  uint32_t securetimer;        /* TIM_SecureTimer  */
+};
+
+/* Layout-compatible mirror of the fwlib TIM_CCInitTypeDef (same field
+ * order); passed by address to RTIM_CCStructInit()/RTIM_CCxInit().
+ */
+
+struct ameba_tim_cc_s
+{
+  uint32_t ccmode;             /* TIM_CCMode       */
+  uint32_t ccpolarity;         /* TIM_CCPolarity   */
+  uint32_t ocprotection;       /* TIM_OCProtection */
+  uint32_t ocpulse;            /* TIM_OCPulse      */
+  uint32_t icpulsemode;        /* TIM_ICPulseMode  */
+};
+
+struct ameba_pwm_dev_s
+{
+  const struct pwm_ops_s *ops; /* PWM lower half (must be first) */
+  uintptr_t base;              /* Non-secure PWM timer register base */
+  uint32_t  periph;            /* APBPeriph function mask (RCC arg 1) */
+  uint32_t  clk;               /* APBPeriph clock mask (RCC arg 2) */
+  uint32_t  clkfreq;           /* Timer input clock (Hz) */
+  uint32_t  frequency;         /* Currently programmed frequency (Hz) */
+  uint32_t  arr;               /* Currently programmed auto-reload value */
+  uint32_t  psc;               /* Currently programmed prescaler */
+  uint8_t   idx;               /* Timer index (TIM_Idx) */
+  uint8_t   nchan;             /* Wired hardware channel count */
+  uint8_t   chanen;            /* Bitmask of channels enabled since start */
+  bool      running;           /* Time base is running */
+
+  /* Output pad per hardware channel, AMEBA_PWM_PIN_NC when unused. */
+
+  uint8_t   pins[AMEBA_PWM_NCHAN];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* SDK fwlib RTIM/pin/clock API (ROM for the time base, RAM ameba_tim.c for
+ * the compare/period helpers).  The RTIM_TypeDef pointer is passed as the
+ * raw register base cast to void *.
+ */
+
+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 RTIM_TimeBaseStructInit(struct ameba_tim_timebase_s *init);
+extern void RTIM_TimeBaseInit(void *timx,
+                              struct ameba_tim_timebase_s *init,
+                              int irqnum, void *usercb, uint32_t cbdata);
+extern void RTIM_PrescalerConfig(void *timx, uint32_t prescaler,
+                                 uint32_t reloadmode);
+extern void RTIM_ChangePeriod(void *timx, uint32_t autoreload);
+extern void RTIM_Cmd(void *timx, uint32_t newstate);
+extern void RTIM_CCStructInit(struct ameba_tim_cc_s *init);
+extern void RTIM_CCxInit(void *timx, struct ameba_tim_cc_s *init,
+                         uint16_t channel);
+extern void RTIM_CCRxSet(void *timx, uint32_t compare, uint16_t channel);
+extern void RTIM_CCxCmd(void *timx, uint16_t channel, uint32_t state);
+
+/* PWM lower-half operations. */
+
+static int ameba_pwm_setup(struct pwm_lowerhalf_s *dev);
+static int ameba_pwm_shutdown(struct pwm_lowerhalf_s *dev);
+static int ameba_pwm_start(struct pwm_lowerhalf_s *dev,
+                           const struct pwm_info_s *info);
+static int ameba_pwm_stop(struct pwm_lowerhalf_s *dev);
+static int ameba_pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd,
+                           unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct pwm_ops_s g_ameba_pwm_ops =
+{
+  .setup    = ameba_pwm_setup,
+  .shutdown = ameba_pwm_shutdown,
+  .start    = ameba_pwm_start,
+  .stop     = ameba_pwm_stop,
+  .ioctl    = ameba_pwm_ioctl,
+};
+
+/* Crossbar pad-mux function code per channel, from the chip header.  Indexed
+ * by channel number minus one; the driver only indexes it, never computes a
+ * code, so a chip with non-contiguous codes is handled by its header alone.
+ */
+
+static const uint8_t g_pwm_fids[AMEBA_PWM_NCHAN] = AMEBA_PWM_PINMUX_FIDS;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_pwm_timebase
+ *
+ * Description:
+ *   Pick the smallest prescaler whose auto-reload value fits 16 bits for the
+ *   requested frequency, so f_out = clkfreq / ((PSC + 1) * (ARR + 1)) is as
+ *   close as possible while keeping the most duty resolution.
+ *
+ ****************************************************************************/
+
+static void ameba_pwm_timebase(uint32_t clkfreq, uint32_t frequency,
+                               uint32_t *psc, uint32_t *arr)
+{
+  uint32_t ticks;
+  uint32_t p;
+  uint32_t a;
+
+  /* ticks = (PSC + 1) * (ARR + 1), the total divider needed. */
+
+  ticks = clkfreq / frequency;
+  if (ticks == 0)
+    {
+      ticks = 1;
+    }
+
+  /* Smallest prescaler that brings ARR within the 16-bit range. */
+
+  p = (ticks - 1) >> 16;
+  if (p > AMEBA_TIM_MAX16)
+    {
+      p = AMEBA_TIM_MAX16;
+    }
+
+  a = ticks / (p + 1);
+  if (a == 0)
+    {
+      a = 1;
+    }
+
+  a -= 1;
+  if (a > AMEBA_TIM_MAX16)
+    {
+      a = AMEBA_TIM_MAX16;
+    }
+
+  *psc = p;
+  *arr = a;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_duty2ccr
+ *
+ * Description:
+ *   Convert the upper half's ub16_t duty fraction to a compare value:
+ *   CCR = duty * (ARR + 1), clamped so CCR = 0 gives 0 % and CCR = ARR gives
+ *   100 % (the SDK treats CCR >= period as full scale).
+ *
+ ****************************************************************************/
+
+static uint32_t ameba_pwm_duty2ccr(ub16_t duty, uint32_t arr)
+{
+  uint32_t ccr = ((uint64_t)duty * (arr + 1)) >> 16;
+
+  if (ccr > arr)
+    {
+      ccr = arr;
+    }
+
+  return ccr;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_setperiod
+ *
+ * Description:
+ *   Program the shared time base for the requested frequency, creating it on
+ *   the first start and hot-updating the prescaler/period afterwards.
+ *
+ ****************************************************************************/
+
+static void ameba_pwm_setperiod(struct ameba_pwm_dev_s *priv,
+                                uint32_t frequency)
+{
+  struct ameba_tim_timebase_s tb;
+  uint32_t psc;
+  uint32_t arr;
+
+  ameba_pwm_timebase(priv->clkfreq, frequency, &psc, &arr);
+
+  if (!priv->running)
+    {
+      memset(&tb, 0, sizeof(tb));
+      RTIM_TimeBaseStructInit(&tb);
+
+      tb.prescaler     = psc;
+      tb.period        = arr;
+      tb.idx           = priv->idx;
+      tb.arrprotection = AMEBA_ENABLE;
+
+      RTIM_TimeBaseInit((void *)priv->base, &tb, AMEBA_PWM_IRQ, NULL,
+                        (uint32_t)(uintptr_t)&tb);
+      RTIM_PrescalerConfig((void *)priv->base, psc,
+                           AMEBA_TIM_PSCRELOAD_UPDATE);
+    }
+  else if (frequency != priv->frequency)
+    {
+      /* Hot update: the auto-reload protection defers both changes to the
+       * next update event so the running output does not glitch.
+       */
+
+      RTIM_PrescalerConfig((void *)priv->base, psc,
+                           AMEBA_TIM_PSCRELOAD_UPDATE);
+      RTIM_ChangePeriod((void *)priv->base, arr);
+    }
+
+  priv->psc       = psc;
+  priv->arr       = arr;
+  priv->frequency = frequency;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_setchannel
+ *
+ * Description:
+ *   Program one compare channel for its duty cycle.  A channel that has not
+ *   run since the last stop is fully initialised (PWM mode, active-high,
+ *   preload) and enabled; an already-running channel just has its compare
+ *   value updated (deferred to the next update event by the preload).
+ *
+ ****************************************************************************/
+
+static void ameba_pwm_setchannel(struct ameba_pwm_dev_s *priv,
+                                 uint8_t hwch, uint32_t ccr)
+{
+  struct ameba_tim_cc_s cc;
+
+  if ((priv->chanen & (1 << hwch)) == 0)
+    {
+      memset(&cc, 0, sizeof(cc));
+      RTIM_CCStructInit(&cc);
+
+      cc.ccmode       = AMEBA_TIM_CCMODE_PWM;
+      cc.ccpolarity   = AMEBA_TIM_CCPOL_HIGH;
+      cc.ocprotection = AMEBA_TIM_OCPRELOAD_ENABLE;
+      cc.ocpulse      = ccr;
+
+      RTIM_CCxInit((void *)priv->base, &cc, hwch);
+      RTIM_CCxCmd((void *)priv->base, hwch, AMEBA_TIM_CCX_ENABLE);
+      priv->chanen |= (1 << hwch);
+    }
+  else
+    {
+      RTIM_CCRxSet((void *)priv->base, ccr, hwch);
+    }
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_setup
+ ****************************************************************************/
+
+static int ameba_pwm_setup(struct pwm_lowerhalf_s *dev)
+{
+  struct ameba_pwm_dev_s *priv = (struct ameba_pwm_dev_s *)dev;
+
+  /* The peripheral clock and pad mux are latched at registration; nothing to
+   * do until the first start().
+   */
+
+  UNUSED(priv);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_shutdown
+ ****************************************************************************/
+
+static int ameba_pwm_shutdown(struct pwm_lowerhalf_s *dev)
+{
+  return ameba_pwm_stop(dev);
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_start
+ *
+ * Description:
+ *   Program the shared frequency and every requested channel's duty, then
+ *   run the time base.  info->channels[] carries 1-based channel numbers;
+ *   entries that are out of range or map to an unwired pad are skipped.
+ *
+ ****************************************************************************/
+
+static int ameba_pwm_start(struct pwm_lowerhalf_s *dev,
+                           const struct pwm_info_s *info)
+{
+  struct ameba_pwm_dev_s *priv = (struct ameba_pwm_dev_s *)dev;
+  int i;
+
+  if (info->frequency == 0 || info->frequency > priv->clkfreq)
+    {
+      pwmerr("ERROR: frequency %" PRIu32 " out of range\n",
+             info->frequency);
+      return -ERANGE;
+    }
+
+  ameba_pwm_setperiod(priv, info->frequency);
+
+  for (i = 0; i < CONFIG_PWM_NCHANNELS; i++)
+    {
+      int8_t   ch = info->channels[i].channel;
+      uint8_t  hwch;
+      uint32_t ccr;
+
+      if (ch < 1 || ch > priv->nchan)
+        {
+          continue;
+        }
+
+      hwch = (uint8_t)(ch - 1);
+      if (priv->pins[hwch] == AMEBA_PWM_PIN_NC)
+        {
+          pwmwarn("WARNING: channel %d has no pad wired\n", ch);
+          continue;
+        }
+
+      ccr = ameba_pwm_duty2ccr(info->channels[i].duty, priv->arr);
+      ameba_pwm_setchannel(priv, hwch, ccr);
+
+      pwminfo("channel %d: duty=%08" PRIx32 " ccr=%" PRIu32 "/%" PRIu32 "\n",
+              ch, (uint32_t)info->channels[i].duty, ccr, priv->arr);
+    }
+
+  if (!priv->running)
+    {
+      RTIM_Cmd((void *)priv->base, AMEBA_ENABLE);
+      priv->running = true;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_stop
+ ****************************************************************************/
+
+static int ameba_pwm_stop(struct pwm_lowerhalf_s *dev)
+{
+  struct ameba_pwm_dev_s *priv = (struct ameba_pwm_dev_s *)dev;
+  int i;
+
+  for (i = 0; i < priv->nchan; i++)
+    {
+      if (priv->chanen & (1 << i))
+        {
+          RTIM_CCxCmd((void *)priv->base, i, AMEBA_TIM_CCX_DISABLE);
+        }
+    }
+
+  RTIM_Cmd((void *)priv->base, AMEBA_DISABLE);
+
+  priv->chanen    = 0;
+  priv->running   = false;
+  priv->frequency = 0;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_pwm_ioctl
+ ****************************************************************************/
+
+static int ameba_pwm_ioctl(struct pwm_lowerhalf_s *dev, int cmd,
+                           unsigned long arg)
+{
+  UNUSED(dev);
+  UNUSED(cmd);
+  UNUSED(arg);
+  return -ENOTTY;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_pwm_register
+ *
+ * Description:
+ *   See ameba_pwm.h.
+ *
+ ****************************************************************************/
+
+int ameba_pwm_register(const char *path, const uint8_t *pins,
+                       unsigned int npins)
+{
+  struct ameba_pwm_dev_s *priv;
+  unsigned int i;
+  int ret;
+
+  priv = kmm_zalloc(sizeof(struct ameba_pwm_dev_s));
+  if (priv == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  priv->ops     = &g_ameba_pwm_ops;
+  priv->base    = AMEBA_PWM_BASE;
+  priv->periph  = AMEBA_PWM_APBPERIPH;
+  priv->clk     = AMEBA_PWM_APBPERIPH_CLK;
+  priv->clkfreq = AMEBA_PWM_CLKFREQ;
+  priv->idx     = AMEBA_PWM_TIMER_IDX;
+
+  if (npins > AMEBA_PWM_NCHAN)
+    {
+      npins = AMEBA_PWM_NCHAN;
+    }
+
+  priv->nchan = (uint8_t)npins;
+  memset(priv->pins, AMEBA_PWM_PIN_NC, sizeof(priv->pins));
+
+  /* Gate the PWM peripheral clock and route each wired channel to its pad
+   * through the crossbar.  The per-channel function code comes from the
+   * chip header table (g_pwm_fids), never computed, so a chip whose codes
+   * are not contiguous (a single shared code, or grouped per timer) is
+   * expressed by its header alone without touching this driver.
+   */
+
+  RCC_PeriphClockCmd(priv->periph, priv->clk, AMEBA_ENABLE);
+
+  for (i = 0; i < npins; i++)
+    {
+      priv->pins[i] = pins[i];
+      if (pins[i] != AMEBA_PWM_PIN_NC)
+        {
+          Pinmux_Config(pins[i], g_pwm_fids[i]);
+        }
+    }
+
+  ret = pwm_register(path, (struct pwm_lowerhalf_s *)priv);
+  if (ret < 0)
+    {
+      _err("ERROR: pwm_register(%s) failed: %d\n", path, ret);
+      kmm_free(priv);
+      return ret;
+    }
+
+  return OK;
+}
diff --git a/arch/arm/src/common/ameba/ameba_pwm.h 
b/arch/arm/src/common/ameba/ameba_pwm.h
new file mode 100644
index 00000000000..49a28baf639
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_pwm.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_pwm.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_PWM_H
+#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_PWM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* The Ameba PWM timer drives several compare channels off ONE shared time
+ * base, so a single /dev/pwmN device exposes every wired channel: one
+ * frequency applies to all, while each channel carries its own duty cycle
+ * (this is the CONFIG_PWM_NCHANNELS multi-channel form of the NuttX PWM
+ * upper half).  The board supplies, per hardware channel, the output pad it
+ * is routed to (encoded with the AMEBA_PA()/AMEBA_PB() PinName codes used by
+ * the GPIO driver); channels the board does not use carry AMEBA_PWM_PIN_NC.
+ */
+
+#define AMEBA_PWM0            0
+
+/* Sentinel pad code for a hardware channel the board leaves unrouted. */
+
+#define AMEBA_PWM_PIN_NC      0xff
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ameba_pwm_register
+ *
+ * Description:
+ *   Configure the Ameba PWM timer and register it with the NuttX PWM
+ *   character driver at the given path (typically "/dev/pwm0").  All wired
+ *   channels share the timer's single time base (one frequency) and each
+ *   carries its own duty cycle.
+ *
+ * Input Parameters:
+ *   path  - The device path to register, e.g. "/dev/pwm0".
+ *   pins  - Array giving, for each hardware channel, the output pad encoded
+ *           with AMEBA_PA()/AMEBA_PB(), or AMEBA_PWM_PIN_NC for a channel
+ *           the board does not use.
+ *   npins - Number of entries in pins (clamped to the timer's channel
+ *           count).
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int ameba_pwm_register(const char *path, const uint8_t *pins,
+                       unsigned int npins);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_PWM_H */
diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt 
b/arch/arm/src/rtl8721dx/CMakeLists.txt
index b43237a62a0..d480f2b1074 100644
--- a/arch/arm/src/rtl8721dx/CMakeLists.txt
+++ b/arch/arm/src/rtl8721dx/CMakeLists.txt
@@ -56,6 +56,10 @@ if(CONFIG_AMEBA_SPI)
   list(APPEND SRCS ${AMEBA_COMMON}/ameba_spi.c)
 endif()
 
+if(CONFIG_AMEBA_PWM)
+  list(APPEND SRCS ${AMEBA_COMMON}/ameba_pwm.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 333db513af8..64adf3980e4 100644
--- a/arch/arm/src/rtl8721dx/Make.defs
+++ b/arch/arm/src/rtl8721dx/Make.defs
@@ -66,6 +66,10 @@ ifeq ($(CONFIG_AMEBA_SPI),y)
 CHIP_CSRCS += ameba_spi.c
 endif
 
+ifeq ($(CONFIG_AMEBA_PWM),y)
+CHIP_CSRCS += ameba_pwm.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 24b5d30e26f..bd665af7bf5 100644
--- a/arch/arm/src/rtl8721dx/ameba_board.mk
+++ b/arch/arm/src/rtl8721dx/ameba_board.mk
@@ -155,6 +155,15 @@ ifeq ($(CONFIG_AMEBA_SPI),y)
 AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_spi.c
 endif
 
+# PWM timer register layer.  The time-base entry points (RTIM_TimeBaseInit/
+# StructInit/Cmd) are in ROM, but the compare/period helpers the PWM driver
+# (arch/.../common/ameba/ameba_pwm.c) calls -- RTIM_CCStructInit/CCxInit/
+# CCRxSet/CCxCmd/ChangePeriod/PrescalerConfig -- are compiled from this RAM
+# source and linked in (--gc-sections drops the unused input-capture paths).
+ifeq ($(CONFIG_AMEBA_PWM),y)
+AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_tim.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_pwm_chip.h 
b/arch/arm/src/rtl8721dx/ameba_pwm_chip.h
new file mode 100644
index 00000000000..b7d3e073105
--- /dev/null
+++ b/arch/arm/src/rtl8721dx/ameba_pwm_chip.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+ * arch/arm/src/rtl8721dx/ameba_pwm_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_PWM_CHIP_H
+#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_PWM_CHIP_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Per-chip PWM wiring for RTL8721DX (amebadplus).  The shared driver
+ * (arch/arm/src/common/ameba/ameba_pwm.c) includes this header to learn
+ * which timer generates PWM, how many output channels it drives, its
+ * register base, input clock, peripheral-clock masks and crossbar pad-mux
+ * codes.  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 -- the pad-mux codes are a per-channel table, NOT computed).
+ *
+ * What differs per chip, from the SDK fwlib headers (verified, not guessed):
+ *
+ *   chip         PWM timer(s)   pad-mux codes (per channel)
+ *   -----------  ------------   ---------------------------------------
+ *   amebadplus   TIM8           PWM0..PWM7        = 52..59 (8, contig)
+ *   amebalite    TIM8           PWM0..            = 55..   (8, contig)
+ *   amebasmart   TIM8           PWM               = 10     (single code)
+ *   amebagreen2  TIM4/5/6..     TIM4_PWM0..3      = 111..  (4 per timer)
+ *   RTL8720F     TIM4/TIM5      TIM4_PWM0..3      = 45..   (4 per timer)
+ *
+ * The three pad-mux layouts (contiguous / single shared code / grouped four
+ * per timer) are all expressed the same way: AMEBA_PWM_PINMUX_FIDS is a
+ * per-channel list.  Contiguous chips list 52,53,...; a single-code chip
+ * (amebasmart) lists 10,10,...; a grouped chip lists that timer's codes.
+ * So a new chip only edits this header -- see the SPI/I2C/UART chip headers,
+ * which pass their pad-mux codes as the same kind of per-signal table.
+ *
+ * Other per-chip macros:
+ *   - AMEBA_PWM_TIMER_IDX / _NCHAN: the timer index this driver drives and
+ *     its compare-channel count (8 CCRs on amebadplus, 4 on green2/8720F).
+ *   - AMEBA_PWM_BASE: the NON-secure timer alias (0x411xxxxx); the secure
+ *     alias (0x511xxxxx) must not be used from the non-secure world the KM4
+ *     runs in (same rule as the GPIO/SPI drivers).
+ *   - AMEBA_PWM_CLKFREQ: TIM8 is clocked from the 40 MHz XTAL on amebadplus;
+ *     f_out = CLKFREQ / ((PSC + 1) * (ARR + 1)) with 16-bit PSC and ARR.
+ *   - AMEBA_PWM_APBPERIPH / _CLK: the "function" and "clock" args to
+ *     RCC_PeriphClockCmd(); equal on this chip, bit30 group selector 0.
+ *   - AMEBA_PWM_IRQ: RTIM_TimeBaseInit() takes it even though this polling
+ *     driver registers no callback (NULL); TIM8 is IRQ 18.
+ */
+
+#define AMEBA_PWM_TIMER_IDX       8            /* TIM8 (IS_TIM_PWM_TIM)     */
+#define AMEBA_PWM_NCHAN           8            /* CCR0..CCR7 (PWM_CHAN_MAX) */
+#define AMEBA_PWM_BASE            0x41100000ul /* NON-secure TIM8 base      */
+#define AMEBA_PWM_CLKFREQ         40000000ul   /* TIM8 input clock (40 MHz) */
+#define AMEBA_PWM_IRQ             18           /* TIMER8_IRQ                */
+
+/* Crossbar pad-mux function code per channel, indexed by channel number
+ * minus one (channel n uses AMEBA_PWM_PINMUX_FIDS[n - 1]).  On amebadplus
+ * these are the contiguous PINMUX_FUNCTION_PWM0..PWM7 (52..59).
+ */
+
+#define AMEBA_PWM_PINMUX_FIDS     { 52, 53, 54, 55, 56, 57, 58, 59 }
+
+/* APBPeriph_PWM0 (function) and APBPeriph_PWM0_CLOCK masks.  Equal on this
+ * chip; the group selector (bit30) is 0 for the PWM block on amebadplus.
+ */
+
+#define AMEBA_PWM_APBPERIPH       (((uint32_t)0 << 30) | ((uint32_t)1 << 23))
+#define AMEBA_PWM_APBPERIPH_CLK   (((uint32_t)0 << 30) | ((uint32_t)1 << 23))
+
+#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_PWM_CHIP_H */
diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/pwm/defconfig 
b/boards/arm/rtl8721dx/pke8721daf/configs/pwm/defconfig
new file mode 100644
index 00000000000..2b0336d22b2
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/configs/pwm/defconfig
@@ -0,0 +1,52 @@
+#
+# 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_PWM=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_EXAMPLES_PWM=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_PWM_NCHANNELS=2
+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 34abcac3a23..b4722787e6c 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
+++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
@@ -38,12 +38,17 @@ if(CONFIG_AMEBA_SPI)
   list(APPEND SRCS rtl8721dx_spi.c)
 endif()
 
+if(CONFIG_AMEBA_PWM)
+  list(APPEND SRCS rtl8721dx_pwm.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_SPI
+   OR CONFIG_AMEBA_PWM)
   # 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 03eda20df62..158e7fa6446 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile
+++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
@@ -60,4 +60,13 @@ CSRCS += rtl8721dx_spi.c
 CFLAGS += 
${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
 endif
 
+ifeq ($(CONFIG_AMEBA_PWM),y)
+CSRCS += rtl8721dx_pwm.c
+
+# The board PWM 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 5ff19f3177f..bc4f3847d39 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
@@ -148,6 +148,16 @@ int rtl8721dx_bringup(void)
     }
 #endif
 
+#ifdef CONFIG_AMEBA_PWM
+  /* Register the board's PWM timer at /dev/pwm0. */
+
+  ret = rtl8721dx_pwm_initialize();
+  if (ret < 0)
+    {
+      syslog(LOG_ERR, "ERROR: rtl8721dx_pwm_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 63a31287359..f712e50da38 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
@@ -121,6 +121,19 @@ int rtl8721dx_i2c_initialize(void);
 int rtl8721dx_spi_initialize(void);
 #endif
 
+#ifdef CONFIG_AMEBA_PWM
+/****************************************************************************
+ * Name: rtl8721dx_pwm_initialize
+ *
+ * Description:
+ *   Register the board's PWM timer at /dev/pwm0
+ *   (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.c).
+ *
+ ****************************************************************************/
+
+int rtl8721dx_pwm_initialize(void);
+#endif
+
 #ifdef CONFIG_RTL8721DX_FLASH_FS
 /****************************************************************************
  * Name: ameba_flash_fs_initialize
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.c 
b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.c
new file mode 100644
index 00000000000..58746b83979
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.c
@@ -0,0 +1,89 @@
+/****************************************************************************
+ * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pwm.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_pwm.h"
+#include "rtl8721dx_pke8721daf.h"
+
+#ifdef CONFIG_AMEBA_PWM
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* Output pad for each PWM hardware channel (0..AMEBA_PWM_NCHAN-1), all off
+ * the one shared time base.  Channels the board does not use carry
+ * AMEBA_PWM_PIN_NC.  The pads are examples used by the `pwm` config
+ * (examples/pwm); adjust them to match your board's wiring.  Any pad can be
+ * routed to any channel through the crossbar, so only this table changes.
+ */
+
+static const uint8_t g_pwm_pins[] =
+{
+  AMEBA_PB(18),      /* channel 1 -> PWM0 */
+  AMEBA_PB(19),      /* channel 2 -> PWM1 */
+  AMEBA_PWM_PIN_NC,  /* channel 3 unused  */
+  AMEBA_PWM_PIN_NC,  /* channel 4 unused  */
+  AMEBA_PWM_PIN_NC,  /* channel 5 unused  */
+  AMEBA_PWM_PIN_NC,  /* channel 6 unused  */
+  AMEBA_PWM_PIN_NC,  /* channel 7 unused  */
+  AMEBA_PWM_PIN_NC,  /* channel 8 unused  */
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rtl8721dx_pwm_initialize
+ *
+ * Description:
+ *   Register the board's PWM timer at /dev/pwm0.
+ *
+ ****************************************************************************/
+
+int rtl8721dx_pwm_initialize(void)
+{
+  int ret;
+
+  ret = ameba_pwm_register("/dev/pwm0", g_pwm_pins, nitems(g_pwm_pins));
+  if (ret < 0)
+    {
+      syslog(LOG_ERR,
+             "ERROR: ameba_pwm_register(/dev/pwm0) failed: %d\n", ret);
+      return ret;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_AMEBA_PWM */
diff --git a/tools/nxstyle.c b/tools/nxstyle.c
index ddf8faa59ca..89683934df7 100644
--- a/tools/nxstyle.c
+++ b/tools/nxstyle.c
@@ -288,6 +288,7 @@ static const char *g_white_prefix[] =
   "RCC_",
   "RTC_",             /* RTC_InitTypeDef, RTC_Enable, RTC_SetTime, etc. */
   "RTCIO_",
+  "RTIM_",            /* RTIM_TimeBaseInit, RTIM_Cmd, RTIM_CCxCmd, etc. */
   "SDM32K_",          /* SDM32K_Enable */
   "SSI_",             /* SSI_Init, SSI_SetRole, SSI_WriteData, etc. */
   "Set_OSC131_",      /* Set_OSC131_STATE — Ameba SDK RTC accessor */

Reply via email to