Linux驱动开发:uboot移植流程

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

修改 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.hxxx.c 这两个文件中进行的,xxx 为板子名称,比如 mx6ull_alientek_emmc.hmx6ull_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 这个网络芯片,在一些没有内部 MACCPU 中,比如三星的 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 芯片的连接引脚与芯片驱动进行修改!

正点原子 IMX6ULLENET1

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_DEV 的 01 决定是选择启用 ENET1 和  ENET2

需要修改的代码部分如下:

**1、**修改 ENET1 网络 PHY 的地址。

**2、**修改 ENET2 网络 PHY 的地址。

**3、**使能 SMSC 公司的 PHY 驱动。

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

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

ENET1 的复位引脚连接到 SNVS_TAMPER7 上,对应 GPIO5_IO07,ENET2 的复位引脚连接到 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 函数用于控制 74LV595 的 IO 输出电平,将这两个函数全部删除掉!

mx6ull_alientek_emmc.c 中找到 board_init 函数,此函数是板子初始化函数,会被 board_init_r 调用,board_init 会调用 imx_iomux_v3_setup_multiple_padsiox74lv_init 这两个函数来初始化 74lv595 的 GPIO,将这两行删除掉。至此,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

2.4.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 如下所示:

/**
 * 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 中有两个非常重要的环境变量 bootcmdbootargs,接下来看一下这两个环境变量。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=ttymxc0baudrate=115200mmcroot=/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 内核镜像与设备树,并挂载根文件系统:

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

** 使用了串口和屏幕显示,通过 tftp下载 Linux 内核镜像,nfs 挂载根文件系统;

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

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-dhxDvWl2-1715887663321)]
[外链图片转存中…(img-MuD9wSBb-1715887663322)]

如果你需要这些资料,可以戳这里获取

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值