diff --git a/drivers/firmware/scmi/clk.c b/drivers/firmware/scmi/clk.c index 1abd04f1c49a0..514772572efba 100644 --- a/drivers/firmware/scmi/clk.c +++ b/drivers/firmware/scmi/clk.c @@ -6,6 +6,7 @@ #include #include +#include /* TODO: if extended attributes are supported this should be moved * to the header file so that users will have access to it. @@ -57,7 +58,7 @@ int scmi_clock_rate_get(struct scmi_protocol *proto, reply.len = sizeof(reply_buffer); reply.content = &reply_buffer; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -98,7 +99,7 @@ int scmi_clock_rate_set(struct scmi_protocol *proto, struct scmi_clock_rate_conf reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -134,7 +135,7 @@ int scmi_clock_parent_get(struct scmi_protocol *proto, uint32_t clk_id, uint32_t reply.len = sizeof(reply_buffer); reply.content = &reply_buffer; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -172,7 +173,7 @@ int scmi_clock_parent_set(struct scmi_protocol *proto, uint32_t clk_id, uint32_t reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -223,7 +224,7 @@ int scmi_clock_config_set(struct scmi_protocol *proto, reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -260,7 +261,7 @@ int scmi_clock_protocol_attributes(struct scmi_protocol *proto, uint32_t *attrib reply.len = sizeof(reply_buffer); reply.content = &reply_buffer; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } diff --git a/drivers/firmware/scmi/core.c b/drivers/firmware/scmi/core.c index 58e354485ee51..7b18315ffcd5e 100644 --- a/drivers/firmware/scmi/core.c +++ b/drivers/firmware/scmi/core.c @@ -8,6 +8,7 @@ #include #include #include +#include "mailbox.h" LOG_MODULE_REGISTER(scmi_core); @@ -87,12 +88,34 @@ static int scmi_core_setup_chan(const struct device *transport, return 0; } +static int scmi_interrupt_enable(struct scmi_channel *chan, bool enable) +{ + struct scmi_mbox_channel *mbox_chan; + struct mbox_dt_spec *tx_reply; + bool compInt; + + mbox_chan = chan->data; + compInt = enable ? SCMI_SHMEM_CHAN_FLAG_IRQ_BIT : 0; + + if (mbox_chan->tx_reply.dev) { + tx_reply = &mbox_chan->tx_reply; + } else { + tx_reply = &mbox_chan->tx; + } + + /* re-set completion interrupt */ + scmi_shmem_update_flags(mbox_chan->shmem, SCMI_SHMEM_CHAN_FLAG_IRQ_BIT, compInt); + + return mbox_set_enabled_dt(tx_reply, enable); +} + static int scmi_send_message_pre_kernel(struct scmi_protocol *proto, struct scmi_message *msg, struct scmi_message *reply) { int ret; + scmi_interrupt_enable(proto->tx, false); ret = scmi_transport_send_message(proto->transport, proto->tx, msg); if (ret < 0) { return ret; @@ -113,6 +136,7 @@ static int scmi_send_message_pre_kernel(struct scmi_protocol *proto, return ret; } + scmi_interrupt_enable(proto->tx, true); return ret; } @@ -159,7 +183,7 @@ static int scmi_send_message_post_kernel(struct scmi_protocol *proto, } int scmi_send_message(struct scmi_protocol *proto, struct scmi_message *msg, - struct scmi_message *reply) + struct scmi_message *reply, bool pre_kernel) { if (!proto->tx) { return -ENODEV; @@ -169,7 +193,7 @@ int scmi_send_message(struct scmi_protocol *proto, struct scmi_message *msg, return -EINVAL; } - if (k_is_pre_kernel()) { + if (pre_kernel) { return scmi_send_message_pre_kernel(proto, msg, reply); } else { return scmi_send_message_post_kernel(proto, msg, reply); diff --git a/drivers/firmware/scmi/nxp/cpu.c b/drivers/firmware/scmi/nxp/cpu.c index 6c3aaa0982247..caead1904e3f3 100644 --- a/drivers/firmware/scmi/nxp/cpu.c +++ b/drivers/firmware/scmi/nxp/cpu.c @@ -6,6 +6,7 @@ #include #include +#include DT_SCMI_PROTOCOL_DEFINE_NODEV(DT_INST(0, nxp_scmi_cpu), NULL); @@ -33,7 +34,43 @@ int scmi_cpu_sleep_mode_set(struct scmi_cpu_sleep_mode_config *cfg) reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, true); + if (ret < 0) { + return ret; + } + + if (status != SCMI_SUCCESS) { + return scmi_status_to_errno(status); + } + + return 0; +} + +int scmi_cpu_pd_lpm_set(struct scmi_cpu_pd_lpm_config *cfg) +{ + struct scmi_protocol *proto = &SCMI_PROTOCOL_NAME(SCMI_PROTOCOL_CPU_DOMAIN); + struct scmi_message msg, reply; + int status, ret; + + /* sanity checks */ + if (!proto || !cfg) { + return -EINVAL; + } + + if (proto->id != SCMI_PROTOCOL_CPU_DOMAIN) { + return -EINVAL; + } + + msg.hdr = SCMI_MESSAGE_HDR_MAKE(SCMI_CPU_DOMAIN_MSG_CPU_PD_LPM_CONFIG_SET, SCMI_COMMAND, + proto->id, 0x0); + msg.len = sizeof(*cfg); + msg.content = cfg; + + reply.hdr = msg.hdr; + reply.len = sizeof(status); + reply.content = &status; + + ret = scmi_send_message(proto, &msg, &reply, true); if (ret < 0) { return ret; } diff --git a/drivers/firmware/scmi/pinctrl.c b/drivers/firmware/scmi/pinctrl.c index fd0ef80d2ad58..b0600956a27b0 100644 --- a/drivers/firmware/scmi/pinctrl.c +++ b/drivers/firmware/scmi/pinctrl.c @@ -5,6 +5,7 @@ */ #include +#include DT_SCMI_PROTOCOL_DEFINE_NODEV(DT_INST(0, arm_scmi_pinctrl), NULL); @@ -50,7 +51,7 @@ int scmi_pinctrl_settings_configure(struct scmi_pinctrl_settings *settings) reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } diff --git a/drivers/firmware/scmi/power.c b/drivers/firmware/scmi/power.c index e2c639a03fc36..f7f5e74a8777a 100644 --- a/drivers/firmware/scmi/power.c +++ b/drivers/firmware/scmi/power.c @@ -6,6 +6,7 @@ #include #include +#include DT_SCMI_PROTOCOL_DEFINE_NODEV(DT_INST(0, arm_scmi_power), NULL); @@ -39,7 +40,7 @@ int scmi_power_state_get(uint32_t domain_id, uint32_t *power_state) reply.len = sizeof(reply_buffer); reply.content = &reply_buffer; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } @@ -82,7 +83,7 @@ int scmi_power_state_set(struct scmi_power_state_config *cfg) reply.len = sizeof(status); reply.content = &status; - ret = scmi_send_message(proto, &msg, &reply); + ret = scmi_send_message(proto, &msg, &reply, k_is_pre_kernel()); if (ret < 0) { return ret; } diff --git a/dts/arm/nxp/nxp_imx95_m7.dtsi b/dts/arm/nxp/nxp_imx95_m7.dtsi index a5b6cdb1837d4..0bef43499e473 100644 --- a/dts/arm/nxp/nxp_imx95_m7.dtsi +++ b/dts/arm/nxp/nxp_imx95_m7.dtsi @@ -15,9 +15,10 @@ #address-cells = <1>; #size-cells = <0>; - cpu@0 { + cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-m7"; + cpu-power-states = <&wait &stop &suspend>; reg = <0>; #address-cells = <1>; @@ -27,6 +28,30 @@ compatible = "arm,armv7m-mpu"; reg = <0xe000ed90 0x40>; }; + + }; + + power-states { + wait: power-state-wait { + compatible = "zephyr,power-state"; + power-state-name = "runtime-idle"; + min-residency-us = <100>; + exit-latency-us = <50>; + }; + + stop: power-state-stop { + compatible = "zephyr,power-state"; + power-state-name = "suspend-to-idle"; + min-residency-us = <1000>; + exit-latency-us = <200>; + }; + + suspend: power-state-suspend { + compatible = "zephyr,power-state"; + power-state-name = "standby"; + min-residency-us = <5000>; + exit-latency-us = <1000>; + }; }; }; diff --git a/include/zephyr/drivers/firmware/scmi/nxp/cpu.h b/include/zephyr/drivers/firmware/scmi/nxp/cpu.h index 687175912ec30..fcb81dd6bcb9d 100644 --- a/include/zephyr/drivers/firmware/scmi/nxp/cpu.h +++ b/include/zephyr/drivers/firmware/scmi/nxp/cpu.h @@ -21,6 +21,8 @@ #define SCMI_PROTOCOL_CPU_DOMAIN 130 +#define SCMI_CPU_MAX_PDCONFIGS_T 7U + /** * @struct scmi_cpu_sleep_mode_config * @@ -33,6 +35,23 @@ struct scmi_cpu_sleep_mode_config { uint32_t sleep_mode; }; +struct scmi_pd_lpm_settings { + uint32_t domainId; + uint32_t lpmSetting; + uint32_t retMask; +}; + +/** + * @struct scmi_cpu_pd_lpm_config + * + * @brief Describes cpu power domain low power mode setting + */ +struct scmi_cpu_pd_lpm_config { + uint32_t cpu_id; + uint32_t num_cfg; + struct scmi_pd_lpm_settings cfgs[SCMI_CPU_MAX_PDCONFIGS_T]; +}; + /** * @brief CPU domain protocol command message IDs */ @@ -64,4 +83,14 @@ enum scmi_cpu_domain_message { */ int scmi_cpu_sleep_mode_set(struct scmi_cpu_sleep_mode_config *cfg); +/** + * @brief Send the SCMI_CPU_DOMAIN_MSG_CPU_PD_LPM_CONFIG_SET command and get its reply + * + * @param cfg pointer to structure containing configuration + * to be set + * + * @retval 0 if successful + * @retval negative errno if failure + */ +int scmi_cpu_pd_lpm_set(struct scmi_cpu_pd_lpm_config *cfg); #endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_CPU_H_ */ diff --git a/include/zephyr/drivers/firmware/scmi/protocol.h b/include/zephyr/drivers/firmware/scmi/protocol.h index 13ac8ba248c00..89f17330135b5 100644 --- a/include/zephyr/drivers/firmware/scmi/protocol.h +++ b/include/zephyr/drivers/firmware/scmi/protocol.h @@ -115,8 +115,10 @@ int scmi_status_to_errno(int scmi_status); * * @retval 0 if successful * @retval negative errno if failure + * @param pre_kernel current kernel state */ int scmi_send_message(struct scmi_protocol *proto, - struct scmi_message *msg, struct scmi_message *reply); + struct scmi_message *msg, struct scmi_message *reply, + bool pre_kernel); #endif /* _INCLUDE_ZEPHYR_DRIVERS_FIRMWARE_SCMI_PROTOCOL_H_ */ diff --git a/soc/nxp/imx/imx9/imx95/Kconfig b/soc/nxp/imx/imx9/imx95/Kconfig index 60d1f424b0a5a..ca5e78253bf15 100644 --- a/soc/nxp/imx/imx9/imx95/Kconfig +++ b/soc/nxp/imx/imx9/imx95/Kconfig @@ -13,6 +13,7 @@ config SOC_MIMX9596_M7 select SOC_LATE_INIT_HOOK select HAS_MCUX select HAS_MCUX_CACHE + select HAS_PM config SOC_MIMX9596_A55 select ARM64 diff --git a/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 b/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 index ee356a0ead0d4..64ba81caf7d43 100644 --- a/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 +++ b/soc/nxp/imx/imx9/imx95/Kconfig.defconfig.mimx95.m7 @@ -50,5 +50,11 @@ config CACHE_MANAGEMENT config ETH_NXP_IMX_MSGINTR default 2 +if PM +# PM code that runs from the idle loop has a large +# footprint. Hence increase the size when PM is enabled. +config IDLE_STACK_SIZE + default 640 +endif endif # SOC_MIMX9596_M7 diff --git a/soc/nxp/imx/imx9/imx95/m7/soc.c b/soc/nxp/imx/imx9/imx95/m7/soc.c index db60e45170eab..b93a85f638c7c 100644 --- a/soc/nxp/imx/imx9/imx95/m7/soc.c +++ b/soc/nxp/imx/imx9/imx95/m7/soc.c @@ -88,6 +88,95 @@ static int soc_init(void) return ret; } +void pm_state_before(void) +{ + struct scmi_cpu_pd_lpm_config cpu_pd_lpm_cfg; + + /* + * 1. Set M7 mix as power on state in suspend mode + * 2. Keep wakeupmix power on whatever low power mode, as lpuart3(console) in there. + * To do: in order to reduce power consumption, the M7 core in the i.MX95 + * should be powered down during suspend. + * However, after being woken up by a wakeup source, the M7 CPU will restart + * execution from the vector table address, which is not the desired behavior. + * Instead, the vector value should be set to the address where the CPU + * was before entering suspend, and the CPU state should be restored to + * what it was prior to suspend. + */ + + cpu_pd_lpm_cfg.cpu_id = CPU_IDX_M7P; + cpu_pd_lpm_cfg.num_cfg = 2; + cpu_pd_lpm_cfg.cfgs[0].domainId = PWR_MIX_SLICE_IDX_M7; + cpu_pd_lpm_cfg.cfgs[0].lpmSetting = SCMI_CPU_LPM_SETTING_ON_ALWAYS; + cpu_pd_lpm_cfg.cfgs[0].retMask = 1U << PWR_MEM_SLICE_IDX_M7; + cpu_pd_lpm_cfg.cfgs[1].domainId = PWR_MIX_SLICE_IDX_WAKEUP; + cpu_pd_lpm_cfg.cfgs[1].lpmSetting = SCMI_CPU_LPM_SETTING_ON_ALWAYS; + cpu_pd_lpm_cfg.cfgs[1].retMask = 0; + + scmi_cpu_pd_lpm_set(&cpu_pd_lpm_cfg); +} + + +void pm_state_set(enum pm_state state, uint8_t substate_id) +{ + struct scmi_cpu_sleep_mode_config cpu_cfg = {0}; + + pm_state_before(); + + /* iMX95 M7 core is based on ARMv7-M architecture. For this architecture, + * the current implementation of arch_irq_lock of zephyr is based on BASEPRI, + * which will only retain abnormal interrupts such as NMI, + * and all other interrupts from the CPU(including systemtick) will be masked, + * which makes the CORE unable to be woken up from WFI. + * Set PRIMASK as workaround, Shield the CPU from responding to interrupts, + * the CPU will not jump to the interrupt service routine (ISR). + */ + __disable_irq(); + /* Set BASEPRI to 0 */ + irq_unlock(0); + + switch (state) { + case PM_STATE_RUNTIME_IDLE: + cpu_cfg.cpu_id = CPU_IDX_M7P; + cpu_cfg.sleep_mode = CPU_SLEEP_MODE_WAIT; + scmi_cpu_sleep_mode_set(&cpu_cfg); + __DSB(); + __WFI(); + break; + case PM_STATE_SUSPEND_TO_IDLE: + cpu_cfg.cpu_id = CPU_IDX_M7P; + cpu_cfg.sleep_mode = CPU_SLEEP_MODE_STOP; + scmi_cpu_sleep_mode_set(&cpu_cfg); + __DSB(); + __WFI(); + break; + case PM_STATE_STANDBY: + cpu_cfg.cpu_id = CPU_IDX_M7P; + cpu_cfg.sleep_mode = CPU_SLEEP_MODE_SUSPEND; + scmi_cpu_sleep_mode_set(&cpu_cfg); + __DSB(); + __WFI(); + break; + default: + break; + } +} + +/* Handle SOC specific activity after Low Power Mode Exit */ +void pm_state_exit_post_ops(enum pm_state state, uint8_t substate_id) +{ + ARG_UNUSED(state); + + struct scmi_cpu_sleep_mode_config cpu_cfg = {0}; + /* restore M7 core state into ACTIVE. */ + cpu_cfg.cpu_id = CPU_IDX_M7P; + cpu_cfg.sleep_mode = CPU_SLEEP_MODE_RUN; + scmi_cpu_sleep_mode_set(&cpu_cfg); + + /* Clear PRIMASK */ + __enable_irq(); +} + /* * Because platform is using ARM SCMI, drivers like scmi, mbox etc. are * initialized during PRE_KERNEL_1. Common init hooks is not able to use. diff --git a/soc/nxp/imx/imx9/imx95/scmi_cpu_soc.h b/soc/nxp/imx/imx9/imx95/scmi_cpu_soc.h index ac23b1fd8b9a6..8afa9f2f370be 100644 --- a/soc/nxp/imx/imx9/imx95/scmi_cpu_soc.h +++ b/soc/nxp/imx/imx9/imx95/scmi_cpu_soc.h @@ -23,4 +23,85 @@ #define CPU_SLEEP_MODE_STOP 2U #define CPU_SLEEP_MODE_SUSPEND 3U +/*! + * @name SCMI CPU LPM settings + */ +#define SCMI_CPU_LPM_SETTING_ON_NEVER 0U +#define SCMI_CPU_LPM_SETTING_ON_RUN 1U +#define SCMI_CPU_LPM_SETTING_ON_RUN_WAIT 2U +#define SCMI_CPU_LPM_SETTING_ON_RUN_WAIT_STOP 3U +#define SCMI_CPU_LPM_SETTING_ON_ALWAYS 4U + +#define CPU_PER_LPI_IDX_GPIO1 0U +#define CPU_PER_LPI_IDX_GPIO2 1U +#define CPU_PER_LPI_IDX_GPIO3 2U +#define CPU_PER_LPI_IDX_GPIO4 3U +#define CPU_PER_LPI_IDX_GPIO5 4U +#define CPU_PER_LPI_IDX_CAN1 5U +#define CPU_PER_LPI_IDX_CAN2 6U +#define CPU_PER_LPI_IDX_CAN3 7U +#define CPU_PER_LPI_IDX_CAN4 8U +#define CPU_PER_LPI_IDX_CAN5 9U +#define CPU_PER_LPI_IDX_LPUART1 10U +#define CPU_PER_LPI_IDX_LPUART2 11U +#define CPU_PER_LPI_IDX_LPUART3 12U +#define CPU_PER_LPI_IDX_LPUART4 13U +#define CPU_PER_LPI_IDX_LPUART5 14U +#define CPU_PER_LPI_IDX_LPUART6 15U +#define CPU_PER_LPI_IDX_LPUART7 16U +#define CPU_PER_LPI_IDX_LPUART8 17U +#define CPU_PER_LPI_IDX_WDOG3 18U +#define CPU_PER_LPI_IDX_WDOG4 19U +#define CPU_PER_LPI_IDX_WDOG5 20U + + +/* MIX definitions */ +#define PWR_NUM_MIX_SLICE 23U + +#define PWR_MIX_SLICE_IDX_ANA 0U +#define PWR_MIX_SLICE_IDX_AON 1U +#define PWR_MIX_SLICE_IDX_BBSM 2U +#define PWR_MIX_SLICE_IDX_CAMERA 3U +#define PWR_MIX_SLICE_IDX_CCMSRCGPC 4U +#define PWR_MIX_SLICE_IDX_A55C0 5U +#define PWR_MIX_SLICE_IDX_A55C1 6U +#define PWR_MIX_SLICE_IDX_A55C2 7U +#define PWR_MIX_SLICE_IDX_A55C3 8U +#define PWR_MIX_SLICE_IDX_A55C4 9U +#define PWR_MIX_SLICE_IDX_A55C5 10U +#define PWR_MIX_SLICE_IDX_A55P 11U +#define PWR_MIX_SLICE_IDX_DDR 12U +#define PWR_MIX_SLICE_IDX_DISPLAY 13U +#define PWR_MIX_SLICE_IDX_GPU 14U +#define PWR_MIX_SLICE_IDX_HSIO_TOP 15U +#define PWR_MIX_SLICE_IDX_HSIO_WAON 16U +#define PWR_MIX_SLICE_IDX_M7 17U +#define PWR_MIX_SLICE_IDX_NETC 18U +#define PWR_MIX_SLICE_IDX_NOC 19U +#define PWR_MIX_SLICE_IDX_NPU 20U +#define PWR_MIX_SLICE_IDX_VPU 21U +#define PWR_MIX_SLICE_IDX_WAKEUP 22U + +#define PWR_MEM_SLICE_IDX_AON 0U +#define PWR_MEM_SLICE_IDX_CAMERA 1U +#define PWR_MEM_SLICE_IDX_A55C0 2U +#define PWR_MEM_SLICE_IDX_A55C1 3U +#define PWR_MEM_SLICE_IDX_A55C2 4U +#define PWR_MEM_SLICE_IDX_A55C3 5U +#define PWR_MEM_SLICE_IDX_A55C4 6U +#define PWR_MEM_SLICE_IDX_A55C5 7U +#define PWR_MEM_SLICE_IDX_A55P 8U +#define PWR_MEM_SLICE_IDX_A55L3 9U +#define PWR_MEM_SLICE_IDX_DDR 10U +#define PWR_MEM_SLICE_IDX_DISPLAY 11U +#define PWR_MEM_SLICE_IDX_GPU 12U +#define PWR_MEM_SLICE_IDX_HSIO 13U +#define PWR_MEM_SLICE_IDX_M7 14U +#define PWR_MEM_SLICE_IDX_NETC 15U +#define PWR_MEM_SLICE_IDX_NOC1 16U +#define PWR_MEM_SLICE_IDX_NOC2 17U +#define PWR_MEM_SLICE_IDX_NPU 18U +#define PWR_MEM_SLICE_IDX_VPU 19U +#define PWR_MEM_SLICE_IDX_WAKEUP 20U + #endif /* ZEPHYR_NXP_IMX95_SCMI_CPU_SOC_H_ */ diff --git a/tests/subsys/pm/power_mgmt_soc/testcase.yaml b/tests/subsys/pm/power_mgmt_soc/testcase.yaml index c6a14a3b582cc..2e455a3928560 100644 --- a/tests/subsys/pm/power_mgmt_soc/testcase.yaml +++ b/tests/subsys/pm/power_mgmt_soc/testcase.yaml @@ -17,6 +17,7 @@ tests: - max32690evkit/max32690/m4 - max32655evkit/max32655/m4 - max32655fthr/max32655/m4 + - imx95_evk/mimx9596/m7 tags: pm integration_platforms: - mec15xxevb_assy6853