U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Kathpalia, Tanmay" <tanmay.kathpalia@altera.com>
To: Eric Chung <eric.chung@riscstar.com>,
	u-boot-spacemit@groups.io, u-boot@lists.denx.de
Cc: Tom Rini <trini@konsulko.com>, Peng Fan <peng.fan@nxp.com>,
	Huan Zhou <pericycle.cc@gmail.com>,
	Raymond Mao <raymond.mao@riscstar.com>,
	Jaehoon Chung <jh80.chung@samsung.com>,
	Bhimeswararao Matsa <bhimeswararao.matsa@gmail.com>,
	Kaustabh Chakraborty <kauschluss@disroot.org>,
	Han Xu <han.xu@nxp.com>, Yanir Levin <yanir.levin@tandemg.com>,
	Christoph Stoidner <c.stoidner@phytec.de>,
	Balsundar Ponnusamy <balsundar.ponnusamy@altera.com>,
	Daniel Palmer <daniel@thingy.jp>, Anshul Dalal <anshuld@ti.com>,
	Bastien Curutchet <bastien.curutchet@bootlin.com>,
	Angelo Dureghello <angelo@kernel-space.org>,
	Johan Jonker <jbx6244@gmail.com>, Rick Chen <rick@andestech.com>,
	Leo <ycliang@andestech.com>,
	Sam Protsenko <semen.protsenko@linaro.org>,
	Guodong Xu <guodong.xu@riscstar.com>,
	Tim Ouyang <tim609@andestech.com>,
	Leo Liang <leo.liang@sifive.com>
Subject: Re: [PATCH v3 4/9] mmc: k1: add sdhci platform driver
Date: Sun, 5 Jul 2026 15:50:06 +0530	[thread overview]
Message-ID: <eee5cbaf-3641-4e9f-b50e-b5931478e933@altera.com> (raw)
In-Reply-To: <20260705-m4-v3-4-5a9bc7f25fc5@riscstar.com>


On 7/5/2026 11:37 AM, Eric Chung wrote:
> 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: Eric Chung <eric.chung@riscstar.com>
>
> ---
> v3:
> - Enable CMD23 in capability.
> v2:
> - Enable ADMA mode support.
> - Use CMD23 for multi-block read/write.
> - Move ASR/AIB register into pinctrl driver.
> - Correct pinctrl state from "fast" to "uhs".
> - Migrate tuning support from the spacemit linux driver.
> ---
>   drivers/mmc/Kconfig          |   7 +
>   drivers/mmc/Makefile         |   1 +
>   drivers/mmc/spacemit_sdhci.c | 680 +++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 688 insertions(+)
>
> diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
> index 131be3106a1..f05b376ab72 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..8622a3e21f1
> --- /dev/null
> +++ b/drivers/mmc/spacemit_sdhci.c
> @@ -0,0 +1,680 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Driver for Spacemit K1 Mobile Storage Host Controller
> + *
> + * Copyright (C) 2023-2026 Spacemit Inc.
> + * Copyright (C) 2026 RISCstar Ltd.
> + */
> +
> +#define LOG_CATEGORY UCLASS_MMC
> +
> +#include <clk.h>
> +#include <dm.h>
> +#include <fdtdec.h>
> +#include <log.h>
> +#include <malloc.h>
> +#include <sdhci.h>
> +#include <reset-uclass.h>
> +#include <mapmem.h>
> +#include <dm/pinctrl.h>
> +#include <linux/libfdt.h>
> +#include <linux/delay.h>
> +#include <power/regulator.h>
> +
> +#define FIELD_PREP(mask, val)		(((val) << __ffs(mask)) & (mask))

<linux/bitfield.h> is already used elsewhere include it instead.

> +
> +/* SDH register definitions */
> +#define SPACEMIT_SDHC_OP_EXT_REG	0x108
> +#define  SDHC_OVRRD_CLK_OEN		BIT(11)
> +#define  SDHC_FORCE_CLK_ON		BIT(12)
> +
> +#define SPACEMIT_SDHC_LEGACY_CTRL_REG	0x10C
> +#define  SDHC_GEN_PAD_CLK_ON		BIT(6)
> +
> +#define SPACEMIT_SDHC_MMC_CTRL_REG	0x114
> +#define  SDHC_MISC_INT_EN		BIT(1)
> +#define  SDHC_MISC_INT			BIT(2)
> +#define  SDHC_ENHANCE_STROBE_EN		BIT(8)
> +#define  SDHC_MMC_HS400			BIT(9)
> +#define  SDHC_MMC_HS200			BIT(10)
> +#define  SDHC_MMC_CARD_MODE		BIT(12)
> +
> +#define SPACEMIT_SDHC_TX_CFG_REG	0x11C
> +#define  SDHC_TX_INT_CLK_SEL		BIT(30)
> +#define  SDHC_TX_MUX_SEL		BIT(31)
> +
> +#define SPACEMIT_SDHC_PHY_CTRL_REG	0x160
> +#define  SDHC_PHY_FUNC_EN		BIT(0)
> +#define  SDHC_PHY_PLL_LOCK		BIT(1)
> +#define  SDHC_HOST_LEGACY_MODE		BIT(31)
> +
> +#define SPACEMIT_SDHC_PHY_FUNC_REG	0x164
> +#define  SDHC_PHY_TEST_EN		BIT(7)
> +#define  SDHC_HS200_USE_RFIFO		BIT(15)
> +
> +#define SPACEMIT_SDHC_PHY_DLLCFG	0x168
> +#define  SDHC_DLL_PREDLY_NUM		GENMASK(3, 2)
> +#define  SDHC_DLL_FULLDLY_RANGE		GENMASK(5, 4)
> +#define  SDHC_DLL_VREG_CTRL		GENMASK(7, 6)
> +#define  SDHC_DLL_ENABLE		BIT(31)
> +
> +#define SPACEMIT_SDHC_PHY_DLLCFG1	0x16C
> +#define  SDHC_DLL_REG1_CTRL		GENMASK(7, 0)
> +#define  SDHC_DLL_REG2_CTRL		GENMASK(15, 8)
> +#define  SDHC_DLL_REG3_CTRL		GENMASK(23, 16)
> +#define  SDHC_DLL_REG4_CTRL		GENMASK(31, 24)
> +
> +#define SPACEMIT_SDHC_PHY_DLLSTS	0x170
> +#define  SDHC_DLL_LOCK_STATE		BIT(0)
> +
> +#define SPACEMIT_SDHC_PHY_PADCFG_REG	0x178
> +#define  SDHC_PHY_DRIVE_SEL		GENMASK(2, 0)
> +#define  SDHC_RX_BIAS_CTRL		BIT(5)
> +
> +#define SPACEMIT_SDHC_RX_CFG_REG        0x118
> +#define  SDHC_RX_SDCLK_SEL0_MASK        GENMASK(1, 0)
> +#define  SDHC_RX_SDCLK_SEL1_MASK        GENMASK(3, 2)
> +#define  SDHC_RX_SDCLK_SEL1             FIELD_PREP(SDHC_RX_SDCLK_SEL1_MASK, 1)
> +
> +#define SPACEMIT_SDHC_DLINE_CTRL_REG    0x130
> +#define  SDHC_DLINE_PU                  BIT(0)
> +#define  SDHC_RX_DLINE_CODE_MASK        GENMASK(23, 16)
> +#define  SDHC_TX_DLINE_CODE_MASK        GENMASK(31, 24)
> +
> +#define SPACEMIT_SDHC_DLINE_CFG_REG     0x134
> +#define  SDHC_RX_DLINE_REG_MASK         GENMASK(7, 0)
> +#define  SDHC_RX_DLINE_GAIN             BIT(8)
> +#define  SDHC_TX_DLINE_REG_MASK         GENMASK(23, 16)
> +
> +#define SPACEMIT_RX_DLINE_REG		9
> +#define SPACEMIT_RX_TUNE_DELAY_MIN	0x0
> +#define SPACEMIT_RX_TUNE_DELAY_MAX	0xFF
> +
> +#define SPACEMIT_TX_TUNING_DLINE_REG	0x00
> +#define SPACEMIT_TX_TUNING_DELAYCODE	127
> +
> +struct spacemit_sdhci_plat {
> +	struct mmc_config cfg;
> +	struct mmc mmc;
> +	struct reset_ctl_bulk resets;
> +	struct clk_bulk clks;
> +};
> +
> +struct spacemit_sdhci_priv {
> +	struct sdhci_host host;
> +};
> +
> +/* All helper functions will update clr/set while preserve rest bits */
> +static inline void spacemit_sdhci_setbits(struct sdhci_host *host, u32 val,
> +					  int reg)
> +{
> +	sdhci_writel(host, sdhci_readl(host, reg) | val, reg);
> +}
> +
> +static inline void spacemit_sdhci_clrbits(struct sdhci_host *host, u32 val,
> +					  int reg)
> +{
> +	sdhci_writel(host, sdhci_readl(host, reg) & ~val, reg);
> +}
> +
> +static inline void spacemit_sdhci_clrsetbits(struct sdhci_host *host, u32 clr,
> +					     u32 set, int reg)
> +{
> +	u32 val = sdhci_readl(host, reg);
> +
> +	val = (val & ~clr) | set;
> +	sdhci_writel(host, val, reg);
> +}
> +
> +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING)
> +static void spacemit_sdhci_set_rx_delay(struct sdhci_host *host, u8 delay)
> +{
> +	spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_CODE_MASK,
> +				  FIELD_PREP(SDHC_RX_DLINE_CODE_MASK, delay),
> +				  SPACEMIT_SDHC_DLINE_CTRL_REG);
> +}
> +
> +static void spacemit_sdhci_set_tx_delay(struct sdhci_host *host, u8 delay)
> +{
> +	spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_CODE_MASK,
> +				  FIELD_PREP(SDHC_TX_DLINE_CODE_MASK, delay),
> +				  SPACEMIT_SDHC_DLINE_CTRL_REG);
> +}
> +
> +static void spacemit_sdhci_set_tx_dline_reg(struct sdhci_host *host,
> +					    u8 dline_reg)
> +{
> +	spacemit_sdhci_clrsetbits(host, SDHC_TX_DLINE_REG_MASK,
> +				  FIELD_PREP(SDHC_TX_DLINE_REG_MASK, dline_reg),
> +				  SPACEMIT_SDHC_DLINE_CFG_REG);
> +}
> +
> +static void spacemit_sdhci_tx_tuning_prepare(struct sdhci_host *host)
> +{
> +	spacemit_sdhci_setbits(host, SDHC_TX_MUX_SEL, SPACEMIT_SDHC_TX_CFG_REG);
> +	spacemit_sdhci_setbits(host, SDHC_DLINE_PU,
> +			       SPACEMIT_SDHC_DLINE_CTRL_REG);
> +	udelay(5);
> +}
> +
> +static void spacemit_sdhci_prepare_tuning(struct sdhci_host *host)
> +{
> +	spacemit_sdhci_clrsetbits(host, SDHC_RX_DLINE_REG_MASK,
> +				  FIELD_PREP(SDHC_RX_DLINE_REG_MASK, SPACEMIT_RX_DLINE_REG),
> +				  SPACEMIT_SDHC_DLINE_CFG_REG);
> +
> +	spacemit_sdhci_setbits(host, SDHC_DLINE_PU,
> +			       SPACEMIT_SDHC_DLINE_CTRL_REG);
> +	udelay(5);
> +
> +	spacemit_sdhci_clrsetbits(host, SDHC_RX_SDCLK_SEL1_MASK,
> +				  SDHC_RX_SDCLK_SEL1,
> +				  SPACEMIT_SDHC_RX_CFG_REG);
> +
> +	if (host->mmc->selected_mode == MMC_HS_200)
> +		spacemit_sdhci_setbits(host, SDHC_HS200_USE_RFIFO,
> +				       SPACEMIT_SDHC_PHY_FUNC_REG);
> +}
> +#endif /* MMC_SUPPORTS_TUNING */
> +
> +/*
> + * 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_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, SPACEMIT_SDHC_MMC_CTRL_REG);
> +		reg |= SDHC_MMC_CARD_MODE;
> +		sdhci_writel(host, reg, SPACEMIT_SDHC_MMC_CTRL_REG);
> +
> +		/* Use PHY functional mode */
> +		reg = sdhci_readl(host, SPACEMIT_SDHC_PHY_CTRL_REG);
> +		reg |= (SDHC_PHY_FUNC_EN | SDHC_PHY_PLL_LOCK);
> +		sdhci_writel(host, reg, SPACEMIT_SDHC_PHY_CTRL_REG);
> +
> +		reg = sdhci_readl(host, SPACEMIT_SDHC_PHY_PADCFG_REG);
> +		reg |= SDHC_RX_BIAS_CTRL;
> +		sdhci_writel(host, reg, SPACEMIT_SDHC_PHY_PADCFG_REG);
> +	} else {
> +		reg = sdhci_readl(host, SPACEMIT_SDHC_TX_CFG_REG);
> +		reg |= SDHC_TX_INT_CLK_SEL;
> +		sdhci_writel(host, reg, SPACEMIT_SDHC_TX_CFG_REG);
> +	}
> +
> +	reg = sdhci_readl(host, SPACEMIT_SDHC_MMC_CTRL_REG);
> +	reg &= ~SDHC_ENHANCE_STROBE_EN;
> +	sdhci_writel(host, reg, SPACEMIT_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 = 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_clk_gate(struct sdhci_host *host, int auto_gate)
> +{
> +	u32 reg;
> +
> +	reg = sdhci_readl(host, SPACEMIT_SDHC_OP_EXT_REG);
> +	if (auto_gate)
> +		reg &= ~(SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON);
> +	else
> +		reg |= (SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON);
> +	sdhci_writel(host, reg, SPACEMIT_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);
> +
> +	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, SPACEMIT_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 |= SDHC_TX_INT_CLK_SEL;
> +	} else {
> +		reg &= ~SDHC_TX_INT_CLK_SEL;
> +	}
> +	sdhci_writel(host, reg, SPACEMIT_SDHC_TX_CFG_REG);
> +
> +	/* Set pinctrl state */
> +	if (IS_ENABLED(CONFIG_PINCTRL)) {
> +		if (mmc->clock >= 200000000)
> +			pinctrl_select_state(mmc->dev, "uhs");
> +		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, SPACEMIT_SDHC_MMC_CTRL_REG);
> +		if (mmc->selected_mode == MMC_HS_200)
> +			reg |= SDHC_MMC_HS200;
> +		else
> +			reg |= SDHC_MMC_HS400;
> +		sdhci_writew(host, reg, SPACEMIT_SDHC_MMC_CTRL_REG);
> +	} else {
> +		reg = sdhci_readw(host, SPACEMIT_SDHC_MMC_CTRL_REG);
> +		reg &= ~(SDHC_MMC_HS200 | SDHC_MMC_HS400 | SDHC_ENHANCE_STROBE_EN);
> +		sdhci_writew(host, reg, SPACEMIT_SDHC_MMC_CTRL_REG);
> +	}
> +
> +	sdhci_set_uhs_timing(host);
> +}
> +
> +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING)
> +static int spacemit_sdhci_execute_tuning(struct mmc *mmc, u8 opcode)
> +{
> +	struct sdhci_host *host = mmc->priv;
> +	int current_len = 0, current_start = 0;
> +	int max_pass_len = 0, max_pass_start = 0;
> +	u8 final_delay;
> +	int ret = 0;
> +	int i;
> +
> +	/*
> +	 * Tuning is required for SDR50/SDR104, HS200/HS400 cards and
> +	 * if clock frequency is greater than 100MHz in these modes.
> +	 */
> +	if (host->clock < 100 * 1000 * 1000 ||
> +	    !(mmc->selected_mode == MMC_HS_200 ||
> +	      mmc->selected_mode == UHS_SDR50 ||
> +	      mmc->selected_mode == UHS_SDR104))
> +		return 0;
> +
> +	if (IS_SD(host->mmc)) {
> +		spacemit_sdhci_set_tx_dline_reg(host, SPACEMIT_TX_TUNING_DLINE_REG);
> +		spacemit_sdhci_set_tx_delay(host, SPACEMIT_TX_TUNING_DELAYCODE);
> +		spacemit_sdhci_tx_tuning_prepare(host);
> +
> +		log_debug("TX tuning: dline_reg=%d, delaycode=%d\n",
> +			  SPACEMIT_TX_TUNING_DLINE_REG, SPACEMIT_TX_TUNING_DELAYCODE);
> +	}
> +
> +	spacemit_sdhci_prepare_tuning(host);
> +
> +	for (i = SPACEMIT_RX_TUNE_DELAY_MIN; i <= SPACEMIT_RX_TUNE_DELAY_MAX; i++) {
> +		spacemit_sdhci_set_rx_delay(host, i);
> +		ret = mmc_send_tuning(host->mmc, opcode);
> +
> +		log_debug("RX delay %d: %s\n",
> +			  i, ret == 0 ? "pass" : "fail");
> +
> +		if (ret == 0) {
> +			/* Test passed - extend current window */
> +			if (current_len == 0)
> +				current_start = i;
> +			current_len++;
> +		} else {
> +			/* Test failed - check if current window is best so far */
> +			if (current_len > max_pass_len) {
> +				max_pass_len = current_len;
> +				max_pass_start = current_start;
> +			}
> +			current_len = 0;
> +		}
> +	}
> +
> +	if (current_len > max_pass_len) {
> +		max_pass_len = current_len;
> +		max_pass_start = current_start;
> +	}
> +
> +	if (max_pass_len < 3) {
> +		log_err("Tuning failed: no stable window found\n");
> +		return -EIO;
> +	}
> +
> +	final_delay = max_pass_start + max_pass_len / 2;
> +	spacemit_sdhci_set_rx_delay(host, final_delay);
> +	ret = mmc_send_tuning(host->mmc, opcode);
> +	if (ret) {
> +		u8 retry_delays[] = {
> +			max_pass_start + max_pass_len / 4,
> +			max_pass_start + (3 * max_pass_len) / 4,
> +			max_pass_start,
> +			max_pass_start + max_pass_len - 1
> +		};
> +		int retry_count = ARRAY_SIZE(retry_delays);
> +
> +		log_warning("Primary delay %d failed, trying alternatives\n",
> +			    final_delay);
> +
> +		for (i = 0; i < retry_count; i++) {
> +			if (retry_delays[i] >= SPACEMIT_RX_TUNE_DELAY_MIN &&
> +			    retry_delays[i] <= SPACEMIT_RX_TUNE_DELAY_MAX) {
> +				spacemit_sdhci_set_rx_delay(host, retry_delays[i]);
> +				ret = mmc_send_tuning(host->mmc, opcode);
> +				if (!ret) {
> +					final_delay = retry_delays[i];
> +					log_info("Retry successful with delay %d\n",
> +						 final_delay);
> +					break;
> +				}
> +			}
> +		}
> +
> +		if (ret) {
> +			log_err("All retry attempts failed\n");
> +			return -EIO;
> +		}
> +	}
> +
> +	log_debug("Tuning successful: window %d-%d, using delay %d\n",
> +		  max_pass_start, max_pass_start + max_pass_len - 1, final_delay);
> +
> +	return 0;
> +}
> +#endif /* MMC_SUPPORTS_TUNING */
> +
> +#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 predly, fulldly, and vreg */
> +	spacemit_sdhci_clrsetbits(host, SDHC_DLL_PREDLY_NUM |
> +				  SDHC_DLL_FULLDLY_RANGE |
> +				  SDHC_DLL_VREG_CTRL,
> +				  FIELD_PREP(SDHC_DLL_PREDLY_NUM, 1) |
> +				  FIELD_PREP(SDHC_DLL_FULLDLY_RANGE, 1) |
> +				  FIELD_PREP(SDHC_DLL_VREG_CTRL, 1),
> +				  SPACEMIT_SDHC_PHY_DLLCFG);
> +
> +	reg = sdhci_readl(host, SPACEMIT_SDHC_PHY_DLLCFG1);
> +	reg |= FIELD_PREP(SDHC_DLL_REG1_CTRL, 0x92);
> +	sdhci_writel(host, reg, SPACEMIT_SDHC_PHY_DLLCFG1);
> +
> +	/* Enable DLL */
> +	reg = sdhci_readl(host, SPACEMIT_SDHC_PHY_DLLCFG);
> +	reg |= SDHC_DLL_ENABLE;
> +	sdhci_writel(host, reg, SPACEMIT_SDHC_PHY_DLLCFG);
> +
> +	/* Wait for DLL lock */
> +	i = 0;
> +	while (i++ < 100) {
> +		if (sdhci_readl(host, SPACEMIT_SDHC_PHY_DLLSTS) & SDHC_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, SPACEMIT_SDHC_MMC_CTRL_REG);
> +	reg |= SDHC_ENHANCE_STROBE_EN;
> +	sdhci_writel(host, reg, SPACEMIT_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,
> +#if CONFIG_IS_ENABLED(MMC_SUPPORTS_TUNING)
> +	.platform_execute_tuning	= spacemit_sdhci_execute_tuning,
> +#endif
> +#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;
> +	}

The previously clk_enable_bulk()'d clocks are never released better to
add to cleanup paths.

> +
> +	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 |
> +			  MMC_CAP_CMD23;
> +	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);
> +	if (drv_data)
> +		memcpy(plat, drv_data, sizeof(struct spacemit_sdhci_plat));
> +	return sdhci_bind(dev, &plat->mmc, &plat->cfg);
> +}
> +
> +static const struct udevice_id spacemit_sdhci_ids[] = {
> +	{
> +		.compatible = "spacemit,k1-sdhci",
> +		.data = 0,
> +	}, {
> +	}
> +};
> +
> +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),
> +};
>

  reply	other threads:[~2026-07-05 10:20 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-05  6:07 [PATCH v3 0/9] spacemit mmc driver Eric Chung
2026-07-05  6:07 ` [PATCH v3 1/9] spacemit: k1: select boot device via config registers Eric Chung
2026-07-05  6:07 ` [PATCH v3 2/9] pinctrl: k1: add IO power domain configuration support Eric Chung
2026-07-05  6:07 ` [PATCH v3 3/9] mmc: sdhci: enable CMD23 for multi-block transfers Eric Chung
2026-07-05  9:51   ` Kathpalia, Tanmay
2026-07-05 15:21     ` Eric Chung
2026-07-05 18:28       ` Kathpalia, Tanmay
2026-07-05  6:07 ` [PATCH v3 4/9] mmc: k1: add sdhci platform driver Eric Chung
2026-07-05 10:20   ` Kathpalia, Tanmay [this message]
2026-07-05  6:07 ` [PATCH v3 5/9] dts: k1: add SD card support in u-boot overlay Eric Chung
2026-07-05  6:07 ` [PATCH v3 6/9] configs: k1: enable SD and eMMC support Eric Chung
2026-07-05  6:07 ` [PATCH v3 7/9] doc: spacemit: flash on K1 SoC based boards Eric Chung
2026-07-05  6:07 ` [PATCH v3 8/9] config: k1: enable ENV support for eMMC Eric Chung
2026-07-05  6:08 ` [PATCH v3 9/9] spacemit: k1: load product name from environment variable Eric Chung

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=eee5cbaf-3641-4e9f-b50e-b5931478e933@altera.com \
    --to=tanmay.kathpalia@altera.com \
    --cc=angelo@kernel-space.org \
    --cc=anshuld@ti.com \
    --cc=balsundar.ponnusamy@altera.com \
    --cc=bastien.curutchet@bootlin.com \
    --cc=bhimeswararao.matsa@gmail.com \
    --cc=c.stoidner@phytec.de \
    --cc=daniel@thingy.jp \
    --cc=eric.chung@riscstar.com \
    --cc=guodong.xu@riscstar.com \
    --cc=han.xu@nxp.com \
    --cc=jbx6244@gmail.com \
    --cc=jh80.chung@samsung.com \
    --cc=kauschluss@disroot.org \
    --cc=leo.liang@sifive.com \
    --cc=peng.fan@nxp.com \
    --cc=pericycle.cc@gmail.com \
    --cc=raymond.mao@riscstar.com \
    --cc=rick@andestech.com \
    --cc=semen.protsenko@linaro.org \
    --cc=tim609@andestech.com \
    --cc=trini@konsulko.com \
    --cc=u-boot-spacemit@groups.io \
    --cc=u-boot@lists.denx.de \
    --cc=yanir.levin@tandemg.com \
    --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