Merge "feat(mt8196): add SMMU driver for PM" into integration
diff --git a/plat/mediatek/drivers/smmu/rules.mk b/plat/mediatek/drivers/smmu/rules.mk
new file mode 100644
index 0000000..76086d9
--- /dev/null
+++ b/plat/mediatek/drivers/smmu/rules.mk
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2025, MediaTek Inc. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+LOCAL_DIR := $(call GET_LOCAL_DIR)
+
+MODULE := smmu
+
+LOCAL_SRCS-y := ${LOCAL_DIR}/smmu.c
+
+ifeq ($(MTK_SOC), mt8196)
+CFLAGS += -DMTK_SMMU_MT8196
+endif
+
+$(eval $(call MAKE_MODULE,$(MODULE),$(LOCAL_SRCS-y),$(MTK_BL)))
diff --git a/plat/mediatek/drivers/smmu/smmu.c b/plat/mediatek/drivers/smmu/smmu.c
new file mode 100644
index 0000000..4d31071
--- /dev/null
+++ b/plat/mediatek/drivers/smmu/smmu.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <errno.h>
+
+#include <common/debug.h>
+#include <lib/mmio.h>
+#include <lib/spinlock.h>
+
+#include <drivers/apusys_rv_public.h>
+#include <drivers/mminfra_public.h>
+#include <lib/mtk_init/mtk_init.h>
+#include <mtk_sip_svc.h>
+
+#define TAG		"[MTK_SMMU]"
+
+#ifdef SMMU_DBG
+#define SMMUDBG(fmt, args...)	INFO(TAG fmt, ##args)
+#else
+#define SMMUDBG(fmt, args...)	VERBOSE(TAG fmt, ##args)
+#endif
+
+#define SMMU_SUCCESS			0
+#define SMMU_ID_ERR			1
+#define SMMU_CMD_ERR			2
+
+#define F_MSK_SHIFT(val, h, l)		(((val) & GENMASK(h, l)) >> (l))
+
+#define SMMU_SMC_ID_H			(10)
+#define SMMU_SMC_ID_L			(8)
+#define SMMU_SMC_CMD_H			(7)
+#define SMMU_SMC_CMD_L			(0)
+
+/* SMMU CMD Definition From Rich OS */
+enum smc_cmd {
+	SMMU_SECURE_PM_GET,
+	SMMU_SECURE_PM_PUT,
+	SMMU_CMD_NUM
+};
+
+enum smmu_id {
+	MTK_SMMU_ID_MM,
+	MTK_SMMU_ID_APU,
+	MTK_SMMU_ID_SOC,
+	MTK_SMMU_ID_GPU,
+	MTK_SMMU_ID_NUM,
+};
+
+enum cmd_source {
+	SMMU_CMD_SOURCE_KERNEL = 0, /* Command comes from kernel */
+	SMMU_CMD_SOURCE_TFA,
+	SMMU_CMD_SOURCE_HYP,	    /* Command comes from hypervisor */
+	SMMU_CMD_SOURCE_NUM
+};
+
+struct hw_sema_t {
+	enum smmu_id id;
+	uint32_t vote[SMMU_CMD_SOURCE_NUM]; /* SW vote count */
+	spinlock_t lock;
+	bool active;
+};
+
+static struct hw_sema_t *hw_semas;
+
+static inline uint32_t vote_count_inc(struct hw_sema_t *sema, enum cmd_source id)
+{
+	if (sema->vote[id] < UINT32_MAX) {
+		sema->vote[id]++;
+		return sema->vote[id];
+	}
+
+	ERROR(TAG "%s:id:%u:source_id:%u overflow\n", __func__, sema->id, id);
+	return 0;
+}
+
+static inline uint32_t vote_count_dec(struct hw_sema_t *sema, enum cmd_source id)
+{
+	if (sema->vote[id] > 0) {
+		sema->vote[id]--;
+		return sema->vote[id];
+	}
+
+	ERROR(TAG "%s:id:%u:source_id:%u underflow\n", __func__, sema->id, id);
+	return 0;
+}
+
+static inline uint32_t vote_count(struct hw_sema_t *sema)
+{
+	uint32_t i, count = 0;
+
+	for (i = 0; i < SMMU_CMD_SOURCE_NUM; i++)
+		count += sema->vote[i];
+
+	return count;
+}
+
+static struct hw_sema_t *mtk_smmu_get_hw_sema_cfg(enum smmu_id id)
+{
+	if (hw_semas == NULL) {
+		ERROR(TAG "%s failed, hw_sema config not ready\n", __func__);
+		return NULL;
+	}
+
+	if (id >= MTK_SMMU_ID_NUM) {
+		ERROR(TAG "%s id:%u not support\n", __func__, id);
+		return NULL;
+	}
+	return &hw_semas[id];
+}
+
+static int mm_pm_get_if_in_use(struct hw_sema_t *sema, enum cmd_source id)
+{
+	uint32_t count;
+	int ret;
+
+	ret = mminfra_get_if_in_use();
+	if (ret != MMINFRA_RET_POWER_ON) {
+		count = vote_count(sema);
+		VERBOSE(TAG "%s:id:%u:source_id:%u:vote:%u:vote_count:%u ret:%d\n",
+			__func__, sema->id, id, sema->vote[id], count, ret);
+		return SMMU_CMD_ERR;
+	}
+	return SMMU_SUCCESS;
+}
+
+static int mm_pm_put(struct hw_sema_t *sema, enum cmd_source id)
+{
+	uint32_t count;
+	int ret;
+
+	ret = mminfra_put();
+	if (ret < 0) {
+		count = vote_count(sema);
+		VERBOSE(TAG "%s:id:%u:source_id:%u:vote:%u:vote_count:%u ret:%d\n",
+			__func__, sema->id, id, sema->vote[id], count, ret);
+		return SMMU_CMD_ERR;
+	}
+	return SMMU_SUCCESS;
+}
+
+static int mtk_smmu_pm_get(enum smmu_id id, enum cmd_source source_id)
+{
+	struct hw_sema_t *hw_sema = mtk_smmu_get_hw_sema_cfg(id);
+	uint32_t count;
+	int ret = SMMU_SUCCESS;
+
+	if (!hw_sema || !hw_sema->active)
+		return 0; /* hw_sema not ready or support, bypass */
+
+	spin_lock(&hw_sema->lock);
+	count = vote_count(hw_sema);
+
+	SMMUDBG("%s:id:%u:source_id:%u:vote:%u:vote_count:%u start\n",
+		__func__, id, source_id, hw_sema->vote[source_id], count);
+
+	if (count > 0) {
+		/* hw_sem was already got */
+		vote_count_inc(hw_sema, source_id);
+		goto out;
+	}
+
+	if (id == MTK_SMMU_ID_APU) {
+		ret = apusys_rv_iommu_hw_sem_trylock();
+	} else if (id == MTK_SMMU_ID_MM) {
+		ret = mm_pm_get_if_in_use(hw_sema, source_id);
+	}
+
+	if (ret == SMMU_SUCCESS)
+		vote_count_inc(hw_sema, source_id);
+
+out:
+	count = vote_count(hw_sema);
+	SMMUDBG("%s:id:%u:source_id:%u:vote:%u:vote_count:%u end ret:%d\n",
+		__func__, id, source_id, hw_sema->vote[source_id], count, ret);
+
+	spin_unlock(&hw_sema->lock);
+	return ret;
+}
+
+static int mtk_smmu_pm_put(enum smmu_id id, enum cmd_source source_id)
+{
+	struct hw_sema_t *hw_sema = mtk_smmu_get_hw_sema_cfg(id);
+	uint32_t count;
+	int ret = SMMU_SUCCESS;
+
+	if (!hw_sema || !hw_sema->active)
+		return 0; /* hw_sema not ready or support, bypass */
+
+	spin_lock(&hw_sema->lock);
+	count = vote_count(hw_sema);
+
+	SMMUDBG("%s:id:%u:source_id:%u:vote:%u:vote_count:%u start\n",
+		__func__, id, source_id, hw_sema->vote[source_id], count);
+
+	if (count == 0) {
+		/* hw_sem was already released */
+		ERROR(TAG "%s:id:%u, hw_sem already released\n", __func__, id);
+		goto out;
+	}
+
+	if (hw_sema->vote[source_id] == 0) {
+		/* hw_sem was already released */
+		ERROR(TAG "%s:id:%u:source_id:%u, hw_sem already released\n",
+		      __func__, id, source_id);
+		goto out;
+	}
+
+	vote_count_dec(hw_sema, source_id);
+	count = vote_count(hw_sema);
+	if (count > 0)
+		goto out; /* hw_sem only vote */
+
+	if (id == MTK_SMMU_ID_APU) {
+		ret = apusys_rv_iommu_hw_sem_unlock();
+	} else if (id == MTK_SMMU_ID_MM) {
+		ret = mm_pm_put(hw_sema, source_id);
+	}
+out:
+	SMMUDBG("%s:id:%u:source_id:%u:vote:%u:vote_count:%u end ret:%d\n",
+		__func__, id, source_id, hw_sema->vote[source_id], count, ret);
+
+	spin_unlock(&hw_sema->lock);
+	return ret;
+}
+
+/*
+ * The function is used handle some request from Rich OS.
+ * x1: TF-A cmd (format: sec[11:11] + smmu_id[10:8] + cmd_id[7:0])
+ * x2: other parameters
+ */
+static u_register_t mtk_smmu_handler(u_register_t x1, u_register_t x2,
+				     u_register_t x3, u_register_t x4,
+				     void *handle, struct smccc_res *smccc_ret)
+{
+	uint32_t ret = SMMU_CMD_ERR;
+	uint32_t cmd_id = F_MSK_SHIFT(x1, SMMU_SMC_CMD_H, SMMU_SMC_CMD_L);
+	enum smmu_id smmu_id = F_MSK_SHIFT(x1, SMMU_SMC_ID_H, SMMU_SMC_ID_L);
+	enum cmd_source source_id = (enum cmd_source)x2;
+
+	if (smmu_id >= MTK_SMMU_ID_NUM || source_id >= SMMU_CMD_SOURCE_NUM)
+		return SMMU_ID_ERR;
+
+	switch (cmd_id) {
+	case SMMU_SECURE_PM_GET:
+		ret = mtk_smmu_pm_get(smmu_id, source_id);
+		break;
+	case SMMU_SECURE_PM_PUT:
+		ret = mtk_smmu_pm_put(smmu_id, source_id);
+		break;
+	default:
+		break;
+	}
+
+	if (ret)
+		ERROR(TAG "%s, smmu_%u cmd:%u fail:%u\n", __func__, smmu_id, cmd_id, ret);
+
+	return ret;
+}
+/* Register MTK SMMU service */
+DECLARE_SMC_HANDLER(MTK_SIP_IOMMU_CONTROL, mtk_smmu_handler);
+
+#if defined(MTK_SMMU_MT8196)
+static struct hw_sema_t smmu_hw_semas[MTK_SMMU_ID_NUM] = {
+	{
+		.id = MTK_SMMU_ID_MM,
+		.active = true,
+	},
+	{
+		.id = MTK_SMMU_ID_APU,
+		.active = true,
+	},
+	{
+		.id = MTK_SMMU_ID_SOC,
+		.active = false,
+	},
+	{
+		.id = MTK_SMMU_ID_GPU,
+		.active = true,
+	},
+};
+#else
+static struct hw_sema_t *smmu_hw_semas;
+#endif
+
+/* Register MTK SMMU driver setup init function */
+static int mtk_smmu_init(void)
+{
+	hw_semas = smmu_hw_semas;
+
+	if (!hw_semas) {
+		ERROR("%s: failed.\n", __func__);
+		return -ENODEV;
+	}
+	SMMUDBG("%s done.\n", __func__);
+	return 0;
+}
+MTK_PLAT_SETUP_0_INIT(mtk_smmu_init);
diff --git a/plat/mediatek/mt8196/platform.mk b/plat/mediatek/mt8196/platform.mk
index 3a459c2..5050809 100644
--- a/plat/mediatek/mt8196/platform.mk
+++ b/plat/mediatek/mt8196/platform.mk
@@ -48,6 +48,7 @@
 MODULES-y += $(MTK_PLAT)/drivers/gicv3
 MODULES-y += $(MTK_PLAT)/drivers/mcusys
 MODULES-y += $(MTK_PLAT)/drivers/mminfra
+MODULES-y += $(MTK_PLAT)/drivers/smmu
 MODULES-y += $(MTK_PLAT)/drivers/spm
 MODULES-y += $(MTK_PLAT)/drivers/timer
 MODULES-y += $(MTK_PLAT)/drivers/vcp