Skip to content

Commit

Permalink
drivers: timer: NXP OS Timer updated for low power modes
Browse files Browse the repository at this point in the history
Add ability to set a wakeup counter in case OS Timer is
disabled in certain low power modes. Also add code to
compensate the tick value.

Signed-off-by: Mahesh Mahadevan <mahesh.mahadevan@nxp.com>
  • Loading branch information
mmahadevan108 authored and aescolar committed Mar 25, 2024
1 parent 0e94934 commit 4045975
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 7 deletions.
10 changes: 10 additions & 0 deletions drivers/timer/Kconfig.mcux_os
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ config MCUX_OS_TIMER
help
This module implements a kernel device driver for the NXP OS
event timer and provides the standard "system clock driver" interfaces.

if MCUX_OS_TIMER

config MCUX_OS_TIMER_PM_POWERED_OFF
bool "Reinitialize the OS Timer"
help
OS Timer is turned off in certain low power modes. When this option is
picked, OS Timer will take steps to store state and reinitialize on wakeups.

endif # MCUX_OS_TIMER
161 changes: 154 additions & 7 deletions drivers/timer/mcux_os_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <zephyr/irq.h>
#include <zephyr/sys_clock.h>
#include <zephyr/spinlock.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/pm/pm.h>
#include "fsl_ostimer.h"
#include "fsl_power.h"

Expand All @@ -27,13 +29,26 @@
static struct k_spinlock lock;
static uint64_t last_count;
static OSTIMER_Type *base;
/* Total cycles of the timer compensated to include the time lost in "sleep/deep sleep" modes.
* This maintains the timer count to account for the case if the OS Timer is reset in
* certain deep sleep modes and the time elapsed when it is powered off.
*/
static uint64_t cyc_sys_compensated;
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
static const struct device *counter_dev;
#endif

static uint64_t mcux_lpc_ostick_get_compensated_timer_value(void)
{
return (OSTIMER_GetCurrentTimerValue(base) + cyc_sys_compensated);
}

void mcux_lpc_ostick_isr(const void *arg)
{
ARG_UNUSED(arg);

k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t now = OSTIMER_GetCurrentTimerValue(base);
uint64_t now = mcux_lpc_ostick_get_compensated_timer_value();
uint32_t dticks = (uint32_t)((now - last_count) / CYC_PER_TICK);

/* Clear interrupt flag by writing 1. */
Expand All @@ -54,6 +69,104 @@ void mcux_lpc_ostick_isr(const void *arg)
sys_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? dticks : 1);
}

#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM

/* The OS Timer is disabled in certain low power modes and cannot wakeup the system
* on timeout. This function will be called by the low power code to allow the
* OS Timer to save off the count if needed and also start a wakeup counter
* that would wakeup the system from deep power down modes.
*/
static uint32_t mcux_lpc_ostick_set_counter_timeout(int32_t curr_timeout)
{
uint32_t ret = 0;

if (counter_dev) {
uint32_t timeout;
int32_t ticks;
struct counter_top_cfg top_cfg = { 0 };

timeout = k_ticks_to_us_ceil32(curr_timeout);

ticks = counter_us_to_ticks(counter_dev, timeout);
ticks = CLAMP(ticks, 1, counter_get_max_top_value(counter_dev));

top_cfg.ticks = ticks;
top_cfg.callback = NULL;
top_cfg.user_data = NULL;
top_cfg.flags = 0;
if (counter_set_top_value(counter_dev, &top_cfg) != 0) {
/* Setting top value failed, try setting an alarm */
struct counter_alarm_cfg alarm_cfg;

alarm_cfg.ticks = ticks;
alarm_cfg.callback = NULL;
alarm_cfg.user_data = NULL;
alarm_cfg.flags = 0;

if (counter_set_channel_alarm(counter_dev, 0, &alarm_cfg) != 0) {
ret = 1;
goto done;
}
}

#if CONFIG_MCUX_OS_TIMER_PM_POWERED_OFF
/* Capture the current timer value for cases where it loses its state
* in low power modes.
*/
cyc_sys_compensated += OSTIMER_GetCurrentTimerValue(base);
#endif

/* Counter is set to wakeup the system after the requested time */
if (counter_start(counter_dev) != 0) {
ret = 1;
}
} else {
ret = 1;
}

done:
return ret;
}

/* After exit from certain low power modes where the OS Timer was disabled, the
* current tick value should be updated to account for the period when the OS Timer
* was disabled. Also in certain cases, the OS Timer might lose its state and needs
* to be reinitialized.
*/
static uint32_t mcux_lpc_ostick_compensate_system_timer(void)
{
uint32_t ret = 0;

if (counter_dev) {
uint32_t slept_time_ticks;
uint32_t slept_time_us;

counter_stop(counter_dev);

counter_get_value(counter_dev, &slept_time_ticks);

if (!(counter_is_counting_up(counter_dev))) {
slept_time_ticks = counter_get_top_value(counter_dev) - slept_time_ticks;
}
slept_time_us = counter_ticks_to_us(counter_dev, slept_time_ticks);
cyc_sys_compensated += (k_us_to_ticks_floor32(slept_time_us) * CYC_PER_TICK);

#if CONFIG_MCUX_OS_TIMER_PM_POWERED_OFF
/* Reactivate os_timer for cases where it loses its state */
OSTIMER_Init(base);
#endif

/* Announce the time slept to the kernel*/
mcux_lpc_ostick_isr(NULL);
} else {
ret = 1;
}

return ret;
}

#endif

void sys_clock_set_timeout(int32_t ticks, bool idle)
{
ARG_UNUSED(idle);
Expand All @@ -63,11 +176,28 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
return;
}

#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
if (idle) {
/* OS Timer may not be able to wakeup in certain low power modes.
* For these cases, we start a counter that can wakeup
* from low power modes.
*/
if (pm_state_next_get(0)->state == PM_STATE_STANDBY) {
if (mcux_lpc_ostick_set_counter_timeout(ticks) == 0) {
/* A low power counter has been started. No need to
* go further, simply return
*/
return;
}
}
}
#endif

ticks = ticks == K_TICKS_FOREVER ? MAX_TICKS : ticks;
ticks = CLAMP(ticks - 1, 0, (int32_t)MAX_TICKS);

k_spinlock_key_t key = k_spin_lock(&lock);
uint64_t now = OSTIMER_GetCurrentTimerValue(base);
uint64_t now = mcux_lpc_ostick_get_compensated_timer_value();
uint32_t adj, cyc = ticks * CYC_PER_TICK;

/* Round up to next tick boundary. */
Expand All @@ -83,7 +213,7 @@ void sys_clock_set_timeout(int32_t ticks, bool idle)
cyc += CYC_PER_TICK;
}

OSTIMER_SetMatchValue(base, cyc + last_count, NULL);
OSTIMER_SetMatchValue(base, cyc + last_count - cyc_sys_compensated, NULL);

k_spin_unlock(&lock, key);
}
Expand All @@ -96,7 +226,7 @@ uint32_t sys_clock_elapsed(void)
}

k_spinlock_key_t key = k_spin_lock(&lock);
uint32_t ret = ((uint32_t)OSTIMER_GetCurrentTimerValue(base) -
uint32_t ret = ((uint32_t)mcux_lpc_ostick_get_compensated_timer_value() -
(uint32_t)last_count) / CYC_PER_TICK;

k_spin_unlock(&lock, key);
Expand All @@ -105,12 +235,24 @@ uint32_t sys_clock_elapsed(void)

uint32_t sys_clock_cycle_get_32(void)
{
return (uint32_t)OSTIMER_GetCurrentTimerValue(base);
return (uint32_t)mcux_lpc_ostick_get_compensated_timer_value();
}

uint64_t sys_clock_cycle_get_64(void)
{
return OSTIMER_GetCurrentTimerValue(base);
return mcux_lpc_ostick_get_compensated_timer_value();
}

void sys_clock_idle_exit(void)
{
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
/* The tick should be compensated for states where the
* OS Timer is disabled
*/
if (pm_state_next_get(0)->state == PM_STATE_STANDBY) {
mcux_lpc_ostick_compensate_system_timer();
}
#endif
}

static int sys_clock_driver_init(void)
Expand All @@ -129,12 +271,17 @@ static int sys_clock_driver_init(void)
/* Initialize the OS timer, setting clock configuration. */
OSTIMER_Init(base);

last_count = OSTIMER_GetCurrentTimerValue(base);
last_count = mcux_lpc_ostick_get_compensated_timer_value();
OSTIMER_SetMatchValue(base, last_count + CYC_PER_TICK, NULL);

/* Enable event timer interrupt */
irq_enable(DT_INST_IRQN(0));

/* On some SoC's, OS Timer cannot wakeup from low power mode in standby modes */
#if DT_NODE_HAS_STATUS(DT_NODELABEL(standby), okay) && CONFIG_PM
counter_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, deep_sleep_counter));
#endif

return 0;
}

Expand Down
7 changes: 7 additions & 0 deletions dts/bindings/timer/nxp,os-timer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ properties:

interrupts:
required: true

deep-sleep-counter:
type: phandle
description: |
Instance of a counter peripheral. The OS Timer maybe powered off in
certain deep power down modes. The OS Timer driver will use this
counter to wakeup and also to keep track of system time.

0 comments on commit 4045975

Please sign in to comment.