[coreboot-gerrit] Patch set updated for coreboot: mediatek/mt8173: Add EMI driver, DRAM initialization

Patrick Georgi (pgeorgi@google.com) gerrit at coreboot.org
Fri Jan 22 21:35:46 CET 2016


Patrick Georgi (pgeorgi at google.com) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/13105

-gerrit

commit cf14cab270146df9e4924611908092357cd8893a
Author: Peter Kao <peter.kao at mediatek.com>
Date:   Fri Jul 31 17:11:14 2015 +0800

    mediatek/mt8173: Add EMI driver, DRAM initialization
    
    BUG=none
    TEST=emerge-oak coreboot
    BRANCH=none
    
    Change-Id: I6b05898de2d0022e0de7b18f1db3c3e9c06d8135
    Signed-off-by: Patrick Georgi <pgeorgi at chromium.org>
    Original-Commit-Id: b614eeb1bba5660438c214e82225832809caca8e
    Original-Change-Id: I0f7b0a426dae1548b34114a024c92befdf6002f6
    Original-Signed-off-by: Peter Kao <peter.kao at mediatek.com>
    Original-Reviewed-on: https://chromium-review.googlesource.com/292692
    Original-Commit-Ready: Yidi Lin <yidi.lin at mediatek.com>
    Original-Tested-by: Yidi Lin <yidi.lin at mediatek.com>
    Original-Reviewed-by: Julius Werner <jwerner at chromium.org>
---
 src/soc/mediatek/mt8173/Kconfig                    |    4 +
 src/soc/mediatek/mt8173/Makefile.inc               |    3 +
 src/soc/mediatek/mt8173/dramc_pi_basic_api.c       |  879 +++++++++++++++
 src/soc/mediatek/mt8173/dramc_pi_calibration_api.c | 1150 ++++++++++++++++++++
 src/soc/mediatek/mt8173/emi.c                      |  142 +++
 src/soc/mediatek/mt8173/include/soc/dramc_common.h |   42 +
 src/soc/mediatek/mt8173/include/soc/dramc_pi_api.h |  187 ++++
 .../mediatek/mt8173/include/soc/dramc_register.h   |  522 +++++++++
 src/soc/mediatek/mt8173/include/soc/emi.h          |  140 +++
 src/soc/mediatek/mt8173/include/soc/pll.h          |    5 +
 src/soc/mediatek/mt8173/memory.c                   |  357 ++++++
 src/soc/mediatek/mt8173/pll.c                      |   26 +
 12 files changed, 3457 insertions(+)

diff --git a/src/soc/mediatek/mt8173/Kconfig b/src/soc/mediatek/mt8173/Kconfig
index b44c360..ec192e5 100644
--- a/src/soc/mediatek/mt8173/Kconfig
+++ b/src/soc/mediatek/mt8173/Kconfig
@@ -18,6 +18,10 @@ config SOC_MEDIATEK_MT8173
 
 if SOC_MEDIATEK_MT8173
 
+config MEMORY_TEST
+	bool
+	default n
+
 config DEBUG_SOC_DRIVER
 	bool "The top level switch for soc driver debug message"
 	default n
diff --git a/src/soc/mediatek/mt8173/Makefile.inc b/src/soc/mediatek/mt8173/Makefile.inc
index fa344c0..7b6dc75 100644
--- a/src/soc/mediatek/mt8173/Makefile.inc
+++ b/src/soc/mediatek/mt8173/Makefile.inc
@@ -43,6 +43,7 @@ verstage-$(CONFIG_SPI_FLASH) += flash_controller.c
 ################################################################################
 
 romstage-$(CONFIG_SPI_FLASH) += flash_controller.c
+romstage-y += pll.c
 romstage-y += timer.c
 
 romstage-$(CONFIG_DRIVERS_UART) += uart.c
@@ -50,6 +51,8 @@ romstage-y += cbmem.c
 romstage-y += spi.c
 romstage-y += gpio.c
 romstage-y += pmic_wrap.c mt6391.c
+romstage-y += memory.c
+romstage-y += emi.c dramc_pi_basic_api.c dramc_pi_calibration_api.c
 romstage-y += mmu_operations.c
 romstage-y += rtc.c
 
diff --git a/src/soc/mediatek/mt8173/dramc_pi_basic_api.c b/src/soc/mediatek/mt8173/dramc_pi_basic_api.c
new file mode 100644
index 0000000..ac28373
--- /dev/null
+++ b/src/soc/mediatek/mt8173/dramc_pi_basic_api.c
@@ -0,0 +1,879 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <arch/barrier.h>
+#include <arch/io.h>
+#include <assert.h>
+#include <console/console.h>
+#include <delay.h>
+#include <soc/addressmap.h>
+#include <soc/dramc_common.h>
+#include <soc/dramc_register.h>
+#include <soc/dramc_pi_api.h>
+#include <soc/emi.h>
+#include <soc/mt6391.h>
+#include <soc/pll.h>
+#include <soc/spm.h>
+#include <string.h>
+#include <types.h>
+
+struct mem_pll {
+	u8 delay;
+	u8 phase;
+	u8 done;
+};
+
+inline u8 is_dual_rank(u32 channel,
+		       const struct mt8173_sdram_params *sdram_params)
+{
+	/* judge ranks from EMI_CONA[17] (cha) and EMI_CONA[16] (chb) */
+        return (sdram_params->emi_set.cona & (1 << (17 - channel))) ? 1 : 0;
+}
+
+static void mem_pll_pre_init(u32 channel)
+{
+	write32(&ch[channel].ddrphy_regs->lpddr2_3, 0x1 << 29 | 0x1 << 25 |
+						    0xf << 16 | 0xffff);
+
+	write32(&ch[channel].ddrphy_regs->lpddr2_4, 0x1 << 29 | 0x1 << 25 |
+						    0xf << 16 | 0xffff);
+
+	/* adjust DQS/DQM phase to get best margin */
+	write32(&ch[channel].ddrphy_regs->selph12, 0x1 << 28 | 0xf << 20 |
+						   0x1 << 12 | 0xf << 4);
+	/* adjust DQ phase to get best margin */
+	write32(&ch[channel].ddrphy_regs->selph13, 0xffffffff << 0);
+	write32(&ch[channel].ddrphy_regs->selph14, 0xffffffff << 0);
+
+	/* fix OCV effect */
+	write32(&ch[channel].ddrphy_regs->selph15, 0x1 << 4 | 0xf << 0);
+
+	/* pll register control by CPU and select internal pipe path */
+	write32(&ch[channel].ddrphy_regs->peri[2], 0x11 << 24 | 0x11 << 16 |
+						   0xff << 8  | 0x11 << 0);
+	write32(&ch[channel].ddrphy_regs->peri[3], 0x11 << 24 | 0x51 << 16 |
+						   0x11 << 8  | 0x11 << 0);
+
+	/* enable clock sync and spm control clock */
+	write32(&ch[channel].ddrphy_regs->mempll_divider, 0x9 << 24 |
+							  0x1 << 15 |
+							  0x2 << 4  |
+							  0x1 << 1  |
+							  0x1 << 0);
+	/* pll2 enable from CPU control */
+	write32(&ch[channel].ddrphy_regs->mempll05_divider, 0x1 << 27);
+
+	/* enable chip top memory clock */
+	setbits_le32(&ch[channel].ddrphy_regs->mempll_divider, 0x1 << 4);
+
+	/* disable C/A and DQ M_CK clock gating */
+	clrbits_le32(&ch[channel].ddrphy_regs->ddrphy_cg_ctrl, 0x1 << 2 |
+							       0x1 << 1);
+
+	/* enable spm control clock */
+	clrbits_le32(&ch[channel].ddrphy_regs->mempll_divider, 0x1 << 15 |
+							       0x1 << 0);
+	/* enable dramc 2X mode */
+	setbits_le32(&ch[channel].ao_regs->ddr2ctl, 1 << 0);
+
+	/* select internal clock path */
+	write32(&ch[channel].ddrphy_regs->peri[0], 0x21 << 24 | 0x27 << 16 |
+						   0x1b << 8  | 0x3 << 0);
+
+	write32(&ch[channel].ddrphy_regs->peri[1], 0x50 << 24 | 0x96 << 16 |
+						   0x6 << 8   | 0x1e << 0);
+
+	/* trigger to make memory clock correct phase */
+	setbits_le32(&ch[channel].ddrphy_regs->mempll_divider, 0x1 << 24 |
+							       0x1 << 7);
+
+	if(channel == CHANNEL_A) {
+		/* select memory clock sync for channel A (internal source) */
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll_divider, 0x1 << 3);
+	}
+}
+
+static void mem_pll_init_set_params(u32 channel)
+{
+	u32 pattern1, pattern2, pattern3;
+	u32 mempll_ic_3_0, mempll_bp_3_0;
+	u32 mempll_fbdiv_6_0, mempll_m4pdiv_1_0;
+	u32 mempll_br_1_0, mempll_bc_1_0, mempll_ir_3_0;
+
+	mempll_fbdiv_6_0 = 0x7 << 16;
+	mempll_br_1_0 = 0x1 << 10;
+	mempll_bc_1_0 = 0x0 << 8;
+	mempll_ir_3_0 = 0xc << 28;
+	mempll_ic_3_0 = 0x6 << 8;
+	mempll_bp_3_0 = 0x1 << 12;
+	mempll_m4pdiv_1_0 = 0x0 << 28;
+
+	write32(&ch[channel].ddrphy_regs->mempll[14], 0x0);
+
+	write32(&ch[channel].ddrphy_regs->mempll[3], 0x3 << 30 |
+						     0x1 << 28);
+	/* mempll 2 config */
+	pattern1 = mempll_ir_3_0 | mempll_fbdiv_6_0 | mempll_ic_3_0;
+	pattern2 = mempll_m4pdiv_1_0;
+	pattern3 = mempll_bp_3_0 | mempll_br_1_0 | mempll_bc_1_0;
+
+	/* mempll2_autok_en = 1, mempll2_autok_load = 1 */
+	write32(&ch[channel].ddrphy_regs->mempll[5], 0x1 << 26 | 0x3 << 24 |
+						     0x1 << 23 | pattern1);
+	write32(&ch[channel].ddrphy_regs->mempll[6], 0x1 << 30 | 0x3 << 26 |
+						     0x3 << 14 | pattern2);
+	write32(&ch[channel].ddrphy_regs->mempll[7], 0x1 << 17 | 0x1 << 0  |
+						     pattern3);
+	/* mempll 4 */
+	write32(&ch[channel].ddrphy_regs->mempll[11], 0x1 << 26 | 0x3 << 24 |
+						      0x1 << 23 | pattern1);
+	write32(&ch[channel].ddrphy_regs->mempll[12], 0x1 << 30 | 0x3 << 26 |
+						      0x3 << 14 | pattern2);
+	write32(&ch[channel].ddrphy_regs->mempll[13], 0x1 << 0  | pattern3);
+
+	/* mempll 3 - enable signal tie together */
+	write32(&ch[channel].ddrphy_regs->mempll[8], 0x1 << 26 | 0x3 << 24 |
+						     0x1 << 23 | pattern1);
+	write32(&ch[channel].ddrphy_regs->mempll[9], 0x1 << 30 | 0x3 << 26 |
+						     0x3 << 14 | pattern2);
+	write32(&ch[channel].ddrphy_regs->mempll[10], 0x1 << 17 | 0x1 << 0 |
+						      pattern3);
+}
+
+static void mem_pll_init_phase_sync(u32 channel)
+{
+	write32(&ch[channel].ddrphy_regs->mempll_divider, BIT(27) | BIT(24) |
+							  BIT(7)  | BIT(5)  |
+							  BIT(4)  | BIT(0));
+	/* spm control clock enable */
+	clrsetbits_le32(&ch[channel].ddrphy_regs->mempll_divider, BIT(0),
+								  BIT(1));
+
+	clrsetbits_le32(&ch[channel].ddrphy_regs->mempll_divider, BIT(1),
+								  BIT(0));
+}
+
+static void pll_phase_adjust(u32 channel, struct mem_pll *mempll, int reg_offs)
+{
+	switch (mempll->phase) {
+
+	case MEMPLL_INIT:
+		/* initial phase: zero out RG_MEPLL(2,3,4)_(REF_DL,FB)_DL */
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[reg_offs],
+			     0x1f << MEMPLL_REF_DL_SHIFT |
+			     0x1f << MEMPLL_FB_DL_SHIFT);
+		break;
+
+	case MEMPLL_REF_LAG:
+		/* REF lag FBK, delay FBK */
+		clrsetbits_le32(&ch[channel].ddrphy_regs->mempll[reg_offs],
+				0x1f << MEMPLL_REF_DL_SHIFT |
+				0x1f << MEMPLL_FB_DL_SHIFT,
+				mempll->delay << MEMPLL_FB_DL_SHIFT);
+		break;
+
+	case MEMPLL_REF_LEAD:
+		/* REF lead FBK, delay REF */
+		clrsetbits_le32(&ch[channel].ddrphy_regs->mempll[reg_offs],
+				0x1f << MEMPLL_REF_DL_SHIFT |
+				0x1f << MEMPLL_FB_DL_SHIFT,
+				mempll->delay << MEMPLL_REF_DL_SHIFT);
+	};
+}
+
+static void pll_phase_check(u32 channel, struct mem_pll *mempll, int idx)
+{
+	u32 value = read32(&ch[channel].ddrphy_regs->jmeter_pll_st[idx]);
+	u16 one_count = (u16)((value >> 16) & 0xffff);
+	u16 zero_count = (u16)(value & 0xffff);
+
+	dramc_dbg_msg("PLL %d, phase %d, one_count %d, zero_count %d\n",
+		      (idx + 2), mempll->phase, one_count, zero_count);
+
+	switch (mempll->phase) {
+
+	case MEMPLL_INIT:
+		if ((one_count - zero_count) > JMETER_COUNT_N) {
+			/* REF lag FBK */
+			mempll->phase = MEMPLL_REF_LAG;
+			mempll->delay++;
+		} else if ((zero_count - one_count) > JMETER_COUNT_N) {
+			/* REF lead FBK */
+			mempll->phase = MEMPLL_REF_LEAD;
+			mempll->delay++;
+		} else {
+			/* in-phase at initial */
+			mempll->done = 1;
+		}
+		break;
+
+	case MEMPLL_REF_LAG:
+		if (JMETER_COUNT_N >= (one_count - zero_count)) {
+			mempll->done = 1;
+		} else {
+			mempll->delay++;
+		}
+		break;
+
+	case MEMPLL_REF_LEAD:
+		if (JMETER_COUNT_N >= (zero_count - one_count)) {
+			mempll->done = 1;
+		} else {
+			mempll->delay++;
+		}
+	}
+}
+
+static void mem_pll_phase_cali(u32 channel)
+{
+	u32 i;
+
+	struct mem_pll mempll[3] =
+	{
+		{0, 0, 0},
+		{0, 0, 0},
+		{0, 0, 0},
+	};
+
+	dramc_dbg_msg("[PLL_Phase_Calib] ===== PLL Phase Calibration: ");
+	dramc_dbg_msg("CHANNEL %d (0: CHA, 1: CHB) =====\n", channel);
+
+	/* 1. set jitter meter count number to 1024 for mempll 2 3 4 */
+	for (i = 0; i < 3; i++)
+		clrsetbits_le32(&ch[channel].ddrphy_regs->jmeter[i],
+				JMETER_COUNTER_MASK,
+				JMETER_COUNT << JMETER_COUNTER_SHIFT);
+
+	while (1) {
+
+		for (i = 0; i < 3; i++) {
+			if (!mempll[i].done) {
+				pll_phase_adjust(channel, &mempll[i], (i + 2) * 3);
+			}
+		}
+
+		udelay(20); /* delay 20us for external loop pll stable */
+
+		/* 2. enable mempll 2 3 4 jitter meter */
+		for (i = 0; i < 3; i++)
+			setbits_le32(&ch[channel].ddrphy_regs->jmeter[i],
+				     JMETER_EN_BIT);
+
+		/* 3. wait for jitter meter complete */
+		udelay(JMETER_WAIT_DONE_US);
+
+		/* 4. check jitter meter counter value for mempll 2 3 4 */
+		for (i = 0; i < 3; i++) {
+			if (!mempll[i].done) {
+				pll_phase_check(channel, &mempll[i], i);
+			}
+		}
+
+		/* 5. disable mempll 2 3 4 jitter meter */
+		for (i = 0; i < 3; i++)
+			clrbits_le32(&ch[channel].ddrphy_regs->jmeter[i],
+				     JMETER_EN_BIT);
+
+		/* 6. all done early break */
+		if (mempll[0].done && mempll[1].done && mempll[2].done)
+			break;
+
+		/* 7. delay line overflow break */
+		for (i = 0; i < 3; i++) {
+			if(mempll[i].delay >= 32) {
+				die("MEMPLL calibration fail\n");
+			}
+		}
+	}
+
+	dramc_dbg_msg("pll done: ");
+
+	dramc_dbg_msg("%d, %d, %d\n",
+		      mempll[0].done, mempll[1].done, mempll[2].done);
+	dramc_dbg_msg("pll dl: %d, %d, %d\n",
+		      mempll[0].delay, mempll[1].delay, mempll[2].delay);
+}
+
+void mem_pll_init(const struct mt8173_sdram_params *sdram_params)
+{
+	u32 channel;
+
+	/* udelay waits for PLL to stabilize in this function */
+	printk(BIOS_DEBUG, "[PLL] mempll_init and cali\n");
+
+	/* mempll pre_init for two channels */
+	for (channel = 0; channel < CHANNEL_NUM; channel++)
+		mem_pll_pre_init(channel);
+
+	/* only set once in MPLL */
+	mt_mem_pll_config_pre(sdram_params);
+
+	for (channel = 0; channel < CHANNEL_NUM; channel++)
+		mem_pll_init_set_params(channel);
+
+	udelay(1); /* wait after da_mpll_sdm_iso_en goes low */
+
+	/* only set once in MPLL */
+	mt_mem_pll_config_post();
+
+	udelay(100);
+
+	for (channel = 0; channel < CHANNEL_NUM; channel++) {
+
+		/* mempll_bias_en */
+		write32(&ch[channel].ddrphy_regs->mempll[3], 0xd << 28 |
+							     0x1 << 6);
+		udelay(2);
+
+		/* mempll2_en -> mempll4_en -> mempll3_en */
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[5], 1 << 0);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[11], 1 << 0);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[8], 1 << 0);
+
+		udelay(100);
+
+		/* mempll_bias_lpf_en */
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[3], 1 << 7);
+
+		udelay(30);
+
+		/* select mempll4 band register */
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[4], 1 << 26);
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[4], 1 << 26);
+
+		/* PLL ready */
+
+		/* disable mempll2_en -> mempll4_en -> mempll3_en */
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[5], 1 << 0);
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[11], 1 << 0);
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[8], 1 << 0);
+
+		/* disable autok mempll2_en -> mempll4_en -> mempll3_en */
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[5], 1 << 23);
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[11], 1 << 23);
+		clrbits_le32(&ch[channel].ddrphy_regs->mempll[8], 1 << 23);
+
+		udelay(1);
+
+		/* mempll[2->4->3]_fb_mck_sel=1 (switch to outer loop) */
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[6], 1 << 25);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[12], 1 << 25);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[9], 1 << 25);
+
+		udelay(1);
+
+		/* enable mempll2_en -> mempll4_en -> mempll3_en */
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[5], 1 << 0);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[11], 1 << 0);
+		setbits_le32(&ch[channel].ddrphy_regs->mempll[8], 1 << 0);
+	}
+
+	/* mempll new power-on */
+	write32(&mt8173_spm->poweron_config_set, 0x1 << 0 |
+						 SPM_PROJECT_CODE << 16);
+	/* request mempll reset/pdn mode */
+	setbits_le32(&mt8173_spm->power_on_val0, 0x1 << 27);
+
+	udelay(2);
+
+	/* unrequest mempll reset/pdn mode and wait settle */
+	clrbits_le32(&mt8173_spm->power_on_val0, 0x1 << 27);
+
+	udelay(31); /* PLL ready */
+
+	for (channel = 0; channel < CHANNEL_NUM; channel++)
+		mem_pll_init_phase_sync(channel);
+
+	udelay(1);
+
+	/* mempll calibration for two channels */
+	for (channel = 0; channel < CHANNEL_NUM; channel++)
+		mem_pll_phase_cali(channel);
+
+	div2_phase_sync(); /* phase sync for channel B */
+
+	mt_mem_pll_mux();
+}
+
+void dramc_pre_init(u32 channel, const struct mt8173_sdram_params *sdram_params)
+{
+	/* txdly_cs, txdly_cs1 */
+	write32(&ch[channel].ao_regs->selph1, 0x0);
+	/* txdly_dqsgate, txdly_dqsgate_p1 */
+	write32(&ch[channel].ao_regs->selph2, 0x3 << 20 | 0x2 << 12);
+	/* txldy_ra* */
+	write32(&ch[channel].ao_regs->selph3, 0x0);
+	/* txldy_ra* */
+	write32(&ch[channel].ao_regs->selph4, 0x0);
+
+	/* setting of write latency (WL=8) */
+	write32(&ch[channel].ao_regs->selph7, 0x3333 << 16 | 0x3333);
+	write32(&ch[channel].ao_regs->selph8, 0x3333 << 16 | 0x3333);
+	write32(&ch[channel].ao_regs->selph9, 0x3333 << 16 | 0x3333);
+	write32(&ch[channel].ao_regs->selph10, 0x5555 << 16 | 0xffff);
+	write32(&ch[channel].ao_regs->selph11, 0x55 << 16 | 0xff);
+
+	write32(&ch[channel].ao_regs->selph5, 0x1 << 26 | 0x2 << 22 |
+					      0x1 << 20 | 0x5 << 16 |
+					      0x5555);
+
+	write32(&ch[channel].ao_regs->selph6_1, 0x4 << 8 | 0x3 << 4 |
+						0x2 << 0);
+
+	write32(&ch[channel].ao_regs->ac_time_05t,
+		sdram_params->ac_timing.actim05t);
+}
+
+static void mrs_write(int channel, int rank, u32 mrs_value, unsigned int dly)
+{
+	write32(&ch[channel].ao_regs->mrs, rank << 28 | mrs_value);
+
+	write32(&ch[channel].ao_regs->spcmd, 0x1);
+	udelay(dly);
+	write32(&ch[channel].ao_regs->spcmd, 0x0);
+}
+
+static void dramc_set_mrs_value(int channel, int rank,
+			        const struct mt8173_sdram_params *sdram_params)
+{
+	/* MR63 -> Reset, Wait >=10us if not check DAI */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_63, 10);
+	/* MR10 -> ZQ Init, tZQINIT>=1us */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_10, 1);
+	/* MR3 driving stregth set to max */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_3, 1);
+	/* MR1 */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_1, 1);
+	/* MR2 */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_2, 1);
+	/* MR11 ODT disable */
+	mrs_write(channel, rank, sdram_params->mrs_set.mrs_11, 1);
+}
+
+void dramc_init(u32 channel, const struct mt8173_sdram_params *sdram_params)
+{
+	u32 bit, dual_rank_set;
+
+	const struct mt8173_calib_params *calib_params;
+
+	dual_rank_set = is_dual_rank(channel, sdram_params);
+	calib_params = &sdram_params->calib_params;
+
+	write32(&ch[channel].ddrphy_regs->peri[2], 0x1 << 12 |
+						   0x1 << 4);
+
+	write32(&ch[channel].ddrphy_regs->peri[3], 0x0);
+
+	write32(&ch[channel].ao_regs->test2_4,
+		sdram_params->ac_timing.test2_4);
+
+	write32(&ch[channel].ao_regs->clk1delay, 0x1 << 23 |
+						 0x1 << 22 |
+						 0x1 << 21);
+
+	/* rank config */
+	assert((sdram_params->ac_timing.rkcfg & 0x1) == dual_rank_set);
+	write32(&ch[channel].ao_regs->rkcfg,
+		sdram_params->ac_timing.rkcfg);
+
+	/* pimux */
+	write32(&ch[channel].ao_regs->mckdly, 0x1 << 30 |
+					      0x1 << 20 |
+					      0x1 << 4);
+
+	write32(&ch[channel].ddrphy_regs->mckdly, 0x1 << 8);
+
+	write32(&ch[channel].ao_regs->padctl4, 0x1 << 0);
+
+	/* tCKEH/tCKEL extend 1T */
+	write32(&ch[channel].ao_regs->dummy, 0x1 << 31 |
+					     0x3 << 10 |
+					     0x1 << 4);
+
+	/* driving control */
+	write32(&ch[channel].ao_regs->iodrv6, DEFAULT_DRIVING |
+					      DRIVING_DS2_0 << 20 |
+					      DRIVING_DS2_0 << 4);
+
+	write32(&ch[channel].ddrphy_regs->drvctl1, DEFAULT_DRIVING |
+						   DRIVING_DS2_0 << 20);
+
+	write32(&ch[channel].ao_regs->drvctl1, DEFAULT_DRIVING |
+					       DRIVING_DS2_0 << 4);
+
+	/* enable dqs signal output */
+	write32(&ch[channel].ddrphy_regs->ioctl, 0x0);
+
+	/* rank 0 dqs gating delay */
+	write32(&ch[channel].ao_regs->dqsien[0], 0x40 << 24 |
+						 0x40 << 16 |
+						 0x40 << 8  |
+						 0x40 << 0);
+
+	write32(&ch[channel].ao_regs->dqsctl1, 0x1 << 28 |
+					       0x5 << 24);
+
+	write32(&ch[channel].ao_regs->dqsctl2, 0x5 << 0);
+	write32(&ch[channel].ao_regs->phyctl1, 0x1 << 25);
+	write32(&ch[channel].ao_regs->gddr3ctl1, 0x1 << 24);
+	write32(&ch[channel].ddrphy_regs->gddr3ctl1, 0x1 << 28);
+	write32(&ch[channel].ao_regs->arbctl0, 0x80 << 0);
+
+	/* enable clock pad 0 */
+	write32(&ch[channel].ao_regs->clkctl, 0x1 << 28);
+
+	udelay(1);
+
+	write32(&ch[channel].ao_regs->conf1,
+		sdram_params->ac_timing.conf1);
+
+	write32(&ch[channel].ddrphy_regs->dqsgctl, 0x1 << 31 |
+						   0x1 << 30 |
+						   0x1 << 4  |
+						   0x1 << 0);
+
+	write32(&ch[channel].ao_regs->dqscal0, 0x0);
+	write32(&ch[channel].ddrphy_regs->dqscal0, 0x0);
+
+	write32(&ch[channel].ao_regs->actim0,
+		sdram_params->ac_timing.actim);
+
+	write32(&ch[channel].ao_regs->misctl0,
+		sdram_params->ac_timing.misctl0);
+	write32(&ch[channel].ddrphy_regs->misctl0,
+		sdram_params->ac_timing.misctl0);
+
+	write32(&ch[channel].ao_regs->perfctl0, 0x1 << 20);
+
+	write32(&ch[channel].ao_regs->ddr2ctl,
+		sdram_params->ac_timing.ddr2ctl);
+	write32(&ch[channel].ddrphy_regs->ddr2ctl,
+		sdram_params->ac_timing.ddr2ctl);
+
+	write32(&ch[channel].ao_regs->misc, 0xb << 8 |
+					    0x1 << 7 |
+					    0x1 << 6 |
+					    0x1 << 5);
+
+	write32(&ch[channel].ao_regs->dllconf, 0xf << 28 |
+					       0x1 << 24);
+
+	write32(&ch[channel].ao_regs->actim1,
+		sdram_params->ac_timing.actim1);
+
+	write32(&ch[channel].ddrphy_regs->dqsisel, 0x0);
+
+	/* disable ODT before ZQ calibration */
+	write32(&ch[channel].ao_regs->wodt, 0x1 << 0);
+
+	write32(&ch[channel].ao_regs->padctl4, 0x1 << 2 |
+					       0x1 << 0);
+
+	udelay(200); /* tINIT3 > 200us */
+
+	write32(&ch[channel].ao_regs->gddr3ctl1, 0x1 << 24 |
+						 0x1 << 20);
+
+	write32(&ch[channel].ddrphy_regs->gddr3ctl1, 0x1 << 28);
+
+	/* set mode register value */
+	dramc_set_mrs_value(channel, 0, sdram_params);
+
+	if (dual_rank_set)
+		dramc_set_mrs_value(channel, 1, sdram_params);
+
+	write32(&ch[channel].ao_regs->gddr3ctl1,
+		sdram_params->ac_timing.gddr3ctl1);
+	write32(&ch[channel].ddrphy_regs->gddr3ctl1,
+		sdram_params->ac_timing.gddr3ctl1);
+
+	write32(&ch[channel].ao_regs->dramc_pd_ctrl,
+		sdram_params->ac_timing.pd_ctrl);
+
+	write32(&ch[channel].ao_regs->padctl4, 0x1 << 0);
+	write32(&ch[channel].ao_regs->perfctl0, 0x1 << 20 | 0x1 << 0);
+	write32(&ch[channel].ao_regs->zqcs, 0xa << 8 | 0x56 << 0);
+	write32(&ch[channel].ddrphy_regs->padctl1, 0x0);
+
+	write32(&ch[channel].ao_regs->test2_3,
+		sdram_params->ac_timing.test2_3);
+
+	write32(&ch[channel].ao_regs->conf2,
+		sdram_params->ac_timing.conf2);
+
+	write32(&ch[channel].ddrphy_regs->padctl2, 0x0);
+
+	/* DISABLE_DRVREF */
+	write32(&ch[channel].ao_regs->ocdk, 0x0);
+	write32(&ch[channel].ddrphy_regs->ocdk, 0x0);
+
+	write32(&ch[channel].ao_regs->r1deldly, 0x12 << 24 |
+						0x12 << 16 |
+						0x12 << 8  |
+						0x12 << 0);
+
+	write32(&ch[channel].ao_regs->padctl7, 0x0);
+
+	/* CLKTDN, DS0TDN, DS1TDN, DS2TDN, DS3TDN */
+	setbits_le32(&ch[channel].ddrphy_regs->tdsel[2], 0x1 << 31 |
+							 0x1 << 29 |
+							 0x1 << 27 |
+							 0x1 << 25 |
+							 0x1 << 1);
+	/* DISABLE_PERBANK_REFRESH */
+	clrbits_le32(&ch[channel].ao_regs->rkcfg, 0x1 << 7);
+
+	/* clear R_DMREFTHD to reduce MR4 wait refresh queue time */
+	clrbits_le32(&ch[channel].ao_regs->conf2, 0x7 << 24);
+
+	/* duty default value */
+	write32(&ch[channel].ddrphy_regs->phyclkduty, 0x1 << 28 |
+						      0x1 << 16);
+
+	if (!dual_rank_set) {
+		/* single rank, CKE1 always off */
+		setbits_le32(&ch[channel].ao_regs->gddr3ctl1, 0x1 << 21);
+	}
+
+	/* default dqs rx perbit input delay */
+	write32(&ch[channel].ao_regs->r0deldly,
+		calib_params->rx_dqs_dly[channel]);
+
+	write32(&ch[channel].ao_regs->r1deldly,
+		calib_params->rx_dqs_dly[channel]);
+
+	for (bit = 0; bit < DQS_BIT_NUMBER; bit++)
+		write32(&ch[channel].ao_regs->dqidly[bit],
+			calib_params->rx_dq_dly[channel][bit]);
+}
+
+void div2_phase_sync(void)
+{
+	clrbits_le32(&ch[CHANNEL_B].ddrphy_regs->mempll_divider,
+			1 << MEMCLKENB_SHIFT);
+	udelay(1);
+
+	setbits_le32(&ch[CHANNEL_B].ddrphy_regs->mempll_divider,
+			1 << MEMCLKENB_SHIFT);
+}
+
+void dramc_phy_reset(u32 channel)
+{
+	/* reset phy */
+	setbits_le32(&ch[channel].ddrphy_regs->phyctl1,
+		     1 << PHYCTL1_PHYRST_SHIFT);
+
+	/* read data counter reset */
+	setbits_le32(&ch[channel].ao_regs->gddr3ctl1,
+		     1 << GDDR3CTL1_RDATRST_SHIFT);
+
+	udelay(1); /* delay 1ns */
+
+	clrbits_le32(&ch[channel].ao_regs->gddr3ctl1,
+		     1 << GDDR3CTL1_RDATRST_SHIFT);
+
+	clrbits_le32(&ch[channel].ddrphy_regs->phyctl1,
+		     1 << PHYCTL1_PHYRST_SHIFT);
+}
+
+void dramc_runtime_config(u32 channel,
+			  const struct mt8173_sdram_params *sdram_params)
+{
+	/* enable hw gating */
+	setbits_le32(&ch[channel].ao_regs->dqscal0,
+		     1 << DQSCAL0_STBCALEN_SHIFT);
+
+	/* if frequency >1600, tCKE should >7 clk */
+	setbits_le32(&ch[channel].ao_regs->dummy, 0x1 << 4);
+
+	if(sdram_params->dram_freq * 2 < 1600 * MHz)
+		die("set tCKE error in runtime config");
+
+	/* DDRPHY C/A and DQ M_CK clock gating enable */
+	setbits_le32(&ch[channel].ddrphy_regs->ddrphy_cg_ctrl, 0x1 << 2 |
+							       0x1 << 1);
+
+	setbits_le32(&ch[channel].ao_regs->perfctl0, BIT(19) | BIT(14) |
+						     BIT(11) | BIT(10) |
+						     BIT(9)  | BIT(8)  |
+						     BIT(4)  | BIT(0));
+	/* ZQCS_ENABLE */
+	if (sdram_params->emi_set.cona & 0x1) {
+		/* dual channel, clear ZQCSCNT */
+		clrbits_le32(&ch[channel].ao_regs->spcmd, 0xff << 16);
+		/* set ZQCSMASK for different channels */
+		if (channel == CHANNEL_A) {
+			clrbits_le32(&ch[channel].ao_regs->perfctl0, 0x1 << 24);
+		} else {
+			setbits_le32(&ch[channel].ao_regs->perfctl0, 0x1 << 24);
+		}
+		/* enable ZQCSDUAL */
+		setbits_le32(&ch[channel].ao_regs->perfctl0, 0x1 << 25);
+	} else {
+		/* single channel, set ZQCSCNT */
+		setbits_le32(&ch[channel].ao_regs->spcmd, 0x8 << 16);
+	}
+}
+
+void transfer_to_spm_control(void)
+{
+	u32 msk;
+
+	msk = BIT(7) | BIT(11) | BIT(15);
+	clrbits_le32(&mt8173_apmixed->ap_pll_con3, msk);
+
+	msk = BIT(0) | BIT(4) | BIT(8);
+	clrbits_le32(&ch[CHANNEL_A].ddrphy_regs->peri[3], msk);
+
+	msk = BIT(0) | BIT(8);
+	clrbits_le32(&ch[CHANNEL_B].ddrphy_regs->peri[3], msk);
+
+	msk = BIT(0) | BIT(9) | BIT(10) | BIT(11) | BIT(16) | BIT(24);
+	clrbits_le32(&ch[CHANNEL_A].ddrphy_regs->peri[2], msk);
+	clrbits_le32(&ch[CHANNEL_B].ddrphy_regs->peri[2], msk);
+}
+
+void transfer_to_reg_control(void)
+{
+	u32 val;
+
+	val = BIT(7) | BIT(11) | BIT(15);
+	setbits_le32(&mt8173_apmixed->ap_pll_con3, val);
+
+	val = BIT(0) | BIT(4) | BIT(8);
+	setbits_le32(&ch[CHANNEL_A].ddrphy_regs->peri[3], val);
+
+	val = BIT(0) | BIT(8);
+	write32(&ch[CHANNEL_B].ddrphy_regs->peri[3], val);
+
+	val = BIT(0) | BIT(9) | BIT(10) | BIT(11) | BIT(16) | BIT(24);
+	setbits_le32(&ch[CHANNEL_A].ddrphy_regs->peri[2], val);
+	setbits_le32(&ch[CHANNEL_B].ddrphy_regs->peri[2], val);
+}
+
+u32 dramc_engine2(u32 channel, enum dram_tw_op wr, u32 test2_1, u32 test2_2,
+		  u8 testaudpat, u8 log2loopcount)
+{
+	u32 value;
+
+	if (log2loopcount > 15)
+		die("Invalid loopcount of engine2!");
+
+	/* Disable Test Agent1, Test Agent2 write/read */
+	clrbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST1_EN |
+						  CONF2_TEST2R_EN |
+						  CONF2_TEST2W_EN);
+
+	/* 1. set pattern, base address, offset address */
+	write32(&ch[channel].nao_regs->test2_1, test2_1);
+	write32(&ch[channel].nao_regs->test2_2, test2_2);
+
+	/* 2. select test pattern */
+	/*       TESTXTALKPAT | TESTAUDPAT
+	 * ISI              0 | 0
+	 * AUD              0 | 1
+	 * XTALK            1 | 0
+	 * UNKNOW           1 | 1
+	 */
+	switch (testaudpat) {
+	case XTALK:
+		/* TESTAUDPAT = 0 */
+		clrbits_le32(&ch[channel].ao_regs->test2_3,
+			     TEST2_3_TESTAUDPAT_EN);
+		/* TESTXTALKPAT  = 1, select xtalk pattern
+		 * TESTAUDMODE   = 0, read only
+		 * TESTAUDBITINV = 0, no bit inversion
+		 */
+		clrsetbits_le32(&ch[channel].ao_regs->test2_4,
+				TEST2_4_TESTAUDBITINV_EN |
+				TEST2_4_TESTAUDMODE_EN,
+				TEST2_4_TESTXTALKPAT_EN);
+		break;
+	case AUDIO:
+		/* TESTAUDPAT = 1 */
+		setbits_le32(&ch[channel].ao_regs->test2_3,
+			     TEST2_3_TESTAUDPAT_EN);
+		/* TESTXTALKPAT  = 0
+		 * TESTAUDINIT   = 0x11
+		 * TESTAUDINC    = 0x0d
+		 * TESTAUDBITINV = 1
+		 * TESTAUDMODE   = 1
+		 */
+		clrsetbits_le32(&ch[channel].ao_regs->test2_4,
+				TEST2_4_TESTXTALKPAT_EN |
+				TEST2_4_TESTAUDINIT_MASK |
+				TEST2_4_TESTAUDINC_MASK,
+				TEST2_4_TESTAUDMODE_EN |
+				TEST2_4_TESTAUDBITINV_EN |
+				0x11 << TEST2_4_TESTAUDINIT_SHIFT |
+				0xd << TEST2_4_TESTAUDINC_SHIFT);
+
+		break;
+	case ISI:
+		/* TESTAUDPAT = 0 */
+		clrbits_le32(&ch[channel].ao_regs->test2_3,
+			     TEST2_3_TESTAUDPAT_EN);
+		/* TESTXTALKPAT = 0 */
+		clrbits_le32(&ch[channel].ao_regs->test2_4,
+			     TEST2_4_TESTXTALKPAT_EN);
+	}
+
+	/* 3. set loop number */
+	clrsetbits_le32(&ch[channel].ao_regs->test2_3, TEST2_3_TESTCNT_MASK,
+			log2loopcount << TEST2_3_TESTCNT_SHIFT);
+
+	/* 4. enable read/write test */
+	if (wr == TE_OP_READ_CHECK) {
+			if ((testaudpat == 1) || (testaudpat == 2)) {
+			/* if audio pattern, enable read only */
+			/* (disable write after read), */
+			/* AUDMODE=0x48[15]=0 */
+			clrbits_le32(&ch[channel].ao_regs->test2_4,
+				     TEST2_4_TESTAUDMODE_EN);
+		}
+
+		/* enable read, 0x008[30:30] */
+		setbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST2R_EN);
+	} else if (wr == TE_OP_WRITE_READ_CHECK) {
+		/* enable write, 0x008[31:31] */
+		setbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST2W_EN);
+
+		/* check "read data compare ready" bit */
+		do {
+			value = read32(&ch[channel].nao_regs->testrpt);
+		} while ((value & (1 << TESTRPT_DM_CMP_CPT_SHIFT)) == 0);
+
+		/* Disable Test Agent2 write and enable Test Agent2 read */
+		clrbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST2W_EN);
+		setbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST2R_EN);
+	}
+
+	/* 5 check "read data compare ready" bit */
+	do {
+		value = read32(&ch[channel].nao_regs->testrpt);
+	} while ((value & (1 << TESTRPT_DM_CMP_CPT_SHIFT)) == 0);
+
+	/* delay 10ns after ready check from DE suggestion (1us here) */
+	udelay(1);
+
+	/* read CMP_ERR result */
+	value = read32(&ch[channel].nao_regs->cmp_err);
+
+	/* 6 disable read */
+	clrbits_le32(&ch[channel].ao_regs->conf2, CONF2_TEST2R_EN);
+
+	/* return CMP_ERR result, pass: 0, failure: otherwise */
+	return value;
+}
diff --git a/src/soc/mediatek/mt8173/dramc_pi_calibration_api.c b/src/soc/mediatek/mt8173/dramc_pi_calibration_api.c
new file mode 100644
index 0000000..1237b2e
--- /dev/null
+++ b/src/soc/mediatek/mt8173/dramc_pi_calibration_api.c
@@ -0,0 +1,1150 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <delay.h>
+#include <boardid.h>
+#include <arch/io.h>
+#include <stdlib.h>
+#include <soc/addressmap.h>
+#include <soc/dramc_common.h>
+#include <soc/dramc_register.h>
+#include <soc/dramc_pi_api.h>
+#include <soc/emi.h>
+
+static u8 opt_gw_coarse_value[CHANNEL_NUM][DUAL_RANKS];
+static u8 opt_gw_fine_value[CHANNEL_NUM][DUAL_RANKS];
+static s8 wrlevel_dqs_dly[CHANNEL_NUM][DQS_NUMBER];
+
+void sw_impedance_cal(u32 channel,
+		      const struct mt8173_sdram_params *sdram_params)
+{
+	u32 mask, value;
+
+	const struct mt8173_calib_params *params = &sdram_params->calib_params;
+
+	dramc_dbg_msg("[Imp Calibration] DRVP:%d\n", params->impedance_drvp);
+	dramc_dbg_msg("[Imp Calibration] DRVN:%d\n", params->impedance_drvn);
+
+	mask = 0xf << 28 | 0xf << 24 | 0xf << 12 | 0xf << 8; /* driving */
+
+	value =  params->impedance_drvp << 28 | params->impedance_drvn << 24 |
+		 params->impedance_drvp << 12 | params->impedance_drvn << 8;
+
+	/* DQS and DQ */
+	clrsetbits_le32(&ch[channel].ao_regs->iodrv6, mask, value);
+	/* CLK and CMD */
+	clrsetbits_le32(&ch[channel].ao_regs->drvctl1, mask, value);
+	clrsetbits_le32(&ch[channel].ddrphy_regs->drvctl1, mask, value);
+	/* DQ_2 and CMD_2 */
+	clrsetbits_le32(&ch[channel].ao_regs->iodrv4, mask, value);
+	/* disable impcal calibration */
+	clrbits_le32(&ch[channel].ao_regs->impcal, 1 << IMP_CALI_ENP_SHIFT |
+						   1 << IMP_CALI_ENN_SHIFT |
+						   1 << IMP_CALI_EN_SHIFT  |
+						   0xf << IMP_CALI_DRVP_SHIFT |
+						   0xf << IMP_CALI_DRVN_SHIFT);
+}
+
+void ca_training(u32 channel, const struct mt8173_sdram_params *sdram_params)
+{
+	const struct mt8173_calib_params *params = &sdram_params->calib_params;
+
+	u32 i, ca_shift_avg32 = 0;
+	s8 ca_max_center, ca_shift_avg8 = 0, order, ca_shift[CATRAINING_NUM];
+
+	s8 shift[CATRAINING_NUM] = {
+		CMDDLY0_RA2_SHIFT, CMDDLY1_RA7_SHIFT, CMDDLY3_BA0_SHIFT,
+		CMDDLY3_BA1_SHIFT, CMDDLY3_BA2_SHIFT, CMDDLY4_RAS_SHIFT,
+		CMDDLY4_CAS_SHIFT, CMDDLY5_RA13_SHIFT, CMDDLY5_WE_SHIFT
+	};
+
+	s8 ca_order[CHANNEL_NUM][CATRAINING_NUM] = {
+		{ 7, 5, 6, 1, 3, 0, 9, 8, 2, 4},
+		{ 2, 0, 3, 7, 5, 9, 4, 1, 6, 8}
+	};
+
+	s8 cmd_order[CATRAINING_NUM] = {
+		0, 1, 3, 3, 3, 4, 4, 5, 5
+	};
+
+	for(i = 0; i < CATRAINING_NUM; i++) {
+		ca_shift[i] = params->ca_train[channel][i];
+		ca_shift_avg8 += ca_shift[i];
+	}
+
+	/* CA pins align the center */
+	ca_max_center = params->ca_train_center[channel];
+
+	/* set CA pins output delay */
+	for (i = 0; i < (CATRAINING_NUM - 1); i++) {
+		order = ca_order[channel][i];
+		clrsetbits_le32(&ch[channel].ddrphy_regs->cmddly[cmd_order[i]],
+				0xf << shift[i], ca_shift[order] << shift[i]);
+	}
+
+	order = ca_order[channel][9];
+	clrsetbits_le32(&ch[channel].ddrphy_regs->dqscal0,
+			0xf << DQSCAL0_RA14_SHIFT,
+			ca_shift[order] << DQSCAL0_RA14_SHIFT);
+
+	/* CKE and CS delay */
+	ca_shift_avg32 = (u32)(ca_shift_avg8 + (CATRAINING_NUM >> 1));
+	ca_shift_avg32 /= (u32) CATRAINING_NUM;
+
+	/* CKEDLY */
+	clrsetbits_le32(&ch[channel].ddrphy_regs->cmddly[4],
+			0x1f << CMDDLY4_CS_SHIFT |
+			0x1f << CMDDLY4_CKE_SHIFT,
+			ca_shift_avg32 << CMDDLY4_CS_SHIFT |
+			ca_shift_avg32 << CMDDLY4_CKE_SHIFT);
+
+	/* CKE1DLY */
+	clrsetbits_le32(&ch[channel].ao_regs->dqscal1,
+			0x1f << DQSCAL1_CKE1_SHIFT,
+			ca_shift_avg32 << DQSCAL1_CKE1_SHIFT);
+
+	/* CS1DLY */
+	clrsetbits_le32(&ch[channel].ddrphy_regs->padctl1,
+			0xf << PADCTL1_CS1_SHIFT,
+			ca_shift_avg32 << PADCTL1_CS1_SHIFT);
+
+	/* set max center into clk output delay */
+	clrsetbits_le32(&ch[channel].ddrphy_regs->padctl1,
+			0xf << PADCTL1_CLK_SHIFT,
+			ca_max_center << PADCTL1_CLK_SHIFT);
+
+	dramc_dbg_msg("=========================================\n");
+	dramc_dbg_msg("   [Channel %d] CA training\n", channel);
+	dramc_dbg_msg("=========================================\n");
+
+	for (i = 0; i < CATRAINING_NUM; i++)
+		dramc_dbg_msg("[CA] CA %d\tShift %d\n", i, ca_shift[i]);
+
+	dramc_dbg_msg("[CA] Reg CMDDLY4 = %xh\n",
+			read32(&ch[channel].ddrphy_regs->cmddly[4]));
+	dramc_dbg_msg("[CA] Reg DQSCAL1 = %xh\n",
+			read32(&ch[channel].ao_regs->dqscal1));
+	dramc_dbg_msg("[CA] Reg PADCTL1 = %xh\n",
+			read32(&ch[channel].ddrphy_regs->padctl1));
+}
+
+void write_leveling(u32 channel, const struct mt8173_sdram_params *sdram_params)
+{
+	u8 i, byte_i;
+	u32 value;
+
+	for (i = 0; i < DQS_NUMBER; i++)
+		wrlevel_dqs_dly[channel][i] =
+			sdram_params->calib_params.wr_level[channel][i];
+	/* DQS */
+	value = 0;
+	for (i = 0; i < DQS_NUMBER; i++) {
+		value += ((u32)wrlevel_dqs_dly[channel][i]) << (4 * i);
+	}
+	write32(&ch[channel].ddrphy_regs->padctl3, value);
+
+	/* DQM */
+	clrsetbits_le32(&ch[channel].ddrphy_regs->padctl2, MASK_PADCTL2_32BIT,
+			(value << PADCTL2_SHIFT) & MASK_PADCTL2_32BIT);
+
+	/* DQ */
+	for (byte_i = 0; byte_i < DQS_NUMBER; byte_i++) {
+		value = 0;
+		for (i = 0; i < DQS_BIT_NUMBER; i++) {
+			s8 val = wrlevel_dqs_dly[channel][byte_i];
+			value += (((u32)val) << (4 * i));
+		}
+		write32(&ch[channel].ddrphy_regs->dqodly[byte_i], value);
+	}
+
+	dramc_dbg_msg("========================================\n");
+	dramc_dbg_msg("[Channel %d] dramc_write_leveling_swcal\n", channel);
+	dramc_dbg_msg("========================================\n");
+
+	dramc_dbg_msg("[WL] DQS: %#x",
+			read32(&ch[channel].ddrphy_regs->padctl3));
+	dramc_dbg_msg("[WL] DQM: %#x\n",
+			read32(&ch[channel].ddrphy_regs->padctl2));
+
+	for (byte_i = 0; byte_i < DQS_NUMBER; byte_i++)
+		dramc_dbg_msg("[WL] DQ byte%d: %#x\n", byte_i,
+			     read32(&ch[channel].ddrphy_regs->dqodly[byte_i]));
+}
+
+static void set_gw_coarse_factor(u32 channel, u8 curr_val)
+{
+	u8 curr_val_p1, selph2_dqsgate, selph2_dqsgate_p1;
+
+	u32 coarse_tune_start = curr_val >> 2;
+
+	if (coarse_tune_start > 3) {
+		coarse_tune_start -= 3;
+	} else {
+		if (coarse_tune_start) {
+			coarse_tune_start = 1;
+		}
+	}
+
+	if (coarse_tune_start > 15) {
+		coarse_tune_start = 15;
+	}
+
+	curr_val_p1 = curr_val + 2; /* diff is 0.5T */
+
+	/* Rank 0 P0/P1 coarse tune settings */
+	clrsetbits_le32(&ch[channel].ao_regs->dqsctl1,
+			0xf << DQSCTL1_DQSINCTL_SHIFT,
+			coarse_tune_start << DQSCTL1_DQSINCTL_SHIFT &
+			0xf << DQSCTL1_DQSINCTL_SHIFT);
+
+	/* DQSINCTL does not have P1. */
+	/* Need to use TXDLY_DQSGATE/TXDLY_DQSGATE_P1 to set */
+	/* different 1 M_CK coarse tune values for P0 & P1. */
+	selph2_dqsgate = (curr_val >> 2) - coarse_tune_start;
+	selph2_dqsgate_p1 = (curr_val_p1 >> 2) - coarse_tune_start;
+
+	clrsetbits_le32(&ch[channel].ao_regs->selph2,
+			0x7 << SELPH2_TXDLY_DQSGATE_SHIFT |
+			0x7 << SELPH2_TXDLY_DQSGATE_P1_SHIFT,
+			selph2_dqsgate << SELPH2_TXDLY_DQSGATE_SHIFT |
+			selph2_dqsgate_p1 << SELPH2_TXDLY_DQSGATE_P1_SHIFT);
+
+	/* dly_DQSGATE and dly_DQSGATE_P1 */
+	clrsetbits_le32(&ch[channel].ao_regs->selph5,
+			0x3 << SELPH5_DLY_DQSGATE_SHIFT |
+			0x3 << SELPH5_DLY_DQSGATE_P1_SHIFT,
+			(curr_val & 0x3) << SELPH5_DLY_DQSGATE_SHIFT |
+			(curr_val_p1 & 0x3) << SELPH5_DLY_DQSGATE_P1_SHIFT);
+}
+
+static void set_gw_fine_factor(u32 channel, u8 curr_val, u8 rank)
+{
+	u32 set = curr_val & (0x7f << DQSIEN_DQS0IEN_SHIFT);
+
+	clrsetbits_le32(&ch[channel].ao_regs->dqsien[rank],
+			 0x7f << DQSIEN_DQS0IEN_SHIFT |
+			 0x7f << DQSIEN_DQS1IEN_SHIFT |
+			 0x7f << DQSIEN_DQS2IEN_SHIFT |
+			 0x7f << DQSIEN_DQS3IEN_SHIFT,
+			 set << DQSIEN_DQS0IEN_SHIFT |
+			 set << DQSIEN_DQS1IEN_SHIFT |
+			 set << DQSIEN_DQS2IEN_SHIFT |
+			 set << DQSIEN_DQS3IEN_SHIFT);
+}
+
+static void set_gw_coarse_factor_rank1(u32 channel, u8 curr_val, u8 dqsinctl)
+{
+	u8 curr_val_p1, r1dqsgate, r1dqsgate_p1;
+
+	curr_val_p1 = curr_val + 2; /* diff is 0.5T */
+
+	clrsetbits_le32(&ch[channel].ao_regs->dqsctl2,
+			0xf << DQSCTL2_DQSINCTL_SHIFT,
+			dqsinctl << DQSCTL2_DQSINCTL_SHIFT);
+
+	/* TXDLY_R1DQSGATE and TXDLY_R1DQSGATE_P1 */
+	r1dqsgate = (curr_val >> 2) - dqsinctl;
+	r1dqsgate_p1 = (curr_val_p1 >> 2) - dqsinctl;
+
+	clrsetbits_le32(&ch[channel].ao_regs->selph6_1,
+			0x7 << SELPH6_1_TXDLY_R1DQSGATE_SHIFT |
+			0x7 << SELPH6_1_TXDLY_R1DQSGATE_P1_SHIFT,
+			r1dqsgate << SELPH6_1_TXDLY_R1DQSGATE_SHIFT |
+			r1dqsgate_p1 << SELPH6_1_TXDLY_R1DQSGATE_P1_SHIFT);
+
+	/* dly_R1DQSGATE and dly_R1DQSGATE_P1 */
+	clrsetbits_le32(&ch[channel].ao_regs->selph6_1,
+			0x3 << SELPH6_1_DLY_R1DQSGATE_SHIFT |
+			0x3 << SELPH6_1_DLY_R1DQSGATE_P1_SHIFT,
+			(curr_val & 0x3) << SELPH6_1_DLY_R1DQSGATE_SHIFT |
+			(curr_val_p1 & 0x3) << SELPH6_1_DLY_R1DQSGATE_P1_SHIFT);
+}
+
+static void dqs_gw_counter_reset(u32 channel)
+{
+	/* reset dqs counter (1 to 0) */
+	setbits_le32(&ch[channel].ao_regs->spcmd, 1 << SPCMD_DQSGCNTRST_SHIFT);
+	clrbits_le32(&ch[channel].ao_regs->spcmd, 1 << SPCMD_DQSGCNTRST_SHIFT);
+	dramc_phy_reset(channel);
+}
+
+static int dqs_gw_test(u32 channel)
+{
+	u32 coarse_result01, coarse_result23;
+
+	/* read data counter reset in PHY layer */
+	dqs_gw_counter_reset(channel);
+
+	/* use audio pattern to run the test */
+	dramc_engine2(channel, TE_OP_READ_CHECK, DQS_GW_PATTERN2,
+		      DQS_GW_PATTERN1 | DQS_GW_TE_OFFSET, 1, 0);
+
+	/* get coarse result of DQS0, 1, 2, 3 */
+	coarse_result01 = read32(&ch[channel].nao_regs->dqsgnwcnt[0]);
+	coarse_result23 = read32(&ch[channel].nao_regs->dqsgnwcnt[1]);
+
+	if (coarse_result01 == DQS_GW_GOLD_COUNTER_32BIT &&
+	    coarse_result23 == DQS_GW_GOLD_COUNTER_32BIT)
+		return 1;
+
+	return 0;
+}
+
+static u8 dqs_gw_fine_tune_calib(u32 channel, u8 fine_val)
+{
+	u8 i, opt_fine_val;
+	s8 gw_ret[3], delta[3] = {0, -16, 16};
+
+	for (i = 0; i < 3; i++) {
+		/* adjust gw fine tune */
+		opt_fine_val = fine_val + delta[i];
+		set_gw_fine_factor(channel, opt_fine_val, 0);
+		/* get gw test result */
+		gw_ret[i] = dqs_gw_test(channel);
+	}
+
+	/* start fine tune adjustment from default fine value */
+	opt_fine_val = fine_val;
+
+	if (gw_ret[0] && gw_ret[1] && gw_ret[2]) {
+		opt_fine_val += ((delta[0] + delta[1] + delta[2]) / 3);
+	}
+	else if (gw_ret[0] && gw_ret[1]) {
+		opt_fine_val += ((delta[0] + delta[1]) / 2);
+	}
+	else if (gw_ret[0] && gw_ret[2]) {
+		opt_fine_val += ((delta[0] + delta[2]) / 2);
+	}
+	else {  /* abnormal test result, set to default fine tune value */
+		printk(BIOS_ERR, "[GW] ERROR, No found fine tune!!!\n");
+	}
+
+	return opt_fine_val;
+}
+
+static u8 dqs_gw_coarse_tune_calib(u32 channel, u8 coarse_val)
+{
+	u8 i, opt_coarse_val[3];
+	s8 gw_ret[3], delta[3] = {0, 1, -1};
+
+	for (i = 0; i < 3; i++) {
+		/* adjust gw coarse tune value */
+		opt_coarse_val[i] = coarse_val + delta[i];
+		set_gw_coarse_factor(channel, opt_coarse_val[i]);
+		/* get gw test result */
+		gw_ret[i] = dqs_gw_test(channel);
+		/* judge test result */
+		if (gw_ret[i] != 0)
+                        return opt_coarse_val[i];
+	}
+
+	/* abnormal test result, set to default coarse tune value */
+	printk(BIOS_ERR, "[GW] ERROR, No found coarse tune!!!\n");
+
+	return coarse_val;
+}
+
+void rx_dqs_gating_cal(u32 channel, u8 rank,
+		       const struct mt8173_sdram_params *sdram_params)
+{
+	u8 gw_coarse_val, gw_fine_val;
+
+	/* disable HW gating */
+	clrbits_le32(&ch[channel].ao_regs->dqscal0,
+				  1 << DQSCAL0_STBCALEN_SHIFT);
+	/* enable DQS gating window counter */
+	setbits_le32(&ch[channel].ao_regs->dqsctl1,
+				  1 << DQSCTL1_DQSIENMODE_SHIFT);
+	setbits_le32(&ch[channel].ao_regs->spcmd,
+				  1 << SPCMD_DQSGCNTEN_SHIFT);
+	/* dual-phase DQS clock gating control enabling */
+	setbits_le32(&ch[channel].ddrphy_regs->dqsgctl,
+				  1 << DQSGCTL_DQSGDUALP_SHIFT);
+
+	/* gating calibration value */
+	gw_coarse_val = sdram_params->calib_params.gating_win[channel][rank][0];
+	gw_fine_val = sdram_params->calib_params.gating_win[channel][rank][1];
+
+	dramc_dbg_msg("****************************************************\n");
+	dramc_dbg_msg("Channel %d Rank %d DQS GW Calibration\n", channel, rank);
+	dramc_dbg_msg("Default (coarse, fine) tune value %d, %d.\n",
+		       gw_coarse_val, gw_fine_val);
+	dramc_dbg_msg("****************************************************\n");
+
+	/* set default coarse and fine value */
+	set_gw_coarse_factor(channel, gw_coarse_val);
+	set_gw_fine_factor(channel, gw_fine_val, 0);
+
+	/* adjust gw coarse tune */
+	opt_gw_coarse_value[channel][rank] =
+		dqs_gw_coarse_tune_calib(channel, gw_coarse_val);
+
+	/* set adjusted gw coarse tune */
+	set_gw_coarse_factor(channel, opt_gw_coarse_value[channel][rank]);
+
+	/* adjust gw fine tune */
+	opt_gw_fine_value[channel][rank] =
+		dqs_gw_fine_tune_calib(channel, gw_fine_val);
+
+	/* set adjusted gw fine tune */
+	set_gw_fine_factor(channel, opt_gw_fine_value[channel][rank], 0);
+
+	/* read data counter reset in PHY layer */
+	dqs_gw_counter_reset(channel);
+
+	/* gating window training result */
+	printk(BIOS_INFO, "[GW] [Channel %d] [Rank %d] adjusted (coarse, fine) tune value: %d, %d.\n",
+	       channel, rank, opt_gw_coarse_value[channel][rank],
+	       opt_gw_fine_value[channel][rank]);
+}
+
+void dual_rank_rx_dqs_gating_cal(u32 channel,
+				 const struct mt8173_sdram_params *sdram_params)
+{
+	u32 dqsinctl;
+
+	/* rank 0 gw calibration */
+	rx_dqs_gating_cal(channel, 0, sdram_params);
+
+	/* get dqsinctl after rank 0 calibration */
+	dqsinctl = read32(&ch[channel].ao_regs->dqsctl1);
+	dqsinctl = (dqsinctl >> DQSCTL1_DQSINCTL_SHIFT) & (0xf << 0);
+
+	/* swap cs0 and cs1 */
+	setbits_le32(&ch[channel].ao_regs->rkcfg, MASK_RKCFG_RKSWAP_EN);
+
+	/* rank 1 gw calibration */
+	rx_dqs_gating_cal(channel, 1, sdram_params);
+
+	/* set rank 1 coarse tune and fine tune */
+	set_gw_coarse_factor_rank1(channel, opt_gw_coarse_value[channel][1],
+				   dqsinctl);
+	set_gw_fine_factor(channel, opt_gw_fine_value[channel][1], 1);
+
+	/* swap cs back */
+	clrbits_le32(&ch[channel].ao_regs->rkcfg, MASK_RKCFG_RKSWAP_EN);
+
+	/* set rank 0 coarse tune and fine tune back */
+	set_gw_coarse_factor(channel, opt_gw_coarse_value[channel][0]);
+	set_gw_fine_factor(channel, opt_gw_fine_value[channel][0], 0);
+}
+
+void dramc_rankinctl_config(u32 channel,
+			    const struct mt8173_sdram_params *sdram_params)
+{
+	u32 value;
+
+	if (is_dual_rank(channel, sdram_params)) {
+		/* RANKINCTL_ROOT1 = DQSINCTL + reg_TX_DLY_DQSGATE */
+		value = min(opt_gw_coarse_value[channel][0],
+			    opt_gw_coarse_value[channel][1]) >> 2;
+
+		clrsetbits_le32(&ch[channel].ao_regs->dummy, 0xf, value);
+
+		/* RANKINCTL = RANKINCTL_ROOT1 */
+		clrsetbits_le32(&ch[channel].ao_regs->dqscal1,
+				0xf << 16, value << 16);
+	}
+	/* disable per-bank refresh when refresh rate >= 5 */
+	setbits_le32(&ch[channel].ao_regs->rkcfg,
+		     1 << RKCFG_PBREF_DISBYRATE_SHIFT);
+}
+
+u32 dram_k_perbit(u32 channel)
+{
+	u32 err_value = 0x0;
+
+	/* use XTALK pattern to run the test */
+	err_value = dramc_engine2(channel, TE_OP_WRITE_READ_CHECK,
+				  DEFAULT_TEST2_1_CAL, DEFAULT_TEST2_2_CAL,
+				  2, 0);
+	return err_value;
+}
+
+void dramk_check_dqs_win(struct dqs_perbit_dly *p, u8 dly_step, u8 last_step,
+			 u32 fail_bit)
+{
+	s8 dqsdly_pass_win, best_pass_win;
+
+	if (fail_bit == 0) {
+		if (p->first_dqsdly_pass == -1) {
+			/* first DQS pass delay tap */
+			p->first_dqsdly_pass = dly_step;
+		}
+		if ((p->last_dqsdly_pass == -2) && (dly_step == last_step)) {
+			/* pass to the last tap */
+			p->last_dqsdly_pass = dly_step;
+			dqsdly_pass_win = p->last_dqsdly_pass -
+					  p->first_dqsdly_pass;
+			best_pass_win = p->best_last_dqsdly_pass -
+					p->best_first_dqsdly_pass;
+			if (dqsdly_pass_win > best_pass_win) {
+				p->best_last_dqsdly_pass =  p->last_dqsdly_pass;
+				p->best_first_dqsdly_pass = p->first_dqsdly_pass;
+			}
+			/* clear to find the next pass range if it has */
+			p->first_dqsdly_pass = -1;
+			p->last_dqsdly_pass = -2;
+		}
+	} else {
+		if ((p->first_dqsdly_pass != -1) && (p->last_dqsdly_pass == -2)) {
+			p->last_dqsdly_pass = dly_step - 1;
+			dqsdly_pass_win = p->last_dqsdly_pass -
+					  p->first_dqsdly_pass;
+			best_pass_win = p->best_last_dqsdly_pass -
+					p->best_first_dqsdly_pass;
+			if (dqsdly_pass_win > best_pass_win) {
+				p->best_last_dqsdly_pass =  p->last_dqsdly_pass;
+				p->best_first_dqsdly_pass = p->first_dqsdly_pass;
+			}
+			/* clear to find the next pass range if it has */
+			p->first_dqsdly_pass = -1;
+			p->last_dqsdly_pass = -2;
+		}
+	}
+}
+
+void dramk_check_dq_win(struct dqs_perbit_dly *p, u8 dly_step, u8 last_step,
+			u32 fail_bit)
+{
+	s8 dqdly_pass_win, best_pass_win;
+
+	if (fail_bit == 0) {
+		if (p->first_dqdly_pass == -1) {
+			/* first DQ pass delay tap */
+			p->first_dqdly_pass = dly_step;
+		}
+
+		if ((p->last_dqdly_pass == -2) && (dly_step == last_step)) {
+			/* pass to the last tap */
+			p->last_dqdly_pass = dly_step;
+			dqdly_pass_win = p->last_dqdly_pass -
+					 p->first_dqdly_pass;
+			best_pass_win = p->best_last_dqdly_pass -
+					p->best_first_dqdly_pass;
+			if (dqdly_pass_win > best_pass_win) {
+				p->best_last_dqdly_pass =  p->last_dqdly_pass;
+				p->best_first_dqdly_pass = p->first_dqdly_pass;
+			}
+			/* clear to find the next pass range if it has */
+			p->first_dqdly_pass = -1;
+			p->last_dqdly_pass = -2;
+		}
+	} else {
+		if ((p->first_dqdly_pass != -1) && (p->last_dqdly_pass == -2)) {
+			p->last_dqdly_pass = dly_step - 1;
+			dqdly_pass_win = p->last_dqdly_pass -
+					 p->first_dqdly_pass;
+			best_pass_win = p->best_last_dqdly_pass -
+					p->best_first_dqdly_pass;
+			if (dqdly_pass_win > best_pass_win) {
+				p->best_last_dqdly_pass =  p->last_dqdly_pass;
+				p->best_first_dqdly_pass = p->first_dqdly_pass;
+			}
+			/* clear to find the next pass range if it has */
+			p->first_dqdly_pass = -1;
+			p->last_dqdly_pass = -2;
+		}
+	}
+}
+
+u8 dramk_calcu_best_dly(u8 bit, struct dqs_perbit_dly *p, u8 *p_max_byte)
+{
+	u8 fail = 0;
+	u8 hold, setup;
+
+	/* hold time = DQS pass taps */
+	hold = p->best_last_dqsdly_pass - p->best_first_dqsdly_pass + 1;
+	/* setup time = DQ pass taps */
+	setup = p->best_last_dqdly_pass - p->best_first_dqdly_pass + 1;
+
+	/* The relationship of setup and hold time of dqs and dq signals
+	 * is represented with delay tap in the following format:
+	 *
+	 *   setup time(dq delay)  hold time(dqs delay)
+	 *   xxxxxxxxxxxxxoooooooo|ooooooooooooooooooooxxxxx
+	 *   15		         0 1		      15 tap
+	 */
+
+	if (hold > setup) {
+		/* like this: (setup time != 0) */
+		/* xxxxxxxxxxxxxoooooooo|ooooooooooooooooooooxxxxx */
+		/* like this: (setup time == 0) */
+		/* xxxxxxxxxxxxxxxxxxxxx|xxxooooooooooxxxxxxxxxxxx */
+
+		p->best_dqdly = 0;
+		p->best_dqsdly = (setup != 0)? (hold - setup) / 2:
+				 (hold - setup) / 2 + p->best_first_dqsdly_pass;
+
+		if (p->best_dqsdly > *p_max_byte) {
+			*p_max_byte = p->best_dqsdly;
+		}
+
+	} else if (hold < setup) {
+		/* like this: (hold time != 0 )*/
+		/* xxxoooooooooooooooooo|ooooooooxxxxxxxxxxxxxxxxx */
+		/* like this: (hold time == 0 ) */
+		/* xxxoooooooooooooooxxx|xxxxxxxxxxxxxxxxxxxxxxxxx */
+
+		p->best_dqsdly = 0;
+		p->best_dqdly = (hold != 0)? (setup - hold) / 2:
+				(setup - hold) / 2 + p->best_first_dqdly_pass;
+
+	} else { /* hold time == setup time */
+		p->best_dqsdly = 0;
+		p->best_dqdly = 0;
+
+		if (hold == 0) {
+			/* like this: (mean this bit is error) */
+			/* xxxxxxxxxxxxxxxxxxxxx|xxxxxxxxxxxxxxxxxxxxxxxxx */
+			printk(BIOS_ERR, "ERROR, error bit %d, "
+					 "setup_time = hold_time = 0!!\n", bit);
+			fail = 1;
+		}
+	}
+
+	dramc_dbg_msg("bit#%d : dq =%d dqs=%d win=%d (%d, %d)\n",
+		      bit, setup, hold, setup + hold,
+		      p->best_dqdly, p->best_dqsdly);
+
+	return fail;
+}
+
+void clk_duty_cal(u32 channel)
+{
+	u8  max_duty_sel, max_duty;
+	u32 max_win_size = 0;
+
+	max_duty_sel = max_duty = 1;
+
+	clrsetbits_le32(&ch[channel].ddrphy_regs->phyclkduty,
+			0x3 << PHYCLKDUTY_CMDCLKP0DUTYN_SHIFT |
+			1 << PHYCLKDUTY_CMDCLKP0DUTYP_SHIFT,
+			1 << PHYCLKDUTY_CMDCLKP0DUTYSEL_SHIFT |
+			max_duty << PHYCLKDUTY_CMDCLKP0DUTYN_SHIFT);
+
+	max_win_size = read32(&ch[channel].ddrphy_regs->phyclkduty);
+
+	dramc_dbg_msg("[Channel %d CLK DUTY CALIB] ", channel);
+	dramc_dbg_msg("Final DUTY_SEL=%d, DUTY=%d, rx window size=%d\n",
+		      max_duty_sel, max_duty, max_win_size);
+}
+
+static void set_dle_factor(u32 channel, u8 curr_val)
+{
+	clrsetbits_le32(&ch[channel].ao_regs->ddr2ctl,
+			0x7 << DDR2CTL_DATLAT_SHIFT,
+			(curr_val & 0x7) << DDR2CTL_DATLAT_SHIFT);
+
+	clrsetbits_le32(&ch[channel].ao_regs->padctl4,
+			0x1 << PADCTL4_DATLAT3_SHIFT,
+			((curr_val >> 3) & 0x1) << PADCTL4_DATLAT3_SHIFT);
+
+	clrsetbits_le32(&ch[channel].ao_regs->phyctl1,
+			0x1 << PHYCTL1_DATLAT4_SHIFT,
+			((curr_val >> 4) & 0x1) << PHYCTL1_DATLAT4_SHIFT);
+
+	clrsetbits_le32(&ch[channel].ao_regs->misc,
+			0x1f << MISC_DATLAT_DSEL_SHIFT,
+			(curr_val - 8) << MISC_DATLAT_DSEL_SHIFT);
+
+	/* optimize bandwidth for HW run time test engine use */
+	clrsetbits_le32(&ch[channel].ao_regs->misc,
+			0x1f << MISC_LATNORMP_SHIFT,
+			(curr_val - 3) << MISC_LATNORMP_SHIFT);
+}
+
+void dual_rank_rx_datlat_cal(u32 channel,
+			     const struct mt8173_sdram_params *sdram_params)
+{
+	u8 r0_dle_setting, r1_dle_setting;
+
+	/* rank 0 dle calibration */
+	r0_dle_setting = rx_datlat_cal(channel, 0, sdram_params);
+
+	/* swap cs0 and cs1 */
+	setbits_le32(&ch[channel].ao_regs->rkcfg, MASK_RKCFG_RKSWAP_EN);
+
+	/* set rank 1 coarse tune and fine tune back */
+	set_gw_coarse_factor(channel, opt_gw_coarse_value[channel][1]);
+	set_gw_fine_factor(channel, opt_gw_fine_value[channel][1], 0);
+
+	/* rank 1 dle calibration */
+	r1_dle_setting = rx_datlat_cal(channel, 1, sdram_params);
+
+	/* set rank 0 coarse tune and fine tune back */
+	set_gw_coarse_factor(channel, opt_gw_coarse_value[channel][0]);
+	set_gw_fine_factor(channel, opt_gw_fine_value[channel][0], 0);
+
+	/* swap cs back */
+	clrbits_le32(&ch[channel].ao_regs->rkcfg, MASK_RKCFG_RKSWAP_EN);
+
+	/* output dle setting of rank 0 and 1 */
+	dramc_dbg_msg("[DLE] Rank 0 DLE calibrated setting = %xh.\n"
+		      "[DLE] Rank 1 DLE calibrated setting = %xh.\n",
+		      r0_dle_setting, r1_dle_setting);
+
+	if (r1_dle_setting < r0_dle_setting) {
+		/* compare dle setting of two ranks */
+		dramc_dbg_msg("[DLE] rank 0 > rank 1. set to rank 0.\n");
+		/* case 1: set rank 0 dle setting */
+		set_dle_factor(channel, r0_dle_setting);
+	} else {
+		/* compare dle setting of two ranks */
+		dramc_dbg_msg("[DLE] rank 0 < rank 1. use rank 1.\n");
+		/* case 2: set rank 1 dle setting */
+		set_dle_factor(channel, r1_dle_setting);
+	}
+}
+
+u8 rx_datlat_cal(u32 channel, u8 rank,
+		 const struct mt8173_sdram_params *sdram_params)
+{
+	u8 i, best_step;
+	u32 err[DLE_TEST_NUM];
+
+	dramc_dbg_msg("=========================================\n");
+	dramc_dbg_msg("[Channel %d] [Rank %d] DATLAT calibration\n",
+		       channel, rank);
+	dramc_dbg_msg("=========================================\n");
+
+	clrbits_le32(&ch[channel].ao_regs->mckdly,
+		     0x11 << MCKDLY_DQIENQKEND_SHIFT |
+		     0x1  << MCKDLY_DQIENLAT_SHIFT);
+
+	/* set dle calibration initial value */
+	best_step = sdram_params->calib_params.datlat_ucfirst + 1;
+
+	/* do dle calibration test */
+	for (i = 0; i < DLE_TEST_NUM; i++) {
+		set_dle_factor(channel, best_step - i);
+		err[i] = dramc_engine2(channel, TE_OP_WRITE_READ_CHECK,
+				       DEFAULT_TEST2_1_CAL,
+				       DEFAULT_TEST2_2_CAL, 2, 0);
+	}
+
+	if (err[0]) {
+		/* dle test error */
+		printk(BIOS_ERR, "[DLE] calibration ERROR!\n");
+	} else {
+		/* judge dle test result */
+		for (i = 0; i < DLE_TEST_NUM; i++) {
+			if (!err[i] && (i + 1 == DLE_TEST_NUM || err[i + 1])) {
+				/* dle test ok */
+				best_step -= (i - 1);
+				break;
+			}
+		}
+	}
+
+	/* Default dle value is set when test error (error recovery).
+         * Others, adjusted dle calibration value is set normally.
+	 */
+	set_dle_factor(channel, best_step);
+
+	dramc_dbg_msg("[DLE] adjusted value = %#x\n", best_step);
+
+	return best_step;
+}
+
+void tx_delay_for_wrleveling(u32 channel,
+			     struct dqs_perbit_dly *dqdqs_perbit_dly,
+			     u8 *max_dqsdly_byte, u8 *ave_dqdly_byte)
+{
+	s8 i, delta, index, max_taps;
+
+	max_taps = MAX_DQDLY_TAPS - 1;
+
+	for (i = 0; i < DATA_WIDTH_32BIT; i++) {
+
+		index = i / DQS_BIT_NUMBER;
+
+		if (i % DQS_BIT_NUMBER == 0)
+			dramc_dbg_msg("DQS%d: %d  \n", index,
+				       wrlevel_dqs_dly[channel][index]);
+
+		if (max_dqsdly_byte[index] <= wrlevel_dqs_dly[channel][index]) {
+			/* set diff value (delta) */
+			delta = wrlevel_dqs_dly[channel][index] -
+				max_dqsdly_byte[index];
+
+			dqdqs_perbit_dly[i].best_dqdly += delta;
+
+			/* max limit to 15 */
+			if (dqdqs_perbit_dly[i].best_dqdly > max_taps)
+				dqdqs_perbit_dly[i].best_dqdly = max_taps;
+
+			if ((i + 1) % DQS_BIT_NUMBER == 0) {
+				/* DQS */
+				max_dqsdly_byte[index] =
+					wrlevel_dqs_dly[channel][index];
+				/* DQM */
+				ave_dqdly_byte[index] += delta;
+				/* max limit to 15 */
+				if (ave_dqdly_byte[index] > max_taps)
+					ave_dqdly_byte[index] = max_taps;
+			}
+
+		} else if (i % DQS_BIT_NUMBER == 0) {
+			/* max_dqsdly_byte[j] > wrlevel_dqs_dly[channel][j]
+			 * Originally, we should move clk and CA delay.
+			 * Then, do GW calibration again. However, DQ/DQS
+			 * skew should not be large in MT8173, so we sacrifice
+			 * the Clk/DQS margin by keeping the clk out delay.
+			 */
+			printk(BIOS_ERR, "[Warning] DQSO %d in TX "
+					 "per-bit = %d > DQSO %d in WL = %d  ",
+					 index, max_dqsdly_byte[index], index,
+					 wrlevel_dqs_dly[channel][index]);
+		}
+	}
+}
+
+static void set_rx_dly_factor(u32 channel, u32 curr_val, u8 type)
+{
+	u32 i, value = 0;
+
+	for (i = 0; i < DQS_NUMBER; i++)
+		value += (curr_val << (8 * i));
+
+	switch (type) {
+	case RX_DQS:
+		write32(&ch[channel].ao_regs->r0deldly, value);
+		break;
+	case RX_DQ:
+		for (i = 0; i < DATA_WIDTH_32BIT; i += 4)
+			write32(&ch[channel].ao_regs->dqidly[i/4], value);
+		break;
+	}
+}
+
+static void set_tx_dly_factor(u32 channel, u32 curr_val, u8 type)
+{
+	u32 i, bit_num, value = 0;
+
+	bit_num = (type == TX_DQ)? DQS_BIT_NUMBER: DQS_NUMBER;
+
+	for (i = 0; i < bit_num; i++)
+		value += (curr_val << (4 * i));
+
+	switch (type) {
+
+	case TX_DQS:
+		write32(&ch[channel].ddrphy_regs->padctl3, value);
+		break;
+	case TX_DQM:
+		write32(&ch[channel].ddrphy_regs->padctl2, value);
+		break;
+	case TX_DQ:
+		for (i = 0; i < DQS_NUMBER; i++)
+			write32(&ch[channel].ddrphy_regs->dqodly[i], value);
+		break;
+	}
+}
+
+static void set_dly_factor(u32 channel, u8 stage, u8 type, u8 dly)
+{
+	switch (stage | type << 1) {
+	/* set delay for DQ/DQM/DQS by setup/hold stage and window type */
+	case STAGE_SETUP_TX_WIN:
+		/* set DQ/DQM delay for tx window */
+		set_tx_dly_factor(channel, dly, TX_DQ);
+		set_tx_dly_factor(channel, dly, TX_DQM);
+		break;
+	case STAGE_SETUP_RX_WIN:
+		/* set DQ delay for rx window */
+		set_rx_dly_factor(channel, dly, RX_DQ);
+		break;
+	case STAGE_HOLD_TX_WIN:
+		/* set DQS delay for tx window */
+		set_tx_dly_factor(channel, dly, TX_DQS);
+		break;
+	case STAGE_HOLD_RX_WIN:
+		/* set DQS delay for rx window */
+		set_rx_dly_factor(channel, dly, RX_DQS);
+		break;
+	}
+}
+
+static void set_rx_best_dly_factor(u32 channel,
+				   struct dqs_perbit_dly *dqdqs_perbit_dly,
+				   u8 *max_dqsdly_byte)
+{
+	u32 i, value = 0;
+
+	for (i = 0; i < DQS_NUMBER; i++)
+		value += (((u32)max_dqsdly_byte[i]) << (8 * i));
+
+	write32(&ch[channel].ao_regs->r0deldly, value);
+	write32(&ch[channel].ao_regs->r1deldly, value);
+
+	dramc_dbg_msg("[RX] DQS Reg R0DELDLY=%xh\n",
+			read32(&ch[channel].ao_regs->r0deldly));
+	dramc_dbg_msg("[RX] DQS Reg R1DELDLY=%xh\n",
+			read32(&ch[channel].ao_regs->r1deldly));
+
+	for (i = 0; i < DATA_WIDTH_32BIT; i += 4) {
+		/* every 4bit dq have the same delay register address */
+		value = ((u32)dqdqs_perbit_dly[i].best_dqdly) +
+			(((u32)dqdqs_perbit_dly[i + 1].best_dqdly) << 8) +
+			(((u32)dqdqs_perbit_dly[i + 2].best_dqdly) << 16) +
+			(((u32)dqdqs_perbit_dly[i + 3].best_dqdly) << 24);
+
+		write32(&ch[channel].ao_regs->dqidly[i / 4], value);
+		dramc_dbg_msg("[RX] DQ DQIDLY%d = %xh\n", (i + 4) / 4, value);
+	}
+}
+
+static void set_tx_best_dly_factor(u32 channel,
+				   struct dqs_perbit_dly *dqdqs_perbit_dly,
+				   u8 *max_dqsdly_byte, u8 *ave_dqdly_byte)
+{
+	u32 bit, value, shift, dqs_index = 0;
+
+	value = 0;
+	for (bit = 0; bit < DQS_NUMBER; bit++) {
+		value += (((u32)max_dqsdly_byte[bit]) << (4 * bit));
+	}
+
+	write32(&ch[channel].ddrphy_regs->padctl3, value);
+	dramc_dbg_msg("[TX] DQS PADCTL3 Reg = %#x\n", value);
+
+	/* DQ delay */
+	for (bit = 0; bit < DATA_WIDTH_32BIT; bit++) {
+		/* every 8 DQ reset */
+		if (bit % DQS_BIT_NUMBER == 0) {
+			value = 0;
+			dqs_index = bit / DQS_BIT_NUMBER;
+		}
+		/* 4 bits field for each DQ */
+		shift = 4 * (bit % DQS_BIT_NUMBER);
+		value += (((u32)(dqdqs_perbit_dly[bit].best_dqdly)) << shift);
+		/* each register is with 8 DQ */
+		if ((bit + 1) % DQS_BIT_NUMBER == 0) {
+			write32(&ch[channel].ddrphy_regs->dqodly[dqs_index], value);
+			dramc_dbg_msg("[TX] DQ DQ0DLY%d = %xh\n",
+					dqs_index + 1, value);
+		}
+	}
+
+	/* DQM delay */
+	value = read32(&ch[channel].ddrphy_regs->padctl2);
+	value &= MASK_PADCTL2;
+
+	for (bit = 0; bit < DQS_NUMBER; bit++) {
+		value += (((u32)ave_dqdly_byte[bit]) << (4 * bit));
+	}
+	write32(&ch[channel].ddrphy_regs->padctl2, value);
+	dramc_dbg_msg("[TX] DQM PADCTL2 Reg = %#x\n", value);
+}
+
+void perbit_window_cal(u32 channel, u8 type)
+{
+	u8 i, dly, bit, max_dqs_taps, fail = 0;
+	u8 max_dqsdly_byte[DQS_NUMBER], ave_dqdly_byte[DQS_NUMBER];
+	u32 err_value, fail_bit, max_limit, index;
+
+	struct dqs_perbit_dly dqdqs_perbit_dly[DQ_DATA_WIDTH];
+
+	dramc_dbg_msg("\n[Channel %d] %s DQ/DQS per bit :\n",
+			channel, (type == TX_WIN)? "TX": "RX");
+
+	if (type == TX_WIN)
+		dramc_phy_reset(channel);
+
+	for (i = 0; i < DATA_WIDTH_32BIT; i++) {
+		dqdqs_perbit_dly[i].first_dqdly_pass = -1;
+		dqdqs_perbit_dly[i].last_dqdly_pass = -2;
+		dqdqs_perbit_dly[i].first_dqsdly_pass = -1;
+		dqdqs_perbit_dly[i].last_dqsdly_pass = -2;
+		dqdqs_perbit_dly[i].best_first_dqdly_pass = -1;
+		dqdqs_perbit_dly[i].best_last_dqdly_pass = -2;
+		dqdqs_perbit_dly[i].best_first_dqsdly_pass = -1;
+		dqdqs_perbit_dly[i].best_last_dqsdly_pass = -2;
+	}
+
+	/* 1. delay DQ ,find the pass widnow (left boundary)
+	 * 2. delay DQS find the pass window (right boundary)
+	 * 3. find the best DQ / DQS to satify the middle value
+	 *    of the overall pass window per bit
+	 * 4. set DQS delay to the max per byte, delay DQ to de-skew
+	 */
+
+	/* 1. set DQS delay to 0 first */
+	set_dly_factor(channel, STAGE_HOLD, type, FIRST_DQS_DELAY);
+
+	dramc_dbg_msg("----------------------------------"
+			"--------------------\n");
+	dramc_dbg_msg("Start DQ delay to find pass range,"
+			"DQS delay fixed to %#x...\n", FIRST_DQS_DELAY);
+	dramc_dbg_msg("----------------------------------"
+			"-------------------\n");
+	dramc_dbg_msg("x-axis is bit #; y-axis is DQ delay (%d~%d)\n",
+			FIRST_DQ_DELAY, MAX_DQDLY_TAPS - 1);
+
+	/* delay DQ from 0 to 15 to get the setup time */
+	for (dly = FIRST_DQ_DELAY; dly < MAX_DQDLY_TAPS; dly++) {
+
+		set_dly_factor(channel, STAGE_SETUP, type, dly);
+		err_value = dram_k_perbit(channel);
+
+		/* check fail bit, 0 ok, others fail */
+		for (bit = 0; bit < DATA_WIDTH_32BIT; bit++) {
+			fail_bit = err_value & ((u32)1 << bit);
+			dramk_check_dq_win(&(dqdqs_perbit_dly[bit]), dly,
+					   MAX_DQDLY_TAPS - 1, fail_bit);
+			if (fail_bit == 0) {
+				dramc_dbg_msg("o");
+			} else {
+				dramc_dbg_msg("x");
+			}
+		}
+		dramc_dbg_msg("\n");
+	}
+
+	/* 2. set DQ delay to 0 */
+	set_dly_factor(channel, STAGE_SETUP, type, FIRST_DQ_DELAY);
+
+	/* DQS delay taps: tx and rx are 16 and 64 taps */
+	max_dqs_taps = (type == TX_WIN)? MAX_TX_DQSDLY_TAPS: MAX_RX_DQSDLY_TAPS;
+
+	dramc_dbg_msg("-----------------------------------"
+			"-------------------\n");
+	dramc_dbg_msg("Start DQS delay to find pass range,"
+			"DQ delay fixed to %#x...\n", FIRST_DQ_DELAY);
+	dramc_dbg_msg("------------------------------------"
+			"------------------\n");
+	dramc_dbg_msg("x-axis is bit #; y-axis is DQS delay (%d~%d)\n",
+		      FIRST_DQS_DELAY + 1, max_dqs_taps - 1);
+
+	/* delay DQS to get the hold time, dq_dly = dqs_dly = 0 is counted */
+	/* when we delay dq, so we set first dqs delay to 1 */
+	for (dly = (FIRST_DQS_DELAY + 1); dly < max_dqs_taps; dly++) {
+
+		set_dly_factor(channel, STAGE_HOLD, type, dly);
+		err_value = dram_k_perbit(channel);
+
+		/* check fail bit, 0 ok, others fail */
+		for (bit = 0; bit < DATA_WIDTH_32BIT; bit++) {
+			fail_bit = err_value & ((u32)1 << bit);
+			dramk_check_dqs_win(&(dqdqs_perbit_dly[bit]), dly,
+					    max_dqs_taps - 1, fail_bit);
+			if (fail_bit == 0) {
+				dramc_dbg_msg("o");
+			} else {
+				dramc_dbg_msg("x");
+			}
+		}
+		dramc_dbg_msg("\n");
+	}
+
+	/* 3 calculate dq and dqs time */
+	dramc_dbg_msg("-------------------------------"
+			"-----------------------\n");
+	dramc_dbg_msg("Start calculate dq time and dqs "
+			"time:\n");
+	dramc_dbg_msg("Find max DQS delay per byte / "
+			"Adjust DQ delay to align DQS...\n");
+	dramc_dbg_msg("--------------------------------"
+			"----------------------\n");
+
+	/* As per byte, check max DQS delay in 8-bit.
+	 * Except for the bit of max DQS delay, delay
+	 * DQ to fulfill setup time = hold time
+	 */
+	for (i = 0; i < DQS_NUMBER; i++) {
+		max_dqsdly_byte[i] = 0;
+		ave_dqdly_byte[i] = 0;
+	}
+
+	for (i = 0; i < DATA_WIDTH_32BIT; i++) {
+		/* we delay DQ or DQS to let DQS sample the middle */
+		/* of tx/rx pass window for all the 8 bits */
+		index = i / DQS_BIT_NUMBER;
+		fail |= dramk_calcu_best_dly(i, &dqdqs_perbit_dly[i],
+					     &max_dqsdly_byte[index]);
+
+		if ((i + 1) % DQS_BIT_NUMBER == 0)
+			dramc_dbg_msg("----separate line----\n");
+	}
+
+	for (i = 0; i < DATA_WIDTH_32BIT; i++) {
+		/* dqs index for every 8-bit */
+		index = i / DQS_BIT_NUMBER;
+		/* set DQS to max for 8-bit */
+		if (dqdqs_perbit_dly[i].best_dqsdly < max_dqsdly_byte[index]) {
+			/* delay DQ to compensate extra DQS delay */
+			dly = max_dqsdly_byte[index] -
+			      dqdqs_perbit_dly[i].best_dqsdly;
+			dqdqs_perbit_dly[i].best_dqdly += dly;
+			/* max limit to 15 */
+			max_limit = MAX_DQDLY_TAPS - 1;
+			if (dqdqs_perbit_dly[i].best_dqdly > max_limit) {
+				dqdqs_perbit_dly[i].best_dqdly = max_limit;
+			}
+		}
+		/* accumulation variable for TX DQM */
+		ave_dqdly_byte[index] += dqdqs_perbit_dly[i].best_dqdly;
+		/* take the average of DQ for TX DQM */
+		if ((i + 1) % DQS_BIT_NUMBER == 0) {
+			ave_dqdly_byte[index] /= DQS_BIT_NUMBER;
+		}
+	}
+
+	if (fail == 1) /* error handling */
+		die("fail on perbit_window_cal()\n");
+
+	dramc_dbg_msg("==================================================\n");
+	dramc_dbg_msg("        dramc_perbit_window_swcal:\n");
+	dramc_dbg_msg("           channel=%d(0:cha, 1:chb)\n", channel);
+	dramc_dbg_msg("           bus width=%d\n", DATA_WIDTH_32BIT);
+	dramc_dbg_msg("==================================================\n");
+	dramc_dbg_msg("DQS Delay :\n DQS0 = %d DQS1 = %d DQS2 = %d DQS3 = %d\n",
+		       max_dqsdly_byte[0], max_dqsdly_byte[1],
+		       max_dqsdly_byte[2], max_dqsdly_byte[3]);
+
+	if (type == TX_WIN)
+		dramc_dbg_msg("DQM Delay :\n"
+			      "DQM0 = %d DQM1 = %d DQM2 = %d DQM3 = %d\n",
+			       ave_dqdly_byte[0], ave_dqdly_byte[1],
+			       ave_dqdly_byte[2], ave_dqdly_byte[3]);
+
+	dramc_dbg_msg("DQ Delay :\n");
+	for (i = 0; i < DATA_WIDTH_32BIT; i++) {
+		dramc_dbg_msg("DQ%d = %d ", i, dqdqs_perbit_dly[i].best_dqdly);
+		if ( ((i + 1) % 4) == 0)
+			dramc_dbg_msg("\n");
+	}
+
+	dramc_dbg_msg("____________________________________"
+		      "____________________________________\n");
+
+	if (type == TX_WIN) {
+		/* Add CLK to DQS/DQ skew after write leveling */
+		dramc_dbg_msg("Add CLK to DQS/DQ skew based on write leveling.\n");
+		/* this subroutine add clk delay to DQS/DQ after WL */
+		tx_delay_for_wrleveling(channel, dqdqs_perbit_dly,
+					max_dqsdly_byte, ave_dqdly_byte);
+	}
+
+	if (type == TX_WIN)
+		set_tx_best_dly_factor(channel, dqdqs_perbit_dly,
+				       max_dqsdly_byte, ave_dqdly_byte);
+	else
+		set_rx_best_dly_factor(channel, dqdqs_perbit_dly,
+				       max_dqsdly_byte);
+
+	dramc_phy_reset(channel);
+}
diff --git a/src/soc/mediatek/mt8173/emi.c b/src/soc/mediatek/mt8173/emi.c
new file mode 100644
index 0000000..f6ef40f
--- /dev/null
+++ b/src/soc/mediatek/mt8173/emi.c
@@ -0,0 +1,142 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <arch/io.h>
+#include <assert.h>
+#include <boardid.h>
+#include <console/console.h>
+#include <delay.h>
+#include <string.h>
+#include <soc/addressmap.h>
+#include <soc/dramc_common.h>
+#include <soc/dramc_register.h>
+#include <soc/dramc_pi_api.h>
+#include <soc/mt6391.h>
+#include <soc/pll.h>
+
+struct emi_regs *emi_regs = (void *)EMI_BASE;
+
+static void dram_vcore_adjust(void)
+{
+	/* options: Vcore_HV_LPPDR3/Vcore_NV_LPPDR3/Vcore_LV_LPPDR3 */
+	mt6391_write(PMIC_RG_VCORE_CON9, Vcore_NV_LPPDR3, 0x7F, 0);
+	mt6391_write(PMIC_RG_VCORE_CON10, Vcore_NV_LPPDR3, 0x7F, 0);
+}
+
+static void dram_vmem_adjust(void)
+{
+	/* options: Vmem_HV_LPPDR3/Vmem_NV_LPPDR3/Vmem_LV_LPPDR3 */
+	mt6391_write(PMIC_RG_VDRM_CON9, Vmem_NV_LPDDR3, 0x7F, 0);
+	mt6391_write(PMIC_RG_VDRM_CON10, Vmem_NV_LPDDR3, 0x7F, 0);
+}
+
+static void emi_init(const struct mt8173_sdram_params *sdram_params)
+{
+	/* EMI setting initialization */
+	write32(&emi_regs->emi_conf, sdram_params->emi_set.conf);
+	write32(&emi_regs->emi_conm, sdram_params->emi_set.conm_1);
+	write32(&emi_regs->emi_arbi, sdram_params->emi_set.arbi);
+	write32(&emi_regs->emi_arba, sdram_params->emi_set.arba);
+	write32(&emi_regs->emi_arbc, sdram_params->emi_set.arbc);
+	write32(&emi_regs->emi_arbd, sdram_params->emi_set.arbd);
+	write32(&emi_regs->emi_arbe, sdram_params->emi_set.arbe);
+	write32(&emi_regs->emi_arbf, sdram_params->emi_set.arbf);
+	write32(&emi_regs->emi_arbg, sdram_params->emi_set.arbg);
+	write32(&emi_regs->emi_arbj, sdram_params->emi_set.arbj);
+	write32(&emi_regs->emi_cona, sdram_params->emi_set.cona);
+	write32(&emi_regs->emi_testd, sdram_params->emi_set.testd);
+	write32(&emi_regs->emi_bmen, sdram_params->emi_set.bmen);
+	write32(&emi_regs->emi_conb, sdram_params->emi_set.conb);
+	write32(&emi_regs->emi_conc, sdram_params->emi_set.conc);
+	write32(&emi_regs->emi_cond, sdram_params->emi_set.cond);
+	write32(&emi_regs->emi_cone, sdram_params->emi_set.cone);
+	write32(&emi_regs->emi_cong, sdram_params->emi_set.cong);
+	write32(&emi_regs->emi_conh, sdram_params->emi_set.conh);
+	write32(&emi_regs->emi_slct, sdram_params->emi_set.slct_1);
+	write32(&emi_regs->emi_mdct, sdram_params->emi_set.mdct_1);
+	write32(&emi_regs->emi_arbk, sdram_params->emi_set.arbk);
+	write32(&emi_regs->emi_testc, sdram_params->emi_set.testc);
+	write32(&emi_regs->emi_mdct, sdram_params->emi_set.mdct_2);
+	write32(&emi_regs->emi_testb, sdram_params->emi_set.testb);
+	write32(&emi_regs->emi_slct, sdram_params->emi_set.slct_2);
+	write32(&emi_regs->emi_conm, sdram_params->emi_set.conm_2);
+	write32(&emi_regs->emi_test0, sdram_params->emi_set.test0);
+	write32(&emi_regs->emi_test1, sdram_params->emi_set.test1);
+}
+
+static void do_calib(const struct mt8173_sdram_params *sdram_params)
+{
+	u32 channel;
+
+	sw_impedance_cal(CHANNEL_A, sdram_params);
+	sw_impedance_cal(CHANNEL_B, sdram_params);
+
+	/* SPM_CONTROL_AFTERK */
+	transfer_to_reg_control();
+
+	/* do dram calibration for channel A and B */
+	for(channel = 0; channel < CHANNEL_NUM; channel++) {
+		ca_training(channel, sdram_params);
+		write_leveling(channel, sdram_params);
+
+		/* rx gating and datlat for single or dual rank */
+		if (is_dual_rank(channel, sdram_params)) {
+			dual_rank_rx_dqs_gating_cal(channel, sdram_params);
+			dual_rank_rx_datlat_cal(channel, sdram_params);
+		} else {
+			rx_dqs_gating_cal(channel, 0, sdram_params);
+			rx_datlat_cal(channel, 0, sdram_params);
+		}
+
+		clk_duty_cal(channel);
+		/* rx window perbit calibration */
+		perbit_window_cal(channel, RX_WIN);
+		/* tx window perbit calibration */
+		perbit_window_cal(channel, TX_WIN);
+
+		dramc_rankinctl_config(channel, sdram_params);
+		dramc_runtime_config(channel, sdram_params);
+	}
+
+	/* SPM_CONTROL_AFTERK */
+	transfer_to_spm_control();
+}
+
+static void init_dram(const struct mt8173_sdram_params *sdram_params)
+{
+	emi_init(sdram_params);
+
+	dramc_pre_init(CHANNEL_A, sdram_params);
+	dramc_pre_init(CHANNEL_B, sdram_params);
+
+	div2_phase_sync();
+
+	dramc_init(CHANNEL_A, sdram_params);
+	dramc_init(CHANNEL_B, sdram_params);
+}
+
+void mt_set_emi(const struct mt8173_sdram_params *sdram_params)
+{
+	/* voltage info */
+	dram_vcore_adjust();
+	dram_vmem_adjust();
+
+	if (sdram_params->type != TYPE_LPDDR3) {
+		die("The DRAM type is not supported");
+	}
+
+	init_dram(sdram_params);
+	do_calib(sdram_params);
+}
diff --git a/src/soc/mediatek/mt8173/include/soc/dramc_common.h b/src/soc/mediatek/mt8173/include/soc/dramc_common.h
new file mode 100644
index 0000000..084e7de
--- /dev/null
+++ b/src/soc/mediatek/mt8173/include/soc/dramc_common.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DRAMC_COMMON_H_
+#define _DRAMC_COMMON_H_
+
+enum {
+	CHANNEL_A = 0,
+	CHANNEL_B,
+	CHANNEL_NUM
+};
+
+enum {
+	GW_PARAM_COARSE = 0,
+	GW_PARAM_FINE,
+	GW_PARAM_NUM
+};
+
+enum {
+	DUAL_RANKS = 2,
+	CATRAINING_NUM = 10
+};
+
+enum {
+	DQ_DATA_WIDTH = 32,
+	DQS_BIT_NUMBER = 8,
+	DQS_NUMBER = (DQ_DATA_WIDTH / DQS_BIT_NUMBER)
+};
+
+#endif   /* _DRAMC_COMMON_H_ */
diff --git a/src/soc/mediatek/mt8173/include/soc/dramc_pi_api.h b/src/soc/mediatek/mt8173/include/soc/dramc_pi_api.h
new file mode 100644
index 0000000..1411d39
--- /dev/null
+++ b/src/soc/mediatek/mt8173/include/soc/dramc_pi_api.h
@@ -0,0 +1,187 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DRAMC_PI_API_H
+#define _DRAMC_PI_API_H
+
+#include <soc/emi.h>
+#include <console/console.h>
+
+enum {
+	MAX_CLKO_DELAY = 15
+};
+
+enum {
+	/* jitter meter for PLL phase calibration */
+	JMETER_COUNT = 1024,
+	JMETER_COUNT_N = JMETER_COUNT/10,
+	/* 10us for more margin, Fin = 52 */
+	JMETER_WAIT_DONE_US = (JMETER_COUNT/52 + 10)
+};
+
+enum {
+	DLE_TEST_NUM = 4
+};
+
+enum {
+	/* window type: tx/rx */
+	RX_WIN = 0,
+	TX_WIN = 1,
+	/* stage type: setup/hold time */
+	STAGE_SETUP = 0,
+	STAGE_HOLD = 1,
+	/* combinational flags of stage and window type */
+	STAGE_SETUP_RX_WIN = STAGE_SETUP | RX_WIN << 1,
+	STAGE_SETUP_TX_WIN = STAGE_SETUP | TX_WIN << 1,
+	STAGE_HOLD_RX_WIN = STAGE_HOLD | RX_WIN << 1,
+	STAGE_HOLD_TX_WIN = STAGE_HOLD | TX_WIN << 1
+};
+
+enum {
+	RX_DQ = 0,
+	RX_DQS,
+	TX_DQ,
+	TX_DQS,
+	TX_DQM
+};
+
+enum {
+	AUDIO = 1,
+	XTALK,
+	ISI
+};
+
+enum {
+	MEMPLL_INIT = 0,
+	MEMPLL_REF_LAG,
+	MEMPLL_REF_LEAD
+};
+
+enum {
+	FIRST_DQ_DELAY = 0,       /* first DQ delay taps */
+	FIRST_DQS_DELAY = 0,      /* first DQS delay taps */
+	MAX_DQDLY_TAPS = 16,	  /* max DQ delay taps */
+	MAX_TX_DQSDLY_TAPS = 16,  /* max TX DQS delay taps */
+	MAX_RX_DQSDLY_TAPS = 64   /* max RX DQS delay taps */
+};
+
+enum {
+	DRAMK_READ = 0,
+	DRAMK_WRITE = 1
+};
+
+enum {
+	ENABLE = 1,
+	DISABLE = 0
+};
+
+enum {
+	DATA_WIDTH_16BIT = 16,
+	DATA_WIDTH_32BIT = 32
+};
+
+enum dram_tw_op {
+	TE_OP_WRITE_READ_CHECK = 0,
+	TE_OP_READ_CHECK
+};
+
+enum {
+	DQS_GW_TE_OFFSET = 0x10,
+	DQS_GW_GOLD_COUNTER_32BIT = 0x20202020,
+	DQS_GW_PATTERN1 = 0xaa000000,
+	DQS_GW_PATTERN2 = 0x55000000
+};
+
+enum {
+	/* pattern0 and base address for test engine when we do calibration */
+	DEFAULT_TEST2_1_CAL = 0x55000000,
+	/* for testing, to separate TA4-3 address for running simultaneously */
+	/* pattern1 and offset address for test engine when we  do calibraion */
+	DEFAULT_TEST2_2_CAL = 0xaa000400,
+	/* pattern0 and base addr. for test engine when doing dqs GW */
+	DEFAULT_TEST2_1_DQSIEN = 0x55000000,
+	/* pattern1 and offset addr. for test engine when doing dqs GW */
+	DEFAULT_TEST2_2_DQSIEN = 0xaa000010,
+	/* gold pattern */
+	DEFAULT_GOLD_DQSIEN = 0x20202020
+};
+
+enum {
+	TEST_ISI_PATTERN = 0,
+	TEST_AUDIO_PATTERN,
+	TEST_TA1_SIMPLE,
+	TEST_TESTPAT4,
+	TEST_TESTPAT4_3,
+	TEST_XTALK_PATTERN,
+	TEST_MIX_PATTERN
+};
+
+struct dqs_perbit_dly {
+	s8 first_dqdly_pass;
+	s8 last_dqdly_pass;
+	s8 first_dqsdly_pass;
+	s8 last_dqsdly_pass;
+	s8 best_first_dqdly_pass;
+	s8 best_last_dqdly_pass;
+	s8 best_first_dqsdly_pass;
+	s8 best_last_dqsdly_pass;
+	u8 best_dqdly;
+	u8 best_dqsdly;
+};
+
+void transfer_to_spm_control(void);
+void transfer_to_reg_control(void);
+void dramc_phy_reset(u32 channel);
+void clk_duty_cal(u32 channel);
+void div2_phase_sync(void);
+void dramc_runtime_config(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void dramc_rankinctl_config(u32 channel, const struct mt8173_sdram_params *sdram_params);
+
+/* dramc init prototypes */
+void mem_pll_init(const struct mt8173_sdram_params *sdram_params);
+void dramc_init(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void dramc_pre_init(u32 channel, const struct mt8173_sdram_params *sdram_params);
+
+/* mandatory calibration function prototypes */
+void tx_window_perbit_cal(u32 channel);
+void rx_window_perbit_cal(u32 channel);
+void perbit_window_cal(u32 channel, u8 type);
+void sw_impedance_cal(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void ca_training(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void rx_dqs_gating_cal(u32 channel, u8 rank, const struct mt8173_sdram_params *sdram_params);
+void dual_rank_rx_datlat_cal(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void dual_rank_rx_dqs_gating_cal(u32 channel, const struct mt8173_sdram_params *sdram_params);
+void write_leveling(u32 channel, const struct mt8173_sdram_params *sdram_params);
+
+u8 dramk_calcu_best_dly(u8 bit, struct dqs_perbit_dly *p, u8 *p_max_byte);
+u8 is_dual_rank(u32 channel, const struct mt8173_sdram_params *sdram_params);
+u8 rx_datlat_cal(u32 channel, u8 rank, const struct mt8173_sdram_params *sdram_params);
+u32 dram_k_perbit(u32 channel);
+u32 dramc_engine2(u32 channel, enum dram_tw_op wr, u32 test2_1, u32 test2_2,
+		  u8 testaudpat, u8 log2loopcount);
+
+void dramk_check_dqs_win(struct dqs_perbit_dly *p, u8 dly_step, u8 last_step, u32 fail_bit);
+void dramk_check_dq_win(struct dqs_perbit_dly *p, u8 dly_step, u8 last_step, u32 fail_bit);
+
+void tx_delay_for_wrleveling(u32 channel, struct dqs_perbit_dly *dqdqs_perbit_dly,
+			     u8 *ave_dqdly_byte, u8 *max_dqsdly_byte);
+
+#if CONFIG_DEBUG_DRAM
+#define dramc_dbg_msg(_x_...) printk(BIOS_DEBUG, _x_)
+#else
+#define dramc_dbg_msg(_x_...)
+#endif
+
+#endif /* _PI_API_H */
diff --git a/src/soc/mediatek/mt8173/include/soc/dramc_register.h b/src/soc/mediatek/mt8173/include/soc/dramc_register.h
new file mode 100755
index 0000000..ce65173
--- /dev/null
+++ b/src/soc/mediatek/mt8173/include/soc/dramc_register.h
@@ -0,0 +1,522 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DRAMC_REGISTER_H_
+#define _DRAMC_REGISTER_H_
+
+#include <stddef.h>
+#include <types.h>
+
+#define DRIVING_DS2_0	    7	/* DS[2:0] 7->6 */
+#define DEFAULT_DRIVING  0x99009900
+
+enum {
+	/* CONF2 = 0x008 */
+	CONF2_TEST1_EN  = BIT(29),
+	CONF2_TEST2R_EN = BIT(30),
+	CONF2_TEST2W_EN = BIT(31),
+	/* PADCTL1 = 0x00c */
+	PADCTL1_CLK_SHIFT = 24,
+	PADCTL1_CS1_SHIFT = 28,
+	/* PADCTL2 = 0x010 */
+	MASK_PADCTL2_16BIT = 0x000000ff,
+	MASK_PADCTL2_32BIT = 0x0000ffff,
+	MASK_PADCTL2	   = 0xffff0000,
+	PADCTL2_SHIFT = 0,
+	/* TEST2_3 = 0x044 */
+	TEST2_3_TESTCNT_SHIFT = 0,
+	TEST2_3_TESTCNT_MASK  = (0xful << TEST2_3_TESTCNT_SHIFT),
+	TEST2_3_TESTAUDPAT_EN = BIT(7),
+	TEST2_3_ADVREFEN_EN   = BIT(30),
+	/* TEST2_4 = 0x048 */
+	TEST2_4_TESTAUDINC_SHIFT  = 0,
+	TEST2_4_TESTAUDINC_MASK   = (0x1ful << TEST2_4_TESTAUDINC_SHIFT),
+	TEST2_4_TESTAUDINIT_SHIFT = 8,
+	TEST2_4_TESTAUDINIT_MASK  = (0x1ful << TEST2_4_TESTAUDINIT_SHIFT),
+	TEST2_4_TESTAUDBITINV_EN  = BIT(14),
+	TEST2_4_TESTAUDMODE_EN    = BIT(15),
+	TEST2_4_TESTXTALKPAT_EN   = BIT(16),
+	/* DDR2CTL = 0x07c */
+	DDR2CTL_WOEN_SHIFT = 3,
+	DDR2CTL_DATLAT_SHIFT = 4,
+	/* MISC = 0x80 */
+	MISC_LATNORMP_SHIFT = 0,
+	MISC_DATLAT_DSEL_SHIFT = 8,
+	/* MRS = 0x088 */
+	MASK_MR2_OP = 0x00800000,
+	/* R0 R1 DQSIEN = 0x094 */
+	DQSIEN_DQS0IEN_SHIFT = 0,
+	DQSIEN_DQS1IEN_SHIFT = 8,
+	DQSIEN_DQS2IEN_SHIFT = 16,
+	DQSIEN_DQS3IEN_SHIFT = 24,
+	/* MCKDLY = 0x0d8 */
+	MCKDLY_DQIENLAT_SHIFT = 4,
+	MCKDLY_DQIENQKEND_SHIFT = 10,
+	MCKDLY_FIXDQIEN_SHIFT = 12,
+	MCKDLY_FIXODT_SHIFT = 23,
+	/* DQSCTL1 = 0x0e0 */
+	DQSCTL1_DQSINCTL_SHIFT = 24,
+	DQSCTL1_DQSIENMODE_SHIFT = 28,
+	/* PADCTL4 = 0x0e4 */
+	PADCTL4_CKEFIXON_SHIFT = 2,
+	PADCTL4_DATLAT3_SHIFT = 4,
+	/* PHYCTL1 = 0x0f0 */
+	PHYCTL1_DATLAT4_SHIFT = 25,
+	PHYCTL1_PHYRST_SHIFT = 28,
+	/* GDDR3CTL1 = 0x0f4 */
+	GDDR3CTL1_BKSWAP_SHIFT = 20,
+	GDDR3CTL1_RDATRST_SHIFT = 25,
+	GDDR3CTL1_DQMSWAP_SHIFT = 31,
+	/* RKCFG = 0x110 */
+	MASK_RKCFG_RKSWAP_EN = 0x08,
+	RKCFG_PBREF_DISBYRATE_SHIFT = 6,
+	RKCFG_WDATKEY64_SHIFT = 29,
+	/* DQSCTL2 = 0x118 */
+	DQSCTL2_DQSINCTL_SHIFT = 0,
+	/* DQSGCTL = 0x124 */
+	DQSGCTL_DQSGDUALP_SHIFT = 30,
+	/* PHYCLKDUTY = 0x148 */
+	PHYCLKDUTY_CMDCLKP0DUTYN_SHIFT = 16,
+	PHYCLKDUTY_CMDCLKP0DUTYP_SHIFT = 18,
+	PHYCLKDUTY_CMDCLKP0DUTYSEL_SHIFT = 28,
+	/* CMDDLY0 = 0x1a8 */
+	CMDDLY0_RA0_SHIFT = 0,
+	CMDDLY0_RA1_SHIFT = 8,
+	CMDDLY0_RA2_SHIFT = 16,
+	CMDDLY0_RA3_SHIFT = 24,
+	/* CMDDLY1 = 0x1ac */
+	CMDDLY1_RA7_SHIFT = 24,
+	/* CMDDLY3 = 0x1b4 */
+	CMDDLY3_BA0_SHIFT = 8,
+	CMDDLY3_BA1_SHIFT = 16,
+	CMDDLY3_BA2_SHIFT = 24,
+	/* CMDDLY4 = 0x1b8 */
+	CMDDLY4_CS_SHIFT = 0,
+	CMDDLY4_CKE_SHIFT = 8,
+	CMDDLY4_RAS_SHIFT = 16,
+	CMDDLY4_CAS_SHIFT = 24,
+	/* CMDDLY5 = 0x1bc */
+	CMDDLY5_WE_SHIFT = 8,
+	CMDDLY5_RA13_SHIFT = 16,
+	/* DQSCAL0 = 0x1c0 */
+	DQSCAL0_RA14_SHIFT = 24,
+	DQSCAL0_STBCALEN_SHIFT = 31,
+	/* DQSCAL1 = 0x1c4 */
+	DQSCAL1_CKE1_SHIFT = 24,
+	/* IMPCAL = 0x1c8 */
+	IMP_CALI_EN_SHIFT = 0,
+	IMP_CALI_HW_SHIFT = 1,
+	IMP_CALI_ENN_SHIFT = 4,
+	IMP_CALI_ENP_SHIFT = 5,
+	IMP_CALI_PDN_SHIFT = 6,
+	IMP_CALI_PDP_SHIFT = 7,
+	IMP_CALI_DRVP_SHIFT = 8,
+	IMP_CALI_DRVN_SHIFT = 12,
+	/* JMETER for PLL2, PLL3, PLL4 */
+	JMETER_EN_BIT= BIT(0),
+	JMETER_COUNTER_SHIFT = 16,
+	JMETER_COUNTER_MASK = (0xffff << JMETER_COUNTER_SHIFT),
+	/* SPCMD = 0x1e4 */
+	SPCMD_MRWEN_SHIFT = 0,
+	SPCMD_DQSGCNTEN_SHIFT = 8,
+	SPCMD_DQSGCNTRST_SHIFT = 9,
+	/* JMETER for PLL2/3/4 ST */
+	JMETER_PLL_ZERO_SHIFT = 0,
+	JMETER_PLL_ONE_SHIFT = 16,
+	/* TESTRPT = 0x3fc */
+	TESTRPT_DM_CMP_CPT_SHIFT = 10,
+	TESTRPT_DM_CMP_ERR_SHIFT = 14,
+	/* SELPH2 = 0x404 */
+	SELPH2_TXDLY_DQSGATE_SHIFT = 12,
+	SELPH2_TXDLY_DQSGATE_P1_SHIFT = 20,
+	/* SELPH5 = 0x410 */
+	SELPH5_DLY_DQSGATE_SHIFT = 22,
+	SELPH5_DLY_DQSGATE_P1_SHIFT = 24,
+	/* SELPH6_1 = 0x418 */
+	SELPH6_1_DLY_R1DQSGATE_SHIFT = 0,
+	SELPH6_1_DLY_R1DQSGATE_P1_SHIFT = 2,
+	SELPH6_1_TXDLY_R1DQSGATE_SHIFT = 4,
+	SELPH6_1_TXDLY_R1DQSGATE_P1_SHIFT = 8,
+	/* MEMPLL_S14 = 0x638 */
+	MASK_MEMPLL_DL = 0xc0ffffff,
+	MEMPLL_FB_DL_SHIFT = 0,
+	MEMPLL_REF_DL_SHIFT = 8,
+	MEMPLL_DL_SHIFT = 24,
+	MEMPLL_MODE_SHIFT = 29,
+	/* MEMPLL_DIVIDER = 0x640 */
+	MEMCLKENB_SHIFT = 5
+};
+
+struct dramc_ao_regs {
+	uint32_t actim0;		/* 0x0 */
+	uint32_t conf1;			/* 0x4 */
+	uint32_t conf2;			/* 0x8 */
+	uint32_t rsvd_ao1[3];		/* 0xc */
+	uint32_t r0deldly;		/* 0x18 */
+	uint32_t r1deldly;		/* 0x1c */
+	uint32_t r0difdly;		/* 0x20 */
+	uint32_t r1difdly;		/* 0x24 */
+	uint32_t dllconf;		/* 0x28 */
+	uint32_t rsvd_ao2[6];		/* 0x2c */
+	uint32_t test2_3;		/* 0x44 */
+	uint32_t test2_4;		/* 0x48 */
+	uint32_t catraining;		/* 0x4c */
+	uint32_t catraining2;		/* 0x50 */
+	uint32_t wodt;			/* 0x54 */
+	uint32_t rsvd_ao3[9];		/* 0x58 */
+	uint32_t ddr2ctl;		/* 0x7c */
+	uint32_t misc;			/* 0x80 */
+	uint32_t zqcs;			/* 0x84 */
+	uint32_t mrs;			/* 0x88 */
+	uint32_t clk1delay;		/* 0x8c */
+	uint32_t rsvd_ao4[1];		/* 0x90 */
+	uint32_t dqsien[2];		/* 0x94 */
+	uint32_t rsvd_ao5[2];		/* 0x9c */
+	uint32_t iodrv1;		/* 0xa4 */
+	uint32_t iodrv2;		/* 0xa8 */
+	uint32_t iodrv3;		/* 0xac */
+	uint32_t iodrv4;		/* 0xb0 */
+	uint32_t iodrv5;		/* 0xb4 */
+	uint32_t iodrv6;		/* 0xb8 */
+	uint32_t drvctl1;		/* 0xbc */
+	uint32_t dllsel;		/* 0xc0 */
+	uint32_t rsvd_ao7[5];		/* 0xc4 */
+	uint32_t mckdly;		/* 0xd8 */
+	uint32_t rsvd_ao8[1];		/* 0xdc */
+	uint32_t dqsctl1;		/* 0xe0 */
+	uint32_t padctl4;		/* 0xe4 */
+	uint32_t rsvd_ao9[2];		/* 0xe8 */
+	uint32_t phyctl1;		/* 0xf0 */
+	uint32_t gddr3ctl1;		/* 0xf4 */
+	uint32_t padctl7;		/* 0xf8 */
+	uint32_t misctl0;		/* 0xfc */
+	uint32_t ocdk;			/* 0x100 */
+	uint32_t rsvd_ao10[3];		/* 0x104 */
+	uint32_t rkcfg;			/* 0x110 */
+	uint32_t ckphdet;		/* 0x114 */
+	uint32_t dqsctl2;		/* 0x118 */
+	uint32_t rsvd_ao11[5];		/* 0x11c */
+	uint32_t clkctl;		/* 0x130 */
+	uint32_t rsvd_ao12[1];		/* 0x134 */
+	uint32_t dummy;			/* 0x138 */
+	uint32_t write_leveling;	/* 0x13c */
+	uint32_t rsvd_ao13[10];		/* 0x140 */
+	uint32_t arbctl0;		/* 0x168 */
+	uint32_t rsvd_ao14[21];		/* 0x16c */
+	uint32_t dqscal0;		/* 0x1c0 */
+	uint32_t dqscal1;		/* 0x1c4 */
+	uint32_t impcal;		/* 0x1c8 */
+	uint32_t rsvd_ao15[4];		/* 0x1cc */
+	uint32_t dramc_pd_ctrl;		/* 0x1dc */
+	uint32_t lpddr2_3;		/* 0x1e0 */
+	uint32_t spcmd;			/* 0x1e4 */
+	uint32_t actim1;		/* 0x1e8 */
+	uint32_t perfctl0;		/* 0x1ec */
+	uint32_t ac_derating;		/* 0x1f0 */
+	uint32_t rrrate_ctl;		/* 0x1f4 */
+	uint32_t ac_time_05t;		/* 0x1f8 */
+	uint32_t mrr_ctl;		/* 0x1fc */
+	uint32_t rsvd_ao16[4];		/* 0x200 */
+	uint32_t dqidly[9];		/* 0x210 */
+	uint32_t rsvd_ao17[115];	/* 0x234 */
+	uint32_t selph1;		/* 0x400 */
+	uint32_t selph2;		/* 0x404 */
+	uint32_t selph3;		/* 0x408 */
+	uint32_t selph4;		/* 0x40c */
+	uint32_t selph5;		/* 0x410 */
+	uint32_t selph6;		/* 0x414 */
+	uint32_t selph6_1;		/* 0x418 */
+	uint32_t selph7;		/* 0x41c */
+	uint32_t selph8;		/* 0x420 */
+	uint32_t selph9;		/* 0x424 */
+	uint32_t selph10;		/* 0x428 */
+	uint32_t selph11;		/* 0x42c */
+};
+
+check_member(dramc_ao_regs, selph11, 0x42c);
+
+struct dramc_nao_regs {
+	uint32_t rsvd_nao1[11];			/* 0x0 */
+	uint32_t test_mode;			/* 0x2c */
+	uint32_t rsvd_nao2[3];			/* 0x30 */
+	uint32_t test2_1;			/* 0x3c */
+	uint32_t test2_2;			/* 0x40 */
+	uint32_t rsvd_nao3[48];			/* 0x44 */
+	uint32_t lbwdat0;			/* 0x104 */
+	uint32_t lbwdat1;			/* 0x108 */
+	uint32_t lbwdat2;			/* 0x10c */
+	uint32_t rsvd_nao4[1];			/* 0x110 */
+	uint32_t ckphdet;			/* 0x114 */
+	uint32_t rsvd_nao5[48];			/* 0x118 */
+	uint32_t dmmonitor;			/* 0x1d8 */
+	uint32_t rsvd_nao6[41];			/* 0x1dc */
+	uint32_t r2r_page_hit_counter;		/* 0x280 */
+	uint32_t r2r_page_miss_counter;		/* 0x284 */
+	uint32_t r2r_interbank_counter;		/* 0x288 */
+	uint32_t r2w_page_hit_counter;		/* 0x28c */
+	uint32_t r2w_page_miss_counter;		/* 0x290 */
+	uint32_t r2w_interbank_counter;		/* 0x294 */
+	uint32_t w2r_page_hit_counter;		/* 0x298 */
+	uint32_t w2r_page_miss_counter;		/* 0x29c */
+	uint32_t w2r_page_interbank_counter;	/* 0x2a0 */
+	uint32_t w2w_page_hit_counter;		/* 0x2a4 */
+	uint32_t w2w_page_miss_counter;		/* 0x2a8 */
+	uint32_t w2w_page_interbank_counter;	/* 0x2ac */
+	uint32_t dramc_idle_counter;		/* 0x2b0 */
+	uint32_t freerun_26m_counter;		/* 0x2b4 */
+	uint32_t refresh_pop_counter;		/* 0x2b8 */
+	uint32_t jmeter_st;			/* 0x2bc */
+	uint32_t dq_cal_max[8];			/* 0x2c0 */
+	uint32_t dqs_cal_min[8];		/* 0x2e0 */
+	uint32_t dqs_cal_max[8];		/* 0x300 */
+	uint32_t rsvd_nao7[4];			/* 0x320 */
+	uint32_t read_bytes_counter;		/* 0x330 */
+	uint32_t write_bytes_counter;		/* 0x334 */
+	uint32_t rsvd_nao8[6];			/* 0x338 */
+	uint32_t dqical[4];			/* 0x350 */
+	uint32_t rsvd_nao9[4];			/* 0x360 */
+	uint32_t cmp_err;			/* 0x370 */
+	uint32_t r0dqsiendly;			/* 0x374 */
+	uint32_t r1dqsiendly;			/* 0x378 */
+	uint32_t rsvd_nao10[9];			/* 0x37c */
+	uint32_t dqsdly0;			/* 0x3a0 */
+	uint32_t rsvd_nao11[4];			/* 0x3a4 */
+	uint32_t mrrdata;			/* 0x3b4 */
+	uint32_t spcmdresp;			/* 0x3b8 */
+	uint32_t iorgcnt;			/* 0x3bc */
+	uint32_t dqsgnwcnt[6];			/* 0x3c0 */
+	uint32_t rsvd_nao12[4];			/* 0x3d8 */
+	uint32_t ckphcnt;			/* 0x3e8 */
+	uint32_t rsvd_nao13[4];			/* 0x3ec */
+	uint32_t testrpt;			/* 0x3fc */
+};
+
+check_member(dramc_nao_regs, testrpt, 0x3fc);
+
+struct dramc_ddrphy_regs {
+	uint32_t rsvd_phy1[3];		/* 0x0 */
+	uint32_t padctl1;		/* 0xc */
+	uint32_t padctl2;		/* 0x10 */
+	uint32_t padctl3;		/* 0x14 */
+	uint32_t rsvd_phy2[25];		/* 0x18 */
+	uint32_t ddr2ctl;		/* 0x7c */
+	uint32_t rsvd_phy3[3];		/* 0x80 */
+	uint32_t clk1delay;		/* 0x8c */
+	uint32_t ioctl;			/* 0x90 */
+	uint32_t rsvd_phy4[7];		/* 0x94 */
+	uint32_t iodrv4;		/* 0xb0 */
+	uint32_t iodrv5;		/* 0xb4 */
+	uint32_t iodrv6;		/* 0xb8 */
+	uint32_t drvctl1;		/* 0xbc */
+	uint32_t dllsel;		/* 0xc0 */
+	uint32_t rsvd_phy5[2];		/* 0xc4 */
+	uint32_t tdsel[3];		/* 0xcc */
+	uint32_t mckdly;		/* 0xd8 */
+	uint32_t dqsctl0;		/* 0xdc */
+	uint32_t dqsctl1;		/* 0xe0 */
+	uint32_t dqsctl4;		/* 0xe4 */
+	uint32_t dqsctl5;		/* 0xe8 */
+	uint32_t dqsctl6;		/* 0xec */
+	uint32_t phyctl1;		/* 0xf0 */
+	uint32_t gddr3ctl1;		/* 0xf4 */
+	uint32_t rsvd_phy6[1];		/* 0xf8 */
+	uint32_t misctl0;		/* 0xfc */
+	uint32_t ocdk;			/* 0x100 */
+	uint32_t rsvd_phy7[8];		/* 0x104 */
+	uint32_t dqsgctl;		/* 0x124 */
+	uint32_t rsvd_phy8[6];		/* 0x128 */
+	uint32_t ddrphydqsgctl;		/* 0x140 */
+	uint32_t dqsgct2;		/* 0x144 */
+	uint32_t phyclkduty;		/* 0x148 */
+	uint32_t rsvd_phy9[3];		/* 0x14c */
+	uint32_t dqsisel;		/* 0x158 */
+	uint32_t dqmdqs_sel;		/* 0x15c */
+	uint32_t rsvd_phy10[10];	/* 0x160 */
+	uint32_t jmeterpop1;		/* 0x188 */
+	uint32_t jmeterpop2;		/* 0x18c */
+	uint32_t jmeterpop3;		/* 0x190 */
+	uint32_t jmeterpop4;		/* 0x194 */
+	uint32_t rsvd_phy11[4];		/* 0x198 */
+	uint32_t cmddly[6];		/* 0x1a8 */
+	uint32_t dqscal0;		/* 0x1c0 */
+	uint32_t rsvd_phy12[2];		/* 0x1c4 */
+	uint32_t jmeter[3];		/* 0x1cc */
+	uint32_t rsvd_phy13[2];		/* 0x1d8 */
+	uint32_t lpddr2_3;		/* 0x1e0 */
+	uint32_t spcmd;			/* 0x1e4 */
+	uint32_t rsvd_phy14[6];		/* 0x1e8 */
+	uint32_t dqodly[4];		/* 0x200 */
+	uint32_t rsvd_phy15[11];	/* 0x210 */
+	uint32_t lpddr2_4;		/* 0x23c */
+	uint32_t rsvd_phy16[56];	/* 0x240 */
+	uint32_t jmeter_pll_st[3];	/* 0x320 */
+	uint32_t jmeter_done_st;	/* 0x32c */
+	uint32_t rsvd_phy17[2];		/* 0x330 */
+	uint32_t jmeter_pll1_st;	/* 0x338 */
+	uint32_t jmeter_pop_pll2_st;	/* 0x33c */
+	uint32_t jmeter_pop_pll3_st;	/* 0x340 */
+	uint32_t jmeter_pop_pll4_st;	/* 0x344 */
+	uint32_t jmeter_pop_pll1_st;	/* 0x348 */
+	uint32_t rsvd_phy18[13];	/* 0x34c */
+	uint32_t dq_o1;			/* 0x380 */
+	uint32_t rsvd_phy19[2];		/* 0x384 */
+	uint32_t stben[4];		/* 0x38c */
+	uint32_t rsvd_phy20[16];	/* 0x39c */
+	uint32_t dllcnt0;		/* 0x3dc */
+	uint32_t pllautok;		/* 0x3e0 */
+	uint32_t poppllautok;		/* 0x3e4 */
+	uint32_t rsvd_phy21[18];	/* 0x3e8 */
+	uint32_t selph12;		/* 0x430 */
+	uint32_t selph13;		/* 0x434 */
+	uint32_t selph14;		/* 0x438 */
+	uint32_t selph15;		/* 0x43c */
+	uint32_t selph16;		/* 0x440 */
+	uint32_t selph17;		/* 0x444 */
+	uint32_t selph18;		/* 0x448 */
+	uint32_t selph19;		/* 0x44c */
+	uint32_t selph20;		/* 0x450 */
+	uint32_t rsvd_phy22[91];	/* 0x454 */
+	uint32_t peri[4];		/* 0x5c0 */
+	uint32_t rsvd_phy23[12];	/* 0x5d0 */
+	uint32_t mempll[15];		/* 0x600 */
+	uint32_t ddrphy_cg_ctrl;	/* 0x63c */
+	uint32_t mempll_divider;	/* 0x640 */
+	uint32_t vrefctl0;		/* 0x644 */
+	uint32_t rsvd_phy24[18];	/* 0x648 */
+	uint32_t mempll05_divider;	/* 0x690 */
+};
+
+check_member(dramc_ddrphy_regs, mempll05_divider, 0x690);
+
+struct emi_regs {
+	uint32_t emi_cona;		/* 0x0 */
+	uint32_t rsvd_emi1;		/* 0x4 */
+	uint32_t emi_conb;		/* 0x08 */
+	uint32_t rsvd_emi2;		/* 0x0c */
+	uint32_t emi_conc;		/* 0x10 */
+	uint32_t rsvd_emi3;		/* 0x14 */
+	uint32_t emi_cond;		/* 0x18 */
+	uint32_t rsvd_emi4;		/* 0x1c */
+	uint32_t emi_cone;		/* 0x20 */
+	uint32_t rsvd_emi5;		/* 0x24 */
+	uint32_t emi_conf;		/* 0x28 */
+	uint32_t rsvd_emi6;		/* 0x2c */
+	uint32_t emi_cong;		/* 0x30 */
+	uint32_t rsvd_emi7;		/* 0x34 */
+	uint32_t emi_conh;		/* 0x38 */
+	uint32_t rsvd_emi8[9];		/* 0x3c */
+	uint32_t emi_conm;		/* 0x60 */
+	uint32_t rsvd_emi9[5];		/* 0x64 */
+	uint32_t emi_mdct;		/* 0x78 */
+	uint32_t rsvd_emi10[21];	/* 0x7c */
+	uint32_t emi_test0;		/* 0xd0 */
+	uint32_t rsvd_emi11;		/* 0xd4 */
+	uint32_t emi_test1;		/* 0xd8 */
+	uint32_t rsvd_emi12;		/* 0xdc */
+	uint32_t emi_testa;		/* 0xe0 */
+	uint32_t rsvd_emi13;		/* 0xe4 */
+	uint32_t emi_testb;		/* 0xe8 */
+	uint32_t rsvd_emi14;		/* 0xec */
+	uint32_t emi_testc;		/* 0xf0 */
+	uint32_t rsvd_emi15;		/* 0xf4 */
+	uint32_t emi_testd;		/* 0xf8 */
+	uint32_t rsvd_emi16;		/* 0xfc */
+	uint32_t emi_arba;		/* 0x100 */
+	uint32_t rsvd_emi17[3];		/* 0x104 */
+	uint32_t emi_arbc;		/* 0x110 */
+	uint32_t rsvd_emi18;		/* 0x114 */
+	uint32_t emi_arbd;		/* 0x118 */
+	uint32_t rsvd_emi19;		/* 0x11c */
+	uint32_t emi_arbe;		/* 0x120 */
+	uint32_t rsvd_emi20;		/* 0x124 */
+	uint32_t emi_arbf;		/* 0x128 */
+	uint32_t rsvd_emi21;		/* 0x12c */
+	uint32_t emi_arbg;		/* 0x130 */
+	uint32_t rsvd_emi22;		/* 0x134 */
+	uint32_t emi_arbh;		/* 0x138 */
+	uint32_t rsvd_emi23;		/* 0x13c */
+	uint32_t emi_arbi;		/* 0x140 */
+	uint32_t emi_arbi_2nd;		/* 0x144 */
+	uint32_t emi_arbj;		/* 0x148 */
+	uint32_t emi_arbj_2nd;		/* 0x14c */
+	uint32_t emi_arbk;		/* 0x150 */
+	uint32_t emi_arbk_2nd;		/* 0x154 */
+	uint32_t emi_slct;		/* 0x158 */
+	uint32_t rsvd_emi24;		/* 0x15C */
+	uint32_t emi_mpua;		/* 0x160 */
+	uint32_t rsvd_emi25;		/* 0x164 */
+	uint32_t emi_mpub;		/* 0x168 */
+	uint32_t rsvd_emi26;		/* 0x16c */
+	uint32_t emi_mpuc;		/* 0x170 */
+	uint32_t rsvd_emi27;		/* 0x174 */
+	uint32_t emi_mpud;		/* 0x178 */
+	uint32_t rsvd_emi28;		/* 0x17C */
+	uint32_t emi_mpue;		/* 0x180 */
+	uint32_t rsvd_emi29;		/* 0x184 */
+	uint32_t emi_mpuf;		/* 0x188 */
+	uint32_t rsvd_emi30;		/* 0x18C */
+	uint32_t emi_mpug;		/* 0x190 */
+	uint32_t rsvd_emi31;		/* 0x194 */
+	uint32_t emi_mpuh;		/* 0x198 */
+	uint32_t rsvd_emi32;		/* 0x19C */
+	uint32_t emi_mpui;		/* 0x1A0 */
+	uint32_t rsvd_emi33;		/* 0x1A4 */
+	uint32_t emi_mpuj;		/* 0x1A8 */
+	uint32_t rsvd_emi34;		/* 0x1AC */
+	uint32_t emi_mpuk;		/* 0x1B0 */
+	uint32_t rsvd_emi35;		/* 0x1B4 */
+	uint32_t emi_mpul;		/* 0x1B8 */
+	uint32_t rsvd_emi36;		/* 0x1BC */
+	uint32_t emi_mpum;		/* 0x1C0 */
+	uint32_t rsvd_emi37;		/* 0x1C4 */
+	uint32_t emi_mpun;		/* 0x1C8 */
+	uint32_t rsvd_emi38;		/* 0x1CC */
+	uint32_t emi_mpuo;		/* 0x1D0 */
+	uint32_t rsvd_emi39;		/* 0x1D4 */
+	uint32_t emi_mpup;		/* 0x1D8 */
+	uint32_t rsvd_emi40;		/* 0x1DC */
+	uint32_t emi_mpuq;		/* 0x1E0 */
+	uint32_t rsvd_emi41;		/* 0x1E4 */
+	uint32_t emi_mpur;		/* 0x1E8 */
+	uint32_t rsvd_emi42;		/* 0x1EC */
+	uint32_t emi_mpus;		/* 0x1F0 */
+	uint32_t rsvd_emi43;		/* 0x1F4 */
+	uint32_t emi_mput;		/* 0x1F8 */
+	uint32_t rsvd_emi44;		/* 0x1FC */
+	uint32_t emi_mpuu;		/* 0x200 */
+	uint32_t rsvd_emi45[7];		/* 0x204 */
+	uint32_t emi_mpuy;		/* 0x220 */
+	uint32_t rsvd_emi46[119];	/* 0x224 */
+	uint32_t emi_bmen;		/* 0x400 */
+};
+
+check_member(emi_regs, emi_bmen, 0x400);
+
+extern struct dramc_ao_regs *ao_regs;
+extern struct dramc_nao_regs *nao_regs;
+extern struct dramc_ddrphy_regs *ddrphy_regs;
+
+struct dramc_channel {
+	struct dramc_ao_regs *ao_regs;
+	struct dramc_nao_regs *nao_regs;
+	struct dramc_ddrphy_regs *ddrphy_regs;
+};
+
+static struct dramc_channel const ch[2] = {
+	{(void *)CHA_DRAMCAO_BASE, (void *)CHA_DRAMCNAO_BASE, (void *)CHA_DDRPHY_BASE},
+	{(void *)CHB_DRAMCAO_BASE, (void *)CHB_DRAMCNAO_BASE, (void *)CHB_DDRPHY_BASE}
+};
+
+#endif /* _DRAMC_REGISTER_H_ */
diff --git a/src/soc/mediatek/mt8173/include/soc/emi.h b/src/soc/mediatek/mt8173/include/soc/emi.h
new file mode 100644
index 0000000..d3a2aee
--- /dev/null
+++ b/src/soc/mediatek/mt8173/include/soc/emi.h
@@ -0,0 +1,140 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef SOC_MEDIATEK_MT8173_EMI_H
+#define SOC_MEDIATEK_MT8173_EMI_H
+
+#include <soc/dramc_common.h>
+#include <stdint.h>
+
+/* DDR type */
+enum ram_type {
+	TYPE_INVALID,
+	TYPE_DDR1,
+	TYPE_LPDDR2,
+	TYPE_LPDDR3,
+	TYPE_PCDDR3
+};
+
+enum {
+	/*
+	 * Vmem voltage adjustment:
+	 * 1) HV: high voltage
+	 * 2) NV: normal voltage
+	 * 3) LV: low voltage
+	 */
+	Vmem_HV_LPDDR3 = 0x50,	/* 1.300V */
+	Vmem_NV_LPDDR3 = 0x44,	/* 1.225V */
+	Vmem_LV_LPDDR3 = 0x36	/* 1.138V */
+};
+
+enum {
+	/*
+	 * Vcore voltage adjustment:
+	 * 1) HHV: extra high voltage
+	 * 2) HV: high voltage
+	 * 3) NV: normal voltage
+	 * 4) LV: low voltage
+	 * 5) LLV: extra low voltage
+	 */
+	Vcore_HHV_LPPDR3 = 0x60,   /* 1.300V */
+	Vcore_HV_LPPDR3  = 0x48,   /* 1.150V */
+	Vcore_NV_LPPDR3  = 0x44,   /* 1.125V */
+	Vcore_LV_LPPDR3  = 0x34,   /* 1.025V */
+	Vcore_LLV_LPPDR3 = 0x25    /* 0.931V */
+};
+
+struct mt8173_calib_params {
+	u8 impedance_drvp;
+	u8 impedance_drvn;
+	u8 datlat_ucfirst;
+	s8 ca_train[CHANNEL_NUM][CATRAINING_NUM];
+	s8 ca_train_center[CHANNEL_NUM];
+	s8 wr_level[CHANNEL_NUM][DQS_NUMBER];
+	u8 gating_win[CHANNEL_NUM][DUAL_RANKS][GW_PARAM_NUM];
+	u32 rx_dqs_dly[CHANNEL_NUM];
+	u32 rx_dq_dly[CHANNEL_NUM][DQS_BIT_NUMBER];
+};
+
+struct mt8173_timing_params {
+	u32 actim;
+	u32 actim1;
+	u32 actim05t;
+	u32 conf1;
+	u32 conf2;
+	u32 ddr2ctl;
+	u32 gddr3ctl1;
+	u32 misctl0;
+	u32 pd_ctrl;
+	u32 rkcfg;
+	u32 test2_4;
+	u32 test2_3;
+};
+
+struct mt8173_emi_params {
+	u32 cona;
+	u32 conb;
+	u32 conc;
+	u32 cond;
+	u32 cone;
+	u32 conf;
+	u32 cong;
+	u32 conh;
+	u32 conm_1;
+	u32 conm_2;
+	u32 mdct_1;
+	u32 mdct_2;
+	u32 test0;
+	u32 test1;
+	u32 testb;
+	u32 testc;
+	u32 testd;
+	u32 arba;
+	u32 arbc;
+	u32 arbd;
+	u32 arbe;
+	u32 arbf;
+	u32 arbg;
+	u32 arbi;
+	u32 arbj;
+	u32 arbk;
+	u32 slct_1;
+	u32 slct_2;
+	u32 bmen;
+};
+
+struct mt8173_mrs_params {
+	u32 mrs_1;
+	u32 mrs_2;
+	u32 mrs_3;
+	u32 mrs_10;
+	u32 mrs_11;
+	u32 mrs_63;
+};
+
+struct mt8173_sdram_params {
+	struct mt8173_calib_params calib_params;
+	struct mt8173_timing_params ac_timing;
+	struct mt8173_emi_params emi_set;
+	struct mt8173_mrs_params mrs_set;
+	enum ram_type type;
+	unsigned int dram_freq;
+};
+
+void mt_set_emi(const struct mt8173_sdram_params *sdram_params);
+void mt_mem_init(const struct mt8173_sdram_params *sdram_params);
+const struct mt8173_sdram_params *get_sdram_config(void);
+
+#endif
diff --git a/src/soc/mediatek/mt8173/include/soc/pll.h b/src/soc/mediatek/mt8173/include/soc/pll.h
index 1eab709..6d38bb3 100644
--- a/src/soc/mediatek/mt8173/include/soc/pll.h
+++ b/src/soc/mediatek/mt8173/include/soc/pll.h
@@ -16,6 +16,7 @@
 #ifndef SOC_MEDIATEK_MT8173_PLL_H
 #define SOC_MEDIATEK_MT8173_PLL_H
 
+#include <soc/emi.h>
 #include <soc/addressmap.h>
 
 struct mt8173_topckgen_regs {
@@ -285,5 +286,9 @@ void mt_pll_post_init(void);
 void mt_pll_init(void);
 void mt_pll_set_aud_div(u32 rate);
 void mt_pll_enable_ssusb_clk(void);
+void mt_mem_pll_set_clk_cfg(void);
+void mt_mem_pll_config_pre(const struct mt8173_sdram_params *sdram_params);
+void mt_mem_pll_config_post(void);
+void mt_mem_pll_mux(void);
 
 #endif /* SOC_MEDIATEK_MT8173_PLL_H */
diff --git a/src/soc/mediatek/mt8173/memory.c b/src/soc/mediatek/mt8173/memory.c
new file mode 100644
index 0000000..3db9df8
--- /dev/null
+++ b/src/soc/mediatek/mt8173/memory.c
@@ -0,0 +1,357 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 MediaTek Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <arch/io.h>
+#include <assert.h>
+#include <console/console.h>
+#include <soc/addressmap.h>
+#include <soc/dramc_register.h>
+#include <soc/dramc_pi_api.h>
+#include <soc/emi.h>
+#include <soc/pll.h>
+
+enum {
+	/* test patterns */
+	PATTERN0 = 0x00000000,
+	PATTERN1 = 0x5A5A5A5A,
+	PATTERN2 = 0xA5A5A5A5,
+	PATTERN3 = 0xA5A5A500,
+	PATTERN4 = 0xA500A500,
+	PATTERN5 = 0xA5000000,
+	PATTERN6 = 0xFFFF0000,
+	PATTERN7 = 0x0000FFFF,
+	PATTERN8 = 0x00000012,
+	PATTERN9 = 0x00000034,
+	PATTERNA = 0x00000056,
+	PATTERNB = 0x00000078,
+	PATTERNC = 0x00001234,
+	PATTERND = 0x00005678,
+	PATTERNE = 0x12345678,
+	PATTERNF = 0xFFFFFFFF
+};
+
+static int complex_mem_test(unsigned int start, unsigned int len)
+{
+	unsigned char *mem8_base = (unsigned char *)(uintptr_t)start;
+	unsigned short *mem16_base = (unsigned short *)(uintptr_t)start;
+	unsigned int *mem32_base = (unsigned int *)(uintptr_t)start;
+	unsigned int *mem_base = (unsigned int *)(uintptr_t)start;
+	unsigned char pattern8;
+	unsigned short pattern16;
+	unsigned int i, j, size, pattern32;
+	unsigned int value;
+
+	size = len >> 2;
+
+	/*  verify the tied bits (tied high)  */
+	for (i = 0; i < size; i++) {
+		mem32_base[i] = PATTERN0;
+	}
+
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN0) {
+			return -1;
+		} else {
+			mem32_base[i] = PATTERNF;
+		}
+	}
+
+	/*  verify the tied bits (tied low)  */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERNF) {
+			return -2;
+		} else
+			mem32_base[i] = PATTERN0;
+	}
+
+	/*  verify pattern 1 (0x00~0xff)  */
+	pattern8 = PATTERN0;
+	for (i = 0; i < len; i++)
+		mem8_base[i] = pattern8++;
+	pattern8 = PATTERN0;
+	for (i = 0; i < len; i++) {
+		if (mem8_base[i] != pattern8++) {
+			return -3;
+		}
+	}
+
+	/*  verify pattern 2 (0x00~0xff)  */
+	pattern8 = PATTERN0;
+	for (i = j = 0; i < len; i += 2, j++) {
+		if (mem8_base[i] == pattern8)
+			mem16_base[j] = pattern8;
+		if (mem16_base[j] != pattern8) {
+			return -4;
+		}
+		pattern8 += 2;
+	}
+
+	/*  verify pattern 3 (0x00~0xffff)  */
+	pattern16 = PATTERN0;
+	for (i = 0; i < (len >> 1); i++)
+		mem16_base[i] = pattern16++;
+	pattern16 = PATTERN0;
+	for (i = 0; i < (len >> 1); i++) {
+		if (mem16_base[i] != pattern16++) {
+			return -5;
+		}
+	}
+
+	/*  verify pattern 4 (0x00~0xffffffff)  */
+	pattern32 = PATTERN0;
+	for (i = 0; i < (len >> 2); i++)
+		mem32_base[i] = pattern32++;
+	pattern32 = PATTERN0;
+	for (i = 0; i < (len >> 2); i++) {
+		if (mem32_base[i] != pattern32++) {
+			return -6;
+		}
+	}
+
+	/*  pattern 5: filling memory range with 0x12345678  */
+	for (i = 0; i < size; i++)
+		mem32_base[i] = PATTERNE;
+
+	/*  read check then fill memory with a5a5a5a5 pattern  */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERNE) {
+			return -7;
+		} else {
+			mem32_base[i] = PATTERN2;
+		}
+	}
+
+	/* read check then fill memory with 00 byte pattern at offset 0h */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN2) {
+			return -8;
+		} else {
+			mem8_base[i * 4] = PATTERN0;
+		}
+	}
+
+	/* read check then fill memory with 00 byte pattern at offset 2h */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN3) {
+			return -9;
+		} else {
+			mem8_base[i * 4 + 2] = PATTERN0;
+		}
+	}
+
+	/*  read check then fill memory with 00 byte pattern at offset 1h  */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN4) {
+			return -10;
+		} else {
+			mem8_base[i * 4 + 1] = PATTERN0;
+		}
+	}
+
+	/*  read check then fill memory with 00 byte pattern at offset 3h  */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN5) {
+			return -11;
+		} else {
+			mem8_base[i * 4 + 3] = PATTERN0;
+		}
+	}
+
+	/*  read check then fill memory with ffff word pattern at offset 1h */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN0) {
+			return -12;
+		} else {
+			mem16_base[i * 2 + 1] = PATTERN7;
+		}
+	}
+
+	/*  read check then fill memory with ffff word pattern at offset 0h */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERN6) {
+			return -13;
+		} else {
+			mem16_base[i * 2] = PATTERN7;
+		}
+	}
+
+	/*  read check  */
+	for (i = 0; i < size; i++) {
+		if (mem32_base[i] != PATTERNF) {
+			return -14;
+		}
+	}
+
+	/*  stage 1 => write 0  */
+	for (i = 0; i < size; i++) {
+		mem_base[i] = PATTERN1;
+	}
+
+	/*  stage 2 => read 0, write 0xf  */
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+
+		if (value != PATTERN1) {
+			return -15;
+		}
+		mem_base[i] = PATTERN2;
+	}
+
+	/*  stage 3 => read 0xf, write 0  */
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+		if (value != PATTERN2) {
+			return -16;
+		}
+		mem_base[i] = PATTERN1;
+	}
+
+	/*  stage 4 => read 0, write 0xf  */
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+		if (value != PATTERN1) {
+			return -17;
+		}
+		mem_base[i] = PATTERN2;
+	}
+
+	/*  stage 5 => read 0xf, write 0  */
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+		if (value != PATTERN2) {
+			return -18;
+		}
+		mem_base[i] = PATTERN1;
+	}
+
+	/*  stage 6 => read 0  */
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+		if (value != PATTERN1) {
+			return -19;
+		}
+	}
+
+	/*  1/2/4-byte combination test  */
+	i = (unsigned int)(uintptr_t)mem_base;
+
+	while (i < (unsigned int)(uintptr_t)mem_base + (size << 2)) {
+		*((unsigned char *)(uintptr_t)i) = PATTERNB;
+		i += 1;
+		*((unsigned char *)(uintptr_t)i) = PATTERNA;
+		i += 1;
+		*((unsigned short *)(uintptr_t)i) = PATTERNC;
+		i += 2;
+		*((unsigned int *)(uintptr_t)i) = PATTERNE;
+		i += 4;
+		*((unsigned short *)(uintptr_t)i) = PATTERND;
+		i += 2;
+		*((unsigned char *)(uintptr_t)i) = PATTERN9;
+		i += 1;
+		*((unsigned char *)(uintptr_t)i) = PATTERN8;
+		i += 1;
+		*((unsigned int *)(uintptr_t)i) = PATTERNE;
+		i += 4;
+		*((unsigned char *)(uintptr_t)i) = PATTERNB;
+		i += 1;
+		*((unsigned char *)(uintptr_t)i) = PATTERNA;
+		i += 1;
+		*((unsigned short *)(uintptr_t)i) = PATTERNC;
+		i += 2;
+		*((unsigned int *)(uintptr_t)i) = PATTERNE;
+		i += 4;
+		*((unsigned short *)(uintptr_t)i) = PATTERND;
+		i += 2;
+		*((unsigned char *)(uintptr_t)i) = PATTERN9;
+		i += 1;
+		*((unsigned char *)(uintptr_t)i) = PATTERN8;
+		i += 1;
+		*((unsigned int *)(uintptr_t)i) = PATTERNE;
+		i += 4;
+	}
+
+	for (i = 0; i < size; i++) {
+		value = mem_base[i];
+		if (value != PATTERNE) {
+			return -20;
+		}
+	}
+
+	/*  verify pattern 1 (0x00~0xff)  */
+	pattern8 = PATTERN0;
+	mem8_base[0] = pattern8;
+	for (i = 0; i < size * 4; i++) {
+		unsigned char waddr8, raddr8;
+
+		waddr8 = i + 1;
+		raddr8 = i;
+		if (i < size * 4 - 1)
+			mem8_base[waddr8] = pattern8 + 1;
+		if (mem8_base[raddr8] != pattern8) {
+			return -21;
+		}
+		pattern8++;
+	}
+
+	/*  verify pattern 2 (0x00~0xffff)  */
+	pattern16 = PATTERN0;
+	mem16_base[0] = pattern16;
+	for (i = 0; i < size * 2; i++) {
+		if (i < size * 2 - 1)
+			mem16_base[i + 1] = pattern16 + 1;
+		if (mem16_base[i] != pattern16) {
+			return -22;
+		}
+		pattern16++;
+	}
+
+	/*  verify pattern 3 (0x00~0xffffffff)  */
+	pattern32 = PATTERN0;
+	mem32_base[0] = pattern32;
+	for (i = 0; i < size; i++) {
+		if (i < size - 1)
+			mem32_base[i + 1] = pattern32 + 1;
+		if (mem32_base[i] != pattern32) {
+			return -23;
+		}
+		pattern32++;
+	}
+
+	return 0;
+}
+
+void mt_mem_init(const struct mt8173_sdram_params *sdram_params)
+{
+	int i = 0;
+
+	/* init mempll */
+	mem_pll_init(sdram_params);
+
+	/* memory calibration */
+	mt_set_emi(sdram_params);
+
+	if (IS_ENABLED(CONFIG_MEMORY_TEST)) {
+		/* do memory test:
+		 * set memory scan range 0x2000
+		 * larger test length, longer system boot up time
+		 */
+		i = complex_mem_test(DDR_BASE, 0x2000);
+
+		printk(BIOS_DEBUG, "[MEM] complex R/W mem test %s : %d\n",
+		       (i == 0) ? "pass" : "fail", i);
+
+		ASSERT(i == 0);
+	}
+}
diff --git a/src/soc/mediatek/mt8173/pll.c b/src/soc/mediatek/mt8173/pll.c
index 3faf785..d54538d 100644
--- a/src/soc/mediatek/mt8173/pll.c
+++ b/src/soc/mediatek/mt8173/pll.c
@@ -505,3 +505,29 @@ void mt_pll_set_aud_div(u32 rate)
 				7 << 28);
 	}
 }
+
+void mt_mem_pll_config_pre(const struct mt8173_sdram_params *sdram_params)
+{
+	u32 mpll_sdm_pcw_20_0 = 0xF13B1;
+
+	/* disable MPLL for adjusting memory clk frequency */
+	clrbits_le32(&mt8173_apmixed->mpll_con0, BIT(0));
+	/* MPLL configuration: mode selection */
+	setbits_le32(&mt8173_apmixed->mpll_con0, BIT(16));
+	clrbits_le32(&mt8173_apmixed->mpll_con0, 0x7 << 4);
+	clrbits_le32(&mt8173_apmixed->pll_test_con0, 1 << 31);
+	/* set RG_MPLL_SDM_PCW for feedback divide ratio */
+	clrsetbits_le32(&mt8173_apmixed->mpll_con1, 0x1fffff, mpll_sdm_pcw_20_0);
+}
+
+void mt_mem_pll_config_post(void)
+{
+	/* power up sequence starts: enable MPLL */
+	setbits_le32(&mt8173_apmixed->mpll_con0, BIT(0));
+}
+
+void mt_mem_pll_mux(void)
+{
+	/* CLK_CFG_0 */
+	mux_set_sel(&muxes[TOP_MEM_SEL], 1); /* 1: dmpll_ck */
+}



More information about the coreboot-gerrit mailing list