怎么写一个驱动程序
在设备树中设置硬件信息: 设备树中包含了硬件平台和设备的描述信息,包括设备的寄存器地址、中断号、资源大小等。驱动程序在设备树中查找并解析这些信息来了解硬件的配置和功能。
在驱动程序中定义涉及的寄存器: 驱动程序需要定义与硬件设备相关的寄存器,通常使用C语言的结构体或宏来表示寄存器。这些结构体或宏包含了寄存器的地址、偏移量以及相关的位字段等信息。
在驱动程序中进行寄存器地址映射: 驱动程序在初始化过程中会将涉及的寄存器地址映射到内核地址空间,这样内核就可以直接通过访问这些映射地址来与硬件设备进行通信。
使用函数获得设备树中的寄存器设置赋值给映射好的寄存器: 驱动程序在初始化过程中会通过设备树获取硬件配置信息,如设备树节点中的寄存器地址、寄存器位字段的设置等。然后,驱动程序会将这些设备树中的设置赋值给映射好的寄存器,以正确地配置硬件设备。
简而言之,设备树提供了硬件平台和设备的描述信息,驱动程序会解析这些信息,初始化并配置硬件设备。在这个过程中,驱动程序需要定义寄存器的表示方式,并将硬件的寄存器地址映射到内核地址空间,然后使用设备树中的信息来正确配置这些寄存器。这样,硬件设备就能够在操作系统中正常工作了。
一、深入理解pinctrl 和gpio子系统
pinctrl子系统
绑定文档,官网为了不同的芯片的pinctrl规范写了一个模板
linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/Documentation/devicetree/bindings/pinctrl$
imx芯片的文档是 fsl,imx-pinctrl.txt
将一个硬件设备需要的引脚配置都写好,放在一个节点下
以imx芯片系列为例,公共的设备树文件是imx.dts,然后使用&在其他设备树文件中追加代码
引脚复用:一个引脚可以用作很多功能,比如i2c1的SCL、SDA 也可以作为UART4的接收、发送引脚,只需要将这个两个引脚连接到串口控制的引脚上就可以实现,pinctrl子系统将各种复用引脚设置好了,我们只需要输入参数就可以设置成我们想要的配置
借助pinctrl子系统来设置一个PIN的复用(用作什么功能和电气属性)
pinctrl_test: testgrp { //pinctrl_test 节点标签一般用这个:节点实际名字
fsl,pins = <
//下面这句话是描述这个pin的设置信息,也就是需要设置成什么样子
MX6UL_PAD_GPIO1_IO00__GPIO1_IO09 0x17059 /*config 引脚复用宏定义 引脚电气属性*/
>;
}
这个引脚功能宏定义在imx6ull-pinfunc里面,是根据数据手册改写的宏。意思是将 MX6UL_PAD_UART1_RTS_B(数据手册可以查找定义)复用为GPIO1_IO19
下面是解释一下 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 具体代表那几个寄存器,
然后使用0x17059(电气属性) 对寄存器直接赋值,就设置完寄存器了
# define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000
0x5 0x0右边 五个参数 <mux_reg conf_reg input_reg mux_mode input_val>
0x0090:mux_reg 寄存器偏移地址,0x020e0000+mux_reg 就是 PIN 的复用寄存器地址
0x031C:conf_reg 寄存器偏移地址
0x0000:input_reg 寄存器偏移地址
0x5 : mux_reg 寄 存 器 值 , 也即是设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19
0x0:input_reg 寄存器值,在这里无效
[linux驱动开发] 设备树pinctrl子系统中第二个参数的配置_WH^2的博客-CSDN博客
电气属性值的设置
MX6UL_PAD_GPIO1_IO00__GPIO1_IO09 0x17059 电气属性也就是上拉速率之类的
这个值是conf_reg的值,直接查找上面的conf_reg寄存器地址,找到conf_reg寄存器
conf_reg寄存器的值是0x17059 ,拆成二进制是 0001 0111 0000 0101 1001(低位是右边的)
0位 设置压摆率、设置为高速压摆率(1)
2~1 位设置为0(00)
5~3 位 设置驱动能力(011)
7~6 设置管脚速率(01)
10~8 设置零
11 ODE - 0 作为输出时候使用,禁止开路输出。1则为允许(0)
12 1 使能上下拉保持器。0则为不使能(1)
13 PUE - 1 拉动/保持字段。使其可以外部电路断电保持拉动状态(1)
15~14 配置上拉电阻(01)
16 HYS - 1 使能迟滞比较器,0 则不使能(1)
31~17 设置零
GPIO子系统
使用gpio子系统来控制gpio,控制gpio的输入输出
在设备树中添加设备节点
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
// pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点
pinctrl-0 = <&pinctrl_led>;
下面这个与gpio子系统有关
//led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1这一组第四个引脚,低电平有效(也急速GPIO1d第三个引脚)
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
三、在驱动程序中操作GPIO
1、先从设备树中获得GPIO
2、配置为输出引脚
二、设备树
设备树就是将关于硬件配置信息的文件独立出去,驱动程序中只留下关于硬件的操作
修改设备树
1、添加pinctrl节点
2、添加设备节点
三、平台总线框架
主机驱动:一般负责对具体设备进行硬件级别的操作,然后向外提交api函数接口,主机驱动一般都是开发板厂家写好的
设备驱动:所有的设备驱动都可以调用主机驱动的api函数去实现与外界交互功能
当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看是有没有设备与之匹配的设备,如果有的话就将二者联系起来,同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找有没有与之匹配的设备
驱动与设备分离
我们知道设备驱动的分离,并且引出了总线、驱动、设备的模型,比如i2c、spi、usb等总线。但是在soc中有些外设没有这个概念,但是我们有想使用这个模型,Linux提出了platform这虚拟总线,于是就对应的platfor_driver和platform_device
当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配
的设备,如果有的话就将两者联系起来。同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。Linux 内核中大量的驱动程序都采用总线、驱动和设备模式,我们一会要重点讲解的 platform 驱动就是这一思想下的产物。
1、platform总线
Linux系统内核用bus_type结构体表示总线
bus_type结构体中 platform_match函数负责驱动与设备的匹配
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
}
2、platform驱动
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; //这个结构体变量中包含了设备树匹配的of_match_tableha函数
const struct platform_device_id *id_table; //无设备树匹配
bool prevent_deferred_probe;
}
第二行: probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,probe函数就是负 责注册驱动设备到内核的哪些东西
第 七 行 driver 成员,该结构体中包含了设备树匹配的of_match_table函数
第八行 :id_table 用在与无设备树匹配
注意:在无设备树的时候platform_driver name 和 platfor_device name 对应就可以匹配上
struct device_driver
{
const struct of_device_id *of_match_table; //设备树匹配
}
驱动入口函数里面调用的platform_driver_register函数向Linux内核注册一个platform驱动
驱动卸载函数里面调用platform_driver_unregister函数卸载platform驱动
编写platform驱动需要的一些东西
0、寄存器地址定义、因为这里是用地址映射,用虚拟地址进行操作 //传统字符设备驱动
1、设备结构体
2、设备具体操作函数
3、字符设备驱动操作集(file_operations)
4、platform驱动的probe函数,驱动与设备匹配后此函数就会执行(注册字符设备驱动,初始化设备(寄存器地址映射、设备))
5、remove()(卸载字符设备驱动,取消寄存器地址映射)
6、匹配列表(如果使用设备树的话通过此匹配表进行驱动匹配)
7、platform平台驱动结构体(其中包含name(其中name移动要和设备字段相对应),匹配列表,probe和remove)
8、驱动模块的加载/卸载
三、设备树
platform_devices和设备里面具体的节点功能是一样的,所以说如果有设备树,就没必要整个platform_devices。在编写基于设备树的platform驱动我们需要注意以下几点:
1.在设备树中创建设备节点
2.编写platform要注意兼容属性
这里需要注意,我们就是用
设备节点的compatible属性:“atkalpha-gpioled”和platform驱动的of_match_table属性表中compatible属性:”atkalpha-gpioled"来进行匹配的
驱动与设备匹配
在平台框架下,总线结构体里面有一个platform_match函数,里面定义了几种匹配方法函数
1、有设备树,驱动结构体中有一个of_match _table 表中的compatible属性与设备树的compatible匹配
2、无设备树,一般看设备结构体和驱动结构体的name属性
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c" //无设备树
.of_match_table = ap3216c_of_match, //设备树匹配函数
},
};
/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};