SD 卡 SDIO 电压切换全链路分析:从 3.3V 默认模式到 1.8V UHS-I 的时序约束与硬件死锁排查
SD 卡 SDIO 电压切换全链路分析:从 3.3V 默认模式到 1.8V UHS-I 的时序约束与硬件死锁排查
一、UHS-I 切换失败的真实现场:当 SD 卡在 1.8V 下彻底"失联"时
在一块工业单板机的 bring-up 阶段,SD 卡在 3.3V 默认速度模式(Default Speed, 25MHz)下读写正常,但启用 UHS-I SDR50 模式(1.8V 信号电压,100MHz 时钟)后,整个 SD 卡通信链路完全中断。mmc0: error -110 whilst initialising SD card的内核日志指向电压切换失败,但逻辑分析仪抓取的总线波形显示 CMD11 响应正常,却没有任何后续的时钟切换行为。
这个问题的根因在于 SD 卡电压切换是一个多阶段、跨域协调的过程,涉及 SD Host Controller 的寄存器配置、外部电压调节器的 I/O 切换、以及卡内部的电压检测电路。任何一个环节的时序不匹配都会导致切换失败——最棘手的是,一旦卡进入了 1.8V 信令模式但 Host 仍输出 3.3V,卡的 1.8V 输入钳位二极管将限制信号摆幅,导致 CMD 和 DATA 线路的双向通信完全中断。
UHS-I(Ultra High Speed Phase I)是 SD 卡从 3.3V 信号切换到 1.8V 信号以支持更高时钟频率(最高 208MHz SDR104)的关键跃迁。这一过程不仅仅是电压的简单降低,还涉及驱动强度调整、采样点重对齐和时钟调谐等底层硬件控制。理解其完整的时序约束是排查此类故障的基础。
二、UHS-I 电压切换协议的状态迁移与时钟调谐
SD 卡从上电到进入 UHS-I 模式,需要经历初始化、卡识别、电压切换、速度模式协商和时钟调谐五个阶段。其中电压切换由 CMD11(VOLTAGE_SWITCH)命令触发。
电压切换的关键时序约束由 SD 物理层规范 3.3.1 定义。Host 在 CMD11 完成后需要立即停止提供 SDCLK,并将 CMD 和 DAT[3:0] 驱动为低电平。这确保了一个明确的电气状态转换,卡检测到这些信号全部为低电平后开始内部电压切换。如果 Host 不能将 DAT[3:0] 驱动为低电平(例如使用了开漏输出模式),卡的电压切换可能不会正确触发。
恢复 SDCLK 后的等待时间(1ms + 74 个时钟周期)至关重要。如果 Host 提前发送 CMD13,卡可能还在初始化内部 PLL 或基准电压电路,导致命令超时。这就是很多 "error -110" 的根因——Host 没有等待足够的稳定时间。
三、SD Host Controller 电压切换的寄存器级实现
以下代码展示了通过操作 SD Host Controller 寄存器来完成电压切换的完整流程:
/* sdio_uhs1.c — SD 卡 UHS-I 电压切换的 Host Controller 寄存器操作 */ /* SD Host Controller 关键寄存器偏移 (SDHCI 3.0 标准) */ #define SDHCI_PRESENT_STATE 0x24 /* 当前状态寄存器 */ #define SDHCI_HOST_CONTROL2 0x3E /* Host Control 2 — 电压选择 */ #define SDHCI_POWER_CONTROL 0x29 /* 电源控制寄存器 */ #define SDHCI_CLOCK_CONTROL 0x2C /* 时钟控制寄存器 */ #define SDHCI_SOFTWARE_RESET 0x2F /* 软件复位寄存器 */ /* Host Control 2 寄存器位域 */ #define SDHCI_CTRL_VDD_180 (1 << 3) /* 1.8V 信号使能 */ #define SDHCI_CTRL_DRV_TYPE_A (0 << 4) /* 驱动类型 A (默认) */ #define SDHCI_CTRL_DRV_TYPE_B (1 << 4) /* 驱动类型 B */ #define SDHCI_CTRL_DRV_TYPE_C (2 << 4) /* 驱动类型 C (SDR104) */ #define SDHCI_CTRL_DRV_TYPE_D (3 << 4) /* 驱动类型 D (SDR50/DDR50) */ #define SDHCI_CTRL_UHS_SDR25 (0 << 16) /* UHS SDR25 */ #define SDHCI_CTRL_UHS_SDR50 (1 << 16) /* UHS SDR50 */ #define SDHCI_CTRL_UHS_SDR104 (2 << 16) /* UHS SDR104 */ #define SDHCI_CTRL_UHS_DDR50 (3 << 16) /* UHS DDR50 */ /* Present State 寄存器位域 */ #define SDHCI_CMD_INHIBIT (1 << 0) /* 命令线忙 */ #define SDHCI_DAT_INHIBIT (1 << 1) /* 数据线忙 */ #define SDHCI_DAT_LINE_ACTIVE (1 << 2) /* DAT 线活跃 */ #define SDHCI_CARD_INSERTED (1 << 16) /* 卡插入检测 */ #define SDHCI_CARD_STABLE (1 << 17) /* 卡稳定 */ /* 电压切换完整流程 — 中断上下文安全版本 */ int sdio_voltage_switch_to_1v8(uintptr_t sdhci_base) { uint32_t reg_val; int retry; /* 阶段 1: 发送 CMD11 — 通知卡准备电压切换 */ /* CMD11 的参数固定为 0,卡回复 R1 响应 */ /* 此步骤由上层 MMC 核心执行,这里假设 CMD11 已成功 */ /* 阶段 2: 停止 SDCLK — 写入 Clock Control 寄存器 */ /* 先将 SDCLK Enable 位清零 */ reg_val = readl(sdhci_base + SDHCI_CLOCK_CONTROL); reg_val &= ~0x0002; /* 清除 Internal Clock Enable */ writel(reg_val, sdhci_base + SDHCI_CLOCK_CONTROL); /* SDCLK Enable 位清零后,等待时钟真正停止 */ retry = 1000; while (retry--) { reg_val = readl(sdhci_base + SDHCI_CLOCK_CONTROL); if (!(reg_val & 0x0002)) { break; /* Internal Clock Stable 位已清除 */ } udelay(1); } if (retry <= 0) { return -ETIMEDOUT; /* 时钟未能停止,可能是卡仍在执行命令 */ } /* 阶段 3: 将 DAT[3:0] 驱动为低电平 — 通知卡进入电压切换 */ /* 通过 Preset Value 或直接写 Signal Voltage 位 */ reg_val = readl(sdhci_base + SDHCI_HOST_CONTROL2); /* 某些 Host Controller 需要先写 0 再写 1 才能正确驱动 */ writel(reg_val & ~0x0007, sdhci_base + SDHCI_HOST_CONTROL2); udelay(10); /* 等待至少 5us 确保卡检测到数据线低电平 */ /* 阶段 4: 切换外部电压调节器到 1.8V */ /* 此操作是平台相关的 — 通过 GPIO 或 PMIC I2C 控制 */ int ret = platform_vqmmc_set_voltage(1800000); /* 1.8V = 1800000uV */ if (ret < 0) { /* 电压调节器切换失败 — 不能继续,否则可能损坏卡 */ return -EIO; } /* 等待电压稳定 — 规范要求至少 1ms */ mdelay(5); /* 留足余量,有些调节器上升斜率较慢 */ /* 阶段 5: 配置 Host Controller 为 1.8V 信号模式 */ reg_val = readl(sdhci_base + SDHCI_HOST_CONTROL2); reg_val |= SDHCI_CTRL_VDD_180; /* 1.8V Signaling Enable */ writel(reg_val, sdhci_base + SDHCI_HOST_CONTROL2); /* 阶段 6: 恢复 SDCLK — 使用初始化频率 (400KHz) */ /* 先配置 SDCLK 分频器 */ reg_val = readl(sdhci_base + SDHCI_CLOCK_CONTROL); reg_val &= ~0xFF00; /* 清除分频比 */ reg_val |= (0x80 << 8); /* 二分频 (base_clock / 2 = 400KHz) */ writel(reg_val, sdhci_base + SDHCI_CLOCK_CONTROL); /* 使能 SDCLK */ reg_val |= 0x0001; /* SD Clock Enable */ writel(reg_val, sdhci_base + SDHCI_CLOCK_CONTROL); /* 等待时钟稳定 */ retry = 1000; while (retry--) { reg_val = readl(sdhci_base + SDHCI_CLOCK_CONTROL); if (reg_val & 0x0002) { break; /* Internal Clock Stable */ } udelay(1); } if (retry <= 0) { return -ETIMEDOUT; } /* 阶段 7: 等待卡完成电压切换 — 规范要求至少 1ms + 74 clock */ mdelay(2); /* 1ms + 74 × 2.5us (400KHz 周期) ≈ 1.2ms */ /* 阶段 8: 验证电压切换成功 — 发送 CMD13 */ /* dat0 应该被卡拉高,表示卡已就绪 */ retry = 100; while (retry--) { reg_val = readl(sdhci_base + SDHCI_PRESENT_STATE); if (!(reg_val & SDHCI_CMD_INHIBIT)) { break; /* CMD 线空闲,可以发送命令 */ } udelay(100); } if (retry <= 0) { return -ETIMEDOUT; /* CMD 线一直被占用 — 可能卡未完成切换 */ } /* 这里上层 MMC 核心发送 CMD13 验证卡状态 */ /* 如果 CMD13 失败,电压切换整体失败 */ return 0; } /* 时钟调谐 (Tuning) 流程 — UHS-I SDR50/SDR104 必需 */ /* Host Controller 发送 CMD19 (SEND_TUNING_BLOCK) 并采样 */ int sdio_execute_tuning(uintptr_t sdhci_base) { uint32_t ctrl2; int ret; /* 1. 检查控制器是否支持硬件调谐 */ ctrl2 = readl(sdhci_base + SDHCI_HOST_CONTROL2); if (ctrl2 & (1 << 6)) { /* Use Tuning for SDR50 */ /* 2. 使能内部调谐电路 */ /* Tuning 过程中控制器自动调整采样点延迟 */ /* 3. 发送最多 40 次 CMD19,每次返回 64 字节调谐块 */ for (int i = 0; i < 40; i++) { ret = sdio_send_cmd19(); /* 发送 CMD19 */ if (ret < 0 && i > 5) { /* 前 5 次失败可能是延迟尚未稳定,超过 5 次则视为调谐失败 */ return -EIO; } if (ret == 0) { /* 调谐成功 — 当前采样点有效 */ } } } return 0; }四、驱动强度选择与信号完整性的架构权衡
UHS-I 规范定义了四种驱动强度类型(Type A/B/C/D),分别对应不同的输出阻抗和信号上升时间。Type A(默认)驱动能力最弱,适合短走线和低容性负载场景;Type D 驱动能力最强,适合长走线和多卡总线。错误的驱动强度选择会导致信号过冲(驱动太强)或采样窗口不足(驱动太弱)。
在 PCB Layout 层面,SD 卡信号走线长度需要满足等长约束。SDR50 模式下时钟频率 100MHz,信号周期为 10ns,要求 CLK 与 CMD/DATA 之间的走线偏差控制在 ±500ps 以内(FR-4 板材约 1mm = 5.8ps 延迟,即走线偏差不超过 ±8.6mm)。对于非等长 Layout 的现成开发板,即使 Host 侧正确配置了电压切换,卡侧的建立/保持时间违规也会导致随机 CRC 错误。
五、总结
SD 卡从 3.3V 到 1.8V UHS-I 的电压切换是一个涉及 Host 控制器、外部电源和卡内部电路的三方协同过程。关键要点包括:
- CMD11 后的硬件切换时序不可省略,停时钟 → 拉低 DAT → 切换 VDD → 等稳定 → 恢复时钟,每一步都有规范定义的最小延迟;
- 1ms + 74 个时钟周期的等待是卡内部 PLL 稳定所需的底线,提前通信会导致 -110 错误;
- 外部电压调节器的稳定性直接影响切换成功率,部分低质量 PMIC 的 1.8V LDO 启动时间可能超过规范允许的 5ms;
- 驱动强度和走线等长约束是信号完整性的保证,错误的配置在高时钟频率下表现为随机 CRC 错误。
