Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions drivers/counter/counter_mspm0_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
#include <zephyr/sys/atomic.h>
#include <zephyr/logging/log.h>

/* True for the instance chosen as the system-timer low-power companion. */
#if DT_HAS_CHOSEN(zephyr_system_timer_companion)
#define COUNTER_MSPM_TIMER_IS_COMPANION(n) \
DT_SAME_NODE(DT_DRV_INST(n), DT_CHOSEN(zephyr_system_timer_companion))
#else
#define COUNTER_MSPM_TIMER_IS_COMPANION(n) 0
#endif /* DT_HAS_CHOSEN(zephyr_system_timer_companion) */

LOG_MODULE_REGISTER(mspm0_counter, CONFIG_COUNTER_LOG_LEVEL);

/* GPTIMER register map — TRM Chapter 34 */
Expand Down Expand Up @@ -187,6 +195,7 @@ struct counter_mspm0_config {
uint32_t prescaler;
unsigned int irqn;
void (*irq_config_func)(void);
bool is_companion;
};

/* Software-pending interrupt for late alarm detection */
Expand Down Expand Up @@ -471,6 +480,13 @@ static int counter_mspm0_init(const struct device *dev)

config->irq_config_func();

/* Start the counter since its expected to be on when its used as lpm
* companion.
*/
if (config->is_companion) {
counter_mspm0_start(dev);
}

return 0;
}

Expand Down Expand Up @@ -537,6 +553,7 @@ static void counter_mspm0_isr(void *arg)
}
}

/* clang-format off */
#define MSPM0_COUNTER_IRQ_REGISTER(n) \
static void mspm0_ ## n ##_irq_register(void) \
{ \
Expand All @@ -560,15 +577,16 @@ static void counter_mspm0_isr(void *arg)
.clk = DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), 0, clk), \
}, \
.irq_config_func = (mspm0_ ## n ##_irq_register), \
.clk_sel = MSPM0_CLOCK_PERIPH_REG_MASK( \
.clk_sel = MSPM0_CLOCK_PERIPH_REG_MASK( \
DT_CLOCKS_CELL_BY_IDX(DT_INST_PARENT(n), 0, clk)), \
.clk_div_reg = DT_PROP(DT_INST_PARENT(n), ti_clk_div) - 1U, \
.prescaler = DT_PROP(DT_INST_PARENT(n), ti_clk_prescaler), \
.irqn = DT_IRQN(DT_INST_PARENT(n)), \
.counter_info = {.max_top_value = (DT_INST_PROP(n, resolution) == 32) \
? UINT32_MAX : UINT16_MAX, \
.flags = COUNTER_CONFIG_INFO_COUNT_UP, \
.channels = DT_INST_PROP(n, channels)}, \
.channels = DT_INST_PROP(n, channels)}, \
.is_companion = COUNTER_MSPM_TIMER_IS_COMPANION(n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, \
Expand All @@ -578,4 +596,5 @@ static void counter_mspm0_isr(void *arg)
&counter_mspm0_config_ ## n, \
POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY, \
&mspm0_counter_api);
/* clang-format on */
DT_INST_FOREACH_STATUS_OKAY(COUNTER_DEVICE_INIT_MSPM0)
6 changes: 6 additions & 0 deletions tests/boards/ti/pm/companion_timer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(pm_companion_timer_test)

target_sources(app PRIVATE src/main.c)
29 changes: 29 additions & 0 deletions tests/boards/ti/pm/companion_timer/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
PM Companion Timer Test
#######################

Overview
********

Tests low-power state entry and measures sleep duration. The test automatically
adapts to available timing sources:

- **Systick**: Uses ARM SysTick timer when no companion timer is
available. Timing granularity is coarser and doesn't run in certain
low power modes.

- **Companion Timer**: Uses dedicated low-power counter for precise
microsecond-level timing that works in all sleep states.

Device Tree
***********

Optionally configure a companion timer in the board overlay:

.. code-block:: devicetree

chosen {
zephyr,system-timer-companion = &counterg0; /* Optional */
zephyr,test-pm-state = &stdby1;
};

If not defined, the test automatically uses systick.
26 changes: 26 additions & 0 deletions tests/boards/ti/pm/companion_timer/boards/lp_mspm0g3519.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Texas Instruments
* SPDX-License-Identifier: Apache-2.0
*/

/ {
chosen {
zephyr,system-timer-companion = &counterg0;
zephyr,test-pm-state = &stdby1;
};
};

&counterg0 {
status = "okay";
};

&timg0 {
status = "okay";
clocks = <&ckm MSPM0_CLOCK_LFCLK>;
ti,clk-prescaler = <59>;
wakeup-source;
};

&cpu0 {
cpu-power-states = <&run0 &run1 &run2 &stdby1>;
};
7 changes: 7 additions & 0 deletions tests/boards/ti/pm/companion_timer/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CONFIG_TICKLESS_KERNEL=y
CONFIG_PM=y
CONFIG_COUNTER=y
CONFIG_SYSTEM_TIMER_LPM_COMPANION_COUNTER=y
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_MODE_IMMEDIATE=y
155 changes: 155 additions & 0 deletions tests/boards/ti/pm/companion_timer/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright (c) 2026 Texas Instruments
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>
#include <zephyr/pm/pm.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#include <zephyr/sys/atomic.h>

LOG_MODULE_REGISTER(pm_companion_timer_test);

#define FIRST_SLEEP_OFFSET_US 129923
#define SECOND_SLEEP_OFFSET_US 1413080
#define THIRD_SLEEP_OFFSET_US 2590173

const struct device *lpm_timer_dev;
atomic_t correct_state_entered_during_sleep;
enum pm_state target_pm_state;
uint8_t target_pm_substate;

static void pm_state_entry(enum pm_state state)
{
if (state == target_pm_state) {
atomic_set(&correct_state_entered_during_sleep, 1);
}
}

static void pm_state_exit(enum pm_state state)
{
ARG_UNUSED(state);
}

static struct pm_notifier notifier = {
.state_entry = pm_state_entry,
.state_exit = pm_state_exit,
};

void get_lpm_timer_count(uint32_t *count)
{
if (lpm_timer_dev == NULL) {
*count = (uint32_t)k_uptime_ticks();
} else {
counter_get_value(lpm_timer_dev, count);
}
}

uint32_t get_lpm_timer_duration(uint32_t *start, uint32_t *end)
{
if (lpm_timer_dev == NULL) {
uint32_t tick_delta = *end - *start;

return (uint64_t)tick_delta * 1000000 / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
} else {
return counter_ticks_to_us(lpm_timer_dev, (*end - *start));
}
}

uint32_t get_lpm_timer_frequency(void)
{
if (lpm_timer_dev == NULL) {
return CONFIG_SYS_CLOCK_TICKS_PER_SEC;
} else {
return counter_get_frequency(lpm_timer_dev);
}
}

const struct device *get_lpm_timer(void)
{
#if DT_HAS_CHOSEN(zephyr_system_timer_companion)
return DEVICE_DT_GET(DT_CHOSEN(zephyr_system_timer_companion));
#else
return NULL;
#endif
}

int main(void)
{
lpm_timer_dev = get_lpm_timer();

if (lpm_timer_dev == NULL) {
LOG_INF("Using Systick timer");
} else {
LOG_INF("Using Companion Counter");
if (!device_is_ready(lpm_timer_dev)) {
LOG_ERR("ERROR: Counter device not ready");
return -1;
}
}

uint32_t counter_freq = get_lpm_timer_frequency();

LOG_INF("Low Power Mode Clock frequency: %u Hz", counter_freq);

/* Get test power state from device tree */
const struct pm_state_info target_state =
PM_STATE_INFO_DT_INIT(DT_CHOSEN(zephyr_test_pm_state));

target_pm_state = target_state.state;
target_pm_substate = target_state.substate_id;

/* Get power state node name */
const char *pm_state_name = DT_NODE_FULL_NAME(DT_CHOSEN(zephyr_test_pm_state));

/* Register power state notifier */
pm_notifier_register(&notifier);

/* Force the target power state */
pm_state_force(0, &target_state);

LOG_INF("Forcing LPM '%s' (mapped to system power state: '%s')", pm_state_name,
pm_state_to_str(target_pm_state));
LOG_INF("Min residency: %u us, Exit latency: %u us", target_state.min_residency_us,
target_state.exit_latency_us);

uint32_t min_residency_us = target_state.min_residency_us;
uint32_t sleep_durations_us[] = {min_residency_us + FIRST_SLEEP_OFFSET_US,
min_residency_us + SECOND_SLEEP_OFFSET_US,
min_residency_us + THIRD_SLEEP_OFFSET_US};

for (int i = 0; i < 3; i++) {
LOG_INF("--- Sleep cycle %d: %u us ---", i + 1, sleep_durations_us[i]);
log_process();

uint32_t end_time = 0;
uint32_t start_time = 0;

atomic_set(&correct_state_entered_during_sleep, 0);

get_lpm_timer_count(&start_time);

k_sleep(K_USEC(sleep_durations_us[i]));

get_lpm_timer_count(&end_time);

uint32_t actual_duration_us = get_lpm_timer_duration(&start_time, &end_time);

if (atomic_get(&correct_state_entered_during_sleep)) {
LOG_INF("✓ Duration: %u us (expected: %u us)", actual_duration_us,
sleep_durations_us[i]);
} else {
LOG_ERR("✗ Duration: %u us (expected: %u us) - "
"WARNING: Did not enter target state",
actual_duration_us, sleep_durations_us[i]);
}
}

pm_notifier_unregister(&notifier);
LOG_INF("=== Test Complete ===");

return 0;
}
7 changes: 7 additions & 0 deletions tests/boards/ti/pm/companion_timer/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
tests:
boards.ti.pm.companion_timer:
tags: pm
harness: ztest
platform_allow:
- lp_mspm0g3519/mspm0g3519
timeout: 10
Loading