blob: 9f966841dd940956435370f7bc379081bb7cd78f [file]
/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2026 Espressif Systems (Shanghai) Co., Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/init.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys/clock.h>
#include <zephyr/irq.h>
#include "xtensa_sys_timer.h"
#define TIMER_IRQ UTIL_CAT(XCHAL_TIMER, \
UTIL_CAT(CONFIG_XTENSA_TIMER_ID, _INTERRUPT))
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = UTIL_CAT(XCHAL_TIMER,
UTIL_CAT(CONFIG_XTENSA_TIMER_ID, _INTERRUPT));
#endif
static uint32_t ccount_compensation;
#ifdef CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK
static uint32_t ccount_pre_idle;
static uint64_t lptim_pre_idle;
static bool timeout_idle;
#endif
static uint32_t ccount_comp(void)
{
if (IS_ENABLED(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK)) {
return ccount_compensation;
}
return 0;
}
static void set_ccompare(uint32_t val)
{
__asm__ volatile ("wsr.CCOMPARE" STRINGIFY(CONFIG_XTENSA_TIMER_ID) " %0"
:: "r"(val));
}
static uint32_t ccount_raw(void)
{
uint32_t val;
__asm__ volatile ("rsr.CCOUNT %0" : "=r"(val));
return val;
}
static uint32_t ccount(void)
{
return ccount_raw() + ccount_comp();
}
/*
* Free-running 32-bit CCOUNT plus a CCOMPARE register that fires only on
* CCOUNT == CCOMPARE, so an already-past target is missed until CCOUNT wraps.
* That is the COMPARE_EXACT backend: the core rewrites through its verify loop,
* replacing the MIN_DELAY floor this driver used to apply by hand. Writing
* CCOMPARE clears the pending interrupt. The cycle count carries the low-power
* compensation (ccount_comp), so the raw register value written is the target
* minus that offset.
*/
#define TIMER_CORE_BACKEND_COMPARE_EXACT
static inline uint32_t timer_driver_cycle_get(void)
{
return ccount();
}
static inline void timer_driver_set_compare(uint64_t cycles)
{
set_ccompare((uint32_t)cycles - ccount_comp());
}
#include "system_timer_generic.h"
static void ccompare_isr(const void *arg)
{
ARG_UNUSED(arg);
/* Disarm the IRQ. Writing CCOMPARE is what clears the pending timer
* interrupt, and the match is on equality, so a compare one behind the
* current count both acknowledges it and puts the next match a whole
* counter period away, which is as far as a 32-bit comparator reaches.
* This interrupt then fires once for the deadline it was armed with,
* whether or not another one is programmed afterwards.
*/
set_ccompare(ccount_raw() - 1);
timer_core_announce();
}
#ifdef CONFIG_SMP
void smp_timer_init(void)
{
timer_core_smp_prime();
irq_enable(TIMER_IRQ);
}
#endif
#ifdef CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK
void sys_clock_idle_enter(uint32_t ticks)
{
/* Arm the comparator for the wakeup, then hand off to the low-power
* timer that keeps time while CCOUNT is stalled.
*/
sys_clock_set_timeout(ticks, false);
/* ticks may be SYS_CLOCK_IDLE_FOREVER, which would overflow the conversion.
* The core has already clamped what it armed above, so clamp this the
* same way.
*/
uint32_t lp_ticks = MIN(ticks, k_cyc_to_ticks_floor32(TIMER_CORE_ALARM_MAX_CYCLES));
uint64_t timeout_us = k_ticks_to_us_ceil64(lp_ticks);
lptim_pre_idle = z_xtensa_lptim_hook_on_lpm_entry(timeout_us);
ccount_pre_idle = ccount();
timeout_idle = true;
}
void sys_clock_idle_exit(void)
{
if (!timeout_idle) {
return;
}
k_spinlock_key_t key = sys_clock_lock();
uint64_t lptim_now = z_xtensa_lptim_hook_on_lpm_exit();
uint32_t ccount_diff = (uint32_t)ccount() - ccount_pre_idle;
uint64_t lptim_diff = lptim_now - lptim_pre_idle;
uint64_t expected_cycles =
(lptim_diff * sys_clock_hw_cycles_per_sec()) / z_xtensa_lptim_hook_get_freq();
uint64_t missed_cycles = 0;
if (expected_cycles > ccount_diff) {
/* CCOUNT stalled during LPM. Fold the missed cycles into the
* compensation so the cycle count picks up the real elapsed time,
* and hand the span to the core directly: a sleep can outrun the
* 32-bit CCOUNT, so the announce cannot come from the counter.
* What CCOUNT did see stays there for the next announce.
*/
missed_cycles = expected_cycles - ccount_diff;
ccount_compensation += (uint32_t)missed_cycles;
}
timeout_idle = false;
timer_core_announce_cycles64_from(key, missed_cycles);
}
#endif /* CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK */
static int sys_clock_driver_init(void)
{
IRQ_CONNECT(TIMER_IRQ, 0, ccompare_isr, 0, 0);
timer_core_init();
irq_enable(TIMER_IRQ);
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);