|
Linux下bb-black的GPIO驱动程序
作者:Mlo_lv
QQ:102578546
(如需转载,请注明出处)
最近几天拿到了一块bb-black,参照这《LINUX设备驱动程序》想着用来练习Linux下的驱动程序编写。我也是新手,本文仅贡交流之用,有写的不好的地方,还望各位高手指正,不吝赐教!
本文参照了:《Linux下AM335X的GPIO控制》:http://bbs.eeworld.com.cn/thread-327156-1-1.html
《Linux下TI omap芯片 MUX 配置分析(以AM335X芯片为例)》:http://blog.chinaunix.net/uid-20543672-id-3067021.html
《BeagleBone Black Linux驱动程序开发入门(1): LED驱动程序》:https://www.cirmall.com/bbs/forum ... id=15284&highlight=
还有其他的一些博文,对我的帮助也很大,在这就不在这一一列举了!
/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
我相信各位开发环境搭建已经不成问题了,如果开发环境没有搭建好的话,可以参照:http://bbs.eeworld.com.cn/thread-375502-1-1.html
说实话,我并没有采用TI提供的SDK包中的交叉编译器,而是采用了embest的arm-none-linux-gnueabi-4.5,其实我尝试了很多编译器,最后还是这个好用,网上可以下到,如果各位功力够深的话,可以自己下载linaro提供的GCC源码制作的交叉编译器!我就偷懒了!还有关于链工具命名规则,可以参照:http://blog.sina.com.cn/s/blog_76550fd70101bd43.html 没事可以了解一下!
说的太多了,进入主题吧:
/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
内核空间
驱动程序gpioCtl.c:- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/printk.h>
- #include <linux/fs.h>
- #include <linux/cdev.h>
- #include <linux/init.h>
- #include <linux/i2c.h>
- #include <linux/delay.h>
- #include <linux/gpio.h>
- #include <linux/slab.h>
- #include <asm/uaccess.h>
- /*-----------------------------------------------------*/
- #define DEVICE_NAME "gpioCtl"
- #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
- /*------------------------------------------------------*/
- struct gpio_qset;
- /*设备编号存储结构体*/
- struct dev_num
- {
- dev_t devNum;
- unsigned int major;
- unsigned int minor;
- unsigned int minor_first;
- unsigned int count;
- };
- struct dev_num gpio_dev_num;
- /*设备描述结构体*/
- struct gpio_dev
- {
- struct cdev cdev;
- struct gpio_qset* dptr; //设备数据存储链表第一项
- unsigned long size; //链表长度(随着申请的GPIO端口数增长)
- };
- struct gpio_dev *gpio_devp;
- /*设备数据存储结构体(采用链式存储,不理解的可以看《数据结构》)*/
- struct gpio_qset
- {
- unsigned long port; //端口号 #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
- unsigned int ddr; //方向(输入(0)或输出(1))
- char value; //高(1)低(0)电平
- unsigned long num; //当前编号(按照申请顺序编号)
- char label[10]; //申请的GPIO使用名称
- struct gpio_qset* next; //指向链表下一项的指针
- };
- /**
- * 功能:初始化gpio_dev
- * *inode:
- * *filp:
- * 描述:用户空间调用open时运行
- * int (*open) (struct inode *, struct file *);
- * 返回值:0
- */
- static int gpio_open(struct inode *inode, struct file *filp)
- {
- struct gpio_dev *dev;
-
- //由container_of获得结构体指针inode中结构体cdev的指针,
- //让结构体指针dev指向上述的指针所指的地址,
- //再让file->private指向这一地址,
- dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
- filp->private_data = dev;
- dev->dptr = NULL;
- dev->size = 0;
- //printk(KERN_ERR "gpio_open success!\n");
- return 0;
- }
- /**
- * 功能:释放申请的GPIO端口以及释放链表(gpio_qset)
- * *inode:
- * *filp:
- * 描述:用户空间调用close时运行
- * int (*release) (struct inode *, struct file *);
- * 返回值:0
- */
- static int gpio_close(struct inode *inode, struct file *filp)
- {
- struct gpio_dev *dev = filp->private_data;
- struct gpio_qset *qset, *qsetTmp;
- qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
- for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
- {
- qsetTmp = qset->next;
- gpio_free(qset->port); //释放申请的端口
- kfree(qset); //释放gpio_qset内存
- }
- gpio_free(qsetTmp->port);
- kfree(qsetTmp);
- //printk(KERN_ERR "gpio release!\n");
- return 0;
- }
- /**
- * 功能:申请新的gpio_qset的内存,确定相应的GPIO端口功能
- * *inode:
- * *filp:
- * cmd:实现的功能,
- * 0:读,
- * 1:写,
- * 2:释放GPIO端口
- * arg:执行操作的GPIO端口
- * 描述:用户空间调用ioctl时运行
- * long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
- * 返回值:成功返回操作的GPIO端口号
- * 错误返回相应的错误码
- */
- static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
- {
- int ret;
- struct gpio_dev *dev = filp->private_data;
- struct gpio_qset *qset;
- //cmd == 2 设计成为释放arg端口的功能,之后申请内存的任务便不必执行了,所以直接返回
- if(cmd == 2)
- {
- gpio_free(arg);
- return arg;
- }
- dev->size++;
- if(dev->dptr == NULL)
- {
- dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
- qset = dev->dptr;
- }
- else
- {
- for(qset = dev->dptr; qset->next != NULL; qset = qset->next); //找到链表最后一项
- qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
- qset = qset->next;
- }
- /*链表数据*/
- qset->num = dev->size; //确定自己的编号
- qset->ddr = cmd; //确定方向
- qset->port = arg; //确定端口号
- qset->next = NULL; //最后一项地址清空
- //printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
- sprintf(qset->label, "gpio%ld", qset->port); //确定申请的GPIO使用名称(和端口直接相关)
- ret = gpio_request(qset->port, qset->label); //申请端口
-
- /*由于gpio_requset会自己判断成功与否并且退出函数,故注释掉对ret的判断
- if(ret < 0)
- printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
- */
- /*判断GPIO工作方向(输出或输出)*/
- switch(qset->ddr)
- {
- case 0: ret = gpio_direction_input(qset->port);
- if(ret < 0)
- printk(KERN_ERR "gpio_direction_input failled!\n");
- break;
- case 1: ret = gpio_direction_output(qset->port, 1);
- if(ret < 0)
- printk(KERN_ERR "gpio_direction_output failled!\n");
- break;
- default:
- return -EPERM; /* Operation not permitted */
- }
- return qset->num;
- }
- /**
- * 功能:获取相应端口电平,写入相应的qset_dev->value,写到用户空间的readBuf
- * *inode:
- * *filp:
- * *readBuf:读数据缓存指针,用户空间的指针
- * port:被读取的GPIO端口号
- * *offp:
- * 描述:用户空间调用read时运行
- * ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
- * 返回值:成功返回qset->value
- * 错误返回相应的错误码
- */
- static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
- {
- long ret;
- struct gpio_dev *dev = filp->private_data;
- struct gpio_qset *qset;
-
- if(dev->dptr == NULL)
- return -ENODEV; /* No such device */
-
- for(qset = dev->dptr; qset != NULL; qset = qset->next)
- {
- if(qset->port == port)
- break;
- if(qset->next == NULL)
- return -ENODEV; /* No such device */
- }
- if(qset->ddr != 0) //判断是否ioctl设置为读操作
- return -EPERM; /* Operation not permitted */
- qset->value = gpio_get_value(qset->port);
- //printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
- switch(qset->value)
- {
- case 0: ret = copy_to_user(readBuf, "0", 1);
- break;
- case 1: ret = copy_to_user(readBuf, "1", 1);
- break;
- }
- return qset->value;
- }
- /**
- * 功能:写入相应端口电平,写入相应的qset_dev->value,数据来自用户空间的writeBuf
- * *inode:
- * *filp:
- * *writeBuf:写数据缓存指针,用户空间的指针
- * port:被写入的GPIO端口号
- * *offp:
- * 描述:用户空间调用write时运行
- * ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
- * 返回值:成功返回qset->value
- * 错误返回相应的错误码
- */
- static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
- {
- long ret;
- struct gpio_dev *dev = filp->private_data;
- struct gpio_qset *qset;
- if(dev->dptr == NULL)
- return -ENODEV; /* No such device */
-
- for(qset = dev->dptr; qset != NULL; qset = qset->next)
- {
- // printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
- if(qset->port == port)
- break;
- if(qset->next == NULL)
- return -ENODEV; /* No such device */
- }
- if(qset->ddr != 1) //判断是否ioctl设置为写操作
- return -EPERM; /* Operation not permitted */
- ret = copy_from_user(&qset->value, writeBuf, 1);
- //printk(KERN_ERR "write:%d\n", qset->value);
- switch(qset->value)
- {
- case '0': qset->value = 0;
- gpio_set_value(qset->port, 0);
- break;
- default : qset->value = 1;
- gpio_set_value(qset->port, 1);
- break;
- }
- return qset->value;
- }
- /*文件操作结构体*/
- static const struct file_operations gpio_fops = {
- .owner = THIS_MODULE,
- .open = gpio_open,
- .unlocked_ioctl = gpio_ioctl,
- .write = gpio_write,
- .read = gpio_read,
- .release = gpio_close,
- };
- /*cdev注册函数*/
- static void gpio_setup_cdev(struct gpio_dev *dev, int index)
- {
- int ret,devno = gpio_dev_num.devNum + index;
- cdev_init(&dev->cdev, &gpio_fops);
- dev->cdev.owner = THIS_MODULE;
- dev->cdev.ops = &gpio_fops;
- ret = cdev_add(&dev->cdev, devno, 1);
- if(ret)
- printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
- }
- /*设备初始化函数*/
- static int __init omap3gpio_init(void)
- {
- int ret;
- gpio_dev_num.count = 1;
- gpio_dev_num.minor_first = 0;
- ret = alloc_chrdev_region(&gpio_dev_num.devNum, gpio_dev_num.minor_first, gpio_dev_num.count, DEVICE_NAME);
-
- if(ret < 0)
- return ret;
-
- gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
- gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);
- gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);
- gpio_setup_cdev(gpio_devp, 0);
- printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n", gpio_dev_num.major);
- return 0;
- }
- /*设备释放函数*/
- static void __exit omap3gpio_exit(void)
- {
- /*test*/
- //struct file *filp;
- //struct inode *inode;
- //inode = container_of(&gpio_devp->cdev, struct inode, i_cdev);
- //filp = container_of(gpio_devp, struct file, private_data);
- //gpio_close(inode, filp);
- /*test*/
- cdev_del(&gpio_devp->cdev);
- kfree(gpio_devp);
- unregister_chrdev_region(gpio_dev_num.devNum, 1);
- printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n", gpio_dev_num.major);
- }
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Mlo_Lv,Tute-421E-studio");
- MODULE_DESCRIPTION("This module is used to conrtol omap3 gpio");
- module_init(omap3gpio_init);
- module_exit(omap3gpio_exit);
复制代码
我的驱动程序文件名是gpioCtl.c,如果各位想直接使用这一驱动的话,请使用这个文件名,不要修改。还有就是其中对于GPIO操作的一些函数,例如"gpio_requset()","gpio_free()","gpio_set_value()",如果有不理解的可以参照:http://blog.csdn.net/beyondioi/article/details/6984406,这篇博文详细讲解了omap系列产品用于操作GPIO的函数,其实看看源码应该能够明白这写函数的意思。
Makefile:- #Makefile for gpioCtl.c
- ARCH=arm
- CROSS_COMPILE=arm-none-linux-gnueabi-
- ifneq ($(KERNELRELEASE),)
- obj-m := gpioCtl.o
- else
- KERNELDIR ?= /usr/embeded/bb-black/sources/kernel/kernel
- PWD := $(shell pwd)
- default:
- make -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
- app: app.c
- $(CROSS_COMPILE)gcc -o app app.c
- clean:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
- rm -rf modules.order
- cleanr:
- rm *~
- endif
复制代码
如果各位使用的交叉编译器和我的不一样的话,请修改CROSS_COMPILE(一样的话就免了),然后修改KERNELDIR成为自己内核的存储路径,至此所有有关驱动的程序就写完了,将上面的两短代码放在同一文件目录下,make(编译驱动之前,请先编译好内核),就会生成相应的驱动gpioCtl.ko
/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
用户空间
应用程序app.c:- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <linux/input.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
- #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
- int main(int argc, char * argv)
- {
- int i, n, fd;
- char num;
- int ret;
- fd = open("/dev/gpioCtl", O_RDWR); //打开设备
- if (fd < 0)
- {
- printf("can't open /dev/gpioCtl!\n");
- exit(1);
- }
- sleep(1);
- ioctl(fd, 1, GPIO_TO_PIN(1,22)); //设置gpio1-22为输出(user:led3)
- ioctl(fd, 0, GPIO_TO_PIN(2, 1)); //设置gpio2-1 为输入(p8-18)
- while (1)
- {
- num = 1;
- ret = write(fd,"1",GPIO_TO_PIN(1,22));
- if(ret < 0)
- {
- perror("write");
- return -1;
- }
- sleep(1);
- ret = write(fd,"0",GPIO_TO_PIN(1,22));
- if(ret < 0)
- {
- perror("write");
- return -1;
复制代码
执行make app 交叉编译应用程序。此程序能实现user:led3闪烁效果以及读取gpio2-1的电平!
/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
shell gpioCtl.sh:- #!/bin/sh
- insmod gpioCtl.ko
- mknod /dev/gpioCtl c 245 0
复制代码
我在上面的驱动程序编号申请采用动态分配方式,但是每次分配的主设备号都是254,所以shell中的设备号采用了245,如果各位动态分配的结果不是245,可能需要修改shell,cat /proc/devices 可以察看到分配到的设备号。
现在将所有文件拷贝到板子上
chmod +x gpioCtl.sh 为脚本增加执行权
./gpioCtl.sh 运行脚本
./app 运行程序
如果没有问题的话,现在就能看到 led 闪烁以及读取到的 p2-1的电平了!
今天就写到这里吧!感谢各位捧场!
|
|