All of 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.