3. CI系统核心组件

U-Boot 在车载 SoC 开发中扮演的角色,说白了就是「硬件管家」。它负责把芯片从复位状态带到能跑 Linux 内核的环境。TI Jacinto 平台的启动流程比普通嵌入式板子复杂不少,因为涉及 SPL、ATF 等多级引导。我刚开始接触 Jacinto 时,也被这层层嵌套的启动链搞得有点晕。

这一章,咱们就把它拆开揉碎了讲。

3.1 U-Boot 源码结构:别被目录吓到

U-Boot 源码树看起来目录很多,但核心就几块。我个人习惯先看顶层 Makefile,再顺着 arch/ 和 board/ 往下摸。

目录/文件作用我的关注点
arch/arm/CPU 架构相关代码启动汇编、异常向量表
board/ti/j721e/TI Jacinto 板级代码DDR 配置、时钟树、板级初始化
common/U-Boot 通用逻辑命令行、环境变量、启动命令
drivers/外设驱动DDR、Timer、Clock、网络、存储
dts/设备树源文件硬件描述,移植重点
spl/SPL 第二阶段加载器早期初始化、DDR 训练
我的习惯:拿到一个新板子,先 grep 一下 board_init_f 和 board_init_r,这两个函数是板级初始化的入口,顺着它们就能理清整个启动脉络。

3.2 SPL 与 ATF 启动流程:三级跳

TI Jacinto 的启动不是一步到位的。它分三个阶段:ROM → SPL → ATF → U-Boot。为什么这么折腾?因为芯片复位后,DDR 还没初始化,片上 SRAM 又小,装不下完整的 U-Boot。所以得先跑一个小加载器(SPL),把 DDR 初始化好,再把 ATF 和 U-Boot 主体加载到 DDR 里。

我画了一张流程图,帮你理清这个三级跳的关系:

TI Jacinto 三级启动流程 TI Jacinto 三级启动流程(ROM → SPL → ATF → U-Boot) ROM Boot 片上 ROM,不可修改 加载 SPL SPL 初始化 DDR、Timer 加载 ATF ATF (BL31) 安全监控、PSCI 返回 SPL U-Boot 主体 加载内核、设备树 启动 Linux 内核 关键点: • SPL 运行在片上 SRAM,DDR 初始化后才能加载 ATF 和 U-Boot • ATF 提供安全监控层,处理 PSCI(电源管理)和异常等级切换

你看,SPL 是承上启下的关键。它不仅要初始化 DDR,还要把 ATF 和 U-Boot 从存储介质(eMMC、SD 卡、NAND)搬到 DDR 里。ATF 跑完后,控制权回到 SPL,再由 SPL 跳转到 U-Boot 主体。

我曾经踩过的坑:SPL 的链接地址必须和 ROM 约定的加载地址一致。有一次我改了 CONFIG_SPL_TEXT_BASE,忘了同步 SPL_LOAD_ADDR,结果 SPL 加载 ATF 时直接写到了错误地址,板子死活起不来。查了两天才发现是地址没对齐。

3.3 TI Jacinto 平台 DTS 配置:硬件描述的艺术

设备树(DTS)是 U-Boot 和内核共享的硬件描述语言。TI Jacinto 的 DTS 文件在 arch/arm/dts/ 目录下,命名规则是 k3-j721e-*.dts。我一般从 k3-j721e-common-proc-board.dts 入手,这个文件包含了大部分板级配置。

DTS 配置的核心是描述「外设挂在哪个总线上、用了哪些引脚、时钟频率多少」。举个例子,配置 DDR 控制器:

// k3-j721e-ddr.dtsi (片段)
&ddr0 {
    status = "okay";
    reg = <0x0 0x80000000 0x0 0x80000000>;  // 2GB DDR 地址范围
    ti,ddr-type = <DDR_TYPE_LPDDR4>;
    ti,ddr-speed-bin = <DDR_SPEED_3733>;
    ti,ddr-fdqs = <1>;
    ti,ddr-fdqs-mb = <0>;
};

这里 ti,ddr-type 和 ti,ddr-speed-bin 是 TI 自定义的属性,U-Boot 的 DDR 驱动会解析这些值来配置时序参数。你想想看,如果这里写错了,DDR 训练就会失败,板子连 SPL 都过不去。

移植 DTS 的步骤(我自己的流程):
  1. 从 TI 官方 SDK 复制一份相近的 DTS 文件
  2. 修改 model 和 compatible 字符串,匹配你的板子
  3. 对照原理图,调整 pinctrl 引脚复用配置
  4. 根据 DDR 颗粒手册,更新 ddr 节点下的时序参数
  5. 编译测试,用 fdtget 和 fdtput 调试

3.4 DDR/Timer/Clock 外设初始化:三板斧

SPL 里最核心的三件事:配时钟、初始化 DDR、启动 Timer。这三件事的顺序不能乱——先有时钟,DDR 才能工作;DDR 好了,才能把 U-Boot 主体加载进来。

3.4.1 Clock 初始化:先让芯片「心跳」起来

TI Jacinto 的时钟树很复杂,有 PLL、分频器、门控。SPL 里一般只初始化必要的时钟域:CPU、DDR、UART、MMC。代码在 board/ti/j721e/ 下的 clock.c 里。

// clock.c 片段
void spl_clock_init(void)
{
    /* 使能主 PLL,输出 2GHz */
    pll_enable(MAIN_PLL);
    pll_set_freq(MAIN_PLL, 2000000000);
    
    /* 配置 DDR PLL,输出 933MHz(DDR 频率的一半) */
    pll_enable(DDR_PLL);
    pll_set_freq(DDR_PLL, 933000000);
    
    /* 使能 UART 时钟 */
    clock_enable(UART0_CLK);
}

嗯,这里要注意:DDR PLL 的频率不是 DDR 的工作频率,而是 DDR 控制器的参考时钟。LPDDR4 的传输速率是 3733MT/s,控制器时钟一般是 933MHz(1/4 速率)。

3.4.2 DDR 初始化:最考验耐心的部分

DDR 初始化是 SPL 里最复杂的部分。TI 提供了 ddr_init() 函数,它会做以下事情:

  • 配置 DDR 控制器寄存器:时序参数、刷新率、ODT 配置
  • 执行 DDR 训练:写电平校准、读 DQ 训练、CA 训练
  • 验证 DDR 可用性:写一个模式再读回来,确认无误

我遇到过最头疼的问题,是 DDR 训练时写电平校准失败。后来发现是 PCB 走线等长没做好,导致 DQS 和 DQ 的延迟差太多。硬件改版后就好了。

调试技巧:如果 DDR 初始化失败,先检查 CONFIG_DDR_TRAINING 是否开启。关闭训练模式可以跳过校准,但只能用于验证基本读写。量产时必须开启完整训练。

3.4.3 Timer 初始化:给系统一个「心跳」

Timer 的作用是提供延时和超时功能。SPL 里一般用 ARM 的通用定时器(Generic Timer),或者 SoC 内部的定时器模块。初始化很简单:

// timer.c 片段
void timer_init(void)
{
    /* 使能定时器时钟 */
    clock_enable(TIMER0_CLK);
    
    /* 设置定时器频率为 1MHz */
    timer_set_freq(TIMER0, 1000000);
    
    /* 启动定时器 */
    timer_start(TIMER0);
}

有了 Timer,U-Boot 才能做 mdelay()、udelay() 这些延时操作。没有 Timer,DDR 训练时的等待时序就没法保证。

3.5 U-Boot 网络与存储驱动:让板子「联网」和「存数据」

U-Boot 的网络和存储驱动,是移植工作中最常打交道的地方。因为你要从网络下载内核,从存储设备加载设备树。

3.5.1 网络驱动:CPSW 与 MDIO

TI Jacinto 使用 CPSW(Common Platform Switch)作为以太网控制器。驱动在 drivers/net/ti/ 下。移植时主要配置:

  • PHY 地址:通过 MDIO 总线访问 PHY,地址由硬件决定
  • MAC 地址:可以从 EEPROM 或 OTP 读取,也可以随机生成
  • DMA 描述符:配置发送和接收描述符的地址和数量
// 网络初始化示例
int board_eth_init(struct bd_info *bis)
{
    /* 复位 PHY */
    phy_reset(PHY_ADDR);
    mdelay(100);
    
    /* 配置 MAC 地址 */
    eth_env_set_enetaddr("ethaddr", mac_addr);
    
    /* 注册 CPSW 设备 */
    cpsw_register(&cpsw_data);
    return 0;
}

3.5.2 存储驱动:eMMC 与 SD 卡

存储驱动基于 MMC 子系统。TI Jacinto 的 MMC 控制器是 sdhci 兼容的。移植时关注:

配置项说明常见值
CONFIG_MMC启用 MMC 子系统y
CONFIG_MMC_SDHCI启用 sdhci 驱动y
CONFIG_MMC_SDHCI_ADMA启用 ADMA2 模式y
CONFIG_SUPPORT_EMMC_BOOT支持 eMMC 启动分区y

我建议你在移植时,先用 SD 卡启动调试,等 U-Boot 跑稳了再切到 eMMC。因为 SD 卡可以随时拔下来换镜像,调试效率高很多。

注意:eMMC 的启动分区(BOOT0/BOOT1)是只读的,写入前需要先 mmc bootbus 和 mmc partconf 配置。我曾经不小心写错了分区,导致 eMMC 启动失败,最后用 SD 卡救回来的。

3.6 本章小结

U-Boot 启动移植,说白了就是三板斧:看懂源码结构、理清 SPL→ATF→U-Boot 的启动链、配好 DTS 和外设驱动。TI Jacinto 平台虽然复杂,但只要抓住 DDR 初始化这个核心,其他都是水到渠成的事。

我个人觉得,移植过程中最有成就感的一刻,就是看到串口输出 U-Boot SPL 2023.04 那行字。嗯,那一刻你知道,板子活了。