3. 显示控制器设备树绑定

设备树(Device Tree)是描述硬件拓扑与资源的关键数据结构。对于显示控制器(Display Controller)而言,设备树节点不仅需要声明寄存器地址、中断号等基础资源,还必须精确描述时钟/复位/电源域依赖、以及显示流水线中多个IP模块之间的数据连接关系。本章将围绕通用显示控制器的设备树绑定规范,从节点编写、资源属性到端口/端点定义进行系统讲解。

核心要点: 显示控制器设备树绑定的三个核心要素——基础资源声明(reg、interrupts)、电源管理属性(clocks、resets、power-domains)、拓扑连接描述(ports/endpoints)。三者缺一不可,共同构成KMS驱动正确初始化的硬件描述基础。

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() 关联,确保在显示控制器工作时电源域处于开启状态。

电源域依赖链: 显示控制器的电源域可能进一步依赖上层电源域(如 SoC 的 Display Subsystem 域)。设备树中只需声明直接依赖的电源域,内核 PM 框架会自动处理层级依赖关系。在调试时,可通过 /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>
多路复用场景: 当显示控制器支持多种输出接口(如同时支持 HDMI 和 DP)时,可以在同一个 port@0 下定义多个 endpoint,每个 endpoint 对应一个下游接口。驱动在运行时根据实际连接的显示设备选择激活哪个 endpoint。这种设计避免了为每种输出组合编写独立的设备树节点。

3.4 显示控制器设备树拓扑结构图

以下 SVG 图展示了一个典型的显示控制器设备树拓扑结构,包含时钟/复位/电源域依赖关系以及端口/端点连接关系:

显示控制器设备树拓扑结构 显示控制器设备树拓扑结构 display-controller reg / interrupts / compatible clkctrl (时钟控制器) pclk / aclk / core_clk resetctrl (复位控制器) core_rst pd_display (电源域) PM domain port@0 (输出端口) reg = <0> endpoint@0 → hdmi_in endpoint@1 → dp_in port@1 (Writeback端口) reg = <1> writeback_out → wb_engine_in HDMI TX hdmi_in endpoint DP TX dp_in endpoint Writeback Engine wb_engine_in 说明: 每个 port 代表一个独立的数据流方向,每个 endpoint 代表一个具体的连接目标。 remote-endpoint 属性建立双向连接,驱动通过 graph 辅助函数遍历拓扑。

3.5 驱动中解析设备树端点的典型流程

KMS 驱动在 probe 阶段通过以下步骤解析设备树中的端口/端点信息,建立显示流水线拓扑:

  1. 获取端口节点: 使用 of_graph_get_port_by_id() 或 of_graph_get_next_port() 遍历所有 port 子节点。
  2. 获取端点节点: 对每个 port,使用 of_graph_get_next_endpoint() 遍历其下的 endpoint 子节点。
  3. 解析远程端点: 通过 of_graph_get_remote_endpoint() 获取 remote-endpoint 指向的 phandle,再通过 of_graph_get_remote_port_parent() 获取对端设备节点。
  4. 建立连接结构: 将解析结果填充到自定义的连接结构体中,供 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>;,避免继承父节点的地址空间配置。