前言:Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段 bootloader 程序。而 uboot 作为最常见的 bootloader 选择,是每个 Linux 驱动工程师必需掌握的。本文为博主总结的 uboot 移植笔记,包含作者总结的 uboot 启动流程概述,希望能给大家移植和学习 uboot 的时候提供参考与帮助。

实验硬件:imx6ull;uboot版本:2016.03

一、U-Boot简介

1.1 Bootloader介绍

Linux系统启动的第一个关键程序bootloader,也就是说芯片上电后先运行一段bootloader程序。bootloader 和 Linux 内核的关系 就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。所以Linux 系统移植第一步就需要进行bootloader的移植,现有的bootloader有很多,比如:U-Boot、vivi、RedBoot 等,其中以 U-Boot 使用最为广泛。

常见的Bootloader:

1.2 uboot启动流程

1、uboot的读取方式

(1)、SD卡读取;(2)、Flash读取;(考虑到部分Linux开发板的Flash为NAND Flash每次重写烧入需要反复擦除后重写,为了保证Flash的寿命,大都采用SD卡读取启动的方式)

2、uboot的启动流程

uboot的两大主题:(1)稳定性;(2)速度;
稳定性分析:uboot作为芯片上电第一道运行的程序,如果它出错将导致后续内核启动,根文件系统挂载等都无从谈起。
速度分析:uboot的速度是直接与性能挂钩的,uboot为了提升自身速度,会选择自挂载挂载内核到内存上运行,提升速度。

★作者将uboot的总体启动流程分为2部分:arch级初始化(架构)板级初始化

uboot启动流程总结:

uboot的语言构成:10%的汇编语言;90%的C语言
uboot的启动特性:稳定性;速度
uboot的简化版启动流程:
1、设置状态寄存器 cpsr ,使CPU进入 SVC 特权模式,并且禁止 FIQ 和 IRQ;
2、关闭看门狗、中断、MMU、Cache;
3、初始化部分寄存器和外设(时钟、串口、Flash、内存);
4、自搬移uboot到内存中运行;
5、设置栈空间并初始化global_data;
6、剩余大部分硬件的初始化;
7、搬移Linux内核到内存;

推荐学习博客:Linux驱动开发:uboot启动流程详解_uboot启动linux_混分巨兽龙某某的博客-CSDN博客

二、uboot移植教学

嵌入式工程必备的技能就是贯通,需要从一种开发板的学习去学会其他开发板的大致开发流程(以一窥视万物)。

2.1 NXP官方uboot移植

为了保证产品研发的速度以及降低研发过程中错误率,公司产品一般都会以半导体厂商的开发板作为 ”蓝本“。自然地,uboot 的移植和使用肯定也是可以直接借鉴该 ”蓝本“。

NXP 官方原版的 Uboot 发送到 Ubuntu 中并解压,然后创建 VSCode 工程。

在 NXP 官方原版的 Uboot 中的 configs 中提供了很多的默认配置文件,其中以 mx6ul 开头的是 I.MX6UL 芯片的,mx6ull 开头的是 I.MX6ULL 开发板的。我们以关联性最强的 mx6ull_14x14_evk_emmc_defconfig 作为默认配置文件。

为了方便后续进行修改编译,作者建议编写如下的 shell 脚本(详情见代码注释),shell 脚本名为 mx6ull_14x14_emmc.sh,代码如下:

#!/bin/bash
#清理工程,每次编译uboot之前都清理一下工程
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
#配置uboot,选择与自己开发板最接近的uboot配置文件进行编译,方便后期修改
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
#“make -j12”也就是使用 12 核来编译 uboot;v=1是设置编译过程的信息输出级别,1完全输出,0/无简化
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

通过命令行:chmod 777 mx6ull_14x14_emmc.sh 赋予 shell 脚本可执行权限!通过下述命令运行该 shell 脚本:

./mx6ull_14x14_emmc.sh

编译完成以后会生成 u-boot.bin、u-boot.imx 等文件,但是这些文件是 NXP 官方 I.MX6ULL EVK 开发板。

imxdownload 软件拷贝到 uboot 源码根目录下,然后使用 imxdownload 软件将 u-boot.bin 烧写到 SD 卡中,烧写命令如下:

chmod 777 imxdownload //给予 imxdownload 可执行权限
./imxdownload u-boot.bin /dev/sdb //烧写到 SD 卡中,不能烧写到/dev/sda 或 sda1 里面

友情提示:SD 卡在 Linux 系统下的设备名称不一定是sdd亦或是sdb,通过:sudo fdisk -l 命令去检查一下 SD 卡到底是什么之后再进行烧入!

烧写完成以后将 SD 卡插入 I.MX6U-ALPHA 开发板的 TF 卡槽中,最后设置开发板从 SD 卡启动。打开 MobaXterm,设置好开发板所使用的串口并打开,复位开发板,MobaXterm 接收到如下图所示信息:

uboot 成功加载之后通常需要检查:(1)、SD 卡和 EMMC 驱动是否正常(常规情况下都是正常的),(2)、LCD 屏幕驱动是否正常(问题不大);(3)、网络驱动是否正常(核心部分)

考虑到后续需要通过网络加载 Linux 内核镜像等,所以网络驱动是必不可缺的部分!

uboot 启动的时候提示 “Board Net Initialization Failed” “No ethernet found.” 这两行,说明网络驱动存在大问题。

作者概述:实际工作中, 一般情况下 SD 卡和 EMMC 等内存驱动都不会存在太大问题,核心部分的问题还是出现在网络驱动部分(比如:S3C2440)!!!

2.2 修改NXP官方uboot

先在 configs 目录下创建默认配置文件,复制 mx6ull_14x14_evk_emmc_defconfig,然后重命名为 mx6ull_alientek_emmc_defconfig,命令如下:

cd configs
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_alientek_emmc_defconfig

然后将文件 mx6ull_alientek_emmc_defconfig 中的内容改成下面的:

CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
CONFIG_CMD_GPIO=y

可以看出,mx6ull_alientek_emmc_defconfig 基本和 mx6ull_14x14_evk_emmc_defconfig 中的内容一样,只是第 1 行和第 4 行做了修改。修改的 2 行就是编译 uboot 配置时可以根据自己复制的副本进行配置编译了!

2.2.1 添加开发板对应的头文件

在目录 include/configs 下添加 I.MX6ULL-ALPHA 开 发 板 对 应 的 头 文 件 , 复 制 include/configs/mx6ullevk.h,并重命名为 mx6ull_alientek_emmc.h,命令如下:

cp include/configs/mx6ullevk.h mx6ull_alientek_emmc.h

修改防止重定义的 #ifndef#define 的预处理代码:

mx6ull_alientek_emmc.h 里面有很多宏定义,这些宏定义基本用于配置 uboot,也有一些 I.MX6ULL 的配置项目。如果我们自己要想使能或者禁止 uboot 的某些功能,那就在 mx6ull_alientek_emmc.h 里面做修改即可。

2.2.2 添加开发板对应的板级文件夹

uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。NXP 的 I.MX 系列芯片的所有板级文件夹都存放在 board/freescale 目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为 mx6ull_alientek_emmc,命令如下:

cd board/freescale/
cp mx6ullevk/ -r mx6ull_alientek_emmc

进入 mx6ull_alientek_emmc 目录中 , 将 其 中 的 mx6ullevk.c 文 件 重 命 名 为 mx6ull_alientek_emmc.c,命令如下:

cd mx6ull_alientek_emmc
mv mx6ullevk.c mx6ull_alientek_emmc.c

我们还需要对 mx6ull_alientek_emmc 目录下的文件做一些修改

1、修改 mx6ull_alientek_emmc 目录下的 Makefile 文件

mx6ull_alientek_emmc 下的 Makefile 文件内容改为如下所示:

第 6 行的 obj-y,改为 mx6ull_alientek_emmc.o,这样才会编译 mx6ull_alientek_emmc.c 这个文件。

2、修改 mx6ull_alientek_emmc 目录下的 imximage.cfg 文件

imximage.cfg 中的下面一句:PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000

改为:PLUGIN board/freescale/mx6ull-alientek_emmc/plugin.bin 0x00907000

3、修改 mx6ull_alientek_emmc 目录下的 Kconfig 文件

修改 Kconfig 文件,修改后的内容如下:

if TARGET_MX6ULL_ALIENTEK_EMMC 

config SYS_BOARD
    default "mx6ull_alientek_emmc"

config SYS_VENDOR
    default "freescale"

config SYS_CONFIG_NAME
    default "mx6ull_alientek_emmc"

endif

4、修改 mx6ull_alientek_emmc 目录下的 MAINTAINERS 文件

修改 MAINTAINERS 文件,修改后的内容如下:

MX6ULLEVK BOARD
M:    Peng Fan <peng.fan@nxp.com>
S:    Maintained
F:    board/freescale/mx6ull_alientek_emmc/
F:    include/configs/mx6ull_alientek_emmc.h
F:    configs/mx6ull_alientek_emmc_deconfig

2.2.3 修改 U-Boot 图形界面配置文件

uboot 是支持图形界面配置。修改文件 arch/arm/cpu/armv7/mx6/Kconfig (如果用的 I.MX6UL 的话,应该修改 arch/arm/Kconfig 这个文件),在 207 行加入如下内容:

config TARGET_MX6ULL_ALIENTEK_EMMC
    bool "Support mx6ull_alientek_emmc"
    select MX6ULL
    select DM
    select DM_THERMAL

在最后一行的 endif 的前一行添加如下内容:

source "board/freescale/mx6ull_alientek_emmc/Kconfig"

上述操作都是为了能够创建出自己制作的开发板的编译和配置文件,这样以后就可以在自己创建的副本中改动,不需要在半导体厂商提供的 ”蓝本“ 代码上改动!

2.3 LCD驱动修改

一般 uboot 中修改驱动基本都是在 xxx.h xxx.c 这两个文件中进行的,xxx 为板子名称,比如 mx6ull_alientek_emmc.h mx6ull_alientek_emmc.c 这两个文件。

一般修改 LCD 驱动重点注意以下几点:
①、LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
②、LCD 背光引脚 GPIO 的配置。
③、LCD 配置参数是否正确。

正点原子的 I.MX6U-ALPHA 开发板 LCD 原理图和 NXP 官方 I.MX6ULL 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。需要修改的是 LCD 参数,打开文件 mx6ull_alientek_emmc.c,找到如下所示内容:

struct display_info_t const displays[] = {{
    .bus = MX6UL_LCDIF1_BASE_ADDR,
    .addr = 0,
    .pixfmt = 24,
    .detect = NULL,
    .enable    = do_enable_parallel_lcd,
    .mode    = {
        .name            = "TFT43AB",
        .xres           = 480,
        .yres           = 272,
        .pixclock       = 108695,
        .left_margin    = 8,
        .right_margin   = 4,
        .upper_margin   = 2,
        .lower_margin   = 4,
        .hsync_len      = 41,
        .vsync_len      = 10,
        .sync           = 0,
        .vmode          = FB_VMODE_NONINTERLACED
        } 
    } 
};

代码中定义了一个变量 displays,类型为 display_info_t,这个结构体是 LCD 信息结构体,其中包括了 LCD 的分辨率,像素格式,LCD 的各个参数等。 我们需要根据自己 LCD 屏幕的参数去修改该 displays 变量内的数据,作者是 7 寸屏幕,修改后如下:

struct display_info_t const displays[] = {{
    .bus = MX6UL_LCDIF1_BASE_ADDR,
    .addr = 0,
    .pixfmt = 24,
    .detect = NULL,
    .enable    = do_enable_parallel_lcd,
    .mode    = {
        .name            = "TFT7016",
        .xres           = 1024,
        .yres           = 600,
        .pixclock       = 19531,
        .left_margin    = 140,
        .right_margin   = 160,
        .upper_margin   = 20,
        .lower_margin   = 12,
        .hsync_len      = 20,
        .vsync_len      = 3,
        .sync           = 0,
        .vmode          = FB_VMODE_NONINTERLACED
        } 
    } 
};

打开 mx6ull_alientek_emmc.h,找到所有如下语句:

panel = TFT43AB 改为 panel = TFT7016

2.4 网络驱动修改

网络驱动的修改是非常繁琐且重要的,因为后续 Linux 内核的加载,虚拟根文件系统 rootfs 都离不开网络驱动的支持。

2.4.1 I.MX6U-ALPHA 开发板网络简介

I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET,需要外接一个 PHY 芯片来实现网络通信功能,也就是内部 MAC+外部 PHY 芯片的方案。大家可能听过 DM9000 这个网络芯片,在一些没有内部 MAC CPU 中,比如三星的 2440,4412 等,就会采用 DM9000 来实现联网功能。DM9000 提供了一个类似 SRAM 的访问接口,主控 CPU 通过这个接口即可与 DM9000 进行通信,DM9000 就是一个 MAC+PHY 芯片。

I.MX6UL/ULL 有两个网络接口 ENET1ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1ENET2 都使用 LAN8720A 作为 PHY 芯片(正点的外部 PHY 芯片与 NXP 官方的 IMX6ULL 不一样)。

网络驱动的匹配需要根据硬件的 PCB 原理图来进行分析,需要把握住根据 PHY 芯片的连接引脚与芯片驱动进行修改!

正点原子 IMX6ULL ENET1

ENET1 的网络 PHY 芯片为 LAN8720A,通过 RMII 接口与 I.MX6ULL 相连,正点原子 I.MX6U-ALPHA 开发板的 ENET1 引脚与 NXP 官方的 I.MX6ULL EVK 开发板基本一样,唯独复位引脚不同。从上图可以看出,正点原子 I.MX6U-ALPHA 开发板的 ENET1 复位引脚 ENET1_RST 接到了 I.M6ULLSNVS_TAMPER7 这个引脚上。I.MX6U-ALPHA 开发板 ENET1 上连接的 LAN8720A器件地址为 0X0,所示我们要修改 ENET1 网络驱动的话重点就三点:

①、ENET1 复位引脚初始化。
②、LAN8720A 的器件 ID。
③、LAN8720 驱动

正点原子 IMX6ULLENET2

关于 ENET2 网络驱动的修改也注意一下三点:

①、ENET2 的复位引脚,从上图可以看出,ENET2 的复位引脚 ENET2_RST 接到了 I.MX6ULL 的 SNVS_TAMPER8 上。
②、ENET2 所使用的 PHY 芯片器件地址,从上图可以看出,PHY 器件地址为 0X1。
③、LAN8720 驱动,ENET1 和 ENET2 都使用的 LAN8720,所以驱动肯定是一样的。

2.4.2 网络 PHY 地址修改

首先修改 uboot 中的 ENET1ENET2PHY 地址和驱动,打开 mx6ull_alientek_emmc.h 这个文件,找到如下代码并进行如下修改:

如果要使用 LAN8720A,那么就得将 CONFIG_PHY_MICREL 改为 CONFIG_PHY_SMSC,也就是使能 uboot 中的 SMSC 公司中的 PHY 驱动,因为 LAN8720A 就是 SMSC 公司生产的。

通过宏定义:CONFIG_FEC_ENET_DEV01 决定是选择启用 ENET1ENET2

需要修改的代码部分如下:
1、修改 ENET1 网络 PHY 的地址。
2、修改 ENET2 网络 PHY 的地址。
3、使能 SMSC 公司的 PHY 驱动。

2.4.3 删除 uboot 中 74LV595 的驱动代码

uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开 mx6ull_alientek_emmc.c,找到如下代码进行如下修改:

ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07ENET2 的复位引脚连接到 SNVS_TAMPER8 上,对应 GPIO5_IO08

/* #define IOX_SDI IMX_GPIO_NR(5, 10)
#define IOX_STCP IMX_GPIO_NR(5, 7)
#define IOX_SHCP IMX_GPIO_NR(5, 11)
#define IOX_OE IMX_GPIO_NR(5, 8) */
#define ENET1_RESET IMX_GPIO_NR(5,7)
#define ENET2_RESET IMX_GPIO_NR(5,8)

继续在 mx6ull_alientek_emmc.c 中找到如下代码也删除:

static iomux_v3_cfg_t const iox_pads[] = {
    /* IOX_SDI */
    MX6_PAD_BOOT_MODE0__GPIO5_IO10 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_SHCP */
    MX6_PAD_BOOT_MODE1__GPIO5_IO11 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_STCP */
    MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
    /* IOX_nOE */
    MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

继续在 mx6ull_alientek_emmc.c 中找到函数 iox74lv_initiox74lv_init 函数是 74LV595 的初始化函数,iox74lv_set 函数用于控制 74LV595IO 输出电平,将这两个函数全部删除掉!

mx6ull_alientek_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被 board_init_r 调用,board_init 会调用 imx_iomux_v3_setup_multiple_padsiox74lv_init 这两个函数来初始化 74lv595GPIO,将这两行删除掉。至此,mx6ull_alientek_emmc.c 中关于 74LV595 芯片的驱动代码都删除掉了,接下来就是添加 I.MX6U-ALPHA 开发板两个网络复位引脚了。

2.4.4 添加 I.MX6U-ALPHA 开发板网络复位引脚驱动

mx6ull_alientek_emmc.c 中存在结构体数组 fec1_padsfec2_padsENET1ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数,完成以后如下所示:

/*
 * pin conflicts for fec1 and fec2, GPIO1_IO06 and GPIO1_IO07 can only
 * be used for ENET1 or ENET2, cannot be used for both.
 */
static iomux_v3_cfg_t const fec1_pads[] = {
    MX6_PAD_GPIO1_IO06__ENET1_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
    MX6_PAD_GPIO1_IO07__ENET1_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_TX_DATA0__ENET1_TDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_TX_DATA1__ENET1_TDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_TX_EN__ENET1_TX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 | MUX_PAD_CTRL(ENET_CLK_PAD_CTRL),
    MX6_PAD_ENET1_RX_DATA0__ENET1_RDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_RX_DATA1__ENET1_RDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_RX_ER__ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET1_RX_EN__ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_SNVS_TAMPER7__GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

static iomux_v3_cfg_t const fec2_pads[] = {
    MX6_PAD_GPIO1_IO06__ENET2_MDIO | MUX_PAD_CTRL(MDIO_PAD_CTRL),
    MX6_PAD_GPIO1_IO07__ENET2_MDC | MUX_PAD_CTRL(ENET_PAD_CTRL),

    MX6_PAD_ENET2_TX_DATA0__ENET2_TDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET2_TX_DATA1__ENET2_TDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 | MUX_PAD_CTRL(ENET_CLK_PAD_CTRL),
    MX6_PAD_ENET2_TX_EN__ENET2_TX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),

    MX6_PAD_ENET2_RX_DATA0__ENET2_RDATA00 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET2_RX_DATA1__ENET2_RDATA01 | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET2_RX_EN__ENET2_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_ENET2_RX_ER__ENET2_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
    MX6_PAD_SNVS_TAMPER8__GPIO5_IO08 | MUX_PAD_CTRL(NO_PAD_CTRL),
};

继续在文件 mx6ull_alientek_emmc.c 中找到函数 setup_iomux_fec,函数 setup_iomux_fec 就是根据 fec1_padsfec2_pads 这两个网络 IO 配置数组来初始化 I.MX6ULL 的网络 IO。我们需要在其中添加网络复位 IO 的初始化代码,并且复位一下 PHY 芯片,修改后的 setup_iomux_fec 函数如下:

static void setup_iomux_fec(int fec_id)
{
    if (fec_id == 0)
    {
        imx_iomux_v3_setup_multiple_pads(fec1_pads,
                         ARRAY_SIZE(fec1_pads));
        gpio_direction_output(ENET1_RESET,1);
        gpio_set_value(ENET1_RESET,0);
        mdelay(20);
        gpio_set_value(ENET1_RESET,1);
    }
    else
    {
        imx_iomux_v3_setup_multiple_pads(fec2_pads,
                         ARRAY_SIZE(fec2_pads));
        gpio_direction_output(ENET2_RESET,1);
        gpio_set_value(ENET2_RESET,0);
        mdelay(20);
        gpio_set_value(ENET2_RESET,1);
    }        
}

代码中分别对 ENET1ENET2 的复位 IO 初始化,将这两个 IO 设置为输出并且硬件复位一下 LAN8720A,这个硬件复位很重要!否则可能导致 uboot 无法识别 LAN8720A

大功基本上告成,还差最后一步,uboot 中的 LAN8720A 驱动有点问题,打开文件 drivers/net/phy/phy.c,找到函数 genphy_update_link,这是个通用 PHY 驱动函数,此函数用于更新 PHY 的连接状态和速度。使用 LAN8720A 的时候需要在此函数中添加一些代码,修改后的函数 genphy_update_link 如下所示:

/**
 * genphy_update_link - update link status in @phydev
 * @phydev: target phy_device struct
 *
 * Description: Update the value in phydev->link to reflect the
 *   current link value.  In order to do this, we need to read
 *   the status register twice, keeping the second value.
 */
int genphy_update_link(struct phy_device *phydev)
{
    unsigned int mii_reg;

     static int lan8720_flag = 0;
     int bmcr_reg = 0;
     if (lan8720_flag == 0) {
     bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); 
     phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET); 
     while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000) {
     udelay(100); 
     }
     phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg); 
     lan8720_flag = 1;
     }
    /*
     * Wait if the link is up, and autonegotiation is in progress
     * (ie - we're capable and it's not done)
     */
    mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

    /*
     * If we already saw the link up, and it hasn't gone down, then
     * we don't need to wait for autoneg again
     */
    if (phydev->link && mii_reg & BMSR_LSTATUS)
        return 0;

    if ((phydev->autoneg == AUTONEG_ENABLE) &&
        !(mii_reg & BMSR_ANEGCOMPLETE)) {
        int i = 0;

        printf("%s Waiting for PHY auto negotiation to complete",
            phydev->dev->name);
        while (!(mii_reg & BMSR_ANEGCOMPLETE)) {
            /*
             * Timeout reached ?
             */
            if (i > PHY_ANEG_TIMEOUT) {
                printf(" TIMEOUT !\n");
                phydev->link = 0;
                return 0;
            }

            if (ctrlc()) {
                puts("user interrupt!\n");
                phydev->link = 0;
                return -EINTR;
            }

            if ((i++ % 500) == 0)
                printf(".");

            udelay(1000);    /* 1 ms */
            mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
        }
        printf(" done\n");
        phydev->link = 1;
    } else {
        /* Read the link a second time to clear the latched state */
        mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);

        if (mii_reg & BMSR_LSTATUS)
            phydev->link = 1;
        else
            phydev->link = 0;
    }

    return 0;
}

至此网络的复位引脚驱动修改完成,重新编译 uboot,然后将 u-boot.bin 烧写到 SD 卡中并启动,uboot 启动信息如图所示:

IMX6ULLLCD 屏幕上也被成功点亮 NXP 的标志!可是我们同样发现,FEC1 的地址并未设置,该错误原因:未配置网络信息,接下来我们根据自己实际情况配置一下网络信息。为了保证虚拟机下的 UbuntuPC 以后可以互通,一定要保证在一个网络频段内!

uboot 中使用网络之前要先设置几个环境变量,命令如下:

setenv ipaddr 192.168.163.50 //开发板 IP 地址
setenv ethaddr b8:ae:1d:01:00:00 //开发板网卡 MAC 地址
setenv gatewayip 192.168.163.1 //开发板默认网关
setenv netmask 255.255.255.0 //开发板子网掩码
setenv serverip 192.168.163.129 //服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量

之后使用 ping 指令即可 ping 通局域网内其他主机

三、bootcmd 和 bootargs 环境变量

uboot 中有两个非常重要的环境变量 bootcmd bootargs,接下来看一下这两个环境变量。bootcmdbootagrs 是采用类似 shell 脚本语言编写的,里面有很多的变量引用,这些变量其实都 是 环 境 变 量 , 有 很 多 是 NXP 自 己 定 义 的 。 文 件 mx6ull_alientek_emmc.h 中的宏CONFIG_EXTRA_ENV_SETTINGS 保存着这些环境变量的默认值

3.1 环境变量 bootcmd

bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。这些命令一般都是用来启动 Linux 内核的,比如读取 EMMC 或者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。可以在 uboot 启动以后进入命令行设置 bootcmd 环境变量的值。如果 EMMC 或者 NAND 中没有保存 bootcmd 的值,那么 uboot 就会使用默认的值,板子第一次运行 uboot 的时候都会使用默认值来设置 bootcmd 环境变量。

我们都知道 Linux 内核的成功启动需要 Linux kernel 和 dtb 的配合才行,作者这里采取 tftp 协议去加载 Linux 内核和设备树,并在指定地址内启动,代码命令如下:

setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'

3.2 环境变量 bootargs

bootargs 保存着 uboot 传递给 Linux 内核的参数,bootargs 环境变量是由 mmcargs 设置的,mmcargs 环境变量如下:

mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}

其中 console=ttymxc0,baudrate=115200,mmcroot=/dev/mmcblk1p2 rootwait rw,因此将 mmcargs 展开以后就是:

mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw

可以看出环境变量 mmcargs 就是设置 bootargs 的值为 “console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw”,bootargs 就是设置了很多的参数的值,这些参数 Linux 内核会使用到。

3.3 从网络启动 Linux 系统

为了方便后续 Linux 驱动和应用程序的开发,通常研发阶段都会采用从网络启动 Linux 系统的方式(当然,产品化之后肯定都需要从 EMMC 中启动的),设置 bootargsbootcmd 这两个环境变量,设置如下:

setenv bootargs 'console=tty1  console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.163.129:/home/senak/linux/nfs/rootfs ip=192.168.163.50:192.168.163.129:192.168.163.1:255.255.255.0::eth0:off'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv

作者这里的控制台 console 使用了串口和屏幕显示,通过 tftp 下载 Linux 内核镜像,nfs 挂载根文件系统;

之后重启 uboot 进行引导 Linux 内核镜像与设备树,并挂载根文件系统:

四、uboot移植流程总结

★简单总结一下 uboot 移植的过程

1、不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 dmeo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、linux kernel 和 rootfs 等,最终制作好 BSP 包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。
2、我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo 板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。
3、一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot 的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。
4、在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。