系列文章目录
第一章 Linux 中内核与驱动程序 第二章 Linux 设备驱动编写 (misc) 第三章 Linux 设备驱动编写及设备节点自动生成 (cdev) 第四章 Linux 平台总线platform与设备树 第五章 Linux 设备树中pinctrl与gpio(lichee nano pi)
前言
如果你在学习Linux 设备驱动编程过程中,如果你一上来直接就去看设备树,或者去看别人写的一些驱动代码,你可能会迷惑:什么是prob函数?device.c和driver.c的区别和联系?为什么要用设备树?
一、平台总线
1.1 平台总线简介
1.平台总线定义 平台总线模型也叫platform总线模型。是 Linux内核虚拟出来的一条总线,不是真实的导线。 平台总线模型就是把原来的驱动C文件给分成了俩个C文件,一个是device.c,一个是driver.c 把稳定不变的放在driver.c里面,需要变得就放在了device.c里面。 2.为什么会有平台总线模型? (1)可以提高代码的重用性(2)减少重复性代码。 设备 总线 I驱动 device.c driver.c 3.怎么编写以平台总线模型设计的驱动? 一个是device.c,一个是driver.c,然后分别注册device.c和driver.c。平台总线是以名字来匹配的,实际上就是字符串比较。
1.2 以平台总线模型设计的驱动
1.2.1 注册device
device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,中断号,时钟等硬件资源。 在Linux 内核里面,我们是用一个结构体来描述硬件资源的。
struct platform_device {
const char *name; //平台总线进行匹配的时候用到的name,/sys/bus..
int id; //设备id,一般写-1
struct device dev; //内嵌的device结构体
{
u32 num_resources; //资源的个数
struct resource *resource; //device里面的硬件资源。
};
};
现在给出一个例子,当中的描述的是蜂鸣器的资源,根据每个开发板的具体情况进行更改。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
struct resource beep_res = {
[0]={
.start = 0x2OACO00,
.end = 0x2OAC083,
.flags = IORESOURCE_MEM,
.name = "GPIO5_DR",
};
};
struct platform_device beep_device = {;
.name = "beep_test",
.id = -1,
.resource =beep_res,
.num_resources =ARRAY_SIZE(beep_res),
}
static int device_init(void)
{
printk(KERN_ALERT "device_init");
return platform_device_register (&beep_device);
}
static void device_exit(void)
{
printk(KERN_ALERT "device_exit");
return platform_device_unregister (&beep_device);
}
module_init( device_init );
module_exit( device_exit );
MODULE_LICENSE("Dual BSD/GPL");
1.2.2 注册driver
现在来编写driver.c
struct platform_driver {
int (*probe)(struct platform_device *);//当driver和device 匹配成功的时候,就会执行probe函数
int (*remove)(struct platform_device *);//当driver和device任意一个remove的时候,就会执行这个函数
void (*shutdown)(struct platform_device *);//当设备收到shutdown命令的时候,就会执行这个函数
int (*suspend)(struct platform_device *. pm_message_t state);//当设备收到suspend命令的时候,就会执行这个函数
int (*resume)(struct platform_device *);//当设备收到resume命令的时候,就会执行这个函数
struct device_driver driver;
conststruct patrorm_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver {
const char *name;//这个是我们匹配设备时候用到的名字
struct module *owner;
};
代码如下
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
int beep_probe(struct platform_device *pdev)
{
printk( "beep_probe\n");
return 0;
}
int beep_remove(struct platform_device *pdev)
{
printk( "beep_remove\n");
return 0;
}
struct platform_driver beep_driver{
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test"
}
//const struct platform_device_id *id_table;
};
static int driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&beep_driver);
if(ret < 0){
printk( "platform_driver_register error \n");
return ret;
}
printk(KERN_ALERT "driver_init");
return 0;
}
}
static void device_exit(void)
{
platform_driver_unregister(&beep_driver);
printk(KERN_ALERT "driver_exit");
}
module_init( driver_init );
module_exit( driver_exit );
MODULE_LICENSE("Dual BSD/GPL");
device.c和driver.c是通过name来匹配的,当两者都insmod之后,如果两者匹配的话,会进入driver.c当中的prob函数。重点变成都在prob函数中。我们可以通过of系列函数获得device中的硬件资源,具体的prob函数编写现在不在详细讲解。 以上就将硬件信息和相同的驱动代码分割开了。 平台总线platfrom解决了问题,当我们在另一个开发板上要运行相同的驱动,只需要改改引脚就可以了。 但是又有个问题出现了,一个开发板上肯定不止有一个硬件、一个驱动。一个驱动写一个device.c去描写其硬件资源,那么好多就显得很冗余,因为device.c和driver.c不同,它的结构很固定。那么有没有一种方法将开发板的你需要的硬件资源都汇总到一个文件? 设备树就出现了。
二、设备树
1.基本语法
1节点 什么是节点呢?节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝,这个就叫节点。在代码中的节点是什么样子的呢。我们把上面模板中的根节点摘出来,如下图所示,这个就是根节点。相当于大树的树干。
/{
};//分号
而树枝就相当于设备树的子节点,同样我们把子节点摘出来就是根节点里面的node1和node2,如下图所示:
/{ //根节点
node1//子节点node1
{
}
node2//子节点node2
{
};
};//分号
一个树枝是不是也可以继续分成好几个树枝呢,也就是说子节点里面可以包含子子节点。所以child-node1和child-node2是node1和node1的子节点,如下图所示:
{//根节点
node1//子节点node1
{
child-node1//子子节点
{
};
};
};
node2//子节点node2
{
};
看设备树文件要从上往下看,从左往右看
2节点名称 节点的命名有一个固定的格式。
格式:<名称>[@<设备地址>]
<名称>节点的名称也不是任意起的,一般要体现设备的类型而不是特点的型号,比如网口,应该命名为ethernet,而不是随意起一个,比如111。 <设备地址>就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,他主要用来区分用。 注意事项: <1>同一级的节点只要地址不一样,名字是可以不唯一的。 <2>设备地址是一个可选选项,可以不写。但为了容易区分和理解,一般是都写的。 3节点别名 当我们找一个节点的时候,我们必须书写完整的节点路径,如果我们的节点名很长,那么我们在引用的时候就十分不方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名)。
pio: pinctrl@1c20800
4节点引用 要对子节点进行更改时我们一般不在根节点里面进行更改,而是在外面。通过引用的方式对其进行更改。
/ {
pio:pinctrl@1c20800{
};
};
&pio{
//通过&引用,在这里进行更改
};
5 一些属性的讲解在此不做赘述。链接: link
2.实例讲解
例1 Q:有的父节点有compatible属性,字节点也有自己的compatible,那么该匹配那个呢?A:用你那个节点就在驱动中与之匹配。 Q:这样看来父子节点之间好像没什么关系,A:我的理解是:父节点中定义#address-cells 与#size-cells这样就可以完成一类的字节点的编写,有共性。
例2设备树中不仅要看该设备树中的根节点,还要从头文件中找到根节点,这两个文件中的根节点运行的时候会合并起来。就像下面的例子中,
例3
//根节点
/ {
#address-cells = <1>; //带有#一般都是描述子节点的属性,
#size-cells = <1>; //这个与上一行的经常一块使用,描述字节点的地址大小相关
interrupt-parent = <&intc>;
//子节点
clocks {
#address-cells = <1>;
#size-cells = <1>;
ranges;
//子节点下的chirld节点
osc24M: clk-24M {
#clock-cells = <0>;
compatible = "fixed-clock"; //compatible ,与driver中的name匹配,会进入driver.c的prob函数
clock-frequency = <24000000>;
clock-output-names = "osc24M";
};
...
}
|