进入U-Boot文件夹,执行命令:
make distclean make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defcon fig make V=1 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- DEVICE_TREE=stm 32mp157d-atk all
上面命令每次编译的时候都要指定 ARCH、CROSS_COMPILE 和 DEVICE_TREE,这三个 含义如下:
ARCH:指定所使用的平台架构,这里肯定是 arm。
CROSS_COMPILE:所使用的交叉编译器前缀,本教程使用的是交叉编译器前缀为 arm-none-linux-gnueabihf-。
DEVICE_TREE:设备树文件,uboot 也支持设备树,所以在编译的时候需要指定设备树文 件,不同的硬件其设备树文件肯定不同,这里为 stm32mp157d_atk,也就是正点原子的 STM32MP157 开发板对应的设备树。
编译的时候每次都输入 ARCH 和 CROSS_COMPILE 比较麻烦,为了方便直接修改 uboot 的 Makefile 文件,在里面直接对 ARCH 和 CROSS_COMPILE 进行赋值,也就是直接将 ARCH 设置为 arm,CROSS_COMPILE 设置为 arm-none-linux-gnueabihf-。(在文件266行自己添加)
注意
不能在 Makefile 里面对 DEVICE_TREE 进行复制,因为没用,必须在编译的时候手 动输入!
设置好 Makefile 里面的 ARCH 和 CROSS_COMPILE 以后就可以将编译命令简化为如下所 示:
make distclean //清除 make stm32mp157d_atk_defconfig //配置 uboot make V=1 DEVICE_TREE=stm32mp157d-atk all //编译
“make V=1”是真正的编译命令,V=1 表示编译 uboot 的时候输出详细的编译过程,方便我们观察 uboot 编译过程。直接输入“make”命令的话默认使用单线程编译,编译速度会比较慢,可以通过添加“-j”选项来使用多线程编译,比如使用 8 线程编译,最后的编译命令就是:
make V=1 DEVICE_TREE=stm32mp157d-atk all -j8 //8 线程编译
编译完成以后 uboot 源码多了一些文件,重点是 u-boot.bin 和 u-boot.stm32 这两个文件。u-boot.bin 是 uboot 的二进制可执行文件,u-boot.stm32 是在 u-boot.bin 前面添加了256 个字节头部信息。STM32MP1 内部 ROM 代码和 TF-A 在运行 uboot 的时候要求前面添加头部信息,所以这就是为什么 uboot 也有这个头部信息的原因。
简单讲解一下 uboot 启动过程的 log 信息:
第 1 行是 uboot 版本号和编译时间。
第 3 行是 CPU 的信息。
第 4 行是板子信息,这个信息是可以改的。
第 5 行是板子的一些信息,比如工作在 trusted 模式下。
第 6 行是 DDR 的大小。
第 7-12 行它们的频率分别为,MPU 频率、MCU 频率、AXI 总线频率、PER 的频率、DDR 频率。
第 13 行是看门狗信息,喂狗时间为 32s。
第 14 行是 NAND 的大小。
第 15 行是板子上 MMC 设备,一共有两个,SD/MMC0 (SD 卡)和 SD/MMC1 (EMMC)。
第 16 行是从 MMC 里获取环境变量。
第 17-19 行是标准输入、标准输出和标准错误所使用的终端,这里都使用串口(serial)作为 终端。
第 20-23 行是网络相关信息,网络的 MAC 地址从 OTP 里获取,因为我们的 OTP 没有设置 MAC 地址,所以就获取失败。这里的网络是可以用的,只是因为没有 MAC 地址所以提示没有找到网络,可以自行添加相关环境变量来设置 MAC 地址,后面会讲如何设置。
第 25 行是倒计时提示,默认倒计时 1 秒,倒计时结束之前按下回车键就会进入 Linux 命 令行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动,Linux 内核一 旦启动,uboot 就会寿终正寝。
uboot 的主要作用是引导 kernel,经进入 uboot 的命令行模式,进入命令行模式以后就可以给 uboot 发号施令了。
提示
如果编译后出现下面的报错信息:
collect2: error: ld returned 1 exit status make[2]: *** [scripts/Makefile.host:106:scripts/dtc/dtc] 错误 1 make[1]: *** [scripts/Makefile.build:432:scripts/dtc] 错误 2 make: *** [Makefile:556:scripts] 错误 2
在顶层文件Makefile中添加HOSTCFLAGS += -fcommon,前往scripts/dtc/dtc-lexer.l文件,找到YYLTYPE yylloc;并注释掉即可。
uboot 支持 TAB 键自动补全功能。
进入 uboot 的命令行模式以后输入“help”或者“?”,然后按下回车即可查看当前 uboot 所 支持的命令,输入“help(或?) 命令名”既可以查看命令的详细用法。
令,此命令用于查看板子信息,直接输入“bdinfo”即可。可以看出 DRAM 的起始地址和大小、BOOT 参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息。
命令“printenv”用于输出环境变量信息。STM32MP1 系列的环境变量有很多,比如baudrate、board、board_name、boot_device、bootcmd、bootdelay 等等。
命令 version 用于查看 uboot 的版本号。
环境变量的操作涉及到两个命令:setenv 和 saveenv,setenv 命令用于设置或者修改环境变量的值。命令 saveenv 用于保存修改后的环境变量,一般环境变量存放在外部 flash 中,uboot 启动的时候会将环境变量从 flash 读取到 DRAM 中。所以使用命令 setenv 修改的是 DRAM中的环境变量值,修改以后要使用 saveenv 命令将修改后的环境变量保存到 flash 中,否则uboot 下一次重启会继续使用以前的环境变量值。
命令 setenv 也可以用于新建命令,用法和修改环境变量一样,删除环境变量也是使用命令 setenv,要删除一个环境变量只要给这个环境变量赋空值即可。
md 命令用于显示内存值,格式如下:
md[.b, .w, .l] address [# of objects]
命令中的[.b .w .l]
对应 、 和 ,也就是分别以 1 个字节、2 个字节、4 个字节来显示内存值。 就是要查看的内存起始地址,[# of objects]
表示要查看的数据长度,这个数据长度单位不是字节,而是跟你所选择的显示格式有关。比如你设置要查看的内存长度为20(十六进制为 0x14),如果显示格式为.b 的话那就表示 20 个字节;如果显示格式为 的话就表示 20 个 ,也就是 个字节;如果显示格式为.l 的话就表示 20 个 ,也就是 个字节。
提示
uboot 命令中的数字都是十六进制的!不是十进制的!
nm [.b, .w, .l] address
nm 命令同样可以以.b、.w 和.l 来指定操作格式。
mm 命令也是修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用 nm 命令的话地址不会自增。
命令 mw 用于使用一个指定的数据填充一段内存,命令格式如下:
mw [.b, .w, .l] address value [count]
mw 命令同样以.b、.w 和.l 来指定操作格式,address 表示要填充的内存起始地址,value 为 要填充的数据,count 是填充的长度。
cp [.b, .w, .l] source target count
cp 命令同样以.b、.w 和.l 来指定操作格式,source 为源地址,target 为目的地址,count 为 拷贝的长度。
cmp [.b, .w, .l] addr1 addr2 count
cmp 命令同样以.b、.w 和.l 来指定操作格式,addr1 为第一段内存首地址,addr2 为第二段 内存首地址,count 为要比较的长度。
几个网络相关环境变量:
环境变量 | 描述 |
---|---|
ipaddr | 开发板 ip 地址,可以不设置,使用 dhcp 命令来从路由器获取 IP 地址。 |
ethaddr | 开发板的 MAC 地址,一定要设置。 |
gatewayip | 网关地址。 |
netmask | 子网掩码。 |
serverip | 服务器 IP 地址,也就是 Ubuntu 主机 IP 地址,用于调试代码。 |
提示
注意,网络地址环境变量的设置要根据自己的实际情况,确保 Ubuntu 主机和开发板的 IP地址在同一个网段内,比如我现在的开发板和电脑都在 192.168.1.0 这个网段内,所以设置开发 板的 IP 地址为 192.168.1.250,我的 Ubuntu 主机的地址为 192.168.1.249,因此 serverip 就是192.168.1.249。ethaddr 为网络 MAC 地址,是一个 48bit 的地址,如果在同一个网段内有多个开发板的话一定要保证每个开发板的 ethaddr 是不同的,否则通信会有问题!设置好网络相关的环境变量以后就可以使用网络相关命令了。
提示
注意!只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 的话会失败!
nfs(Network File System)网络文件系统,通过 nfs 可以在计算机之间通过网络来分享资源, 比如我们将 linux 镜像和设备树文件放到 Ubuntu 中,然后在 uboot 中使用 nfs 命令将 Ubuntu 中的 linux 镜像和设备树下载到开发板的 DRAM 中。这样做的目的是为了方便调试 linux 镜像和设备树,也就是网络调试,网络调试是 Linux 开发中最常用的调试方法。以通过网络将编译好的 linux 镜像和设备树文件下载到 DRAM 中,然后就可以直接运行。
nfs [loadAddress] [[hostIPaddr:]bootfilename]
loadAddress
是要保存的 DRAM 地址,[[hostIPaddr:]bootfilename]
是要下载的文件地址。
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]
看起来和 nfs 命 令 格式 一 样 的 , loadAddress
是文件在 DRAM 中 的存 放 地 址 ,
[[hostIPaddr:]bootfilename]
是要从 Ubuntu 中下载的文件。但是和 nfs 命令的区别在于,tftp 命令不需要输入文件在 Ubuntu 中的完整路径,只需要输入文件名即可。
uboot 支持 EMMC 和 SD 卡,因此也要提供 EMMC 和 SD 卡的操作命令。一般认为 EMMC 和 SD 卡是同一个东西,所以没有特殊说明。
命令 | 描述 |
---|---|
mmc info | 输出 MMC 设备信息 |
mmc read | 读取 MMC 中的数据。 |
mmc wirte | 向 MMC 设备写入数据。 |
mmc rescan | 扫描 MMC 设备。 |
mmc part | 列出 MMC 设备的分区。 |
mmc dev | 切换 MMC 设备。 |
mmc list | 列出当前有效的所有 MMC 设备。 |
mmc hwpartition | 设置 MMC 设备的分区。 |
mmc bootbus…… | 设置指定 MMC 设备的 BOOT_BUS_WIDTH 域的值。 |
mmc bootpart…… | 设置指定 MMC 设备的 boot 和 RPMB 分区的大小。 |
mmc partconf…… | 设置指定 MMC 设备的 PARTITION_CONFG 域的值。 |
mmc rst | 复位 MMC 设备 |
mmc setdsr | 设置 DSR 寄存器的值 |
uboot 有 ext2 和 ext4 这两种格式的文件系统的操作命令,STM32MP1 的系统镜像都是 ext4 格式的,所以我们重点讲解一下和 ext4 有关的三个命令:ext4ls、ext4load 和 ext4write。
提示
注意,由于只有 linux 内核、设备树和根文件系统是以 ext4 格式存放在 EMMC 中的,因此在测试 ext4相关命令的时候要先确保 EMMC 里面烧写了完整的出厂系统!
ext4ls <interface> [<dev[:part]>] [directory]
interface 是要查询的接口,比如 mmc,dev 是要查询的设备号,part 是要查询的分区,directory是要查询的目录。
extload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
interface 为接口,比如 mmc,dev 是设备号,part 是分区,addr 是保存在 DRAM 中的起始 地址,filename 是要读取的文件名字。bytes 表示读取多少字节的数据,如果 bytes 为 0 或者省略的话表示读取整个文件。pos 是要读的文件相对于文件首地址的偏移,如果为 0 或者省略的 话表示从文件首地址开始读取。
ext4write <interface> <dev[:part]> <addr> <absolute filename path> [sizebytes] [file offset]
interface 为接口,比如 mmc;dev 是设备号;part 是分区;addr 是要写入的数据在 DRAM 中的起始地址;absolute filename path 是写入的数据文件名字,注意是要带有绝对路径,以‘/’开始;sizebytes 表示要写入多少字节的数据;file offset 为文件偏移。我们可以通过 fatwrite 命令在 uboot 中更新 linux 镜像文件和设备树。
要启动 Linux,需要先将 Linux 镜像文件拷贝到 DRAM 中,如果使用到设备树的话也需要 将设备树拷贝到 DRAM 中。可以从 EMMC 或者 NAND 等存储设备中将 Linux 镜像和设备树文 件拷贝到 DRAM,也可以通过 nfs 或者 tftp 将 Linux 镜像文件和设备树文件下载到 DRAM 中。 不管用那种方法,只要能将 Linux 镜像和设备树文件存到 DRAM 中就行,然后使用 bootm 命令 来启动,bootm 命令用于启动 uImage 镜像文件。
bootm [addr [arg ...]]
命令 bootm 主要有三个参数,addr 是 Linux 镜像文件在 DRAM 中的位置,后面的“arg…” 表示其他可选的参数,比如要指定 initrd 的话,第二个参数就是 initrd 在 DRAM 中的地址。如果 Linux 内核使用设备树的话还需要第三个参数,用来指定设备树在 DRAM 中的地址,如果不 需要 initrd 的话第二个参数就用‘-’来代替。
提示
注意!只要打印出 Linux 内核启动信息就说明 Linux 系统启动成功,由于没有给定 bootargs 参数,所以 Linux 系统启动最终会失败,因为找不到根文件系统!
bootz [addr [initrd[:size]] [fdt]]
命令 bootz 有三个参数,addr 是 Linux 镜像文件在 DRAM 中的位置,initrd 是 initrd 文件在DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可,fdt 就是设备树文件在 DRAM 中 的地址,使用方法和 bootm 一模一样,只是所引导的 Linux 镜像格式不同,NXP 的 I.MX6ULL 就是使用 bootz 命令来引导 Linux 内核的。
boot 和 bootd 其实是一个命令,它们最终执行的是同一个函数。为了方便起见,后面就统 一使用 boot 命令,此命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统,bootcmd 是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引 导”和“命令”,说明这个环境变量保存着引导命令,其实就是多条启动命令的集合,具体的引 导命令内容是可以修改的。
提示
注意!ST 官方 uboot 并没有使能 boot 和 bootd 这两个命令,需要自行配置 uboot 来启动这 两个命令,
要想使用 tftp 命令从网络启动 Linux 那么就可以设置bootcmd 为“tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000”,
然后使用 saveenv 将 bootcmd 保存起来。然后直接输入 boot 命令即可从网络启动 Linux 系统,命令如下:
setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000' saveenv boot
如果想从 EMMC 启动系统,那就设置 bootcmd 环境变量为“ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000”
,然后使用boot 命令启动即可,命令如下:
setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157datk.dtb;bootm c2000000 - c4000000' saveenv boot
如果不修改 bootcmd 的话,每次开机 uboot 倒计时结束以后都会自动从 EMMC 里面读取 uImage 和 stm32mp157d-atk.dtb,然后启动 Linux。在启动 Linux 内核的时候可能会遇到如下错误:
“---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---”
这个错误的原因是 linux 内核没有找到根文件系统,这个很正常,因为没有在 bootargs 环境 变量中指定根文件系统路径,关于 bootargs 环境变量后面会讲解!此处我们重点是验证 boot 命令,linux 内核已经成功启动了,说明 boot 命令工作正常。
在 uboot 下我们可以将开发板虚拟成一个 U 盘,我们可以选择使用哪个 Flash 作为这个 U 盘的存储器,比如将正点原子 STM32MP157 开发板上的 EMMC 或者 SD 卡虚拟成 U 盘。当我 们将 EMMC 虚拟成 U 盘以后就可以直接在电脑上向开发板拷贝文件了,uboot 提供的 ums 命令就是来完成此功能的。
ums <USB_controller> [<devtype>] <dev[:part]>
其中 USB_controller 是 usb 接口索引,有的开发板有多个 USB SLAVE 接口,具体要使用哪 个就可以通过 USB_controller 参数指定。
reset 命令顾名思义就是复位的,输入“reset”即可复位重启。
go 命令用于跳到指定的地址处执行应用,命令格式如下:
go addr [arg ...]
addr 是应用在 DRAM 中的首地址。
run 命令用于运行环境变量中定义的命令,比如可以通过“run bootcmd”来运行 bootcmd 中的启动命令,但是 run 命令最大的作用在于运行我们自定义的环境变量。在后面调试 Linux 系统的时候常常要在网络启动和 EMMC 启动之间来回切换,而 bootcmd 只能保存一种启动方式,如果要换另外一种启动方式的话就得重写 bootcmd,会很麻烦。这里我们就可以通过自定义环境变量来实现不同的启动方式,
mtest 命令是一个简单的内存读写测试命令,可以用来测试自己开发板上的 DDR,命令格式如下:
mtest [start [end [pattern [iterations]]]]
start 是 要 测 试 的 DRAM 开始地址, end 是 结 束 地 址 。
类型 | 名字 | 描述 | 备注 |
---|---|---|---|
文件夹 | api | 与硬件无关的 API 函数。 | uboot 自带 |
文件夹 | arch | 与硬件架构体系有关的代码。 | uboot 自带 |
文件夹 | board | 不同板子(开发板)的定制代码。 | uboot 自带 |
文件夹 | cmd | 命令相关代码。 | uboot 自带 |
文件夹 | common | 通用代码。 | uboot 自带 |
文件夹 | configs | 配置文件。 | uboot 自带 |
文件夹 | disk | 磁盘分区相关代码 | uboot 自带 |
文件夹 | doc | 文档。 | uboot 自带 |
文件夹 | drivers | 驱动代码。 | uboot 自带 |
文件夹 | 设备树。 | uboot 自带 | |
文件夹 | examples | 示例代码。 | uboot 自带 |
文件夹 | fs | 文件系统。 | uboot 自带 |
文件夹 | include | 头文件。 | uboot 自带 |
文件夹 | lib | 库文件。 | uboot 自带 |
文件夹 | Licenses | 许可证相关文件。 | uboot 自带 |
文件夹 | net | 网络相关代码。 | uboot 自带 |
文件夹 | post | 上电自检程序。 | uboot 自带 |
文件夹 | scripts | 脚本文件。 | uboot 自带 |
文件夹 | test | 㺃试代码。 | uboot 自带 |
文件夹 | tools | 工具文件夹。 | uboot 自带 |
文件 | .config | 配置文件, 重要的文件。 | 编译生成的文件 |
文件 | .config.old | 老的配置文件 | |
文件 | gitignore | git 工具相关文件。 | uboot 自带 |
文件 | mailmap | 邮件列表。 | |
文件 | u-boot.xxx.cmd (一系列) | 这是一系列的文件, 用于保存着一 些命令。 | 编译生成的文件 |
文件 | configmk | 某个 Makefile 会调用此文件。 | uboot 自带 |
文件 | Kbuild | 用于生成一些和汇编有关的文件。 | uboot 自带 |
文件 | Kconfig | 图形配置界面描述文件。 | |
文件 | MAINTAINERS | 维护者联系方式文件。 | |
文件 | Makefile | 主 Makefile, 重要文件! | |
文件 | README | 相当于帮助文档。 | uboot 自带 |
文件 | System,map | 系统映射文件 | 编译出来的文件 |
文件 | u-boot | 编译出来的 u-boot 文件。 | |
文件 | u-boot.xxx (一系列) | 生成的一些 u-boot 相关文件, 包括 u-boot.bin、u-boot.stm 32.等 |
configs 文件夹为 uboot 配置文件夹,uboot 是可配置的,但是你要是自己从头开始一个一个项目的配置,那就太麻烦了,因此一般半导体或者开发板厂商都会制作好一个配置文件。我们可以在这个做好的配置文件基础上来添加自己想要的功能,这些半导体厂商或者开发板厂商制作好的配置文件统一命名为“xxx_defconfig”,xxx 表示开发板名字,这些 defconfig 文件都存放在configs 文件夹,因此,ST 官方开发板和正点原子的开发板配置文件肯定也在这个文件夹中,stm32mp157d_atk_defconfig 就是正点原子 STM32MP157 开发板对应的默认配置文件。使“make xxx_defconfig”命令即可配置 uboot,比如:
make stm32mp157d_atk_defconfig
上述命令就是配置正点原子 STM32MP157 核心板所使用的 uboot。在编译 uboot 之前一定要使用 defconfig 来配置 uboot!
u-boot.xxx 文件同样也是一系列文件,包括 u-boot、u-boot-dtb.bin、u-boot-nodtb.bin、u-boot.bin、u-boot.cfg、u-boot.dtb、u-boot.lds、u-boot.map、u-boot.srec、u-boot.stm32 和 u-boot.sym,这些文件的含义如下:
.config 文件中都是以“CONFIG_”开始的配置项,这些配置项就是 Makefile 中的 变量,因此后面都跟有相应的值,uboot 的顶层 Makefile 或子 Makefile 会调用这些变量值。在.config 中会有大量的变量值为‘y’,这些为‘y’的变量一般用于控制某项功能是否使能,为‘y’的话就表示功能使能。
编 译 uboot 的 时 候 需 要 设 置 目 标 板 架 构 和 交 叉 编 译 器 ,“ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf-”就是用于设置 ARCH 和 CROSS_COMPILE,在顶层 Makefile 中代码如下:
261 # set default to nothing for native builds 262 ifeq ($(HOSTARCH),$(ARCH)) 263 CROSS_COMPILE ?= 264 endif 265 266 KCONFIG_CONFIG ?= .config 267 export KCONFIG_CONFIG
上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还没有设置,顶层 Makefile 中相关代码如下:
383 # Make variables (CC, etc...) 384 385 AS = $(CROSS_COMPILE)as 386 # Always use GNU ld 387 ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),) 388 LD = $(CROSS_COMPILE)ld.bfd 389 else 390 LD = $(CROSS_COMPILE)ld 391 endif 392 CC = $(CROSS_COMPILE)gcc 393 CPP = $(CC) -E 394 AR = $(CROSS_COMPILE)ar 395 NM = $(CROSS_COMPILE)nm 396 LDR = $(CROSS_COMPILE)ldr 397 STRIP = $(CROSS_COMPILE)strip 398 OBJCOPY = $(CROSS_COMPILE)objcopy 399 OBJDUMP = $(CROSS_COMPILE)objdump
进入到对应的uboot 源码目录,命令如下:
cd u-boot-stm32mp-2020.01/ //进入 uboot 源码目录 for p in `ls -1 ../*.patch`;do patch -p1 < $p;done //打补丁
首先根据官方配置文件创建自己所使用开发板对应的默认配置文件和对应的设备树,官方文件如下:
stm32mp15_trusted_defconfig stm32mp157d-ed1 stm32mp15xx-edx.dtsi stm32mp157a-ed1-u-boot.dtsi
打开 stm32mp157d-atk.dts 文件,要修改一下其中的一个头文件引用,stm32mp157d-atk.dts默认头文件引用如下:
1 // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) 2 /* 3 * Copyright (C) STMicroelectronics 2019 - All Rights Reserved 4 * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics. 5 */ 6 /dts-v1/; 7 8 #include "stm32mp157.dtsi" 9 #include "stm32mp15xd.dtsi" 10 #include "stm32mp15-pinctrl.dtsi" 11 #include "stm32mp15xxaa-pinctrl.dtsi" 12 #include "stm32mp157-m4-srm.dtsi" 13 #include "stm32mp157-m4-srm-pinctrl.dtsi" 14 #include "stm32mp15xx-edx.dtsi"
注意,第 14 行引用的是 stm32mp15xx-edx.dtsi 这个设备树头文件,我们要将其改为上面创建的 stm32mp15d-atk.dtsi。
打开创建的 stm32mp157d-atk-u-boot.dtsi 这个文件。前 53 行的内容如下所示:
1 // SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause 2 /* 3 * Copyright : STMicroelectronics 2018 4 */ 5 6 #include <dt-bindings/clock/stm32mp1-clksrc.h> 7 #include "stm32mp15-u-boot.dtsi" 8 #include "stm32mp15-ddr3-2x4Gb-1066-binG.dtsi" 9 10 / { 11 aliases { 12 i2c3 = &i2c4; 13 mmc0 = &sdmmc1; 14 mmc1 = &sdmmc2; 15 }; 16 17 config { 18 u-boot,boot-led = "heartbeat"; 19 u-boot,error-led = "error"; 20 u-boot,mmc-env-partition = "ssbl"; 21 st,fastboot-gpios = <&gpioa 13 GPIO_ACTIVE_LOW>; 22 st,stm32prog-gpios = <&gpioa 14 GPIO_ACTIVE_LOW>; 23 }; 24 25 led { 26 red { 27 label = "error"; 28 gpios = <&gpioa 13 GPIO_ACTIVE_LOW>; 29 default-state = "off"; 30 status = "okay"; 31 }; 32 }; 33 }; 34 35 #ifndef CONFIG_STM32MP1_TRUSTED 36 &clk_hse { 37 st,digbypass; 38 }; 39 40 &i2c4 { 41 u-boot,dm-pre-reloc; 42 }; 43 44 &i2c4_pins_a { 45 u-boot,dm-pre-reloc; 46 pins { 47 u-boot,dm-pre-reloc; 48 }; 49 }; 50 51 &pmic { 52 u-boot,dm-pre-reloc; 53 };
把 21-22 行、26-31 行和 51-53 行都删除。
接着修改 stm32mp157d-atk.dtsi 文件,找到如下所示代码:
90 &adc { 91 /* ANA0, ANA1 are dedicated pins and don't need pinctrl: only in6. */ 92 pinctrl-0 = <&adc1_in6_pins_a>; 93 pinctrl-names = "default"; 94 vdd-supply = <&vdd>; 95 vdda-supply = <&vdda>; 96 vref-supply = <&vdda>; 97 status = "disabled"; 98 adc1: adc@0 { 99 st,adc-channels = <0 1 6>; 100 /* 16.5 ck_cycles sampling time */ 101 st,min-sample-time-nsecs = <400>; 102 status = "okay"; 103 }; 104 }; 105 106 &cpu0{ 107 cpu-supply = <&vddcore>; 108 }; 109 110 &crc1 { 111 status = "okay"; 112 }; 113 114 &dac { 115 pinctrl-names = "default"; 116 pinctrl-0 = <&dac_ch1_pins_a &dac_ch2_pins_a>; 117 vref-supply = <&vdda>; 118 status = "disabled"; 119 dac1: dac@1 { 120 status = "okay"; 121 }; 122 dac2: dac@2 { 123 status = "okay"; 124 }; 125 }; 126 127 &dma1 { 128 sram = <&dma_pool>; 129 }; ...... 142 143 &i2c4 { 144 pinctrl-names = "default", "sleep"; 145 pinctrl-0 = <&i2c4_pins_a>; 146 pinctrl-1 = <&i2c4_pins_sleep_a>; 147 i2c-scl-rising-time-ns = <185>; 148 i2c-scl-falling-time-ns = <20>; 149 clock-frequency = <400000>; 150 status = "okay"; 151 /* spare dmas for other usage */ 152 /delete-property/dmas; 153 /delete-property/dma-names; 154 155 pmic: stpmic@33 { 156 compatible = "st,stpmic1"; 157 reg = <0x33>; 158 interrupts-extended = <&exti_pwr 55 IRQ_TYPE_EDGE_FALLING>; 159 interrupt-controller; 160 #interrupt-cells = <2>; 161 status = "okay"; 162 163 regulators { 164 compatible = "st,stpmic1-regulators"; 165 buck1-supply = <&vin>; 166 buck2-supply = <&vin>; 167 buck3-supply = <&vin>; 168 buck4-supply = <&vin>; 169 ldo1-supply = <&v3v3>; 170 ldo2-supply = <&v3v3>; 171 ldo3-supply = <&vdd_ddr>; 172 ldo4-supply = <&vin>; 173 ldo5-supply = <&v3v3>; 174 ldo6-supply = <&v3v3>; 175 vref_ddr-supply = <&vin>; 176 boost-supply = <&vin>; 177 pwr_sw1-supply = <&bst_out>; 178 pwr_sw2-supply = <&bst_out>; 179 180 vddcore: buck1 { 181 regulator-name = "vddcore"; 182 regulator-min-microvolt = <1200000>; 183 regulator-max-microvolt = <1350000>; 184 regulator-always-on; 185 regulator-initial-mode = <0>; 186 regulator-over-current-protection; 187 }; ...... 278 vbus_sw: pwr_sw2 { 279 regulator-name = "vbus_sw"; 280 interrupts = <IT_OCP_SWOUT 0>; 281 regulator-active-discharge = <1>; 282 }; 283 }; 284 285 onkey { 286 compatible = "st,stpmic1-onkey"; 287 interrupts = <IT_PONKEY_F 0>, <IT_PONKEY_R 0>; 288 interrupt-names = "onkey-falling", "onkey-rising"; 289 power-off-time-sec = <10>; 290 status = "okay"; 291 }; 292 293 watchdog { 294 compatible = "st,stpmic1-wdt"; 295 status = "disabled"; 296 }; 297 }; 298 };
将 90-104 行的 adc 节点、114~125 行的 dac 节点以及 143-298 行的i2c4 节点全部删除掉。
继续修改 stm32mp157d-atk.dtsi 文件,找到如下所示代码:
58 led { 59 compatible = "gpio-leds"; 60 blue { 61 label = "heartbeat"; 62 gpios = <&gpiod 9 GPIO_ACTIVE_HIGH>; 63 linux,default-trigger = "heartbeat"; 64 default-state = "off"; 65 }; 66 }; 67 68 sd_switch: regulator-sd_switch { 69 compatible = "regulator-gpio"; 70 regulator-name = "sd_switch"; 71 regulator-min-microvolt = <1800000>; 72 regulator-max-microvolt = <2900000>; 73 regulator-type = "voltage"; 74 regulator-always-on; 75 76 gpios = <&gpiof 14 GPIO_ACTIVE_HIGH>; 77 gpios-states = <0>; 78 states = <1800000 0x1 2900000 0x0>; 79 };
上述代码是 led 和 sd_switch 节点信息,将这两个节点都删除掉。
最后我们需要向 stm32mp157d-atk.dtsi 文件的根节点‘/’下添加自己的电源管理配置,将下面的代码添加到上面修改后代码的 58 行处(即vin:vin {
上面一行):
vddcore: regulator-vddcore { compatible = "regulator-fixed"; regulator-name = "vddcore"; regulator-min-microvolt = <1200000>; regulator-max-microvolt = <1350000>; regulator-always-on; regulator-boot-on; }; v3v3: regulator-3p3v { compatible = "regulator-fixed"; regulator-name = "v3v3"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; regulator-boot-on; }; v1v8_audio: regulator-v1v8-audio { compatible = "regulator-fixed"; regulator-name = "v1v8_audio"; regulator-min-microvolt = <1800000>; regulator-max-microvolt = <1800000>; regulator-always-on; regulator-boot-on; }; vdd: regulator-vdd { compatible = "regulator-fixed"; regulator-name = "vdd"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; regulator-boot-on; }; vdd_usb: regulator-vdd-usb { compatible = "regulator-fixed"; regulator-name = "vdd_usb"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; regulator-boot-on; };
上述代码定义了 5 个电源节点,分别为:vddcore、v3v3、v1v8_audio、vdd 和 vdd_usb。
继续修改 stm32mp157d-atk.dtsi 文件,找到 sdmmc1 和 sdmmc2 这两个节点,将这两个节点改为如下所示内容:
&sdmmc1 { pinctrl-names = "default", "opendrain", "sleep"; pinctrl-0 = <&sdmmc1_b4_pins_a>; pinctrl-1 = <&sdmmc1_b4_od_pins_a>; pinctrl-2 = <&sdmmc1_b4_sleep_pins_a>; st,neg-edge; broken-cd; bus-width = <4>; vmmc-supply = <&v3v3>; status = "okay"; }; &sdmmc2 { pinctrl-names = "default", "opendrain", "sleep"; pinctrl-0 = <&sdmmc2_b4_pins_a &sdmmc2_d47_pins_a>; pinctrl-1 = <&sdmmc2_b4_od_pins_a &sdmmc2_d47_pins_a>; pinctrl-2 = <&sdmmc2_b4_sleep_pins_a &sdmmc2_d47_sleep_pins_a>; non-removable; st,neg-edge; bus-width = <8>; vmmc-supply = <&v3v3>; keep-power-in-suspend; status = "okay"; };
打开 stm32mp157d-atk.dtsi 文件,将如下所示的 ethernet0 节点 加添加到最后面:
ðernet0 { status = "okay"; pinctrl-0 = <ðernet0_rgmii_pins_a>; pinctrl-1 = <ðernet0_rgmii_pins_sleep_a>; pinctrl-names = "default", "sleep"; phy-mode = "rgmii-id"; max-speed = <1000>; phy-handle = <&phy0>; mdio0 { #address-cells = <1>; #size-cells = <0>; compatible = "snps,dwmac-mdio"; phy0: ethernet-phy@0 { reg = <0>; }; }; };
正点原子V1.3及以后版本的核心板开始将网络PHY芯片更换为了国产裕太电子的YT8511,所以需要修改一下驱动。phy.c 是正点原子修改后支持 YT8511,此文件同时支持 RTL8211 以及 YT8511。用修改后的 phy.c 文件替换掉uboot 下的/drivers/net/phy/phy.c 文件即可,设备树无需做任何修改,直接编译即可。
// SPDX-License-Identifier: GPL-2.0+ /* * Generic PHY Management code * * Copyright 2011 Freescale Semiconductor, Inc. * author Andy Fleming * * Based loosely off of Linux's PHY Lib */ #include <common.h> #include <console.h> #include <dm.h> #include <malloc.h> #include <net.h> #include <command.h> #include <miiphy.h> #include <phy.h> #include <errno.h> #include <linux/err.h> #include <linux/compiler.h> DECLARE_GLOBAL_DATA_PTR; /* Generic PHY support and helper functions */ /** * genphy_config_advert - sanitize and advertise auto-negotiation parameters * @phydev: target phy_device struct * * Description: Writes MII_ADVERTISE with the appropriate values, * after sanitizing the values to make sure we only advertise * what is supported. Returns < 0 on error, 0 if the PHY's advertisement * hasn't changed, and > 0 if it has changed. */ static int genphy_config_advert(struct phy_device *phydev) { u32 advertise; int oldadv, adv, bmsr; int err, changed = 0; /* Only allow advertising what this PHY supports */ phydev->advertising &= phydev->supported; advertise = phydev->advertising; /* Setup standard advertisement */ adv = phy_read(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE); oldadv = adv; if (adv < 0) return adv; adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); if (advertise & ADVERTISED_10baseT_Half) adv |= ADVERTISE_10HALF; if (advertise & ADVERTISED_10baseT_Full) adv |= ADVERTISE_10FULL; if (advertise & ADVERTISED_100baseT_Half) adv |= ADVERTISE_100HALF; if (advertise & ADVERTISED_100baseT_Full) adv |= ADVERTISE_100FULL; if (advertise & ADVERTISED_Pause) adv |= ADVERTISE_PAUSE_CAP; if (advertise & ADVERTISED_Asym_Pause) adv |= ADVERTISE_PAUSE_ASYM; if (advertise & ADVERTISED_1000baseX_Half) adv |= ADVERTISE_1000XHALF; if (advertise & ADVERTISED_1000baseX_Full) adv |= ADVERTISE_1000XFULL; if (adv != oldadv) { err = phy_write(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE, adv); if (err < 0) return err; changed = 1; } bmsr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); if (bmsr < 0) return bmsr; /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a * logical 1. */ if (!(bmsr & BMSR_ESTATEN)) return changed; /* Configure gigabit if it's supported */ adv = phy_read(phydev, MDIO_DEVAD_NONE, MII_CTRL1000); oldadv = adv; if (adv < 0) return adv; adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF); if (phydev->supported & (SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full)) { if (advertise & SUPPORTED_1000baseT_Half) adv |= ADVERTISE_1000HALF; if (advertise & SUPPORTED_1000baseT_Full) adv |= ADVERTISE_1000FULL; } if (adv != oldadv) changed = 1; err = phy_write(phydev, MDIO_DEVAD_NONE, MII_CTRL1000, adv); if (err < 0) return err; return changed; } /** * genphy_setup_forced - configures/forces speed/duplex from @phydev * @phydev: target phy_device struct * * Description: Configures MII_BMCR to force speed/duplex * to the values in phydev. Assumes that the values are valid. */ static int genphy_setup_forced(struct phy_device *phydev) { int err; int ctl = BMCR_ANRESTART; phydev->pause = 0; phydev->asym_pause = 0; if (phydev->speed == SPEED_1000) ctl |= BMCR_SPEED1000; else if (phydev->speed == SPEED_100) ctl |= BMCR_SPEED100; if (phydev->duplex == DUPLEX_FULL) ctl |= BMCR_FULLDPLX; err = phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, ctl); return err; } /** * genphy_restart_aneg - Enable and Restart Autonegotiation * @phydev: target phy_device struct */ int genphy_restart_aneg(struct phy_device *phydev) { int ctl; ctl = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); if (ctl < 0) return ctl; ctl |= (BMCR_ANENABLE | BMCR_ANRESTART); /* Don't isolate the PHY if we're negotiating */ ctl &= ~(BMCR_ISOLATE); ctl = phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, ctl); return ctl; } /** * genphy_config_aneg - restart auto-negotiation or write BMCR * @phydev: target phy_device struct * * Description: If auto-negotiation is enabled, we configure the * advertising, and then restart auto-negotiation. If it is not * enabled, then we write the BMCR. */ int genphy_config_aneg(struct phy_device *phydev) { int result; if (phydev->autoneg != AUTONEG_ENABLE) return genphy_setup_forced(phydev); result = genphy_config_advert(phydev); if (result < 0) /* error */ return result; if (result == 0) { /* * Advertisment hasn't changed, but maybe aneg was never on to * begin with? Or maybe phy was isolated? */ int ctl = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); if (ctl < 0) return ctl; if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) result = 1; /* do restart aneg */ } /* * Only restart aneg if we are advertising something different * than we were before. */ if (result > 0) result = genphy_restart_aneg(phydev); return result; } /***************alientek zuozhongkai add 2021/4/23****************/ #define YT8511_REG_DEBUG_ADDR_OFFSET 0x1e #define YT8511_REG_DEBUG_DATA 0x1f static int yt8511_rd_ext(struct phy_device *phydev, u32 regnum) { int val; phy_write(phydev, MDIO_DEVAD_NONE, YT8511_REG_DEBUG_ADDR_OFFSET, regnum); val = phy_read(phydev, MDIO_DEVAD_NONE, YT8511_REG_DEBUG_DATA); return val; } static int yt8511_wr_ext(struct phy_device *phydev, u32 regnum, u16 val) { int ret; ret = phy_write(phydev, MDIO_DEVAD_NONE, YT8511_REG_DEBUG_ADDR_OFFSET, regnum); ret = phy_write(phydev, MDIO_DEVAD_NONE, YT8511_REG_DEBUG_DATA, val); return ret; } int yt8511_config_txdelay(struct phy_device *phydev, u8 delay) { int ret; int val; /* disable auto sleep */ val = yt8511_rd_ext(phydev, 0x27); if (val < 0) return val; val &= (~BIT(15)); ret = yt8511_wr_ext(phydev, 0x27, val); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ val = yt8511_rd_ext(phydev, 0xc); if (val < 0) return val; /* ext reg 0xc b[7:4] Tx Delay time = 150ps * N – 250ps */ val &= ~(0xf << delay); val |= (0x7 << delay); //150ps * 7 - 250ps ret = yt8511_wr_ext(phydev, 0xc, val); return ret; } int yt8511_config_out_125m(struct phy_device *phydev) { int ret; int val; /* disable auto sleep */ val = yt8511_rd_ext(phydev, 0x27); if (val < 0) return val; val &= (~BIT(15)); ret = yt8511_wr_ext(phydev, 0x27, val); if (ret < 0) return ret; /* enable RXC clock when no wire plug */ val = yt8511_rd_ext(phydev, 0xc); if (val < 0) return val; /* ext reg 0xc.b[2:1] 00-----25M from pll; 01---- 25M from xtl;(default) 10-----62.5M from pll; 11----125M from pll(here set to this value) */ val |= (3 << 1); ret = yt8511_wr_ext(phydev, 0xc, val); return ret; } /*********************end add***************************/ /** * genphy_update_link - update link status in @phydev * @phydev: target phy_device struct * * Description: Update the value in phydev->link to reflect the * current link value. In order to do this, we need to read * the status register twice, keeping the second value. */ int genphy_update_link(struct phy_device *phydev) { unsigned int mii_reg; /************alientek zuozhongkai add 2021/4/23********/ unsigned int phyid1, phyid2; phyid1 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID1); phyid2 = phy_read(phydev, MDIO_DEVAD_NONE, MII_PHYSID2); if((phyid1 == 0X0) && (phyid2 == 0x10a)) { yt8511_config_out_125m(phydev); yt8511_config_txdelay(phydev, 4); } /*********************end add***************************/ /* * Wait if the link is up, and autonegotiation is in progress * (ie - we're capable and it's not done) */ mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); /* * If we already saw the link up, and it hasn't gone down, then * we don't need to wait for autoneg again */ if (phydev->link && mii_reg & BMSR_LSTATUS) return 0; if ((phydev->autoneg == AUTONEG_ENABLE) && !(mii_reg & BMSR_ANEGCOMPLETE)) { int i = 0; printf("%s Waiting for PHY auto negotiation to complete", phydev->dev->name); while (!(mii_reg & BMSR_ANEGCOMPLETE)) { /* * Timeout reached ? */ if (i > PHY_ANEG_TIMEOUT) { printf(" TIMEOUT !\n"); phydev->link = 0; return -ETIMEDOUT; } if (ctrlc()) { puts("user interrupt!\n"); phydev->link = 0; return -EINTR; } if ((i++ % 10) == 0) printf("."); mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); mdelay(50); /* 50 ms */ } printf(" done\n"); phydev->link = 1; } else { /* Read the link a second time to clear the latched state */ mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); if (mii_reg & BMSR_LSTATUS) phydev->link = 1; else phydev->link = 0; } return 0; } /* * Generic function which updates the speed and duplex. If * autonegotiation is enabled, it uses the AND of the link * partner's advertised capabilities and our advertised * capabilities. If autonegotiation is disabled, we use the * appropriate bits in the control register. * * Stolen from Linux's mii.c and phy_device.c */ int genphy_parse_link(struct phy_device *phydev) { int mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); /* We're using autonegotiation */ if (phydev->autoneg == AUTONEG_ENABLE) { u32 lpa = 0; int gblpa = 0; u32 estatus = 0; /* Check for gigabit capability */ if (phydev->supported & (SUPPORTED_1000baseT_Full | SUPPORTED_1000baseT_Half)) { /* We want a list of states supported by * both PHYs in the link */ gblpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_STAT1000); if (gblpa < 0) { debug("Could not read MII_STAT1000. "); debug("Ignoring gigabit capability\n"); gblpa = 0; } gblpa &= phy_read(phydev, MDIO_DEVAD_NONE, MII_CTRL1000) << 2; } /* Set the baseline so we only have to set them * if they're different */ phydev->speed = SPEED_10; phydev->duplex = DUPLEX_HALF; /* Check the gigabit fields */ if (gblpa & (PHY_1000BTSR_1000FD | PHY_1000BTSR_1000HD)) { phydev->speed = SPEED_1000; if (gblpa & PHY_1000BTSR_1000FD) phydev->duplex = DUPLEX_FULL; /* We're done! */ return 0; } lpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE); lpa &= phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA); if (lpa & (LPA_100FULL | LPA_100HALF)) { phydev->speed = SPEED_100; if (lpa & LPA_100FULL) phydev->duplex = DUPLEX_FULL; } else if (lpa & LPA_10FULL) { phydev->duplex = DUPLEX_FULL; } /* * Extended status may indicate that the PHY supports * 1000BASE-T/X even though the 1000BASE-T registers * are missing. In this case we can't tell whether the * peer also supports it, so we only check extended * status if the 1000BASE-T registers are actually * missing. */ if ((mii_reg & BMSR_ESTATEN) && !(mii_reg & BMSR_ERCAP)) estatus = phy_read(phydev, MDIO_DEVAD_NONE, MII_ESTATUS); if (estatus & (ESTATUS_1000_XFULL | ESTATUS_1000_XHALF | ESTATUS_1000_TFULL | ESTATUS_1000_THALF)) { phydev->speed = SPEED_1000; if (estatus & (ESTATUS_1000_XFULL | ESTATUS_1000_TFULL)) phydev->duplex = DUPLEX_FULL; } } else { u32 bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); phydev->speed = SPEED_10; phydev->duplex = DUPLEX_HALF; if (bmcr & BMCR_FULLDPLX) phydev->duplex = DUPLEX_FULL; if (bmcr & BMCR_SPEED1000) phydev->speed = SPEED_1000; else if (bmcr & BMCR_SPEED100) phydev->speed = SPEED_100; } return 0; } int genphy_config(struct phy_device *phydev) { int val; u32 features; features = (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_AUI | SUPPORTED_FIBRE | SUPPORTED_BNC); /* Do we support autonegotiation? */ val = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); if (val < 0) return val; if (val & BMSR_ANEGCAPABLE) features |= SUPPORTED_Autoneg; if (val & BMSR_100FULL) features |= SUPPORTED_100baseT_Full; if (val & BMSR_100HALF) features |= SUPPORTED_100baseT_Half; if (val & BMSR_10FULL) features |= SUPPORTED_10baseT_Full; if (val & BMSR_10HALF) features |= SUPPORTED_10baseT_Half; if (val & BMSR_ESTATEN) { val = phy_read(phydev, MDIO_DEVAD_NONE, MII_ESTATUS); if (val < 0) return val; if (val & ESTATUS_1000_TFULL) features |= SUPPORTED_1000baseT_Full; if (val & ESTATUS_1000_THALF) features |= SUPPORTED_1000baseT_Half; if (val & ESTATUS_1000_XFULL) features |= SUPPORTED_1000baseX_Full; if (val & ESTATUS_1000_XHALF) features |= SUPPORTED_1000baseX_Half; } phydev->supported &= features; phydev->advertising &= features; genphy_config_aneg(phydev); return 0; } int genphy_startup(struct phy_device *phydev) { int ret; ret = genphy_update_link(phydev); if (ret) return ret; return genphy_parse_link(phydev); } int genphy_shutdown(struct phy_device *phydev) { return 0; } static struct phy_driver genphy_driver = { .uid = 0xffffffff, .mask = 0xffffffff, .name = "Generic PHY", .features = PHY_GBIT_FEATURES | SUPPORTED_MII | SUPPORTED_AUI | SUPPORTED_FIBRE | SUPPORTED_BNC, .config = genphy_config, .startup = genphy_startup, .shutdown = genphy_shutdown, }; int genphy_init(void) { return phy_register(&genphy_driver); } static LIST_HEAD(phy_drivers); int phy_init(void) { #ifdef CONFIG_NEEDS_MANUAL_RELOC /* * The pointers inside phy_drivers also needs to be updated incase of * manual reloc, without which these points to some invalid * pre reloc address and leads to invalid accesses, hangs. */ struct list_head *head = &phy_drivers; head->next = (void *)head->next + gd->reloc_off; head->prev = (void *)head->prev + gd->reloc_off; #endif #ifdef CONFIG_B53_SWITCH phy_b53_init(); #endif #ifdef CONFIG_MV88E61XX_SWITCH phy_mv88e61xx_init(); #endif #ifdef CONFIG_PHY_AQUANTIA phy_aquantia_init(); #endif #ifdef CONFIG_PHY_ATHEROS phy_atheros_init(); #endif #ifdef CONFIG_PHY_BROADCOM phy_broadcom_init(); #endif #ifdef CONFIG_PHY_CORTINA phy_cortina_init(); #endif #ifdef CONFIG_PHY_DAVICOM phy_davicom_init(); #endif #ifdef CONFIG_PHY_ET1011C phy_et1011c_init(); #endif #ifdef CONFIG_PHY_LXT phy_lxt_init(); #endif #ifdef CONFIG_PHY_MARVELL phy_marvell_init(); #endif #ifdef CONFIG_PHY_MICREL_KSZ8XXX phy_micrel_ksz8xxx_init(); #endif #ifdef CONFIG_PHY_MICREL_KSZ90X1 phy_micrel_ksz90x1_init(); #endif #ifdef CONFIG_PHY_MESON_GXL phy_meson_gxl_init(); #endif #ifdef CONFIG_PHY_NATSEMI phy_natsemi_init(); #endif #ifdef CONFIG_PHY_REALTEK phy_realtek_init(); #endif #ifdef CONFIG_PHY_SMSC phy_smsc_init(); #endif #ifdef CONFIG_PHY_TERANETICS phy_teranetics_init(); #endif #ifdef CONFIG_PHY_TI phy_ti_init(); #endif #ifdef CONFIG_PHY_VITESSE phy_vitesse_init(); #endif #ifdef CONFIG_PHY_XILINX phy_xilinx_init(); #endif #ifdef CONFIG_PHY_MSCC phy_mscc_init(); #endif #ifdef CONFIG_PHY_FIXED phy_fixed_init(); #endif #ifdef CONFIG_PHY_XILINX_GMII2RGMII phy_xilinx_gmii2rgmii_init(); #endif genphy_init(); return 0; } int phy_register(struct phy_driver *drv) { INIT_LIST_HEAD(&drv->list); list_add_tail(&drv->list, &phy_drivers); #ifdef CONFIG_NEEDS_MANUAL_RELOC if (drv->probe) drv->probe += gd->reloc_off; if (drv->config) drv->config += gd->reloc_off; if (drv->startup) drv->startup += gd->reloc_off; if (drv->shutdown) drv->shutdown += gd->reloc_off; if (drv->readext) drv->readext += gd->reloc_off; if (drv->writeext) drv->writeext += gd->reloc_off; if (drv->read_mmd) drv->read_mmd += gd->reloc_off; if (drv->write_mmd) drv->write_mmd += gd->reloc_off; #endif return 0; } int phy_set_supported(struct phy_device *phydev, u32 max_speed) { /* The default values for phydev->supported are provided by the PHY * driver "features" member, we want to reset to sane defaults first * before supporting higher speeds. */ phydev->supported &= PHY_DEFAULT_FEATURES; switch (max_speed) { default: return -ENOTSUPP; case SPEED_1000: phydev->supported |= PHY_1000BT_FEATURES; /* fall through */ case SPEED_100: phydev->supported |= PHY_100BT_FEATURES; /* fall through */ case SPEED_10: phydev->supported |= PHY_10BT_FEATURES; } return 0; } static int phy_probe(struct phy_device *phydev) { int err = 0; phydev->advertising = phydev->drv->features; phydev->supported = phydev->drv->features; phydev->mmds = phydev->drv->mmds; if (phydev->drv->probe) err = phydev->drv->probe(phydev); return err; } static struct phy_driver *generic_for_interface(phy_interface_t interface) { #ifdef CONFIG_PHYLIB_10G if (is_10g_interface(interface)) return &gen10g_driver; #endif return &genphy_driver; } static struct phy_driver *get_phy_driver(struct phy_device *phydev, phy_interface_t interface) { struct list_head *entry; int phy_id = phydev->phy_id; struct phy_driver *drv = NULL; list_for_each(entry, &phy_drivers) { drv = list_entry(entry, struct phy_driver, list); if ((drv->uid & drv->mask) == (phy_id & drv->mask)) return drv; } /* If we made it here, there's no driver for this PHY */ return generic_for_interface(interface); } static struct phy_device *phy_device_create(struct mii_dev *bus, int addr, u32 phy_id, bool is_c45, phy_interface_t interface) { struct phy_device *dev; /* * We allocate the device, and initialize the * default values */ dev = malloc(sizeof(*dev)); if (!dev) { printf("Failed to allocate PHY device for %s:%d\n", bus->name, addr); return NULL; } memset(dev, 0, sizeof(*dev)); dev->duplex = -1; dev->link = 0; dev->interface = interface; #ifdef CONFIG_DM_ETH dev->node = ofnode_null(); #endif dev->autoneg = AUTONEG_ENABLE; dev->addr = addr; dev->phy_id = phy_id; dev->is_c45 = is_c45; dev->bus = bus; dev->drv = get_phy_driver(dev, interface); if (phy_probe(dev)) { printf("%s, PHY probe failed\n", __func__); return NULL; } if (addr >= 0 && addr < PHY_MAX_ADDR) bus->phymap[addr] = dev; return dev; } /** * get_phy_id - reads the specified addr for its ID. * @bus: the target MII bus * @addr: PHY address on the MII bus * @phy_id: where to store the ID retrieved. * * Description: Reads the ID registers of the PHY at @addr on the * @bus, stores it in @phy_id and returns zero on success. */ int __weak get_phy_id(struct mii_dev *bus, int addr, int devad, u32 *phy_id) { int phy_reg; /* * Grab the bits from PHYIR1, and put them * in the upper half */ phy_reg = bus->read(bus, addr, devad, MII_PHYSID1); if (phy_reg < 0) return -EIO; *phy_id = (phy_reg & 0xffff) << 16; /* Grab the bits from PHYIR2, and put them in the lower half */ phy_reg = bus->read(bus, addr, devad, MII_PHYSID2); if (phy_reg < 0) return -EIO; *phy_id |= (phy_reg & 0xffff); return 0; } static struct phy_device *create_phy_by_mask(struct mii_dev *bus, uint phy_mask, int devad, phy_interface_t interface) { u32 phy_id = 0xffffffff; bool is_c45; while (phy_mask) { int addr = ffs(phy_mask) - 1; int r = get_phy_id(bus, addr, devad, &phy_id); /* * If the PHY ID is flat 0 we ignore it. There are C45 PHYs * that return all 0s for C22 reads (like Aquantia AQR112) and * there are C22 PHYs that return all 0s for C45 reads (like * Atheros AR8035). */ if (r == 0 && phy_id == 0) goto next; /* If the PHY ID is mostly f's, we didn't find anything */ if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) { is_c45 = (devad == MDIO_DEVAD_NONE) ? false : true; return phy_device_create(bus, addr, phy_id, is_c45, interface); } next: phy_mask &= ~(1 << addr); } return NULL; } static struct phy_device *search_for_existing_phy(struct mii_dev *bus, uint phy_mask, phy_interface_t interface) { /* If we have one, return the existing device, with new interface */ while (phy_mask) { int addr = ffs(phy_mask) - 1; if (bus->phymap[addr]) { bus->phymap[addr]->interface = interface; return bus->phymap[addr]; } phy_mask &= ~(1 << addr); } return NULL; } static struct phy_device *get_phy_device_by_mask(struct mii_dev *bus, uint phy_mask, phy_interface_t interface) { int i; struct phy_device *phydev; phydev = search_for_existing_phy(bus, phy_mask, interface); if (phydev) return phydev; /* Try Standard (ie Clause 22) access */ /* Otherwise we have to try Clause 45 */ for (i = 0; i < 5; i++) { phydev = create_phy_by_mask(bus, phy_mask, i ? i : MDIO_DEVAD_NONE, interface); if (IS_ERR(phydev)) return NULL; if (phydev) return phydev; } debug("\n%s PHY: ", bus->name); while (phy_mask) { int addr = ffs(phy_mask) - 1; debug("%d ", addr); phy_mask &= ~(1 << addr); } debug("not found\n"); return NULL; } /** * get_phy_device - reads the specified PHY device and returns its * @phy_device struct * @bus: the target MII bus * @addr: PHY address on the MII bus * * Description: Reads the ID registers of the PHY at @addr on the * @bus, then allocates and returns the phy_device to represent it. */ static struct phy_device *get_phy_device(struct mii_dev *bus, int addr, phy_interface_t interface) { return get_phy_device_by_mask(bus, 1 << addr, interface); } int phy_reset(struct phy_device *phydev) { int reg; int timeout = 500; int devad = MDIO_DEVAD_NONE; if (phydev->flags & PHY_FLAG_BROKEN_RESET) return 0; #ifdef CONFIG_PHYLIB_10G /* If it's 10G, we need to issue reset through one of the MMDs */ if (is_10g_interface(phydev->interface)) { if (!phydev->mmds) gen10g_discover_mmds(phydev); devad = ffs(phydev->mmds) - 1; } #endif if (phy_write(phydev, devad, MII_BMCR, BMCR_RESET) < 0) { debug("PHY reset failed\n"); return -1; } #ifdef CONFIG_PHY_RESET_DELAY udelay(CONFIG_PHY_RESET_DELAY); /* Intel LXT971A needs this */ #endif /* * Poll the control register for the reset bit to go to 0 (it is * auto-clearing). This should happen within 0.5 seconds per the * IEEE spec. */ reg = phy_read(phydev, devad, MII_BMCR); while ((reg & BMCR_RESET) && timeout--) { reg = phy_read(phydev, devad, MII_BMCR); if (reg < 0) { debug("PHY status read failed\n"); return -1; } udelay(1000); } if (reg & BMCR_RESET) { puts("PHY reset timed out\n"); return -1; } return 0; } int miiphy_reset(const char *devname, unsigned char addr) { struct mii_dev *bus = miiphy_get_dev_by_name(devname); struct phy_device *phydev; /* * miiphy_reset was only used on standard PHYs, so we'll fake it here. * If later code tries to connect with the right interface, this will * be corrected by get_phy_device in phy_connect() */ phydev = get_phy_device(bus, addr, PHY_INTERFACE_MODE_MII); return phy_reset(phydev); } struct phy_device *phy_find_by_mask(struct mii_dev *bus, uint phy_mask, phy_interface_t interface) { /* Reset the bus */ if (bus->reset) { bus->reset(bus); /* Wait 15ms to make sure the PHY has come out of hard reset */ mdelay(15); } return get_phy_device_by_mask(bus, phy_mask, interface); } #ifdef CONFIG_DM_ETH void phy_connect_dev(struct phy_device *phydev, struct udevice *dev) #else void phy_connect_dev(struct phy_device *phydev, struct eth_device *dev) #endif { /* Soft Reset the PHY */ phy_reset(phydev); if (phydev->dev && phydev->dev != dev) { printf("%s:%d is connected to %s. Reconnecting to %s\n", phydev->bus->name, phydev->addr, phydev->dev->name, dev->name); } phydev->dev = dev; debug("%s connected to %s\n", dev->name, phydev->drv->name); } #ifdef CONFIG_PHY_XILINX_GMII2RGMII #ifdef CONFIG_DM_ETH static struct phy_device *phy_connect_gmii2rgmii(struct mii_dev *bus, struct udevice *dev, phy_interface_t interface) #else static struct phy_device *phy_connect_gmii2rgmii(struct mii_dev *bus, struct eth_device *dev, phy_interface_t interface) #endif { struct phy_device *phydev = NULL; int sn = dev_of_offset(dev); int off; while (sn > 0) { off = fdt_node_offset_by_compatible(gd->fdt_blob, sn, "xlnx,gmii-to-rgmii-1.0"); if (off > 0) { phydev = phy_device_create(bus, off, PHY_GMII2RGMII_ID, false, interface); break; } if (off == -FDT_ERR_NOTFOUND) sn = fdt_first_subnode(gd->fdt_blob, sn); else printf("%s: Error finding compat string:%d\n", __func__, off); } return phydev; } #endif #ifdef CONFIG_PHY_FIXED #ifdef CONFIG_DM_ETH static struct phy_device *phy_connect_fixed(struct mii_dev *bus, struct udevice *dev, phy_interface_t interface) #else static struct phy_device *phy_connect_fixed(struct mii_dev *bus, struct eth_device *dev, phy_interface_t interface) #endif { struct phy_device *phydev = NULL; int sn; const char *name; sn = fdt_first_subnode(gd->fdt_blob, dev_of_offset(dev)); while (sn > 0) { name = fdt_get_name(gd->fdt_blob, sn, NULL); if (name && strcmp(name, "fixed-link") == 0) { phydev = phy_device_create(bus, sn, PHY_FIXED_ID, false, interface); break; } sn = fdt_next_subnode(gd->fdt_blob, sn); } return phydev; } #endif #ifdef CONFIG_DM_ETH struct phy_device *phy_connect(struct mii_dev *bus, int addr, struct udevice *dev, phy_interface_t interface) #else struct phy_device *phy_connect(struct mii_dev *bus, int addr, struct eth_device *dev, phy_interface_t interface) #endif { struct phy_device *phydev = NULL; uint mask = (addr >= 0) ? (1 << addr) : 0xffffffff; #ifdef CONFIG_PHY_FIXED phydev = phy_connect_fixed(bus, dev, interface); #endif #ifdef CONFIG_PHY_XILINX_GMII2RGMII if (!phydev) phydev = phy_connect_gmii2rgmii(bus, dev, interface); #endif if (!phydev) phydev = phy_find_by_mask(bus, mask, interface); /***********zuozhongkai add 2021/4/23****************/ if (!phydev) /* 如果还没有获取到phy_device,尝试YT8511 */ { addr = 0; mask = (addr >= 0) ? (1 << addr) : 0xffffffff; phydev = phy_find_by_mask(bus, mask, interface); } /******************end add****************************/ if (phydev) phy_connect_dev(phydev, dev); else printf("Could not get PHY for %s: addr %d\n", bus->name, addr); return phydev; } /* * Start the PHY. Returns 0 on success, or a negative error code. */ int phy_startup(struct phy_device *phydev) { if (phydev->drv->startup) return phydev->drv->startup(phydev); return 0; } __weak int board_phy_config(struct phy_device *phydev) { if (phydev->drv->config) return phydev->drv->config(phydev); return 0; } int phy_config(struct phy_device *phydev) { /* Invoke an optional board-specific helper */ return board_phy_config(phydev); } int phy_shutdown(struct phy_device *phydev) { if (phydev->drv->shutdown) phydev->drv->shutdown(phydev); return 0; } int phy_get_interface_by_name(const char *str) { int i; for (i = 0; i < PHY_INTERFACE_MODE_COUNT; i++) { if (!strcmp(str, phy_interface_strings[i])) return i; } return -1; }
注意
注意,正点原子 V1.2 版本核心板上使用的 RTL8211,其 PHY 地址为 0X01。V1.3 以后的核心板使用 YT8511,其 PHY 地址为 0X00,因此在设备树里面需要修改 PHY 地址为 0X00。但是这样修改以后会导致 YT8511 和 RTL8211 的设备树不兼容,需要多编写一份设备树,导致出厂设备树过多。因此为了一个设备树兼容 YT8511 和 RTL8211,正点原子团队修改了 phy.c 文件中的 phy_connect 函数,直接给定 YT8511 的地址为 0X00,也就是不受设备树控制,这种做法虽然不合规则,但是也是为了一个设备树兼容两个地址不同的 PHY 芯片而不得已采取的方法。
给 uboot 添加 USB_OTG 功能,操作 stm32mp157d-atk.dtsi 这个文件,在根节点“/”下添加名为“usb_phy_tuning”的子节点,节点内容如下:
usb_phy_tuning: usb-phy-tuning { st,hs-dc-level = <2>; st,fs-rftime-tuning; st,hs-rftime-reduction; st,hs-current-trim = <15>; st,hs-impedance-trim = <1>; st,squelch-level = <3>; st,hs-rx-offset = <2>; st,no-lsfs-sc; };
正点原子 STM32MP157 开发板上的 USB OTG 接口类型为 Type-C,使用 STUSB1600 芯片来实现此接口功能,STUSB1600 有一个 I2C 接口,此 I2C 接口用来配置芯片,因此我们还需要在设备树中添加 STUSB1600 相关的 I2C 节点内容。将如下内容添加到 stm32mp157d-atk.dtsi 的最后面:
&i2c1 { pinctrl-names = "default", "sleep"; pinctrl-0 = <&i2c1_pins_a>; pinctrl-1 = <&i2c1_pins_sleep_a>; i2c-scl-rising-time-ns = <100>; i2c-scl-falling-time-ns = <7>; status = "okay"; /delete-property/dmas; /delete-property/dma-names; stusb1600@28 { compatible = "st,stusb1600"; reg = <0x28>; interrupts = <2 IRQ_TYPE_EDGE_FALLING>; interrupt-parent = <&gpiog>; pinctrl-names = "default"; pinctrl-0 = <&stusb1600_pins_a>; status = "okay"; vdd-supply = <&vin>; connector { compatible = "usb-c-connector"; label = "USB-C"; power-role = "dual"; power-opmode = "default"; port { con_usbotg_hs_ep: endpoint { remote-endpoint = <&usbotg_hs_ep>; }; }; }; }; };
由于 STUSB1600 是挂在 STM32MP157 的 I2C1 接口下,因此示例代码 13.2.7.2 是向 I2C1节点追加内容。
继续向 stm32mp157d-atk.dtsi 文件添加 USB 接口相关节点内容,内容如下:
&usbh_ehci { phys = <&usbphyc_port0>; status = "okay"; }; &usbotg_hs { phys = <&usbphyc_port1 0>; phy-names = "usb2-phy"; usb-role-switch; status = "okay"; port { usbotg_hs_ep: endpoint { remote-endpoint = <&con_usbotg_hs_ep>; }; }; }; &usbphyc { status = "okay"; };
上述代码中有三个节点 usbh_ehci、usbotg_hs 和 usbphyc,其中 usbotg_hs 默认就有。
最后,我们需要在 stm32mp157d-atk-u-boot.dtsi 文件里面197行添加 usbotg_hs 节点,节点内容如下所示:
&usbotg_hs { u-boot,force-b-session-valid; hnp-srp-disable; /* TEMP: force peripheral for USB OTG */ dr_mode = "peripheral"; };
重新编译 uboot 并烧写,然后使用 ums 命令测试,看看能不能将 EMMC 模拟成 U 盘,挂载到电脑上,命令如下:
ums 0 mmc 1
ST 官方 uboot 默认并没有使能 boot 和 bootd 这两个命令,这两个命令的实现源文件为 cmd/bootm.c,bootm.c, 如 果 要 使 能 boot 和 bootd 这 两 个 命 令 , 必 须 定 义 宏CONFIG_CMD_BOOT。打开 include/configs/stm32mp1.h,然后在后面259行添加如下宏定义:
#define CONFIG_CMD_BOOTD /* 使能 boot 和 bootd 命令 */
uboot 也是支持 LCD 显示的,但是要进行相应的设置,主要是设置屏幕背光、屏幕时序参数这些,这些直接在设备树里面修改即可。打开 stm32mp157d-atk.dts 文件,在里面添加 LCD 相关节点信息,首先在根节点“/”下添加 panel_backlight 和 panel_rgb 这两个节点,节点内容如下:
panel_backlight: panel-backlight { compatible = "gpio-backlight"; gpios = <&gpiod 13 GPIO_ACTIVE_HIGH>; default-on; status = "okay"; }; panel_rgb: panel-rgb { compatible = "simple-panel"; pinctrl-names = "default", "sleep"; pinctrl-0 = <<dc_pins_b>; pinctrl-1 = <<dc_pins_sleep_b>; backlight = <&panel_backlight>; status = "okay"; port { panel_in_rgb: endpoint { remote-endpoint = <<dc_ep0_out>; }; }; display-timings { native-mode = <&timing0>; /* 时序信息 */ timing0: timing0 { /* 7 寸 1024*600 分辨率 */ clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */ hactive = <1024>; /* LCD X 轴像素个数 */ vactive = <600>; /* LCD Y 轴像素个数 */ hfront-porch = <160>; /* LCD hfp 参数 */ hback-porch = <140>; /* LCD hbp 参数 */ hsync-len = <20>; /* LCD hspw 参数 */ vback-porch = <20>; /* LCD vbp 参数 */ vfront-porch = <12>; /* LCD vfp 参数 */ vsync-len = <3>; /* LCD vspw 参数 */ }; }; };
第 1~6 行,panel_backlight 为 LCD 的背光控制节点,主要指定 LCD 背光 IO 所使用的引脚,正点原子的 STM32MP157 开发板 LCD 背光引脚为 PD13。
第 8~38 行,panel_rgb 为 RGB LCD 节点,指定了 LTDC 接口所使用的 IO、屏幕时序参数等。
第 1112 行指定 LTDC 接口的 IO,ltdc_pins_b 和 ltdc_pins_sleep_b 定义在 stm32mp15-pinctrl.dtsi 文件中。第 2235 行的 display-timings 是非常重要的 LCD 时序参数,不同的屏幕其时序参数不同,这里演示的是正点原子 7 寸 1024600 分辨率的。正点原子有 4 款 RGB 接口屏幕,分别为 4.3 寸 480272 和 800480 分辨率、7 寸 800480 和 1024*600 分辨率,这款屏幕时序参数如表 13.2.9.1 所示:
屏幕型号 | 参数 | 值 | 单位 |
---|---|---|---|
ATK4342 | 水平显示区域 | 480 | tCLK |
ATK4342 | HSPW(thp) | 1 | tCLK |
ATK4342 | HBP(thb) | 40 | tCLK |
ATK4342 | HFP(thf) | 5 | tCLK |
ATK4342 | 垂直显示区域 | 272 | th |
ATK4342 | VSPW(tvp) | 1 | th |
ATK4342 | VBP(tvb) | 8 | th |
ATK4342 | VFP(tvf) | 8 | th |
ATK4342 | 像素时钟 | 9 | MHz |
ATK4384 | 水平显示区域 | 800 | tCLK |
ATK4384 | HSPW(thp) | 48 | |
ATK4384 | HBP(thb) | 88 | tCLK |
ATK4384 | HFP(thf) | 40 | tCLK |
ATK4384 | 垂直显示区域 | 480 | th |
ATK4384 | VSPW(tvp) | 3 | th |
ATK4384 | VBP(tvb) | 32 | th |
ATK4384 | VFP(tvf) | 13 | th |
ATK4384 | 像素时钟 | 31 | MHz |
ATK7084 | 水平显示区域 | 800 | tCLK |
ATK7084 | HSPW(thp) | 1 | tCLK |
ATK7084 | HBP(thb) | 46 | tCLK |
ATK7084 | HFP(thf) | 210 | tCLK |
ATK7084 | 垂直显示区域 | 480 | th |
ATK7084 | VSPW(tvp) | 1 | th |
ATK7084 | VBP(tvb) | 23 | th |
ATK7084 | VFP(tvf) | 22 | th |
ATK7084 | 像素时钟 | 33.3 | MHz |
ATK7016 | 水平显示区域 | 1024 | tCLK |
ATK7016 | HSPW(thp) | 20 | tCLK |
ATK7016 | HBP(thb) | 140 | tCLK |
ATK7016 | HFP(thf) | 160 | tCLK |
ATK7016 | 垂直显示区域 | 600 | th |
ATK7016 | VSPW(tvp) | 3 | th |
ATK7016 | VBP(tvb) | 20 | th |
ATK7016 | VFP(tvf) | 12 | th |
ATK7016 | 像素时钟 | 51.2 | MHz |
正点原子其他 3 款 RGB 屏幕的时序参数如下所示:
/* 4.3 寸 480*272 分辨率 */ display-timings { native-mode = <&timing0>; /* 时序信息 */ timing0: timing0 { clock-frequency = <9200000>; /* LCD 像素时钟,单位 Hz */ hactive = <480>; /* LCD X 轴像素个数 */ vactive = <272>; /* LCD Y 轴像素个数 */ hfront-porch = <8>; /* LCD hfp 参数 */ hback-porch = <4>; /* LCD hbp 参数 */ hsync-len = <41>; /* LCD hspw 参数 */ vback-porch = <2>; /* LCD vbp 参数 */ vfront-porch = <4>; /* LCD vfp 参数 */ vsync-len = <10>; /* LCD vspw 参数 */ }; } /* 4.3 寸 800*480 分辨率 */ display-timings { native-mode = <&timing0>; /* 时序信息 */ timing0: timing0 { clock-frequency = <31000000>; /* LCD 像素时钟,单位 Hz */ hactive = <800>; /* LCD X 轴像素个数 */ vactive = <480>; /* LCD Y 轴像素个数 */ hfront-porch = <40>; /* LCD hfp 参数 */ hback-porch = <88>; /* LCD hbp 参数 */ hsync-len = <48>; /* LCD hspw 参数 */ vback-porch = <32>; /* LCD vbp 参数 */ vfront-porch = <13>; /* LCD vfp 参数 */ vsync-len = <3>; /* LCD vspw 参数 */ }; } /* 7 寸 800*480 分辨率 */ display-timings { native-mode = <&timing0>; /* 时序信息 */ timing0: timing0 { clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */ hactive = <800>; /* LCD X 轴像素个数 */ vactive = <480>; /* LCD Y 轴像素个数 */ hfront-porch = <210>; /* LCD hfp 参数 */ hback-porch = <46>; /* LCD hbp 参数 */ hsync-len = <1>; /* LCD hspw 参数 */ vback-porch = <23>; /* LCD vbp 参数 */ vfront-porch = <22>; /* LCD vfp 参数 */ vsync-len = <1>; /* LCD vspw 参数 */ }; }
最后还需要在 stm32mp157d-atk.dts 文件里面向 ltdc 节点追加一些内容,内容如下:
<dc { status = "okay"; pinctrl-names = "default"; port { #address-cells = <1>; #size-cells = <0>; ltdc_ep0_out: endpoint@0 { reg = <0>; remote-endpoint = <&panel_in_rgb>; }; }; };
以前编译 uboot 都是自己输入一条一条命令编译,我们可以创建一个 shell 脚 本,将所有的编译命令都写到这个 shell 脚本里面,然后每次的时候只需要执行一下这个 shell脚本即可。在 uboot 源码根目录下新建一个名为 tm32mp157d_alientek.sh 的 shell 脚本,在这个 shell 脚本里面输入如下内容:
1 #!/bin/bash 2 3 make distclean 4 make stm32mp15_atk_trusted_defconfig 5 make DEVICE_TREE=stm32mp157d-atk all -j12
第 3 行清零 uboot。
第 4 行使用 stm32mp15_atk_trusted_defconfig 来配置 uboot。
第 5 行编译 uboot,设备树为 stm32mp157d-atk.dts。
给予 stm32mp157d_alientek.sh 可执行权限,然后运行脚本来完成编译,命令如下:
chmod 777 stm32mp157d_alientek.sh //给予可执行权限,一次即可 ./stm32mp157d_alientek.sh //运行脚本编译 uboot
在编译 uboot 之 前 要 先 让 编 译 器 知 道 我 们 要 编 译 哪 个 设 备 树 文 件 , 打 开arch/arm/dts/Makefile 文件,找到“dtb-$(CONFIG_STM32MP15x)”配置项,然后在此配置项中 加入“stm32mp157d-atk.dtb”。
“ethernet@5800a00 address not set.”说明已经找到了网络外设并且网络外设已经启动,但是还是会报“No ethernet found.”错误,这是因为 uboot 启动的时候获取不到 MAC 地址,我们只需要设置一些地址相关的环境变量即可,前面讲解 uboot 的网络相关命令的时候已经讲过了,命令如下:
setenv ipaddr 192.168.1.250 //开发板 IP 地址 setenv ethaddr 00:04:9f:04:d2:35 //开发板网卡 MAC 地址 setenv gatewayip 192.168.1.1 //开发板默认网关 setenv netmask 255.255.255.0 //开发板子网掩码 setenv serverip 192.168.1.249 //服务器地址,也就是 Ubuntu 地址 saveenv
我们可以使用uboot下的 bmp 命令在 LCD 上显示一张bmp图片。
bmp info <imageAddr>
bmp info 命令用于显示 BMP 图片信息,imageAddr 就是 BMP 图片在 RAM 中的起始地址。
bmp display 命令用于显示 bmp 图片,imageAddr 是要显示的 BMP 图片在 RAM 中的起始 地址,用于指定 BMP 图片左上角在屏幕上的显示坐标。
首先将要显示的 BMP 图片放到 ubuntu 的 TFTP 服务器目录下,我们通过网络将 BMP 图片下载到板子的 DDR 中,然后再用bmp 命令显示。命令如下:
tftp c0000000 test.bmp //下载 bmp 图片 bmp info c0000000 //显示图片信息
输入如下命令在 LCD 上显示图片:
bmp display c0000000 0 0 //显示 bmp 图片
bootcmd 保存着 uboot 默认命令,uboot 倒计时结束以后就会执行 bootcmd 中的命令。这些命令一般都是用来启动 Linux 内核的,比如读取 EMMC 或者 NAND Flash 中的 Linux 内核镜像文件和设备树文件到 DRAM 中,然后启动 Linux 内核。可以在 uboot 启动以后进入命令行设置 bootcmd 环境变量的值。如果 EMMC 或者 NAND 中没有保存 bootcmd 的值,那么 uboot 就会使用默认的值,板子第一次运行 uboot 的时候都会使用默认值来设置 bootcmd 环境变量。在文件 include/env_default.h中。embedded_environment 是个 env_t 类型的变量,保存在 environment 段里面,env_t 结构体中的 crc 为 CRC 值,flags 是标志位,data 数组就是环境变量值。,比如 bootcmd 的默认值就是 CONFIG_BOOTCOMMAND,bootargs 的默认值就是 CONFIG_BOOTARGS。我们可以直接在 stm32mp1.h 文件中通过设置宏CONFIG_BOOTCOMMAND 来设置 bootcmd 的默认值。
bootargs 保存着 uboot 传递给 Linux 内核的参数,比如指定 Linux 内核所使用的 console、指定根文件系统所在的分区等,如下面 bootargs 环境变量值:
console=ttySTM0,115200 root=/dev/mmcblk2p3 rootwait rw
console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互,是串口还是 LCD 屏幕?如果是串口的话应该是串口几等等。一般设置串口作为 Linux 终端,这样我们就可以在电脑上通过 MobaXterm 来和 linux 交互了。这里设置 console 为 ttySTM0,因为linux 启动以后 STM32MP1 的串口 4 在 linux 下的设备文件就是/dev/ttySTM0,在 Linux 下,一切皆文件。ttySTM0 后面有个“,115200”,这是设置串口的波特率,console=ttySTM0,115200 综合起来就是设置 ttySTM0(也就是串口 4)作为 Linux 的终端,并且串口波特率设置为 115200。
root 用来设置根文件系统的位置,root=/dev/mmcblk2p3 用于指明根文件系统存放在mmcblk2 设备的分区 3 中。正点原子的 STM32MP1 核心板启动 linux 以后会存在/dev/mmcblk1、/dev/mmcblk2 、 /dev/mmcblk1p1 、 /dev/mmcblk1p2 、 /dev/mmcblk2p1 、 /dev/mmcblk2p2 和/dev/mmcblk2p3 这 样 的 文 件 , 其 中 /dev/mmcblkx(x=0n) 表 示 mmc 设备,而/dev/mmcblkxpy(x=0n,y=1~n)表示 mmc 设备 x 的分区 y。在 STM32MP1 开发板中/dev/mmcblk2表示 EMMC,而/dev/mmcblk2p3 表示 EMMC 的分区 3。
root 后面有“rootwait rw”,rootwait 表示等待 mmc 设备初始化完成以后再挂载,否则的话mmc 设备还没初始化完成就挂载根文件系统会出错的。rw 表示根文件系统是可以读写的,不加rw 的话可能无法在根文件系统中进行写操作,只能进行读操作。
此选项一般配合 root 一起使用,rootfstype 用于指定根文件系统类型,如果根文件系统为ext 格式的话此选项无所谓。如果根文件系统是 yaffs、jffs 或 ubifs 的话就需要设置此选项,指定根文件系统的类型。
在打开图形化配置界面之前,要先使用“make xxx_defconfig”对 uboot 进行一次默认配置,只需要一次即可。如果使用“make clean”清理了工程的话就那就需要重新使用“makexxx_defconfig”再对 uboot 进行一次配置。进入 uboot 根目录,输入如下命令:
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp15_atk_trusted_d efconfig make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig
如果已经在 uboot 的顶层 Makefile 中定义了 ARCH 和 CROSS_COMPILE 的值,那么上述 命令可以简化为:
make stm32mp15_atk_trusted_defconfig make menuconfig
主界面上方的英文就是简单的操作说明,操作方法如下:
通过键盘上的“↑”和“↓”键来选择要配置的菜单,按下“Enter”键进入子菜单。菜单中高亮的字母就是此菜单的热键,在键盘上按下此高亮字母对应的键可以快速选中对应的菜单。选中子菜单以后按下“Y”键就会将相应的代码编译进 Uboot 中,菜单前面变为“[*]。按下“N”键不编译相应的代码,按下“M”键就会将相应的代码编译为模块,菜单前面变为“[M ]”。按两下“Esc”键退出,也就是返回到上一级,按下“?”键查看此菜单的帮助信息,按下“/”键打开搜索框,可以在搜索框输入要搜索的内容。在配置界面下方会有五个按钮,这五个按钮的功能如下:
完成后使用如下命令编译 uboot:
make DEVICE_TREE=stm32mp157d-atk all -j12
千万不能使用如下命令:
./stm32mp157d_alientek.sh
因为 stm32mp157d_alientek.sh 在编译之前会清理工程,会删除掉.config 文件!通过图形化 界面配置所有配置项都会被删除。
本文作者:古月流新
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!