7| uboot移植

uboot移植

一、获取uboot

uboot官网的源码,对某一芯片的支持肯定没有原厂完善,所以一般选择原厂的uboot源码。

这里我们以 NXP 官方的 uboot 移植到正点原子的 I.MX6ULL 开发板上为例。

uboot官网下载

http://www.denx.de/wiki/U-Boot/

选择源码

image-20200817174610893

ftp下载

image-20200817174626000 选择要下载的版本

image-20200817174641016

从你的板子参考厂商获取修改过的uboot

image-20200817175657019

首先 uboot 官方的基本是不会用的,因为支持太弱了。最常用的就是半导体厂商或者开发板厂商的 uboot,如果你用的半导体厂商的评估板,那么就使用半导体厂商的 uboot,如果你是购买的第三方开发板,比如正点原子的 I.MX6ULL 开发板,那么就使用正点原子提供的 uboot 源码(也是在半导体厂商的 uboot 上修改的)。当然了,你也可以在购买了第三方开发板以后使用半导体厂商提供的 uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的 uboot 移植

uboot 的移植并不是说我们完完全全的从零开始将 uboot 移植到我们现在所使用的开发板或者开发平台上。这个对于我们来说基本是不可能的,这个工作一般是半导体厂商做的,半导
体厂商负责将 uboot 移植到他们的芯片上,因此半导体厂商都会自己做一个开发板,这个开发板就叫做原厂开发板。半导体厂商会将 uboot 移植到他们自己的原厂开发板上,测试好以后就会将这个 uboot 发布出去,这就是大家常说的原厂 BSP 包。

二、uboot初次编译

首先在 Ubuntu 中安装 ncurses 库,否则编译会报错,安装命令如下:

sudo apt-get install libncurses5-dev

1.解压uboot源码

获取uboot源码后,解压uboot得到这样一个目录

image-20200817183254084

2.配置源码

在目录下configs里找到默认的配置文件

如IMX6uLL的官方开发板EVK的配置文件为

image-20200817183701255

使用make xxx_defconfig命令即可配置 uboot,比如:**(注意要在uboot根目录输入命令)**

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig

3.编译源码

编译 NXP 官方开发板对应的 uboot:

make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12
  • V=1 查看完整的编译信息
  • -j1212核编译

这里每次输入那么长,很麻烦,可以直接在Makefile里给ARCHCORSS_COMPILE 赋值, 或者可以写个SHELL脚本

image-20200817192750821

这样我们就可以使用如下简短的命令来编译 uboot 了:

make mx6ull_14x14_evk_emmc_defconfig
make V=1 -j16

4.烧写未修改过的uboot测试

sudo fdisk -l查看当前储存设备。

将 imxdownload 软件拷贝到 uboot 源码根目录下然后使用 imxdownload 软件将 u-boot.bin烧写到 SD 卡中,烧写命令如下:
chmod 777 imxdownload 给予 imxdownload 可执行权限
./imxdownload u-boot.bin /dev/sdb 烧写 u-boot.bin 到 SD 卡中

三、移植uboot

1.添加开发板默认配置文件

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

cd configs
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_lxg_emmc_defconfig

然后将文件 mx6ull_lxg_emmc_defconfig 中的内容改成下面的:
示例代码 33.2.1.1 mx6ull_alientek_emmc_defconfig 文件

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

mx6ull_lxg_emmc_defconfig 基本和 mx6ull_14x14_evk_emmc_defconfig 中
的内容一样,只是第 1 行和第 4 行做了修改。

2.添加开发板对应头文件

在目录include/configs 下添加 I.MX6ULL-ALPHA 开发板对应的头文件

复制include/configs/mx6ullevk.h,并重命名为 mx6ull_lxg_emmc.h,命令如下:

cp include/configs/mx6ullevk.h include/configs/mx6ull_lxg_emmc.h

拷贝完成以后编辑文件,将:

#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H

改为:

#ifndef __MX6ULL_LXG_EMMC_CONFIG_H
#define __MX6ULL_LXG_EMMC_CONFIG_H

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

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

uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。

NXP 的 I.MX 系列芯片的所有板级文件夹都存放在board/freescale目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为 mx6ull_lxg_emmc,命令如下:

cd board/freescale/
cp mx6ullevk/ -r mx6ull_lxg_emmc

进 入 mx6ull_alientek_emmc 目录中,将其中的 mx6ullevk.c 文件重命名为mx6ull_lxg_emmc.c

cd mx6ull_lxg_emmc
mv mx6ullevk.c mx6ull_lxg_emmc.c

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

  • 修改 mx6ull_lxg_emmc 目录下的 Makefile 如下

    # (C) Copyright 2015 Freescale Semiconductor, Inc.
    #
    # SPDX-License-Identifier:	GPL-2.0+
    #
    
    obj-y  := mx6ull_lxg_emmc.o
    
    extra-$(CONFIG_USE_PLUGIN) :=  plugin.bin
    $(obj)/plugin.bin: $(obj)/plugin.o
    	$(OBJCOPY) -O binary --gap-fill 0xff $< $@
    
  • 修改 mx6ull_lxg_emmc 目录下的 imximage.cfg

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

  • 修改 mx6ull_alientek_emmc 目录下的 Kconfig

    if TARGET_MX6ULL_LXG_EMMC
    
    config SYS_BOARD
    	default "mx6ull_lxg_emmc"
    
    config SYS_VENDOR
    	default "freescale"
    
    config SYS_SOC
    	default "mx6"
    
    config SYS_CONFIG_NAME
    	default "mx6ull_lxg_emmc"
    
    endif
    
  • 修改 mx6ull_alientek_emmc 目录下的 MAINTAINERS

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

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

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

uboot 是支持图形界面配置,关于 uboot 的图形界面配置下一章会详细的讲解。修改文件arch/arm/cpu/armv7/mx6/Kconfig(如果用的 I.MX6UL 的话,应该修改 arch/arm/Kconfig 这个文
件)

  • 在 207 行加入如下内容:
config TARGET_MX6ULL_LXG_EMMC
	bool "Support mx6ull_lxg_emmc"
	select MX6ULL
	select DM
	select DM_THERMAL

image-20200818200355231

  • 在最后一行的 endif 的前一行添加如下内容:
source "board/freescale/mx6ull_lxg_emmc/Kconfig"

image-20200818200505813


到此为止,I.MX6U-ALPHA 开发板就已经添加到 uboot 中了,接下来就是编译这个新添加
的开发板。

5.使用新添加的板子配置编译uboot

在uboot根目录下新建一个名为 mx6ull_lxg_emmc.sh 的 shell 脚本,在这个shell脚本里面输入如下内容:

#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_lxg_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

第3行我们使用的默认配置文件就是之前新建的 mx6ull_alientek_emmc_defconfig这个配置文件。

给予 mx6ull_lxg_emmc.sh 可执行权限,然后运行脚本来完成编译,命令如下:

chmod 777 mx6ull_alientek_emmc.sh 
./mx6ull_alientek_emmc.sh

等待编译完成,编译完成以后输入如下命令,查看一下之前添加的mx6ull_lxg_emmc.h这个头文件有没有被引用, 输入命令:

grep -nR "`mx6ull_lxg_emmc.h"

如果有很多文件都引用了mx6ull_lxg_emmc.h这个头文件,那就说明新板子添加成功,

image-20200818201707668

编译完成以后就使用 imxdownload 将新编译出来的 u-boot.bin 烧写到 SD 卡中测试

image-20200818203813549

从图中第一行的编译时间可以看出,这是我们刚刚编译的uboot,此时的 Board 还是“MX6ULL 14x14 EVK”,因为我们参考的 NXP官方的 I.MX6ULL 开发板来添加自己的开发板。接了 LCD 屏幕的话会发现 LCD 屏幕并没有显示 NXP 的 logo,而且从图可以看出此时的网络同样也没识别出来。如果你的开发板和官方的有些区别,默认uboot中的LCD 驱动和网络驱动是有问题的,需要修改。

6.LCD驱动修改

一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的,xxx 为板子名称,比如 mx6ull_lxg_emmc.h 和 mx6ull_lxg_emmc.c 这两个文件。
一般修改 LCD 驱动重点注意以下几点:
①、LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
②、LCD 背光引脚 GPIO 的配置。
③、LCD 配置参数是否正确。

这里以正点原子的 I.MX6U-ALPHA 开发板 为例:

LCD 原理图和 NXP 官方 I.MX6ULL 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。需要修改的只有 LCD 参数,打开文件 mx6ull_lxg_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 的各个参数等。

display_info_t 定义在文件 arch/arm/include/asm/imx-common/video.h 中,定义如下:

struct display_info_t {
	int	bus;
	int	addr;
	int	pixfmt;
	int	(*detect)(struct display_info_t const *dev);
	void	(*enable)(struct display_info_t const *dev);
	struct	fb_videomode mode;
};

pixfmt 是像素格式,也就是一个像素点是多少位,如果是 RGB565 的话就是 16 位,如果是 888 的话就是 24 位,一般使用 RGB888。

结构体 display_info_t 还有个 mode 成员变量,此成员变量也是个结构体,为 fb_videomode,定义在文件 include/linux/fb.h 中,定义如下:

struct fb_videomode {
	const char *name;	/* optional */
	u32 refresh;		/* optional */
	u32 xres;
	u32 yres;
	u32 pixclock;
	u32 left_margin;
	u32 right_margin;
	u32 upper_margin;
	u32 lower_margin;
	u32 hsync_len;
	u32 vsync_len;
	u32 sync;
	u32 vmode;
	u32 flag;
};

结构体 fb_videomode 里面的成员变量为 LCD 的参数,这些成员变量函数如下:

  • name :LCD 名字,要和环境变量中的 panel 相等。
  • xres 、yres :LCD X 轴和 Y 轴像素数量。
  • pixclock:像素时钟,每个像素时钟周期的长度,单位为皮秒。
  • left_margin :HBP,水平同步后肩。
  • right_margin :HFP,水平同步前肩。
  • upper_margin:VBP,垂直同步后肩。
  • lower_margin:VFP,垂直同步前肩。
  • hsync_len :HSPW,行同步脉宽。
  • vsync_len:VSPW,垂直同步脉宽。
  • vmode :大多数使用 FB_VMODE_NONINTERLACED,也就是不使用隔行扫描。

可以看出,这些参数与裸机驱动 RGB LCD 的时候参数基本一样,唯一不同的像素时钟 pixclock 的含义不同,以正点原子的 7 寸 1024x600 分辨率的屏幕(ATK7016)为例,屏幕要求的像素时钟为 51.2MHz,因此:
pixclock=(1/51200000)*10^12=19531

在根据其他的屏幕参数,可以得出 ATK7016 屏幕的配置参数如下:

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_lxg_emmc.c 中 uboot 默认的屏幕参数。

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

panel=TFT43AB

将其改为:

panel=TFT7016

也就是设置 panel 为 TFT7016,panel 的值要与display_info_t结构体变量中的.name 成员变量的值
一致。修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。

重启以后 LCD 驱动一般就会工作正常了,LCD 上回显示 NXP 的 logo。但是有可能会遇到LCD 并没有工作,还是黑屏,这是什么原因呢?在 uboot 命令模式输入“print”来查看环境变量 panel 的值,会发现 panel 的值是 TFT43AB(或其他的,反正不是 TFT7016)

这是因为之前有将环境变量保存到 EMMC 中,uboot 启动以后会先从 EMMC 中读取环境变量,如果EMMC 中没有环境变量的话才会使用 mx6ull_alientek_emmc.h 中的默认环境变量。如果 EMMC 中的环境变量panel 不等于 TFT7016,那么 LCD 显示肯定不正常,我们只需要在uboot 中修改 panel 的值为 TFT7016 即可,在 uboot 的命令模式下输入如下命令:

setenv panel TFT7016
saveenv

上述命令修改环境变量 panel 为 TFT7016,然后保存,重启 uboot,此时 LCD 驱动就工作正常了。如果 LCD 还是没有正常工作的,那就要检查自己哪里有没有改错,或者还有哪里没有修改。

7.网络驱动修改

1、网络硬件介绍

I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET,需要外接一个 PHY 芯片来实现网络通信功能,也就是内部 MAC+外部 PHY 芯片的方案。

大家可能听过 DM9000 这个网络芯片,在一些没有内部 MAC 的 CPU 中,比如三星的 2440,4412 等,就会采用 DM9000 来实现联网功能。DM9000 提供了一个类似 SRAM 的访问接口,主控 CPU 通过这个接口即可与DM9000 进行通信,DM9000 就是一个 MAC+PHY 芯片。这个方案就相当于外部 MAC+外部PHY,那么 I.MX6U 这样的内部 MAC+PHY 芯片与 DM9000 方案比有什么优势吗?首先就是通信效率和速度,一般 SOC 内部的 MAC 是带有一个专用 DMA 的,专门用于处理网络数据包,采用 SRAM 来读写 DM9000 的速度是压根就没法和内部 MAC+外部 PHY 芯片的速度比。采用外部 DM9000 完全是无奈之举,谁让2440,4412 这些芯片内部没有以太网外设呢,现在又想用有线网络,没有办法只能找个 DM9000 的方案。

I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1 和 ENET2 都使用 LAN8720A 作为 PHY 芯片。NXP 官方的I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片,LAN8720A 相比 KSZ8081 具有体积小、外围器件少、价格便宜等优点。

换了PHY芯片,这个时候官方的驱动就不适用啦,需要修改驱动,使网络工作正常。

正点原子I.MX6U-ALPHA 开发板 ENET1 的网络原理图如图:

image-20200818211726560

ENET1 的网络 PHY 芯片为 LAN8720A,通过 RMII 接口与 I.MX6ULL 相连,正点原子I.MX6U-ALPHA 开发板的 ENET1 引脚与 NXP 官方的 I.MX6ULL EVK 开发板基本一样,唯独复位引脚不同。从图可以看出,正点原子 I.MX6U-ALPHA 开发板的 ENET1 复位引脚ENET1_RST 接到了 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。

LAN8720A 内部是有寄存器的,I.MX6ULL 会读取 LAN8720 内部寄存器来判断当前的物理链接状态、连接速度(10M 还是 100M)和双工状态(半双工还是全双工)。I.MX6ULL 通过 MDIO接口来读取 PHY 芯片的内部寄存器,MDIO 接口有两个引脚,ENET_MDC 和 ENET_MDIO,ENET_MDC 提供时钟,ENET_MDIO 进行数据传输。一个 MIDO 接口可以管理 32 个 PHY 芯片,同一个 MDIO 接口下的这些 PHY 使用不同的器件地址来做区分,MIDO 接口通过不同的器件地址即可访问到相应的 PHY 芯片。I.MX6U-ALPHA 开发板 ENET1 上连接的 LAN8720A器件地址为 0X0,所示我们要修改 ENET1 网络驱动的话重点就三点:

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

再来看一下 开发板上的 ENET2 的原理图,如图所示:

image-20200818212327169

关于 ENET2 网络驱动的修改也注意一下三点:
①、ENET2 的复位引脚,从图可以看出,ENET2 的复位引脚 ENET2_RST 接到了I.MX6ULL 的 SNVS_TAMPER8 上。
②、ENET2 所使用的 PHY 芯片器件地址,从图可以看出,PHY 器件地址为 0X1
③、LAN8720 驱动,ENET1 和 ENET2 都使用的 LAN8720,所以驱动肯定是一样的。

2、网络 PHY 地址修改

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

#ifdef CONFIG_CMD_NET
#define CONFIG_CMD_PING
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MII
#define CONFIG_FEC_MXC
#define CONFIG_MII
#define CONFIG_FEC_ENET_DEV		1

#if (CONFIG_FEC_ENET_DEV == 0)
#define IMX_FEC_BASE			ENET_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR          0x2
#define CONFIG_FEC_XCV_TYPE             RMII
#elif (CONFIG_FEC_ENET_DEV == 1)
#define IMX_FEC_BASE			ENET2_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR			0x1
#define CONFIG_FEC_XCV_TYPE				RMII
#endif
#define CONFIG_ETHPRIME			"FEC"

#define CONFIG_PHYLIB
#define CONFIG_PHY_MICREL
#endif

**宏CONFIG_FEC_ENET_DEV**用于选择使用哪个网口,默认为 1,也就是选择ENET2。

**宏IMX_FEC_BASE**为该ENET接口的寄存器基地址。

第一个**CONFIG_FEC_MXC_PHYADDR宏**为 ENET1 的 PHY 地址,默认是 0X2,第二个为 ENET2 的 PHY 地址,默认为 0x1。

根据前面的分析可知,正点原子的 I.MX6U-ALPHA 开发板 ENET1 的 PHY 地址为0X0,ENET2 的 PHY 地址为 0X1,所以需要将第第一个**CONFIG_FEC_MXC_PHYADDR宏**改为 0x0 。

CONFIG_PHY_MICREL用于使能 uboot 中 Micrel 公司的 PHY驱动,KSZ8081 这颗 PHY 芯片就是 Micrel 公司生产的,不过 Micrel 已经被 Microchip 收购了。如果要使用 LAN8720A,那么就得将CONFIG_PHY_MICREL 改为 **CONFIG_PHY_SMSC**,也就是使能 uboot 中的 SMSC 公司中的 PHY 驱动,因为 LAN8720A 就是 SMSC 公司生产的。所以示例代码有三处要修改:

①、修改 ENET1 网络 PHY 的地址。
②、修改 ENET2 网络 PHY 的地址。
③、使能 SMSC 公司的 PHY 驱动。
修改后的网络 PHY 地址参数如下所示:

#ifdef CONFIG_CMD_NET
#define CONFIG_CMD_PING
#define CONFIG_CMD_DHCP
#define CONFIG_CMD_MII
#define CONFIG_FEC_MXC
#define CONFIG_MII
#define CONFIG_FEC_ENET_DEV		1

#if (CONFIG_FEC_ENET_DEV == 0)
#define IMX_FEC_BASE			ENET_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR          0x0
#define CONFIG_FEC_XCV_TYPE             RMII
#elif (CONFIG_FEC_ENET_DEV == 1)
#define IMX_FEC_BASE			ENET2_BASE_ADDR
#define CONFIG_FEC_MXC_PHYADDR			0x1
#define CONFIG_FEC_XCV_TYPE				RMII
#endif
#define CONFIG_ETHPRIME			"FEC"

#define CONFIG_PHYLIB
#define CONFIG_PHY_SMSC
#endif

3 、删除 uboot 中 中 74LV595 的驱动代码

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

#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)

以上示例代码中以 IOX 开头的宏定义是 74LV595 的相关 GPIO,因为 NXP 官方I.MX6ULL EVK 开发板使用 74LV595 来扩展 IO,两个网络的复位引脚就是由 74LV595 来控制的。正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将示例代码中的代码删除掉,替换为如下所示代码:

#define ENET1_RESET IMX_GPIO_NR(5, 7)
#define ENET2_RESET IMX_GPIO_NR(5, 8)

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

继续在 mx6ull_lxg_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),
};

同理,示例代码是 74LV595 的 IO 配置参数结构体,将其删除掉。继续在mx6ull_lxg_emmc.c 中找到函数 iox74lv_init,如下所示:

static void iox74lv_init(void)
{
	int i;

	gpio_direction_output(IOX_OE, 0);

	for (i = 7; i >= 0; i--) {
		gpio_direction_output(IOX_SHCP, 0);
		gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
		udelay(500);
		gpio_direction_output(IOX_SHCP, 1);
		udelay(500);
	}
	......
    ......
	/*
	 * shift register will be output to pins
	 */
	gpio_direction_output(IOX_STCP, 1);
};

void iox74lv_set(int index)
{
	int i;

	for (i = 7; i >= 0; i--) {
		gpio_direction_output(IOX_SHCP, 0);

		if (i == index)
			gpio_direction_output(IOX_SDI, seq[qn_output[i]][0]);
		else
			gpio_direction_output(IOX_SDI, seq[qn_output[i]][1]);
		udelay(500);
		gpio_direction_output(IOX_SHCP, 1);
		udelay(500);
	}
	......
    ......
	/*
	  * shift register will be output to pins
	  */
	gpio_direction_output(IOX_STCP, 1);
};

iox74lv_init 函数是 74LV595 的初始化函数,iox74lv_set 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉!

继续在 mx6ull_lxg_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被board_init_r 调用,board_init 函数内容如下:

int board_init(void)
{
	......
	imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));
	iox74lv_init();
	......
	return 0;
}

board_init 会调用 imx_iomux_v3_setup_multiple_pads 和 iox74lv_init 这两个函数来初始化74lv595 的 GPIO,将这两行删除掉

至此,mx6ull_alientek_emmc.c 中关于 74LV595 芯片的驱动代码都删除掉了,接下来就是添加 I.MX6U-ALPHA 开发板两个网络复位引脚了。

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

在 mx6ull_lxg_emmc.c 中找到如下所示代码:

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_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),
};

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_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),
};

结构体数组 fec1_pads 和 fec2_pads 是 ENET1 和 ENET2 这两个网口的 IO 配置参数,在这两个数组中添加两个网口的复位 IO 配置参数,完成以后如下所示:

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_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_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),
};

添加的两行分别是 ENET1 和 ENET2 的复位 IO 配置参数。继续在文件 mx6ull_lxg_emmc.c 中找到函数 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));
	else
		imx_iomux_v3_setup_multiple_pads(fec2_pads,
						 ARRAY_SIZE(fec2_pads));
}

函数 setup_iomux_fec 就是根据 fec1_pads 和 fec2_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);
    }
}

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

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

int genphy_update_link(struct phy_device *phydev)
{
	unsigned int mii_reg;

    #ifdef CONFIG_PHY_SMSC
    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;
    }
    #endif
    
	/*
	 * 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);
......
	return 0;
}

前面**#ifdef**里就是新添加的代码,为条件编译代码段,只有使用 SMSC 公司的 PHY 这段代码才会执行(目前只测试了 LAN8720A,SMSC 公司其他的芯片还未测试)。

bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);读取LAN8720A 的 BMCR 寄存器(寄存器地址为 0),此寄存器为 LAN8720A 的配置寄存器,这里先读取此寄存器的默认值并保存起来。

phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);向寄存器 BMCR 寄存器写入BMCR_RESET(值为0X8000),因为 BMCR 的 bit15 是软件复位控制位,因此此行就是软件复位LAN8720A,复位完成以后此位会自动清零。

接下来while等待 LAN8720A 软件复位完成,也就是判断 BMCR的 bit15 位是否为 1,为 1 的话表示还没有复位完成。

phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);重新向 BMCR 寄存器写入以前
的值,也就是之前读出的那个值。

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

image-20200818221858986

可以看到“Net: FEC1”这一行,提示当前使用的 FEC1 这个网口,也就是 ENET2。

6、uboot网络环境变量的配置

setenv ipaddr 192.168.0.120  //开发板 IP 地址
setenv ethaddr 00:04:9f:04:d2:35 //开发板网卡 MAC 地址
setenv gatewayip 192.168.0.1 //开发板默认网关
setenv netmask 255.255.255.0  //开发板子网掩码
setenv serverip 192.168.0.111  //服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量

以上请根据自己的网络环境具体配置。

设置好环境变量以后就可以在 uboot 中使用网络了,用网线将 I.MX6U-ALPHA 上的 ENET2与电脑或者路由器连接起来,保证开发板和电脑在同一个网段内,通过 ping 命令来测试一下网络连接,命令如下:

ping 192.168.0.111

image-20200818223729557

从图可以看出,有“host 192.168.1.250 is alive”这句,说明 ping 主机成功,说明ENET2网络工作正常。再来测试一下ENET1的网络是否正常工作,打开mx6ull_lxg_emmc.h,将 CONFIG_FEC_ENET_DEV 改为 0,然后重新编译一下 uboot 并烧写到 SD 卡中重启。重启开发板

image-20200818224125442

image-20200818224138597

从图可以看出,ping 主机也成功,说明 ENET1 网络也工作正常,至此,I.MX6U-ALPHA 开发板的两个网络都工作正常了.

8、其他需要修改的地方

在 uboot 启动信息中会有“Board: MX6ULL 14x14 EVK”这一句,也就是说板子名字为“MX6ULL 14x14 EVK”,要将其改为我们所使用的板子名字,比如“MX6ULL LXG EMMC”或者“MX6ULL LXG NAND”。打开文件 mx6ull_lxg_emmc.c,找到函数checkboard,将其改为如下所示内容:

int checkboard(void)
{
	if (is_mx6ull_9x9_evk())
		puts("Board: MX6ULL 9x9 EVK\n");
	else
		puts("Board: MX6ULL LXG EMMC\n");

	return 0;
}

修改完成以后重新编译 uboot 并烧写到 SD 卡中验证,uboot 启动信息如图所示:

image-20200818224533045

从图可以看出,Board 变成了“MX6ULL LXG EMMC”。至此 uboot 的驱动部分就修改完成了,uboot 移植也完成了,uboot 的最终目的就是启动 Linux 内核,所以需要通过启动 Linux 内核来判断 uboot 移植是否成功。在启动 Linux 内核之前我们先来学习两个重要的环境变量 bootcmd 和 bootargs。

四、uboot下的环境变量

uboot 中有两个非常重要的环境变量 bootcmd 和 bootargs,接下来看一下这两个环境变量。

bootcmd 和 bootagrs 是采用类似 shell 脚本语言编写的,里面有很多的变量引用,这些变量其实都是环境变量,有很多是NXP自己定义的。文 件 mx6ull_lxg_emmc.h 中的宏CONFIG_EXTRA_ENV_SETTINGS保存着这些环境变量的默认值,内容如下:

#if defined(CONFIG_SYS_BOOT_NAND)
#define CONFIG_EXTRA_ENV_SETTINGS \
	CONFIG_MFG_ENV_SETTINGS \
	"panel=TFT7016\0" \
	"fdt_addr=0x83000000\0" \
	"fdt_high=0xffffffff\0"	  \
......
		"bootz ${loadaddr} - ${fdt_addr}\0"

#else
#define CONFIG_EXTRA_ENV_SETTINGS \
	CONFIG_MFG_ENV_SETTINGS \
	"script=boot.scr\0" \
	"image=zImage\0" \
	"console=ttymxc0\0" \
	"fdt_high=0xffffffff\0" \
	"initrd_high=0xffffffff\0" \
	"fdt_file=undefined\0" \
......
		"findfdt="\
			"if test $fdt_file = undefined; then " \
				"if test $board_name = EVK && test $board_rev = 9X9; then " \
					"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
				"if test $board_name = EVK && test $board_rev = 14X14; then " \
					"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
				"if test $fdt_file = undefined; then " \
					"echo WARNING: Could not determine dtb to use; fi; " \
			"fi;\0" \

宏 CONFIG_EXTRA_ENV_SETTINGS 是个条件编译语句,使用 NAND 和 EMMC 的时候宏 CONFIG_EXTRA_ENV_SETTINGS 的值是不同的。

板子第一次运行 uboot 的时候都会使用默认值来设置环境变量。打开文件 include/env_default.h,在此文件中有如下所示内容:

env_t environment __PPCENV__ = {
	ENV_CRC,	/* CRC Sum */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
	1,		/* Flags: valid */
#endif
	{
#elif defined(DEFAULT_ENV_INSTANCE_STATIC)
static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef	CONFIG_ENV_CALLBACK_LIST_DEFAULT
	ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
#endif
#ifdef	CONFIG_ENV_FLAGS_LIST_DEFAULT
	ENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
#endif
#ifdef	CONFIG_BOOTARGS
	"bootargs="	CONFIG_BOOTARGS			"\0"
#endif
#ifdef	CONFIG_BOOTCOMMAND
	"bootcmd="	CONFIG_BOOTCOMMAND		"\0"
#endif
#ifdef	CONFIG_RAMBOOTCOMMAND
	"ramboot="	CONFIG_RAMBOOTCOMMAND		"\0"
#endif
#ifdef	CONFIG_NFSBOOTCOMMAND
	"nfsboot="	CONFIG_NFSBOOTCOMMAND		"\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
	"bootdelay="	__stringify(CONFIG_BOOTDELAY)	"\0"
#endif
........
........
#endif
        
#ifdef	CONFIG_EXTRA_ENV_SETTINGS
	CONFIG_EXTRA_ENV_SETTINGS
#endif
        
	"\0"
#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
	}
#endif
};

从示例代码的第 1 行可以看出,environment 是个 env_t 类型的变量,env_t 类型如下:

typedef struct environment_s {
	uint32_t	crc;		/* CRC32 over data bytes	*/
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
	unsigned char	flags;		/* active/obsolete flags	*/
#endif
	unsigned char	data[ENV_SIZE]; /* Environment data		*/
} env_t

env_t结构体中的crc为CRC值,flags是标志位,data数组就是环境变量值。因此,environment就是用来保存默认环境变量的.

1、环境变量 bootcmd

bootcmd默认宏定义设置的解析

bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。

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

在上一个示例代码中指定了很多环境变量的默认值,比如bootcmd 的 默 认 值 就 是CONFIG_BOOTCOMMAND,bootargs 的默认值就是CONFIG_BOOTARGS 。 我们可以在mx6ull_lxg_emmc.h文件中通过设置宏CONFIG_BOOTCOMMAND 来 设 置 bootcmd 的 默 认 值 , NXP 官 方 设 置 的CONFIG_BOOTCOMMAND 值如下:

#define CONFIG_BOOTCOMMAND \
	   "run findfdt;" \
	   "mmc dev ${mmcdev};" \
	   "mmc dev ${mmcdev}; if mmc rescan; then " \
		   "if run loadbootscript; then " \
			   "run bootscript; " \
		   "else " \
			   "if run loadimage; then " \
				   "run mmcboot; " \
			   "else run netboot; " \
			   "fi; " \
		   "fi; " \
	   "else run netboot; fi"
#endif

看起来很复杂的样子!因为 uboot 使用了类似 shell 脚本语言的方式来编写的,我们一行一行来分析。

  • 第二行 run findfdt;使用的是 uboot 的 run 命令来运行 findfdt,findfdt 是 NXP 自行添加的环境变量。findfdt 是用来查找开发板对应的设备树文件(.dtb)。IMX6ULL EVK 的设备树文件为 imx6ull-14x14-evk.dtb,findfdt 内容如下:

    "findfdt="\
              "if test $fdt_file = undefined; then " \
    		"if test $board_name = EVK && test $board_rev = 9X9; then " \
    			"setenv fdt_file imx6ull-9x9-evk.dtb; fi; " \
    		"if test $board_name = EVK && test $board_rev = 14X14; then " \
    			"setenv fdt_file imx6ull-14x14-evk.dtb; fi; " \
    		"if test $fdt_file = undefined; then " \
    			"echo WARNING: Could not determine dtb to use; fi; " \
    	"fi;\0" \

    findfdt 里面用到的变量有 fdt_fileboard_nameboard_rev,这三个变量内容如下:

    fdt_file=undefined,board_name=EVK,board_rev=14X14

    findfdt 做的事情就是判断这些变量值从而得出所需的.dtb 文件名。此时 fdt_file 为 undefined,所以根据 board_name 和board_rev 来判断实际所需的.dtb 文件。

    根据当前变量值,板子的设备树文件就是 imx6ull-14x14-evk.dtb,因此 run findfdt 的结果就是设置 fdt_file 为 imx6ull-14x14-evk.dtb。

  • mmc dev ${mmcdev}用于切换 mmc 设备,mmcdev 为 1,因此这行代码就是:mmc dev 1,也就是切换到 EMMC 上。

  • "mmc dev ${mmcdev}; if mmc rescan; then " 先执行 mmc dev ${mmcdev}切换到 EMMC 上,然后使用命令 mmc rescan 扫描看有没有 SD 卡或者 EMMC 存在,如果没有的话就直接跳到run netboot;"执行 run netboot,netboot也是一个自定义的环境变量,这个变量是从网络启动 Linux 的。如果 mmc 设备存在的话就从mmc 设备启动。

  • 第五行"if run loadbootscript; then "运行 loadbootscript 环境变量,此环境变量内容如下:

    loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};

    其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,script= boot.scr,因此展开以后就是:

    loadbootscript=fatload mmc 1:1 0x80800000 boot.scr;

    loadbootscript 就是从 mmc1 的分区 1 中读取文件 boot.src 到 DRAM 的 0X80800000 处。但是 mmc1 的分区 1 中没有 boot.src 这个文件,可以使用命令“ls mmc 1:1”查看一下 mmc1 分区1 中的所有文件,看看有没有 boot.src 这个文件。

    之后的一行"run bootscript; "如果加载 boot.src 文件成功的话就运行 bootscript 环境变量,bootscript 的内容如下:
    bootscript=echo Running bootscript from mmc ...;
    source
    因为 boot.src 文件不存在,所以 bootscript 也就不会运行。

  • ```
    “else “
    “if run loadimage; then “
    “run mmcboot; “
    “else run netboot; “
    “fi; “
    “fi; “ \

    
      如果 loadbootscript 没有找到 boot.src 的话就运行环境变量`run loadimage`,环境变量loadimage 内容如下:
      `loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}`
      其中 mmcdev=1,mmcpart=1,loadaddr=0x80800000,image = zImage,展开以后就是:
      `loadimage=fatload mmc 1:1 0x80800000 zImage`
      可以看出 loadimage 就是从 mmc1 的分区中读取 zImage 到内存的 0X80800000 处,而 mmc1
      的分区 1 中存在 zImage。
    
    - `"run mmcboot; "`,加载 linux 镜像文件 zImage 成功以后就运行环境变量 mmcboot,否则的话运行netboot 环境变量。mmcboot 环境变量如下:
    
      ```c
      	"mmcboot=echo Booting from mmc ...; " \
      		"run mmcargs; " \
      		"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
      			"if run loadfdt; then " \
      				"bootz ${loadaddr} - ${fdt_addr}; " \
      			"else " \
      				"if test ${boot_fdt} = try; then " \
      					"bootz; " \
      				"else " \
      					"echo WARN: Cannot load the DT; " \
      				"fi; " \
      			"fi; " \
      		"else " \
      			"bootz; " \
      		"fi;\0" \

Linux 内核启动,如此复杂的设置就是为了从 EMMC 中读取 zImage 镜像文件和设备树文件。经过分析,浓缩出来的仅仅是 4 行精华:

mmc dev 1  //切换到 EMMC
fatload mmc 1:1 0x80800000 zImage //读取 zImage 到 0x80800000 处
fatload mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb //读取设备树到 0x83000000 处
bootz 0x80800000 - 0x83000000 //启动 Linux

NXP 官方将 CONFIG_BOOTCOMMAND 写的这么复杂只有一个目的:为了兼容多个板子,所以写了个很复杂的脚本。当我们明确知道我们所使用的板子的时候就可以大幅简化宏CONFIG_BOOTCOMMAND 的 设 置 ,比 如 我 们 要 从 EMMC 启 动 , 那 么 宏CONFIG_BOOTCOMMAND 就可简化为:

#define CONFIG_BOOTCOMMAND \
"mmc dev 1;" \
"fatload mmc 1:1 0x80800000 zImage;" \
"fatload mmc 1:1 0x83000000 imx6ull-alientek-emmc.dtb;" \
"bootz 0x80800000 - 0x83000000;"

或者可以直接在 uboot 中设置 bootcmd 的值,这个值就是保存到 EMMC 中的,命令如下:

setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-lxg-emmc.dtb; bootz 80800000 - 83000000;'
bootcmd设置栗子
从EMMC启 动
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-lxg-emmc.dtb; bootz 80800000 - 83000000;'
从SD卡 启动
setenv bootcmd 'mmc dev 0; fatload mmc 0:1 80800000 zImage; fatload mmc 0:1 83000000 imx6ull-lxg-emmc.dtb; bootz 80800000 – 83000000;'

2、bootargs环境变量

bootargs默认宏定义设置的解析

bootargs 保存着 uboot 传递给 Linux 内核的参数,是由 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 就是设置了很多的参数的值。

例:

setenv bootartgs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'

1 、console

console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这样我们就可以在电脑上通过 SecureCRT 来和 linux 交互了。这里设置 console 为 ttymxc0,因为 linux启动以后 I.MX6ULL 的串口 1 在 linux 下的设备文件就是/dev/ttymxc0,在 Linux 下,一切皆文件。ttymxc0 后面有个“,115200”,这是设置串口的波特率,console=ttymxc0,115200 综合起来就是设置 ttymxc0(也就是串口 1)作为 Linux 的终端,并且串口波特率设置为 115200。

2 、root

root 用来设置根文件系统的位置,root=/dev/mmcblk1p2 用于指明根文件系统存放在mmcblk1 设备的分区 2 中。root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的

3 、rootfstype

此选项一般配置 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。如果根文件系统是 yaffs、jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。

bootargs 常设置的选项就这三个


bootargs设置栗子
挂载根文件至mmc设备:

例如:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw'

挂载根文件至nfs网络下:

例如:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.0.111:/home/firestaradmin/linux/nfs/rootfs ip=192.168.0.112:192.168.0.111:192.168.0.1:255.255.255.0::eth0:off'

在 Linux 内核源码里面有相应的文档讲解如何设置,文档为Documentation/filesystems/nfs/nfsroot.txt,格式如下:

root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] 
ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:
<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>

: 服务器 IP 地址,也就是存放根文件系统主机的 IP 地址,那就是Ubuntu 的 IP地址,比如我的 Ubuntu 主机 IP 地址为 192.168.1.250。

: 根文件系统的存放路径,比如我的就是/home/firestaradmin/linux/nfs/rootfs。

: NFS 的其他可选选项,一般不设置。

: 客户端 IP 地址,也就是我们开发板的 IP 地址,Linux 内核启动以后就会使用此 IP 地址来配置开发板。

: 服务器 IP 地址,前面已经说了。

: 网关地址,我的就是 192.168.1.1。

: 子网掩码,我的就是 255.255.255.0。

: 客户机的名字,一般不设置,此值可以空着。

: 设备名,也就是网卡名,一般是 eth0,eth1….,正点原子的 I.MX6U-ALPHA 开发板的 ENET2 为 eth0,ENET1 为 eth1。如果你的电脑只有一个网卡,那么基本只能是 eth0。这里我们使用 ENET2,所以网卡名就是 eth0。

: 自动配置,一般不使用,所以设置为 off。

: DNS0 服务器 IP 地址,不使用。

: DNS1 服务器 IP 地址,不使用。


Pc机Ubuntn 的nfs配置:

安装NFS服务:

sudo apt-get install nfs-kernel-server rpcbind

配置NFS文件:

打开 nfs 配置文件/etc/exports:

打开/etc/exports 以后在文件后面添加如下所示内容:

/home/firestaradmin/linux/nfs *(rw,sync,no_root_squash)

重启 NFS 服务,使用命令如下:

sudo /etc/init.d/nfs-kernel-server restart

uboot图形化配置

介绍:

在前两章中我们知道 uboot 可以通过 mx6ull_lxg_emmc_defconfig 来配置,或者通过文件mx6ull_lxg_emmc.h 来配置 uboot。还有另外一种配置 uboot 的方法,就是图形化配置.

uboot 或 Linux 内核可以通过输入“make menuconfig”来打开图形化配置界面,menuconfig是一套图形化的配置工具,需要 ncurses 库支持。ncurses 库提供了一系列的 API 函数供调用者生成基于文本的图形界面,因此需要先在 Ubuntu 中安装 ncurses 库,命令如下:

sudo apt-get install build-essential
sudo apt-get install libncurses5-dev

menuconfig 重点会用到uboot根目录的两个文件:.configKconfig.config 文件前面已经说了,这个文件保存着 uboot 的配置项,使用 menuconfig 配置完 uboot 以后肯定要更新.config 文件。Kconfig文件是图形界面的描述文件,也就是描述界面应该有什么内容,很多目录下都会有 Kconfig 文件。

在打开图形化配置界面之前,要先使用“make xxx_defconfig”对 uboot 进行一次默认配置,只需要一次即可。如果使用“make clean”清理了工程的话就那就需要重新使用“make xxx_defconfig”再对 uboot 进行一次配置。进入 uboot 根目录,输入如下命令:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_lxg_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

如果已经在 uboot 的顶层 Makefile 中定义了 ARCH 和 CROSS_COMPILE 的值,那么上述命令可以简化为:

make mx6ull_lxg_emmc_defconfig
make menuconfig

打开后的图形化界面如图 34.1.1 所示:

image-20200818235039839

使能 dns 命令

我们就以如何使能 dns 命令为例,讲解一下如何通过图形化界面来配置 uboot。进入“Command line interface —>”这个配置项,此配置项用于配置 uboot 的命令,进入以后如图所示:

image-20200819112010754

从图 34.1.2 可以看出,有很多配置项,这些配置项也有子配置项,

选择“Network commands—>”,进入网络相关命令配置项,如图所示:

image-20200819112039004

从图可以看出,uboot 中有很多和网络有关的命令,比如 bootp、tftpboot、dhcp 等等。选中 dns,然后按下键盘上的“Y”键,此时 dns 前面的“[ ]”变成了“[ * ]”,如图所示:

image-20200819112102964

每个选项有 3 种编译选项:

  • 编译进 uboot 中(也就是编译进 u-boot.bin 中)
  • 取消编译(也就是不编译这个功能模块)
  • 编译为模块

按下“Y”键表示编译进 uboot 中,此时“[ ]”变成了“[ * ]”;

按下“N”表示不编译,“[ ]”默认表示不编译;

有些功能模块是支持编译为模块的,这个一般在 Linux 内核里面很常用,uboot 下面不使用,如果要将某个功能编译为模块,那就按下“M”,此时“[ ]”就会变为“< M >”。

细心的朋友应该会发现,在 mx6ull_lxg_emmc.h 里面我们配置使能了 dhcp 和 ping 命令,但是在图 中 dhcp 和 ping 前面的“[ ]”并不是“[ * ]”,也就是说不编译 dhcp 和 ping命令,这不是冲突了吗?实际情况是 dhcp 和 ping 命令是会编译的。之所以在图中没有体现出来时因为我们是直接在mx6ull_lxg_emmc.h 中定义的宏 CONFIG_CMD_PING 和CONFIG_CMD_DHCP,而 menuconfig 是通过读取.config 文件来判断使能了哪些功能,.config里面并没有宏CONFIG_CMD_PING ,CONFIG_CMD_DHCP,所以menuconfig就会识别出错。

选中 dns,然后按下“H”或者“?”键可以打开 dns 命令的提示信息,如图所示:

image-20200819112413619


image-20200819112518396

保存会询问是否保存新的配置文件,通过键盘的←或→键来选择“Yes”项,然后按下键盘上的回车键确认保存。至此,我们就完成了通过图形界面使能了 uboot 的 dns 命令,打开.config文件,会发现多了“CONFIG_CMD_DNS=y”这一行,如图所示:

image-20200819112728189

使用如下命令编译 uboot:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

千万不能使用如下命令:

./mx6ull_lxg_emmc.sh

因为 mx6ull_lxg_emmc.sh 在编译之前会清理工程,会删除掉.config 文件!通过图形化界面配置所有配置项都会被删除,结果就是竹篮打水一场空。编译完成以后烧写到 SD 卡中,重启开发板进入 uboot 命令模式,输入“?”查看是否有“dns”命令,一般肯定有的。测试一下 dns 命令工作是否正常,使用 dns 命令来查看一下百度官网“www.baidu.com”的 IP 地址。要先设置一下 dns 服务器的 IP 地址,也就是设置环境变量 dnsip的值,命令如下:

setenv dnsip 114.114.114.114
saveenv

设置好以后就可以使用 dns 命令查看百度官网的 IP 地址了,输入命令:
dns www.baidu.com
结果如图所示:

image-20200819114151182

从图可以看出,“www.baidu.com”的 IP 地址,说明 dns 命令工作正常。这个就是通过图形化命令来配置 uboot,一般用来使能一些命令还是很方便的,这样就不需要到处找命令的配置宏是什么,然后在到配置文件里面去定义。

参考正点原子I.MX6U驱动开发指南的第34.2节。

Kconfig 语法简介

参考正点原子I.MX6U驱动开发指南的第34.2节。

添加自定义菜单

参考正点原子I.MX6U驱动开发指南的第34.3节。