3、U-Boot板级移植实战:添加新板级支持、修改设备树(DTB)、配置DDR/LPDDR参数

好,咱们直接进入正题。这一章是U-Boot移植里最核心、也最折腾人的部分。说白了,就是让U-Boot在你的新板子上跑起来,能认内存、能启动内核。

我刚开始做移植的时候,以为改改配置文件就完事了。结果第一次烧进去,串口连个log都没有。嗯,后来才发现,板级支持没加对,DDR参数配错了。今天我就把这些坑一个个给你讲清楚。

3.1 添加新板级支持:从零开始

U-Boot的板级支持,说白了就是告诉U-Boot:你的板子叫什么名字、用什么CPU、外设怎么初始化。我习惯从已有的参考板开始改,这样最快。

3.4.1 创建板级目录

先看看U-Boot源码里的board/目录。比如你的芯片是i.MX6ULL,那就找board/freescale/mx6ull_14x14_evk/。把这个目录复制一份,改成你的板子名字:

cd u-boot
cp -r board/freescale/mx6ull_14x14_evk board/freescale/myboard

然后改里面的文件。主要改这几个:

  • Kconfig:定义板子的配置选项
  • MAINTAINERS:写上你的名字和邮箱
  • Makefile:编译规则
  • board.c:板级初始化代码

我的习惯:改Kconfig的时候,把板子名字写清楚。比如TARGET_MYBOARD,后面编译的时候要用这个宏。

3.1.2 配置头文件

头文件在include/configs/目录下。同样复制参考板的头文件:

cp include/configs/mx6ull_14x14_evk.h include/configs/myboard.h

这个头文件里定义了板子的各种参数,比如:

  • DDR大小和地址
  • 环境变量分区
  • 串口、网口等外设配置
  • 启动命令

举个例子,DDR配置部分:

#define PHYS_SDRAM_SIZE           (512 * 1024 * 1024)  /* 512MB DDR */
#define CONFIG_SYS_SDRAM_BASE      PHYS_SDRAM
#define CONFIG_SYS_INIT_RAM_ADDR   0x00900000
#define CONFIG_SYS_INIT_RAM_SIZE   0x00020000

我曾经踩过的坑:DDR大小写错了,U-Boot能启动,但内核一跑就挂。后来查了半天,发现是头文件里PHYS_SDRAM_SIZE少写了一个0。这种低级错误,调试起来真要命。

3.2 修改设备树(DTB):让内核认识你的硬件

设备树,说白了就是硬件的“户口本”。U-Boot启动内核的时候,会把DTB传给内核,内核才知道有哪些外设、怎么初始化。

3.2.1 DTS文件结构

设备树源文件(.dts)在arch/arm/dts/目录下。同样复制参考板的:

cp arch/arm/dts/imx6ull-14x14-evk.dts arch/arm/dts/imx6ull-myboard.dts

一个典型的DTS文件长这样:

/dts-v1/;
#include "imx6ull.dtsi"

/ {
    model = "MyBoard i.MX6ULL Board";
    compatible = "myboard,imx6ull", "fsl,imx6ull";

    memory {
        reg = <0x80000000 0x20000000>;  /* 512MB DDR */
    };

    chosen {
        stdout-path = &uart1;
    };
};

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1>;
    status = "okay";
};

这里要注意几个关键点:

  • model:板子名字,随便写,但要有辨识度
  • compatible:兼容性字符串,内核匹配驱动用的
  • memory:DDR的起始地址和大小
  • chosen:指定控制台串口

3.2.2 修改引脚复用

每个外设都要配置引脚复用。比如你的板子把UART1改到了其他引脚,那就得改pinctrl部分:

&iomuxc {
    pinctrl_uart1: uart1grp {
        fsl,pins = <
            MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX   0x1b0b1
            MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX   0x1b0b1
        >;
    };
};

关键点:引脚配置值(比如0x1b0b1)决定了驱动能力、上下拉、速度等。这个值得看芯片手册,或者从参考板抄过来改。我一般先抄参考板的,跑起来再调。

3.3 配置DDR/LPDDR参数:内存初始化

DDR配置是移植里最头疼的部分。参数配错了,轻则系统不稳定,重则直接死机。我见过有人调DDR参数调了一周。

3.3.1 DDR控制器初始化流程

U-Boot里DDR初始化一般在SPL阶段完成。流程大概是:

  1. 配置DDR控制器时钟
  2. 设置DDR时序参数
  3. 校准DDR PHY
  4. 读写测试验证

以i.MX6ULL为例,DDR初始化代码在board/freescale/myboard/spl.c里:

void board_init_f(ulong dummy)
{
    /* 初始化DDR */
    ddr_init(&ddr_settings);
    
    /* 配置DDR控制器 */
    mmdc_configure(&ddr_cfg);
    
    /* 校准DDR */
    ddr_calibration();
    
    /* 跳转到DRAM */
    dram_init();
}

3.3.2 DDR参数表

DDR参数通常放在一个结构体里。我习惯用表格把关键参数列出来:

参数 说明 典型值(DDR3 512MB)
tCL CAS延迟 11
tRCD 行地址到列地址延迟 11
tRP 行预充电时间 11
tRAS 行激活时间 28
tWR 写恢复时间 12
tRFC 刷新周期 208
时钟频率 DDR时钟 400MHz

这些参数从哪里来?从DDR芯片的数据手册里找。比如你用的是MT41K256M16,那就去Micron官网下载datasheet,找到时序参数表。

避坑指南:我曾经图省事,直接抄了参考板的DDR参数。结果板子跑起来一会儿就死机。后来发现参考板用的是DDR3L(1.35V),我的板子用的是DDR3(1.5V),时序参数不一样。所以,一定要看自己板子上DDR芯片的datasheet。

3.3.3 LPDDR的特殊处理

LPDDR和普通DDR3/4不太一样。主要区别:

  • 电压更低:LPDDR3是1.2V,LPDDR4是1.1V
  • 刷新方式不同:LPDDR有自动刷新和自刷新
  • 时序更紧:延迟参数更小

配置LPDDR的时候,要特别注意ZQ校准和ODT配置。我一般会在初始化代码里加一段调试打印,把DDR的配置值打出来确认:

printf("DDR: %dMB, %dMHz, tCL=%d, tRCD=%d\n",
       ddr_size, ddr_freq, ddr_cfg.tCL, ddr_cfg.tRCD);

3.4 编译与验证

配置改完了,就该编译验证了。我习惯分三步走:

  1. 编译SPL:先确认SPL能跑起来,串口有输出
  2. 编译U-Boot:SPL起来后,加载U-Boot到DDR
  3. 启动内核:用U-Boot启动内核,验证设备树

编译命令:

make myboard_defconfig
make -j4

烧录到SD卡或eMMC:

sudo dd if=SPL of=/dev/sdb bs=1k seek=1
sudo dd if=u-boot.img of=/dev/sdb bs=1k seek=69

我的调试技巧:第一次烧录,先别急着跑内核。在U-Boot命令行里敲md命令读一下DDR地址,确认内存能正常读写。比如md 0x80000000 10,如果能读出数据,说明DDR初始化基本没问题。

3.5 本章知识体系

下面这张图把整个板级移植的流程串起来了。你可以对照着看,每一步该做什么:

U-Boot板级移植核心流程 创建板级目录 配置头文件 修改设备树(DTB) 配置DDR/LPDDR参数 编译与验证 关键文件 board/<vendor>/<board>/ include/configs/<board>.h arch/arm/dts/<board>.dts 调试技巧 串口log确认启动流程 md命令验证DDR读写 printenv查看环境变量 目标:U-Boot能启动 → 内核能加载 → 系统能运行

嗯,这张图把整个流程串起来了。从创建板级目录开始,到配置头文件、修改设备树、配置DDR参数,最后编译验证。每一步都有对应的文件和调试方法。

说实话,板级移植没有捷径。我第一次移植的时候,光是DDR参数就调了三天。但只要你按照这个流程来,每一步都验证通过,最后一定能跑起来。记住:串口log是你的好朋友,多打印、多确认,问题就能定位得快。