diff options
Diffstat (limited to 'drivers/mmc/host/renesas_sdhi_core.c')
-rw-r--r-- | drivers/mmc/host/renesas_sdhi_core.c | 183 |
1 files changed, 135 insertions, 48 deletions
diff --git a/drivers/mmc/host/renesas_sdhi_core.c b/drivers/mmc/host/renesas_sdhi_core.c index 35cb24cd45b4..df826661366f 100644 --- a/drivers/mmc/host/renesas_sdhi_core.c +++ b/drivers/mmc/host/renesas_sdhi_core.c @@ -250,20 +250,25 @@ static int renesas_sdhi_start_signal_voltage_switch(struct mmc_host *mmc, #define SH_MOBILE_SDHI_SCC_CKSEL 0x006 #define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008 #define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A +#define SH_MOBILE_SDHI_SCC_SMPCMP 0x00C #define SH_MOBILE_SDHI_SCC_TMPPORT2 0x00E -/* Definitions for values the SH_MOBILE_SDHI_SCC_DTCNTL register */ #define SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN BIT(0) #define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT 16 #define SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK 0xff -/* Definitions for values the SH_MOBILE_SDHI_SCC_CKSEL register */ #define SH_MOBILE_SDHI_SCC_CKSEL_DTSEL BIT(0) -/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSCNTL register */ + #define SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN BIT(0) -/* Definitions for values the SH_MOBILE_SDHI_SCC_RVSREQ register */ + +#define SH_MOBILE_SDHI_SCC_RVSREQ_REQTAPDOWN BIT(0) +#define SH_MOBILE_SDHI_SCC_RVSREQ_REQTAPUP BIT(1) #define SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR BIT(2) -/* Definitions for values the SH_MOBILE_SDHI_SCC_TMPPORT2 register */ + +#define SH_MOBILE_SDHI_SCC_SMPCMP_CMD_REQDOWN BIT(8) +#define SH_MOBILE_SDHI_SCC_SMPCMP_CMD_REQUP BIT(24) +#define SH_MOBILE_SDHI_SCC_SMPCMP_CMD_ERR (BIT(8) | BIT(24)) + #define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL BIT(4) #define SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN BIT(31) @@ -316,17 +321,9 @@ static unsigned int renesas_sdhi_init_tuning(struct tmio_mmc_host *host) SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK; } -static void renesas_sdhi_prepare_tuning(struct tmio_mmc_host *host, - unsigned long tap) -{ - struct renesas_sdhi *priv = host_to_priv(host); - - /* Set sampling clock position */ - sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap); -} - -static void renesas_sdhi_hs400_complete(struct tmio_mmc_host *host) +static void renesas_sdhi_hs400_complete(struct mmc_host *mmc) { + struct tmio_mmc_host *host = mmc_priv(mmc); struct renesas_sdhi *priv = host_to_priv(host); sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN & @@ -339,6 +336,12 @@ static void renesas_sdhi_hs400_complete(struct tmio_mmc_host *host) sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DT2FF, priv->scc_tappos_hs400); + /* Gen3 can't do automatic tap correction with HS400, so disable it */ + if (sd_ctrl_read16(host, CTL_VERSION) == SDHI_VER_GEN3_SDMMC) + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL, + ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & + sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL)); + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TMPPORT2, (SH_MOBILE_SDHI_SCC_TMPPORT2_HS400EN | SH_MOBILE_SDHI_SCC_TMPPORT2_HS400OSEL) | @@ -352,7 +355,7 @@ static void renesas_sdhi_hs400_complete(struct tmio_mmc_host *host) if (priv->quirks && priv->quirks->hs400_4taps) sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, - host->tap_set / 2); + priv->tap_set / 2); sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL, SH_MOBILE_SDHI_SCC_CKSEL_DTSEL | @@ -374,8 +377,9 @@ static void renesas_sdhi_reset_scc(struct tmio_mmc_host *host, SH_MOBILE_SDHI_SCC_CKSEL)); } -static void renesas_sdhi_disable_scc(struct tmio_mmc_host *host) +static void renesas_sdhi_disable_scc(struct mmc_host *mmc) { + struct tmio_mmc_host *host = mmc_priv(mmc); struct renesas_sdhi *priv = host_to_priv(host); renesas_sdhi_reset_scc(host, priv); @@ -410,9 +414,12 @@ static void renesas_sdhi_reset_hs400_mode(struct tmio_mmc_host *host, sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); } -static void renesas_sdhi_prepare_hs400_tuning(struct tmio_mmc_host *host) +static int renesas_sdhi_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios) { + struct tmio_mmc_host *host = mmc_priv(mmc); + renesas_sdhi_reset_hs400_mode(host, host_to_priv(host)); + return 0; } #define SH_MOBILE_SDHI_MAX_TAP 3 @@ -426,6 +433,8 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host) unsigned long ntap; /* temporary counter of tuning success */ unsigned long i; + priv->doing_tune = false; + /* Clear SCC_RVSREQ */ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0); @@ -434,11 +443,11 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host) * result requiring the tap to be good in both runs before * considering it for tuning selection. */ - for (i = 0; i < host->tap_num * 2; i++) { - int offset = host->tap_num * (i < host->tap_num ? 1 : -1); + for (i = 0; i < priv->tap_num * 2; i++) { + int offset = priv->tap_num * (i < priv->tap_num ? 1 : -1); - if (!test_bit(i, host->taps)) - clear_bit(i + offset, host->taps); + if (!test_bit(i, priv->taps)) + clear_bit(i + offset, priv->taps); } /* @@ -450,8 +459,8 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host) ntap = 0; tap_start = 0; tap_end = 0; - for (i = 0; i < host->tap_num * 2; i++) { - if (test_bit(i, host->taps)) { + for (i = 0; i < priv->tap_num * 2; i++) { + if (test_bit(i, priv->taps)) { ntap++; } else { if (ntap > tap_cnt) { @@ -470,12 +479,12 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host) } if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP) - host->tap_set = (tap_start + tap_end) / 2 % host->tap_num; + priv->tap_set = (tap_start + tap_end) / 2 % priv->tap_num; else return -EIO; /* Set SCC */ - sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, host->tap_set); + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, priv->tap_set); /* Enable auto re-tuning */ sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL, @@ -485,6 +494,97 @@ static int renesas_sdhi_select_tuning(struct tmio_mmc_host *host) return 0; } +static int renesas_sdhi_execute_tuning(struct tmio_mmc_host *host, u32 opcode) +{ + struct renesas_sdhi *priv = host_to_priv(host); + int i; + + priv->tap_num = renesas_sdhi_init_tuning(host); + if (!priv->tap_num) + return 0; /* Tuning is not supported */ + + if (priv->tap_num * 2 >= sizeof(priv->taps) * BITS_PER_BYTE) { + dev_err(&host->pdev->dev, + "Too many taps, please update 'taps' in tmio_mmc_host!\n"); + return -EINVAL; + } + + priv->doing_tune = true; + bitmap_zero(priv->taps, priv->tap_num * 2); + + /* Issue CMD19 twice for each tap */ + for (i = 0; i < 2 * priv->tap_num; i++) { + /* Set sampling clock position */ + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, i % priv->tap_num); + + if (mmc_send_tuning(host->mmc, opcode, NULL) == 0) + set_bit(i, priv->taps); + } + + return renesas_sdhi_select_tuning(host); +} + +static bool renesas_sdhi_manual_correction(struct tmio_mmc_host *host, bool use_4tap) +{ + struct renesas_sdhi *priv = host_to_priv(host); + unsigned long new_tap = priv->tap_set; + u32 val; + + val = sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ); + if (!val) + return false; + + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0); + + /* Change TAP position according to correction status */ + if (sd_ctrl_read16(host, CTL_VERSION) == SDHI_VER_GEN3_SDMMC && + host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { + /* + * With HS400, the DAT signal is based on DS, not CLK. + * Therefore, use only CMD status. + */ + u32 smpcmp = sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_SMPCMP) & + SH_MOBILE_SDHI_SCC_SMPCMP_CMD_ERR; + if (!smpcmp) + return false; /* no error in CMD signal */ + else if (smpcmp == SH_MOBILE_SDHI_SCC_SMPCMP_CMD_REQUP) + new_tap++; + else if (smpcmp == SH_MOBILE_SDHI_SCC_SMPCMP_CMD_REQDOWN) + new_tap--; + else + return true; /* need retune */ + } else { + if (val & SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) + return true; /* need retune */ + else if (val & SH_MOBILE_SDHI_SCC_RVSREQ_REQTAPUP) + new_tap++; + else if (val & SH_MOBILE_SDHI_SCC_RVSREQ_REQTAPDOWN) + new_tap--; + else + return false; + } + + priv->tap_set = (new_tap % priv->tap_num); + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, + priv->tap_set / (use_4tap ? 2 : 1)); + + return false; +} + +static bool renesas_sdhi_auto_correction(struct tmio_mmc_host *host) +{ + struct renesas_sdhi *priv = host_to_priv(host); + + /* Check SCC error */ + if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ) & + SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) { + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0); + return true; + } + + return false; +} + static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host) { struct renesas_sdhi *priv = host_to_priv(host); @@ -499,20 +599,14 @@ static bool renesas_sdhi_check_scc_error(struct tmio_mmc_host *host) !(host->mmc->ios.timing == MMC_TIMING_MMC_HS400 && !use_4tap)) return false; - if (mmc_doing_retune(host->mmc)) + if (mmc_doing_retune(host->mmc) || priv->doing_tune) return false; - /* Check SCC error */ if (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL) & - SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN && - sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ) & - SH_MOBILE_SDHI_SCC_RVSREQ_RVSERR) { - /* Clear SCC error */ - sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0); - return true; - } + SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN) + return renesas_sdhi_auto_correction(host); - return false; + return renesas_sdhi_manual_correction(host, use_4tap); } static void renesas_sdhi_hw_reset(struct tmio_mmc_host *host) @@ -531,10 +625,6 @@ static void renesas_sdhi_hw_reset(struct tmio_mmc_host *host) ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL)); - sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL, - ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & - sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL)); - if (host->pdata->flags & TMIO_MMC_MIN_RCAR2) sd_ctrl_write32_as_16_and_16(host, CTL_IRQ_MASK, TMIO_MASK_INIT_RCAR2); @@ -811,14 +901,11 @@ int renesas_sdhi_probe(struct platform_device *pdev, if (!hit) dev_warn(&host->pdev->dev, "Unknown clock rate for tuning\n"); - host->init_tuning = renesas_sdhi_init_tuning; - host->prepare_tuning = renesas_sdhi_prepare_tuning; - host->select_tuning = renesas_sdhi_select_tuning; - host->check_scc_error = renesas_sdhi_check_scc_error; - host->prepare_hs400_tuning = - renesas_sdhi_prepare_hs400_tuning; - host->hs400_downgrade = renesas_sdhi_disable_scc; - host->hs400_complete = renesas_sdhi_hs400_complete; + host->execute_tuning = renesas_sdhi_execute_tuning; + host->check_retune = renesas_sdhi_check_scc_error; + host->ops.prepare_hs400_tuning = renesas_sdhi_prepare_hs400_tuning; + host->ops.hs400_downgrade = renesas_sdhi_disable_scc; + host->ops.hs400_complete = renesas_sdhi_hs400_complete; } num_irqs = platform_irq_count(pdev); |