[PATCH 7/7] mmc: sdhci-of-k1: Implement SD clock gating for voltage switch

From: Yixun Lan

Date: Wed Sep 02 2026 - 04:07:55 EST


From: Long Wan <long.wan@xxxxxxxxxxxxxxxxxx>

reset() permanently set OVRRD_CLK_OEN and FORCE_CLK_ON for SD/SDIO hosts
and never cleared them, so the SD clock was forced on for the lifetime of
the controller and hardware auto gating never took effect.

These bits are only needed during the CMD11 1.8V voltage switch, where
the clock has to keep running so DAT[3:0] can be sampled after the
switch. Add a set_clk_gate() helper, force the clock on from set_clock()
when CMD11 is in flight, and restore auto gating from card_busy() once
the switch completes.

card_busy() is registered for all card types since the CMD11 sequence is
an SD/SDIO path and the DAT[0] level check is generic.

Signed-off-by: Long Wan <long.wan@xxxxxxxxxxxxxxxxxx>
Signed-off-by: Yixun Lan <dlan@xxxxxxxxxx>
---
drivers/mmc/host/sdhci-of-k1.c | 49 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 45 insertions(+), 4 deletions(-)

diff --git a/drivers/mmc/host/sdhci-of-k1.c b/drivers/mmc/host/sdhci-of-k1.c
index a07e7db0853e..5260421d394c 100644
--- a/drivers/mmc/host/sdhci-of-k1.c
+++ b/drivers/mmc/host/sdhci-of-k1.c
@@ -12,6 +12,7 @@
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
@@ -162,6 +163,16 @@ static void spacemit_sdhci_prepare_tuning(struct sdhci_host *host)
spacemit_sdhci_setbits(host, SDHC_HS200_USE_RFIFO, SPACEMIT_SDHC_PHY_FUNC_REG);
}

+static void spacemit_sdhci_set_clk_gate(struct sdhci_host *host, unsigned int auto_gate)
+{
+ if (auto_gate)
+ spacemit_sdhci_clrbits(host, SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON,
+ SPACEMIT_SDHC_OP_EXT_REG);
+ else
+ spacemit_sdhci_setbits(host, SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON,
+ SPACEMIT_SDHC_OP_EXT_REG);
+}
+
static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask)
{
sdhci_reset(host, mask);
@@ -172,9 +183,6 @@ static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask)
if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
/* SD/SDIO: bypass PHY, use TX internal clock */
spacemit_sdhci_setbits(host, SDHC_TX_INT_CLK_SEL, SPACEMIT_SDHC_TX_CFG_REG);
-
- spacemit_sdhci_setbits(host, SDHC_OVRRD_CLK_OEN | SDHC_FORCE_CLK_ON,
- SPACEMIT_SDHC_OP_EXT_REG);
} else {
/* eMMC: use PHY function mode */
spacemit_sdhci_setbits(host, SDHC_PHY_FUNC_EN | SDHC_PHY_PLL_LOCK,
@@ -209,6 +217,20 @@ static void spacemit_sdhci_set_clock(struct sdhci_host *host, unsigned int clock
spacemit_sdhci_clrbits(host, SDHC_TX_INT_CLK_SEL, SPACEMIT_SDHC_TX_CFG_REG);

sdhci_set_clock(host, clock);
+
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+ /*
+ * During CMD11 voltage switch to 1.8V, temporarily force the
+ * clock on to ensure DAT[3:0] can be sampled correctly after
+ * the switch. Auto gating is restored in card_busy() once the
+ * switch completes.
+ */
+ if ((SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) == SD_SWITCH_VOLTAGE) &&
+ host->mmc->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
+ if (clock)
+ spacemit_sdhci_set_clk_gate(host, 0);
+ }
+ }
};

static void spacemit_sdhci_phy_dll_init(struct sdhci_host *host)
@@ -491,6 +513,23 @@ static inline void spacemit_sdhci_get_pins(struct device *dev,
sdhst->pinctrl_default, sdhst->pinctrl_uhs);
}

+static int spacemit_sdhci_card_busy(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u32 present_state;
+
+ present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
+
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+ if ((SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) == SD_SWITCH_VOLTAGE) &&
+ host->mmc->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_180)
+ /* Recover auto clock after voltage switch */
+ spacemit_sdhci_set_clk_gate(host, 1);
+ }
+
+ return !(present_state & SDHCI_DATA_0_LVL_MASK);
+}
+
static const struct sdhci_ops spacemit_sdhci_ops = {
.get_max_clock = spacemit_sdhci_clk_get_max_clock,
.reset = spacemit_sdhci_reset,
@@ -555,8 +594,10 @@ static int spacemit_sdhci_probe(struct platform_device *pdev)

sdhci_get_of_property(pdev);

+ mops = &host->mmc_host_ops;
+ mops->card_busy = spacemit_sdhci_card_busy;
+
if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)) {
- mops = &host->mmc_host_ops;
mops->hs400_prepare_ddr = spacemit_sdhci_pre_select_hs400;
mops->hs400_complete = spacemit_sdhci_post_select_hs400;
mops->hs400_downgrade = spacemit_sdhci_pre_hs400_to_hs200;

--
2.55.0