0%

Linux驱动学习实战(1) 移植UBoot到SD卡启动

干什么

  昨天我跟随着正点原子的教程学习了U-Boot如何移植,今天我决定从官网上下载最新的U-Boot并尝试移植到自己的板子上。访问官网,下载最新版的U-Boot源码压缩包(截止文章创建时是2026.01),拖入虚拟机并解压。(亲测在正点原子提供的16.04下会出许多问题,所以我在下载好编译器后转到了24.04的ubuntu版本运行。)

图 1 文件目录

怎么干

配置 Makefile

  在根目录下的Makefile中添加以下语句,并保存

1
2
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-

尝试编译标准配置

  在根目录下configs文件夹中找到mx6ull_14x14_evk_defconfig,在终端中输入如下命令:

make clean
make mx6ull_14x14_evk_defconfig
make V=1 -j16

  不出意外的话就要出意外了,正点原子提供的编译器版本过老,需要先升级一下。

图 2 报错

在网站下载11.2版本的GCC工具链,并在虚拟机中安装。先下载安装包,拖入后解压,将U-Boot根目录下Makefile改为

CROSS_COMPILE = /home/alientek/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-

再次编译,会报一些无依赖的错误,我们依次安装。最终编译成功

图 3 第一次编译成功

将SD卡插入开发板,用串口连接输出以下信息

图 4 输出

  由上图可见,只有网络设备出了问题,我们就移植这一部分。

创建自己的开发板文件

新建 defconfig 文件

  在根目录下的configs文件夹中复制一份mx6ull_14x14_evk_defconfig并重命名为mx6ull_hcw_defconfig。

新建include/configs/xxx.h文件

  在此文件夹中复制一份mx6ull_14x14_evk.h,并将其改名为mx6ull_hcw.h

新建板级文件

  在board/freescale文件夹下,复制mx6ullevk并重命名为mx6ull_hcw,修改其中C文件为文件夹同名.c,并且修改其中Makefile为:

1
2
3
4
# SPDX-License-Identifier: GPL-2.0+
# (C) Copyright 2016 Freescale Semiconductor, Inc.

obj-y := mx6ull_hcw.o

将 imximage.cfg 中的下面一句:

PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000

改为:

PLUGIN board/freescale/mx6ull_hcw/plugin.bin 0x00907000

将原文件中的原目录名改成现目录名

1
2
3
4
5
6
7
8
9
10
11
12
if TARGET_MX6UL_14X14_EVK || TARGET_MX6UL_9X9_EVK

config SYS_BOARD
default "mx6ull_alientek_emmc"

config SYS_VENDOR
default "freescale"

config SYS_CONFIG_NAME
default "mx6ull_alientek_emmc"

endif

修改此目录下的MAINTAIN文件,同上几步,也是改成自己的文件目录

试着编译一下

运行

1
2
3
make clean
make mx6ull_hcw_defconfig
make V=1 -j16

无报错,烧录,可以看到我们新建的板级文件可以正常运行,接下来让我们修改网口部分。

图 5 输出

移植网口部分

  2026版本和2016不同的是使用了默认设备树方式进行配置,从mx6ull_hcw_defconfig中可以看出

1
CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk"

  进入/arch/arm/dts文件夹,打开imx6ull-14x14-evk.dts,可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
//
// Copyright (C) 2016 Freescale Semiconductor, Inc.

/dts-v1/;

#include "imx6ull.dtsi"
#include "imx6ul-14x14-evk.dtsi"

/ {
model = "Freescale i.MX6 UltraLiteLite 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
};

&clks {
assigned-clocks = <&clks IMX6UL_CLK_PLL3_PFD2>;
assigned-clock-rates = <320000000>;
};

dts和dtsi文件的关系如下

.dts = 最终可用的“整板描述文件”
.dtsi = 被包含的“设备树片段 / 模块”
dts会被直接编译生成dtb,能被uboot使用

而我们涉及到的这几个文件的主要作用分别是:

1
2
3
imx6ull.dtsi            ← 芯片出厂说明书
imx6ul-14x14-evk.dtsi ← 官方参考板接线图
imx6ull-14x14-evk.dts ← “把这块板子声明出来”

  因此我们应着重修改imx6ul-14x14-evk.dtsi文件。

  复制imx6ull-14x14-evk.dts和imx6ul-14x14-evk.dtsi,并将其重命名为imx6ull_hcw.dts和imx6ul-hcw.dtsi,将imx6ull-hcw.dts中的头文件包含改为。

1
2
#include "imx6ull.dtsi"
#include "imx6ul-hcw.dtsi"

  同时需要在 xxx_defconfig 中将CONFIG_DEFAULT_DEVICE_TREE修改为 “imx6ull-hcw”

  修改好后,试着重新编译,发现无报错。接下来我们就分析imx6ul-hcw.dtsi文件,让网口驱动跑起来!

  打开imx6ul-hcw.dtsi,找到原来的网口配置代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <&ethphy1>;
phy-supply = <&reg_peri_3v3>;
status = "okay";

mdio {
#address-cells = <1>;
#size-cells = <0>;

ethphy0: ethernet-phy@2 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <2>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";

};

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-id0022.1560";
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};
姓名 是什么 作用
FEC/MAC i.MX6ULL 芯片内部的一个 硬件模块 帧收发、DMA、校验和、高速数据通道。
PHY 一颗真正的芯片 模拟信号、10/100M编码、网线插拔检测、LED驱动,网口与SOC之间的桥梁
MDIO 一条管理总线 读 PHY ID、读链路状态、配速率。MDIO 引脚是 从 FEC 出来的,PHY 只是“被管理对象”

  SoC 与网口之间的数据传输通过 PHY 完成,而 PHY 的配置与状态管理通过 MDIO 进行。 以 fec2(第二个 FEC MAC)为例,这份代码第2-3行配置了这个设备只有一种引脚模式,为default,并且配置是pinctrl_enet2,在下面代码中有定义。phy-mode = “rmii”表明在这颗phy和soc之间使用的是RMII接口模式,phy-handle = <&ethphy1>表明这个网口接的是哪个芯片,在下面的代码中有定义,phy-supply = <&reg_peri_3v3>告诉系统这个PHY的电源使用3.3V,status = “okay”表示设备启用。

  mdio{…}是指MDIO总线节点,ethphy0是我的第一个phy,compatible = “ethernet-phy-ieee802.3-c22”表示这是一个符合 IEEE 802.3 Clause 22 规范的以太网 PHY,设备地址是2,clocks = <&clks IMX6UL_CLK_ENET_REF>;表明这个 PHY 使用 i.MX6ULL 输出的 ENET 参考时钟,clock-names 用于为该时钟命名以便驱动引用。

那么接下来我们开始移植,找到引脚配置代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pinctrl_enet1: enet1grp {
fsl,pins = <
MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031
>;
};

pinctrl_enet2: enet2grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0
MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0
MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0
MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0
MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031
>;
};

  以pinctrl_enet1: enet1grp为例,这段代码定义了一组ENET1网口要用到的引脚,并指定每个引脚的功能,每个引脚用什么电气参数(PAD控制),其格式为

[物理引脚]__ [复用成什么功能]    [电气参数]

我们新建一个网口复位引脚,如下

1
2
3
4
5
6
7
8
9
10
pinctrl_enet1_reset: enet1resetgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x000000
>;
};
pinctrl_enet2_reset: enet2resetgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x000000
>;
};

  FEC配置代码修改后如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <&ethphy0>;
phy-supply = <&reg_peri_3v3>;
status = "disabled";
};

&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2>;
phy-mode = "rmii";
phy-handle = <&ethphy1>;
phy-supply = <&reg_peri_3v3>;
status = "okay";
local-mac-address = [02 12 34 56 78 9a];

mdio {
#address-cells = <1>;
#size-cells = <0>;

ethphy0: ethernet-phy@2 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <2>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";

reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <10000>;

};

ethphy1: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";


reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <10000>;
};
};
};

  主要修改了以下几个部分

①、关闭FEC1,只使能FEC2,因为正点原子的MINI板上与ENET相连的是FEC2。
②、修改 compatible ,告诉内核 PHY 是一个标准 Clause22 PHY,让内核自己识别。
③、添加复位代码,当 U-Boot 决定要用这个 PHY 时,拉这根复位脚。
④、设定本地 MAC 地址
图 6 网卡驱动成功

结语

在这个过程中,我逐步理清了几个过去容易混淆的概念:FEC 只是 SoC 内部的 MAC 控制器,真正与网线打交道的是 PHY;RMII 承载的是高速数据,而 MDIO 只是一个低速的“管理通道”;设备树的职责不是“写驱动”,而是准确描述硬件连接关系,让通用驱动能够工作。

接下来,可以在此基础上继续深入:例如将 MAC 地址移入 EEPROM、分析 PHY 自协商过程,或者进一步对接 Linux 内核网络子系统。

但无论走到哪一步,这次移植经历已经证明了一点——理解结构,比跑通结果更重要。