From 7de9fcb1977ca76e203e424f7a3ae69afae5994b Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Mon, 17 Nov 2025 16:23:23 +0800 Subject: [PATCH 01/11] dts: bindings: os-timer: Add resets array Add to support reset OS_TIMER in driver Signed-off-by: Jason Yu --- dts/bindings/timer/nxp,os-timer.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dts/bindings/timer/nxp,os-timer.yaml b/dts/bindings/timer/nxp,os-timer.yaml index eb945b807fc47..519e20f73b531 100644 --- a/dts/bindings/timer/nxp,os-timer.yaml +++ b/dts/bindings/timer/nxp,os-timer.yaml @@ -20,3 +20,7 @@ properties: 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. + + resets: + type: phandle-array + description: reset line From b1c73a20123496a2e643b2aeab550fc914bd45f1 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Thu, 6 Nov 2025 17:16:21 +0800 Subject: [PATCH 02/11] drivers: timer: ostimer: Change to use reset API Use reset API to reset OSTIMER for better portability. Signed-off-by: Jason Yu --- drivers/timer/Kconfig.mcux_os | 1 + drivers/timer/mcux_os_timer.c | 6 +++++- dts/arm/nxp/nxp_mcxn23x_common.dtsi | 1 + dts/arm/nxp/nxp_rt5xx_common.dtsi | 1 + dts/arm/nxp/nxp_rt6xx_common.dtsi | 1 + dts/arm/nxp/nxp_rw6xx_common.dtsi | 1 + 6 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/timer/Kconfig.mcux_os b/drivers/timer/Kconfig.mcux_os index ed2920bf30015..420594a5180da 100644 --- a/drivers/timer/Kconfig.mcux_os +++ b/drivers/timer/Kconfig.mcux_os @@ -9,6 +9,7 @@ config MCUX_OS_TIMER depends on DT_HAS_NXP_OS_TIMER_ENABLED select TICKLESS_CAPABLE select TIMER_HAS_64BIT_CYCLE_COUNTER + select RESET help This module implements a kernel device driver for the NXP OS event timer and provides the standard "system clock driver" interfaces. diff --git a/drivers/timer/mcux_os_timer.c b/drivers/timer/mcux_os_timer.c index bbadcece8a37e..d4107f68f276b 100644 --- a/drivers/timer/mcux_os_timer.c +++ b/drivers/timer/mcux_os_timer.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "fsl_ostimer.h" #if !defined(CONFIG_SOC_FAMILY_MCXN) && !defined(CONFIG_SOC_FAMILY_MCXA) #include "fsl_power.h" @@ -179,9 +180,12 @@ static uint32_t mcux_lpc_ostick_compensate_system_timer(void) } slept_time_us = counter_ticks_to_us(counter_dev, slept_time_ticks); cyc_sys_compensated += CYC_PER_US * slept_time_us; + if (IS_ENABLED(CONFIG_MCUX_OS_TIMER_PM_POWERED_OFF)) { /* Reset the OS Timer to a known state */ - RESET_PeripheralReset(kOSEVENT_TIMER_RST_SHIFT_RSTn); + const struct reset_dt_spec reset = RESET_DT_SPEC_GET(DT_DRV_INST(0)); + + reset_line_toggle_dt(&reset); /* Reactivate os_timer for cases where it loses its state */ OSTIMER_Init(base); } diff --git a/dts/arm/nxp/nxp_mcxn23x_common.dtsi b/dts/arm/nxp/nxp_mcxn23x_common.dtsi index 359f8c5760e1c..4185cd4874df0 100644 --- a/dts/arm/nxp/nxp_mcxn23x_common.dtsi +++ b/dts/arm/nxp/nxp_mcxn23x_common.dtsi @@ -570,6 +570,7 @@ compatible = "nxp,os-timer"; reg = <0x49000 0x1000>; interrupts = <57 0>; + resets = <&reset NXP_SYSCON_RESET(1, 1)>; status = "disabled"; }; diff --git a/dts/arm/nxp/nxp_rt5xx_common.dtsi b/dts/arm/nxp/nxp_rt5xx_common.dtsi index c7caa322cea2d..f9f88c52814c7 100644 --- a/dts/arm/nxp/nxp_rt5xx_common.dtsi +++ b/dts/arm/nxp/nxp_rt5xx_common.dtsi @@ -494,6 +494,7 @@ compatible = "nxp,os-timer"; reg = <0x113000 0x1000>; interrupts = <41 0>; + resets = <&rstctl1 NXP_SYSCON_RESET(0, 27)>; status = "disabled"; }; diff --git a/dts/arm/nxp/nxp_rt6xx_common.dtsi b/dts/arm/nxp/nxp_rt6xx_common.dtsi index 937cbf663c9ef..6f406160debe7 100644 --- a/dts/arm/nxp/nxp_rt6xx_common.dtsi +++ b/dts/arm/nxp/nxp_rt6xx_common.dtsi @@ -438,6 +438,7 @@ compatible = "nxp,os-timer"; reg = <0x113000 0x1000>; interrupts = <41 0>; + resets = <&rstctl1 NXP_SYSCON_RESET(0, 27)>; status = "disabled"; }; diff --git a/dts/arm/nxp/nxp_rw6xx_common.dtsi b/dts/arm/nxp/nxp_rw6xx_common.dtsi index eb62e3b918db0..7703ede39f1cf 100644 --- a/dts/arm/nxp/nxp_rw6xx_common.dtsi +++ b/dts/arm/nxp/nxp_rw6xx_common.dtsi @@ -610,6 +610,7 @@ compatible = "nxp,os-timer"; reg = <0x13b000 0x1000>; interrupts = <41 0>; + resets = <&rstctl1 NXP_SYSCON_RESET(0, 27)>; status = "disabled"; }; From e90532c10b925a11c776c3a756e962501640ce2b Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Mon, 3 Nov 2025 14:44:18 +0800 Subject: [PATCH 03/11] drivers: timer: ostimer: Fix run fail when no deep_sleep_counter Only access deep_sleep_counter when it is available. Signed-off-by: Jason Yu --- drivers/timer/mcux_os_timer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/timer/mcux_os_timer.c b/drivers/timer/mcux_os_timer.c index d4107f68f276b..8b483292532a9 100644 --- a/drivers/timer/mcux_os_timer.c +++ b/drivers/timer/mcux_os_timer.c @@ -341,7 +341,9 @@ static int sys_clock_driver_init(void) /* On some SoC's, OS Timer cannot wakeup from low power mode in standby modes */ #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(standby)) && CONFIG_PM counter_dev = DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(0, deep_sleep_counter)); - counter_max_val = counter_get_max_top_value(counter_dev); + if (NULL != counter_dev) { + counter_max_val = counter_get_max_top_value(counter_dev); + } #endif #if (DT_INST_PROP(0, wakeup_source)) From b6b43f7c23916f6c8adfbf0ce42f38250cd0a7a3 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Thu, 6 Nov 2025 15:12:10 +0800 Subject: [PATCH 04/11] drivers: dma: dma_mcux_lpc: Support power device constraint Call pm_policy_device_power_lock_put/pm_policy_device_power_lock_get to coordinate with system level power modes. Signed-off-by: Jason Yu --- drivers/dma/dma_mcux_lpc.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dma_mcux_lpc.c b/drivers/dma/dma_mcux_lpc.c index 2cc7500dfc2a0..42b9f3bb92baa 100644 --- a/drivers/dma/dma_mcux_lpc.c +++ b/drivers/dma/dma_mcux_lpc.c @@ -23,6 +23,7 @@ #include #include #include +#include #define DT_DRV_COMPAT nxp_lpc_dma @@ -108,6 +109,10 @@ static void nxp_lpc_dma_callback(dma_handle_t *handle, void *param, ret = DMA_STATUS_COMPLETE; } + if (!data->busy) { + pm_policy_device_power_lock_put(data->dev); + } + if (data->dma_callback) { data->dma_callback(data->dev, data->user_data, channel, ret); } @@ -563,6 +568,7 @@ static int dma_mcux_lpc_configure(const struct device *dev, uint32_t channel, if (data->busy) { DMA_AbortTransfer(p_handle); + pm_policy_device_power_lock_put(dev); } LOG_DBG("channel is %d", p_handle->channel); @@ -815,6 +821,7 @@ static int dma_mcux_lpc_start(const struct device *dev, uint32_t channel) LOG_DBG("START TRANSFER"); LOG_DBG("DMA CTRL 0x%x", DEV_BASE(dev)->CTRL); data->busy = true; + pm_policy_device_power_lock_get(dev); /* In case of a restart after a stop, reinstall the DMA callback * that was removed by the stop. */ @@ -836,7 +843,10 @@ static int dma_mcux_lpc_stop(const struct device *dev, uint32_t channel) DMA_AbortTransfer(p_handle); DMA_DisableChannel(DEV_BASE(dev), p_handle->channel); - data->busy = false; + if (data->busy) { + data->busy = false; + pm_policy_device_power_lock_put(dev); + } /* Handle race condition where if this is called from an ISR * and the DMA channel completion interrupt becomes pending From 50089c43e44c2c80e17805fe08b7d3cb3db2fa66 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Fri, 14 Nov 2025 15:56:58 +0800 Subject: [PATCH 05/11] drivers: dma: dma_mcux_lpc: Add register backup and restore Add for the case that the registers are not kept in some low power modes. Signed-off-by: Jason Yu --- drivers/dma/dma_mcux_lpc.c | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/drivers/dma/dma_mcux_lpc.c b/drivers/dma/dma_mcux_lpc.c index 42b9f3bb92baa..af26e2a52f05d 100644 --- a/drivers/dma/dma_mcux_lpc.c +++ b/drivers/dma/dma_mcux_lpc.c @@ -29,6 +29,28 @@ LOG_MODULE_REGISTER(dma_mcux_lpc, CONFIG_DMA_LOG_LEVEL); +#if CONFIG_PM_DEVICE +/* + * Data structures to backup DMA registers when the + * register content lost in low power modes. + */ +struct dma_backup_reg { + /* + * Backup the control registers. + * Don't need to backup CTRL and SRAMBASE, they + * are configured in function DMA_Init. + */ + uint32_t enableset; /* Register ENABLESET */ + uint32_t intenset; /* Register INTENSET */ +}; + +struct dma_ch_backup_reg { + /* Don't need to backup status register CTLSTAT. */ + uint32_t cfg; /* Register CFG */ + uint32_t xfercfg; /* Register XFERCFG */ +}; +#endif /* CONFIG_PM_DEVICE */ + struct dma_mcux_lpc_config { DMA_Type *base; uint32_t otrig_base_address; @@ -54,6 +76,9 @@ struct channel_data { uint8_t num_of_descriptors; bool descriptors_queued; bool busy; +#if CONFIG_PM_DEVICE + struct dma_ch_backup_reg backup_reg; +#endif /* CONFIG_PM_DEVICE */ }; struct dma_otrig { @@ -68,6 +93,9 @@ struct dma_mcux_lpc_dma_data { struct dma_otrig *otrig_array; int8_t *channel_index; uint8_t num_channels_used; +#if CONFIG_PM_DEVICE + struct dma_backup_reg backup_reg; +#endif /* CONFIG_PM_DEVICE */ }; struct k_spinlock configuring_otrigs; @@ -948,6 +976,69 @@ static int dma_mcux_lpc_get_attribute(const struct device *dev, uint32_t type, u return 0; } +#if CONFIG_PM_DEVICE +static void dma_mcux_lpc_backup_reg(const struct device *dev) +{ + struct dma_mcux_lpc_dma_data *dma_data = dev->data; + const struct dma_mcux_lpc_config *config = dev->config; + struct channel_data *p_channel_data; + uint32_t virtual_channel; + DMA_Type *dma_base = DEV_BASE(dev); + + dma_data->backup_reg.enableset = dma_base->COMMON[0].ENABLESET; + dma_data->backup_reg.intenset = dma_base->COMMON[0].INTENSET; + + /* Only backup the used channels */ + virtual_channel = 0; + for (uint32_t channel = 0; channel < config->num_of_channels; channel++) { + if (dma_data->channel_index[channel] != -1) { + p_channel_data = &dma_data->channel_data[virtual_channel]; + + p_channel_data->backup_reg.xfercfg = dma_base->CHANNEL[channel].XFERCFG; + p_channel_data->backup_reg.cfg = dma_base->CHANNEL[channel].CFG; + virtual_channel++; + } + } +} + +static void dma_mcux_lpc_restore_reg(const struct device *dev) +{ + struct dma_mcux_lpc_dma_data *dma_data = dev->data; + const struct dma_mcux_lpc_config *config = dev->config; + struct channel_data *p_channel_data; + uint32_t virtual_channel; + DMA_Type *dma_base = DEV_BASE(dev); + + dma_base->COMMON[0].ENABLESET = dma_data->backup_reg.enableset; + dma_base->COMMON[0].INTENSET = dma_data->backup_reg.intenset; + + /* Only backup the used channels */ + virtual_channel = 0; + for (uint32_t channel = 0; channel < config->num_of_channels; channel++) { + if (dma_data->channel_index[channel] != -1) { + p_channel_data = &dma_data->channel_data[virtual_channel]; + + dma_base->CHANNEL[channel].XFERCFG = p_channel_data->backup_reg.xfercfg; + dma_base->CHANNEL[channel].CFG = p_channel_data->backup_reg.cfg; + virtual_channel++; + } + } +} + +#else /* !CONFIG_PM_DEVICE */ + +static inline static void dma_mcux_lpc_backup_reg(const struct device *dev) +{ + ARG_UNUSED(dev); +} + +static inline static void dma_mcux_lpc_restore_reg(const struct device *dev) +{ + ARG_UNUSED(dev); +} + +#endif /* CONFIG_PM_DEVICE */ + static int dma_mcux_lpc_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { @@ -956,9 +1047,11 @@ static int dma_mcux_lpc_pm_action(const struct device *dev, enum pm_device_actio case PM_DEVICE_ACTION_SUSPEND: break; case PM_DEVICE_ACTION_TURN_OFF: + dma_mcux_lpc_backup_reg(dev); break; case PM_DEVICE_ACTION_TURN_ON: DMA_Init(DEV_BASE(dev)); + dma_mcux_lpc_restore_reg(dev); break; default: return -ENOTSUP; From 075aae2fbedb8aad159c4b7596cbf6396c92f884 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Fri, 14 Nov 2025 15:56:21 +0800 Subject: [PATCH 06/11] drivers: counter: nxp_mrt: Support power device constraint Call pm_policy_device_power_lock_put/pm_policy_device_power_lock_get to coordinate with system level power modes. Signed-off-by: Jason Yu --- drivers/counter/counter_nxp_mrt.c | 5 +++++ drivers/dma/dma_mcux_lpc.c | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/counter/counter_nxp_mrt.c b/drivers/counter/counter_nxp_mrt.c index b08a6765fc42c..9e1446260f4f8 100644 --- a/drivers/counter/counter_nxp_mrt.c +++ b/drivers/counter/counter_nxp_mrt.c @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -73,6 +74,8 @@ static int nxp_mrt_stop(const struct device *dev) /* LOAD bit and 0 ivalue allows us to forcibly stop the timer */ base->CHANNEL[channel_id].INTVAL = MRT_CHANNEL_INTVAL_LOAD(1); + pm_policy_device_power_lock_put(dev); + return 0; } @@ -90,6 +93,8 @@ static int nxp_mrt_start(const struct device *dev) data->top = config->info.max_top_value; } + pm_policy_device_power_lock_get(dev); + /* Start with previously configured top value (if already running this has no effect) */ base->CHANNEL[channel_id].INTVAL = data->top; diff --git a/drivers/dma/dma_mcux_lpc.c b/drivers/dma/dma_mcux_lpc.c index af26e2a52f05d..4f75c8cc34a02 100644 --- a/drivers/dma/dma_mcux_lpc.c +++ b/drivers/dma/dma_mcux_lpc.c @@ -1027,12 +1027,12 @@ static void dma_mcux_lpc_restore_reg(const struct device *dev) #else /* !CONFIG_PM_DEVICE */ -static inline static void dma_mcux_lpc_backup_reg(const struct device *dev) +static inline void dma_mcux_lpc_backup_reg(const struct device *dev) { ARG_UNUSED(dev); } -static inline static void dma_mcux_lpc_restore_reg(const struct device *dev) +static inline void dma_mcux_lpc_restore_reg(const struct device *dev) { ARG_UNUSED(dev); } From fa4a89454837ca9c49deb2a9b6bb622191f4c001 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Fri, 14 Nov 2025 15:58:26 +0800 Subject: [PATCH 07/11] drivers: interrupt: pint: Add API to get pin used IRQ slot PINT connects GPIO pin to seperate IRQ slot. Add new API to get which IRQ slot is connected to, based on pin index. Signed-off-by: Jason Yu --- drivers/interrupt_controller/intc_nxp_pint.c | 16 ++++++++++++++++ .../drivers/interrupt_controller/nxp_pint.h | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/drivers/interrupt_controller/intc_nxp_pint.c b/drivers/interrupt_controller/intc_nxp_pint.c index 7ed5a8741c008..ad95e78b6a063 100644 --- a/drivers/interrupt_controller/intc_nxp_pint.c +++ b/drivers/interrupt_controller/intc_nxp_pint.c @@ -175,6 +175,22 @@ void nxp_pint_pin_unset_callback(uint8_t pin) pint_irq_cfg[slot].callback = NULL; } +int nxp_pint_pin_get_slot_index(uint8_t pin) +{ + int slot; + + if (pin > ARRAY_SIZE(pin_pint_id)) { + return -EINVAL; + } + + slot = pin_pint_id[pin]; + if (slot == NO_PINT_ID) { + return -EINVAL; + } + + return slot; +} + /* NXP PINT ISR handler- called with PINT slot ID */ static void nxp_pint_isr(uint8_t *slot) { diff --git a/include/zephyr/drivers/interrupt_controller/nxp_pint.h b/include/zephyr/drivers/interrupt_controller/nxp_pint.h index 6bf6d586d83e1..def135616641a 100644 --- a/include/zephyr/drivers/interrupt_controller/nxp_pint.h +++ b/include/zephyr/drivers/interrupt_controller/nxp_pint.h @@ -79,5 +79,12 @@ int nxp_pint_pin_set_callback(uint8_t pin, nxp_pint_cb_t cb, void *data); */ void nxp_pint_pin_unset_callback(uint8_t pin); +/** + * @brief Get PINT slot index the pin is allocated to + * + * @param pin: The pin to get the PINT slot index for + * @return The allocated slot index, if not allocated, return -EINVAL. + */ +int nxp_pint_pin_get_slot_index(uint8_t pin); #endif /* ZEPHYR_DRIVERS_INTERRUPT_CONTROLLER_INTC_NXP_PINT_H_ */ From 566d83c4998f696c7b17762c2b245a4dbcf202e0 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Mon, 1 Dec 2025 10:50:25 +0800 Subject: [PATCH 08/11] soc: nxp: mcxw2xx: Improve OS tick timer selection When os_timer is enabled in dts, then os_timer will be used as OS tick timer. To make systick as the default OS tick timer, currently os_timer is not enabled in dts. When users want to use os_timer as OS tick timer, they need to override the dts. Improve the method, enable the os_timer is dts, but not enable in Kconfig by default. If need to use os_timer as OS tick, just pass CONFIG_MCUX_OS_TIMER=y Signed-off-by: Jason Yu --- boards/nxp/frdm_mcxw23/frdm_mcxw23_common.dtsi | 9 ++++----- boards/nxp/mcxw23_evk/mcxw23_evk_common.dtsi | 9 ++++----- soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig | 6 ++++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/boards/nxp/frdm_mcxw23/frdm_mcxw23_common.dtsi b/boards/nxp/frdm_mcxw23/frdm_mcxw23_common.dtsi index 205594e59d760..c40b2b4795dcc 100644 --- a/boards/nxp/frdm_mcxw23/frdm_mcxw23_common.dtsi +++ b/boards/nxp/frdm_mcxw23/frdm_mcxw23_common.dtsi @@ -176,15 +176,14 @@ pinctrl-names = "default"; }; -/* - * MCXW23 FRDM board uses SYSTICK timer as the kernel timer. - * In case we need to switch to OS timer, then - * replace &systick with &os_timer - */ &systick { status = "okay"; }; +&os_timer { + status = "okay"; +}; + &wwdt0 { status = "okay"; }; diff --git a/boards/nxp/mcxw23_evk/mcxw23_evk_common.dtsi b/boards/nxp/mcxw23_evk/mcxw23_evk_common.dtsi index c21e8e277696c..75e8ed58a1761 100644 --- a/boards/nxp/mcxw23_evk/mcxw23_evk_common.dtsi +++ b/boards/nxp/mcxw23_evk/mcxw23_evk_common.dtsi @@ -156,15 +156,14 @@ pinctrl-names = "default"; }; -/* - * MCXW23 EVK board uses SYSTICK timer as the kernel timer. - * In case we need to switch to OS timer, then - * replace &systick with &os_timer - */ &systick { status = "okay"; }; +&os_timer { + status = "okay"; +}; + &wwdt0 { status = "okay"; }; diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig index 3b260c2abb4fd..1f96114f62b21 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig +++ b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig @@ -4,6 +4,12 @@ if SOC_SERIES_MCXW2XX +# MCXW23 uses SYSTICK timer as the kernel timer by default. +# In case we need to switch to OS timer, then +# define CONFIG_MCUX_OS_TIMER=y +config MCUX_OS_TIMER + default n + config CORTEX_M_SYSTICK default n if MCUX_OS_TIMER From e90e7a561a76d75c785820277d6dbfdcaaac329b Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Mon, 1 Dec 2025 11:26:25 +0800 Subject: [PATCH 09/11] soc: nxp: mcxw2xx: Enable the power management Enabled modes: idle: SLEEP suspend: DEEP-SLEEP standby: POWER-DOWN with CPU retention OS Time Base: OSTIMER with 32K clock source Signed-off-by: Jason Yu --- dts/arm/nxp/nxp_mcxw23x_common.dtsi | 52 ++++++ soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig | 41 +++- soc/nxp/mcx/mcxw/mcxw2xx/power.c | 207 ++++++++++++++++++++- soc/nxp/mcx/mcxw/mcxw2xx/soc.c | 20 +- 4 files changed, 313 insertions(+), 7 deletions(-) diff --git a/dts/arm/nxp/nxp_mcxw23x_common.dtsi b/dts/arm/nxp/nxp_mcxw23x_common.dtsi index 69069e29a4fa4..8604526bec490 100644 --- a/dts/arm/nxp/nxp_mcxw23x_common.dtsi +++ b/dts/arm/nxp/nxp_mcxw23x_common.dtsi @@ -32,12 +32,47 @@ reg = <0>; #address-cells = <1>; #size-cells = <1>; + cpu-power-states = <&idle &suspend &standby>; mpu: mpu@e000ed90 { compatible = "arm,armv8m-mpu"; reg = <0xe000ed90 0x40>; }; }; + + power-states { + /* Idle mode maps to Sleep mode. */ + idle: idle { + compatible = "zephyr,power-state"; + power-state-name = "runtime-idle"; + min-residency-us = <60>; + exit-latency-us = <16>; + zephyr,pm-device-disabled; + }; + + /* Suspend mode maps to Deep Sleep mode. */ + suspend: suspend { + compatible = "zephyr,power-state"; + power-state-name = "suspend-to-idle"; + min-residency-us = <1300>; + exit-latency-us = <749>; + }; + + /* Standby mode maps to Power Down mode with CPU retention . */ + standby: standby { + compatible = "zephyr,power-state"; + power-state-name = "standby"; + min-residency-us = <2000>; + exit-latency-us = <1345>; + }; + }; + }; + + /* For the resource which will be off and lose content. */ + standby_off_domain: standby-off-domain { + compatible = "power-domain-soc-state-change"; + #power-domain-cells = <0>; + onoff-power-states = <&standby>; }; sysclk: system-clock { @@ -175,6 +210,7 @@ mode = <0>; input = <0>; prescale = <0>; + zephyr,disabling-power-states = <&suspend &standby>; }; ctimer1: ctimer@9000 { @@ -187,6 +223,7 @@ mode = <0>; input = <0>; prescale = <0>; + zephyr,disabling-power-states = <&suspend &standby>; }; ctimer2: ctimer@28000 { @@ -199,6 +236,7 @@ mode = <0>; input = <0>; prescale = <0>; + zephyr,disabling-power-states = <&suspend &standby>; }; ctimer3: ctimer@29000 { @@ -211,6 +249,7 @@ mode = <0>; input = <0>; prescale = <0>; + zephyr,disabling-power-states = <&suspend &standby>; }; ctimer4: ctimer@2a000 { @@ -223,6 +262,7 @@ mode = <0>; input = <0>; prescale = <0>; + zephyr,disabling-power-states = <&suspend &standby>; }; sc_timer: pwm@85000 { @@ -232,6 +272,7 @@ status = "disabled"; clocks = <&syscon MCUX_SCTIMER_CLK>; prescaler = <2>; + zephyr,disabling-power-states = <&suspend &standby>; #pwm-cells = <3>; }; @@ -239,6 +280,7 @@ compatible = "nxp,os-timer"; reg = <0x2d000 0x1000>; interrupts = <38 1>; + resets = <&reset NXP_SYSCON_RESET(1, 1)>; status = "disabled"; }; @@ -250,6 +292,7 @@ resets = <&reset NXP_SYSCON_RESET(1, 11)>; dmas = <&dma0 4 &dma0 5>; dma-names = "rx", "tx"; + zephyr,disabling-power-states = <&suspend &standby>; status = "disabled"; }; @@ -261,6 +304,8 @@ resets = <&reset NXP_SYSCON_RESET(1, 12)>; dmas = <&dma0 6 &dma0 7>; dma-names = "rx", "tx"; + zephyr,disabling-power-states = <&suspend &standby>; + power-domains = <&standby_off_domain>; status = "disabled"; }; @@ -272,6 +317,8 @@ resets = <&reset NXP_SYSCON_RESET(1, 13)>; dmas = <&dma0 8 &dma0 9>; dma-names = "rx", "tx"; + zephyr,disabling-power-states = <&suspend &standby>; + power-domains = <&standby_off_domain>; status = "disabled"; }; @@ -287,6 +334,8 @@ reg = <0x82000 0x1000>; interrupts = <1 1>; dma-channels = <23>; + zephyr,disabling-power-states = <&suspend &standby>; + power-domains = <&standby_off_domain>; status = "disabled"; #dma-cells = <1>; }; @@ -296,6 +345,8 @@ reg = <0xa7000 0x1000>; interrupts = <58 1>; dma-channels = <10>; + zephyr,disabling-power-states = <&suspend &standby>; + power-domains = <&standby_off_domain>; status = "disabled"; #dma-cells = <1>; }; @@ -328,6 +379,7 @@ resets = <&reset NXP_SYSCON_RESET(1, 0)>; #address-cells = <1>; #size-cells = <0>; + zephyr,disabling-power-states = <&suspend &standby>; mrt0_channel0: mrt0_channel@0 { compatible = "nxp,mrt-channel"; diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig index 1f96114f62b21..5c9db7b0e35e6 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig +++ b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig @@ -8,7 +8,8 @@ if SOC_SERIES_MCXW2XX # In case we need to switch to OS timer, then # define CONFIG_MCUX_OS_TIMER=y config MCUX_OS_TIMER - default n + default y if PM + default n if !PM config CORTEX_M_SYSTICK default n if MCUX_OS_TIMER @@ -19,9 +20,14 @@ config NUM_IRQS DT_SYSCLK_PATH := $(dt_nodelabel_path,sysclk) config SYS_CLOCK_HW_CYCLES_PER_SEC - default 1000000 if MCUX_OS_TIMER + default 32768 if MCUX_OS_TIMER && PM + default 1000000 if MCUX_OS_TIMER && !PM default $(dt_node_int_prop_int,$(DT_SYSCLK_PATH),clock-frequency) if CORTEX_M_SYSTICK +config SYS_CLOCK_TICKS_PER_SEC + default 512 if MCUX_OS_TIMER && PM + default 1000 if MCUX_OS_TIMER && !PM + # Set to the minimal size of data which can be written. config FLASH_FILL_BUFFER_SIZE default 512 @@ -42,4 +48,35 @@ config MAIN_STACK_SIZE endif # BT +if PM + +# Enable PM_DEVICE for Suspend mode and Standby mode, +# because some devices can't work in these modes. +config PM_DEVICE + default y if "$(dt_nodelabel_enabled,standby)" || "$(dt_nodelabel_enabled,suspend)" + +config PM_POLICY_DEVICE_CONSTRAINTS + default y if PM_DEVICE + +config IDLE_STACK_SIZE + default 640 + +config TICKLESS_KERNEL + default y + +if BT +choice PM_POLICY + default PM_POLICY_CUSTOM +endchoice +endif # BT + +endif # PM + +if PM_DEVICE + +config POWER_DOMAIN + default y + +endif # PM_DEVICE + endif # SOC_SERIES_MCXW2XX diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/power.c b/soc/nxp/mcx/mcxw/mcxw2xx/power.c index 9c92e8275e322..2f178b3bf6b3d 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/power.c +++ b/soc/nxp/mcx/mcxw/mcxw2xx/power.c @@ -5,24 +5,223 @@ */ #include +#include #include +#include +#include +#include +#include -void pm_state_set(enum pm_state state, uint8_t id) +#if CONFIG_PM_POLICY_CUSTOM +#include +#endif /* CONFIG_PM_POLICY_CUSTOM */ + +#ifdef CONFIG_BT +#include "ll_intf.h" +#endif /* CONFIG_BT */ + +LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL); + +/* Wakeup pin. */ +#if CONFIG_GPIO && DT_NODE_EXISTS(DT_NODELABEL(btn_wk)) +#define WAKEUP_PIN_ENABLE 1 +#else +#define WAKEUP_PIN_ENABLE 0 +#endif + +#if WAKEUP_PIN_ENABLE +#define WAKEUP_BUTTON_NODE DT_NODELABEL(btn_wk) +static const struct gpio_dt_spec wakeup_pin_dt = GPIO_DT_SPEC_GET(WAKEUP_BUTTON_NODE, gpios); +#endif /* WAKEUP_PIN_ENABLE */ + +#if defined(CONFIG_BT) && !defined(CONFIG_PM_POLICY_CUSTOM) +#error Select CONFIG_PM_POLICY_CUSTOM when CONFIG_BT is selected +#endif + +#if CONFIG_PM_POLICY_CUSTOM +__weak const struct pm_state_info *pm_policy_next_state(uint8_t cpu, int32_t ticks) +{ + uint8_t num_cpu_states; + const struct pm_state_info *cpu_states; + const struct pm_state_info *out_state = NULL; + +#ifdef CONFIG_PM_NEED_ALL_DEVICES_IDLE + if (pm_device_is_any_busy()) { + return NULL; + } +#endif + +#ifdef CONFIG_BT + if (bt_is_ready()) { + uint32_t remaining_time; + ble_stat_t stat; + + stat = BLEController_GetRemainingTimeForNextEventUnsafe(&remaining_time); + /* Is link layer busy? */ + if ((stat != SUCCESS) || (remaining_time == 0)) { + return NULL; + } + /* Any future activity? */ + if (remaining_time < 0xffffffff) { + ticks = MIN(ticks, k_us_to_ticks_floor32(remaining_time)); + } + } +#endif /* CONFIG_BT */ + + num_cpu_states = pm_state_cpu_get_all(cpu, &cpu_states); + + for (uint32_t i = 0; i < num_cpu_states; i++) { + const struct pm_state_info *state = &cpu_states[i]; + uint32_t min_residency_ticks = 0; + uint32_t min_residency_us = state->min_residency_us + state->exit_latency_us; + + /* If the input is zero, avoid 64-bit conversion from microseconds to ticks. */ + if (min_residency_us > 0) { + min_residency_ticks = k_us_to_ticks_ceil32(min_residency_us); + } + + if (ticks < min_residency_ticks) { + /* If current state has higher residency then use the previous state; */ + break; + } + + /* check if state is available. */ + if (!pm_policy_state_is_available(state->state, state->substate_id)) { + continue; + } + + out_state = state; + } + + return out_state; +} +#endif /* CONFIG_PM_POLICY_CUSTOM */ + +static void pm_get_lowpower_resource_list(uint32_t *exclude_from_pd, + uint64_t *wakeup_sources, + bool is_standby) +{ + *exclude_from_pd = kLOWPOWERCFG_DCDC_BYPASS; + *wakeup_sources = 0; + +#ifdef CONFIG_BT + /* BT needs 32kHz clock. */ + *exclude_from_pd |= (kLOWPOWERCFG_XTAL32K | kLOWPOWERCFG_BLE_WUP); +#endif + +#ifdef CONFIG_MCUX_OS_TIMER + /* OS_TIMER uses 32K clock as clock source, keep it running. */ + *exclude_from_pd |= kLOWPOWERCFG_XTAL32K; + *wakeup_sources |= kWAKEUP_OS_EVENT; +#endif + +#if WAKEUP_PIN_ENABLE + static const wakeup_irq_t pint_wakeup_sources[] = { + kWAKEUP_PIN_INT0, + kWAKEUP_PIN_INT1, + kWAKEUP_PIN_INT2, + kWAKEUP_PIN_INT3, + kWAKEUP_PIN_INT4, + kWAKEUP_PIN_INT5, + kWAKEUP_PIN_INT6, + kWAKEUP_PIN_INT7 + }; + + /* PINT doesn't work in standby mode. */ + if (!is_standby) { + int slot = nxp_pint_pin_get_slot_index(wakeup_pin_dt.pin); + + if (slot >= 0 && slot < ARRAY_SIZE(pint_wakeup_sources)) { + *wakeup_sources |= pint_wakeup_sources[slot]; + } + } +#endif +} + +__weak void pm_state_set(enum pm_state state, uint8_t id) { ARG_UNUSED(id); + uint32_t exclude_from_pd; + uint64_t wakeup_sources; + status_t status; + + /* Set PRIMASK */ + __disable_irq(); + /* Set BASEPRI to 0 */ + irq_unlock(0); switch (state) { case PM_STATE_RUNTIME_IDLE: - k_cpu_idle(); + POWER_EnterSleep(); + break; + + case PM_STATE_SUSPEND_TO_IDLE: + pm_get_lowpower_resource_list(&exclude_from_pd, &wakeup_sources, false); + status = POWER_EnterDeepSleep(exclude_from_pd, wakeup_sources); + if (status != kStatus_Success) { + LOG_ERR("Failed to enter deep sleep mode: %d", status); + } break; - default: + case PM_STATE_STANDBY: + pm_get_lowpower_resource_list(&exclude_from_pd, &wakeup_sources, true); + status = POWER_EnterPowerDown(exclude_from_pd, wakeup_sources, 1); + if (status != kStatus_Success) { + LOG_ERR("Failed to enter power down mode: %d", status); + } + break; + + default: + LOG_DBG("Unsupported power state %u", state); break; } } -void pm_state_exit_post_ops(enum pm_state state, uint8_t id) +__weak void pm_state_exit_post_ops(enum pm_state state, uint8_t id) { ARG_UNUSED(state); ARG_UNUSED(id); + + /* Clear PRIMASK */ + __enable_irq(); +} + +#if WAKEUP_PIN_ENABLE +static void init_wakeup_gpio_pins(void) +{ + int ret; + + if (!device_is_ready(wakeup_pin_dt.port)) { + LOG_ERR("Wake-up GPIO device not ready"); + return; + } + + ret = gpio_pin_configure_dt(&wakeup_pin_dt, GPIO_INPUT); + if (ret != 0) { + LOG_ERR("Error %d: failed to configure wakeup pin\n", ret); + return; + } + + ret = gpio_pin_interrupt_configure_dt(&wakeup_pin_dt, GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("Error %d: failed to configure wakeup pin interrupt\n", ret); + return; + } +} +#endif + +static int nxp_mcxw2xx_power_init(void) +{ +#if WAKEUP_PIN_ENABLE + init_wakeup_gpio_pins(); +#endif /* WAKEUP_PIN_ENABLE */ + + return 0; } + +void nxp_mcxw2xx_power_early_init(void) +{ + POWER_Init(); +} + +SYS_INIT(nxp_mcxw2xx_power_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/soc.c b/soc/nxp/mcx/mcxw/mcxw2xx/soc.c index e561ca8d7e78c..6b35ab5dbb8be 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/soc.c +++ b/soc/nxp/mcx/mcxw/mcxw2xx/soc.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -28,10 +29,16 @@ #include #endif +LOG_MODULE_REGISTER(soc, CONFIG_SOC_LOG_LEVEL); + /* System clock frequency */ extern uint32_t SystemCoreClock; extern void nxp_nbu_init(void); +#if CONFIG_PM +void nxp_mcxw2xx_power_early_init(void); +#endif /* CONFIG_PM */ + #define CTIMER_CLOCK_SOURCE(node_id) \ TO_CTIMER_CLOCK_SOURCE(DT_CLOCKS_CELL(node_id, name), DT_PROP(node_id, clk_source)) #define TO_CTIMER_CLOCK_SOURCE(inst, val) TO_CLOCK_ATTACH_ID(inst, val) @@ -98,10 +105,17 @@ __weak void clock_init(void) configure_32k_osc(); #if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(os_timer), nxp_os_timer, okay) - /*!< OS event timer select FRO 1 MHz clock */ + /* + * OS event timer generally uses FRO 1 MHz clock. + * When power management is enabled, uses 32K clock for lower power. + */ PMC->OSTIMERr &= ~PMC_OSTIMER_OSTIMERCLKSEL_MASK; +#if CONFIG_PM + PMC->OSTIMERr |= OSTIMERCLKSEL_32768 << PMC_OSTIMER_OSTIMERCLKSEL_SHIFT; +#else PMC->OSTIMERr |= OSTIMERCLKSEL_FRO_1MHz << PMC_OSTIMER_OSTIMERCLKSEL_SHIFT; #endif +#endif #if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(iap), nxp_iap_fmc55, okay) /* kCLOCK_Sysctl must be enabled for FLASH_CacheClear, @@ -132,6 +146,10 @@ void soc_early_init_hook(void) { z_arm_clear_faults(); +#if CONFIG_PM + nxp_mcxw2xx_power_early_init(); +#endif /* CONFIG_PM */ + /* Initialize FRO/system clock to 96 MHz */ clock_init(); From 8ca7b4b5e94a8378e4bc3cc77246d1c3cbb4cb8a Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Mon, 17 Nov 2025 16:20:34 +0800 Subject: [PATCH 10/11] soc: nxp: mcxw2xx: Add poweroff support Add poweroff MCXW2xx SoCs, support btn_wk pin wakeup. Signed-off-by: Jason Yu --- soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt | 2 ++ soc/nxp/mcx/mcxw/mcxw2xx/Kconfig | 3 +++ soc/nxp/mcx/mcxw/mcxw2xx/poweroff.c | 34 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 soc/nxp/mcx/mcxw/mcxw2xx/poweroff.c diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt b/soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt index 6b55a6def0569..1ad3b988cf4c1 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt +++ b/soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt @@ -6,6 +6,8 @@ zephyr_sources(soc.c) zephyr_sources_ifdef(CONFIG_PM power.c) +zephyr_sources_ifdef(CONFIG_POWEROFF poweroff.c) + zephyr_include_directories(./) set(SOC_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld CACHE INTERNAL "") diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig index 7172ccebf93ba..210dbc43eb2e0 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig +++ b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig @@ -1,3 +1,6 @@ # Copyright 2025 NXP # # SPDX-License-Identifier: Apache-2.0 + +config SOC_SERIES_MCXW2XX + select HAS_POWEROFF diff --git a/soc/nxp/mcx/mcxw/mcxw2xx/poweroff.c b/soc/nxp/mcx/mcxw/mcxw2xx/poweroff.c new file mode 100644 index 0000000000000..31e250d262773 --- /dev/null +++ b/soc/nxp/mcx/mcxw/mcxw2xx/poweroff.c @@ -0,0 +1,34 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#if CONFIG_GPIO && DT_NODE_EXISTS(DT_NODELABEL(btn_wk)) + +#if (DT_GPIO_FLAGS(DT_NODELABEL(btn_wk), gpios)) & GPIO_ACTIVE_LOW +#define POWEROFF_WAKEUP (kWAKEUP_PIN_ENABLE | \ + kWAKEUP_PIN_PUP_EN | \ + kWAKEUP_PIN_WAKEUP_LOW_LVL) +#else /* !GPIO_ACTIVE_LOW */ +#define POWEROFF_WAKEUP (kWAKEUP_PIN_ENABLE | \ + kWAKEUP_PIN_PDN_EN | \ + kWAKEUP_PIN_WAKEUP_HIGH_LVL) +#endif /* GPIO_ACTIVE_LOW */ + +#else +#define POWEROFF_WAKEUP kWAKEUP_PIN_DISABLE +#endif /* CONFIG_GPIO && DT_NODE_EXISTS(DT_NODELABEL(btn_wk)) */ + +void z_sys_poweroff(void) +{ + POWER_EnterPowerOff(0, POWEROFF_WAKEUP); + + CODE_UNREACHABLE; +} From 2e2eba2b051f938354d593060e18fe0412cca5c9 Mon Sep 17 00:00:00 2001 From: Jason Yu Date: Fri, 7 Nov 2025 10:46:18 +0800 Subject: [PATCH 11/11] boards: nxp: mcxw23: Add power management MCXW23 board Add mcxw23_evk and frdm_mcxw23 platforms to the power management test suite to validate power management functionality on MCXW23 hardware. Signed-off-by: Jason Yu --- boards/nxp/frdm_mcxw23/doc/index.rst | 8 ++++++++ boards/nxp/mcxw23_evk/doc/index.rst | 8 ++++++++ tests/subsys/pm/power_mgmt_soc/testcase.yaml | 2 ++ 3 files changed, 18 insertions(+) diff --git a/boards/nxp/frdm_mcxw23/doc/index.rst b/boards/nxp/frdm_mcxw23/doc/index.rst index eaf5ed522c2dd..dfb4e6fa56559 100644 --- a/boards/nxp/frdm_mcxw23/doc/index.rst +++ b/boards/nxp/frdm_mcxw23/doc/index.rst @@ -198,6 +198,14 @@ should see the following message in the terminal: *** Booting Zephyr OS build v4.2.0-2105-g48f2ffda26de *** Hello World! frdm_mcxw23/mcxw236 +Power Management +================ + +When Power Management is enabled :kconfig:option:`CONFIG_PM`, OSTIMER is used as +OS tick timer. + +Limitation: Wakeup pin can't be used as wakeup source in Standby mode. + .. include:: ../../common/board-footer.rst.inc .. _MCXW23 SoC Website: diff --git a/boards/nxp/mcxw23_evk/doc/index.rst b/boards/nxp/mcxw23_evk/doc/index.rst index 866dc06715f6b..49285337fe9bb 100644 --- a/boards/nxp/mcxw23_evk/doc/index.rst +++ b/boards/nxp/mcxw23_evk/doc/index.rst @@ -180,6 +180,14 @@ should see the following message in the terminal: *** Booting Zephyr OS build v4.2.0-2105-g9da1d56da9e7 *** Hello World! mcxw23_evk/mcxw236 +Power Management +================ + +When Power Management is enabled :kconfig:option:`CONFIG_PM`, OSTIMER is used as +OS tick timer. + +Limitation: Wakeup pin can't be used as wakeup source in Standby mode. + .. include:: ../../common/board-footer.rst.inc .. _MCXW23 SoC Website: diff --git a/tests/subsys/pm/power_mgmt_soc/testcase.yaml b/tests/subsys/pm/power_mgmt_soc/testcase.yaml index 4f17ce2010a64..5869ed545fdbc 100644 --- a/tests/subsys/pm/power_mgmt_soc/testcase.yaml +++ b/tests/subsys/pm/power_mgmt_soc/testcase.yaml @@ -29,6 +29,8 @@ tests: - mck_ra8t2/r7ka8t2lfecac/cm85 - ek_ra8d2/r7ka8d2kflcac/cm85 - ek_ra8m2/r7ka8m2jflcac/cm85 + - mcxw23_evk + - frdm_mcxw23 tags: pm integration_platforms: - mec15xxevb_assy6853