Bootstrap

Linux内核编译以及移植

目录

1.Linux内核获取

2.内核初次编译

 3.内核顶层Makefile介绍

3.1版本号

3.2、MAKEFLAGS 变量

3.3、命令输出

3.4、静默输出 

3.5、设置编译结果输出目录 

3.6代码检查 

3. 7、模块编译

3.8、设置目标架构和交叉编译器

3.9、交叉编译工具变量设置  

4.内核移植

1.修改nxp顶层Makefile

2. 配置并编译 Linux 内核

5.在 Linux 中添加自己的开发板

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

2.添加开发板对应的设备树文件

3.编译脚本

4.CPU 主频和网络驱动修改

1、设置 I.MX6U-ALPHA 开发板工作在 792MHz

2、超频至 700MHz

 3.使能 8 线 EMMC 驱动

4.修改网络驱动

6.总结


1.Linux内核获取

Linux 官网为 https://www.kernel.org

2.内核初次编译

编译内核之前需要先在 ubuntu 上安装 lzop 库,否则内核编译会失败!命令如下:

sudo apt-get install lzop

解压完成以后的 Linux 源码根目录如图 35.2.1 所示:

 新建编译脚本内容如下:

1 # ! /bin/sh
2 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - distclean
3 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - imx_v7_defconfig
4 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - menuconfig
5 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - all - j16

Linux 的编译过程基本和 uboot 一样,都要先执行“ make xxx_defconfig ”来配置
一下,然后在执行“ make ”进行编译。如果需要使用图形界面配置的话就执行“ make menuconfig ”。
编译完成以后就会在 arch/arm/boot 这个目录下生成一个叫做 zImage 的文件, zImage 就是
我们要用的 Linux 镜像文件。另外也会在 arch/arm/boot/dts 下生成很多 .dtb 文件,这些 .dtb 就是
设备树文件。
编译 Linux 内核的时候可能会提示“ recipe for target ‘arch/arm/boot/compressed/piggy.lzo’
failed”,原因是没有安装 lzop 库,输入如下命令安装 lzop 库,即可解决:
sudo apt-get install lzop

 3.内核顶层Makefile介绍

3.1版本号

示例代码 35.5.1 顶层 Makefile 代码段
1 VERSION = 4
2 PATCHLEVEL = 1
3 SUBLEVEL = 15
4 EXTRAVERSION =

3.2MAKEFLAGS 变量

示例代码 35.5.2 顶层 Makefile 代码段
16 MAKEFLAGS += - rR -- include - dir = $ ( CURDIR )

3.3、命令输出

Linux 编译的时候也可以通过“ V=1 ”来输出完整的命令,这个和 uboot 一样,相关代码如
下所示;
示例代码 35.5.3 顶层 Makefile 代码段
69 ifeq ( "$(origin V)" , "command line" )
70 KBUILD_VERBOSE = $ ( V )
71 endif
72 ifndef KBUILD_VERBOSE
73 KBUILD_VERBOSE = 0
74 endif
75
76 ifeq ( $ ( KBUILD_VERBOSE ), 1 )
77 quiet =
78 Q =
79 else
80 quiet = quiet_
81 Q = @
82 endif

3.4、静默输出 

Linux 编译的时候使用“ make -s ”就可实现静默编译,编译的时候就不会打印任何的信息,
uboot 一样,相关代码如下:
示例代码 35.5.4 顶层 Makefile 代码段
87 ifneq ( $ ( filter 4. %, $ ( MAKE_VERSION )),) # make - 4
88 ifneq ( $ ( filter % s , $ ( firstword x$ ( MAKEFLAGS ))),)
89 quiet = silent_
90 endif
91 else # make - 3.8x
92 ifneq ( $ ( filter s % - s %, $ ( MAKEFLAGS )),)
93 quiet = silent_
94 endif
95 endif
96
97 export quiet Q KBUILD_VERBOSE

3.5、设置编译结果输出目录 

Linux 编译的时候使用“ O=xxx ”即可将编译产生的过程文件输出到指定的目录中,相关代
码如下:
示例代码 35.5.5 顶层 Makefile 代码段
116 ifeq ( $ ( KBUILD_SRC ),)
117
118 # OK , Make called in directory where kernel src resides
119 # Do we want to locate output files in a separate directory ?
120 ifeq ( "$(origin O)" , "command line" )
121 KBUILD_OUTPUT := $ ( O )
122 endif

3.6代码检查 

Linux 也支持代码检查,使用命令“ make C=1 ”使能代码检查,检查那些需要重新编译的
文件。“ make C=2 ”用于检查所有的源码文件,顶层 Makefile 中的代码如下:
示例代码 35.5.6 顶层 Makefile 代码段
172 ifeq ( "$(origin C)" , "command line" )
173 KBUILD_CHECKSRC = $ ( C )
174 endif
175 ifndef KBUILD_CHECKSRC
176 KBUILD_CHECKSRC = 0
177 endif

3. 7、模块编译

Linux 允许单独编译某个模块,使用命令“ make M=dir ”即可,旧语法“ make SUBDIRS=dir
也是支持的。顶层 Makefile 中的代码如下:
示例代码 35.5.7 顶层 Makefile 代码段
179 # Use make M = dir to specify directory of external module to build
180 # Old syntax make ... SUBDIRS = $PWD is still supported
181 # Setting the environment variable KBUILD_EXTMOD take precedence
182 ifdef SUBDIRS
183 KBUILD_EXTMOD ?= $ ( SUBDIRS )
184 endif
185
186 ifeq ( "$(origin M)" , "command line" )
187 KBUILD_EXTMOD := $ ( M )
188 endif
189
190 # If building an external module we do not care about the all : rule
191 # but instead _all depend on modules
192 PHONY += all
193 ifeq ( $ ( KBUILD_EXTMOD ),)
194 _all : all
195 else
196 _all : modules
197 endif
198
199 ifeq ( $ ( KBUILD_SRC ),)
200 # building in the source tree
201 srctree := .
202 else
203 ifeq ( $ ( KBUILD_SRC )/, $ ( dir $ ( CURDIR )))
204 # building in a subdirectory of the source tree
205 srctree := ..
206 else
207 srctree := $ ( KBUILD_SRC )
208 endif
209 endif
210 objtree := .
211 src := $ ( srctree )
212 obj := $ ( objtree )
213
214 VPATH := $ ( srctree ) $ ( if $ ( KBUILD_EXTMOD ),: $ ( KBUILD_EXTMOD ))
215
216 export srctree objtree VPATH
外部模块编译过程和 uboot 也一样,最终导出 srctree objtree VPATH 这三个变量的值,
其中 srctree=. ,也就是当前目录, objtree 同样为“ . ”。

3.8、设置目标架构和交叉编译器

uboot 一样, Linux 编译的时候需要设置目标板架构 ARCH 和交叉编译器 CROSS_COMPILE
在顶层 Makefile 中代码如下:
示例代码 35.5.8 顶层 Makefile 代码段
252 ARCH
?= $ ( SUBARCH )
253 CROSS_COMPILE ?= $ ( CONFIG_CROSS_COMPILE : "%" =%)

为了方便,一般直接修改顶层 Makefile 中的 ARCH CROSS_COMPILE ,直接将其设置
为对应的架构和编译器,比如本教程将 ARCH 设置为为 arm CROSS_COMPILE 设置为 arm-
linux-gnueabihf- ,如下所示:
示例代码 35.5.9 顶层 Makefile 代码段
252 ARCH
?= arm
253 CROSS_COMPILE ?= arm-linux-gnueabihf-
设置好以后我们就可以使用如下命令编译 Linux 了:
make xxx_defconfig //使用默认配置文件配置 Linux
make menuconfig //启动图形化配置界面
make -j16 //编译 Linux

3.9、交叉编译工具变量设置  

示例代码 35.5.11 顶层 Makefile 代码段
353 AS = $ ( CROSS_COMPILE ) as
354 LD = $ ( CROSS_COMPILE ) ld
355 CC = $ ( CROSS_COMPILE ) gcc
356 CPP = $ ( CC ) - E
357 AR = $ ( CROSS_COMPILE ) ar
358 NM = $ ( CROSS_COMPILE ) nm
359 STRIP = $ ( CROSS_COMPILE ) strip
360 OBJCOPY = $ ( CROSS_COMPILE ) objcopy
361 OBJDUMP = $ ( CROSS_COMPILE ) objdump

 LALDCC 等这些都是交叉编译器所使用的工具。

4.内核移植

1.修改nxp顶层Makefile

修改顶层 Makefile ,直接在顶层 Makefile 文件里面定义 ARCH CROSS_COMPILE 这两
个的变量值为 arm arm-linux-gnueabihf-

2. 配置并编译 Linux 内核

uboot 一样,在编译 Linux 内核之前要先配置 Linux 内核。每个板子都有其对应的默认
配 置 文 件 , 这 些 默 认 配 置 文 件 保 存 在 arch/arm/configs 目录中。 imx_v7_defconfig
imx_v7_mfg_defconfig 都可作为 I.MX6ULL EVK 开发板所使用的默认配置文件。但是这里建议
使用 imx_v7_mfg_defconfig 这个默认配置文件,首先此配置文件默认支持 I.MX6UL 这款芯片,
而且重要的一点就是此文件编译出来的 zImage 可以通过 NXP 官方提供的 MfgTool 工具烧写!!
imx_v7_mfg_defconfig 中的“ mfg ”的意思就是 MfgTool
进入到 Ubuntu 中的 Linux 源码根目录下,执行如下命令配置 Linux 内核:
make clean//第一次编译 Linux 内核之前先清理一下
make imx_v7_mfg_defconfig //配置 Linux 内核
make -j16 //编译 Linux 内核
Linux 内核编译完成以后会在 arch/arm/boot 目录下生成 zImage 镜像文件,如果使用设备树
的话还会在 arch/arm/boot/dts 目录下开发板对应的 .dtb( 设备树 ) 文件,比如 imx6ull-14x14-evk.dtb
就是 NXP 官方的 I.MX6ULL EVK 开发板对应的设备树文件。至此我们得到两个文件:
①、 Linux 内核镜像文件: zImage
②、 NXP 官方 I.MX6ULL EVK 开发板对应的设备树文件: imx6ull-14x14-evk.dtb

5.在 Linux 中添加自己的开发板

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

arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 , 命 名 为
imx_alientek_emmc_defconfig ,命令如下:
cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig

以后就可以使用如下命令来配置正点原子 EMMC 版开发板对应的 Linux 内核了:

make imx_alientek_emmc_defconfig 

2.添加开发板对应的设备树文件

添加适合正点原子 EMMC 版开发板的设备树文件,进入目录 arch/arm/boot/dts 中,复制一
imx6ull-14x14-evk.dts ,然后将其重命名为 imx6ull-alientek-emmc.dts ,命令如下:
cd arch/arm/boot/dts
cp imx6ull-14x14-evk.dts imx6ull-alientek-emmc.dts
.dts 是设备树源码文件,编译 Linux 的时候会将其编译为 .dtb 文件。 imx6ull-alientek-emmc.dts
创 建 好 以 后 我 们 还 需 要 修 改 文 件 arch/arm/boot/dts/Makefile , 找 到 “ dtb-
$(CONFIG_SOC_IMX6ULL) ”配置项,在此配置项中加入“ imx6ull-alientek-emmc.dtb ” ,如下
所示:
示例代码 37.3.2.1 arch/arm/boot/dts/Makefile 代码段
400 dtb - $ ( CONFIG_SOC_IMX6ULL ) += \
401 imx6ull - 14x14 - ddr3 - arm2 . dtb \
402 imx6ull - 14x14 - ddr3 - arm2 - adc . dtb \
403 imx6ull - 14x14 - ddr3 - arm2 - cs42888 . dtb \
404 imx6ull - 14x14 - ddr3 - arm2 - ecspi . dtb \
405 imx6ull - 14x14 - ddr3 - arm2 - emmc . dtb \
406 imx6ull - 14x14 - ddr3 - arm2 - epdc . dtb \
407 imx6ull - 14x14 - ddr3 - arm2 - flexcan2 . dtb \
408 imx6ull - 14x14 - ddr3 - arm2 - gpmi - weim . dtb \
409 imx6ull - 14x14 - ddr3 - arm2 - lcdif . dtb \
410 imx6ull - 14x14 - ddr3 - arm2 - ldo . dtb \
411 imx6ull - 14x14 - ddr3 - arm2 - qspi . dtb \
412 imx6ull - 14x14 - ddr3 - arm2 - qspi - all . dtb \
413 imx6ull - 14x14 - ddr3 - arm2 - tsc . dtb \
414 imx6ull - 14x14 - ddr3 - arm2 - uart2 . dtb \
415 imx6ull - 14x14 - ddr3 - arm2 - usb . dtb \
416 imx6ull - 14x14 - ddr3 - arm2 - wm8958 . dtb \
417 imx6ull - 14x14 - evk . dtb \
418 imx6ull - 14x14 - evk - btwifi . dtb \
419 imx6ull - 14x14 - evk - emmc . dtb \
420 imx6ull - 14x14 - evk - gpmi - weim . dtb \
421 imx6ull - 14x14 - evk - usb - certi . dtb \
422 imx6ull - alientek - emmc . dtb \
422 行为“ imx6ull-alientek-emmc.dtb ”,这样编译 Linux 的时候就可以从 imx6ull-alientek
emmc.dts 编译出 imx6ull-alientek-emmc.dtb 文件了。

3.编译脚本

1 # ! /bin/sh
2 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - distclean
3 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf
imx_alientek_emmc_defconfig
4 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - menuconfig
5 make ARCH = arm CROSS_COMPILE = arm - linux - gnueabihf - all - j16

4.CPU 主频和网络驱动修改

正点原子 I.MX6U-ALPHA 开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的,也就是
NXP 官方宣传的 800MHz 版本。后续可能会生产 528MHz 核心板供企业级批量用户,但是开发
板搭配的都是 792MHz 主频的,

1、设置 I.MX6U-ALPHA 开发板工作在 792MHz

进入图 37.4.1.1 所示的命令行以后输入如下命令查看 cpu 信息:
cat /proc/cpuinfo
在图 37.4.1.2 中有 BogoMIPS 这一条,此时 BogoMIPS 3.00 BogoMIPS Linux 系统中
衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高, BogoMIPS 值就越大。
BogoMIPS 只是粗略的计算 CPU 性能,并不十分准确。但是我们可以通过 BogoMIPS 值来大致
的判断当前处理器的性能。在图 37.4.1.2 中并没有看到当前 CPU 的工作频率,那我们就转变另
一种方法查看当前 CPU 的工作频率。进入到目录 /sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下 会有很多文件,如图 37.4.1.3 所示:
此目录中记录了 CPU 频率等信息,这些文件的含义如下:
cpuinfo_cur_freq :当前 cpu 工作频率,从 CPU 寄存器读取到的工作频率。
cpuinfo_max_freq :处理器所能运行的最高工作频率 ( 单位 : KHz )。
cpuinfo_min_freq :处理器所能运行的最低工作频率 ( 单位 : KHz )。
cpuinfo_transition_latency :处理器切换频率所需要的时间 ( 单位 :ns)
scaling_available_frequencies :处理器支持的主频率列表 ( 单位 : KHz )。
scaling_available_governors :当前内核中支持的所有 governor( 调频 ) 类型。
scaling_cur_freq :保存着 cpufreq 模块缓存的当前 CPU 频率,不会对 CPU 硬件寄存器进
行检查。
scaling_driver :该文件保存当前 CPU 所使用的调频驱动。
scaling_governor governor( 调频 ) 策略, Linux 内核一共有 5 中调频策略,
①、 Performance ,最高性能,直接用最高频率,不考虑耗电。
②、 Interactive ,一开始直接用最高频率,然后根据 CPU 负载慢慢降低。
③、 Powersave ,省电模式,通常以最低频率运行,系统性能会受影响,一般不会用这个!
④、 Userspace ,可以在用户空间手动调节频率。
⑤、 Ondemand ,定时检查负载,然后根据负载来调节频率。负载低的时候降低 CPU 频率,
这样省电,负载高的时候提高 CPU 频率,增加性能。
scaling_max_freq governor( 调频 ) 可以调节的最高频率。
cpuinfo_min_freq governor( 调频 ) 可以调节的最低频率。
stats 目录下给出了CPU 各种运行频率的统计情况,比如 CPU 在各频率下的运行时间以及
变频次数。
使用如下命令查看当前 CPU 频率:
cat cpuinfo_cur_freq
当前 CPU 支持 198MHz 396MHz 528Mhz 792MHz 四种频率切换,其中调 频策略为 ondemand ,也就是定期检查负载,然后根据负载情况调节 CPU 频率。因为当前我们 开发板并没有做什么工作,因此 CPU 频率降低为 198MHz 以省电。如果开发板做一些高负载的 工作,比如播放视频等操作那么 CPU 频率就会提升上去。查看 stats 目录下的 time_in_state 文 件可以看到 CPU 在各频率下的工作时间,命令如下:
cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state
假如我们想让 CPU 一直工作在 792MHz 那该怎么办?很简单, 配置 Linux 内核,将调频策略选择为 performance 。或者修改 imx_alientek_emmc_defconfig 文件, 此文件中有下面几行:
41 CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND = y
42 CONFIG_CPU_FREQ_GOV_POWERSAVE = y
43 CONFIG_CPU_FREQ_GOV_USERSPACE = y
44 CONFIG_CPU_FREQ_GOV_INTERACTIVE = y
41 行,配置 ondemand 为默认调频策略。
42 行,使能 powersave 策略。
43 行,使能 userspace 策略。
44 行,使能 interactive 策略。
41 行屏蔽掉,然后在 44 行后面添加:
CONFIG_CPU_FREQ_GOV_ONDEMAND=y

修改完成以后重新编译 Linux 内核,编译之前先清理一下工程!因为我们重新修改过默认
配置文件了,编译完成以后使用新的 zImage 镜 像 文 件 重新 启动 Linux
我们再来看一下如何通过图形化界面配置 Linux 内核的 CPU 调频策略,输入“ make
menuconfig ”打开 Linux 内核的图形化配置界面,如图 37.4.1.8 所示:
进入以下路径
CPU Power Management
-> CPU Frequency scaling
-> Default CPUFreq governor

选择“ performance ”即可,选择以后退出图形化配置界面,然后编译 Linux
内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。

2、超频至 700MHz

超频设置其实很简单,修改一下设备树文件 arch/arm/boot/dts/imx6ull.dtsi 即可,打开
imx6ull.dtsi ,找到下面代码:
示例代码 37.4.1.3 imx6ull.dtsi 文件代码段
54 cpu0 : cpu@0 {
55 compatible = "arm,cortex-a7" ;
56 device_type = "cpu" ;
57 reg = < 0 >;
58 clock - latency = < 61036 >; /* two CLK32 periods */
59 operating - points = <
60 /* kHz uV */
61 996000 1275000
62 792000 1225000
63 528000 1175000
64 396000 1025000
65 198000 950000
66 >;
67
fsl , soc - operating - points = <
68
/* KHz uV */
69
996000 1175000
70
792000 1175000
71
528000 1175000
72
396000 1175000
73
198000 1175000
74 >;

修改以后代码如下:

56 device_type = "cpu" ;
57 reg = < 0 >;
58 clock - latency = < 61036 >; /* two CLK32 periods */
59 operating - points = <
60 /* kHz uV */
61 996000 1275000
62 792000 1225000
63 696000 1225000
64 528000 1175000
65 396000 1025000
66 198000 950000
67 >;
68
fsl , soc - operating - points = <
69
/* KHz uV */
70
996000 1175000
71
792000 1175000
72
696000 1175000
73
528000 1175000
74
396000 1175000
75
198000 1175000
76 >;

63 行,加入了“ 696000 1225000 ”,这个就是 696MHz 的支持。
72 行,加入了“ 696000 1175000 ”,也是对 696MHz 的支持。
修改好以后保存,并且编译设备树,在 Linux 内核源码根目录下输入如下命令编译设备树:
make dtbs

 3.使能 8 线 EMMC 驱动

修改方法很简单,直接修改设备树即可,打开文 件 imx6ull-alientek-emmc.dts ,找到如下所示内容:
734 & usdhc2 {
735 pinctrl - names = "default" ;
736 pinctrl - 0 = <& pinctrl_usdhc2 >;
737 non - removable ;
738 status = "okay" ;
739 };

只需要将其改为如下代码即可:

734 & usdhc2 {
735 pinctrl - names = "default" , "state_100mhz" , "state_200mhz" ;
736 pinctrl - 0 = <& pinctrl_usdhc2_8bit >;
737 pinctrl - 1 = <& pinctrl_usdhc2_8bit_100mhz >;
738 pinctrl - 2 = <& pinctrl_usdhc2_8bit_200mhz >;
739 bus - width = < 8 >;
740 non - removable ;
741 status = "okay" ;
742 };

修改完成以后保存一下 imx6ull-alientek-emmc.dts ,然后使用命令“ make dtbs ”重新编译一
下设备树,编译完成以后使用新的设备树重启 Linux 系统即可。

4.修改网络驱动

正点原子开发板的网络和 NXP 官方的网络硬件上 不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A ,两个网络 PHY 芯片的复位 IO 也不同。 所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,需要做修 改。
1 、修改 LAN8720 的复位以及网络时钟引脚驱动
打开设备树文件 imx6ull-alientek-emmc.dts,找到如下代码:
584 pinctrl_spi4 : spi4grp {
585 fsl , pins = <
586
MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1
587
MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1
588
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x70a1
589
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x80000000
590 >;
591 };

588 589 行就是初始化 SNVS_TAMPER7 SNVS_TAMPER8 这两个 引脚的,不过看样子好像是作为了 SPI4 IO ,这不是我们想要的,所以将 588 589 这两行 删除掉!删除掉以后继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:
125 spi4 {
126 compatible = "spi-gpio" ;
127 pinctrl - names = "default" ;
128 pinctrl - 0 = <& pinctrl_spi4 >;
129 pinctrl - assert - gpios = <& gpio5 8 GPIO_ACTIVE_LOW >;
......
133 cs - gpios = <& gpio5 7 0 >;

129 行,设置 GPIO5_IO08 SPI4 的一个功能引脚 ( 我也不清楚具体作为什么功能用 )
GPIO5_IO08 就是 SNVS_TAMPER8 GPIO 功能引脚。
133 行,设置 GPIO5_IO07 作为 SPI4 的片选引脚,而 GPIO5_IO07 就是 SNVS_TAMPER7
GPIO 功能引脚。
现在我们需要 GPIO5_IO07 GPIO5_IO08 分别作为 ENET1 ENET2 的复位引脚,而不
SPI4 的什么功能引脚,因此将示例代码 37.4.3.2 中的第 129 行和第 133 行处的代码删除掉!!
否则会干扰到网络复位引脚!
imx6ull-alientek-emmc.dts 里面找到名为“ iomuxc_snvs ”的节点 ( 就是直接搜索 ) ,然后在
此节点下添加网络复位引脚信息,添加完成以后的“ iomuxc_snvs ”的节点内容如下:
1 & iomuxc_snvs {
2 pinctrl - names = "default_snvs" ;
3 pinctrl - 0 = <& pinctrl_hog_2 >;
4 imx6ul - evk {
5
......
/* 省略掉其他 */
43
44
/*enet1 reset zuozhongkai*/
45
pinctrl_enet1_reset : enet1resetgrp {
46
fsl , pins = <
47
/* used for enet1 reset */
48
MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0
49
>;
50
};
51
52
/*enet2 reset zuozhongkai*/
53
pinctrl_enet2_reset : enet2resetgrp {
54
fsl , pins = <
55
/* used for enet2 reset */
56
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0
57
>;
58
};
59 };
60 };
1 行, imx6ull-alientek-emmc.dts 文件中 iomuxc_snvs 节点。
45~50 行, ENET1 网络复位引脚配置信息。
53~58 行, ENET2 网络复位引脚配置信息。
最后还需要修改一下 ENET1 ENET2 的网络时钟引脚配置,继续在 imx6ull-alientek
emmc.dts 中找到如下所示代码:
309 pinctrl_enet1 : enet1grp {
310 fsl , pins = <
311 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN
0x1b0b0
312 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER
0x1b0b0
313 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0
314 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0
315 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN
0x1b0b0
316 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0
317 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0
318 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009
319 >;
320 };
321
322 pinctrl_enet2 : enet2grp {
323 fsl , pins = <
324 MX6UL_PAD_GPIO1_IO07__ENET2_MDC
0x1b0b0
325 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO
0x1b0b0
326 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN
0x1b0b0
327 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER
0x1b0b0
328 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0
329 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0
330 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN
0x1b0b0
331 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0
332 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0
333 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009
334 >;
335 };

318 333 行,分别为 ENET1 ENET2 的网络时钟引脚配置信息,将这两个引脚的电
气属性值改为 0x4001b009 ,原来默认值为 0x4001b031
修改完成以后记得保存一下 imx6ull-alientek-emmc.dts ,网络复位以及时钟引脚驱动就修改
好了。
2 、修改 fec1 fec2 节点的 pinctrl-0 属性
imx6ull-alientek-emmc.dts 文件中找到名为“ fec1 ”和“ fec2 ”的这两个节点,修改其中的
pinctrl-0 ”属性值,修改以后如下所示:
1 & fec1 {
2 pinctrl - names = "default" ;
3
pinctrl - 0 = <& pinctrl_enet1
4
& pinctrl_enet1_reset >;
5
phy - mode = "rmii" ;
......
9
status = "okay" ;
10 };
11
12 & fec2 {
13
pinctrl - names = "default" ;
14
pinctrl - 0 = <& pinctrl_enet2
15 & pinctrl_enet2_reset >;
16
phy - mode = "rmii" ;
......
36 };
3~4 行,修改后的 fec1 节点“ pinctrl-0 ”属性值。
14~15 行,修改后的 fec2 节点“ pinctrl-0 ”属性值。
3 、修改 LAN8720A PHY 地址
ENET1 LAN8720A 地址为 0x0 ENET2 LAN8720A 地址为 0x1 。在 imx6ull-alientek-emmc.dts 中找到如下代码:

171 & fec1 {
172 pinctrl - names = "default" ;
......
175 phy - handle = <& ethphy0 >;
176 status = "okay" ;
177 };
178
179 & fec2 {
180 pinctrl - names = "default" ;
......
183 phy - handle = <& ethphy1 >;
184 status = "okay" ;
185
186 mdio {
187 #address - cells = < 1 >;
188 #size - cells = < 0 >;
189
190 ethphy0 : ethernet - phy@0 {
191 compatible = "ethernet-phy-ieee802.3-c22" ;
192 reg = < 2 >;
193 };
194
195 ethphy1 : ethernet - phy@1 {
196 compatible = "ethernet-phy-ieee802.3-c22" ;
197 reg = < 1 >;
198 };
199 };
200 };
171~177 行, ENET1 对应的设备树节点。
179~200 行, ENET2 对应的设备树节点。但是第 186~198 行的 mdio 节点描述了 ENET1
ENET2 PHY 地址信息。将示例代码 37.4.3.6 改为如下内容:
171 & fec1 {
172 pinctrl - names = "default" ;
173 pinctrl - 0 = <& pinctrl_enet1
174 & pinctrl_enet1_reset >;
175 phy - mode = "rmii" ;
176 phy - handle = <& ethphy0 >;
177 phy - reset - gpios = <& gpio5 7 GPIO_ACTIVE_LOW >;
178 phy - reset - duration = < 200 >;
179 status = "okay" ;
180 };
181
182 & fec2 {
183 pinctrl - names = "default" ;
184 pinctrl - 0 = <& pinctrl_enet2
185 & pinctrl_enet2_reset >;
186 phy - mode = "rmii" ;
187 phy - handle = <& ethphy1 >;
188 phy - reset - gpios = <& gpio5 8 GPIO_ACTIVE_LOW >;
189 phy - reset - duration = < 200 >;
190 status = "okay" ;
191
192 mdio {
193 #address - cells = < 1 >;
194 #size - cells = < 0 >;
195
196 ethphy0 : ethernet - phy@0 {
197 compatible = "ethernet-phy-ieee802.3-c22" ;
198 smsc , disable - energy - detect ;
199 reg = < 0 >;
200 };
201
202 ethphy1 : ethernet - phy@1 {
203 compatible = "ethernet-phy-ieee802.3-c22" ;
204 smsc , disable - energy - detect ;
205 reg = < 1 >;
206 };
207 };
208 };
177 178 行,添加了 ENET1 网络复位引脚所使用的 IO GPIO5_IO07 ,低电平有效。
复位低电平信号持续时间为 200ms
188 189 行, ENET2 网络复位引脚所使用的 IO GPIO5_IO08 ,同样低电平有效,持
续时间同样为 200ms
198 204 行,“ smsc,disable-energy-detect ”表明 PHY 芯片是 SMSC 公司的,这样 Linux
内核就会找到 SMSC 公司的 PHY 芯片驱动来驱动 LAN8720A
196 行,注意“ ethernet-phy@ ”后面的数字是 PHY 的地址, ENET1 PHY 地址为 0
所以“ @ ”后面是 0( 默认为 2)
199 行, reg 的值也表示 PHY 地址, ENET1 PHY 地址为 0 ,所以 reg=0
202 行, ENET2 PHY 地址为 1 ,因此“ @ ”后面为 1
205 行,因为 ENET2 PHY 地址为 1 ,所以 reg=1
至此, LAN8720A PHY 地址就改好了,保存一下 imx6ull-alientek-emmc.dts 文件。然后
使用“ make dtbs ”命令重新编译一下设备树。
3 、修改 fec_main.c 文件
要 在 I.MX6ULL 上 使 用 LAN8720A , 需 要 修 改 一 下 Linux 内 核 源 码 , 打 开
drivers/net/ethernet/freescale/fec_main.c ,找到函数 fec_probe ,在 fec_probe 中加入如下代码:
3438 static int
3439 fec_probe ( struct platform_device * pdev )
3440 {
3441 struct fec_enet_private * fep ;
3442 struct fec_platform_data * pdata ;
3443 struct net_device * ndev ;
3444 int i , irq , ret = 0 ;
3445 struct resource * r ;
3446 const struct of_device_id * of_id ;
3447 static int dev_id ;
3448 struct device_node * np = pdev -> dev . of_node , * phy_node ;
3449 int num_tx_qs ;
3450 int num_rx_qs ;
3451
3452 /* 设置 MX6UL_PAD_ENET1_TX_CLK MX6UL_PAD_ENET2_TX_CLK
3453 * 这两个 IO 的复用寄存器的 SION 位为 1
3454 */
3455 void __iomem * IMX6U_ENET1_TX_CLK ;
3456 void __iomem * IMX6U_ENET2_TX_CLK ;
3457
3458 IMX6U_ENET1_TX_CLK = ioremap ( 0X020E00DC , 4 );
3459 writel ( 0X14 , IMX6U_ENET1_TX_CLK );
3460
3461 IMX6U_ENET2_TX_CLK = ioremap ( 0X020E00FC , 4 );
3462 writel ( 0X14 , IMX6U_ENET2_TX_CLK );
3463
......
3656 return ret ;
3657 }
3455~3462 就是新加入的代码,如果要在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 ENET2 TX_CLK 引脚复位寄存器的 SION 位为 1
4 、配置 Linux 内核,使能 LAN8720 驱动
输入命令“ make menuconfig ”,打开图形化配置界面,选择使能 LAN8720A 的驱动,路径
如下:
-> Device Drivers
-> Network device support
-> PHY Device support and infrastructure
-> Drivers for SMSC PHYs

 

37.4.3.1 中选择将“ Drivers for SMSC PHYs ”编译到 Linux 内核中,因此“ <> ”里面变
为了“ * ”。 LAN8720A SMSC 公司出品的,因此勾选这个以后就会编译 LAN8720 驱动,配
置好以后退出配置界面,然后重新编译一下 Linux 内核。
5 、修改 smsc.c 文件
在测试 NFS 挂载文件系统的时候发现文件系统挂载成功率很低!老是提示 NFS 服务器找不
到,三四次就有一次挂载失败!很折磨人。 NFS 挂载就是通过网络来挂载文件系统,这样做的
好处就是方便我们后续调试 Linux 驱动。既然老是挂载失败那么可以肯定的是网络驱动有问题,
网络驱动分两部分:内部 MAC+ 外部 PHY ,内部 MAC 驱动是由 NXP 提供的,一般不会出问
题,否则的话用户早就给 NXP 反馈了。而且我用 NXP 官方的开发板测试网络是一直正常的,
但是 NXP 官方的开发板所使用的 PHY 芯片为 KSZ8081 。所以只有可能是外部 PHY ,也就是
LAN8720A 的驱动可能出问题了。鉴于 LAN8720A 有“前车之鉴”,那就是在 uboot 中需要对
LAN8720A 进行一次软复位,要设置 LAN8720A BMCR( 寄存器地址为 0) 寄存器 bit15 1
所以我猜测,在 Linux 中也需要对 LAN8720A 进行一次软复位。
首先需要找到 LAN8720A 的驱动文件, LAN8720A 的驱动文件是 drivers/net/phy/smsc.c
在此文件中有个叫做 smsc_phy_reset 的函数,看名字都知道这是 SMSC PHY 的复位函数,因
此, LAN8720A 肯定也会使用到这个复位函数,修改此函数的内容,修改以后的 smsc_phy_reset
函数内容如下所示:
1 static int smsc_phy_reset ( struct phy_device * phydev )
2 {
3
int err , phy_reset ;
4
int msec = 1 ;
5
struct device_node * np ;
6
int timeout = 50000 ;
7
if ( phydev -> addr == 0 ) /* FEC1 */ {
8
np = of_find_node_by_path ( "/soc/aips-bus@02100000/ethernet@
02188000" );
9
if ( np == NULL ) {
10
return - EINVAL ;
11
}
12
}
13
14
if ( phydev -> addr == 1 ) /* FEC2 */ {
15
np = of_find_node_by_path ( "/soc/aips-bus@02000000/ethernet@
020b4000" );
16
if ( np == NULL ) {
17
return - EINVAL ;
18
}
19
}
20
21
err = of_property_read_u32 ( np , "phy-reset-duration" , & msec );
22
/* A sane reset duration should not be longer than 1s */
23
if (! err && msec > 1000 )
24
msec = 1 ;
25
phy_reset = of_get_named_gpio ( np , "phy-reset-gpios" , 0 );
26
if (! gpio_is_valid ( phy_reset ))
27
return ;
28
29
gpio_direction_output ( phy_reset , 0 );
30
gpio_set_value ( phy_reset , 0 );
31
msleep ( msec );
32
gpio_set_value ( phy_reset , 1 );
33
34
int rc = phy_read ( phydev , MII_LAN83C185_SPECIAL_MODES );
35
if ( rc < 0 )
36
return rc ;
37
38
/* If the SMSC PHY is in power down mode, then set it
39 * in all capable mode before using it.
40 */
41
if (( rc & MII_LAN83C185_MODE_MASK ) ==
MII_LAN83C185_MODE_POWERDOWN ) {
42
43
/* set "all capable" mode and reset the phy */
44
rc |= MII_LAN83C185_MODE_ALL ;
45
phy_write ( phydev , MII_LAN83C185_SPECIAL_MODES , rc );
46
}
47
48
phy_write ( phydev , MII_BMCR , BMCR_RESET );
49
/* wait end of reset (max 500 ms) */
50
51
do {
52
udelay ( 10 );
53
if ( timeout -- == 0 )
54
return - 1 ;
55
rc = phy_read ( phydev , MII_BMCR );
56
} while ( rc & BMCR_RESET );
57
return 0 ;
58 }

7~12 行,获取 FEC1 网卡对应的设备节点。
14~19 行,获取 FEC2 网卡对应的设备节点。
21 行,从设备树中获取“ phy-reset-duration ”属性信息,也就是复位时间。
25 行,从设备树中获取“ phy-reset-gpios ”属性信息,也就是复位 IO
29~32 行,设置 PHY 的复位 IO ,复位 LAN8720A
41~48 行,以前的 smsc_phy_reset 函数会判断 LAN8720 是否处于 Powerdown 模式,只
有处于 Powerdown 模式的时候才会软复位 LAN8720 。这里我们将软复位代码移出来,这样每
次调用 smsc_phy_reset 函数 LAN8720A 都会被软复位。
最后我们还需要在 drivers/net/phy/smsc.c 文件中添加两个头文件,因为修改后的
smsc_phy_reset 函数用到了 gpio_direction_output gpio_set_value 这两个函数,需要添加的头
文件如下所示:
#include <linux/of_gpio.h>
#include <linux/io.h>

保存修改后的图形化配置文件

在修改网络驱动的时候我们通过图形界面使能了 LAN8720A 的驱动,使能以后会在 .config
中存在如下代码:
CONFIG_SMSC_PHY=y

 打开 drivers/net/phy/Makefile,有如下代码:

11 obj - $ ( CONFIG_SMSC_PHY ) += smsc . o
CONFIG_SMSC_PHY=y 的时候就会编译 smsc.c 这个文件, smsc.c 就是 LAN8720A 的驱
动文件。但是当我们执行“ make clean ”清理工程以后 .config 文件就会被删除掉,因此我们所有
原子哥在线教学 :www.yuanzige.com
论坛 :www.openedv.com
978
I.MX6U
嵌入式 Linux 驱动开发指南
的配置内容都会丢失,结果就是前功尽弃,一“删”回到解放前!所以我们在配置完图形界面
以后经过测试没有问题,就必须要保存一下配置文件。保存配置的方法有两个。
1 、直接另存为 .config 文件
既然图形化界面配置后的配置项保存在 .config 中,那么就简单粗暴,直接将 .config 文件另
存为 imx_alientek_emmc_defconfig ,然后其复制到 arch/arm/configs 目录下,替换以前的
imx_alientek_emmc_defconfig 。这样以后执行“ make imx_alientek_emmc_defconfig ”重新配置
Linux 内核的时候就会使用新的配置文件,默认就会使能 LAN8720A 的驱动。
2 、通过图形界面保存配置文件
相比于第 1 种直接另存为 .config 文件,第 2 种方法就很“文雅”了,在图形界面中保存配
置文件,在图形界面中会有“ < Save > ”选项,如图 37.4.4.1 所示:

通过键盘的“→”键,移动到“ < Save > ”选项,然后按下回车键,打开文件名输入对话框,
如图 37.4.4.2 所示:
在图 37.4.4.2 中输入要保存的文件名,可以带路径,一般是相对路径 ( 相对于 Linux 内核源
码 根目 录 ) 。 比如 我们要 将新 的配 置文 件保存 到目 录 arch/arm/configs 下 , 文件 名为
imx_alientek_emmc_defconfig ,也就是用新的配置文件替换掉老的默认配置文件。那么我们在图
37.4.4.2 中输入“ arch/arm/configs/imx_alientek_emmc_defconfig ”即可,如图 37.4.4.3 所示:

设置好文件名以后选择下方的“ < Ok > ”按钮,保存文件并退出。退出以后再打开imx_alientek_emmc_defconfig 文件,就会在此文件中找到“ CONFIG_SMSC_PHY=y ”这一行,
如图 37.4.4.4 所示:

同样的,使用“ make imx_alientek_emmc_defconfig ”重新配置 Linux 内核的时候, LAN8720A
的驱动就会使能,并被编译进 Linux 镜像文件 zImage 中。

6.总结

①、在 Linux 内核中查找可以参考的板子,一般都是半导体厂商自己做的开发板。
②、编译出参考板子对应的 zImage .dtb 文件。
③、使用参考板子的 zImage 文件和 .dtb 文件在我们所使用的板子上启动 Linux 内核,看能
否启动。
④、如果能启动的话就万事大吉,如果不能启动那就悲剧了,需要调试 Linux 内核。不过
一般都会参考半导体官方的开发板设计自己的硬件,所以大部分情况下都会启动起来。启动
Linux 内核用到的外设不多,一般就 DRAM(Uboot 都初始化好的 ) 和串口。作为终端使用的串口
一般都会参考半导体厂商的 Demo 板。
⑤、修改相应的驱动,像 NAND Flash EMMC SD 卡等驱动官方的 Linux 内核都是已经
提供好了,基本不会出问题。重点是网络驱动,因为 Linux 驱动开发一般都要通过网络调试代
码,所以一定要确保网络驱动工作正常。如果是处理器内部 MAC+ 外部 PHY 这种网络方案的
话,一般网络驱动都很好处理,因为在 Linux 内核中是有外部 PHY 通用驱动的。只要设置好复
位引脚、 PHY 地址信息基本上都可以驱动起来。
⑥、 Linux 内核启动以后需要根文件系统,如果没有根文件系统的话肯定会崩溃,所以确定 Linux
内核移植成功以后就要开始根文件系统的构建。
;