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
