blob: 811e00cc5903ebf360e1b9426deecdcd6ad2853b [file]
/*
* Copyright (c) 2021-2026 Espressif Systems (Shanghai) Co., Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <soc/soc_caps.h>
#include <soc/soc.h>
#include <hal/systimer_hal.h>
#include <hal/systimer_ll.h>
#include <esp_private/systimer.h>
#include <rom/ets_sys.h>
#include <esp_attr.h>
#include <zephyr/drivers/interrupt_controller/intc_esp32.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/sys/clock.h>
#include <soc.h>
#include <zephyr/init.h>
#include <zephyr/spinlock.h>
#include "esp32_sys_timer.h"
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = DT_IRQN(DT_NODELABEL(systimer0));
#endif
#if defined(CONFIG_PM)
static uint64_t systimer_pre_idle;
static uint64_t lptim_pre_idle;
static bool timeout_idle;
#endif
/* Systimer HAL layer object */
static systimer_hal_context_t systimer_hal;
static void set_systimer_alarm(uint64_t time)
{
systimer_hal_select_alarm_mode(&systimer_hal,
SYSTIMER_ALARM_OS_TICK_CORE0, SYSTIMER_ALARM_MODE_ONESHOT);
systimer_counter_value_t alarm = {.val = time};
systimer_ll_enable_alarm(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, false);
systimer_ll_set_alarm_target(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, alarm.val);
systimer_ll_apply_alarm_value(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0);
systimer_ll_enable_alarm(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, true);
systimer_ll_enable_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, true);
}
static uint64_t get_systimer_alarm(void)
{
return systimer_hal_get_counter_value(&systimer_hal, SYSTIMER_COUNTER_OS_TICK);
}
/* Counter value sampled by sys_clock_disable(), reported by the read below
* once the hardware is gone.
*/
static uint64_t stopped_cycles;
/*
* A free-running counter and a one-shot alarm: a COMPARE_ORDERED backend. The
* counter is 52 bits wide, read back as a 64-bit value. Every SoC this driver
* builds for defines SYSTIMER_LL_ALARM_MISS_COMPENSATE, so an alarm armed at or
* behind the counter is raised at once instead of being lost, and the target
* needs no minimum distance.
*/
#define TIMER_CORE_BACKEND_COMPARE_ORDERED
#define TIMER_CORE_COUNTER_WIDTH 52
static inline uint64_t timer_driver_cycle_get(void)
{
/* sys_clock_disable() deinitializes the HAL and clears dev; a stray core
* read afterwards must not dereference it. Report the value the counter
* stopped at, so the elapsed span holds instead of stepping backwards.
*/
if (systimer_hal.dev == NULL) {
return stopped_cycles;
}
return get_systimer_alarm();
}
static void timer_driver_set_compare(uint64_t cycles)
{
/* Same post-sys_clock_disable() guard as timer_driver_cycle_get(): once
* the HAL is deinitialized and dev cleared, a stray arm must not
* dereference it.
*/
if (systimer_hal.dev == NULL) {
return;
}
set_systimer_alarm(cycles);
}
#include "system_timer_generic.h"
static void IRAM_ATTR sys_timer_isr(void *arg)
{
ARG_UNUSED(arg);
systimer_ll_clear_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0);
timer_core_announce();
}
void sys_clock_disable(void)
{
stopped_cycles = get_systimer_alarm();
systimer_ll_enable_alarm(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, false);
systimer_ll_enable_alarm_int(systimer_hal.dev, SYSTIMER_ALARM_OS_TICK_CORE0, false);
systimer_hal_deinit(&systimer_hal);
}
#if defined(CONFIG_PM)
void sys_clock_idle_enter(uint32_t ticks)
{
sys_clock_set_timeout(ticks, false);
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL) || systimer_hal.dev == NULL) {
return;
}
uint64_t timeout_us = k_ticks_to_us_ceil64(ticks);
lptim_pre_idle = esp32_lptim_hook_on_lpm_entry(timeout_us);
systimer_pre_idle = get_systimer_alarm();
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 = esp32_lptim_hook_on_lpm_exit();
uint64_t systimer_now = get_systimer_alarm();
uint64_t lptim_diff = lptim_now - lptim_pre_idle;
uint64_t systimer_diff = systimer_now - systimer_pre_idle;
uint64_t expected_cycles =
(lptim_diff * sys_clock_hw_cycles_per_sec()) / esp32_lptim_hook_get_freq();
uint64_t missed_cycles = 0;
if (expected_cycles > systimer_diff) {
missed_cycles = expected_cycles - systimer_diff;
}
uint64_t new_value =
systimer_hal_get_counter_value(&systimer_hal, SYSTIMER_COUNTER_OS_TICK) +
missed_cycles;
/* Compensate systimer missed cycles while in sleep */
systimer_ll_set_counter_value(systimer_hal.dev, SYSTIMER_COUNTER_OS_TICK, new_value);
systimer_ll_apply_counter_value(systimer_hal.dev, SYSTIMER_COUNTER_OS_TICK);
timeout_idle = false;
/* Announce OS ticks as systimer remained stalled while in light sleep */
timer_core_announce_from(key);
}
#endif
static int sys_clock_driver_init(void)
{
int ret;
ret = esp_intr_alloc(
DT_IRQ_BY_IDX(DT_NODELABEL(systimer0), 0, irq),
ESP_PRIO_TO_FLAGS(DT_IRQ_BY_IDX(DT_NODELABEL(systimer0), 0, priority)) |
ESP_INT_FLAGS_CHECK(DT_IRQ_BY_IDX(DT_NODELABEL(systimer0), 0, flags)) |
ESP_INTR_FLAG_IRAM,
sys_timer_isr, NULL, NULL);
if (ret != 0) {
return ret;
}
systimer_hal_init(&systimer_hal);
systimer_hal_connect_alarm_counter(&systimer_hal,
SYSTIMER_ALARM_OS_TICK_CORE0, SYSTIMER_COUNTER_OS_TICK);
systimer_hal_enable_counter(&systimer_hal, SYSTIMER_COUNTER_OS_TICK);
systimer_hal_counter_can_stall_by_cpu(&systimer_hal, SYSTIMER_COUNTER_OS_TICK, 0, true);
#if defined(CONFIG_SMP)
systimer_hal_counter_can_stall_by_cpu(&systimer_hal, SYSTIMER_COUNTER_OS_TICK, 1, true);
#endif
/* Seed the announce baseline from the systimer and arm the first tick. */
timer_core_init();
return 0;
}
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);