U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Raymond Mao <raymondmaoca@gmail.com>
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 <guodong@riscstar.com>
Subject: [PATCH 2/8] mmc: k1: add sdhci platform driver
Date: Fri, 12 Jun 2026 16:18:55 -0400	[thread overview]
Message-ID: <20260612201901.73657-3-raymondmaoca@gmail.com> (raw)
In-Reply-To: <20260612201901.73657-1-raymondmaoca@gmail.com>

From: Guodong Xu <guodong@riscstar.com>

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 <guodong@riscstar.com>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 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 <clk.h>
+#include <dm.h>
+#include <dm/pinctrl.h>
+#include <fdtdec.h>
+#include <linux/libfdt.h>
+#include <linux/delay.h>
+#include <log.h>
+#include <malloc.h>
+#include <sdhci.h>
+#include <reset-uclass.h>
+#include <power/regulator.h>
+#include <mapmem.h>
+
+/* 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


  parent reply	other threads:[~2026-06-12 20:19 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-12 20:18 [PATCH 0/8] Add SD card and eMMC support for SpacemiT K1 Raymond Mao
2026-06-12 20:18 ` [PATCH 1/8] spacemit: k1: select boot device via config registers Raymond Mao
2026-06-13  3:50   ` Yao Zi
2026-06-12 20:18 ` Raymond Mao [this message]
2026-06-13  6:43   ` [PATCH 2/8] mmc: k1: add sdhci platform driver Yao Zi
2026-06-12 20:18 ` [PATCH 3/8] dts: k1: add SD card support in u-boot overlay Raymond Mao
2026-06-12 20:18 ` [PATCH 4/8] configs: k1: enable SD and eMMC support Raymond Mao
2026-06-12 20:18 ` [PATCH 5/8] doc: spacemit: flash on K1 SoC based boards Raymond Mao
2026-06-12 20:18 ` [PATCH 6/8] cmd: meminfo: widen memory map addresses to phys_addr_t Raymond Mao
2026-06-12 20:19 ` [PATCH 7/8] cmd: meminfo: fix the lmb info for large DRAM Raymond Mao
2026-06-12 20:19 ` [PATCH 8/8] cmd: tlv_eeprom: fix accessing invalid parameter Raymond Mao

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260612201901.73657-3-raymondmaoca@gmail.com \
    --to=raymondmaoca@gmail.com \
    --cc=dlan@gentoo.org \
    --cc=guodong@riscstar.com \
    --cc=hs@nabladev.com \
    --cc=jh80.chung@samsung.com \
    --cc=junhui.liu@pigmoral.tech \
    --cc=lukma@denx.de \
    --cc=neil.armstrong@linaro.org \
    --cc=peng.fan@nxp.com \
    --cc=quentin.schulz@cherry.de \
    --cc=randolph@andestech.com \
    --cc=raymond.mao@riscstar.com \
    --cc=rick@andestech.com \
    --cc=samuel@sholland.org \
    --cc=trini@konsulko.com \
    --cc=u-boot-spacemit@groups.io \
    --cc=u-boot@lists.denx.de \
    --cc=uboot@riscstar.com \
    --cc=xypron.glpk@gmx.de \
    --cc=ycliang@andestech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox