3. RK3568 PCIe RC模式驱动开发

好,咱们进入正题。把RK3568配成Root Complex,说白了就是让这颗芯片当PCIe总线上的“老大”。我刚开始接触这个的时候,总觉得设备树配好就完事了,结果板子死活不枚举设备。后来才发现,时钟、复位、核心控制器初始化,哪一步没到位都不行。

这一节,我带你一步步把RC模式跑起来。咱们从设备树配置开始,一路干到链路训练完成。

3.1 设备树配置——硬件怎么“说”给内核听

设备树是硬件和内核沟通的“翻译官”。RC模式下,你得告诉内核:PCIe控制器挂在哪、用哪个时钟、复位信号怎么控、地址空间怎么映射。

我个人习惯先把pcie0节点打开,再配compatible、reg、interrupts这些基础属性。下面是一个典型配置片段:

&pcie0 {
    status = "okay";
    num-lanes = <2>;
    max-link-speed = <3>;  /* Gen3 */
    reset-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>;
    vpcie3v3-supply = <&vcc3v3_pcie>;
    vpcie1v8-supply = <&vcc1v8_pcie>;
    pinctrl-names = "default";
    pinctrl-0 = <&pcie0_clkreqn &pcie0_perstn>;
};

这里有几个坑,我踩过:

  • num-lanes:RK3568最多支持4 lane,但实际走线可能只用了2 lane。配多了,链路训练会一直尝试连不存在的lane,浪费时间。
  • reset-gpios:PERST#信号必须配对。我曾经把GPIO号搞反,结果设备一直处于复位状态,链路死活起不来。
  • vpcie3v3/vpcie1v8:这两个电源域必须稳定。如果供电纹波大,链路训练会频繁失败。
我的习惯:设备树配完后,先检查/sys/firmware/devicetree/base/pcie0/下的属性值,确认内核解析正确。这一步能省掉很多调试时间。

3.2 时钟与复位管理——别让“心跳”出问题

PCIe对时钟极其敏感。RC模式下,你需要提供参考时钟(REFCLK)给下游设备。RK3568内部有PLL,可以生成100MHz差分时钟。

时钟配置一般在clk节点里搞定:

&pcie0 {
    clocks = <&cru ACLK_PCIE0_MST>,
             <&cru ACLK_PCIE0_SLV>,
             <&cru ACLK_PCIE0_DBI>,
             <&cru PCLK_PCIE0>,
             <&cru CLK_PCIE0_AUX>;
    clock-names = "aclk_mst", "aclk_slv",
                  "aclk_dbi", "pclk", "aux";
};

复位管理更关键。RC模式下,复位序列是:

  1. 先释放控制器内部复位(通过CRU寄存器)。
  2. 再释放PERST#信号给下游设备。
  3. 等待100ms以上,让设备准备好。

我记得有一次,PERST#释放后只等了50ms,结果某款SSD死活不响应。后来查手册才发现,它要求至少100ms的复位延迟。嗯,从此我老老实实加了msleep(100)。

注意:时钟频率必须精确。RK3568的REFCLK要求100MHz ±300ppm。如果时钟偏差太大,链路训练时PLL锁不住,你会看到LTSSM一直在Polling.Active状态打转。

3.3 核心控制器初始化——让硬件“醒”过来

设备树配好了,时钟复位也搞定了,接下来就是让PCIe控制器真正跑起来。这部分工作在内核的pcie-rockchip.c驱动里完成。

初始化流程大致如下:

步骤 操作 关键寄存器/函数
1 使能时钟门控 clk_bulk_enable()
2 释放复位 reset_control_deassert()
3 配置DBI(Device Bus Interface) rockchip_pcie_dbi_init()
4 设置地址空间映射 rockchip_pcie_cfg_atu()
5 启动链路训练 rockchip_pcie_start_link()

其中,ATU(Address Translation Unit)配置是重中之重。RC模式下,你需要把CPU的物理地址映射到PCIe总线地址空间。说白了,就是告诉控制器:CPU发往0x30000000的访问,要转换成PCIe总线上的0x80000000。

static int rockchip_pcie_cfg_atu(struct rockchip_pcie *rockchip)
{
    /* 配置内存空间映射 */
    rockchip_pcie_writel(rockchip, 0x30000000, PCIE_ATU_LOWER_BASE);
    rockchip_pcie_writel(rockchip, 0x80000000, PCIE_ATU_LOWER_TARGET);
    rockchip_pcie_writel(rockchip, 0x10000000 - 1, PCIE_ATU_LIMIT);
    rockchip_pcie_writel(rockchip, PCIE_ATU_TYPE_MEM, PCIE_ATU_CR1);

    return 0;
}
核心要点:ATU映射必须和dts中的ranges属性一致。否则,CPU访问PCIe设备时会触发异常,内核直接oops给你看。

3.4 链路训练——最磨人的阶段

链路训练(Link Training)是PCIe初始化的最后一步,也是最容易出问题的一步。LTSSM(Link Training and Status State Machine)会经历Detect → Polling → Configuration → L0这几个状态。

我习惯在驱动里加调试打印,实时监控LTSSM状态:

static int rockchip_pcie_start_link(struct rockchip_pcie *rockchip)
{
    int ret;
    u32 val;

    /* 触发链路训练 */
    ret = rockchip_pcie_writel(rockchip, PCIE_CLIENT_LINK_TRAIN_EN,
                               PCIE_CLIENT_CONFIG);
    if (ret)
        return ret;

    /* 轮询LTSSM状态,超时5秒 */
    ret = readl_poll_timeout(rockchip->apb_base + PCIE_LTSSM_STATUS,
                             val, (val & LTSSM_STATE_MASK) == L0_STATE,
                             1000, 5000000);
    if (ret) {
        dev_err(rockchip->dev, "Link training timeout, LTSSM=0x%x\n", val);
        return -ETIMEDOUT;
    }

    dev_info(rockchip->dev, "Link up at Gen%d, x%d\n",
             (val & LTSSM_SPEED_MASK) >> 16,
             (val & LTSSM_WIDTH_MASK) >> 8);
    return 0;
}

如果链路训练失败,别慌。先看LTSSM停在了哪个状态:

  • Detect.Quiet:说明没检测到设备。检查PERST#信号和REFCLK。
  • Polling.Active:设备有回应,但训练参数不匹配。检查lane数和速度配置。
  • Configuration.LanenumWait:lane协商失败。可能走线有问题,或者设备只支持x1。

我曾经遇到一个奇葩问题:链路训练总是超时,但用示波器量REFCLK和PERST#都正常。最后发现是PCB上PCIe差分对的阻抗不匹配,导致信号质量太差。嗯,硬件问题有时候比软件更难查。

调试技巧:在/sys/kernel/debug/pci/下可以查看LTSSM状态和链路信息。配合lspci -vvv,基本能定位90%的问题。

3.5 知识结构图——RC模式初始化全景

下面这张图,把整个RC模式初始化的流程串起来了。你对照着看,思路会更清晰。

RK3568 PCIe RC模式初始化流程 RK3568 PCIe RC模式初始化流程 设备树配置 dts节点/属性 时钟与复位 PLL/PERST# 核心初始化 DBI/ATU 链路训练 LTSSM状态机 Detect 检测设备 Polling 协商参数 Configuration 分配资源 L0 正常运作 说明: 设备树配置是基础,时钟复位是前提,核心初始化是关键,链路训练是验证。 任何一个环节出错,PCIe设备都无法正常工作。建议按顺序排查。

3.6 调试与验证——怎么确认RC模式跑起来了

初始化完成后,怎么确认RC模式正常工作?我一般做三步检查:

  1. 看内核日志:搜索pcie-rockchip和Link up关键字。如果看到Link up at Gen3, x2,说明链路训练成功。
  2. 用lspci:执行lspci -vvv,看能不能枚举到下游设备。如果看不到,检查ATU映射和地址空间。
  3. 读写测试:用devmem2或pciutils工具,对PCIe设备的BAR空间进行读写。能正常读写,说明RC模式完全OK。
常见失败场景:如果lspci能看到设备,但读写时系统卡死,多半是ATU映射范围不对。检查dts中的ranges属性,确保CPU地址和PCIe总线地址一一对应。