From 86dd08d838a6a49e7915df0f20c90c3a3e003056 Mon Sep 17 00:00:00 2001 From: Yong Wu <yong.wu@mediatek.com> Date: Mon, 30 Dec 2024 13:12:00 +0800 Subject: [PATCH] feat(mt8196): add SMMU driver for PM Add MediaTek SMMU power driver. This driver tracks the reference counter for power domain access on SMMU hardware, including Multimedia SMMU and APU SMMU. The PM get/put commands may come from linux(EL1) and hypervisor(EL2). Change-Id: I60f83c4e3d87059b0549b2ed8c68367be3bfbbc5 Signed-off-by: Yong Wu <yong.wu@mediatek.com> --- plat/mediatek/drivers/smmu/rules.mk | 17 ++ plat/mediatek/drivers/smmu/smmu.c | 301 ++++++++++++++++++++++++++++ plat/mediatek/mt8196/platform.mk | 1 + 3 files changed, 319 insertions(+) create mode 100644 plat/mediatek/drivers/smmu/rules.mk create mode 100644 plat/mediatek/drivers/smmu/smmu.c diff --git a/plat/mediatek/drivers/smmu/rules.mk b/plat/mediatek/drivers/smmu/rules.mk new file mode 100644 index 000000000..76086d966 --- /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 000000000..4d3107106 --- /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 3e1165e3e..2fdef57d0 100644 --- a/plat/mediatek/mt8196/platform.mk +++ b/plat/mediatek/mt8196/platform.mk @@ -35,6 +35,7 @@ MODULES-y += $(MTK_PLAT)/drivers/emi 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