3. 显示时钟与复位:DPLL配置、时钟树分析、复位序列与电源域管理
好,咱们进入第三章。这一章讲的是显示子系统的“心跳”和“命门”——时钟与复位。
说实话,做显示驱动这么多年,我见过太多奇奇怪怪的“花屏”、“黑屏”、“闪屏”问题。排查到最后,十有八九都跟时钟和复位脱不了干系。你想想看,一个像素从内存到屏幕,中间要经过多少级流水线?哪一级的时钟没对齐,或者复位时序没处理好,画面立马给你颜色看。
所以这一章,咱们就把DPLL配置、时钟树分析、复位序列和电源域管理这几个硬骨头啃下来。嗯,内容有点干,但都是实战经验。
3.1 DPLL配置:给显示引擎一个精准的“心跳”
DPLL,全称是Display PLL。说白了,它就是专门给显示子系统提供时钟的锁相环。高通平台上的DPLL,通常集成在MDSS(Mobile Display Subsystem)内部。
我个人习惯,把DPLL配置分成三步走:
- 选择参考时钟源:一般是XO(晶振)或者GPLL0。选哪个?看你的功耗和精度要求。
- 配置分频系数:包括VCO(压控振荡器)的分频、后分频等。这里要算好,确保输出频率在VCO的锁定范围内。
- 等待锁定:配置完寄存器后,必须轮询PLL_STATUS寄存器,确认LOCK位被置起。这一步不能省,我见过有人直接跳过,结果系统跑起来后时钟漂移,画面间歇性抖动。
来看一段典型的DPLL配置代码。这是高通平台上的写法,寄存器地址我做了简化:
// 配置DPLL,目标频率: 594MHz (用于1080p @ 60fps)
void dpll_config(uint32_t target_freq_khz) {
// 1. 先复位DPLL
writel(0x1, DPLL_SOFT_RESET);
mdelay(1);
writel(0x0, DPLL_SOFT_RESET);
// 2. 配置VCO分频
// 假设参考时钟是19.2MHz
uint32_t vco_div = target_freq_khz / 19200;
writel(vco_div, DPLL_VCO_DIV);
// 3. 配置后分频
writel(0x1, DPLL_POST_DIV); // 1分频
// 4. 使能DPLL
writel(0x1, DPLL_ENABLE);
// 5. 等待锁定,超时100ms
int timeout = 100;
while (!(readl(DPLL_STATUS) & BIT(0)) && timeout--) {
mdelay(1);
}
if (timeout <= 0) {
// 这里要报错,我曾经遇到过VCO配置超出范围导致锁不住
pr_err("DPLL lock timeout! Check VCO range.\n");
}
}
3.2 时钟树分析:理清显示子系统的“血脉”
时钟树,说白了就是一张“血脉图”。你得知道时钟从哪里来,经过哪些门控,最终流向了哪里。
高通8155的显示时钟树,大致是这样的结构:
| 时钟名称 | 来源 | 流向 | 典型频率 |
|---|---|---|---|
| MDSS_DPLL_CLK | DPLL | MDSS内部核心逻辑 | 594MHz / 864MHz |
| MDSS_AHB_CLK | GCC (Global Clock Controller) | MDSS寄存器配置总线 | 100MHz - 200MHz |
| MDSS_AXI_CLK | GCC | MDSS到内存的读写接口 | 400MHz - 800MHz |
| MDSS_VSYNC_CLK | GCC | VSYNC中断产生逻辑 | 19.2MHz |
| MDSS_ESC_CLK | GCC | DSI接口的逃逸时钟 | 19.2MHz |
这里有个关键点:MDSS_AXI_CLK 和 MDSS_DPLL_CLK 之间是有频率关系的。AXI时钟必须足够快,才能保证DPLL时钟产生的像素数据能及时从内存搬过来。否则就会出现“欠载”(underflow),画面撕裂。
我建议你在调试时,先通过寄存器把这几路时钟的实际频率读出来,确认它们都达到了预期值。我在项目中遇到过一个问题:系统休眠唤醒后,MDSS_AXI_CLK的频率被降到了200MHz,但DPLL还是594MHz,结果画面一卡一卡的。查了半天,原来是电源管理框架在唤醒时没有正确恢复AXI时钟频率。
3.3 复位序列:先断电源,再清状态
复位,听起来简单,不就是拉低一个引脚吗?但在显示子系统里,复位序列是有严格顺序的。搞反了,轻则寄存器写不进去,重则损坏硬件。
高通8155的显示复位序列,我总结为“三步走”:
- 先复位硬件逻辑:通过GCC(Global Clock Controller)的寄存器,将MDSS的复位信号拉低。这一步会清空所有内部状态机。
- 再复位软件状态:驱动里把所有的软件状态变量清零,包括缓冲区指针、中断标志等。
- 最后释放复位:按顺序释放复位信号,同时开始配置时钟。
来看一个实际的复位序列代码:
void mdss_reset_sequence(void) {
// 第一步:硬件复位
// GCC寄存器中,MDSS复位位在BIT(0)
writel(0x0, GCC_MDSS_BCR); // 拉低复位
mdelay(5); // 等待5ms,确保复位完成
// 第二步:软件状态清零
memset(&mdss_global_state, 0, sizeof(mdss_global_state));
mdss_global_state.reset_done = false;
// 第三步:释放复位
writel(0x1, GCC_MDSS_BCR); // 拉高复位,释放
mdelay(1);
// 等待硬件稳定
while (!(readl(MDSS_HW_VERSION) & 0xFFFFFFFF)) {
// 这里可以加个超时,防止死循环
udelay(10);
}
mdss_global_state.reset_done = true;
pr_info("MDSS reset sequence completed.\n");
}
3.4 电源域管理:别让显示子系统“饿着”
电源域管理,说白了就是给显示子系统供电。高通8155的显示子系统,通常有多个电源域:
- MDSS_GDSC:主电源域,给MDSS核心逻辑供电。这个必须最先打开。
- MDSS_DPHY_GDSC:给DSI PHY供电。如果不用DSI接口(比如用DP),这个可以关掉。
- MDSS_HDMI_GDSC:给HDMI接口供电。8155上这个用得少,但也要知道。
电源域的上电顺序,我建议是:
- 先打开MDSS_GDSC,等待它稳定。
- 再打开对应的接口电源域(比如MDSS_DPHY_GDSC)。
- 最后才能配置时钟和复位。
为什么是这个顺序?因为时钟发生器(DPLL)本身也需要供电。如果你先配时钟,再开电源,时钟发生器可能处于未供电状态,寄存器写不进去。
我曾经在调试一个低功耗场景时,为了省电,把MDSS_GDSC关掉了,但忘了关DPLL。结果DPLL在无供电状态下还在尝试锁定,导致漏电流增大,功耗反而没降下来。嗯,这是个教训。
3.5 实战避坑指南
最后,我把自己这些年踩过的坑,总结成几条避坑指南:
- 时钟频率别算错:DPLL的VCO频率一定要在规格范围内。我建议你写个脚本,把目标像素时钟、DSI Lane数、BPP(每像素比特数)都输进去,自动算出DPLL配置。手算容易出错。
- 复位时序别乱搞:硬件复位和软件复位之间,一定要有足够的延迟。我见过有人只等了1us,结果状态机没清干净,导致后续配置异常。
- 电源域别漏开:如果你发现某个寄存器写不进去,先检查对应的电源域有没有打开。这是最容易被忽略的问题。
- 调试时多用示波器:别光看寄存器。拿示波器量一下DPLL的输出引脚,看看频率对不对,波形干不干净。有时候寄存器显示锁定,但实际时钟有抖动,画面还是会闪。
好了,这一章的内容就到这里。时钟和复位是显示驱动的基石,搞懂了它们,后面的MDP、DSI、DP等模块才能跑得稳。下一章,咱们开始讲MDP(Mobile Display Processor)的流水线配置,那才是真正开始“画画”了。