diff options
author | Wenbin Mei <wenbin.mei@mediatek.com> | 2021-03-25 14:37:35 +0800 |
---|---|---|
committer | Patrick Georgi <pgeorgi@google.com> | 2021-04-09 06:05:12 +0000 |
commit | df062044fd1cc8a02e0f76ab806851eeabc1b4f6 (patch) | |
tree | 0e23944dc69ae72287e874b8922ea5d9d65d1da0 /src/soc | |
parent | d382f3d39f8438179822c83bcf643927105dc9da (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>
Diffstat (limited to 'src/soc')
-rw-r--r-- | src/soc/mediatek/common/include/soc/msdc.h | 169 | ||||
-rw-r--r-- | src/soc/mediatek/common/msdc.c | 488 |
2 files changed, 657 insertions, 0 deletions
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; +} |