Skip to content
Draft
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
42 changes: 42 additions & 0 deletions dts/arm/nxp/nxp_mcxw7x_common.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
cpu0: cpu@0 {
compatible = "arm,cortex-m33f";
reg = <0>;
cpu-power-states = <&sleep &sleep_optimized &deep_sleep>;
#address-cells = <1>;
#size-cells = <1>;

Expand All @@ -40,6 +41,28 @@
reg = <0xe000ed90 0x40>;
};
};

power-states {
sleep: sleep {
compatible = "zephyr,power-state";
power-state-name = "runtime-idle";
min-residency-us = <100>;
exit-latency-us = <0>;
};
sleep_optimized: sleep-optimized {
compatible = "zephyr,power-state";
power-state-name = "suspend-to-idle";
substate-id = <0>;
min-residency-us = <500>;
exit-latency-us = <10>;
};
deep_sleep: deep-sleep {
compatible = "zephyr,power-state";
power-state-name = "standby";
min-residency-us = <1000>;
exit-latency-us = <11>;
};
};
};

soc {
Expand Down Expand Up @@ -114,6 +137,24 @@
#address-cells = <1>;
#size-cells = <1>;

cmc: system-modules@1000 {
compatible = "nxp,cmc";
reg = <0x1000 0x1000>;
interrupts = <1 0>;
};

spc: system-modules@16000 {
compatible = "nxp,spc";
reg = <0x16000 0x1000>;
interrupts = <21 0>;
};

wuu: system-modules@19000 {
compatible = "nxp,wuu";
reg = <0x19000 0x1000>;
interrupts = <22 0>;
};

scg: clock-controller@1e000 {
compatible = "nxp,scg-k4";
reg = <0x1e000 0x404>;
Expand Down Expand Up @@ -248,6 +289,7 @@
};

vbat: vbat@2b000 {
compatible = "nxp,vbat";
reg = <0x2b000 0x33c>;
interrupts = <74 0>;
};
Expand Down
12 changes: 12 additions & 0 deletions dts/bindings/power/nxp,cmc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2025 NXP
# SPDX-License-Identifier: Apache-2.0

description: NXP Core Mode Controller (CMC)

compatible: "nxp,cmc"

include: base.yaml

properties:
reg:
required: true
12 changes: 12 additions & 0 deletions dts/bindings/power/nxp,spc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2025 NXP
# SPDX-License-Identifier: Apache-2.0

description: NXP System Power Control (SPC)

compatible: "nxp,spc"

include: base.yaml

properties:
reg:
required: true
12 changes: 12 additions & 0 deletions dts/bindings/power/nxp,vbat.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2025 NXP
# SPDX-License-Identifier: Apache-2.0

description: NXP Smart Power Switch (VBAT)

compatible: "nxp,vbat"

include: base.yaml

properties:
reg:
required: true
12 changes: 12 additions & 0 deletions dts/bindings/power/nxp,wuu.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2025 NXP
# SPDX-License-Identifier: Apache-2.0

description: NXP Wakeup Unit (WUU)

compatible: "nxp,wuu"

include: base.yaml

properties:
reg:
required: true
8 changes: 4 additions & 4 deletions modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ set_variable_ifdef(CONFIG_UART_MCUX_IUART CONFIG_MCUX_COMPONENT_driver.iua
set_variable_ifdef(CONFIG_ADC_MCUX_12B1MSPS_SAR CONFIG_MCUX_COMPONENT_driver.adc_12b1msps_sar)
set_variable_ifdef(CONFIG_HWINFO_MCUX_MCX_CMC CONFIG_MCUX_COMPONENT_driver.mcx_cmc)
set_variable_ifdef(CONFIG_HWINFO_MCUX_SRC CONFIG_MCUX_COMPONENT_driver.src)
set_variable_ifdef(CONFIG_DT_HAS_NXP_SPC_ENABLED CONFIG_MCUX_COMPONENT_driver.spc)
set_variable_ifdef(CONFIG_DT_HAS_NXP_CMC_ENABLED CONFIG_MCUX_COMPONENT_driver.cmc)
set_variable_ifdef(CONFIG_DT_HAS_NXP_VBAT_ENABLED CONFIG_MCUX_COMPONENT_driver.vbat)
set_variable_ifdef(CONFIG_DT_HAS_NXP_WUU_ENABLED CONFIG_MCUX_COMPONENT_driver.wuu)
set_variable_ifdef(CONFIG_HWINFO_MCUX_SIM CONFIG_MCUX_COMPONENT_driver.sim)
set_variable_ifdef(CONFIG_HWINFO_MCUX_RCM CONFIG_MCUX_COMPONENT_driver.rcm)
set_variable_ifdef(CONFIG_IPM_MCUX CONFIG_MCUX_COMPONENT_driver.mailbox)
Expand Down Expand Up @@ -185,10 +189,6 @@ if(CONFIG_SOC_FAMILY_MCXN OR CONFIG_SOC_FAMILY_MCXA)
set(CONFIG_MCUX_COMPONENT_driver.mcx_spc ON)
endif()

if(CONFIG_BT_NXP AND CONFIG_SOC_FAMILY_MCXW OR CONFIG_IEEE802154_MCXW)
set(CONFIG_MCUX_COMPONENT_driver.spc ON)
endif()

if(((${MCUX_DEVICE} MATCHES "MIMXRT1[0-9][0-9][0-9]") AND (NOT (CONFIG_SOC_MIMXRT1166_CM4 OR CONFIG_SOC_MIMXRT1176_CM4 OR CONFIG_SOC_MIMXRT1189_CM33))) OR
((${MCUX_DEVICE} MATCHES "MIMX9596") AND CONFIG_SOC_MIMX9596_M7))
set_variable_ifdef(CONFIG_HAS_MCUX_CACHE CONFIG_MCUX_COMPONENT_driver.cache_armv7_m7)
Expand Down
1 change: 1 addition & 0 deletions soc/nxp/mcx/mcxw/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ config SOC_FAMILY_MCXW
select SOC_EARLY_INIT_HOOK
select CLOCK_CONTROL
select HAS_SEGGER_RTT if ZEPHYR_SEGGER_MODULE
select HAS_PM
2 changes: 2 additions & 0 deletions soc/nxp/mcx/mcxw/mcxw7xx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ zephyr_sources_ifdef(CONFIG_NXP_NBU
../../../common/nxp_nbu.c
)

zephyr_sources_ifdef(CONFIG_PM power.c)

zephyr_include_directories(./)

set(SOC_LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld CACHE INTERNAL "")
Expand Down
5 changes: 5 additions & 0 deletions soc/nxp/mcx/mcxw/mcxw7xx/Kconfig.defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ config NUM_IRQS
default 77 if SOC_MCXW727C
default 75

config MCUX_LPTMR_TIMER
default y if PM

DT_LPTMR_PATH := $(dt_nodelabel_path,lptmr0)
config SYS_CLOCK_HW_CYCLES_PER_SEC
default 96000000 if CORTEX_M_SYSTICK
default $(dt_node_int_prop_int,$(DT_LPTMR_PATH),clock-frequency) if MCUX_LPTMR_TIMER
Comment on lines 12 to +17
Copy link
Member

Choose a reason for hiding this comment

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

are you sure it's getting set right, I thought the first value will take precedence here, and I don't see wheere would "CORTEX_M_SYSTICK" be unset

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CORTEX_M_SYSTICK is disabled when LPTMR is enabled.

Copy link
Member

Choose a reason for hiding this comment

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

where does that happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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


config MCUX_FLASH_K4_API
default y
Expand Down
202 changes: 202 additions & 0 deletions soc/nxp/mcx/mcxw/mcxw7xx/power.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/pm/pm.h>
#include "fsl_cmc.h"
#include "fsl_spc.h"
#include "fsl_vbat.h"

#include <zephyr/logging/log.h>

LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL);

#define WUU_WAKEUP_LPTMR_IDX 0

/*
* 1. Set power mode protection
* 2. Disable low power mode debug
* 3. Enable Flash Doze mode.
*/
static void set_cmc_configuration(void)
{
CMC_SetPowerModeProtection(CMC0, kCMC_AllowAllLowPowerModes);
CMC_LockPowerModeProtectionSetting(CMC0);
CMC_EnableDebugOperation(CMC0, IS_ENABLED(CONFIG_DEBUG));
CMC_ConfigFlashMode(CMC0, false, false, false);
}

/*
* Disable Backup SRAM regulator, FRO16K and Bandgap which
* locates in VBAT power domain for most of power modes.
*
*/
static void deinit_vbat(void)
{
VBAT_EnableBackupSRAMRegulator(VBAT0, false);
VBAT_EnableFRO16k(VBAT0, false);
while (VBAT_CheckFRO16kEnabled(VBAT0)) {
};
VBAT_EnableBandgap(VBAT0, false);
while (VBAT_CheckBandgapEnabled(VBAT0)) {
};
}

/* Invoke Low Power/System Off specific Tasks */
__weak void pm_state_set(enum pm_state state, uint8_t substate_id)
{
/* Set PRIMASK */
__disable_irq();
/* Set BASEPRI to 0 */
irq_unlock(0);
Comment on lines +50 to +53
Copy link
Member

Choose a reason for hiding this comment

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

should not be necessary, already done by arch code last time I checked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is needed. We can discuss where you see this implemented.

Copy link
Member

Choose a reason for hiding this comment

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

(void) arch_irq_lock();

Copy link
Contributor Author

Choose a reason for hiding this comment

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


if (state == PM_STATE_RUNTIME_IDLE) {
k_cpu_idle();
return;
}

set_cmc_configuration();
deinit_vbat();

switch (state) {
case PM_STATE_SUSPEND_TO_IDLE:
cmc_power_domain_config_t config;

if (substate_id == 0) {
/* Set NBU into Sleep Mode */
RFMC->RF2P4GHZ_CTRL = (RFMC->RF2P4GHZ_CTRL &
(~RFMC_RF2P4GHZ_CTRL_LP_MODE_MASK)) |
RFMC_RF2P4GHZ_CTRL_LP_MODE(0x1);
RFMC->RF2P4GHZ_CTRL |= RFMC_RF2P4GHZ_CTRL_LP_ENTER_MASK;

/* Set MAIN_CORE and MAIN_WAKE power domain into sleep mode. */
config.clock_mode = kCMC_GateAllSystemClocksEnterLowPowerMode;
config.main_domain = kCMC_SleepMode;
config.wake_domain = kCMC_SleepMode;
CMC_EnterLowPowerMode(CMC0, &config);
Comment on lines +74 to +78
Copy link
Member

Choose a reason for hiding this comment

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

we should not be setting domain states in the SOC power driver, this is backwards. the state of the domains should be handled by domain drivers and the result will affect what actual SOC state is selected in the first place based on device runtime PM constraints. cc @bjarki-andreasen

Copy link
Contributor

Choose a reason for hiding this comment

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

Optimally the devicetree would contain the power domains which manage the main and wake domains, if anything needs them to be resumed, the power domains would be resumed directly (thus not in sleep mode) and the sleep mode which can only be entered if they are suspended, would have been blocked by the drivers/power domains with pm_policy_state_lock_get() or similar.

That said, some hardware is just quirky, it may make no sense to manage these power domains independently, which is maybe hinted at by the CMC_EnterLowPowerMode(CMC0, &config); taking one config which configures all of them together, so this may just be the way to enter one particular sleep mode.

} else if (substate_id == 1) {
} else {
/* Nothing to do */
}
break;
case PM_STATE_STANDBY:
/* Enable CORE VDD Voltage scaling. */
SPC_EnableLowPowerModeCoreVDDInternalVoltageScaling(SPC0, true);

/* Set NBU into Deep Sleep Mode */
RFMC->RF2P4GHZ_CTRL = (RFMC->RF2P4GHZ_CTRL & (~RFMC_RF2P4GHZ_CTRL_LP_MODE_MASK)) |
RFMC_RF2P4GHZ_CTRL_LP_MODE(0x3);
RFMC->RF2P4GHZ_CTRL |= RFMC_RF2P4GHZ_CTRL_LP_ENTER_MASK;

/* Set MAIN_CORE and MAIN_WAKE power domain into Deep Sleep Mode. */
config.clock_mode = kCMC_GateAllSystemClocksEnterLowPowerMode;
config.main_domain = kCMC_DeepSleepMode;
config.wake_domain = kCMC_DeepSleepMode;

CMC_EnterLowPowerMode(CMC0, &config);

break;
default:
LOG_DBG("Unsupported power state %u", state);
break;
}
}

/* Handle SOC specific activity after Low Power Mode Exit */
__weak void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id)
{
ARG_UNUSED(state);
ARG_UNUSED(substate_id);

/* Clear PRIMASK */
__enable_irq();

if (state == PM_STATE_RUNTIME_IDLE) {
return;
}

if (SPC_CheckPowerDomainLowPowerRequest(SPC0, kSPC_PowerDomain0)) {
SPC_ClearPowerDomainLowPowerRequestFlag(SPC0, kSPC_PowerDomain0);
}
if (SPC_CheckPowerDomainLowPowerRequest(SPC0, kSPC_PowerDomain1)) {
SPC_ClearPowerDomainLowPowerRequestFlag(SPC0, kSPC_PowerDomain1);
}
if (SPC_CheckPowerDomainLowPowerRequest(SPC0, kSPC_PowerDomain2)) {
RFMC->RF2P4GHZ_CTRL = (RFMC->RF2P4GHZ_CTRL & (~RFMC_RF2P4GHZ_CTRL_LP_MODE_MASK));
RFMC->RF2P4GHZ_CTRL &= ~RFMC_RF2P4GHZ_CTRL_LP_ENTER_MASK;
SPC_ClearPowerDomainLowPowerRequestFlag(SPC0, kSPC_PowerDomain2);
}
SPC_ClearLowPowerRequest(SPC0);
}

/*
* In active mode, all HVDs/LVDs are disabled.
* DCDC regulated to 1.8V, Core LDO regulated to 1.1V;
* In low power modes, all HVDs/LVDs are disabled.
* Bandgap is disabled, DCDC regulated to 1.25V, Core LDO regulated to 1.05V.
*/
__weak void set_spc_configuration(void)
{
/* Disable LVDs and HVDs in Active mode. */
SPC_EnableActiveModeCoreHighVoltageDetect(SPC0, false);
SPC_EnableActiveModeCoreLowVoltageDetect(SPC0, false);
SPC_EnableActiveModeSystemHighVoltageDetect(SPC0, false);
SPC_EnableActiveModeSystemLowVoltageDetect(SPC0, false);
SPC_EnableActiveModeIOHighVoltageDetect(SPC0, false);
SPC_EnableActiveModeIOLowVoltageDetect(SPC0, false);
while (SPC_GetBusyStatusFlag(SPC0)) {
}

spc_active_mode_regulators_config_t active_mode_regulator;

active_mode_regulator.bandgapMode = kSPC_BandgapEnabledBufferDisabled;
active_mode_regulator.lpBuff = false;
/* DCDC regulate to 1.8V. */
active_mode_regulator.DCDCOption.DCDCVoltage = kSPC_DCDC_SafeModeVoltage;
active_mode_regulator.DCDCOption.DCDCDriveStrength = kSPC_DCDC_NormalDriveStrength;
active_mode_regulator.SysLDOOption.SysLDOVoltage = kSPC_SysLDO_NormalVoltage;
active_mode_regulator.SysLDOOption.SysLDODriveStrength = kSPC_SysLDO_NormalDriveStrength;
/* Core LDO regulate to 1.1V. */
active_mode_regulator.CoreLDOOption.CoreLDOVoltage = kSPC_CoreLDO_MidDriveVoltage;
#if defined(FSL_FEATURE_SPC_HAS_CORELDO_VDD_DS) && FSL_FEATURE_SPC_HAS_CORELDO_VDD_DS
active_mode_regulator.CoreLDOOption.CoreLDODriveStrength = kSPC_CoreLDO_NormalDriveStrength;
#endif /* FSL_FEATURE_SPC_HAS_CORELDO_VDD_DS */

SPC_SetActiveModeDCDCRegulatorConfig(SPC0, &active_mode_regulator.DCDCOption);

while (SPC_GetBusyStatusFlag(SPC0)) {
}

SPC_SetActiveModeSystemLDORegulatorConfig(SPC0, &active_mode_regulator.SysLDOOption);

SPC_SetActiveModeBandgapModeConfig(SPC0, active_mode_regulator.bandgapMode);

SPC_SetActiveModeCoreLDORegulatorConfig(SPC0, &active_mode_regulator.CoreLDOOption);

SPC_EnableActiveModeCMPBandgapBuffer(SPC0, active_mode_regulator.lpBuff);

spc_lowpower_mode_regulators_config_t low_power_regulator;

low_power_regulator.lpIREF = false;
low_power_regulator.bandgapMode = kSPC_BandgapDisabled;
low_power_regulator.lpBuff = false;
low_power_regulator.CoreIVS = false;
low_power_regulator.DCDCOption.DCDCVoltage = kSPC_DCDC_LowUnderVoltage;
low_power_regulator.DCDCOption.DCDCDriveStrength = kSPC_DCDC_LowDriveStrength;
low_power_regulator.SysLDOOption.SysLDODriveStrength = kSPC_SysLDO_LowDriveStrength;
low_power_regulator.CoreLDOOption.CoreLDOVoltage = kSPC_CoreLDO_MidDriveVoltage;
low_power_regulator.CoreLDOOption.CoreLDODriveStrength = kSPC_CoreLDO_LowDriveStrength;

SPC_SetLowPowerModeRegulatorsConfig(SPC0, &low_power_regulator);

SPC_SetLowPowerWakeUpDelay(SPC0, 0xFFFFU);
}

void nxp_mcxw7x_power_init(void)
{
set_spc_configuration();
/* Enable LPTMR0 as wakeup source */
NXP_ENABLE_WAKEUP_SIGNAL(WUU_WAKEUP_LPTMR_IDX);
}
Loading