Linux驱动开发六.pinctl和gpio子系统2——蜂鸣器驱动

在前面我们大致讲了下pinctrl和gpio两个子系统的基础概念,但是二者在内核中的流程分析如果后面有机会我们在来看一下,下面我们要结合实际开发板的功能来使用一下这两个子系统(教程给定的是使用LED,但是无论如何LED无法点亮,驱动流程是没问题的,改用蜂鸣器来操作)

设备树的修改

设备树的修改主要分pinctrl部分和gpio部分,在pinctrl中我们主要将pin复用为GPIO并设置其电气属性,在gpio中主要设置gpio对应关系。

pinctrl属性

设备树的修改可以仿照别的设备节点,pinctrl的路径在iomuxc/imx6u-evk路径下添加下面的节点

pinctrl_gpiobeep: beepgrp {
    fsl,pins = <
        MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01    0x10b
    >;
};

注意看下路径,我把能折叠的节点都折叠起来了

 

 主要内容就是将SNVS_TAMPER1功能脚复用为GPIO5_IO01,并且将电气属性设置为0x10b,实际功能不在分析了,前面多的是。只用这一句代码,就完成了GPIO的各种初始化设置,连寄存器地址什么的都不再需要里,是不是简单的多!

gpio属性

gpio属性是在设备节点中的,我们可以在根节点下添加蜂鸣器的设备节点

/*gpio蜂鸣器节点*/
beep{
    compatible = "alientek,beep";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpiobeep>;
    beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
    status = "okay";
};

其中主要是加粗的两行内容,一个是pinctrl-0节点,表示这个beep设备的PIN信息保存在pinctrl_gpiobeep节点中(就是前面我们添加的pinctrl节点)

另一行beep-gpios属性描述了beep这个设备所用的gpio信息。

江浙两段代码添加在设备树文件中,设备树文件就完成了!make dtbs编译一下,使用新的设备树文件启动系统,就可以看到新添加的设备节点,

beep下所有属性节点也可以看到

在/dev路径下可以看到该设备

 

 

下面就要看gpio的相关操作了

驱动部分

设备树编译正常后就要做驱动部分了,驱动的架构不用变,主要就是在dev结构体里添加几个新成员

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;
};

新增成员为上面代码中加粗的部分,就是一个整形的数据,对应申请GPIO编号。

这里着重要讲的部分是GPIO的操作

/**
 * @brief beep功能对应GPIO初始化
 * 
 * @return int 
 */
static int beep_gpio_init(void){
    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;
}

整个GPIO的操作流程就是这样的,首先通过设备树路径获取设备节点dev_nd,然后要获取gpio号,gpio的获取是通过函数

static inline int of_get_named_gpio(struct device_node *np,
                                   const char *propname, int index)
{
    return of_get_named_gpio_flags(np, propname, index, NULL);
}

参数np为设备id,propname为设备树中节点内gpio属性,对下面的应设备树信息

beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;

注意名字一定要和属性名字一样,否则就找不到这个节点了。

参数index为索引,有些时候设备会用到多个gpio属性,这里我们只用了1个,所以索引就是0。完了以后一定要申请一下GPIO,如果设备树中有些设备节点已经用过该PIN,初始化时候就会报错(下面图是我找了个设备树中已经存在的节点)

 

这就是GPIO资源冲突了。需要在设备树中找一下都有哪些设备用了该PIN资源,需要做好相应的处理,可以吧status改成disable进行调试。

这里的异常处理我是放到整个初始化的函数中了,因为前面的异常处理用到了errno-base.h中定义的异常编号,该文件中使用的异常编号是从1到34的,我重新从100开始防止冲突。其中gpio的申请、gpio编号查询和设备id的查询异常时是不需要做额外的处理的,只有设置输入/输出时如果异常需要释放掉GPIO资源。剩下的就没什么可说的了,把整个驱动放在最后面

/**
 * @file gpiobeep.c
 * @author your name (you@domain.com)
 * @brief GPIO操作beep功能
 * @version 0.1
 * @date 2022-07-03
 * 
 * @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>

#define DEVICE_CNT      1
#define DEVICE_NAME    "GPIOBEEP"

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,
};



/**
 * @brief beep功能对应GPIO初始化
 * 
 * @return int 
 */
static int beep_gpio_init(void){
    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(void){

    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();
    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 void __exit beep_exit(void){

    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);
}


module_init(beep_init);
module_exit(beep_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeqiZ");
gpiobeep.c

因为使用了gpio和一些相关函数,这里头文件的引用和前面是有些区别的,一定要注意!测试用到的APP程序和最开始的LED测试一样,就是通过APP向驱动内核写入0或1来改变GPIO输出状态

 

posted @ 2022-07-03 11:58  银色的音色  阅读(594)  评论(0编辑  收藏  举报