/* * Copyright (c) 2025, MediaTek Inc. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #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);