SA8155 DRM/KMS显示驱动开发与CRTC集成调试 - 第3节

3.1 KMS 驱动入口与 probe 函数

KMS 驱动的初始化始于 probe 函数。当内核发现设备树中匹配的显示控制器节点时,会调用驱动注册的 probe 回调。对于 SA8155 平台,probe 函数负责完成以下核心任务:

  • 分配 DRM 设备:调用 drm_dev_alloc() 创建 struct drm_device 实例。
  • 设置私有数据:将平台设备私有数据与 DRM 设备关联。
  • 初始化 component 框架:为后续子组件(如 DSI、HDMI TX)的绑定做准备。
  • 解析设备树:读取 reg、interrupts、clocks、ports 等关键属性。
  • 注册 DRM 驱动:调用 drm_dev_register() 使设备对外可见。
关键点:SA8155 的显示控制器在设备树中通常以 display-subsystem 为父节点,其子节点包括 mdss、dp、dsi 等。probe 函数必须确保所有子组件就绪后,才能完成模式设置的初始化。
KMS probe 函数执行流程 drm_dev_alloc() 设置私有数据 component 初始化 设备树解析 资源映射 (ioremap) 中断注册 drm_dev_register()

3.2 component 框架:解耦与同步

SA8155 显示子系统由多个硬件模块组成:MDP(显示处理)、DP(显示端口)、DSI(显示串行接口)等。这些模块的初始化顺序和依赖关系复杂,Linux 内核使用 component 框架 来管理它们的绑定与解绑。

3.2.1 核心数据结构

结构体作用关键字段
struct component表示一个子组件ops、dev、master
struct component_master表示主控设备(显示控制器)ops、match、dev
struct component_ops子组件的操作回调bind、unbind

3.2.2 工作流程

  1. 主控注册:probe 函数中调用 component_master_add_with_match(),传入匹配表。
  2. 子组件注册:每个子模块(如 DSI 控制器)的 probe 中调用 component_add()。
  3. 匹配与绑定:当所有子组件都注册后,主控的 bind 回调被触发,此时可以安全地初始化 CRTC、Encoder、Connector。
  4. 解绑:任一子组件移除时,主控的 unbind 被调用,释放资源。
设计意图:component 框架解决了“所有依赖设备都准备好后再初始化”的问题,避免了竞态条件和空指针访问。在 SA8155 上,MDP 作为 master,DSI 和 DP 作为 component。
component 框架绑定流程 MDP (Master) DSI (Component) DP (Component) HDMI (Component) 所有组件绑定 → bind() 回调

3.3 设备树解析与匹配

设备树(Device Tree)是描述硬件拓扑和配置的关键手段。KMS 驱动通过解析设备树节点来获取显示控制器的寄存器地址、中断号、时钟频率、端口连接关系等信息。

3.3.1 关键设备树节点

display-subsystem {
    compatible = "qcom,sdm845-display", "simple-bus";
    
    mdss: mdss@ae00000 {
        compatible = "qcom,sdm845-mdss";
        reg = <0x0ae00000 0x1000>;
        interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&gcc GCC_DISP_AHB_CLK>,
                 <&gcc GCC_DISP_AXI_CLK>;
        
        mdp: mdp@ae01000 {
            compatible = "qcom,sdm845-mdp5";
            reg = <0x0ae01000 0x84000>;
            interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
        };
        
        dsi0: dsi@ae94000 {
            compatible = "qcom,sdm845-dsi-ctrl";
            reg = <0x0ae94000 0x400>;
            clocks = <&mdss_dsi0_clk>;
            ports {
                #address-cells = <1>;
                #size-cells = <0>;
                port@0 {
                    reg = <0>;
                    dsi0_in: endpoint {
                        remote-endpoint = <&mdp_intf0_out>;
                    };
                };
            };
        };
    };
};

3.3.2 解析流程

步骤API 函数获取内容
1. 获取父节点of_parse_phandle()display-subsystem 句柄
2. 读取寄存器地址platform_get_resource()reg 属性 → struct resource
3. 映射 I/O 内存devm_ioremap_resource()虚拟地址空间
4. 获取中断platform_get_irq()中断号
5. 解析时钟devm_clk_get()时钟句柄
6. 解析端口of_graph_get_endpoint_by_regs()端口连接关系
常见陷阱:设备树中 reg 地址范围必须与硬件手册严格一致,否则 ioremap 会失败。另外,clocks 属性中的时钟名称需要与驱动中 clk_get 的字符串匹配,否则时钟框架无法正确使能。

3.3.3 匹配机制

驱动与设备树的匹配通过 of_match_table 实现。在 platform_driver 中定义:

static const struct of_device_id msm_mdp_of_match[] = {
    { .compatible = "qcom,sdm845-mdp5" },
    { .compatible = "qcom,sa8155-mdp5" },
    { }
};

static struct platform_driver mdp5_driver = {
    .probe  = mdp5_probe,
    .remove = mdp5_remove,
    .driver = {
        .name = "msm_mdp5",
        .of_match_table = msm_mdp_of_match,
    },
};

当内核遍历设备树节点时,会比对 compatible 字符串。匹配成功后调用 probe 函数,并将 struct platform_device 传入,其中包含了设备树解析后的资源信息。

设备树解析与匹配流程 设备树节点 of_match_table 比对 probe 函数调用 platform_get_resource() devm_ioremap_resource() devm_clk_get()

3.4 初始化流程总结

将 probe、component 框架、设备树解析三个环节串联起来,KMS 驱动的完整初始化流程如下:

  1. 平台驱动注册:module_platform_driver() 注册 MDP 驱动。
  2. 设备树匹配:内核根据 compatible 找到对应节点,调用 mdp5_probe()。
  3. DRM 设备创建:drm_dev_alloc() 分配 DRM 核心结构。
  4. 资源获取:通过设备树 API 获取寄存器、中断、时钟。
  5. component 主控注册:component_master_add_with_match() 等待子组件。
  6. 子组件绑定:DSI、DP 等驱动 probe 后调用 component_add()。
  7. bind 回调:所有组件就绪后,初始化 CRTC、Encoder、Connector。
  8. DRM 设备注册:drm_dev_register() 对外暴露显示能力。
性能考量:在 SA8155 上,MDP 的 probe 函数执行时间直接影响开机启动速度。建议将耗时的固件加载、时钟初始化等操作放在 bind 回调中,避免阻塞 probe 路径。