4. 二级Bootloader(SPL/MLO):从存储介质加载、DDR初始化、时钟配置

在嵌入式系统的启动流程中,一级Bootloader(ROM Code)完成最基本的硬件探测后,控制权移交给二级Bootloader。在TI OMAP/AM335x等典型SoC中,二级Bootloader被称为SPL(Secondary Program Loader)或MLO(MMC Loader)。本章深入剖析SPL/MLO的核心职责:从NAND、eMMC或SD卡等存储介质中加载后续镜像、完成DDR控制器的初始化以及配置系统时钟树。

4.1 SPL/MLO的定位与加载流程

SPL/MLO是ROM Code与完整Bootloader(如U-Boot proper)之间的桥梁。其体积被严格限制(通常不超过SoC内部SRAM的大小,例如TI AM335x的SRAM为64KB~128KB),因此必须精简高效。

核心约束: SPL/MLO必须能够在DDR尚未初始化、仅有内部SRAM可用的环境下运行。所有代码和数据必须位于SRAM内,直到DDR初始化完成。

典型的加载流程如下:

  1. ROM Code加载SPL/MLO: ROM Code根据启动引脚(Boot Pins)的配置,从指定的存储介质(如SD卡的第1个分区、eMMC的BOOT分区、NAND的起始块)读取固定大小(如128KB)的SPL/MLO镜像到内部SRAM。
  2. SPL/MLO执行: CPU从SRAM中执行SPL/MLO代码。此时系统运行在低速时钟下,DDR不可用。
  3. 基础硬件初始化: SPL/MLO首先配置关键的时钟源(如PLL)、电源管理单元(PMIC)以及引脚复用(Pin Mux),为DDR初始化做准备。
  4. DDR初始化: 调用DDR控制器驱动,配置时序参数(如CAS Latency、tRCD、tRP等),完成DDR控制器的校准(如写电平校准、读数据眼图校准)。
  5. 加载U-Boot proper: DDR初始化成功后,SPL/MLO从存储介质中读取完整的U-Boot镜像(通常为u-boot.img或u-boot-dtb.img)到DDR中的指定地址。
  6. 跳转执行: SPL/MLO设置好栈指针和参数(如设备树地址、ATAG参数),然后跳转到DDR中的U-Boot入口点。
SPL/MLO加载与执行流程 SPL/MLO 加载与执行流程 ROM Code 存储介质 (SD/eMMC/NAND) SPL/MLO (SRAM) 时钟 & DDR 初始化 加载 U-Boot (DDR) 跳转执行 U-Boot 读取SPL 加载到SRAM 初始化 DDR就绪 加载完成

4.2 从存储介质加载:NAND、eMMC与SD卡

SPL/MLO必须支持多种存储介质,每种介质的访问方式和初始化流程差异巨大。下表对比了三种主流介质的加载特性:

特性 NAND Flash eMMC SD卡
接口协议 ONFI / Toggle MMC 协议 (v4.x/v5.x) SPI / SD 协议
坏块管理 必须软件处理坏块 内部FTL,对主机透明 内部FTL,对主机透明
ECC需求 需要硬件ECC引擎(如BCH) 内部处理,主机无需关心 内部处理,主机无需关心
启动分区 固定起始块(如Block 0) 专用BOOT分区(可配置) FAT32分区,特定文件名(如MLO)
SPL/MLO大小限制 通常受限于1个Page大小(如4KB~16KB) 受限于SRAM大小(如128KB) 受限于SRAM大小(如128KB)
初始化复杂度 高(需初始化NAND控制器、ECC引擎) 中(需发送MMC命令、设置总线宽度) 低(SPI模式最简单)
工程实践: 对于NAND启动,SPL/MLO通常使用“工厂坏块跳过”策略。ROM Code读取前几个块(如Block 0~3),如果发现坏块则自动跳过。SPL/MLO本身必须包含坏块表,或者使用“镜像备份”机制(在多个块中存放相同镜像)。

4.3 DDR初始化:从时序到校准

DDR初始化是SPL/MLO中最关键也最复杂的部分。错误的时序参数将导致系统无法启动或数据损坏。DDR初始化通常包含以下步骤:

  1. DDR控制器复位与时钟使能: 确保DDR控制器处于复位状态,使能DDR时钟(如DDR PLL输出)。
  2. DDR PHY配置: 配置物理层(PHY)的阻抗、驱动强度、ODT(片上端接)等模拟参数。
  3. 时序参数编程: 根据DDR芯片的数据手册,设置控制器寄存器中的CL、tRCD、tRP、tRAS、tRFC等参数。
  4. ZQ校准: 对于DDR3/DDR4,需要执行ZQ校准(通过ZQ引脚)来校准输出驱动和ODT。
  5. 写电平校准(Write Leveling): 对于DDR3/DDR4,用于补偿DQS与CK之间的飞行时间差。
  6. 读数据眼图校准(Read DQS Gating / Training): 调整DQS门控信号,确保正确捕获读数据。
常见陷阱: DDR初始化时序高度依赖PCB布局和DDR芯片批次。在量产阶段,必须对每块板子进行DDR训练(Training),并将训练好的参数保存到非易失存储中,供SPL/MLO在下次启动时使用。否则,温度或电压变化可能导致启动失败。
DDR初始化流程 DDR 初始化流程 控制器复位 PHY配置 时序参数编程 ZQ校准 写电平校准 读数据眼图校准 DDR初始化完成

4.4 时钟配置:PLL与时钟树

在DDR初始化之前,必须配置好系统时钟。SoC内部通常包含多个PLL(锁相环),用于生成CPU、DDR、外设等不同频率的时钟。SPL/MLO需要完成以下时钟配置:

  • 主PLL配置: 将低频晶振(如24MHz)倍频到CPU核心频率(如1GHz)。
  • DDR PLL配置: 生成DDR控制器和PHY所需的工作频率(如DDR3-800对应400MHz时钟)。
  • 外设PLL配置: 为存储介质接口(如MMC、NAND控制器)提供时钟。
  • 时钟分频与门控: 设置各模块的时钟分频系数,并确保未使用的外设时钟处于关闭状态以降低功耗。
关键指标: 时钟配置的稳定性直接影响DDR的读写时序。例如,DDR PLL的抖动(Jitter)必须满足DDR芯片的规范(通常要求RMS Jitter < 10ps)。SPL/MLO中通常使用SoC厂商提供的寄存器配置表,但工程师需要理解每个寄存器的含义,以便在遇到时钟相关问题时进行调试。

4.5 代码示例:SPL/MLO中DDR初始化片段

以下是一个简化的DDR初始化代码片段,展示了在TI AM335x平台上配置DDR2时序的过程。实际代码会包含更多的寄存器配置和校准步骤。

/* ddr_init.c - 简化版DDR2初始化 */
#include <asm/arch/ddr_defs.h>

void ddr_init(void)
{
    /* 1. 使能DDR时钟 */
    writel(0x00000001, &cm_per->l4ls_clkstctrl);
    writel(0x00000002, &cm_per->l3_clkstctrl);
    writel(0x00000002, &cm_dpll->ddr_clkctrl);

    /* 2. 配置DDR PLL (假设输入24MHz, 目标DDR频率400MHz) */
    /* 设置倍频因子: M = 400 * 2 / 24 = 33.33, 取整为33 */
    writel(0x00000021, &cm_dpll->ddr_clksel); /* 设置M=33, N=1 */
    /* 等待PLL锁定 */
    while (!(readl(&cm_dpll->ddr_clksel) & (1 << 6)));

    /* 3. 配置DDR控制器时序 (以MT47H128M16为例) */
    struct ddr_ctrl_regs regs = {
        .sdram_config = (SDRAM_TYPE_DDR2 << 0) |
                        (BANK_8 << 4) |
                        (ROW_14 << 8) |
                        (COL_10 << 12),
        .sdram_timing1 = (CL_5 << 0) |   /* CAS Latency = 5 */
                         (tRCD_5 << 8) |  /* tRCD = 5 cycles */
                         (tRP_5 << 16),   /* tRP = 5 cycles */
        .sdram_timing2 = (tRFC_127 << 0), /* tRFC = 127 cycles */
        .sdram_timing3 = (tRAS_15 << 0),  /* tRAS = 15 cycles */
    };
    writel(regs.sdram_config, &ddr_ctrl->sdram_config);
    writel(regs.sdram_timing1, &ddr_ctrl->sdram_timing1);
    writel(regs.sdram_timing2, &ddr_ctrl->sdram_timing2);
    writel(regs.sdram_timing3, &ddr_ctrl->sdram_timing3);

    /* 4. 执行ZQ校准 (DDR2需要) */
    writel(0x00000001, &ddr_phy->zq_config);
    while (readl(&ddr_phy->zq_status) & 0x1);

    /* 5. 等待DDR控制器就绪 */
    while (!(readl(&ddr_ctrl->status) & (1 << 0)));

    printf("DDR initialization complete.\n");
}
调试技巧: 如果DDR初始化后系统无法启动,首先检查PLL锁定状态和DDR控制器的状态寄存器。使用示波器或逻辑分析仪测量DDR时钟(CK)和DQS信号,确认频率和波形是否符合预期。在SPL/MLO中增加简单的LED闪烁或串口输出,可以帮助定位初始化失败的具体步骤。

4.6 本章小结

二级Bootloader(SPL/MLO)是嵌入式系统启动流程中的关键一环,它负责在资源极度受限的环境下完成存储介质加载、DDR初始化和时钟配置。理解SPL/MLO的工作原理,对于调试启动问题、优化启动时间以及移植新硬件平台至关重要。本章涵盖了从存储介质特性对比到DDR初始化流程的完整知识链,为后续深入分析U-Boot proper的启动过程奠定了坚实基础。