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