RK3568 的板卡调试,MIPI 屏是最常见的“劝退”项目之一。很多人遇到的场景就是这样:uboot 阶段 logo 正常显示,看着一切就要成了,结果 Linux 内核跑起来之后,屏幕直接黑掉,串口里系统其实已经正常启动了。背光有时候亮着,有时候不亮,敲键盘也没反应,整个人瞬间回到原点。下面我就把这类“uboot 显示正常、进内核黑屏”的问题完整拆解一遍,从现象分类、设备树链路核对、屏参断层、背光时序,到 modetest 和 DRM 调试节点的实际用法,最后用一个 ST7701S 屏幕的完整修复过程收尾。正在调 RK3568/RK3588 平台 MIPI 屏的 BSP 工程师,或者刚接触 Rockchip DRM 显示链路的嵌入式开发者,都可以照着这个思路走一遍。
1. 先界定故障边界:是真黑屏还是假黑屏,问题到底在哪一段
1.1 先把“黑屏”拆成三种
经验里最忌讳的就是拿到“黑屏”两个字就直接翻设备树。黑屏不是一个单一原因,至少可以拆成三种完全不同的现象:
- 完全黑屏,背光也不亮,屏幕像没通电一样。
- 背光亮着,但没有画面,屏幕只是发光。
- 开机瞬间有画面,然后黑掉,偶尔还带着闪屏或者条纹。
三种现象对应的排查方向差异极大。完全黑屏优先查 LCD 电源、背光使能、reset 时序,背光亮无画面优先查 DSI 数据链路和 panel 初始化序列,画面出现后再消失优先查 logo 继承、fbcon 抢占或者 route 节点配置。花五分钟确定现象属于哪一类,比盲目看日志效率高得多。
1.2 uboot 能点亮,只代表“硬件链路没有死”
这一点值得反复强调:uboot 和内核用的是完全独立的两套显示框架。uboot 阶段是 U-Boot 自带的 video driver,读的是 uboot.dts 里的 display-timings 或 panel 节点,完成一次最简单的 mode set,然后往 framebuffer 里画 logo。内核阶段则是 DRM、panel driver、VOP2 这套完整的 display pipeline,读的是 kernel dts,走的是组件绑定流程。
这两套东西不是同一份代码,也不是同一份设备树。所以 uboot 的 logo 正常只能说明几件事:MIPI 的物理链路没有断,PCB 走线和连接器大概率没问题;屏幕的供电和 reset 大体上是对的;uboot 里配的屏参和激励信号能被屏幕接受。
但内核能不能点亮,完全取决于内核设备树里的 route 配置、panel compatible 匹配、DRM 组件绑定、背光设备这几个环节是否齐全。uboot 亮了而内核黑屏,恰恰说明“断层”出现在这两套框架交替的位置。
1.3 进入正题前,先回答三个问题
不管日志多复杂,我建议先确认三件最基础的事:
- 内核是否真的起来了,能不能通过串口登录。
- DRM 相关设备有没有绑定成功。
- /sys/class/drm/ 下面有没有 DSI connector。
先保证串口能登录系统,然后执行:
dmesg | grep -iE "drm|vop|dsi|panel|backlight" | tail -n 100 cat /sys/class/drm/card0-DSI-1/status如果根本不存在 card0-DSI-1 这个目录,说明 DSI encoder 或者 panel 驱动没有 probe 成功,大概率是设备树 compatible 或端口连接问题。如果节点存在,状态是 connected,说明链路已经注册了,问题很可能出在 mode set、背光或初始化序列上。这三个答案直接决定后面往哪个方向钻。
2. 设备树接线图排查:从 display-subsystem 到 panel 的完整链路核对
2.1 一条完整显示链路在设备树里长什么样
RK3568 的显示链路在设备树里是一条很明确的“接线图”:
display-subsystem → vop2 → VP 端口 → route_dsi0 → dsi0 controller → MIPI D-PHY → panel 节点
每个环节都有对应的节点和 status 控制。我见过很多次黑屏就是因为 route_dsi0 的 status 还是 disabled,或者 connect 属性指到了别的 VP 上。典型的 RK3568 设备树结构大概是这样(省略无关内容):
&vop2 { vop2_out: port { #address-cells = <1>; #size-cells = <0>; route_dsi0: route-dsi0 { status = "okay"; logo,uboot = "logo.bmp"; connect = <&vp2_out_dsi0>; }; }; }; &dsi0 { status = "okay"; panel@0 { compatible = "st7701s,720x1280"; reg = <0>; backlight = <&backlight>; reset-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_LOW>; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; dsi0_out_panel: endpoint { remote-endpoint = <&dsi0_in_vp2>; }; }; }; }; };这里的 endpoint 名称只是示意,不同 SDK 版本写法会有差异。核心是:如果 route_dsi0 没有配成 okay,或者 connect 没有连到 dsi0 对应的 VP 输出,内核 DRM 根本不会把 VOP 输出路由到 DSI。这个节点太容易被遗漏了,尤其是从某个参考板 dts 裁剪过来的时候,经常保留着原来的 disabled 状态。
2.2 route 节点里三个属性,少一个都不行
Rockchip 的 route 节点不是普通设备树节点那么直观,但理解起来其实不难。
- status:控制这个视频输出路由是否参与组件绑定。如果 disabled,VOP2 对应 VP 不会绑定到 DSI,现象就是完全没有显示。
- connect:指定 VP 输出 endpoint。在支持多路显示的平台上,如果 connect 指错,比如想用 dsi0 却 connect 到 vp1_out_dsi1,同样会黑屏,或者画面跑到错误接口上。
- logo,uboot:决定 uboot 的 logo 内存区域要不要被内核继承。这一个属性在配置了开机动画后特别关键,因为 uboot 切到内核时会经历一次 pipeline teardown,如果 logo 继承失败,屏会先黑一下,严重时直接停在黑屏。
判断 route 是否生效,最直接的方法是看内核日志里有没有对应的组件绑定信息,比如drm/rockchip: bound ...。如果日志里完全没有,优先确认 route 节点有没有被 include 到最终 DTB 里。有时候你改了 dtsi 文件,但实际编译用的是另一个 dts,这种低级错误我在项目里遇到过不止一次。
2.3 panel 节点核对清单
接下来快速过一遍 panel 节点本身,我习惯按下面几项逐条核对:
- compatible:是否匹配内核已有的 panel 驱动或 panel-simple 匹配表,不匹配的话设备树不会 probe。
- reset-gpios:GPIO 编号和 active 状态(高有效还是低有效)是否正确。
- backlight:是否正确引用 backlight 节点,且默认亮度是否够大。
- power-supply / vddio-supply:panel 的电源域是否在开机阶段被正确拉起来。
- ports:panel 的 endpoint 是否与 dsi0 的 endpoint 对应上。
下面的表格可以当排查清单用:
| 检查项 | 正常情况 | 异常影响 |
|---|---|---|
| route_dsi0.status | okay | disabled 时完全无输出 |
| route_dsi0.connect | 指向 dsi0 对应 VP 输出 | 输出到错误接口或黑屏 |
| panel compatible | 能匹配到驱动 | probe 失败,无 connector |
| reset-gpios 极性 | 与屏规格书一致 | 屏幕一直处于 reset 状态 |
| backlight 引用 | 存在且默认亮度>0 | 画面正常但亮度为 0 |
3. 屏参与初始化序列断层:uboot 能亮不等于内核能亮
3.1 两种 panel 的本质区别
在设备树里,MIPI 屏大致分两类。
一类是简单的 panel-simple 或 panel-timing,屏幕只需要 hback-porch、vfront-porch 这些时序参数,上电后就能直接接收像素流。这类屏通常是 RGB 接口转 MIPI 的转换模块,或者内部没有复杂标定逻辑的屏幕,设备树里给几组 timing 就行。
另一类是带控制器的屏幕,像 ST7701S 就是一颗很常见的 LCD 驱动控制器。它不仅要 timing,还必须在开机时通过 MIPI DSI 短包和长包写入初始化序列。初始化序列包括退出睡眠模式、设置显示分辨率、调整内部寄存器、打开显示等。如果内核启动时没有完整执行这段序列,屏幕就停留在默认的异常状态,表现出来就是黑屏、白屏或者花屏。
这种差异正是“uboot 亮、内核黑”最常见的原因之一:uboot 里的驱动执行了一遍屏幕初始化序列,效果是好的;内核的 panel 驱动如果有同样的序列或者能匹配到兼容驱动,问题不大;但一旦内核的序列缺失或时序不对,屏幕就废了。
3.2 ST7701S 这类屏的初始化序列怎么搬运
Rockchip 的内核里,很多 SDK 通过rockchip,panel-init-sequence这个属性直接在设备树里描述初始化序列,由 panel-simple 的 Rockchip 变体解析执行。格式很有规律:
- 第一个字节:命令类型,0x05 表示短包不带参数,0x15 表示短包带参数,0x39 表示长包。
- 第二个字节:写完命令后的延时时长,单位是 ms。
- 第三个字节:后面携带的数据长度。
- 之后是真正的命令和数据。
给一段简化的示例:
panel-init-sequence = [ 05 78 01 11 05 14 01 36 39 0a 03 FF 77 01 05 96 01 29 ];第一行表示发送命令 0x11(退出睡眠模式),然后延时 120ms;第二行命令 0x36(设置扫描方向),延时 20ms;第三行是长包命令,写入三个参数;最后发送 0x29(打开显示),延时 150ms。
很多人直接把 uboot 里能正常显示的初始化序列复制到内核设备树,却发现不行。原因通常是延时字段被改了,或者长包里的数据长度写错。ST7701S 这类屏对时序非常敏感:sleep out 之后如果没有足够的延时,后续命令会全部丢失,屏幕就会停在“初始化一半”的状态,表现出来就是黑屏或者异常条纹。
3.3 怎么确认初始化序列到底执行了没有
不要靠猜,直接看日志。正常的初始化过程里,dmesg 会打印类似下面的信息:
st7701s dsi-panel: MIPI DSI init sequence applied具体字符串每个驱动都不一样。更通用的方法是确认有没有 error 级别的 DCS 写失败:
dmesg | grep -iE "mipi_dsi_dcs_write|failed to write"如果在日志里看到mipi_dsi_dcs_write returned -22或者-EIO,说明链路根本没建立起来,或者序列本身有问题。如果没有任何报错,但屏幕还是黑的,我建议用 modetest 强制触发一次 mode set,看看是“一直黑”还是“能亮一下”。这一步能把问题分为“初始化序列问题”和“内核根本没有触发 mode set”两类,排查方向完全不同。
4. 背光与供电时序:黑屏的首席背锅侠
4.1 PWM 背光配置最常见的大坑:默认亮度为 0
这一条值得单独拎出来说。RK3568 平台上用 pwm-backlight 很常见,节点大概长这样:
backlight: backlight { compatible = "pwm-backlight"; pwms = <&pwm12 0 1000000 0>; brightness-levels = <0 10 20 30 40 50 60 70 80 90 100>; default-brightness-level = <9>; enable-gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_HIGH>; };如果 default-brightness-level 设成 0,或者根本没有这个属性,背光就会以最低亮度点亮,在白天环境下看起来和黑屏几乎没有区别。我之前排查过一个“内核起来黑屏”的问题,折腾了两天,最后发现只是 brightness-levels 里最小档是 0,默认档也是 0,PWM 确实有输出,但占空比接近零。
所以无论调试什么问题,第一步先把 default-brightness-level 调到最大那一档,排除“亮度太低”这种乌龙。这个操作成本最低,收益却很大。
4.2 reset 脚和电源时序:让屏幕在正确的状态进入工作
好多 MIPI 屏对时序非常挑剔:VCI 上电之后要等 10ms,RESX 拉低保持至少 10ms,再拉高,等待 120ms,然后才允许通过 MIPI 发送任何命令。如果 reset 极性反了,reset-gpios 设为 ACTIVE_HIGH 但实际屏是低有效,屏幕会一直被按在 reset 状态,无论发什么命令都不理你。
如果你在 uboot 和内核两边都看到屏幕能起来,但每次进内核后就黑,可以重点查一下内核的 GPIO 请求是不是成功了。有些 GPIO 被其他驱动占用,也会导致 reset 从来没被正确释放。日志里一般会有gpio ... already requested之类的警告,别忽略。
供电时序也一样,很多屏幕有两个电源域:模拟电源 AVDD 和数字电源 VDDIO。设备树里分别用 power-supply 和 vddio-supply 描述。如果 VDDIO 晚于 AVDD,甚至一直没有起来,屏幕的 IO 状态就是不确定的。在调试阶段,我习惯直接在板子上用万用表量这几个电压,确认时序无误后,再回过头怀疑软件。
4.3 手电筒法物理排除
背光亮不亮,和屏幕有没有画面,是两件事。遇到“背光不亮”的黑屏,先用强光手电筒贴着屏幕照,如果能看到隐约的内容或者操作界面的变化,说明 LCD 其实已经点亮了,只是背光通路有问题。这个方法虽然原始,但极其有效。
手电筒照不到内容的,再看 MIPI 链路。有条件的话用示波器探 MIPI 的 clock lane,看有没有连续时钟输出。有波形但黑屏,通常是初始化序列或 panel 状态问题;完全没有波形,问题就往 VOP 路由、DSI 控制器时钟配置方向查。这也是为什么调试圈里经常搜索“mipi时钟信号示波器波形”的原因——看到波形,心里就有底。
5. modetest 与 DRM 调试节点:把面板踢一脚验证真伪
5.1 modetest 强制触发一次 mode set
当怀疑是“内核没主动输出”而不是“panel 坏了”时,modetest 是最好用的探针。libdrm 自带的这个工具,几乎能覆盖所有 DRM 调试场景。用法很简单:
modetest -M rockchip -p先列出所有 connector、encoder、crtc 和 modes。比如看到 DSI-1 连接的是 720x1280 的屏,再手动指定 connector id 触发一次 mode set:
modetest -M rockchip -s 78:720x1280这里的 78 是 DSI-1 的 connector id,在实际板子上会不同,以上一条命令的显示为准。
如果在 modetest 强制输出后屏幕亮了,说明 panel 驱动、初始化序列、VOP 配置都没有问题,黑屏只是系统启动时没有触发 mode set——这时候去查开机 logo 继承、fbcon 或者应用层的 framebuffer 使能逻辑。如果 modetest 也点不亮,那就别怀疑系统层了,专心查设备树、硬件链路和初始化序列。
注意,rootfs 里不一定预装了 modetest。如果是在开发板阶段,我一般直接交叉编译 libdrm 里的 modetest 放进去,或者用 Buildroot 打开BR2_PACKAGE_LIBDRM的测试工具选项。这个工具早装晚装都要装,省得排障时干瞪眼。
5.2 debugfs summary:DRM 驱动自己汇报状态
Rockchip 的 DRM 驱动在 debugfs 下提供了很多节点,最常用的是:
cat /sys/kernel/debug/dri/0/summary输出内容大概会有 VOP2 各个 VP 的状态:
VOP2 VP0: enable=1, mode=720x1280@60, connectors=DSI-1 plane [0]: win0, enabled=1, fmt=XR24 plane [1]: cursor, enabled=0如果 enable=1,说明 VP 已经工作;enable=0,说明 route 没有成功或者 mode set 没触发。另外像/sys/kernel/debug/dri/0/state也能看到整个 DRM 状态机。在嵌入式板子上,这些 debugfs 节点比什么工具都直接。
需要提醒的是,部分内核配置需要打开CONFIG_DRM_ROCKCHIP_DEBUG或CONFIG_DEBUG_FS才有这些节点。如果 cat 不到,优先检查内核配置,而不是怀疑路径错了。
5.3 dmesg 关键字速查表
在调试中我经常反复搜索下面几个关键字,整理成表:
| 关键字 | 含义 | 故障方向 |
|---|---|---|
| bound 0x...vop | VOP2 组件绑定成功 | 正常,继续查下一环 |
| failed to bind | 组件绑定失败 | route/端口配置问题 |
| No panel found | 没有匹配的 panel | compatible 不匹配 |
| mipi_dsi_dcs_write failed | DSI 写命令失败 | 链路或初始化序列问题 |
| get-clk failed | 时钟获取失败 | dts 时钟配置问题 |
| unable to request gpio | GPIO 被占用 | 硬件资源冲突 |
日志不可能替你解决全部问题,但它能帮你在最短时间内把排查范围缩小到某一层。真正动手改设备树之前,把日志里这几类关键字过一遍,能避免很多重复劳动。
6. 修复实例复盘:从 LOGO 正常到黑屏再到稳定显示
6.1 项目背景与现象
这块板子是 RK3568 配了一块 6.5 寸的 ST7701S 方案 MIPI 屏,分辨率 720x1280,RGB 接口转 MIPI 的模组。烧完固件后,uboot 阶段 logo 显示正常,进入内核之后屏幕直接黑掉。串口里系统已经启动完成,也能登录,DTS 里的 route_dsi0 也已经确认是 okay。于是我开始了完整的排查流程。
6.2 第一次修改:先把背光最大亮度拉起来
刚开始我并没有急着看 debugfs,而是先验证现象。cat /sys/class/drm/card0-DSI-1/status能看到 connected,modetest 手动触发 mode set 也能看到一条 mode,但是屏幕始终没有光。拿手电筒照屏幕,隐约能看到功能界面的轮廓——这几乎可以确定 LCD 本身已经在接收画面,只是背光没动作。
查 backlight 节点,发现 default-brightness-level 被设成了 0。我把默认值调到 90% 位置,重新烧录后再看,屏幕直接亮起来了。亮度问题虽然简单,但它藏得很深,因为日志里 PWM 驱动没有任何报错,看起来“背光正常”。
6.3 第二次反复:初始化序列里的延时被拍扁了
背光亮了之后,画面仍然不稳定。每次重启,有时候能正常显示,有时候起一半就黑掉,而且黑掉的时候 dmesg 里会出现:
st7701s dsi-panel: mipi_dsi_dcs_write returned -22这个报错直接指向初始化序列写入失败。我对比了屏幕模组厂提供的初始化代码和内核里rockchip,panel-init-sequence的配置,发现一个关键差异:模组厂给的序列在 sleep out 之后要求延时 120ms,设备树里却写的是 5ms。也就是说,屏幕还没从睡眠状态缓过来,后续命令就已经发过去了,要么被丢弃,要么解析错误。
我把所有关键命令后的延时按模组规格书重新对齐,尤其是 0x11(sleep out)之后的 120ms 和 0x29(display on)之前的 150ms,然后再次烧录测试。这次重启多次都稳定显示,黑屏问题彻底消失。
6.4 复盘:为什么一开始没有发现
这个案例里最大的教训是:不要因为 uboot 阶段显示正常,就觉得初始化序列一定是好的。uboot 和内核使用了两套驱动、两份设备树、两段初始化流程,哪怕同一块屏,两边的配置也可能不同。我在初始阶段被“uboot logo 正常”误导,浪费了不少时间在检查 VOP 路由和硬件链路上,而真正问题反而在初始化序列的延时参数里。
另一个教训是:看到 dmesg 报错再动手,要看完整上下文,不要只盯第一条错误。mipi_dsi_dcs_write returned -22这个报错出现之后,后面跟着的往往是几十条连续失败,但其实源头是第一二条命令的时序不对。把第一处修好,后面的问题自然消失。
最后再补一个实际调试经验:遇到“uboot 亮、内核黑”的问题,不要急着改内核配置,先把三类证据抓到手——屏幕当前是完全没有画面还是只有背光问题、dmesg 里有没有 DRM/DSI/panel 相关报错、modetest 能不能强制点亮。这三类证据基本就决定了问题在设备树路由、初始化序列还是背光配置。我后来在 RK3588 的另一个项目上遇到类似情况,也是按这个顺序定位到 panel reset 极性错误。流程本身不复杂,关键是别被“uboot 能亮”这个假象带偏。