查看: 19017|回复: 5

Linux下bb-black的GPIO驱动程序

[复制链接]

该用户从未签到

发表于 2013-9-16 10:41:07 | 显示全部楼层 |阅读模式
分享到:
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:
  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/printk.h>
  4. #include <linux/fs.h>
  5. #include <linux/cdev.h>
  6. #include <linux/init.h>
  7. #include <linux/i2c.h>
  8. #include <linux/delay.h>
  9. #include <linux/gpio.h>
  10. #include <linux/slab.h>
  11. #include <asm/uaccess.h>
  12. /*-----------------------------------------------------*/
  13. #define DEVICE_NAME        "gpioCtl"
  14. #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
  15. /*------------------------------------------------------*/

  16. struct gpio_qset;

  17. /*设备编号存储结构体*/
  18. struct dev_num
  19. {
  20.         dev_t devNum;
  21.         unsigned int major;
  22.         unsigned int minor;
  23.         unsigned int minor_first;
  24.         unsigned int count;
  25. };
  26. struct dev_num gpio_dev_num;

  27. /*设备描述结构体*/
  28. struct gpio_dev
  29. {
  30.         struct cdev cdev;
  31.         struct gpio_qset* dptr;        //设备数据存储链表第一项
  32.         unsigned long size;        //链表长度(随着申请的GPIO端口数增长)
  33. };
  34. struct gpio_dev *gpio_devp;

  35. /*设备数据存储结构体(采用链式存储,不理解的可以看《数据结构》)*/
  36. struct gpio_qset
  37. {
  38.         unsigned long port;        //端口号 #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
  39.         unsigned int ddr;        //方向(输入(0)或输出(1))
  40.         char value;                //高(1)低(0)电平
  41.         unsigned long num;        //当前编号(按照申请顺序编号)
  42.         char label[10];                //申请的GPIO使用名称
  43.         struct gpio_qset* next;        //指向链表下一项的指针
  44. };

  45. /**
  46. * 功能:初始化gpio_dev
  47. * *inode:
  48. * *filp:
  49. * 描述:用户空间调用open时运行
  50. *         int (*open) (struct inode *, struct file *);
  51. * 返回值:0
  52. */
  53. static int gpio_open(struct inode *inode, struct file *filp)
  54. {
  55.         struct gpio_dev *dev;
  56.         
  57.         //由container_of获得结构体指针inode中结构体cdev的指针,
  58.         //让结构体指针dev指向上述的指针所指的地址,
  59.         //再让file->private指向这一地址,
  60.         dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
  61.         filp->private_data = dev;
  62.         dev->dptr = NULL;
  63.         dev->size = 0;
  64.         //printk(KERN_ERR "gpio_open success!\n");
  65.         return 0;
  66. }

  67. /**
  68. * 功能:释放申请的GPIO端口以及释放链表(gpio_qset)
  69. * *inode:
  70. * *filp:
  71. * 描述:用户空间调用close时运行
  72. *         int (*release) (struct inode *, struct file *);
  73. * 返回值:0
  74. */
  75. static int gpio_close(struct inode *inode, struct file *filp)
  76. {
  77.         struct gpio_dev *dev = filp->private_data;
  78.         struct gpio_qset *qset, *qsetTmp;
  79.         qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  80.         for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
  81.         {
  82.                 qsetTmp = qset->next;
  83.                 gpio_free(qset->port);        //释放申请的端口
  84.                 kfree(qset);                //释放gpio_qset内存
  85.         }
  86.         gpio_free(qsetTmp->port);
  87.         kfree(qsetTmp);
  88.          //printk(KERN_ERR "gpio release!\n");
  89.         return 0;
  90. }

  91. /**
  92. * 功能:申请新的gpio_qset的内存,确定相应的GPIO端口功能
  93. * *inode:
  94. * *filp:
  95. * cmd:实现的功能,
  96. *     0:读,
  97. *     1:写,
  98. *     2:释放GPIO端口
  99. * arg:执行操作的GPIO端口
  100. * 描述:用户空间调用ioctl时运行
  101. *         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
  102. * 返回值:成功返回操作的GPIO端口号
  103. *           错误返回相应的错误码
  104. */
  105. static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  106. {
  107.         int ret;
  108.         struct gpio_dev *dev = filp->private_data;
  109.         struct gpio_qset *qset;

  110.         //cmd == 2 设计成为释放arg端口的功能,之后申请内存的任务便不必执行了,所以直接返回
  111.         if(cmd == 2)
  112.         {
  113.                 gpio_free(arg);
  114.                 return arg;
  115.         }

  116.         dev->size++;

  117.         if(dev->dptr == NULL)
  118.         {
  119.                 dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  120.                 qset = dev->dptr;
  121.         }
  122.         else
  123.         {
  124.                 for(qset = dev->dptr; qset->next != NULL; qset = qset->next);                        //找到链表最后一项
  125.                 qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  126.                 qset = qset->next;
  127.         }
  128.         /*链表数据*/
  129.         qset->num = dev->size;        //确定自己的编号
  130.         qset->ddr = cmd;        //确定方向
  131.         qset->port = arg;        //确定端口号
  132.         qset->next = NULL;        //最后一项地址清空

  133.         //printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
  134.         sprintf(qset->label, "gpio%ld", qset->port);                //确定申请的GPIO使用名称(和端口直接相关)
  135.         ret = gpio_request(qset->port, qset->label);                //申请端口
  136.         
  137.         /*由于gpio_requset会自己判断成功与否并且退出函数,故注释掉对ret的判断
  138.         if(ret < 0)
  139.                 printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
  140.         */

  141.         /*判断GPIO工作方向(输出或输出)*/        
  142.         switch(qset->ddr)
  143.         {
  144.                 case 0:        ret = gpio_direction_input(qset->port);
  145.                         if(ret < 0)
  146.                                 printk(KERN_ERR "gpio_direction_input failled!\n");
  147.                         break;
  148.                 case 1:        ret = gpio_direction_output(qset->port, 1);
  149.                         if(ret < 0)
  150.                                 printk(KERN_ERR "gpio_direction_output failled!\n");
  151.                         break;
  152.                 default:
  153.                         return -EPERM;        /* Operation not permitted */
  154.         }
  155.         return qset->num;
  156. }

  157. /**
  158. * 功能:获取相应端口电平,写入相应的qset_dev->value,写到用户空间的readBuf
  159. * *inode:
  160. * *filp:
  161. * *readBuf:读数据缓存指针,用户空间的指针
  162. * port:被读取的GPIO端口号
  163. * *offp:
  164. * 描述:用户空间调用read时运行
  165. *         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  166. * 返回值:成功返回qset->value
  167. *           错误返回相应的错误码
  168. */
  169. static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
  170. {
  171.         long ret;
  172.         struct gpio_dev *dev = filp->private_data;
  173.         struct gpio_qset *qset;
  174.         
  175.         if(dev->dptr == NULL)        
  176.                 return -ENODEV;                /* No such device */
  177.                         
  178.         for(qset = dev->dptr; qset != NULL; qset = qset->next)
  179.         {
  180.                 if(qset->port == port)
  181.                         break;
  182.                 if(qset->next == NULL)        
  183.                         return -ENODEV;        /* No such device */
  184.         }

  185.         if(qset->ddr != 0)                //判断是否ioctl设置为读操作
  186.                 return -EPERM;                /* Operation not permitted */
  187.         qset->value = gpio_get_value(qset->port);
  188.         //printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
  189.         switch(qset->value)
  190.         {
  191.                 case 0:        ret = copy_to_user(readBuf, "0", 1);
  192.                         break;
  193.                 case 1:        ret = copy_to_user(readBuf, "1", 1);
  194.                         break;
  195.         }
  196.         return qset->value;
  197. }
  198. /**
  199. * 功能:写入相应端口电平,写入相应的qset_dev->value,数据来自用户空间的writeBuf
  200. * *inode:
  201. * *filp:
  202. * *writeBuf:写数据缓存指针,用户空间的指针
  203. * port:被写入的GPIO端口号
  204. * *offp:
  205. * 描述:用户空间调用write时运行
  206. *         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  207. * 返回值:成功返回qset->value
  208. *           错误返回相应的错误码
  209. */
  210. static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
  211. {
  212.         long ret;
  213.         struct gpio_dev *dev = filp->private_data;
  214.         struct gpio_qset *qset;

  215.         if(dev->dptr == NULL)        
  216.                 return -ENODEV;                /* No such device */
  217.                         
  218.         for(qset = dev->dptr; qset != NULL; qset = qset->next)
  219.         {
  220.         //        printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
  221.                 if(qset->port == port)
  222.                         break;
  223.                 if(qset->next == NULL)        
  224.                         return -ENODEV;        /* No such device */
  225.         }

  226.         if(qset->ddr != 1)                //判断是否ioctl设置为写操作
  227.                 return -EPERM;                /* Operation not permitted */

  228.         ret = copy_from_user(&qset->value, writeBuf, 1);
  229.         //printk(KERN_ERR "write:%d\n", qset->value);
  230.         switch(qset->value)
  231.         {
  232.                 case '0': qset->value = 0;
  233.                           gpio_set_value(qset->port, 0);
  234.                           break;
  235.                 default : qset->value = 1;
  236.                           gpio_set_value(qset->port, 1);
  237.                           break;
  238.         }
  239.         return qset->value;
  240. }

  241. /*文件操作结构体*/
  242. static const struct file_operations gpio_fops = {
  243.         .owner = THIS_MODULE,
  244.         .open = gpio_open,
  245.         .unlocked_ioctl = gpio_ioctl,
  246.         .write = gpio_write,
  247.         .read = gpio_read,
  248.         .release = gpio_close,
  249. };

  250. /*cdev注册函数*/
  251. static void gpio_setup_cdev(struct gpio_dev *dev, int index)
  252. {
  253.         int ret,devno = gpio_dev_num.devNum + index;
  254.         cdev_init(&dev->cdev, &gpio_fops);
  255.         dev->cdev.owner = THIS_MODULE;
  256.         dev->cdev.ops = &gpio_fops;

  257.         ret = cdev_add(&dev->cdev, devno, 1);

  258.         if(ret)
  259.                 printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
  260. }

  261. /*设备初始化函数*/
  262. static int __init omap3gpio_init(void)
  263. {
  264.         int ret;

  265.         gpio_dev_num.count = 1;
  266.         gpio_dev_num.minor_first = 0;
  267.         ret = alloc_chrdev_region(&gpio_dev_num.devNum, gpio_dev_num.minor_first, gpio_dev_num.count, DEVICE_NAME);
  268.         
  269.         if(ret < 0)        
  270.                 return ret;
  271.         
  272.         gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
  273.         gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);

  274.         gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);

  275.         gpio_setup_cdev(gpio_devp, 0);

  276.         printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n", gpio_dev_num.major);
  277.         return 0;
  278. }

  279. /*设备释放函数*/
  280. static void __exit omap3gpio_exit(void)
  281. {
  282.         /*test*/
  283.         //struct file *filp;
  284.         //struct inode *inode;
  285.         //inode = container_of(&gpio_devp->cdev, struct inode, i_cdev);
  286.         //filp = container_of(gpio_devp, struct file, private_data);
  287.         //gpio_close(inode, filp);
  288.         /*test*/

  289.         cdev_del(&gpio_devp->cdev);
  290.         kfree(gpio_devp);
  291.         unregister_chrdev_region(gpio_dev_num.devNum, 1);
  292.         printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n", gpio_dev_num.major);
  293. }

  294. MODULE_LICENSE("Dual BSD/GPL");
  295. MODULE_AUTHOR("Mlo_Lv,Tute-421E-studio");
  296. MODULE_DESCRIPTION("This module is used to conrtol omap3 gpio");

  297. module_init(omap3gpio_init);
  298. 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:
  1. #Makefile for gpioCtl.c
  2. ARCH=arm
  3. CROSS_COMPILE=arm-none-linux-gnueabi-
  4. ifneq ($(KERNELRELEASE),)
  5.         obj-m := gpioCtl.o
  6. else
  7.         KERNELDIR ?= /usr/embeded/bb-black/sources/kernel/kernel
  8.         PWD := $(shell pwd)
  9. default:
  10.         make -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules

  11. app: app.c
  12.         $(CROSS_COMPILE)gcc -o app app.c
  13. clean:
  14.         $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
  15.         rm -rf modules.order
  16. cleanr:        
  17.         rm *~
  18. endif
复制代码

        如果各位使用的交叉编译器和我的不一样的话,请修改CROSS_COMPILE(一样的话就免了),然后修改KERNELDIR成为自己内核的存储路径,至此所有有关驱动的程序就写完了,将上面的两短代码放在同一文件目录下,make(编译驱动之前,请先编译好内核),就会生成相应的驱动gpioCtl.ko


截图1.png


/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
用户空间
应用程序app.c:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/stat.h>
  5. #include <fcntl.h>
  6. #include <linux/input.h>
  7. #include <unistd.h>
  8. #include <sys/ioctl.h>

  9. #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

  10. int main(int argc, char * argv)
  11. {
  12.         int i, n, fd;
  13.         char num;
  14.         int ret;
  15.         fd = open("/dev/gpioCtl", O_RDWR);                //打开设备
  16.         if (fd < 0)
  17.         {
  18.             printf("can't open /dev/gpioCtl!\n");
  19.             exit(1);
  20.         }
  21.         sleep(1);
  22.         ioctl(fd, 1, GPIO_TO_PIN(1,22));                //设置gpio1-22为输出(user:led3)
  23.         ioctl(fd, 0, GPIO_TO_PIN(2, 1));                //设置gpio2-1 为输入(p8-18)
  24.         while (1)
  25.         {
  26.                 num = 1;
  27.                 ret = write(fd,"1",GPIO_TO_PIN(1,22));
  28.                 if(ret < 0)
  29.                 {
  30.                         perror("write");
  31.                         return -1;
  32.                 }
  33.                 sleep(1);
  34.                 ret = write(fd,"0",GPIO_TO_PIN(1,22));
  35.                 if(ret < 0)
  36.                 {
  37.                     perror("write");
  38.                     return -1;
复制代码

        执行make app 交叉编译应用程序。此程序能实现user:led3闪烁效果以及读取gpio2-1的电平!


exp_p9.png
exp_p8.png

/*-------------------------------------------------------------------------------------------------------------------------------------------------------*/
shell gpioCtl.sh:
  1. #!/bin/sh
  2. insmod gpioCtl.ko
  3. mknod /dev/gpioCtl c 245 0
复制代码

        我在上面的驱动程序编号申请采用动态分配方式,但是每次分配的主设备号都是254,所以shell中的设备号采用了245,如果各位动态分配的结果不是245,可能需要修改shell,cat /proc/devices 可以察看到分配到的设备号。


        现在将所有文件拷贝到板子上
chmod +x gpioCtl.sh  为脚本增加执行权
./gpioCtl.sh                  运行脚本
./app                             运行程序

如果没有问题的话,现在就能看到 led 闪烁以及读取到的 p2-1的电平了!


截图2.png

        今天就写到这里吧!感谢各位捧场!

点评

app.c文件不完整,你们都没发现吗?最初我还以为楼主不小心少了2个分号,运行发现,没有检测P2-1端口和打印这部分的代码。  发表于 2014-1-26 13:15
回复

使用道具 举报

  • TA的每日心情
    奋斗
    2014-7-16 09:10
  • 签到天数: 361 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2013-9-17 08:52:18 | 显示全部楼层
    大力支持,要多多原创哟!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2015-8-17 09:38
  • 签到天数: 361 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2013-9-17 22:31:36 | 显示全部楼层
    好复杂的驱动啊
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2014-3-24 12:06:15 | 显示全部楼层
    楼主应用程序不全啊
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2017-12-27 09:25
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    发表于 2017-12-27 11:43:17 | 显示全部楼层
    谢谢分享。
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    擦汗
    2018-3-28 09:41
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    发表于 2018-3-22 11:40:23 | 显示全部楼层
    你是用什么软件编的程序?
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 04:48 , Processed in 0.166343 second(s), 29 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.