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

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


The following commit(s) were added to refs/heads/master by this push:
     new 20f3b659372 arm/nrf54l: add SAADC support
20f3b659372 is described below

commit 20f3b659372c64220a2b97f43a4c1b31de5c9dd3
Author: raiden00pl <[email protected]>
AuthorDate: Sun Sep 20 16:22:17 2026 +0200

    arm/nrf54l: add SAADC support
    
    arm/nrf54l: add SAADC support
    
    Assisted-by: Codex:GPT-6
    Signed-off-by: raiden00pl <[email protected]>
---
 Documentation/platforms/arm/nrf54l/index.rst |  17 +-
 arch/arm/src/nrf54l/CMakeLists.txt           |   3 +
 arch/arm/src/nrf54l/Kconfig                  |  67 ++
 arch/arm/src/nrf54l/Make.defs                |   4 +
 arch/arm/src/nrf54l/hardware/nrf54l_saadc.h  | 235 +++++++
 arch/arm/src/nrf54l/nrf54l_adc.c             | 900 +++++++++++++++++++++++++++
 arch/arm/src/nrf54l/nrf54l_adc.h             | 155 +++++
 7 files changed, 1380 insertions(+), 1 deletion(-)

diff --git a/Documentation/platforms/arm/nrf54l/index.rst 
b/Documentation/platforms/arm/nrf54l/index.rst
index 0dc99f96282..1c7340e263d 100644
--- a/Documentation/platforms/arm/nrf54l/index.rst
+++ b/Documentation/platforms/arm/nrf54l/index.rst
@@ -21,7 +21,7 @@ PWM         Yes
 QDEC        No
 RADIO       Yes     Bluetooth LE through SDC
 RRAMC       Yes     Progmem erase/write interface
-SAADC       No
+SAADC       Yes
 SPIM        No
 TIMER       Yes
 TWIM        No
@@ -83,6 +83,21 @@ RRAMC
 overwriting either bit value; erase operations fill emulated 4 KiB blocks
 with ``0xff``. Writes require word-aligned addresses and lengths.
 
+SAADC
+-----
+
+``nrf54l_adcinitialize()`` provides the ADC lower half, with up to eight
+single-ended or differential channels, 8/10/12/14-bit resolution and
+optional oversampling. Each ``ANIOC_TRIGGER`` collects one result per
+channel. The local timer supports a single channel; scanning with
+oversampling requires burst mode on every channel.
+
+The internal reference is 0.9 V. Available gains range from 1/4 to 2.
+External analog and reference pins must have their digital input buffers,
+outputs and pulls disconnected before opening the device. AIN0 through
+AIN7 map to P1.04/05/06/07/11/12/13/14 on L15 and
+P1.00/31/30/29/06/05/04/03 on LM20.
+
 TIMER
 -----
 
diff --git a/arch/arm/src/nrf54l/CMakeLists.txt 
b/arch/arm/src/nrf54l/CMakeLists.txt
index f3f1b86ccac..2ca9e701030 100644
--- a/arch/arm/src/nrf54l/CMakeLists.txt
+++ b/arch/arm/src/nrf54l/CMakeLists.txt
@@ -46,6 +46,9 @@ endif()
 if(CONFIG_NRF54L_UART)
   list(APPEND SRCS nrf54l_serial.c)
 endif()
+if(CONFIG_NRF54L_SAADC)
+  list(APPEND SRCS nrf54l_adc.c)
+endif()
 if(CONFIG_NRF54L_GPIOTE)
   list(APPEND SRCS nrf54l_gpiote.c)
 endif()
diff --git a/arch/arm/src/nrf54l/Kconfig b/arch/arm/src/nrf54l/Kconfig
index 44539a84ee3..e74892745b6 100644
--- a/arch/arm/src/nrf54l/Kconfig
+++ b/arch/arm/src/nrf54l/Kconfig
@@ -163,6 +163,10 @@ config NRF54L_PWM2
        depends on PWM
        default n
 
+config NRF54L_SAADC
+       bool "SAADC"
+       default n
+
 config NRF54L_GRTC
        bool "GRTC"
        default n
@@ -369,6 +373,69 @@ endif # NRF54L_PWM
 
 endmenu # PWM configuration
 
+menu "SAADC Configuration"
+
+if NRF54L_SAADC
+
+choice
+       prompt "SAADC trigger selection"
+       default NRF54L_SAADC_TASK
+
+config NRF54L_SAADC_TASK
+       bool "SAADC Task trigger"
+
+config NRF54L_SAADC_TIMER
+       bool "SAADC Timer trigger"
+       ---help---
+               Use the local timer for a single channel. Each ANIOC_TRIGGER
+               collects one result and then stops the timer.
+
+endchoice # SAADC trigger selection
+
+if NRF54L_SAADC_TIMER
+
+config NRF54L_SAADC_TIMER_CC
+       int "SAADC Timer CC"
+       default 192
+       range 8 2047
+       ---help---
+               Sample rate is 16 MHz / CC. The period must accommodate the
+               channel acquisition time plus 2 us conversion time, multiplied
+               by the oversampling factor when burst is enabled.
+
+endif # NRF54L_SAADC_TIMER
+
+config NRF54L_SAADC_OVERSAMPLE
+       int "SAADC oversample"
+       default 0
+       range 0 8
+       ---help---
+               Zero disables oversampling; values 1 through 8 select 2 through
+               256 samples per result. Scanning multiple channels requires
+               burst mode on every channel when oversampling is enabled.
+
+config NRF54L_SAADC_RESOLUTION
+       int "SAADC resolution"
+       default 0
+       range 0 3
+       ---help---
+               SAADC resolution: 0 - 8 bits, 1 - 10 bits, 2 - 12 bits, 3 - 14 
bits.
+
+config NRF54L_SAADC_CHANNELS
+       int "SAADC channels"
+       default 8
+       range 1 8
+
+config NRF54L_SAADC_LIMITS
+       bool "SAADC limits enable"
+       default n
+       ---help---
+               Configure the high and low limit events for each channel.
+
+endif # NRF54L_SAADC
+
+endmenu # SAADC Configuration
+
 menuconfig NRF54L_SOFTDEVICE_CONTROLLER
        bool "SoftDevice Controller"
        depends on ALLOW_BSDNORDIC_COMPONENTS
diff --git a/arch/arm/src/nrf54l/Make.defs b/arch/arm/src/nrf54l/Make.defs
index ded19109ce8..0654b99c67d 100644
--- a/arch/arm/src/nrf54l/Make.defs
+++ b/arch/arm/src/nrf54l/Make.defs
@@ -46,6 +46,10 @@ ifeq ($(CONFIG_NRF54L_UART),y)
 CHIP_CSRCS += nrf54l_serial.c
 endif
 
+ifeq ($(CONFIG_NRF54L_SAADC),y)
+CHIP_CSRCS += nrf54l_adc.c
+endif
+
 ifeq ($(CONFIG_NRF54L_GPIOTE),y)
 CHIP_CSRCS += nrf54l_gpiote.c
 endif
diff --git a/arch/arm/src/nrf54l/hardware/nrf54l_saadc.h 
b/arch/arm/src/nrf54l/hardware/nrf54l_saadc.h
new file mode 100644
index 00000000000..b66ab075d49
--- /dev/null
+++ b/arch/arm/src/nrf54l/hardware/nrf54l_saadc.h
@@ -0,0 +1,235 @@
+/***************************************************************************
+ * arch/arm/src/nrf54l/hardware/nrf54l_saadc.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_NRF54L_HARDWARE_NRF54L_SAADC_H
+#define __ARCH_ARM_SRC_NRF54L_HARDWARE_NRF54L_SAADC_H
+
+/***************************************************************************
+ * Included Files
+ ***************************************************************************/
+
+#include <nuttx/config.h>
+#include "hardware/nrf54l_memorymap.h"
+
+/***************************************************************************
+ * Pre-processor Definitions
+ ***************************************************************************/
+
+/* Register offsets for SAADC **********************************************/
+
+#define NRF54L_SAADC_TASKS_START_OFFSET        0x0000                 /* Start 
the SAADC */
+#define NRF54L_SAADC_TASKS_SAMPLE_OFFSET       0x0004                 /* Takes 
one SAADC sample */
+#define NRF54L_SAADC_TASKS_STOP_OFFSET         0x0008                 /* Stop 
the SAADC */
+#define NRF54L_SAADC_TASKS_CALOFFSET_OFFSET    0x000c                 /* 
Starts offset auto-calibration */
+#define NRF54L_SAADC_SUB_START_OFFSET          0x0080                 /* 
Subscribe START */
+#define NRF54L_SAADC_SUB_SAMPLE_OFFSET         0x0084                 /* 
Subscribe SAMPLE */
+#define NRF54L_SAADC_SUB_STOP_OFFSET           0x0088                 /* 
Subscribe STOP */
+#define NRF54L_SAADC_SUB_CALOFFSET_OFFSET      0x008c                 /* 
Subscribe CALIBRATEOFFSET */
+#define NRF54L_SAADC_EVENTS_STARTED_OFFSET     0x0100                 /* The 
SAADC has started */
+#define NRF54L_SAADC_EVENTS_END_OFFSET         0x0104                 /* The 
SAADC has filled up the result buffer */
+#define NRF54L_SAADC_EVENTS_DONE_OFFSET        0x0108                 /* A 
conversion task has been completed */
+#define NRF54L_SAADC_EVENTS_RESDONE_OFFSET     0x010c                 /* 
Result ready for transfer to RAM */
+#define NRF54L_SAADC_EVENTS_CALDONE_OFFSET     0x0110                 /* 
Calibration is complete */
+#define NRF54L_SAADC_EVENTS_STOPPED_OFFSET     0x0114                 /* The 
SAADC has stopped */
+#define NRF54L_SAADC_EVENTS_CHLIMH_OFFSET(x)   (0x118 + ((x) * 0x8))  /* Limit 
high event for channel x */
+#define NRF54L_SAADC_EVENTS_CHLIML_OFFSET(x)   (0x11c + ((x) * 0x8))  /* Limit 
low event for channel x */
+#define NRF54L_SAADC_PUB_STARTED_OFFSET        0x0180                 /* 
Publish STARTED */
+#define NRF54L_SAADC_PUB_END_OFFSET            0x0184                 /* 
Publish END */
+#define NRF54L_SAADC_PUB_DONE_OFFSET           0x0188                 /* 
Publish DONE */
+#define NRF54L_SAADC_PUB_RESDONE_OFFSET        0x018c                 /* 
Publish RESULTDONE */
+#define NRF54L_SAADC_PUB_CALDONE_OFFSET        0x0190                 /* 
Publish CALIBRATEDONE */
+#define NRF54L_SAADC_PUB_STOPPED_OFFSET        0x0194                 /* 
Publish STOPPED */
+#define NRF54L_SAADC_PUB_CHLIMH_OFFSET(x)      (0x198 + ((x) * 0x8))  /* 
Publish channel high limit */
+#define NRF54L_SAADC_PUB_CHLIML_OFFSET(x)      (0x19c + ((x) * 0x8))  /* 
Publish channel low limit */
+#define NRF54L_SAADC_SHORTS_OFFSET             0x0200                 /* 
Shortcuts */
+#define NRF54L_SAADC_INTEN_OFFSET              0x0300                 /* 
Enable or disable interrupt */
+#define NRF54L_SAADC_INTENSET_OFFSET           0x0304                 /* 
Enable interrupt */
+#define NRF54L_SAADC_INTENCLR_OFFSET           0x0308                 /* 
Disable interrupt */
+#define NRF54L_SAADC_STATUS_OFFSET             0x0400                 /* 
Status */
+#define NRF54L_SAADC_LINCALCOEFF_OFFSET(x)     (0x440 + ((x) * 0x4))  /* 
Linearity calibration coefficient */
+#define NRF54L_SAADC_ENABLE_OFFSET             0x0500                 /* 
Enable or disable SAADC */
+#define NRF54L_SAADC_CHPSELP_OFFSET(x)         (0x510 + ((x) * 0x10)) /* Input 
positive pin for CH[x] */
+#define NRF54L_SAADC_CHPSELN_OFFSET(x)         (0x514 + ((x) * 0x10)) /* Input 
negative pin for CH[x] */
+#define NRF54L_SAADC_CHCONFIG_OFFSET(x)        (0x518 + ((x) * 0x10)) /* Input 
configuration for CH[x] */
+#define NRF54L_SAADC_CHLIMIT_OFFSET(x)         (0x51c + ((x) * 0x10)) /* 
High/low limits for event monitoring of a CH[x] */
+#define NRF54L_SAADC_BURST_OFFSET              0x05e8                 /* 
Global burst control (LM20) */
+#define NRF54L_SAADC_RESOLUTION_OFFSET         0x05f0                 /* 
Resolution configuration */
+#define NRF54L_SAADC_OVERSAMPLE_OFFSET         0x05f4                 /* 
Oversampling configuration */
+#define NRF54L_SAADC_SAMPLERATE_OFFSET         0x05f8                 /* 
Controls normal or continuous sample rate */
+#define NRF54L_SAADC_PTR_OFFSET                0x062c                 /* Data 
pointer */
+#define NRF54L_SAADC_MAXCNT_OFFSET             0x0630                 /* 
Maximum number of bytes */
+#define NRF54L_SAADC_AMOUNT_OFFSET             0x0634                 /* Bytes 
written at END or STOPPED */
+#define NRF54L_SAADC_CURRENTAMOUNT_OFFSET      0x0638                 /* Bytes 
written since START */
+#define NRF54L_SAADC_NOISESHAPE_OFFSET         0x0654                 /* Noise 
shaping mode */
+
+/* Register Bitfield Definitions for SAADC *********************************/
+
+/* SUBSCRIBE/PUBLISH Registers */
+
+#define SAADC_SUBPUB_CHIDX_SHIFT        (0)
+#define SAADC_SUBPUB_CHIDX_MASK         (0xff << SAADC_SUBPUB_CHIDX_SHIFT)
+#define SAADC_SUBPUB_ENABLE             (0x80000000)
+
+/* SHORTS Register */
+
+#define SAADC_SHORT_DONE_SAMPLE        (1 << 0)
+#define SAADC_SHORT_END_START          (1 << 1)
+
+/* INTEN/INTENSET/INTENCLR Register */
+
+#define SAADC_INT_STARTED              (1 << 0)         /* Bit 0: Interrupt 
for event STARTED */
+#define SAADC_INT_END                  (1 << 1)         /* Bit 1: Interrupt 
for event END */
+#define SAADC_INT_DONE                 (1 << 2)         /* Bit 2: Interrupt 
for event DONE */
+#define SAADC_INT_RESDONE              (1 << 3)         /* Bit 3: Interrupt 
for event RESULTDONE */
+#define SAADC_INT_CALDONE              (1 << 4)         /* Bit 4: Interrupt 
for event CALIBRATEDONE */
+#define SAADC_INT_STOPPED              (1 << 5)         /* Bit 5: Interrupt 
for event STOPPED */
+#define SAADC_INT_CHXLIMH(x)           (1 << (2 * (x) + 6))
+#define SAADC_INT_CHXLIML(x)           (1 << (2 * (x) + 7))
+#define SAADC_INT_ALL                  (0x003fffff)
+
+/* STATUS Register */
+
+#define SAADC_STATUS_READY             (0)       /* Bit 0: SAADC is ready */
+#define SAADC_STATUS_BUSY              (1 << 0)  /* Bit 0: SAADC is busy */
+
+/* ENABLE Register */
+
+#define SAADC_ENABLE_DIS               (0)       /* Bit 0: Disable SAADC */
+#define SAADC_ENABLE_EN                (1 << 0)  /* Bit 0: Enable SAADC */
+
+/* CH[n] PSELx Register */
+
+#define SAADC_CHPSEL_PIN_SHIFT         (0)       /* Bits 0-4: GPIO pin */
+#define SAADC_CHPSEL_PIN_MASK          (0x1f << SAADC_CHPSEL_PIN_SHIFT)
+#define SAADC_CHPSEL_PORT_SHIFT        (8)       /* Bits 8-11: GPIO port */
+#define SAADC_CHPSEL_PORT_MASK         (0xf << SAADC_CHPSEL_PORT_SHIFT)
+#define SAADC_CHPSEL_INTERNAL_SHIFT    (12)      /* Bits 12-13: Internal input 
*/
+#define SAADC_CHPSEL_INTERNAL_MASK     (0x3 << SAADC_CHPSEL_INTERNAL_SHIFT)
+#  define SAADC_CHPSEL_AVDD            (0x0 << SAADC_CHPSEL_INTERNAL_SHIFT)
+#  define SAADC_CHPSEL_DVDD            (0x1 << SAADC_CHPSEL_INTERNAL_SHIFT)
+#  define SAADC_CHPSEL_VDD             (0x2 << SAADC_CHPSEL_INTERNAL_SHIFT)
+#define SAADC_CHPSEL_CONNECT_MASK      (0xc0000000)
+#  define SAADC_CHPSEL_NC              (0x00000000)
+#  define SAADC_CHPSEL_ANALOG          (0x40000000)
+#  define SAADC_CHPSEL_INTERNAL        (0x80000000)
+
+/* CH[n] CONFIG Register */
+
+#define SAADC_CONFIG_CHOPPING_DIS      (0 << 0)  /* Bit 0: Chopping disabled 
(LM20) */
+#define SAADC_CONFIG_CHOPPING_EN       (1 << 0)  /* Bit 0: Chopping enabled 
(LM20) */
+#define SAADC_CONFIG_GAIN_SHIFT        (8)       /* Bits 8-10: Gain control */
+#define SAADC_CONFIG_GAIN_MASK         (0x7 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_2          (0x0 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_1          (0x1 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_2P3        (0x2 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_1P2        (0x3 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_2P5        (0x4 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_1P3        (0x5 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_2P7        (0x6 << SAADC_CONFIG_GAIN_SHIFT)
+#  define SAADC_CONFIG_GAIN_1P4        (0x7 << SAADC_CONFIG_GAIN_SHIFT)
+#define SAADC_CONFIG_BURS_DIS          (0 << 11) /* Bit 11: Burst disabled 
(L15) */
+#define SAADC_CONFIG_BURS_EN           (1 << 11) /* Bit 11: Burst enabled 
(L15) */
+#define SAADC_CONFIG_REFSEL_INTERNAL   (0 << 12) /* Bit 12: Internal reference 
(0.9V) */
+#define SAADC_CONFIG_REFSEL_EXTERNAL   (1 << 12) /* Bit 12: External reference 
*/
+#define SAADC_CONFIG_MODE_SE           (0 << 15) /* Bit 15: Single-ended */
+#define SAADC_CONFIG_MODE_DIFF         (1 << 15) /* Bit 15: Differential */
+#define SAADC_CONFIG_TACQ_SHIFT        (16)      /* Bits 16-24: Acquisition 
time */
+#define SAADC_CONFIG_TACQ_MASK         (0x1ff << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_3US        (23 << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_5US        (39 << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_10US       (79 << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_15US       (119 << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_20US       (159 << SAADC_CONFIG_TACQ_SHIFT)
+#  define SAADC_CONFIG_TACQ_40US       (319 << SAADC_CONFIG_TACQ_SHIFT)
+#define SAADC_CONFIG_TCONV_SHIFT       (28)      /* Bits 28-30: Conversion 
time */
+#define SAADC_CONFIG_TCONV_MASK        (0x7 << SAADC_CONFIG_TCONV_SHIFT)
+#  define SAADC_CONFIG_TCONV_2US       (7 << SAADC_CONFIG_TCONV_SHIFT)
+
+/* CH[n] LIMIT Register */
+
+#define SAADC_CHLIMIT_LOW_SHIFT        (0)       /* Bits 0-15: Low level limit 
*/
+#define SAADC_CHLIMIT_LOW_MASK         (0xffff << SAADC_CHLIMIT_LOW_SHIFT)
+#define SAADC_CHLIMIT_HIGH_SHIFT       (16)      /* Bits 16-31: High level 
limit */
+#define SAADC_CHLIMIT_HIGH_MASK        (0xffff << SAADC_CHLIMIT_HIGH_SHIFT)
+
+/* BURST Register (LM20) */
+
+#define SAADC_BURST_DIS                (0 << 0)
+#define SAADC_BURST_EN                 (1 << 0)
+
+/* RESOLUTION Register */
+
+#define SAADC_RESOLUTION_SHIFT         (0)       /* Bits 0-2: SAADC resolution 
*/
+#define SAADC_RESOLUTION_MASK          (0x7 << SAADC_RESOLUTION_SHIFT)
+#  define SAADC_RESOLUTION_8BIT        (0x0 << SAADC_RESOLUTION_SHIFT)
+#  define SAADC_RESOLUTION_10BIT       (0x1 << SAADC_RESOLUTION_SHIFT)
+#  define SAADC_RESOLUTION_12BIT       (0x2 << SAADC_RESOLUTION_SHIFT)
+#  define SAADC_RESOLUTION_14BIT       (0x3 << SAADC_RESOLUTION_SHIFT)
+
+/* OVERSAMPLE Register */
+
+#define SAADC_OVERSAMPLE_SHIFT         (0)       /* Bit 0-3: Oversample 
control */
+#define SAADC_OVERSAMPLE_MASK          (0xf << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_NONE        (0x0 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_2X          (0x1 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_4X          (0x2 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_8X          (0x3 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_16X         (0x4 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_32X         (0x5 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_64X         (0x6 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_128X        (0x7 << SAADC_OVERSAMPLE_SHIFT)
+#  define SAADC_OVERSAMPLE_256X        (0x8 << SAADC_OVERSAMPLE_SHIFT)
+
+/* SAMPLERATE Register */
+
+#define SAADC_SAMPLERATE_CC_SHIFT      (0)       /* Bits 0-10: Capture and 
compare value */
+#define SAADC_SAMPLERATE_CC_MASK       (0x7ff << SAADC_SAMPLERATE_CC_SHIFT)
+#define SAADC_SAMPLERATE_MODE_TASK     (0 << 12) /* Bit 12: Rate is controlled 
from SAMPLE task */
+#define SAADC_SAMPLERATE_MODE_TIMERS   (1 << 12) /* Bit 12: Rate is controlled 
from local timer */
+
+/* MAXCNT Register */
+
+#define SAADC_MAXCNT_SHIFT             (0)       /* Bits 0-14: Maximum byte 
count */
+#define SAADC_MAXCNT_MASK              (0x7fff)
+
+/* AMOUNT Register */
+
+#define SAADC_AMOUNT_SHIFT             (0)       /* Bits 0-14: Byte count */
+#define SAADC_AMOUNT_MASK              (0x7fff)
+
+/* CURRENTAMOUNT Register */
+
+#define SAADC_CURRENTAMOUNT_MASK       (0x7fff)
+
+/* TRIM.LINCALCOEFF Registers */
+
+#define SAADC_LINCALCOEFF_MASK         (0xffff)
+
+/* NOISESHAPE Register */
+
+#define SAADC_NOISESHAPE_MASK          (0x3)
+#  define SAADC_NOISESHAPE_DISABLE     (0x0)
+#  define SAADC_NOISESHAPE_NS1         (0x1)
+#  define SAADC_NOISESHAPE_NS2         (0x2)
+#  define SAADC_NOISESHAPE_NS3         (0x3)      /* LM20 only */
+
+#endif /* __ARCH_ARM_SRC_NRF54L_HARDWARE_NRF54L_SAADC_H */
diff --git a/arch/arm/src/nrf54l/nrf54l_adc.c b/arch/arm/src/nrf54l/nrf54l_adc.c
new file mode 100644
index 00000000000..3b51caa9f1d
--- /dev/null
+++ b/arch/arm/src/nrf54l/nrf54l_adc.c
@@ -0,0 +1,900 @@
+/****************************************************************************
+ * arch/arm/src/nrf54l/nrf54l_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 <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <nuttx/debug.h>
+#include <nuttx/irq.h>
+#include <nuttx/arch.h>
+#include <nuttx/analog/adc.h>
+#include <nuttx/analog/ioctl.h>
+
+#include "arm_internal.h"
+#include "nrf54l_adc.h"
+
+#include "hardware/nrf54l_saadc.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct nrf54l_adc_s
+{
+  /* Upper-half callback */
+
+  const struct adc_callback_s *cb;
+
+  /* Channels configuration */
+
+  struct nrf54l_adc_channel_s channels[CONFIG_NRF54L_SAADC_CHANNELS];
+
+  /* Samples buffer */
+
+  int16_t                    buffer[CONFIG_NRF54L_SAADC_CHANNELS]
+                             aligned_data(4);
+
+  uint8_t                    chan_len;   /* Configured channels */
+  uint32_t                   base;       /* Base address of ADC register */
+  uint32_t                   irq;        /* ADC interrupt */
+  uint8_t                    resolution; /* ADC resolution */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* ADC Register access */
+
+static inline void nrf54l_adc_putreg(struct nrf54l_adc_s *priv,
+                                     uint32_t offset,
+                                     uint32_t value);
+static inline uint32_t nrf54l_adc_getreg(struct nrf54l_adc_s *priv,
+                                         uint32_t offset);
+
+/* ADC helpers */
+
+static int nrf54l_adc_configure(struct nrf54l_adc_s *priv);
+static int nrf54l_adc_calibrate(struct nrf54l_adc_s *priv);
+static uint32_t nrf54l_adc_ch_config(struct nrf54l_adc_channel_s *cfg);
+static uint32_t nrf54l_adc_chanpsel(int psel);
+static int nrf54l_adc_chancfg(struct nrf54l_adc_s *priv, uint8_t chan,
+                              struct nrf54l_adc_channel_s *cfg);
+static int nrf54l_adc_isr(int irq, void *context, void *arg);
+
+/* ADC Driver Methods */
+
+static int  nrf54l_adc_bind(struct adc_dev_s *dev,
+                            const struct adc_callback_s *callback);
+static void nrf54l_adc_reset(struct adc_dev_s *dev);
+static int  nrf54l_adc_setup(struct adc_dev_s *dev);
+static void nrf54l_adc_shutdown(struct adc_dev_s *dev);
+static void nrf54l_adc_rxint(struct adc_dev_s *dev, bool enable);
+static int  nrf54l_adc_ioctl(struct adc_dev_s *dev, int cmd,
+                             unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* ADC interface operations */
+
+static const struct adc_ops_s g_nrf54l_adcops =
+{
+  .ao_bind        = nrf54l_adc_bind,
+  .ao_reset       = nrf54l_adc_reset,
+  .ao_setup       = nrf54l_adc_setup,
+  .ao_shutdown    = nrf54l_adc_shutdown,
+  .ao_rxint       = nrf54l_adc_rxint,
+  .ao_ioctl       = nrf54l_adc_ioctl,
+};
+
+/* SAADC device */
+
+struct nrf54l_adc_s g_nrf54l_adcpriv =
+{
+  .cb         = NULL,
+  .base       = NRF54L_SAADC_BASE,
+  .irq        = NRF54L_IRQ_SAADC,
+  .resolution = CONFIG_NRF54L_SAADC_RESOLUTION
+};
+
+/* Upper-half ADC device */
+
+static struct adc_dev_s g_nrf54l_adc =
+{
+  .ad_ops      = &g_nrf54l_adcops,
+  .ad_priv     = &g_nrf54l_adcpriv,
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf54l_adc_putreg
+ *
+ * Description:
+ *   Put a 32-bit register value by offset
+ *
+ ****************************************************************************/
+
+static inline void nrf54l_adc_putreg(struct nrf54l_adc_s *priv,
+                                     uint32_t offset,
+                                     uint32_t value)
+{
+  DEBUGASSERT(priv);
+
+  putreg32(value, priv->base + offset);
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_getreg
+ *
+ * Description:
+ *   Get a 32-bit register value by offset
+ *
+ ****************************************************************************/
+
+static inline uint32_t nrf54l_adc_getreg(struct nrf54l_adc_s *priv,
+                                         uint32_t offset)
+{
+  DEBUGASSERT(priv);
+
+  return getreg32(priv->base + offset);
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_isr
+ *
+ * Description:
+ *   Common ADC interrupt service routine
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_isr(int irq, void *context, void *arg)
+{
+  struct adc_dev_s   *dev  = (struct adc_dev_s *) arg;
+  struct nrf54l_adc_s *priv = NULL;
+  int                 ret  = OK;
+  int                 i    = 0;
+
+  DEBUGASSERT(dev);
+
+  priv = (struct nrf54l_adc_s *) dev->ad_priv;
+  DEBUGASSERT(priv);
+
+  ainfo("nrf54l_adc_isr\n");
+
+  /* END event */
+
+  if (nrf54l_adc_getreg(priv, NRF54L_SAADC_EVENTS_END_OFFSET) == 1)
+    {
+      DEBUGASSERT(priv->cb != NULL);
+      DEBUGASSERT(priv->cb->au_receive != NULL);
+
+      /* Give the ADC data to the ADC driver */
+
+      for (i = 0; i < priv->chan_len; i += 1)
+        {
+          priv->cb->au_receive(dev, i, priv->buffer[i]);
+        }
+
+      /* Clear event */
+
+      nrf54l_adc_putreg(priv, NRF54L_SAADC_EVENTS_END_OFFSET, 0);
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_configure
+ *
+ * Description:
+ *   Configure ADC
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_configure(struct nrf54l_adc_s *priv)
+{
+  int regval = 0;
+
+  DEBUGASSERT(priv);
+
+  /* Configure ADC resolution */
+
+  regval = CONFIG_NRF54L_SAADC_RESOLUTION;
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_RESOLUTION_OFFSET, regval);
+
+  /* Configure oversampling */
+
+  regval = CONFIG_NRF54L_SAADC_OVERSAMPLE;
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_OVERSAMPLE_OFFSET, regval);
+
+#ifndef CONFIG_ARCH_CHIP_NRF54L15
+  /* LM20 has global burst control instead of a per-channel CONFIG bit */
+
+  regval = priv->channels[0].burst ? SAADC_BURST_EN : SAADC_BURST_DIS;
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_BURST_OFFSET, regval);
+#endif
+
+  /* Configure sample rate */
+
+#if defined(CONFIG_NRF54L_SAADC_TIMER)
+  /* Trigger from local timer */
+
+  regval = SAADC_SAMPLERATE_MODE_TIMERS;
+  regval |= ((CONFIG_NRF54L_SAADC_TIMER_CC & SAADC_SAMPLERATE_CC_MASK)
+             << SAADC_SAMPLERATE_CC_SHIFT);
+#elif defined(CONFIG_NRF54L_SAADC_TASK)
+  /* Trigger on SAMPLE tas */
+
+  regval = SAADC_SAMPLERATE_MODE_TASK;
+#else
+#  error SAADC trigger not selected
+#endif
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_SAMPLERATE_OFFSET, regval);
+
+  /* Configure ADC buffer */
+
+  regval = (uintptr_t)&priv->buffer;
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_PTR_OFFSET, regval);
+
+  /* Buffer size is in bytes */
+
+  regval = priv->chan_len * sizeof(int16_t);
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_MAXCNT_OFFSET, regval);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_calibrate
+ *
+ * Description:
+ *   Calibrate ADC
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_calibrate(struct nrf54l_adc_s *priv)
+{
+  /* Clear Event */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_EVENTS_CALDONE_OFFSET, 0);
+
+  /* Start calibration */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_TASKS_CALOFFSET_OFFSET, 1);
+
+  /* Wait for calibration done */
+
+  while (nrf54l_adc_getreg(priv, NRF54L_SAADC_EVENTS_CALDONE_OFFSET) != 1);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_ch_config
+ ****************************************************************************/
+
+static uint32_t nrf54l_adc_ch_config(struct nrf54l_adc_channel_s *cfg)
+{
+  uint32_t regval = 0;
+
+  /* Gain control */
+
+  switch (cfg->gain)
+    {
+      case NRF54L_ADC_GAIN_2_3:
+        {
+          regval |= SAADC_CONFIG_GAIN_2P3;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_2_5:
+        {
+          regval |= SAADC_CONFIG_GAIN_2P5;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_1_4:
+        {
+          regval |= SAADC_CONFIG_GAIN_1P4;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_1_3:
+        {
+          regval |= SAADC_CONFIG_GAIN_1P3;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_1_2:
+        {
+          regval |= SAADC_CONFIG_GAIN_1P2;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_1:
+        {
+          regval |= SAADC_CONFIG_GAIN_1;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_2:
+        {
+          regval |= SAADC_CONFIG_GAIN_2;
+          break;
+        }
+
+      case NRF54L_ADC_GAIN_2_7:
+        {
+          regval |= SAADC_CONFIG_GAIN_2P7;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid cfg->gain: %d\n", cfg->gain);
+        }
+    }
+
+  /* Reference control */
+
+  switch (cfg->refsel)
+    {
+      case NRF54L_ADC_REFSEL_INTERNAL:
+        {
+          regval |= SAADC_CONFIG_REFSEL_INTERNAL;
+          break;
+        }
+
+      case NRF54L_ADC_REFSEL_EXTERNAL:
+        {
+          regval |= SAADC_CONFIG_REFSEL_EXTERNAL;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid cfg->refsel: %d\n", cfg->refsel);
+        }
+    }
+
+  /* Acquisition time */
+
+  switch (cfg->tacq)
+    {
+      case NRF54L_ADC_TACQ_3US:
+        {
+          regval |= SAADC_CONFIG_TACQ_3US;
+          break;
+        }
+
+      case NRF54L_ADC_TACQ_5US:
+        {
+          regval |= SAADC_CONFIG_TACQ_5US;
+          break;
+        }
+
+      case NRF54L_ADC_TACQ_10US:
+        {
+          regval |= SAADC_CONFIG_TACQ_10US;
+          break;
+        }
+
+      case NRF54L_ADC_TACQ_15US:
+        {
+          regval |= SAADC_CONFIG_TACQ_15US;
+          break;
+        }
+
+      case NRF54L_ADC_TACQ_20US:
+        {
+          regval |= SAADC_CONFIG_TACQ_20US;
+          break;
+        }
+
+      case NRF54L_ADC_TACQ_40US:
+        {
+          regval |= SAADC_CONFIG_TACQ_40US;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid cfg->tacq: %d\n", cfg->tacq);
+        }
+    }
+
+  /* Conversion time */
+
+  regval |= SAADC_CONFIG_TCONV_2US;
+
+  /* Singe-ended or differential mode */
+
+  switch (cfg->mode)
+    {
+      case NRF54L_ADC_MODE_SE:
+        {
+          regval |= SAADC_CONFIG_MODE_SE;
+          break;
+        }
+
+      case NRF54L_ADC_MODE_DIFF:
+        {
+          regval |= SAADC_CONFIG_MODE_DIFF;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid cfg->mode: %d\n", cfg->mode);
+        }
+    }
+
+  /* Burst mode is configured per channel on L15 */
+
+#ifdef CONFIG_ARCH_CHIP_NRF54L15
+  switch (cfg->burst)
+    {
+      case NRF54L_ADC_BURST_DISABLE:
+        {
+          regval |= SAADC_CONFIG_BURS_DIS;
+          break;
+        }
+
+      case NRF54L_ADC_BURST_ENABLE:
+        {
+          regval |= SAADC_CONFIG_BURS_EN;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid cfg->burst: %d\n", cfg->burst);
+        }
+    }
+#endif
+
+  return regval;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_chanpsel
+ ****************************************************************************/
+
+static uint32_t nrf54l_adc_chanpsel(int psel)
+{
+  uint32_t regval = 0;
+
+  /* AIN0 through AIN7 are located on different P1 pins on L15 and LM20 */
+
+#ifdef CONFIG_ARCH_CHIP_NRF54L15
+  static const uint8_t pins[8] =
+  {
+    4, 5, 6, 7, 11, 12, 13, 14
+  };
+#else
+  static const uint8_t pins[8] =
+  {
+    0, 31, 30, 29, 6, 5, 4, 3
+  };
+#endif
+
+  switch (psel)
+    {
+      case NRF54L_ADC_IN_NC:
+        {
+          regval = SAADC_CHPSEL_NC;
+          break;
+        }
+
+      case NRF54L_ADC_IN_IN0:
+      case NRF54L_ADC_IN_IN1:
+      case NRF54L_ADC_IN_IN2:
+      case NRF54L_ADC_IN_IN3:
+      case NRF54L_ADC_IN_IN4:
+      case NRF54L_ADC_IN_IN5:
+      case NRF54L_ADC_IN_IN6:
+      case NRF54L_ADC_IN_IN7:
+        {
+          regval = SAADC_CHPSEL_ANALOG | (1 << SAADC_CHPSEL_PORT_SHIFT) |
+                   pins[psel - NRF54L_ADC_IN_IN0];
+          break;
+        }
+
+      case NRF54L_ADC_IN_VDD:
+        {
+          regval = SAADC_CHPSEL_INTERNAL | SAADC_CHPSEL_VDD;
+          break;
+        }
+
+      case NRF54L_ADC_IN_AVDD:
+        {
+          regval = SAADC_CHPSEL_INTERNAL | SAADC_CHPSEL_AVDD;
+          break;
+        }
+
+      case NRF54L_ADC_IN_DVDD:
+        {
+          regval = SAADC_CHPSEL_INTERNAL | SAADC_CHPSEL_DVDD;
+          break;
+        }
+
+      default:
+        {
+          aerr("ERROR: invalid psel: %d\n", psel);
+        }
+    }
+
+  return regval;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_chancfg
+ *
+ * Description:
+ *   Configure ADC channel
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_chancfg(struct nrf54l_adc_s *priv, uint8_t chan,
+                              struct nrf54l_adc_channel_s *cfg)
+{
+  uint32_t regval = 0;
+  int      ret    = OK;
+
+  DEBUGASSERT(priv);
+
+  /* Configure positive input */
+
+  regval = nrf54l_adc_chanpsel(cfg->p_psel);
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_CHPSELP_OFFSET(chan), regval);
+
+  /* Configure negative input */
+
+  regval = nrf54l_adc_chanpsel(cfg->n_psel);
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_CHPSELN_OFFSET(chan), regval);
+
+  /* Get channel configuration */
+
+  regval = nrf54l_adc_ch_config(cfg);
+
+  /* Write channel configuration */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_CHCONFIG_OFFSET(chan), regval);
+
+#ifdef CONFIG_NRF54L_SAADC_LIMITS
+  /* Configure limits */
+
+  regval = ((uint32_t)(uint16_t)cfg->limith << SAADC_CHLIMIT_HIGH_SHIFT) |
+           ((uint32_t)(uint16_t)cfg->limitl << SAADC_CHLIMIT_LOW_SHIFT);
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_CHLIMIT_OFFSET(chan), regval);
+#endif
+
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_bind
+ *
+ * Description:
+ *   Bind the upper-half driver callbacks to the lower-half implementation.
+ *   This must be called early in order to receive ADC event notifications.
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_bind(struct adc_dev_s *dev,
+                           const struct adc_callback_s *callback)
+{
+  struct nrf54l_adc_s *priv = (struct nrf54l_adc_s *) dev->ad_priv;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  priv->cb = callback;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_reset
+ *
+ * Description:
+ *   Reset the ADC device.  Called early to initialize the hardware.
+ *   This is called, before adc_setup() and on error conditions.
+ *
+ ****************************************************************************/
+
+static void nrf54l_adc_reset(struct adc_dev_s *dev)
+{
+  struct nrf54l_adc_s *priv = (struct nrf54l_adc_s *) dev->ad_priv;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  /* TODO */
+
+  UNUSED(priv);
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_setup
+ *
+ * Description:
+ *   Configure the ADC. This method is called the first time that the ADC
+ *   device is opened.  This will occur when the port is first opened.
+ *   This setup includes configuring and attaching ADC interrupts.
+ *   Interrupts are all disabled upon return.
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_setup(struct adc_dev_s *dev)
+{
+  struct nrf54l_adc_s *priv = (struct nrf54l_adc_s *) dev->ad_priv;
+  int                 i    = 0;
+  int                 ret  = OK;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  /* Disable ADC */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_ENABLE_OFFSET, 0);
+
+  /* Configure ADC */
+
+  ret = nrf54l_adc_configure(priv);
+  if (ret < 0)
+    {
+      aerr("ERROR: nrf54l_adc_configure failed: %d\n", ret);
+      goto errout;
+    }
+
+  /* Configure ADC channels */
+
+  for (i = 0; i < priv->chan_len; i += 1)
+    {
+      ret = nrf54l_adc_chancfg(priv, i, &priv->channels[i]);
+      if (ret < 0)
+        {
+          aerr("ERROR: chancfg failed: %d %d\n", i, ret);
+          goto errout;
+        }
+    }
+
+  /* Enable ADC */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_ENABLE_OFFSET, 1);
+
+  /* Calibrate ADC */
+
+  ret = nrf54l_adc_calibrate(priv);
+  if (ret < 0)
+    {
+      aerr("ERROR: adc calibration failed: %d\n", ret);
+      goto errout;
+    }
+
+  /* Attach the ADC interrupt */
+
+  ret = irq_attach(priv->irq, nrf54l_adc_isr, dev);
+  if (ret < 0)
+    {
+      aerr("ERROR: irq_attach failed: %d\n", ret);
+      goto errout;
+    }
+
+  /* Enable the ADC interrupt */
+
+  up_enable_irq(priv->irq);
+
+errout:
+  return ret;
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_shutdown
+ *
+ * Description:
+ *   Disable the ADC.  This method is called when the ADC device is closed.
+ *   This method reverses the operation the setup method.
+ *
+ ****************************************************************************/
+
+static void nrf54l_adc_shutdown(struct adc_dev_s *dev)
+{
+  struct nrf54l_adc_s *priv = (struct nrf54l_adc_s *) dev->ad_priv;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  /* Stop SAADC */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_TASKS_STOP_OFFSET, 1);
+
+  /* Wait for SAADC stopped */
+
+  while (nrf54l_adc_getreg(priv, NRF54L_SAADC_EVENTS_STOPPED_OFFSET) != 1);
+
+  /* Disable SAADC */
+
+  nrf54l_adc_putreg(priv, NRF54L_SAADC_ENABLE_OFFSET, 0);
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_rxint
+ *
+ * Description:
+ *   Call to enable or disable RX interrupts.
+ *
+ ****************************************************************************/
+
+static void nrf54l_adc_rxint(struct adc_dev_s *dev, bool enable)
+{
+  struct nrf54l_adc_s *priv   = (struct nrf54l_adc_s *) dev->ad_priv;
+  uint32_t            regval = 0;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  ainfo("RXINT enable: %d\n", enable ? 1 : 0);
+
+  regval = SAADC_INT_END;
+
+  if (enable)
+    {
+      nrf54l_adc_putreg(priv, NRF54L_SAADC_INTENSET_OFFSET, regval);
+    }
+  else
+    {
+      nrf54l_adc_putreg(priv, NRF54L_SAADC_INTENCLR_OFFSET, regval);
+    }
+}
+
+/****************************************************************************
+ * Name: nrf54l_adc_ioctl
+ *
+ * Description:
+ *   All ioctl calls will be routed through this method.
+ *
+ ****************************************************************************/
+
+static int nrf54l_adc_ioctl(struct adc_dev_s *dev, int cmd,
+                            unsigned long arg)
+{
+  struct nrf54l_adc_s *priv = (struct nrf54l_adc_s *) dev->ad_priv;
+  int ret                  = OK;
+
+  DEBUGASSERT(dev);
+  DEBUGASSERT(priv);
+
+  switch (cmd)
+    {
+      case ANIOC_TRIGGER:
+        {
+          /* Start ADC */
+
+          nrf54l_adc_putreg(priv, NRF54L_SAADC_TASKS_START_OFFSET, 1);
+
+          /* Trigger first sample */
+
+          nrf54l_adc_putreg(priv, NRF54L_SAADC_TASKS_SAMPLE_OFFSET, 1);
+        }
+        break;
+
+      case ANIOC_GET_NCHANNELS:
+        {
+          /* Return the number of configured channels */
+
+          ret = priv->chan_len;
+        }
+        break;
+
+      default:
+        {
+          aerr("ERROR: Unknown cmd: %d\n", cmd);
+          ret = -ENOTTY;
+        }
+        break;
+    }
+
+  return ret;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf54l_adcinitialize
+ *
+ * Description:
+ *   Initialize the ADC. See nrf54l_adc.c for more details.
+ *
+ * Input Parameters:
+ *   chanlist  - channels configuration
+ *   nchannels - number of channels
+ *
+ * Returned Value:
+ *   Valid ADC device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+struct adc_dev_s *nrf54l_adcinitialize(
+    const struct nrf54l_adc_channel_s *chan, int channels)
+{
+  struct adc_dev_s   *dev  = NULL;
+  struct nrf54l_adc_s *priv = NULL;
+  int                 i    = 0;
+
+  DEBUGASSERT(chan != NULL);
+  DEBUGASSERT(channels <= CONFIG_NRF54L_SAADC_CHANNELS);
+
+#ifdef CONFIG_NRF54L_SAADC_TIMER
+  if (channels > 1)
+    {
+      aerr("ERROR: timer trigger works only for 1 channel!\n");
+      goto errout;
+    }
+#endif
+
+  /* Get device */
+
+  dev = &g_nrf54l_adc;
+
+  /* Get private data */
+
+  priv = (struct nrf54l_adc_s *) dev->ad_priv;
+
+  /* Copy channels configuration */
+
+  ainfo("channels: %d\n", channels);
+
+  for (i = 0; i < channels; i += 1)
+    {
+      memcpy(&priv->channels[i], &chan[i],
+             sizeof(struct nrf54l_adc_channel_s));
+    }
+
+  priv->chan_len = channels;
+
+#ifdef CONFIG_NRF54L_SAADC_TIMER
+errout:
+#endif
+  return dev;
+}
diff --git a/arch/arm/src/nrf54l/nrf54l_adc.h b/arch/arm/src/nrf54l/nrf54l_adc.h
new file mode 100644
index 00000000000..af1ab2b619e
--- /dev/null
+++ b/arch/arm/src/nrf54l/nrf54l_adc.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+ * arch/arm/src/nrf54l/nrf54l_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_NRF54L_NRF54L_ADC_H
+#define __ARCH_ARM_SRC_NRF54L_NRF54L_ADC_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include "chip.h"
+
+#include <nuttx/analog/adc.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* ADC input */
+
+enum nrf54l_adc_ain_e
+{
+  NRF54L_ADC_IN_NC       = 0,   /* Not connected */
+  NRF54L_ADC_IN_IN0      = 1,   /* Analog input 0 */
+  NRF54L_ADC_IN_IN1      = 2,   /* Analog input 1 */
+  NRF54L_ADC_IN_IN2      = 3,   /* Analog input 2 */
+  NRF54L_ADC_IN_IN3      = 4,   /* Analog input 3 */
+  NRF54L_ADC_IN_IN4      = 5,   /* Analog input 4 */
+  NRF54L_ADC_IN_IN5      = 6,   /* Analog input 5 */
+  NRF54L_ADC_IN_IN6      = 7,   /* Analog input 6 */
+  NRF54L_ADC_IN_IN7      = 8,   /* Analog input 7 */
+  NRF54L_ADC_IN_VDD      = 9,   /* VDD */
+  NRF54L_ADC_IN_AVDD     = 10,  /* Internal analog supply */
+  NRF54L_ADC_IN_DVDD     = 11,  /* Internal digital supply */
+};
+
+/* Gain control */
+
+enum nrf54l_adc_gain_e
+{
+  NRF54L_ADC_GAIN_2   = 0,       /* 2 */
+  NRF54L_ADC_GAIN_1   = 1,       /* 1 */
+  NRF54L_ADC_GAIN_2_3 = 2,       /* 2/3 */
+  NRF54L_ADC_GAIN_1_2 = 3,       /* 1/2 */
+  NRF54L_ADC_GAIN_2_5 = 4,       /* 2/5 */
+  NRF54L_ADC_GAIN_1_3 = 5,       /* 1/3 */
+  NRF54L_ADC_GAIN_2_7 = 6,       /* 2/7 */
+  NRF54L_ADC_GAIN_1_4 = 7        /* 1/4 */
+};
+
+/* Reference control */
+
+enum nrf54l_adc_refsel_e
+{
+  NRF54L_ADC_REFSEL_INTERNAL = 0, /* Internal reference (0.9V) */
+  NRF54L_ADC_REFSEL_EXTERNAL = 1  /* External reference */
+};
+
+/* Acquisition time control */
+
+enum nrf54l_adc_tacq_e
+{
+  NRF54L_ADC_TACQ_3US  = 0,      /* 3 us */
+  NRF54L_ADC_TACQ_5US  = 1,      /* 5 us */
+  NRF54L_ADC_TACQ_10US = 2,      /* 10 us */
+  NRF54L_ADC_TACQ_15US = 3,      /* 15 us */
+  NRF54L_ADC_TACQ_20US = 4,      /* 20 us */
+  NRF54L_ADC_TACQ_40US = 5       /* 40 us */
+};
+
+/* ADC mode control */
+
+enum nrf54l_adc_mode_e
+{
+  NRF54L_ADC_MODE_SE   = 0,      /* Single-ended mode */
+  NRF54L_ADC_MODE_DIFF = 1       /* Differential mode */
+};
+
+/* ADC burst control */
+
+enum nrf54l_adc_burst_e
+{
+  NRF54L_ADC_BURST_DISABLE = 0,  /* Disable burst mode */
+  NRF54L_ADC_BURST_ENABLE  = 1   /* Enable burst mode */
+};
+
+/* NRF54L ADC channel configuration */
+
+struct nrf54l_adc_channel_s
+{
+  uint32_t p_psel;              /* P pin */
+  uint32_t n_psel;              /* N pin */
+#ifdef CONFIG_NRF54L_SAADC_LIMITS
+  int16_t limith;               /* High limit */
+  int16_t limitl;               /* Low limit */
+#endif
+  uint8_t gain:3;               /* Gain control */
+  uint8_t refsel:1;             /* Reference control */
+  uint8_t tacq:3;               /* Acquisition time */
+  uint8_t mode:1;               /* Single-ended or differential mode */
+  uint8_t burst:1;              /* Burst mode */
+  uint8_t _res:7;               /* Reserved */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: nrf54l_adcinitialize
+ *
+ * Description:
+ *   Initialize the ADC with a list of channel configurations. External
+ *   analog and reference pins must be disconnected from digital inputs,
+ *   outputs and pulls by the board before opening the device.
+ *
+ * Input Parameters:
+ *   chan     - Channel configurations
+ *   channels - Number of channels
+ *
+ * Returned Value:
+ *   Valid ADC device structure reference on success; a NULL on failure
+ *
+ ****************************************************************************/
+
+struct adc_dev_s *nrf54l_adcinitialize(
+    const struct nrf54l_adc_channel_s *chan,
+    int channels);
+
+#endif /* __ARCH_ARM_SRC_NRF54L_NRF54L_ADC_H */

Reply via email to