SoC 板级 Bring-up 工程手册
3 分钟阅读
从硬件三要素,到用证据逼出根因。
这份手册是我在 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 的思路,是三级节点:
- 逻辑节点——表示开发者能控制的那个逻辑设备(例如 usb)的全部。一般写在板级 / 产品级的 dtsi 里。
- 控制器节点——逻辑节点里会有一个控制器节点。
- 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。此刻大概率不行——这才进入正题。 但在钻理论之前,先做一轮不依赖上层应用的「底层验证」,确认驱动到底加载没、物理信号到底出没出。以显示接口为例,这套底层验证是最关键的一步:
看内核日志:
dmesg | grep -i panel——看 panel 驱动有没有被成功probe。报错的话,日志会告诉你原因(pinctrl 没找到、时钟获取失败等)。dmesg | grep -i dsi/dmesg | grep -i drm——看 DRM 子系统和控制器有没有报错。
看 sysfs 接口:
ls /sys/class/drm/——应能看到代表屏幕的节点,如card0-DSI-1。cat /sys/class/drm/card0-DSI-1/modes——内核识别到的分辨率/时序,必须和 dts 里配的一致。
物理信号测量(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 成功或失败的原因到底是什么。 而在这个过程里,我提炼出三条可以脱离任何具体模块的判据:
缺席即证据——log 里「有 BEFORE 无 AFTER」是 hang 位置的证据;
clk_summary里「该有的时钟不在、或频率不对」是配置没生效的证据。debug 时不要只盯「显示了什么」,也要盯「该显示却没显示什么」——沉默本身就是信息。两个相邻子通路互为对照——别把一个控制器当成「一个时钟、一条路」。找两个功能相邻但时钟源/总线独立的子通路(如 PCIe 的 APB vs DBI),一通一挂,故障层立刻收敛。
有寄存器 ≠ 能用——silicon 里留了某个 mux/寄存器,不代表 BSP/CRU 把它配成了可用状态。要分清「芯片能力」和「BSP 完成度」,否则会往一条半成品路径上白烧好几版。
这三条,加上前面那 7 步主线,就是我现在一提到 bring-up 就会在脑子里过一遍的东西。它不保证每次都调通,但它保证:无论通还是不通,我最后都拿得出证据,说清楚为什么。