2017-01-26 15:51:33 +01:00
|
|
|
/*
|
2018-10-23 09:08:58 -07:00
|
|
|
* Copyright (c) 2018 Intel Corporation
|
2026-01-19 12:04:16 -03:00
|
|
|
* Copyright (c) 2026 Espressif Systems (Shanghai) Co., Ltd.
|
2018-10-23 09:08:58 -07:00
|
|
|
*
|
2017-01-26 15:51:33 +01:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
2023-08-28 13:15:43 +02:00
|
|
|
#include <zephyr/init.h>
|
2022-05-06 10:25:46 +02:00
|
|
|
#include <zephyr/drivers/timer/system_timer.h>
|
2026-07-03 07:45:26 +02:00
|
|
|
#include <zephyr/sys/clock.h>
|
2022-10-17 10:24:11 +02:00
|
|
|
#include <zephyr/irq.h>
|
2017-01-26 15:51:33 +01:00
|
|
|
|
2026-01-19 12:04:16 -03:00
|
|
|
#include "xtensa_sys_timer.h"
|
|
|
|
|
|
2018-10-23 09:08:58 -07:00
|
|
|
#define TIMER_IRQ UTIL_CAT(XCHAL_TIMER, \
|
|
|
|
|
UTIL_CAT(CONFIG_XTENSA_TIMER_ID, _INTERRUPT))
|
2017-10-27 21:38:42 +05:30
|
|
|
|
2022-06-28 17:58:40 -04:00
|
|
|
#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
|
|
|
|
|
|
2026-01-19 12:04:16 -03:00
|
|
|
static uint32_t ccount_compensation;
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
#ifdef CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK
|
2026-01-19 12:04:16 -03:00
|
|
|
static uint32_t ccount_pre_idle;
|
|
|
|
|
static uint64_t lptim_pre_idle;
|
|
|
|
|
static bool timeout_idle;
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
#endif
|
2026-01-19 12:04:16 -03:00
|
|
|
|
|
|
|
|
static uint32_t ccount_comp(void)
|
|
|
|
|
{
|
|
|
|
|
if (IS_ENABLED(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK)) {
|
|
|
|
|
return ccount_compensation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-27 11:26:57 -05:00
|
|
|
static void set_ccompare(uint32_t val)
|
2017-10-27 21:38:42 +05:30
|
|
|
{
|
2018-10-23 09:08:58 -07:00
|
|
|
__asm__ volatile ("wsr.CCOMPARE" STRINGIFY(CONFIG_XTENSA_TIMER_ID) " %0"
|
|
|
|
|
:: "r"(val));
|
2017-10-27 21:38:42 +05:30
|
|
|
}
|
|
|
|
|
|
2026-08-11 13:09:04 -04:00
|
|
|
static uint32_t ccount_raw(void)
|
2017-10-27 21:38:42 +05:30
|
|
|
{
|
2020-05-27 11:26:57 -05:00
|
|
|
uint32_t val;
|
2017-10-27 21:38:42 +05:30
|
|
|
|
2018-10-23 09:08:58 -07:00
|
|
|
__asm__ volatile ("rsr.CCOUNT %0" : "=r"(val));
|
2026-08-11 13:09:04 -04:00
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static uint32_t ccount(void)
|
|
|
|
|
{
|
|
|
|
|
return ccount_raw() + ccount_comp();
|
2026-01-19 12:04:16 -03:00
|
|
|
}
|
|
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
/*
|
|
|
|
|
* 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
|
2026-07-22 09:25:23 -04:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
static inline uint32_t timer_driver_cycle_get(void)
|
|
|
|
|
{
|
|
|
|
|
return ccount();
|
|
|
|
|
}
|
2026-07-22 09:25:23 -04:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
static inline void timer_driver_set_compare(uint64_t cycles)
|
|
|
|
|
{
|
|
|
|
|
set_ccompare((uint32_t)cycles - ccount_comp());
|
2017-10-27 21:38:42 +05:30
|
|
|
}
|
|
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
#include "system_timer_generic.h"
|
|
|
|
|
|
2026-07-22 09:25:23 -04:00
|
|
|
static void ccompare_isr(const void *arg)
|
2017-10-27 21:38:42 +05:30
|
|
|
{
|
2026-07-22 09:25:23 -04:00
|
|
|
ARG_UNUSED(arg);
|
2017-10-27 21:38:42 +05:30
|
|
|
|
2026-08-11 13:09:04 -04:00
|
|
|
/* 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);
|
|
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
timer_core_announce();
|
2017-01-26 15:51:33 +01:00
|
|
|
}
|
2019-01-28 09:12:13 -08:00
|
|
|
|
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
|
void smp_timer_init(void)
|
|
|
|
|
{
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
timer_core_smp_prime();
|
2019-01-28 09:12:13 -08:00
|
|
|
irq_enable(TIMER_IRQ);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2021-11-04 12:51:39 +01:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
#ifdef CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK
|
|
|
|
|
void sys_clock_idle_enter(uint32_t ticks)
|
2021-11-04 12:51:39 +01:00
|
|
|
{
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
/* 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);
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
/* 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);
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
lptim_pre_idle = z_xtensa_lptim_hook_on_lpm_entry(timeout_us);
|
|
|
|
|
ccount_pre_idle = ccount();
|
|
|
|
|
timeout_idle = true;
|
|
|
|
|
}
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
void sys_clock_idle_exit(void)
|
|
|
|
|
{
|
|
|
|
|
if (!timeout_idle) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
k_spinlock_key_t key = sys_clock_lock();
|
2026-07-22 09:25:23 -04:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
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;
|
|
|
|
|
}
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
timeout_idle = false;
|
2026-01-19 12:04:16 -03:00
|
|
|
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
timer_core_announce_cycles64_from(key, missed_cycles);
|
2026-01-19 12:04:16 -03:00
|
|
|
}
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
#endif /* CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK */
|
2026-01-19 12:04:16 -03:00
|
|
|
|
|
|
|
|
static int sys_clock_driver_init(void)
|
|
|
|
|
{
|
2021-11-04 12:51:39 +01:00
|
|
|
IRQ_CONNECT(TIMER_IRQ, 0, ccompare_isr, 0, 0);
|
drivers: timer: xtensa_sys_timer: use the generic timer core
Convert the Xtensa CCOUNT/CCOMPARE timer to system_timer_generic.h.
CCOMPARE fires only on CCOUNT == CCOMPARE, so an already-past target is
missed until CCOUNT wraps: that is the COMPARE_EXACT backend, whose
verify loop replaces the minimum-delay bump the driver applied by hand.
The driver keeps timer_driver_cycle_get() (CCOUNT) and
timer_driver_set_compare() (CCOMPARE), and the core takes over the tick
accounting: the last_count baseline, the tickful re-arm and the
sys_clock_elapsed()/set_timeout() tick math all go. smp_timer_init()
primes the per-CPU timer through timer_core_smp_prime().
ccount() still adds the low-power compensation offset, so the cycle count
and every announced delta pick it up. The Espressif LPM hook
(CONFIG_XTENSA_TIMER_LPM_TIMER_HOOK) moves to sys_clock_idle_enter() and
folds the missed cycles into that offset; the non-low-power idle path
drops to the weak sys_clock_idle_enter() default, which arms through
sys_clock_set_timeout().
The low-power exit hands its recovered span to the core directly rather
than folding it into the counter for the announce to find. CCOUNT is 32
bits, so a sleep can outrun what the masked delta resolves, which at
240 MHz is under nine seconds. The compensation still takes the same
span, so the counter stays in step and what CCOUNT did see is left for
the next announce.
kernel.timer.sloppy_idle gains qemu_xtensa/dc233c: with nothing pending
the core arms the longest interval it allows, so the time base keeps
running.
timer_api, context, sleep and tickless pass on qemu_xtensa/dc233c.
Build-verified on esp32 with the LPM hook (CONFIG_PM=y) and with SMP.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
2026-07-22 17:05:44 -04:00
|
|
|
timer_core_init();
|
2021-11-04 12:51:39 +01:00
|
|
|
irq_enable(TIMER_IRQ);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2,
|
|
|
|
|
CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|