From patchwork Tue May 24 02:43:16 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Horman X-Patchwork-Id: 9132657 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id C5ED8607D6 for ; Tue, 24 May 2016 02:43:30 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B9BAD28247 for ; Tue, 24 May 2016 02:43:30 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AEABF2825C; Tue, 24 May 2016 02:43:30 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 105672824E for ; Tue, 24 May 2016 02:43:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752488AbcEXCn3 (ORCPT ); Mon, 23 May 2016 22:43:29 -0400 Received: from kirsty.vergenet.net ([202.4.237.240]:35212 "EHLO kirsty.vergenet.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750892AbcEXCn2 (ORCPT ); Mon, 23 May 2016 22:43:28 -0400 Received: from reginn.isobedori.kobe.vergenet.net (p6216-ipbfp1501kobeminato.hyogo.ocn.ne.jp [114.153.217.216]) by kirsty.vergenet.net (Postfix) with ESMTPA id ACB7425BE44; Tue, 24 May 2016 12:43:18 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=verge.net.au; s=mail; t=1464057798; bh=k0NZ1PM3aWGL0hCW8mkCRtNiZDcPaAQmyNYsXkgcM3I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QkjNupXuW5JABSqDZQW1wdYQvBex4Wd2jG48xsNvNvZ367JyNep1GlSn3HkUIg+cP s0OR5Pg59mNPyEshysDqvo+pYNx2fHlZdTS4gn7WDi5VM5YRTJg3+rK6bgitICZReN AzQs3muLldYyLy5HdDc9n3C1IFR/BahXV8hzcAuw= Received: by reginn.isobedori.kobe.vergenet.net (Postfix, from userid 7100) id B2589940499; Tue, 24 May 2016 11:43:18 +0900 (JST) From: Simon Horman To: Wolfram Sang , Ulf Hansson Cc: Magnus Damm , linux-mmc@vger.kernel.org, linux-renesas-soc@vger.kernel.org, Ai Kyuse , Simon Horman Subject: [PATCH v3 3/4] mmc: sh_mobile_sdhi: Add tuning support Date: Tue, 24 May 2016 11:43:16 +0900 Message-Id: <1464057797-29951-4-git-send-email-horms+renesas@verge.net.au> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1464057797-29951-1-git-send-email-horms+renesas@verge.net.au> References: <1464057797-29951-1-git-send-email-horms+renesas@verge.net.au> Sender: linux-mmc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-mmc@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Ai Kyuse Add tuning support for use with SDR104 mode This includes adding support for the sampling clock controller (SCC). Signed-off-by: Ai Kyuse Signed-off-by: Simon Horman --- v3 [Simon Horman] * As suggested by Kuninori Morimoto: - Do not add unused retuning callback to struct tmio_mmc_host - Change return type of prepare_tuning callback to void - Add tap_size parameter to select_tuning callback v2 [Simon Horman] * As suggested by Kuninori Morimoto - Use host->mmc->caps & MMC_CAP_UHS_SDR104 instead of pdata->flags & TMIO_MMC_HAS_UHS_SCC to avoid needing the MMC_CAP_UHS_SDR104 flag at all. N.B: Morimoto-san suggested using but this flag is not actually set there in by current probe come. - Simplify logic in sh_mobile_sdhi_inquiry_tuning * As suggested by Wolfram Sang - Use clk_rate instead of clk for field in struct sh_mobile_sdhi_scc - Remove inquiry_tuning callback which is now unnecessary as calling of tuning is handled by the core - Removed unused sh_mobile_sdhi_set_clk_div callback - Save sci_base address rather than calculating it on each read and write * Update selection logic in sh_mobile_sdhi_select_tuning to match spec * Use bool instead of long for taps parameter of sh_mobile_sdhi_select_tuning() * Return 0 from sh_mobile_sdhi_init_tuning() if the SDR104 capability is not set and thus tuning should not be performed because it is not supported by the hardware v1 [Simon Horman] * Rebase * Always use value of 0x8 for TAPNUM field of DTCNTL register rather than reading value from DT property. There does not seem to be a need to expose this in DT at this point. * Do not include tmio_mmc_start_signal_voltage_switch changes which are already in mainline in a different form * Do not add renesas,clk-rate property as the max-frequency property, which is now present in mainline, seems to provide the needed rate * Omit Gen3 specific changes * Do not provide renesas,mmc-scc-tappos DT property. Instead, always use taps provided in driver. * Do not parse sd-uhs-sdr50 and sd-uhs-sdr104 properties. This is handled by the core. v0 [Ai Kyuse] --- drivers/mmc/host/sh_mobile_sdhi.c | 203 +++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 5309c73be1f0..3ad52de45b89 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -41,6 +41,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; unsigned long capabilities; @@ -48,6 +53,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 = { @@ -60,12 +68,27 @@ static const struct sh_mobile_sdhi_of_data of_rcar_gen1_compatible = { .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), }; static const struct sh_mobile_sdhi_of_data of_rcar_gen3_compatible = { @@ -98,6 +121,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) @@ -241,6 +265,155 @@ static int sh_mobile_sdhi_start_signal_voltage_switch(struct mmc_host *mmc, 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(1) +/* 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, int addr) +{ + return readl(host_to_priv(host)->scc_ctl + (addr << host->bus_shift)); +} + +static inline void sd_scc_write32(struct tmio_mmc_host *host, int addr, u32 val) +{ + writel(val, host_to_priv(host)->scc_ctl + (addr << host->bus_shift)); +} + +static unsigned int sh_mobile_sdhi_init_tuning(struct tmio_mmc_host *host) +{ + if (!(host->mmc->caps & MMC_CAP_UHS_SDR104)) + return 0; + + /* set sampling clock selection range */ + sd_scc_write32(host, 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, 0x00000000); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_DTCNTL, + SH_MOBILE_SDHI_SCC_DTCNTL_TAPEN | + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_DTCNTL)); + + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 & + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_CKSEL, + SH_MOBILE_SDHI_SCC_CKSEL_DTSEL | + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_CKSEL)); + + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 | + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL, + ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_DT2FF, host->scc_tappos); + + /* Read TAPNUM */ + return (sd_scc_read32(host, 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) +{ + /* Set sampling clock position */ + sd_scc_write32(host, 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, + bool *tap, int tap_size) +{ + unsigned long tap_num, i; + int ok_count; + + /* Clear SCC_RVSREQ */ + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSREQ, 0); + + /* Select SCC */ + tap_num = (sd_scc_read32(host, SH_MOBILE_SDHI_SCC_DTCNTL) >> + SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) & + SH_MOBILE_SDHI_SCC_DTCNTL_TAPNUM_MASK; + + if (tap_num * 2 != tap_size) + return -EINVAL; + + /* + * Select clock where three consecutive bock reads succeeded. + * + * There may be multiple occurrences of three successive reads + * and selecting any of them is correct. Here the first one is + * selected. + */ + ok_count = 0; + for (i = 0; i < tap_size; i++) { + if (tap[i]) + ok_count++; + else + ok_count = 0; + if (ok_count == 3) + break; + } + + if (ok_count != 3) + return -EIO; + + /* Set SCC */ + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_TAPSET, i); + + /* Enable auto re-tuning */ + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL, + SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN | + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL)); + + return 0; +} + +static void sh_mobile_sdhi_hw_reset(struct tmio_mmc_host *host) +{ + if (host->mmc->caps & MMC_CAP_UHS_SDR104) { + /* Reset SCC */ + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 & + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_CKSEL, + ~SH_MOBILE_SDHI_SCC_CKSEL_DTSEL & + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_CKSEL)); + + sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 | + sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL, + ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL)); + + sd_scc_write32(host, SH_MOBILE_SDHI_SCC_RVSCNTL, + ~SH_MOBILE_SDHI_SCC_RVSCNTL_RVSEN & + sd_scc_read32(host, SH_MOBILE_SDHI_SCC_RVSCNTL)); + } +} + static int sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host) { int timeout = 1000; @@ -311,7 +484,7 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) 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); @@ -364,6 +537,10 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) host->clk_disable = sh_mobile_sdhi_clk_disable; host->multi_io_quirk = sh_mobile_sdhi_multi_io_quirk; 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->hw_reset = sh_mobile_sdhi_hw_reset; /* Orginally registers were 16 bit apart, could be 32 or 64 nowadays */ if (!host->bus_shift && resource_size(res) > 0x100) /* old way to determine the shift */ @@ -403,6 +580,30 @@ static int sh_mobile_sdhi_probe(struct platform_device *pdev) 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 = of_id->data; + const struct sh_mobile_sdhi_scc *taps = of_data->taps; + bool hit = false; + + 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 and HS200\n"); + + priv->scc_ctl = host->ctl + of_data->scc_offset; + } + + i = 0; while (1) { irq = platform_get_irq(pdev, i); if (irq < 0)