查看完整版本: 嵌入式Linux设备驱动开发之:GPIO驱动程序实例

admin 发表于 2014-10-10 07:33:17

嵌入式Linux设备驱动开发之:GPIO驱动程序实例

        11.3GPIO驱动程序实例

        11.3.1GPIO工作原理

        FS2410开发板的S3C2410处理器具有117个多功能通用I/O(GPIO)端口管脚,包括GPIO 8个端口组,分别为GPA(23个输出端口)、GPB(11个输入/输出端口)、GPC(16个输入/输出端口)、GPD(16个输入/输出端口)、GPE(16个输入/输出端口)、GPF(8个输入/输出端口)、GPH(11个输入/输出端口)。根据各种系统设计的需求,通过软件方法可以将这些端口配置成具有相应功能(例如:外部中断或数据总线)的端口。
       
        为了控制这些端口,S3C2410处理器为每个端口组分别提供几种相应的控制寄存器。其中最常用的有端口配置寄存器(GPACON ~ GPHCON)和端口数据寄存器(GPADAT ~ GPHDAT)。因为大部分I/O管脚可以提供多种功能,通过配置寄存器(PnCON)设定每个管脚用于何种目的。数据寄存器的每位将对应于某个管脚上的输入或输出。所以通过对数据寄存器(PnDAT)的位读写,可以进行对每个端口的输入或输出。
       
        在此主要以发光二极管(LED)和蜂鸣器为例,讨论GPIO设备的驱动程序。它们的硬件驱动电路的原理图如图11.4所示。
                          http://www.eefocus.com/embedded/322841/file:///C:DOCUME~1ADMINI~1LOCALS~1Tempksohtmlwps_clip_image-12738.png
        图11.4LED(左)和蜂鸣器(右)的驱动电路原理图
       
        在图11.4中,可知使用S3C2410处理器的通用I/O口GPF4、GPF5、GPF6和GPF7分别直接驱动LED D12、D11、D10以及D9,而使用GPB0端口驱动蜂鸣器。4个LED分别在对应端口(GPF4~GPF7)为低电平时发亮,而蜂鸣器在GPB0为高电平时发声。这5个端口的数据流方向均为输出。
       
        在表11.15中,详细描述了GPF的主要控制寄存器。GPB的相关寄存器的描述与此类似,具体可以参考S3C2410处理器数据手册。
        表11.15 GPF端口(GPF0-GPF7)的主要控制寄存器
                                                                                                                        寄存器
                                                                                                                        地址
                                                                                                                        R/W
                                                                                                                        功能
                                                                                                                        初始值
                                                                                                                                                        GPFCON
                                                                                                                        0x56000050
                                                                                                                        R/W
                                                                                                                        配置GPF端口组
                                                                                                                        0x0
                                                                                                                                                        GPFDAT
                                                                                                                        0x56000054
                                                                                                                        R/W
                                                                                                                        GPF端口的数据寄存器
                                                                                                                        未定义
                                                                                                                                                        GPFUP
                                                                                                                        0x56000058
                                                                                                                        R/W
                                                                                                                        GPF端口的取消上拉寄存器
                                                                                                                        0x0
                                                                                                                                                        GPFCON
                                                                                                                        位
                                                                                                                        描述
                                                                                                                                                        GPF7
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT711 = 保留
                                                                                                                                                        GPF6
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT611 = 保留
                                                                                                                                                        GPF5
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT511 = 保留
                                                                                                                                                        GPF4
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT411 = 保留
                                                                                                                                                        GPF3
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT311 = 保留
                                                                                                                                                        GPF2
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT211 = 保留
                                                                                                                                                        GPF1
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT111 = 保留
                                                                                                                                                        GPF0
                                                                                                                       
                                                                                                                        00 = 输入01 = 输出10 = EINT011 = 保留
                                                       
                                                                                                                        GPFDAT
                                                                                                                        位
                                                                                                                        描述
                                                                                                                                                        GPF
                                                                                                                       
                                                                                                                        每位对应于相应的端口,若端口用于输入,则可以通过相应的位读取数据;若端口用于输出,则可以通过相应的位输出数据;若端口用于其他功能,则其值无法确定。
                                                       
                                                                                                                        GPFUP
                                                                                                                        位
                                                                                                                        描述
                                                                                                                                                        GPF
                                                                                                                       
                                                                                                                        0:向相应端口管脚赋予上拉(pull-up)功能
                                                                        1:取消上拉功能
                                                       
        为了驱动LED和蜂鸣器,首先通过端口配置寄存器将5个相应寄存器配置为输出模式。然后通过对端口数据寄存器的写操作,实现对每个GPIO设备的控制(发亮或发声)。在下一个小节中介绍的驱动程序中,s3c2410_gpio_cfgpin()函数和s3c2410_gpio_pullup()函数将进行对某个端口的配置,而s3c2410_gpio_setpin()函数实现向数据寄存器的某个端口的输出。
       
        11.3.2GPIO驱动程序

        GPIO驱动程序代码如下所示:
       
        /* gpio_drv.h */
        #ifndef   FS2410_GPIO_SET_H
        #define   FS2410_GPIO_SET_H
        #include    <linux/ioctl.h>
        #define   GPIO_DEVICE_NAME       "gpio"
        #define   GPIO_DEVICE_FILENAME"/dev/gpio"
        #define   LED_NUM                  4
        #define   GPIO_IOCTL_MAGIC       &#39;G&#39;
        #define   LED_D09_SWT             _IOW(GPIO_IOCTL_MAGIC, 0, unsigned int)
        #define   LED_D10_SWT             _IOW(GPIO_IOCTL_MAGIC, 1, unsigned int)
        #define   LED_D11_SWT             _IOW(GPIO_IOCTL_MAGIC, 2, unsigned int)
        #define   LED_D12_SWT             _IOW(GPIO_IOCTL_MAGIC, 3, unsigned int)
        #define   BEEP_SWT               _IOW(GPIO_IOCTL_MAGIC, 4, unsigned int)
        #define   LED_SWT_ON            0
        #define   LED_SWT_OFF             1
        #define   BEEP_SWT_ON             1
        #define   BEEP_SWT_OFF            0
        #endif /* FS2410_GPIO_SET_H */
       
        /* gpio_drv.c */
        #include <linux/config.h>
        #include <linux/module.h>
        #include <linux/moduleparam.h>
        #include <linux/init.h>
        #include <linux/kernel.h>   /* printk() */
        #include <linux/slab.h>      /* kmalloc() */
        #include <linux/fs.h>       /* everything... */
        #include <linux/errno.h>    /* error codes */
        #include <linux/types.h>    /* size_t */
        #include <linux/mm.h>
        #include <linux/kdev_t.h>
        #include <linux/cdev.h>
        #include <linux/delay.h>
        #include <linux/device.h>
        #include <asm/io.h>
        #include <asm/uaccess.h>
        #include <asm/arch-s3c2410/regs-gpio.h>
        #include "gpio_drv.h"
       
        static int major = 0; /* 采用字符设备号的动态分配 */
        module_param(major, int, 0); /* 以参数的方式可以指定设备的主设备号*/
       
        void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
        { /* 对某个管脚进行配置(输入/输出/其他功能)*/
             unsigned long base = S3C2410_GPIO_BASE(pin); /* 获得端口的组基地址*/
             unsigned long shift = 1;
             unsigned long mask = 0x03; /* 通常用配置寄存器的两位表示一个端口*/
             unsigned long con;
             unsigned long flags;
       
             if (pin < S3C2410_GPIO_BANKB)
             {   
                shift = 0;
                mask= 0x01; /* 在GPA端口中用配置寄存器的一位表示一个端口*/
             }   
             mask <<= (S3C2410_GPIO_OFFSET(pin) << shift);
             local_irq_save(flags); /* 保存现场,保证下面一段是原子操作 */
             con = __raw_readl(base + 0x00);
             con &= ~mask;
          con |= function;
          __raw_writel(con, base + 0x00); /* 向配置寄存器写入新配置数据 */
          local_irq_restore(flags); /* 恢复现场 */
        }
       
        void s3c2410_gpio_pullup(unsigned int pin, unsigned int to)
        { /* 配置上拉功能 */
          unsigned long base = S3C2410_GPIO_BASE(pin); /* 获得端口的组基地址*/
          unsigned long offs = S3C2410_GPIO_OFFSET(pin);/* 获得端口的组内偏移地址 */
          unsigned long flags;
          unsigned long up;
          
          if (pin < S3C2410_GPIO_BANKB)
          {
                return;
          }
       
          local_irq_save(flags);
          up = __raw_readl(base + 0x08);
          up &= ~(1 << offs);
          up |= to << offs;
          __raw_writel(up, base + 0x08); /* 向上拉功能寄存器写入新配置数据*/
          local_irq_restore(flags);
        }
       
        void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
        { /* 向某个管脚进行输出 */
          unsigned long base = S3C2410_GPIO_BASE(pin);
          unsigned long offs = S3C2410_GPIO_OFFSET(pin);
          unsigned long flags;
          unsigned long dat;
       
          local_irq_save(flags);
          dat = __raw_readl(base + 0x04);
          dat &= ~(1 << offs);
          dat |= to << offs;
          __raw_writel(dat, base + 0x04); /* 向数据寄存器写入新数据*/
          local_irq_restore(flags);
        }
       
        int gpio_open (struct inode *inode, struct file *filp)
        { /* open操作函数:进行寄存器配置*/
          s3c2410_gpio_pullup(S3C2410_GPB0, 1); /* BEEP*/   
          s3c2410_gpio_pullup(S3C2410_GPF4, 1); /* LED D12 */   
          s3c2410_gpio_pullup(S3C2410_GPF5, 1); /* LED D11 */   
          s3c2410_gpio_pullup(S3C2410_GPF6, 1); /* LED D10 */   
          s3c2410_gpio_pullup(S3C2410_GPF7, 1); /* LED D9 */   
          s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);
          s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);
          s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF5_OUTP);
          s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF6_OUTP);
          s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF7_OUTP);
          return 0;
        }
        ssize_t gpio_read(struct file *file, char __user *buff,
                                                          size_t count, loff_t *offp)
        { /* read操作函数:没有实际功能*/
          return 0;
        }
        ssize_t gpio_write(struct file *file, const char __user *buff,
                                                             size_t count, loff_t *offp)
        { /* write操作函数:没有实际功能*/
          return 0;
        }
       
        int switch_gpio(unsigned int pin, unsigned int swt)
        { /* 向5个端口中的一个输出ON/OFF值 */
          if (!((pin <= S3C2410_GPF7) && (pin >= S3C2410_GPF4))
                             && (pin != S3C2410_GPB0))
          {
                printk("Unsupported pin");
                return 1;
          }
          s3c2410_gpio_setpin(pin, swt);
          return 0;
        }
       
        static int gpio_ioctl(struct inode *inode, struct file *file,
                                              unsigned int cmd, unsigned long arg)
        { /* ioctl函数接口:主要接口的实现。对5个GPIO设备进行控制(发亮或发声) */
          unsigned int swt = (unsigned int)arg;
          switch (cmd)
          {
                case LED_D09_SWT:
                {
                    switch_gpio(S3C2410_GPF7, swt);   
                }
                break;
       
                case LED_D10_SWT:
                {
                    switch_gpio(S3C2410_GPF6, swt);
                }
                break;
       
                case LED_D11_SWT:
                {
                    switch_gpio(S3C2410_GPF5, swt);
                }
                break;
                case LED_D12_SWT:
                {
                    switch_gpio(S3C2410_GPF4, swt);
                }
                break;
       
                case BEEP_SWT:
                {
                    switch_gpio(S3C2410_GPB0, swt);
                    break;
                }
       
                default:
                {
                    printk("Unsupported command\n");
                    break;
                }
          }
          return 0;
        }
       
        static int gpio_release(struct inode *node, struct file *file)
        { /* release操作函数,熄灭所有灯和关闭蜂鸣器 */
          switch_gpio(S3C2410_GPB0, BEEP_SWT_OFF);
          switch_gpio(S3C2410_GPF4, LED_SWT_OFF);
          switch_gpio(S3C2410_GPF5, LED_SWT_OFF);
          switch_gpio(S3C2410_GPF6, LED_SWT_OFF);
          switch_gpio(S3C2410_GPF7, LED_SWT_OFF);
          return 0;
        }
       
        static void gpio_setup_cdev(struct cdev *dev, int minor,
                struct file_operations *fops)
        { /* 字符设备的创建和注册 */
          int err, devno = MKDEV(major, minor);   
          cdev_init(dev, fops);
          dev->owner = THIS_MODULE;
          dev->ops = fops;
          err = cdev_add (dev, devno, 1);
          if (err)
          {
                printk (KERN_NOTICE "Error %d adding gpio %d", err, minor);
          }
        }
       
        static struct file_operations gpio_fops =
        { /* gpio设备的file_operations结构定义 */
          .owner   = THIS_MODULE,
          .open    = gpio_open,      /* 进行初始化配置*/
          .release = gpio_release,    /* 关闭设备*/
          .read    = gpio_read,      
          .write   = gpio_write,
          .ioctl   = gpio_ioctl,      /* 实现主要控制功能*/
        };
       
        static struct cdev gpio_devs;
        static int gpio_init(void)
        {
          int result;
          dev_t dev = MKDEV(major, 0);
       
          if (major)
          { /* 设备号的动态分配 */
                result = register_chrdev_region(dev, 1, GPIO_DEVICE_NAME);
          }
          else
          { /* 设备号的动态分配 */
                result = alloc_chrdev_region(&dev, 0, 1, GPIO_DEVICE_NAME);
                major = MAJOR(dev);
          }
          if (result < 0)
          {
                printk(KERN_WARNING "Gpio: unable to get major %d\n", major);
                return result;
          }
          gpio_setup_cdev(&gpio_devs, 0, &gpio_fops);
          printk("The major of the gpio device is %d\n", major);
          return 0;
        }
       
        static void gpio_cleanup(void)
        {
          cdev_del(&gpio_devs); /* 字符设备的注销 */
          unregister_chrdev_region(MKDEV(major, 0), 1); /* 设备号的注销*/
          printk("Gpio device uninstalled\n");
        }
       
        module_init(gpio_init);
        module_exit(gpio_cleanup);
        MODULE_AUTHOR("David");
        MODULE_LICENSE("Dual BSD/GPL");         
       
        下面列出GPIO驱动程序的测试用例:
       
        /* gpio_test.c */
        #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        #include <fcntl.h>
        #include <string.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include "gpio_drv.h"
       
        int led_timer(int dev_fd, int led_no, unsigned int time)
        { /*指定LED发亮一段时间之后熄灭它*/
          led_no %= 4;
          ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_ON); /* 发亮*/
          sleep(time);
          ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_OFF); /* 熄灭 */
        }
       
        int beep_timer(int dev_fd, unsigned int time)
        {/* 开蜂鸣器一段时间之后关闭*/
          ioctl(dev_fd, BEEP_SWT, BEEP_SWT_ON); /* 发声*/
          sleep(time);
          ioctl(dev_fd, BEEP_SWT, BEEP_SWT_OFF);    /* 关闭 */
        }
       
        int main()
        {
          int i = 0;
          int dev_fd;
                /* 打开gpio设备 */
          dev_fd = open(GPIO_DEVICE_FILENAME, O_RDWR | O_NONBLOCK);
          if ( dev_fd == -1 )
          {
                printf("Cann't open gpio device file\n");
                exit(1);
          }
       
          while(1)
          {
                i = (i + 1) % 4;
                led_timer(dev_fd, i, 1);
                beep_timer(dev_fd, 1);      
          }
          close(dev_fd);
          return 0;
        }         
       
        具体运行过程如下所示。首先编译并加载驱动程序:
       
        $ make clean;make /* 驱动程序的编译*/
        $ insmod gpio_drv.ko /* 加载gpio驱动 */
        $ cat /proc/devices /* 通过这个命令可以查到gpio设备的主设备号 */
        $ mknod /dev/gpioc2520/* 假设主设备号为252, 创建设备文件节点*/
       
        然后编译并运行驱动测试程序:
       
        $ arm-linux-gcc &ndash;o gpio_testgpio_test.c
        $ ./gpio_test
       
        运行结果为4个LED轮流闪烁,同时蜂鸣器以一定周期发出声响。
页: [1]
查看完整版本: 嵌入式Linux设备驱动开发之:GPIO驱动程序实例