SoC 板级 Bring-up 工程手册

一套我自己在 RK3568 / RK3588 多次 bring-up 后沉淀下来的方法论:不靠运气、不靠穷举,用有限的时间在近乎无限的内核打击面里定位根因。

从硬件三要素,到用证据逼出根因。

这份手册是我在 RK3568、RK3588 上做过 GMAC、显示(LVDS/MIPI/HDMI)、USB、PCIe 等多个模块的 bring-up 之后,回头总结出来的一套东西。它已经脱离了任何单一模块,是我下次面对一颗陌生 SoC、一个陌生外设时,脑子里第一时间会浮现的操作主线。

我把以前拆散写过的几篇笔记(电源/时钟/复位、硬件分层架构、dts 三级节点、显示 SOP)全部融进了这条主线——因为它们本来就是同一条脉络上被拆下来的零件。


一、先讲两条底层认知(心法)

在讲具体怎么做之前,得先说清楚我对 bring-up 这件事本身的两个判断。这两条决定了后面所有操作的取舍。

1. bring-up 的精华不是「调通」,是「找到整条事情的脉络」

即便你把硬件调通了,若只是碰巧,或者不改 dts、不动驱动代码、甚至是不敢改,那也没意义。调通只是结果,能说清楚它为什么通、为什么之前不通,才是本事。

尤其是失败的时候——成功了大家都默认自己做得对;但失败了,多数时候也是猜测:猜测应该是哪里、感觉是哪里,基本上找到一个不合理的地方,所有人就都拥上去说是这个导致的。

但 kernel 已经做得极其复杂,一层一层封装包裹,控制器一般是厂商外面买的 IP,所以又在原厂基础上做一层自己的 wrapper,一层一层调用,就得一层一层去解包。这个层层解包的过程,本身就足以劝退一个刚接触这个硬件模块的人。所以单拿一个显眼的错误就说自己找到了根源,是不牢靠的。

2. bring-up 的根本矛盾:有限的时间 vs 近乎无限的打击面

bring-up 总是很赶。不是因为没有时间预算,而是因为你面对的可能是浩瀚 kernel 里的任何一个问题(Linus 说过,如今 3500 万行以上的 kernel,已经没人能完全精通了)。而你不可能现场把内核学精通了再回来调。

所以这是一个矛盾体:

有限的时间 vs 近乎无限的打击面(内核)。

因此,bring-up 最重要的能力,不是「懂很多」,而是定位问题、缩小打击面的能力。靠什么做到?靠脉络。下面这 7 步,就是我缩小打击面的主线。


二、脉络:7 步操作主线

这 7 步是有严格先后的。核心原则:假设越靠前的层越可能出问题,就越先排除它;每一步都尽量用最便宜的手段(读一个文件、测一个脚、加一条 printk)得到一个「通 / 挂」的二元结论,一层层往下逼。

第 1 步 · 以 SDK 为起跳点

SDK 本身是最佳起点。原厂给的 SDK 至少保证「能跑起来、不出 bug」,这是你唯一确定的地面。所有改动都从这个已知可用的基线出发,改坏了随时能退回来对比。先让原始 SDK 跑通,再谈改。

第 2 步 · 先鸟瞰扫盲协议,做一份 wiki.md

接手时,你很可能同时面对四种陌生:硬件不了解、内核代码不了解、dts 不了解、协议本身(PCIe / MIPI CSI / USB…)也不了解。这时候不要急着钻代码,要熬住,先用鸟瞰式的方式把协议原理扫一遍,把关键词汇整理成一份自己的 wiki.md。

这件事 AI 可以帮上大忙,而且做出来的「教材」非常贴合自己当下的需要——你要的不是一本大部头,而是「够我看懂 SDK 里那些名词」的最小知识集。

产出:一份属于你自己的术语表 + 数据流草图。它是后面每一步的地图。

第 3 步 · 从 dts 入手

先假设 kernel 代码 100% OK,只看 dts 开放了什么入口、我们能设什么值。看 rk3588.dtsi(或对应 SoC 的 dtsi)——这里是最全的 SoC 设备百科全书。pinctrl / pinconf 都是辅助这本百科全书的角色(pinctrl 讲 GPIO 复用配置,pinconf 功能类似,姑且先归一类)。

外设 dts 的思路,是三级节点:

  1. 逻辑节点——表示开发者能控制的那个逻辑设备(例如 usb)的全部。一般写在板级 / 产品级的 dtsi 里。
  2. 控制器节点——逻辑节点里会有一个控制器节点。
  3. phy 节点——控制器节点里会有 phy 节点。(如果是 usb3.0,会有两个 phy:一个提供 usb2.0、一个提供 usb3.0——因为 usb 协议向前兼容,usb3.0 接口要能接 usb2.0 设备,就必须带一个 2.0 的 phy。)

最后在产品级 dtsi 里,把逻辑节点 / 控制器节点 / phy 节点全部打开、且配置到正确的模式。

怎么找哪个是板级 / 产品级文件? 看 dtb 的文件名——编译产物 dtb 只会有一个,与它同名的那个 dts 就是入口。

怎么从代码里认出「控制器节点」是哪一个? 一个控制器节点通常同时具备这些特征:

  • 有中断,能通知 CPU(interrupts)
  • 拥有 PHY(phys, phy-names)
  • 与驱动程序绑定(有 compatible)
  • 拥有大量寄存器空间(如 reg = <0x0 0xfcc00000 0x0 0x400000>)
  • 拥有复杂的时钟和复位逻辑
  • 拥有高层模式配置(例如 dr_mode = "otg")

补充:usb 里有两类容易混的节点——只能做 host 的一般叫 usbhost30;能做 host/device 的 otg,一般叫 usbdrd30。

dts 的分层(soc / vendor / board 三级)我另有专文展开,此处不赘述。

第 4 步 · 对照原理图改 dts(先确认硬件三要素活着)

硬件多少都有改动,不然就不需要 bring-up 了——硬件 it is down,所以需要你 bring it up。找到原理图上相对参考设计改动的地方,研究这样配置是要调什么模式、还是屏蔽某些可选功能。

如果对 SoC 的模块结构 / GRF 寄存器很熟,当然好;但如果不懂,也别硬去现学——先回到 dts 去猜,凭对 dts 语法的熟悉,猜可能是哪一个、有几个节点。(以 RK3588 的 PCIe 为例,控制器一共 5 个:pcie3x4 / pcie3x2 / pcie2x1l0 / pcie2x1l1 / pcie2x1l2;PHY 有 4 个:3 个 combo phy + 1 个 pcie30phy。对照原理图先配上一版。)

但在动 dts 之前,硬件地基必须先确认活着——电源、时钟、复位,缺一不可。 这是万用表和示波器的活,不是软件的活。以我调 RK3568 GMAC(PHY 是 YT8521SC)为例:

a. 电源——先有,而且正常。「正常」的定义在芯片手册里,不同电源脚要求不同电压:

  • AVDDL / DVDDL:要求 1.2V 左右
  • DVDD33 / VDD33 / AVDD33:要求 3.3V 左右
  • DVDD_RGMII(特殊,小心):RGMII I/O 与 MDC/MDIO 的供电,由 CFG_LDO[1:0] 决定来源。CFG_LDO[1:0] = 00 时由外部 3.3V 供,否则走内部 LDO——务必对照实测电压确认走的是哪条路。

b. 时钟(晶振)——这一组关乎「有没有时钟源」:

  • XTAL_I(晶振输入脚)与 XTAL_O(晶振输出脚):25MHz 应能从 XTAL_O 侧测出。
  • 之后再看 MDC(MDIO 的时钟)/ MDIO(MDIO 的数据)——这俩是 MAC(在 RK3568 内)控制 PHY(YT8521SC)的电路,一个时钟一个数据,都得有,正常值 3.3V 或接近,明显低于(如 1V 以下)就是错的。

时钟一定要分清「谁给谁、谁倍频」:25MHz 原始时钟是谁给的?倍频是谁做的?倍频后给到谁了?这条链没理清,后面全是乱猜。

c. 复位——RESET_N 脚,dts 里对应 snps,reset-gpio 这类属性。正确表现是:开机瞬间有一个拉低,然后拉高;如果一直低、或从不拉低,就是复位没做对。

三要素全部实测活着,再回头改 dts;否则你在软件上怎么配都没用——因为你配的东西根本没上电、没时钟、没被复位释放。

第 5 步 · 编译 → 跑内核 → 串口抓 log → grep(底层验证)

dts 配好、内核编过,就烧录、开机、串口抓 log。此刻大概率不行——这才进入正题。 但在钻理论之前,先做一轮不依赖上层应用的「底层验证」,确认驱动到底加载没、物理信号到底出没出。以显示接口为例,这套底层验证是最关键的一步:

  1. 看内核日志:

    • dmesg | grep -i panel——看 panel 驱动有没有被成功 probe。报错的话,日志会告诉你原因(pinctrl 没找到、时钟获取失败等)。
    • dmesg | grep -i dsi / dmesg | grep -i drm——看 DRM 子系统和控制器有没有报错。
  2. 看 sysfs 接口:

    • ls /sys/class/drm/——应能看到代表屏幕的节点,如 card0-DSI-1。
    • cat /sys/class/drm/card0-DSI-1/modes——内核识别到的分辨率/时序,必须和 dts 里配的一致。
  3. 物理信号测量(The Moment of Truth):用示波器测接口的时钟线(有波形说明 PHY 和控制器至少在工作;频率对不对能验证 clock-frequency 生效没)和数据线。如果屏幕/对端还没工作,但示波器上已经看到正确的时钟和数据——恭喜,SoC 侧驱动大概率是对的,问题转移到了对端。

这一步的意义是切分战场:把「SoC 软件侧」和「对端硬件侧」这两个可能的故障域分开。没做完这一步,不要进入下一阶段。

第 6 步 · 打不通,才去看 TRM 架构图

前面几步都过了还是挂,才值得投入时间读 TRM——看这个硬件模块在 SoC 里的架构图。这里要建立一个通用的心智模型:现代 SoC 里几乎所有高速外设,都遵循同一套三层分层架构(这是一种「分离关注点」的设计哲学,不是巧合)。硬件基本跑不开 PHY 和 Controller 这两块,先把它们找出来:

  • 阶段一 · PHY(物理层)——「感官」:系统与物理世界交互的模拟前端。负责模拟↔数字信号转换、信号调理(放大/滤波/均衡)、时钟数据恢复(CDR)。它是「媒质专家」,只关心怎么在物理介质上可靠收发 0 和 1,但不理解这些 0 和 1 的意义。
  • 阶段二 · Controller / MAC(协议层)——「语言中枢」:数字后端。负责成帧(把比特流切成有意义的帧/包)、协议解析、错误校验(CRC)、媒体访问控制。它是「语法专家」,把无意义的比特流组织成结构化数据。
  • 阶段三 · System Interface(系统接口)——「通往大脑的神经」:连接外设与 SoC 核心(CPU/内存)的桥梁。负责数据缓冲(FIFO)、总线适配(AXI/AHB)、DMA、中断生成。

以 USB 为例串起完整数据通路:

U盘 → USB接口 → PHY(模拟转数字)→ Controller(组成数据包)→ DMA → 内部总线 → DDR内存 → 中断 → CPU(最终处理)

graph TD
    A([外部物理世界<br/>e.g., U盘])
    B["<b>PHY (物理层)</b><br/>模拟信号 <=> 数字比特流"]
    C["<b>Controller / MAC (协议层)</b><br/>比特流 <=> 数据包/帧"]
    E([<b>CPU 中央处理器</b><br/>最终进行软件处理])

    subgraph "SoC内部数据通路 (Internal Data Path)"
        direction LR
        D_DMA["<b>DMA Engine</b><br/>高效的数据搬运工"]
        D_BUS["<b>内部总线 (System Bus)</b><br/>e.g., AXI, AHB<br/>SoC内部的高速公路"]
        D_MEM(["<b>DDR 内存 (Memory)</b><br/>数据的临时存储区"])
    end

    A -- "模拟信号" --> B
    B -- "数字比特流" --> C
    C -- "数据包/帧" --> D_DMA
    D_DMA -- "通过总线传输数据" --> D_BUS
    D_BUS -- "写入内存" --> D_MEM

    D_DMA -.-> |传输完成, 发送中断信号| E
    D_MEM -.-> |CPU响应中断, 从内存读取数据| E

这个模型是普适的,换个接口只是换称呼:以太网是 PHY + MAC + DMA;CAN 是 CAN Transceiver + Controller;EtherCAT 复用以太网 PHY + 专门的 ESC 从站控制器。脑子里有这张分层图,配 dts、看 log 时就知道自己现在工作在哪一个 component。

有了架构图,再看系统总线怎么走:interconnect 是访问各硬件模块的中转和路由器,一头是 CPU,另一头就是硬件模块。 对照架构图去看代码跑到哪里、出错点 / RCU stall / kernel panic 的点落在哪,判断是数据流还是控制流走到哪个硬件 IP 出的问题。

第 7 步 · 专项定位:抠 IP 的子通路

锁定到某个 IP 之后,再往里抠——这个 IP 内部有哪些子通路。以 PCIe 为例,控制器对外/对内用到四种协议通路:

  • AXI master:控制器做主,EP 把数据写到 host 的路径
  • AXI slave:控制器做从,CPU 把数据写到 EP 的路径
  • DBI:CPU 戳控制器自己的寄存器
  • APB:CPU 戳 RK 的胶水逻辑(wrapper)

它们分别是控制器做主/做从、发起/回答时走的通路。定位的问题变成:卡在哪一条子通路上?是做什么事的时候整体卡住的? 到这一层,两个功能相邻但走不同时钟域/不同总线的子通路,就能互为对照——一个通、一个挂,故障层当场被钉死。

这一步的完整实战,见我这次 RK3588 PCIe 的案例:《不靠猜:如何用 TRM 推导出 PCIe 模块的问题》——里面用「APB 通、DBI 挂」一个对照实验,就把「PHY 层可能有问题」的猜测,变成了「PHY 层必然有问题」的证据。


三、结语:一套可复用的判据

不断用这套 7 步方法迭代,最终就能拿到一个可靠的证据,证明这次 bring-up 成功或失败的原因到底是什么。 而在这个过程里,我提炼出三条可以脱离任何具体模块的判据:

  1. 缺席即证据——log 里「有 BEFORE 无 AFTER」是 hang 位置的证据;clk_summary 里「该有的时钟不在、或频率不对」是配置没生效的证据。debug 时不要只盯「显示了什么」,也要盯「该显示却没显示什么」——沉默本身就是信息。

  2. 两个相邻子通路互为对照——别把一个控制器当成「一个时钟、一条路」。找两个功能相邻但时钟源/总线独立的子通路(如 PCIe 的 APB vs DBI),一通一挂,故障层立刻收敛。

  3. 有寄存器 ≠ 能用——silicon 里留了某个 mux/寄存器,不代表 BSP/CRU 把它配成了可用状态。要分清「芯片能力」和「BSP 完成度」,否则会往一条半成品路径上白烧好几版。

这三条,加上前面那 7 步主线,就是我现在一提到 bring-up 就会在脑子里过一遍的东西。它不保证每次都调通,但它保证:无论通还是不通,我最后都拿得出证据,说清楚为什么。