diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
index 00e013b..1339884 100644
--- a/drivers/memory/Kconfig
+++ b/drivers/memory/Kconfig
@@ -218,5 +218,6 @@ config STM32_FMC2_EBI
 
 source "drivers/memory/samsung/Kconfig"
 source "drivers/memory/tegra/Kconfig"
+source "drivers/memory/mediatek/Kconfig"
 
 endif
diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
index e71cf7b..e3ac040 100644
--- a/drivers/memory/Makefile
+++ b/drivers/memory/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_RENESAS_RPCIF)	+= renesas-rpc-if.o
 obj-$(CONFIG_STM32_FMC2_EBI)	+= stm32-fmc2-ebi.o
 
 obj-$(CONFIG_SAMSUNG_MC)	+= samsung/
+obj-$(CONFIG_HAVE_MTK_MC)       += mediatek/
 obj-$(CONFIG_TEGRA_MC)		+= tegra/
 obj-$(CONFIG_TI_EMIF_SRAM)	+= ti-emif-sram.o
 ti-emif-sram-objs		:= ti-emif-pm.o ti-emif-sram-pm.o
diff --git a/drivers/memory/mediatek/Kconfig b/drivers/memory/mediatek/Kconfig
new file mode 100644
index 0000000..37535bb
--- /dev/null
+++ b/drivers/memory/mediatek/Kconfig
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config HAVE_MTK_MC
+	bool "MediaTek Memory Controller support"
+	select MTK_EMI
+	help
+	  Support for the Memory Controller (MC) devices found on
+	  MediaTek(R) SoCs.
+	  This config enables MTK_EMI and MTK_DRAMC for EMI and
+	  DRAMC drivers.
+
+if HAVE_MTK_MC
+
+config MTK_EMI
+	tristate "MediaTek External Memory Interface driver"
+	depends on HAVE_MTK_MC
+	help
+	  This selects the MediaTek(R) EMI driver.
+	  Provide the API for MPU registration, EMI MPU violation handling,
+	  the API for EMI information, the BWL golden setting,
+	  and the sysfs for EMI ISU control.
+
+endif
diff --git a/drivers/memory/mediatek/Makefile b/drivers/memory/mediatek/Makefile
new file mode 100644
index 0000000..22947c1
--- /dev/null
+++ b/drivers/memory/mediatek/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_MTK_EMI) += emi.o
+emi-objs := emi-cen.o
+obj-$(CONFIG_MTK_EMI) += emi-mpu.o
diff --git a/drivers/memory/mediatek/emi-cen.c b/drivers/memory/mediatek/emi-cen.c
new file mode 100644
index 0000000..6cdb4b1
--- /dev/null
+++ b/drivers/memory/mediatek/emi-cen.c
@@ -0,0 +1,1305 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.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_device.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <soc/mediatek/emi.h>
+
+/*
+ * EMI address-to-dram setting's structure.
+ * a2d is the abbreviation of addr2dram.
+ * s6s is the abbreviation of settings.
+ */
+struct a2d_s6s_v1 {
+	unsigned int magics[8];
+	unsigned long cas;
+	unsigned long chab_rk0_sz, chab_rk1_sz;
+	unsigned long chcd_rk0_sz, chcd_rk1_sz;
+	unsigned int channels;
+	unsigned int dualrk_ch0, dualrk_ch1;
+	unsigned int chn_hash_lsb, chnpos;
+	unsigned int chab_row_mask[2], chcd_row_mask[2];
+	unsigned int chab_col_mask[2], chcd_col_mask[2];
+	unsigned int dw32;
+	unsigned int chn_4bank_mode;
+};
+
+struct a2d_s6s_v2 {
+	unsigned int chn_bit_position;
+	unsigned int chn_en;
+	unsigned int magics[8];
+
+	unsigned int dual_rank_en;
+	unsigned int dw32_en;
+	unsigned int bg1_bk3_pos;
+	unsigned int rank_pos;
+	unsigned int magics2[7];
+	unsigned int rank0_row_width, rank0_bank_width, rank0_col_width;
+	unsigned int rank0_size_MB, rank0_bg_16bank_mode;
+	unsigned int rank1_row_width, rank1_bank_width, rank1_col_width;
+	unsigned int rank1_size_MB, rank1_bg_16bank_mode;
+};
+
+struct emi_cen {
+	/*
+	 * EMI setting from device tree
+	 */
+	int ver;
+	unsigned int emi_cen_cnt;
+	unsigned int ch_cnt;
+	unsigned int rk_cnt;
+	unsigned long long *rk_size;
+	void __iomem **emi_cen_base;
+	void __iomem **emi_chn_base;
+
+	/* address from the sysfs file for EMI addr2dram */
+	unsigned long a2d_addr;
+
+	/*
+	 * EMI addr2dram settings from device tree
+	 */
+	unsigned int disph;
+	unsigned int hash;
+
+	/*
+	 * EMI addr2dram settings calculated at run time
+	 */
+	unsigned long offset;
+	unsigned long max;
+	union {
+		struct a2d_s6s_v1 v1;
+		struct a2d_s6s_v2 v2;
+	} a2d_s6s;
+};
+
+#define EMI_CONA_DW32_EN 1
+#define EMI_CONA_CHN_POS_0 2
+#define EMI_CONA_CHN_POS_1 3
+#define EMI_CONA_COL 4
+#define EMI_CONA_COL2ND 6
+#define EMI_CONA_CHN_EN 8
+#define EMI_CONA_ROW 12
+#define EMI_CONA_ROW2ND 14
+#define EMI_CONA_DUAL_RANK_EN_CHN1 16
+#define EMI_CONA_DUAL_RANK_EN 17
+#define EMI_CONA_CAS_SIZE 18
+#define EMI_CONA_CHN1_COL 20
+#define EMI_CONA_CHN1_COL2ND 22
+#define EMI_CONA_ROW_EXT0 24
+#define EMI_CONA_ROW2ND_EXT0 25
+#define EMI_CONA_CAS_SIZE_BIT3 26
+#define EMI_CONA_RANK_POS 27
+#define EMI_CONA_CHN1_ROW 28
+#define EMI_CONA_CHN1_ROW2ND 30
+
+#define EMI_CONH_CHN1_ROW_EXT0 4
+#define EMI_CONH_CHN1_ROW2ND_EXT0 5
+#define EMI_CONH_CHNAB_RANK0_SIZE 16
+#define EMI_CONH_CHNAB_RANK1_SIZE 20
+#define EMI_CONH_CHNCD_RANK0_SIZE 24
+#define EMI_CONH_CHNCD_RANK1_SIZE 28
+
+#define EMI_CONH_2ND_CHN_4BANK_MODE 6
+
+#define EMI_CONK_CHNAB_RANK0_SIZE_EXT 16
+#define EMI_CONK_CHNAB_RANK1_SIZE_EXT 20
+#define EMI_CONK_CHNCD_RANK0_SIZE_EXT 24
+#define EMI_CONK_CHNCD_RANK1_SIZE_EXT 28
+
+#define EMI_CHN_CONA_DUAL_RANK_EN 0
+#define EMI_CHN_CONA_DW32_EN 1
+#define EMI_CHN_CONA_ROW_EXT0 2
+#define EMI_CHN_CONA_ROW2ND_EXT0 3
+#define EMI_CHN_CONA_COL 4
+#define EMI_CHN_CONA_COL2ND 6
+#define EMI_CHN_CONA_RANK0_SIZE_EXT 8
+#define EMI_CHN_CONA_RANK1_SIZE_EXT 9
+#define EMI_CHN_CONA_16BANK_MODE 10
+#define EMI_CHN_CONA_16BANK_MODE_2ND 11
+#define EMI_CHN_CONA_ROW 12
+#define EMI_CHN_CONA_ROW2ND 14
+#define EMI_CHN_CONA_RANK0_SZ 16
+#define EMI_CHN_CONA_RANK1_SZ 20
+#define EMI_CHN_CONA_4BANK_MODE 24
+#define EMI_CHN_CONA_4BANK_MODE_2ND 25
+#define EMI_CHN_CONA_RANK_POS 27
+#define EMI_CHN_CONA_BG1_BK3_POS 31
+
+#define MTK_EMI_DRAM_OFFSET 0x40000000
+#define MTK_EMI_HASH 0x7
+#define MTK_EMI_DISPATCH 0x0
+
+static unsigned int emi_a2d_con_offset[] = {
+	/* central EMI CONA, CONF, CONH, CONH_2ND, CONK */
+	0x00, 0x28, 0x38, 0x3c, 0x50
+};
+
+static unsigned int emi_a2d_chn_con_offset[] = {
+	/* channel EMI CONA, CONC, CONC_2ND */
+	0x00, 0x10, 0x14
+};
+
+/* global pointer for exported functions */
+static struct emi_cen *global_emi_cen;
+
+/*
+ * NoteXXX: emimpu_ap_region_init() will initialize the latest EMI MPU
+ * region for AP CPU. It must be called after initialization of drivers
+ * in which call mtk_emimpu_set_protection() to initialize other EMI
+ * MPU regions.
+ *
+ * For guaranteeing the call sequence, emimpu_ap_region_init() is
+ * either called via late_initcall_sync (when EMI drivers are not built
+ * as kernel modules) or called in emicen_init() (When EMI drivers are
+ * kernel modules).
+ *
+ * Scenario #1: EMI drivers are kernel modules:
+ * Other drivers (which call mtk_emimpu_set_protection()) must be
+ * loaded before emi-cen.ko is loaded.
+ *
+ * Scenario #2. EMI drivers are NOT kernel modules:
+ * Other drivers must be initialized before kernel's initcall stage.
+ */
+#if !defined(MODULE)
+static void __init emicen_init_mpu_ap_region(void)
+{
+	emimpu_ap_region_init();
+}
+late_initcall_sync(emicen_init_mpu_ap_region);
+#endif
+
+/*
+ * mtk_emicen_get_ch_cnt - get the channel count
+ *
+ * Returns the channel count
+ */
+unsigned int mtk_emicen_get_ch_cnt(void)
+{
+	return (global_emi_cen) ? global_emi_cen->ch_cnt : 0;
+}
+EXPORT_SYMBOL(mtk_emicen_get_ch_cnt);
+
+/*
+ * mtk_emicen_get_rk_cnt - get the rank count
+ *
+ * Returns the rank count
+ */
+unsigned int mtk_emicen_get_rk_cnt(void)
+{
+	return (global_emi_cen) ? global_emi_cen->rk_cnt : 0;
+}
+EXPORT_SYMBOL(mtk_emicen_get_rk_cnt);
+
+/*
+ * mtk_emicen_get_rk_size - get the rank size of target rank
+ * @rk_id: the id of target rank
+ *
+ * Returns the rank size of target rank
+ */
+unsigned int mtk_emicen_get_rk_size(unsigned int rk_id)
+{
+	if (rk_id < mtk_emicen_get_rk_cnt())
+		return (global_emi_cen) ? global_emi_cen->rk_size[rk_id] : 0;
+	else
+		return 0;
+}
+EXPORT_SYMBOL(mtk_emicen_get_rk_size);
+
+/*
+ * prepare_a2d_v1: a helper function to initialize and calculate settings for
+ *                 the mtk_emicen_addr2dram_v1() function
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static inline void prepare_a2d_v1(struct emi_cen *cen)
+{
+	const unsigned int mask_4b = 0xf, mask_2b = 0x3;
+	void __iomem *emi_cen_base;
+	struct a2d_s6s_v1 *s6s;
+	unsigned long emi_cona;
+	unsigned long emi_conf;
+	unsigned long emi_conh;
+	unsigned long emi_conh_2nd;
+	unsigned long emi_conk;
+	unsigned long tmp;
+
+	if (!cen)
+		return;
+
+	emi_cen_base = cen->emi_cen_base[0];
+	s6s = &cen->a2d_s6s.v1;
+
+	emi_cona = readl(emi_cen_base + emi_a2d_con_offset[0]);
+	emi_conf = readl(emi_cen_base + emi_a2d_con_offset[1]);
+	emi_conh = readl(emi_cen_base + emi_a2d_con_offset[2]);
+	emi_conh_2nd = readl(emi_cen_base + emi_a2d_con_offset[3]);
+	emi_conk = readl(emi_cen_base + emi_a2d_con_offset[4]);
+
+	cen->offset = MTK_EMI_DRAM_OFFSET;
+
+	s6s->magics[0] = emi_conf & mask_4b;
+	s6s->magics[1] = (emi_conf >> 4) & mask_4b;
+	s6s->magics[2] = (emi_conf >> 8) & mask_4b;
+	s6s->magics[3] = (emi_conf >> 12) & mask_4b;
+	s6s->magics[4] = (emi_conf >> 16) & mask_4b;
+	s6s->magics[5] = (emi_conf >> 20) & mask_4b;
+	s6s->magics[6] = (emi_conf >> 24) & mask_4b;
+	s6s->magics[7] = (emi_conf >> 28) & mask_4b;
+
+	s6s->dw32 = test_bit(EMI_CONA_DW32_EN, &emi_cona) ? 1 : 0;
+
+	s6s->channels = (emi_cona >> EMI_CONA_CHN_EN) & mask_2b;
+
+	s6s->cas = (emi_cona >> EMI_CONA_CAS_SIZE) & mask_2b;
+	s6s->cas += s6s->dw32 << 2;
+	s6s->cas += ((emi_cona >> EMI_CONA_CAS_SIZE_BIT3) & 1) << 3;
+	s6s->cas = s6s->cas << 28;
+	s6s->cas = s6s->cas << s6s->channels;
+
+	s6s->dualrk_ch0 = test_bit(EMI_CONA_DUAL_RANK_EN, &emi_cona) ?  1 : 0;
+	s6s->dualrk_ch1 = test_bit(EMI_CONA_DUAL_RANK_EN_CHN1,
+				   &emi_cona) ? 1 : 0;
+
+	s6s->chn_hash_lsb = 7 + (cen->hash & (~(cen->hash) + 1));
+	if (cen->hash) {
+		s6s->chnpos = s6s->chn_hash_lsb;
+	} else {
+		s6s->chnpos = test_bit(EMI_CONA_CHN_POS_1, &emi_cona) ?  2 : 0;
+		s6s->chnpos |= test_bit(EMI_CONA_CHN_POS_0, &emi_cona) ?  1 : 0;
+	}
+
+	tmp = (emi_conh >> EMI_CONH_CHNAB_RANK0_SIZE) & mask_4b;
+	tmp += ((emi_conk >> EMI_CONK_CHNAB_RANK0_SIZE_EXT) & mask_4b) << 4;
+	if (tmp) {
+		s6s->chab_rk0_sz = tmp << 8;
+	} else {
+		tmp = (emi_cona >> EMI_CONA_COL) & mask_2b;
+		tmp += (emi_cona >> EMI_CONA_ROW) & mask_2b;
+		tmp += test_bit(EMI_CONA_ROW_EXT0, &emi_cona) ? 4 : 0;
+		tmp += s6s->dw32;
+		tmp += 7;
+		s6s->chab_rk0_sz = 1 << tmp;
+	}
+
+	tmp = (emi_conh >> EMI_CONH_CHNAB_RANK1_SIZE) & mask_4b;
+	tmp += ((emi_conk >> EMI_CONK_CHNAB_RANK1_SIZE_EXT) & mask_4b) << 4;
+	if (tmp) {
+		s6s->chab_rk1_sz = tmp << 8;
+	} else if (!test_bit(EMI_CONA_DUAL_RANK_EN, &emi_cona)) {
+		s6s->chab_rk1_sz = 0;
+	} else {
+		tmp = (emi_cona >> EMI_CONA_COL2ND) & mask_2b;
+		tmp += (emi_cona >> EMI_CONA_ROW2ND) & mask_2b;
+		tmp += test_bit(EMI_CONA_ROW2ND_EXT0, &emi_cona) ? 4 : 0;
+		tmp += s6s->dw32;
+		tmp += 7;
+		s6s->chab_rk1_sz = 1 << tmp;
+	}
+
+	tmp = (emi_conh >> EMI_CONH_CHNCD_RANK0_SIZE) & mask_4b;
+	tmp += ((emi_conk >> EMI_CONK_CHNCD_RANK0_SIZE_EXT) & mask_4b) << 4;
+	if (tmp) {
+		s6s->chcd_rk0_sz = tmp << 8;
+	} else {
+		tmp = (emi_cona >> EMI_CONA_CHN1_COL) & mask_2b;
+		tmp += (emi_cona >> EMI_CONA_CHN1_ROW) & mask_2b;
+		tmp += test_bit(EMI_CONH_CHN1_ROW_EXT0, &emi_conh) ? 4 : 0;
+		tmp += s6s->dw32;
+		tmp += 7;
+		s6s->chcd_rk0_sz = 1 << tmp;
+	}
+
+	tmp = (emi_conh >> EMI_CONH_CHNCD_RANK1_SIZE) & mask_4b;
+	tmp += ((emi_conk >> EMI_CONK_CHNCD_RANK1_SIZE_EXT) & mask_4b) << 4;
+	if (tmp) {
+		s6s->chcd_rk1_sz = tmp << 8;
+	} else if (!test_bit(EMI_CONA_DUAL_RANK_EN_CHN1, &emi_cona)) {
+		s6s->chcd_rk1_sz = 0;
+	} else {
+		tmp = (emi_cona >> EMI_CONA_CHN1_COL2ND) & mask_2b;
+		tmp += (emi_cona >> EMI_CONA_CHN1_ROW2ND) & mask_2b;
+		tmp += test_bit(EMI_CONH_CHN1_ROW2ND_EXT0, &emi_conh) ? 4 : 0;
+		tmp += s6s->dw32;
+		tmp += 7;
+		s6s->chcd_rk1_sz = 1 << tmp;
+	}
+
+	cen->max = s6s->chab_rk0_sz + s6s->chab_rk1_sz;
+	cen->max += s6s->chcd_rk0_sz + s6s->chcd_rk0_sz;
+	if (s6s->channels > 1 || cen->disph > 0)
+		cen->max *= 2;
+	cen->max = cen->max << 20;
+
+	s6s->chab_row_mask[0] = (emi_cona >> EMI_CONA_ROW) & mask_2b;
+	s6s->chab_row_mask[0] += test_bit(EMI_CONA_ROW_EXT0,
+					&emi_cona) ? 4 : 0;
+	s6s->chab_row_mask[0] += 13;
+	s6s->chab_row_mask[1] = (emi_cona >> EMI_CONA_ROW2ND) & mask_2b;
+	s6s->chab_row_mask[1] += test_bit(EMI_CONA_ROW2ND_EXT0,
+					&emi_cona) ? 4 : 0;
+	s6s->chab_row_mask[1] += 13;
+
+	s6s->chcd_row_mask[0] = (emi_cona >> EMI_CONA_CHN1_ROW) & mask_2b;
+	s6s->chcd_row_mask[0] += test_bit(EMI_CONH_CHN1_ROW_EXT0,
+					&emi_conh) ? 4 : 0;
+	s6s->chcd_row_mask[0] += 13;
+	s6s->chcd_row_mask[1] = (emi_cona >> EMI_CONA_CHN1_ROW2ND) & mask_2b;
+	s6s->chcd_row_mask[1] += test_bit(EMI_CONH_CHN1_ROW2ND_EXT0,
+					&emi_conh) ? 4 : 0;
+	s6s->chcd_row_mask[1] += 13;
+
+	s6s->chab_col_mask[0] = (emi_cona >> EMI_CONA_COL) & mask_2b;
+	s6s->chab_col_mask[0] += 9;
+	s6s->chab_col_mask[1] = (emi_cona >> EMI_CONA_COL2ND) & mask_2b;
+	s6s->chab_col_mask[1] += 9;
+
+	s6s->chcd_col_mask[0] = (emi_cona >> EMI_CONA_CHN1_COL) & mask_2b;
+	s6s->chcd_col_mask[0] += 9;
+	s6s->chcd_col_mask[1] = (emi_cona >> EMI_CONA_CHN1_COL2ND) & mask_2b;
+	s6s->chcd_col_mask[1] += 9;
+
+	s6s->chn_4bank_mode = test_bit(EMI_CONH_2ND_CHN_4BANK_MODE,
+				       &emi_conh_2nd) ? 1 : 0;
+}
+
+/*
+ * use_a2d_magic_v1: a helper function to calculate the input address
+ *                   for the mtk_emicen_addr2dram_v1() function
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static inline unsigned int use_a2d_magic_v1(unsigned long addr,
+					    unsigned int bit)
+{
+	unsigned long magic;
+	unsigned int ret;
+
+	if (!global_emi_cen)
+		return 0;
+
+	magic = global_emi_cen->a2d_s6s.v1.magics[((bit >= 9) & (bit <= 16)) ?
+						(bit - 9) : 0];
+
+	ret = test_bit(bit, &addr) ? 1 : 0;
+	ret ^= (test_bit(16, &addr) && test_bit(0, &magic)) ? 1 : 0;
+	ret ^= (test_bit(17, &addr) && test_bit(1, &magic)) ? 1 : 0;
+	ret ^= (test_bit(18, &addr) && test_bit(2, &magic)) ? 1 : 0;
+	ret ^= (test_bit(19, &addr) && test_bit(3, &magic)) ? 1 : 0;
+
+	return ret;
+}
+
+/*
+ * mtk_emicen_addr2dram_v1 - Translate a physical address to
+ *                           a DRAM-point-of-view map for EMI v1
+ * @addr - input physical address
+ * @map - output map stored in struct emi_addr_map
+ *
+ * Return 0 on success, -1 on failures.
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static int mtk_emicen_addr2dram_v1(unsigned long addr,
+				   struct emi_addr_map *map)
+{
+	struct a2d_s6s_v1 *s6s;
+	unsigned long disph, hash;
+	unsigned long saddr, bfraddr, chnaddr;
+	unsigned long max_rk0_sz;
+	unsigned int tmp;
+	unsigned int chn_hash_lsb, row_mask, col_mask;
+	bool ch_ab_not_cd;
+
+	if (!global_emi_cen)
+		return -1;
+	if (!map)
+		return -1;
+	if (addr < global_emi_cen->offset)
+		return -1;
+
+	addr -= global_emi_cen->offset;
+	if (addr > global_emi_cen->max)
+		return -1;
+
+	map->emi = -1;
+	map->channel = -1;
+	map->rank = -1;
+	map->bank = -1;
+	map->row = -1;
+	map->column = -1;
+
+	s6s = &global_emi_cen->a2d_s6s.v1;
+	disph = global_emi_cen->disph;
+	hash = global_emi_cen->hash;
+	chn_hash_lsb = s6s->chn_hash_lsb;
+
+	tmp = (test_bit(8, &addr) & test_bit(0, &disph)) ? 1 : 0;
+	tmp ^= (test_bit(9, &addr) & test_bit(1, &disph)) ? 1 : 0;
+	tmp ^= (test_bit(10, &addr) & test_bit(2, &disph)) ? 1 : 0;
+	tmp ^= (test_bit(11, &addr) & test_bit(3, &disph)) ? 1 : 0;
+	map->emi = tmp;
+
+	saddr = addr;
+	clear_bit(9, &saddr);
+	clear_bit(10, &saddr);
+	clear_bit(11, &saddr);
+	clear_bit(12, &saddr);
+	clear_bit(13, &saddr);
+	clear_bit(14, &saddr);
+	clear_bit(15, &saddr);
+	clear_bit(16, &saddr);
+	saddr |= use_a2d_magic_v1(addr, 9) << 9;
+	saddr |= use_a2d_magic_v1(addr, 10) << 10;
+	saddr |= use_a2d_magic_v1(addr, 11) << 11;
+	saddr |= use_a2d_magic_v1(addr, 12) << 12;
+	saddr |= use_a2d_magic_v1(addr, 13) << 13;
+	saddr |= use_a2d_magic_v1(addr, 14) << 14;
+	saddr |= use_a2d_magic_v1(addr, 15) << 15;
+	saddr |= use_a2d_magic_v1(addr, 16) << 16;
+
+	if (global_emi_cen->disph <= 0) {
+		bfraddr = saddr;
+	} else {
+		tmp = 7 + __ffs(disph);
+		bfraddr = (saddr >> (tmp + 1)) << tmp;
+		bfraddr += saddr & ((1 << tmp) - 1);
+	}
+
+	if (bfraddr < s6s->cas)
+		return -1;
+
+	if (!s6s->channels) {
+		map->channel = s6s->channels;
+	} else if (hash) {
+		tmp = (test_bit(8, &addr) && test_bit(0, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(9, &addr) && test_bit(1, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(10, &addr) && test_bit(2, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(11, &addr) && test_bit(3, &hash)) ? 1 : 0;
+		map->channel = tmp;
+	} else {
+		if (s6s->channels == 1) {
+			tmp = 0;
+			switch (s6s->chnpos) {
+			case 0:
+				tmp = 7;
+				break;
+			case 1:
+				tmp = 8;
+				break;
+			case 2:
+				tmp = 9;
+				break;
+			case 3:
+				tmp = 12;
+				break;
+			default:
+				return -1;
+			}
+			map->channel = (bfraddr >> tmp) % 2;
+		} else if (s6s->channels == 2) {
+			tmp = 0;
+			switch (s6s->chnpos) {
+			case 0:
+				tmp = 7;
+				break;
+			case 1:
+				tmp = 8;
+				break;
+			case 2:
+				tmp = 9;
+				break;
+			case 3:
+				tmp = 12;
+				break;
+			default:
+				return -1;
+			}
+			map->channel = (bfraddr >> tmp) % 4;
+		} else {
+			return -1;
+		}
+	}
+
+	if (map->channel > 1) {
+		ch_ab_not_cd = 0;
+	} else {
+		if (map->channel == 1)
+			ch_ab_not_cd = (s6s->channels > 1) ?  1 : 0;
+		else
+			ch_ab_not_cd = 1;
+	}
+
+	max_rk0_sz = (ch_ab_not_cd) ?  s6s->chab_rk0_sz : s6s->chcd_rk0_sz;
+	max_rk0_sz = max_rk0_sz << 20;
+
+	if (!s6s->channels) {
+		chnaddr = bfraddr;
+	} else if (s6s->chnpos > 3) {
+		tmp = chn_hash_lsb;
+		chnaddr = bfraddr >> (tmp + 1);
+		chnaddr = chnaddr << tmp;
+		chnaddr += bfraddr & ((1 << tmp) - 1);
+	} else if (s6s->channels == 1 ||
+			s6s->channels == 2) {
+		tmp = 0;
+		switch (s6s->chnpos) {
+		case 0:
+			tmp = 7;
+			break;
+		case 1:
+			tmp = 8;
+			break;
+		case 2:
+			tmp = 9;
+			break;
+		case 3:
+			tmp = 12;
+			break;
+		default:
+			break;
+		}
+		chnaddr = bfraddr >> (tmp + (s6s->channels - 1));
+		chnaddr = chnaddr << tmp;
+		chnaddr += bfraddr & ((1 << tmp) - 1);
+	} else {
+		return -1;
+	}
+
+	if (map->channel ? !s6s->dualrk_ch1 : !s6s->dualrk_ch0) {
+		map->rank = 0;
+	} else {
+		if (chnaddr > max_rk0_sz)
+			map->rank = 1;
+		else
+			map->rank = 0;
+	}
+
+	row_mask = (ch_ab_not_cd) ?
+			((map->rank) ?
+				s6s->chab_row_mask[1] : s6s->chab_row_mask[0]) :
+			((map->rank) ?
+				s6s->chcd_row_mask[1] : s6s->chcd_row_mask[0]);
+	col_mask = (ch_ab_not_cd) ?
+			((map->rank) ?
+				s6s->chab_col_mask[1] : s6s->chab_col_mask[0]) :
+			((map->rank) ?
+				s6s->chcd_col_mask[1] : s6s->chcd_col_mask[0]);
+
+	tmp = chnaddr - (max_rk0_sz * map->rank);
+	tmp /= 1 << (s6s->dw32 + 1 + col_mask + 3);
+	tmp &= (1 << row_mask) - 1;
+	map->row = tmp;
+
+	tmp = chnaddr;
+	tmp /= 1 << (s6s->dw32 + 1 + col_mask);
+	tmp &= ((!s6s->chn_4bank_mode) ? 8 : 4) - 1;
+	map->bank = tmp;
+
+	tmp = chnaddr;
+	tmp /= 1 << (s6s->dw32 + 1);
+	tmp &= (1 << col_mask) - 1;
+	map->column = tmp;
+
+	return 0;
+}
+
+/*
+ * prepare_a2d_v2: a helper function to initialize and calculate settings for
+ *                 the mtk_emicen_addr2dram_v2() function
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static inline void prepare_a2d_v2(struct emi_cen *cen)
+{
+	const unsigned int mask_4b = 0xf, mask_2b = 0x3;
+	struct a2d_s6s_v2 *s6s;
+	void __iomem *emi_cen_base, *emi_chn_base;
+	unsigned long emi_cona, emi_conf, emi_conh, emi_conh_2nd, emi_conk;
+	unsigned long emi_chn_cona, emi_chn_conc, emi_chn_conc_2nd;
+	int tmp;
+	int col, col2nd, row, row2nd, row_ext0, row2nd_ext0;
+	int rank0_size, rank1_size, rank0_size_ext, rank1_size_ext;
+	int chn_4bank_mode, chn_bg_16bank_mode, chn_bg_16bank_mode_2nd;
+	int b11s, b12s, b13s, b14s, b15s, b16s;
+	int b8s, b11s_ext, b12s_ext, b13s_ext, b14s_ext, b15s_ext, b16s_ext;
+	unsigned long ch0_rk0_sz, ch0_rk1_sz;
+	unsigned long ch1_rk0_sz, ch1_rk1_sz;
+
+	if (!cen)
+		return;
+
+	s6s = &cen->a2d_s6s.v2;
+	cen->offset = MTK_EMI_DRAM_OFFSET;
+
+	emi_cen_base = cen->emi_cen_base[0];
+	emi_cona = readl(emi_cen_base + emi_a2d_con_offset[0]);
+	emi_conf = readl(emi_cen_base + emi_a2d_con_offset[1]);
+	emi_conh = readl(emi_cen_base + emi_a2d_con_offset[2]);
+	emi_conh_2nd = readl(emi_cen_base + emi_a2d_con_offset[3]);
+	emi_conk = readl(emi_cen_base + emi_a2d_con_offset[4]);
+
+	emi_chn_base = cen->emi_chn_base[0];
+	emi_chn_cona = readl(emi_chn_base + emi_a2d_chn_con_offset[0]);
+	emi_chn_conc = readl(emi_chn_base + emi_a2d_chn_con_offset[1]);
+	emi_chn_conc_2nd = readl(emi_chn_base + emi_a2d_chn_con_offset[2]);
+
+	tmp = (emi_cona >> EMI_CONA_CHN_POS_0) & mask_2b;
+	switch (tmp) {
+	case 3:
+		s6s->chn_bit_position = 12;
+		break;
+	case 2:
+		s6s->chn_bit_position = 9;
+		break;
+	case 1:
+		s6s->chn_bit_position = 8;
+		break;
+	default:
+		s6s->chn_bit_position = 7;
+		break;
+	}
+
+	s6s->chn_en = (emi_cona >> EMI_CONA_CHN_EN) & mask_2b;
+
+	s6s->magics[0] = emi_conf & mask_4b;
+	s6s->magics[1] = (emi_conf >> 4) & mask_4b;
+	s6s->magics[2] = (emi_conf >> 8) & mask_4b;
+	s6s->magics[3] = (emi_conf >> 12) & mask_4b;
+	s6s->magics[4] = (emi_conf >> 16) & mask_4b;
+	s6s->magics[5] = (emi_conf >> 20) & mask_4b;
+	s6s->magics[6] = (emi_conf >> 24) & mask_4b;
+	s6s->magics[7] = (emi_conf >> 28) & mask_4b;
+
+	s6s->dual_rank_en =
+		test_bit(EMI_CHN_CONA_DUAL_RANK_EN, &emi_chn_cona) ?  1 : 0;
+	s6s->dw32_en = test_bit(EMI_CHN_CONA_DW32_EN, &emi_chn_cona) ? 1 : 0;
+	row_ext0 = test_bit(EMI_CHN_CONA_ROW_EXT0, &emi_chn_cona) ? 1 : 0;
+	row2nd_ext0 = test_bit(EMI_CHN_CONA_ROW2ND_EXT0, &emi_chn_cona) ? 1 : 0;
+	col = (emi_chn_cona >> EMI_CHN_CONA_COL) & mask_2b;
+	col2nd = (emi_chn_cona >> EMI_CHN_CONA_COL2ND) & mask_2b;
+	rank0_size_ext =
+		test_bit(EMI_CHN_CONA_RANK0_SIZE_EXT, &emi_chn_cona) ? 1 : 0;
+	rank1_size_ext =
+		test_bit(EMI_CHN_CONA_RANK1_SIZE_EXT, &emi_chn_cona) ? 1 : 0;
+	chn_bg_16bank_mode =
+		test_bit(EMI_CHN_CONA_16BANK_MODE, &emi_chn_cona) ? 1 : 0;
+	chn_bg_16bank_mode_2nd =
+		test_bit(EMI_CHN_CONA_16BANK_MODE_2ND, &emi_chn_cona) ? 1 : 0;
+	row = (emi_chn_cona >> EMI_CHN_CONA_ROW) & mask_2b;
+	row2nd = (emi_chn_cona >> EMI_CHN_CONA_ROW2ND) & mask_2b;
+	rank0_size = (emi_chn_cona >> EMI_CHN_CONA_RANK0_SZ) & mask_4b;
+	rank1_size = (emi_chn_cona >> EMI_CHN_CONA_RANK1_SZ) & mask_4b;
+	chn_4bank_mode =
+		test_bit(EMI_CHN_CONA_4BANK_MODE, &emi_chn_cona) ? 1 : 0;
+	s6s->rank_pos = test_bit(EMI_CHN_CONA_RANK_POS, &emi_chn_cona) ? 1 : 0;
+	s6s->bg1_bk3_pos =
+		test_bit(EMI_CHN_CONA_BG1_BK3_POS, &emi_chn_cona) ? 1 : 0;
+
+	b11s = (emi_chn_conc >> 8) & mask_4b;
+	b12s = (emi_chn_conc >> 12) & mask_4b;
+	b13s = (emi_chn_conc >> 16) & mask_4b;
+	b14s = (emi_chn_conc >> 20) & mask_4b;
+	b15s = (emi_chn_conc >> 24) & mask_4b;
+	b16s = (emi_chn_conc >> 28) & mask_4b;
+
+	b11s_ext = (emi_chn_conc_2nd >> 4) & mask_2b;
+	b12s_ext = (emi_chn_conc_2nd >> 6) & mask_2b;
+	b13s_ext = (emi_chn_conc_2nd >> 8) & mask_2b;
+	b14s_ext = (emi_chn_conc_2nd >> 10) & mask_2b;
+	b15s_ext = (emi_chn_conc_2nd >> 12) & mask_2b;
+	b16s_ext = (emi_chn_conc_2nd >> 14) & mask_2b;
+	b8s = (emi_chn_conc_2nd >> 16) & mask_2b;
+
+	s6s->magics2[0] = b8s;
+	s6s->magics2[1] = b11s_ext * 16 + b11s;
+	s6s->magics2[2] = b12s_ext * 16 + b12s;
+	s6s->magics2[3] = b13s_ext * 16 + b13s;
+	s6s->magics2[4] = b14s_ext * 16 + b14s;
+	s6s->magics2[5] = b15s_ext * 16 + b15s;
+	s6s->magics2[6] = b16s_ext * 16 + b16s;
+
+	s6s->rank0_row_width = row_ext0 * 4 + row + 13;
+	s6s->rank0_bank_width = (chn_bg_16bank_mode == 1) ? 4 :
+				(chn_4bank_mode == 1) ? 2 : 3;
+	s6s->rank0_col_width = col + 9;
+	s6s->rank0_bg_16bank_mode = chn_bg_16bank_mode;
+	s6s->rank0_size_MB = (rank0_size_ext * 16 + rank0_size) * 256;
+	if (!(s6s->rank0_size_MB)) {
+		tmp = s6s->rank0_row_width + s6s->rank0_bank_width;
+		tmp += s6s->rank0_col_width + s6s->dw32_en;
+		s6s->rank0_size_MB = 2 << (tmp - 20);
+	}
+
+	s6s->rank1_row_width = row2nd_ext0 * 4 + row2nd + 13;
+	s6s->rank1_bank_width = (chn_bg_16bank_mode_2nd == 1) ? 4 :
+				(chn_4bank_mode == 1) ? 2 : 3;
+	s6s->rank1_col_width = col2nd + 9;
+	s6s->rank1_bg_16bank_mode = chn_bg_16bank_mode_2nd;
+	s6s->rank1_size_MB = (rank1_size_ext * 16 + rank1_size) * 256;
+	if (!(s6s->rank1_size_MB)) {
+		tmp = s6s->rank1_row_width + s6s->rank1_bank_width;
+		tmp += s6s->rank1_col_width + s6s->dw32_en;
+		s6s->rank1_size_MB = 2 << (tmp - 20);
+	}
+
+	if (s6s->rank0_size_MB) {
+		ch0_rk0_sz = s6s->rank0_size_MB;
+	} else {
+		tmp = s6s->rank0_row_width + s6s->rank0_bank_width;
+		tmp += s6s->rank0_col_width + s6s->dw32_en ? 2 : 1;
+		tmp -= 20;
+		ch0_rk0_sz = 1 << tmp;
+	}
+	ch1_rk0_sz = ch0_rk0_sz;
+	if (s6s->rank1_size_MB) {
+		ch0_rk1_sz = s6s->rank1_size_MB;
+	} else {
+		tmp = s6s->rank1_row_width + s6s->rank1_bank_width;
+		tmp += s6s->rank1_col_width + s6s->dw32_en ? 2 : 1;
+		tmp -= 20;
+		ch0_rk1_sz = 1 << tmp;
+	}
+	ch1_rk1_sz = ch0_rk1_sz;
+
+	cen->max = ch0_rk0_sz;
+	if (s6s->dual_rank_en)
+		cen->max += ch0_rk1_sz;
+	if (s6s->chn_en)
+		cen->max += ch1_rk0_sz + ((s6s->dual_rank_en) ? ch1_rk1_sz : 0);
+	if (cen->disph)
+		cen->max *= 2;
+	cen->max = cen->max << 20;
+}
+
+/*
+ * use_a2d_magic_v2: a helper function to calculate the input address
+ *                   for the mtk_emicen_addr2dram_v2() function
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static inline unsigned int use_a2d_magic_v2(unsigned long addr,
+					    unsigned long magic,
+					    unsigned int bit)
+{
+	unsigned int ret;
+
+	ret = test_bit(bit, &addr) ? 1 : 0;
+	ret ^= (test_bit(16, &addr) & test_bit(0, &magic)) ? 1 : 0;
+	ret ^= (test_bit(17, &addr) & test_bit(1, &magic)) ? 1 : 0;
+	ret ^= (test_bit(18, &addr) & test_bit(2, &magic)) ? 1 : 0;
+	ret ^= (test_bit(19, &addr) & test_bit(3, &magic)) ? 1 : 0;
+	ret ^= (test_bit(20, &addr) & test_bit(4, &magic)) ? 1 : 0;
+	ret ^= (test_bit(21, &addr) & test_bit(5, &magic)) ? 1 : 0;
+
+	return ret;
+}
+
+/*
+ * a2d_rm_bit: a helper function to calculate the input address
+ *             for the mtk_emicen_addr2dram_v2() function
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static inline unsigned long a2d_rm_bit(unsigned long taddr, int bit)
+{
+	unsigned long ret;
+
+	ret = taddr;
+	clear_bit(bit, &ret);
+
+	ret = ret >> (bit + 1);
+	ret = ret << bit;
+	ret = ret & ~((1UL << bit) - 1);
+
+	ret = ret | (taddr & ((1UL << bit) - 1));
+
+	return ret;
+}
+
+/*
+ * mtk_emicen_addr2dram_v2 - Translate a physical address to
+ *                           a DRAM-point-of-view map for EMI v2
+ * @addr - input physical address
+ * @map - output map stored in struct emi_addr_map
+ *
+ * Return 0 on success, -1 on failures.
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+static int mtk_emicen_addr2dram_v2(unsigned long addr,
+				   struct emi_addr_map *map)
+{
+	struct a2d_s6s_v2 *s6s;
+	unsigned long disph, hash;
+	unsigned long saddr, taddr, bgaddr, noraddr;
+	unsigned long tmp;
+	int emi_tpos, chn_tpos;
+
+	if (!global_emi_cen)
+		return -1;
+	if (!map)
+		return -1;
+	if (addr < global_emi_cen->offset)
+		return -1;
+
+	addr -= global_emi_cen->offset;
+	if (addr > global_emi_cen->max)
+		return -1;
+
+	map->emi = -1;
+	map->channel = -1;
+	map->rank = -1;
+	map->bank = -1;
+	map->row = -1;
+	map->column = -1;
+
+	s6s = &global_emi_cen->a2d_s6s.v2;
+	disph = global_emi_cen->disph;
+	hash = global_emi_cen->hash;
+
+	saddr = addr;
+	clear_bit(9, &saddr);
+	clear_bit(10, &saddr);
+	clear_bit(11, &saddr);
+	clear_bit(12, &saddr);
+	clear_bit(13, &saddr);
+	clear_bit(14, &saddr);
+	clear_bit(15, &saddr);
+	clear_bit(16, &saddr);
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[0], 9) << 9;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[1], 10) << 10;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[2], 11) << 11;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[3], 12) << 12;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[4], 13) << 13;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[5], 14) << 14;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[6], 15) << 15;
+	saddr |= use_a2d_magic_v2(addr, s6s->magics[7], 16) << 16;
+
+	if (!hash) {
+		map->channel = test_bit(s6s->chn_bit_position, &saddr) ? 1 : 0;
+
+		chn_tpos = s6s->chn_bit_position;
+	} else {
+		tmp = (test_bit(8, &saddr) && test_bit(0, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(9, &saddr) && test_bit(1, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(10, &saddr) && test_bit(2, &hash)) ? 1 : 0;
+		tmp ^= (test_bit(11, &saddr) && test_bit(3, &hash)) ? 1 : 0;
+		map->channel = tmp;
+
+		if (test_bit(0, &hash))
+			chn_tpos = 8;
+		else if (test_bit(1, &hash))
+			chn_tpos = 9;
+		else if (test_bit(2, &hash))
+			chn_tpos = 10;
+		else if (test_bit(3, &hash))
+			chn_tpos = 11;
+		else
+			chn_tpos = -1;
+	}
+
+	if (!disph) {
+		map->emi = 0;
+
+		emi_tpos = -1;
+	} else {
+		tmp = (test_bit(8, &saddr) && test_bit(0, &disph)) ? 1 : 0;
+		tmp ^= (test_bit(9, &saddr) && test_bit(1, &disph)) ? 1 : 0;
+		tmp ^= (test_bit(10, &saddr) && test_bit(2, &disph)) ? 1 : 0;
+		tmp ^= (test_bit(11, &saddr) && test_bit(3, &disph)) ? 1 : 0;
+		map->emi = tmp;
+
+		if (test_bit(0, &disph))
+			emi_tpos = 8;
+		else if (test_bit(1, &disph))
+			emi_tpos = 9;
+		else if (test_bit(2, &disph))
+			emi_tpos = 10;
+		else if (test_bit(3, &disph))
+			emi_tpos = 11;
+		else
+			emi_tpos = -1;
+	}
+
+	taddr = saddr;
+	if (!disph) {
+		if (!s6s->chn_en)
+			taddr = saddr;
+		else
+			taddr = a2d_rm_bit(taddr, chn_tpos);
+	} else {
+		if (chn_tpos < 0 || emi_tpos < 0)
+			return -1;
+		if (!s6s->chn_en) {
+			taddr = a2d_rm_bit(taddr, emi_tpos);
+		} else if (emi_tpos > chn_tpos) {
+			taddr = a2d_rm_bit(taddr, emi_tpos);
+			taddr = a2d_rm_bit(taddr, chn_tpos);
+		} else {
+			taddr = a2d_rm_bit(taddr, chn_tpos);
+			taddr = a2d_rm_bit(taddr, emi_tpos);
+		}
+	}
+
+	saddr = taddr;
+	clear_bit(8, &saddr);
+	clear_bit(11, &saddr);
+	clear_bit(12, &saddr);
+	clear_bit(13, &saddr);
+	clear_bit(14, &saddr);
+	clear_bit(15, &saddr);
+	clear_bit(16, &saddr);
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[0], 8) << 8;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[1], 11) << 11;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[2], 12) << 12;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[3], 13) << 13;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[4], 14) << 14;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[5], 15) << 15;
+	saddr |= use_a2d_magic_v2(taddr, s6s->magics2[6], 16) << 16;
+
+	if (!s6s->dual_rank_en) {
+		map->rank = 0;
+	} else {
+		if (!s6s->rank_pos) {
+			map->rank = ((saddr >> 20) > s6s->rank0_size_MB) ?
+					1 : 0;
+		} else {
+			tmp = 1 + s6s->dw32_en;
+			tmp += s6s->rank0_col_width + s6s->rank0_bank_width;
+			map->rank = saddr >> tmp;
+		}
+	}
+
+	tmp = (map->rank)
+		? s6s->rank1_bg_16bank_mode
+		: s6s->rank0_bg_16bank_mode;
+	if (tmp) {
+		bgaddr = a2d_rm_bit(saddr, 8);
+		map->column = (bgaddr >> (1 + s6s->dw32_en))
+			% (1 << ((map->rank)
+				? s6s->rank1_col_width
+				: s6s->rank0_col_width));
+
+		tmp = (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
+		tmp = (bgaddr >> (1 + s6s->dw32_en + tmp))
+			% (1 << ((map->rank)
+				? s6s->rank1_bank_width - 1
+				: s6s->rank0_bank_width - 1));
+		map->bank = test_bit((s6s->bg1_bk3_pos) ? 0 : 1, &tmp) ? 1 : 0;
+		map->bank += test_bit((s6s->bg1_bk3_pos) ? 1 : 2, &tmp) ? 2 : 0;
+		map->bank += test_bit(8, &saddr) ? 4 : 0;
+		map->bank += test_bit((s6s->bg1_bk3_pos) ? 2 : 0, &tmp) ? 8 : 0;
+	} else {
+		map->column = (saddr >> (1 + s6s->dw32_en))
+			% (1 << ((map->rank)
+				? s6s->rank1_col_width
+				: s6s->rank0_col_width));
+
+		tmp = (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
+		map->bank = (saddr >> (1 + s6s->dw32_en + tmp))
+			% (1 << ((map->rank)
+				? s6s->rank1_bank_width
+				: s6s->rank0_bank_width));
+	}
+
+	if (!s6s->rank_pos) {
+		noraddr = (map->rank) ?
+			saddr - (s6s->rank0_size_MB << 20) : saddr;
+	} else {
+		tmp = 1 + s6s->dw32_en;
+		tmp += (map->rank) ?
+			s6s->rank1_bank_width : s6s->rank0_bank_width;
+		tmp += (map->rank) ?
+			s6s->rank1_col_width : s6s->rank0_col_width;
+		noraddr = a2d_rm_bit(saddr, tmp);
+	}
+	tmp = 1 + s6s->dw32_en;
+	tmp += (map->rank) ? s6s->rank1_bank_width : s6s->rank0_bank_width;
+	tmp += (map->rank) ? s6s->rank1_col_width : s6s->rank0_col_width;
+	noraddr = noraddr >> tmp;
+	tmp = (map->rank) ? s6s->rank1_row_width : s6s->rank0_row_width;
+	map->row = noraddr % (1 << tmp);
+
+	return 0;
+}
+
+/*
+ * mtk_emicen_addr2dram - Translate a physical address to
+			a DRAM-point-of-view map
+ * @addr - input physical address
+ * @map - output map stored in struct emi_addr_map
+ *
+ * Return 0 on success, -1 on failures.
+ *
+ * There is no code comment for the translation. This is intended since
+ * the fomular of translation is derived from the implementation of EMI.
+ */
+int mtk_emicen_addr2dram(unsigned long addr, struct emi_addr_map *map)
+{
+	if (!global_emi_cen)
+		return -1;
+
+	if (global_emi_cen->ver == 1)
+		return mtk_emicen_addr2dram_v1(addr, map);
+	else
+		return mtk_emicen_addr2dram_v2(addr, map);
+}
+EXPORT_SYMBOL(mtk_emicen_addr2dram);
+
+static ssize_t emicen_addr2dram_show(struct device_driver *driver, char *buf)
+{
+	int ret;
+	struct emi_addr_map map;
+	unsigned long addr;
+
+	if (!global_emi_cen)
+		return 0;
+
+	addr = global_emi_cen->a2d_addr;
+
+	ret = mtk_emicen_addr2dram(addr, &map);
+	if (!ret) {
+		return snprintf(buf, PAGE_SIZE,
+		     "0x%lx\n->\nemi%d\nchn%d\nrank%d\nbank%d\nrow%d\ncol%d\n",
+		     addr, map.emi, map.channel, map.rank,
+		     map.bank, map.row, map.column);
+	} else {
+		return snprintf(buf, PAGE_SIZE, "0x%lx\n->failed\n", addr);
+	}
+}
+
+static ssize_t emicen_addr2dram_store
+	(struct device_driver *driver, const char *buf, size_t count)
+{
+	u64 addr;
+	int ret;
+
+	if (!global_emi_cen)
+		return count;
+
+	ret = kstrtou64(buf, 16, &addr);
+	if (ret)
+		return ret;
+
+	global_emi_cen->a2d_addr = (unsigned long)addr;
+
+	return ret ? : count;
+}
+
+static DRIVER_ATTR_RW(emicen_addr2dram);
+
+static int emicen_probe(struct platform_device *pdev)
+{
+	struct device_node *emicen_node = pdev->dev.of_node;
+	struct device_node *emichn_node =
+		of_parse_phandle(emicen_node, "mediatek,emi-reg", 0);
+	struct emi_cen *cen;
+	unsigned int i;
+	int ret;
+
+	dev_info(&pdev->dev, "driver probed\n");
+
+	cen = devm_kzalloc(&pdev->dev,
+			   sizeof(struct emi_cen), GFP_KERNEL);
+	if (!cen)
+		return -ENOMEM;
+
+	cen->ver = (int)of_device_get_match_data(&pdev->dev);
+
+	ret = of_property_read_u32(emicen_node,
+				   "ch_cnt", &cen->ch_cnt);
+	if (ret) {
+		dev_err(&pdev->dev, "No ch_cnt\n");
+		return -ENXIO;
+	}
+
+	ret = of_property_read_u32(emicen_node,
+				   "rk_cnt", &cen->rk_cnt);
+	if (ret) {
+		dev_err(&pdev->dev, "No rk_cnt\n");
+		return -ENXIO;
+	}
+
+	cen->rk_size = devm_kmalloc_array(&pdev->dev,
+					  cen->rk_cnt,
+					  sizeof(unsigned long long),
+					  GFP_KERNEL);
+	if (!cen->rk_size)
+		return -ENOMEM;
+
+	ret = of_property_read_u64_array(emicen_node,
+					 "rk_size", cen->rk_size, cen->rk_cnt);
+	if (ret) {
+		dev_err(&pdev->dev, "No rk_size\n");
+		return -ENXIO;
+	}
+
+	ret = of_property_count_elems_of_size(emicen_node,
+					      "reg", sizeof(unsigned int) * 4);
+	if (ret <= 0) {
+		dev_err(&pdev->dev, "No reg\n");
+		return -ENXIO;
+	}
+	cen->emi_cen_cnt = (unsigned int)ret;
+
+	cen->emi_cen_base = devm_kmalloc_array(&pdev->dev,
+					       cen->emi_cen_cnt,
+					       sizeof(phys_addr_t),
+					       GFP_KERNEL);
+	if (!cen->emi_cen_base)
+		return -ENOMEM;
+	for (i = 0; i < cen->emi_cen_cnt; i++)
+		cen->emi_cen_base[i] = of_iomap(emicen_node, i);
+
+	cen->emi_chn_base = devm_kmalloc_array(&pdev->dev,
+					       cen->ch_cnt,
+					       sizeof(phys_addr_t),
+					       GFP_KERNEL);
+	if (!cen->emi_chn_base)
+		return -ENOMEM;
+	for (i = 0; i < cen->ch_cnt; i++)
+		cen->emi_chn_base[i] = of_iomap(emichn_node, i);
+
+	ret = of_property_read_u32(emicen_node,
+				   "a2d_disph", &cen->disph);
+	if (ret) {
+		dev_info(&pdev->dev, "No a2d_disph\n");
+		cen->disph = MTK_EMI_DISPATCH;
+	}
+
+	ret = of_property_read_u32(emicen_node,
+				   "a2d_hash", &cen->hash);
+	if (ret) {
+		dev_info(&pdev->dev, "No a2d_hash\n");
+		cen->hash = MTK_EMI_HASH;
+	}
+
+	ret = of_property_read_u32_array(emicen_node,
+					 "a2d_conf_offset", emi_a2d_con_offset,
+		ARRAY_SIZE(emi_a2d_con_offset));
+	if (ret)
+		dev_info(&pdev->dev, "No a2d_conf_offset\n");
+
+	ret = of_property_read_u32_array(emicen_node,
+					 "a2d_chn_conf_offset",
+					 emi_a2d_chn_con_offset,
+					 ARRAY_SIZE(emi_a2d_chn_con_offset));
+	if (ret)
+		dev_info(&pdev->dev, "No a2d_chn_conf_offset\n");
+
+	if (cen->ver == 1)
+		prepare_a2d_v1(cen);
+	else if (cen->ver == 2)
+		prepare_a2d_v2(cen);
+	else
+		return -ENXIO;
+
+	global_emi_cen = cen;
+
+	dev_info(&pdev->dev, "%s(%d) %s(%d), %s(%d)\n",
+		 "version", cen->ver,
+		 "ch_cnt", cen->ch_cnt,
+		 "rk_cnt", cen->rk_cnt);
+
+	for (i = 0; i < cen->rk_cnt; i++)
+		dev_info(&pdev->dev, "rk_size%d(0x%llx)\n",
+			 i, cen->rk_size[i]);
+
+	dev_info(&pdev->dev, "a2d_disph %d\n", cen->disph);
+
+	dev_info(&pdev->dev, "a2d_hash %d\n", cen->hash);
+
+	for (i = 0; i < ARRAY_SIZE(emi_a2d_con_offset); i++)
+		dev_info(&pdev->dev, "emi_a2d_con_offset[%d] %d\n",
+			 i, emi_a2d_con_offset[i]);
+
+	for (i = 0; i < ARRAY_SIZE(emi_a2d_chn_con_offset); i++)
+		dev_info(&pdev->dev, "emi_a2d_chn_con_offset[%d] %d\n",
+			 i, emi_a2d_chn_con_offset[i]);
+
+	return 0;
+}
+
+static int emicen_remove(struct platform_device *pdev)
+{
+	global_emi_cen = NULL;
+
+	return 0;
+}
+
+static const struct of_device_id emicen_of_ids[] = {
+	{.compatible = "mediatek,common-emicen", .data = (void *)1 },
+	{.compatible = "mediatek,mt6779-emicen", .data = (void *)1 },
+	{}
+};
+
+static struct platform_driver emicen_drv = {
+	.probe = emicen_probe,
+	.remove = emicen_remove,
+	.driver = {
+		.name = "emicen_drv",
+		.owner = THIS_MODULE,
+		.of_match_table = emicen_of_ids,
+	},
+};
+
+__weak int emiisu_init(void)
+{
+	return 0;
+}
+
+static __init int emicen_init(void)
+{
+	int ret;
+
+	pr_info("emicen was loaded\n");
+
+	ret = platform_driver_register(&emicen_drv);
+	if (ret) {
+		pr_err("emicen: failed to register driver\n");
+		return ret;
+	}
+
+	ret = driver_create_file(&emicen_drv.driver,
+				 &driver_attr_emicen_addr2dram);
+	if (ret) {
+		pr_err("emicen: failed to create addr2dram file\n");
+		return ret;
+	}
+
+	ret = emiisu_init();
+	if (ret) {
+		pr_err("emicen: failed to init isu\n");
+		return ret;
+	}
+
+#if defined(MODULE)
+	emimpu_ap_region_init();
+#endif
+
+	return 0;
+}
+
+module_init(emicen_init);
+
+MODULE_DESCRIPTION("MediaTek EMI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/memory/mediatek/emi-mpu.c b/drivers/memory/mediatek/emi-mpu.c
new file mode 100644
index 0000000..3cfe9a1
--- /dev/null
+++ b/drivers/memory/mediatek/emi-mpu.c
@@ -0,0 +1,908 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 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>
+
+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();
+
+	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 on success, -EINVAL if rg_info or rg_num is invalid,
+ * -ENODEV if the emi-mpu driver is not probed successfully,
+ * -ENOMEM if out of memory
+ */
+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;
+
+	if (rg_info)
+		memset(rg_info, 0, sizeof(struct emimpu_region_t));
+	else
+		return -EINVAL;
+
+	mpu = global_emi_mpu;
+	if (!mpu)
+		return -ENODEV;
+
+	if (rg_num >= mpu->region_cnt) {
+		pr_info("%s: fail, out-of-range region\n", __func__);
+		return -EINVAL;
+	}
+
+	size = sizeof(unsigned int) * mpu->domain_cnt;
+	rg_info->apc = kmalloc(size, GFP_KERNEL);
+	if (!(rg_info->apc))
+		return -ENOMEM;
+	for (i = 0; i < mpu->domain_cnt; i++)
+		rg_info->apc[i] = MTK_EMIMPU_FORBIDDEN;
+
+	rg_info->rg_num = rg_num;
+
+	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 on success, -EINVAL if rg_info is invalid
+ */
+int mtk_emimpu_free_region(struct emimpu_region_t *rg_info)
+{
+	if (rg_info && rg_info->apc) {
+		kfree(rg_info->apc);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+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 on success, -EINVAL if rg_info is invalid
+ */
+int mtk_emimpu_set_addr(struct emimpu_region_t *rg_info,
+			unsigned long long start, unsigned long long end)
+{
+	if (rg_info) {
+		rg_info->start = start;
+		rg_info->end = end;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+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 on success, -EINVAL if rg_info or d_num is invalid,
+ * -ENODEV if the emi-mpu driver is not probed successfully
+ */
+int mtk_emimpu_set_apc(struct emimpu_region_t *rg_info,
+		       unsigned int d_num, unsigned int apc)
+{
+	struct emi_mpu *mpu;
+
+	if (!rg_info)
+		return -EINVAL;
+
+	mpu = global_emi_mpu;
+	if (!mpu)
+		return -ENODEV;
+
+	if (d_num >= mpu->domain_cnt) {
+		pr_info("%s: fail, out-of-range domain\n", __func__);
+		return -EINVAL;
+	}
+
+	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 on success, -EINVAL if rg_info is invalid
+ */
+int mtk_emimpu_lock_region(struct emimpu_region_t *rg_info, bool lock)
+{
+	if (rg_info) {
+		rg_info->lock = lock;
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+EXPORT_SYMBOL(mtk_emimpu_lock_region);
+
+/*
+ * mtk_emimpu_set_protection - set emimpu protect into device
+ * @rg_info:	the target region information
+ *
+ * Returns 0 on success, -EINVAL if rg_info is invalid,
+ * -ENODEV if the emi-mpu driver is not probed successfully,
+ * -EPERM if the SMC call returned failure
+ */
+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;
+
+	if (!rg_info)
+		return -EINVAL;
+
+	if (!(rg_info->apc)) {
+		pr_info("%s: fail, protect without init\n", __func__);
+		return -EINVAL;
+	}
+
+	mpu = global_emi_mpu;
+	if (!mpu)
+		return -ENODEV;
+
+	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 -EPERM;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mtk_emimpu_set_protection);
+
+/*
+ * mtk_emimpu_clear_protection - clear emimpu protection
+ * @rg_info:	the target region information
+ *
+ * Returns 0 on success, -EINVAL if rg_info is invalid,
+ * -ENODEV if the emi-mpu driver is not probed successfully,
+ * -EPERM if the SMC call returned failure
+ */
+int mtk_emimpu_clear_protection(struct emimpu_region_t *rg_info)
+{
+	struct emi_mpu *mpu;
+	struct arm_smccc_res smc_res;
+
+	if (!rg_info)
+		return -EINVAL;
+
+	mpu = global_emi_mpu;
+	if (!mpu)
+		return -ENODEV;
+
+	if (rg_info->rg_num > mpu->region_cnt) {
+		pr_info("%s: region %u overflow\n", __func__, rg_info->rg_num);
+		return -EINVAL;
+	}
+
+	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 -EPERM;
+	}
+	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(*targ_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",},
+	{.compatible = "mediatek,mt6779-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(*mpu->ap_rg_info),
+					  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");
diff --git a/include/linux/soc/mediatek/mtk_sip_svc.h b/include/linux/soc/mediatek/mtk_sip_svc.h
index 082398e..65a959a 100644
--- a/include/linux/soc/mediatek/mtk_sip_svc.h
+++ b/include/linux/soc/mediatek/mtk_sip_svc.h
@@ -21,5 +21,8 @@
 #define MTK_SIP_SMC_CMD(fn_id) \
 	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, MTK_SIP_SMC_CONVENTION, \
 			   ARM_SMCCC_OWNER_SIP, fn_id)
+/* EMI MPU */
+#define MTK_SIP_EMIMPU_CONTROL \
+	MTK_SIP_SMC_CMD(0x50B)
 
 #endif
diff --git a/include/soc/mediatek/emi.h b/include/soc/mediatek/emi.h
new file mode 100644
index 0000000..e766a7f
--- /dev/null
+++ b/include/soc/mediatek/emi.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 MediaTek Inc.
+ */
+
+#ifndef __EMI_H__
+#define __EMI_H__
+
+#include <linux/irqreturn.h>
+
+#define MTK_EMIMPU_NO_PROTECTION	0
+#define MTK_EMIMPU_SEC_RW		1
+#define MTK_EMIMPU_SEC_RW_NSEC_R	2
+#define MTK_EMIMPU_SEC_RW_NSEC_W	3
+#define MTK_EMIMPU_SEC_R_NSEC_R		4
+#define MTK_EMIMPU_FORBIDDEN		5
+#define MTK_EMIMPU_SEC_R_NSEC_RW	6
+
+#define MTK_EMIMPU_UNLOCK		false
+#define MTK_EMIMPU_LOCK			true
+
+#define MTK_EMIMPU_SET			0
+#define MTK_EMIMPU_CLEAR		1
+#define MTK_EMIMPU_READ			2
+#define MTK_EMIMPU_SLVERR		3
+#define MTK_EMIDBG_DUMP			4
+#define MTK_EMIDBG_MSG			5
+
+#define MTK_EMIMPU_READ_SA		0
+#define MTK_EMIMPU_READ_EA		1
+#define MTK_EMIMPU_READ_APC		2
+
+#define MTK_EMI_MAX_TOKEN		4
+#define MTK_EMI_MAX_CMD_LEN		4096
+
+struct emi_addr_map {
+	int emi;
+	int channel;
+	int rank;
+	int bank;
+	int row;
+	int column;
+};
+
+struct reg_info_t {
+	unsigned int offset;
+	unsigned int value;
+	unsigned int leng;
+};
+
+struct emimpu_region_t {
+	unsigned long long start;
+	unsigned long long end;
+	unsigned int rg_num;
+	bool lock;
+	unsigned int *apc;
+};
+
+typedef irqreturn_t (*emimpu_pre_handler)(unsigned int emi_id,
+					  struct reg_info_t *dump,
+					  unsigned int leng);
+typedef void (*emimpu_post_clear)(unsigned int emi_id);
+typedef void (*emimpu_md_handler)(unsigned int emi_id,
+				  struct reg_info_t *dump,
+				  unsigned int leng);
+typedef void (*emimpu_debug_dump)(void);
+
+struct emimpu_dbg_cb {
+	emimpu_debug_dump func;
+	struct emimpu_dbg_cb *next_dbg_cb;
+};
+
+/* mtk emicen api */
+unsigned int mtk_emicen_get_ch_cnt(void);
+unsigned int mtk_emicen_get_rk_cnt(void);
+unsigned int mtk_emicen_get_rk_size(unsigned int rk_id);
+int mtk_emicen_addr2dram(unsigned long addr, struct emi_addr_map *map);
+
+/* mtk emidbg api */
+void mtk_emidbg_dump(void);
+
+/* mtk emimpu api */
+int emimpu_ap_region_init(void);
+int mtk_emimpu_init_region(struct emimpu_region_t *rg_info,
+			   unsigned int rg_num);
+int mtk_emimpu_set_addr(struct emimpu_region_t *rg_info,
+			unsigned long long start, unsigned long long end);
+int mtk_emimpu_set_apc(struct emimpu_region_t *rg_info,
+		       unsigned int d_num, unsigned int apc);
+int mtk_emimpu_lock_region(struct emimpu_region_t *rg_info, bool lock);
+int mtk_emimpu_set_protection(struct emimpu_region_t *rg_info);
+int mtk_emimpu_free_region(struct emimpu_region_t *rg_info);
+int mtk_emimpu_clear_protection(struct emimpu_region_t *rg_info);
+int mtk_emimpu_prehandle_register(emimpu_pre_handler bypass_func);
+int mtk_emimpu_postclear_register(emimpu_post_clear clear_func);
+int mtk_emimpu_md_handling_register(emimpu_md_handler md_handling_func);
+int mtk_emimpu_debugdump_register(emimpu_debug_dump debug_func);
+void mtk_clear_md_violation(void);
+
+#endif /* __EMI_H__ */
+
