前言
最近在调试 RK3588 平台的显示输出时遇到个头疼的问题:用 HDMI 转 DVI 转接器连显示器,冷启动就黑屏,但拔掉 HDMI 线重新插上就好了。HDMI 直连显示器完全没问题。折腾了好几天,试了各种方案,最后发现居然只要改一行设备树就搞定了。记录一下排查过程,希望能帮到遇到类似问题的朋友。
问题现象
- 硬件环境: RK3588 开发板,HDMI0 和 HDMI1 两个输出口
- 问题表现 :
- HDMI 转 DVI 转接器连接显示器,冷启动黑屏
- 拔掉 HDMI 线重新插上,显示正常
- HDMI 直连显示器,一切正常
- 影响范围: HDMI0 和 HDMI1 都有这个问题
排查过程
第一步:抓日志分析
先看启动日志,发现 HPD (热插拔检测) 信号是正常的:
[ 0.xxx] rockchip-hdmi fde80000.hdmi: HDMI0: HPD detected
但是 EDID 读取有问题,经常读不到或者读取失败。这就奇怪了,HPD 都检测到了,为啥 EDID 读不出来?
第二步:对比 HDMI 直连和 DVI 转接器
用示波器测了一下时序,发现问题了:
- HDMI 直连: HPD 信号和 I2C (DDC) 几乎同时就绪,300ms 内搞定
- DVI 转接器: HPD 信号 300ms 就来了,但 I2C 总线要 1-2 秒才稳定
这就是问题所在!系统启动时检测显示器,HPD 有信号就去读 EDID,但这时候 DVI 转接器的 I2C 还没初始化好,读不到 EDID,系统就认为没显示器。
第三步:尝试驱动层修改(弯路)
一开始想在驱动里加延时重试机制,试了好几个方案:
-
HPD 多次检测 : 改
dw_hdmi_rk3588_read_hpd()函数,循环检测 10 次,每次间隔 100ms- 结果:没用,HPD 本来就是好的
-
延迟 reprobe: 启动 1 秒后再触发一次热插拔事件,强制重新读 EDID
- 结果:有点效果,但不稳定
-
强制路由: 强制 HDMI0 只能用 vp0,防止系统乱选
- 结果:治标不治本
折腾了好几天,驱动改得乱七八糟,问题还是时好时坏。
第四步:回到设备树(正解)
冷静下来重新看了一遍设备树配置,发现了关键点:
父设备树 (rk3588-nvr-demo.dtsi):
&route_hdmi0 {
status = "okay";
force-output; // 注意这个属性
connect = <&vp2_out_hdmi0>;
};
子设备树 (rk3588-nvr-demo-v10-android-a5.dts):
&route_hdmi0 {
status = "okay";
connect = <&vp0_out_hdmi0>;
/delete-property/ force-output; // 把父设备树的 force-output 删了
/delete-node/ force_timing;
};
看到 /delete-property/ force-output 这行,突然想起来,force-output 是干啥的?
翻了一下 rockchip_drm_logo.c 的代码:
if (force_output)
connector->force = DRM_FORCE_ON;
if (connector->force) {
if (connector->force == DRM_FORCE_ON ||
connector->force == DRM_FORCE_ON_DIGITAL)
connector->status = connector_status_connected; // 强制标记为已连接
...
}
原来 force-output 会强制把 connector 标记为已连接状态,跳过 HPD 检测!这不就是我们需要的吗?
而且看代码,启动完成后会自动清除这个标记:
list_for_each_entry_safe(set, tmp, &mode_set_list, head) {
if (set->force_output)
set->sub_dev->connector->force = DRM_FORCE_UNSPECIFIED; // 恢复正常
...
}
也就是说,force-output 只在启动时生效,启动完成后会自动恢复,不影响热插拔!
解决方案
只需要删除设备树中的一行代码!
修改文件
android/kernel-5.10/arch/arm64/boot/dts/rockchip/rk3588-nvr-demo-v10-android-a5.dts
修改前
&route_hdmi0 {
status = "okay";
connect = <&vp0_out_hdmi0>;
/delete-property/ force-output; // 删掉这行
/delete-node/ force_timing;
};
&route_hdmi1 {
status = "disabled";
connect = <&vp1_out_hdmi1>;
/delete-property/ force-output; // 删掉这行
/delete-node/ force_timing;
};
修改后
&route_hdmi0 {
status = "okay";
connect = <&vp0_out_hdmi0>;
/delete-node/ force_timing;
};
&route_hdmi1 {
status = "disabled";
connect = <&vp1_out_hdmi1>;
/delete-node/ force_timing;
};
就这么简单,把 /delete-property/ force-output; 这行删掉,让子设备树继承父设备树的 force-output 属性。
编译测试
cd android/kernel-5.10
make ARCH=arm64 rockchip_defconfig
make ARCH=arm64 dtbs -j8
烧录测试,完美解决!
原理说明
为什么有效?
-
启动阶段:
force-output属性生效- 系统强制将 HDMI 标记为"已连接"
- 不管 EDID 读没读到,都会初始化显示输出
- DVI 转接器的 I2C 慢慢初始化,系统会等它
-
启动完成后:
- DRM 框架自动清除
force-output标记 - 恢复正常的 HPD 检测机制
- 热插拔功能完全正常
- DRM 框架自动清除
为什么之前删掉了 force-output?
看了一下 git 历史,之前删掉 force-output 是为了支持多屏输出,避免启动时强制激活所有输出。但这样就导致 DVI 转接器的时序问题暴露出来了。
其实 force-output 只在启动时生效,启动完成后会自动清除,并不影响多屏切换。所以保留它是没问题的。
测试结果
- ✅ HDMI0 + DVI 转接器:冷启动正常
- ✅ HDMI1 + DVI 转接器:冷启动正常(通过 dpms 启用后)
- ✅ HDMI0 直连:正常
- ✅ HDMI1 直连:正常
- ✅ 热插拔:完全正常
- ✅ 多屏切换:正常
经验总结
-
遇到显示问题,先看设备树
- 很多显示相关的配置都在设备树里
- 驱动层的修改往往治标不治本
-
理解硬件时序差异
- HDMI 和 DVI 虽然兼容,但时序特性不同
- DVI 转接器的 I2C 初始化比 HPD 慢很多
-
善用现有机制
force-output就是为了解决这类问题设计的- 不要一上来就改驱动,先看看有没有现成的配置项
-
测试要全面
- 不仅要测 DVI 转接器,还要测 HDMI 直连
- 不仅要测冷启动,还要测热插拔
- 不仅要测单屏,还要测多屏切换
参考资料
- Rockchip DRM 驱动文档
- Linux DRM 子系统文档
- RK3588 TRM (Technical Reference Manual)
写在最后
这个问题前前后后折腾了好几天,改了一堆驱动代码,最后发现只要删一行设备树就搞定了。有时候问题的解决方案往往比想象中简单,关键是要理解底层原理,找对方向。
希望这篇文章能帮到遇到类似问题的朋友。如果有疑问或者更好的解决方案,欢迎在评论区交流!
环境信息:
- 平台: RK3588
- 内核: Linux 5.10
- Android 版本: Android 12
- 问题芯片: HDMI 转 DVI 转接器
关键字: RK3588, HDMI, DVI, 黑屏, force-output, 设备树, DTS