概述
从这边文章开始,我们真正进入到RK3506 Linux的系统定制开发,在讲述的过程中,我会将驱动和应用结合在一起,RK3506 Linux的驱动定制步骤,对应驱动的应用开发步骤和行业内的应用案例。
本文为RK3506 Linux的串口驱动的定制部分,从linux-3.10开始。arm/arm64平台上的驱动开发,由原来的mach-xxx下的硬编码变为了设备树文件配置的形式。不管是硬编码还是设备树文件的形式,都是用来描述我们板子的硬件拓扑结构。
需要提前掌握的知识
1、串口控制器,如果你之前接触过单片机的串口控制器的话,阅读本文就更容易;
2、串口协议中的TTL RS232和RS485的区别;
3、linux 设备树文件的概念;
linux设备树的组成
我们这里讲述RK3506平台上linux设备树文件的组成形式。关于设备树的语法,我们在讲述的过程中涉及到,不再单独的讲述,其实最好的学习方法就是不断地练习。我们也需要明白一点,虽然现在linux 内核下有大量的设备驱动程序,平时我们调试设备驱动时,大部分只需要修改设备树文件就可以完成工作,但是设备树不是驱动,最终驱动还是由drivers/目录下的程序实现。
RK3506 Linux的设备树文件在kernel/arch/arm/boot/dts/下,比如我们上一章编译的rockchip_rk3506_g_evb2_defconfig平台
RK_ROOTFS_UBI=yRK_ROOTFS_INSTALL_MODULES=yRK_WIFIBT_CHIP="AP6256"# RK_ROOTFS_LOG_GUARDIAN is not setRK_UBOOT_CFG_FRAGMENTS="rk3506_tb"RK_UBOOT_SPL=yRK_KERNEL_CFG="rk3506_defconfig"RK_KERNEL_CFG_FRAGMENTS="rk3506-display.config"RK_KERNEL_DTS_NAME="rk3506g-evb2-v10"RK_BOOT_COMPRESSED=yRK_BOOT_FIT_ITS_NAME="thunderboot.its"RK_RECOVERY_FIT_ITS_NAME="thunderboot4recovery.its"RK_FLASH_SIZE=2048RK_EXTRA_PARTITION_1_SRC="rk3506_oem"RK_PARAMETER="parameter-128M.txt"RK_USE_FIT_IMG=y
使用的设备树文件是rk3506g-evb2-v10如下:// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2025 Rockchip Electronics Co., Ltd. *//dts-v1/; -------------------------- (1)#include"rk3506.dtsi" ------------------------(2)#include"rk3506-evb2-v10.dtsi" ------------------------(3)#include"rk3506-rk816.dtsi" ------------------------(4)/ { ------------------------(5) model = "Rockchip RK3506G(QFN128) EVB2 V10 Board"; compatible = "rockchip,rk3506g-evb2-v10", "rockchip,rk3506";};
设备树文件语法也采用了类似C的文件包含,将设备树文件的公共部分进行抽象存在到.dtsi的文件中,比如,所有的RK3506设备树文件都包含了CPU部分的dtsi文件。
(1):设备树文件的语法版本,在使用dtc(设备树文件编译器,编译后生成dtb格式)编译设备树文件时,通过该字段识别为V1的设备树文件语法格式;
(2):设备树文件公共部分包含,这里是RK3506 CPU相关的;
(3):这个是RK EVB板子的设备树文件公共部分,我们在日常开发时,会参考这个文件中的设备节点的编写;
(4):这是使用电源管理芯片时需要包含的设备树文件;
(5):设备树文件的根节点,设备树书文件从根节点开始,在它的下面会挂载子节点,子节点存放描述子节点属性的信息,比如rk3506.dtsi文件中:
/ { compatible = "rockchip,rk3506"; ...... display_subsystem: display-subsystem { ...... route { route_dsi: route-dsi { status = "disabled"; logo,uboot = "logo.bmp"; logo,kernel = "logo_kernel.bmp"; logo,mode = "center"; charge_logo,mode = "center";connect = <&vop_out_dsi>; }; route_rgb: route-rgb { status = "disabled"; logo,uboot = "logo.bmp"; logo,kernel = "logo_kernel.bmp"; logo,mode = "center"; charge_logo,mode = "center";connect = <&vop_out_rgb>; }; }; };
根节点中会挂载display_subsystem子节点,然后在display_subsystem子节点中会挂载route_dsi,route_dsi节点中存放该节点的属性信息;
RK3506 UART设备树节点解析
在RK3506 平台中。UART节点存放在rk3502.dtsi文件中,为了简化,我只保留了一个节点,如下所示:
/{ uart3: serial@ff0d0000 { -------------------------------------(1) compatible = "rockchip,rk3506-uart", "snps,dw-apb-uart"; -------(2) reg = <0xff0d0000 0x100>; --------------------(3) interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>; -----------------(4) reg-shift = <2>; ----------------------------------------------(5) reg-io-width = <4>; --------------------------------------------(6) dmas = <&dmac0 100xff2880ac0x00300010 0x0 0x0>,, <&dmac0 110xff2880ac0x00c00040 0x0 0x0>;; --------------(7) clocks = <&cru SCLK_UART3>, <&cru PCLK_UART3>; --------------(8) clock-names = "baudclk", "apb_pclk"; ----------------(9) status = "disabled"; ----------------(10) };}
(1): serial@ff0b0000 uart3的硬件设备控制器的寄存器地址;
(2):compatible: 这个字段描述该设备节点对应的设备驱动程序,这里有一个小技巧,给你一个设备树节点信息,可以通过compatible字段的信息,定位到使用的设备驱动源码,比如,compatible = "rockchip,rk3506-uart", "snps,dw-apb-uart"; 这个 字段有两个信息,优先匹配“rockchip,rk3506-uart”,如果这个信息没有匹配到,其次匹配“snps,dw-apb-uart”;
我们可以定位到使用的驱动源码是kernel/drivers/tty/serial/8250/8250_dw.c文件,如果开启early打印的话,或加载kernel/drivers/tty/serial/8250/8250_early.c这个驱动;
(3): reg = <0xff0b0000 0x100>第一个参数指定了寄存器的起始地址0xff0b0000,0x100指定了寄存器空间的大小;
(4)interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; 指定了uart1控制器使用的gic中断控制器中的SPI中断类型,硬件中断号是35,中断触发方式为高电平触发(感兴趣的共同学可以阅读RK3506 的gic中断控制器手册获取gic的SPI中断的特性);
(7)如果使用dma传输方式的话,dma传输使用的通道信息;
(8):uart3设备节点使用的时钟源;
(9):status 这个设备节点的状态,disabled 表示为禁止状态,okay: 表示使能状态;
从uart1设备节点中,我们知道了一个硬件设备控制器关键的几项信息,比如寄存器地址空间,中断源,时钟源,如果使用dma的话,使用的dma通道等。
RK3506 UART设备节点的配置
上面,我们讲述了RK3506 串口控制器节点的信息,这章我们讲述如何配置uart设备节点;在实际的uart设备树节点修改中,我们大部分只关注两个信息:
1、uart控制器的引脚配置,因为RK3506的CPU中大部分引脚都存在引脚复用的情况,也就是说,一个引脚有好几个功能,在驱动加载初始化的时候,确定该引脚的功能。我们在调试驱动时,需要跟硬件工程师打交道,需要学会阅读硬件原理图,比如如下的使用uart3的引脚硬件原理图:
从硬件原理图中,我们获得UART3_TX = GPIO0_B7; UART_TX使用了GPIO0中的B7引脚,UART3_RX = GPIO0_B6; UART_RX使用了GPIO0中的B6引脚; 因此我们的uart3控制器节点中需要添加引脚配置信息如下:
&uart3 { pinctrl-0 = <&rm_io14_uart3_tx &rm_io15_uart3_rx>; ------------- (1) status = "okay"; ------------- (2)};
将引脚配置信息添加到uart3设备节点的属性信息中。大家从之前的uart3设备节点信息描述中也发现,节点不包含引脚配置信息。因为在不同的项目上,硬件设计可能使用不同的引脚,因此引脚配置信息并不包含在公共设备树文件中,因此我们将设置节点的引脚配置信息放入到rk3506g-evb2-v10.dts文件中,如下所示:// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2025 Rockchip Electronics Co., Ltd. *//dts-v1/; -------------------------- (1)#include"rk3506.dtsi" ------------------------(2)#include"rk3506-evb2-v10.dtsi" ------------------------(3)#include"rk3506-rk816.dtsi" ------------------------(4)/ { ------------------------(5) model = "Rockchip RK3506G(QFN128) EVB2 V10 Board"; compatible = "rockchip,rk3506g-evb2-v10", "rockchip,rk3506";};&uart3 { -----------------------------------------------------------(1) pinctrl-names = "default"; pinctrl-0 = <&rm_io14_uart3_tx &rm_io15_uart3_tx>; status = "okay"; };
(1)如果设备树的节点信息放到根节点外面,则需要加&节点地址引用。我们还可以使用另外一种方式,如下所示:// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2025 Rockchip Electronics Co., Ltd. *//dts-v1/; -------------------------- (1)#include"rk3506.dtsi" ------------------------(2)#include"rk3506-evb2-v10.dtsi" ------------------------(3)#include"rk3506-rk816.dtsi" ------------------------(4)/ { ------------------------(5) model = "Rockchip RK3506G(QFN128) EVB2 V10 Board"; compatible = "rockchip,rk3506g-evb2-v10", "rockchip,rk3506"; uart3 { -----------------------------------------------------------(1) pinctrl-names = "default"; pinctrl-0 = <&rm_io14_uart3_tx &rm_io15_uart3_rx>; status = "okay"; };};
1)我们将uart3存放到根节点的下面,为什么我们可以存放到根节点的下面,感兴趣的同学可以动手使验一下,我们再扩展一下,是不是,所有节点,都可以直接放置到根节点下面呢。大家思考一下;
设备树文件的编译
当我们编辑好设备树文件后,我们需要使用dtc编译器,将设备树文件编译成dtb文件。如果之前没有安装过dtc工具的话,可以通过如下的命令进行安装:
sudo apt install device-tree-compiler
dtc工具除了可以将dts文件编译为dtb外,也可以将dtb文件反编译为dts文件
dtc将dts文件编译为dtb的命令:
dtc -I dts -O dtb -o output.dtbinput.dts
dtc -I dtb -O dts -o output.dtsinput.dtb
dtc参数说明:
-I dts 输入格式为 dts
-O dtb 输出二进制 dtb
-o xxx 指定输出文件名字
当然在RK3506 Linux SDK已经集成了dtb的编译命令,我们在实际开发时,并不需要手动使用dtc进行设备树的编译;
UART串口驱动确认
上面我们描述了在RK3506 Linux平台上uart驱动调试的步骤,当我们将修改的设备树文件进行编译烧写后,我们可以通过如下的内核信息来确认,我们修改的驱动是否生效:
在RK3506 Linux平台上,串口的默认名字都是以ttyS开头的。
UART驱动的测试
当我们的驱动被加载成功后,我们可以通过如下的步骤,进行简单的测试。
1、硬件设置,对于TTL和RS232来说,我们可以将引脚RX和TX进行短接,形成一个信号环路,但是对于RS485来说,就必须的接另外一路485了,不能将A和B短接;
2、设置待测试串口的信息
stty -F /dev/ttyS3 raw speed 9600 -echo min 0 time 155
cat /dev/ttyS3 &echo 12345 > /dev/ttyS3
注:
当上述的驱动加载都正常时,发现收不到数据时,请检查引脚复用是否存在冲突;
总结
本文描述了RK3506平台上UART驱动的配置步骤,以及驱动的简单测试,下一章,讲述UART的应用编程框架;