From aeaf718fc9db4c1713c1a2ff7bb0f2bc367600cc Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Sat, 25 Jul 2026 02:24:12 +0200 Subject: [PATCH] rp23xx: Add /dev/timer driver on the TIMER0/TIMER1 blocks. The RP2350 has two system timer blocks (TIMER0, TIMER1), each a free-running 64-bit counter incremented once per microsecond by the TICKS block (set up in rp23xx_clock.c). They are independent of the ARM SysTick that drives the OS tick, so they are free for application use, but the port had no driver for them. Add rp23xx_timer.c, a NuttX timer lower-half that binds a block to a /dev/timerN device. It uses ALARM0 of the block, which matches the low 32 bits of the microsecond counter, to implement single-shot and periodic timeouts with 1 us resolution and a maximum interval of 2^32 - 1 us (~71.5 minutes). Periodic reloads are scheduled relative to the previous expiry to avoid drift, but never behind the counter (an alarm set in the past would not match until the 32-bit counter wraps). Enable with CONFIG_RP23XX_TIMER (which selects CONFIG_TIMER), then turn on each block independently: CONFIG_RP23XX_TIMER0 registers /dev/timer0 and CONFIG_RP23XX_TIMER1 registers /dev/timer1. A block claimed by the tickless oneshot (CONFIG_RP23XX_SYSTIMER_TICKLESS) is excluded from these choices in Kconfig, so the two features can be enabled together without colliding on the same block or its alarm IRQ. Tested on Pimoroni Pico Plus 2 (RP2350B) hardware with examples/timer and a CMSIS-DAP probe. The device registers as /dev/timer0; the ALARM0 interrupt fires at the programmed period (verified over SWD at the configured 1 s interval, not a busy loop), and the full path -- alarm match, the driver ISR, the timer notification and delivery of the SIGNO to user space -- reaches the example's signal handler and increments its counter. Assisted-by: Claude Opus 4.8 (1M context) Signed-off-by: Marco Casaroli --- Documentation/platforms/arm/rp23xx/index.rst | 24 ++ arch/arm/src/rp23xx/CMakeLists.txt | 4 + arch/arm/src/rp23xx/Kconfig | 40 ++ arch/arm/src/rp23xx/Make.defs | 4 + arch/arm/src/rp23xx/rp23xx_timer.c | 395 ++++++++++++++++++ arch/arm/src/rp23xx/rp23xx_timer.h | 74 ++++ .../rp23xx/common/src/rp23xx_common_bringup.c | 25 ++ 7 files changed, 566 insertions(+) create mode 100644 arch/arm/src/rp23xx/rp23xx_timer.c create mode 100644 arch/arm/src/rp23xx/rp23xx_timer.h diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst index e788ace692c..b8422a9e688 100644 --- a/Documentation/platforms/arm/rp23xx/index.rst +++ b/Documentation/platforms/arm/rp23xx/index.rst @@ -43,6 +43,7 @@ SRAM Boot Working Requires external SWD debugger PSRAM Working Three modes of heap allocation described below TRNG Working Hardware RNG at /dev/random and /dev/urandom Flash MTD Working Unused flash tail as an MTD device, answers BIOC_XIPBASE +Timer Working /dev/timerN on TIMER0/TIMER1, 1 us resolution ============== ============ ===== Installation @@ -171,6 +172,29 @@ external PSRAM is configured as a separate user heap called `psram` and can be used through the global variable `g_psramheap` after including `rp23xx_heaps.h` +Timer +===== + +The RP2350 has two system timer blocks, TIMER0 and TIMER1, each a +free-running 64-bit counter incremented once per microsecond. They are +independent of the ARM SysTick that drives the OS tick, so they are +available for application timers. + +Enable the driver with `RP23XX_TIMER` (which selects `TIMER`), then turn on +each block you want: `RP23XX_TIMER0` registers `/dev/timer0` (TIMER0) and +`RP23XX_TIMER1` registers `/dev/timer1` (TIMER1). Each device implements the +standard NuttX timer lower-half: single-shot or periodic timeouts with 1 us +resolution and a maximum interval of 2^32 - 1 us (about 71.5 minutes), driven +by ALARM0 of the block. + +A block claimed by the tickless-OS oneshot +(`RP23XX_SYSTIMER_TICKLESS`) is removed from the choices above, so a +`/dev/timer` device and the tickless OS time source never collide on the same +block -- you can run both at once (e.g. tickless on TIMER0, `/dev/timer1` on +TIMER1). With tickless disabled, both `/dev/timer0` and +`/dev/timer1` are available. The `examples/timer` application can exercise a +device; point `CONFIG_EXAMPLES_TIMER_DEVNAME` at the block you enabled. + Programming ============ diff --git a/arch/arm/src/rp23xx/CMakeLists.txt b/arch/arm/src/rp23xx/CMakeLists.txt index 0d21226da91..5c52dca2aa0 100644 --- a/arch/arm/src/rp23xx/CMakeLists.txt +++ b/arch/arm/src/rp23xx/CMakeLists.txt @@ -56,6 +56,10 @@ if(CONFIG_RP23XX_PWM) list(APPEND SRCS rp23xx_pwm.c) endif() +if(CONFIG_RP23XX_TIMER) + list(APPEND SRCS rp23xx_timer.c) +endif() + if(CONFIG_RP23XX_I2C) list(APPEND SRCS rp23xx_i2c.c) endif() diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig index 0a4f29e33a0..e3f2f499a05 100644 --- a/arch/arm/src/rp23xx/Kconfig +++ b/arch/arm/src/rp23xx/Kconfig @@ -736,6 +736,46 @@ config RP23XX_SPISD_SPI_CH endif # SPISD Configuration +##################################################################### +# Timer Configuration +##################################################################### + +menuconfig RP23XX_TIMER + bool "System timer (/dev/timer) support" + default n + select TIMER + ---help--- + Build the /dev/timerN driver on top of the RP2350 system timer + blocks (TIMER0 and TIMER1). Each block is a free-running 64-bit + microsecond counter, independent of the ARM SysTick that drives + the OS tick, so it is available for application timers. The + driver uses ALARM0 of a block for a single-shot or periodic + timeout with 1 us resolution. + + Enable one or both blocks below. A block claimed by the tickless + oneshot (CONFIG_RP23XX_SYSTIMER_TICKLESS) is removed from the list + so the two features never collide on the same block. + +if RP23XX_TIMER + +config RP23XX_TIMER0 + bool "Register TIMER0 as /dev/timer0" + default y + depends on !RP23XX_SYSTIMER_TICKLESS_TIMER0 + ---help--- + Bind the TIMER0 block to /dev/timer0. Unavailable when the + tickless oneshot is configured to use TIMER0. + +config RP23XX_TIMER1 + bool "Register TIMER1 as /dev/timer1" + default n + depends on !RP23XX_SYSTIMER_TICKLESS_TIMER1 + ---help--- + Bind the TIMER1 block to /dev/timer1. Unavailable when the + tickless oneshot is configured to use TIMER1. + +endif # RP23XX_TIMER + ##################################################################### # ADC Configuration ##################################################################### diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs index 71f3475b6c9..fb41bb5c6a8 100644 --- a/arch/arm/src/rp23xx/Make.defs +++ b/arch/arm/src/rp23xx/Make.defs @@ -60,6 +60,10 @@ ifeq ($(CONFIG_RP23XX_PWM),y) CHIP_CSRCS += rp23xx_pwm.c endif +ifeq ($(CONFIG_RP23XX_TIMER),y) +CHIP_CSRCS += rp23xx_timer.c +endif + ifeq ($(CONFIG_RP23XX_I2C),y) CHIP_CSRCS += rp23xx_i2c.c endif diff --git a/arch/arm/src/rp23xx/rp23xx_timer.c b/arch/arm/src/rp23xx/rp23xx_timer.c new file mode 100644 index 00000000000..91e4e66ccf4 --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_timer.c @@ -0,0 +1,395 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_timer.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 + * + * A /dev/timerN driver on top of an RP2350 system timer block (TIMER0 or + * TIMER1). Each block is a free-running 64-bit counter incremented once per + * microsecond (the 1 MHz tick is set up by the TICKS block in + * rp23xx_clock.c) and independent of the ARM SysTick that drives the OS + * tick, so it is free for application timers. This driver uses ALARM0 of + * the selected block, which matches against the low 32 bits of the counter, + * giving a single-shot or periodic timeout with 1 us resolution and a + * maximum interval of 2^32 - 1 us (~71.5 minutes). + * + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "chip.h" +#include "arm_internal.h" +#include "rp23xx_timer.h" +#include "hardware/rp23xx_timer.h" +#include "hardware/rp23xx_memorymap.h" + +#ifdef CONFIG_RP23XX_TIMER + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* This driver uses ALARM0 of the block; alarm 0 is bit 0 in the ARMED, + * INTE, INTF, INTR and INTS registers. + */ + +#define TIMER_ALARM0_BIT (1 << 0) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct rp23xx_timer_lowerhalf_s +{ + const struct timer_ops_s *ops; /* Lower-half operations (must be 1st) */ + uint32_t base; /* TIMER block base address */ + int irq; /* ALARM0 interrupt number */ + tccb_t callback; /* Current user interrupt callback */ + void *arg; /* Argument passed to upper half cb */ + uint32_t timeout; /* The current timeout value (us) */ + bool started; /* True if the alarm is armed */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int rp23xx_timer_start(FAR struct timer_lowerhalf_s *lower); +static int rp23xx_timer_stop(FAR struct timer_lowerhalf_s *lower); +static int rp23xx_timer_getstatus(FAR struct timer_lowerhalf_s *lower, + FAR struct timer_status_s *status); +static int rp23xx_timer_settimeout(FAR struct timer_lowerhalf_s *lower, + uint32_t timeout); +static void rp23xx_timer_setcallback(FAR struct timer_lowerhalf_s *lower, + tccb_t callback, FAR void *arg); +static int rp23xx_timer_maxtimeout(FAR struct timer_lowerhalf_s *lower, + FAR uint32_t *maxtimeout); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct timer_ops_s g_rp23xx_timer_ops = +{ + .start = rp23xx_timer_start, + .stop = rp23xx_timer_stop, + .getstatus = rp23xx_timer_getstatus, + .settimeout = rp23xx_timer_settimeout, + .setcallback = rp23xx_timer_setcallback, + .ioctl = NULL, + .maxtimeout = rp23xx_timer_maxtimeout, +}; + +static struct rp23xx_timer_lowerhalf_s g_rp23xx_timer_lowerhalf[2] = +{ + { + .ops = &g_rp23xx_timer_ops, + }, + { + .ops = &g_rp23xx_timer_ops, + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_timer_interrupt + * + * Description: + * The ALARM0 interrupt handler. Acknowledges the alarm, invokes the + * upper-half callback and, for a periodic timer, re-arms the alarm for the + * next expiry. + * + ****************************************************************************/ + +static int rp23xx_timer_interrupt(int irq, FAR void *context, FAR void *arg) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = arg; + uint32_t next; + uint32_t now; + uint32_t target; + + /* Acknowledge the alarm (write-1-clear of the latched interrupt). The + * alarm auto-disarms in hardware when it fires. + */ + + putreg32(TIMER_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET); + + if (priv->callback != NULL) + { + next = priv->timeout; + if (priv->callback(&next, priv->arg) && next > 0) + { + /* Periodic: schedule the next expiry relative to the previous + * target so the period does not drift with interrupt latency, but + * never behind the counter -- an alarm set in the past would not + * match until the 32-bit microsecond counter wraps ~71 minutes + * later. + */ + + priv->timeout = next; + now = getreg32(priv->base + RP23XX_TIMER_TIMERAWL_OFFSET); + target = getreg32(priv->base + RP23XX_TIMER_ALARM0_OFFSET) + next; + + if ((int32_t)(target - now) <= 0) + { + target = now + next; + } + + putreg32(target, priv->base + RP23XX_TIMER_ALARM0_OFFSET); + } + else + { + rp23xx_timer_stop((FAR struct timer_lowerhalf_s *)priv); + } + } + else + { + rp23xx_timer_stop((FAR struct timer_lowerhalf_s *)priv); + } + + return OK; +} + +/**************************************************************************** + * Name: rp23xx_timer_start + ****************************************************************************/ + +static int rp23xx_timer_start(FAR struct timer_lowerhalf_s *lower) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = + (FAR struct rp23xx_timer_lowerhalf_s *)lower; + irqstate_t flags; + uint32_t now; + + if (priv->timeout == 0) + { + return -EINVAL; + } + + flags = enter_critical_section(); + + now = getreg32(priv->base + RP23XX_TIMER_TIMERAWL_OFFSET); + putreg32(now + priv->timeout, priv->base + RP23XX_TIMER_ALARM0_OFFSET); + modifyreg32(priv->base + RP23XX_TIMER_INTE_OFFSET, 0, TIMER_ALARM0_BIT); + priv->started = true; + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: rp23xx_timer_stop + ****************************************************************************/ + +static int rp23xx_timer_stop(FAR struct timer_lowerhalf_s *lower) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = + (FAR struct rp23xx_timer_lowerhalf_s *)lower; + irqstate_t flags; + + flags = enter_critical_section(); + + /* Disable the interrupt, force-disarm the alarm (writing a 1 to the ARMED + * bit disarms it) and clear any latched status. + */ + + modifyreg32(priv->base + RP23XX_TIMER_INTE_OFFSET, TIMER_ALARM0_BIT, 0); + putreg32(TIMER_ALARM0_BIT, priv->base + RP23XX_TIMER_ARMED_OFFSET); + putreg32(TIMER_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET); + priv->started = false; + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: rp23xx_timer_getstatus + ****************************************************************************/ + +static int rp23xx_timer_getstatus(FAR struct timer_lowerhalf_s *lower, + FAR struct timer_status_s *status) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = + (FAR struct rp23xx_timer_lowerhalf_s *)lower; + irqstate_t flags; + + flags = enter_critical_section(); + + status->flags = 0; + if (priv->started) + { + status->flags |= TCFLAGS_ACTIVE; + } + + if (priv->callback != NULL) + { + status->flags |= TCFLAGS_HANDLER; + } + + status->timeout = priv->timeout; + + if (priv->started) + { + uint32_t now = getreg32(priv->base + RP23XX_TIMER_TIMERAWL_OFFSET); + uint32_t target = getreg32(priv->base + RP23XX_TIMER_ALARM0_OFFSET); + + /* Unsigned subtraction wraps mod 2^32, matching the counter. */ + + status->timeleft = target - now; + } + else + { + status->timeleft = 0; + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: rp23xx_timer_settimeout + ****************************************************************************/ + +static int rp23xx_timer_settimeout(FAR struct timer_lowerhalf_s *lower, + uint32_t timeout) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = + (FAR struct rp23xx_timer_lowerhalf_s *)lower; + irqstate_t flags; + + if (timeout == 0) + { + return -EINVAL; + } + + flags = enter_critical_section(); + + priv->timeout = timeout; + if (priv->started) + { + uint32_t now = getreg32(priv->base + RP23XX_TIMER_TIMERAWL_OFFSET); + putreg32(now + timeout, priv->base + RP23XX_TIMER_ALARM0_OFFSET); + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: rp23xx_timer_setcallback + ****************************************************************************/ + +static void rp23xx_timer_setcallback(FAR struct timer_lowerhalf_s *lower, + tccb_t callback, FAR void *arg) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv = + (FAR struct rp23xx_timer_lowerhalf_s *)lower; + irqstate_t flags; + + flags = enter_critical_section(); + priv->callback = callback; + priv->arg = arg; + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: rp23xx_timer_maxtimeout + ****************************************************************************/ + +static int rp23xx_timer_maxtimeout(FAR struct timer_lowerhalf_s *lower, + FAR uint32_t *maxtimeout) +{ + UNUSED(lower); + + /* ALARM0 matches the low 32 bits of the microsecond counter. */ + + *maxtimeout = UINT32_MAX; + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_timer_initialize + * + * Description: + * Bind TIMER0 or TIMER1 to a /dev/timerN device. See rp23xx_timer.h. + * + ****************************************************************************/ + +int rp23xx_timer_initialize(FAR const char *devpath, int instance) +{ + FAR struct rp23xx_timer_lowerhalf_s *priv; + FAR void *handle; + + if (instance != 0 && instance != 1) + { + return -EINVAL; + } + + priv = &g_rp23xx_timer_lowerhalf[instance]; + + priv->base = (instance == 0) ? RP23XX_TIMER0_BASE : RP23XX_TIMER1_BASE; + priv->irq = (instance == 0) ? + RP23XX_TIMER0_IRQ_0 : RP23XX_TIMER1_IRQ_0; + priv->callback = NULL; + priv->arg = NULL; + priv->timeout = 0; + priv->started = false; + + /* Start from a known-idle state: interrupt disabled, alarm disarmed, any + * latched status cleared. + */ + + modifyreg32(priv->base + RP23XX_TIMER_INTE_OFFSET, TIMER_ALARM0_BIT, 0); + putreg32(TIMER_ALARM0_BIT, priv->base + RP23XX_TIMER_ARMED_OFFSET); + putreg32(TIMER_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET); + + irq_attach(priv->irq, rp23xx_timer_interrupt, priv); + up_enable_irq(priv->irq); + + handle = timer_register(devpath, (FAR struct timer_lowerhalf_s *)priv); + if (handle == NULL) + { + up_disable_irq(priv->irq); + irq_detach(priv->irq); + return -EEXIST; + } + + return OK; +} + +#endif /* CONFIG_RP23XX_TIMER */ diff --git a/arch/arm/src/rp23xx/rp23xx_timer.h b/arch/arm/src/rp23xx/rp23xx_timer.h new file mode 100644 index 00000000000..9372593cbeb --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_timer.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_timer.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_RP23XX_RP23XX_TIMER_H +#define __ARCH_ARM_SRC_RP23XX_RP23XX_TIMER_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: rp23xx_timer_initialize + * + * Description: + * Bind one of the two RP2350 system timer blocks (TIMER0 or TIMER1) to a + * /dev/timerN device. Each block is a free-running 64-bit microsecond + * counter; this driver uses its ALARM0 to implement the NuttX timer + * lower-half (single-shot and periodic timeouts, 1 us resolution, up to + * 2^32 - 1 us ~= 71.5 minutes per interval). + * + * Input Parameters: + * devpath - The full path to the timer device, e.g. "/dev/timer0". + * instance - The timer block to use: 0 for TIMER0, 1 for TIMER1. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int rp23xx_timer_initialize(FAR const char *devpath, int instance); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_TIMER_H */ diff --git a/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c b/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c index e293897eef0..33645f16490 100644 --- a/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c +++ b/boards/arm/rp23xx/common/src/rp23xx_common_bringup.c @@ -45,6 +45,10 @@ #include "rp23xx_adc.h" #endif +#ifdef CONFIG_RP23XX_TIMER +#include "rp23xx_timer.h" +#endif + #ifdef CONFIG_WATCHDOG # include "rp23xx_wdt.h" #endif @@ -492,6 +496,27 @@ int rp23xx_common_bringup(void) #endif /* defined(CONFIG_ADC) && defined(CONFIG_RP23XX_ADC) */ + /* Initialize the system timer blocks as /dev/timerN. Each enabled block + * is independent; a block claimed by the tickless oneshot is excluded in + * Kconfig, so the two features can be enabled together. + */ + +#ifdef CONFIG_RP23XX_TIMER0 + ret = rp23xx_timer_initialize("/dev/timer0", 0); + if (ret < 0) + { + syslog(LOG_ERR, "Failed to initialize /dev/timer0: %d\n", ret); + } +#endif + +#ifdef CONFIG_RP23XX_TIMER1 + ret = rp23xx_timer_initialize("/dev/timer1", 1); + if (ret < 0) + { + syslog(LOG_ERR, "Failed to initialize /dev/timer1: %d\n", ret); + } +#endif + /* Initialize board neo-pixel */ #if defined(CONFIG_RP23XX_BOARD_HAS_WS2812) && defined(CONFIG_WS2812)