3. 时钟树配置与PLL初始化

3.1 SoC时钟源概述

SoC的时钟树以外部晶振(XTAL)为根,通过多级锁相环(PLL)倍频、分频后产生各模块所需的独立时钟域。XBL初始化阶段需完成以下操作:

  • 选择主参考时钟源(XTAL或外部差分时钟)
  • 配置各PLL的倍频因子(M/N/D),使输出频率满足目标
  • 等待PLL锁定标志置位
  • 将锁定后的PLL输出分配给系统总线、CPU、UFS控制器等关键模块
时钟源类型 典型频率 用途
XTAL(晶体振荡器) 19.2 MHz / 24 MHz PLL参考输入、RTC
外部差分时钟 100 MHz / 125 MHz 高速串行接口(PCIe、SATA)
PLL0(系统PLL) 614.4 MHz(典型) 系统总线、内存控制器
UFS PLL 26.0 MHz(HS-G1)~ 239.4 MHz(HS-G4) UFS M-PHY参考时钟
参考手册查阅: PLL的M/N/D系数、锁定时间、VCO范围需依据具体SoC的《Clock Controller Register Description》章节,不同SoC的寄存器偏移和位域布局可能完全不同。

3.2 PLL锁定流程

PLL的锁定过程遵循“配置→启动→等待锁定→锁定确认→启用输出”的序列。任何一步出错都会导致时钟失效。

  1. 禁用输出: 在配置之前,先清除PLL输出使能位,避免未锁定的次谐波送入下游。
  2. 配置倍频系数: 写入分频器配置寄存器(PLL_M、PLL_N、PLL_D),注意写入顺序和更新触发位。
  3. 启动PLL: 设置PLL使能位(PLL_EN),PLL开始根据参考时钟和配置系数锁定。
  4. 轮询锁定状态: 循环读取PLL状态寄存器中的LOCK位(通常为状态寄存器bit0),直到该位为1。
  5. 启用输出: 设置输出使能位(PLL_OUT_EN),PLL时钟开始供给后续分频器或直接输出。
PLL 锁定与时钟分配流程 外部参考时钟 (XTAL) 配置 M / N / D 使能 PLL_EN 等待 LOCK=1 (Timeout: ~500μs) 锁定标志检查 状态寄存器 bit0 EN 输出使能 分配至分频器 常见锁定失败原因 • 参考时钟不稳 • M/N值超出VCO范围 • 供电或去耦不足 • 复位后时钟域冲突 分频后输出至各模块

3.3 寄存器配置示例

以 QCS8250 平台为例,UFS PLL 配置寄存器定义如下(实际基地址请参考具体芯片手册):

寄存器名称 偏移地址 位域 描述
PLL_CONFIG 0x1B00000 [31:0] M/N/D 系数配置
PLL_STATUS 0x1B00004 [0] LOCK 锁定标志,1=锁定
PLL_CTRL 0x1B00008 [0] PLL_EN
[1] PLL_OUT_EN
PLL使能与输出使能
UFS_CLK_DIV 0x1B00010 [3:0] DIV_SEL 输出分频系数(1~16)

以下为 XBL 早期初始化代码中配置 PLL 的典型片段(C风格,基于 MMIO):

#define PLL_BASE          0x1B00000
#define PLL_CONFIG         (PLL_BASE + 0x00)
#define PLL_STATUS         (PLL_BASE + 0x04)
#define PLL_CTRL           (PLL_BASE + 0x08)

#define PLL_M              0x3C    /* 60 */
#define PLL_N              0x01    /* 1  */
#define PLL_D              0x02    /* 2  */

void pll_init(void)
{
    uint32_t reg;
    /* 1. 关闭输出 */
    writel(0x0, PLL_CTRL);

    /* 2. 配置 M/N/D: 假设寄存器格式 M[15:8], N[7:4], D[3:0] */
    reg = ((PLL_M & 0xFF) << 8) | ((PLL_N & 0x0F) << 4) | (PLL_D & 0x0F);
    writel(reg, PLL_CONFIG);

    /* 3. 使能 PLL */
    writel(0x1, PLL_CTRL);

    /* 4. 等待锁定,超时 50000 循环 */
    for (int i = 0; i < 50000; i++) {
        if (readl(PLL_STATUS) & 0x1) {
            break;
        }
        udelay(10);
    }
    if (!(readl(PLL_STATUS) & 0x1)) {
        /* 错误处理:锁定超时 */
        ErrorHandler("PLL lock timeout");
        return;
    }

    /* 5. 启用输出 */
    writel(0x3, PLL_CTRL);  /* bit0=PLL_EN, bit1=PLL_OUT_EN */
}
关键校验: 写入配置后必须等待至少一个参考时钟周期再使能 PLL,否则配置可能被忽略。部分 SoC 要求写入配置后必须先通过软件复位 PLL 再启动。

3.4 时钟输出验证

锁定后的 PLL 输出可通过以下两种方式验证:

  • 直接读取状态寄存器:读取 PLL_STATUS,确认 LOCK 位持续为 1。
  • GPIO 引出时钟测试点:将 PLL 输出映射到 GPIO(通常通过时钟输出复用寄存器),用示波器或频谱仪测量频率。

示例:将 UFS PLL 输出映射到 GPIO 5(假设 SoC 支持):

// 将 UFS_PLL 时钟输出到 GPIO5(复用功能选择)
#define TCSR_GPIOn_CFG   0x1C00000 + 5*4    // GPIO5 配置
writel(0x2, TCSR_GPIOn_CFG);   // 0x2 表示 UFS_PLL 时钟输出

// 之后用示波器测量 GPIO5 引脚,预期频率 = 26 MHz (HS-G1) 或更高

3.5 调试与常见错误

错误现象 可能原因 排查方法
PLL 始终未锁定 参考时钟未起振
VCO 频率超限
供电电压异常
测量 XTAL 引脚波形;检查 PLL 的 M/N 范围;用示波器测电源纹波
锁定标志间歇性消失 热噪声 / 电磁干扰
时钟复用竞争
增加去耦电容;检查时钟控制寄存器是否有其他主控制器重写
输出频率为 0 输出使能未设置
分频器配置错误
读取 PLL_CTRL 确认 OUT_EN 位;计算实际分频比,对比预期
警告: 切勿在 PLL 未锁定时将输出使能!未锁定状态输出的时钟频率不稳定,可能导致总线锁死、UFS 初始化失败,甚至 SoC 永久损坏(因过压/欠压烧毁高速接口)。建议在锁定轮询代码中加入超时保护,超时后立即关闭 PLL 并打印错误码。

3.6 性能与稳定性优化

  • 电源去耦: PLL 对供电噪声极其敏感,PCB 上必须在 PLL 供电引脚附近放置 0.1μF + 10μF 电容,且 XBL 中可等待额外 100μs 让电源稳定后再启动 PLL。
  • 寄存器强制清零: 在写入新配置前,先执行 writel(0x0, PLL_CTRL) 并读回确认,避免前次 BootROM 或 XBL 第一阶段的残留配置。
  • 热切换保护: 如果 UFS 需要切换速率(如从 HS-G1 切换至 HS-G4),必须先关闭 PLL,重新配置 M/N/D,等待新锁定后再使能。不能直接在线修改系数。
  • 轮询超时自适应: 锁定时间与参考时钟频率、PLL 类型有关。官方手册通常给出典型值 50~500μs,建议轮询超时设为 2ms,并加入递减计数,避免无休止等待。
实践技巧: 在调试阶段,可在锁定轮询循环内插入 RTT 打印(如debug_printf(".")),通过观察打印密度判断锁定耗时。线上版本要去掉所有打印,改用定时器中断或 DMA 传输状态。