| From 843c1f55ac23205c5c729044f959a5d856895371 Mon Sep 17 00:00:00 2001 |
| From: Simon Horman <horms+renesas@verge.net.au> |
| Date: Thu, 3 Nov 2016 15:16:04 +0100 |
| Subject: [PATCH 152/299] mmc: sh_mobile_sdhi: Add tuning support |
| |
| Add tuning support for use with SDR104 mode |
| This includes adding support for the sampling clock controller (SCC). |
| |
| Based on work by Ai Kyuse. |
| |
| Cc: Ai Kyuse <ai.kyuse.uw@renesas.com> |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com> |
| Tested-by: Wolfram Sang <wsa+renesas@sang-engineering.com> |
| Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> |
| (cherry picked from commit 06f438dd389a699d27585f2a4d3685fd1ce05a75) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/mmc/host/sh_mobile_sdhi.c | 265 +++++++++++++++++++++++++++++++++++++- |
| 1 file changed, 264 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/mmc/host/sh_mobile_sdhi.c |
| +++ b/drivers/mmc/host/sh_mobile_sdhi.c |
| @@ -47,6 +47,11 @@ |
| |
| #define host_to_priv(host) container_of((host)->pdata, struct sh_mobile_sdhi, mmc_data) |
| |
| +struct sh_mobile_sdhi_scc { |
| + unsigned long clk_rate; /* clock rate for SDR104 */ |
| + u32 tap; /* sampling clock position for SDR104 */ |
| +}; |
| + |
| struct sh_mobile_sdhi_of_data { |
| unsigned long tmio_flags; |
| u32 tmio_ocr_mask; |
| @@ -55,6 +60,9 @@ struct sh_mobile_sdhi_of_data { |
| enum dma_slave_buswidth dma_buswidth; |
| dma_addr_t dma_rx_offset; |
| unsigned bus_shift; |
| + int scc_offset; |
| + struct sh_mobile_sdhi_scc *taps; |
| + int taps_num; |
| }; |
| |
| static const struct sh_mobile_sdhi_of_data of_default_cfg = { |
| @@ -73,12 +81,35 @@ static const struct sh_mobile_sdhi_of_da |
| .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, |
| }; |
| |
| +/* Definitions for sampling clocks */ |
| +static struct sh_mobile_sdhi_scc rcar_gen2_scc_taps[] = { |
| + { |
| + .clk_rate = 156000000, |
| + .tap = 0x00000703, |
| + }, |
| + { |
| + .clk_rate = 0, |
| + .tap = 0x00000300, |
| + }, |
| +}; |
| + |
| static const struct sh_mobile_sdhi_of_data of_rcar_gen2_compatible = { |
| .tmio_flags = TMIO_MMC_HAS_IDLE_WAIT | TMIO_MMC_WRPROTECT_DISABLE | |
| TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2, |
| .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, |
| .dma_buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES, |
| .dma_rx_offset = 0x2000, |
| + .scc_offset = 0x0300, |
| + .taps = rcar_gen2_scc_taps, |
| + .taps_num = ARRAY_SIZE(rcar_gen2_scc_taps), |
| +}; |
| + |
| +/* Definitions for sampling clocks */ |
| +static struct sh_mobile_sdhi_scc rcar_gen3_scc_taps[] = { |
| + { |
| + .clk_rate = 0, |
| + .tap = 0x00000300, |
| + }, |
| }; |
| |
| static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = { |
| @@ -86,6 +117,9 @@ static const struct sh_mobile_sdhi_of_da |
| TMIO_MMC_CLK_ACTUAL | TMIO_MMC_MIN_RCAR2, |
| .capabilities = MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ, |
| .bus_shift = 2, |
| + .scc_offset = 0x1000, |
| + .taps = rcar_gen3_scc_taps, |
| + .taps_num = ARRAY_SIZE(rcar_gen3_scc_taps), |
| }; |
| |
| static const struct of_device_id sh_mobile_sdhi_of_match[] = { |
| @@ -113,6 +147,7 @@ struct sh_mobile_sdhi { |
| struct tmio_mmc_dma dma_priv; |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *pins_default, *pins_uhs; |
| + void __iomem *scc_ctl; |
| }; |
| |
| static void sh_mobile_sdhi_sdbuf_width(struct tmio_mmc_host *host, int width) |
| @@ -263,6 +298,201 @@ static int sh_mobile_sdhi_start_signal_v |
| return pinctrl_select_state(priv->pinctrl, pin_state); |
| } |
| |
| +/* SCC registers */ |
| +#define SH_MOBILE_SDHI_SCC_DTCNTL 0x000 |
| +#define SH_MOBILE_SDHI_SCC_TAPSET 0x002 |
| +#define SH_MOBILE_SDHI_SCC_DT2FF 0x004 |
| +#define SH_MOBILE_SDHI_SCC_CKSEL 0x006 |
| +#define SH_MOBILE_SDHI_SCC_RVSCNTL 0x008 |
| +#define SH_MOBILE_SDHI_SCC_RVSREQ 0x00A |
| + |
| +/* 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_RVSERR BIT(2) |
| + |
| +static inline u32 sd_scc_read32(struct tmio_mmc_host *host, |
| + struct sh_mobile_sdhi *priv, int addr) |
| +{ |
| + return readl(priv->scc_ctl + (addr << host->bus_shift)); |
| +} |
| + |
| +static inline void sd_scc_write32(struct tmio_mmc_host *host, |
| + struct sh_mobile_sdhi *priv, |
| + int addr, u32 val) |
| +{ |
| + writel(val, priv->scc_ctl + (addr << host->bus_shift)); |
| +} |
| + |
| +static unsigned int sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host) |
| +{ |
| + struct sh_mobile_sdhi *priv; |
| + |
| + if (!(host->mmc->caps & MMC_CAP_UHS_SDR104)) |
| + return 0; |
| + |
| + priv = host_to_priv(host); |
| + |
| + /* set sampling clock selection range */ |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL, |
| + 0x8 << SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT); |
| + |
| + /* Initialize SCC */ |
| + sd_ctrl_write32_as_16_and_16(host, CTL_STATUS, 0x0); |
| + |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL, |
| + SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN | |
| + sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL)); |
| + |
| + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN & |
| + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); |
| + |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL, |
| + SH_MOBILE_SDHI_SCC_CKSEL_DTSEL | |
| + sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL)); |
| + |
| + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN | |
| + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); |
| + |
| + 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_DT2FF, host->scc_tappos); |
| + |
| + /* Read TAPNUM */ |
| + return (sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_DTCNTL) >> |
| + SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) & |
| + SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK; |
| +} |
| + |
| +static void sh_mobile_sdhi_prepare_tuning(struct tmio_mmc_host *host, |
| + unsigned long tap) |
| +{ |
| + struct sh_mobile_sdhi *priv = host_to_priv(host); |
| + |
| + /* Set sampling clock position */ |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap); |
| +} |
| + |
| +#define SH_MOBILE_SDHI_MAX_TAP 3 |
| + |
| +static int sh_mobile_sdhi_select_tuning(struct tmio_mmc_host *host) |
| +{ |
| + struct sh_mobile_sdhi *priv = host_to_priv(host); |
| + unsigned long tap_cnt; /* counter of tuning success */ |
| + unsigned long tap_set; /* tap position */ |
| + unsigned long tap_start;/* start position of tuning success */ |
| + unsigned long tap_end; /* end position of tuning success */ |
| + unsigned long ntap; /* temporary counter of tuning success */ |
| + unsigned long i; |
| + |
| + /* Clear SCC_RVSREQ */ |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_RVSREQ, 0); |
| + |
| + /* |
| + * Find the longest consecutive run of successful probes. If that |
| + * is more than SH_MOBILE_SDHI_MAX_TAP probes long then use the |
| + * center index as the tap. |
| + */ |
| + tap_cnt = 0; |
| + ntap = 0; |
| + tap_start = 0; |
| + tap_end = 0; |
| + for (i = 0; i < host->tap_num * 2; i++) { |
| + if (test_bit(i, host->taps)) |
| + ntap++; |
| + else { |
| + if (ntap > tap_cnt) { |
| + tap_start = i - ntap; |
| + tap_end = i - 1; |
| + tap_cnt = ntap; |
| + } |
| + ntap = 0; |
| + } |
| + } |
| + |
| + if (ntap > tap_cnt) { |
| + tap_start = i - ntap; |
| + tap_end = i - 1; |
| + tap_cnt = ntap; |
| + } |
| + |
| + if (tap_cnt >= SH_MOBILE_SDHI_MAX_TAP) |
| + tap_set = (tap_start + tap_end) / 2 % host->tap_num; |
| + else |
| + return -EIO; |
| + |
| + /* Set SCC */ |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_TAPSET, tap_set); |
| + |
| + /* Enable auto re-tuning */ |
| + 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)); |
| + |
| + return 0; |
| +} |
| + |
| + |
| +static bool sh_mobile_sdhi_check_scc_error(struct tmio_mmc_host *host) |
| +{ |
| + struct sh_mobile_sdhi *priv; |
| + |
| + if (!(host->mmc->caps & MMC_CAP_UHS_SDR104)) |
| + return 0; |
| + |
| + priv = host_to_priv(host); |
| + |
| + /* 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; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host) |
| +{ |
| + struct sh_mobile_sdhi *priv; |
| + |
| + if (!(host->mmc->caps & MMC_CAP_UHS_SDR104)) |
| + return; |
| + |
| + priv = host_to_priv(host); |
| + |
| + /* Reset SCC */ |
| + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~CLK_CTL_SCLKEN & |
| + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); |
| + |
| + sd_scc_write32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL, |
| + ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL & |
| + sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_CKSEL)); |
| + |
| + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, CLK_CTL_SCLKEN | |
| + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); |
| + |
| + 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_RVSCNTL, |
| + ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & |
| + sd_scc_read32(host, priv, SH_MOBILE_SDHI_SCC_RVSCNTL)); |
| +} |
| + |
| static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host) |
| { |
| int timeout = 1000; |
| @@ -333,7 +563,7 @@ static int sh_mobile_sdhi_probe(struct p |
| struct tmio_mmc_data *mmd = pdev->dev.platform_data; |
| struct tmio_mmc_host *host; |
| struct resource *res; |
| - int irq, ret, i = 0; |
| + int irq, ret, i; |
| struct tmio_mmc_dma *dma_priv; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| @@ -393,6 +623,11 @@ static int sh_mobile_sdhi_probe(struct p |
| host->card_busy = sh_mobile_sdhi_card_busy; |
| host->start_signal_voltage_switch = |
| sh_mobile_sdhi_start_signal_voltage_switch; |
| + host->init_tuning = sh_mobile_sdhi_init_tuning; |
| + host->prepare_tuning = sh_mobile_sdhi_prepare_tuning; |
| + host->select_tuning = sh_mobile_sdhi_select_tuning; |
| + host->check_scc_error = sh_mobile_sdhi_check_scc_error; |
| + host->hw_reset = sh_mobile_sdhi_hw_reset; |
| } |
| |
| /* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */ |
| @@ -433,6 +668,34 @@ static int sh_mobile_sdhi_probe(struct p |
| if (ret < 0) |
| goto efree; |
| |
| + if (host->mmc->caps & MMC_CAP_UHS_SDR104) { |
| + host->mmc->caps |= MMC_CAP_HW_RESET; |
| + |
| + if (of_id && of_id->data) { |
| + const struct sh_mobile_sdhi_of_data *of_data; |
| + const struct sh_mobile_sdhi_scc *taps; |
| + bool hit = false; |
| + |
| + of_data = of_id->data; |
| + taps = of_data->taps; |
| + |
| + for (i = 0; i < of_data->taps_num; i++) { |
| + if (taps[i].clk_rate == 0 || |
| + taps[i].clk_rate == host->mmc->f_max) { |
| + host->scc_tappos = taps->tap; |
| + hit = true; |
| + break; |
| + } |
| + } |
| + |
| + if (!hit) |
| + dev_warn(&host->pdev->dev, "Unknown clock rate for SDR104\n"); |
| + |
| + priv->scc_ctl = host->ctl + of_data->scc_offset; |
| + } |
| + } |
| + |
| + i = 0; |
| while (1) { |
| irq = platform_get_irq(pdev, i); |
| if (irq < 0) |