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
8 changes: 8 additions & 0 deletions boards/nxp/frdm_mcxw23/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

This comment was marked as resolved.

================

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:
Expand Down
8 changes: 8 additions & 0 deletions boards/nxp/mcxw23_evk/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
105 changes: 104 additions & 1 deletion drivers/dma/dma_mcux_lpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,34 @@
#include <zephyr/sys/util_macro.h>
#include <zephyr/drivers/dma/dma_mcux_lpc.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>

#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;
Expand All @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
51 changes: 51 additions & 0 deletions dts/arm/nxp/nxp_mcxw23x_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

@yeaissa yeaissa Nov 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep sleep shall point to standby (This is the case for other NXP SoCs).
mcux_os_timer driver only supports the standby state in case of low power/wake up timer. please check:
https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/timer/mcux_os_timer.c#L97
Since we shall support other states that needs os_timer wakeup, we may need to update the driver by adding the higher states that requires this feature. @mmahadevan108 any idea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is added because ostimer can't work in standby mode for some SOCs, but it work in MCXW23 deep sleep and power down mode, so that code is not necessary.

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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that power_domain_soc_state_change is likely only useful when PM_DEVICE_SYSTEM_MANAGED is enabled.
Reference https://github.com/zephyrproject-rtos/zephyr/blob/enable-cmc-power-domain/drivers/power_domain/power_domain_soc_state_change.c#L50
This function relies on knowing the next allowed system state. However, when PM_DEVICE_SYSTEM_MANAGED is disabled, the next state after a power domain is suspended could still be active, which makes the logic less reliable in that scenario.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when PM_DEVICE_SYSTEM_MANAGED is disabled, the next state after a power domain is suspended could still be active

Could you please share more details?

#power-domain-cells = <0>;
onoff-power-states = <&standby>;
};

sysclk: system-clock {
Expand Down Expand Up @@ -175,6 +210,7 @@
mode = <0>;
input = <0>;
prescale = <0>;
zephyr,disabling-power-states = <&suspend &standby>;
};

ctimer1: ctimer@9000 {
Expand All @@ -187,6 +223,7 @@
mode = <0>;
input = <0>;
prescale = <0>;
zephyr,disabling-power-states = <&suspend &standby>;
};

ctimer2: ctimer@28000 {
Expand All @@ -199,6 +236,7 @@
mode = <0>;
input = <0>;
prescale = <0>;
zephyr,disabling-power-states = <&suspend &standby>;
};

ctimer3: ctimer@29000 {
Expand All @@ -211,6 +249,7 @@
mode = <0>;
input = <0>;
prescale = <0>;
zephyr,disabling-power-states = <&suspend &standby>;
};

ctimer4: ctimer@2a000 {
Expand All @@ -223,6 +262,7 @@
mode = <0>;
input = <0>;
prescale = <0>;
zephyr,disabling-power-states = <&suspend &standby>;
};

sc_timer: pwm@85000 {
Expand All @@ -232,6 +272,7 @@
status = "disabled";
clocks = <&syscon MCUX_SCTIMER_CLK>;
prescaler = <2>;
zephyr,disabling-power-states = <&suspend &standby>;
#pwm-cells = <3>;
};

Expand All @@ -251,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";
};

Expand All @@ -262,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";
};

Expand All @@ -273,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";
};

Expand All @@ -288,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>;
};
Expand All @@ -297,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>;
};
Expand Down Expand Up @@ -329,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";
Expand Down
2 changes: 2 additions & 0 deletions soc/nxp/mcx/mcxw/mcxw2xx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 "")
3 changes: 3 additions & 0 deletions soc/nxp/mcx/mcxw/mcxw2xx/Kconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Copyright 2025 NXP
#
# SPDX-License-Identifier: Apache-2.0

config SOC_SERIES_MCXW2XX
select HAS_POWEROFF
Loading