1 NXP官方开发板uboot编译测试
1 查找 NXP 官方的开发板默认配置文件
因为我们的开发板是参考 NXP 官方的 I.MX6ULL EVK 开发板做的硬件,因此我们在移植 uboot 的时候就可以以 NXP 官方的 I.MX6ULL EVK 开发板为蓝本。
在 NXP 官方 I.MX6UL/6ULL 默认配置文件中找到mx6ull_14x14_evk_emmc_defconfig
2 编译 NXP 官方开发板对应的 uboot
进入uboot-imx-rel_imx_4.1.15_2.1.0_ga,编写编译shell脚本
1 #!/bin/bash
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
4 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
执行编译脚本
./mx6ull_14x14_evk_emmc.sh
编译结果如下所示
3 烧写验证与驱动测试
使用 imxdownload 软件将 u-boot.bin烧写到 SD 卡中
./imxdownload u-boot.bin /dev/sdb
设置开发板从SD卡启动,复位开发板,Mobaxterm打印如下信息
uboot 启动正常,虽然我们用的是 NXP 官方 I.MX6ULL 开发板的uboot,但是在正点原子的 I.MX6ULL 开发板上是可以正常启动的。
经过测试,开发板sd卡驱动和emmc驱动正常。需要修改LCD驱动和网络驱动。
2 在 U-Boot 中添加自己的开发板
2.1 添加开发板默认配置文件
先在 configs 目录下创建默认配置文件,复制 mx6ull_14x14_evk_emmc_defconfig,然后重命名为 mx6ull_znn_emmc_defconfig
cd configs
cp mx6ull_14x14_evk_emmc_defconfig mx6ull_alientek_emmc_defconfig
将文件 mx6ull_znn_emmc_defconfig 中的内容改成下面内容:
1 CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_znn_emmc/imximag e.cfg,MX6ULL_EVK_EMMC_REWORK"
2 CONFIG_ARM=y
3 CONFIG_ARCH_MX6=y
4 CONFIG_TARGET_MX6ULL_ZNN_EMMC=y
5 CONFIG_CMD_GPIO=y
2.2 添加开发板对应的头文件
在目录 include/configs 下添加 I.MX6ULL-ALPHA 开 发 板 对 应 的 头 文 件
cp include/configs/mx6ullevk.h mx6ull_znn_emmc.h
将
#ifndef __MX6ULLEVK_CONFIG_H
#define __MX6ULLEVK_CONFIG_H
修改为
#ifndef __MX6ULLEVK_ZNN_EMMC_CONFIG_H
#define __MX6ULLEVK_ZNN_EMMC_CONFIG_H
2.3 添加开发板对应的板级文件夹
uboot 中每个板子都有一个对应的文件夹来存放板级文件,比如开发板上外设驱动文件等等。在 board/freescale 目录下,在这个目录下有个名为 mx6ullevk 的文件夹,这个文件夹就是 NXP 官方 I.MX6ULL EVK 开发板的板级文件夹。复制 mx6ullevk,将其重命名为 mx6ull_znn_emmc,命令如下:
cd board/freescale/
cp mx6ullevk/ -r mx6ull_znn_emmc
进 入 mx6ull_znn_emmc 目 录 中 , 将 其 中 的 mx6ullevk.c 文 件 重 命 名 为mx6ull_znn_emmc.c,命令如下:
cd mx6ull_znn_emmc
mv mx6ullevk.c mx6ull_znn_emmc.c
我们还需要对 mx6ull_znn_emmc 目录下的文件做一些修改:
1、修改 mx6ull_znn_emmc 目录下的 Makefile 文件
1 # (C) Copyright 2015 Freescale Semiconductor, Inc.
2 #
3 # SPDX-License-Identifier: GPL-2.0+
4 #
5
6 obj-y := mx6ull_znn_emmc.o
7
8 extra-$(CONFIG_USE_PLUGIN) := plugin.bin
9 $(obj)/plugin.bin: $(obj)/plugin.o
10 $(OBJCOPY) -O binary --gap-fill 0xff $< $@
重点是第 6 行的 obj-y,改为 mx6ull_znn_emmc.o,这样才会编译 mx6ull_znn_emmc.c
这个文件。
2、修改 mx6ull_znn_emmc 目录下的 imximage.cfg 文件
将 imximage.cfg 中的下面一句:
PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000
修改为
PLUGIN board/freescale/mx6ull_znn_emmc/plugin.bin 0x00907000
3、修改 mx6ull_znn_emmc 目录下的 Kconfig 文件
修改 Kconfig 文件,修改后的内容如下:
1 if TARGET_MX6ULL_ZNN_EMMC
2
3 config SYS_BOARD
4 default "mx6ull_znn_emmc"
5
6 config SYS_VENDOR
7 default "freescale"
8
9 config SYS_SOC
10 default "mx6"
11
12 config SYS_CONFIG_NAME
13 default "mx6ull_znn_emmc"
14
15 endif
4、修改 mx6ull_znn_emmc 目录下的 MAINTAINERS 文件
修改 MAINTAINERS 文件,修改后的内容如下:
1 MX6ULL_ZNN_EMMC BOARD
2 M: Peng Fan <peng.fan@nxp.com>
3 S: Maintained
4 F: board/freescale/mx6ull_znn_emmc/
5 F: include/configs/mx6ull_znn_emmc.h
6 F: /configs/mx6ull_znn_emmc_defconfig
2.4 修改 U-Boot 图形界面配置文件
修改文件arch/arm/cpu/armv7/mx6/Kconfig,在 207 行加入如下内容:
207 config TARGET_MX6ULL_ZNN_EMMC
208 bool "Support mx6ull_znn_emmc"
209 select MX6ULL
210 select DM
211 select DM_THERMAL
在最后一行的 endif 的前一行添加如下内容:
288 source "board/freescale/mx6ull_znn_emmc/Kconfig"
添加完成以后的 Kconfig 文件如图所示
到此为止,I.MX6U-ALPHA 开发板就已经添加到 uboot 中了,接下来就是编译这个新添加的开发板。
2.5 使用新添加的板子配置编译 uboot
在 uboot 根目录下新建一个名为 mx6ull_znn_emmc.sh 的 shell 脚本,在这个 shell 脚本里面输入如下内容:
1 #!/bin/bash
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_znn_emmc_defconfig
4 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
第 3 行我们使用的默认配置文件就是2.1小节的默认配置文件。
运行脚本编译 uboot
./mx6ull_znn_emmc.sh
等 待 编 译 完 成 , 编 译 完 成 以 后 输 入 如 下 命 令 , 查 看 一 下2.2 小节中 添加的mx6ull_znn_emmc.h 这个头文件有没有被引用。
grep -nR "mx6ull_znn_emmc.h"
编译完成以后就使用 imxdownload 将新编译出来的 u-boot.bin 烧写到 SD 卡中测试,Mobaxterm 输出结果如图所示:
从图中可以看出,我们新编译的uboot启动正常。此时的 Board 还是“MX6ULL 14x14 EVK”,因为我们参考的 NXP官方的 I.MX6ULL 开发板来添加自己的开发板。如果接了 LCD 屏幕的话会发现 LCD 屏幕并没
有显示 NXP 的 logo,而且从图可以看出此时的网络同样也没识别出来。接下来就修改LCD驱动和网络驱动。
2.6 LCD 驱动修改
一般 uboot 中修改驱动基本都是在 xxx.h 和 xxx.c 这两个文件中进行的,xxx 为板子名称,比如 mx6ull_znn_emmc.h 和 mx6ull_znn_emmc.c 这两个文件。
一般修改LCD驱动需要重点关注以下几点
①、LCD 所使用的 GPIO,查看 uboot 中 LCD 的 IO 配置是否正确。
②、LCD 背光引脚 GPIO 的配置。
③、LCD 配置参数是否正确。
I.MX6U-ALPHA 开发板 LCD 原理图和 NXP 官方 I.MX6ULL 开发板一致,也就是 LCD 的 IO 和背光 IO 都一样的,所以 IO 部分就不用修改了。需要修改第三项 LCD 参数。
使用 cd board/freescale/mx6ull_znn_emmc 进入mx6ull_znn_emmc目录,打开mx6ull_znn_emmc.c文件。找到如下所示内容:
780 struct display_info_t const displays[] = {{
781 .bus = MX6UL_LCDIF1_BASE_ADDR,
782 .addr = 0,
783 .pixfmt = 24,
784 .detect = NULL,
785 .enable = do_enable_parallel_lcd,
786 .mode = {
787 .name = "TFT43AB",
788 .xres = 480,
789 .yres = 272,
790 .pixclock = 108695,
791 .left_margin = 8,
792 .right_margin = 4,
793 .upper_margin = 2,
794 .lower_margin = 4,
795 .hsync_len = 41,
796 .vsync_len = 10,
797 .sync = 0,
798 .vmode = FB_VMODE_NONINTERLACED
799 } } };
示例代码中定义了一个变量 displays,类型为 display_info_t,这个结构体是 LCD信息结构体,其中包括了 LCD 的分辨率,像素格式,LCD 的各个参数等。
display_info_t 定义在文件 arch/arm/include/asm/imx-common/video.h 中,定义如下:
17 struct display_info_t {
18 int bus;
19 int addr;
20 int pixfmt;
21 int (*detect)(struct display_info_t const *dev);
22 void (*enable)(struct display_info_t const *dev);
23 struct fb_videomode mode;
24 };
pixfmt 是像素格式,也就是一个像素点是多少位,如果是 RGB565 的话就是 16 位,如果是 888 的话就是 24 位,一般使用 RGB888。结构体 display_info_t 还有个 mode 成员变量,此成员变量也是个结构体,为 fb_videomode,定义在文件 include/linux/fb.h 中,定义如下:
598 struct fb_videomode {
599 const char *name; /* optional */
600 u32 refresh; /* optional */
601 u32 xres;
602 u32 yres;
603 u32 pixclock;
604 u32 left_margin;
605 u32 right_margin;
606 u32 upper_margin;
607 u32 lower_margin;
608 u32 hsync_len;
609 u32 vsync_len;
610 u32 sync;
611 u32 vmode;
612 u32 flag;
613 };
结构体 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,也就是不使用隔行扫描。
正点原子的 7 寸 1024*600 分辨率的屏幕(ATK7016)屏幕要求的像素时钟为 51.2MHz,因此:
pixclock=(1/51200000)*10^12=19531
根据其他的屏幕参数,可以得出 ATK7016 屏幕的配置参数如下:
780 struct display_info_t const displays[] = {{
781 .bus = MX6UL_LCDIF1_BASE_ADDR,
782 .addr = 0,
783 .pixfmt = 24,
784 .detect = NULL,
785 .enable = do_enable_parallel_lcd,
786 .mode = {
787 .name = "TFT7016",
788 .xres = 1024,
789 .yres = 600,
790 .pixclock = 19531,
791 .left_margin = 140,
792 .right_margin = 160,
793 .upper_margin = 20,
794 .lower_margin = 12,
795 .hsync_len = 20,
796 .vsync_len = 3,
797 .sync = 0,
798 .vmode = FB_VMODE_NONINTERLACED
799 } } };
打开 mx6ull_alientek_emmc.h,找到所有如下语句:
panel=TFT43AB
将其改为:
panel=TFT7016
也就是设置 panel 为 TFT7016,panel 的值要与代码中的.name 成员变量的值一致。修改完成以后重新编译一遍 uboot 并烧写到 SD 中启动。
重启以后 LCD 驱动一般就会工作正常了,LCD 上回显示 NXP 的 logo。
2.7 网络驱动修改
1、I.MX6U-ALPHA 开发板网络简介
I.MX6UL/ULL 内部有个以太网 MAC 外设,也就是 ENET,需要外接一个 PHY 芯片来实现网络通信功能,也就是内部 MAC+外部 PHY 芯片的方案。I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1 和 ENET2 都使用 LAN8720A 作为 PHY 芯片。NXP 官方的I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片,正点原子的 I.MX6U-ALPHA 开发板将 ENET1 和 ENET2的 PHY 换成了 LAN8720A。
接下来调整网络驱动,使网络工作正常。
I.MX6U-ALPHA 开发板 ENET1 的网络原理图如图所示:
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 进行数据传输。一个 MDIO 接口可以管理 32 个 PHY 芯片,同一个 MDIO 接口下的这些 PHY 使用不同的器件地址来做区分,MIDO 接口通过不同的器件地址即可访问到相应的 PHY 芯片。I.MX6U-ALPHA 开发板 ENET1 上连接的 LAN8720A器件地址为 0X0,所示我们要修改 ENET1 网络驱动的话重点就三点:
①、ENET1 复位引脚初始化。
②、LAN8720A 的器件 ID。
③、LAN8720 驱动
再来看一下 ENET2 的原理图,如图所示:
关于 ENET2 网络驱动的修改也注意一下三点
①、ENET2 的复位引脚,从上图可以看出,ENET2 的复位引脚 ENET2_RST 接到了
I.MX6ULL 的 SNVS_TAMPER8 上。
②、ENET2 所使用的 PHY 芯片器件地址,从上图可以看出,PHY 器件地址为 0X1。
③、LAN8720 驱动,ENET1 和 ENET2 都使用的 LAN8720,所以驱动肯定是一样的。
2、网络 PHY 地址修改
首先修改 uboot 中的 ENET1 和 ENET2 的 PHY 地址和驱动,打开 mx6ull_znn_emmc.h这个文件,找到如下代码:
318 #ifdef CONFIG_CMD_NET
319 #define CONFIG_CMD_PING
320 #define CONFIG_CMD_DHCP
321 #define CONFIG_CMD_MII
322 #define CONFIG_FEC_MXC
323 #define CONFIG_MII
324 #define CONFIG_FEC_ENET_DEV 1
325
326 #if (CONFIG_FEC_ENET_DEV == 0)
327 #define IMX_FEC_BASE ENET_BASE_ADDR
328 #define CONFIG_FEC_MXC_PHYADDR 0x2
329 #define CONFIG_FEC_XCV_TYPE RMII
330 #elif (CONFIG_FEC_ENET_DEV == 1)
331 #define IMX_FEC_BASE ENET2_BASE_ADDR
332 #define CONFIG_FEC_MXC_PHYADDR 0x1
333 #define CONFIG_FEC_XCV_TYPE RMII
334 #endif
335 #define CONFIG_ETHPRIME "FEC"
336
337 #define CONFIG_PHYLIB
338 #define CONFIG_PHY_MICREL
339 #endif
第 324 行的宏 CONFIG_FEC_ENET_DEV 用于选择使用哪个网口,默认为 1,也就是选择ENET2。第 328 行为 ENET1 的 PHY 地址,默认是 0X2,第 332行为 ENET2 的 PHY 地址,默认为 0x1。根据前面的分析可知,正点原子的 I.MX6U-ALPHA 开发板 ENET1 的 PHY 地址为0X0,ENET2 的 PHY 地址为 0X1,所以需要将第 328行的宏 CONFIG_FEC_MXC_PHYADDR改为 0x0。
第 338 行定了一个宏 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 地址参数如下所示:
318 #ifdef CONFIG_CMD_NET
319 #define CONFIG_CMD_PING
320 #define CONFIG_CMD_DHCP
321 #define CONFIG_CMD_MII
322 #define CONFIG_FEC_MXC
323 #define CONFIG_MII
324 #define CONFIG_FEC_ENET_DEV 1
325
326 #if (CONFIG_FEC_ENET_DEV == 0)
327 #define IMX_FEC_BASE ENET_BASE_ADDR
328 #define CONFIG_FEC_MXC_PHYADDR 0x0
329 #define CONFIG_FEC_XCV_TYPE RMII
330 #elif (CONFIG_FEC_ENET_DEV == 1)
331 #define IMX_FEC_BASE ENET2_BASE_ADDR
332 #define CONFIG_FEC_MXC_PHYADDR 0x1
333 #define CONFIG_FEC_XCV_TYPE RMII
334 #endif
335 #define CONFIG_ETHPRIME "FEC"
336
337 #define CONFIG_PHYLIB
338 #define CONFIG_PHY_SMSC
339 #endif
3、删除 uboot 中 74LV595 的驱动代码
uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开mx6ull_znn_emmc.c,找到如下代码:
91 #define IOX_SDI IMX_GPIO_NR(5, 10)
92 #define IOX_STCP IMX_GPIO_NR(5, 7)
93 #define IOX_SHCP IMX_GPIO_NR(5, 11)
94 #define IOX_OE IMX_GPIO_NR(5, 8)
示例代码 中以 IOX 开头的宏定义是 74LV595 的相关 GPIO,因为 NXP 官方I.MX6ULL EVK 开发板使用 74LV595 来扩展 IO,两个网络的复位引脚就是由 74LV595 来控制的。正点原子的 I.MX6U-ALPHA 开发板并没有使用 74LV595,因此我们将示例代码中的代码删除掉,替换为如下所示代码:
91 #define ENET1_RESET IMX_GPIO_NR(5, 7)
92 #define ENET2_RESET IMX_GPIO_NR(5, 8)
ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07,ENET2 的复位引脚连
接到 SNVS_TAMPER8 上,对应 GPIO5_IO08。
继续在 mx6ull_znn_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_alientek_emmc.c 中找到函数 iox74lv_init和iox74lv_set,如下所示:
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);
}
gpio_direction_output(IOX_STCP, 0);
udelay(500);
/*
* shift register will be output to pins
*/
gpio_direction_output(IOX_STCP, 1);
for (i = 7; i >= 0; i--)
{
gpio_direction_output(IOX_SHCP, 0);
gpio_direction_output(IOX_SDI, seq[qn_output[i]][1]);
udelay(500);
gpio_direction_output(IOX_SHCP, 1);
udelay(500);
}
gpio_direction_output(IOX_STCP, 0);
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);
}
gpio_direction_output(IOX_STCP, 0);
udelay(500);
/*
* shift register will be output to pins
*/
gpio_direction_output(IOX_STCP, 1);
for (i = 7; i >= 0; i--)
{
gpio_direction_output(IOX_SHCP, 0);
gpio_direction_output(IOX_SDI, seq[qn_output[i]][1]);
udelay(500);
gpio_direction_output(IOX_SHCP, 1);
udelay(500);
}
gpio_direction_output(IOX_STCP, 0);
udelay(500);
/*
* shift register will be output to pins
*/
gpio_direction_output(IOX_STCP, 1);
};
iox74lv_init 函数是 74LV595 的初始化函数,iox74lv_set 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉!
在 mx6ull_znn_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被board_init_r 调用,board_init 函数内容如下:
int board_init(void)
{
/* Address of boot parameters */
gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;
imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));
iox74lv_init();
#ifdef CONFIG_SYS_I2C_MXC
setup_i2c(0, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info1);
#endif
#ifdef CONFIG_FEC_MXC
setup_fec(CONFIG_FEC_ENET_DEV);
#endif
#ifdef CONFIG_USB_EHCI_MX6
setup_usb();
#endif
#ifdef CONFIG_FSL_QSPI
board_qspi_init();
#endif
#ifdef CONFIG_NAND_MXS
setup_gpmi_nand();
#endif
return 0;
}
board_init 会调用 imx_iomux_v3_setup_multiple_pads 和 iox74lv_init 这两个函数来初始化74lv595 的 GPIO,将这两行删除掉。至此,mx6ull_znn_emmc.c 中关于 74LV595 芯片的驱动代码都删除掉了,接下来就是添加 I.MX6U-ALPHA 开发板两个网络复位引脚了。
4、添加 I.MX6U-ALPHA 开发板网络复位引脚驱动
在 mx6ull_znn_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_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),
};
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),
};
结构体数组 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_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),
};
示例代码中添加的两行分别是 ENET1 和 ENET2 的复位 IO 配置参数。继
续在文件 mx6ull_znn_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。
5、修改 drivers/net/phy/phy.c 文件中的函数 genphy_update_link
uboot 中的 LAN8720A 驱动有点问题,打开文件drivers/net/phy/phy.c,找到函数 genphy_update_link,这是个通用 PHY 驱动函数,此函数用于更新 PHY 的连接状态和速度。使用 LAN8720A 的时候需要在此函数中添加一些代码,修改后的函数 genphy_update_link 如下所示:
221 int genphy_update_link(struct phy_device *phydev)
222 {
223 unsigned int mii_reg;
224
225 #ifdef CONFIG_PHY_SMSC
226 static int lan8720_flag = 0;
227 int bmcr_reg = 0;
228 if (lan8720_flag == 0)
229 {
230 bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
231 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
232 while (phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000)
233 {
234 udelay(100);
235 }
236 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);
237 lan8720_flag = 1;
238 }
239 #endif
240
241 /*
242 * Wait if the link is up, and autonegotiation is in progress
243 * (ie - we're capable and it's not done)
244 */
245 mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
246
......
297 phydev->link = 0;
298 }
299
300 return 0;
301 }
225 行~239行就是新添加的代码,为条件编译代码段,只有使用 SMSC 公司的 PHY 这段代码才会执行。第 230行读取LAN8720A 的 BMCR 寄存器(寄存器地址为 0),此寄存器为 LAN8720A 的配置寄存器,这里先读取此寄存器的默认值并保存起来。231 行向寄存器 BMCR 寄存器写入 BMCR_RESET(值为0X8000),因为 BMCR 的 bit15 是软件复位控制位,因此 231 行就是软件复位 LAN8720A,复位完成以后此位会自动清零。第 232~235 行等待 LAN8720A 软件复位完成,也就是判断 BMCR的 bit15 位是否为 1,为 1 的话表示还没有复位完成。第 236 行重新向 BMCR 寄存器写入以前的值,也就是 230 行读出的那个值。
至此网络的复位引脚驱动修改完成,重新编译 uboot,然后将 u-boot.bin 烧写到 SD 卡中并启动,uboot 启动信息如图所示:
从图中可以看到“Net: FEC1”这一行,提示当前使用的 FEC1 这个网口,也就是 ENET2。在 uboot 中使用网络之前要先设置几个环境变量,命令如下:
setenv ipaddr 192.168.0.24 //开发板 IP 地址
setenv ethaddr b8:ae:1d:01:00:00 //开发板网卡 MAC 地址
setenv gatewayip 192.168.0.1 //开发板默认网关
setenv netmask 255.255.255.0 //开发板子网掩码
setenv serverip 192.168.0.23 //服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量
设置好环境变量以后就可以在 uboot 中使用网络了,用网线将 I.MX6U-ALPHA 上的 ENET2与电脑或者路由器连接起来,保证开发板和电脑在同一个网段内,通过 ping 命令来测试一下网络连接,命令如下:
ping 192.168.0.23
结果如下图 所示:
有“host 192.168.1.250 is alive”这句,说明 ping 主机成功,说明ENET2 网络工作正常。
打开 mx6ull_alientek_emmc.h将CONFIG_FEC_ENET_DEV 改为 0,然后重新编译一下 uboot 并烧写到 SD 卡中重启。再测试下ENET1网络是否工作正常。
2.8 其他需要修改的地方
在 uboot 启动信息中会有“Board: MX6ULL 14x14 EVK”这一句,也就是说板子名字为“MX6ULL 14x14 EVK”,要将其改为我们所使用的板子名字,比如“MX6ULL ZNN EMMC”。打开文件 mx6ull_znn_emmc.c,找到函数checkboard,将其改为如下所示内容:
int checkboard(void)
{
if (is_mx6ull_9x9_evk())
puts("Board: MX6ULL 9x9 EVK\n");
else
puts("Board: MX6ULL ZNN EMMC\n");
return 0;
}
修改完成以后重新编译 uboot 并烧写到 SD 卡中验证,uboot 启动信息如图所示:
从图中可以看出,Board 变成了“MX6ULL ZNN EMMC”。至此 uboot 的驱动部分就修改完成了,uboot 移植也完成了。
uboot 的最终目的就是启动 Linux 内核,所以需要通过启动 Linux 内核来判断 uboot 移植是否成功。在启动 Linux 内核之前我们需要先来学习两个重要的环境变量 bootcmd 和 bootargs。
3 bootcmd 和 bootargs 环境变量
3.1 环境变量 bootcmd
bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。这些命令一般都是用来启动 Linux 内核的,比如读取 EMMC 或 者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。可以在 uboot 启动以后进入命令行设置 bootcmd 环境变量的值。
例如,我们要从EMMC 启动,命令如下:
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-znn-emmc.dtb; bootz 80800000 - 83000000;
3.2 环境变量 bootargs
bootargs 保存着 uboot 传递给 Linux 内核的参数,常用的参数有:
1、console
console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?
2、root
root 用来设置根文件系统的位置,root=/dev/mmcblk1p2 用于指明根文件系统存放在mmcblk1 设备的分区 2 中。
root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话
mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加
rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
3、rootfstype
此选项一般配置 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。
4 uboot 启动 Linux 测试
uboot 已经移植好了,bootcmd 和 bootargs 这两个重要的环境变量也讲解了,接下来就要测试一下 uboot 能不能完成它的工作:启动 Linux 内核。我们测试两种启动 Linux 内核的方法,一种是直接从 EMMC 启动,一种是从网络启动。
4.1 从 EMMC 启动 Linux 系统
从 EMMC 启动也就是将编译出来的 Linux 镜像文件 zImage 和设备树文件保存在 EMMC中,uboot 从 EMMC 中读取这两个文件并启动,这个是我们产品最终的启动方式。先检查一下 EMMC 的分区 1 中有没有zImage 文件和设备树文件,输入命令“ls mmc 1:1”,结果如图所示:
从图 中可以看出,此时 EMMC 分区 1 中存在 zimage 和 imx6ull-14x14-evk.dtb这两个文件,所以我们可以测试新移植的 uboot 能不能启动 linux 内核.
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000
imx6ull-14x14-evk.dtb; bootz 80800000 - 83000000;'
saveenv
设置好以后直接输入 boot,或者 run bootcmd 即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如图所示的启动信息:
4.2 从网络启动 Linux 系统
从网络启动 linux 系统的唯一目的就是为了调试!不管是为了调试 linux 系统还是 linux 下的驱动。每次修改 linux 系统文件或者 linux 下的某个驱动以后都要将其烧写到 EMMC 中去测试,这样太麻烦了。我们可以设置 linux 从网络启动,也就是将 linux 镜像文件和根文件系统都放到 Ubuntu 下某个指定的文件夹中,这样每次重新编译 linux 内核或者某个 linux 驱动以后只需要使用 cp 命令将其拷贝到这个指定的文件夹中即可,这样就不用需要频繁的烧写 EMMC,这样就加快了开发速度。
这里我们使用 tftp 从 Ubuntu 中下载 zImage 和设备树文件,前提是要将 zImage 和设备树文件放到 Ubuntu 下的 tftp 目录中。
设置 bootargs 和 bootcmd 这两个环境变量,设置如下:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz
80800000 - 83000000'
saveenv
一开始是通过tftp下载zImage和imx6ull-alientek-emmc.dtb这两个文件,过程如下图所示:
uboot 移植到此结束,简单总结一下 uboot 移植的过程:
①、不管是购买的开发板还是自己做的开发板,基本都是参考半导体厂商的 dmeo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、linux kernel 和 rootfs 等,最终制作好 BSP包提供给用户。我们可以在官方提供的 BSP 包的基础上添加我们的板子,也就是俗称的移植。
②、我们购买的开发板或者自己做的板子一般都不会原封不动的照抄半导体厂商的 demo板,都会根据实际的情况来做修改,既然有修改就必然涉及到 uboot 下驱动的移植。
③、一般 uboot 中需要解决串口、NAND、EMMC 或 SD 卡、网络和 LCD 驱动,因为 uboot的主要目的就是启动 Linux 内核,所以不需要考虑太多的外设驱动。
④、在 uboot 中添加自己的板子信息,根据自己板子的实际情况来修改 uboot 中的驱动。