3. 案例3
任务目标
本案例要求在U-Boot的board_init_f阶段之前,编写平台相关的时钟初始化代码,通过配置CPU的PLL寄存器将系统主频设置为目标频率(例如:将外部24MHz晶振倍频到600MHz),并通过读取PLL锁定状态与分频寄存器值来验证配置是否生效。
核心实现
以下代码以ARMv7 Cortex-A9(如i.MX6DL)为例,展示最小可复现的PLL初始化流程。代码直接操作CCM(Clock Controller Module)寄存器,配置ARM PLL倍频因子,并等待PLL锁定。
// arch/arm/mach-imx/system_clock_init.c
#include <common.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
/* i.MX6DL CCM寄存器地址 */
#define CCM_BASE_ADDR 0x020C4000
#define CCM_ANALOG_BASE 0x020C8000
#define CCM_CCSR (CCM_BASE_ADDR + 0x0C)
#define CCM_CDCDR (CCM_BASE_ADDR + 0x14)
#define CCM_CACDR (CCM_BASE_ADDR + 0x18)
#define PLL_ARM_CTRL (CCM_ANALOG_BASE + 0x00)
#define PLL_ARM_NUM (CCM_ANALOG_BASE + 0x04)
#define PLL_ARM_DENOM (CCM_ANALOG_BASE + 0x08)
#define PLL_ARM_STATUS (CCM_ANALOG_BASE + 0x0C)
/* PLL_ARM_CTRL位定义 */
#define PLL_ENABLE (1 << 0)
#define PLL_BYPASS (1 << 4)
#define PLL_LOCK (1 << 31)
#define PLL_DIV_SEL_SHIFT 0
#define PLL_DIV_SEL_MASK (0x7F << PLL_DIV_SEL_SHIFT)
/* 外部晶振频率 */
#define REF_CLK_24M 24000000
/* 目标频率:600MHz */
#define TARGET_ARM_CLK 600000000
static void setup_arm_pll(void)
{
u32 reg;
u32 div_sel;
/* 计算倍频因子:Fout = Fin * div_sel / 2 (i.MX6 PLL结构) */
div_sel = (TARGET_ARM_CLK * 2) / REF_CLK_24M; // 600M*2/24M = 50
/* 1. 将PLL置于旁路模式,关闭PLL输出 */
reg = readl(PLL_ARM_CTRL);
reg |= PLL_BYPASS;
reg &= ~PLL_ENABLE;
writel(reg, PLL_ARM_CTRL);
/* 2. 设置倍频因子 */
reg = readl(PLL_ARM_CTRL);
reg &= ~PLL_DIV_SEL_MASK;
reg |= (div_sel << PLL_DIV_SEL_SHIFT);
writel(reg, PLL_ARM_CTRL);
/* 3. 使能PLL,等待锁定 */
reg = readl(PLL_ARM_CTRL);
reg |= PLL_ENABLE;
reg &= ~PLL_BYPASS;
writel(reg, PLL_ARM_CTRL);
/* 4. 等待PLL锁定(超时保护) */
int timeout = 10000;
while (!(readl(PLL_ARM_STATUS) & PLL_LOCK) && timeout--) {
udelay(10);
}
if (timeout <= 0) {
printf("ERROR: ARM PLL lock timeout!\n");
return;
}
printf("ARM PLL locked. DIV_SEL=%d, expected clock=%dMHz\n",
div_sel, TARGET_ARM_CLK / 1000000);
}
/* 时钟树切换:将ARM时钟源切换到PLL */
static void switch_arm_clock_to_pll(void)
{
u32 reg;
/* 切换ARM时钟源到PLL (CCSR bit[2:0] = 0b001 表示PLL) */
reg = readl(CCM_CCSR);
reg &= ~0x7;
reg |= 0x1;
writel(reg, CCM_CCSR);
/* 设置ARM分频器为1 (CACDR bit[3:0] = 0) */
reg = readl(CCM_CACDR);
reg &= ~0xF;
writel(reg, CCM_CACDR);
}
/* 验证函数:读取寄存器确认配置 */
static void verify_pll_config(void)
{
u32 ctrl = readl(PLL_ARM_CTRL);
u32 status = readl(PLL_ARM_STATUS);
u32 div_sel = (ctrl & PLL_DIV_SEL_MASK) >> PLL_DIV_SEL_SHIFT;
u32 ccsr = readl(CCM_CCSR);
printf("=== PLL Verification ===\n");
printf("PLL_ARM_CTRL = 0x%08X\n", ctrl);
printf("PLL_ARM_STATUS = 0x%08X\n", status);
printf("DIV_SEL = %d\n", div_sel);
printf("PLL Locked = %s\n", (status & PLL_LOCK) ? "YES" : "NO");
printf("ARM Clock Src = %s\n", ((ccsr & 0x7) == 0x1) ? "PLL" : "OTHER");
/* 计算实际频率 */
u32 actual_clk = (REF_CLK_24M * div_sel) / 2;
printf("Calculated ARM Clock = %d MHz\n", actual_clk / 1000000);
if ((status & PLL_LOCK) && (actual_clk == TARGET_ARM_CLK)) {
printf("PLL Verification PASSED\n");
} else {
printf("PLL Verification FAILED\n");
}
}
/* U-Boot初始化入口 */
int board_early_init_f(void)
{
setup_arm_pll();
switch_arm_clock_to_pll();
verify_pll_config();
return 0;
}
集成方式
将上述文件添加到U-Boot源码的板级目录中,并在板级头文件(如include/configs/myboard.h)中使能早期初始化:
#define CONFIG_BOARD_EARLY_INIT_F
编译与运行
- 编译U-Boot:
make myboard_defconfig && make -j4 - 将生成的
u-boot.bin烧录到SD卡或通过JTAG加载 - 上电后连接串口,波特率115200,观察启动日志
验证方法
启动后串口输出应包含以下关键信息:
ARM PLL locked. DIV_SEL=50, expected clock=600MHz
=== PLL Verification ===
PLL_ARM_CTRL = 0x00000051
PLL_ARM_STATUS = 0x80000000
DIV_SEL = 50
PLL Locked = YES
ARM Clock Src = PLL
Calculated ARM Clock = 600 MHz
PLL Verification PASSED
通过标准:串口输出显示PLL Locked = YES且Calculated ARM Clock = 600 MHz,同时PLL Verification PASSED。若出现lock timeout或计算频率与目标不符,则检查外部晶振频率和PLL倍频因子计算是否正确。
常见问题
- PLL锁定超时:确认外部晶振实际频率是否为24MHz,若为其他频率需调整
REF_CLK_24M宏。 - 系统启动异常:切换时钟源后若系统挂死,可能是分频器设置不当导致ARM频率过高,检查
CACDR分频设置。 - 寄存器地址不匹配:不同i.MX6型号寄存器地址略有差异,请对照对应芯片参考手册确认。