ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-08-20 00:04:26  阅读:165  来源: 互联网

标签:struct dev beep platform Linux device 驱动 include


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

标签:struct,dev,beep,platform,Linux,device,驱动,include
来源: https://www.cnblogs.com/yinsedeyinse/p/16600597.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有