feat(mt8196): add mcdi driver

Add MCDI driver to manage CPU idle states and optimize power consumption.

Change-Id: I3a2e163730dd997dd72f2ebc1375dea38d728cb7
diff --git a/plat/mediatek/common/lpm/mt_lpm_dispatch.c b/plat/mediatek/common/lpm/mt_lpm_dispatch.c
new file mode 100644
index 0000000..6b30ff7
--- /dev/null
+++ b/plat/mediatek/common/lpm/mt_lpm_dispatch.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cdefs.h>
+
+#include <lpm/mt_lpm_dispatch.h>
+
+struct mt_dispatch_ctrl mt_dispatcher __section("mt_lpm_s") = {
+	.enable = 0,
+};
+
+struct mt_dispatch_ctrl mt_secure_dispatcher __section("mt_secure_lpm_s") = {
+	.enable = 0,
+};
+
+u_register_t invoke_mt_lpm_dispatch(u_register_t x1,
+				    u_register_t x2,
+				    u_register_t x3,
+				    u_register_t x4,
+				    void *handle,
+				    struct smccc_res *smccc_ret)
+{
+	uint64_t res = 0;
+	uint32_t user;
+
+	if (!IS_MT_LPM_SMC(x1))
+		return 0;
+
+	user = MT_LPM_SMC_USER(x1);
+	if ((user < MT_LPM_SMC_USER_MAX) &&
+	    (mt_dispatcher.enable & (BIT(user)))) {
+		res = mt_dispatcher.fn[user](MT_LPM_SMC_USER_ID(x1),
+					     x2,
+					     x3,
+					     x4,
+					     handle,
+					     smccc_ret);
+	}
+
+	return res;
+}
+DECLARE_SMC_HANDLER(MTK_SIP_MTK_LPM_CONTROL, invoke_mt_lpm_dispatch);
+
+u_register_t invoke_mt_secure_lpm_dispatch(u_register_t x1,
+					   u_register_t x2,
+					   u_register_t x3,
+					   u_register_t x4,
+					   void *handle,
+					   struct smccc_res *smccc_ret)
+{
+	uint64_t res = 0;
+	uint32_t user;
+
+	if (!IS_MT_LPM_SMC(x1))
+		return 0;
+
+	user = MT_LPM_SMC_USER(x1);
+	if (mt_secure_dispatcher.enable & (BIT(user))) {
+		res = mt_secure_dispatcher.fn[user](MT_LPM_SMC_USER_ID(x1),
+						    x2,
+						    x3,
+						    x4,
+						    handle,
+						    smccc_ret);
+	}
+
+	return res;
+}
+DECLARE_SMC_HANDLER(MTK_SIP_BL_LPM_CONTROL, invoke_mt_secure_lpm_dispatch);
+
+/* Check lpm smc user number at compile time */
+CASSERT(MT_LPM_SMC_USER_MAX <= MTK_DISPATCH_ID_MAX,
+	lpm_smc_user_declare_too_large);
+
+void mt_lpm_dispatcher_registry(unsigned int id, mt_lpm_dispatch_fn fn)
+{
+	if (id >= MT_LPM_SMC_USER_MAX)
+		return;
+
+	mt_dispatcher.enable |= BIT(id);
+	mt_dispatcher.fn[id] = fn;
+}
+
+void mt_secure_lpm_dispatcher_registry(unsigned int id, mt_lpm_dispatch_fn fn)
+{
+	if (id >= MT_LPM_SMC_USER_MAX)
+		return;
+
+	mt_secure_dispatcher.enable |= BIT(id);
+	mt_secure_dispatcher.fn[id] = fn;
+}
diff --git a/plat/mediatek/common/lpm/rules.mk b/plat/mediatek/common/lpm/rules.mk
index eb68e03..d24f4d8 100644
--- a/plat/mediatek/common/lpm/rules.mk
+++ b/plat/mediatek/common/lpm/rules.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2023, MediaTek Inc. All rights reserved.
+# Copyright (c) 2023-2025, MediaTek Inc. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -11,6 +11,7 @@
 LOCAL_SRCS-y := $(LOCAL_DIR)/mt_lp_api.c
 LOCAL_SRCS-y += $(LOCAL_DIR)/mt_lp_rm.c
 LOCAL_SRCS-y += $(LOCAL_DIR)/mt_lp_rq.c
+LOCAL_SRCS-y += ${LOCAL_DIR}/mt_lpm_dispatch.c
 
 PLAT_INCLUDES += -I${LOCAL_DIR}
 
diff --git a/plat/mediatek/drivers/cpu_pm/cpcv5_4/mt_cpu_pm.c b/plat/mediatek/drivers/cpu_pm/cpcv5_4/mt_cpu_pm.c
index 4537c88..f105b88 100644
--- a/plat/mediatek/drivers/cpu_pm/cpcv5_4/mt_cpu_pm.c
+++ b/plat/mediatek/drivers/cpu_pm/cpcv5_4/mt_cpu_pm.c
@@ -394,6 +394,17 @@
 mt_pwr_mcusysoff_break:
 	plat_mt_lp_cpu_rc = -1;
 
+	if (IS_PLAT_ALL_ONLINE_CORES_S2IDLE(state)) {
+		/* set SPM pending if s2idle fail to turn mcusys off */
+		if (suspend_abort_reason == MTK_PM_SUSPEND_ABORT_PWR_REQ)
+			NOTICE("[LPM] PWR_REQ is held\n");
+		else if (suspend_abort_reason == MTK_PM_SUSPEND_ABORT_LAST_CORE)
+			NOTICE("[LPM] suspend last core prot fail\n");
+		else if (suspend_abort_reason ==
+			 MTK_PM_SUSPEND_ABORT_RC_INVALID)
+			NOTICE("[LPM] no available RC\n");
+	}
+
 	return MTK_CPUPM_E_FAIL;
 }
 
diff --git a/plat/mediatek/include/lpm/mt_lpm_dispatch.h b/plat/mediatek/include/lpm/mt_lpm_dispatch.h
new file mode 100644
index 0000000..9b30dbe
--- /dev/null
+++ b/plat/mediatek/include/lpm/mt_lpm_dispatch.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025, Mediatek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef MT_LPM_DISPATCH_H
+#define MT_LPM_DISPATCH_H
+
+#include <stdint.h>
+
+#include "mt_lpm_smc.h"
+#include <mtk_sip_svc.h>
+
+#define MTK_DISPATCH_ID_MAX	32
+
+typedef uint64_t (*mt_lpm_dispatch_fn)(u_register_t x1, u_register_t x2,
+				       u_register_t x3, u_register_t x4,
+				       void *handle,
+				       struct smccc_res *smccc_ret);
+
+struct mt_dispatch_ctrl {
+	unsigned int enable;
+	mt_lpm_dispatch_fn fn[MT_LPM_SMC_USER_MAX];
+};
+
+void mt_lpm_dispatcher_registry(unsigned int id, mt_lpm_dispatch_fn fn);
+
+void mt_secure_lpm_dispatcher_registry(unsigned int id, mt_lpm_dispatch_fn fn);
+
+extern struct mt_dispatch_ctrl mt_dispatcher;
+extern struct mt_dispatch_ctrl mt_secure_dispatcher;
+#endif
diff --git a/plat/mediatek/include/mtk_sip_def.h b/plat/mediatek/include/mtk_sip_def.h
index 6f496d2..85b7230 100644
--- a/plat/mediatek/include/mtk_sip_def.h
+++ b/plat/mediatek/include/mtk_sip_def.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023, MediaTek Inc. All rights reserved.
+ * Copyright (c) 2024, MediaTek Inc. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -13,6 +13,7 @@
 	_func(MTK_SIP_KERNEL_DFD, 0x205) \
 	_func(MTK_SIP_KERNEL_MSDC, 0x273) \
 	_func(MTK_SIP_VCORE_CONTROL, 0x506) \
+	_func(MTK_SIP_MTK_LPM_CONTROL, 0x507) \
 	_func(MTK_SIP_EMIDBG_CONTROL, 0x50B) \
 	_func(MTK_SIP_IOMMU_CONTROL, 0x514) \
 	_func(MTK_SIP_AUDIO_CONTROL, 0x517) \
@@ -27,6 +28,7 @@
 
 #define MTK_SIP_SMC_FROM_BL33_TABLE(_func) \
 	_func(MTK_SIP_KERNEL_BOOT, 0x115) \
+	_func(MTK_SIP_BL_LPM_CONTROL, 0x410) \
 	_func(MTK_SIP_BL_EMIMPU_CONTROL, 0x415)
 
 #endif /* MTK_SIP_DEF_H */
diff --git a/plat/mediatek/lib/pm/armv9_0/pwr_ctrl.c b/plat/mediatek/lib/pm/armv9_0/pwr_ctrl.c
index a0171bf..73b1f68 100644
--- a/plat/mediatek/lib/pm/armv9_0/pwr_ctrl.c
+++ b/plat/mediatek/lib/pm/armv9_0/pwr_ctrl.c
@@ -371,6 +371,19 @@
 }
 #endif
 
+static void __dead2 pwr_domain_pwr_down_wfi(const psci_power_state_t *req_state)
+{
+	unsigned int cpu = plat_my_core_pos();
+	int ret = MTK_CPUPM_E_NOT_SUPPORT;
+
+	if (IS_CPUIDLE_FN_ENABLE(MTK_CPUPM_FN_PWR_DOMAIN_POWER_DOWN_WFI))
+		ret = imtk_cpu_pwr.ops->pwr_domain_pwr_down_wfi(cpu);
+	if (ret == MTK_CPUPM_E_OK)
+		plat_panic_handler();
+	else
+		psci_power_down_wfi();
+}
+
 static void pm_smp_init(unsigned int cpu_id, uintptr_t entry_point)
 {
 	if (entry_point == 0) {
@@ -382,6 +395,16 @@
 	INFO("[%s:%d] - Initialize finished\n", __func__, __LINE__);
 }
 
+static struct plat_pm_pwr_ctrl armv9_0_pwr_ops = {
+	.pwr_domain_suspend = power_domain_suspend,
+	.pwr_domain_suspend_finish = power_domain_suspend_finish,
+	.validate_power_state = validate_power_state,
+#if CONFIG_MTK_SUPPORT_SYSTEM_SUSPEND
+	.get_sys_suspend_power_state = get_sys_suspend_power_state,
+#endif
+	.pwr_domain_pwr_down_wfi = pwr_domain_pwr_down_wfi,
+};
+
 struct plat_pm_smp_ctrl armv9_0_smp_ops = {
 	.init = pm_smp_init,
 	.pwr_domain_on = power_domain_on,
@@ -416,6 +439,50 @@
 	return ret;
 }
 
+int register_cpu_pm_ops(unsigned int fn_flags, struct mtk_cpu_pm_ops *ops)
+{
+	int success = 1;
+	unsigned int fns = 0;
+
+	if (!ops || imtk_cpu_pwr.ops) {
+		ERROR("[%s:%d] register cpu_pm fail !!\n", __FILE__, __LINE__);
+		return MTK_CPUPM_E_ERR;
+	}
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_RESUME_CORE,
+			cpu_resume, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_SUSPEND_CORE,
+			cpu_suspend, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_RESUME_CLUSTER,
+			cluster_resume, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_SUSPEND_CLUSTER,
+			cluster_suspend, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_RESUME_MCUSYS,
+			mcusys_resume, 1,
+			success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_SUSPEND_MCUSYS,
+			mcusys_suspend, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_CPUPM_GET_PWR_STATE,
+			get_pstate, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_PWR_STATE_VALID,
+			pwr_state_valid, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_INIT,
+			init, 1, success, fns);
+	CPM_PM_FN_CHECK(fn_flags, ops, MTK_CPUPM_FN_PWR_DOMAIN_POWER_DOWN_WFI,
+			pwr_domain_pwr_down_wfi, 1, success, fns);
+	if (success) {
+		imtk_cpu_pwr.ops = ops;
+		imtk_cpu_pwr.fn_mask |= fns;
+		plat_pm_ops_setup_pwr(&armv9_0_pwr_ops);
+		INFO("[%s:%d] CPU pwr ops register success, support:0x%x\n",
+					__func__, __LINE__, fns);
+	} else {
+		ERROR("[%s:%d] register cpu_pm ops fail !, fn:0x%x\n",
+		      __func__, __LINE__, fn_flags);
+		assert(0);
+	}
+	return MTK_CPUPM_E_OK;
+}
+
 int register_cpu_smp_ops(unsigned int fn_flags, struct mtk_cpu_smp_ops *ops)
 {
 	int success = 1;
diff --git a/plat/mediatek/lib/pm/mtk_pm.c b/plat/mediatek/lib/pm/mtk_pm.c
index 3dbeb51..772c2d7 100644
--- a/plat/mediatek/lib/pm/mtk_pm.c
+++ b/plat/mediatek/lib/pm/mtk_pm.c
@@ -45,6 +45,9 @@
 		mtk_pm_ops.get_sys_suspend_power_state = ops->get_sys_suspend_power_state;
 	}
 
+	if (!mtk_pm_ops.pwr_domain_pwr_down_wfi)
+		mtk_pm_ops.pwr_domain_pwr_down_wfi = ops->pwr_domain_pwr_down_wfi;
+
 	mtk_pm_status |= MTK_PM_ST_PWR_READY;
 #endif
 	return MTK_CPUPM_E_OK;
diff --git a/plat/mediatek/mt8196/include/platform_def.h b/plat/mediatek/mt8196/include/platform_def.h
index 362f93f..1c01483 100644
--- a/plat/mediatek/mt8196/include/platform_def.h
+++ b/plat/mediatek/mt8196/include/platform_def.h
@@ -258,7 +258,6 @@
  ******************************************************************************/
 #define CPU_EB_TCM_BASE		0x0C2CF000
 #define CPU_EB_TCM_SIZE		0x1000
-#define CPU_EB_MBOX3_OFFSET	0xFCE0
 #define CPU_EB_TCM_CNT_BASE	0x0C2CC000
 
 /*******************************************************************************
diff --git a/plat/mediatek/mt8196/plat_config.mk b/plat/mediatek/mt8196/plat_config.mk
index e4a56c8..7d43097 100644
--- a/plat/mediatek/mt8196/plat_config.mk
+++ b/plat/mediatek/mt8196/plat_config.mk
@@ -36,17 +36,20 @@
 CONFIG_MTK_APUSYS_SETUP_CE := y
 CONFIG_MTK_MCUSYS := y
 MCUSYS_VERSION := v4
+MCUPM_VERSION := v3
 CONFIG_MTK_PM_SUPPORT := y
 CONFIG_MTK_PM_ARCH := 9_0
 CONFIG_MTK_CPU_PM_SUPPORT := y
 CONFIG_MTK_CPU_PM_ARCH := 5_4
 CONFIG_MTK_SMP_EN := y
-CONFIG_MTK_CPU_SUSPEND_EN := n
+CONFIG_MTK_CPU_SUSPEND_EN := y
 CONFIG_MTK_SPM_VERSION := mt8196
 CONFIG_MTK_SUPPORT_SYSTEM_SUSPEND := n
 CONFIG_MTK_TINYSYS_VCP := y
 CPU_PWR_TOPOLOGY := group_4_3_1
 CPU_PM_CORE_ARCH64_ONLY := y
+CPU_PM_DOMAIN_CORE_ONLY := n
+CPU_PM_SUSPEND_NOTIFY := y
 CPU_PM_TINYSYS_SUPPORT := y
 MTK_PUBEVENT_ENABLE := y
 CONFIG_MTK_PMIC := y
diff --git a/plat/mediatek/mt8196/platform.mk b/plat/mediatek/mt8196/platform.mk
index c8bfb47..068fe5e 100644
--- a/plat/mediatek/mt8196/platform.mk
+++ b/plat/mediatek/mt8196/platform.mk
@@ -24,6 +24,7 @@
 		 -Idrivers/arm/gic \
 
 MODULES-y += $(MTK_PLAT)/common
+MODULES-y += $(MTK_PLAT)/common/lpm
 MODULES-y += $(MTK_PLAT)/lib/mtk_init
 MODULES-y += $(MTK_PLAT)/lib/pm
 MODULES-y += $(MTK_PLAT)/drivers/apusys