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/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/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/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/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 2cc7500dfc2a0..4f75c8cc34a02 100644 --- a/drivers/dma/dma_mcux_lpc.c +++ b/drivers/dma/dma_mcux_lpc.c @@ -23,11 +23,34 @@ #include #include #include +#include #define DT_DRV_COMPAT nxp_lpc_dma 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; @@ -53,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 { @@ -67,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; @@ -108,6 +137,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 +596,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 +849,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 +871,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 @@ -938,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 void dma_mcux_lpc_backup_reg(const struct device *dev) +{ + ARG_UNUSED(dev); +} + +static inline 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) { @@ -946,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; 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/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..8b483292532a9 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); } @@ -337,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)) 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_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/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"; }; 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 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_ */ 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/Kconfig.defconfig b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig index 3b260c2abb4fd..5c9db7b0e35e6 100644 --- a/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig +++ b/soc/nxp/mcx/mcxw/mcxw2xx/Kconfig.defconfig @@ -4,6 +4,13 @@ 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 y if PM + default n if !PM + config CORTEX_M_SYSTICK default n if MCUX_OS_TIMER @@ -13,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 @@ -36,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/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; +} 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(); 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