/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * MTK MSDC Host Controller interface specific code */ #include #include #include #include #include #include #include #include #include #include #include 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) { 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) { if (media.op_cond_response & OCR_HCS) set_early_mmc_wake_status(MMC_STATUS_CMD1_READY_HCS); else 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; }