On 2025-10-22 08:14, Peng Fan wrote:
Hi Kaustabh,

On Fri, Oct 17, 2025 at 08:54:09PM +0530, Kaustabh Chakraborty wrote:
During a voltage switch command (CMD11, opcode: SD_CMD_SWITCH_UHS18V),
certain hosts tend to stop responding to subsequent commands. This is
addressed by introducing an additional command flag,
DWMCI_CMD_VOLT_SWITCH.

is there any errata or spec have this information public?

I'm not aware of any. However, this behavior is found in the Linux kernel driver. See commit 0173055842cd1 ("mmc: dw_mmc: Support voltage changes").
The state tracking in the kernel is however much more complex.



The associated interrupt bit is defined as DWMCI_INTMSK_VOLTSW. This is
set high when a voltage switch is issued, this needs to be waited for
and set to low. Implement the same in the timeout loop. Do note that
since DWMCI_INTMSK_VOLTSW shares the same bit as DWMCI_INTMSK_HTO (bit
10), the interrupt bit needs to be polled for only if the volt switch
command is issued.

DWMCI_CMD_VOLT_SWITCH also needs to be set for subsequent clken commands after the volt switch. To ensure this, add a boolean member in the host private struct (herein named volt_switching), which informs if the last
command issued was for volt switching or not.

Signed-off-by: Kaustabh Chakraborty <[email protected]>
---
drivers/mmc/dw_mmc.c | 15 +++++++++++++--
include/dwmmc.h      |  4 ++++
2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/dw_mmc.c b/drivers/mmc/dw_mmc.c
index 1aa992c352c3f11ccdd1c02745fa988646952261..94b6641c44c39e67aac453c027d519c0e1580de6 100644
--- a/drivers/mmc/dw_mmc.c
+++ b/drivers/mmc/dw_mmc.c
@@ -419,6 +419,10 @@ static int dwmci_send_cmd_common(struct dwmci_host *host, struct mmc_cmd *cmd,
        if (cmd->resp_type & MMC_RSP_CRC)
                flags |= DWMCI_CMD_CHECK_CRC;

+       host->volt_switching = (cmd->cmdidx == SD_CMD_SWITCH_UHS18V);

[1]

+       if (host->volt_switching)
+               flags |= DWMCI_CMD_VOLT_SWITCH;
+
        flags |= cmd->cmdidx | DWMCI_CMD_START | DWMCI_CMD_USE_HOLD_REG;

        debug("Sending CMD%d\n", cmd->cmdidx);
@@ -427,6 +431,10 @@ static int dwmci_send_cmd_common(struct dwmci_host *host, struct mmc_cmd *cmd,

        for (i = 0; i < retry; i++) {
                mask = dwmci_readl(host, DWMCI_RINTSTS);
+               if (host->volt_switching && (mask & DWMCI_INTMSK_VOLTSW)) {
+                       dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_VOLTSW);
+                       break;
+               }
                if (mask & DWMCI_INTMSK_CDONE) {
                        if (!data)
                                dwmci_writel(host, DWMCI_RINTSTS, mask);
@@ -508,12 +516,15 @@ static int dwmci_control_clken(struct dwmci_host *host, bool on)
        const u32 val = on ? DWMCI_CLKEN_ENABLE | DWMCI_CLKEN_LOW_PWR : 0;
        const u32 cmd_only_clk = DWMCI_CMD_PRV_DAT_WAIT | DWMCI_CMD_UPD_CLK;
        int i, timeout = 10000;
-       u32 mask;
+       u32 flags, mask;

        dwmci_writel(host, DWMCI_CLKENA, val);

        /* Inform CIU */
-       dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_START | cmd_only_clk);
+       flags = DWMCI_CMD_START | cmd_only_clk;
+       if (host->volt_switching)
+               flags |= DWMCI_CMD_VOLT_SWITCH;
+       dwmci_writel(host, DWMCI_CMD, flags);

        for (i = 0; i < timeout; i++) {
                mask = dwmci_readl(host, DWMCI_RINTSTS);
diff --git a/include/dwmmc.h b/include/dwmmc.h
index 639a2d28e7860f2ceb09955ee11550e406fd1bd2..47e3220985e900050d9db9d80e0d45efe6c2e545 100644
--- a/include/dwmmc.h
+++ b/include/dwmmc.h
@@ -72,6 +72,7 @@
#define DWMCI_INTMSK_RTO        BIT(8)
#define DWMCI_INTMSK_DRTO       BIT(9)
#define DWMCI_INTMSK_HTO        BIT(10)
+#define DWMCI_INTMSK_VOLTSW    BIT(10) /* overlap! */
#define DWMCI_INTMSK_FRUN       BIT(11)
#define DWMCI_INTMSK_HLE        BIT(12)
#define DWMCI_INTMSK_SBE        BIT(13)
@@ -104,6 +105,7 @@
#define DWMCI_CMD_ABORT_STOP    BIT(14)
#define DWMCI_CMD_PRV_DAT_WAIT  BIT(13)
#define DWMCI_CMD_UPD_CLK       BIT(21)
+#define DWMCI_CMD_VOLT_SWITCH  BIT(28)
#define DWMCI_CMD_USE_HOLD_REG  BIT(29)
#define DWMCI_CMD_START         BIT(31)

@@ -190,6 +192,7 @@ struct dwmci_idmac_regs {
 * @cfg:        Internal MMC configuration, for !CONFIG_BLK cases
 * @fifo_mode:  Use FIFO mode (not DMA) to read and write data
 * @dma_64bit_address: Whether DMA supports 64-bit address mode or not
+ * @volt_switching: Whether SD voltage switching is in process or not

Since volt_switching y means in process, I not see it is cleared when it is
not needed. Is this expected?

See [1] marked above. Its enabled when SD_CMD_SWITCH_UHS18V is the opcode,
disabled otherwise.


Thanks,
Peng

 * @regs:       Registers that can vary for different DW MMC block versions
 */
struct dwmci_host {
@@ -229,6 +232,7 @@ struct dwmci_host {

        bool fifo_mode;
        bool dma_64bit_address;
+       bool volt_switching;
        const struct dwmci_idmac_regs *regs;
};


--
2.51.0

Reply via email to