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 */
