3. 显示时钟与复位:DPLL配置、时钟树分析、复位序列与电源域管理

好,咱们进入第三章。这一章讲的是显示子系统的“心跳”和“命门”——时钟与复位。

说实话,做显示驱动这么多年,我见过太多奇奇怪怪的“花屏”、“黑屏”、“闪屏”问题。排查到最后,十有八九都跟时钟和复位脱不了干系。你想想看,一个像素从内存到屏幕,中间要经过多少级流水线?哪一级的时钟没对齐,或者复位时序没处理好,画面立马给你颜色看。

所以这一章,咱们就把DPLL配置、时钟树分析、复位序列和电源域管理这几个硬骨头啃下来。嗯,内容有点干,但都是实战经验。

3.1 DPLL配置:给显示引擎一个精准的“心跳”

DPLL,全称是Display PLL。说白了,它就是专门给显示子系统提供时钟的锁相环。高通平台上的DPLL,通常集成在MDSS(Mobile Display Subsystem)内部。

我个人习惯,把DPLL配置分成三步走:

  1. 选择参考时钟源:一般是XO(晶振)或者GPLL0。选哪个?看你的功耗和精度要求。
  2. 配置分频系数:包括VCO(压控振荡器)的分频、后分频等。这里要算好,确保输出频率在VCO的锁定范围内。
  3. 等待锁定:配置完寄存器后,必须轮询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");
    }
}
注意:DPLL的VCO频率范围是有严格限制的。高通8155的DPLL,VCO通常要求在2.5GHz到5GHz之间。你算出来的VCO频率如果超出这个范围,锁相环是锁不住的。我曾经在调试4K分辨率时,算错了分频比,VCO跑到了6GHz,结果DPLL死活不锁定,折腾了两天才发现是这个问题。

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_CLKMDSS_DPLL_CLK 之间是有频率关系的。AXI时钟必须足够快,才能保证DPLL时钟产生的像素数据能及时从内存搬过来。否则就会出现“欠载”(underflow),画面撕裂。

我建议你在调试时,先通过寄存器把这几路时钟的实际频率读出来,确认它们都达到了预期值。我在项目中遇到过一个问题:系统休眠唤醒后,MDSS_AXI_CLK的频率被降到了200MHz,但DPLL还是594MHz,结果画面一卡一卡的。查了半天,原来是电源管理框架在唤醒时没有正确恢复AXI时钟频率。

3.3 复位序列:先断电源,再清状态

复位,听起来简单,不就是拉低一个引脚吗?但在显示子系统里,复位序列是有严格顺序的。搞反了,轻则寄存器写不进去,重则损坏硬件。

高通8155的显示复位序列,我总结为“三步走”:

  1. 先复位硬件逻辑:通过GCC(Global Clock Controller)的寄存器,将MDSS的复位信号拉低。这一步会清空所有内部状态机。
  2. 再复位软件状态:驱动里把所有的软件状态变量清零,包括缓冲区指针、中断标志等。
  3. 最后释放复位:按顺序释放复位信号,同时开始配置时钟。

来看一个实际的复位序列代码:

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");
}
小技巧:在释放复位后,我习惯先读一下MDSS_HW_VERSION寄存器。如果读出来是0xFFFFFFFF或者0x00000000,说明复位没有完全释放,或者硬件还处于复位状态。这个寄存器是只读的,能帮你快速判断复位是否成功。

3.4 电源域管理:别让显示子系统“饿着”

电源域管理,说白了就是给显示子系统供电。高通8155的显示子系统,通常有多个电源域:

  • MDSS_GDSC:主电源域,给MDSS核心逻辑供电。这个必须最先打开。
  • MDSS_DPHY_GDSC:给DSI PHY供电。如果不用DSI接口(比如用DP),这个可以关掉。
  • MDSS_HDMI_GDSC:给HDMI接口供电。8155上这个用得少,但也要知道。

电源域的上电顺序,我建议是:

  1. 先打开MDSS_GDSC,等待它稳定。
  2. 再打开对应的接口电源域(比如MDSS_DPHY_GDSC)。
  3. 最后才能配置时钟和复位。

为什么是这个顺序?因为时钟发生器(DPLL)本身也需要供电。如果你先配时钟,再开电源,时钟发生器可能处于未供电状态,寄存器写不进去。

我曾经在调试一个低功耗场景时,为了省电,把MDSS_GDSC关掉了,但忘了关DPLL。结果DPLL在无供电状态下还在尝试锁定,导致漏电流增大,功耗反而没降下来。嗯,这是个教训。

3.5 实战避坑指南

最后,我把自己这些年踩过的坑,总结成几条避坑指南:

  • 时钟频率别算错:DPLL的VCO频率一定要在规格范围内。我建议你写个脚本,把目标像素时钟、DSI Lane数、BPP(每像素比特数)都输进去,自动算出DPLL配置。手算容易出错。
  • 复位时序别乱搞:硬件复位和软件复位之间,一定要有足够的延迟。我见过有人只等了1us,结果状态机没清干净,导致后续配置异常。
  • 电源域别漏开:如果你发现某个寄存器写不进去,先检查对应的电源域有没有打开。这是最容易被忽略的问题。
  • 调试时多用示波器:别光看寄存器。拿示波器量一下DPLL的输出引脚,看看频率对不对,波形干不干净。有时候寄存器显示锁定,但实际时钟有抖动,画面还是会闪。

好了,这一章的内容就到这里。时钟和复位是显示驱动的基石,搞懂了它们,后面的MDP、DSI、DP等模块才能跑得稳。下一章,咱们开始讲MDP(Mobile Display Processor)的流水线配置,那才是真正开始“画画”了。