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 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.