4. 座舱显示接口与协议

做座舱多屏互联,最绕不开的就是显示驱动。高通SA8295的显示子系统叫MDSS(Mobile Display Subsystem),说白了就是一块专门管屏幕输出的硬件模块。我刚开始接触8295时,也被这套架构绕晕过——它跟普通手机上的显示驱动还真不太一样。

MDSS驱动框架概览

MDSS在Linux内核里是一个完整的DRM(Direct Rendering Manager)驱动。它把硬件抽象成几个层次:

  • MDP(Mobile Display Processor):核心显示处理器,负责图层合成、旋转、缩放
  • DPU(Display Processing Unit):显示处理单元,做色彩管理、HDR处理
  • DSI/DP/eDP:物理接口控制器,负责把数据送到屏幕
  • Connector:DRM抽象出来的连接器,代表一个物理显示输出

我习惯把MDSS想象成一个多路视频切换台。MDP是主控台,DPU是调色师,DSI/DP就是输出线缆。每个屏幕对应一条独立的输出通道。

关键点:SA8295的MDSS支持最多7个显示流水线(Display Pipeline),每个流水线可以独立配置分辨率、刷新率、色彩格式。这意味着理论上可以同时驱动7块屏幕——当然实际座舱场景一般用到3-4块。

设备树配置:让内核认识你的屏幕

设备树(DT)是嵌入式Linux里描述硬件的方式。MDSS的设备树配置,说白了就是告诉内核:你有几块屏、接在哪个接口上、用什么参数驱动。

我曾经在一个项目里踩过坑——设备树里DSI的clock-frequency配错了,导致屏幕一直闪。查了两天才发现是频率小数点后一位写错了。嗯,这种细节真的不能马虎。

单屏配置示例(DSI接口)

&mdss_mdp {
    status = "okay";
};

&dsi0 {
    status = "okay";
    panel@0 {
        compatible = "boe,tv110c9m-ll08";
        reg = <0>;
        vdd-supply = <&pm8150a_l11>;
        avdd-supply = <&pm8150a_l17>;
        reset-gpios = <&tlmm 75 GPIO_ACTIVE_LOW>;
        
        port {
            panel_in: endpoint {
                remote-endpoint = <&dsi0_out>;
            };
        };
    };
};

&dsi0_out {
    remote-endpoint = <&panel_in>;
    data-lanes = <0 1 2 3>;
};

这里要注意几个关键字段:

  • data-lanes:DSI数据通道数,4通道可以支持更高分辨率
  • reset-gpios:屏幕复位引脚,时序必须跟面板规格书一致
  • vdd-supply:供电稳压器,电压不对屏幕直接不亮

双屏配置:DSI + DP

座舱里常见的是仪表屏用DSI,中控屏用DP。设备树里需要分别配置两个接口:

&mdss_mdp {
    status = "okay";
};

/* 仪表屏 - DSI0 */
&dsi0 {
    status = "okay";
    panel@0 {
        compatible = "sharp,ls055d1sj04";
        reg = <0>;
        /* ... 面板参数 ... */
    };
};

/* 中控屏 - DP0 */
&dp0 {
    status = "okay";
    compatible = "qcom,dp-display";
    qcom,mdss-dp-panel = <0>;
    qcom,mdss-dp-interface = "dp";
    qcom,mdss-dp-lanes = <4>;
    qcom,mdss-dp-link-rate = <810000>;
    
    ports {
        #address-cells = <1>;
        #size-cells = <0>;
        port@0 {
            reg = <0>;
            dp0_in: endpoint {
                remote-endpoint = <&mdss_dp0_intf0_out>;
            };
        };
    };
};

个人经验:双屏配置最容易出问题的是时钟域同步。DSI和DP的时钟源不同,如果两个屏幕刷新率不一致(比如仪表60fps、中控30fps),MDP的合成器需要做帧率适配。我一般建议仪表和中控都用60fps,省去很多同步麻烦。

三屏显示实例:仪表+中控+副驾屏

三屏是高端座舱的标配。SA8295的MDSS支持三个独立流水线,每个流水线可以绑定不同的接口。下面是一个典型的三屏配置:

屏幕 接口 分辨率 刷新率 色彩深度
仪表屏 DSI0 1920x720 60fps 8bit
中控屏 DP0 1920x1080 60fps 10bit
副驾屏 DP1 1920x720 60fps 8bit

设备树里需要为每个屏幕分配独立的MDP流水线ID:

&mdss_mdp {
    status = "okay";
    qcom,mdss-mdp-intf = <0 1 2>;  /* 三个接口 */
};

&dsi0 {
    status = "okay";
    qcom,mdss-mdp-intf-id = <0>;    /* 流水线0 */
    /* 仪表屏面板配置 */
};

&dp0 {
    status = "okay";
    qcom,mdss-mdp-intf-id = <1>;    /* 流水线1 */
    /* 中控屏配置 */
};

&dp1 {
    status = "okay";
    qcom,mdss-mdp-intf-id = <2>;    /* 流水线2 */
    /* 副驾屏配置 */
};

避坑指南:我曾经在一个三屏项目里,副驾屏死活不亮。查了半天发现是DP1的PHY供电没使能。SA8295的DP PHY需要独立的电源域,设备树里漏配了vdda-phy-supply和vdda-pll-supply。记住:每个DP接口都要配齐供电,一个都不能少。

MDSS驱动核心流程

驱动加载时,MDSS会经历这几个阶段:

  1. 硬件探测:读取设备树,初始化MDP、DPU、接口控制器
  2. 面板探测:通过DSI/DP读取屏幕EDID或初始化序列
  3. DRM注册:为每个屏幕创建CRTC、Encoder、Connector、Plane
  4. 帧缓冲创建:分配显存,配置图层格式
  5. 显示使能:启动时钟,输出视频信号

你想想看,这个过程其实跟PC的显卡驱动很像。只不过嵌入式里多了很多电源管理和时序约束。

SVG:MDSS多屏驱动架构图

SA8295 MDSS 多屏驱动架构 应用层(SurfaceFlinger / Wayland) DRM核心层(CRTC / Encoder / Connector / Plane) MDSS驱动层(MDP / DPU / 接口控制器) 流水线0 | 流水线1 | 流水线2 | ... | 流水线6 DSI0(仪表) DP0(中控) DP1(副驾) 仪表屏(1920x720) 中控屏(1920x1080) 副驾屏(1920x720) 每个流水线独立配置分辨率/刷新率/色彩格式,通过DRM统一管理 设备树中通过 qcom,mdss-mdp-intf-id 绑定流水线与物理接口

调试与验证

配置完设备树,怎么确认驱动加载正常?我一般用这几个方法:

  • dmesg | grep mdss:看驱动初始化日志,有没有报错
  • cat /sys/kernel/debug/dri/0/state:查看DRM状态,确认每个CRTC/Connector是否正常
  • modetest -M msm_dpu:直接输出测试画面,验证显示通路
  • cat /sys/class/graphics/fb0/modes:查看帧缓冲支持的显示模式

个人习惯:我会在设备树里给每个屏幕加一个status = "disabled",然后逐个使能调试。这样如果某个屏幕有问题,不会影响其他屏幕的验证。先让仪表亮起来,再开中控,最后加副驾屏——分步验证,定位问题快很多。

MDSS驱动这块,说白了就是理解硬件流水线、配好设备树、调通物理接口。你只要把这三个环节吃透,多屏显示的基本框架就搭起来了。剩下的就是上层应用怎么往不同屏幕送内容——那是后面章节要聊的事。