rp23xx: Add tickless OS support using the RP2350 hardware TIMER.

The port could only run with a periodic ARM SysTick tick.  Add an alarm/
oneshot lower-half backed by an RP2350 system timer block so the scheduler
can run tickless, waking the CPU only when a timer actually expires.

rp23xx_oneshot.c implements the ONESHOT_COUNT lower-half (mirroring the
RISC-V mtimer driver).  A timer block is a free-running 64-bit microsecond
counter (clocked by the TICKS block, independent of the SysTick), which serves
directly as the monotonic time base returned by current(), so timekeeping is
exact to 1 us.  The one adaptation versus a full 64-bit compare timer is that
the RP2350 ALARM registers match only the low 32 bits of the counter:
max_delay() is therefore capped below 2^32 counts (~71.5 minutes) so the
scheduler never asks for a longer interval, and any deadline that is already
due -- or that the counter reaches while the alarm is being armed -- is
raised immediately through the INTF force register instead of waiting a full
32-bit wrap for the compare to match again.

Enabled with CONFIG_RP23XX_SYSTIMER_TICKLESS (mutually exclusive with
RP23XX_SYSTIMER_SYSTICK), which selects ONESHOT, ONESHOT_COUNT and
ALARM_ARCH; up_timer_initialize() then hands the oneshot to
up_alarm_set_lowerhalf().  ARCH_CHIP_RP23XX now selects ARCH_HAVE_TICKLESS.
The block is selectable with CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER0 (default)
or _TIMER1; the chosen block is claimed exclusively by the scheduler and is
excluded from the /dev/timer driver (CONFIG_RP23XX_TIMER) in Kconfig, so the
tickless clock and a /dev/timer device can coexist on different blocks.

Tested on Pimoroni Pico Plus 2 (RP2350B) hardware with CONFIG_SCHED_TICKLESS
and CONFIG_SCHED_TICKLESS_ALARM: the image boots to nsh and keeps accurate
time -- "uptime" advances at real-time rate (17 s over a measured 17.4 s) and
"sleep 4" blocks for 4.25 s of wall time -- confirming the oneshot both drives
the scheduler and provides a correct 1 MHz monotonic clock.

Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-07-25 02:50:26 +02:00 committed by Alan C. Assis
parent b00d7005b0
commit 5fd518a27c
8 changed files with 480 additions and 2 deletions

View file

@ -44,6 +44,7 @@ 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
Tickless Working Optional, RP2350 TIMER via the alarm/oneshot
============== ============ =====
Installation
@ -195,6 +196,28 @@ 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.
Tickless OS
===========
By default the OS tick is a periodic ARM SysTick interrupt. The RP2350 can
instead run tickless, driving the scheduler from a hardware alarm so the CPU
is only interrupted when a timer actually expires.
Enable `RP23XX_SYSTIMER_TICKLESS` together with `SCHED_TICKLESS` and
`SCHED_TICKLESS_ALARM`. Choose the timer block with the "Tickless timer
block" option (`RP23XX_SYSTIMER_TICKLESS_TIMER0`, the default, or
`RP23XX_SYSTIMER_TICKLESS_TIMER1`). The system time is then taken from that
block -- a free-running 64-bit microsecond counter -- and its ALARM0 provides
the next-event interrupt through the alarm/oneshot lower-half
(`rp23xx_oneshot.c`). Because the 64-bit counter is the monotonic time base,
timekeeping is exact to 1 us, and a single alarm can schedule up to ~71
minutes ahead, so long idle periods need no wake-ups. This is mutually
exclusive with `RP23XX_SYSTIMER_SYSTICK`.
The block chosen here is claimed exclusively by the scheduler and is removed
from the `/dev/timer` driver's choices (see the Timer section), so the tickless
clock and a `/dev/timer` device can run at the same time on different blocks.
Programming
============

View file

@ -394,6 +394,7 @@ config ARCH_CHIP_RP23XX
select ARM_HAVE_DSP
select ARCH_HAVE_FPU
select ARCH_HAVE_CUSTOM_TESTSET
select ARCH_HAVE_TICKLESS
select ARCH_BOARD_COMMON
---help---
Raspberry Pi RP23XX architectures (ARM dual Cortex-M33 or RISC-V).

View file

@ -60,6 +60,10 @@ if(CONFIG_RP23XX_TIMER)
list(APPEND SRCS rp23xx_timer.c)
endif()
if(CONFIG_RP23XX_SYSTIMER_TICKLESS)
list(APPEND SRCS rp23xx_oneshot.c)
endif()
if(CONFIG_RP23XX_I2C)
list(APPEND SRCS rp23xx_i2c.c)
endif()

View file

@ -23,6 +23,40 @@ config RP23XX_SYSTIMER_SYSTICK
---help---
Use ARM SysTick.
config RP23XX_SYSTIMER_TICKLESS
bool "Tickless OS using the RP2350 hardware TIMER"
default n
depends on !RP23XX_SYSTIMER_SYSTICK
select ONESHOT
select ONESHOT_COUNT
select ALARM_ARCH
---help---
Use an RP2350 system timer block (TIMER0 or TIMER1, chosen below)
as the OS time source and drive the scheduler through the
alarm/oneshot interface. Each block is a free-running 64-bit
microsecond counter, independent of the ARM SysTick. This is the
arch-side support required by CONFIG_SCHED_TICKLESS (with
CONFIG_SCHED_TICKLESS_ALARM) and gives long tickless idle intervals
-- up to ~71 minutes per alarm -- with 1 us resolution.
choice
prompt "Tickless timer block"
default RP23XX_SYSTIMER_TICKLESS_TIMER0
depends on RP23XX_SYSTIMER_TICKLESS
---help---
Which RP2350 system timer block drives the tickless OS clock. The
block used here is claimed exclusively by the scheduler; the other
block stays available for the /dev/timer driver (CONFIG_RP23XX_TIMER),
so both features can be enabled at once.
config RP23XX_SYSTIMER_TICKLESS_TIMER0
bool "TIMER0"
config RP23XX_SYSTIMER_TICKLESS_TIMER1
bool "TIMER1"
endchoice
#####################################################################
# UART Configuration
#####################################################################

View file

@ -64,6 +64,10 @@ ifeq ($(CONFIG_RP23XX_TIMER),y)
CHIP_CSRCS += rp23xx_timer.c
endif
ifeq ($(CONFIG_RP23XX_SYSTIMER_TICKLESS),y)
CHIP_CSRCS += rp23xx_oneshot.c
endif
ifeq ($(CONFIG_RP23XX_I2C),y)
CHIP_CSRCS += rp23xx_i2c.c
endif

View file

@ -0,0 +1,331 @@
/****************************************************************************
* arch/arm/src/rp23xx/rp23xx_oneshot.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 oneshot lower-half backed by an RP2350 system timer block (TIMER0 or
* TIMER1, selected by CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMERn), used to drive
* the tickless scheduler via up_alarm_set_lowerhalf(). The 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 serves directly
* as the monotonic time base returned by current(). The other block is left
* free for the /dev/timer driver.
*
* The one wrinkle versus a full 64-bit compare timer (e.g. the RISC-V mtimer
* this mirrors) is that the RP2350 ALARM registers match only the low 32
* bits of the counter. max_delay() is therefore capped below 2^32 counts so
* the scheduler never asks for a longer interval, and any deadline that is
* already due (or that the counter reaches while the alarm is being armed)
* is raised at once through the INTF force register rather than waiting a
* full 32-bit wrap (~71.5 minutes) for the compare to match again.
*
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/timers/oneshot.h>
#include "chip.h"
#include "arm_internal.h"
#include "rp23xx_oneshot.h"
#include "hardware/rp23xx_timer.h"
#include "hardware/rp23xx_memorymap.h"
#ifdef CONFIG_RP23XX_SYSTIMER_TICKLESS
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* This driver uses ALARM0 of TIMER0; alarm 0 is bit 0 in the ARMED, INTE,
* INTF, INTR and INTS registers.
*/
#define ONESHOT_ALARM0_BIT (1 << 0)
/* The counter is clocked at 1 MHz (1 us per count). */
#define ONESHOT_FREQ_HZ 1000000
/* The ALARM compare is only 32 bits wide, so never schedule further ahead
* than this many counts.
*/
#define ONESHOT_MAX_COUNTS ((clkcnt_t)UINT32_MAX)
/****************************************************************************
* Private Types
****************************************************************************/
/* Cast-compatible with struct oneshot_lowerhalf_s (embedded first). */
struct rp23xx_oneshot_lowerhalf_s
{
struct oneshot_lowerhalf_s lower; /* Lower-half instance (must be first) */
uint32_t base; /* TIMER block base address */
int irq; /* ALARM0 interrupt number */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static clkcnt_t rp23xx_oneshot_max_delay(FAR struct oneshot_lowerhalf_s *l);
static clkcnt_t rp23xx_oneshot_current(FAR struct oneshot_lowerhalf_s *l);
static void rp23xx_oneshot_start_absolute(FAR struct oneshot_lowerhalf_s *l,
clkcnt_t expected);
static void rp23xx_oneshot_start(FAR struct oneshot_lowerhalf_s *l,
clkcnt_t delta);
static void rp23xx_oneshot_cancel(FAR struct oneshot_lowerhalf_s *l);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct oneshot_operations_s g_rp23xx_oneshot_ops =
{
.current = rp23xx_oneshot_current,
.start = rp23xx_oneshot_start,
.start_absolute = rp23xx_oneshot_start_absolute,
.cancel = rp23xx_oneshot_cancel,
.max_delay = rp23xx_oneshot_max_delay,
};
static struct rp23xx_oneshot_lowerhalf_s g_rp23xx_oneshot =
{
.lower.ops = &g_rp23xx_oneshot_ops,
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: rp23xx_oneshot_count
*
* Description:
* Read the free-running 64-bit microsecond counter, guarding against a
* carry from the low word into the high word between the two reads.
*
****************************************************************************/
static uint64_t
rp23xx_oneshot_count(FAR struct rp23xx_oneshot_lowerhalf_s *p)
{
uint32_t hi = getreg32(p->base + RP23XX_TIMER_TIMERAWH_OFFSET);
uint32_t lo;
uint32_t next;
for (; ; )
{
lo = getreg32(p->base + RP23XX_TIMER_TIMERAWL_OFFSET);
next = getreg32(p->base + RP23XX_TIMER_TIMERAWH_OFFSET);
if (hi == next)
{
break;
}
hi = next;
}
return ((uint64_t)hi << 32) | lo;
}
/****************************************************************************
* Name: rp23xx_oneshot_setcompare
*
* Description:
* Arm ALARM0 for the absolute counter value "target". Must be called with
* interrupts disabled. If the target is already due -- or the counter
* reaches it while the alarm is being armed -- the interrupt is forced
* immediately through INTF instead of waiting a full 32-bit wrap for the
* compare to match.
*
****************************************************************************/
static void
rp23xx_oneshot_setcompare(FAR struct rp23xx_oneshot_lowerhalf_s *priv,
uint64_t target)
{
/* Clear any stale force left from a previous expiry. */
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, ONESHOT_ALARM0_BIT, 0);
if ((int64_t)(target - rp23xx_oneshot_count(priv)) <= 0)
{
/* Already due: raise the interrupt now. */
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, 0,
ONESHOT_ALARM0_BIT);
}
else
{
/* Writing ALARM0 arms it against the low 32 bits of the counter. */
putreg32((uint32_t)target, priv->base + RP23XX_TIMER_ALARM0_OFFSET);
/* Close the race: if the counter reached the target while we were
* arming, force so we do not wait a full wrap for the match.
*/
if ((int64_t)(rp23xx_oneshot_count(priv) - target) >= 0)
{
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, 0,
ONESHOT_ALARM0_BIT);
}
}
}
/****************************************************************************
* Name: rp23xx_oneshot_interrupt
****************************************************************************/
static int rp23xx_oneshot_interrupt(int irq, FAR void *context,
FAR void *arg)
{
FAR struct rp23xx_oneshot_lowerhalf_s *priv = arg;
/* Disarm the alarm, drop any force and clear the latched match. */
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_ARMED_OFFSET);
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, ONESHOT_ALARM0_BIT, 0);
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET);
oneshot_process_callback(&priv->lower);
return OK;
}
/****************************************************************************
* Name: rp23xx_oneshot_max_delay
****************************************************************************/
static clkcnt_t rp23xx_oneshot_max_delay(FAR struct oneshot_lowerhalf_s *l)
{
UNUSED(l);
return ONESHOT_MAX_COUNTS;
}
/****************************************************************************
* Name: rp23xx_oneshot_current
****************************************************************************/
static clkcnt_t rp23xx_oneshot_current(FAR struct oneshot_lowerhalf_s *l)
{
return rp23xx_oneshot_count((FAR struct rp23xx_oneshot_lowerhalf_s *)l);
}
/****************************************************************************
* Name: rp23xx_oneshot_start_absolute
****************************************************************************/
static void rp23xx_oneshot_start_absolute(FAR struct oneshot_lowerhalf_s *l,
clkcnt_t expected)
{
FAR struct rp23xx_oneshot_lowerhalf_s *priv =
(FAR struct rp23xx_oneshot_lowerhalf_s *)l;
irqstate_t flags = up_irq_save();
rp23xx_oneshot_setcompare(priv, expected);
up_irq_restore(flags);
}
/****************************************************************************
* Name: rp23xx_oneshot_start
****************************************************************************/
static void rp23xx_oneshot_start(FAR struct oneshot_lowerhalf_s *l,
clkcnt_t delta)
{
FAR struct rp23xx_oneshot_lowerhalf_s *priv =
(FAR struct rp23xx_oneshot_lowerhalf_s *)l;
irqstate_t flags = up_irq_save();
rp23xx_oneshot_setcompare(priv, rp23xx_oneshot_count(priv) + delta);
up_irq_restore(flags);
}
/****************************************************************************
* Name: rp23xx_oneshot_cancel
****************************************************************************/
static void rp23xx_oneshot_cancel(FAR struct oneshot_lowerhalf_s *l)
{
FAR struct rp23xx_oneshot_lowerhalf_s *priv =
(FAR struct rp23xx_oneshot_lowerhalf_s *)l;
irqstate_t flags = up_irq_save();
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_ARMED_OFFSET);
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, ONESHOT_ALARM0_BIT, 0);
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET);
up_irq_restore(flags);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rp23xx_oneshot_initialize
*
* Description:
* Initialise the selected system timer block (TIMER0 or TIMER1) as a
* oneshot lower-half. See rp23xx_oneshot.h.
*
****************************************************************************/
FAR struct oneshot_lowerhalf_s *rp23xx_oneshot_initialize(void)
{
FAR struct rp23xx_oneshot_lowerhalf_s *priv = &g_rp23xx_oneshot;
#ifdef CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER1
priv->base = RP23XX_TIMER1_BASE;
priv->irq = RP23XX_TIMER1_IRQ_0;
#else
priv->base = RP23XX_TIMER0_BASE;
priv->irq = RP23XX_TIMER0_IRQ_0;
#endif
oneshot_count_init(&priv->lower, ONESHOT_FREQ_HZ);
/* Start from a known-idle state: alarm disarmed, no force, status clear,
* then enable the ALARM0 interrupt at the peripheral and the NVIC.
*/
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_ARMED_OFFSET);
modifyreg32(priv->base + RP23XX_TIMER_INTF_OFFSET, ONESHOT_ALARM0_BIT, 0);
putreg32(ONESHOT_ALARM0_BIT, priv->base + RP23XX_TIMER_INTR_OFFSET);
modifyreg32(priv->base + RP23XX_TIMER_INTE_OFFSET, 0, ONESHOT_ALARM0_BIT);
irq_attach(priv->irq, rp23xx_oneshot_interrupt, priv);
up_enable_irq(priv->irq);
return &priv->lower;
}
#endif /* CONFIG_RP23XX_SYSTIMER_TICKLESS */

View file

@ -0,0 +1,71 @@
/****************************************************************************
* arch/arm/src/rp23xx/rp23xx_oneshot.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_ONESHOT_H
#define __ARCH_ARM_SRC_RP23XX_RP23XX_ONESHOT_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/timers/oneshot.h>
#ifndef __ASSEMBLY__
#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: rp23xx_oneshot_initialize
*
* Description:
* Initialise the RP2350 system timer TIMER0 as a oneshot lower-half and
* return it, ready to be handed to up_alarm_set_lowerhalf() to drive the
* tickless scheduler. TIMER0 is a free-running 64-bit microsecond counter
* whose ALARM0 provides the compare event.
*
* Returned Value:
* The oneshot lower-half instance on success; NULL never happens (the
* instance is statically allocated).
*
****************************************************************************/
FAR struct oneshot_lowerhalf_s *rp23xx_oneshot_initialize(void);
#undef EXTERN
#if defined(__cplusplus)
}
#endif
#endif /* __ASSEMBLY__ */
#endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_ONESHOT_H */

View file

@ -40,6 +40,11 @@
#include "chip.h"
#include "systick.h"
#ifdef CONFIG_RP23XX_SYSTIMER_TICKLESS
# include <nuttx/timers/arch_alarm.h>
# include "rp23xx_oneshot.h"
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
@ -76,7 +81,8 @@
* of the systems.
*
****************************************************************************/
#ifndef CONFIG_RP23XX_SYSTIMER_SYSTICK
#if !defined(CONFIG_RP23XX_SYSTIMER_SYSTICK) && \
!defined(CONFIG_RP23XX_SYSTIMER_TICKLESS)
static int rp23xx_timerisr(int irq, uint32_t *regs, void *arg)
{
/* Process timer interrupt */
@ -110,7 +116,11 @@ void up_timer_initialize(void)
regval |= (NVIC_SYSH_PRIORITY_DEFAULT << NVIC_SYSH_PRIORITY_PR15_SHIFT);
putreg32(regval, NVIC_SYSH12_15_PRIORITY);
#ifdef CONFIG_RP23XX_SYSTIMER_SYSTICK
#if defined(CONFIG_RP23XX_SYSTIMER_TICKLESS)
/* Drive the tickless scheduler from the selected RP2350 hardware TIMER. */
up_alarm_set_lowerhalf(rp23xx_oneshot_initialize());
#elif defined(CONFIG_RP23XX_SYSTIMER_SYSTICK)
up_timer_set_lowerhalf(systick_initialize(true, SYSTICK_CLOCK, -1));
#else