文章目录
[一、I.MX6U-ALPHA 开发板网络简介](#一、I.MX6U-ALPHA 开发板网络简介)
[1.ENET1 的网络原理图](#1.ENET1 的网络原理图)
[2.ENET2 的网络原理图](#2.ENET2 的网络原理图)
[1、网络 PHY 地址修改](#1、网络 PHY 地址修改)
[2、删除 uboot 中 74LV595 的驱动代码](#2、删除 uboot 中 74LV595 的驱动代码)
[3、添加 I.MX6U-ALPHA 开发板网络复位引脚驱动](#3、添加 I.MX6U-ALPHA 开发板网络复位引脚驱动)
[4、修改 drivers/net/phy/phy.c 文件中的函数 genphy_update_link](#4、修改 drivers/net/phy/phy.c 文件中的函数 genphy_update_link)
前言
接着上一期修改网络驱动。
一、I.MX6U-ALPHA 开发板网络简介
I.MX6UL/ULL内部有个以太网 MAC外设,也就是 ENET,需要外接 一个 PHY芯片来实现网络通信功能,也就是内部 MAC+外部 PHY芯片的方案。
有些设备用的DM9000芯片,DM9000 是 "MAC+PHY" 一体芯片,用于无内部 MAC 的处理器,通过类 SRAM 接口通信。
I.MX6U 的内部 MAC 带有专用 DMA,处理网络数据包的效率和速度远高于 DM9000 的 SRAM 读写方式。
I.MX6UL/ULL 有两个网络接口 ENET1 和 ENET2,正点原子的 I.MX6U-ALPHA 开发板提供了这两个网络接口,其中 ENET1 和 ENET2 都使用 LAN8720A 作为 PHY 芯片。NXP 官方的I.MX6ULL EVK 开发板使用 KSZ8081 这颗 PHY 芯片,但实际产品中不一定会使用这个芯片,所以现在我们来讲解一下更换了PHY 芯片,该如何调整网络驱动,使网络工作正常。
二、硬件原理图原理
1.ENET1 的网络原理图
I.MX6U-ALPHA 开发板 ENET1 的网络原理图如下图所示:

ENET1 的网络 PHY 芯片为 LAN8720A ,通过 RMII 接口与 I.MX6ULL 相连, I.MX6U-ALPHA 开发板的 ENET1 引脚与 NXP 官方的 I.MX6ULL EVK 开发板基本一样,唯独复位引脚不同。
从上图中可以看出 ENET1 复位引脚ENET1_RST 接到了 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。
LAN8720A 内部有寄存器, I.MX6ULL 会读取 LAN8720 内部寄存器来判断当前的物理链接状态、连接速度(10M 还是 100M) 和双工状态 ( 半双工还是全双工)。I.MX6ULL 通过 MDIO接口来读取PHY 芯片的内部寄存器,此端口有两个引脚,为 ENET_MDC 和 ENET_MDIO。
1、ENET_MDC 提供时钟。
2、ENET_MDIO 进行数据传输。
注意:一个MDIO接口可以管理32个PHY芯片。MIDO通过PHY ADDR来确定访问那个PHY芯片。ALPHA开发板ENET1的PHY ADDR是0x0,ENET2的PHY ADDR是0X1.
所以我们要修改 ENET1 网络驱动的话重点就三点:
①、 ENET1 复位引脚初始化。
②、 LAN8720A 的器件 ID 。
③、 LAN8720 驱动
2.ENET2 的网络原理图
再来看一下 ENET2 的原理图:

关于 ENET2 网络驱动的修改也注意一下三点:
①、ENET2 的复位引脚,从上图可以看出,ENET2 的复位引脚 ENET2_RST 接到了I.MX6ULL 的 SNVS_TAMPER8 上。
②、ENET2 所使用的 PHY 芯片器件地址,从上图可以看出,PHY 器件地址为 0X1。
③、LAN8720 驱动,ENET1 和 ENET2 都使用的 LAN8720,所以驱动肯定是一样的。
三、程序代码修改
1、网络 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。
此代码中 ENET1 的 PHY 地址,默认是 0X2 ,第 339 行为 ENET2 的 PHY 地址,默
认为 0x1。
而I.MX6U-ALPHA开发板ENET1 的 PHY 地址为0X0,ENET2 的 PHY 地址为0X1。
#define CONFIG_PHY_MICREL ,这个宏用于使能 uboot 中 Micrel 公司的 PHY驱动,KSZ8081 这颗 PHY 芯片就是 Micrel 公司生产的。
我们使用的是 为 LAN8720A芯片,而这个芯片是 SMSC 公司生产的。
这里我们得 将 CONFIG_PHY_MICREL 改为 CONFIG_PHY_SMSC
所以我们要修改以下三个地方:
①、修改 ENET1 网络 PHY 的地址。
②、修改 ENET2 网络 PHY 的地址。
③、使能 SMSC 公司的 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
2、删除 uboot 中 74LV595 的驱动代码
uboot 中网络 PHY 芯片地址修改完成以后就是网络复位引脚的驱动修改了,打开
mx6ull_alientek_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)
这些的宏定义是 74LV595 的相关 GPIO,而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。查找IMX6UL数据手册如下:

继续在 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),
};
同理,示例代码 33.2.7.5 是 74LV595 的 IO 配置参数结构体,将其删除掉。继续在
mx6ull_alientek_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[gp_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[gp_output[i]][0]);
else
gpio_direction_output(IOX_SDI, seq[gp_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_alientek_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 开发板两个网络复位引脚了。
3、添加I.MX6U-ALPHA开发板网络复位引脚驱动
在 mx6ull_alientek_emmc.c 中找到结构体数组 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_CLK_1 | 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_EN_ENET1_RX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
MX6_PAD_ENET1_RX_ER_ENET1_RX_ER | MUX_PAD_CTRL(ENET_PAD_CTRL),
MX6_PAD_SNVS_TAMPER7_GPIO5_IO07 | MUX_PAD_CTRL(NO_PAD_CTRL), /* 初始化ETH1 RESET引脚 */
};
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_EN_ENET2_TX_EN | MUX_PAD_CTRL(ENET_PAD_CTRL),
MX6_PAD_ENET2_TX_CLK_ENET2_REF_CLK2 | MUX_PAD_CTRL(ENET_CLK_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), /* 初始化ETH2 RESET引脚 */
};
有注释的那两行代码就是ENET1 和 ENET2 的复位 IO 配置参数。
继续在文件 mx6ull_alientek_emmc.c 中找到函数 setup_iomux_fec,此函数默认代码如下:
函数 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(100);
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(100);
gpio_set_value(ENET2_RESET, 1);
}
}
gpio_direction_output(ENET1_RESET, 1);
gpio_set_value(ENET1_RESET, 0);
mdelay(100);
gpio_set_value(ENET1_RESET, 1);
这个就是对应 ENET1的复位 IO 初始化,将这两个 IO 设置为输出并且硬件复位一下 LAN8720A
同理ENET2也是一样的。
4、修改 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 bmcr_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
230 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
231 while(phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR) & 0X8000) {
232 udelay(100);
233 }
234 phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, bmcr_reg);
235 lan8720_flag = 1;
236 }
237 #endif
238
239 /*
240 * Wait if the link is up, and autonegotiation is in progress
241 * (ie - we're capable and it's not done)
242 */
243 mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
291
292 return 0;
293 }
225 行~237 行就是新添加的代码,为条件编译代码段,只有使用 SMSC 公司的 PHY 这段代码才会执行。
第 229 行读取 LAN8720A 芯片的 BMCR 寄存器(寄存器地址为 0),该寄存器是 LAN8720A 的核心配置寄存器,此行的作用是读取并暂存该寄存器的默认配置值。
第 230 行向 BMCR 寄存器写入复位指令 BMCR_RESET(数值为 0X8000),由于 BMCR 寄存器的第 15 位为软件复位控制位,因此该行操作实质是对 LAN8720A 执行软件复位,复位完成后该位会自动清零。
第 231 至 233 行循环等待 LAN8720A 软件复位完成,具体判断逻辑为:持续读取 BMCR 寄存器的第 15 位,若该位仍为 1,则表示复位尚未完成,需等待(每次等待 100 微秒);若为 0 则说明复位完成,退出循环。
第 234 行将第 229 行读取并保存的 BMCR 寄存器原始配置值重新写入,确保复位后 LAN8720A 恢复到复位前的配置状态。
至此网络的复位引脚驱动修改完成。
四、编译下载
重新编译 uboot ,然后将 u-boot.bin 烧写到 SD 卡中并 启动,uboot 启动信息如下图 所示:

从图中可以看到"Net: FEC1"这一行,提示当前使用的 FEC1 这个网口,也就是 ENET2。在 uboot 中使用网络之前要先设置几个环境变量,命令如下:
setenv ipaddr 192.168.1.55 //开发板 IP 地址
setenv ethaddr b8:ae:1d:01:00:00 //开发板网卡 MAC 地址
setenv gatewayip 192.168.1.1 //开发板默认网关
setenv netmask 255.255.255.0 //开发板子网掩码
setenv serverip 192.168.1.250 //服务器地址,也就是 Ubuntu 地址
saveenv //保存环境变量
设置好环境变量以后就可以在 uboot 中使用网络了,用网线将 I.MX6U-ALPHA 上的 ENET2
与电脑或者路由器连接起来,保证开发板和电脑在同一个网段内。
总结
修改开发板的uboot网络驱动.