arch/arm/rp23xx: Add RTC using the POWMAN always-on timer.

The RP2350 has no dedicated RTC block (the RP2040 one was removed).  Use the
POWMAN always-on timer instead: a 64-bit millisecond counter clocked from the
low-power oscillator, so it keeps running across warm resets.

Enabling CONFIG_RP23XX_RTC selects CONFIG_RTC and builds the driver, which
implements the simple up_rtc_initialize()/up_rtc_time()/up_rtc_settime()
interface backing the system clock (seconds resolution).  up_rtc_initialize()
sources the timer from the low-power oscillator at a 1 kHz tick and starts it,
preserving the value when the timer is already running.  POWMAN register writes
carry the required 0x5afe password.

Document the RTC on the rp23xx platform page: an "RTC" section explaining how
the POWMAN always-on timer is used (LPOSC clocking, the 0x5afe password and the
four 16-bit time registers, stopping the counter to reload it), with reference
links to the RP2350 datasheet Power chapter and the pico-sdk hardware_powman /
pico_aon_timer implementation the driver mirrors.  The same references are
noted in the driver header.

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 01:41:08 +02:00 committed by Xiang Xiao
parent 216edd74db
commit f0e4f61add
5 changed files with 288 additions and 0 deletions

View file

@ -45,6 +45,7 @@ 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
RTC Working POWMAN always-on timer, backs the system clock
============== ============ =====
Installation
@ -218,6 +219,44 @@ 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.
RTC
===
The RP2350 has no dedicated RTC block -- the one on the RP2040 was removed --
so NuttX drives the real-time clock from the POWMAN *always-on timer*
instead. This is a 64-bit millisecond counter that can be clocked from the
low-power oscillator (LPOSC, nominally 32.768 kHz), so it keeps counting
across warm resets and through the low-power states managed by POWMAN.
Enable it with `RP23XX_RTC` (which selects `RTC`). The driver
(`rp23xx_rtc.c`) implements the lightweight `up_rtc_initialize()`,
`up_rtc_time()` and `up_rtc_settime()` interface that backs the NuttX system
clock at one-second resolution. `up_rtc_initialize()` sources the timer from
the low-power oscillator at a 1 kHz tick and starts it, but preserves the
current value if the bootrom (or a previous boot) already left it running, so
the wall-clock time survives a warm reset.
Two POWMAN details the driver has to honour:
* Every POWMAN register write must carry the `0x5afe` password in its top 16
bits or it is silently ignored, so only the low 16 bits of each register
hold data. The 64-bit time is therefore written and read through four
16-bit registers, and bit operations use the atomic `SET`/`CLR` register
aliases.
* The counter must be stopped (`TIMER.RUN` cleared) while a new value is
loaded and then restarted, otherwise the write is not latched cleanly.
The implementation follows the POWMAN "Always-on Timer" description in the
Power chapter of the `RP2350 datasheet
<https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf>`_ and the
reference `pico-sdk hardware_powman
<https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/hardware_powman>`_
library (`powman_timer_start_lposc()`, `powman_timer_set_ms()` and
`powman_timer_get_ms()`), which the pico-sdk in turn exposes through its
`pico_aon_timer
<https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/pico_aon_timer>`_
wrapper.
Programming
============

View file

@ -109,4 +109,8 @@ if(CONFIG_RP23XX_PSRAM)
list(APPEND SRCS rp23xx_psram.c rp23xx_heaps.c)
endif()
if(CONFIG_RP23XX_RTC)
list(APPEND SRCS rp23xx_rtc.c)
endif()
target_sources(arch PRIVATE ${SRCS})

View file

@ -868,6 +868,16 @@ config RP23XX_RNG
/dev/random (and /dev/urandom when CONFIG_DEV_URANDOM selects the
architecture source) via the TRNG entropy register (EHR).
config RP23XX_RTC
bool "Enable RTC (POWMAN always-on timer)"
default n
select RTC
---help---
Use the RP2350 POWMAN always-on timer as the real-time clock backing
the system clock. The RP2350 has no dedicated RTC block; the POWMAN
timer is a 64-bit millisecond counter clocked from the low-power
oscillator, so it keeps running across warm resets.
config RP23XX_ADC
bool "Enable ADC Support"
default n

View file

@ -113,3 +113,7 @@ ifeq ($(CONFIG_RP23XX_PSRAM),y)
CHIP_CSRCS += rp23xx_psram.c
CHIP_CSRCS += rp23xx_heaps.c
endif
ifeq ($(CONFIG_RP23XX_RTC),y)
CHIP_CSRCS += rp23xx_rtc.c
endif

View file

@ -0,0 +1,231 @@
/****************************************************************************
* arch/arm/src/rp23xx/rp23xx_rtc.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
*
* The RP2350 has no dedicated RTC block (the RP2040 one was removed). This
* driver uses the POWMAN always-on timer instead: a 64-bit millisecond
* counter, clocked from the low-power oscillator so it keeps running across
* warm resets. It backs the NuttX system clock through the simple up_rtc_*
* interface (seconds resolution).
*
* Reference: the POWMAN "Always-on Timer" in the Power chapter of the RP2350
* datasheet (https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf)
* and the pico-sdk hardware_powman library
* (https://github.com/raspberrypi/pico-sdk, src/rp2_common/hardware_powman),
* whose powman_timer_start_lposc()/set_ms()/get_ms() this mirrors.
*
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <errno.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include "arm_internal.h"
#include "hardware/rp23xx_powman.h"
#ifdef CONFIG_RTC
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Every POWMAN write must carry this password in the top 16 bits, otherwise
* it is ignored (and flagged in BADPASSWD). Only the low 16 bits are data,
* which is why the 64-bit time is split across four registers.
*/
#define POWMAN_PASSWORD 0x5afe0000
/* Atomic set/clear aliases of a bus register (RP2350 memory map). */
#define POWMAN_SET_ALIAS 0x2000
#define POWMAN_CLR_ALIAS 0x3000
/* Nominal low-power oscillator frequency. */
#define LPOSC_FREQ_HZ 32768
/****************************************************************************
* Public Data
****************************************************************************/
/* Variable determines the state of the RTC module; set once the counter is
* running so the rest of the system knows the RTC is available.
*/
volatile bool g_rtc_enabled = false;
/****************************************************************************
* Private Functions
****************************************************************************/
static inline void powman_write(uint32_t reg, uint32_t value)
{
putreg32(POWMAN_PASSWORD | (value & 0xffff), reg);
}
static inline void powman_setbits(uint32_t reg, uint32_t bits)
{
putreg32(POWMAN_PASSWORD | bits, reg + POWMAN_SET_ALIAS);
}
static inline void powman_clrbits(uint32_t reg, uint32_t bits)
{
putreg32(POWMAN_PASSWORD | bits, reg + POWMAN_CLR_ALIAS);
}
/* Read the 64-bit millisecond counter, guarding against a carry from the low
* word to the high word between the two reads.
*/
static uint64_t powman_get_ms(void)
{
uint32_t hi = getreg32(RP23XX_POWMAN_READ_TIME_UPPER);
uint32_t lo;
for (; ; )
{
uint32_t next;
lo = getreg32(RP23XX_POWMAN_READ_TIME_LOWER);
next = getreg32(RP23XX_POWMAN_READ_TIME_UPPER);
if (hi == next)
{
break;
}
hi = next;
}
return ((uint64_t)hi << 32) | lo;
}
/* The counter must be stopped while its value is (re)loaded. */
static void powman_set_ms(uint64_t ms)
{
bool running =
(getreg32(RP23XX_POWMAN_TIMER) & RP23XX_POWMAN_TIMER_RUN) != 0;
if (running)
{
powman_clrbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_RUN);
}
powman_write(RP23XX_POWMAN_SET_TIME_15TO0, (uint32_t)(ms));
powman_write(RP23XX_POWMAN_SET_TIME_31TO16, (uint32_t)(ms >> 16));
powman_write(RP23XX_POWMAN_SET_TIME_47TO32, (uint32_t)(ms >> 32));
powman_write(RP23XX_POWMAN_SET_TIME_63TO48, (uint32_t)(ms >> 48));
if (running)
{
powman_setbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_RUN);
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_rtc_initialize
*
* Description:
* Initialize the POWMAN always-on timer. If it is not already running
* (e.g. left running by the bootrom), source it from the low-power
* oscillator at a 1 kHz tick and start it. Preserves the current value
* when it is already running.
*
****************************************************************************/
int up_rtc_initialize(void)
{
if ((getreg32(RP23XX_POWMAN_TIMER) & RP23XX_POWMAN_TIMER_RUN) == 0)
{
uint32_t khz_int = LPOSC_FREQ_HZ / 1000;
uint32_t khz_frac = (LPOSC_FREQ_HZ % 1000) * 65536 / 1000;
int timeout = 1000000;
powman_write(RP23XX_POWMAN_LPOSC_FREQ_KHZ_INT, khz_int);
powman_write(RP23XX_POWMAN_LPOSC_FREQ_KHZ_FRAC, khz_frac);
powman_setbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_USE_LPOSC);
powman_setbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_RUN);
/* Wait for the timer to accept the low-power oscillator source, but do
* not spin forever if it never does.
*/
while ((getreg32(RP23XX_POWMAN_TIMER) &
RP23XX_POWMAN_TIMER_USING_LPOSC) == 0 && --timeout > 0)
{
}
}
g_rtc_enabled = true;
return OK;
}
/****************************************************************************
* Name: up_rtc_time
*
* Description:
* Get the current time in seconds.
*
****************************************************************************/
time_t up_rtc_time(void)
{
return (time_t)(powman_get_ms() / 1000);
}
/****************************************************************************
* Name: up_rtc_settime
*
* Description:
* Set the RTC to the provided time value.
*
****************************************************************************/
int up_rtc_settime(FAR const struct timespec *tp)
{
irqstate_t flags;
uint64_t ms;
ms = (uint64_t)tp->tv_sec * 1000 + tp->tv_nsec / 1000000;
flags = enter_critical_section();
powman_set_ms(ms);
leave_critical_section(flags);
return OK;
}
#endif /* CONFIG_RTC */