blob: 87dd94524d7d8473f81abd96c7c16df721b0b6b5 [file]
/*
* Copyright (c) 2026 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/init.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/clock.h>
#include <fsl_stm.h>
#define STM_NODE DT_CHOSEN(zephyr_system_timer)
BUILD_ASSERT(DT_HAS_CHOSEN(zephyr_system_timer),
"zephyr,system-timer must be set to an nxp,stm node");
BUILD_ASSERT(DT_NODE_HAS_COMPAT(STM_NODE, nxp_stm),
"zephyr,system-timer must point to an nxp,stm compatible node");
#define STM_BASE ((STM_Type *)DT_REG_ADDR(STM_NODE))
#define STM_IRQN DT_IRQN(STM_NODE)
#define STM_IRQ_PRIORITY DT_IRQ(STM_NODE, priority)
#define STM_PRESCALER DT_PROP(STM_NODE, prescaler)
#define STM_CHANNEL kSTM_Channel_0
extern unsigned int z_clock_hw_cycles_per_sec;
static uint32_t cycles_per_tick;
static uint32_t cycles_max;
static uint32_t last_count;
static uint32_t last_elapsed;
static void stm_set_compare(uint32_t compare)
{
uint32_t now;
STM_DisableCompareChannel(STM_BASE, STM_CHANNEL);
STM_ClearStatusFlags(STM_BASE, STM_CHANNEL);
STM_SetCompare(STM_BASE, STM_CHANNEL, compare);
now = STM_GetTimerCount(STM_BASE);
if (unlikely((int32_t)(compare - now) <= 0)) {
uint32_t bump = 1U;
do {
compare = now + bump;
bump *= 2U;
STM_SetCompare(STM_BASE, STM_CHANNEL, compare);
now = STM_GetTimerCount(STM_BASE);
} while ((int32_t)(compare - now) <= 0);
}
}
static void stm_disable_compare(void)
{
STM_DisableCompareChannel(STM_BASE, STM_CHANNEL);
STM_ClearStatusFlags(STM_BASE, STM_CHANNEL);
}
static void mcux_stm_timer_isr(const void *arg)
{
ARG_UNUSED(arg);
k_spinlock_key_t key = sys_clock_lock();
uint32_t now = STM_GetTimerCount(STM_BASE);
uint32_t delta_cycles = now - last_count;
uint32_t delta_ticks = (unsigned long)delta_cycles / cycles_per_tick;
last_count += delta_ticks * cycles_per_tick;
last_elapsed = 0U;
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
stm_set_compare(last_count + cycles_per_tick);
} else {
stm_disable_compare();
}
sys_clock_announce_locked(delta_ticks, key);
}
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;
}
uint64_t wait_ticks = (uint64_t)last_elapsed + (uint64_t)ticks;
uint64_t wait_cycles = wait_ticks * (uint64_t)cycles_per_tick;
uint32_t cycles = (wait_cycles > cycles_max) ? cycles_max : (uint32_t)wait_cycles;
stm_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 = STM_GetTimerCount(STM_BASE);
uint32_t delta_cycles = now - last_count;
uint32_t delta_ticks = (unsigned long)delta_cycles / cycles_per_tick;
last_elapsed = delta_ticks;
return delta_ticks;
}
uint32_t sys_clock_cycle_get_32(void)
{
return STM_GetTimerCount(STM_BASE);
}
void sys_clock_disable(void)
{
stm_disable_compare();
STM_StopTimer(STM_BASE);
irq_disable(STM_IRQN);
}
static int sys_clock_driver_init(void)
{
const struct device *clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(STM_NODE));
clock_control_subsys_t clock_subsys =
(clock_control_subsys_t)DT_CLOCKS_CELL(STM_NODE, name);
stm_config_t config;
uint32_t clock_freq;
uint32_t timer_freq;
if (!device_is_ready(clock_dev)) {
return -ENODEV;
}
if (clock_control_on(clock_dev, clock_subsys) != 0) {
return -EINVAL;
}
if (clock_control_get_rate(clock_dev, clock_subsys, &clock_freq) != 0) {
return -EINVAL;
}
timer_freq = clock_freq / (STM_PRESCALER + 1U);
if ((timer_freq == 0U) ||
((timer_freq % CONFIG_SYS_CLOCK_TICKS_PER_SEC) != 0U)) {
return -EINVAL;
}
z_clock_hw_cycles_per_sec = timer_freq;
cycles_per_tick = timer_freq / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
cycles_max = (INT32_MAX / cycles_per_tick) * cycles_per_tick;
STM_GetDefaultConfig(&config);
config.enableIRQ = true;
config.prescale = (uint8_t)STM_PRESCALER;
STM_Init(STM_BASE, &config);
IRQ_CONNECT(STM_IRQN, STM_IRQ_PRIORITY, mcux_stm_timer_isr, NULL, 0);
irq_enable(STM_IRQN);
STM_StartTimer(STM_BASE);
last_count = (STM_GetTimerCount(STM_BASE) / cycles_per_tick) * cycles_per_tick;
last_elapsed = 0U;
stm_set_compare(last_count + cycles_per_tick);
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);