1 Linux platform子系统
在Linux 2.6内核中,提出了总线、设备、驱动的架构,目的是让我们写出来的驱动通用性更强。
arm核内部总线结构:
[图片:soc内部总线结构]
1.1 核心思想
-
将设备的信息从驱动中分离出来,我们需要在操作系统中,添加设备和驱动两部分。
-
设备中包含是设备的信息(资源),驱动中包含的是操作设备函数接口。
-
为了能让驱动最终能操作我们的硬件设备,我们在驱动中必须获取设备的信息(资源)。驱动是如何获取具体设备信息呢?
-
设备和驱动都会注册到总线上,当注册设备的时候,会去寻找同名的驱动,当注册驱动的时候,也会去找同名的设备。相互查找。一旦匹配成功,操作系统就会自动调用驱动提供的probe函数。我们只需要在probe函数中,使用操作系统提供的通用API获取硬件的资源即可。
[图片:设备注册时查找过程]
1.2 Linux总线的理解
- 总线在操作系统中本质就是两个链表: 挂载设备的链表和挂载驱动的链表。
- 在操作系统中总线种类可以分成两大类:
- 平台总线:平台总线挂载都是控制器设备,用于CPU核与硬件控制器之间的通信。在Linux系统中用"platform bus"表示。
- 边缘设备之间通信的总线:边缘设备之间通信的总线挂载是符合总线时序的外围设备如:i2c , spi ,usb , uart 等。不同的边缘设备之间通信的总线,总线时序是不一样的,
- 对于这些总线,Linux 内核是单独实现的
1.3 基于总线写驱动的思路
- 根据自己的设备,确定总线的类型
- 根据总线的类型, 确定设备在总线上如何描述
- 根据总线的类型, 确定驱动在总线上如何描述
- 根据总线的类型,确定在总线上如何注册设备
- 根据总线的类型,确定在总线上如何注册驱动
- 根据总线的类型, 确定设备和驱动匹配原则
设备和驱动匹配后,操作系统就会调用驱动提供的probe
函数。在这个函数中,一般需要做两件事情:
- (1)获取匹配的硬件资源
- (2)向上层提供硬件设备的操作函数接口(如:注册字符设备)
案例代码:
c
todo
2 Linux设备树
-
Linux内核在启动的时候,要求把设备树文件传递给它。它拿到设备树之后,会解析设备树文件,从而识别设备信息
-
因为设备的信息是针对于特定平台的,如果我们在Linux内核中包含太多设备信息,则Linux内核移植性就会变差。引入设备树之后,设备的信息的描述不再在是以代码的形式存在于Linux内核源代码中
-
dtc,device tree compiler,是将.dts 编译为.dtb需要用到的**编译工具,**是编译设备树的小工具
-
dts,device tree source,是设备树**源码文件,**是描述硬件信息的asiII文本文件
-
dtsi,device tree source include,是设备树源码文件要用到的头文件,类似头文件,描述平台共性
-
dtb,device tree binary,是将DTS 编译以后得到的二进制文件是编译后的二进制文件,可被bootloader/kernel识别并解析
一般开发的时候,把这些设备树文件拷贝出来,方便查找:
imx6ull-14x14-smartcar.dts
:外围设备相关设备树文件imx6ull.dtsi
:芯片相关设备树文件
将dtb文件反编译成dts文件 :C.将test.dtb文件反编译成test.dts文件:dtc -O dts -I dtb -o test.dts test.dtb
2.1 设备树语法规则
一个节点就是用来描述一个设备的信息,每个节点必须有一个<名称>[@<设备地址>]
形式的名字。
- 1、名称就是一个ascii字符串,节点的命名应该根据设备的功能来命名。例如一个3com以太网适配器的节点就应该命名为ethernet,而不应该是3com509
- 2、如果存在
reg
属性:则设备地址
是reg属性的第一个数字。 - 3、每个设备的节点都应该有一个compatible属性
2.2 节点属性
属性有两种格式:
- 格式1(没有值) : property-name
- 格式2(键值对) : property-name = value
2.3 添加设备树节点
在设备树文件的首节点的最后一个大括号之前添加新节点: 每个设备树节点必须要有compatible属性。compatible用于与驱动匹配,reg表示寄存器地址和大小。
c
};
// 在首节点的最后一个大括号之前添加新节点。节点名称格式为:名称@reg中第一个数字
smartcar-led@20c406c {
compatible = "imx-led"; // 这个名称与驱动匹配
reg = <0x20c406c 0x4>,<0x20e00b0 0x4>,<0x209c000 0x8>; // 设备的寄存器地址,多个寄存器,用逗号隔开
};
};
在开发板的/sys/firemware/devicetree/base
目录下可以查看设备树是否添加成功。
2.4 在驱动中匹配设备树节点
通过struct device_driver
结构体中的of_match_table
成员,来匹配设备树节点。
c
static struct platform_driver imx_led_driver = {
.driver = {
.of_match_table = led_dt_ids, // 匹配设备树节点
},
};
3 pinctrl和GPIO子系统
所谓的子系统,实质上就是设备树中,对SOC芯片不同寄存器进行了定义和配置。驱动开发时,仅需了解了SOC芯片厂商是如何在设备树中定义这些寄存器的,然后将这些子系统配置,以节点属性的方式添加到我们新增的设备树节点中;最后编译设备树,并在驱动程序中调用各子系统的系统API,操作寄存器。
比如:使用IMX6ULL,通过GPIO管脚控制LED流水灯亮、灭。
- 控制流水灯,需要根据硬件电路图,找到对应的控制管脚,设置管脚IO复用模式IOMUX为GPIO的工作模式(IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA)
- 配置好IOMUX后,还需要配置管脚PAD属性,这些属性配置比较复杂,需要根据SOC厂商的芯片说明(IOMUXC_SW_PAD_CTL_PAD_UART1_TX_DATA),进行配置,通常参考设备树中其他pinctrl的配置值即可。
- 最后要配置管脚的输入输出工作模式。如果为输出工作模式,还需要配置高低电平。
基于以上流程:
- SOC芯片厂商,在设备树中,对IOMUX和输入工作模式配置,通过一个宏定义直接做好了,我们只需找到对应的宏即可
- 管脚PAD属性:参考设备树中,同一IOMUX工作模式下,厂商的配置。
- 最后,将这些配置,在pinctrl子系统中,新增子节点。
c
pinctrl_rgb_led: rgb_led {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x1b0b1
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x1b0b1
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x1b0b1
>;
};
GPIO子系统:
- 在设备树文件
imx6ull.dtsi
中,定义了各gpioX组的设备信息,只需在我们新增的设备树节点中,以属性的方式引用对应的gpioX组;最后在驱动程序中,调用系统提供的API操作GPIO管脚。
c
};
// 在首节点的最后一个大括号之前添加新节点
smartcar-led@20c406c {
compatible = "imx-led"; // 这个名称与驱动匹配
reg = <0x20c406c 0x4>,<0x20e00b0 0x4>,<0x209c000 0x8>; // 设备的寄存器地址
status = okay;
pinctrl-0 = <&pinctrl_rgb_led>;
rgb_led_red = <gpio1 4 GPIO_ACTIVE_LOW>;
rgb_led_green = <gpio4 20 GPIO_ACTIVE_LOW>;
rgb_led_red = <gpio4 19 GPIO_ACTIVE_LOW>;
};
};