From patchwork Tue Feb 16 14:16:19 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andre Przywara X-Patchwork-Id: 8326621 Return-Path: X-Original-To: patchwork-linux-mmc@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 6299AC02AE for ; Tue, 16 Feb 2016 14:16:40 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 38DE520279 for ; Tue, 16 Feb 2016 14:16:39 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id BE582202E6 for ; Tue, 16 Feb 2016 14:16:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932768AbcBPOQf (ORCPT ); Tue, 16 Feb 2016 09:16:35 -0500 Received: from foss.arm.com ([217.140.101.70]:53308 "EHLO foss.arm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932705AbcBPOQc (ORCPT ); Tue, 16 Feb 2016 09:16:32 -0500 Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.72.51.249]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 5F2823A1; Tue, 16 Feb 2016 06:15:42 -0800 (PST) Received: from e104803-lin.lan (unknown [10.1.203.153]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id B3D8C3F246; Tue, 16 Feb 2016 06:16:30 -0800 (PST) From: Andre Przywara To: Chen-Yu Tsai , Maxime Ripard , Hans de Goede Cc: Ulf Hansson , linux-mmc@vger.kernel.org, linux-sunxi@googlegroups.com, linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH] (broken) implementation of A64 MMC delay calibration Date: Tue, 16 Feb 2016 14:16:19 +0000 Message-Id: <1455632179-8102-1-git-send-email-andre.przywara@arm.com> X-Mailer: git-send-email 2.6.4 Sender: linux-mmc-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-mmc@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Hi, as people were asking for it, here is my first attempt on proper Allwinner A64 MMC support. Former Allwinner MMC implementations used a special MMC clock, where two clocks could be phase-offset-ed to match the timing requirements. Those clocks have vanished, instead the phase seems to be now created internally in the MMC blob using some new registers. Also there is an auto-calibration feature with should help us to get the right values. This patch is a naive implementation by just looking at the manual. Unfortunately it does not work: The first calibration loop seems to work, but returns all 1's as the result, which sounds suspicious. The second calibration loop hangs (which reminds me of adding a timeout): [ 4.187144] sunxi_mmc: starting sample delay calibration [ 4.192306] calibrate 0x144: register reads as 0x2000 [ 4.201770] calibrate 0x144: tried 16 times, reg=0x7f00 [ 4.206976] sunxi-mmc: delay for register 0x144: 63 [ 4.211665] calibrate 0x144: writing: 0x7fbf [ 4.216353] sunxi_mmc: starting data strobe delay calibration [ 4.221865] calibrate 0x148: register reads as 0x2000 (hang) I didn't have time (and courage) yet to take a closer look at the BSP kernel, but it seems like they don't use this feature for some reason? Also my knowledge of MMC is very limited, so I might be missing something obvious. Do we need to setup this other register (SMHC Drive Delay Control Register at 0x0140) as well or are the default settings good enough? I am posting this so that other people can have a look or use it as a base for own experiments. Signed-off-by: Andre Przywara --- drivers/mmc/host/sunxi-mmc.c | 103 +++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/drivers/mmc/host/sunxi-mmc.c b/drivers/mmc/host/sunxi-mmc.c index 8372a41..0c81a4d 100644 --- a/drivers/mmc/host/sunxi-mmc.c +++ b/drivers/mmc/host/sunxi-mmc.c @@ -71,6 +71,8 @@ #define SDXC_REG_IDIE (0x8C) /* SMC IDMAC Interrupt Enable Register */ #define SDXC_REG_CHDA (0x90) #define SDXC_REG_CBDA (0x94) +#define SDXC_REG_SAMP_DL_REG 0x144 /* SMC sample delay control */ +#define SDXC_REG_DS_DL_REG 0x148 /* SMC data strobe delay control */ #define mmc_readl(host, reg) \ readl((host)->reg_base + SDXC_##reg) @@ -217,6 +219,13 @@ #define SDXC_CLK_50M_DDR 3 #define SDXC_CLK_50M_DDR_8BIT 4 +#define SDXC_CAL_START BIT(15) +#define SDXC_CAL_DONE BIT(14) +#define SDXC_CAL_DL_SHIFT 8 +#define SDXC_CAL_DL_SW_EN BIT(7) +#define SDXC_CAL_DL_SW_SHIFT 0 +#define SDXC_CAL_DL_MASK 0x3f + struct sunxi_mmc_clk_delay { u32 output; u32 sample; @@ -261,6 +270,9 @@ struct sunxi_mmc_host { /* vqmmc */ bool vqmmc_enabled; + + /* does the IP block support autocalibration? */ + bool can_calibrate; }; static int sunxi_mmc_reset_host(struct sunxi_mmc_host *host) @@ -653,10 +665,62 @@ static int sunxi_mmc_oclk_onoff(struct sunxi_mmc_host *host, u32 oclk_en) return 0; } +static int sunxi_mmc_calibrate(struct sunxi_mmc_host *host, + struct mmc_ios *ios, int reg_off) +{ + u32 reg = readl(host->reg_base + reg_off); + u32 delay; + + reg &= ~(SDXC_CAL_DL_MASK << SDXC_CAL_DL_SW_SHIFT); + reg &= ~SDXC_CAL_DL_SW_EN; + + writel(reg | SDXC_CAL_START, host->reg_base + reg_off); + + while (!((reg = readl(host->reg_base + reg_off)) & SDXC_CAL_DONE)) + cpu_relax(); + + delay = (reg >> SDXC_CAL_DL_SHIFT) & SDXC_CAL_DL_MASK; + + reg &= ~SDXC_CAL_START; + reg |= (delay << SDXC_CAL_DL_SW_SHIFT) | SDXC_CAL_DL_SW_EN; + + writel(reg, host->reg_base + reg_off); + + return 0; +} + +static int sunxi_mmc_determine_delays(struct sunxi_mmc_host *host, + struct mmc_ios *ios, int rate) +{ + int index; + + if (rate <= 400000) { + index = SDXC_CLK_400K; + } else if (rate <= 25000000) { + index = SDXC_CLK_25M; + } else if (rate <= 52000000) { + if (ios->timing != MMC_TIMING_UHS_DDR50 && + ios->timing != MMC_TIMING_MMC_DDR52) { + index = SDXC_CLK_50M; + } else if (ios->bus_width == MMC_BUS_WIDTH_8) { + index = SDXC_CLK_50M_DDR_8BIT; + } else { + index = SDXC_CLK_50M_DDR; + } + } else { + return -EINVAL; + } + + clk_set_phase(host->clk_sample, host->clk_delays[index].sample); + clk_set_phase(host->clk_output, host->clk_delays[index].output); + + return 0; +} + static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, struct mmc_ios *ios) { - u32 rate, oclk_dly, rval, sclk_dly; + u32 rate, rval; u32 clock = ios->clock; int ret; @@ -692,32 +756,20 @@ static int sunxi_mmc_clk_set_rate(struct sunxi_mmc_host *host, } mmc_writel(host, REG_CLKCR, rval); - /* determine delays */ - if (rate <= 400000) { - oclk_dly = host->clk_delays[SDXC_CLK_400K].output; - sclk_dly = host->clk_delays[SDXC_CLK_400K].sample; - } else if (rate <= 25000000) { - oclk_dly = host->clk_delays[SDXC_CLK_25M].output; - sclk_dly = host->clk_delays[SDXC_CLK_25M].sample; - } else if (rate <= 52000000) { - if (ios->timing != MMC_TIMING_UHS_DDR50 && - ios->timing != MMC_TIMING_MMC_DDR52) { - oclk_dly = host->clk_delays[SDXC_CLK_50M].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M].sample; - } else if (ios->bus_width == MMC_BUS_WIDTH_8) { - oclk_dly = host->clk_delays[SDXC_CLK_50M_DDR_8BIT].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M_DDR_8BIT].sample; - } else { - oclk_dly = host->clk_delays[SDXC_CLK_50M_DDR].output; - sclk_dly = host->clk_delays[SDXC_CLK_50M_DDR].sample; - } + if (host->can_calibrate) { + ret = sunxi_mmc_calibrate(host, ios, SDXC_REG_SAMP_DL_REG); + if (ret) + return ret; + + ret = sunxi_mmc_calibrate(host, ios, SDXC_REG_DS_DL_REG); + if (ret) + return ret; } else { - return -EINVAL; + ret = sunxi_mmc_determine_delays(host, ios, rate); + if (ret) + return ret; } - clk_set_phase(host->clk_sample, sclk_dly); - clk_set_phase(host->clk_output, oclk_dly); - return sunxi_mmc_oclk_onoff(host, 1); } @@ -990,6 +1042,9 @@ static int sunxi_mmc_resource_request(struct sunxi_mmc_host *host, else host->clk_delays = sunxi_mmc_clk_delays; + if (of_device_is_compatible(np, "allwinner,sun50i-a64-mmc")) + host->can_calibrate = true; + ret = mmc_regulator_get_supply(host->mmc); if (ret) { if (ret != -EPROBE_DEFER)