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的锁定过程遵循“配置→启动→等待锁定→锁定确认→启用输出”的序列。任何一步出错都会导致时钟失效。
- 禁用输出: 在配置之前,先清除PLL输出使能位,避免未锁定的次谐波送入下游。
- 配置倍频系数: 写入分频器配置寄存器(PLL_M、PLL_N、PLL_D),注意写入顺序和更新触发位。
- 启动PLL: 设置PLL使能位(PLL_EN),PLL开始根据参考时钟和配置系数锁定。
- 轮询锁定状态: 循环读取PLL状态寄存器中的LOCK位(通常为状态寄存器bit0),直到该位为1。
- 启用输出: 设置输出使能位(PLL_OUT_EN),PLL时钟开始供给后续分频器或直接输出。
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 传输状态。