| /* |
| * Copyright 2023-2025 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <zephyr/kernel.h> |
| #include <zephyr/pm/pm.h> |
| #include <fsl_clock.h> |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(standby)) |
| #include <fsl_rtc.h> |
| #endif |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #if CONFIG_GPIO && (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1))) |
| #include <zephyr/drivers/gpio/gpio_mcux_lpc.h> |
| #endif |
| #include <zephyr/drivers/timer/system_timer.h> |
| #include <zephyr/drivers/timer/nxp_os_timer.h> |
| #include <zephyr/platform/hooks.h> |
| #include "fsl_power.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_DECLARE(soc, CONFIG_SOC_LOG_LEVEL); |
| |
| /* Active mode */ |
| #define POWER_MODE0 0 |
| /* Idle mode */ |
| #define POWER_MODE1 1 |
| /* Standby mode */ |
| #define POWER_MODE2 2 |
| /* Sleep mode */ |
| #define POWER_MODE3 3 |
| /* Deep Sleep mode */ |
| #define POWER_MODE4 4 |
| |
| #define NODE_ID DT_INST(0, nxp_pdcfg_power) |
| |
| extern void clock_init(void); |
| |
| power_sleep_config_t slp_cfg; |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) || DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1)) |
| pinctrl_soc_pin_t pin_cfg; |
| #if CONFIG_GPIO |
| const struct device *gpio; |
| #endif |
| #endif |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) |
| static void pin0_isr(const struct device *dev) |
| { |
| uint8_t level = ~(DT_ENUM_IDX(DT_NODELABEL(pin0), wakeup_level)) & 0x1; |
| |
| POWER_ConfigWakeupPin(kPOWER_WakeupPin0, level); |
| NVIC_ClearPendingIRQ(DT_IRQN(DT_NODELABEL(pin0))); |
| DisableIRQ(DT_IRQN(DT_NODELABEL(pin0))); |
| POWER_DisableWakeup(DT_IRQN(DT_NODELABEL(pin0))); |
| } |
| #endif |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1)) |
| static void pin1_isr(const struct device *dev) |
| { |
| uint8_t level = ~(DT_ENUM_IDX(DT_NODELABEL(pin1), wakeup_level)) & 0x1; |
| |
| POWER_ConfigWakeupPin(kPOWER_WakeupPin1, level); |
| NVIC_ClearPendingIRQ(DT_IRQN(DT_NODELABEL(pin1))); |
| DisableIRQ(DT_IRQN(DT_NODELABEL(pin1))); |
| POWER_DisableWakeup(DT_IRQN(DT_NODELABEL(pin1))); |
| } |
| #endif |
| |
| #ifdef CONFIG_MPU |
| |
| #define MPU_NUM_REGIONS 8 |
| |
| typedef struct mpu_conf { |
| uint32_t CTRL; |
| uint32_t RNR; |
| uint32_t RBAR[MPU_NUM_REGIONS]; |
| uint32_t RLAR[MPU_NUM_REGIONS]; |
| uint32_t RBAR_A1; |
| uint32_t RLAR_A1; |
| uint32_t RBAR_A2; |
| uint32_t RLAR_A2; |
| uint32_t RBAR_A3; |
| uint32_t RLAR_A3; |
| uint32_t MAIR0; |
| uint32_t MAIR1; |
| } mpu_conf; |
| mpu_conf saved_mpu_conf; |
| |
| static void save_mpu_state(void) |
| { |
| uint32_t index; |
| |
| /* |
| * MPU is divided in `MPU_NUM_REGIONS` regions. |
| * Here we save those regions configuration to be restored after PM3 entry |
| */ |
| for (index = 0U; index < MPU_NUM_REGIONS; index++) { |
| MPU->RNR = index; |
| saved_mpu_conf.RBAR[index] = MPU->RBAR; |
| saved_mpu_conf.RLAR[index] = MPU->RLAR; |
| } |
| |
| saved_mpu_conf.CTRL = MPU->CTRL; |
| saved_mpu_conf.RNR = MPU->RNR; |
| saved_mpu_conf.RBAR_A1 = MPU->RBAR_A1; |
| saved_mpu_conf.RLAR_A1 = MPU->RLAR_A1; |
| saved_mpu_conf.RBAR_A2 = MPU->RBAR_A2; |
| saved_mpu_conf.RLAR_A2 = MPU->RLAR_A2; |
| saved_mpu_conf.RBAR_A3 = MPU->RBAR_A3; |
| saved_mpu_conf.RLAR_A3 = MPU->RLAR_A3; |
| saved_mpu_conf.MAIR0 = MPU->MAIR0; |
| saved_mpu_conf.MAIR1 = MPU->MAIR1; |
| } |
| |
| static void restore_mpu_state(void) |
| { |
| uint32_t index; |
| |
| for (index = 0U; index < MPU_NUM_REGIONS; index++) { |
| MPU->RNR = index; |
| MPU->RBAR = saved_mpu_conf.RBAR[index]; |
| MPU->RLAR = saved_mpu_conf.RLAR[index]; |
| } |
| |
| MPU->RNR = saved_mpu_conf.RNR; |
| MPU->RBAR_A1 = saved_mpu_conf.RBAR_A1; |
| MPU->RLAR_A1 = saved_mpu_conf.RLAR_A1; |
| MPU->RBAR_A2 = saved_mpu_conf.RBAR_A2; |
| MPU->RLAR_A2 = saved_mpu_conf.RLAR_A2; |
| MPU->RBAR_A3 = saved_mpu_conf.RBAR_A3; |
| MPU->RLAR_A3 = saved_mpu_conf.RLAR_A3; |
| MPU->MAIR0 = saved_mpu_conf.MAIR0; |
| MPU->MAIR1 = saved_mpu_conf.MAIR1; |
| |
| /* |
| * CTRL register must be restored last in case MPU was enabled, |
| * because some MPU registers can't be programmed while the MPU is enabled |
| */ |
| MPU->CTRL = saved_mpu_conf.CTRL; |
| } |
| |
| #endif /* CONFIG_MPU */ |
| static void config_wakeup_gpio_pins(void) |
| { |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) |
| pin_cfg = IOMUX_GPIO_IDX(24) | IOMUX_TYPE(IOMUX_GPIO); |
| pinctrl_configure_pins(&pin_cfg, 1, 0); |
| #endif |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1)) |
| pin_cfg = IOMUX_GPIO_IDX(25) | IOMUX_TYPE(IOMUX_GPIO); |
| pinctrl_configure_pins(&pin_cfg, 1, 0); |
| #endif |
| } |
| |
| /* Invoke Low Power/System Off specific Tasks */ |
| __weak void pm_state_set(enum pm_state state, uint8_t substate_id) |
| { |
| ARG_UNUSED(substate_id); |
| |
| config_wakeup_gpio_pins(); |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) |
| POWER_ConfigWakeupPin(kPOWER_WakeupPin0, DT_ENUM_IDX(DT_NODELABEL(pin0), wakeup_level)); |
| POWER_ClearWakeupStatus(DT_IRQN(DT_NODELABEL(pin0))); |
| NVIC_ClearPendingIRQ(DT_IRQN(DT_NODELABEL(pin0))); |
| EnableIRQ(DT_IRQN(DT_NODELABEL(pin0))); |
| POWER_EnableWakeup(DT_IRQN(DT_NODELABEL(pin0))); |
| #endif |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1)) |
| POWER_ConfigWakeupPin(kPOWER_WakeupPin1, DT_ENUM_IDX(DT_NODELABEL(pin1), wakeup_level)); |
| POWER_ClearWakeupStatus(DT_IRQN(DT_NODELABEL(pin1))); |
| NVIC_ClearPendingIRQ(DT_IRQN(DT_NODELABEL(pin1))); |
| EnableIRQ(DT_IRQN(DT_NODELABEL(pin1))); |
| POWER_EnableWakeup(DT_IRQN(DT_NODELABEL(pin1))); |
| #endif |
| |
| /* Set PRIMASK */ |
| __disable_irq(); |
| /* Set BASEPRI to 0 */ |
| irq_unlock(0); |
| |
| switch (state) { |
| case PM_STATE_RUNTIME_IDLE: |
| POWER_SetSleepMode(POWER_MODE1); |
| __WFI(); |
| break; |
| case PM_STATE_SUSPEND_TO_IDLE: |
| /* save old value of main clock mux and switch to lposc */ |
| uint32_t main_sel_a = CLKCTL0->MAINCLKSELA; |
| uint32_t main_sel_b = CLKCTL0->MAINCLKSELB; |
| |
| CLKCTL0->MAINCLKSELA = 2; |
| CLKCTL0->MAINCLKSELB = 0; |
| POWER_EnterPowerMode(POWER_MODE2, &slp_cfg); |
| /* restore previous main clock */ |
| CLKCTL0->MAINCLKSELA = main_sel_a; |
| CLKCTL0->MAINCLKSELB = main_sel_b; |
| |
| break; |
| case PM_STATE_STANDBY: |
| #ifdef CONFIG_MPU |
| /* Save MPU state before entering PM3 */ |
| save_mpu_state(); |
| #endif /* CONFIG_MPU */ |
| |
| POWER_EnableWakeup(DT_IRQN(DT_NODELABEL(rtc))); |
| |
| sys_clock_set_timeout(0, true); |
| |
| if (POWER_EnterPowerMode(POWER_MODE3, &slp_cfg)) { |
| /* Go back to PM Mode 3 if RTC wakeup is to be ignored.*/ |
| while (z_nxp_os_timer_ignore_timer_wakeup() && |
| (PMU->WAKEUP_STATUS & PMU_WAKEUP_STATUS_RTC_MASK)) { |
| config_wakeup_gpio_pins(); |
| /* Reinitialize the OS Timer */ |
| CLOCK_AttachClk(kLPOSC_to_OSTIMER_CLK); |
| /* Clear the RTC wakeup bits */ |
| POWER_ClearWakeupStatus(DT_IRQN(DT_NODELABEL(rtc))); |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(standby)) |
| RTC_ClearStatusFlags(RTC, kRTC_WakeupFlag); |
| #endif |
| NVIC_ClearPendingIRQ(DT_IRQN(DT_NODELABEL(rtc))); |
| sys_clock_idle_exit(); |
| sys_clock_set_timeout(0, true); |
| /* GDET got enabled when exiting PM3, disable it |
| * again before re-entering PM3. |
| */ |
| POWER_DisableGDetVSensors(); |
| |
| /* Reinitialize the board specific power rails */ |
| board_early_init_hook(); |
| |
| if (!(POWER_EnterPowerMode(POWER_MODE3, &slp_cfg))) { |
| break; |
| } |
| } |
| #ifdef CONFIG_MPU |
| /* Restore MPU as is lost after PM3 exit*/ |
| restore_mpu_state(); |
| #endif /* CONFIG_MPU */ |
| clock_init(); |
| |
| sys_clock_idle_exit(); |
| } |
| |
| /* Clear the RTC wakeup bits */ |
| POWER_ClearWakeupStatus(DT_IRQN(DT_NODELABEL(rtc))); |
| POWER_DisableWakeup(DT_IRQN(DT_NODELABEL(rtc))); |
| |
| 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); |
| |
| #if CONFIG_GPIO && (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1))) |
| if (state == PM_STATE_STANDBY) { |
| /* GPIO_0_24 & GPIO_0_25 are used for wakeup */ |
| uint32_t pins = PMU->WAKEUP_STATUS & |
| (PMU_WAKEUP_STATUS_PIN0_MASK | PMU_WAKEUP_STATUS_PIN1_MASK); |
| gpio_mcux_lpc_trigger_cb(gpio, (pins << 24)); |
| } |
| #endif |
| /* Clear PRIMASK */ |
| __enable_irq(); |
| } |
| |
| void nxp_rw6xx_power_init(void) |
| { |
| uint32_t suspend_sleepconfig[5] = DT_PROP_OR(NODE_ID, deep_sleep_config, {}); |
| |
| slp_cfg.pm2MemPuCfg = suspend_sleepconfig[0]; |
| slp_cfg.pm2AnaPuCfg = suspend_sleepconfig[1]; |
| slp_cfg.clkGate = suspend_sleepconfig[2]; |
| slp_cfg.memPdCfg = suspend_sleepconfig[3]; |
| slp_cfg.pm3BuckCfg = suspend_sleepconfig[4]; |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) |
| /* PIN 0 uses GPIO0_24, confiure the pin as GPIO */ |
| pin_cfg = IOMUX_GPIO_IDX(24) | IOMUX_TYPE(IOMUX_GPIO); |
| pinctrl_configure_pins(&pin_cfg, 1, 0); |
| |
| /* Initialize the settings in the PMU for this wakeup interrupt */ |
| pin0_isr(NULL); |
| |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(pin0)), DT_IRQ(DT_NODELABEL(pin0), priority), pin0_isr, |
| NULL, 0); |
| #endif |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1)) |
| /* PIN 1 uses GPIO0_25, confiure the pin as GPIO */ |
| pin_cfg = IOMUX_GPIO_IDX(25) | IOMUX_TYPE(IOMUX_GPIO); |
| pinctrl_configure_pins(&pin_cfg, 1, 0); |
| |
| /* Initialize the settings in the PMU for this wakeup interrupt */ |
| pin1_isr(NULL); |
| |
| IRQ_CONNECT(DT_IRQN(DT_NODELABEL(pin1)), DT_IRQ(DT_NODELABEL(pin1), priority), pin1_isr, |
| NULL, 0); |
| #endif |
| |
| /* Clear the RTC wakeup bits */ |
| POWER_ClearWakeupStatus(DT_IRQN(DT_NODELABEL(rtc))); |
| POWER_DisableWakeup(DT_IRQN(DT_NODELABEL(rtc))); |
| |
| #if CONFIG_GPIO && (DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin0)) || \ |
| DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(pin1))) |
| gpio = DEVICE_DT_GET(DT_NODELABEL(hsgpio0)); |
| if (!device_is_ready(gpio)) { |
| return; |
| } |
| #endif |
| } |