学习正点原子《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》个人笔记
常用操作&相关知识
压缩解压
解压.tar.bz2 tar -vxjf linux-imx-4.1.15-2.1.0-g8a006db.tar.bz2 压缩.tar.bz2 tar -vcjf alientek_uboot.tar.bz2 alientek_uboot
编译
将前面编译出来的 led.o 文件链接到 0X87800000 这个地址,使用如下命令: arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf 上述命令中-Ttext 就是指定链接地址,“-o”选项指定链接生成的 elf 文件名,这里我们命名为 led.elf。上述命令执行完以后就会在工程目录下多一个 led.elf 文件
led.elf 文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin 文件,因此还需要将 led.elf 文件转换为.bin 文件,这里我们就需要用到 arm-linux-gnueabihf-objcopy 这个工具了。
arm-linux-gnueabihf-objcopy 更像一个格式转换工具,我们需要用它将 led.elf 文件转换为led.bin 文件,命令如下: arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin 上述命令中,“-O”选项指定以什么格式输出,后面的“binary”表示以二进制格式输出,选项“-S”表示不要复制源文件中的重定位信息和符号信息,“-g”表示不复制源文件中的调试信息。
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,比如如下命令: arm-linux-gnueabihf-objdump -D led.elf > led.dis 上述代码中的“-D”选项表示反汇编所有的段,反汇编完成以后就会在当前目录下出现一个名为 led.dis 文件 可以打开 led.dis 文件看一下,看看是不是汇编代码 从图 可以看出 led.dis 里面是汇编代码,而且还可以看到内存分配情况。在0X87800000 处就是全局标号_start,也就是程序开始的地方。通过 led.dis 这个反汇编文件可以明显的看出我们的代码已经链接到了以 0X87800000 为起始地址的区域。
makefile
objs := start.o main.o
ledc.bin:$(objs)
arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
%.o:%.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis
第 1 行定义了一个变量 objs, objs 包含着要生成 ledc.bin 所需的材料: start.o 和 main.o,也就是当前工程下的 start.s 和 main.c 这两个文件编译后的.o 文件。这里要注意 start.o 一定要放到最前面!因为在后面链接的时候 start.o 要在最前面,因为 start.o 是最先要执行的文件! 第 3 行就是默认目标,目的是生成最终的可执行文件 ledc.bin, ledc.bin 依赖 start.o 和 main.o如果当前工程没有 start.o 和 main.o 的时候就会找到相应的规则去生成 start.o 和 main.o。比如start.o 是 start.s 文件编译生成的,因此会执行第 8 行的规则。 第 4 行是使用 arm-linux-gnueabihf-ld 进行链接,链接起始地址是 0X87800000,但是这一行用到了自动变量“$^”,“$^”的意思是所有依赖文件的集合,在这里就是 objs 这个变量的值:start.o 和 main.o。链接的时候 start.o 要链接到最前面,因为第一行代码就是 start.o 里面的,因此这一行就相当于: arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf start.o main.o 第 5 行使用 arm-linux-gnueabihf-objcopy 来将 ledc.elf 文件转为 ledc.bin,本行也用到了自动变量“$@”,“$@”的意思是目标集合,在这里就是“ledc.bin”,那么本行就相当于: arm-linux-gnueabihf-objcopy -O binary -S ledc.elf ledc.bin 第 6 行使用 arm-linux-gnueabihf-objdump 来反汇编,生成 ledc.dis 文件。 第 8~15 行就是针对不同的文件类型将其编译成对应的.o 文件,其实就是汇编.s(.S)和.c 文件,比如 start.s 就会使用第 8 行的规则来生成对应的 start.o 文件。第 9 行就是具体的命令,这行也用到了自动变量“$@”和“$<”,其中“$<”的意思是依赖目标集合的第一个文件。比如start.s 要编译成 start.o 的话第 8 行和第 9 行就相当于:
start.o:start.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s
第 17 行就是工程清理规则,通过命令“make clean”就可以清理工程。
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第 1 行, KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。 第 2 行, CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。 第 3 行, obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。 第 8 行,具体的编译命令,后面的 modules 表示编译模块, -C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。 M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。
Makefile 编写好以后输入“make”命令编译驱动模块
链接脚本
SECTIONS{
. = 0X10000000;
.text : {*(.text)}
. = 0X30000000;
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) }
}
第 1 行我们先写了一个关键字“SECTIONS”,后面跟了一个大括号,这个大括号和第 7 行的大括号是一对,这是必须的。看起来就跟 C 语言里面的函数一样。 第 2 行对一个特殊符号“.”进行赋值,“.”在链接脚本里面叫做定位计数器,默认的定位计数器为 0。我们要求代码链接到以0X10000000 为起始地址的地方,因此这一行给“.”赋值0X10000000,表示以 0X10000000 开始,后面的文件或者段都会以0X10000000 为起始地址开始链接。 第 3 行的“.text”是段名,后面的冒号是语法要求,冒号后面的大括号里面可以填上要链接到“.text”这个段里面的所有文件,“*(.text)”中的“*”是通配符,表示所有输入文件的.text段都放到“.text”中。 第 4 行,我们的要求是数据放到 0X30000000 开始的地方,所以我们需要重新设置定位计数器“.”,将其改为 0X30000000。如果不重新设置的话会怎么样?假设“.text”段大小为 0X10000,那么接下来的.data 段开始地址就是0X10000000+0X10000=0X10010000,这明显不符合我们的要求。所以我们必须调整定位计数器为 0X30000000。 第 5 行跟第 3 行一样,定义了一个名为“.data”的段,然后所有文件的“.data”段都放到这里面。但是这一行多了一个“ALIGN(4)”,这是什么意思呢?这是用来对“.data”这个段的起始地址做字节对齐的, ALIGN(4)表示 4 字节对齐。也就是说段“.data”的起始地址要能被 4 整除,一般常见的都是 ALIGN(4)或者 ALIGN(8),也就是 4 字节或者 8 字节对齐。 第 6 行定义了一个“.bss”段,所有文件中的“.bss”数据都会被放到这个里面,“.bss”数据就是那些定义了但是没有被初始化的变量。
上面就是链接脚本最基本的语法格式,我们接下来就按照这个基本的语法格式来编写我们本试 验的链接脚本,我们本试验的链接脚本要求如下: ①、链接起始地址为 0X87800000。 ②、 start.o 要被链接到最开始的地方,因为 start.o 里面包含这第一个要执行的命令。 根据要求,在 Makefile 同目录下新建一个名为“imx6ul.lds”的文件,然后在此文件里面输入如下所示代码:
SECTIONS{
. = 0X87800000;
.text :
{
start.o
main.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
第 2 行设置定位计数器为0X87800000,因为我们的链接地址就是0X87800000。 第5行设置链接到开始位置的文件为start.o,因为 start.o 里面包含着第一个要执行的指令,所以一定要链接到最开始的地方。 第 6 行是 main.o这个文件,其实可以不用写出来,因为 main.o 的位置就无所谓了,可以由编译器自行决定链接位置。 在第 11、 13 行有“__bss_start”和“__bss_end”这两个东西?这个是什么呢?“__bss_start”和“__bss_end”是符号,第 11、 13 这两行其实就是对这两个符号进行赋值,其值为定位符“.”,这两个符号用来保存.bss 段的起始地址和结束地址。前面说了.bss 段是定义了但是没有被初始化的变量,我们需要手动对.bss 段的变量清零的,因此我们需要知道.bss 段的起始和结束地址,这样我们直接对这段内存赋 0 即可完成清零。通过第 11、 13 行代码, .bss 段的起始地址和结束地址就保存在了“__bss_start”和“__bss_end”中,我们就可以直接在汇编或者 C 文件里面使用这两个符号。
arm-linux-gnueabihf-ld -Ttext 0X87800000 -o ledc.elf $^ 改为: arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^ 其实就是将-T 后面的 0X87800000 改为 imx6ul.lds,表示使用 imx6ul.lds 这个链接脚本文件。
内核目录
linux内核编译过程
make xxx_defconfig命令配置 Linux 的时候如下两行命令会执行脚本scripts/Makefile.build:
@make -f ./scripts/Makefile.build obj=scripts/basic
@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
%_defconfig: scripts/kconfig/conf
@ scripts/kconfig/conf --defconfig=arch/arm/configs/%_defconfig Kconfig
%_defconfig依赖scripts/kconfig/conf,所以会编译scripts/kconfig/conf.c生成conf这个软件。 此软件就会将%_defconfig 中的配置输出到.config 文件中,最终生成 Linux kernel 根目录下的.config 文件。
使用命令“make xxx_defconfig”配置好 Linux 内核以后就可以使用“make”或者“make all”命令进行编译。顶层 Makefile 有如下代码:
125 PHONY := _all
126 _all:
......
192 PHONY += all
193 ifeq ($(KBUILD_EXTMOD),)
194 _all: all
195 else
196 _all: modules
197 endif
......
608 all: vmlinux
第 126 行, _all 是默认目标,如果使用命令“make”编译 Linux 的话此目标就会被匹配。 第 193 行,如果 KBUILD_EXTMOD 为空的话 194 行的代码成立。 第 194 行,默认目标_all 依赖 all。 第 608 行,目标 all 依赖 vmlinux,所以接下来的重点就是 vmlinux!
要分析 Linux 启动流程,同样需要先编译一下 Linux 源码,因为有很多文件是需要编译才会生成的。首先分析 Linux 内核的连接脚本文件 arch/arm/kernel/vmlinux.lds,通过链接脚本可以找到 Linux 内核的第一行程序是从哪里执行的。 vmlinux.lds 中有如下代码:
492 OUTPUT_ARCH(arm)
493 ENTRY(stext)
494 jiffies = jiffies_64;
第 493 行的 ENTRY 指明了了 Linux 内核入口,入口为 stext, stext 定义在文件arch/arm/kernel/head.S 中 , 因 此 要 分 析 Linux 内 核 的 启 动 流 程 , 就 得 先 从 文 件arch/arm/kernel/head.S 的 stext 处开始分析。
linux内核移植步骤
1、设置目标架构和交叉编译器
同 uboot一样, Linux编译的时候需要设置目标板架构ARCH 和交叉编译器 CROSS_COMPILE,在顶层 Makefile 中代码如下:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
为了方便,一般直接修改顶层 Makefile 中的 ARCH 和 CROSS_COMPILE,直接将其设置为对应的架构和编译器,比如本教程将 ARCH 设置为为 arm, CROSS_COMPILE 设置为 armlinux-gnueabihf-,如下所示:
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
设置好以后我们就可以使用如下命令编译 Linux 了:
make xxx_defconfig //使用默认配置文件配置 Linux
make menuconfig //启动图形化配置界面
make -j16 //编译 Linux
内核启动测试
在上一小节我们已经得到了 NXP 官方 I.MX6ULL EVK 开发板对应的 zImage 和 imx6ull-14x14-evk.dtb 这两个文件。这两个文件能不能在正点原子的 I.MX6U-ALPHA EMMC 版开发板上启动呢?测试一下不就知道了,在测试之前确保 uboot 中的环境变量 bootargs 内容如下: console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw 将上一小节编译出来的 zImage 和 imx6ull-14x14-evk.dtb 复制到 Ubuntu 中的 tftp 目录下,因为我们要在 uboot 中使用 tftp 命令将其下载到开发板中,拷贝命令如下:
cp arch/arm/boot/zImage /home/zuozhongkai/linux/tftpboot/ -f
cp arch/arm/boot/dts/imx6ull-14x14-evk.dtb /home/zuozhongkai/linux/tftpboot/ -f
拷贝完成以后就可以测试了,启动开发板,进入 uboot 命令行模式,然后输入如下命令将zImage 和 imx6ull-14x14-evk.dtb 下载到开发板中并启动:
tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000
Linux 内核启动以后是需要根文件系统的,根文件系统存在哪里是由 uboot 的 bootargs 环境变量指定 , bootargs 会传递给 Linux 内核作为命令行参数 。 比如上面设置root=/dev/mmcblk1p2,也就是说根文件系统存储在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2中。这是因为正点原子的 EMMC 版本开发板出厂的时候已经 EMMC 的分区 2 中烧写好了根文件系统,所以设置 root=/dev/mmcblk1p2。
在 Linux 中添加自己的开发板
通过编译 NXP 官方 I.MX6ULL EVK 开发板对应的 Linux 内核,发现其可以在正点原子的 EMMC 版本开发板启动,所以我们就参考 I.MX6ULL EVK 开发板的设置,在 Linux 内核中添加正点原子的 I.MX6U-ALPHA 开发板。
添加开发板默认配置文件
将 arch/arm/configs 目 录 下 的 imx_v7_mfg_defconfig 重 新 复 制 一 份 , 命 名 为imx_alientek_emmc_defconfig,命令如下:
cd arch/arm/configs
cp imx_v7_mfg_defconfig imx_alientek_emmc_defconfig
以后 imx_alientek_emmc_defconfig 就是正点原子的 EMMC 版开发板默认配置文件了。 以后就可以使用如下命令来配置正点原子 EMMC 版开发板对应的 Linux 内核了: make imx_alientek_emmc_defconfig
添加开发板对应的设备树文件
添加适合正点原子 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” 这样编译 Linux 的时候就可以从 imx6ull-alientekemmc.dts 编译出 imx6ull-alientek-emmc.dtb 文件了。
编译测试
可以创建一个编译脚本 ,imx6ull_alientek_emmc.sh,脚本内容如下:
1 #!/bin/sh
2 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihfimx_alientek_emmc_defconfig
4 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
5 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
第 2 行,清理工程。 第 3 行,使用默认配置文件 imx_alientek_emmc_defconfig 来配置 Linux 内核。 第 4 行,打开 Linux 的图形配置界面,如果不需要每次都打开图形配置界面可以删除此行。 第 5 行,编译 Linux。
执行 shell 脚本 imx6ull_alientek_emmc.sh 编译 Linux 内核, 命令如下:
chmod 777 imx6ull_alientek_emmc.sh //给予可执行权限
./imx6ull_alientek_emmc.sh //执行 shell 脚本编译内核
编译完成以后就会在目录 arch/arm/boot 下生成 zImage 镜像文件。在 arch/arm/boot/dts 目录下生成 imx6ull-alientek-emmc.dtb 文件。
CPU 主频修改
执行cat /proc/cpuinfo 有 BogoMIPS 这一条,此时 BogoMIPS 为 3.00, BogoMIPS 是 Linux 系统中衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高, BogoMIPS 值就越大。BogoMIPS 只是粗略的计算 CPU 性能,并不十分准确。但是我们可以通过 BogoMIPS 值来大致的判断当前处理器的性能。在图中并没有看到当前 CPU 的工作频率,那我们就转变另一种方法查看当前 CPU 的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件 此目录中记录了 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,工作频率很低!其他的值如下:
cpuinfo_cur_freq = 198000
cpuinfo_max_freq = 792000
cpuinfo_min_freq = 198000
scaling_cur_freq = 198000
scaling_max_freq = 792000
cat scaling_min_freq = 198000
scaling_available_frequencies = 198000 396000 528000 792000
cat scaling_governor = ondemand
可以看出,当前 CPU 支持 198MHz、 396MHz、 528Mhz 和 792000 四种频率切换,其中调频策略为 ondemand,也就是定期检查负载,然后根据负载情况调节 CPU 频率。因为当前我们开发板并没有做什么工作,因此 CPU 频率降低为 198MHz 以省电。如果开发板做一些高负载的工作,比如播放视频等操作那么 CPU 频率就会提升上去。 查看 stats 目录下的 time_in_state 文件可以看到 CPU 在各频率下的工作时间,命令如下: cat /sys/bus/cpu/devices/cpu0/cpufreq/stats/time_in_state 从图中可以看出, CPU 在 198MHz、 396MHz、 528MHz 和 792MHz 都工作过,其中 198MHz 的工作时间最长!假如我们想让 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
结果下所示:
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
45 CONFIG_CPU_FREQ_GOV_ONDEMAND=y
修改完成以后重新编译 Linux 内核,编译之前先清理一下工程!因为我们重新修改过默认配置文件了 , 编译完成以后使用新的 zImage镜像文件重新 启动 Linux 。 再 次 查 看/sys/devices/system/cpu/cpu0/cpufreq/ cpuinfo_cur_freq 文件的值,如图 可以看出,当前 CPU 频率为 792MHz 了。查看 scaling_governor 文件看一下当前的调频策略,如图 可以看出当前的 CPU 调频策略为 preformance,也就是高性能模式,一直以最高主频运行。 我们再来看一下如何通过图形化界面配置 Linux 内核的 CPU 调频策略,输入“ make menuconfig”打开 Linux 内核的图形化配置界面,进入如下路径:
CPU Power Management
-> CPU Frequency scaling
-> Default CPUFreq governor
打开默认调频策略选择界面,选择“performance”。选择以后退出图形化配置界面,然后编译 Linux内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。编译完成以后使用新的zImage 重启 Linux,查看当前 CPU 的工作频率和调频策略。 我们学习的时候为了高性能,大家可以使用 performance 模式。但是在以后的实际产品开发中,从省电的角度考虑,建议大家使用 ondemand 模式,一来可以省电,二来可以减少发热。
超频&修改频率列表
超频设置其实很简单,修改一下设备树文件 arch/arm/boot/dts/imx6ull.dtsi 即可,打开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 >;
上面代码就是设置 CPU 频率的,第 61-65 行和第 69-73 行就是 I.MX6ULL 所支持的频率,单位为 KHz,可以看出 I.MX6ULL(视具体型号而定)支持 996MHz、 792MHz、 528MHz、396MHz 和 198MHz。在上一小节中,我们知道 Linux 内核默认支持 198MHz、 396MHz、 528MHz和 792MHz, 如果是 MCIMX6Y2CVM05AB 这颗芯片的话,默认最高只能运行在 528MHz, 我 们在示例代码中加入针对 696MHz 的支持,修改以后代码如下:
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 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 >;
修改好以后保存,并且编译设备树,在 Linux 内核源码根目录下输入如下命令编译设备树: make dtbs 命令“make dtbs”只编译设备树文件,也就是将.dts 编译为.dtb,编译完成以后使用新的设备 树 文 件 imx6ull-alientek_emmc.dtb 启 动 Linux 。 重 启 以 后 查 看 文 件/sys/devices/system/cpu/cpu0/cpufreq/ scaling_available_frequencies 的内容,如图
使能8线EMMC驱动
正点原子 EMMC 版本核心板上的 EMMC 采用的 8 位数据线 Linux 内核驱动里面 EMMC 默认是 4 线模式的, 4 线模式肯定没有 8 线模式的速度快,所以本节我们将 EMMC 的驱动修改为 8 线模式。修改方法很简单,直接修改设备树即可,打开文件 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 系统即可。
修改网络驱动
因为在后面学习 Linux 驱动开发的时候要用到网络调试驱动,所以必须要把网络驱动调试好。在讲解 uboot 移植的时候就已经说过了,正点原子开发板的网络和 NXP 官方的网络硬件上不同,网络 PHY 芯片由 KSZ8081 换为了 LAN8720A,两个网络 PHY 芯片的复位 IO 也不同。所以 Linux 内核自带的网络驱动是驱动不起来 I.MX6U-ALPHA 开发板上的网络的,需要做修改。
1、修改 LAN8720 的复位以及网络时钟引脚驱动
ENET1 复位引脚 ENET1_RST 连接在 I.M6ULL 的 SNVS_TAMPER7 这个引脚上。 ENET2的复位引脚 ENET2_RST 连接在 I.MX6ULL 的 SNVS_TAMPER8 上。打开设备树文件 imx6ullalientek-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-alientekemmc.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 地址
在 uboot 移植章节中,我们说过 ENET1 的 LAN8720A 地址为 0x0, ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alientek-emmc.dts 中找到如下代码:
171 &fec1 {
172 pinctrl-names = "default";
......
175 phy-handle = <ðphy0>;
176 status = "okay";
177 };
178
179 &fec2 {
180 pinctrl-names = "default";
......
183 phy-handle = <ðphy1>;
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 = <ðphy0>;
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 = <ðphy1>;
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
图中选择将“Drivers for SMSC PHYs”编译到 Linux 内核中,因此“<>”里面变为了“*”。 LAN8720A 是 SMSC 公司出品的,因此勾选这个以后就会编译 LAN8720 驱动,配置好以后退出配置界面,然后重新编译一下 Linux 内核。
5、修改 smsc.c 文件
在修改 smsc.c 文件之前先说点题外话,那就是我是怎么确定要修改 smsc.c 这个文件的。在写本书之前我并没有修改过 smsc.c 这个文件,都是使能 LAN8720A 驱动以后就直接使用。但是我在测试 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函数内容如下所示:
6、网络驱动测试
修改好设备树和 Linux 内核以后重新编译一下,得到新的 zImage 镜像文件和 imx6ullalientek-emmc.dtb 设备树文件,使用网线将 I.MX6U-ALPHA 开发板的两个网口与路由器或者电脑连接起来,最后使用新的文件启动 Linux 内核。启动以后使用“ifconfig”命令查看一下当前活动的网卡有哪些,结果如图 37.4.3.2 所示: 从图 37.4.3.2 可以看出,当前没有活动的网卡。输入命令“ifconfig -a”来查看一下开发板中存在的所有网卡,结果如图 37.4.3.3 所示:
|