soc/mediatek: add new driver 'msdc' for eMMC

Add MTK host mmc driver support.
MTK host controller supports eMMC5.1 spec.

BUG=b:177389446
TEST=emerge-asurada coreboot
BRANCH=asurada

Signed-off-by: Wenbin Mei <wenbin.mei@mediatek.com>
Change-Id: I54a7749ed167c00cd631a76af7c67c654c7bc725
Reviewed-on: https://review.coreboot.org/c/coreboot/+/51966
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@google.com>
diff --git a/src/commonlib/include/commonlib/sd_mmc_ctrlr.h b/src/commonlib/include/commonlib/sd_mmc_ctrlr.h
index 1b1dd51..6ac2740 100644
--- a/src/commonlib/include/commonlib/sd_mmc_ctrlr.h
+++ b/src/commonlib/include/commonlib/sd_mmc_ctrlr.h
@@ -13,6 +13,14 @@
 #define CARD_TIMEOUT		-19
 #define CARD_IN_PROGRESS	-20 /* operation is in progress */
 
+/* MMC status in CBMEM_ID_MMC_STATUS */
+enum {
+	MMC_STATUS_NEED_RESET = 0,
+	MMC_STATUS_CMD1_READY_OR_IN_PROGRESS,
+	MMC_STATUS_CMD1_READY,
+	MMC_STATUS_CMD1_IN_PROGRESS,
+};
+
 struct mmc_command {
 	uint16_t cmdidx;
 
diff --git a/src/soc/mediatek/common/include/soc/msdc.h b/src/soc/mediatek/common/include/soc/msdc.h
new file mode 100644
index 0000000..6208b72
--- /dev/null
+++ b/src/soc/mediatek/common/include/soc/msdc.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MSDC_H_
+#define __MSDC_H_
+
+#include <commonlib/sd_mmc_ctrlr.h>
+
+/*------------------------------*/
+/* Register Offset              */
+/*------------------------------*/
+#define MSDC_CFG	0x0
+#define MSDC_IOCON	0x04
+#define MSDC_PS		0x08
+#define MSDC_INT	0x0c
+#define MSDC_INTEN	0x10
+#define MSDC_FIFOCS	0x14
+#define MSDC_TXDATA	0x18
+#define MSDC_RXDATA	0x1c
+#define SDC_CFG		0x30
+#define SDC_CMD		0x34
+#define SDC_ARG		0x38
+#define SDC_STS		0x3c
+#define SDC_RESP0	0x40
+#define SDC_RESP1	0x44
+#define SDC_RESP2	0x48
+#define SDC_RESP3	0x4c
+#define SDC_BLK_NUM	0x50
+#define SDC_ADV_CFG0	0x64
+#define EMMC_IOCON	0x7c
+#define SDC_ACMD_RESP	0x80
+#define DMA_SA_H4BIT	0x8c
+#define MSDC_DMA_SA	0x90
+#define MSDC_DMA_CTRL	0x98
+#define MSDC_DMA_CFG	0x9c
+#define MSDC_PATCH_BIT	0xb0
+#define MSDC_PATCH_BIT1	0xb4
+#define MSDC_PATCH_BIT2	0xb8
+#define MSDC_PAD_TUNE	0xec
+#define MSDC_PAD_TUNE0	0xf0
+#define PAD_DS_TUNE	0x188
+#define PAD_CMD_TUNE	0x18c
+#define EMMC51_CFG0	0x204
+#define EMMC50_CFG0	0x208
+#define EMMC50_CFG1	0x20c
+#define EMMC50_CFG3	0x220
+#define SDC_FIFO_CFG	0x228
+
+/*-------------------------------*/
+/* Top Pad Register Offset       */
+/*-------------------------------*/
+#define EMMC_TOP_CONTROL	0x00
+#define EMMC_TOP_CMD		0x04
+#define EMMC50_PAD_DS_TUNE	0x0c
+
+/*--------------------------------------------------------------------------*/
+/* Register Mask                                                            */
+/*--------------------------------------------------------------------------*/
+
+/* MSDC_CFG mask */
+#define MSDC_CFG_MODE		(0x1 << 0)	/* RW */
+#define MSDC_CFG_CKPDN		(0x1 << 1)	/* RW */
+#define MSDC_CFG_RST		(0x1 << 2)	/* RW */
+#define MSDC_CFG_PIO		(0x1 << 3)	/* RW */
+#define MSDC_CFG_CKSTB		(0x1 << 7)	/* R  */
+
+/* MSDC_IOCON mask */
+#define MSDC_IOCON_DDLSEL	(0x1 << 3)	/* RW */
+
+/* MSDC_INT mask */
+#define MSDC_INT_CMDRDY		(0x1 << 8)	/* W1C */
+#define MSDC_INT_CMDTMO		(0x1 << 9)	/* W1C */
+#define MSDC_INT_RSPCRCERR	(0x1 << 10)	/* W1C */
+
+/* MSDC_FIFOCS mask */
+#define MSDC_FIFOCS_RXCNT	(0xff << 0)	/* R */
+#define MSDC_FIFOCS_TXCNT	(0xff << 16)	/* R */
+#define MSDC_FIFOCS_CLR		(0x1 << 31)	/* RW */
+
+/* SDC_CFG mask */
+#define SDC_CFG_BUSWIDTH	(0x3 << 16)	/* RW */
+#define SDC_CFG_SDIO		(0x1 << 19)	/* RW */
+#define SDC_CFG_SDIOIDE		(0x1 << 20)	/* RW */
+#define SDC_CFG_DTOC		(0xff << 24)	/* RW */
+
+/* SDC_CMD */
+#define SDC_CMD_CMD_S		0
+#define SDC_CMD_CMD_M		(0x3f << SDC_CMD_CMD_S)
+#define SDC_CMD_RSPTYP_S	7
+#define SDC_CMD_RSPTYP_M	(0x7 << SDC_CMD_RSPTYP_S)
+#define SDC_CMD_DTYPE_S		11
+#define SDC_CMD_DTYPE_M		(0x3 << SDC_CMD_DTYPE_S)
+#define SDC_CMD_WR		(1 << 13)
+#define SDC_CMD_STOP		(1 << 14)
+#define SDC_CMD_BLK_LEN_S	16
+#define SDC_CMD_BLK_LEN_M	(0xfff << SDC_CMD_BLK_LEN_S)
+
+/* SDC_STS mask */
+#define SDC_STS_SDCBUSY		(0x1 << 0)	/* RW */
+#define SDC_STS_CMDBUSY		(0x1 << 1)	/* RW */
+
+/* SDC_ADV_CFG0 mask */
+#define SDC_DAT1_IRQ_TRIGGER	(0x1 << 19)	/* RW */
+#define SDC_RX_ENHANCE_EN	(0x1 << 20)	/* RW */
+
+/* MSDC_PATCH_BIT mask */
+#define MSDC_CKGEN_MSDC_DLY_SEL	(0x1f << 10)
+
+/* PATCH_BIT1 mask */
+#define MSDC_PATCH_BIT1_STOP_DLY	(0xf << 8)	/* RW */
+
+/* PATCH_BIT2 mask */
+#define MSDC_PATCH_BIT2_CFGRESP		(0x1 << 15)	/* RW */
+#define MSDC_PATCH_BIT2_CFGCRCSTS	(0x1 << 28)	/* RW */
+#define MSDC_PB2_RESPWAIT		(0x3 << 2)	/* RW */
+
+/* PAD_TUNE mask */
+#define MSDC_PAD_TUNE_RD_SEL	(0x1 << 13)	/* RW */
+#define MSDC_PAD_TUNE_CMD_SEL	(0x1 << 21)	/* RW */
+
+/* EMMC50_CFG mask */
+#define EMMC50_CFG_CFCSTS_SEL	(0x1 << 4)	/* RW */
+
+/* SDC_FIFO mask */
+#define SDC_FIFO_CFG_WRVALIDSEL	(0x1 << 24)	/* RW */
+#define SDC_FIFO_CFG_RDVALIDSEL	(0x1 << 25)	/* RW */
+
+/* EMMC_TOP_CONTROL mask */
+#define PAD_DAT_RD_RXDLY_SEL	(0x1 << 13)	/* RW */
+#define DATA_K_VALUE_SEL	(0x1 << 14)	/* RW */
+#define SDC_RX_ENH_EN		(0x1 << 15)	/* TW */
+
+/* EMMC_TOP_CMD mask */
+#define PAD_CMD_RD_RXDLY_SEL	(0x1 << 11)	/* RW */
+
+#define CMD_TIMEOUT_MS		(5 * 100)	/* 500ms */
+#define MSDC_TIMEOUT_US		(1000 * 1000)	/* 1s */
+
+/* SDC_CFG_BUSWIDTH */
+#define MSDC_BUS_1BITS	0x0
+#define MSDC_BUS_4BITS	0x1
+#define MSDC_BUS_8BITS	0x2
+
+#define MSDC_SUCCESS	0x0
+#define MSDC_NOT_READY	0x1
+
+#define EIO		5	/* I/O error */
+#define ETIMEDOUT	110	/* I/O timed out */
+
+#define CMD_INTS_MASK   \
+	(MSDC_INT_CMDRDY | MSDC_INT_RSPCRCERR | MSDC_INT_CMDTMO)
+
+struct msdc_ctrlr {
+	struct sd_mmc_ctrlr sd_mmc_ctrlr;
+	void *base;		/* IO address */
+	void *top_base;		/* Top IO address */
+
+	uint32_t clock;		/* Current clock frequency */
+	uint32_t src_hz;	/* Source clock frequency */
+
+	bool initialized;
+};
+
+#define msdc_debug(format...) printk(BIOS_DEBUG, format)
+#define msdc_trace(format...) printk(BIOS_DEBUG, format)
+#define msdc_error(format...) printk(BIOS_ERR, "ERROR: " format)
+
+int mtk_emmc_early_init(void *base, void *top_base);
+
+#endif // MTK_MMC_H_
diff --git a/src/soc/mediatek/common/msdc.c b/src/soc/mediatek/common/msdc.c
new file mode 100644
index 0000000..a5a8a6b
--- /dev/null
+++ b/src/soc/mediatek/common/msdc.c
@@ -0,0 +1,488 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * MTK MSDC Host Controller interface specific code
+ */
+#include <assert.h>
+#include <cbmem.h>
+#include <commonlib/bsd/helpers.h>
+#include <commonlib/storage/sd_mmc.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <lib.h>
+#include <soc/msdc.h>
+#include <string.h>
+#include <timer.h>
+
+static inline void msdc_set_field(void *reg, u32 field, u32 val)
+{
+	clrsetbits32(reg, field, val << __ffs(field));
+}
+
+/*
+ * Periodically poll an address until a condition is met or a timeout occurs
+ * @addr: Address to poll
+ * @mask: mask condition
+ * @timeout: Timeout in us, 0 means never timeout
+ *
+ * Returns 0 on success and -MSDC_NOT_READY upon a timeout.
+ */
+static int msdc_poll_timeout(void *addr, u32 mask)
+{
+	struct stopwatch timer;
+	stopwatch_init_usecs_expire(&timer, MSDC_TIMEOUT_US);
+	u32 reg;
+
+	do {
+		reg = read32(addr);
+		if (stopwatch_expired(&timer))
+			return -MSDC_NOT_READY;
+		udelay(1);
+	} while (reg & mask);
+
+	return MSDC_SUCCESS;
+}
+
+/*
+ * Wait for a bit mask in a given register. To avoid endless loops, a
+ * time-out is implemented here.
+ */
+static int msdc_wait_done(void *addr, u32 mask, u32 *status)
+{
+	struct stopwatch timer;
+	stopwatch_init_usecs_expire(&timer, CMD_TIMEOUT_MS);
+	u32 reg;
+
+	do {
+		reg = read32(addr);
+		if (stopwatch_expired(&timer)) {
+			if (status)
+				*status = reg;
+			return -MSDC_NOT_READY;
+		}
+		udelay(1);
+	} while (!(reg & mask));
+
+	if (status)
+		*status = reg;
+
+	return MSDC_SUCCESS;
+}
+
+static void msdc_reset_hw(struct msdc_ctrlr *host)
+{
+	u32 val;
+
+	setbits32(host->base + MSDC_CFG, MSDC_CFG_RST);
+	if (msdc_poll_timeout(host->base + MSDC_CFG, MSDC_CFG_RST) != MSDC_SUCCESS)
+		msdc_error("Softwave reset timeout!\n");
+
+	setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR);
+	if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS)
+		msdc_error("Clear FIFO timeout!\n");
+
+	val = read32(host->base + MSDC_INT);
+	write32(host->base + MSDC_INT, val);
+}
+
+static void msdc_init_hw(struct msdc_ctrlr *host)
+{
+	/* Configure to MMC/SD mode */
+	setbits32(host->base + MSDC_CFG, MSDC_CFG_MODE);
+
+	/* Reset */
+	msdc_reset_hw(host);
+
+	/* Set PIO mode */
+	setbits32(host->base + MSDC_CFG, MSDC_CFG_PIO);
+
+	write32(host->top_base + EMMC_TOP_CONTROL, 0);
+	write32(host->top_base + EMMC_TOP_CMD, 0);
+
+	write32(host->base + MSDC_IOCON, 0);
+	clrbits32(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL);
+	write32(host->base + MSDC_PATCH_BIT, 0x403c0046);
+	msdc_set_field(host->base + MSDC_PATCH_BIT, MSDC_CKGEN_MSDC_DLY_SEL, 1);
+	write32(host->base + MSDC_PATCH_BIT1, 0xffff4089);
+	setbits32(host->base + EMMC50_CFG0, EMMC50_CFG_CFCSTS_SEL);
+
+	msdc_set_field(host->base + MSDC_PATCH_BIT1,
+		       MSDC_PATCH_BIT1_STOP_DLY, 3);
+	clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_WRVALIDSEL);
+	clrbits32(host->base + SDC_FIFO_CFG, SDC_FIFO_CFG_RDVALIDSEL);
+
+	clrbits32(host->base + MSDC_PATCH_BIT1, (1 << 7));
+
+	msdc_set_field(host->base + MSDC_PATCH_BIT2, MSDC_PB2_RESPWAIT, 3);
+	if (host->top_base)
+		setbits32(host->top_base + EMMC_TOP_CONTROL, SDC_RX_ENH_EN);
+	else
+		setbits32(host->base + SDC_ADV_CFG0, SDC_RX_ENHANCE_EN);
+	/* Use async fifo, then no need to tune internal delay */
+	clrbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGRESP);
+	setbits32(host->base + MSDC_PATCH_BIT2, MSDC_PATCH_BIT2_CFGCRCSTS);
+
+	if (host->top_base) {
+		setbits32(host->top_base + EMMC_TOP_CONTROL,
+			  PAD_DAT_RD_RXDLY_SEL);
+		clrbits32(host->top_base + EMMC_TOP_CONTROL, DATA_K_VALUE_SEL);
+		setbits32(host->top_base + EMMC_TOP_CMD, PAD_CMD_RD_RXDLY_SEL);
+	} else {
+		setbits32(host->base + MSDC_PAD_TUNE,
+			  MSDC_PAD_TUNE_RD_SEL | MSDC_PAD_TUNE_CMD_SEL);
+	}
+
+	/* Configure to enable SDIO mode. Otherwise, sdio cmd5 will fail. */
+	setbits32(host->base + SDC_CFG, SDC_CFG_SDIO);
+
+	/* Config SDIO device detect interrupt function */
+	clrbits32(host->base + SDC_CFG, SDC_CFG_SDIOIDE);
+	setbits32(host->base + SDC_ADV_CFG0, SDC_DAT1_IRQ_TRIGGER);
+
+	/* Configure to default data timeout */
+	msdc_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, 3);
+
+	msdc_debug("init hardware done!\n");
+}
+
+static void msdc_fifo_clr(struct msdc_ctrlr *host)
+{
+	setbits32(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR);
+
+	if (msdc_poll_timeout(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR) != MSDC_SUCCESS)
+		msdc_error("Clear FIFO timeout!\n");
+}
+
+static u32 msdc_cmd_find_resp(struct msdc_ctrlr *host, struct mmc_command *cmd)
+{
+	switch (cmd->resp_type) {
+	case CARD_RSP_R1:
+		return 0x1;
+	case CARD_RSP_R1b:
+		return 0x7;
+	case CARD_RSP_R2:
+		return 0x2;
+	case CARD_RSP_R3:
+		return 0x3;
+	case CARD_RSP_NONE:
+	default:
+		return 0x0;
+	}
+}
+
+static bool msdc_cmd_is_ready(struct msdc_ctrlr *host)
+{
+	int ret;
+
+	ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_CMDBUSY);
+	if (ret != MSDC_SUCCESS) {
+		msdc_error("CMD bus busy detected, SDC_STS: %#x\n",
+			   read32(host->base + SDC_STS));
+		msdc_reset_hw(host);
+		return false;
+	}
+
+	ret = msdc_poll_timeout(host->base + SDC_STS, SDC_STS_SDCBUSY);
+	if (ret != MSDC_SUCCESS) {
+		msdc_error("SD controller busy detected, SDC_STS: %#x\n",
+			   read32(host->base + SDC_STS));
+		msdc_reset_hw(host);
+		return false;
+	}
+
+	return true;
+}
+
+static u32 msdc_cmd_prepare_raw_cmd(struct msdc_ctrlr *host,
+				    struct mmc_command *cmd,
+				    struct mmc_data *data)
+{
+	u32 opcode = cmd->cmdidx;
+	u32 resp_type = msdc_cmd_find_resp(host, cmd);
+	u32 blocksize = 0;
+	u32 dtype = 0;
+	u32 rawcmd = 0;
+
+	switch (opcode) {
+	case MMC_CMD_WRITE_MULTIPLE_BLOCK:
+	case MMC_CMD_READ_MULTIPLE_BLOCK:
+		dtype = 2;
+		break;
+	case MMC_CMD_WRITE_SINGLE_BLOCK:
+	case MMC_CMD_READ_SINGLE_BLOCK:
+	case MMC_CMD_AUTO_TUNING_SEQUENCE:
+		dtype = 1;
+		break;
+	case MMC_CMD_SEND_STATUS:
+		if (data)
+			dtype = 1;
+	}
+
+	if (data) {
+		if (data->flags == DATA_FLAG_READ)
+			rawcmd |= SDC_CMD_WR;
+
+		if (data->blocks > 1)
+			dtype = 2;
+
+		blocksize = data->blocksize;
+	}
+
+	rawcmd |= (opcode << SDC_CMD_CMD_S) & SDC_CMD_CMD_M;
+	rawcmd |= (resp_type << SDC_CMD_RSPTYP_S) & SDC_CMD_RSPTYP_M;
+	rawcmd |= (blocksize << SDC_CMD_BLK_LEN_S) & SDC_CMD_BLK_LEN_M;
+	rawcmd |= (dtype << SDC_CMD_DTYPE_S) & SDC_CMD_DTYPE_M;
+
+	if (opcode == MMC_CMD_STOP_TRANSMISSION)
+		rawcmd |= SDC_CMD_STOP;
+
+	return rawcmd;
+}
+
+static int msdc_cmd_done(struct msdc_ctrlr *host, int events,
+			 struct mmc_command *cmd)
+{
+	u32 *rsp = cmd->response;
+	int ret = 0;
+
+	if (cmd->resp_type & CARD_RSP_PRESENT) {
+		if (cmd->resp_type & CARD_RSP_136) {
+			rsp[0] = read32(host->base + SDC_RESP3);
+			rsp[1] = read32(host->base + SDC_RESP2);
+			rsp[2] = read32(host->base + SDC_RESP1);
+			rsp[3] = read32(host->base + SDC_RESP0);
+		} else {
+			rsp[0] = read32(host->base + SDC_RESP0);
+		}
+	}
+
+	if (!(events & MSDC_INT_CMDRDY)) {
+		if (cmd->cmdidx != MMC_CMD_AUTO_TUNING_SEQUENCE) {
+			/*
+			 * should not clear fifo/interrupt as the tune data
+			 * may have already come.
+			 */
+			msdc_reset_hw(host);
+		}
+		if (events & MSDC_INT_CMDTMO)
+			ret = -ETIMEDOUT;
+		else
+			ret = -EIO;
+	}
+
+	return ret;
+}
+
+static int msdc_start_command(struct msdc_ctrlr *host, struct mmc_command *cmd,
+			      struct mmc_data *data)
+{
+	u32 rawcmd, status;
+	u32 blocks = 0;
+	int ret;
+
+	if (!msdc_cmd_is_ready(host))
+		return -EIO;
+
+	if (read32(host->base + MSDC_FIFOCS) &
+	    (MSDC_FIFOCS_TXCNT | MSDC_FIFOCS_RXCNT)) {
+		msdc_error("TX/RX FIFO non-empty before start of IO. Reset\n");
+		msdc_reset_hw(host);
+	}
+
+	msdc_fifo_clr(host);
+
+	rawcmd = msdc_cmd_prepare_raw_cmd(host, cmd, data);
+
+	if (data)
+		blocks = data->blocks;
+
+	write32(host->base + MSDC_INT, CMD_INTS_MASK);
+	write32(host->base + SDC_BLK_NUM, blocks);
+	write32(host->base + SDC_ARG, cmd->cmdarg);
+	write32(host->base + SDC_CMD, rawcmd);
+
+	ret = msdc_wait_done(host->base + MSDC_INT, CMD_INTS_MASK, &status);
+	if (ret != MSDC_SUCCESS)
+		status = MSDC_INT_CMDTMO;
+
+	return msdc_cmd_done(host, status, cmd);
+}
+
+static int msdc_send_command(struct sd_mmc_ctrlr *ctrlr,
+	struct mmc_command *cmd, struct mmc_data *data)
+{
+	struct msdc_ctrlr *host;
+
+	host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr);
+
+	return msdc_start_command(host, cmd, data);
+}
+
+static void msdc_set_clock(struct msdc_ctrlr *host, u32 clock)
+{
+	u32 mode, mode_shift;
+	u32 div, div_mask;
+	const u32 div_width = 12;
+	u32 sclk;
+	u32 hclk = host->src_hz;
+	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;
+
+	if (clock >= hclk) {
+		mode = 0x1;     /* no divisor */
+		div = 0;
+		sclk = hclk;
+	} else {
+		mode = 0x0;     /* use divisor */
+		if (clock >= (hclk / 2)) {
+			div = 0;        /* mean div = 1/2 */
+			sclk = hclk / 2;        /* sclk = clk / 2 */
+		} else {
+			div = DIV_ROUND_UP(hclk, clock * 4);
+			sclk = (hclk >> 2) / div;
+		}
+	}
+
+	div_mask = (1 << div_width) - 1;
+	mode_shift = 8 + div_width;
+	assert(div <= div_mask);
+
+	clrsetbits_le32(host->base + MSDC_CFG, (0x3 << mode_shift) | (div_mask << 8),
+			(mode << mode_shift) | (div << 8));
+	if (msdc_wait_done(host->base + MSDC_CFG, MSDC_CFG_CKSTB, NULL) != MSDC_SUCCESS)
+		msdc_error("Failed while wait clock stable!\n");
+
+	ctrlr->bus_hz = sclk;
+	msdc_debug("sclk: %d\n", sclk);
+}
+
+static void msdc_set_buswidth(struct msdc_ctrlr *host, u32 width)
+{
+	u32 val = read32(host->base + SDC_CFG);
+
+	val &= ~SDC_CFG_BUSWIDTH;
+
+	switch (width) {
+	default:
+	case 1:
+		val |= (MSDC_BUS_1BITS << 16);
+		break;
+	case 4:
+		val |= (MSDC_BUS_4BITS << 16);
+		break;
+	case 8:
+		val |= (MSDC_BUS_8BITS << 16);
+		break;
+	}
+
+	write32(host->base + SDC_CFG, val);
+	msdc_trace("Bus Width = %d\n", width);
+}
+
+static void msdc_set_ios(struct sd_mmc_ctrlr *ctrlr)
+{
+	struct msdc_ctrlr *host;
+
+	host = container_of(ctrlr, struct msdc_ctrlr, sd_mmc_ctrlr);
+
+	/* Set the clock frequency */
+	if (ctrlr->bus_hz != ctrlr->request_hz)
+		msdc_set_clock(host, ctrlr->request_hz);
+
+	msdc_set_buswidth(host, ctrlr->bus_width);
+
+}
+
+static void msdc_update_pointers(struct msdc_ctrlr *host)
+{
+	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;
+
+	/* Update the routine pointers */
+	ctrlr->send_cmd = &msdc_send_command;
+	ctrlr->set_ios = &msdc_set_ios;
+
+	ctrlr->f_min = 400 * 1000;
+	ctrlr->f_max = 52 * 1000 * 1000;
+	ctrlr->bus_width = 1;
+	ctrlr->caps |= DRVR_CAP_HS | DRVR_CAP_HC;
+	ctrlr->voltages = 0x40ff8080;
+}
+
+static void add_msdc(struct msdc_ctrlr *host)
+{
+	struct sd_mmc_ctrlr *ctrlr = &host->sd_mmc_ctrlr;
+
+	msdc_update_pointers(host);
+
+	/* Initialize the MTK MSDC controller */
+	msdc_init_hw(host);
+
+	/* Display the results */
+	msdc_trace("%#010x: ctrlr->caps\n", ctrlr->caps);
+	msdc_trace("%d.%03d MHz: ctrlr->f_max\n",
+		    ctrlr->f_max / 1000000,
+		    (ctrlr->f_max / 1000) % 1000);
+	msdc_trace("%d.%03d MHz: ctrlr->f_min\n",
+		    ctrlr->f_min / 1000000,
+		    (ctrlr->f_min / 1000) % 1000);
+	msdc_trace("%#010x: ctrlr->voltages\n", ctrlr->voltages);
+}
+
+static void msdc_controller_init(struct msdc_ctrlr *host, void *base, void *top_base)
+{
+	memset(host, 0, sizeof(*host));
+	host->base = base;
+	host->top_base = top_base;
+	host->src_hz = 50 * 1000 * 1000;
+
+	add_msdc(host);
+}
+
+static void set_early_mmc_wake_status(int32_t status)
+{
+	int32_t *ms_cbmem;
+
+	ms_cbmem = cbmem_add(CBMEM_ID_MMC_STATUS, sizeof(status));
+
+	if (ms_cbmem == NULL) {
+		printk(BIOS_ERR,
+		       "%s: Failed to add early mmc wake status to cbmem!\n",
+		       __func__);
+		return;
+	}
+
+	printk(BIOS_DEBUG, "Early init status = %d\n", status);
+	*ms_cbmem = status;
+}
+
+int mtk_emmc_early_init(void *base, void *top_base)
+{
+	struct storage_media media = { 0 };
+	int err;
+	struct msdc_ctrlr msdc_host;
+	struct sd_mmc_ctrlr *mmc_ctrlr = &msdc_host.sd_mmc_ctrlr;
+
+	/* Init mtk mmc ctrlr */
+	msdc_controller_init(&msdc_host, base, top_base);
+
+	media.ctrlr = mmc_ctrlr;
+	SET_CLOCK(mmc_ctrlr, 400 * 1000);
+	SET_BUS_WIDTH(mmc_ctrlr, 1);
+
+	/* Reset emmc, send CMD0 */
+	if (sd_mmc_go_idle(&media))
+		goto out_err;
+
+	/* Send CMD1 */
+	err = mmc_send_op_cond(&media);
+	if (err == 0)
+		set_early_mmc_wake_status(MMC_STATUS_CMD1_READY);
+	else if (err == CARD_IN_PROGRESS)
+		set_early_mmc_wake_status(MMC_STATUS_CMD1_IN_PROGRESS);
+	else
+		goto out_err;
+
+	return 0;
+
+out_err:
+	set_early_mmc_wake_status(MMC_STATUS_NEED_RESET);
+	return -1;
+}