diff options
Diffstat (limited to 'drivers/mmc/host/mtk-sd.c')
-rw-r--r-- | drivers/mmc/host/mtk-sd.c | 306 |
1 files changed, 276 insertions, 30 deletions
diff --git a/drivers/mmc/host/mtk-sd.c b/drivers/mmc/host/mtk-sd.c index 7153500dd007..39568cc29a2a 100644 --- a/drivers/mmc/host/mtk-sd.c +++ b/drivers/mmc/host/mtk-sd.c @@ -26,6 +26,7 @@ #include <linux/pm.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> +#include <linux/slab.h> #include <linux/spinlock.h> #include <linux/mmc/card.h> @@ -64,6 +65,7 @@ #define SDC_RESP2 0x48 #define SDC_RESP3 0x4c #define SDC_BLK_NUM 0x50 +#define EMMC_IOCON 0x7c #define SDC_ACMD_RESP 0x80 #define MSDC_DMA_SA 0x90 #define MSDC_DMA_CTRL 0x98 @@ -71,6 +73,8 @@ #define MSDC_PATCH_BIT 0xb0 #define MSDC_PATCH_BIT1 0xb4 #define MSDC_PAD_TUNE 0xec +#define PAD_DS_TUNE 0x188 +#define EMMC50_CFG0 0x208 /*--------------------------------------------------------------------------*/ /* Register Mask */ @@ -87,6 +91,7 @@ #define MSDC_CFG_CKSTB (0x1 << 7) /* R */ #define MSDC_CFG_CKDIV (0xff << 8) /* RW */ #define MSDC_CFG_CKMOD (0x3 << 16) /* RW */ +#define MSDC_CFG_HS400_CK_MODE (0x1 << 18) /* RW */ /* MSDC_IOCON mask */ #define MSDC_IOCON_SDR104CKS (0x1 << 0) /* RW */ @@ -204,6 +209,17 @@ #define MSDC_PATCH_BIT_SPCPUSH (0x1 << 29) /* RW */ #define MSDC_PATCH_BIT_DECRCTMO (0x1 << 30) /* RW */ +#define MSDC_PAD_TUNE_DATRRDLY (0x1f << 8) /* RW */ +#define MSDC_PAD_TUNE_CMDRDLY (0x1f << 16) /* RW */ + +#define PAD_DS_TUNE_DLY1 (0x1f << 2) /* RW */ +#define PAD_DS_TUNE_DLY2 (0x1f << 7) /* RW */ +#define PAD_DS_TUNE_DLY3 (0x1f << 12) /* RW */ + +#define EMMC50_CFG_PADCMD_LATCHCK (0x1 << 0) /* RW */ +#define EMMC50_CFG_CRCSTS_EDGE (0x1 << 3) /* RW */ +#define EMMC50_CFG_CFCSTS_SEL (0x1 << 4) /* RW */ + #define REQ_CMD_EIO (0x1 << 0) #define REQ_CMD_TMO (0x1 << 1) #define REQ_DAT_ERR (0x1 << 2) @@ -219,6 +235,7 @@ #define CMD_TIMEOUT (HZ/10 * 5) /* 100ms x5 */ #define DAT_TIMEOUT (HZ * 5) /* 1000ms x5 */ +#define PAD_DELAY_MAX 32 /* PAD delay cells */ /*--------------------------------------------------------------------------*/ /* Descriptor Structure */ /*--------------------------------------------------------------------------*/ @@ -265,6 +282,14 @@ struct msdc_save_para { u32 pad_tune; u32 patch_bit0; u32 patch_bit1; + u32 pad_ds_tune; + u32 emmc50_cfg0; +}; + +struct msdc_delay_phase { + u8 maxlen; + u8 start; + u8 final_phase; }; struct msdc_host { @@ -297,8 +322,9 @@ struct msdc_host { u32 mclk; /* mmc subsystem clock frequency */ u32 src_clk_freq; /* source clock frequency */ u32 sclk; /* SD/MS bus clock frequency */ - bool ddr; + unsigned char timing; bool vqmmc_enabled; + u32 hs400_ds_delay; struct msdc_save_para save_para; /* used when gate HCLK */ }; @@ -353,7 +379,10 @@ static void msdc_reset_hw(struct msdc_host *host) static void msdc_cmd_next(struct msdc_host *host, struct mmc_request *mrq, struct mmc_command *cmd); -static u32 data_ints_mask = MSDC_INTEN_XFER_COMPL | MSDC_INTEN_DATTMO | +static const u32 cmd_ints_mask = MSDC_INTEN_CMDRDY | MSDC_INTEN_RSPCRCERR | + MSDC_INTEN_CMDTMO | MSDC_INTEN_ACMDRDY | + MSDC_INTEN_ACMDCRCERR | MSDC_INTEN_ACMDTMO; +static const u32 data_ints_mask = MSDC_INTEN_XFER_COMPL | MSDC_INTEN_DATTMO | MSDC_INTEN_DATCRCERR | MSDC_INTEN_DMA_BDCSERR | MSDC_INTEN_DMA_GPDCSERR | MSDC_INTEN_DMA_PROTECT; @@ -485,7 +514,7 @@ static void msdc_ungate_clock(struct msdc_host *host) cpu_relax(); } -static void msdc_set_mclk(struct msdc_host *host, int ddr, u32 hz) +static void msdc_set_mclk(struct msdc_host *host, unsigned char timing, u32 hz) { u32 mode; u32 flags; @@ -501,8 +530,15 @@ static void msdc_set_mclk(struct msdc_host *host, int ddr, u32 hz) flags = readl(host->base + MSDC_INTEN); sdr_clr_bits(host->base + MSDC_INTEN, flags); - if (ddr) { /* may need to modify later */ - mode = 0x2; /* ddr mode and use divisor */ + sdr_clr_bits(host->base + MSDC_CFG, MSDC_CFG_HS400_CK_MODE); + if (timing == MMC_TIMING_UHS_DDR50 || + timing == MMC_TIMING_MMC_DDR52 || + timing == MMC_TIMING_MMC_HS400) { + if (timing == MMC_TIMING_MMC_HS400) + mode = 0x3; + else + mode = 0x2; /* ddr mode and use divisor */ + if (hz >= (host->src_clk_freq >> 2)) { div = 0; /* mean div = 1/4 */ sclk = host->src_clk_freq >> 2; /* sclk = clk / 4 */ @@ -511,6 +547,14 @@ static void msdc_set_mclk(struct msdc_host *host, int ddr, u32 hz) sclk = (host->src_clk_freq >> 2) / div; div = (div >> 1); } + + if (timing == MMC_TIMING_MMC_HS400 && + hz >= (host->src_clk_freq >> 1)) { + sdr_set_bits(host->base + MSDC_CFG, + MSDC_CFG_HS400_CK_MODE); + sclk = host->src_clk_freq >> 1; + div = 0; /* div is ignore when bit18 is set */ + } } else if (hz >= host->src_clk_freq) { mode = 0x1; /* no divisor */ div = 0; @@ -532,12 +576,12 @@ static void msdc_set_mclk(struct msdc_host *host, int ddr, u32 hz) cpu_relax(); host->sclk = sclk; host->mclk = hz; - host->ddr = ddr; + host->timing = timing; /* need because clk changed. */ msdc_set_timeout(host, host->timeout_ns, host->timeout_clks); sdr_set_bits(host->base + MSDC_INTEN, flags); - dev_dbg(host->dev, "sclk: %d, ddr: %d\n", host->sclk, ddr); + dev_dbg(host->dev, "sclk: %d, timing: %d\n", host->sclk, timing); } static inline u32 msdc_cmd_find_resp(struct msdc_host *host, @@ -725,11 +769,7 @@ static bool msdc_cmd_done(struct msdc_host *host, int events, if (done) return true; - sdr_clr_bits(host->base + MSDC_INTEN, MSDC_INTEN_CMDRDY | - MSDC_INTEN_RSPCRCERR | MSDC_INTEN_CMDTMO | - MSDC_INTEN_ACMDRDY | MSDC_INTEN_ACMDCRCERR | - MSDC_INTEN_ACMDTMO); - writel(cmd->arg, host->base + SDC_ARG); + sdr_clr_bits(host->base + MSDC_INTEN, cmd_ints_mask); if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { @@ -819,10 +859,7 @@ static void msdc_start_command(struct msdc_host *host, rawcmd = msdc_cmd_prepare_raw_cmd(host, mrq, cmd); mod_delayed_work(system_wq, &host->req_timeout, DAT_TIMEOUT); - sdr_set_bits(host->base + MSDC_INTEN, MSDC_INTEN_CMDRDY | - MSDC_INTEN_RSPCRCERR | MSDC_INTEN_CMDTMO | - MSDC_INTEN_ACMDRDY | MSDC_INTEN_ACMDCRCERR | - MSDC_INTEN_ACMDTMO); + sdr_set_bits(host->base + MSDC_INTEN, cmd_ints_mask); writel(cmd->arg, host->base + SDC_ARG); writel(rawcmd, host->base + SDC_CMD); } @@ -896,7 +933,7 @@ static void msdc_data_xfer_next(struct msdc_host *host, struct mmc_request *mrq, struct mmc_data *data) { if (mmc_op_multi(mrq->cmd->opcode) && mrq->stop && !mrq->stop->error && - (!data->bytes_xfered || !mrq->sbc)) + !mrq->sbc) msdc_start_command(host, mrq, mrq->stop); else msdc_request_done(host, mrq); @@ -942,6 +979,8 @@ static bool msdc_data_xfer_done(struct msdc_host *host, u32 events, if (events & MSDC_INT_DATTMO) data->error = -ETIMEDOUT; + else if (events & MSDC_INT_DATCRCERR) + data->error = -EILSEQ; dev_err(host->dev, "%s: cmd=%d; blocks=%d", __func__, mrq->cmd->opcode, data->blocks); @@ -1113,10 +1152,12 @@ static void msdc_init_hw(struct msdc_host *host) writel(0, host->base + MSDC_PAD_TUNE); writel(0, host->base + MSDC_IOCON); - sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL, 1); - writel(0x403c004f, host->base + MSDC_PATCH_BIT); + sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL, 0); + writel(0x403c0046, host->base + MSDC_PATCH_BIT); sdr_set_field(host->base + MSDC_PATCH_BIT, MSDC_CKGEN_MSDC_DLY_SEL, 1); writel(0xffff0089, host->base + MSDC_PATCH_BIT1); + sdr_set_bits(host->base + EMMC50_CFG0, EMMC50_CFG_CFCSTS_SEL); + /* Configure to enable SDIO mode. * it's must otherwise sdio cmd5 failed */ @@ -1148,11 +1189,14 @@ static void msdc_init_gpd_bd(struct msdc_host *host, struct msdc_dma *dma) struct mt_bdma_desc *bd = dma->bd; int i; - memset(gpd, 0, sizeof(struct mt_gpdma_desc)); + memset(gpd, 0, sizeof(struct mt_gpdma_desc) * 2); gpd->gpd_info = GPDMA_DESC_BDP; /* hwo, cs, bd pointer */ gpd->ptr = (u32)dma->bd_addr; /* physical address */ - + /* gpd->next is must set for desc DMA + * That's why must alloc 2 gpd structure. + */ + gpd->next = (u32)dma->gpd_addr + sizeof(struct mt_gpdma_desc); memset(bd, 0, sizeof(struct mt_bdma_desc) * MAX_BD_NUM); for (i = 0; i < (MAX_BD_NUM - 1); i++) bd[i].next = (u32)dma->bd_addr + sizeof(*bd) * (i + 1); @@ -1162,20 +1206,16 @@ static void msdc_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct msdc_host *host = mmc_priv(mmc); int ret; - u32 ddr = 0; pm_runtime_get_sync(host->dev); - if (ios->timing == MMC_TIMING_UHS_DDR50 || - ios->timing == MMC_TIMING_MMC_DDR52) - ddr = 1; - msdc_set_buswidth(host, ios->bus_width); /* Suspend/Resume will do power off/on */ switch (ios->power_mode) { case MMC_POWER_UP: if (!IS_ERR(mmc->supply.vmmc)) { + msdc_init_hw(host); ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); if (ret) { @@ -1206,14 +1246,207 @@ static void msdc_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) break; } - if (host->mclk != ios->clock || host->ddr != ddr) - msdc_set_mclk(host, ddr, ios->clock); + if (host->mclk != ios->clock || host->timing != ios->timing) + msdc_set_mclk(host, ios->timing, ios->clock); end: pm_runtime_mark_last_busy(host->dev); pm_runtime_put_autosuspend(host->dev); } +static u32 test_delay_bit(u32 delay, u32 bit) +{ + bit %= PAD_DELAY_MAX; + return delay & (1 << bit); +} + +static int get_delay_len(u32 delay, u32 start_bit) +{ + int i; + + for (i = 0; i < (PAD_DELAY_MAX - start_bit); i++) { + if (test_delay_bit(delay, start_bit + i) == 0) + return i; + } + return PAD_DELAY_MAX - start_bit; +} + +static struct msdc_delay_phase get_best_delay(struct msdc_host *host, u32 delay) +{ + int start = 0, len = 0; + int start_final = 0, len_final = 0; + u8 final_phase = 0xff; + struct msdc_delay_phase delay_phase; + + if (delay == 0) { + dev_err(host->dev, "phase error: [map:%x]\n", delay); + delay_phase.final_phase = final_phase; + return delay_phase; + } + + while (start < PAD_DELAY_MAX) { + len = get_delay_len(delay, start); + if (len_final < len) { + start_final = start; + len_final = len; + } + start += len ? len : 1; + if (len >= 8 && start_final < 4) + break; + } + + /* The rule is that to find the smallest delay cell */ + if (start_final == 0) + final_phase = (start_final + len_final / 3) % PAD_DELAY_MAX; + else + final_phase = (start_final + len_final / 2) % PAD_DELAY_MAX; + dev_info(host->dev, "phase: [map:%x] [maxlen:%d] [final:%d]\n", + delay, len_final, final_phase); + + delay_phase.maxlen = len_final; + delay_phase.start = start_final; + delay_phase.final_phase = final_phase; + return delay_phase; +} + +static int msdc_tune_response(struct mmc_host *mmc, u32 opcode) +{ + struct msdc_host *host = mmc_priv(mmc); + u32 rise_delay = 0, fall_delay = 0; + struct msdc_delay_phase final_rise_delay, final_fall_delay; + u8 final_delay, final_maxlen; + int cmd_err; + int i; + + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + for (i = 0 ; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_CMDRDLY, i); + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) + rise_delay |= (1 << i); + } + + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + for (i = 0; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_CMDRDLY, i); + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) + fall_delay |= (1 << i); + } + + final_rise_delay = get_best_delay(host, rise_delay); + final_fall_delay = get_best_delay(host, fall_delay); + + final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + if (final_maxlen == final_rise_delay.maxlen) { + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY, + final_rise_delay.final_phase); + final_delay = final_rise_delay.final_phase; + } else { + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL); + sdr_set_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRDLY, + final_fall_delay.final_phase); + final_delay = final_fall_delay.final_phase; + } + + return final_delay == 0xff ? -EIO : 0; +} + +static int msdc_tune_data(struct mmc_host *mmc, u32 opcode) +{ + struct msdc_host *host = mmc_priv(mmc); + u32 rise_delay = 0, fall_delay = 0; + struct msdc_delay_phase final_rise_delay, final_fall_delay; + u8 final_delay, final_maxlen; + int i, ret; + + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL); + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL); + for (i = 0 ; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_DATRRDLY, i); + ret = mmc_send_tuning(mmc, opcode, NULL); + if (!ret) + rise_delay |= (1 << i); + } + + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL); + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL); + for (i = 0; i < PAD_DELAY_MAX; i++) { + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_DATRRDLY, i); + ret = mmc_send_tuning(mmc, opcode, NULL); + if (!ret) + fall_delay |= (1 << i); + } + + final_rise_delay = get_best_delay(host, rise_delay); + final_fall_delay = get_best_delay(host, fall_delay); + + final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + /* Rising edge is more stable, prefer to use it */ + if (final_rise_delay.maxlen >= 10) + final_maxlen = final_rise_delay.maxlen; + if (final_maxlen == final_rise_delay.maxlen) { + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL); + sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL); + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_DATRRDLY, + final_rise_delay.final_phase); + final_delay = final_rise_delay.final_phase; + } else { + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_DSPL); + sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_W_DSPL); + sdr_set_field(host->base + MSDC_PAD_TUNE, + MSDC_PAD_TUNE_DATRRDLY, + final_fall_delay.final_phase); + final_delay = final_fall_delay.final_phase; + } + + return final_delay == 0xff ? -EIO : 0; +} + +static int msdc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct msdc_host *host = mmc_priv(mmc); + int ret; + + pm_runtime_get_sync(host->dev); + ret = msdc_tune_response(mmc, opcode); + if (ret == -EIO) { + dev_err(host->dev, "Tune response fail!\n"); + goto out; + } + ret = msdc_tune_data(mmc, opcode); + if (ret == -EIO) + dev_err(host->dev, "Tune data fail!\n"); + +out: + pm_runtime_mark_last_busy(host->dev); + pm_runtime_put_autosuspend(host->dev); + return ret; +} + +static int msdc_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct msdc_host *host = mmc_priv(mmc); + + writel(host->hs400_ds_delay, host->base + PAD_DS_TUNE); + return 0; +} + +static void msdc_hw_reset(struct mmc_host *mmc) +{ + struct msdc_host *host = mmc_priv(mmc); + + sdr_set_bits(host->base + EMMC_IOCON, 1); + udelay(10); /* 10us is enough */ + sdr_clr_bits(host->base + EMMC_IOCON, 1); +} + static struct mmc_host_ops mt_msdc_ops = { .post_req = msdc_post_req, .pre_req = msdc_pre_req, @@ -1221,6 +1454,9 @@ static struct mmc_host_ops mt_msdc_ops = { .set_ios = msdc_ops_set_ios, .start_signal_voltage_switch = msdc_ops_switch_volt, .card_busy = msdc_card_busy, + .execute_tuning = msdc_execute_tuning, + .prepare_hs400_tuning = msdc_prepare_hs400_tuning, + .hw_reset = msdc_hw_reset, }; static int msdc_drv_probe(struct platform_device *pdev) @@ -1294,6 +1530,11 @@ static int msdc_drv_probe(struct platform_device *pdev) goto host_free; } + if (!of_property_read_u32(pdev->dev.of_node, "hs400-ds-delay", + &host->hs400_ds_delay)) + dev_dbg(&pdev->dev, "hs400-ds-delay: %x\n", + host->hs400_ds_delay); + host->dev = &pdev->dev; host->mmc = mmc; host->src_clk_freq = clk_get_rate(host->src_clk); @@ -1302,6 +1543,7 @@ static int msdc_drv_probe(struct platform_device *pdev) mmc->f_min = host->src_clk_freq / (4 * 255); mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23; + mmc->caps |= MMC_CAP_RUNTIME_RESUME; /* MMC core transfer sizes tunable parameters */ mmc->max_segs = MAX_BD_NUM; mmc->max_seg_size = BDMA_DESC_BUFLEN; @@ -1313,7 +1555,7 @@ static int msdc_drv_probe(struct platform_device *pdev) host->timeout_clks = 3 * 1048576; host->dma.gpd = dma_alloc_coherent(&pdev->dev, - sizeof(struct mt_gpdma_desc), + 2 * sizeof(struct mt_gpdma_desc), &host->dma.gpd_addr, GFP_KERNEL); host->dma.bd = dma_alloc_coherent(&pdev->dev, MAX_BD_NUM * sizeof(struct mt_bdma_desc), @@ -1354,7 +1596,7 @@ release: release_mem: if (host->dma.gpd) dma_free_coherent(&pdev->dev, - sizeof(struct mt_gpdma_desc), + 2 * sizeof(struct mt_gpdma_desc), host->dma.gpd, host->dma.gpd_addr); if (host->dma.bd) dma_free_coherent(&pdev->dev, @@ -1403,6 +1645,8 @@ static void msdc_save_reg(struct msdc_host *host) host->save_para.pad_tune = readl(host->base + MSDC_PAD_TUNE); host->save_para.patch_bit0 = readl(host->base + MSDC_PATCH_BIT); host->save_para.patch_bit1 = readl(host->base + MSDC_PATCH_BIT1); + host->save_para.pad_ds_tune = readl(host->base + PAD_DS_TUNE); + host->save_para.emmc50_cfg0 = readl(host->base + EMMC50_CFG0); } static void msdc_restore_reg(struct msdc_host *host) @@ -1413,6 +1657,8 @@ static void msdc_restore_reg(struct msdc_host *host) writel(host->save_para.pad_tune, host->base + MSDC_PAD_TUNE); writel(host->save_para.patch_bit0, host->base + MSDC_PATCH_BIT); writel(host->save_para.patch_bit1, host->base + MSDC_PATCH_BIT1); + writel(host->save_para.pad_ds_tune, host->base + PAD_DS_TUNE); + writel(host->save_para.emmc50_cfg0, host->base + EMMC50_CFG0); } static int msdc_runtime_suspend(struct device *dev) |