// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc.
 *
 */

#include <linux/arm-smccc.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/soc/mediatek/mtk_sip_svc.h>
#include <soc/mediatek/emi.h>

/* FIXME: un-comment after AEE is ready */
//#include <mt-plat/aee.h>

struct emi_mpu {
	unsigned long long dram_start;
	unsigned long long dram_end;
	unsigned int region_cnt;
	unsigned int domain_cnt;
	unsigned int addr_align;
	unsigned int ctrl_intf;

	unsigned int dump_cnt;
	struct reg_info_t *dump_reg;

	unsigned int clear_reg_cnt;
	struct reg_info_t *clear_reg;

	struct reg_info_t *clear_md_reg;
	unsigned int clear_md_reg_cnt;

	unsigned int emi_cen_cnt;
	void __iomem **emi_cen_base;
	void __iomem **emi_mpu_base;

	struct emimpu_region_t *ap_rg_info;

	/* interrupt id */
	unsigned int irq;

	/* hook functions in ISR */
	emimpu_pre_handler pre_handler;
	emimpu_post_clear post_clear;
	emimpu_md_handler md_handler;

	/* debugging log for EMI MPU violation */
	char *vio_msg;
	unsigned int in_msg_dump;

	/* hook functions in worker thread */
	struct emimpu_dbg_cb *dbg_cb_list;
};

/* global pointer for exported functions */
static struct emi_mpu *global_emi_mpu;

static void set_regs(
	struct reg_info_t *reg_list, unsigned int reg_cnt,
	void __iomem *emi_cen_base)
{
	unsigned int i, j;

	for (i = 0; i < reg_cnt; i++)
		for (j = 0; j < reg_list[i].leng; j++)
			writel(reg_list[i].value, emi_cen_base +
				reg_list[i].offset + 4 * j);

	/*
	 * Use the memory barrier to make sure the interrupt signal is
	 * de-asserted (by programming registers) before exiting the
	 * ISR and re-enabling the interrupt.
	 */
	mb();
}

static void clear_violation(
	struct emi_mpu *mpu, unsigned int emi_id)
{
	void __iomem *emi_cen_base;

	emi_cen_base = mpu->emi_cen_base[emi_id];

	set_regs(mpu->clear_reg,
		mpu->clear_reg_cnt, emi_cen_base);

	if (mpu->post_clear)
		mpu->post_clear(emi_id);
}

static void emimpu_vio_dump(struct work_struct *work)
{
	struct emi_mpu *mpu;
	struct emimpu_dbg_cb *curr_dbg_cb;

	mpu = global_emi_mpu;
	if (!mpu)
		return;

	for (curr_dbg_cb = mpu->dbg_cb_list; curr_dbg_cb;
		curr_dbg_cb = curr_dbg_cb->next_dbg_cb)
		curr_dbg_cb->func();

	/* FIXME: un-comment after AEE is ready */
	//if (mpu->vio_msg)
		//aee_kernel_exception("EMIMPU", mpu->vio_msg);

	mpu->in_msg_dump = 0;
}
static DECLARE_WORK(emimpu_work, emimpu_vio_dump);

static irqreturn_t emimpu_violation_irq(int irq, void *dev_id)
{
	struct emi_mpu *mpu = (struct emi_mpu *)dev_id;
	struct reg_info_t *dump_reg = mpu->dump_reg;
	void __iomem *emi_cen_base;
	unsigned int emi_id, i;
	ssize_t msg_len;
	int n, nr_vio;
	bool violation;

	if (mpu->in_msg_dump)
		goto ignore_violation;

	n = snprintf(mpu->vio_msg, MTK_EMI_MAX_CMD_LEN, "violation\n");
	msg_len = (n < 0) ? 0 : (ssize_t)n;

	nr_vio = 0;
	for (emi_id = 0; emi_id < mpu->emi_cen_cnt; emi_id++) {
		violation = false;
		emi_cen_base = mpu->emi_cen_base[emi_id];

		for (i = 0; i < mpu->dump_cnt; i++) {
			dump_reg[i].value = readl(
				emi_cen_base + dump_reg[i].offset);

			if (msg_len < MTK_EMI_MAX_CMD_LEN) {
				n = snprintf(mpu->vio_msg + msg_len,
					MTK_EMI_MAX_CMD_LEN - msg_len,
					"%s(%d),%s(%x),%s(%x);\n",
					"emi", emi_id,
					"off", dump_reg[i].offset,
					"val", dump_reg[i].value);
				msg_len += (n < 0) ? 0 : (ssize_t)n;
			}

			if (dump_reg[i].value)
				violation = true;
		}

		if (!violation)
			continue;

		/*
		 * The DEVAPC module used the EMI MPU interrupt on some
		 * old smart-phone SoC. For these SoC, the DEVAPC driver
		 * will register a handler for processing its interrupt.
		 * If the handler has processed DEVAPC interrupt (and
		 * returns IRQ_HANDLED), just skip dumping and exit.
		 */
		if (mpu->pre_handler)
			if (mpu->pre_handler(emi_id, dump_reg,
					mpu->dump_cnt) == IRQ_HANDLED) {
				clear_violation(mpu, emi_id);
				mtk_clear_md_violation();
				continue;
			}

		nr_vio++;

		/*
		 * Whenever there is an EMI MPU violation, the Modem
		 * software would like to be notified immediately.
		 * This is because the Modem software wants to do
		 * its coredump as earlier as possible for debugging
		 * and analysis.
		 * (Even if the violated master is not Modem, it
		 *  may still need coredump for clarification.)
		 * Have a hook function in the EMI MPU ISR for this
		 * purpose.
		 */
		if (mpu->md_handler) {
			mpu->md_handler(emi_id,
				dump_reg, mpu->dump_cnt);
		}
	}

	if (nr_vio) {
		pr_info("%s: %s", __func__, mpu->vio_msg);
		mpu->in_msg_dump = 1;
		schedule_work(&emimpu_work);
	}

ignore_violation:
	for (emi_id = 0; emi_id < mpu->emi_cen_cnt; emi_id++)
		clear_violation(mpu, emi_id);

	return IRQ_HANDLED;
}

int emimpu_ap_region_init(void)
{
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return 0;

	if (!(mpu->ap_rg_info))
		return 0;

	pr_info("%s: enable AP region\n", __func__);

	mtk_emimpu_set_protection(mpu->ap_rg_info);
	mtk_emimpu_free_region(mpu->ap_rg_info);

	kfree(mpu->ap_rg_info);
	mpu->ap_rg_info = NULL;

	return 0;
}
EXPORT_SYMBOL(emimpu_ap_region_init);

/*
 * mtk_emimpu_init_region - init rg_info's apc data with default forbidden
 * @rg_info:	the target region for init
 * @rg_num:	the region id for the rg_info
 *
 * Returns 0 for success and 1 for abort
 */
int mtk_emimpu_init_region(
	struct emimpu_region_t *rg_info, unsigned int rg_num)
{
	struct emi_mpu *mpu;
	unsigned int size;
	unsigned int i;

	mpu = global_emi_mpu;
	if (!mpu)
		return -1;

	if (rg_num >= mpu->region_cnt) {
		pr_info("%s: fail, out-of-range region\n", __func__);
		return -1;
	}

	rg_info->start = 0;
	rg_info->end = 0;
	rg_info->rg_num = rg_num;
	rg_info->lock = false;

	size = sizeof(unsigned int) * mpu->domain_cnt;
	rg_info->apc = kmalloc(size, GFP_KERNEL);
	if (!(rg_info->apc))
		return -1;
	for (i = 0; i < mpu->domain_cnt; i++)
		rg_info->apc[i] = MTK_EMIMPU_FORBIDDEN;

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_init_region);

/*
 * mtk_emi_mpu_free_region - free the apc data in rg_info
 * @rg_info:	the target region for free
 *
 * Returns 0 for success
 */
int mtk_emimpu_free_region(struct emimpu_region_t *rg_info)
{
	kfree(rg_info->apc);
	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_free_region);

/*
 * mtk_emimpu_set_addr - set the address space
 * @rg_info:	the target region for address setting
 * @start:	the start address
 * @end:	the end address
 *
 * Returns 0 for success
 */
int mtk_emimpu_set_addr(struct emimpu_region_t *rg_info,
	unsigned long long start, unsigned long long end)
{
	rg_info->start = start;
	rg_info->end = end;
	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_set_addr);

/*
 * mtk_emimpu_set_apc - set access permission for target domain
 * @rg_info:	the target region for apc setting
 * @d_num:	the target domain id
 * @apc:	the access permission setting
 *
 * Returns 0 for success
 */
int mtk_emimpu_set_apc(struct emimpu_region_t *rg_info,
	unsigned int d_num, unsigned int apc)
{
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return -1;

	if (d_num >= mpu->domain_cnt) {
		pr_info("%s: fail, out-of-range domain\n", __func__);
		return -1;
	}

	rg_info->apc[d_num] = apc & 0x7;
	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_set_apc);

/*
 * mtk_emimpu_lock_region - set lock for target region
 * @rg_info:	the target region for lock
 * @lock:	enable/disable lock
 *
 * Returns 0 for success
 */
int mtk_emimpu_lock_region(struct emimpu_region_t *rg_info, bool lock)
{
	rg_info->lock = lock;
	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_lock_region);

/*
 * mtk_emimpu_set_protection - set emimpu protect into device
 * @rg_info:	the target region information
 *
 * Return 0 for success, -1 for fail
 */
int mtk_emimpu_set_protection(struct emimpu_region_t *rg_info)
{
	struct emi_mpu *mpu;
	unsigned int start, end;
	unsigned int group_apc;
	unsigned int d_group;
	struct arm_smccc_res smc_res;
	int i, j;

	mpu = global_emi_mpu;
	if (!mpu)
		return -1;

	if (!(rg_info->apc)) {
		pr_info("%s: fail, protect without init\n", __func__);
		return -1;
	}

	d_group = mpu->domain_cnt / 8;

	start = (unsigned int)
		(rg_info->start >> (mpu->addr_align)) |
		(rg_info->rg_num << 24);

	for (i = d_group - 1; i >= 0; i--) {
		end = (unsigned int)
			(rg_info->end >> (mpu->addr_align)) |
			(i << 24);

		for (group_apc = 0, j = 0; j < 8; j++)
			group_apc |=
				((rg_info->apc[i * 8 + j]) & 0x7) << (3 * j);
		if ((i == 0) && rg_info->lock)
			group_apc |= 0x80000000;

		arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIMPU_SET,
			start, end, group_apc, 0, 0, 0, &smc_res);
		if (smc_res.a0) {
			pr_info("%s:%d failed to set region permission, ret=0x%lx\n",
				__func__, __LINE__, smc_res.a0);
			return -1;
		}

	}

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_set_protection);

/*
 * mtk_emimpu_clear_protection - clear emimpu protection
 * @rg_info:	the target region information
 *
 * Return 0 for success, -1 for fail
 */
int mtk_emimpu_clear_protection(struct emimpu_region_t *rg_info)
{
	struct emi_mpu *mpu;
	struct arm_smccc_res smc_res;

	mpu = global_emi_mpu;
	if (!mpu)
		return -1;

	if (rg_info->rg_num > mpu->region_cnt) {
		pr_info("%s: region %u overflow\n", __func__, rg_info->rg_num);
		return -1;
	}

	arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIMPU_CLEAR,
		rg_info->rg_num, 0, 0, 0, 0, 0, &smc_res);
	if (smc_res.a0) {
		pr_info("%s:%d failed to clear region permission, ret=0x%lx\n",
			__func__, __LINE__, smc_res.a0);
		return -1;
	}
	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_clear_protection);

/*
 * mtk_emimpu_prehandle_register - register callback for irq prehandler
 * @bypass_func:	function point for prehandler
 *
 * Return 0 for success, -EINVAL for fail
 */
int mtk_emimpu_prehandle_register(emimpu_pre_handler bypass_func)
{
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return -EINVAL;

	if (!bypass_func) {
		pr_info("%s: bypass_func is NULL\n", __func__);
		return -EINVAL;
	}

	mpu->pre_handler = bypass_func;

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_prehandle_register);

/*
 * mtk_emimpu_postclear_register - register callback for clear posthandler
 * @clear_func:	function point for posthandler
 *
 * Return 0 for success, -EINVAL for fail
 */
int mtk_emimpu_postclear_register(emimpu_post_clear clear_func)
{
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return -EINVAL;

	if (!clear_func) {
		pr_info("%s: clear_func is NULL\n", __func__);
		return -EINVAL;
	}

	mpu->post_clear = clear_func;

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_postclear_register);

/*
 * mtk_emimpu_md_handling_register - register callback for md handling
 * @md_handling_func:	function point for md handling
 *
 * Return 0 for success, -EINVAL for fail
 */
int mtk_emimpu_md_handling_register(emimpu_md_handler md_handling_func)
{
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return -EINVAL;

	if (!md_handling_func) {
		pr_info("%s: md_handling_func is NULL\n", __func__);
		return -EINVAL;
	}

	mpu->md_handler = md_handling_func;

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_md_handling_register);

/*
 * mtk_clear_md_violation - clear irq for md violation
 *
 * No return
 */
void mtk_clear_md_violation(void)
{
	struct emi_mpu *mpu;
	void __iomem *emi_cen_base;
	unsigned int emi_id;

	mpu = global_emi_mpu;
	if (!mpu)
		return;

	for (emi_id = 0; emi_id < mpu->emi_cen_cnt; emi_id++) {
		emi_cen_base = mpu->emi_cen_base[emi_id];

		set_regs(mpu->clear_md_reg,
			mpu->clear_md_reg_cnt, emi_cen_base);
	}
}
EXPORT_SYMBOL(mtk_clear_md_violation);

/*
 * mtk_emimpu_debugdump_register - register callback for debug info dump
 * @debug_func:	function point for debug info dump
 *
 * Return 0 for success, -EINVAL for fail
 */
int mtk_emimpu_debugdump_register(emimpu_debug_dump debug_func)
{
	struct emimpu_dbg_cb *targ_dbg_cb;
	struct emimpu_dbg_cb *curr_dbg_cb;
	struct emi_mpu *mpu;

	mpu = global_emi_mpu;
	if (!mpu)
		return -EINVAL;

	if (!debug_func) {
		pr_info("%s: debug_func is NULL\n", __func__);
		return -EINVAL;
	}

	targ_dbg_cb = kmalloc(sizeof(struct emimpu_dbg_cb), GFP_KERNEL);
	if (!targ_dbg_cb)
		return -ENOMEM;

	targ_dbg_cb->func = debug_func;
	targ_dbg_cb->next_dbg_cb = NULL;

	if (!(mpu->dbg_cb_list)) {
		mpu->dbg_cb_list = targ_dbg_cb;
		return 0;
	}

	for (curr_dbg_cb = mpu->dbg_cb_list; curr_dbg_cb;
		curr_dbg_cb = curr_dbg_cb->next_dbg_cb) {
		if (!(curr_dbg_cb->next_dbg_cb)) {
			curr_dbg_cb->next_dbg_cb = targ_dbg_cb;
			break;
		}
	}

	return 0;
}
EXPORT_SYMBOL(mtk_emimpu_debugdump_register);

static const struct of_device_id emimpu_of_ids[] = {
	{.compatible = "mediatek,common-emimpu",},
	{}
};
MODULE_DEVICE_TABLE(of, emimpu_of_ids);

static int emimpu_probe(struct platform_device *pdev)
{
	struct device_node *emimpu_node = pdev->dev.of_node;
	struct device_node *emicen_node =
		of_parse_phandle(emimpu_node, "mediatek,emi-reg", 0);
	struct emi_mpu *mpu;
	int ret, size, i;
	struct emimpu_region_t *rg_info;
	struct arm_smccc_res smc_res;
	struct resource *res;
	unsigned int *dump_list;
	unsigned int *ap_apc;
	unsigned int ap_region;
	unsigned int slverr;

	dev_info(&pdev->dev, "driver probed\n");

	if (!emicen_node) {
		dev_err(&pdev->dev, "No emi-reg\n");
		return -ENXIO;
	}

	mpu = devm_kzalloc(&pdev->dev,
		sizeof(struct emi_mpu), GFP_KERNEL);
	if (!mpu)
		return -ENOMEM;

	ret = of_property_read_u32(emimpu_node,
		"region_cnt", &(mpu->region_cnt));
	if (ret) {
		dev_err(&pdev->dev, "No region_cnt\n");
		return -ENXIO;
	}

	ret = of_property_read_u32(emimpu_node,
		"domain_cnt", &(mpu->domain_cnt));
	if (ret) {
		dev_err(&pdev->dev, "No domain_cnt\n");
		return -ENXIO;
	}

	ret = of_property_read_u32(emimpu_node,
		"addr_align", &(mpu->addr_align));
	if (ret) {
		dev_err(&pdev->dev, "No addr_align\n");
		return -ENXIO;
	}

	ret = of_property_read_u64(emimpu_node,
		"dram_start", &(mpu->dram_start));
	if (ret) {
		dev_err(&pdev->dev, "No dram_start\n");
		return -ENXIO;
	}

	ret = of_property_read_u64(emimpu_node,
		"dram_end", &(mpu->dram_end));
	if (ret) {
		dev_err(&pdev->dev, "No dram_end fail\n");
		return -ENXIO;
	}

	ret = of_property_read_u32(emimpu_node,
		"ctrl_intf", &(mpu->ctrl_intf));
	if (ret) {
		dev_err(&pdev->dev, "No ctrl_intf\n");
		return -ENXIO;
	}

	size = of_property_count_elems_of_size(emimpu_node,
		"dump", sizeof(char));
	if (size <= 0) {
		dev_err(&pdev->dev, "No dump\n");
		return -ENXIO;
	}
	dump_list = devm_kmalloc(&pdev->dev, size, GFP_KERNEL);
	if (!dump_list)
		return -ENOMEM;
	size >>= 2;
	mpu->dump_cnt = size;
	ret = of_property_read_u32_array(emimpu_node, "dump",
		dump_list, size);
	if (ret) {
		dev_err(&pdev->dev, "No dump\n");
		return -ENXIO;
	}
	mpu->dump_reg = devm_kmalloc(&pdev->dev,
		size * sizeof(struct reg_info_t), GFP_KERNEL);
	if (!(mpu->dump_reg))
		return -ENOMEM;
	for (i = 0; i < mpu->dump_cnt; i++) {
		mpu->dump_reg[i].offset = dump_list[i];
		mpu->dump_reg[i].value = 0;
		mpu->dump_reg[i].leng = 0;
	}

	size = of_property_count_elems_of_size(emimpu_node,
		"clear", sizeof(char));
	if (size <= 0) {
		dev_err(&pdev->dev, "No clear\n");
		return  -ENXIO;
	}
	mpu->clear_reg = devm_kmalloc(&pdev->dev,
		size, GFP_KERNEL);
	if (!(mpu->clear_reg))
		return -ENOMEM;
	mpu->clear_reg_cnt = size / sizeof(struct reg_info_t);
	size >>= 2;
	ret = of_property_read_u32_array(emimpu_node, "clear",
		(unsigned int *)(mpu->clear_reg), size);
	if (ret) {
		dev_err(&pdev->dev, "No clear\n");
		return -ENXIO;
	}

	size = of_property_count_elems_of_size(emimpu_node,
		"clear_md", sizeof(char));
	if (size <= 0) {
		dev_err(&pdev->dev, "No clear_md\n");
		return -ENXIO;
	}
	mpu->clear_md_reg = devm_kmalloc(&pdev->dev,
		size, GFP_KERNEL);
	if (!(mpu->clear_md_reg))
		return -ENOMEM;
	mpu->clear_md_reg_cnt = size / sizeof(struct reg_info_t);
	size >>= 2;
	ret = of_property_read_u32_array(emimpu_node, "clear_md",
		(unsigned int *)(mpu->clear_md_reg), size);
	if (ret) {
		dev_err(&pdev->dev, "No clear_md\n");
		return -ENXIO;
	}

	mpu->emi_cen_cnt = of_property_count_elems_of_size(
			emicen_node, "reg", sizeof(unsigned int) * 4);
	if (mpu->emi_cen_cnt <= 0) {
		dev_err(&pdev->dev, "No reg\n");
		return -ENXIO;
	}

	mpu->emi_cen_base = devm_kmalloc_array(&pdev->dev,
		mpu->emi_cen_cnt, sizeof(phys_addr_t), GFP_KERNEL);
	if (!(mpu->emi_cen_base))
		return -ENOMEM;

	mpu->emi_mpu_base = devm_kmalloc_array(&pdev->dev,
		mpu->emi_cen_cnt, sizeof(phys_addr_t), GFP_KERNEL);
	if (!(mpu->emi_mpu_base))
		return -ENOMEM;

	for (i = 0; i < mpu->emi_cen_cnt; i++) {
		mpu->emi_cen_base[i] = of_iomap(emicen_node, i);
		if (IS_ERR(mpu->emi_cen_base[i])) {
			dev_err(&pdev->dev, "Failed to map EMI%d CEN base\n",
				i);
			return -EIO;
		}

		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
		mpu->emi_mpu_base[i] =
			devm_ioremap_resource(&pdev->dev, res);
		if (IS_ERR(mpu->emi_mpu_base[i])) {
			dev_err(&pdev->dev, "Failed to map EMI%d MPU base\n",
				i);
			return -EIO;
		}
	}

	mpu->vio_msg = devm_kmalloc(&pdev->dev,
		MTK_EMI_MAX_CMD_LEN, GFP_KERNEL);
	if (!(mpu->vio_msg))
		return -ENOMEM;

	global_emi_mpu = mpu;
	platform_set_drvdata(pdev, mpu);

	ret = of_property_read_u32(emimpu_node, "ap_region", &ap_region);
	if (ret) {
		mpu->ap_rg_info = NULL;
		dev_info(&pdev->dev, "No ap_region\n");
	} else {
		/* initialize the AP region */

		size = sizeof(unsigned int) * mpu->domain_cnt;
		ap_apc = devm_kmalloc(&pdev->dev, size, GFP_KERNEL);
		if (!ap_apc)
			return -ENOMEM;
		ret = of_property_read_u32_array(emimpu_node, "ap_apc",
			(unsigned int *)ap_apc, mpu->domain_cnt);
		if (ret) {
			dev_err(&pdev->dev, "No ap_apc\n");
			return -ENXIO;
		}

		mpu->ap_rg_info = kmalloc(sizeof(struct emimpu_region_t),
					GFP_KERNEL);
		if (!(mpu->ap_rg_info))
			return -ENOMEM;

		rg_info = mpu->ap_rg_info;

		mtk_emimpu_init_region(rg_info, ap_region);

		mtk_emimpu_set_addr(rg_info,
			mpu->dram_start, mpu->dram_end);

		for (i = 0; i < mpu->domain_cnt; i++)
			if (ap_apc[i] != MTK_EMIMPU_FORBIDDEN)
				mtk_emimpu_set_apc(rg_info, i, ap_apc[i]);

		mtk_emimpu_lock_region(rg_info, MTK_EMIMPU_LOCK);
	}

	mpu->irq = irq_of_parse_and_map(emimpu_node, 0);
	if (mpu->irq == 0) {
		dev_err(&pdev->dev, "Failed to get irq resource\n");
		ret = -ENXIO;
		goto free_ap_rg_info;
	}
	ret = request_irq(mpu->irq, (irq_handler_t)emimpu_violation_irq,
		IRQF_TRIGGER_NONE, "emimpu", mpu);
	if (ret) {
		dev_err(&pdev->dev, "Failed to request irq");
		ret = -EINVAL;
		goto free_ap_rg_info;
	}

	ret = of_property_read_u32(emimpu_node, "slverr", &slverr);
	if (!ret && slverr)
		for (i = 0; i < mpu->domain_cnt; i++) {
			arm_smccc_smc(MTK_SIP_EMIMPU_CONTROL, MTK_EMIMPU_SLVERR,
				i, 0, 0, 0, 0, 0, &smc_res);
			if (smc_res.a0) {
				dev_err(&pdev->dev, "Failed to set MPU domain%d Slave Error, ret=0x%lx\n",
					i, smc_res.a0);
				ret = -EINVAL;
				goto free_ap_rg_info;
			}
		}

	devm_kfree(&pdev->dev, ap_apc);
	devm_kfree(&pdev->dev, dump_list);

	dev_info(&pdev->dev, "%s(%d),%s(%d),%s(%d),%s(%llx),%s(%llx),%s(%d)\n",
		"region_cnt", mpu->region_cnt,
		"domain_cnt", mpu->domain_cnt,
		"addr_align", mpu->addr_align,
		"dram_start", mpu->dram_start,
		"dram_end", mpu->dram_end,
		"ctrl_intf", mpu->ctrl_intf);

	return 0;

free_ap_rg_info:
	kfree(mpu->ap_rg_info);

	return ret;
}

static int emimpu_remove(struct platform_device *pdev)
{
	struct emi_mpu *mpu = platform_get_drvdata(pdev);

	dev_info(&pdev->dev, "driver removed\n");

	free_irq(mpu->irq, mpu);

	flush_work(&emimpu_work);

	global_emi_mpu = NULL;

	return 0;
}

static struct platform_driver emimpu_driver = {
	.probe = emimpu_probe,
	.remove = emimpu_remove,
	.driver = {
		.name = "emimpu_driver",
		.owner = THIS_MODULE,
		.of_match_table = emimpu_of_ids,
	},
};

static __init int emimpu_init(void)
{
	int ret;

	pr_info("emimpu was loaded\n");

	ret = platform_driver_register(&emimpu_driver);
	if (ret) {
		pr_err("emimpu: failed to register driver\n");
		return ret;
	}

	return 0;
}

module_init(emimpu_init);

MODULE_DESCRIPTION("MediaTek EMI MPU Driver");
MODULE_LICENSE("GPL v2");
