在上一章节我们使用了platform框架在没有设备树的时候是如何使用的,不过现在的大多数半导体厂商都把设备树给我们完善了。区别就是在没有设备树信息的时候需要我们自己想总线注册platform设备,设备里主要包含寄存器地址信息等资源,而在有设备树支持的条件下,就不需要我们使用platform_device_register函数去向总线里注册设备了,我们只需要修改设备树然后编写驱动就行了。
设备树信息
在结合设备树完成platform驱动框架时,主要的设备树属性就是兼容性节点compatible
1 /*gpio蜂鸣器节点*/
2 beep{
3 compatible = "alientek,beep";
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_gpiobeep>;
6 beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
7 status = "okay";
8 };
这次的驱动我们使用前面讲GPIO子系统时候的蜂鸣器来演示(还是因为LED点亮失败~)。设备树就不用更改了,一定要注意compatible节点的属性值,驱动是通过这个值来和设备进行匹配的。启动开发板以后打印一下设备树信息,看看有没有这个beep的节点

节点正常,compatible属性的值也没问题。
platform_driver对象
和前面一样,我们必须先声明一个platform_driver类型的变量来描述驱动,先回顾一下platform_driver的形式
1 struct platform_driver {
2 int (*probe)(struct platform_device *);
3 int (*remove)(struct platform_device *);
4 void (*shutdown)(struct platform_device *);
5 int (*suspend)(struct platform_device *, pm_message_t state);
6 int (*resume)(struct platform_device *);
7 struct device_driver driver;
8 const struct platform_device_id *id_table;
9 bool prevent_deferred_probe;
10 };
上回我们在没有设备树的时候使用的成员是driver里面的name成员,这次我们要用到另一个
const struct of_device_id *of_match_table;
从名称可以看出来,of是和设备树有关的,match是和匹配有关系的。可以再看看of_device_id的类型
1 /*
2 * Struct used for matching a device
3 */
4 struct of_device_id {
5 char name[32];
6 char type[32];
7 char compatible[128];
8 const void *data;
9 };
可以从注释上看到,这是用来匹配设备的结构体,在和设备树信息进行匹配的时候,我们就要用到里面的compatible成员,这个值一定要和前面我强调的设备树里的compatible属性一致。
1 struct of_device_id beep_of_match[] = {
2 {.compatible = "alientek,beep"},
3 {/*Sentinel*/}, //结尾标识符
4 };
这个compatible的内容一定要注意,必须和设备树里的内容相同顺序也要相同,即便是内容一样但是逗号左右的值反过来也无法匹配成功。
因为在device结构体这个match的是一个table,所以我们要把它声明成一个数组,好用来进行多个兼容性的匹配。要注意的是数组最后要用那个结尾的标识符来结束table的定义。在最后的platform_driver的成员就可以定义成下面这样了
1 static struct platform_driver beepdriver = {
2 .driver = {
3 .name = "imx6ull-led", //无设备树时匹配驱动名称
4
5 .of_match_table = beep_of_match, //设备树匹配表
6 },
7 .probe = beep_probe,
8 .remove = beep_remove,
9 };
主要就是那个driver成员里的of_match_table了。其他就没什么了。
到这一步我们就可以试验一下driver的加载了,准备好ko文件后,启动开发板,可以先在/proc/device-tree下看看有没有我们要使用的设备,这个设备是从设备树中提取的,再加载驱动,可以在probe对应的函数中添加个打印信息检查下是否成功配对。
驱动的文件名我沿用上面章节leddrive没改,所以ko模块的文件名就是leddriver,加载以后如果和设备匹配成功就会执行probe对应的函数,打印出调试信息。
probe、release函数
如果能完成配对,剩下的就是probe函数了。和前面一样,probe主要就是完成设备的初始化、节点的生成什么的。可以把GPIO子系统试验里的初始化过程拿过来放在一个函数里,最后在probe里调用一下就可以了。卸载模块要释放资源的过程都放在release函数里,这样就可以了
/**
* @file leddriver.c
* @author your name ([email protected])
* @brief platfrom结合设备树驱动框架
* @version 0.1
* @date 2022-08-18
*
* @copyright Copyright (c) 2022
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>
#define LEDOFF 0
#define LEDON 1
#define DEVICE_NAME "dtsplf_beep"
#define DEVICE_CNT 1
//内存映射后的地址指针
struct beep_dev
{
dev_t dev_id;
int major;
int minor;
struct class *class;
struct device *device;
struct cdev cdev;
struct device_node *dev_nd;
int beep_gpio;
};
struct beep_dev beep_dev;
static ssize_t beep_write(struct file *filp,const char __user *buf,
size_t count,loff_t *ppos)
{
int ret;
unsigned char databuf[1];
ret = copy_from_user(databuf,buf,count);
if(databuf[0] == 1){
gpio_set_value(beep_dev.beep_gpio,0);
}
else{
gpio_set_value(beep_dev.beep_gpio,1);
}
return ret;
}
/**
* @brief 文件操作集合
*
*/
static const struct file_operations gpiobeep_fops = {
.owner = THIS_MODULE,
.write = beep_write,
// .open = beep_dev_open,
// .release = beep_dev_release,
};
static int led_open(struct inode *inode, struct file *filp)
{
printk("dev open!\r\n");
return 0;
}
static ssize_t led_read(struct file *filp,
__user char *buf,
size_t count,
loff_t *ppos)
{
int ret = 0;
printk("dev read data!\r\n");
if (ret == 0){
return 0;
}
else{
printk("kernel read data error!");
return -1;
}
}
static void led_switch(u8 sta)
{
printk("led sta change %d\r\n",sta);
}
/**
* @brief 改变LED状态
*
* @param file
* @param buf
* @param count
* @param ppos
* @return ssize_t
*/
static ssize_t led_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
int ret = 0;
printk("led write called\r\n");
unsigned char databuf[1]; //待写入的参数
ret = copy_from_user(databuf,buf,count); //获取从用户空间传递来的参数
if (ret == 0){
led_switch(databuf[0]); //根据参数改变LED状态
}
else{
printk("kernelwrite err!\r\n");
return -EFAULT;
}
}
static struct file_operations dev_fops= {
.owner = THIS_MODULE,
.open = led_open,
// .release = led_release,
.read = led_read,
.write = led_write,
};
static int beep_gpio_init(struct platform_device *p_dev){
int ret=0;
//从设备树搜索设备节点
beep_dev.dev_nd = of_find_node_by_path("/beep");
if(beep_dev.dev_nd == 0){
printk("no device found\r\n");
ret = -100; //errno-base.h中定义的异常数值到34,这里从100开始使用防止冲突
}
//获取beep对应GPIO
beep_dev.beep_gpio =of_get_named_gpio(beep_dev.dev_nd,"beep-gpios",0);
printk("beep_gpio=%d\r\n",beep_dev.beep_gpio);
if(beep_dev.beep_gpio<0){
printk("no GPIO get\r\n");
ret = -100; //errno-base.h中定义的异常数值到34,这里从100开始使用防止冲突
return ret;
}
//请求GPIO
ret = gpio_request(beep_dev.beep_gpio,"beep-gpio");
if(ret){
printk("gpio request err\r\n");
ret = -100;
return ret;
}
//设置GPIO为输出,默认输出状态为低电平
ret = gpio_direction_output(beep_dev.beep_gpio,0);
if(ret<0){
ret = -101; //对应异常护理为fail_set
return ret;
}
//GPIO输出低电平,蜂鸣器发声
gpio_set_value(beep_dev.beep_gpio,0);
return ret;
}
static int __init beep_init(struct platform_device *p_dev){
int ret = 0;
//申请设备号
beep_dev.major = 0;
if(beep_dev.major){
//手动指定设备号,使用指定的设备号
beep_dev.dev_id = MKDEV(beep_dev.major,0);
ret = register_chrdev_region(beep_dev.dev_id,DEVICE_CNT,DEVICE_NAME);
}
else{
//设备号未指定,申请设备号
ret = alloc_chrdev_region(&beep_dev.dev_id,0,DEVICE_CNT,DEVICE_NAME);
beep_dev.major = MAJOR(beep_dev.dev_id);
beep_dev.minor = MINOR(beep_dev.dev_id);
}
printk("dev id geted!\r\n");
if(ret<0){
//设备号申请异常,跳转至异常处理
goto faile_devid;
}
//字符设备cdev初始化
beep_dev.cdev.owner = THIS_MODULE;
cdev_init(&beep_dev.cdev,&gpiobeep_fops); //文件操作集合映射
ret = cdev_add(&beep_dev.cdev,beep_dev.dev_id,DEVICE_CNT);
if(ret<0){
//cdev初始化异常,跳转至异常处理
goto fail_cdev;
}
printk("chr dev inited!\r\n");
//自动创建设备节点
beep_dev.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(beep_dev.class)){
//class创建异常处理
printk("class err!\r\n");
ret = PTR_ERR(beep_dev.class);
goto fail_class;
}
printk("dev class created\r\n");
beep_dev.device = device_create(beep_dev.class,NULL,beep_dev.dev_id,NULL,DEVICE_NAME);
if(IS_ERR(beep_dev.device)){
//设备创建异常处理
printk("device err!\r\n");
ret = PTR_ERR(beep_dev.device);
goto fail_device;
}
printk("device created!\r\n");
ret = beep_gpio_init(p_dev);
if(ret == -101){
goto fail_set;
}
else if(ret == -100){
goto fail_nd;
}
return 0;
fail_set:
gpio_free(beep_dev.beep_gpio);
fail_nd:
device_destroy(beep_dev.class,beep_dev.dev_id);
fail_device:
//device创建失败,意味着class创建成功,应该将class销毁
printk("device create err,class destroyed\r\n");
class_destroy(beep_dev.class);
fail_class:
//类创建失败,意味着设备应该已经创建成功,此刻应将其释放掉
printk("class create err,cdev del\r\n");
cdev_del(&beep_dev.cdev);
fail_cdev:
//cdev初始化异常,意味着设备号已经申请完成,应将其释放
printk("cdev init err,chrdev register\r\n");
unregister_chrdev_region(beep_dev.dev_id,DEVICE_CNT);
faile_devid:
//设备号申请异常,由于是第一步操作,不需要进行其他处理
printk("dev id err\r\n");
return ret;
}
static int beep_probe(struct platform_device *dev)
{
printk("beep driver device match\r\n");
beep_init(dev);
return 0;
}
static int beep_remove(struct platform_device *plt_dev)
{
printk("beep driver remove\r\n");
gpio_set_value(beep_dev.beep_gpio,1);
cdev_del(&beep_dev.cdev);
unregister_chrdev_region(beep_dev.dev_id,DEVICE_CNT);
device_destroy(beep_dev.class,beep_dev.dev_id);
class_destroy(beep_dev.class);
gpio_free(beep_dev.beep_gpio);
return 0;
}
struct of_device_id beep_of_match[] = {
{.compatible = "alientek,beep"},
{/*Sentinel*/}, //结尾标识符
};
static struct platform_driver beepdriver = {
.driver = {
.name = "imx6ull-led", //无设备树时匹配驱动名称
.of_match_table = beep_of_match, //设备树匹配表
},
.probe = beep_probe,
.remove = beep_remove,
};
static int __init leddriver_init(void)
{
//注册platform驱动
return platform_driver_register(&beepdriver);
}
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&beepdriver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");
platform设备树驱动
其实现在这样就可以了,但是可以注意一下上面折叠的代码里有个地方
static int __init beep_init(struct platform_device *p_dev){}
在调用设备初始化的时候我往里面穿了个platform_device的设备指针,这时因为这个platform_device结构体里是包含了很多设备树信息供我们直接调用的,就不用再使用of函数从设备树获取信息了。
可以看一下device结构体定义里面有下面一个成员
struct device_node *of_node; /* associated device tree node */
所以我们可以直接用这个值来获取设备节点,而不用of函数
beep_dev.dev_nd = of_find_node_by_path("/beep");
beep_dev.dev_nd = p_dev->dev.of_node;
platform为我们提供了很多的接口去获取设备信息,我们可以慢慢去琢磨。