From patchwork Mon Aug 31 03:25:37 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andre Heider X-Patchwork-Id: 1354090 X-Patchwork-Delegate: sr@denx.de Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=B0EwFsOV; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BfwbG4R6Rz9sTF for ; Mon, 31 Aug 2020 13:26:18 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 43EC782272; Mon, 31 Aug 2020 05:26:08 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="B0EwFsOV"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id EC406821DE; Mon, 31 Aug 2020 05:25:58 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,SPF_HELO_NONE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-wm1-x32c.google.com (mail-wm1-x32c.google.com [IPv6:2a00:1450:4864:20::32c]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id A7CAD81BF1 for ; Mon, 31 Aug 2020 05:25:40 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=a.heider@gmail.com Received: by mail-wm1-x32c.google.com with SMTP id l9so144141wme.3 for ; Sun, 30 Aug 2020 20:25:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=th/2PtEltclU4/NrYKKamCux2LrTMmentS5qJf4h4QI=; b=B0EwFsOVz9puVuE57Q6zuR4fPZOHCtKNUFBVvSr8MbkOWtvt8882emGtx4lDzOtfUd N9pe2BNhh4h98Tug59ftWrQuGQ3XMnb3/pYJOSOzAZ5i1cGoDLpcUulmz/+Oe9HeUFoV mLK1Bef4JS3Q4qObZm2W3z2xp/xcPYL5SbWFSrZOZAUNEt5meEa1iiBoydRSESPd/wNY K4dNja7uixX42yJigOSwLDY01IVkVH2hh/EreXTrcwAoGb91aN3j5eovVGCGjh1OPW8P F9+BFWXEbHNw4PvjUA5IrDJq39Ro2echSTtAhXjrlpKQag+AWIp8esRWpElMKfVt4bkK 3/kw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=th/2PtEltclU4/NrYKKamCux2LrTMmentS5qJf4h4QI=; b=lmpNyz3Fr3CKs1UDKd3eek3jseVzePfyoM507XtsvReVbNoL6gOBVUv7b+sJjfuQhw HQIt79N8R81WCyiEGUsc0FhXUkM+T6kOskqivW4h70yZAWRaJVo+ibusYX6uoIXiQs1N d66bEB+xN0wNBRdHv4NlO50mKb6im9734M8HhoN/IkQD43H0pzpLtv98ZhMPgd4Qpz4u YKXLD2Vx6ljAR4JhAqz3IPrCrEgWvn8tKhT3v4T6a9cQfyHpNO0R4iVRDWZBAc0o0eeN mKuaBCkbDMecWuaptdRzvrQNzbxWbqErIHkFQd3OWDM7wPQiuAGGTTQev3GovMHbwUal 2E9Q== X-Gm-Message-State: AOAM531XXSRIwUQoyT4pAC4yhmFIup8p9aYLsm2HVx3EbbRulkdnD92m LzvoUycguqp766e8S405ILw= X-Google-Smtp-Source: ABdhPJxSr27Atk7BUumPSGxyBuZ8xiOU5xJ5RO2oIQmBT8gwM5+TRod5+NjgpavN/IVrGO+w7z/vLw== X-Received: by 2002:a1c:56d6:: with SMTP id k205mr8961004wmb.88.1598844340131; Sun, 30 Aug 2020 20:25:40 -0700 (PDT) Received: from localhost.localdomain (a89-183-75-181.net-htp.de. [89.183.75.181]) by smtp.gmail.com with ESMTPSA id d18sm1660351wrm.10.2020.08.30.20.25.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 30 Aug 2020 20:25:39 -0700 (PDT) From: Andre Heider To: Stefan Roese , Konstantin Porotchkin Cc: =?utf-8?q?Pali_Roh=C3=A1r?= , u-boot@lists.denx.de Subject: [PATCH 1/2] xenon_sdhci: support for HS200 mode Date: Mon, 31 Aug 2020 05:25:37 +0200 Message-Id: <20200831032538.467336-1-a.heider@gmail.com> X-Mailer: git-send-email 2.28.0 MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.3 at phobos.denx.de X-Virus-Status: Clean From: Wojciech Macek Add support for Marvell Xenon SDHCI HS200 mode. Changes focue mostly on correct PHY initialization. All procedure is similar to the one done by Linux driver, but simplified. Change-Id: I5e2396eeb23784f495abc18ea5a2eb7a92390730 Signed-off-by: Wojciech Macek Reviewed-on: http://vgitil04.il.marvell.com:8080/59230 Tested-by: iSoC Platform CI Reviewed-by: Grzegorz Jaszczyk Reviewed-by: Kostya Porotchkin Reviewed-by: Igal Liberman [a.heider: adapt to mainline] Signed-off-by: Andre Heider --- Missing downstream patch, noticed while diffing branches: https://github.com/MarvellEmbeddedProcessors/u-boot-marvell/commit/387232507a0d9dda3990284221eaf87d7541dd02 drivers/mmc/xenon_sdhci.c | 335 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 323 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/xenon_sdhci.c b/drivers/mmc/xenon_sdhci.c index 7f9a579c83..ec255d30b0 100644 --- a/drivers/mmc/xenon_sdhci.c +++ b/drivers/mmc/xenon_sdhci.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -45,6 +46,7 @@ DECLARE_GLOBAL_DATA_PTR; #define SDHC_SLOT_EMMC_CTRL 0x0130 #define ENABLE_DATA_STROBE_SHIFT 24 +#define ENABLE_DATA_STROBE BIT(ENABLE_DATA_STROBE_SHIFT) #define SET_EMMC_RSTN_SHIFT 16 #define EMMC_VCCQ_MASK 0x3 #define EMMC_VCCQ_1_8V 0x1 @@ -64,6 +66,7 @@ DECLARE_GLOBAL_DATA_PTR; #define OUTPUT_QSN_PHASE_SELECT BIT(17) #define SAMPL_INV_QSP_PHASE_SELECT BIT(18) #define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18 +#define EMMC_PHY_SDIO_MODE BIT(28) #define EMMC_PHY_SLOW_MODE BIT(29) #define PHY_INITIALIZAION BIT(31) #define WAIT_CYCLE_BEFORE_USING_MASK 0xf @@ -90,6 +93,7 @@ DECLARE_GLOBAL_DATA_PTR; #define FC_QSN_RECEN BIT(27) #define OEN_QSN BIT(28) #define AUTO_RECEN_CTRL BIT(30) +#define FC_ALL_CMOS_RECEIVER (REC_EN_MASK << REC_EN_SHIFT) #define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xc) #define EMMC5_1_FC_QSP_PD BIT(9) @@ -99,10 +103,71 @@ DECLARE_GLOBAL_DATA_PTR; #define EMMC5_1_FC_DQ_PD 0xff #define EMMC5_1_FC_DQ_PU (0xff << 16) +#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10) +#define ZNR_MASK 0x1F +#define ZNR_SHIFT 8 +#define ZPR_MASK 0x1F + #define SDHCI_RETUNE_EVT_INTSIG 0x00001000 +#define SDHCI_HOST_CONTROL2 0x3E +#define SDHCI_CTRL_UHS_MASK 0x0007 +#define SDHCI_CTRL_UHS_SDR12 0x0000 +#define SDHCI_CTRL_UHS_SDR25 0x0001 +#define SDHCI_CTRL_UHS_SDR50 0x0002 +#define SDHCI_CTRL_UHS_SDR104 0x0003 +#define SDHCI_CTRL_UHS_DDR50 0x0004 +#define SDHCI_CTRL_HS400 0x0005 /* Non-standard */ +#define SDHCI_CTRL_HS200_ONLY 0x0005 /* Non-standard */ +#define SDHCI_CTRL_HS400_ONLY 0x0006 /* Non-standard */ +#define SDHCI_CTRL_VDD_180 0x0008 +#define SDHCI_CTRL_DRV_TYPE_MASK 0x0030 +#define SDHCI_CTRL_DRV_TYPE_B 0x0000 +#define SDHCI_CTRL_DRV_TYPE_A 0x0010 +#define SDHCI_CTRL_DRV_TYPE_C 0x0020 +#define SDHCI_CTRL_DRV_TYPE_D 0x0030 +#define SDHCI_CTRL_EXEC_TUNING 0x0040 +#define SDHCI_CTRL_TUNED_CLK 0x0080 +#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000 + +/* + * Config to eMMC PHY to prepare for tuning. + * Enable HW DLL and set the TUNING_STEP + */ +#define XENON_SLOT_DLL_CUR_DLY_VAL 0x0150 + +#define XENON_SLOT_OP_STATUS_CTRL 0x0128 +#define XENON_TUN_CONSECUTIVE_TIMES_SHIFT 16 +#define XENON_TUN_CONSECUTIVE_TIMES_MASK 0x7 +#define XENON_TUN_CONSECUTIVE_TIMES 0x4 +#define XENON_TUNING_STEP_SHIFT 12 +#define XENON_TUNING_STEP_MASK 0xF +#define XENON_TUNING_STEP_DIVIDER BIT(6) + +#define XENON_EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14) +#define XENON_EMMC_5_0_PHY_DLL_CONTROL \ + (XENON_EMMC_5_0_PHY_REG_BASE + 0x10) +#define XENON_DLL_ENABLE BIT(31) +#define XENON_DLL_UPDATE_STROBE_5_0 BIT(30) +#define XENON_DLL_REFCLK_SEL BIT(30) +#define XENON_DLL_UPDATE BIT(23) +#define XENON_DLL_PHSEL1_SHIFT 24 +#define XENON_DLL_PHSEL0_SHIFT 16 +#define XENON_DLL_PHASE_MASK 0x3F +#define XENON_DLL_PHASE_90_DEGREE 0x1F +#define XENON_DLL_FAST_LOCK BIT(5) +#define XENON_DLL_GAIN2X BIT(3) +#define XENON_DLL_BYPASS_EN BIT(0) + +#define XENON_SLOT_EXT_PRESENT_STATE 0x014C +#define XENON_DLL_LOCK_STATE 0x1 + /* Hyperion only have one slot 0 */ #define XENON_MMC_SLOT_ID_HYPERION 0 +#define SLOT_MASK(slot) BIT(slot) + +#define XENON_EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18) +#define XENON_LOGIC_TIMING_VALUE 0x00AA8977 #define MMC_TIMING_LEGACY 0 #define MMC_TIMING_MMC_HS 1 @@ -266,6 +331,176 @@ static int xenon_mmc_start_signal_voltage_switch(struct sdhci_host *host) return ret; } +/* + * Xenon defines different values for HS200 and HS400 + * in Host_Control_2 + */ +static void xenon_set_uhs_signaling(struct sdhci_host *host, + unsigned int timing) +{ + u16 ctrl_2; + + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + /* Select Bus Speed Mode for host */ + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if (timing == MMC_TIMING_MMC_HS200) + ctrl_2 |= SDHCI_CTRL_HS200_ONLY; + else if (timing == MMC_TIMING_UHS_SDR104) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (timing == MMC_TIMING_UHS_SDR12) + ctrl_2 |= SDHCI_CTRL_UHS_SDR12; + else if (timing == MMC_TIMING_UHS_SDR25) + ctrl_2 |= SDHCI_CTRL_UHS_SDR25; + else if (timing == MMC_TIMING_UHS_SDR50) + ctrl_2 |= SDHCI_CTRL_UHS_SDR50; + else if ((timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_MMC_DDR52)) + ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + else if (timing == MMC_TIMING_MMC_HS400) + ctrl_2 |= SDHCI_CTRL_HS400_ONLY; + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); +} + +/* + * If eMMC PHY Slow Mode is required in lower speed mode (SDCLK < 55MHz) + * in SDR mode, enable Slow Mode to bypass eMMC PHY. + * SDIO slower SDR mode also requires Slow Mode. + * + * If Slow Mode is enabled, return 0. + * Otherwise, return -EINVAL. + */ +static int xenon_emmc_phy_slow_mode(struct sdhci_host *host, + unsigned char timing) +{ + u32 reg; + int ret = -EINVAL; + + if (host->mmc->tran_speed > 52000000) + return -EINVAL; + + reg = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + /* When in slower SDR mode, enable Slow Mode for SDIO */ + switch (timing) { + case MMC_TIMING_LEGACY: + /* + * If Slow Mode is required, enable Slow Mode by default + * in early init phase to avoid any potential issue. + */ + reg |= EMMC_PHY_SLOW_MODE; + ret = 0; + break; + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_SDR12: + case MMC_TIMING_SD_HS: + case MMC_TIMING_MMC_HS: + if (IS_SD(host->mmc)) { + reg |= EMMC_PHY_SLOW_MODE; + ret = 0; + break; + } + default: + reg &= ~EMMC_PHY_SLOW_MODE; + ret = -EINVAL; + } + + sdhci_writel(host, reg, EMMC_PHY_TIMING_ADJUST); + return ret; +} + +static void xenon_emmc_phy_disable_data_strobe(struct sdhci_host *host) +{ + u32 reg; + + /* Disable SDHC Data Strobe */ + reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL); + reg &= ~ENABLE_DATA_STROBE; + sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL); +} + +/* + * Enable eMMC PHY HW DLL + * DLL should be enabled and stable before HS200/SDR104 tuning, + * and before HS400 data strobe setting. + */ +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host) +{ + u32 reg; + u32 timeout; + + if (host->mmc->tran_speed <= 52000000) + return -EINVAL; + + reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL); + if (reg & XENON_DLL_ENABLE) + return 0; + + /* Enable DLL */ + reg = sdhci_readl(host, XENON_EMMC_PHY_DLL_CONTROL); + reg |= (XENON_DLL_ENABLE | XENON_DLL_FAST_LOCK); + + /* + * Set Phase as 90 degree, which is most common value. + * Might set another value if necessary. + * The granularity is 1 degree. + */ + reg &= ~((XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL0_SHIFT) | + (XENON_DLL_PHASE_MASK << XENON_DLL_PHSEL1_SHIFT)); + reg |= ((XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL0_SHIFT) | + (XENON_DLL_PHASE_90_DEGREE << XENON_DLL_PHSEL1_SHIFT)); + + reg &= ~(XENON_DLL_BYPASS_EN | XENON_DLL_REFCLK_SEL); + reg |= XENON_DLL_UPDATE; + sdhci_writel(host, reg, XENON_EMMC_PHY_DLL_CONTROL); + + /* Wait max 32 ms */ + timeout = 32; + while (!(sdhci_readw(host, XENON_SLOT_EXT_PRESENT_STATE) & + XENON_DLL_LOCK_STATE)) { + if (timeout > 32) { + printf("Wait for DLL Lock time-out\n"); + return -ETIMEDOUT; + } + udelay(1000); + timeout++; + } + return 0; +} + +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host) +{ + u32 reg, tuning_step; + int ret; + + if (host->mmc->tran_speed <= 52000000) + return -EINVAL; + + ret = xenon_emmc_phy_enable_dll(host); + if (ret) + return ret; + + /* Achieve TUNING_STEP with HW DLL help */ + reg = sdhci_readl(host, XENON_SLOT_DLL_CUR_DLY_VAL); + tuning_step = reg / XENON_TUNING_STEP_DIVIDER; + if (unlikely(tuning_step > XENON_TUNING_STEP_MASK)) { + dev_warn(mmc_dev(host->mmc), + "HS200 TUNING_STEP %d is larger than MAX value\n", + tuning_step); + tuning_step = XENON_TUNING_STEP_MASK; + } + + /* Set TUNING_STEP for later tuning */ + reg = sdhci_readl(host, XENON_SLOT_OP_STATUS_CTRL); + reg &= ~(XENON_TUN_CONSECUTIVE_TIMES_MASK << + XENON_TUN_CONSECUTIVE_TIMES_SHIFT); + reg |= (XENON_TUN_CONSECUTIVE_TIMES << + XENON_TUN_CONSECUTIVE_TIMES_SHIFT); + reg &= ~(XENON_TUNING_STEP_MASK << XENON_TUNING_STEP_SHIFT); + reg |= (tuning_step << XENON_TUNING_STEP_SHIFT); + sdhci_writel(host, reg, XENON_SLOT_OP_STATUS_CTRL); + + return 0; +} + static void xenon_mmc_phy_set(struct sdhci_host *host) { struct xenon_sdhci_priv *priv = host->mmc->priv; @@ -273,8 +508,8 @@ static void xenon_mmc_phy_set(struct sdhci_host *host) /* Setup pad, set bit[30], bit[28] and bits[26:24] */ var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL); - var |= AUTO_RECEN_CTRL | OEN_QSN | FC_QSP_RECEN | - FC_CMD_RECEN | FC_DQ_RECEN; + var |= OEN_QSN | FC_QSP_RECEN | FC_CMD_RECEN | FC_DQ_RECEN | + FC_ALL_CMOS_RECEIVER; sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL); /* Set CMD and DQ Pull Up */ @@ -284,20 +519,45 @@ static void xenon_mmc_phy_set(struct sdhci_host *host) sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL1); /* - * If timing belongs to high speed, set bit[17] of + * If Timing belongs to high speed, clear bit[17] of * EMMC_PHY_TIMING_ADJUST register */ + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); if ((priv->timing == MMC_TIMING_MMC_HS400) || (priv->timing == MMC_TIMING_MMC_HS200) || + (priv->timing == MMC_TIMING_MMC_DDR52) || (priv->timing == MMC_TIMING_UHS_SDR50) || (priv->timing == MMC_TIMING_UHS_SDR104) || (priv->timing == MMC_TIMING_UHS_DDR50) || - (priv->timing == MMC_TIMING_UHS_SDR25) || - (priv->timing == MMC_TIMING_MMC_DDR52)) { - var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); - var |= OUTPUT_QSN_PHASE_SELECT; + (priv->timing == MMC_TIMING_UHS_SDR25)) { + var &= ~OUTPUT_QSN_PHASE_SELECT; sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST); } + if (priv->timing == MMC_TIMING_LEGACY) { + xenon_emmc_phy_slow_mode(host, priv->timing); + goto phy_init; + } + + /* + * If SDIO card, set SDIO Mode + * Otherwise, clear SDIO Mode + */ + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + if (IS_SD(host->mmc)) + var |= EMMC_PHY_SDIO_MODE; + else + var &= ~EMMC_PHY_SDIO_MODE; + sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST); + + /* + * Set preferred ZNR and ZPR value + * The ZNR and ZPR value vary between different boards. + * Define them both in sdhci-xenon-emmc-phy.h. + */ + var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL2); + var &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK); + var |= ((0xf << ZNR_SHIFT) | 0xf); + sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL2); /* * When setting EMMC_PHY_FUNC_CONTROL register, @@ -308,11 +568,21 @@ static void xenon_mmc_phy_set(struct sdhci_host *host) sdhci_writew(host, var, SDHCI_CLOCK_CONTROL); var = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL); - if (host->mmc->ddr_mode) { - var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE; - } else { + switch (priv->timing) { + case MMC_TIMING_MMC_HS400: + var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | + CMD_DDR_MODE; + var &= ~DQ_ASYNC_MODE; + break; + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | + CMD_DDR_MODE | DQ_ASYNC_MODE; + break; + default: var &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE); + var |= DQ_ASYNC_MODE; } sdhci_writel(host, var, EMMC_PHY_FUNC_CONTROL); @@ -321,7 +591,26 @@ static void xenon_mmc_phy_set(struct sdhci_host *host) var |= SDHCI_CLOCK_CARD_EN; sdhci_writew(host, var, SDHCI_CLOCK_CONTROL); + udelay(1000); + + /* Quirk, value suggested by hardware team */ + if (priv->timing == MMC_TIMING_MMC_HS400) + /* Hardware team recommend a value for HS400 */ + sdhci_writel(host, XENON_EMMC_PHY_LOGIC_TIMING_ADJUST, + XENON_LOGIC_TIMING_VALUE); + else + xenon_emmc_phy_disable_data_strobe(host); + +phy_init: + + xenon_set_uhs_signaling(host, priv->timing); xenon_mmc_phy_init(host); + + if ((priv->timing == MMC_TIMING_MMC_HS400) || + (priv->timing == MMC_TIMING_MMC_HS200)) { + if (xenon_emmc_phy_config_tuning(host) != 0) + printf("Error, failed to tune MMC PHY\n"); + } } /* Enable/Disable the Auto Clock Gating function of this slot */ @@ -338,8 +627,6 @@ static void xenon_mmc_set_acg(struct sdhci_host *host, bool enable) sdhci_writel(host, var, SDHC_SYS_OP_CTRL); } -#define SLOT_MASK(slot) BIT(slot) - /* Enable specific slot */ static void xenon_mmc_enable_slot(struct sdhci_host *host, u8 slot) { @@ -391,6 +678,7 @@ static int xenon_sdhci_set_ios_post(struct sdhci_host *host) struct xenon_sdhci_priv *priv = host->mmc->priv; uint speed = host->mmc->tran_speed; int pwr_18v = 0; + u32 reg; /* * Signal Voltage Switching is only applicable for Host Controllers @@ -423,12 +711,22 @@ static int xenon_sdhci_set_ios_post(struct sdhci_host *host) /* eMMC */ if (host->mmc->ddr_mode) priv->timing = MMC_TIMING_MMC_DDR52; + else if (speed == 200000000) + priv->timing = MMC_TIMING_MMC_HS200; else if (speed <= 26000000) priv->timing = MMC_TIMING_LEGACY; else priv->timing = MMC_TIMING_MMC_HS; } + if ((priv->timing == MMC_TIMING_MMC_HS400) || + (priv->timing == MMC_TIMING_MMC_HS200) || + (priv->timing == MMC_TIMING_MMC_HS)) { + reg = sdhci_readw(host, SDHCI_HOST_CONTROL2); + reg &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; + sdhci_writew(host, reg, SDHCI_HOST_CONTROL2); + } + /* Re-init the PHY */ xenon_mmc_phy_set(host); @@ -447,6 +745,7 @@ static int xenon_sdhci_probe(struct udevice *dev) struct xenon_sdhci_priv *priv = dev_get_priv(dev); struct sdhci_host *host = dev_get_priv(dev); int ret; + int len; host->mmc = &plat->mmc; host->mmc->priv = host; @@ -500,6 +799,18 @@ static int xenon_sdhci_probe(struct udevice *dev) return -EINVAL; } + /* Support for High Speed modes */ + if (fdt_getprop(gd->fdt_blob, + dev_of_offset(dev), "mmc-hs400-1_8v", &len) != NULL) { + host->host_caps |= (MMC_MODE_HS400 | MMC_MODE_HS200); + sdhci_writeb(host, SDHCI_POWER_180 | + SDHCI_POWER_ON, SDHCI_POWER_CONTROL); + } + if (fdt_getprop(gd->fdt_blob, + dev_of_offset(dev), "mmc-hs200-1_8v", &len) != NULL) { + host->host_caps |= MMC_MODE_HS200; + } + host->ops = &xenon_sdhci_ops; host->max_clk = XENON_MMC_MAX_CLK;