Received: by 2002:ac0:950c:0:0:0:0:0 with SMTP id f12csp1320972imc; Mon, 11 Mar 2019 11:05:26 -0700 (PDT) X-Google-Smtp-Source: APXvYqx7BLaJrfoRIo2LDwrh7PWtcJPiFnUPRy6QRJCAdw1w99nuwmtdkOgZTQEduzUxDwpqsHlb X-Received: by 2002:a63:4287:: with SMTP id p129mr13322201pga.84.1552327526548; Mon, 11 Mar 2019 11:05:26 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1552327526; cv=none; d=google.com; s=arc-20160816; b=awWFx0M4S3DJ6wQtL7bTsxJ1Zxk7CrDIbE/nhKmobuqoRLV/YiGi+ewwBh5HSv3Dxi ELUtKIB/r5sWcVMAaf3Bocl5NB7N1oRtD9GoBSZS7K643h+OA6YClDV8iMVkZVpPlbvx DQ0/DCU6NCGV4DXeJvUB+Z//nnn6GfQZmLPDYAJbAmmgICWcenurTnvHjQ4Hyc2u7cpA OtTxGrW7s5ugFE1peIlPJi/t0HG7Jn9VFoDYwy4g8hUjPnXG4iMw7wA1dImRuAXyhxio 5spTya4+Xp+FS7GDoJNq3MoC+BaMtjvVnTvOKe6ZAC1DgEFyq9uqO5KjiQ5Fu6Ool8HD lF+w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:dkim-signature:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from; bh=LAabiUSvh2LcbCYOoVNC2GRN1XL5TaQgZbKNl3K5mus=; b=sjvH/SBw/FobyfeppB9nx8eivNPA1AvI3USNktBlfYJjn3bM2wvcoOCu12FaoBy05z mlUtQTj63oLThgtwVhGgf0HVnGapqgC7nfxWaDYS7WRBZ5rvBqnM04jtaA0bCjdzlFC0 hAyl9Ogec0xwVx88vJb3ZYEvnCXnGpsrakGcITlk8qo6NanDH3hVjBm1lXekvrGq49s7 MU2eAW+M4ZDzA+2Xz9SiBdgsrcpElb5sEp7e306i3gXgoWYTkJrh2l+dcKS7paolPnfI 4dktOaeTDpPzuQUrAbQgHcuZ9HJazGFVgccRH3KiL8Lf2QXimtNbsTmDGZUy9dnXZlnL sl/w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@nvidia.com header.s=n1 header.b="aOFU/DX1"; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=nvidia.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id e17si5720810pfh.6.2019.03.11.11.05.10; Mon, 11 Mar 2019 11:05:26 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@nvidia.com header.s=n1 header.b="aOFU/DX1"; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=nvidia.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728233AbfCKSDQ (ORCPT + 99 others); Mon, 11 Mar 2019 14:03:16 -0400 Received: from hqemgate16.nvidia.com ([216.228.121.65]:13887 "EHLO hqemgate16.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727984AbfCKSCq (ORCPT ); Mon, 11 Mar 2019 14:02:46 -0400 Received: from hqpgpgate102.nvidia.com (Not Verified[216.228.121.13]) by hqemgate16.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Mon, 11 Mar 2019 11:02:43 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate102.nvidia.com (PGP Universal service); Mon, 11 Mar 2019 11:02:44 -0700 X-PGP-Universal: processed; by hqpgpgate102.nvidia.com on Mon, 11 Mar 2019 11:02:44 -0700 Received: from HQMAIL104.nvidia.com (172.18.146.11) by HQMAIL106.nvidia.com (172.18.146.12) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Mon, 11 Mar 2019 18:02:43 +0000 Received: from hqnvemgw02.nvidia.com (172.16.227.111) by HQMAIL104.nvidia.com (172.18.146.11) with Microsoft SMTP Server (TLS) id 15.0.1473.3 via Frontend Transport; Mon, 11 Mar 2019 18:02:43 +0000 Received: from skomatineni-linux.nvidia.com (Not Verified[10.110.103.53]) by hqnvemgw02.nvidia.com with Trustwave SEG (v7,5,8,10121) id ; Mon, 11 Mar 2019 11:02:43 -0700 From: Sowjanya Komatineni To: , , , , CC: , , , , , , Subject: [PATCH V2 03/10] mmc: tegra: update hw tuning process Date: Mon, 11 Mar 2019 11:02:32 -0700 Message-ID: <1552327359-8036-3-git-send-email-skomatineni@nvidia.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1552327359-8036-1-git-send-email-skomatineni@nvidia.com> References: <1552327359-8036-1-git-send-email-skomatineni@nvidia.com> X-NVConfidentiality: public MIME-Version: 1.0 Content-Type: text/plain DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1552327363; bh=LAabiUSvh2LcbCYOoVNC2GRN1XL5TaQgZbKNl3K5mus=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:X-NVConfidentiality:MIME-Version: Content-Type; b=aOFU/DX1gvRaO7FvXVAztOMXUodc/X9CVumlDeWmz5U5hXuprFjwtxxyRxz7+eIrf QGRz10qelevE9nm92sPmT4ztGKOuq2RuymktHEG00fW5EYPSYzVgTwTGnrX5ckweBn LitH7YuyJFbl0GBLrMxLZKXxNAIshwHVzM8dl7dL4gTaDpNCm6qQwZkmeZz8W3IfeQ YgpGJxNI99DXWPy8k3jkzhvbBAv6BcJgHPC+WDCW+4wnXHPXcWQnSUBvr+8UMqSlhB d/igXqgP4Rlm71NddDKtTyQmbw74Velz6jT+DELP8Ztbs8eFvnzxL8NyCeFjczXXaO PIBtCcwyVD/bA== Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This patch includes below HW tuning related fixes. configures tuning parameters as per Tegra TRM WAR fix for manual tap change HW auto-tuning post process As per Tegra TRM, SDR50 mode tuning execution takes upto maximum of 256 tuning iterations and SDR104/HS200/HS400 modes tuning execution takes upto maximum of 128 tuning iterations. This patch programs tuning control register with maximum tuning iterations needed based on the timing along with the start tap, multiplier, and step size used by the HW tuning. Tegra210 has a known issue of glitch on trimmer output when the tap value is changed with the trimmer input clock running and the WAR is to disable card clock before sending tuning command and after sending tuning command wait for 1usec and issue SW reset followed by enabling card clock. This WAR is applicable when changing tap value manually as well. Tegra SDHCI driver has this implemented correctly for manual tap change but missing SW reset before enabling card clock during sending tuning command. Issuing SW reset during tuning command as a part of WAR and is applicable in cases where tuning is performed with single step size for more iterations. This patch includes this fix. HW auto-tuning finds the best largest passing window and sets the tap at the middle of the window. With some devices like sandisk eMMC driving fast edges and due to high tap to tap delay in the Tegra chipset, auto-tuning does not detect falling tap between the valid windows resulting in a parital window or a merged window and the best tap is set at the signal transition which is actually the worst tap location. Recommended SW solution is to detect if the best passing window picked by the HW tuning is a partial or a merged window based on min and max tap delays found from chip characterization across PVT and perform tuning correction to pick the best tap. This patch has implementation of this post HW tuning process for the tegra hosts that support HW tuning through the callback function tegra_sdhci_execute_hw_tuning and uses the tuned tap delay. Tested-by: Jon Hunter Signed-off-by: Sowjanya Komatineni --- drivers/mmc/host/sdhci-tegra.c | 216 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 46086dd43bfb..f1aa0591112a 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -66,6 +66,22 @@ #define SDHCI_VNDR_TUN_CTRL0_0 0x1c0 #define SDHCI_VNDR_TUN_CTRL0_TUN_HW_TAP 0x20000 +#define SDHCI_VNDR_TUN_CTRL0_START_TAP_VAL_MASK 0x03fc0000 +#define SDHCI_VNDR_TUN_CTRL0_START_TAP_VAL_SHIFT 18 +#define SDHCI_VNDR_TUN_CTRL0_MUL_M_MASK 0x00001fc0 +#define SDHCI_VNDR_TUN_CTRL0_MUL_M_SHIFT 6 +#define SDHCI_VNDR_TUN_CTRL0_TUN_ITER_MASK 0x000e000 +#define SDHCI_VNDR_TUN_CTRL0_TUN_ITER_SHIFT 13 +#define TRIES_128 2 +#define TRIES_256 4 +#define SDHCI_VNDR_TUN_CTRL0_TUN_WORD_SEL_MASK 0x7 + +#define SDHCI_TEGRA_VNDR_TUN_CTRL1_0 0x1c4 +#define SDHCI_TEGRA_VNDR_TUN_STATUS0 0x1C8 +#define SDHCI_TEGRA_VNDR_TUN_STATUS1 0x1CC +#define SDHCI_TEGRA_VNDR_TUN_STATUS1_TAP_MASK 0xFF +#define SDHCI_TEGRA_VNDR_TUN_STATUS1_END_TAP_SHIFT 0x8 +#define TUNING_WORD_BIT_SIZE 32 #define SDHCI_TEGRA_AUTO_CAL_CONFIG 0x1e4 #define SDHCI_AUTO_CAL_START BIT(31) @@ -97,6 +113,8 @@ struct sdhci_tegra_soc_data { const struct sdhci_pltfm_data *pdata; u32 nvquirks; + u8 min_tap_delay; + u8 max_tap_delay; }; /* Magic pull up and pull down pad calibration offsets */ @@ -136,6 +154,8 @@ struct sdhci_tegra { u32 default_trim; u32 dqs_trim; bool enable_hwcq; + unsigned long curr_clk_rate; + u8 tuned_tap_delay; }; static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) @@ -241,6 +261,7 @@ static void tegra210_sdhci_writew(struct sdhci_host *host, u16 val, int reg) if (is_tuning_cmd) { udelay(1); + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); tegra_sdhci_configure_card_clk(host, clk_enabled); } } @@ -722,6 +743,7 @@ static void tegra_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) */ host_clk = tegra_host->ddr_signaling ? clock * 2 : clock; clk_set_rate(pltfm_host->clk, host_clk); + tegra_host->curr_clk_rate = host_clk; if (tegra_host->ddr_signaling) host->max_clk = host_clk; else @@ -770,6 +792,159 @@ static void tegra_sdhci_hs400_dll_cal(struct sdhci_host *host) "HS400 delay line calibration timed out\n"); } +static void tegra_sdhci_tap_correction(struct sdhci_host *host, u8 thd_up, + u8 thd_low, u8 fixed_tap) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host); + u32 val, tun_status; + u8 word, bit, edge1, tap, window; + bool tap_result; + bool start_fail = false; + bool start_pass = false; + bool end_pass = false; + bool first_fail = false; + bool first_pass = false; + u8 start_pass_tap = 0; + u8 end_pass_tap = 0; + u8 first_fail_tap = 0; + u8 first_pass_tap = 0; + u8 total_tuning_words = host->tuning_loop_count / TUNING_WORD_BIT_SIZE; + + /* + * Read auto-tuned results and extract good valid passing window by + * filtering out un-wanted bubble/partial/merged windows. + */ + for (word = 0; word < total_tuning_words; word++) { + val = sdhci_readl(host, SDHCI_VNDR_TUN_CTRL0_0); + val &= ~SDHCI_VNDR_TUN_CTRL0_TUN_WORD_SEL_MASK; + val |= word; + sdhci_writel(host, val, SDHCI_VNDR_TUN_CTRL0_0); + tun_status = sdhci_readl(host, SDHCI_TEGRA_VNDR_TUN_STATUS0); + bit = 0; + while (bit < TUNING_WORD_BIT_SIZE) { + tap = word * TUNING_WORD_BIT_SIZE + bit; + tap_result = tun_status & (1 << bit); + if (!tap_result && !start_fail) { + start_fail = true; + if (!first_fail) { + first_fail_tap = tap; + first_fail = true; + } + + } else if (tap_result && start_fail && !start_pass) { + start_pass_tap = tap; + start_pass = true; + if (!first_pass) { + first_pass_tap = tap; + first_pass = true; + } + + } else if (!tap_result && start_fail && start_pass && + !end_pass) { + end_pass_tap = tap - 1; + end_pass = true; + } else if (tap_result && start_pass && start_fail && + end_pass) { + window = end_pass_tap - start_pass_tap; + /* discard merged window and bubble window */ + if (window >= thd_up || window < thd_low) { + start_pass_tap = tap; + end_pass = false; + } else { + /* set tap at middle of valid window */ + tap = start_pass_tap + window / 2; + tegra_host->tuned_tap_delay = tap; + return; + } + } + + bit++; + } + } + + if (!first_fail) { + WARN_ON("no edge detected, continue with hw tuned delay.\n"); + } else if (first_pass) { + /* set tap location at fixed tap relative to the first edge */ + edge1 = first_fail_tap + (first_pass_tap - first_fail_tap) / 2; + if (edge1 - 1 > fixed_tap) + tegra_host->tuned_tap_delay = edge1 - fixed_tap; + else + tegra_host->tuned_tap_delay = edge1 + fixed_tap; + } +} + +static void tegra_sdhci_post_tuning(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = sdhci_pltfm_priv(pltfm_host); + const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + u32 avg_tap_dly, val, min_tap_dly, max_tap_dly; + u8 fixed_tap, start_tap, end_tap, window_width; + u8 thdupper, thdlower; + u8 num_iter; + u32 clk_rate_mhz, period_ps, bestcase, worstcase; + + /* retain HW tuned tap to use incase if no correction is needed */ + val = sdhci_readl(host, SDHCI_TEGRA_VENDOR_CLOCK_CTRL); + tegra_host->tuned_tap_delay = (val & SDHCI_CLOCK_CTRL_TAP_MASK) >> + SDHCI_CLOCK_CTRL_TAP_SHIFT; + if (soc_data->min_tap_delay && soc_data->max_tap_delay) { + min_tap_dly = soc_data->min_tap_delay; + max_tap_dly = soc_data->max_tap_delay; + clk_rate_mhz = tegra_host->curr_clk_rate / USEC_PER_SEC; + period_ps = USEC_PER_SEC / clk_rate_mhz; + bestcase = period_ps / min_tap_dly; + worstcase = period_ps / max_tap_dly; + /* + * Upper and Lower bound thresholds used to detect merged and + * bubble windows + */ + thdupper = (2 * worstcase + bestcase) / 2; + thdlower = worstcase / 4; + /* + * fixed tap is used when HW tuning result contains single edge + * and tap is set at fixed tap delay relative to the first edge + */ + avg_tap_dly = (period_ps * 2) / (min_tap_dly + max_tap_dly); + fixed_tap = avg_tap_dly / 2; + + val = sdhci_readl(host, SDHCI_TEGRA_VNDR_TUN_STATUS1); + start_tap = val & SDHCI_TEGRA_VNDR_TUN_STATUS1_TAP_MASK; + end_tap = (val >> SDHCI_TEGRA_VNDR_TUN_STATUS1_END_TAP_SHIFT) & + SDHCI_TEGRA_VNDR_TUN_STATUS1_TAP_MASK; + window_width = end_tap - start_tap; + num_iter = host->tuning_loop_count; + /* + * partial window includes edges of the tuning range. + * merged window includes more taps so window width is higher + * than upper threshold. + */ + if (start_tap == 0 || (end_tap == (num_iter - 1)) || + (end_tap == num_iter - 2) || window_width >= thdupper) { + pr_debug("%s: Apply tuning correction\n", + mmc_hostname(host->mmc)); + tegra_sdhci_tap_correction(host, thdupper, thdlower, + fixed_tap); + } + } + + tegra_sdhci_set_tap(host, tegra_host->tuned_tap_delay); +} + +static int tegra_sdhci_execute_hw_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + int err; + + err = sdhci_execute_tuning(mmc, opcode); + if (!err && !host->tuning_err) + tegra_sdhci_post_tuning(host); + + return err; +} + static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing) { @@ -778,17 +953,22 @@ static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host, bool set_default_tap = false; bool set_dqs_trim = false; bool do_hs400_dll_cal = false; + u8 iter = TRIES_256; + u32 val; tegra_host->ddr_signaling = false; switch (timing) { case MMC_TIMING_UHS_SDR50: + break; case MMC_TIMING_UHS_SDR104: case MMC_TIMING_MMC_HS200: /* Don't set default tap on tunable modes. */ + iter = TRIES_128; break; case MMC_TIMING_MMC_HS400: set_dqs_trim = true; do_hs400_dll_cal = true; + iter = TRIES_128; break; case MMC_TIMING_MMC_DDR52: case MMC_TIMING_UHS_DDR50: @@ -800,11 +980,25 @@ static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host, break; } + val = sdhci_readl(host, SDHCI_VNDR_TUN_CTRL0_0); + val &= ~(SDHCI_VNDR_TUN_CTRL0_TUN_ITER_MASK | + SDHCI_VNDR_TUN_CTRL0_START_TAP_VAL_MASK | + SDHCI_VNDR_TUN_CTRL0_MUL_M_MASK); + val |= (iter << SDHCI_VNDR_TUN_CTRL0_TUN_ITER_SHIFT | + 0 << SDHCI_VNDR_TUN_CTRL0_START_TAP_VAL_SHIFT | + 1 << SDHCI_VNDR_TUN_CTRL0_MUL_M_SHIFT); + sdhci_writel(host, val, SDHCI_VNDR_TUN_CTRL0_0); + sdhci_writel(host, 0, SDHCI_TEGRA_VNDR_TUN_CTRL1_0); + + host->tuning_loop_count = (iter == TRIES_128) ? 128 : 256; + sdhci_set_uhs_signaling(host, timing); tegra_sdhci_pad_autocalib(host); - if (set_default_tap) + if (tegra_host->tuned_tap_delay && !set_default_tap) + tegra_sdhci_set_tap(host, tegra_host->tuned_tap_delay); + else tegra_sdhci_set_tap(host, tegra_host->default_tap); if (set_dqs_trim) @@ -1110,6 +1304,8 @@ static const struct sdhci_tegra_soc_data soc_data_tegra210 = { NVQUIRK_DIS_CARD_CLK_CONFIG_TAP | NVQUIRK_ENABLE_SDR50 | NVQUIRK_ENABLE_SDR104, + .min_tap_delay = 106, + .max_tap_delay = 185, }; static const struct sdhci_ops tegra186_sdhci_ops = { @@ -1150,9 +1346,23 @@ static const struct sdhci_tegra_soc_data soc_data_tegra186 = { NVQUIRK_DIS_CARD_CLK_CONFIG_TAP | NVQUIRK_ENABLE_SDR50 | NVQUIRK_ENABLE_SDR104, + .min_tap_delay = 84, + .max_tap_delay = 136, +}; + +static const struct sdhci_tegra_soc_data soc_data_tegra194 = { + .pdata = &sdhci_tegra186_pdata, + .nvquirks = NVQUIRK_NEEDS_PAD_CONTROL | + NVQUIRK_HAS_PADCALIB | + NVQUIRK_DIS_CARD_CLK_CONFIG_TAP | + NVQUIRK_ENABLE_SDR50 | + NVQUIRK_ENABLE_SDR104, + .min_tap_delay = 96, + .max_tap_delay = 139, }; static const struct of_device_id sdhci_tegra_dt_match[] = { + { .compatible = "nvidia,tegra194-sdhci", .data = &soc_data_tegra194 }, { .compatible = "nvidia,tegra186-sdhci", .data = &soc_data_tegra186 }, { .compatible = "nvidia,tegra210-sdhci", .data = &soc_data_tegra210 }, { .compatible = "nvidia,tegra124-sdhci", .data = &soc_data_tegra124 }, @@ -1251,6 +1461,10 @@ static int sdhci_tegra_probe(struct platform_device *pdev) host->mmc_host_ops.hs400_enhanced_strobe = tegra_sdhci_hs400_enhanced_strobe; + if (!host->ops->platform_execute_tuning) + host->mmc_host_ops.execute_tuning = + tegra_sdhci_execute_hw_tuning; + rc = mmc_of_parse(host->mmc); if (rc) goto err_parse_dt; -- 2.7.4