4、设备树配置:UFS控制器设备树节点、时钟与电源配置、PHY参数配置

设备树配置,说白了就是告诉内核:「你的UFS控制器长什么样?接在哪个时钟线上?电源怎么给?PHY怎么调?」。这一步要是配错了,驱动加载时直接卡死,连错误日志都来不及打印。我当年第一次移植海力士UFS时,就因为在设备树里少配了一个时钟,结果内核启动到一半就挂了,查了两天才找到原因。

4.1 UFS控制器设备树节点

先看一个完整的UFS控制器节点长什么样。嗯,这里要注意,不同SoC的写法略有差异,但核心结构是一样的。

/* 海力士UFS控制器设备树节点示例 */
ufs: ufs@15570000 {
    compatible = "samsung,exynos-ufs", "jedec,ufs-2.0";
    reg = <0x0 0x15570000 0x0 0x100>,
          <0x0 0x15570100 0x0 0x100>,
          <0x0 0x15571000 0x0 0x200>;
    reg-names = "hci", "vendor", "unipro";
    interrupts = <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clock CLK_UFS_MPHY>,
             <&clock CLK_UFS_UNIPRO>,
             <&clock CLK_UFS_TCON>;
    clock-names = "mphy", "unipro", "tcon";
    pinctrl-names = "default";
    pinctrl-0 = <&ufs_rst_n &ufs_refclk_out>;
    phys = <&ufs_phy>;
    phy-names = "ufs-phy";
    status = "okay";
};

这里我解释几个关键字段:

  • compatible:驱动匹配的关键。我习惯把SoC厂商的compatible放在前面,然后是jedec标准。这样既保证兼容性,又方便后续扩展。
  • reg和reg-names:UFS控制器内部有多个寄存器区域。HCI是主控接口,Vendor是厂商私有寄存器,Unipro是协议层寄存器。这三个区域必须配全,少一个驱动就报错。
  • interrupts:UFS中断号。注意这里用的是GIC_SPI类型,具体中断号要看SoC手册。我遇到过中断号配错的情况,结果驱动一直轮询,性能直接掉一半。

重要提醒:reg-names的字符串必须和驱动里request_mem_region用的名字完全一致,大小写都不能错。我曾经因为把"hci"写成了"HCI",浪费了一整天。

4.2 时钟与电源配置

时钟配置是UFS设备树里最容易出问题的地方。UFS需要三个时钟:MPHY时钟、Unipro时钟和TCON时钟。这三个时钟的频率和相位必须严格匹配。

时钟名称 典型频率 作用
MPHY时钟 26MHz 物理层时钟,用于M-PHY模块
Unipro时钟 100MHz 协议层时钟,用于Unipro协议处理
TCON时钟 200MHz 传输层时钟,用于数据收发

电源配置方面,UFS设备通常需要两个电源域:

/* 电源配置示例 */
vcc_ufs: regulator-vcc-ufs {
    compatible = "regulator-fixed";
    regulator-name = "vcc_ufs";
    regulator-min-microvolt = <3000000>;
    regulator-max-microvolt = <3000000>;
    gpio = <&gpa1 0 GPIO_ACTIVE_HIGH>;
    enable-active-high;
};

vccq_ufs: regulator-vccq-ufs {
    compatible = "regulator-fixed";
    regulator-name = "vccq_ufs";
    regulator-min-microvolt = <1800000>;
    regulator-max-microvolt = <1800000>;
    gpio = <&gpa1 1 GPIO_ACTIVE_HIGH>;
    enable-active-high;
};

然后在UFS节点里引用:

ufs@15570000 {
    ...
    vcc-supply = <&vcc_ufs>;
    vccq-supply = <&vccq_ufs>;
    vccq2-supply = <&vccq_ufs>;
};

个人经验:我建议在调试阶段把vccq2-supply也配成和vccq一样。虽然有些UFS设备不需要vccq2,但配上了不会出问题,不配反而可能因为电源域缺失导致初始化失败。

4.3 PHY参数配置

PHY参数配置是UFS设备树里最玄学的部分。说白了,就是告诉PHY芯片:「你的眼图要调成什么样?信号摆幅多大?预加重多少?」。这些参数直接决定了UFS链路的稳定性。

先看一个完整的PHY节点:

/* UFS PHY设备树节点 */
ufs_phy: ufs-phy@15571800 {
    compatible = "samsung,exynos-ufs-phy";
    reg = <0x0 0x15571800 0x0 0x100>;
    reg-names = "phy";
    clocks = <&clock CLK_UFS_MPHY>;
    clock-names = "mphy";
    samsung,pmu-syscon = <&pmu_system_controller>;
    #phy-cells = <0>;
    
    /* PHY参数配置 */
    samsung,hs-gear = <4>;  /* HS-G4 */
    samsung,pwr-opt = <0>;
    samsung,hs-series = <1>;  /* 1: HS系列, 0: PWM系列 */
    
    /* 眼图参数 */
    samsung,eye-param {
        tx-diff-amp = <0x28>;  /* 差分摆幅 */
        tx-pre-emphasis = <0x0A>;  /* 预加重 */
        tx-post-emphasis = <0x05>;  /* 后加重 */
        rx-eq-level = <0x03>;  /* 接收均衡 */
    };
    
    status = "okay";
};

这里有几个关键参数需要特别关注:

  • samsung,hs-gear:指定最高速率。HS-G4对应的是11.6Gbps。我建议刚开始调试时先用HS-G2,等链路稳定了再往上提。
  • tx-diff-amp:差分信号摆幅。这个值太小信号传不远,太大又容易产生EMI。我一般从0x28开始调,然后根据眼图测试结果微调。
  • tx-pre-emphasis:预加重。高频信号在传输过程中会有衰减,预加重就是提前把高频分量放大。这个值要根据PCB走线长度来调,没有固定公式。

避坑指南:我曾经遇到过一个问题——PHY参数配好后,UFS设备能识别,但跑压力测试时频繁掉盘。查了两天,最后发现是rx-eq-level设得太高,导致接收端过均衡。把值从0x05降到0x03后,问题就解决了。所以PHY参数一定要在实际工况下验证,不能只看识别结果。

4.4 知识体系总览

为了让你更直观地理解UFS设备树配置的整体结构,我画了一张图:

UFS设备树配置知识体系 UFS控制器节点 时钟配置 电源配置 PHY参数配置 MPHY时钟 Unipro时钟 TCON时钟 VCC (3.0V) VCCQ (1.8V) VCCQ2 (1.8V) 速率等级 眼图参数 均衡设置 三者缺一不可,任何一项配置错误都会导致UFS无法正常工作

从这张图可以看出来,UFS设备树配置其实就三大块:时钟、电源、PHY参数。这三者缺一不可,而且相互关联。比如时钟频率不对,PHY的速率等级就上不去;电源电压不稳,眼图参数调得再好也没用。

我的调试习惯:拿到一块新板子,我会先把时钟和电源配好,确保UFS控制器能正常识别。然后再调PHY参数,从最低速率开始,逐步往上提。每提一档就跑一遍压力测试,确保链路稳定。这样一步步来,出问题了也知道是哪个环节的事。

好了,设备树配置这部分就讲到这里。记住一句话:设备树配得好,驱动调试少烦恼。下一节我们聊聊UFS驱动的初始化流程,到时候会用到今天配的这些参数。


公众号:蓝海资料掘金营,微信deep3321