diff --git a/Documentation/platforms/arm/rp23xx/index.rst b/Documentation/platforms/arm/rp23xx/index.rst index e34b82af760..2b673aba40e 100644 --- a/Documentation/platforms/arm/rp23xx/index.rst +++ b/Documentation/platforms/arm/rp23xx/index.rst @@ -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 +`_ and the +reference `pico-sdk 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 +`_ +wrapper. + Programming ============ diff --git a/arch/arm/src/rp23xx/CMakeLists.txt b/arch/arm/src/rp23xx/CMakeLists.txt index 814a1007859..6bd1eeaf358 100644 --- a/arch/arm/src/rp23xx/CMakeLists.txt +++ b/arch/arm/src/rp23xx/CMakeLists.txt @@ -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}) diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig index 0713e4f8721..7fd81b6f909 100644 --- a/arch/arm/src/rp23xx/Kconfig +++ b/arch/arm/src/rp23xx/Kconfig @@ -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 diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs index 040f23ff868..75366113e42 100644 --- a/arch/arm/src/rp23xx/Make.defs +++ b/arch/arm/src/rp23xx/Make.defs @@ -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 diff --git a/arch/arm/src/rp23xx/rp23xx_rtc.c b/arch/arm/src/rp23xx/rp23xx_rtc.c new file mode 100644 index 00000000000..be78ad3f64d --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_rtc.c @@ -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 + +#include +#include +#include +#include +#include + +#include +#include + +#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 */