diff --git a/drivers/counter/counter_mspm0_timer.c b/drivers/counter/counter_mspm0_timer.c index ebab86bd1eb8..d04a1724255a 100644 --- a/drivers/counter/counter_mspm0_timer.c +++ b/drivers/counter/counter_mspm0_timer.c @@ -16,6 +16,14 @@ #include #include +/* 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 */ @@ -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 */ @@ -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; } @@ -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) \ { \ @@ -560,7 +577,7 @@ 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), \ @@ -568,7 +585,8 @@ static void counter_mspm0_isr(void *arg) .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, \ @@ -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) diff --git a/tests/boards/ti/pm/companion_timer/CMakeLists.txt b/tests/boards/ti/pm/companion_timer/CMakeLists.txt new file mode 100644 index 000000000000..d0ba376763f7 --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/CMakeLists.txt @@ -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) diff --git a/tests/boards/ti/pm/companion_timer/README.rst b/tests/boards/ti/pm/companion_timer/README.rst new file mode 100644 index 000000000000..f7c926139ae2 --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/README.rst @@ -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. diff --git a/tests/boards/ti/pm/companion_timer/boards/lp_mspm0g3519.overlay b/tests/boards/ti/pm/companion_timer/boards/lp_mspm0g3519.overlay new file mode 100644 index 000000000000..a66342027eb3 --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/boards/lp_mspm0g3519.overlay @@ -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>; +}; diff --git a/tests/boards/ti/pm/companion_timer/prj.conf b/tests/boards/ti/pm/companion_timer/prj.conf new file mode 100644 index 000000000000..525c1dee5818 --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/prj.conf @@ -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 diff --git a/tests/boards/ti/pm/companion_timer/src/main.c b/tests/boards/ti/pm/companion_timer/src/main.c new file mode 100644 index 000000000000..43378c76d67e --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/src/main.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2026 Texas Instruments + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +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(¬ifier); + + /* 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(¬ifier); + LOG_INF("=== Test Complete ==="); + + return 0; +} diff --git a/tests/boards/ti/pm/companion_timer/tests.yaml b/tests/boards/ti/pm/companion_timer/tests.yaml new file mode 100644 index 000000000000..49cea0a0a4a0 --- /dev/null +++ b/tests/boards/ti/pm/companion_timer/tests.yaml @@ -0,0 +1,7 @@ +tests: + boards.ti.pm.companion_timer: + tags: pm + harness: ztest + platform_allow: + - lp_mspm0g3519/mspm0g3519 + timeout: 10