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__ */
+--
1.9.1