blob: 3798854481b2324d991f1b7be5b354b290765a11 [file]
/*
* Copyright 2026 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/init.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/devicetree.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/clock.h>
#include <zephyr/irq.h>
#include <fsl_rtc.h>
#define RTC_NODE DT_CHOSEN(zephyr_system_timer)
BUILD_ASSERT(DT_HAS_CHOSEN(zephyr_system_timer),
"zephyr,system-timer must be set to an nxp,rtc-jdp node");
BUILD_ASSERT(DT_NODE_HAS_COMPAT(RTC_NODE, nxp_rtc_jdp),
"zephyr,system-timer must point to an nxp,rtc-jdp compatible node");
#define RTC_JDP_BASE ((RTC_Type *)DT_REG_ADDR(RTC_NODE))
#define RTC_IRQN DT_IRQN(RTC_NODE)
#define RTC_IRQ_PRIO DT_IRQ(RTC_NODE, priority)
#define RTC_CLK_SRC DT_PROP(RTC_NODE, clock_source)
#define RTC_PRESCALER DT_PROP(RTC_NODE, prescaler)
#define RTC_CLK_FREQ DT_PROP(RTC_NODE, clock_frequency)
/*
* The RTC counter rate (clock-frequency / prescaler) is published at runtime
* through z_clock_hw_cycles_per_sec (the driver selects
* TIMER_READS_ITS_FREQUENCY_AT_RUNTIME), so the devicetree prescaler is the
* single source of truth for the tick rate.
*/
extern unsigned int z_clock_hw_cycles_per_sec;
/* Map the numeric prescaler (1/32/512/16384) to the SDK clock-divide enum */
#define RTC_JDP_DIV_ENUM \
(RTC_PRESCALER == 1 ? kRTC_ClockDivide1 \
: RTC_PRESCALER == 32 ? kRTC_ClockDivide32 \
: RTC_PRESCALER == 512 ? kRTC_ClockDivide512 \
: kRTC_ClockDivide16384)
/*
* Bounded spin waiting for an in-flight RTCVAL synchronization to finish (see
* rtc_jdp_set_compare). A sync takes a few RTC cycles (~100 us at 32 kHz); this
* bound is far larger and only prevents an unbounded spin on a stuck peripheral.
*/
#define RTC_JDP_SYNC_SPIN 100000U
/*
* Cycles the compare must stay ahead of the current count when programmed.
* RTCVAL is an equality compare that synchronizes into the slow RTC clock
* domain after the CPU samples the count; this margin absorbs that sync
* latency so the counter cannot reach the compare before it is armed.
*/
#define RTC_JDP_COMPARE_MARGIN (2U * MINIMUM_RTCVAL)
/* Runtime cycle-domain values derived from the RTC rate in init. */
static uint32_t cycles_per_tick;
static uint32_t cycles_max;
/* Absolute RTC count at the last announced tick boundary. */
static uint32_t last_count;
/* Ticks last reported by sys_clock_elapsed(), consumed by sys_clock_set_timeout(). */
static uint32_t last_elapsed;
static inline void rtc_jdp_wait_inv_clear(void)
{
for (uint32_t i = 0U; i < RTC_JDP_SYNC_SPIN; i++) {
if ((RTC_GetStatusFlags(RTC_JDP_BASE) & (uint32_t)kRTC_InvalidRTCFlag) == 0U) {
break;
}
}
}
/*
* Program the next compare (RTCVAL). RTCVAL is a one-shot equality compare
* against the free-running 32-bit counter, so it must be programmed strictly
* ahead of the count, otherwise the match is missed and only recurs after a
* full 32-bit wrap (~36 h at 32 kHz).
*/
static void rtc_jdp_set_compare(uint32_t compare)
{
uint32_t bump = RTC_JDP_COMPARE_MARGIN + 1U;
uint32_t now;
for (;;) {
/* RTCVAL must be greater than MINIMUM_RTCVAL. */
if (compare <= MINIMUM_RTCVAL) {
compare = MINIMUM_RTCVAL + 1U;
}
/*
* An RTCVAL write starts a synchronization into the RTC clock
* domain and sets INV_RTC; while INV_RTC is set the hardware
* ignores further RTCVAL writes (RM ch. 66 "Invalid RTC write").
* Wait for a previous write to synchronize before programming.
*/
rtc_jdp_wait_inv_clear();
RTC_ClearInterruptFlags(RTC_JDP_BASE, kRTC_RTCInterruptFlag);
RTC_SetRTCValue(RTC_JDP_BASE, compare);
/*
* Re-read the counter and, like the retry in mcux_stm_timer.c,
* bump the compare forward if the count has already reached (or is
* within the sync margin of) it, so the equality match is not lost
* to the sync latency sampled above.
*/
now = RTC_GetCountValue(RTC_JDP_BASE);
if (likely((int32_t)(compare - now) > (int32_t)RTC_JDP_COMPARE_MARGIN)) {
break;
}
compare = now + bump;
bump *= 2U;
}
}
void sys_clock_set_timeout(uint32_t ticks, bool idle)
{
ARG_UNUSED(idle);
__ASSERT(sys_clock_is_locked(), "system clock lock not held");
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return;
}
uint32_t cycles;
if (IS_ENABLED(CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE) && ticks == SYS_CLOCK_MAX_WAIT) {
/*
* No pending timeout and no future timer interrupt required:
* wait as long as the hardware allows.
*/
cycles = cycles_max;
} else {
uint64_t wait_ticks = (uint64_t)last_elapsed + (uint64_t)ticks;
uint64_t wait_cycles = wait_ticks * (uint64_t)cycles_per_tick;
cycles = (wait_cycles > cycles_max) ? cycles_max : (uint32_t)wait_cycles;
}
rtc_jdp_set_compare(last_count + cycles);
}
uint32_t sys_clock_elapsed(void)
{
__ASSERT(sys_clock_is_locked(), "system clock lock not held");
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
uint32_t now = RTC_GetCountValue(RTC_JDP_BASE);
uint32_t delta_ticks = (now - last_count) / cycles_per_tick;
last_elapsed = delta_ticks;
return delta_ticks;
}
uint32_t sys_clock_cycle_get_32(void)
{
/* The free-running 32-bit RTC counter is the hardware cycle counter. */
return RTC_GetCountValue(RTC_JDP_BASE);
}
void sys_clock_disable(void)
{
/*
* Terminal teardown only (e.g. before reboot/power-off). Disabling the
* counter resets the RTC logic, so neither the tick nor the cycle counter
* resumes afterwards.
*/
RTC_DisableInterrupts(RTC_JDP_BASE, kRTC_RTCInterruptEnable);
RTC_DisableRTC(RTC_JDP_BASE);
irq_disable(RTC_IRQN);
}
static void mcux_rtc_jdp_timer_isr(const void *arg)
{
ARG_UNUSED(arg);
k_spinlock_key_t key = sys_clock_lock();
uint32_t now = RTC_GetCountValue(RTC_JDP_BASE);
uint32_t delta_ticks = (now - last_count) / cycles_per_tick;
RTC_ClearInterruptFlags(RTC_JDP_BASE, kRTC_RTCInterruptFlag);
last_count += delta_ticks * cycles_per_tick;
last_elapsed = 0U;
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
/* RTCVAL is one-shot; arm the next periodic tick. */
rtc_jdp_set_compare(last_count + cycles_per_tick);
}
sys_clock_announce_locked((int32_t)delta_ticks, key);
}
static int sys_clock_driver_init(void)
{
rtc_config_t config;
uint32_t cycle_rate = RTC_CLK_FREQ / RTC_PRESCALER;
/*
* The RTC rate must divide the tick rate exactly; this also rejects a
* rate below the tick rate, which would make cycles_per_tick zero and
* fault the divisions below.
*/
if ((cycle_rate == 0U) || ((cycle_rate % CONFIG_SYS_CLOCK_TICKS_PER_SEC) != 0U)) {
return -EINVAL;
}
cycles_per_tick = cycle_rate / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
if (cycles_per_tick <= RTC_JDP_COMPARE_MARGIN) {
/* Tick period too short for the RTC compare-sync margin. */
return -EINVAL;
}
z_clock_hw_cycles_per_sec = cycle_rate;
/*
* Schedule at most half the counter range ahead; a compare further out
* would wrap past the 32-bit counter and be indistinguishable from a
* value already in the past under wrap-around arithmetic, stalling the
* tick. Keep it tick-aligned. Mirrors mcux_stm_timer.c.
*/
cycles_max = (INT32_MAX / cycles_per_tick) * cycles_per_tick;
RTC_GetDefaultConfig(&config);
config.clockSource = (rtc_clock_source_t)RTC_CLK_SRC;
config.clockDivide = RTC_JDP_DIV_ENUM;
RTC_Init(RTC_JDP_BASE, &config);
RTC_ClearInterruptFlags(RTC_JDP_BASE, kRTC_AllInterruptFlags);
IRQ_CONNECT(RTC_IRQN, RTC_IRQ_PRIO, mcux_rtc_jdp_timer_isr, NULL, 0);
irq_enable(RTC_IRQN);
/*
* Program the first compare and the match interrupt, then enable the
* counter last so it starts from a fully configured state. Enabling
* resets the RTC logic and starts the count from zero, committing the
* RTCVAL written here (RM ch. 66).
*/
RTC_SetRTCValue(RTC_JDP_BASE, cycles_per_tick);
RTC_EnableInterrupts(RTC_JDP_BASE, kRTC_RTCInterruptEnable);
RTC_EnableRTC(RTC_JDP_BASE);
last_count = 0U;
last_elapsed = 0U;
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);