From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 1FAD3CD98CF for ; Fri, 12 Jun 2026 20:19:33 +0000 (UTC) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id A572A848B0; Fri, 12 Jun 2026 22:19:22 +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="WTxawFXX"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 8F6768063E; Fri, 12 Jun 2026 22:19:21 +0200 (CEST) Received: from mail-qt1-x82b.google.com (mail-qt1-x82b.google.com [IPv6:2607:f8b0:4864:20::82b]) (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 A13698063E for ; Fri, 12 Jun 2026 22:19:18 +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=raymondmaoca@gmail.com Received: by mail-qt1-x82b.google.com with SMTP id d75a77b69052e-5175cb5946dso9928821cf.0 for ; Fri, 12 Jun 2026 13:19:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781295557; x=1781900357; darn=lists.denx.de; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jDstgKX3TlEOfinfzCnSBHb/r5dtsxF1LI7Z5Nbz57c=; b=WTxawFXXh1Qm2fk0V2bH02Iq4+15aQt/bsMvrdKpg3sY35ySAW8uexjO1wx3JSd6Oy 7AZGjYyQr12uYgSls0b2lL25Rt083N8UGpflrk4Mg1zT1NSkNcgb2LUcc03JwgxDb45G 5KrrOfTA04Fj9zzatiyw9qr+4yjQTzQNwi9UVsSvf1VxNvGJE3gV08BMQCcCDYoPYI3M lSisijsyisTA3rRM+gpU5Sc5/0SjnZj0XkAO9/lpVALxVQElQ7x7RlCznyQXF5NWsqtL JNCaUkEAdGJD8bjxuqQTMCVORZ5v5EQNXwsX202fPPZu9pSX7xlqfpZX1cm6n6m7OVm3 PVpg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781295557; x=1781900357; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=jDstgKX3TlEOfinfzCnSBHb/r5dtsxF1LI7Z5Nbz57c=; b=dw19oCmWW8TkSUlTkY4dWbsQFLcXMmgvIjL35XtCiwRdiH6widvK38ivQbe4XbuvTm 0hB3P3qlMri92estbQufFWkBnWBKtIGlws34wOO1M294Z1B7K2AoU6s1wzDbeC0Y4FfQ V2JxrzrG4M8A2nxcuDXLnPa6hKsxpcOL2LRDBLOBMVNkWUZclxo+s8xT76BjbrFBtBwD t9Wr2f+iK7QvvJoAwIaInisJ2HITMGknAsOVAkuy/hIzsQvtx71/nEOQ4zUh/QVeU4Lg Xv6B+fXfZOwfEzkYOrTYYq8A02SMogOAyTinJBhaR2XuGOAirT7CObQBIgkrHHJeKayy oN0Q== X-Gm-Message-State: AOJu0Yw7MFOYhsjbmkC0xdkAy8kugqaZCT/d353xV9Ydm777RuuD2n9R qIo+TEV2gwstuDACLZRsiOxzjqS94dGTnPUXa3OjlZ0Vd+TrrKt/yW6UpD7R9Oqr X-Gm-Gg: Acq92OHZyDEejk2otneXI6aTZIhv4Vpkj9ax8npLcapsmdBq9nEo2j8eyikKzHQPKWg 5eBOxIbNVE+wSXxwRcfAMtYFsmlpE046UAysZKoe40Aom/uK9w53Zt4auBzgjAuEHfLg4Eqep54 pCPFO8ehjDi9+zOFab33zgXp5P45Crc3XqhPCxoG7qVVmFXjDiejM0kYfrDmswhX0IskzR3R5lN 5jsmRj7i2wnXB9PO88qVjzotGDqPe204ECqbCvPZy0rQ4IcjHfMcA1yb500F5NUkxlwQhbEark5 IvHCTqXdqN+w3KibNI8adbyCrkreWt53YgnbN0mo/YNrU69O/H/dTtozdwjGQeoXzKLxdwIVoqf fhwgUqrIcv9+WQLEyLcvPzxIbLplhbEGn9eSdSFMNAR/zBhsWFvwhVNT6d16BSI9uTRMsm4f1Ry r3GDeNb6Tk3sn0N9HrSdpRsGGiaNXe9Uq6FKC/ZVxi7GSWdKlT77oG6oaqNzYcT+qQLTbOu0Iq3 D593LSy X-Received: by 2002:a05:622a:1181:b0:517:6d82:9aec with SMTP id d75a77b69052e-517fdf207c4mr70400661cf.0.1781295557256; Fri, 12 Jun 2026 13:19:17 -0700 (PDT) Received: from ubuntu.localdomain (23-91-246-209.cpe.distributel.net. [23.91.246.209]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-517fb7a3c4asm31304551cf.14.2026.06.12.13.19.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Jun 2026 13:19:16 -0700 (PDT) From: Raymond Mao To: u-boot@lists.denx.de Cc: uboot@riscstar.com, u-boot-spacemit@groups.io, raymond.mao@riscstar.com, rick@andestech.com, ycliang@andestech.com, trini@konsulko.com, lukma@denx.de, hs@nabladev.com, jh80.chung@samsung.com, peng.fan@nxp.com, xypron.glpk@gmx.de, randolph@andestech.com, dlan@gentoo.org, junhui.liu@pigmoral.tech, neil.armstrong@linaro.org, quentin.schulz@cherry.de, samuel@sholland.org, raymondmaoca@gmail.com, Guodong Xu Subject: [PATCH 2/8] mmc: k1: add sdhci platform driver Date: Fri, 12 Jun 2026 16:18:55 -0400 Message-Id: <20260612201901.73657-3-raymondmaoca@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260612201901.73657-1-raymondmaoca@gmail.com> References: <20260612201901.73657-1-raymondmaoca@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.39 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.103.8 at phobos.denx.de X-Virus-Status: Clean From: Guodong Xu Add SDHCI platform driver support for SpacemiT K1 SoC. This driver implements the necessary platform-specific operations for the SDHCI controller, enabling MMC/SD card functionality on K1-based platforms. Signed-off-by: Guodong Xu Signed-off-by: Raymond Mao --- drivers/mmc/Kconfig | 7 + drivers/mmc/Makefile | 1 + drivers/mmc/spacemit_sdhci.c | 934 +++++++++++++++++++++++++++++++++++ 3 files changed, 942 insertions(+) create mode 100644 drivers/mmc/spacemit_sdhci.c diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index 24bd16ad5f3..7914305aae1 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -722,6 +722,13 @@ config MMC_SDHCI_SNPS If unsure, say N. +config MMC_SDHCI_SPACEMIT + bool "Spacemit SDHCI controller" + depends on MMC_SDHCI + help + Support for Secure Digital Host Controller Interface (SDHCI) on + Spacemit K1 SoC. + config MMC_SDHCI_STI bool "SDHCI support for STMicroelectronics SoC" depends on MMC_SDHCI && OF_CONTROL && ARCH_STI diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index a23336d7d8d..aa05cec23be 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_MMC_SDHCI_ROCKCHIP) += rockchip_sdhci.o obj-$(CONFIG_MMC_SDHCI_ADI) += adi_sdhci.o obj-$(CONFIG_MMC_SDHCI_S5P) += s5p_sdhci.o obj-$(CONFIG_MMC_SDHCI_SNPS) += snps_sdhci.o +obj-$(CONFIG_MMC_SDHCI_SPACEMIT) += spacemit_sdhci.o obj-$(CONFIG_MMC_SDHCI_STI) += sti_sdhci.o obj-$(CONFIG_MMC_SDHCI_TANGIER) += tangier_sdhci.o obj-$(CONFIG_MMC_SDHCI_TEGRA) += tegra_mmc.o diff --git a/drivers/mmc/spacemit_sdhci.c b/drivers/mmc/spacemit_sdhci.c new file mode 100644 index 00000000000..392ca389fa9 --- /dev/null +++ b/drivers/mmc/spacemit_sdhci.c @@ -0,0 +1,934 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Spacemit K1x Mobile Storage Host Controller + * + * Copyright (C) 2023 Spacemit Inc. + * Copyright (C) 2026 RISCstar Ltd. + */ + +#define LOG_CATEGORY UCLASS_MMC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SDH register definitions */ +#define SDHC_OP_EXT_REG 0x108 +#define OVRRD_CLK_OEN 0x0800 +#define FORCE_CLK_ON 0x1000 + +#define SDHC_LEGACY_CTRL_REG 0x10C +#define GEN_PAD_CLK_ON 0x0040 + +#define SDHC_MMC_CTRL_REG 0x114 +#define MISC_INT_EN 0x0002 +#define MISC_INT 0x0004 +#define ENHANCE_STROBE_EN 0x0100 +#define MMC_HS400 0x0200 +#define MMC_HS200 0x0400 +#define MMC_CARD_MODE 0x1000 + +#define SDHC_RX_CFG_REG 0x118 +#define RX_SDCLK_SEL0_MASK 0x03 +#define RX_SDCLK_SEL0_SHIFT 0x00 +#define RX_SDCLK_SEL0 0x02 +#define RX_SDCLK_SEL1_MASK 0x03 +#define RX_SDCLK_SEL1_SHIFT 0x02 +#define RX_SDCLK_SEL1 0x01 + +#define SDHC_TX_CFG_REG 0x11C +#define TX_INT_CLK_SEL 0x40000000 +#define TX_MUX_SEL 0x80000000 + +#define SDHC_DLINE_CTRL_REG 0x130 +#define DLINE_PU 0x01 +#define RX_DLINE_CODE_MASK 0xFF +#define RX_DLINE_CODE_SHIFT 0x10 +#define TX_DLINE_CODE_MASK 0xFF +#define TX_DLINE_CODE_SHIFT 0x18 + +#define SDHC_DLINE_CFG_REG 0x134 +#define RX_DLINE_REG_MASK 0xFF +#define RX_DLINE_REG_SHIFT 0x00 +#define RX_DLINE_GAIN_MASK 0x1 +#define RX_DLINE_GAIN_SHIFT 0x8 +#define RX_DLINE_GAIN 0x1 +#define TX_DLINE_REG_MASK 0xFF +#define TX_DLINE_REG_SHIFT 0x10 + +#define SDHC_PHY_CTRL_REG 0x160 +#define PHY_FUNC_EN 0x0001 +#define PHY_PLL_LOCK 0x0002 +#define HOST_LEGACY_MODE 0x80000000 + +#define SDHC_PHY_FUNC_REG 0x164 +#define PHY_TEST_EN 0x0080 +#define HS200_USE_RFIFO 0x8000 + +#define SDHC_PHY_DLLCFG 0x168 +#define DLL_PREDLY_NUM 0x04 +#define DLL_FULLDLY_RANGE 0x10 +#define DLL_VREG_CTRL 0x40 +#define DLL_ENABLE 0x80000000 +#define DLL_REFRESH_SWEN_SHIFT 0x1C +#define DLL_REFRESH_SW_SHIFT 0x1D + +#define SDHC_PHY_DLLCFG1 0x16C +#define DLL_REG2_CTRL 0x0C +#define DLL_REG3_CTRL_MASK 0xFF +#define DLL_REG3_CTRL_SHIFT 0x10 +#define DLL_REG2_CTRL_MASK 0xFF +#define DLL_REG2_CTRL_SHIFT 0x08 +#define DLL_REG1_CTRL 0x92 +#define DLL_REG1_CTRL_MASK 0xFF +#define DLL_REG1_CTRL_SHIFT 0x00 + +#define SDHC_PHY_DLLSTS 0x170 +#define DLL_LOCK_STATE 0x01 + +#define SDHC_PHY_DLLSTS1 0x174 +#define DLL_MASTER_DELAY_MASK 0xFF +#define DLL_MASTER_DELAY_SHIFT 0x10 + +#define SDHC_PHY_PADCFG_REG 0x178 +#define RX_BIAS_CTRL_SHIFT 0x5 +#define PHY_DRIVE_SEL_SHIFT 0x0 +#define PHY_DRIVE_SEL_MASK 0x7 +#define PHY_DRIVE_SEL_DEFAULT 0x4 + +#define MMC1_IO_V18EN 0x04 +#define AKEY_ASFAR 0xBABA +#define AKEY_ASSAR 0xEB10 + +#define SDHC_RX_TUNE_DELAY_MIN 0x0 +#define SDHC_RX_TUNE_DELAY_MAX 0xFF +#define SDHC_RX_TUNE_DELAY_STEP 0x1 + +#define CANDIDATE_WIN_NUM 3 +#define SELECT_DELAY_NUM 9 +#define WINDOW_1ST 0 +#define WINDOW_2ND 1 +#define WINDOW_3RD 2 + +#define RX_TUNING_WINDOW_THRESHOLD 80 +#define RX_TUNING_DLINE_REG 0x09 +#define TX_TUNING_DLINE_REG 0x00 +#define TX_TUNING_DELAYCODE 127 + +enum window_type { + LEFT_WINDOW = 0, + MIDDLE_WINDOW = 1, + RIGHT_WINDOW = 2, +}; + +struct tuning_window { + u8 type; + u8 min_delay; + u8 max_delay; +}; + +struct rx_tuning { + u8 rx_dline_reg; + u8 select_delay_num; + /* 0: biggest window, 1: bigger, 2: small */ + struct tuning_window windows[CANDIDATE_WIN_NUM]; + u8 select_delay[SELECT_DELAY_NUM]; + + u8 window_limit; +}; + +struct spacemit_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; + struct reset_ctl_bulk resets; + struct clk_bulk clks; + + u32 aib_mmc1_io_reg; + u32 apbc_asfar_reg; + u32 apbc_assar_reg; + + u8 tx_dline_reg; + u8 tx_delaycode; + struct rx_tuning rxtuning; +}; + +struct spacemit_sdhci_priv { + struct sdhci_host host; +}; + +static const u32 tuning_pattern4[16] = { + 0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe, + 0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777, + 0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff, + 0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7, +}; + +static const u32 tuning_pattern8[32] = { + 0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc, + 0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff, + 0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff, + 0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb, + 0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc, + 0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff, + 0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb, + 0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77, +}; + +/* + * Reference: PMU_SDH0_CLK_RES_CTRL (0x054), SDH0_CLK_SEL=0x0, + * SDH0_CLK_DIV=0x1. The default clock source is 204.8 MHz + * (pll1_d6_409p6Mhz / 2). + * + * During start-up, use a 200 kHz frequency. + */ +#define SDHC_DEFAULT_MAX_CLOCK (204800000) +#define SDHC_MIN_CLOCK (200 * 1000) + +static void spacemit_sdhci_phy_init(struct udevice *dev, + struct sdhci_host *host) +{ + u32 reg = 0; + + if (dev_read_bool(dev, "no-sd") && dev_read_bool(dev, "no-sdio")) { + /* MMC card mode */ + reg = sdhci_readl(host, SDHC_MMC_CTRL_REG); + reg |= MMC_CARD_MODE; + sdhci_writel(host, reg, SDHC_MMC_CTRL_REG); + + /* Use PHY functional mode */ + reg = sdhci_readl(host, SDHC_PHY_CTRL_REG); + reg |= (PHY_FUNC_EN | PHY_PLL_LOCK); + sdhci_writel(host, reg, SDHC_PHY_CTRL_REG); + + reg = sdhci_readl(host, SDHC_PHY_PADCFG_REG); + reg |= (1 << RX_BIAS_CTRL_SHIFT); + sdhci_writel(host, reg, SDHC_PHY_PADCFG_REG); + } else { + reg = sdhci_readl(host, SDHC_TX_CFG_REG); + reg |= TX_INT_CLK_SEL; + sdhci_writel(host, reg, SDHC_TX_CFG_REG); + } + + reg = sdhci_readl(host, SDHC_MMC_CTRL_REG); + reg &= ~ENHANCE_STROBE_EN; + sdhci_writel(host, reg, SDHC_MMC_CTRL_REG); +} + +static int spacemit_sdhci_set_vqmmc_voltage(struct mmc *mmc, int voltage) +{ +#if CONFIG_IS_ENABLED(DM_REGULATOR) + int ret; + + if (!mmc->vqmmc_supply) + return 0; + + ret = regulator_set_value(mmc->vqmmc_supply, voltage); + if (ret) { + log_err("failed to set vqmmc voltage to %d.%dV\n", + voltage / 1000000, (voltage / 100000) % 10); + return ret; + } + ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true); + if (ret) { + log_err("failed to enable vqmmc supply\n"); + return ret; + } +#endif + return 0; +} + +static void spacemit_sdhci_set_voltage(struct sdhci_host *host) +{ + if (IS_ENABLED(CONFIG_MMC_IO_VOLTAGE)) { + struct mmc *mmc = (struct mmc *)host->mmc; + u32 ctrl; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + switch (mmc->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + case MMC_SIGNAL_VOLTAGE_180: { + bool to_180 = mmc->signal_voltage == + MMC_SIGNAL_VOLTAGE_180; + bool ok; + int voltage_mv = to_180 ? 1800000 : 3300000; + + if (spacemit_sdhci_set_vqmmc_voltage(mmc, voltage_mv)) + return; + if (!IS_SD(mmc)) + return; + if (to_180) + ctrl |= SDHCI_CTRL_VDD_180; + else + ctrl &= ~SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + mdelay(5); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ok = !!(ctrl & SDHCI_CTRL_VDD_180) == to_180; + if (ok) + return; + + log_err("%d.%dV regulator output not stable\n", + voltage_mv / 1000000, + (voltage_mv / 100000) % 10); + break; + } + default: + /* No signal voltage switch required */ + return; + } + } +} + +static void spacemit_sdhci_set_aib_mmc1_io(struct sdhci_host *host, int voltage) +{ + struct mmc *mmc = host->mmc; + struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev); + void __iomem *aib_mmc1_io, *apbc_asfar, *apbc_assar; + u32 reg; + + if (!plat->aib_mmc1_io_reg || !plat->apbc_asfar_reg || + !plat->apbc_assar_reg) + return; + + aib_mmc1_io = map_sysmem((uintptr_t)plat->aib_mmc1_io_reg, + sizeof(u32)); + apbc_asfar = map_sysmem((uintptr_t)plat->apbc_asfar_reg, + sizeof(u32)); + apbc_assar = map_sysmem((uintptr_t)plat->apbc_assar_reg, + sizeof(u32)); + + writel(AKEY_ASFAR, apbc_asfar); + writel(AKEY_ASSAR, apbc_assar); + reg = readl(aib_mmc1_io); + + switch (voltage) { + case MMC_SIGNAL_VOLTAGE_180: + reg |= MMC1_IO_V18EN; + break; + default: + reg &= ~MMC1_IO_V18EN; + break; + } + writel(AKEY_ASFAR, apbc_asfar); + writel(AKEY_ASSAR, apbc_assar); + writel(reg, aib_mmc1_io); +} + +static void spacemit_sdhci_set_clk_gate(struct sdhci_host *host, int auto_gate) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_OP_EXT_REG); + if (auto_gate) + reg &= ~(OVRRD_CLK_OEN | FORCE_CLK_ON); + else + reg |= (OVRRD_CLK_OEN | FORCE_CLK_ON); + sdhci_writel(host, reg, SDHC_OP_EXT_REG); +} + +static bool spacemit_sdhci_is_voltage_switch_cmd(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + u32 cmd; + + if (!IS_SD(mmc)) + return false; + + cmd = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); + return cmd == SD_CMD_SWITCH_UHS18V && + mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180; +} + +static int spacemit_sdhci_wait_dat0(struct udevice *dev, int state, + int timeout_us) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct sdhci_host *host = mmc->priv; + unsigned long timeout = timer_get_us() + timeout_us; + u32 tmp; + + /* + * readx_poll_timeout is unsuitable because sdhci_readl accepts + * two arguments + */ + do { + tmp = sdhci_readl(host, SDHCI_PRESENT_STATE); + if (!!(tmp & SDHCI_DATA_0_LVL_MASK) == !!state) { + if (spacemit_sdhci_is_voltage_switch_cmd(host)) + spacemit_sdhci_set_clk_gate(host, 1); + return 0; + } + } while (!timeout_us || !time_after(timer_get_us(), timeout)); + + return -ETIMEDOUT; +} + +static void spacemit_sdhci_set_control_reg(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + u32 reg; + + spacemit_sdhci_set_voltage(host); + spacemit_sdhci_set_aib_mmc1_io(host, mmc->signal_voltage); + + if (spacemit_sdhci_is_voltage_switch_cmd(host)) + spacemit_sdhci_set_clk_gate(host, 0); + + /* + * Set TX_INT_CLK_SEL to guarantee hold time at default speed, + * HS, SDR12/SDR25/SDR50 modes. See SDHC_TX_CFG_REG (0x11c). + */ + reg = sdhci_readl(host, SDHC_TX_CFG_REG); + if (mmc->selected_mode == MMC_LEGACY || + mmc->selected_mode == MMC_HS || + mmc->selected_mode == SD_HS || + mmc->selected_mode == UHS_SDR12 || + mmc->selected_mode == UHS_SDR25 || + mmc->selected_mode == UHS_SDR50) { + reg |= TX_INT_CLK_SEL; + } else { + reg &= ~TX_INT_CLK_SEL; + } + sdhci_writel(host, reg, SDHC_TX_CFG_REG); + + /* Set pinctrl state */ + if (IS_ENABLED(CONFIG_PINCTRL)) { + if (mmc->clock >= 200000000) + pinctrl_select_state(mmc->dev, "fast"); + else + pinctrl_select_state(mmc->dev, "default"); + } + + if (mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == MMC_HS_400 || + mmc->selected_mode == MMC_HS_400_ES) { + reg = sdhci_readw(host, SDHC_MMC_CTRL_REG); + if (mmc->selected_mode == MMC_HS_200) + reg |= MMC_HS200; + else + reg |= MMC_HS400; + sdhci_writew(host, reg, SDHC_MMC_CTRL_REG); + } else { + reg = sdhci_readw(host, SDHC_MMC_CTRL_REG); + reg &= ~(MMC_HS200 | MMC_HS400 | ENHANCE_STROBE_EN); + sdhci_writew(host, reg, SDHC_MMC_CTRL_REG); + } + + sdhci_set_uhs_timing(host); +} + +static void spacemit_sdhci_rx_tuning_prepare(struct sdhci_host *host, + u8 dline_reg) +{ + struct mmc *mmc = host->mmc; + u32 reg; + + reg = sdhci_readl(host, SDHC_DLINE_CFG_REG); + reg &= ~(RX_DLINE_REG_MASK << RX_DLINE_REG_SHIFT); + reg |= dline_reg << RX_DLINE_REG_SHIFT; + + reg &= ~(RX_DLINE_GAIN_MASK << RX_DLINE_GAIN_SHIFT); + if (mmc->selected_mode == UHS_SDR50 && (reg & 0x40)) + reg |= RX_DLINE_GAIN << RX_DLINE_GAIN_SHIFT; + + sdhci_writel(host, reg, SDHC_DLINE_CFG_REG); + + reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG); + reg |= DLINE_PU; + sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG); + udelay(5); + + reg = sdhci_readl(host, SDHC_RX_CFG_REG); + reg &= ~(RX_SDCLK_SEL1_MASK << RX_SDCLK_SEL1_SHIFT); + reg |= RX_SDCLK_SEL1 << RX_SDCLK_SEL1_SHIFT; + sdhci_writel(host, reg, SDHC_RX_CFG_REG); + + if (mmc->selected_mode == MMC_HS_200) { + reg = sdhci_readl(host, SDHC_PHY_FUNC_REG); + reg |= HS200_USE_RFIFO; + sdhci_writel(host, reg, SDHC_PHY_FUNC_REG); + } +} + +static void spacemit_sdhci_rx_set_delaycode(struct sdhci_host *host, u32 delay) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG); + reg &= ~(RX_DLINE_CODE_MASK << RX_DLINE_CODE_SHIFT); + reg |= (delay & RX_DLINE_CODE_MASK) << RX_DLINE_CODE_SHIFT; + sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG); +} + +static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host) +{ + u32 reg; + + /* Set TX_MUX_SEL */ + reg = sdhci_readl(host, SDHC_TX_CFG_REG); + reg |= TX_MUX_SEL; + sdhci_writel(host, reg, SDHC_TX_CFG_REG); + + reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG); + reg |= DLINE_PU; + sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG); + udelay(5); +} + +static void spacemit_sdhci_tx_set_dlinereg(struct sdhci_host *host, + u8 dline_reg) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_DLINE_CFG_REG); + reg &= ~(TX_DLINE_REG_MASK << TX_DLINE_REG_SHIFT); + reg |= dline_reg << TX_DLINE_REG_SHIFT; + sdhci_writel(host, reg, SDHC_DLINE_CFG_REG); +} + +static void spacemit_sdhci_tx_set_delaycode(struct sdhci_host *host, u32 delay) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG); + reg &= ~(TX_DLINE_CODE_MASK << TX_DLINE_CODE_SHIFT); + reg |= (delay & TX_DLINE_CODE_MASK) << TX_DLINE_CODE_SHIFT; + sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG); +} + +static void spacemit_sdhci_clear_set_irqs(struct sdhci_host *host, + u32 clr, u32 set) +{ + u32 ier; + + ier = sdhci_readl(host, SDHCI_INT_ENABLE); + ier &= ~clr; + ier |= set; + sdhci_writel(host, ier, SDHCI_INT_ENABLE); + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE); +} + +static int spacemit_sdhci_tuning_pattern_check(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + u32 read_pattern; + unsigned int i; + u32 *tuning_pattern; + int pattern_len; + int err = 0; + + if (mmc->bus_width == 8) { + tuning_pattern = (u32 *)tuning_pattern8; + pattern_len = ARRAY_SIZE(tuning_pattern8); + } else { + tuning_pattern = (u32 *)tuning_pattern4; + pattern_len = ARRAY_SIZE(tuning_pattern4); + } + + for (i = 0; i < pattern_len; i++) { + read_pattern = sdhci_readl(host, SDHCI_BUFFER); + if (read_pattern != tuning_pattern[i]) + err++; + } + + return err; +} + +static int spacemit_sdhci_send_tuning(struct sdhci_host *host, u32 opcode, + int *cmd_error) +{ + struct mmc *mmc = host->mmc; + struct mmc_cmd cmd; + int size, blk_size, err; + + size = sizeof(tuning_pattern4); + cmd.cmdidx = opcode; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + blk_size = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 64); + sdhci_writew(host, blk_size, SDHCI_BLOCK_SIZE); + sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + log_warning("%s: tuning send cmd err: %d\n", host->name, err); + return err; + } + return 0; +} + +static int spacemit_sdhci_send_tuning_cmd(struct sdhci_host *host, u32 opcode) +{ + int err = 0; + + err = spacemit_sdhci_send_tuning(host, opcode, NULL); + if (err) { + log_warning("%s: send tuning err:%d\n", host->name, err); + return err; + } + + err = spacemit_sdhci_tuning_pattern_check(host); + return err; +} + +static void spacemit_sdhci_clear_tuned_clk(struct sdhci_host *host) +{ + u16 ctrl; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING); + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); +} + +static int spacemit_sdhci_rx_select_window(struct sdhci_host *host, u32 opcode) +{ + int min, max; + int i, j, len; + int err = 0; + u32 ier; + struct tuning_window tmp; + struct mmc *mmc = host->mmc; + struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev); + struct rx_tuning *rxtuning = &plat->rxtuning; + + /* Change to PIO mode during tuning */ + ier = sdhci_readl(host, SDHCI_INT_ENABLE); + spacemit_sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL); + + min = SDHC_RX_TUNE_DELAY_MIN; + do { + /* Find the minimum delay that can pass tuning */ + while (min < SDHC_RX_TUNE_DELAY_MAX) { + spacemit_sdhci_rx_set_delaycode(host, min); + err = spacemit_sdhci_send_tuning_cmd(host, opcode); + if (!err) + break; + spacemit_sdhci_clear_tuned_clk(host); + min += SDHC_RX_TUNE_DELAY_STEP; + } + + /* Find the maximum delay that cannot pass tuning */ + max = min + SDHC_RX_TUNE_DELAY_STEP; + while (max < SDHC_RX_TUNE_DELAY_MAX) { + spacemit_sdhci_rx_set_delaycode(host, max); + err = spacemit_sdhci_send_tuning_cmd(host, opcode); + if (err) { + spacemit_sdhci_clear_tuned_clk(host); + break; + } + max += SDHC_RX_TUNE_DELAY_STEP; + } + + log_info("%s: pass window [%d %d)\n", host->name, min, max); + /* Store the top 3 windows */ + if ((max - min) >= rxtuning->window_limit) { + tmp.max_delay = max; + tmp.min_delay = min; + tmp.type = MIDDLE_WINDOW; + for (i = 0; i < CANDIDATE_WIN_NUM; i++) { + len = rxtuning->windows[i].max_delay - + rxtuning->windows[i].min_delay; + if ((tmp.max_delay - tmp.min_delay) > len) { + for (j = CANDIDATE_WIN_NUM - 1; + j > i; j--) + rxtuning->windows[j] = + rxtuning->windows[j - 1]; + rxtuning->windows[i] = tmp; + break; + } + } + } + min = max + SDHC_RX_TUNE_DELAY_STEP; + } while (min < SDHC_RX_TUNE_DELAY_MAX); + + spacemit_sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier); + return 0; +} + +static int spacemit_sdhci_rx_select_delay(struct sdhci_host *host) +{ + int i; + int win_len, min, max, mid; + u8 n; + struct tuning_window *window; + struct mmc *mmc = host->mmc; + struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev); + struct rx_tuning *tuning = &plat->rxtuning; + + for (i = 0; i < CANDIDATE_WIN_NUM; i++) { + window = &tuning->windows[i]; + min = window->min_delay; + max = window->max_delay; + mid = (min + max - 1) / 2; + win_len = max - min; + if (win_len < tuning->window_limit) + continue; + + n = tuning->select_delay_num; + if (window->type == LEFT_WINDOW) { + tuning->select_delay[n++] = min + win_len / 3; + tuning->select_delay[n++] = min + win_len / 2; + } else if (window->type == RIGHT_WINDOW) { + tuning->select_delay[n++] = max - win_len / 4; + tuning->select_delay[n++] = min - win_len / 3; + } else { + tuning->select_delay[n++] = mid; + tuning->select_delay[n++] = mid + win_len / 4; + tuning->select_delay[n++] = mid - win_len / 4; + } + tuning->select_delay_num = n; + } + + return tuning->select_delay_num; +} + +static int spacemit_sdhci_execute_tuning(struct mmc *mmc, u8 opcode) +{ + struct sdhci_host *host = mmc->priv; + struct spacemit_sdhci_plat *plat = dev_get_plat(mmc->dev); + struct rx_tuning *rxtuning = &plat->rxtuning; + int ret; + + /* + * Tuning is required for SDR50/SDR104 mode + */ + if (!IS_SD(host->mmc) || + !(mmc->selected_mode == UHS_SDR50 || + mmc->selected_mode == UHS_SDR104)) + return 0; + + /* TX tuning config */ + if (IS_SD(host->mmc)) { + spacemit_sdhci_tx_set_dlinereg(host, plat->tx_dline_reg); + spacemit_sdhci_tx_set_delaycode(host, plat->tx_delaycode); + log_info("%s: set tx_delaycode: %d\n", + host->name, plat->tx_delaycode); + spacemit_sdhci_tx_tuning_prepare(host); + } + + rxtuning->select_delay_num = 0; + memset(rxtuning->windows, 0, sizeof(rxtuning->windows)); + memset(rxtuning->select_delay, 0xFF, sizeof(rxtuning->select_delay)); + + spacemit_sdhci_rx_tuning_prepare(host, rxtuning->rx_dline_reg); + ret = spacemit_sdhci_rx_select_window(host, opcode); + if (ret) { + log_warning("%s: abort tuning, err:%d\n", host->name, ret); + return ret; + } + + if (!spacemit_sdhci_rx_select_delay(host)) { + log_warning("%s: fail to get delaycode\n", host->name); + return -EIO; + } + + spacemit_sdhci_rx_set_delaycode(host, rxtuning->select_delay[0]); + log_info("%s: tuning done, use the firstly delay_code:%d\n", + host->name, rxtuning->select_delay[0]); + return 0; +} + +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) +static int spacemit_sdhci_phy_dll_init(struct sdhci_host *host) +{ + u32 reg; + int i; + + /* Configure DLL_REG1 and DLL_REG2 */ + reg = sdhci_readl(host, SDHC_PHY_DLLCFG); + reg |= (DLL_PREDLY_NUM | DLL_FULLDLY_RANGE | DLL_VREG_CTRL); + sdhci_writel(host, reg, SDHC_PHY_DLLCFG); + + reg = sdhci_readl(host, SDHC_PHY_DLLCFG1); + reg |= (DLL_REG1_CTRL & DLL_REG1_CTRL_MASK); + sdhci_writel(host, reg, SDHC_PHY_DLLCFG1); + + /* Enable DLL */ + reg = sdhci_readl(host, SDHC_PHY_DLLCFG); + reg |= DLL_ENABLE; + sdhci_writel(host, reg, SDHC_PHY_DLLCFG); + + /* Wait for DLL lock */ + i = 0; + while (i++ < 100) { + if (sdhci_readl(host, SDHC_PHY_DLLSTS) & DLL_LOCK_STATE) + break; + udelay(10); + } + if (i == 100) { + log_err("%s: phy dll lock timeout\n", host->name); + return -ETIMEDOUT; + } + + return 0; +} + +static int spacemit_sdhci_hs400_enhanced_strobe(struct sdhci_host *host) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_MMC_CTRL_REG); + reg |= ENHANCE_STROBE_EN; + sdhci_writel(host, reg, SDHC_MMC_CTRL_REG); + + return spacemit_sdhci_phy_dll_init(host); +} +#endif + +const struct sdhci_ops spacemit_sdhci_ops = { + .set_control_reg = spacemit_sdhci_set_control_reg, + .platform_execute_tuning = spacemit_sdhci_execute_tuning, +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + .set_enhanced_strobe = spacemit_sdhci_hs400_enhanced_strobe, +#endif +}; + +static struct dm_mmc_ops spacemit_mmc_ops; + +static int spacemit_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct spacemit_sdhci_priv *priv = dev_get_priv(dev); + struct spacemit_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = &priv->host; + struct clk clk; + int ret = 0; + + host->mmc = &plat->mmc; + host->mmc->priv = host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + spacemit_mmc_ops = sdhci_ops; + spacemit_mmc_ops.wait_dat0 = spacemit_sdhci_wait_dat0; + + ret = clk_get_bulk(dev, &plat->clks); + if (ret) { + log_err("Can't get clk: %d\n", ret); + return ret; + } + + ret = clk_enable_bulk(&plat->clks); + if (ret) { + log_err("Failed to enable clk: %d\n", ret); + return ret; + } + + ret = reset_get_bulk(dev, &plat->resets); + if (ret) { + log_err("Can't get reset: %d\n", ret); + return ret; + } + + ret = reset_deassert_bulk(&plat->resets); + if (ret) { + log_err("Failed to reset: %d\n", ret); + return ret; + } + + ret = clk_get_by_index(dev, 1, &clk); + if (ret) { + log_err("Can't get io clk: %d\n", ret); + return ret; + } + + ret = clk_set_rate(&clk, plat->cfg.f_max); + if (ret) { + log_err("Failed to set io clk: %d\n", ret); + return ret; + } + + /* Set quirks */ + if (IS_ENABLED(CONFIG_SPL_BUILD)) + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD; + else + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | + SDHCI_QUIRK_32BIT_DMA_ADDR; + host->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz; + host->max_clk = plat->cfg.f_max; + + plat->cfg.f_min = SDHC_MIN_CLOCK; + host->ops = &spacemit_sdhci_ops; + + ret = sdhci_setup_cfg(&plat->cfg, host, plat->cfg.f_max, + SDHC_MIN_CLOCK); + if (ret) + return ret; + + ret = sdhci_probe(dev); + if (ret) + return ret; + + spacemit_sdhci_phy_init(dev, host); + return ret; +} + +static int spacemit_sdhci_of_to_plat(struct udevice *dev) +{ + struct spacemit_sdhci_plat *plat = dev_get_plat(dev); + struct spacemit_sdhci_priv *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + int ret = 0; + + host->name = dev->name; + host->ioaddr = (void *)dev_read_addr(dev); + + ret = mmc_of_parse(dev, &plat->cfg); + + return ret; +} + +static int spacemit_sdhci_bind(struct udevice *dev) +{ + struct spacemit_sdhci_plat *drv_data; + struct spacemit_sdhci_plat *plat = dev_get_plat(dev); + + drv_data = (struct spacemit_sdhci_plat *)dev_get_driver_data(dev); + memcpy(plat, drv_data, sizeof(struct spacemit_sdhci_plat)); + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct spacemit_sdhci_plat k1_data = { + .aib_mmc1_io_reg = 0xd401e81c, + .apbc_asfar_reg = 0xd4015050, + .apbc_assar_reg = 0xd4015054, + .tx_dline_reg = TX_TUNING_DLINE_REG, + .tx_delaycode = 0x5f, + .rxtuning = { + .rx_dline_reg = RX_TUNING_DLINE_REG, + .window_limit = 50, + }, +}; + +static const struct udevice_id spacemit_sdhci_ids[] = { + { + .compatible = "spacemit,k1-sdhci", + .data = (ulong)&k1_data, + }, { + } +}; + +U_BOOT_DRIVER(spacemit_sdhci_drv) = { + .name = "spacemit_sdhci", + .id = UCLASS_MMC, + .of_match = spacemit_sdhci_ids, + .of_to_plat = spacemit_sdhci_of_to_plat, + .ops = &spacemit_mmc_ops, + .bind = spacemit_sdhci_bind, + .probe = spacemit_sdhci_probe, + .priv_auto = sizeof(struct spacemit_sdhci_priv), + .plat_auto = sizeof(struct spacemit_sdhci_plat), +}; -- 2.25.1