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

编译与运行

  1. 编译U-Boot:make myboard_defconfig && make -j4
  2. 将生成的u-boot.bin烧录到SD卡或通过JTAG加载
  3. 上电后连接串口,波特率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型号寄存器地址略有差异,请对照对应芯片参考手册确认。