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
next prev 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