在上一章节我们使用了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为我们提供了很多的接口去获取设备信息,我们可以慢慢去琢磨。