blob: 3f34e4d8cbbf48c84f4d9f1a71f630d2a516871f [file]
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2025 Analog Devices, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT adi_max32_wut_timer
#include <zephyr/init.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys/clock.h>
#include <zephyr/spinlock.h>
#include <zephyr/irq.h>
#include <wut.h>
#include <wrap_max32_lp.h>
#include <wrap_max32_sys.h>
#define WUT_NODE DT_INST_PARENT(0)
#define WUT_REGS ((mxc_wut_regs_t *)DT_REG_ADDR(WUT_NODE))
#define WUT_PRESCALER DT_PROP(WUT_NODE, prescaler)
#define WUT_CLK_SRC DT_PROP_OR(WUT_NODE, clock_source, ADI_MAX32_PRPH_CLK_SRC_ERTCO)
#define WUT_CLOCK_FREQ (32768 / WUT_PRESCALER)
#define CYC_PER_TICK (WUT_CLOCK_FREQ / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define MAX_CYC UINT32_MAX
#define MAX_TICKS ((MAX_CYC / 2) / CYC_PER_TICK)
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = DT_IRQN(WUT_NODE);
#endif
/*
* Allow at least three timer cycles before next tick
*/
#define MIN_DELAY_CYC 3
BUILD_ASSERT(CYC_PER_TICK >= MIN_DELAY_CYC,
"CONFIG_SYS_CLOCK_TICKS_PER_SEC is too high for WUT clock frequency");
static struct k_spinlock lock;
/* Counter value when last announcement was made */
static uint32_t last_count;
static void max32_wut_timer_isr(const void *arg)
{
ARG_UNUSED(arg);
MXC_WUT_ClearFlags(WUT_REGS);
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t curr = MXC_WUT_GetCount(WUT_REGS);
uint32_t delta_ticks = (curr - last_count) / CYC_PER_TICK;
last_count += delta_ticks * CYC_PER_TICK;
#if !IS_ENABLED(CONFIG_TICKLESS_KERNEL)
/* Set up next compare for non-tickless mode */
uint32_t next_cyc = last_count + CYC_PER_TICK;
if ((next_cyc - curr) < MIN_DELAY_CYC) {
next_cyc += CYC_PER_TICK;
}
MXC_WUT_SetCompare(WUT_REGS, next_cyc);
#endif
k_spin_unlock(&lock, key);
sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? delta_ticks : 1);
}
void sys_clock_set_timeout(uint32_t ticks, bool idle)
{
ARG_UNUSED(idle);
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return;
}
ticks = CLAMP(ticks, 1, MAX_TICKS) - 1;
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t curr = MXC_WUT_GetCount(WUT_REGS);
uint32_t next;
uint32_t adj, cyc = ticks * CYC_PER_TICK;
adj = (curr - last_count) + (CYC_PER_TICK - 1);
if (cyc <= MAX_CYC - adj) {
cyc += adj;
} else {
cyc = MAX_CYC;
}
cyc = (cyc / CYC_PER_TICK) * CYC_PER_TICK;
next = last_count + cyc;
if ((next - curr) < MIN_DELAY_CYC) {
next = curr + MIN_DELAY_CYC;
}
MXC_WUT_SetCompare(WUT_REGS, next);
k_spin_unlock(&lock, key);
}
uint32_t sys_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t curr = MXC_WUT_GetCount(WUT_REGS);
uint32_t delta_ticks = (curr - last_count) / CYC_PER_TICK;
k_spin_unlock(&lock, key);
return delta_ticks;
}
uint64_t sys_clock_cycle_get_64(void)
{
uint32_t cnt = MXC_WUT_GetCount(WUT_REGS);
return (uint64_t)cnt * sys_clock_hw_cycles_per_sec() / WUT_CLOCK_FREQ;
}
uint32_t sys_clock_cycle_get_32(void)
{
return (uint32_t)sys_clock_cycle_get_64();
}
static void max32_wut_irq_config(void)
{
IRQ_CONNECT(DT_IRQN(WUT_NODE), DT_IRQ(WUT_NODE, priority),
max32_wut_timer_isr, NULL, 0);
irq_enable(DT_IRQN(WUT_NODE));
}
#ifdef CONFIG_PM_S2RAM
void sys_clock_idle_exit(void)
{
if (!irq_is_enabled(DT_IRQN(WUT_NODE))) {
max32_wut_irq_config();
if (DT_NODE_HAS_PROP(WUT_NODE, wakeup_source)) {
MXC_LP_EnableWUTAlarmWakeup();
}
}
}
#endif /* CONFIG_PM_S2RAM */
static int sys_clock_driver_init(void)
{
uint8_t prescaler_lo, prescaler_hi;
mxc_wut_pres_t pres;
mxc_wut_cfg_t wut_cfg;
/* Select 32kHz clock source */
Wrap_MXC_SYS_Select32KClockSource(WUT_CLK_SRC);
/* Calculate prescaler register values */
prescaler_lo = FIELD_GET(GENMASK(2, 0), LOG2(WUT_PRESCALER));
prescaler_hi = FIELD_GET(BIT(3), LOG2(WUT_PRESCALER));
pres = (prescaler_hi << MXC_F_WUT_CTRL_PRES3_POS) |
(prescaler_lo << MXC_F_WUT_CTRL_PRES_POS);
/* Initialize WUT peripheral */
MXC_WUT_Init(WUT_REGS, pres);
/* Configure for Compare mode */
wut_cfg.mode = MXC_WUT_MODE_COMPARE;
#if IS_ENABLED(CONFIG_TICKLESS_KERNEL)
wut_cfg.cmp_cnt = CYC_PER_TICK * MAX_TICKS;
#else
wut_cfg.cmp_cnt = CYC_PER_TICK;
#endif
MXC_WUT_Config(WUT_REGS, &wut_cfg);
last_count = 0;
MXC_WUT_SetCount(WUT_REGS, 0);
/* Setup interrupt */
max32_wut_irq_config();
/* Enable as wakeup source if configured */
if (DT_NODE_HAS_PROP(WUT_NODE, wakeup_source)) {
MXC_LP_EnableWUTAlarmWakeup();
}
/* Start the timer */
MXC_WUT_Enable(WUT_REGS);
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);