3. 显示控制器设备树绑定
设备树(Device Tree)是描述硬件拓扑与资源的关键数据结构。对于显示控制器(Display Controller)而言,设备树节点不仅需要声明寄存器地址、中断号等基础资源,还必须精确描述时钟/复位/电源域依赖、以及显示流水线中多个IP模块之间的数据连接关系。本章将围绕通用显示控制器的设备树绑定规范,从节点编写、资源属性到端口/端点定义进行系统讲解。
3.1 通用显示控制器设备树节点结构
一个标准的显示控制器设备树节点通常包含以下字段。以ARM Mali Display Processor(DP500/DP550/DP650)和Synopsys DesignWare HDMI TX为例,节点结构如下:
display_controller: display-controller@xxx00000 {
compatible = "vendor,display-controller-ip", "generic-display-controller";
reg = <0x0 0x10000000 0x0 0x10000>,
<0x0 0x10010000 0x0 0x2000>;
reg-names = "core", "gamma_lut";
interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "vsync";
clocks = <&clock_gates>;
clock-names = "pclk", "aclk", "core_clk", "axi_clk";
resets = <&reset_controller 0>;
reset-names = "core_rst";
power-domains = <&pd_display>;
/* 显示流水线端口定义 */
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
display_out: endpoint {
remote-endpoint = <&hdmi_in>;
};
};
};
};
| 属性 | 类型 | 说明 | 是否必需 |
|---|---|---|---|
compatible |
string list | 兼容性字符串,驱动匹配依据 | 是 |
reg |
u64/u32 数组 | 寄存器地址与长度,支持多段 | 是 |
reg-names |
string list | 寄存器段名称,与reg一一对应 | 推荐 |
interrupts |
u32 数组 | 中断号与触发类型 | 是 |
clocks |
phandle 数组 | 时钟源句柄列表 | 是 |
clock-names |
string list | 时钟名称,驱动通过名称获取 | 是 |
resets |
phandle 数组 | 复位控制器句柄 | 推荐 |
power-domains |
phandle 数组 | 电源域句柄 | 推荐 |
ports |
node | 端口描述子节点,定义数据流拓扑 | 是 |
reg 属性时,建议将显示控制器的核心寄存器、Gamma LUT、Writeback 等独立功能块拆分为多段 reg,并配合 reg-names 命名。这有助于驱动在 probe 阶段按名称映射 ioremap,提高代码可读性与可维护性。
3.2 时钟、复位与电源域属性详解
显示控制器通常需要多个时钟域协同工作:APB 总线时钟(pclk)、AXI 数据总线时钟(aclk)、核心处理时钟(core_clk)、像素时钟(pixel_clk)等。复位属性确保硬件在驱动加载前处于已知状态。电源域属性则用于控制显示控制器所在电源域的开关,实现系统级功耗管理。
3.2.1 时钟属性编写规范
clocks = <&clkctrl CLK_DISPLAY_PCLK>,
<&clkctrl CLK_DISPLAY_ACLK>,
<&clkctrl CLK_DISPLAY_CORE>,
<&clkctrl CLK_DISPLAY_PIXEL>;
clock-names = "pclk", "aclk", "core_clk", "pixel_clk";
clock-frequency = <0>, <0>, <400000000>, <148500000>;
- pclk:APB 配置总线时钟,用于寄存器读写,通常为 50-100 MHz。
- aclk:AXI 数据总线时钟,用于帧缓冲数据搬运,通常为 200-400 MHz。
- core_clk:显示核心处理时钟,负责图层混合、缩放等操作。
- pixel_clk:像素时钟,与输出分辨率/刷新率直接相关,例如 1080p60 对应 148.5 MHz。
clock-frequency 属性直接写在设备树节点中,期望内核自动配置时钟频率。实际上,clock-frequency 仅用于固定频率时钟的声明,对于可编程时钟源,驱动应通过 clk_set_rate() 在运行时设置。设备树中声明 clock-frequency 仅作为参考值,不应替代驱动中的时钟配置逻辑。
3.2.2 复位与电源域
resets = <&resetctrl DISPLAY_RESET>;
reset-names = "core_rst";
power-domains = <&pd_display>;
power-domain-names = "display";
复位属性通常指向系统复位控制器(reset-controller)的某个输出线。驱动在 probe 阶段通过 devm_reset_control_get() 获取复位句柄,并在初始化前执行 reset_control_deassert() 释放复位。电源域属性则指向系统 PM 域控制器,驱动通过 dev_pm_domain_attach() 关联,确保在显示控制器工作时电源域处于开启状态。
/sys/kernel/debug/pm_genpd/pm_genpd_summary 查看电源域状态。
3.3 端口(port)与端点(endpoint)定义
端口与端点是设备树中描述显示流水线拓扑连接的核心机制。一个显示控制器可能包含多个端口:port@0 通常用于输出到外部显示接口(HDMI/DP/LVDS),port@1 可能用于写入后端(Writeback),port@2 可能用于输入捕获(Capture)。每个端口下可包含一个或多个端点,用于连接不同的下游设备。
3.3.1 端口定义规范
ports {
#address-cells = <1>;
#size-cells = <0>;
/* 输出端口:连接外部显示接口 */
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
display_out_hdmi: endpoint@0 {
reg = <0>;
remote-endpoint = <&hdmi_in>;
};
display_out_dp: endpoint@1 {
reg = <1>;
remote-endpoint = <&dp_in>;
};
};
/* 写入后端端口:用于屏幕截图或合成输出 */
port@1 {
reg = <1>;
writeback_out: endpoint {
remote-endpoint = <&wb_engine_in>;
};
};
};
3.3.2 端点属性详解
| 属性 | 类型 | 说明 | 示例 |
|---|---|---|---|
remote-endpoint |
phandle | 指向对端设备的端点句柄 | <&hdmi_in> |
bus-type |
u32 | 总线类型枚举(0: RGB, 1: LVDS, 2: DSI, 3: HDMI) | <3> |
data-lanes |
u32 数组 | 数据通道映射(用于 MIPI DSI/CSI) | <1 2 3 4> |
clock-lanes |
u32 | 时钟通道索引 | <0> |
lane-polarity |
u32 数组 | 通道极性反转标志 | <0 0 1 0> |
3.4 显示控制器设备树拓扑结构图
以下 SVG 图展示了一个典型的显示控制器设备树拓扑结构,包含时钟/复位/电源域依赖关系以及端口/端点连接关系:
3.5 驱动中解析设备树端点的典型流程
KMS 驱动在 probe 阶段通过以下步骤解析设备树中的端口/端点信息,建立显示流水线拓扑:
- 获取端口节点: 使用
of_graph_get_port_by_id()或of_graph_get_next_port()遍历所有 port 子节点。 - 获取端点节点: 对每个 port,使用
of_graph_get_next_endpoint()遍历其下的 endpoint 子节点。 - 解析远程端点: 通过
of_graph_get_remote_endpoint()获取remote-endpoint指向的 phandle,再通过of_graph_get_remote_port_parent()获取对端设备节点。 - 建立连接结构: 将解析结果填充到自定义的连接结构体中,供 KMS 的
bridge或connector初始化使用。
static int display_controller_parse_graph(struct device *dev,
struct display_controller *dc)
{
struct device_node *port, *endpoint, *remote;
int ret = 0;
/* 遍历所有端口 */
for_each_child_of_node(dc->of_node, port) {
if (!of_node_name_eq(port, "port"))
continue;
/* 遍历端口下的所有端点 */
for_each_child_of_node(port, endpoint) {
if (!of_node_name_eq(endpoint, "endpoint"))
continue;
/* 获取远程端点父设备 */
remote = of_graph_get_remote_port_parent(endpoint);
if (!remote) {
dev_warn(dev, "endpoint %pOF has no remote\n", endpoint);
continue;
}
/* 根据端口号注册对应的 bridge/connector */
ret = register_remote_device(dev, dc, port, remote);
of_node_put(remote);
if (ret)
goto err;
}
}
return 0;
err:
of_node_put(port);
return ret;
}
of_graph_dump() 函数(需开启 CONFIG_OF_GRAPH)打印完整的设备树图拓扑信息。该函数会递归遍历所有 port/endpoint 节点,输出远程连接关系,是排查设备树连接错误的利器。
3.6 常见设备树绑定错误与排查方法
| 错误类型 | 现象 | 排查方法 |
|---|---|---|
remote-endpoint 指向错误 |
驱动 probe 失败,graph 解析返回 NULL | 检查 phandle 是否拼写正确,使用 dtc -I dtb -O dts 反编译验证 |
| 时钟名称不匹配 | clk_get 返回 -ENOENT | 核对 clock-names 与驱动中 devm_clk_get() 使用的名称 |
| 电源域未定义 | pm_runtime_get_sync 返回 -EACCES | 确认 power-domains 属性存在且 phandle 有效 |
| 端口 reg 地址冲突 | of_graph_get_port_by_id 返回错误端口 | 确保每个 port 的 reg 值唯一且与驱动逻辑一致 |
#address-cells 和 #size-cells 必须在 ports 节点中正确设置。如果父节点(display-controller)的 #address-cells 为 2,而 ports 子节点未重新声明,会导致 port 的 reg 解析错误。建议在 ports 节点中显式声明 #address-cells = <1>; #size-cells = <0>;,避免继承父节点的地址空间配置。