aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWenbin Mei <wenbin.mei@mediatek.com>2021-03-25 14:37:35 +0800
committerPatrick Georgi <pgeorgi@google.com>2021-04-09 06:05:12 +0000
commitdf062044fd1cc8a02e0f76ab806851eeabc1b4f6 (patch)
tree0e23944dc69ae72287e874b8922ea5d9d65d1da0
parentd382f3d39f8438179822c83bcf643927105dc9da (diff)
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>
-rw-r--r--src/commonlib/include/commonlib/sd_mmc_ctrlr.h8
-rw-r--r--src/soc/mediatek/common/include/soc/msdc.h169
-rw-r--r--src/soc/mediatek/common/msdc.c488
3 files changed, 665 insertions, 0 deletions
diff --git a/src/commonlib/include/commonlib/sd_mmc_ctrlr.h b/src/commonlib/include/commonlib/sd_mmc_ctrlr.h
index 1b1dd51479..6ac274090f 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 0000000000..6208b72f6e
--- /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 0000000000..a5a8a6bbd9
--- /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;
+}