-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
timer: cortex-m systick: add idle timer #63187
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
#include <cmsis_core.h> | ||
#include <zephyr/irq.h> | ||
#include <zephyr/sys/util.h> | ||
#include <zephyr/drivers/counter.h> | ||
|
||
#define COUNTER_MAX 0x00ffffff | ||
#define TIMER_STOPPED 0xff000000 | ||
|
@@ -77,6 +78,26 @@ static cycle_t announced_cycles; | |
*/ | ||
static volatile uint32_t overflow_cyc; | ||
|
||
#ifdef CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER | ||
/* This local variable indicates that the timeout was set right before | ||
* entering idle state. | ||
* | ||
* It is used for chips that has to use a separate idle timer in such | ||
* case because the Cortex-m SysTick is not clocked in the low power | ||
* mode state. | ||
*/ | ||
static bool timeout_idle; | ||
|
||
/* Cycle counter before entering the idle state. */ | ||
static cycle_t cycle_pre_idle; | ||
|
||
/* Idle timer value before entering the idle state. */ | ||
static uint32_t idle_timer_pre_idle; | ||
|
||
/* Idle timer used for timer while entering the idle state */ | ||
static const struct device *idle_timer = DEVICE_DT_GET(DT_CHOSEN(zephyr_cortex_m_idle_timer)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that you're predicating this on a DTS counter device already, you could do this entirely with devicetree and avoid the extra kconfig. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, but I would like to keep the kconfig with the help for additional documentation. If you want, I can change the config to non user-configurable one as a follow-up PR. Does it make sense? |
||
#endif /* CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER */ | ||
|
||
/* This internal function calculates the amount of HW cycles that have | ||
* elapsed since the last time the absolute HW cycles counter has been | ||
* updated. 'cycle_count' may be updated either by the ISR, or when we | ||
|
@@ -143,6 +164,19 @@ void sys_clock_isr(void *arg) | |
cycle_count += overflow_cyc; | ||
overflow_cyc = 0; | ||
|
||
#ifdef CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER | ||
/* Rare case, when the interrupt was triggered, with previously programmed | ||
* LOAD value, just before entering the idle mode (SysTick is clocked) or right | ||
* after exiting the idle mode, before executing the procedure in the | ||
* sys_clock_idle_exit function. | ||
*/ | ||
if (timeout_idle) { | ||
z_arm_int_exit(); | ||
|
||
return; | ||
} | ||
#endif /* CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER */ | ||
|
||
if (TICKLESS) { | ||
/* In TICKLESS mode, the SysTick.LOAD is re-programmed | ||
* in sys_clock_set_timeout(), followed by resetting of | ||
|
@@ -180,6 +214,36 @@ void sys_clock_set_timeout(int32_t ticks, bool idle) | |
return; | ||
} | ||
|
||
#ifdef CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER | ||
if (idle) { | ||
uint64_t timeout_us = | ||
((uint64_t)ticks * USEC_PER_SEC) / CONFIG_SYS_CLOCK_TICKS_PER_SEC; | ||
struct counter_alarm_cfg cfg = { | ||
.callback = NULL, | ||
.ticks = counter_us_to_ticks(idle_timer, timeout_us), | ||
.user_data = NULL, | ||
.flags = 0, | ||
}; | ||
|
||
timeout_idle = true; | ||
|
||
/* Set the alarm using timer that runs the idle. | ||
* Needed rump-up/setting time, lower accurency etc. should be | ||
* included in the exit-latency in the power state definition. | ||
*/ | ||
counter_cancel_channel_alarm(idle_timer, 0); | ||
counter_set_channel_alarm(idle_timer, 0, &cfg); | ||
|
||
/* Store current values to calculate a difference in | ||
* measurements after exiting the idle state. | ||
*/ | ||
counter_get_value(idle_timer, &idle_timer_pre_idle); | ||
cycle_pre_idle = cycle_count + elapsed(); | ||
|
||
return; | ||
} | ||
#endif /* CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER */ | ||
|
||
#if defined(CONFIG_TICKLESS_KERNEL) | ||
uint32_t delay; | ||
uint32_t val1, val2; | ||
|
@@ -284,6 +348,43 @@ uint64_t sys_clock_cycle_get_64(void) | |
|
||
void sys_clock_idle_exit(void) | ||
{ | ||
#ifdef CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER | ||
if (timeout_idle) { | ||
cycle_t systick_diff, missed_cycles; | ||
uint32_t idle_timer_diff, idle_timer_post, dcycles, dticks; | ||
uint64_t systick_us, idle_timer_us, measurement_diff_us; | ||
|
||
/* Get current values for both timers */ | ||
counter_get_value(idle_timer, &idle_timer_post); | ||
systick_diff = cycle_count + elapsed() - cycle_pre_idle; | ||
|
||
/* Calculate has much time has pasted since last measurement for both timers */ | ||
idle_timer_diff = idle_timer_post - idle_timer_pre_idle; | ||
idle_timer_us = counter_ticks_to_us(idle_timer, idle_timer_diff); | ||
systick_us = | ||
((uint64_t)systick_diff * USEC_PER_SEC) / sys_clock_hw_cycles_per_sec(); | ||
|
||
/* Calculate difference in measurements to get how much time | ||
* the SysTick missed in idle state. | ||
*/ | ||
measurement_diff_us = idle_timer_us - systick_us; | ||
missed_cycles = | ||
(sys_clock_hw_cycles_per_sec() * measurement_diff_us) / USEC_PER_SEC; | ||
|
||
/* Update the cycle counter to include the cycles missed in idle */ | ||
cycle_count += missed_cycles; | ||
|
||
/* Announce the passed ticks to the kernel */ | ||
dcycles = cycle_count + elapsed() - announced_cycles; | ||
dticks = dcycles / CYC_PER_TICK; | ||
announced_cycles += dticks * CYC_PER_TICK; | ||
sys_clock_announce(dticks); | ||
|
||
/* We've alredy performed all needed operations */ | ||
timeout_idle = false; | ||
} | ||
#endif /* CONFIG_CORTEX_M_SYSTICK_IDLE_TIMER */ | ||
|
||
if (last_load == TIMER_STOPPED) { | ||
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to document the required interface for this somewhere. "The idle timer must be a counter device chosen in device tree via...", etc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requirement is mentioned later in the help message