3. 显示驱动基础
设备树配置,说白了就是告诉内核:「你的硬件长什么样,怎么叫醒它干活」。R-Car 的显示驱动移植,设备树写不对,后面全是白搭。我刚开始接触 R-Car 时,就吃过这个亏——时钟配错一个 parent,屏幕死活不亮,查了三天才发现是设备树里一个 clock-names 写错了。
这一章,我们直接动手。我会把 DU(Display Unit)的节点掰开揉碎,时钟复位怎么配,LVDS 和 HDMI 的 output 端口怎么接,全部过一遍。
3.1 显示控制器(DU)设备树节点详解
R-Car 的显示控制器在设备树里叫 du。不同 SoC 的 DU 实例数量不一样,比如 R-Car H3 有 3 个 DU 通道,M3 有 2 个。但节点结构是统一的。
先看一个典型的 DU 节点骨架:
&du {
clocks = <&cpg CPG_MOD 724>,
<&cpg CPG_MOD 723>,
<&cpg CPG_MOD 722>,
<&cpg CPG_MOD 721>,
<&cpg CPG_MOD 727>;
clock-names = "du.0", "du.1", "du.2", "du.3", "lvds.0";
resets = <&cpg 724>, <&cpg 723>, <&cpg 722>, <&cpg 721>;
reset-names = "du.0", "du.1", "du.2", "du.3";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
du_out_rgb: endpoint {
remote-endpoint = <&rgb_connector>;
};
};
port@1 {
reg = <1>;
du_out_lvds0: endpoint {
remote-endpoint = <&lvds0_in>;
};
};
port@2 {
reg = <2>;
du_out_hdmi: endpoint {
remote-endpoint = <&hdmi_in>;
};
};
};
};
这里有几个关键点,我一个个说。
3.1.1 clocks 与 clock-names
DU 的时钟源不止一个。每个 DU 通道有自己的时钟门控,LVDS 也有独立的 PLL 时钟。你看上面的例子,5 个 clock 分别对应 4 个 DU 通道 + 1 个 LVDS。
| clock-names | 对应模块 | CPG 模块 ID(H3 为例) |
|---|---|---|
| du.0 | DU 通道 0 | 724 |
| du.1 | DU 通道 1 | 723 |
| du.2 | DU 通道 2 | 722 |
| du.3 | DU 通道 3 | 721 |
| lvds.0 | LVDS PLL | 727 |
3.1.2 resets 与 reset-names
复位配置和时钟是一一对应的。每个 DU 通道在使能时钟前,必须先释放复位。顺序不能乱——先复位,再时钟,否则硬件会挂。
我遇到过一个问题:复位配了,但 reset-names 写成了 "du0" 而不是 "du.0",少了个点。内核驱动匹配不到,复位一直没释放,DU 模块直接罢工。这种坑,你想想看,查 log 都查不出来,因为驱动根本没报错,只是默默不干活。
3.1.3 ports 输出端口
每个 port 对应一个 DU 通道的物理输出。port@0 通常是 RGB 并行接口,port@1 是 LVDS,port@2 是 HDMI。但这不是绝对的,具体要看 SoC 的 pinmux 和 board 设计。
每个 port 里有一个 endpoint,通过 remote-endpoint 连接到下游的显示接口节点。这个连接关系,就是整个显示链路的数据流走向。
3.2 时钟与复位配置实战
时钟配置是设备树里最容易出错的地方。我建议你按照这个步骤来检查:
- 确认 SoC 的 DU 通道数量 —— 查 datasheet 或参考设备树。
- 找到对应的 CPG 模块 ID —— 在
clk-rcar-gen3.c或类似文件中可以找到。 - 按顺序列出 clocks —— 先 DU 通道,再 LVDS PLL。
- 配齐 resets —— 数量和顺序与 clocks 保持一致。
- 检查 clock-names 和 reset-names —— 名字必须和驱动里定义的字符串完全一致。
du_probe 函数,看它调用了 devm_clk_get 时传的参数是什么。驱动怎么写,设备树就怎么配。
举个例子,R-Car H3 的 DU 驱动里,clock 获取代码大概是这样的:
du->clocks[i] = devm_clk_get(&pdev->dev, clock_names[i]);
而 clock_names 数组定义在 rcar_du_of_clks 里:
static const char *const rcar_du_of_clks[H3_NUM_DU_CHANNELS + 1] = {
"du.0", "du.1", "du.2", "du.3", "lvds.0"
};
看到没?名字必须一模一样。多一个空格都不行。
3.3 LVDS 输出接口配置
LVDS 在 R-Car 上通常用于连接 LCD 屏。配置 LVDS 接口,需要两个部分:DU 端的输出端口,和 LVDS 编码器节点。
先看 LVDS 编码器节点:
&lvds0 {
clocks = <&cpg CPG_MOD 727>;
clock-names = "fck";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
lvds0_in: endpoint {
remote-endpoint = <&du_out_lvds0>;
};
};
port@1 {
reg = <1>;
lvds0_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
};
};
这里有两个 port:
- port@0 —— 输入口,连接 DU 的 LVDS 输出。
- port@1 —— 输出口,连接实际的 LCD 屏。
我曾经犯过一个错:把 port 的 reg 写反了。port@0 配成了输出,port@1 配成了输入。结果内核在解析 endpoint 时,发现数据流方向不对,直接报错 EPROBE_DEFER,驱动加载失败。查了半天才发现是 reg 顺序搞反了。
3.4 HDMI 输出接口配置
HDMI 配置比 LVDS 复杂一些,因为涉及 HDMI 编码器(如 ADV7511 或 SiI9022)的 I2C 配置。R-Car 的 DU 输出的是并行 RGB 或数字视频信号,需要外接 HDMI 编码器转成 HDMI 协议。
设备树里,HDMI 编码器通常是一个 I2C 设备节点:
&i2c2 {
hdmi-encoder@39 {
compatible = "adi,adv7511";
reg = <0x39>;
interrupt-parent = <&gpio1>;
interrupts = <15 IRQ_TYPE_LEVEL_LOW>;
clocks = <&cpg CPG_MOD 722>;
clock-names = "pixel";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
hdmi_in: endpoint {
remote-endpoint = <&du_out_hdmi>;
};
};
port@1 {
reg = <1>;
hdmi_out: endpoint {
remote-endpoint = <&hdmi_connector>;
};
};
};
};
};
这里要注意几个点:
- compatible —— 必须和驱动匹配。ADV7511 用 "adi,adv7511",SiI9022 用 "sil,sii9022"。
- reg —— I2C 地址,查编码器 datasheet。
- pixel clock —— HDMI 编码器需要像素时钟,通常来自 DU 的某个通道时钟。
3.5 完整的数据流链路
把上面所有节点串起来,整个显示链路是这样的:
从 DU 出来,数据流向 LVDS 或 HDMI 编码器,再到达最终的显示设备。每个环节的 endpoint 必须一一对应,少一个或者配错一个,屏幕都不会亮。
3.6 常见问题与排查方法
设备树配完了,屏幕不亮怎么办?我总结了几种常见情况:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 内核启动 log 无 DU 相关输出 | 设备节点未使能,或 compatible 不匹配 | 检查 status = "okay" 是否添加 |
| 屏幕全黑,无背光 | 时钟或复位配置错误 | 检查 /sys/kernel/debug/clk 下时钟是否使能 |
| 屏幕有背光但无画面 | endpoint 连接错误 | 检查 remote-endpoint 的 phandle 是否正确 |
| 画面花屏或闪烁 | 像素时钟频率不匹配 | 用示波器测时钟频率,或调整 DU 的时钟 parent |
| HDMI 无信号 | HDMI 编码器 I2C 通信失败 | 用 i2cdetect 检查编码器地址是否响应 |
make dtbs 编译,然后用 dtc -I dtb -O dts 反编译确认生成的 DTS 内容。有时候语法错误不会报错,但生成的 DTB 是坏的。反编译看一眼,心里踏实。
嗯,设备树配置这块,说白了就是细心活。时钟、复位、端口、endpoint,每个字段都得对上。我刚开始做 R-Car 移植时,光设备树就改了十几版。但只要你把这一章的内容吃透了,后面调显示输出就会顺很多。