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。这一步其实很快,通常几十微秒就完成了。

电源轨道典型电压上电顺序要求
VCC2.5V / 3.3V先上
VCCQ1.8V后上(晚于 VCC ≥ 100μs)
VCCQ21.2V最后上(可选)
注意: 有些 UFS 设备对 VCCQ 的上升沿斜率有要求,太慢会导致启动失败。我曾经遇到过一颗三星的 UFS 3.1 芯片,VCCQ 上升时间超过 300μs 就进不了 LINERESET 状态。后来加了快速稳压器才搞定。

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等)
小技巧: 识别阶段发出的 QUERY REQUEST 如果超时,不要立刻认为设备坏了。可以先发一个 UIC RESET,再发 NOP OUT 重新握手。我在阿里云测试平台上遇到过一批设备,第一次 QUERY 总是超时,重试一次就好了。后来发现是链路训练参数没配好。

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 初始化流程全景图

下面这张图把整个初始化链路串起来了。重点看箭头上的时序关系,很多问题就出在某个状态转移的等待时间不够。

UFS 设备初始化流程状态机 1. 上电 / 复位 2. 启动序列 (Boot) 3. 设备识别 4. 模式切换 (HS) 5. 就绪 (Active) 切换失败回退 关键时序 • 上电 → 时钟稳定 ≥ 1ms • 启动序列 ≤ 100μs • 识别超时 ≥ 5ms • 模式切换等待 5ms

4.5 实战中的调试手段

初始化阶段出问题,最有效的工具是逻辑分析仪抓 UniPro 层的帧。你看 NOP IN 有没有回、QUERY RESPONSE 的负载对不对。要是链路死了,连帧都看不到,那就先查物理层——差分信号摆幅对不对,共模电压在不在范围内。

我还习惯在驱动里加一段初始化日志,每完成一步就打印一个标志位。这样一旦卡住,看最后打印就知道死在哪个阶段。代码量不大,但排查效率翻倍。

  • 死在上电阶段: 检查电源时序、参考时钟、复位信号
  • 死在启动序列: 检查 REF_CLK 频率是否准确,展频是否关掉
  • 死在设备识别: 检查链路的 PMU 设置、PA_Mode 是否在慢速模式
  • 死在模式切换: 降低速率重试,检查信号完整性
特别提醒: 千万别在热插拔环境下按软件初始化流程硬跑。UFS 支持热插拔,但初始化必须重新走完整链路训练。我见过有人直接把冷启动的代码用在热插拔上,结果设备反复挂起,最后烧了接口的 ESD 保护二极管。

说实话,UFS 初始化看似只有四个阶段,但每个阶段都有潜规则。你只有亲手调试过几次设备识别失败,才能摸清那些规范里没写的“潜时序”。嗯,这些经验比看一百遍协议都管用。