3.cpu主频和网络驱动修改
1 CPU 主频修改(无需修改)跳过此节即可
正点原子 I.MX6U-ALPHA 开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的,也就是NXP 官方宣传的 800MHz 版本。后续可能会生产 528MHz 核心板供企业级批量用户,但是开发板搭配的都是 792MHz 主频的。
1、设置 I.MX6U-ALPHA 开发板工作在 792MHz
确保 EMMC 中的根文件系统可用!然后重新启动开发板,进入终端(可以输入命令),如图 所示:
进入图所示的命令行以后输入如下命令查看 cpu 信息:
cat /proc/cpuinfo
processor : 0 model name : ARMv7 Processor rev 5 (v7l) BogoMIPS : 12.00 Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xc07 CPU revision : 5 Hardware : Freescale i.MX6 Ultralite (Device Tree) Revision : 0000 Serial : 0000000000000000
在图中有 BogoMIPS 这一条,此时 BogoMIPS 为 12.00, BogoMIPS 是 Linux 系统中衡量处理器运行速度的一个“尺子”,处理器性能越强,主频越高, BogoMIPS 值就越大。BogoMIPS 只是粗略的计算 CPU 性能,并不十分准确。但是我们可以通过 BogoMIPS 值来大致的判断当前处理器的性能。在图中并没有看到当前 CPU 的工作频率,那我们就转变另一种方法查看当前 CPU 的工作频率。进入到目录/sys/bus/cpu/devices/cpu0/cpufreq 中,此目录下会有很多文件,如图所示:
cd /sys/bus/cpu/devices/cpu0/cpufreq
ls
此目录中记录了 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 频率为 792MHz,其他的值如下:
cpuinfo_cur_freq = 792000 cpuinfo_max_freq = 792000 cpuinfo_min_freq = 198000 scaling_cur_freq = 792000 scaling_max_freq = 792000 cat scaling_min_freq = 198000 scaling_available_frequencies = 198000 396000 528000 792000 cat scaling_governor = conservative ondemand userspace powersave performance
可以看出,当前 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 在 198MHz、 396MHz、 528MHz 都没有工作过,其中 ,始终工作在792MHz!假如我们想让 CPU 一直工作在 792MHz 那该怎么办?很简单,配置 Linux 内核,将调频策略选择为 performance。或者修改 imx_alientek_emmc_defconfig 文件,此文件中有下面几行:
cd arch/arm/configs/ vi imx_alientek_emmc_defconfig
42 CONFIG_CPU_FREQ=y 43 CONFIG_CPU_FREQ_GOV_POWERSAVE=y 44 CONFIG_CPU_FREQ_GOV_USERSPACE=y 45 CONFIG_CPU_FREQ_GOV_ONDEMAND=y 46 CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y 47 CONFIG_ARM_IMX6Q_CPUFREQ=y 48 CONFIG_CPU_IDLE=y
第 41 行,配置 ondemand 为默认调频策略。
第 42 行,使能 powersave 策略。
第 43 行,使能 userspace 策略。
第 44 行,使能 interactive 策略。
performance:CPU将始终以其支持的最高运行频率运行。
powersave:CPU将始终以其支持的最低运行频率运行。
userspace:允许用户空间应用程序通过相应的接口调节CPU运行频率。
ondemand:根据系统负载自动调整CPU运行频率。
conservative:类似于ondemand,但调整CPU运行频率时更加保守。
将示例代码 中的第 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 调频策略,输入“ makemenuconfig”打开 Linux 内核的图形化配置界面,如图 所示:
进入如下路径:
CPU Power Management -> CPU Frequency scaling -> Default CPUFreq governor
打开默认调频策略选择界面,选择“performance”,如图所示:
在图 中选择“‘performance’ governor”即可,选择以后退出图形化配置界面,然后编译 Linux内核,一定不要清理工程!否则的话我们刚刚的设置就会被清理掉。编译完成以后使用新的zImage 重启 Linux,查看当前 CPU 的工作频率和调频策略。我们学习的时候为了高性能,大家可以使用 performance 模式。但是在以后的实际产品开发中,从省电的角度考虑,建议大家使用 ondemand 模式,一来可以省电,二来可以减少发热。
2、超频至 700MHz
I.MX6ULL 有多种型号,按照工作频率可以分为 528MHz、 700Mhz(实际 696MHz),800MHz(实际 792MHz)和 900MHz(实际频率未知,应该在 900MHz 左右)。 有些朋友可能用的其他品牌的开发板,其所使用的 I.MX6ULL 主频可能是 528MHz 的,虽然芯片标称是 528MHz 主频,但是其是可以超频的 700MHz 的(这里的 700MHz 实际上只有 696MHz,但是 NXP 官方宣传其为 700MHz,所以我们就统一称为 700MHz 吧)。
对于所用的芯片为 528MHz 主频但是想体验一下高性能的朋友体验一下超频,笔者测试过528MHz 超频到 700MHz,还没有出现过超频不稳定的现象发生,但是!毕竟是超频了的,肯定没有工作在 528MHz 稳定。
超频设置其实很简单,修改一下设备树文件 arch/arm/boot/dts/imx6ull.dtsi 即可,打开imx6ull.dtsi,找到下面代码:
50 cpus { 51 #address-cells = <1>; 52 #size-cells = <0>; 53 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 >; 75 fsl,low-power-run; 76 clocks = <&clks IMX6UL_CLK_ARM>, 77 <&clks IMX6UL_CLK_PLL2_BUS>, 78 <&clks IMX6UL_CLK_PLL2_PFD2>, 79 <&clks IMX6UL_CA7_SECONDARY_SEL>, 80 <&clks IMX6UL_CLK_STEP>, 81 <&clks IMX6UL_CLK_PLL1_SW>, 82 <&clks IMX6UL_CLK_PLL1_SYS>, 83 <&clks IMX6UL_PLL1_BYPASS>, 84 <&clks IMX6UL_CLK_PLL1>, 85 <&clks IMX6UL_PLL1_BYPASS_SRC>, 86 <&clks IMX6UL_CLK_OSC>; 87 clock-names = "arm", "pll2_bus", "pll2_pfd2_396m", "secondary_sel", "step", 88 "pll1_sw", "pll1_sys", "pll1_bypass", "pll1", "pll1_bypass_src", "osc"; 89 }; 90 };
示例代码就是设置 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 >;
第 63 行,加入了“696000 1225000”,这个就是 696MHz 的支持。
第 72 行,加入了“696000 1175000”,也是对 696MHz 的支持。
修改好以后保存,并且编译设备树,在 Linux 内核源码根目录下输入如下命令编译设备树:
make dtbs
命令“make dtbs”只编译设备树文件,也就是将.dts 编译为.dtb,编译完成以后使用新的设备 树 文 件 imx6ull-alientek_emmc.dtb 启 动 Linux 。 重 启 以 后 查 看 文 件/sys/devices/system/cpu/cpu0/cpufreq/ scaling_available_frequencies 的内容,如图 所示:
从图 37.4.1.11 可以看出,此时支持了 696MHz。如果设置调频策略为 performance,那么处理器就会一直工作在 696MHz。可以对比一下工作在 528MHz 和 696MHz 下的 BogoMIPS 的值,528MHz 主频下的 BogoMIPS 值如图所示:
696MHz 主频下的 BogoMIPS 值如图 所示:
从两张图中可以看到, 528MHz 和 696MHz 下的 BogoMIPS 值分别为8.00 和 10.54,相当于性能提升了(10.54/8)-1=31.75%。
2 使能 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 系统即可。
3 修改网络驱动
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>; 130 status = "okay"; 131 gpio-sck = <&gpio5 11 0>; 132 gpio-mosi = <&gpio5 10 0>; 133 cs-gpios = <&gpio5 7 0>; 134 num-chipselects = <1>; 135 #address-cells = <1>; 136 #size-cells = <0>; 137 138 gpio_spi: gpio_spi@0 { 139 compatible = "fairchild,74hc595"; 140 gpio-controller; 141 #gpio-cells = <2>; 142 reg = <0>; 143 registers-number = <1>; 144 registers-default = /bits/ 8 <0x57>; 145 spi-max-frequency = <100000>; 146 }; 147 };
第 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 的什么功能引脚,因此将示例代码 中的第 129 行和第 133 行处的代码删除掉!!否则会干扰到网络复位引脚!
125 spi4 { 126 compatible = "spi-gpio"; 127 pinctrl-names = "default"; 128 pinctrl-0 = <&pinctrl_spi4>; 129 /* pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; */ 130 status = "okay"; 131 gpio-sck = <&gpio5 11 0>; 132 gpio-mosi = <&gpio5 10 0>; 133 /* cs-gpios = <&gpio5 7 0>;*/ 134 num-chipselects = <1>; 135 #address-cells = <1>; 136 #size-cells = <0>; 137 138 gpio_spi: gpio_spi@0 { 139 compatible = "fairchild,74hc595"; 140 gpio-controller; 141 #gpio-cells = <2>; 142 reg = <0>; 143 registers-number = <1>; 144 registers-default = /bits/ 8 <0x57>; 145 spi-max-frequency = <100000>; 146 }; 147 };
在 imx6ull-alientek-emmc.dts 里面找到名为“iomuxc_snvs”的节点(就是直接搜索),然后在此节点下添加网络复位引脚信息,添加完成以后的“iomuxc_snvs”的节点内容如下:
原
修改后
559 &iomuxc_snvs { 560 pinctrl-names = "default_snvs"; 561 pinctrl-0 = <&pinctrl_hog_2>; 562 imx6ul-evk { 563 pinctrl_hog_2: hoggrp-2 { 564 fsl,pins = < 565 MX6ULL_PAD_SNVS_TAMPER0__GPIO5_IO00 0x80000000 566 >; 567 }; 568 569 pinctrl_dvfs: dvfsgrp { 570 fsl,pins = < 571 MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x79 572 >; 573 }; 574 575 pinctrl_lcdif_reset: lcdifresetgrp { 576 fsl,pins = < 577 /* used for lcd reset */ 578 MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x79 579 >; 580 }; 581 582 pinctrl_spi4: spi4grp { 583 fsl,pins = < 584 MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1 585 MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1 586 >; 587 }; 588 589 pinctrl_sai2_hp_det_b: sai2_hp_det_grp { 590 fsl,pins = < 591 MX6ULL_PAD_SNVS_TAMPER4__GPIO5_IO04 0x17059 592 >; 593 }; 594 /*enet1 reset paranoid paranoid*/ 595 pinctrl_enet1_reset: enet1resetgrp { 596 fsl,pins = < 597 /* used for enet1 reset */ 598 MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x10B0 599 >; 600 }; 601 602 /*enet2 reset paranoid paranoid*/ 603 pinctrl_enet2_reset: enet2resetgrp { 604 fsl,pins = < 605 /* used for enet2 reset */ 606 MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x10B0 607 >; 608 }; 609 }; 610 }; };
第 1 行,imx6ull-alientek-emmc.dts 文件中 iomuxc_snvs 节点。
第 36~42 行,ENET1 网络复位引脚配置信息。
第 44~50 行,ENET2 网络复位引脚配置信息。
最后还需要修改一下 ENET1 和 ENET2 的网络时钟引脚配置,继续在 imx6ull-alientek-emmc.dts 中找到如下所示代码:
307 pinctrl_enet1: enet1grp { 308 fsl,pins = < 309 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 310 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 311 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0 312 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0 313 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0 314 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0 315 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0 316 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031 317 >; 318 }; 319 320 pinctrl_enet2: enet2grp { 321 fsl,pins = < 322 MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 323 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 324 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 325 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 326 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 327 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 328 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 329 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 330 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 331 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 332 >;
333 };
第 318 和 333 行, 分别为 ENET1 和 ENET2 的网络时钟引脚配置信息,将这两个引脚的电气属性值改为 0x4001b009,原来默认值为 0x4001b031。
307 pinctrl_enet1: enet1grp { 308 fsl,pins = < 309 MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 310 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 311 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0 312 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0 313 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0 314 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0 315 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0 316 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b009 317 >; 318 }; 319 320 pinctrl_enet2: enet2grp { 321 fsl,pins = < 322 MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 323 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 324 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 325 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 326 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 327 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 328 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 329 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 330 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 331 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b009 332 >; 333 };
修改完成以后记得保存一下 imx6ull-alientek-emmc.dts,网络复位以及时钟引脚驱动就修改好了。
2、修改 fec1 和 fec2 节点的 pinctrl-0 属性
在 imx6ull-alientek-emmc.dts 文件中找到名为“fec1”和“fec2”的这两个节点,修改其中的“pinctrl-0”属性值,修改以后如下所示:
修改前
169 &fec1 { 170 pinctrl-names = "default"; 171 pinctrl-0 = <&pinctrl_enet1>; 172 phy-mode = "rmii"; 173 phy-handle = <ðphy0>; 174 status = "okay"; 175 }; 176 177 &fec2 { 178 pinctrl-names = "default"; 179 pinctrl-0 = <&pinctrl_enet2>; 180 phy-mode = "rmii"; 181 phy-handle = <ðphy1>; 182 status = "okay"; 183 184 mdio { 185 #address-cells = <1>; 186 #size-cells = <0>; 187 188 ethphy0: ethernet-phy@2 { 189 compatible = "ethernet-phy-ieee802.3-c22"; 190 reg = <2>; 191 }; 192 193 ethphy1: ethernet-phy@1 { 194 compatible = "ethernet-phy-ieee802.3-c22"; 195 reg = <1>; 196 }; 197 }; 198 };
修改后
169 &fec1 { 170 pinctrl-names = "default"; 171 pinctrl-0 = <&pinctrl_enet1 172 &pinctrl_enet1_reset>; 173 phy-mode = "rmii"; 174 phy-handle = <ðphy0>; 175 status = "okay"; 176 }; 177 178 &fec2 { 179 pinctrl-names = "default"; 180 pinctrl-0 = <&pinctrl_enet2 181 &pinctrl_enet1_reset>; 182 phy-mode = "rmii"; 183 phy-handle = <ðphy1>; 184 status = "okay"; 185 186 mdio { 187 #address-cells = <1>; 188 #size-cells = <0>; 189 190 ethphy0: ethernet-phy@2 { 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 };
第 3行,修改后的 fec1 节点“pinctrl-0”属性值。第 12 行,修改后的 fec2 节点“pinctrl-0”属性值。
3、修改 LAN8720A 的 PHY 地址
在 uboot 移植章节中,我们说过 ENET1 的 LAN8720A 地址为 0x0, ENET2 的 LAN8720A地址为 0x1。在 imx6ull-alientek-emmc.dts 中找到如下代码:
169 &fec1 { 170 pinctrl-names = "default"; 171 pinctrl-0 = <&pinctrl_enet1 172 &pinctrl_enet1_reset>; 173 phy-mode = "rmii"; 174 phy-handle = <ðphy0>; 175 status = "okay"; 176 }; 177 178 &fec2 { 179 pinctrl-names = "default"; 180 pinctrl-0 = <&pinctrl_enet2 181 &pinctrl_enet1_reset>; 182 phy-mode = "rmii"; 183 phy-handle = <ðphy1>; 184 status = "okay"; 185 186 mdio { 187 #address-cells = <1>; 188 #size-cells = <0>; 189 190 ethphy0: ethernet-phy@2 { 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 };
第 169~177 行, ENET1 对应的设备树节点。第 178~200 行, ENET2 对应的设备树节点。但是第 186~198 行的 mdio 节点描述了 ENET1和 ENET2 的 PHY 地址信息。将示例代码 改为如下内容:
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_enet1_reset>; 186 phy-mode = "rmii"; 187 phy-handle = <ðphy1>; 188 status = "okay"; 189 190 mdio { 191 #address-cells = <1>; 192 #size-cells = <0>; 193 194 ethphy0: ethernet-phy@0 { 195 compatible = "ethernet-phy-ieee802.3-c22"; 196 reg = <0>; 197 }; 198 199 ethphy1: ethernet-phy@1 { 200 compatible = "ethernet-phy-ieee802.3-c22"; 201 reg = <1>; 202 }; 203 }; 204 };
第 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”命令重新编译一下设备树。
4、修改 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 fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs); 3453 3454 /* Init network device */ 3455 ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private), 3456 num_tx_qs, num_rx_qs); 3457 if (!ndev) 3458 return -ENOMEM; 3459 3460 SET_NETDEV_DEV(ndev, &pdev->dev); 3461 3462 /* setup board info structure */ 3463 fep = netdev_priv(ndev); 3464 3465 of_id = of_match_device(fec_dt_ids, &pdev->dev); 3466 if (of_id) 3467 pdev->id_entry = of_id->data; 3468 fep->quirks = pdev->id_entry->driver_data; 3469 3470 fep->netdev = ndev; 3471 fep->num_rx_queues = num_rx_qs; 。。。。。
修改后
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 fec_enet_get_queue_num(pdev, &num_tx_qs, &num_rx_qs); 3464 3465 /* Init network device */ 3466 ndev = alloc_etherdev_mqs(sizeof(struct fec_enet_private), 3467 num_tx_qs, num_rx_qs); 3468 if (!ndev) 3469 return -ENOMEM; 3470 3471 SET_NETDEV_DEV(ndev, &pdev->dev); 3472 3473 /* setup board info structure */
第 3455~3462 就是新加入的代码,如果要在 I.MX6ULL 上使用 LAN8720A 就需要设置ENET1 和 ENET2 的 TX_CLK 引脚复位寄存器的 SION 位为 1。
5、配置 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 驱动,配置好以后退出配置界面。
6、修改 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函数内容如下所示:
修改前
60 static int smsc_phy_reset(struct phy_device *phydev) 61 { 62 int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES); 63 if (rc < 0) 64 return rc; 65 66 /* If the SMSC PHY is in power down mode, then set it 67 * in all capable mode before using it. 68 */ 69 if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) { 70 int timeout = 50000; 71 72 /* set "all capable" mode and reset the phy */ 73 rc |= MII_LAN83C185_MODE_ALL; 74 phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc); 75 phy_write(phydev, MII_BMCR, BMCR_RESET); 76 77 /* wait end of reset (max 500 ms) */ 78 do { 79 udelay(10); 80 if (timeout-- == 0) 81 return -1; 82 rc = phy_read(phydev, MII_BMCR); 83 } while (rc & BMCR_RESET); 84 } 85 return 0; 86 }
修改后
static int smsc_phy_reset(struct phy_device *phydev) { int err, phy_reset; int msec = 1; struct device_node *np; int timeout = 50000; if(phydev->addr == 0) /* FEC1 */ { np = of_find_node_by_path("/soc/aips-bus@02100000/ethernet@02188000"); if(np == NULL) { return -EINVAL; } } if(phydev->addr == 1) /* FEC2 */ { np = of_find_node_by_path("/soc/aips-bus@02000000/ethernet@020b4000"); if(np == NULL) { return -EINVAL; } } err = of_property_read_u32(np, "phy-reset-duration", &msec); /* A sane reset duration should not be longer than 1s */ if (!err && msec > 1000) msec = 1; phy_reset = of_get_named_gpio(np, "phy-reset-gpios", 0); if (!gpio_is_valid(phy_reset)) return; gpio_direction_output(phy_reset, 0); gpio_set_value(phy_reset, 0); msleep(msec); gpio_set_value(phy_reset, 1); int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES); if (rc < 0) return rc; /* If the SMSC PHY is in power down mode, then set it * in all capable mode before using it. */ 102 if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) { 103 104 /* set "all capable" mode and reset the phy */ 105 rc |= MII_LAN83C185_MODE_ALL; 106 phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc); 107 } 108 phy_write(phydev, MII_BMCR, BMCR_RESET); 109 110 /* wait end of reset (max 500 ms) */ 111 do { 112 udelay(10); 113 if (timeout-- == 0) 114 return -1; 115 rc = phy_read(phydev, MII_BMCR); 116 } while (rc & BMCR_RESET); 117 118 return 0; 119 }
第 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>
然后编译源码,启动开发板,你会发现压根进不了系统!!!!!(其他修改能解决)