4. UFS设备初始化流程
UFS 设备上电后怎么才能干活?这个问题看着简单,坑可不少。我每次带新人做 UFS 驱动,都会让他们先把初始化流程完整走一遍,别急着调读写。等你真遇到设备挂死在某个阶段,就知道这一步多重要了。
4.1 上电时序与启动序列
UFS 设备对上电时序很敏感。先看电源:VCC(主电源)、VCCQ(I/O 电源)、VCCQ2(可选低电压)。一般的上电顺序是 VCC 先上,然后 VCCQ,最后 VCCQ2。但具体芯片手册上可能反过来,尤其是一些手机 SoC 的参考设计。
我自己踩过的坑: 有次项目上电顺序反了,VCCQ 比 VCC 早到 200μs,结果设备回传的版本号全是 0xFF。查了三天,最后用示波器抓到电源波形才确认。从此以后我只要搭测试台,第一件事就是测电源时序。
上电稳定后,主控发 REF_CLK(通常 26MHz 或 19.2MHz)。设备内部 Boot ROM 开始跑,拉低 RST_n 信号。然后设备通过 UniPro 链路发送 Boot Sequence。这一步其实很快,通常几十微秒就完成了。
| 电源轨道 | 典型电压 | 上电顺序要求 |
|---|---|---|
| VCC | 2.5V / 3.3V | 先上 |
| VCCQ | 1.8V | 后上(晚于 VCC ≥ 100μs) |
| VCCQ2 | 1.2V | 最后上(可选) |
4.2 设备识别(Device Discovery)
启动完成后,主控通过 UIC 层发 NOP OUT 检查链路。设备回复 NOP IN 就算链路通了。然后主控发 QUERY REQUEST 读设备描述符(Device Descriptor)。这里你会拿到 wManufacturerID、bProductID、bNumberLUN 这些关键信息。
说白了,就是让主控知道:你是谁?你能干嘛?
我建议在设备识别阶段做一次全读校验。不只是读 Device Descriptor,还要读 Geometry Descriptor、Unit Descriptor。几何描述符里写了 LU 数量、最大缓冲大小、擦除块大小。这些在后续压力测试配置分区的时候会用到。
- Device Descriptor(必须)- 厂商、产品版本、UFS 规范版本
- Configuration Descriptor(可选)- 每个 LU 的配置
- Geometry Descriptor(推荐)- 物理块大小、总容量
- Unit Descriptor(每个 LU 一个)- LU 类型(BOOT / RPMB / UFS等)
4.3 模式切换(DME_SET / DME_GET)
设备识别完,链路还是默认的 PWM-G1 模式(低速)。要跑高速必须切到 HS-G1/G2/G3。切换过程靠 DME(Device Management Entity)指令。
标准流程:主控发 DME_SET 命令,把 PA_Mode 从 FAST_MODE 改成 FAST_MODE_AUTO,再设 PA_HS_MODE 选择高速系列。设备收到后开始重新训练链路,切换到新速率。训练失败会回退到之前的状态。
注意点: 切换不能太急。我在项目里试过连续发两次 DME_SET 切换速率,结果设备直接挂了,只能断电重启。后来查规范才确定,每次切换后必须等设备返回 CONFIGURE DONE,至少等 5ms。
// 模式切换伪代码(典型流程)
// 1. 获取当前链路状态
status = uic_dme_get(PA_ActiveTxDataLane);
// 2. 设置目标速率(例如 HS-G2)
uic_dme_set(PA_HSSeries, 2); // HS-G2
uic_dme_set(PA_Mode, FAST_MODE_AUTO);
// 3. 等待设备确认
do {
result = uic_dme_get(PA_ConnectionState);
delay_us(100);
} while (result != CONNECTED && timeout < 10000);
// 4. 检查链路是否稳定
if (result != CONNECTED) {
// 回退到前一个速率
uic_dme_set(PA_Mode, SLOW_MODE);
}
实际工程中,模式切换失败最常见的原因是 PCB 走线太长或参考层不连续导致信号质量差。我曾经在基站存储板上遇到过:切换 HS-G3 必失败,HS-G2 稳如狗。后来量眼图发现 G3 的眼高只有 100mV,完全达不到规范要求。最后只能锁死在 G2。
4.4 初始化流程全景图
下面这张图把整个初始化链路串起来了。重点看箭头上的时序关系,很多问题就出在某个状态转移的等待时间不够。
4.5 实战中的调试手段
初始化阶段出问题,最有效的工具是逻辑分析仪抓 UniPro 层的帧。你看 NOP IN 有没有回、QUERY RESPONSE 的负载对不对。要是链路死了,连帧都看不到,那就先查物理层——差分信号摆幅对不对,共模电压在不在范围内。
我还习惯在驱动里加一段初始化日志,每完成一步就打印一个标志位。这样一旦卡住,看最后打印就知道死在哪个阶段。代码量不大,但排查效率翻倍。
- 死在上电阶段: 检查电源时序、参考时钟、复位信号
- 死在启动序列: 检查 REF_CLK 频率是否准确,展频是否关掉
- 死在设备识别: 检查链路的 PMU 设置、PA_Mode 是否在慢速模式
- 死在模式切换: 降低速率重试,检查信号完整性
说实话,UFS 初始化看似只有四个阶段,但每个阶段都有潜规则。你只有亲手调试过几次设备识别失败,才能摸清那些规范里没写的“潜时序”。嗯,这些经验比看一百遍协议都管用。