Linux驱动开发十三.platform设备驱动——3.设备树下的platform驱动


在上一章节我们使用了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的节点

Linux驱动开发十三.platform设备驱动——3.设备树下的platform驱动

  节点正常,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对应的函数中添加个打印信息检查下是否成功配对。

Linux驱动开发十三.platform设备驱动——3.设备树下的platform驱动

 

驱动的文件名我沿用上面章节leddrive没改,所以ko模块的文件名就是leddriver,加载以后如果和设备匹配成功就会执行probe对应的函数,打印出调试信息。

probe、release函数

如果能完成配对,剩下的就是probe函数了。和前面一样,probe主要就是完成设备的初始化、节点的生成什么的。可以把GPIO子系统试验里的初始化过程拿过来放在一个函数里,最后在probe里调用一下就可以了。卸载模块要释放资源的过程都放在release函数里,这样就可以了

Linux驱动开发十三.platform设备驱动——3.设备树下的platform驱动

/**
* @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为我们提供了很多的接口去获取设备信息,我们可以慢慢去琢磨。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/281220.html

(0)
上一篇 2022年8月20日
下一篇 2022年8月20日

相关推荐

发表回复

登录后才能评论