第2章 显示子系统框架:MDP架构、DSI/DP/eDP接口与显示时钟树

各位同学,咱们今天聊聊MTK8678的显示子系统。说实话,这块内容我当年刚接触时也绕了不少弯路。显示子系统说白了就是手机SoC里负责把图像数据从内存搬到屏幕上的那套硬件逻辑。你想想看,从APP生成一帧画面,到最终在屏幕上亮起来,中间要经过多少道工序?嗯,这就是咱们今天要拆解的核心。

2.1 MDP(多媒体显示处理器)架构

MDP,全称是Multimedia Display Processor。我个人习惯把它理解成「显示流水线的总调度中心」。它不负责具体的像素渲染,而是负责把各路图像数据(比如GPU渲染的、视频解码器输出的、Camera预览的)进行合成、缩放、旋转、叠加,最后输出给显示接口。

MTK8678的MDP架构,我画个简化的框图给你看:

+------------------+     +------------------+
|  GPU渲染帧       |     |  视频解码帧      |
|  (内存中的Buffer) |     |  (内存中的Buffer) |
+--------+---------+     +--------+---------+
         |                        |
         v                        v
+------------------+     +------------------+
|  Layer 0         |     |  Layer 1         |
|  (主图层)        |     |  (视频图层)      |
+--------+---------+     +--------+---------+
         |                        |
         +----------+-------------+
                    |
                    v
         +-----------------------+
         |   MDP 合成引擎        |
         |   (硬件合成器)        |
         +-----------+-----------+
                     |
                     v
         +-----------------------+
         |  显示接口控制器       |
         |  (DSI/DP/eDP)         |
         +-----------------------+

这里有个关键点:MDP内部有多个硬件图层(Layer),每个图层可以独立配置位置、大小、透明度、旋转角度。我在项目中遇到过一个问题:客户要求画中画显示,一个视频窗口悬浮在游戏画面上。如果只用软件合成,帧率直接掉到20fps。后来我改成用MDP的两个硬件图层叠加,帧率稳稳的60fps。这就是硬件加速的价值。

核心要点:MDP的硬件图层数量是有限的。MTK8678通常支持4-6个硬件图层。超过这个数量,系统会自动回退到GPU软件合成,性能会下降。所以设计UI时,图层数量要心里有数。

2.2 DSI/DP/eDP接口介绍

MDP合成完图像后,需要把数据送到屏幕。这就涉及到显示接口了。MTK8678支持三种主流接口:DSI、DP、eDP。我一个个说。

2.2.1 DSI(Display Serial Interface)

DSI是手机和平板上最常用的接口。它基于MIPI协议,采用差分信号传输,抗干扰能力强。说白了,就是一根排线里走高速串行数据。

DSI有几个关键参数:

  • Lane数量:通常1-4 Lane。4 Lane的带宽最高。
  • 速率:每Lane可达1.5Gbps甚至更高。MTK8678支持到2.5Gbps per Lane。
  • 工作模式:Command Mode(命令模式,带帧缓冲)和Video Mode(视频模式,实时刷新)。

我记得有一次调试一个1080p@60fps的屏幕,死活刷不出来。查了半天,发现是DSI的时钟配置错了。DSI的时钟计算公式是这样的:

DSI时钟频率 = (水平像素数 + HBP + HFP + HSA) * (垂直像素数 + VBP + VFP + VSA) * 刷新率 * 每像素位数 / (Lane数 * 2)

嗯,这个公式我建议你记下来。实际项目中,屏幕的时序参数(HBP、HFP等)都在屏幕的规格书里。拿到规格书第一件事,就是把这些参数填进去算一遍。

小技巧:调试DSI时,先用示波器量一下时钟线(CLK Lane)的频率。如果频率不对,多半是PLL配置或者分频系数算错了。我习惯在驱动里加个打印,把计算出的目标频率和实际锁相环频率都打出来对比。

2.2.2 DP(DisplayPort)

DP接口主要用于外接显示器、电视或者扩展坞。MTK8678的DP支持eDP(嵌入式DP)和标准DP两种模式。eDP通常用于笔记本的内屏,标准DP用于外接。

DP的优势是带宽高、支持长距离传输。MTK8678的DP支持到4K@60fps,HDR也没问题。

DP的链路训练(Link Training)是个容易出坑的地方。链路训练说白了就是主机和显示器之间协商速率、Lane数、电压摆幅等参数。我曾经遇到一个兼容性问题:某品牌的显示器在链路训练时总是失败,导致黑屏。后来发现是显示器的EDID(扩展显示标识数据)里写了一个不支持的速率组合。解决办法是在驱动里强制指定一个兼容的速率。

// 强制DP链路训练参数示例
static struct dp_link_config forced_link_cfg = {
    .lane_count = 2,       // 强制使用2 Lane
    .rate = DP_LINK_RATE_5_4G,  // 5.4Gbps per Lane
    .voltage_swing = 0,    // 低电压摆幅,兼容性更好
};

2.2.3 eDP(嵌入式DisplayPort)

eDP本质上是DP的嵌入式版本,主要用于笔记本和平板的内屏。它和DP的区别在于:eDP通常走的是板内走线,距离短,功耗优化更好。MTK8678的eDP支持PSR(面板自刷新)功能,可以降低静态画面时的功耗。

PSR的原理很简单:当画面静止时,eDP把最后一帧存在屏幕的缓冲里,然后关闭主链路。屏幕自己刷新缓冲里的内容。等画面变化时再唤醒主链路。这个功能在阅读场景下特别省电。

注意:PSR功能需要屏幕硬件支持。不是所有eDP屏幕都支持PSR。另外,PSR的唤醒延迟一般在几毫秒到十几毫秒之间。如果应用对延迟敏感(比如游戏),建议关闭PSR。

2.3 显示时钟树

显示时钟树,说白了就是给MDP、DSI、DP这些模块提供时钟的「水管网络」。每个模块需要不同频率的时钟,而且这些时钟之间还有相位关系。时钟树设计不好,显示就会出各种诡异问题:闪屏、花屏、不同步。

MTK8678的显示时钟树大致如下:

+-------------------+
|  主PLL (26MHz)    |
|  (晶振输入)       |
+--------+----------+
         |
         v
+-------------------+
|  DPLL (显示PLL)   |
|  输出: 594MHz     |
|  (用于4K@60fps)   |
+--------+----------+
         |
         +------------------+------------------+
         |                  |                  |
         v                  v                  v
+----------------+  +----------------+  +----------------+
| MDP Core Clock |  | DSI PHY Clock  |  | DP PHY Clock   |
|  (300-600MHz)  |  |  (根据分辨率)   |  |  (根据链路速率) |
+----------------+  +----------------+  +----------------+

这里有个关键点:DSI的PHY时钟和MDP的核心时钟必须满足一定的比例关系。否则会出现「帧不同步」的问题。我举个例子:

  • MDP核心时钟决定了一秒钟能处理多少像素。
  • DSI PHY时钟决定了数据从SoC传到屏幕的速率。
  • 如果MDP处理得太快,DSI传得太慢,缓冲区就会溢出,出现撕裂(Tearing)。
  • 反过来,MDP太慢,DSI太快,屏幕就会反复读取同一帧,造成卡顿。

解决这个问题的标准做法是使用「垂直同步(VSync)」机制。VSync信号由屏幕或者DSI控制器产生,MDP根据VSync的节奏来送帧。MTK8678的MDP内部有一个VSync生成器,可以配置成内部模式或者外部模式。

避坑指南:我曾经在一个项目里,为了省电把MDP核心时钟降到了200MHz。结果发现播放4K视频时,画面每隔几秒就卡一下。用trace工具一抓,发现MDP处理一帧的时间超过了VSync间隔(16.67ms)。说白了就是MDP「忙不过来」。解决办法是把MDP时钟调到400MHz以上。所以,时钟频率不是越低越好,要满足帧率要求。

最后,我建议你在调试显示驱动时,先把时钟树的所有节点频率都打印出来。MTK的调试接口一般会提供这样的命令:

// 查看显示时钟状态(伪代码)
cat /sys/kernel/debug/clk/clk_summary | grep "disp"
// 输出示例:
// disp_mdp_clk        400000000    // MDP核心时钟 400MHz
// disp_dsi0_phy_clk   1500000000   // DSI0 PHY时钟 1.5GHz
// disp_dp_phy_clk     540000000    // DP PHY时钟 540MHz

嗯,看到这些数字,你就能快速判断时钟配置是否合理。如果某个时钟频率是0,那说明对应的模块没使能,或者PLL没锁住。

好了,这一章的内容就到这里。显示子系统框架是后面所有驱动开发的基础。你把这些接口和时钟树搞清楚了,后面写MDP驱动、DSI驱动、DP驱动时就会顺手很多。下一章咱们开始实战,写一个最简单的MDP驱动,点亮一块屏幕。