kernel_samsung_a34x-permissive/drivers/iommu/mtk_iommu_v2.c

4779 lines
117 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 MediaTek Inc.
*/
#include <linux/bootmem.h>
#include <linux/bug.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/dma-iommu.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/iopoll.h>
#include <linux/list.h>
#include <linux/of_address.h>
#include <linux/of_iommu.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/sched/clock.h>
#include <asm/barrier.h>
#include <soc/mediatek/smi.h>
#include <linux/dma-debug.h>
#ifndef CONFIG_ARM64
#include <asm/dma-iommu.h>
#endif
#include "mtk_lpae.h"
//smccc related include
//#include "mtk_secure_api.h" //old
#include <linux/soc/mediatek/mtk_sip_svc.h>
#include <linux/arm-smccc.h>
#include "io-pgtable.h"
#include "mtk_iommu.h"
#include "mach/mt_iommu.h"
#include "mach/mt_iommu_plat.h"
#include "mach/pseudo_m4u.h"
#include "mtk_iommu_ext.h"
#if defined(APU_IOMMU_INDEX) && \
defined(IOMMU_POWER_CLK_SUPPORT) && \
defined(CONFIG_MTK_APUSYS_SUPPORT)
#include "apusys_power.h"
#endif
#if defined(APU_IOMMU_INDEX) && \
defined(MTK_APU_TFRP_SUPPORT)
#include "mnoc_api.h"
#endif
#define PREALLOC_DMA_DEBUG_ENTRIES 4096
#define MTK_IOMMU_DEBUG
/* IO virtual address start page frame number */
#define IOVA_START_PFN (1)
#define IOVA_PFN(addr) ((addr) >> PAGE_SHIFT)
#define DMA_32BIT_PFN IOVA_PFN(DMA_BIT_MASK(32))
#define MTK_PROTECT_PA_ALIGN (256)
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
static int mtk_irq_bank[MTK_IOMMU_M4U_COUNT][MTK_IOMMU_BANK_NODE_COUNT];
#endif
#ifdef IOMMU_DEBUG_ENABLED
static bool g_tf_test;
#endif
void mtk_iommu_switch_tf_test(bool enable,
const char *msg)
{
#ifdef IOMMU_DEBUG_ENABLED
g_tf_test = !!enable;
pr_notice("<<<<<<<<<<< mtk iommu translation fault test is switched to %d by %s >>>>>>>>>>>",
g_tf_test, msg);
#endif
}
struct mtk_iommu_domain *to_mtk_domain(struct iommu_domain *dom)
{
return container_of(dom, struct mtk_iommu_domain, domain);
}
static struct iommu_ops mtk_iommu_ops;
static const struct of_device_id mtk_iommu_of_ids[];
static LIST_HEAD(m4ulist);
static unsigned int total_iommu_cnt;
static unsigned int init_data_id;
static struct mtk_iommu_data *mtk_iommu_get_m4u_data(int id)
{
struct mtk_iommu_data *data;
unsigned int i = 0;
list_for_each_entry(data, &m4ulist, list) {
if (data && data->m4uid == id &&
data->base && !IS_ERR(data->base))
return data;
if (++i >= total_iommu_cnt)
return NULL;
}
pr_notice("%s, %d, failed to get data of %d\n", __func__, __LINE__, id);
return NULL;
}
#if MTK_IOMMU_PAGE_TABLE_SHARE
static struct mtk_iommu_pgtable *m4u_pgtable;
#endif
static struct mtk_iommu_pgtable *mtk_iommu_get_pgtable(
const struct mtk_iommu_data *data, unsigned int data_id)
{
#if !MTK_IOMMU_PAGE_TABLE_SHARE
if (data)
return data->pgtable;
data = mtk_iommu_get_m4u_data(data_id);
if (data)
return data->pgtable;
return NULL;
#else
return m4u_pgtable;
#endif
}
int mtk_iommu_set_pgtable(
const struct mtk_iommu_data *data,
unsigned int data_id,
struct mtk_iommu_pgtable *value)
{
#if !MTK_IOMMU_PAGE_TABLE_SHARE
if (data) {
data->pgtable = value;
} else {
data = mtk_iommu_get_m4u_data(data_id);
if (data)
data->pgtable = value;
else
return -1;
}
#else
m4u_pgtable = value;
#endif
return 0;
}
static unsigned int __mtk_iommu_get_domain_id(
unsigned int larbid, unsigned int portid)
{
unsigned int domain_id = MTK_IOVA_DOMAIN_COUNT;
int i;
if (larbid >= MTK_IOMMU_LARB_NR) {
pr_notice("%s, %d, cannot find domain of port(%d-%d)\n",
__func__, __LINE__,
larbid, portid);
return MTK_IOVA_DOMAIN_COUNT;
}
for (i = 0; i < MTK_IOVA_DOMAIN_COUNT; i++) {
if (mtk_domain_array[i].port_mask[larbid] &
(1 << portid)) {
domain_id = i;
break;
}
}
if (domain_id == MTK_IOVA_DOMAIN_COUNT)
pr_notice("%s, %d, cannot find domain of port(%d-%d)\n",
__func__, __LINE__,
larbid, portid);
return domain_id;
}
static unsigned int mtk_iommu_get_domain_id(
struct device *dev)
{
struct iommu_fwspec *fwspec;
unsigned int larbid, portid, domain_id = 0;
if (!dev)
return MTK_IOVA_DOMAIN_COUNT;
fwspec = dev->iommu_fwspec;
larbid = MTK_IOMMU_TO_LARB(fwspec->ids[0]);
portid = MTK_IOMMU_TO_PORT(fwspec->ids[0]);
domain_id = __mtk_iommu_get_domain_id(larbid, portid);
if (domain_id >= MTK_IOVA_DOMAIN_COUNT)
dev_notice(dev, "%s, %d, cannot find domain of port%d[%d-%d]\n",
__func__, __LINE__, fwspec->ids[0],
larbid, portid);
return domain_id;
}
int mtk_iommu_get_port_id(struct device *dev)
{
if (!dev)
return -ENODEV;
if (!dev->iommu_fwspec ||
!dev->iommu_fwspec->iommu_priv)
return M4U_PORT_GPU;
return dev->iommu_fwspec->ids[0];
}
EXPORT_SYMBOL_GPL(mtk_iommu_get_port_id);
static struct iommu_domain *__mtk_iommu_get_domain(
const struct mtk_iommu_data *data,
unsigned int larbid, unsigned int portid)
{
unsigned int domain_id;
struct mtk_iommu_domain *dom;
domain_id = __mtk_iommu_get_domain_id(
larbid, portid);
if (domain_id == MTK_IOVA_DOMAIN_COUNT)
return NULL;
if (!data->pgtable)
return NULL;
list_for_each_entry(dom, &data->pgtable->m4u_dom, list) {
if (dom->id == domain_id)
return &dom->domain;
}
return NULL;
}
static struct mtk_iommu_domain *__mtk_iommu_get_mtk_domain(
struct device *dev)
{
struct mtk_iommu_data *data;
struct mtk_iommu_domain *dom;
unsigned int domain_id;
if (!dev)
return NULL;
data = dev->iommu_fwspec->iommu_priv;
domain_id = mtk_iommu_get_domain_id(dev);
if (domain_id == MTK_IOVA_DOMAIN_COUNT)
return NULL;
list_for_each_entry(dom, &data->pgtable->m4u_dom, list) {
if (dom->id == domain_id)
return dom;
}
return NULL;
}
static struct iommu_group *mtk_iommu_get_group(
struct device *dev)
{
struct mtk_iommu_domain *dom;
dom = __mtk_iommu_get_mtk_domain(dev);
if (dom)
return dom->group;
return NULL;
}
bool mtk_dev_is_size_alignment(struct device *dev)
{
#ifdef MTK_IOMMU_SIZE_NOT_ALIGNMENT
return false;
#else
struct iommu_fwspec *fwspec;
unsigned int larbid, portid, port;
int i, count;
if (!dev)
return true;
fwspec = dev->iommu_fwspec;
larbid = MTK_IOMMU_TO_LARB(fwspec->ids[0]);
portid = MTK_IOMMU_TO_PORT(fwspec->ids[0]);
port = MTK_M4U_ID(larbid, portid);
count = ARRAY_SIZE(port_size_not_aligned);
for (i = 0; i < count; i++)
if (port == port_size_not_aligned[i])
return false;
return true;
#endif
}
EXPORT_SYMBOL_GPL(mtk_dev_is_size_alignment);
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
static unsigned int __mtk_iommu_get_boundary_id(
unsigned int larbid, unsigned int portid)
{
unsigned int boundary = MTK_IOMMU_IOVA_BOUNDARY_COUNT;
int i;
if (larbid >= MTK_IOMMU_LARB_NR)
return MTK_IOMMU_IOVA_BOUNDARY_COUNT;
for (i = 0; i < MTK_IOVA_DOMAIN_COUNT; i++) {
if (mtk_domain_array[i].port_mask[larbid] &
(1 << portid)) {
boundary = mtk_domain_array[i].boundary;
#ifdef MTK_IOMMU_DEBUG
pr_debug("%s, %d, larb%d, port%d bundary%d\n",
__func__, __LINE__,
larbid, portid, boundary);
#endif
break;
}
}
return boundary;
}
int mtk_iommu_get_boundary_id(struct device *dev)
{
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
unsigned int larbid, portid, boundary;
larbid = MTK_IOMMU_TO_LARB(fwspec->ids[0]);
portid = MTK_IOMMU_TO_PORT(fwspec->ids[0]);
boundary = __mtk_iommu_get_boundary_id(larbid, portid);
if (boundary >= MTK_IOMMU_IOVA_BOUNDARY_COUNT)
return -1;
return boundary;
}
#endif
int __mtk_iommu_atf_call(unsigned int cmd, unsigned int m4u_id,
unsigned int bank, size_t *tf_port,
size_t *tf_iova, size_t *tf_int)
{
#ifdef IOMMU_DESIGN_OF_BANK
unsigned int atf_cmd = 0;
int ret = 0;
struct arm_smccc_res res;
if (cmd >= IOMMU_ATF_CMD_COUNT ||
m4u_id >= MTK_IOMMU_M4U_COUNT ||
bank > MTK_IOMMU_BANK_COUNT) {
pr_notice("%s, %d, invalid m4u:%d, bank:%d, cmd:%d\n",
__func__, __LINE__, m4u_id, bank, cmd);
return -1;
}
atf_cmd = IOMMU_ATF_SET_COMMAND(m4u_id, bank, cmd);
/*pr_notice("%s, M4U CALL ATF CMD:0x%x\n", __func__, atf_cmd);*/
arm_smccc_smc(MTK_M4U_DEBUG_DUMP, atf_cmd,
0, 0, 0, 0, 0, 0, &res);
ret = res.a0;
*tf_port = res.a1;
*tf_iova = res.a2;
*tf_int = res.a3;
return ret;
#else
return 0;
#endif
}
int mtk_iommu_atf_call(unsigned int cmd, unsigned int m4u_id,
unsigned int bank)
{
size_t tf_port = 0, tf_iova = 0, tf_int = 0;
return __mtk_iommu_atf_call(cmd, m4u_id, bank, &tf_port,
&tf_iova, &tf_int);
}
#ifndef SMI_LARB_SEC_CON_EN
int mtk_iommu_dump_sec_larb(int larb, int port)
{
unsigned int atf_cmd = 0;
int ret = 0;
struct arm_smccc_res res;
if (larb >= SMI_LARB_NR ||
port >= ONE_SMI_PORT_NR) {
pr_notice("%s, %d, invalid larb:%d, port:%d\n",
__func__, __LINE__, larb, port);
return -1;
}
atf_cmd = IOMMU_ATF_SET_COMMAND(0, 0, IOMMU_ATF_DUMP_SMI_SEC_LARB);
arm_smccc_smc(MTK_M4U_DEBUG_DUMP, atf_cmd,
MTK_M4U_ID(larb, port), 0, 0, 0, 0, 0, &res);
ret = res.a0;
return ret;
}
#endif
static void mtk_iommu_atf_test_recovery(unsigned int m4u_id, unsigned int cmd)
{
int ret = 0;
if (cmd == IOMMU_ATF_SECURITY_DEBUG_DISABLE)
ret = mtk_iommu_atf_call(
IOMMU_ATF_SECURITY_DEBUG_ENABLE,
m4u_id,
MTK_IOMMU_BANK_COUNT);
if (cmd == IOMMU_ATF_SECURITY_DEBUG_ENABLE)
ret = mtk_iommu_atf_call(
IOMMU_ATF_SECURITY_DEBUG_DISABLE,
m4u_id,
MTK_IOMMU_BANK_COUNT);
}
void mtk_iommu_atf_test(unsigned int m4u_id, unsigned int cmd)
{
int ret = 0, i;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
if (m4u_id >= MTK_IOMMU_M4U_COUNT || !data)
return;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
pr_notice("%s: iommu:%d power off\n",
__func__, m4u_id);
return;
}
#endif
if (cmd < IOMMU_ATF_CMD_COUNT) {
pr_notice("======== IOMMU test ATF cmd %d: %s=========\n",
cmd, iommu_atf_cmd_name[cmd]);
ret = mtk_iommu_atf_call(cmd, m4u_id,
MTK_IOMMU_BANK_COUNT);
pr_notice(">>> cmd:%d %s, ret:%d\n", cmd,
(ret ? "FAIL" : "PASS"), ret);
mtk_iommu_atf_test_recovery(m4u_id, cmd);
return;
}
for (i = 0; i < IOMMU_ATF_DUMP_SECURE_PORT_CONFIG; i++) {
pr_notice("======== IOMMU test ATF cmd %d: %s=========\n",
i, iommu_atf_cmd_name[i]);
ret = mtk_iommu_atf_call(i, m4u_id,
MTK_IOMMU_BANK_COUNT);
pr_notice(">>> cmd:%d %s, ret:%d\n", i,
(ret ? "FAIL" : "PASS"), ret);
mtk_iommu_atf_test_recovery(m4u_id, cmd);
}
}
int mtk_switch_secure_debug_func(unsigned int m4u_id, bool enable)
{
#ifdef IOMMU_SECURITY_DBG_SUPPORT
int ret = 0;
if (m4u_id >= MTK_IOMMU_M4U_COUNT)
return -EINVAL;
if (enable)
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_DEBUG_ENABLE,
m4u_id, MTK_IOMMU_BANK_COUNT);
else
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_DEBUG_DISABLE,
m4u_id, MTK_IOMMU_BANK_COUNT);
if (ret)
return ret;
#endif
return 0;
}
static int mtk_dump_reg(const struct mtk_iommu_data *data,
unsigned int start, unsigned int length, struct seq_file *s)
{
int i = 0;
void __iomem *base;
if (!data) {
mmu_seq_print(s,
"%s, %d, invalid data\n",
__func__, __LINE__);
return -1;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
base = data->base;
for (i = 0; i < length; i += 4) {
if (length - i == 1)
mmu_seq_print(s,
"0x%x=0x%x\n",
start + 4 * i,
readl_relaxed(base + start + 4 * i));
else if (length - i == 2)
mmu_seq_print(s,
"0x%x=0x%x, 0x%x=0x%x\n",
start + 4 * i,
readl_relaxed(base + start + 4 * i),
start + 4 * (i + 1),
readl_relaxed(base + start +
4 * (i + 1)));
else if (length - i == 3)
mmu_seq_print(s,
"0x%x=0x%x, 0x%x=0x%x, 0x%x=0x%x\n",
start + 4 * i,
readl_relaxed(base + start + 4 * i),
start + 4 * (i + 1),
readl_relaxed(base + start +
4 * (i + 1)),
start + 4 * (i + 2),
readl_relaxed(base + start +
4 * (i + 2)));
else if (length - i >= 4)
mmu_seq_print(s,
"0x%x=0x%x, 0x%x=0x%x, 0x%x=0x%x, 0x%x=0x%x\n",
start + 4 * i,
readl_relaxed(base + start + 4 * i),
start + 4 * (i + 1),
readl_relaxed(base + start +
4 * (i + 1)),
start + 4 * (i + 2),
readl_relaxed(base + start +
4 * (i + 2)),
start + 4 * (i + 3),
readl_relaxed(base + start +
4 * (i + 3)));
}
return 0;
}
static int mtk_dump_debug_reg_info(const struct mtk_iommu_data *data,
struct seq_file *s)
{
mmu_seq_print(s,
"------ iommu:%d debug register ------\n",
data->m4uid);
return mtk_dump_reg(data, REG_MMU_DBG(0), MTK_IOMMU_DEBUG_REG_NR, s);
}
static int mtk_dump_rs_sta_info(const struct mtk_iommu_data *data, int mmu,
struct seq_file *s)
{
mmu_seq_print(s,
"------ iommu:%d mmu%d: RS status register ------\n",
data->m4uid, mmu);
mmu_seq_print(s,
"--<0x0>iova/bank --<0x4>descriptor --<0x8>2nd-base --<0xc>status\n");
return mtk_dump_reg(data,
REG_MMU_RS_VA(mmu, 0),
MTK_IOMMU_RS_COUNT * 4, s);
}
int __mtk_dump_reg_for_hang_issue(unsigned int m4u_id,
struct seq_file *s)
{
int cnt, ret, i;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
unsigned long flags;
mmu_seq_print(s,
"==== hang debug reg iommu%d ====\n",
m4u_id);
if (!data || data->base == 0) {
mmu_seq_print(s,
"%s, %d, base is NULL\n",
__func__, __LINE__);
return 0;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
mmu_seq_print(s,
"iommu:%d power off\n", m4u_id);
return 0;
}
#endif
base = data->base;
/* control register */
mmu_seq_print(s,
"REG_MMU_PT_BASE_ADDR(0x0) = 0x%x\n",
readl_relaxed(base + REG_MMU_PT_BASE_ADDR));
mmu_seq_print(s,
"REG_MMU_TFRP_PADDR(0x114) = 0x%x\n",
readl_relaxed(base + REG_MMU_TFRP_PADDR));
mmu_seq_print(s,
"REG_MMU_DUMMY(0x44) = 0x%x\n",
readl_relaxed(base + REG_MMU_DUMMY));
mmu_seq_print(s,
"REG_MMU_MISC_CTRL(0x48) = 0x%x\n",
readl_relaxed(base + REG_MMU_MISC_CTRL));
mmu_seq_print(s,
"REG_MMU_DCM_DIS(0x50) = 0x%x\n",
readl_relaxed(base + REG_MMU_DCM_DIS));
mmu_seq_print(s,
"REG_MMU_WR_LEN_CTRL(0x54) = 0x%x\n",
readl_relaxed(base + REG_MMU_WR_LEN_CTRL));
mmu_seq_print(s,
"REG_MMU_TBW_ID(0xA0) = 0x%x\n",
readl_relaxed(base + REG_MMU_TBW_ID));
mmu_seq_print(s,
"REG_MMU_CTRL_REG(0x110) = 0x%x\n",
readl_relaxed(base + REG_MMU_CTRL_REG));
/* dump five times*/
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
spin_unlock_irqrestore(&data->reg_lock, flags);
mmu_seq_print(s,
"%s, %d, failed to enable secure debug signal\n",
__func__, __LINE__);
return 0;
}
for (cnt = 0; cnt < 3; cnt++) {
mmu_seq_print(s,
"====== the %d time: REG_MMU_STA(0x08) = 0x%x ======\n",
cnt, readl_relaxed(base + REG_MMU_STA));
mtk_dump_debug_reg_info(data, s);
for (i = 0; i < MTK_IOMMU_MMU_COUNT; i++)
mtk_dump_rs_sta_info(data, i, s);
}
mmu_seq_print(s,
"========== dump hang reg end ========\n");
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
mmu_seq_print(s,
"%s, %d, failed to disable secure debug signal\n",
__func__, __LINE__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
void mtk_dump_reg_for_hang_issue(unsigned int type)
{
int i, start = -1, end = -1;
#ifdef APU_IOMMU_INDEX
switch (type) {
case 0: //smi power on before dump
start = 0;
end = APU_IOMMU_INDEX - 1;
break;
case 1: //apu power on before dump
start = APU_IOMMU_INDEX;
end = MTK_IOMMU_M4U_COUNT - 1;
break;
default:
start = -1;
end = -1;
break;
}
#else
start = 0;
end = MTK_IOMMU_M4U_COUNT - 1;
#endif
if (start < 0 || end < 0)
return;
for (i = start; i <= end; i++)
__mtk_dump_reg_for_hang_issue(i, NULL);
}
EXPORT_SYMBOL_GPL(mtk_dump_reg_for_hang_issue);
int mtk_iommu_dump_reg(int m4u_id, unsigned int start,
unsigned int end, char *user)
{
int ret = 0;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
unsigned long flags;
if (!data || !user)
return -1;
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#endif
pr_notice("====== [%s] dump reg of iommu:%d from 0x%x to 0x%x =======\n",
user, m4u_id, start, end);
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
pr_notice("%s, %d, failed to enable secure debug signal\n",
__func__, __LINE__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
mtk_dump_reg(data, start, (end - start + 4) / 4, NULL);
pr_notice("============= dump end ===============\n");
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
pr_notice("%s, %d, failed to disable secure debug signal\n",
__func__, __LINE__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
static unsigned int g_iommu_power_support;
unsigned int mtk_iommu_power_support(void)
{
return g_iommu_power_support;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
static int mtk_iommu_hw_clock_power_switch(const struct mtk_iommu_data *data,
bool enable, char *master, bool is_clk)
{
#ifndef CONFIG_FPGA_EARLY_PORTING
int err_id = -1, ret = 0;
unsigned int i, clk_nr;
struct mtk_iommu_clks *m4u_clks;
struct clk *clk_node;
if (!data) {
pr_notice("%s, %d, invalid data\n", __func__, __LINE__);
return -1;
}
m4u_clks = data->m4u_clks;
if (!m4u_clks) {
pr_notice("%s, %d, invalid m4u_clks\n", __func__, __LINE__);
return -1;
}
if (is_clk)
clk_nr = m4u_clks->nr_clks;
else
clk_nr = m4u_clks->nr_powers;
for (i = 0; i < clk_nr; i++) {
if (is_clk)
clk_node = m4u_clks->clks[i];
else
clk_node = m4u_clks->powers[i];
if (enable)
ret = clk_prepare_enable(clk_node);
else
clk_disable_unprepare(clk_node);
if (ret) {
err_id = i;
if (enable) {
for (i = 0; i < err_id; i++) {
clk_disable_unprepare(
m4u_clks->clks[i]);
}
}
break;
}
}
if (ret)
pr_notice("%s failed to %s %s[%d] of iommu%d, id%d for %s, ret:%d\n",
__func__, (enable ? "enable" : "disable"),
(is_clk ? "clock" : "power"), i,
data->m4uid, err_id, master, ret);
else
pr_debug("%s: %s %s[%d] of iommu%d, id%d for %s, ret:%d\n",
__func__, (enable ? "enable" : "disable"),
(is_clk ? "clock" : "power"), i,
data->m4uid, err_id, master, ret);
return ret;
#else
return 0;
#endif
}
int mtk_iommu_larb_clock_switch(unsigned int larb, bool enable)
{
#ifndef CONFIG_FPGA_EARLY_PORTING
int ret = 0;
unsigned int iommu_id;
struct mtk_iommu_data *data;
if (larb >= ARRAY_SIZE(smi_clk_name)) {
pr_notice("%s, invalid larb %d\n",
__func__, larb);
return -1;
}
iommu_id = mtk_get_iommu_index(larb);
data = mtk_iommu_get_m4u_data(iommu_id);
ret = mtk_iommu_hw_clock_power_switch(data,
enable, smi_clk_name[larb], true);
if (ret)
pr_notice("switch larb clock err:%d, larb:%d, on:%d\n",
ret, larb, enable);
return ret;
#else
return 0;
#endif
}
#endif
int mtk_iommu_port_clock_switch(unsigned int port, bool enable)
{
#ifdef IOMMU_POWER_CLK_SUPPORT
unsigned int larb;
int ret = 0;
larb = MTK_IOMMU_TO_LARB(port);
ret = mtk_iommu_larb_clock_switch(larb, enable);
return ret;
#else
return 0;
#endif
}
static int mtk_iommu_power_switch(struct mtk_iommu_data *data,
bool enable, char *master)
{
#ifndef CONFIG_FPGA_EARLY_PORTING
#ifdef IOMMU_POWER_CLK_SUPPORT
int ret = 0;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->m4u_clks->nr_powers)
return 0;
#endif
ret = mtk_iommu_hw_clock_power_switch(data, enable, master, !enable);
pr_notice("%s: %d: %s %s %s of iommu%d for %s, ret=%d\n",
__func__, __LINE__,
enable ? "enable" : "disable",
enable ? "power" : "clock",
ret ? "error" : "pass",
data->m4uid,
master ? master : "NULL",
ret);
if (ret)
return ret;
ret = mtk_iommu_hw_clock_power_switch(data, enable, master, enable);
pr_notice("%s, %d, %s %s %s at iommu%d, for %s, ret=%d\n",
__func__, __LINE__,
enable ? "enable" : "disable",
enable ? "clock" : "power",
ret ? "error" : "pass",
data->m4uid,
master ? master : "NULL",
ret);
return ret;
#endif
#endif
return 0;
}
int mtk_iommu_power_switch_by_id(unsigned int m4uid,
bool enable, char *master)
{
struct mtk_iommu_data *data;
if (m4uid >= MTK_IOMMU_M4U_COUNT) {
pr_notice("%s, invalid m4uid:%d,%s\n",
__func__, m4uid,
master ? master : "NULL");
return -1;
}
data = mtk_iommu_get_m4u_data(m4uid);
if (!data) {
pr_notice("%s, err data of m4uid:%d,%s\n",
__func__, m4uid,
master ? master : "NULL");
return -2;
}
return mtk_iommu_power_switch(data, enable, master);
}
static void __mtk_iommu_tlb_flush_all(const struct mtk_iommu_data *data)
{
if (!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base\n",
__func__, __LINE__);
return;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return;
#endif
writel_relaxed(F_MMU_INV_EN_L2 | F_MMU_INV_EN_L1,
data->base + REG_INVLID_SEL);
writel_relaxed(F_MMU_INVLDT_ALL,
data->base + REG_MMU_INVLDT);
wmb(); /* Make sure the tlb flush all done */
}
static int __mtk_iommu_tlb_sync(struct mtk_iommu_data *data)
{
return 0;
}
static void __mtk_iommu_tlb_add_flush_nosync(
struct mtk_iommu_data *data,
unsigned long iova_start,
unsigned long iova_end)
{
unsigned int regval;
int ret = 0;
u32 tmp;
unsigned long start, end, flags;
if (!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
return;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
return;
}
#endif
start = round_down(iova_start, SZ_4K);
end = round_down(iova_end, SZ_4K);
//0x38 for V1, 0x2c for V2
writel_relaxed(F_MMU_INV_EN_L2 | F_MMU_INV_EN_L1,
data->base + REG_INVLID_SEL);
regval = (unsigned int)(start &
F_MMU_INVLD_BIT31_12);
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
regval |= (start >> 32) & F_MMU_INVLD_BIT32;
#endif
writel_relaxed(regval, //0x24
data->base + REG_MMU_INVLD_START_A);
regval = (unsigned int)(end &
F_MMU_INVLD_BIT31_12);
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
regval |= (end >> 32) & F_MMU_INVLD_BIT32;
#endif
writel_relaxed(regval, //0x28
data->base + REG_MMU_INVLD_END_A);
writel(F_MMU_INVLDT_RNG, //0x20
data->base + REG_MMU_INVLDT);
wmb(); /*make sure the TLB sync has been triggered*/
ret = readl_poll_timeout_atomic(data->base +
REG_MMU_CPE_DONE, //0x12c
tmp, tmp != 0,
10, 5000);
if (ret) {
dev_notice(data->dev,
"Partial TLB flush time out, iommu:%d,start=0x%lx(0x%lx),end=0x%lx(0x%lx)\n",
data->m4uid, iova_start, start,
iova_end, end);
mtk_dump_reg(data, REG_MMU_PT_BASE_ADDR, 14, NULL);
__mtk_iommu_tlb_flush_all(data);
}
/* Clear the CPE status */
writel_relaxed(0, data->base + REG_MMU_INVLD_START_A);
writel_relaxed(0, data->base + REG_MMU_INVLD_END_A);
writel_relaxed(0, data->base + REG_MMU_CPE_DONE);
wmb(); /*make sure the TLB status has been cleared*/
spin_unlock_irqrestore(&data->reg_lock, flags);
return;
}
#if MTK_IOMMU_PAGE_TABLE_SHARE
void mtk_iommu_dump_iova_space(unsigned long target)
{
struct mtk_iommu_domain *dom;
int i = 0;
struct mtk_iommu_pgtable *pgtable = mtk_iommu_get_pgtable(NULL, 0);
if (!pgtable) {
pr_notice("%s, invalid pgtable\n", __func__);
return;
}
pr_notice("========= %s++ total %d domain ============\n",
__func__, pgtable->domain_count);
list_for_each_entry(dom, &pgtable->m4u_dom, list) {
pr_notice("===== domain %d =====\n", dom->id);
iommu_dma_dump_iovad(&dom->domain, target);
if (++i >= pgtable->domain_count)
break;
}
pr_notice("========= %s-- ============\n", __func__);
}
static void mtk_iommu_tlb_flush_all_lock(void *cookie, bool lock)
{
struct mtk_iommu_data *data, *temp;
int i = 0;
unsigned long flags;
list_for_each_entry_safe(data, temp, &m4ulist, list) {
if (lock)
spin_lock_irqsave(&data->reg_lock, flags);
__mtk_iommu_tlb_flush_all(data);
if (lock)
spin_unlock_irqrestore(&data->reg_lock, flags);
if (++i >= total_iommu_cnt)
return; //do not while loop if m4ulist is destroyed
}
}
static void mtk_iommu_tlb_add_flush_nosync(unsigned long iova,
size_t size,
size_t granule, bool leaf,
void *cookie)
{
struct mtk_iommu_data *data, *temp;
unsigned int i = 0;
unsigned long iova_start = iova;
unsigned long iova_end = iova + size - 1;
list_for_each_entry_safe(data, temp, &m4ulist, list) {
__mtk_iommu_tlb_add_flush_nosync(data, iova_start, iova_end);
if (++i >= total_iommu_cnt)
return; //do not while loop if m4ulist is destroyed
}
}
static void mtk_iommu_tlb_sync(void *cookie)
{
struct mtk_iommu_data *data, *temp;
int i = 0, ret;
list_for_each_entry_safe(data, temp, &m4ulist, list) {
ret = __mtk_iommu_tlb_sync(data);
if (ret)
pr_notice("%s, failed at iommu:%d, of the %d time\n",
__func__, data->m4uid, i);
if (++i >= total_iommu_cnt)
return; //do not while loop if m4ulist is destroyed
}
}
#else
void mtk_iommu_dump_iova_space(unsigned long iova)
{
struct mtk_iommu_domain *dom;
struct mtk_iommu_pgtable *pgtable;
int i = 0;
for (i = 0; i < total_iommu_cnt; i++)
pr_notice("<<<<<<<< iommu %d >>>>>>>>\n", i);
pgtable = mtk_iommu_get_pgtable(NULL, i);
if (!pgtable)
continue;
list_for_each_entry(dom, &pgtable->m4u_dom, list) {
pr_notice("===== domain %d =====\n", dom->id);
iommu_dma_dump_iovad(dom->domain, iova);
pr_notice("=====================\n");
}
pr_notice("<<<<<<<<<<<<>>>>>>>>>>>>\n");
}
}
static void mtk_iommu_tlb_flush_all_lock(void *cookie, bool lock)
{
struct mtk_iommu_data *data = cookie->data;
unsigned long flags;
if (lock)
spin_lock_irqsave(&data->reg_lock, flags);
__mtk_iommu_tlb_flush_all(data);
if (lock)
spin_unlock_irqrestore(&data->reg_lock, flags);
}
static void mtk_iommu_tlb_add_flush_nosync(unsigned long iova,
size_t size,
size_t granule, bool leaf,
void *cookie)
{
const struct mtk_iommu_data *data = cookie->data;
__mtk_iommu_tlb_add_flush_nosync(data, iova_start, iova_end);
}
static void mtk_iommu_tlb_sync(void *cookie)
{
const struct mtk_iommu_data *data = cookie->data;
ret = __mtk_iommu_tlb_sync(data);
if (ret)
pr_notice("%s, failed at iommu:%d\n",
__func__, data->m4uid);
}
#endif
#ifdef MTK_IOMMU_PERFORMANCE_IMPROVEMENT
static void mtk_iommu_tlb_add_flush_nosync_dummy(unsigned long iova,
size_t size,
size_t granule, bool leaf,
void *cookie)
{
/* do nothing for each sg table pa node sync
* but do one time tlb sync at then end of page table ops
*/
}
void mtk_iommu_tlb_flush_all_dummy(void *cookie)
{
/* do nothing for each sg table pa node sync
* but do one time tlb sync at then end of page table ops
*/
}
static void mtk_iommu_tlb_sync_dummy(void *cookie)
{
/* do nothing for each sg table pa node sync
* but do one time tlb sync at then end of page table ops
*/
}
#endif
void mtk_iommu_tlb_flush_all(void *cookie)
{
mtk_iommu_tlb_flush_all_lock(cookie, true);
}
static void mtk_iommu_iotlb_flush_all(struct iommu_domain *domain)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
mtk_iommu_tlb_flush_all(dom);
}
static void mtk_iommu_iotlb_range_add(struct iommu_domain *domain,
unsigned long iova, size_t size)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
mtk_iommu_tlb_add_flush_nosync(iova, size, 0, 0, dom);
}
static void mtk_iommu_iotlb_sync(struct iommu_domain *domain)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
mtk_iommu_tlb_sync(dom);
}
static const struct iommu_gather_ops mtk_iommu_gather_ops = {
#ifdef MTK_IOMMU_PERFORMANCE_IMPROVEMENT
.tlb_add_flush = mtk_iommu_tlb_add_flush_nosync_dummy,
.tlb_flush_all = mtk_iommu_tlb_flush_all_dummy,
.tlb_sync = mtk_iommu_tlb_sync_dummy,
#else
.tlb_flush_all = mtk_iommu_tlb_flush_all,
.tlb_add_flush = mtk_iommu_tlb_add_flush_nosync,
.tlb_sync = mtk_iommu_tlb_sync,
#endif
};
static inline void mtk_iommu_intr_modify_all(unsigned long enable)
{
struct mtk_iommu_data *data, *temp;
unsigned int i = 0;
unsigned long flags;
list_for_each_entry_safe(data, temp, &m4ulist, list) {
if (!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
continue;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
continue;
}
#endif
if (enable) {
writel_relaxed(0x6f,
data->base +
REG_MMU_INT_CONTROL0);
writel_relaxed(0xffffffff,
data->base +
REG_MMU_INT_MAIN_CONTROL);
} else {
writel_relaxed(0,
data->base +
REG_MMU_INT_CONTROL0);
writel_relaxed(0,
data->base +
REG_MMU_INT_MAIN_CONTROL);
}
spin_unlock_irqrestore(&data->reg_lock, flags);
if (++i >= total_iommu_cnt)
return; //do not while loop if m4ulist is destroyed
}
}
static void mtk_iommu_isr_restart(struct timer_list *t)
{
mtk_iommu_intr_modify_all(1);
mtk_iommu_debug_reset();
}
static int mtk_iommu_isr_pause_timer_init(struct mtk_iommu_data *data)
{
timer_setup(&data->iommu_isr_pause_timer, mtk_iommu_isr_restart, 0);
return 0;
}
static int mtk_iommu_isr_pause(int delay, struct mtk_iommu_data *data)
{
mtk_iommu_intr_modify_all(0); /* disable all intr */
/* delay seconds */
data->iommu_isr_pause_timer.expires = jiffies + delay * HZ;
if (!timer_pending(&data->iommu_isr_pause_timer))
add_timer(&data->iommu_isr_pause_timer);
return 0;
}
static void mtk_iommu_isr_record(struct mtk_iommu_data *data)
{
static int isr_cnt;
static unsigned long first_jiffies;
/* we allow one irq in 1s, or we will disable them after 5s. */
if (!isr_cnt || time_after(jiffies, first_jiffies + isr_cnt * HZ)) {
isr_cnt = 1;
first_jiffies = jiffies;
} else {
isr_cnt++;
if (isr_cnt >= 5) {
/* 5 irqs come in 5s, too many ! */
/* disable irq for a while, to avoid HWT timeout */
mtk_iommu_isr_pause(10, data);
isr_cnt = 0;
}
}
}
static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
dma_addr_t iova);
static int __mau_dump_status(int m4u_id, int slave, int mau);
static irqreturn_t mtk_iommu_isr(int irq, void *dev_id)
{
struct mtk_iommu_data *data = NULL;
struct iommu_domain *domain;
u32 int_state, int_state_l2, regval, int_id;
unsigned long fault_iova, fault_pa;
unsigned int fault_larb, fault_port;
bool layer, write, is_vpu;
int slave_id = 0, i, j, port_id;
unsigned int m4uid, bankid = MTK_IOMMU_BANK_NODE_COUNT;
phys_addr_t pa;
unsigned long flags;
int ret = 0, ret1 = 0;
void __iomem *base = NULL;
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
pr_notice("%s, irq=%d\n", __func__, irq);
for (i = 0; i < MTK_IOMMU_M4U_COUNT; i++) {
for (j = 0; j < MTK_IOMMU_BANK_NODE_COUNT; j++) {
if (irq == mtk_irq_bank[i][j]) {
m4uid = i;
bankid = j;
data = mtk_iommu_get_m4u_data(m4uid);
if (!data) {
pr_notice("%s, m4u:%u, bank:%u Invalid bank node\n",
__func__, m4uid, bankid);
return 0;
}
base = data->base_bank[bankid];
break;
}
}
}
if (!data) {
data = dev_id;
if (!data) {
pr_notice("%s, Invalid normal irq %d\n",
__func__, irq);
return 0;
}
m4uid = data->m4uid;
bankid = MTK_IOMMU_BANK_NODE_COUNT;
base = data->base;
}
if (!base || IS_ERR(base)) {
pr_notice("%s, %d, invalid base addr of iommu:%u, bank:%u\n",
__func__, __LINE__, m4uid, bankid);
return 0;
}
#else
data = dev_id;
if (!data) {
pr_notice("%s, Invalid normal irq %d\n",
__func__, irq);
return 0;
}
m4uid = data->m4uid;
if (!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
return 0;
}
base = data->base;
#endif
#ifdef IOMMU_POWER_CLK_SUPPORT
spin_lock_irqsave(&data->reg_lock, flags);
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
data->isr_ref++;
spin_unlock_irqrestore(&data->reg_lock, flags);
#endif
ret1 = mtk_switch_secure_debug_func(data->m4uid, 1);
if (ret1)
pr_notice("%s, %d, m4u:%u, failed to enable secure debug signal\n",
__func__, __LINE__, data->m4uid);
/* Read error info from registers */
int_state_l2 = readl_relaxed(base + REG_MMU_L2_FAULT_ST);
int_state = readl_relaxed(base + REG_MMU_FAULT_ST1);
if (!int_state_l2 && !int_state) {
ret = 0;
goto out;
}
pr_notice("iommu:%u, bank:%u, L2 int sta(0x130)=0x%x, main sta(0x134)=0x%x\n",
m4uid,
bankid == MTK_IOMMU_BANK_NODE_COUNT ? 0 : bankid + 1,
int_state_l2, int_state);
if (int_state_l2 & F_INT_L2_MULTI_HIT_FAULT)
MMU_INT_REPORT(m4uid, 0, F_INT_L2_MULTI_HIT_FAULT);
if (int_state_l2 & F_INT_L2_TABLE_WALK_FAULT) {
unsigned int layer;
MMU_INT_REPORT(m4uid, 0, F_INT_L2_TABLE_WALK_FAULT);
regval = readl_relaxed(base +
REG_MMU_TBWALK_FAULT_VA);
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
fault_iova = ((unsigned long)regval & F_MMU_FAULT_VA_BIT31_12) |
(((unsigned long)regval &
F_MMU_FAULT_VA_BIT32) << 23);
#else
fault_iova = (unsigned long)regval;
#endif
layer = regval & 1;
mmu_aee_print(
"L2 table walk fault: iova=0x%lx, layer=%d\n",
fault_iova, layer);
}
if (int_state_l2 & F_INT_L2_PFH_DMA_FIFO_OVERFLOW)
MMU_INT_REPORT(m4uid, 0,
F_INT_L2_PFH_DMA_FIFO_OVERFLOW);
if (int_state_l2 & F_INT_L2_MISS_DMA_FIFO_OVERFLOW)
MMU_INT_REPORT(m4uid, 0,
F_INT_L2_MISS_DMA_FIFO_OVERFLOW);
if (int_state_l2 & F_INT_L2_INVALID_DONE)
MMU_INT_REPORT(m4uid, 0, F_INT_L2_INVALID_DONE);
if (int_state_l2 & F_INT_L2_PFH_OUT_FIFO_ERROR)
MMU_INT_REPORT(m4uid, 0,
F_INT_L2_PFH_OUT_FIFO_ERROR);
if (int_state_l2 & F_INT_L2_PFH_IN_FIFO_ERROR)
MMU_INT_REPORT(m4uid, 0,
F_INT_L2_PFH_IN_FIFO_ERROR);
if (int_state_l2 & F_INT_L2_MISS_OUT_FIFO_ERROR)
MMU_INT_REPORT(m4uid, 0,
F_INT_L2_MISS_OUT_FIFO_ERROR);
if (int_state_l2 & F_INT_L2_MISS_IN_FIFO_ERR)
MMU_INT_REPORT(m4uid, 0, F_INT_L2_MISS_IN_FIFO_ERR);
for (i = 0; i < MTK_MMU_NUM_OF_IOMMU(m4uid); i++) {
if (int_state & (F_INT_MMU_MAIN_MSK(i) |
F_INT_MAIN_MAU_INT_EN(i))) {
slave_id = i;
break;
}
}
if (i == MTK_IOMMU_MMU_COUNT) {
pr_info("m4u interrupt error: status = 0x%x\n", int_state);
iommu_set_field_by_mask(base, REG_MMU_INT_CONTROL0,
F_INT_CTL0_INT_CLR,
F_INT_CTL0_INT_CLR);
ret = 0;
goto out;
}
if (int_state & F_INT_TRANSLATION_FAULT(slave_id)) {
int_id = readl_relaxed(base + REG_MMU_INT_ID(slave_id));
port_id = mtk_iommu_get_larb_port(
F_MMU_INT_TF_VAL(int_id),
m4uid, &fault_larb,
&fault_port);
pr_notice("iommu:%d, slave:%d, port_id=%d(%d-%d), tf_id:0x%x\n",
m4uid, slave_id, port_id,
fault_larb, fault_port, int_id);
if (port_id < 0) {
WARN_ON(1);
ret = 0;
goto out;
}
/*pseudo_dump_port(port_id, true);*/
regval = readl_relaxed(base +
REG_MMU_FAULT_STATUS(slave_id));
layer = regval & F_MMU_FAULT_VA_LAYER_BIT;
write = regval & F_MMU_FAULT_VA_WRITE_BIT;
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
fault_iova = ((unsigned long)regval & F_MMU_FAULT_VA_BIT31_12) |
(((unsigned long)regval &
F_MMU_FAULT_VA_BIT32) << 23);
#else
fault_iova = (unsigned long)(regval);
#endif
pr_notice("%s, %d, fault_iova=%lx, regval=0x%x\n",
__func__, __LINE__, fault_iova, regval);
domain = __mtk_iommu_get_domain(data,
fault_larb, fault_port);
if (!domain) {
WARN_ON(1);
ret = 0;
goto out;
}
pa = mtk_iommu_iova_to_phys(domain, fault_iova & PAGE_MASK);
fault_pa = readl_relaxed(base +
REG_MMU_INVLD_PA(slave_id));
fault_pa |= (unsigned long)(regval &
F_MMU_FAULT_PA_BIT32) << 26;
pr_notice("fault_pa=0x%lx, get pa=%x, tfrp=0x%x, ptbase=0x%x\n",
fault_pa, (unsigned int)pa,
readl_relaxed(base + REG_MMU_TFRP_PADDR),
readl_relaxed(base + REG_MMU_PT_BASE_ADDR));
#ifdef APU_IOMMU_INDEX
if (m4uid >= APU_IOMMU_INDEX) {
is_vpu = true;
} else {
is_vpu = false;
}
#endif
if (enable_custom_tf_report())
report_custom_iommu_fault(m4uid,
base,
fault_iova,
fault_pa,
F_MMU_INT_TF_VAL(int_id),
is_vpu, false);
if (report_iommu_fault(domain, data->dev, fault_iova,
write ? IOMMU_FAULT_WRITE :
IOMMU_FAULT_READ)) {
dev_err_ratelimited(
data->dev,
"iommu fault type=0x%x iova=0x%lx pa=0x%lx larb=%d port=%d is_vpu=%d layer=%d %s\n",
int_state, fault_iova, fault_pa,
fault_larb, fault_port, is_vpu,
layer, write ? "write" : "read");
}
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
if (bankid < MTK_IOMMU_BANK_NODE_COUNT)
mtk_iommu_atf_call(IOMMU_ATF_BANK_DUMP_INFO,
m4uid, bankid + 1);
#endif
m4u_dump_pgtable(1, fault_iova);
}
if (int_state &
F_INT_MAIN_MULTI_HIT_FAULT(slave_id)) {
MMU_INT_REPORT(m4uid, slave_id,
F_INT_MAIN_MULTI_HIT_FAULT(slave_id));
}
if (int_state &
F_INT_INVALID_PHYSICAL_ADDRESS_FAULT(slave_id)) {
if (!(int_state &
F_INT_TRANSLATION_FAULT(slave_id))) {
MMU_INT_REPORT(m4uid, slave_id,
F_INT_INVALID_PHYSICAL_ADDRESS_FAULT(slave_id));
}
}
if (int_state & F_INT_ENTRY_REPLACEMENT_FAULT(slave_id)) {
MMU_INT_REPORT(m4uid, slave_id,
F_INT_ENTRY_REPLACEMENT_FAULT(slave_id));
}
if (int_state & F_INT_TLB_MISS_FAULT(slave_id))
MMU_INT_REPORT(m4uid, slave_id,
F_INT_TLB_MISS_FAULT(slave_id));
if (int_state & F_INT_MISS_FIFO_ERR(slave_id))
MMU_INT_REPORT(m4uid, slave_id,
F_INT_MISS_FIFO_ERR(slave_id));
if (int_state & F_INT_PFH_FIFO_ERR(slave_id))
MMU_INT_REPORT(m4uid, slave_id,
F_INT_PFH_FIFO_ERR(slave_id));
if (int_state & F_INT_MAIN_MAU_INT_EN(slave_id)) {
MMU_INT_REPORT(m4uid, slave_id,
F_INT_MAIN_MAU_INT_EN(slave_id));
__mau_dump_status(m4uid, slave_id, 0);
}
/* Interrupt clear */
regval = readl_relaxed(base + REG_MMU_INT_CONTROL0);
regval |= F_INT_CTL0_INT_CLR;
writel_relaxed(regval, base + REG_MMU_INT_CONTROL0);
mtk_iommu_tlb_flush_all_lock(data, false);
mtk_iommu_isr_record(data);
ret = IRQ_HANDLED;
out:
spin_lock_irqsave(&data->reg_lock, flags);
data->isr_ref--;
spin_unlock_irqrestore(&data->reg_lock, flags);
ret1 = mtk_switch_secure_debug_func(data->m4uid, 0);
if (ret1)
pr_notice("%s, %d, m4u:%u, failed to disable secure debug signal\n",
__func__, __LINE__, data->m4uid);
return ret;
}
#ifdef MTK_M4U_SECURE_IRQ_SUPPORT
static int mtk_irq_sec[MTK_IOMMU_M4U_COUNT];
irqreturn_t MTK_M4U_isr_sec(int irq, void *dev_id)
{
struct mtk_iommu_data *data = NULL;
size_t tf_port = 0, tf_iova = 0, tf_int = 0;
unsigned int m4u_id = 0;
int i, ret = 0;
unsigned long flags, fault_iova;
for (i = 0; i < MTK_IOMMU_M4U_COUNT; i++) {
if (irq == mtk_irq_sec[i]) {
m4u_id = i;
data = mtk_iommu_get_m4u_data(m4u_id);
break;
}
}
if (!data) {
pr_notice("%s, Invalid secure irq %d\n",
__func__, irq);
return 0;
}
if (!data->base_sec || IS_ERR(data->base_sec) ||
!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base addr of iommu:%d\n",
__func__, __LINE__, m4u_id);
return 0;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
spin_lock_irqsave(&data->reg_lock, flags);
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
data->isr_ref++;
spin_unlock_irqrestore(&data->reg_lock, flags);
#endif
ret = __mtk_iommu_atf_call(IOMMU_ATF_DUMP_SECURE_REG,
m4u_id, 4, &tf_port, &tf_iova, &tf_int);
pr_notice("iommu:%d secure bank fault_id:0x%zx port:0x%zx in normal world!\n",
m4u_id, tf_int, tf_port);
if (!ret && tf_port < M4U_PORT_UNKNOWN) {
bool is_vpu;
if (m4u_id >= APU_IOMMU_INDEX)
is_vpu = true;
else
is_vpu = false;
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
fault_iova = ((unsigned long)tf_iova &
F_MMU_FAULT_VA_BIT31_12) |
(((unsigned long)tf_iova &
F_MMU_FAULT_VA_BIT32) << 23);
#else
fault_iova = (unsigned long)(tf_iova);
#endif
if (enable_custom_tf_report())
report_custom_iommu_fault(m4u_id,
data->base,
fault_iova,
0,
F_MMU_INT_TF_VAL(tf_int),
is_vpu,
true);
}
ret = IRQ_HANDLED;
spin_lock_irqsave(&data->reg_lock, flags);
data->isr_ref--;
spin_unlock_irqrestore(&data->reg_lock, flags);
return ret;
}
#endif
static void mtk_iommu_config(struct mtk_iommu_data *data,
struct device *dev, bool enable)
{
#ifdef CONFIG_MTK_SMI_EXT
struct mtk_smi_larb_iommu *larb_mmu;
unsigned int larbid, portid;
struct iommu_fwspec *fwspec = dev->iommu_fwspec;
int i;
for (i = 0; i < fwspec->num_ids; ++i) {
larbid = MTK_IOMMU_TO_LARB(fwspec->ids[i]);
portid = MTK_IOMMU_TO_PORT(fwspec->ids[i]);
if (larbid >= MTK_LARB_NR_MAX) {
WARN_ON(1);
dev_notice(dev, "%d(%d) exceed the max larb ID\n",
larbid, fwspec->ids[i]);
break;
}
larb_mmu = &data->smi_imu.larb_imu[larbid];
dev_dbg(dev, "%s iommu port: %d\n",
enable ? "enable" : "disable", portid);
if (enable)
larb_mmu->mmu |= MTK_SMI_MMU_EN(portid);
else
larb_mmu->mmu &= ~MTK_SMI_MMU_EN(portid);
}
#endif
}
int __mtk_iommu_get_pgtable_base_addr(
struct mtk_iommu_pgtable *pgtable,
unsigned int *pgd_pa)
{
if (!pgtable)
pgtable = mtk_iommu_get_pgtable(NULL, 0);
if (!pgtable) {
pr_notice("%s, %d, cannot find pgtable\n",
__func__, __LINE__);
return -1;
}
*pgd_pa = pgtable->cfg.arm_v7s_cfg.ttbr[0] & F_MMU_PT_BASE_ADDR_MSK;
if (pgtable->cfg.arm_v7s_cfg.ttbr[1] <
(1 << (CONFIG_MTK_IOMMU_PGTABLE_EXT - 32))) {
*pgd_pa |= pgtable->cfg.arm_v7s_cfg.ttbr[1] &
F_MMU_PT_BASE_ADDR_BIT32;
} else {
pr_notice("%s, %d, invalid pgtable base addr, 0x%x_%x\n",
__func__, __LINE__,
pgtable->cfg.arm_v7s_cfg.ttbr[1],
pgtable->cfg.arm_v7s_cfg.ttbr[0]);
return -2;
}
return 0;
}
int mtk_iommu_get_pgtable_base_addr(unsigned long *pgd_pa)
{
unsigned int pgd_reg_val = 0;
int ret = 0;
ret = __mtk_iommu_get_pgtable_base_addr(NULL, &pgd_reg_val);
if (ret)
return ret;
*pgd_pa = ((unsigned long)(pgd_reg_val &
F_MMU_PT_BASE_ADDR_BIT32) << 32) |
(unsigned long)(pgd_reg_val &
F_MMU_PT_BASE_ADDR_MSK);
return 0;
}
static int mtk_iommu_create_pgtable(struct mtk_iommu_data *data)
{
struct mtk_iommu_pgtable *pgtable =
mtk_iommu_get_pgtable(data, init_data_id);
if (pgtable)
return 0;
pgtable = kzalloc(sizeof(*pgtable), GFP_KERNEL);
if (!pgtable)
return -ENOMEM;
spin_lock_init(&pgtable->pgtlock);
spin_lock_init(&pgtable->domain_lock);
pgtable->domain_count = 0;
INIT_LIST_HEAD(&pgtable->m4u_dom);
pgtable->cfg = (struct io_pgtable_cfg) {
.quirks = IO_PGTABLE_QUIRK_ARM_NS |
IO_PGTABLE_QUIRK_NO_PERMS |
IO_PGTABLE_QUIRK_TLBI_ON_MAP,
.pgsize_bitmap = mtk_iommu_ops.pgsize_bitmap,
#if defined(MTK_IOVA_ADDR_BITS) && defined(MTK_PHYS_ADDR_BITS)
.ias = MTK_IOVA_ADDR_BITS,
.oas = MTK_PHYS_ADDR_BITS,
#else
.ias = 32,
.oas = 32,
#endif
.tlb = &mtk_iommu_gather_ops,
.iommu_dev = data->dev,
};
if (data->enable_4GB)
pgtable->cfg.quirks |= IO_PGTABLE_QUIRK_ARM_MTK_4GB;
pgtable->iop = alloc_io_pgtable_ops(ARM_V7S, &pgtable->cfg, data);
if (!pgtable->iop) {
dev_err(data->dev, "Failed to alloc io pgtable\n");
return -EINVAL;
}
if (mtk_iommu_set_pgtable(data, init_data_id, pgtable)) {
pr_notice("%s, failed to set pgtable\n", __func__);
return -EFAULT;
}
pr_notice("%s, %d, create pgtable done\n",
__func__, __LINE__);
return 0;
}
static int mtk_iommu_attach_pgtable(struct mtk_iommu_data *data,
struct device *dev)
{
struct mtk_iommu_pgtable *pgtable =
mtk_iommu_get_pgtable(data, init_data_id);
unsigned int regval = 0, ret;
unsigned int pgd_pa_reg = 0;
unsigned long flags;
#ifdef IOMMU_POWER_CLK_SUPPORT
struct mtk_iommu_suspend_reg *reg = &data->reg;
#endif
// create pgtable
if (!pgtable) {
ret = mtk_iommu_create_pgtable(data);
if (ret) {
pr_notice("%s, %d, failed to create pgtable, err %d\n",
__func__, __LINE__, ret);
return ret;
}
pgtable = mtk_iommu_get_pgtable(data, init_data_id);
}
// binding to pgtable
data->pgtable = pgtable;
// update HW settings
if (__mtk_iommu_get_pgtable_base_addr(pgtable, &pgd_pa_reg))
return -EFAULT;
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (data->poweron) {
writel(pgd_pa_reg, data->base + REG_MMU_PT_BASE_ADDR);
regval = readl_relaxed(data->base + REG_MMU_PT_BASE_ADDR);
pr_notice("%s, %d, iommu:%d config pgtable base addr=0x%x, quiks=0x%lx\n",
__func__, __LINE__, data->m4uid,
regval, pgtable->cfg.quirks);
spin_unlock_irqrestore(&data->reg_lock, flags);
} else {
spin_unlock_irqrestore(&data->reg_lock, flags);
reg->pt_base = pgd_pa_reg;
pr_notice("%s, %d, iommu:%d backup pgtable base addr=0x%x, quiks=0x%lx\n",
__func__, __LINE__, data->m4uid,
reg->pt_base, pgtable->cfg.quirks);
}
#else
writel(pgd_pa_reg, data->base + REG_MMU_PT_BASE_ADDR);
regval = readl_relaxed(data->base + REG_MMU_PT_BASE_ADDR);
pr_notice("%s, %d, iommu:%d config pgtable base addr=0x%x, quiks=0x%lx\n",
__func__, __LINE__, data->m4uid,
regval, pgtable->cfg.quirks);
spin_unlock_irqrestore(&data->reg_lock, flags);
#endif
return 0;
}
#ifndef CONFIG_ARM64
static int mtk_extend_iommu_mapping(struct dma_iommu_mapping *mapping)
{
int next_bitmap;
if (mapping->nr_bitmaps >= mapping->extensions) {
pr_notice("%s, %d, err nr:0x%x > externsions:0x%x\n",
__func__, __LINE__,
mapping->nr_bitmaps, mapping->extensions);
return -EINVAL;
}
next_bitmap = mapping->nr_bitmaps;
mapping->bitmaps[next_bitmap] = kzalloc(mapping->bitmap_size,
GFP_ATOMIC);
if (!mapping->bitmaps[next_bitmap])
return -ENOMEM;
mapping->nr_bitmaps++;
return 0;
}
static inline int mtk_do_reserve_iova(
struct dma_iommu_mapping *mapping,
dma_addr_t iova,
size_t size, unsigned int pg_off)
{
unsigned long count, start;
unsigned long flags;
int i, sbitmap, ebitmap;
if (iova < mapping->base) {
pr_notice("%s, %d, err iova:0x%x < base:0x%x\n",
__func__, __LINE__, iova, mapping->base);
return -EINVAL;
}
start = (iova - mapping->base) >> pg_off;
count = PAGE_ALIGN(size) >> pg_off;
sbitmap = start / mapping->bits;
ebitmap = (start + count) / mapping->bits;
start = start % mapping->bits;
if (ebitmap > mapping->extensions) {
pr_notice("%s, %d, err end:0x%x > extensions:0x%x\n",
__func__, __LINE__, ebitmap,
mapping->extensions);
return -EINVAL;
}
spin_lock_irqsave(&mapping->lock, flags);
for (i = mapping->nr_bitmaps; i <= ebitmap; i++) {
if (mtk_extend_iommu_mapping(mapping)) {
pr_notice("%s, %d, err extend\n",
__func__, __LINE__);
spin_unlock_irqrestore(&mapping->lock, flags);
return -ENOMEM;
}
}
for (i = sbitmap; count && i < mapping->nr_bitmaps; i++) {
int bits = count;
if (bits + start > mapping->bits)
bits = mapping->bits - start;
bitmap_set(mapping->bitmaps[i], start, bits);
start = 0;
count -= bits;
}
spin_unlock_irqrestore(&mapping->lock, flags);
return 0;
}
static int mtk_iova_reserve_iommu_regions(struct mtk_iommu_domain *dom,
struct device *dev)
{
struct iommu_resv_region *region;
LIST_HEAD(resv_regions);
unsigned int pg_size, pg_off;
struct iommu_domain *domain = &dom->domain;
struct dma_iommu_mapping *mapping = dom->mapping;
int ret = 0;
if (dom->resv_status)
return 0;
if (!domain->ops->pgsize_bitmap) {
WARN_ON(1);
pg_off = PAGE_SHIFT;
} else {
pg_off = __ffs(domain->ops->pgsize_bitmap);
}
pg_size = 1UL << pg_off;
iommu_get_resv_regions(dev, &resv_regions);
/* We need to consider overlapping regions for different devices */
list_for_each_entry(region, &resv_regions, list) {
dma_addr_t start, end, addr;
start = ALIGN(region->start, pg_size);
end = ALIGN(region->start + region->length, pg_size);
for (addr = start; addr < end; addr += pg_size) {
phys_addr_t phys_addr;
phys_addr = iommu_iova_to_phys(domain, addr);
if (phys_addr)
continue;
ret = iommu_map(domain, addr, addr,
pg_size, region->prot);
if (ret)
goto out;
}
ret = mtk_do_reserve_iova(mapping, start, end - start, pg_off);
if (ret != 0) {
pr_notice("%s, %d, err reserve (0x%llx+0x%lx) in the mapping of group %d, pg_off=0x%x\n",
__func__, __LINE__, region->start,
region->length, dom->id, pg_off);
goto out;
} else {
dom->resv_status = 1;
pr_notice("%s, %d, finish reserve (0x%llx+0x%lx) in the mapping of group %d, pg_off=0x%x\n",
__func__, __LINE__, region->start,
region->length, dom->id, pg_off);
}
}
out:
iommu_put_resv_regions(dev, &resv_regions);
return ret;
}
// struct of_phandle_args *args)
static int mtk_iommu_create_mapping(struct device *dev)
{
struct dma_iommu_mapping *mapping;
unsigned long start, end, size;
int ret = 0;
struct mtk_iommu_domain *dom;
dom = __mtk_iommu_get_mtk_domain(dev);
if (!dom) {
pr_notice("%s, %d, err domain\n",
__func__, __LINE__);
return -ENODEV;
}
mapping = dom->mapping;
if (!mapping) {
start = max_t(unsigned long, SZ_4K,
mtk_domain_array[dom->id].min_iova);
end = min_t(unsigned long,
DMA_BIT_MASK(CONFIG_MTK_IOMMU_PGTABLE_EXT),
mtk_domain_array[dom->id].max_iova);
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
if (start >> 32 != end >> 32 ||
start >> 32 != mtk_domain_array[dom->id].boundary) {
pr_notice("%s, %d, err start:0x%lx, end:0x%lx, boundary:%d\n",
__func__, __LINE__, start, end,
mtk_domain_array[dom->id].boundary);
return -EINVAL;
}
#endif
size = end - start + 1;
if (size < 0 || size > DMA_BIT_MASK(
CONFIG_MTK_IOMMU_PGTABLE_EXT)) {
pr_notice("%s, %d, err domain size 0x%x\n",
__func__, __LINE__, size);
return -EINVAL;
}
mapping = arm_iommu_create_mapping(&platform_bus_type,
start, size);
if (IS_ERR(mapping)) {
pr_notice("%s, %d, err mapping\n",
__func__, __LINE__);
return -ENOMEM;
}
dom->mapping = mapping;
dev_notice(dev, "%s, %d, create mapping for group %d, start:0x%x, size:0x%x\n",
__func__, __LINE__, dom->id, start, size);
}
ret = arm_iommu_attach_device(dev, mapping);
if (ret) {
dev_notice(dev, "%s, %d, failed to attach to mapping of group %d\n",
__func__, __LINE__, dom->id);
goto err_release_mapping;
}
return 0;
err_release_mapping:
arm_iommu_release_mapping(mapping);
return ret;
}
#endif
static struct iommu_domain *mtk_iommu_domain_alloc(unsigned int type)
{
struct mtk_iommu_domain *dom;
struct mtk_iommu_pgtable *pgtable =
mtk_iommu_get_pgtable(NULL, init_data_id);
unsigned int id;
#ifdef CONFIG_ARM64
// allocated at device_group for IOVA space management by iovad
unsigned int domain_type = IOMMU_DOMAIN_DMA;
#else
// allocated at create mapping for IOVA space management by mapping
unsigned int domain_type = IOMMU_DOMAIN_UNMANAGED;
#endif
if (!pgtable) {
pr_notice("%s, %d, err pgtabe of iommu%d\n",
__func__, __LINE__, init_data_id);
return NULL;
}
if (type != domain_type) {
pr_notice("%s, %d, err type%d\n",
__func__, __LINE__, type);
return NULL;
}
id = pgtable->init_domain_id;
list_for_each_entry(dom, &pgtable->m4u_dom, list) {
if (dom->id == id)
return &dom->domain;
}
return NULL;
}
static void mtk_iommu_domain_free(struct iommu_domain *domain)
{
unsigned long flags;
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
struct mtk_iommu_pgtable *pgtable = dom->pgtable;
pr_notice("%s, %d, domain_count=%d, free the %d domain\n",
__func__, __LINE__, pgtable->domain_count, dom->id);
#ifdef CONFIG_ARM64
iommu_put_dma_cookie(domain);
#else
arm_iommu_release_mapping(dom->mapping);
#endif
kfree(dom);
spin_lock_irqsave(&pgtable->domain_lock, flags);
pgtable->domain_count--;
if (pgtable->domain_count > 0) {
spin_unlock_irqrestore(&pgtable->domain_lock, flags);
return;
}
spin_unlock_irqrestore(&pgtable->domain_lock, flags);
free_io_pgtable_ops(pgtable->iop);
kfree(pgtable);
}
static int mtk_iommu_attach_device(struct iommu_domain *domain,
struct device *dev)
{
struct mtk_iommu_data *data = dev->iommu_fwspec->iommu_priv;
#if 0
ifndef CONFIG_ARM64 case but not required for now.
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
#endif
if (!data)
return -ENODEV;
mtk_iommu_config(data, dev, true);
#if 0
ifndef CONFIG_ARM64 case but not require for now.
/* reserve IOVA region after pgTable ready */
mtk_iova_reserve_iommu_regions(dom, dev);
#endif
return 0;
}
static void mtk_iommu_detach_device(struct iommu_domain *domain,
struct device *dev)
{
struct mtk_iommu_data *data = dev->iommu_fwspec->iommu_priv;
if (!data)
return;
mtk_iommu_config(data, dev, false);
}
static int mtk_iommu_map(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t size, int prot)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
struct mtk_iommu_pgtable *pgtable = dom->pgtable;
unsigned long flags;
int ret = 0;
#ifdef IOMMU_DEBUG_ENABLED
if (g_tf_test)
return 0;
#endif
spin_lock_irqsave(&pgtable->pgtlock, flags);
ret = pgtable->iop->map(pgtable->iop, iova, paddr, size, prot);
spin_unlock_irqrestore(&pgtable->pgtlock, flags);
return ret;
}
static size_t mtk_iommu_unmap(struct iommu_domain *domain,
unsigned long iova, size_t size)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
struct mtk_iommu_pgtable *pgtable = dom->pgtable;
unsigned long flags;
size_t unmapsz;
#ifdef IOMMU_DEBUG_ENABLED
if (g_tf_test)
return size;
#endif
spin_lock_irqsave(&pgtable->pgtlock, flags);
unmapsz = pgtable->iop->unmap(pgtable->iop, iova, size);
spin_unlock_irqrestore(&pgtable->pgtlock, flags);
return unmapsz;
}
static phys_addr_t mtk_iommu_iova_to_phys(struct iommu_domain *domain,
dma_addr_t iova)
{
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
struct mtk_iommu_pgtable *pgtable = dom->pgtable;
unsigned long flags;
phys_addr_t pa;
spin_lock_irqsave(&pgtable->pgtlock, flags);
pa = pgtable->iop->iova_to_phys(pgtable->iop, iova);
spin_unlock_irqrestore(&pgtable->pgtlock, flags);
return pa;
}
int mtk_iommu_switch_acp(struct device *dev,
unsigned long iova, size_t size, bool is_acp)
{
struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
struct mtk_iommu_domain *dom = to_mtk_domain(domain);
struct mtk_iommu_pgtable *pgtable = dom->pgtable;
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&pgtable->pgtlock, flags);
ret = pgtable->iop->switch_acp(pgtable->iop, iova, size, is_acp);
#ifdef MTK_IOMMU_PERFORMANCE_IMPROVEMENT
mtk_iommu_tlb_add_flush_nosync(iova, size, 0, 0, dom);
mtk_iommu_tlb_sync(dom);
#endif
spin_unlock_irqrestore(&pgtable->pgtlock, flags);
if (ret)
dev_notice(dev, "%s, %d, failed to switch acp, iova:0x%lx, size:0x%lx, acp:%d\n",
__func__, __LINE__, iova, size, is_acp);
return ret;
}
EXPORT_SYMBOL_GPL(mtk_iommu_switch_acp);
static struct iommu_group *mtk_iommu_create_iova_space(
const struct mtk_iommu_data *data, struct device *dev)
{
struct mtk_iommu_pgtable *pgtable =
mtk_iommu_get_pgtable(data, init_data_id);
struct mtk_iommu_domain *dom;
struct iommu_group *group;
unsigned long flags, start, end;
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
unsigned int boundary;
#endif
if (!pgtable) {
pr_notice("%s, %d, err pgtable of iommu%d\n",
__func__, __LINE__, init_data_id);
return NULL;
}
group = mtk_iommu_get_group(dev);
if (group) {
iommu_group_ref_get(group);
return group;
}
// init mtk_iommu_domain
dom = kzalloc(sizeof(*dom), GFP_KERNEL);
if (!dom)
return NULL;
// init iommu_group
group = iommu_group_alloc();
if (IS_ERR(group)) {
dev_notice(dev, "Failed to allocate M4U IOMMU group\n");
goto free_dom;
}
dom->group = group;
dom->id = mtk_iommu_get_domain_id(dev);
if (dom->id >= MTK_IOVA_DOMAIN_COUNT) {
dev_notice(dev, "%s, %d, invalid iommu device, dom id = %d\n",
__func__, __LINE__, dom->id);
goto free_group;
}
spin_lock_irqsave(&pgtable->domain_lock, flags);
if (pgtable->domain_count >= MTK_IOVA_DOMAIN_COUNT) {
spin_unlock_irqrestore(&pgtable->domain_lock, flags);
pr_notice("%s, %d, too many domain, count=%d\n",
__func__, __LINE__, pgtable->domain_count);
goto free_group;
}
pgtable->init_domain_id = dom->id;
pgtable->domain_count++;
spin_unlock_irqrestore(&pgtable->domain_lock, flags);
dom->domain.pgsize_bitmap = pgtable->cfg.pgsize_bitmap;
dom->pgtable = pgtable;
list_add_tail(&dom->list, &pgtable->m4u_dom);
#if !MTK_IOMMU_PAGE_TABLE_SHARE
dom->data = data;
#endif
#ifdef CONFIG_ARM64
// init mtk_iommu_domain
if (iommu_get_dma_cookie(&dom->domain))
goto free_group;
start = mtk_domain_array[dom->id].min_iova;
end = mtk_domain_array[dom->id].max_iova;
#if (CONFIG_MTK_IOMMU_PGTABLE_EXT > 32)
boundary = mtk_domain_array[dom->id].boundary;
if (start >> 32 != end >> 32 ||
start >> 32 != boundary) {
pr_notice("%s, %d, err start:0x%lx, end:0x%lx, boundary:%d\n",
__func__, __LINE__, start, end, boundary);
goto free_group;
}
#endif
dom->domain.geometry.aperture_start = start;
dom->domain.geometry.aperture_end = end;
dom->domain.geometry.force_aperture = true;
#else
dom->resv_status = 0;
#endif
dom->owner = mtk_domain_array[dom->id].owner;
#ifdef IOMMU_DEBUG_ENABLED
pr_notice("%s, %d, dev:%s allocated IOVA group%d:%p, domain%d:%p owner:%d start:0x%llx end:0x%llx ext=%d\n",
__func__, __LINE__, dev_name(dev),
iommu_group_id(group),
group, dom->id, &dom->domain,
dom->owner,
dom->domain.geometry.aperture_start,
dom->domain.geometry.aperture_end,
CONFIG_MTK_IOMMU_PGTABLE_EXT);
#endif
return group;
free_group:
kfree(group);
free_dom:
kfree(dom);
return NULL;
}
static struct iommu_group *mtk_iommu_device_group(struct device *dev)
{
struct mtk_iommu_data *data = dev->iommu_fwspec->iommu_priv;
struct mtk_iommu_pgtable *pgtable;
int ret = 0;
if (!data)
return NULL;
init_data_id = data->m4uid;
pgtable = data->pgtable;
if (!pgtable) {
ret = mtk_iommu_attach_pgtable(data, dev);
if (ret) {
data->pgtable = NULL;
return NULL;
}
}
return mtk_iommu_create_iova_space(data, dev);
}
#ifdef CONFIG_ARM64
static int mtk_iommu_add_device(struct device *dev)
{
struct mtk_iommu_data *data;
struct iommu_group *group;
if (!dev->iommu_fwspec ||
dev->iommu_fwspec->ops != &mtk_iommu_ops) {
return -ENODEV;
}
data = dev->iommu_fwspec->iommu_priv;
iommu_device_link(&data->iommu, dev);
group = iommu_group_get_for_dev(dev);
if (IS_ERR(group)) {
dev_notice(dev, "%s, %d, invalid group\n", __func__, __LINE__);
return PTR_ERR(group);
}
iommu_group_put(group);
return 0;
}
#else
static int mtk_iommu_add_device(struct device *dev)
{
struct of_phandle_args iommu_spec;
struct mtk_iommu_data *data;
struct iommu_group *group;
int idx = 0, ret = 0;
if (!dev->iommu_fwspec ||
dev->iommu_fwspec->ops != &mtk_iommu_ops) {
return -ENODEV; /* Not a iommu client device */
}
// create group, domain
group = mtk_iommu_device_group(dev);
if (IS_ERR(group)) {
dev_notice(dev, "%s, %d, Failed to allocate M4U IOMMU group\n",
__func__, __LINE__);
return -ENOMEM;
}
// attach the device to domain before access it when create mapping
ret = iommu_group_add_device(group, dev);
iommu_group_put(group);
pr_notice("%s, %d\n", __func__, __LINE__);
if (ret < 0) {
dev_notice(dev, "%s, %d, Failed to add device to IPMMU group\n",
__func__, __LINE__);
iommu_group_remove_device(dev);
group = NULL;
return ret;
}
// create mappings
while (!of_parse_phandle_with_args(dev->of_node, "iommus",
"#iommu-cells", idx,
&iommu_spec)) {
mtk_iommu_create_mapping(dev);
of_node_put(iommu_spec.np);
idx++;
}
if (!idx) {
pr_notice("%s, %d invalid idx:%d\n",
__func__, __LINE__, idx);
return -ENODEV;
}
data = dev->iommu_fwspec->iommu_priv;
iommu_device_link(&data->iommu, dev);
return 0;
}
#endif
static void mtk_iommu_remove_device(struct device *dev)
{
struct mtk_iommu_data *data;
if (!dev->iommu_fwspec || dev->iommu_fwspec->ops != &mtk_iommu_ops)
return;
data = dev->iommu_fwspec->iommu_priv;
iommu_device_unlink(&data->iommu, dev);
iommu_group_remove_device(dev);
iommu_fwspec_free(dev);
}
static int mtk_iommu_of_xlate(struct device *dev, struct of_phandle_args *args)
{
struct platform_device *m4updev;
if (args->args_count != 1) {
dev_err(dev, "invalid #iommu-cells(%d) property for IOMMU\n",
args->args_count);
return -EINVAL;
}
if (!dev->iommu_fwspec->iommu_priv) {
/* Get the m4u device */
m4updev = of_find_device_by_node(args->np);
of_node_put(args->np);
if (!m4updev) {
WARN_ON(1);
return -EINVAL;
}
dev->iommu_fwspec->iommu_priv = platform_get_drvdata(m4updev);
}
return iommu_fwspec_add_ids(dev, args->args, 1);
}
static void mtk_iommu_get_resv_region(
struct device *dev,
struct list_head *list)
{
struct iommu_resv_region *region;
const struct mtk_iova_domain_data *dom_data;
struct mtk_iommu_domain *dom;
unsigned int i;
dom = __mtk_iommu_get_mtk_domain(dev);
if (!dom) {
WARN_ON(1);
return;
}
dom_data = &mtk_domain_array[dom->id];
switch (dom_data->resv_type) {
case IOVA_REGION_REMOVE:
for (i = 0; i < MTK_IOVA_REMOVE_CNT; i++) {
if (!dom_data->resv_size[i])
continue;
region = iommu_alloc_resv_region(
dom_data->resv_start[i],
dom_data->resv_size[i],
0, IOMMU_RESV_RESERVED);
if (!region) {
pr_notice("Out of memory allocating dm-regions for %s\n",
dev_name(dev));
return;
}
list_add_tail(&region->list, list);
}
break;
case IOVA_REGION_STAY:
for (i = 0; i < MTK_IOVA_REMOVE_CNT; i++) {
if (!dom_data->resv_size[i])
continue;
if (dom_data->resv_start[i] != 0) {
region = iommu_alloc_resv_region(0x0,
dom_data->resv_start[i],
0, IOMMU_RESV_RESERVED);
if (!region) {
pr_notice("Out of memory allocating dm-regions for %s\n",
dev_name(dev));
return;
}
list_add_tail(&region->list, list);
}
region = iommu_alloc_resv_region(
dom_data->resv_start[i] +
dom_data->resv_size[i],
DMA_BIT_MASK(32) -
dom_data->resv_start[i] -
dom_data->resv_size[i] + 1,
0, IOMMU_RESV_RESERVED);
if (!region) {
pr_notice("Out of memory allocating dm-regions for %s\n",
dev_name(dev));
return;
}
list_add_tail(&region->list, list);
}
break;
default:
break;
}
}
static void mtk_iommu_put_resv_region(
struct device *dev,
struct list_head *list)
{
struct iommu_resv_region *region, *tmp;
list_for_each_entry_safe(region, tmp, list, list)
kfree(region);
}
/*
* func: get the IOVA space of target device
* dev: user input the target device
* base: return the start addr of IOVA space
* max: return the end addr of IOVA space
* list: return the reserved region of IOVA space
* check the usage of struct iommu_resv_region
*/
int mtk_iommu_get_iova_space(struct device *dev,
unsigned long *base, unsigned long *max,
int *owner, struct list_head *list)
{
struct mtk_iommu_domain *dom;
struct mtk_iommu_pgtable *pgtable = mtk_iommu_get_pgtable(NULL, 0);
unsigned long flags = 0;
if (!pgtable)
pr_notice("%s, invalid pgtable\n", __func__);
dom = __mtk_iommu_get_mtk_domain(dev);
if (dom)
*owner = dom->owner;
else
*owner = -1;
if (pgtable)
spin_lock_irqsave(&pgtable->pgtlock, flags);
iommu_dma_get_iovad_info(dev, base, max);
if (pgtable)
spin_unlock_irqrestore(&pgtable->pgtlock, flags);
if (list)
iommu_get_resv_regions(dev, list);
return mtk_iommu_get_domain_id(dev);
}
/*
* func: free the memory allocated by mtk_iommu_get_iova_space()
* list: user input the reserved region list returned by
* mtk_iommu_get_iova_space()
*/
void mtk_iommu_put_iova_space(struct device *dev,
struct list_head *list)
{
struct iommu_resv_region *region, *tmp;
list_for_each_entry_safe(region, tmp, list, list)
kfree(region);
}
static struct iommu_ops mtk_iommu_ops = {
.domain_alloc = mtk_iommu_domain_alloc,
.domain_free = mtk_iommu_domain_free,
.attach_dev = mtk_iommu_attach_device,
.detach_dev = mtk_iommu_detach_device,
.map = mtk_iommu_map,
.unmap = mtk_iommu_unmap,
.map_sg = default_iommu_map_sg,
.flush_iotlb_all = mtk_iommu_iotlb_flush_all,
.iotlb_range_add = mtk_iommu_iotlb_range_add,
.iotlb_sync = mtk_iommu_iotlb_sync,
.iova_to_phys = mtk_iommu_iova_to_phys,
.add_device = mtk_iommu_add_device,
.remove_device = mtk_iommu_remove_device,
.device_group = mtk_iommu_device_group,
.of_xlate = mtk_iommu_of_xlate,
.pgsize_bitmap = SZ_4K | SZ_64K | SZ_1M | SZ_16M,
.get_resv_regions = mtk_iommu_get_resv_region,
.put_resv_regions = mtk_iommu_put_resv_region,
};
unsigned int mtk_get_main_descriptor(const struct mtk_iommu_data *data,
int m4u_slave_id, int idx)
{
unsigned int regValue = 0;
void __iomem *base = data->base;
u32 tmp = 0;
int ret = 0;
regValue = F_READ_ENTRY_EN
| F_READ_ENTRY_MMx_MAIN(m4u_slave_id)
| F_READ_ENTRY_MAIN_IDX(m4u_slave_id, idx);
writel_relaxed(regValue,
base + REG_MMU_READ_ENTRY);
ret = readl_poll_timeout_atomic(base +
REG_MMU_READ_ENTRY, tmp,
(tmp & F_READ_ENTRY_EN) == 0,
10, 1000);
if (ret) {
dev_notice(data->dev, "iommu:%d polling timeout\n",
data->m4uid);
return 0;
}
return readl_relaxed(base + REG_MMU_DES_RDATA);
}
unsigned int mtk_get_main_tag(const struct mtk_iommu_data *data,
int m4u_slave_id, int idx)
{
void __iomem *base = data->base;
return readl_relaxed(base + REG_MMU_MAIN_TAG(m4u_slave_id, idx));
}
static unsigned long imu_main_tag_to_va(unsigned int tag)
{
unsigned long tmp;
tmp = ((unsigned long)tag & F_MAIN_TLB_VA_MSK) |
(((unsigned long)tag & F_MAIN_TLB_VA_BIT32) << 32);
return tmp;
}
void mtk_get_main_tlb(const struct mtk_iommu_data *data,
int m4u_slave_id, int idx,
struct mmu_tlb_t *pTlb)
{
pTlb->tag = mtk_get_main_tag(data, m4u_slave_id, idx);
pTlb->desc = mtk_get_main_descriptor(data, m4u_slave_id, idx);
}
unsigned int mtk_get_pfh_tlb(const struct mtk_iommu_data *data,
int set, int page, int way, struct mmu_tlb_t *pTlb)
{
unsigned int regValue = 0;
void __iomem *base = data->base;
u32 tmp = 0;
int ret = 0;
regValue = F_READ_ENTRY_EN
| F_READ_ENTRY_PFH
| F_READ_ENTRY_PFH_IDX(set)
| F_READ_ENTRY_PFH_PAGE_IDX(page)
| F_READ_ENTRY_PFH_WAY(way);
writel_relaxed(regValue,
base + REG_MMU_READ_ENTRY);
ret = readl_poll_timeout_atomic(base +
REG_MMU_READ_ENTRY, tmp,
(tmp & F_READ_ENTRY_EN) == 0,
10, 1000);
if (ret) {
dev_notice(data->dev, "iommu:%d polling timeout\n",
data->m4uid);
pTlb->desc = 0;
pTlb->tag = 0;
return 0;
}
pTlb->desc = readl_relaxed(base + REG_MMU_DES_RDATA);
pTlb->tag = readl_relaxed(base + REG_MMU_PFH_TAG_RDATA);
return 0;
}
unsigned int mtk_get_pfh_tag(
const struct mtk_iommu_data *data,
int set, int page, int way)
{
struct mmu_tlb_t tlb;
mtk_get_pfh_tlb(data, set, page, way, &tlb);
return tlb.tag;
}
unsigned int mtk_get_pfh_descriptor(
const struct mtk_iommu_data *data,
int set, int page, int way)
{
struct mmu_tlb_t tlb;
mtk_get_pfh_tlb(data, set, page, way, &tlb);
return tlb.desc;
}
int mtk_dump_main_tlb(int m4u_id, int m4u_slave_id,
struct seq_file *s)
{
/* M4U related */
unsigned int i = 0;
struct mmu_tlb_t tlb;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
unsigned long flags;
int ret;
if (!data)
return 0;
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
mmu_seq_print(s,
"iommu:%d power off\n", m4u_id);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#endif
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
mmu_seq_print(s,
"%s, failed to enable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
mmu_seq_print(s,
"==== main tlb iommu%d mmu%d ====\n",
m4u_id, m4u_slave_id);
for (i = 0; i < g_tag_count[m4u_id]; i++) {
mtk_get_main_tlb(data, m4u_slave_id, i, &tlb);
mmu_seq_print(s,
"%d v:%d va:0x%lx bank%d layer%d sec:%d <0x%x-0x%x>\n",
i, !!(tlb.tag & F_MAIN_TLB_VALID_BIT),
imu_main_tag_to_va(tlb.tag),
F_MAIN_TLB_TABLE_ID_BIT(tlb.tag),
!!(tlb.tag & F_MAIN_TLB_LAYER_BIT),
!!(tlb.tag & F_MAIN_TLB_SEC_BIT),
tlb.tag, tlb.desc);
}
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
mmu_seq_print(s,
"%s, failed to disable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#if 0
int mtk_dump_valid_main0_tlb(
const struct mtk_iommu_data *data, int m4u_slave_id)
{
unsigned int i = 0;
struct mmu_tlb_t tlb;
pr_notice("dump main tlb start %d -- %d\n", data->m4uid, m4u_slave_id);
for (i = 0; i < g_tag_count[data->m4uid]; i++) {
mtk_get_main_tlb(data, m4u_slave_id, i, &tlb);
if ((tlb.tag & F_MAIN_TLB_VALID_BIT) == F_MAIN_TLB_VALID_BIT)
pr_info("%d:0x%x:0x%x\n", i, tlb.tag, tlb.desc);
}
pr_notice("dump inv main tlb end\n");
return 0;
}
#endif
int dump_fault_mva_pfh_tlb(const struct mtk_iommu_data *data, unsigned int mva)
{
int set;
int way, page, valid;
struct mmu_tlb_t tlb;
unsigned int regval;
void __iomem *base = data->base;
set = (mva >> 15) & 0x7f;
for (way = 0; way < MTK_IOMMU_WAY_NR; way++) {
for (page = 0; page < MMU_PAGE_PER_LINE; page++) {
regval = readl_relaxed(base +
REG_MMU_PFH_VLD(set, way));
valid = !!(regval & F_MMU_PFH_VLD_BIT(set, way));
mtk_get_pfh_tlb(data, set, page, way, &tlb);
pr_notice(
"fault_mva:0x%x, way:%d, set:%d, page:%d, valid:%d--0x%x, tag:0x%x, des:0x%x\n",
mva, way, set, page, valid,
regval, tlb.tag, tlb.desc);
}
}
return 0;
}
static unsigned long imu_pfh_tag_to_va(int mmu,
int set, int way, unsigned int tag)
{
unsigned long tmp;
tmp = F_PFH_TAG_VA_GET(mmu, tag);
if (tag & F_PFH_TAG_LAYER_BIT)
tmp |= ((set) << 15);
else {
//tmp &= F_MMU_PFH_TAG_VA_LAYER0_MSK(mmu);
tmp |= (set) << 23;
}
return tmp;
}
int mtk_dump_pfh_tlb(int m4u_id,
struct seq_file *s)
{
unsigned int regval;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
int result = 0;
int set_nr, way_nr, set, way;
int valid;
unsigned long flags;
int ret;
if (!data)
return 0;
base = data->base;
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
mmu_seq_print(s,
"iommu:%d power off\n", m4u_id);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#endif
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
mmu_seq_print(s,
"%s, failed to enable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
set_nr = MTK_IOMMU_SET_NR(m4u_id);
way_nr = MTK_IOMMU_WAY_NR;
mmu_seq_print(s,
"==== prefetch tlb iommu%d ====\n", m4u_id);
for (way = 0; way < way_nr; way++) {
for (set = 0; set < set_nr; set++) {
int page;
struct mmu_tlb_t tlb;
regval = readl_relaxed(base +
REG_MMU_PFH_VLD(set, way));
valid = !!(regval & F_MMU_PFH_VLD_BIT(set, way));
mtk_get_pfh_tlb(data, set, 0, way, &tlb);
mmu_seq_print(s,
"%d-%d v:%d va:0x%lx layer%d bank%d sec:%d pfh:%d tag:0x%x <0x%x ",
way, set, valid,
imu_pfh_tag_to_va(m4u_id, set, way, tlb.tag),
!!(tlb.tag & F_PFH_TAG_LAYER_BIT),
(tlb.tag & F_PFH_PT_BANK_BIT),
!!(tlb.tag & F_PFH_TAG_SEC_BIT),
!!(tlb.tag & F_PFH_TAG_AUTO_PFH),
tlb.tag, tlb.desc);
for (page = 1; page < MMU_PAGE_PER_LINE; page++) {
mtk_get_pfh_tlb(data, set, page, way, &tlb);
mmu_seq_print(s, "0x%x ", tlb.desc);
}
mmu_seq_print(s, ">\n");
}
}
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
mmu_seq_print(s,
"%s, failed to disable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return result;
}
#if 0
int mtk_get_pfh_tlb_all(const struct mtk_iommu_data *data,
struct mmu_pfh_tlb_t *pfh_buf)
{
unsigned int regval, m4u_id = data->m4uid;
void __iomem *base = data->base;
int set_nr, way_nr, set, way;
int valid;
int pfh_id = 0;
set_nr = MTK_IOMMU_SET_NR(m4u_id);
way_nr = MTK_IOMMU_WAY_NR;
for (way = 0; way < way_nr; way++) {
for (set = 0; set < set_nr; set++) {
int page;
struct mmu_tlb_t tlb;
regval = readl_relaxed(base +
REG_MMU_PFH_VLD(set, way));
valid = !!(regval & F_MMU_PFH_VLD_BIT(set, way));
mtk_get_pfh_tlb(data, set, 0, way, &tlb);
pfh_buf[pfh_id].tag = tlb.tag;
pfh_buf[pfh_id].va =
imu_pfh_tag_to_va(m4u_id,
set, way, tlb.tag);
pfh_buf[pfh_id].layer =
!!(tlb.tag & F_PFH_TAG_LAYER_BIT);
pfh_buf[pfh_id].bank = !!(tlb.tag & F_PFH_PT_BANK_BIT);
pfh_buf[pfh_id].sec = !!(tlb.tag & F_PFH_TAG_SEC_BIT);
pfh_buf[pfh_id].pfh = !!(tlb.tag & F_PFH_TAG_AUTO_PFH);
pfh_buf[pfh_id].set = set;
pfh_buf[pfh_id].way = way;
pfh_buf[pfh_id].valid = valid;
pfh_buf[pfh_id].desc[0] = tlb.desc;
pfh_buf[pfh_id].page_size =
pfh_buf[pfh_id].layer ?
SZ_4K : SZ_1M;
for (page = 1; page < MMU_PAGE_PER_LINE; page++) {
mtk_get_pfh_tlb(data, set, page, way, &tlb);
pfh_buf[pfh_id].desc[page] = tlb.desc;
}
pfh_id++;
}
}
return 0;
}
#endif
unsigned int mtk_get_victim_tlb(const struct mtk_iommu_data *data, int page,
int entry, struct mmu_tlb_t *pTlb)
{
unsigned int regValue = 0;
void __iomem *base = data->base;
u32 tmp = 0;
int ret = 0;
regValue = F_READ_ENTRY_EN
| F_READ_ENTRY_VICT_TLB_SEL
#if (MMU_ENTRY_PER_VICTIM == 16)
| F_READ_ENTRY_PFH_IDX((entry & 0xc) >> 2)
#endif
| F_READ_ENTRY_PFH_PAGE_IDX(page)
| F_READ_ENTRY_PFH_WAY(entry & 0x3);
writel_relaxed(regValue,
base + REG_MMU_READ_ENTRY);
ret = readl_poll_timeout_atomic(base +
REG_MMU_READ_ENTRY, tmp,
(tmp & F_READ_ENTRY_EN) == 0,
10, 1000);
if (ret) {
dev_notice(data->dev, "iommu:%d polling timeout\n",
data->m4uid);
pTlb->desc = 0;
pTlb->tag = 0;
return 0;
}
pTlb->desc = readl_relaxed(base + REG_MMU_DES_RDATA);
pTlb->tag = readl_relaxed(base + REG_MMU_PFH_TAG_RDATA);
return 0;
}
static unsigned long imu_victim_tag_to_va(int mmu, unsigned int tag)
{
unsigned long tmp;
tmp = F_PFH_TAG_VA_GET(mmu, tag);
if (tag & F_PFH_TAG_LAYER_BIT)
tmp |= F_VIC_TAG_VA_GET_L1(mmu, tag);
return tmp;
}
int mtk_dump_victim_tlb(int m4u_id,
struct seq_file *s)
{
unsigned int regval;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
int result = 0;
int entry, entry_nr;
int valid;
unsigned long flags;
int ret;
if (!data)
return 0;
base = data->base;
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
mmu_seq_print(s,
"iommu:%d power off\n", m4u_id);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#endif
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
mmu_seq_print(s,
"%s, failed to enable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
entry_nr = MMU_ENTRY_PER_VICTIM;
mmu_seq_print(s,
"==== victim tlb iommu%d ====\n", m4u_id);
for (entry = 0; entry < entry_nr; entry++) {
int page;
struct mmu_tlb_t tlb;
regval = readl_relaxed(base + REG_MMU_VICT_VLD);
valid = !!(regval & F_MMU_VICT_VLD_BIT(entry));
mtk_get_victim_tlb(data, 0, entry, &tlb);
mmu_seq_print(s,
"%d v:%d va:0x%lx layer%d bank%d sec:%d pfh:%d tag:0x%x <0x%x ",
entry, valid,
imu_victim_tag_to_va(m4u_id, tlb.tag),
!!(tlb.tag & F_PFH_TAG_LAYER_BIT),
(tlb.tag & F_PFH_PT_BANK_BIT),
!!(tlb.tag & F_PFH_TAG_SEC_BIT),
!!(tlb.tag & F_PFH_TAG_AUTO_PFH),
tlb.tag, tlb.desc);
for (page = 1; page < MMU_PAGE_PER_LINE; page++) {
mtk_get_victim_tlb(data, page, entry, &tlb);
mmu_seq_print(s, "0x%x ", tlb.desc);
}
mmu_seq_print(s, ">\n");
}
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
mmu_seq_print(s,
"%s, failed to disable secure debug signal\n",
__func__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return result;
}
#if 0
int mtk_confirm_main_range_invalidated(
const struct mtk_iommu_data *data,
int m4u_slave_id, unsigned int iova_s,
unsigned int iova_e)
{
unsigned int i;
unsigned int regval;
/* /> check Main TLB part */
for (i = 0; i < g_tag_count[data->m4uid]; i++) {
regval = mtk_get_main_tag(data, m4u_slave_id, i);
if (regval & (F_MAIN_TLB_VALID_BIT)) {
unsigned int tag_s, tag_e, sa, ea;
int layer = regval & F_MAIN_TLB_LAYER_BIT;
int large = regval & F_MAIN_TLB_16X_BIT;
tag_s = regval & F_MAIN_TLB_VA_MSK;
sa = iova_s & (~(PAGE_SIZE - 1));
ea = iova_e | (PAGE_SIZE - 1);
if (layer) { /* pte */
if (large)
tag_e = tag_s + SZ_64K - 1;
else
tag_e = tag_s + PAGE_SIZE - 1;
if (!((tag_e < sa) || (tag_s > ea))) {
pr_notice(
"main: i=%d, idx=0x%x, iova_s=0x%x, iova_e=0x%x, RegValue=0x%x\n",
i, data->m4uid, iova_s,
iova_e, regval);
return -1;
}
} else {
if (large)
tag_e =
tag_s +
SZ_16M -
1;
else
tag_e =
tag_s +
SZ_1M -
1;
if ((tag_s >= sa) && (tag_e <= ea)) {
pr_notice(
"main: i=%d, idx=0x%x, iova_s=0x%x, iova_e=0x%x, RegValue=0x%x\n",
i, data->m4uid,
iova_s, iova_e, regval);
return -1;
}
}
}
}
return 0;
}
int mtk_confirm_range_invalidated(const struct mtk_iommu_data *data,
unsigned int iova_s, unsigned int iova_e)
{
unsigned int i = 0;
unsigned int regval;
void __iomem *base = data->base;
int result = 0;
int set_nr, way_nr, set, way;
/* /> check Main TLB part */
result =
mtk_confirm_main_range_invalidated(
data, 0, iova_s, iova_e);
if (result < 0)
return -1;
if (data->m4uid == 0) {
result =
mtk_confirm_main_range_invalidated(
data, 1, iova_s, iova_e);
if (result < 0)
return -1;
}
set_nr = MTK_IOMMU_SET_NR(data->m4uid);
way_nr = MTK_IOMMU_WAY_NR;
for (way = 0; way < way_nr; way++) {
for (set = 0; set < set_nr; set++) {
regval =
readl_relaxed(base +
REG_MMU_PFH_VLD(set, way));
if (regval & F_MMU_PFH_VLD_BIT(set, way)) {
unsigned int tag =
mtk_get_pfh_tag(data,
set, 0, way);
unsigned int tag_s, tag_e, sa, ea;
int layer = tag & F_PFH_TAG_LAYER_BIT;
int large = tag & F_PFH_TAG_16X_BIT;
tag_s = imu_pfh_tag_to_va(data->m4uid,
set, way, tag);
sa = iova_s & (~(PAGE_SIZE - 1));
ea = iova_e | (PAGE_SIZE - 1);
if (layer) { /* pte */
if (large)
tag_e =
tag_s +
SZ_64K * 8
- 1;
else
tag_e =
tag_s +
PAGE_SIZE * 8 - 1;
if (!((tag_e < sa) || (tag_s > ea))) {
pr_notice(
"main: i=%d, idx=0x%x, iova_s=0x%x, iova_e=0x%x, RegValue=0x%x\n",
i, data->m4uid,
iova_s,
iova_e, regval);
return -1;
}
} else {
if (large)
tag_e =
tag_s +
SZ_16M * 8
- 1;
else
tag_e =
tag_s +
SZ_1M * 8 -
1;
/* if((tag_s>=sa)&&(tag_e<=ea)) */
if (!((tag_e < sa) || (tag_s > ea))) {
pr_notice(
"main: i=%d, idx=0x%x, iova_s=0x%x, iova_e=0x%x, RegValue=0x%x\n",
i, data->m4uid,
iova_s,
iova_e, regval);
return -1;
}
}
}
}
}
return result;
}
int mtk_confirm_main_all_invalid(
const struct mtk_iommu_data *data, int m4u_slave_id)
{
unsigned int i;
unsigned int regval;
for (i = 0; i < g_tag_count[data->m4uid]; i++) {
regval = mtk_get_main_tag(data, m4u_slave_id, i);
if (regval & (F_MAIN_TLB_VALID_BIT)) {
pr_notice(
"main: i=%d, idx=0x%x, RegValue=0x%x\n",
i, data->m4uid, regval);
return -1;
}
}
return 0;
}
int mtk_confirm_pfh_all_invalid(const struct mtk_iommu_data *data)
{
unsigned int regval;
void __iomem *base = data->base;
int set_nr, way_nr, set, way;
set_nr = MTK_IOMMU_SET_NR(data->m4uid);
way_nr = MTK_IOMMU_WAY_NR;
for (way = 0; way < way_nr; way++) {
for (set = 0; set < set_nr; set++) {
regval = readl_relaxed(base +
REG_MMU_PFH_VLD(set, way));
if (regval & F_MMU_PFH_VLD_BIT(set, way))
return -1;
}
}
return 0;
}
int mtk_confirm_all_invalidated(int m4u_id)
{
const struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
if (!data)
return 0;
if (mtk_confirm_main_all_invalid(data, 0))
return -1;
if (m4u_id == 0) {
if (mtk_confirm_main_all_invalid(data, 1))
return -1;
}
if (mtk_confirm_pfh_all_invalid(data))
return -1;
return 0;
}
#endif
int mau_start_monitor(unsigned int m4u_id, unsigned int slave,
unsigned int mau, struct mau_config_info *mau_info)
{
void __iomem *base;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
int ret = 0;
unsigned long flags;
if (!data || !mau_info ||
slave >= MTK_MMU_NUM_OF_IOMMU(m4u_id) ||
mau >= MTK_MAU_NUM_OF_MMU(slave)) {
pr_notice("%s, %d, invalid m4u:%d, slave:%d, mau:%d\n",
__func__, __LINE__, m4u_id, slave, mau);
return -1;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s, iommu%u power off\n",
__func__, data->m4uid);
return 0;
}
#endif
base = data->base;
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s, %d, failed to enable secure debug signal\n",
__func__, __LINE__);
return ret;
}
/*enable interrupt*/
iommu_set_field_by_mask(base,
REG_MMU_INT_MAIN_CONTROL,
F_INT_MAIN_MAU_INT_EN(slave),
F_INT_MAIN_MAU_INT_EN(slave));
/*config start addr*/
writel_relaxed(mau_info->start, base +
REG_MMU_MAU_SA(slave, mau));
writel_relaxed(mau_info->start_bit32, base +
REG_MMU_MAU_SA_EXT(slave, mau));
/*config end addr*/
writel_relaxed(mau_info->end, base +
REG_MMU_MAU_EA(slave, mau));
writel_relaxed(mau_info->end_bit32, base +
REG_MMU_MAU_EA_EXT(slave, mau));
/*config larb id*/
writel_relaxed(mau_info->larb_mask, base +
REG_MMU_MAU_LARB_EN(slave));
/*config port id*/
writel_relaxed(mau_info->port_mask, base +
REG_MMU_MAU_PORT_EN(slave, mau));
iommu_set_field_by_mask(base, REG_MMU_MAU_IO(slave),
F_MAU_BIT_VAL(1, mau),
F_MAU_BIT_VAL(mau_info->io, mau));
iommu_set_field_by_mask(base, REG_MMU_MAU_RW(slave),
F_MAU_BIT_VAL(1, mau),
F_MAU_BIT_VAL(mau_info->wr, mau));
iommu_set_field_by_mask(base, REG_MMU_MAU_VA(slave),
F_MAU_BIT_VAL(1, mau),
F_MAU_BIT_VAL(mau_info->virt, mau));
wmb(); /*make sure the MAU ops has been triggered*/
pr_notice("%s iommu:%d, slave:%d, mau:%d, start=0x%x(0x%x), end=0x%x(0x%x), vir:%d, wr:%d, io:0x%x, port:0x%x, larb:0x%x\n",
__func__, m4u_id, slave, mau,
readl_relaxed(base + REG_MMU_MAU_SA(slave, mau)),
readl_relaxed(base + REG_MMU_MAU_SA_EXT(slave, mau)),
readl_relaxed(base + REG_MMU_MAU_EA(slave, mau)),
readl_relaxed(base + REG_MMU_MAU_EA_EXT(slave, mau)),
readl_relaxed(base + REG_MMU_MAU_VA(slave)),
readl_relaxed(base + REG_MMU_MAU_RW(slave)),
readl_relaxed(base + REG_MMU_MAU_IO(slave)),
readl_relaxed(base + REG_MMU_MAU_PORT_EN(slave, mau)),
readl_relaxed(base + REG_MMU_MAU_LARB_EN(slave)));
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
pr_notice("%s, %d, failed to disable secure debug signal\n",
__func__, __LINE__);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
void mau_stop_monitor(unsigned int m4u_id, unsigned int slave,
unsigned int mau, bool force)
{
unsigned int irq = 0;
void __iomem *base;
const struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
struct mau_config_info *mau_cfg = get_mau_info(m4u_id);
if (force) {
if (!data)
return;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
pr_notice("%s, iommu%u power off\n",
__func__, data->m4uid);
return;
}
#endif
base = data->base;
irq = readl_relaxed(base +
REG_MMU_INT_MAIN_CONTROL);
irq = irq & ~F_INT_MAIN_MAU_INT_EN(slave);
writel_relaxed(irq, base +
REG_MMU_INT_MAIN_CONTROL);
return;
}
mau_start_monitor(m4u_id, slave, mau, mau_cfg);
}
/* notes: must fill cfg->m4u_id/slave/mau before call this func. */
int mau_get_config_info(struct mau_config_info *cfg)
{
int slave = cfg->slave;
int mau = cfg->mau;
void __iomem *base;
const struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(cfg->m4u_id);
if (!data ||
slave >= MTK_MMU_NUM_OF_IOMMU(cfg->m4u_id) ||
mau >= MTK_MAU_NUM_OF_MMU(slave)) {
pr_notice("%s, %d, invalid m4u:%d, slave:%d, mau:%d\n",
__func__, __LINE__, cfg->m4u_id, slave, mau);
return -1;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
base = data->base;
cfg->start = readl_relaxed(base +
REG_MMU_MAU_SA(slave, mau));
cfg->end = readl_relaxed(base +
REG_MMU_MAU_EA(slave, mau));
cfg->start_bit32 = readl_relaxed(base +
REG_MMU_MAU_SA_EXT(slave, mau));
cfg->end_bit32 = readl_relaxed(base +
REG_MMU_MAU_EA_EXT(slave, mau));
cfg->port_mask = readl_relaxed(base +
REG_MMU_MAU_PORT_EN(slave, mau));
cfg->larb_mask = readl_relaxed(base +
REG_MMU_MAU_LARB_EN(slave));
cfg->io =
!!(iommu_get_field_by_mask(base,
REG_MMU_MAU_IO(slave),
F_MAU_BIT_VAL(1, mau)));
cfg->wr =
!!iommu_get_field_by_mask(base,
REG_MMU_MAU_RW(slave),
F_MAU_BIT_VAL(1, mau));
cfg->virt =
!!iommu_get_field_by_mask(base,
REG_MMU_MAU_VA(slave),
F_MAU_BIT_VAL(1, mau));
return 0;
}
int __mau_dump_status(int m4u_id, int slave, int mau)
{
void __iomem *base;
unsigned int status;
unsigned long flags;
unsigned int assert_id, assert_addr, assert_b32;
char *name;
struct mau_config_info mau_cfg;
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
if (!data ||
slave >= MTK_MMU_NUM_OF_IOMMU(m4u_id) ||
mau >= MTK_MAU_NUM_OF_MMU(slave)) {
pr_notice("%s, %d, invalid m4u:%d, slave:%d, mau:%d\n",
__func__, __LINE__, m4u_id, slave, mau);
return -1;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
#endif
base = data->base;
status = readl_relaxed(base + REG_MMU_MAU_ASRT_STA(slave));
if (status & (1 << mau)) {
pr_notice("%s: mau_assert in set %d, status:0x%x\n",
__func__, mau, status);
assert_id = readl_relaxed(base +
REG_MMU_MAU_ASRT_ID(slave, mau));
assert_addr = readl_relaxed(base +
REG_MMU_MAU_ADDR(slave, mau));
assert_b32 = readl_relaxed(base +
REG_MMU_MAU_ADDR_BIT32(slave, mau));
//larb = F_MMU_MAU_ASRT_ID_LARB(assert_id);
//port = F_MMU_MAU_ASRT_ID_PORT(assert_id);
name = mtk_iommu_get_port_name(m4u_id,
(assert_id & F_MMU_MAU_ASRT_ID_VAL) << 2);
pr_notice("%s: mau dump: id=0x%x(%s),addr=0x%x,b32=0x%x\n",
__func__, assert_id, name,
assert_addr, assert_b32);
writel_relaxed((1 << mau), base +
REG_MMU_MAU_CLR(slave));
writel_relaxed(0, base + REG_MMU_MAU_CLR(slave));
wmb(); /*make sure the MAU data is cleared*/
mau_cfg.m4u_id = m4u_id;
mau_cfg.slave = slave;
mau_cfg.mau = mau;
mau_get_config_info(&mau_cfg);
pr_notice(
"%s: mau_cfg: start=0x%x,end=0x%x,virt(%d),io(%d),wr(%d),s_b32(%d),e_b32(%d),larb(0x%x),port(0x%x)\n",
__func__,
mau_cfg.start, mau_cfg.end,
mau_cfg.virt, mau_cfg.io,
mau_cfg.wr,
mau_cfg.start_bit32, mau_cfg.end_bit32,
mau_cfg.larb_mask, mau_cfg.port_mask);
} else
pr_debug("%s: mau no assert in set %d\n",
__func__, mau);
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
int iommu_perf_get_counter(int m4u_id,
int slave, struct IOMMU_PERF_COUNT *p_perf_count)
{
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
int ret = 0;
unsigned long flags;
if (!data ||
slave >= MTK_MMU_NUM_OF_IOMMU(m4u_id)) {
pr_notice("%s, %d, invalid m4u:%d, slave:%d\n",
__func__, __LINE__, m4u_id, slave);
return -1;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s: iommu:%d power off\n",
__func__, m4u_id);
return -2;
}
#endif
base = data->base;
ret = mtk_switch_secure_debug_func(m4u_id, 1);
if (ret) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s, %d, failed to enable secure debug signal\n",
__func__, __LINE__);
return ret;
}
/* Transaction access count */
p_perf_count->transaction_cnt =
readl_relaxed(base + REG_MMU_ACC_CNT(slave));
/* Main TLB miss count */
p_perf_count->main_tlb_miss_cnt =
readl_relaxed(base + REG_MMU_MAIN_L1_MSCNT(slave));
p_perf_count->main_tlb_miss_cnt +=
readl_relaxed(base + REG_MMU_MAIN_L2_MSCNT(slave));
/* /> Prefetch TLB miss count */
p_perf_count->pfh_tlb_miss_cnt =
readl_relaxed(base + REG_MMU_PF_L1_MSCNT);
p_perf_count->pfh_tlb_miss_cnt +=
readl_relaxed(base + REG_MMU_PF_L2_MSCNT);
/* /> Prefetch count */
p_perf_count->pfh_cnt =
readl_relaxed(base + REG_MMU_PF_L1_CNT);
p_perf_count->pfh_cnt +=
readl_relaxed(base + REG_MMU_PF_L2_CNT);
p_perf_count->rs_perf_cnt =
readl_relaxed(base + REG_MMU_RS_PERF_CNT(slave));
spin_unlock_irqrestore(&data->reg_lock, flags);
ret = mtk_switch_secure_debug_func(m4u_id, 0);
if (ret)
pr_notice("%s, %d, failed to disable secure debug signal\n",
__func__, __LINE__);
return 0;
}
void iommu_perf_print_counter(int m4u_id, int slave, const char *msg)
{
struct IOMMU_PERF_COUNT cnt;
int ret = 0;
pr_info(
"==== performance count for %s iommu:%d, slave:%d======\n",
msg, m4u_id, slave);
ret = iommu_perf_get_counter(m4u_id, slave, &cnt);
if (!ret)
pr_info(
">>> total trans=%u, main_miss=%u, pfh_miss=%u, pfh_cnt=%u, rs_perf_cnt=%u\n",
cnt.transaction_cnt, cnt.main_tlb_miss_cnt,
cnt.pfh_tlb_miss_cnt, cnt.pfh_cnt,
cnt.rs_perf_cnt);
else
pr_info("failed to get performance data, ret:%d\n", ret);
}
int iommu_perf_monitor_start(int m4u_id)
{
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
unsigned long flags;
if (!data) {
pr_notice("%s, %d, invalid m4u:%d\n",
__func__, __LINE__, m4u_id);
return -1;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s: iommu:%d power off\n",
__func__, m4u_id);
return 0;
}
#endif
base = data->base;
pr_info("====%s: %d======\n", __func__, m4u_id);
/* clear GMC performance counter */
iommu_set_field_by_mask(base, REG_MMU_CTRL_REG,
F_MMU_CTRL_MONITOR_CLR(1),
F_MMU_CTRL_MONITOR_CLR(1));
iommu_set_field_by_mask(base, REG_MMU_CTRL_REG,
F_MMU_CTRL_MONITOR_CLR(1),
F_MMU_CTRL_MONITOR_CLR(0));
/* enable GMC performance monitor */
iommu_set_field_by_mask(base, REG_MMU_CTRL_REG,
F_MMU_CTRL_MONITOR_EN(1),
F_MMU_CTRL_MONITOR_EN(1));
spin_unlock_irqrestore(&data->reg_lock, flags);
return 0;
}
int iommu_perf_monitor_stop(int m4u_id)
{
struct mtk_iommu_data *data = mtk_iommu_get_m4u_data(m4u_id);
void __iomem *base;
unsigned int i;
unsigned long flags;
if (!data) {
pr_notice("%s, %d, invalid m4u:%d\n",
__func__, __LINE__, m4u_id);
return -1;
}
spin_lock_irqsave(&data->reg_lock, flags);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
spin_unlock_irqrestore(&data->reg_lock, flags);
pr_notice("%s: iommu:%d power off\n",
__func__, m4u_id);
return 0;
}
#endif
base = data->base;
pr_info("====%s: %d======\n", __func__, m4u_id);
/* disable GMC performance monitor */
iommu_set_field_by_mask(base, REG_MMU_CTRL_REG,
F_MMU_CTRL_MONITOR_EN(1),
F_MMU_CTRL_MONITOR_EN(0));
spin_unlock_irqrestore(&data->reg_lock, flags);
for (i = 0; i < MTK_IOMMU_MMU_COUNT; i++)
iommu_perf_print_counter(m4u_id, i, __func__);
return 0;
}
#define IOMMU_REG_BACKUP_SIZE (100 * sizeof(unsigned int))
static unsigned int *p_reg_backup[MTK_IOMMU_M4U_COUNT];
static unsigned int g_reg_backup_real_size[MTK_IOMMU_M4U_COUNT];
static int mau_reg_backup(const struct mtk_iommu_data *data)
{
unsigned int *p_reg;
void __iomem *base = data->base;
int slave;
int mau;
unsigned int real_size;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
if (!p_reg_backup[data->m4uid]) {
pr_notice("%s, %d, iommu:%d no memory for backup\n",
__func__, __LINE__, data->m4uid);
return -1;
}
p_reg = p_reg_backup[data->m4uid];
for (slave = 0; slave < MTK_MMU_NUM_OF_IOMMU(data->m4uid); slave++) {
for (mau = 0; mau < MTK_MAU_NUM_OF_MMU(slave); mau++) {
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_SA(slave, mau));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_SA_EXT(slave, mau));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_EA(slave, mau));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_EA_EXT(slave, mau));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_PORT_EN(slave, mau));
}
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_LARB_EN(slave));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_IO(slave));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_RW(slave));
*(p_reg++) = readl_relaxed(base +
REG_MMU_MAU_VA(slave));
}
/* check register size (to prevent overflow) */
real_size = (p_reg - p_reg_backup[data->m4uid]) * sizeof(unsigned int);
if (real_size > IOMMU_REG_BACKUP_SIZE)
mmu_aee_print("m4u_reg overflow! %d>%d\n",
real_size, (int)IOMMU_REG_BACKUP_SIZE);
g_reg_backup_real_size[data->m4uid] = real_size;
return 0;
}
static int mau_reg_restore(const struct mtk_iommu_data *data)
{
unsigned int *p_reg;
void __iomem *base = data->base;
int slave;
int mau;
unsigned int real_size;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
if (!p_reg_backup[data->m4uid]) {
pr_notice("%s, %d, iommu:%d no memory for restore\n",
__func__, __LINE__, data->m4uid);
return -1;
}
p_reg = p_reg_backup[data->m4uid];
for (slave = 0; slave < MTK_MMU_NUM_OF_IOMMU(data->m4uid); slave++) {
for (mau = 0; mau < MTK_MAU_NUM_OF_MMU(slave); mau++) {
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_SA(slave, mau));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_SA_EXT(slave, mau));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_EA(slave, mau));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_EA_EXT(slave, mau));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_PORT_EN(slave, mau));
}
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_LARB_EN(slave));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_IO(slave));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_RW(slave));
writel_relaxed(*(p_reg++), base +
REG_MMU_MAU_VA(slave));
}
wmb(); /*make sure the MVA data is restored*/
/* check register size (to prevent overflow) */
real_size = (p_reg - p_reg_backup[data->m4uid]) * sizeof(unsigned int);
if (real_size != g_reg_backup_real_size[data->m4uid])
mmu_aee_print("m4u_reg_retore %d!=%d\n",
real_size,
g_reg_backup_real_size[data->m4uid]);
return 0;
}
static int mtk_iommu_reg_backup(struct mtk_iommu_data *data)
{
struct mtk_iommu_suspend_reg *reg = &data->reg;
void __iomem *base = data->base;
int ret = 0;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
if (!base || IS_ERR((void *)(unsigned long)base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
return -1;
}
ret = mtk_switch_secure_debug_func(data->m4uid, 1);
if (ret)
pr_notice("%s, %d, m4u:%u, failed to enable secure debug signal\n",
__func__, __LINE__, data->m4uid);
reg->standard_axi_mode = readl_relaxed(base +
REG_MMU_MISC_CTRL);
reg->dcm_dis = readl_relaxed(base +
REG_MMU_DCM_DIS);
reg->ctrl_reg = readl_relaxed(base +
REG_MMU_CTRL_REG);
reg->int_control0 = readl_relaxed(base +
REG_MMU_INT_CONTROL0);
reg->int_main_control = readl_relaxed(base +
REG_MMU_INT_MAIN_CONTROL);
reg->pt_base = readl_relaxed(base +
REG_MMU_PT_BASE_ADDR);
reg->wr_ctrl = readl_relaxed(base +
REG_MMU_WR_LEN_CTRL);
reg->ivrp_paddr = readl_relaxed(base +
REG_MMU_TFRP_PADDR);
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_BACKUP,
data->m4uid, MTK_IOMMU_BANK_COUNT);
#elif defined(MTK_M4U_SECURE_IRQ_SUPPORT)
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_BACKUP,
data->m4uid, 4);
#endif
mau_reg_backup(data);
ret = mtk_switch_secure_debug_func(data->m4uid, 0);
if (ret)
pr_notice("%s, %d, m4u:%u, failed to disable secure debug signal\n",
__func__, __LINE__, data->m4uid);
return 0;
}
static int mtk_iommu_reg_restore(struct mtk_iommu_data *data)
{
struct mtk_iommu_suspend_reg *reg = &data->reg;
void __iomem *base = data->base;
int ret = 0;
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron)
return 0;
#endif
if (!base || IS_ERR(base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
return -1;
}
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_RESTORE,
data->m4uid, MTK_IOMMU_BANK_COUNT);
#elif defined(MTK_M4U_SECURE_IRQ_SUPPORT)
ret = mtk_iommu_atf_call(IOMMU_ATF_SECURITY_RESTORE,
data->m4uid, 4);
#endif
ret = mtk_switch_secure_debug_func(data->m4uid, 1);
if (ret)
pr_notice("%s, %d, m4u:%u, failed to enable secure debug signal\n",
__func__, __LINE__, data->m4uid);
mau_reg_restore(data);
writel_relaxed(reg->standard_axi_mode, base +
REG_MMU_MISC_CTRL);
writel_relaxed(reg->dcm_dis, base +
REG_MMU_DCM_DIS);
writel_relaxed(reg->ctrl_reg, base +
REG_MMU_CTRL_REG);
writel_relaxed(reg->int_control0, base +
REG_MMU_INT_CONTROL0);
writel_relaxed(reg->int_main_control, base +
REG_MMU_INT_MAIN_CONTROL);
writel_relaxed(reg->pt_base, base +
REG_MMU_PT_BASE_ADDR);
writel_relaxed(reg->wr_ctrl, base +
REG_MMU_WR_LEN_CTRL);
writel_relaxed(reg->ivrp_paddr, base +
REG_MMU_TFRP_PADDR);
wmb(); /*make sure the registers have been restored.*/
ret = mtk_switch_secure_debug_func(data->m4uid, 0);
if (ret)
pr_notice("%s, %d, m4u:%u, failed to disable secure debug signal\n",
__func__, __LINE__, data->m4uid);
return 0;
}
#ifdef MTK_IOMMU_LOW_POWER_SUPPORT
static void mtk_iommu_pg_after_on(enum subsys_id sys)
{
struct mtk_iommu_data *data;
int ret = 0, i;
unsigned long flags;
for (i = 0; i < MTK_IOMMU_M4U_COUNT; i++) {
if (iommu_mtcmos_subsys[i] != sys)
continue;
data = mtk_iommu_get_m4u_data(i);
if (!data) {
pr_notice("%s, %d iommu %d is null\n",
__func__, __LINE__, i);
continue;
}
if (!data->m4u_clks->nr_powers) {
pr_notice("%s, iommu%u power control is not support\n",
__func__, data->m4uid);
continue;
}
spin_lock_irqsave(&data->reg_lock, flags);
if (data->poweron) {
pr_notice("%s, iommu%u already power on, skip restore\n",
__func__, data->m4uid);
spin_unlock_irqrestore(&data->reg_lock, flags);
continue;
}
data->poweron = true;
ret = mtk_iommu_reg_restore(data);
if (ret) {
pr_notice("%s, %d, iommu:%d, sys:%d restore failed %d\n",
__func__, __LINE__, data->m4uid, sys, ret);
data->poweron = false;
spin_unlock_irqrestore(&data->reg_lock, flags);
continue;
}
spin_unlock_irqrestore(&data->reg_lock, flags);
/*pr_notice("%s,%d,iommu:%d,sys:%d restore after on\n",
* __func__, __LINE__, data->m4uid, sys);
*/
}
}
static void mtk_iommu_pg_before_off(enum subsys_id sys)
{
struct mtk_iommu_data *data;
int ret = 0, i;
unsigned long flags;
unsigned long long start = 0, end = 0;
for (i = 0; i < MTK_IOMMU_M4U_COUNT; i++) {
if (iommu_mtcmos_subsys[i] != sys)
continue;
data = mtk_iommu_get_m4u_data(i);
if (!data) {
pr_notice("%s, %d iommu %d is null\n",
__func__, __LINE__, i);
continue;
}
spin_lock_irqsave(&data->reg_lock, flags);
if (!data->poweron) {
pr_notice("%s, iommu%u already power off, skip backup\n",
__func__, data->m4uid);
spin_unlock_irqrestore(&data->reg_lock, flags);
continue;
}
if (data->isr_ref) {
spin_unlock_irqrestore(&data->reg_lock, flags);
start = sched_clock();
/* waiting for irs handling done */
while (data->isr_ref) {
end = sched_clock();
if (end - start > 1000000000ULL) { //10ms
break;
}
}
if (end)
pr_notice("%s pg waiting isr:%lluns, ref:%d\n",
__func__, end - start, data->isr_ref);
spin_lock_irqsave(&data->reg_lock, flags);
}
ret = mtk_iommu_reg_backup(data);
if (ret) {
pr_notice("%s, %d, iommu:%d, sys:%d backup failed %d\n",
__func__, __LINE__, data->m4uid, sys, ret);
data->poweron = false;
spin_unlock_irqrestore(&data->reg_lock, flags);
continue;
}
data->poweron = false;
spin_unlock_irqrestore(&data->reg_lock, flags);
/*pr_notice("%s,%d,iommu:%d,sys:%d backup before off\n",
* __func__, __LINE__, data->m4uid, sys);
*/
}
}
static void mtk_iommu_pg_debug_dump(enum subsys_id sys)
{
struct mtk_iommu_data *data;
int i;
for (i = 0; i < MTK_IOMMU_M4U_COUNT; i++) {
if (iommu_mtcmos_subsys[i] != sys)
continue;
data = mtk_iommu_get_m4u_data(i);
if (!data) {
pr_notice("%s, %d iommu:%d is null\n",
__func__, __LINE__, i);
continue;
}
dev_notice(data->dev, "%s, iommu:%d,status:%d,user failed at power control, iommu:%d\n",
__func__, __LINE__, data->m4uid, data->poweron);
}
}
static struct pg_callbacks mtk_iommu_pg_handle = {
.after_on = mtk_iommu_pg_after_on,
.before_off = mtk_iommu_pg_before_off,
.debug_dump = mtk_iommu_pg_debug_dump,
};
#endif
static int mtk_iommu_hw_init(struct mtk_iommu_data *data)
{
u32 regval, i, wr_en;
unsigned int m4u_id = data->m4uid;
#if defined(MTK_M4U_SECURE_IRQ_SUPPORT) || \
defined(MTK_IOMMU_BANK_IRQ_SUPPORT)
struct device_node *node = NULL;
#endif
if (!data->base || IS_ERR(data->base)) {
pr_notice("%s, %d, invalid base addr\n",
__func__, __LINE__);
return -1;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
if (!data->poweron) {
pr_notice("%s, iommu%u power off\n",
__func__, data->m4uid);
return 0;
}
#endif
regval = readl_relaxed(data->base + REG_MMU_CTRL_REG);
regval = regval | F_MMU_CTRL_PFH_DIS(0)
| F_MMU_CTRL_MONITOR_EN(1)
| F_MMU_CTRL_MONITOR_CLR(0)
| F_MMU_CTRL_INT_FREEZE_EN(0);
writel_relaxed(regval, data->base + REG_MMU_CTRL_REG);
for (i = 0; i < MTK_IOMMU_MMU_COUNT; i++) {
#ifdef APU_IOMMU_INDEX
if (m4u_id < APU_IOMMU_INDEX)
wr_en = 0;
else
wr_en = F_MMU_MISC_CTRL_IN_ORDER_WR_EN(i);
#else
wr_en = 0;
#endif
iommu_set_field_by_mask(data->base, REG_MMU_MISC_CTRL,
F_MMU_MISC_CTRL_COHERENCE_EN(i),
F_MMU_MISC_CTRL_COHERENCE_EN(i));
#ifdef CONFIG_MTK_SMI_EXT
iommu_set_field_by_mask(data->base, REG_MMU_MISC_CTRL,
F_MMU_MISC_CTRL_IN_ORDER_WR_EN(i),
wr_en);
#endif
iommu_set_field_by_mask(data->base, REG_MMU_WR_LEN_CTRL,
F_MMU_WR_LEN_CTRL_THROT_DIS(i), 0);
}
writel_relaxed(0x6f, data->base + REG_MMU_INT_CONTROL0);
writel_relaxed(0xffffffff, data->base + REG_MMU_INT_MAIN_CONTROL);
writel_relaxed(F_MMU_TFRP_PA_SET(data->protect_base, data->enable_4GB),
data->base + REG_MMU_TFRP_PADDR);
writel_relaxed(0x100, data->base + REG_MMU_DCM_DIS);
//writel_relaxed(0, data->base + REG_MMU_STANDARD_AXI_MODE);
if (devm_request_irq(data->dev, data->irq, mtk_iommu_isr, 0,
dev_name(data->dev), (void *)data)) {
writel_relaxed(0, data->base + REG_MMU_PT_BASE_ADDR);
dev_err(data->dev, "Failed @ IRQ-%d Request\n", data->irq);
return -ENODEV;
}
wmb(); /*make sure the HW has been initialized*/
p_reg_backup[m4u_id] = kmalloc(IOMMU_REG_BACKUP_SIZE,
GFP_KERNEL | __GFP_ZERO);
if (p_reg_backup[m4u_id] == NULL)
return -ENOMEM;
#ifdef MTK_M4U_SECURE_IRQ_SUPPORT
/* register secure bank irq */
node = of_find_compatible_node(NULL, NULL,
iommu_secure_compatible[m4u_id]);
if (!node) {
pr_notice(
"%s, WARN: didn't find secure node of iommu:%d\n",
__func__, m4u_id);
return 0;
}
data->base_sec = of_iomap(node, 0);
mtk_irq_sec[m4u_id] = irq_of_parse_and_map(node, 0);
pr_notice("%s, secure bank, of_iomap: 0x%lx, irq_num: %d, m4u_id:%d\n",
__func__, data->base_sec,
mtk_irq_sec[m4u_id], m4u_id);
if (request_irq(mtk_irq_sec[m4u_id], MTK_M4U_isr_sec,
IRQF_TRIGGER_NONE, "secure_m4u", NULL)) {
pr_notice("request secure m4u%d IRQ line failed\n",
m4u_id);
return -ENODEV;
}
#endif
#ifdef MTK_IOMMU_BANK_IRQ_SUPPORT
/* register bank irq */
for (i = 0; i < MTK_IOMMU_BANK_NODE_COUNT; i++) {
node = of_find_compatible_node(NULL, NULL,
iommu_bank_compatible[m4u_id][i]);
if (!node) {
pr_notice(
"%s, WARN: didn't find bank node of iommu:%d\n",
__func__, m4u_id);
continue;
}
data->base_bank[i] = of_iomap(node, 0);
mtk_irq_bank[m4u_id][i] = irq_of_parse_and_map(node, 0);
pr_notice("%s, bank:%d, of_iomap: 0x%lx, irq_num: %d, m4u_id:%d\n",
__func__, i + 1, (uintptr_t)data->base_bank[i],
mtk_irq_bank[m4u_id][i], m4u_id);
if (request_irq(mtk_irq_bank[m4u_id][i], mtk_iommu_isr,
IRQF_TRIGGER_NONE, "bank_m4u", NULL)) {
pr_notice("request bank%d m4u%d IRQ line failed\n",
i + 1, m4u_id);
continue;
}
writel_relaxed(0x6f, data->base_bank[i] +
REG_MMU_INT_CONTROL0);
writel_relaxed(0xffffffff, data->base_bank[i] +
REG_MMU_INT_MAIN_CONTROL);
writel_relaxed(F_MMU_TFRP_PA_SET(data->protect_base,
data->enable_4GB),
data->base_bank[i] + REG_MMU_TFRP_PADDR);
}
#endif
pr_notice("%s, done\n", __func__);
return 0;
}
static const struct component_master_ops mtk_iommu_com_ops = {
.bind = mtk_iommu_bind,
.unbind = mtk_iommu_unbind,
};
static s32 mtk_iommu_clks_get(struct mtk_iommu_data *data)
{
#ifdef IOMMU_POWER_CLK_SUPPORT
struct property *prop;
struct device *dev;
struct clk *clk;
unsigned int nr = 0;
struct mtk_iommu_clks *m4u_clks;
const char *name, *clk_names = "clock-names";
int i, ret = 0;
if (!data || !data->dev) {
pr_info("iommu No such device or address\n");
return -ENXIO;
} else if (data->m4u_clks) {
pr_notice("%s, %d, clk reinit\n", __func__, __LINE__);
return 0;
}
data->poweron = false;
dev = data->dev;
m4u_clks = kzalloc(sizeof(*m4u_clks), GFP_KERNEL);
if (!m4u_clks)
return -ENOMEM;
nr = of_property_count_strings(dev->of_node, clk_names);
if (nr > IOMMU_CLK_ID_COUNT * 2) {
pr_info("iommu clk count %d exceed the max number of %d\n",
nr, IOMMU_CLK_ID_COUNT);
ret = -ENXIO;
goto free_clks;
}
m4u_clks->nr_clks = 0;
m4u_clks->nr_powers = 0;
of_property_for_each_string(dev->of_node, clk_names, prop, name) {
clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
dev_info(dev, "clks of %s init failed\n",
name);
ret = PTR_ERR(clk);
//kfree(clk);
break;
}
if (strcmp(name, "power")) {
m4u_clks->clks[m4u_clks->nr_clks] = clk;
dev_info(dev, "iommu:%d clks%d of %s init done\n",
data->m4uid, m4u_clks->nr_clks, name);
m4u_clks->nr_clks++;
} else {
m4u_clks->powers[m4u_clks->nr_powers] = clk;
dev_info(dev, "iommu:%d power%d of %s init done\n",
data->m4uid, m4u_clks->nr_powers, name);
m4u_clks->nr_powers++;
}
}
if (ret)
goto free_clk;
#if defined(APU_IOMMU_INDEX) && defined(CONFIG_MTK_APUSYS_SUPPORT)
if (data->m4uid >= APU_IOMMU_INDEX &&
!apusys_power_check()) {
m4u_clks->nr_powers = 0;
m4u_clks->nr_clks = 0;
pr_notice("%s, %d, apu power not support, power:%d, clk:%d\n",
__func__, __LINE__,
m4u_clks->nr_powers, m4u_clks->nr_clks);
}
#endif
data->m4u_clks = m4u_clks;
g_iommu_power_support = 1;
return 0;
free_clk:
for (i = 0; i < m4u_clks->nr_clks; i++)
kfree(m4u_clks->clks[i]);
for (i = 0; i < m4u_clks->nr_powers; i++)
kfree(m4u_clks->powers[i]);
free_clks:
kfree(m4u_clks);
return ret;
#else
g_iommu_power_support = 0;
return 0;
#endif
}
#ifdef CONFIG_MTK_SMI_EXT
/*
* if CONFIG_MTK_SMI_EXT is enabled,
* smi larb node will be init at arch_initcall_sync()
* this will support the iommu probe at
* the 1st time of kernel boot up.
* if CONFIG_MTK_SMI_EXT is disabled,
* smi larb node will be init at module_init()
* this will delay the iommu probe,
* and cause iommu devices init failed.
*/
static s32 mtk_iommu_larbs_get(struct mtk_iommu_data *data)
{
int larb_nr, i;
int ret = 0;
struct device *dev;
struct component_match *match = NULL;
if (!data || !data->dev) {
pr_info("iommu No such device or address\n");
return -ENXIO;
}
dev = data->dev;
larb_nr = of_count_phandle_with_args(dev->of_node,
"mediatek,larbs", NULL);
if (larb_nr < 0) {
pr_notice("%s, %d, no larbs of iommu%d, larbnr=%d\n",
__func__, __LINE__, data->m4uid, larb_nr);
data->smi_imu.larb_nr = 0;
return 0;
}
data->smi_imu.larb_nr = larb_nr;
for (i = 0; i < larb_nr; i++) {
struct device_node *larbnode;
struct platform_device *plarbdev;
u32 id;
larbnode = of_parse_phandle(dev->of_node, "mediatek,larbs", i);
if (!larbnode)
return -EINVAL;
if (!of_device_is_available(larbnode))
continue;
ret = of_property_read_u32(larbnode, smi_larb_id, &id);
if (ret) {
/* The id is consecutive on legacy chip */
id = i;
pr_notice("%s, cannot find larbid, id=%d\n",
__func__, id);
}
if (id >= MTK_LARB_NR_MAX) {
WARN_ON(1);
dev_notice(dev, "%d exceed the max larb ID\n",
id);
return -EINVAL;
}
plarbdev = of_find_device_by_node(larbnode);
if (!plarbdev) {
pr_notice("%s, invalid plarbdev, probe defer\n",
__func__);
return -EPROBE_DEFER;
}
data->smi_imu.larb_imu[id].dev = &plarbdev->dev;
component_match_add_release(dev, &match, release_of,
compare_of, larbnode);
}
if (match)
ret = component_master_add_with_match(dev,
&mtk_iommu_com_ops,
match);
else
ret = -ENOMEM;
if (ret)
pr_notice("%s, err add match, ret=%d, probe defer\n",
__func__, ret);
return ret;
}
#endif
static int mtk_iommu_probe(struct platform_device *pdev)
{
struct mtk_iommu_data *data;
struct device *dev = &pdev->dev;
struct resource *res;
resource_size_t ioaddr;
void *protect;
unsigned long protect_pa;
int ret = 0;
unsigned int id = 0, slave = 0, mau = 0;
pr_notice("%s+, %d\n",
__func__, __LINE__);
ret = of_property_read_u32(dev->of_node, "cell-index", &id);
if (ret)
pr_notice("%s, failed to get cell index, ret=%d\n",
__func__, ret);
if (total_iommu_cnt >= MTK_IOMMU_M4U_COUNT ||
id >= MTK_IOMMU_M4U_COUNT) {
pr_notice("%s invalid iommu device: %d\n",
__func__, id);
return 0;
}
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->m4uid = id; //total_iommu_cnt;
data->dev = dev;
data->plat_data = of_device_get_match_data(dev);
/* Protect memory. HW will access here while translation fault.*/
#if defined(APU_IOMMU_INDEX) && \
defined(MTK_APU_TFRP_SUPPORT)
if (id >= APU_IOMMU_INDEX) {
protect_pa = get_apu_iommu_tfrp(id - APU_IOMMU_INDEX);
if (!protect_pa)
return -ENOMEM;
data->protect_base = protect_pa;
} else {
protect = devm_kzalloc(dev,
MTK_PROTECT_PA_ALIGN * 2, GFP_KERNEL);
if (!protect)
return -ENOMEM;
protect_pa = virt_to_phys(protect);
data->protect_base = ALIGN(protect_pa,
MTK_PROTECT_PA_ALIGN);
}
#else
protect = devm_kzalloc(dev, MTK_PROTECT_PA_ALIGN * 2, GFP_KERNEL);
if (!protect)
return -ENOMEM;
protect_pa = virt_to_phys(protect);
data->protect_base = ALIGN(protect_pa,
MTK_PROTECT_PA_ALIGN);
#endif
/* Whether the current dram is over 4GB */
data->enable_4GB = !!(max_pfn > (BIT_ULL(32) >> PAGE_SHIFT));
spin_lock_init(&data->reg_lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
pr_info("%s, get resource is NULL\n", __func__);
return -EINVAL;
}
data->base = devm_ioremap_resource(dev, res);
if (IS_ERR(data->base)) {
pr_notice("mtk_iommu base is null\n");
return PTR_ERR(data->base);
}
ioaddr = res->start;
data->irq = platform_get_irq(pdev, 0);
if (data->irq < 0) {
pr_notice("mtk_iommu irq error\n");
return data->irq;
}
#ifdef CONFIG_MTK_SMI_EXT
ret = mtk_iommu_larbs_get(data);
if (ret) {
pr_notice("%s, failed to get larbs\n", __func__);
return ret;
}
#endif
platform_set_drvdata(pdev, data);
ret = mtk_iommu_clks_get(data);
if (ret) {
pr_notice("%s, failed to get clk\n", __func__);
return ret;
}
ret = mtk_iommu_power_switch(data, true, "iommu_probe");
if (ret) {
pr_notice("%s, failed to power switch on\n", __func__);
return ret;
}
#ifdef IOMMU_POWER_CLK_SUPPORT
data->isr_ref = 0;
if (data->m4u_clks->nr_powers)
data->poweron = true;
else
pr_notice("%s, iommu%u power control is not support\n",
__func__, data->m4uid);
#endif
ret = mtk_iommu_hw_init(data);
if (ret) {
pr_notice("%s, failed to hw init\n", __func__);
return ret;
}
ret = iommu_device_sysfs_add(&data->iommu, dev, NULL,
"mtk-iommu.%pa", &ioaddr);
if (ret) {
pr_notice("%s, failed to sysfs add\n", __func__);
return ret;
}
iommu_device_set_ops(&data->iommu, &mtk_iommu_ops);
iommu_device_set_fwnode(&data->iommu, &pdev->dev.of_node->fwnode);
ret = iommu_device_register(&data->iommu);
if (ret) {
pr_notice("%s, failed to device register\n", __func__);
return ret;
}
list_add_tail(&data->list, &m4ulist);
total_iommu_cnt++;
pr_debug("%s, %d, add m4ulist, use %s pgtable\n",
__func__, __LINE__,
MTK_IOMMU_PAGE_TABLE_SHARE ? "share" : "private");
/*
* trigger the bus to scan all the device to add them to iommu
* domain after all the iommu have finished probe.
*/
if (!iommu_present(&platform_bus_type) &&
total_iommu_cnt == MTK_IOMMU_M4U_COUNT)
bus_set_iommu(&platform_bus_type, &mtk_iommu_ops);
mtk_iommu_isr_pause_timer_init(data);
for (slave = 0;
slave < MTK_MMU_NUM_OF_IOMMU(data->m4uid); slave++)
for (mau = 0; mau < MTK_MAU_NUM_OF_MMU(slave); mau++) {
struct mau_config_info *cfg = get_mau_info(data->m4uid);
mau_start_monitor(data->m4uid, slave, mau, cfg);
}
#ifdef MTK_IOMMU_LOW_POWER_SUPPORT
if (total_iommu_cnt == 1)
register_pg_callback(&mtk_iommu_pg_handle);
ret = mtk_iommu_power_switch(data, false, "iommu_probe");
if (ret)
pr_notice("%s, failed to power switch off\n", __func__);
#endif
pr_notice("%s-, %d,total=%d,m4u%d,base=0x%lx,protect=0x%pa\n",
__func__, __LINE__, total_iommu_cnt, data->m4uid,
(uintptr_t)data->base, &data->protect_base);
return ret;
}
static int mtk_iommu_remove(struct platform_device *pdev)
{
struct mtk_iommu_data *data = platform_get_drvdata(pdev);
if (!data) {
pr_notice("%s, data is NULL\n", __func__);
return 0;
}
pr_notice("%s, %d, iommu%d\n",
__func__, __LINE__, data->m4uid);
iommu_device_sysfs_remove(&data->iommu);
iommu_device_unregister(&data->iommu);
if (iommu_present(&platform_bus_type))
bus_set_iommu(&platform_bus_type, NULL);
#ifdef IOMMU_POWER_CLK_SUPPORT
if (data->m4u_clks->nr_powers)
devm_free_irq(&pdev->dev, data->irq, data);
#endif
component_master_del(&pdev->dev, &mtk_iommu_com_ops);
return 0;
}
static void mtk_iommu_shutdown(struct platform_device *pdev)
{
pr_notice("%s, %d\n",
__func__, __LINE__);
mtk_iommu_remove(pdev);
}
static int mtk_iommu_suspend(struct device *dev)
{
struct mtk_iommu_data *data = dev_get_drvdata(dev);
int ret = 0;
#ifndef MTK_IOMMU_LOW_POWER_SUPPORT
unsigned long flags;
/*
* for IOMMU of DISP and MDP, do power off at suspend
* for IOMMU of APU, power off is controlled by APU
*/
if (!data) {
pr_notice("%s, data is NULL\n", __func__);
return 0;
}
spin_lock_irqsave(&data->reg_lock, flags);
ret = mtk_iommu_reg_backup(data);
if (ret)
pr_notice("%s, %d, iommu:%d, backup failed %d\n",
__func__, __LINE__, data->m4uid, ret);
spin_unlock_irqrestore(&data->reg_lock, flags);
ret = mtk_iommu_power_switch(data, false, "iommu_suspend");
if (ret)
pr_notice("%s, failed to power switch off\n", __func__);
#else
if (!data) {
pr_notice("%s, data is NULL\n", __func__);
return 0;
}
if (data->poweron)
pr_notice("%s, iommu:%d user did not power off\n",
__func__, data->m4uid);
#endif
return ret;
}
static int mtk_iommu_resume(struct device *dev)
{
int ret = 0;
#ifndef MTK_IOMMU_LOW_POWER_SUPPORT
unsigned long flags;
struct mtk_iommu_data *data = dev_get_drvdata(dev);
/*
* for IOMMU of DISP and MDP, do power on at suspend
* for IOMMU of APU, power on is controlled by APU
*/
if (!data) {
pr_notice("%s, data is NULL\n", __func__);
return 0;
}
ret = mtk_iommu_power_switch(data, true, "iommu_resume");
if (ret)
pr_notice("%s, failed to power switch on\n", __func__);
spin_lock_irqsave(&data->reg_lock, flags);
ret = mtk_iommu_reg_restore(data);
if (ret)
pr_notice("%s, %d, iommu:%d, restore failed %d\n",
__func__, __LINE__, data->m4uid, ret);
spin_unlock_irqrestore(&data->reg_lock, flags);
#endif
return ret;
}
static const struct dev_pm_ops mtk_iommu_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(mtk_iommu_suspend, mtk_iommu_resume)
};
const struct mtk_iommu_plat_data mt6xxx_v0_data = {
.m4u_plat = iommu_mt6xxx_v0,
.iommu_cnt = 1,
.has_4gb_mode = true,
};
static const struct of_device_id mtk_iommu_of_ids[] = {
{ .compatible = "mediatek,iommu_v0", .data = (void *)&mt6xxx_v0_data},
{}
};
static struct platform_driver mtk_iommu_driver = {
.probe = mtk_iommu_probe,
.remove = mtk_iommu_remove,
.shutdown = mtk_iommu_shutdown,
.driver = {
.name = "mtk-iommu-v2",
.of_match_table = of_match_ptr(mtk_iommu_of_ids),
.pm = &mtk_iommu_pm_ops,
}
};
#if 0
#ifdef CONFIG_ARM64
static int mtk_iommu_init_fn(struct device_node *np)
{
static bool init_done;
int ret = 0;
struct platform_device *pdev;
if (!np)
pr_notice("%s, %d, error np\n", __func__, __LINE__);
dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);
if (!init_done) {
pdev = of_platform_device_create(np, NULL,
platform_bus_type.dev_root);
if (!pdev) {
pr_notice("%s: Failed to create device\n", __func__);
return -ENOMEM;
}
ret = platform_driver_register(&mtk_iommu_driver);
if (ret) {
pr_notice("%s: Failed to register driver\n", __func__);
return ret;
}
init_done = true;
#ifdef IOMMU_DEBUG_ENABLED
g_tf_test = false;
#endif
}
return 0;
}
#else
static int mtk_iommu_init_fn(struct device_node *np)
{
int ret = 0;
dma_debug_init(PREALLOC_DMA_DEBUG_ENTRIES);
ret = platform_driver_register(&mtk_iommu_driver);
if (ret) {
pr_notice("%s: Failed to register driver\n", __func__);
return ret;
}
#ifdef IOMMU_DEBUG_ENABLED
g_tf_test = false;
#endif
return 0;
}
#endif
IOMMU_OF_DECLARE(mtk_iommu, "mediatek,iommu_v0", mtk_iommu_init_fn);
#else
static int __init mtk_iommu_init(void)
{
int ret = 0;
ret = platform_driver_register(&mtk_iommu_driver);
if (ret != 0)
pr_notice("Failed to register MTK IOMMU driver\n");
else
mtk_iommu_debug_init();
#ifdef IOMMU_DEBUG_ENABLED
g_tf_test = false;
#endif
return ret;
}
subsys_initcall(mtk_iommu_init);
#endif