Linux教程

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

本文主要是介绍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的节点

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

这篇关于Linux驱动开发十三.platform设备驱动——3.设备树下的platform驱动的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!