[ARM Linux系统移植] Linux 内核移植

本章暂且先跳过 linux 内核代码的说明,因为这又将耗费“一本书”的内容。我们直接开始进行 linux 内核的移植工作。

主要的移植内容就是网络驱动,在 u-boot 移植的相关章节中我们也了解到,当前开发板和 i.Mx 使用了不一样的网络芯片。网络驱动的移植工作很重要,因为后续 linux 驱动学习的调试手段都需要用到网络。

1. 添加自己的开发板

和 u-boot 的移植工作类似,为了便于管理,我们为当前开发板引入“专有”的文件:配置文件和设备树文件。

添加配置文件

添加自己的配置文件,直接使用原本的 imx_v7_mfg_defconfig 文件。执行如下命令:

  • > cd arch/arm/configs
  • > cp imx_v7_mfg_defconfig imx_my_emmc_defconfig

添加设备树文件

添加的设备树文件基于原先的 imx6ull-14x14-evk.dts,后续再在其内容上进行修改。执行如下命令:

  • > cd arch/arm/boot/dts
  • > cp imx6ull-14x14-evk.dts imx6ull-my-emmc.dts

如下粗体指示,之后需要在 arch/arm/boot/dts/Makefile 文件内添加 imx6ull-my-emmc.dtb。这样就可以根据 imx6ull-my-emmc.dts 文件编译生成想要的 imx6ull-my-emmc.dtb 文件。

arch/arm/boot/dts/Makefile
  1. dtb-$(CONFIG_SOC_IMX6ULL) += \
  2.     imx6ull-14x14-ddr3-arm2.dtb \
  3.     ……
  4.     imx6ull-14x14-evk-usb-certi.dtb \
  5.     imx6ull-my-emmc.dtb \

在添加了配置文件和设备树文件之后,可以尝试编译验证一下。执行如下命令,需要注意的是其中的配置文件变成了我们添加的配置文件:

  • > make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
  • > make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_my_emmc_defconfig
  • > make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

编译生成之后,将 zImageimx6ull-my-emmc.dtb 文件放在 tftp 服务器上。同时还需要改一下 u-boot 的 bootcmd 环境变量,让它读取现在添加的设备树文件:

  • > setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-my-emmc.dtb; bootz 80800000 - 83000000'

开发板启动之后,我们可以明显地看到网络未准备的提示,后续还需要进行网络驱动的移植。

2. 驱动修改

在添加好自己的配置文件和设备树文件之后,需要修改 emmc 驱动和网络驱动。

2.1 8 线 emmc 驱动

当前默认是使用 4 线的 emmc 驱动,开发板是使用 8 线的 emmc,速度也比 4 线的快。修改相应的设备树文件就可以让内核使用 8 线的 emmc 驱动。我们将设备树内的 &usdhc2 节点修改如下:

  • &usdhc2 {
  •     pinctrl-names = "default", "state_100mhz", "state_200mhz";
  •     pinctrl-0 = <&pinctrl_usdhc2_8bit>;
  •     pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
  •     pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
  •     bus-width = <8>;
  •     non-removable;
  •     status = "okay";
  • };

修改之后,使用

  • > make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

命令,可以直接重新编译生成 dtb 文件,而不用整体编译。

2.2 网络驱动

网络驱动需要修改的地方较多,这边先依葫芦画瓢按照文档中的步骤进行操作。

2.2.1 修改复位引脚和网络时钟引脚

首先按如下代码所示,注释或删除掉粗体标注的地方,它们和管脚 GPIO5_IO07 以及 GPIO5_IO08 相关。这两个管脚现在用于网络芯片的复位引脚,所以需要将之前用到这两个管脚的地方删除掉,以防止干扰到复位引脚。

  • pinctrl_spi4: spi4grp {
  •         fsl,pins = <
  •                 MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10        0x70a1
  •                 MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11        0x70a1
  •                 /*MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07      0x70a1*/
  •                 /*MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08      0x80000000*/
  •         >;
  • };
  • spi4 {
  •     compatible = "spi-gpio";
  •     pinctrl-names = "default";
  •     pinctrl-0 = <&pinctrl_spi4>;
  •     /*pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;*/
  •     status = "okay";
  •     gpio-sck = <&gpio5 11 0>;
  •     gpio-mosi = <&gpio5 10 0>;
  •     /*cs-gpios = <&gpio5 7 0>;*/

接着按如下代码所示,增加两个复位引脚的信息。

  • &iomuxc_snvs {
  •     pinctrl-names = "default_snvs";
  •         pinctrl-0 = <&pinctrl_hog_2>;
  •         imx6ul-evk {
  •        …………
  •        pinctrl_enet1_reset: enet1resetgrp {
  •            fsl,pins = <
  •            /* used for enet1 reset */
  •            MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
  •            >;
  •        };
  •  
  •        pinctrl_enet2_reset: enet2resetgrp {
  •            fsl,pins = <
  •            /* used for enet2 reset */
  •            MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
  •            >;
  •        };
  • };

接着按如下代码所示,修改网络时钟引脚的电气属性:

  • pinctrl_enet1: enet1grp {
  •     fsl,pins = <
  •        MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN  0x1b0b0
  •        MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER  0x1b0b0
  •        MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
  •        MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
  •        MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN  0x1b0b0
  •        MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
  •        MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
  •        MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1  0x4001b009
  •     >;
  • };
  •  
  • pinctrl_enet2: enet2grp {
  •     fsl,pins = <
  •        MX6UL_PAD_GPIO1_IO07__ENET2_MDC     0x1b0b0
  •        MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0
  •        MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN  0x1b0b0
  •        MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER  0x1b0b0
  •        MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
  •        MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
  •        MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN  0x1b0b0
  •        MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
  •        MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
  •        MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2  0x4001b009
  •     >;
  • };

至此,复位引脚和网络时钟引脚的相关内容就修改完毕了。

2.2.2 修改 fec1 和 fec2 节点

按如下代码所示,修改 fec1fec2 节点的 pinctrl-0 属性,添加 2.2.1 中的复位引脚内容。

  • &fec1 {
  •     pinctrl-names = "default";
  •     pinctrl-0 = <&pinctrl_enet1
  •                  pinctrl_enet1_reset>;
  •     phy-mode = "rmii";
  •     phy-handle = <&ethphy0>;
  •     status = "okay";
  • };
  •  
  • &fec2 {
  •     pinctrl-names = "default";
  •     pinctrl-0 = <&pinctrl_enet2
  •                  pinctrl_enet2_reset>;

2.2.3 修改器件地址

在 u-boot 移植实验中已经了解到网口的器件地址也发生了变化,ENET1 为 0x0,ENET2 为 0x1。如下代码中的第 196/199 行、第 202/205 行定义了新的器件地址。

第 177 和 178 行设置了 ENET1 网络复位引脚所使用的 IO,且低电平有效,持续时间为 200 ms。第 188 和 189 行设置 ENET2 的内容同理。

第 198 和 204 行表明芯片是 SMSC 公司的,这样内核就会找到所需的 LAN8720A 驱动。

  1. &fec1 {
  2.     pinctrl-names = "default";
  3.     pinctrl-0 = <&pinctrl_enet1
  4.                  &pinctrl_enet1_reset>;
  5.     phy-mode = "rmii";
  6.     phy-handle = <&ethphy0>;
  7.     phy-reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>;
  8.     phy-reset-duration = <200>;
  9.     status = "okay";
  10. };
  11.  
  12. &fec2 {
  13.     pinctrl-names = "default";
  14.     pinctrl-0 = <&pinctrl_enet2
  15.                  &pinctrl_enet2_reset>;
  16.     phy-mode = "rmii";
  17.     phy-handle = <&ethphy1>;
  18.     phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
  19.     phy-reset-duration = <200>;
  20.     status = "okay";
  21.  
  22.     mdio {
  23.        #address-cells = <1>;
  24.        #size-cells = <0>;
  25.  
  26.        ethphy0: ethernet-phy@0 {
  27.            compatible = "ethernet-phy-ieee802.3-c22";
  28.            smsc,disable-energy-detect;
  29.            reg = <0>;
  30.        };
  31.  
  32.        ethphy1: ethernet-phy@1 {
  33.            compatible = "ethernet-phy-ieee802.3-c22";
  34.            smsc,disable-energy-detect;
  35.            reg = <1>;
  36.        };
  37.     };
  38. };

2.2.4 修改 fec_main.c

drivers/net/ethernet/freescale/fec_main.c 文件中设置两个网络时钟引脚 IO 寄存器的 SION 位为 1。

drivers/net/ethernet/freescale/fec_main.c
  1. static int
  2. fec_probe(struct platform_device *pdev)
  3. {
  4.     struct fec_enet_private *fep;
  5.     struct fec_platform_data *pdata;
  6.     struct net_device *ndev;
  7.     int i, irq, ret = 0;
  8.     struct resource *r;
  9.     const struct of_device_id *of_id;
  10.     static int dev_id;
  11.     struct device_node *np = pdev->dev.of_node, *phy_node;
  12.     int num_tx_qs;
  13.     int num_rx_qs;
  14.     
  15.     void __iomem *IMX6U_ENET1_TX_CLK;
  16.     void __iomem *IMX6U_ENET2_TX_CLK;
  17.     
  18.     IMX6U_ENET1_TX_CLK = ioremap(0X020E00DC4);
  19.     writel(0X14, IMX6U_ENET1_TX_CLK);
  20.     
  21.     IMX6U_ENET2_TX_CLK = ioremap(0X020E00FC4);
  22.     writel(0X14, IMX6U_ENET2_TX_CLK);

2.2.5 修改 smsc.c

在之前 u-boot 的移植过程中,了解到需要强制软复位一下网络芯片,否则会有一些错误发生。在 linux 中也是如此,如下代码把软复位操作移到判断外面,无论什么情况都强制软复位一下。

drivers/net/phy/smsc.c
  1. static int smsc_phy_reset(struct phy_device *phydev)
  2. {
  3.     int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
  4.     if (rc < 0)
  5.         return rc;
  6.  
  7.     /* If the SMSC PHY is in power down mode, then set it
  8.      * in all capable mode before using it.
  9.      */
  10.     if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
  11.         // int timeout = 50000;
  12.  
  13.         /* set "all capable" mode and reset the phy */
  14.         rc |= MII_LAN83C185_MODE_ALL;
  15.         phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
  16.         // phy_write(phydev, MII_BMCR, BMCR_RESET);
  17.  
  18.         // /* wait end of reset (max 500 ms) */
  19.         // do {
  20.         //  udelay(10);
  21.         //  if (timeout-- == 0)
  22.         //      return -1;
  23.         //  rc = phy_read(phydev, MII_BMCR);
  24.         // } while (rc & BMCR_RESET);
  25.     }
  26.  
  27.     int timeout = 50000;
  28.     phy_write(phydev, MII_BMCR, BMCR_RESET);
  29.  
  30.     /* wait end of reset (max 500 ms) */
  31.     do {
  32.         udelay(10);
  33.         if (timeout-- == 0)
  34.             return -1;
  35.         rc = phy_read(phydev, MII_BMCR);
  36.     } while (rc & BMCR_RESET);
  37.  
  38.     return 0;
  39. }

2.2.6 修改 LCD 引脚

这一节的内容文档上没有。自己在实验的过程中,如果没有修改以下复用引脚的配置,网络则会反复重启。最后在论坛上找到了相应的解决方案,虽然目前还不知道为什么 lcd 相关引脚会影响到网络。

  • pinctrl_lcdif_dat: lcdifdatgrp {
  •     fsl,pins = <
  •        MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x49
  •        MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x49
  •        MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x49
  •        MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x49
  •        MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x49
  •        MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x49
  •        MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x49
  •        MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x51
  •        MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x49
  •        MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x49
  •        MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x49
  •        MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x49
  •        MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x49
  •        MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x49
  •        MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x49
  •        MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x51
  •        MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x49
  •        MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x49
  •        MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x49
  •        MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x49
  •        MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x49
  •        MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x49
  •        MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x49
  •        MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x51
  •     >;
  • };
  •  
  • pinctrl_lcdif_ctrl: lcdifctrlgrp {
  •     fsl,pins = <
  •        MX6UL_PAD_LCD_CLK__LCDIF_CLK     0x49
  •        MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x49
  •        MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x49
  •        MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x49
  •     >;
  • };

2.2.7 使能 LAN8720 驱动

输入以下命令:

  • > make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

如下图所示,按路径 Device Drivers - Network device support - PHY Device support and infrastructure,选择开启 Drivers for SMSC PHYs,即可编译 LAN8720 驱动。

图1 使能 LAN8720 驱动

使能后,我们可以核对一下 .config 文件,可以找到如下内容:

  • CONFIG_SMSC_PHY=y

为了防止后续 make distclean 后,还需要重新使能,可以将现在生成的 .config 重命名覆盖我们的配置文件:

  • > cp .config arch/arm/configs/imx_my_emmc_defconfig

2.2.8 网络驱动测试

可以手动给网卡配置 ip 地址:

  • > ifconfig eth0 169.254.155.84

配置完之后,如果 ping 得通主机,则代表网络驱动移植成功。

  • > ping 169.254.155.83