BeagleBone Black GPIO IRQ 驱动

来源:互联网 发布:手机淘宝预约快递寄件 编辑:程序博客网 时间:2024/04/27 20:13

最近打算把GPIO的中断驱动起来,本打算网上能找到现成的驱动程序,可是找了几个测试都不好使,经过一番研究终于测试好使了,在此分享出来。

一、 说明
Beaglebone Black开发板自带GPIO以及IRQ等驱动程序,所以为驱动的开发提供了极大的便利,在此主要分析内核自带的库文件中相关的函数以及驱动编写的步骤。


二、 IRQ库函数分析
 驱动添加的库函数有:
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
其中,库位于/kernel/kernel/include/linux路径下,
gpio.h库函数包含对IO口操作的相关函数,例如
static inline int gpio_get_value(unsigned int gpio) 获取引脚值
static inline void gpio_set_value(unsigned int gpio, int value)设置引脚值
static inline int gpio_to_irq(unsigned int gpio)把gpio引脚号转换为中断号
static inline int irq_to_gpio(unsigned int irq)把中断号转换为引脚号
static inline int gpio_direction_input(unsigned gpio)引脚方向设置为输入
static inline int gpio_direction_output(unsigned gpio, int value)引脚方向设置为输出
static inline int gpio_get_value(unsigned gpio)获取引脚的状态值
以上这些函数实际上已经完全可以实现GPIO所需的功能需求,换句话说,GPIO需要的驱动函数无非以上这些,当然仅限于IO操作,不涉及中断等
interrupt.h库函数主要包含中断相关的参数宏定义以及相关函数:
中断触发方式宏定义:
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001   上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002   下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004高电平触发
#define IRQF_TRIGGER_LOW 0x00000008        低电平触发
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)        貌似任意信号触发
#define IRQF_TRIGGER_PROBE 0x00000010
函数:
中断申请函数
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
中断释放函数
extern void free_irq(unsigned int, void *)






三、 中断程序分析


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>


#define PIN_A_GPIO 65 // is on 2*32+1 设置中断引脚为GPIO2_1
#define PIN_A_FLAGS GPIOF_IN
#define PIN_A_LABEL "HI_PIN_A"
int k;
static irqreturn_t irq_handler_pin_a (int irq, void *dev_id)  //中断处理函数
{
  printk (KERN_INFO "Hello from irq_handler_pin_a...%d\n",k);
  k++;
  return IRQ_HANDLED;
}
//加载函数,实现中断申请等相关功能
static int __init hello_interrupts_start (void) 
{
  int retval, irq, regval;
  printk (KERN_INFO "Loading hello_interrupts module...\n");
  retval = gpio_request_one(PIN_A_GPIO, PIN_A_FLAGS, PIN_A_LABEL);
printk (KERN_INFO "HI:request GPIO pin#%i for IRQ (%i)\n", PIN_A_GPIO, retval);
irq = gpio_to_irq (PIN_A_GPIO);
retval = request_irq (irq, irq_handler_pin_a, 1/*IRQF_TRIGGER_RISING */, PIN_A_LABEL, NULL);
irq_set_irq_type (irq, IRQ_TYPE_EDGE_BOTH);
 return 0;
}
//驱动卸载
static void __exit hello_interrupts_end(void)
 {
  printk ("HI: Releasing IRQ resources...\n");
  free_irq (gpio_to_irq (PIN_A_GPIO), NULL);
  gpio_free (PIN_A_GPIO);
}
module_init (hello_interrupts_start);
module_exit (hello_interrupts_end);
MODULE_AUTHOR("000");
MODULE_DESCRIPTION("A sample driver using interrupts");
MODULE_LICENSE("GPL");
四、  程序使用的函数分析
1、 retval = gpio_request_one(PIN_A_GPIO, PIN_A_FLAGS, PIN_A_LABEL);
申请一个单独的GPIO,使用flag作为初始参数,函数原型为:
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label); 
flag用来配置下面的特性



其实库文件里面提供的函数还有很多的,例如gpio_request_one是只申请一个GPIO,库文件还提供同时申请好几个的函数,在最后分享。最后,把在网上看到的别人的对相关函数分析复制下,造福众人。

(来源:http://blog.csdn.net/yuanlulu/article/details/6425031)

标识端口-------------

gpio使用0~MAX_INT之间的整数标识,不能使用负数。
使用以下函数检查一个端口号的合法性:
int gpio_is_valid(int number);

• 使用gpio
-------------
使用io的第一步是分配端口,使用 gpio_request()。
接下来要做的是标记它的方向。
/*设为输入或者输出,成功返回零或者失败返回负的错误值*/
 int gpio_direction_input(unsigned gpio);
 int gpio_direction_output(unsigned gpio, int value);
通常应该检查它们的返回值。通常应该假定这两个接口会在线程上下文中调用。但是
对于自旋锁安全的gpio也可以在线程出现之前的早期初始化之间使用。

对于输出的gpio,提供的值作为输出的初始值。

• 使用自旋锁安全的gpio
----------------------------------
大部分gpio控制器可以使用内存读写指令来访问,不需要睡眠,可以在中断上下文访问。

对于gpio_cansleep返回假的gpio可以使用下面的接口访问:
/*读取输入,返回零或非零*/
int gpio_get_value(unsigned gpio);
/*输出*/
void gpio_set_value(unsigned gpio, int value);
返回的值是布尔值,零代表低,非零代表搞电平。当读取输出引脚的值,返回值应该是引脚上的实际状态,
这个值不一定等于配置的输出值,因为从设定信号到信号稳定需要一定时间。

不是所有的平台可以读取输出的引脚值,这些平台不能总是返回零。用这两这两个函数访问
不能非在睡眠的上下文中安全访问的gpio将是一个错误。

• 可被休眠上下文中访问的GPIO
----------------------------------------------
一些gpio控制器必须使用基于信息的总线,比如i2c和spi.读写gpio值的命令需要在队列中等待。
操作这些gpio可能会睡眠,不能在中断上下文中调用。

支持这种gpio的平台为了通过在这个函数中返回非零来区分其它
类型的gpio(需要一个已经被gpio_request申请的gpio号):
int gpio_cansleep(unsigned gpio);

为了访问这些端口,定义了另一组函数接口:
/*输入端口:返回零或非零,可能睡眠*/
int gpio_get_value_cansleep(unsigned gpio);
/*输出端口:可能睡眠*/
void gpio_set_value_cansleep(unsigned gpio, int value);
只能在允许睡眠的上下文中访问这些端口,比如线程化的中断中,
必须使用这些接口而不是没有cansleep前缀的自旋锁安全接口。


除了这些接口可能睡眠这个事实之外,它们操作那些不能在中断处理函数中访问的端口,这些调用的表现和
自旋锁安全的调用表现一致。

另外:调用安装和配置这样的gpio必须是在可睡眠的上下文中,因为他们可能需要访问gpio控制器。
        gpio_direction_input()
        gpio_direction_output()
        gpio_request()

##      gpio_request_one()
##      gpio_request_array()
##      gpio_free_array()

        gpio_free()
         gpio_set_debounce()


• 申请和释放gpio
------------------------
为了获取系统配置错误,定义了两个调用:
/*申请gpio,返回0或负的错误值
* 非空的lables指针有助于诊断*/
int gpio_request(unsigned gpio, const char *label);
/*释放之前申请的gpio*/
void gpio_free(unsigned gpio);
应该假设这两个函数实在进程上下文中调用的,但对于自旋锁安全的gpio也可从
在进程建立之前的早期启动过程中调用。

考虑到大多数情况下gpio会在申请过后立即需要被配置,下面三个接口负责这些工作:
/*申请一个单独的gpio,使用“flag”作为初始的配置参数*/
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
/*在一个调用中申请多个gpio*/
int gpio_request_array(struct gpio *array, size_t num);
/*释放多个端口*/
void gpio_free_array(struct gpio *array, size_t num);

“falg”用来配置下面的特性:
 
         * GPIOF_DIR_IN          - 配置方向为输入
         * GPIOF_DIR_OUT         -配置方向为输出
 
         * GPIOF_INIT_LOW        - 做输出引脚,输出低电平
         * GPIOF_INIT_HIGH       - 做输出引脚,输出高电平

为了同时处理多个gpio,定义了一个专门结构体:
         struct gpio {
                 unsigned        gpio;
                 unsigned long   flags;
                 const char      *label;
         };

典型的使用如下:
 327        static struct gpio leds_gpios[] = {
 328                { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
 329                { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
 330                { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
 331                { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
 332                { ... },
 333        };
 334
 335        err = gpio_request_one(31, GPIOF_IN, "Reset Button");
 336        if (err)
 337                ...
 338
 339        err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
 340        if (err)
 341                ...
 342
 343        gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));
  
  • GPIO映射到IRQ
----------------------------
 352        /* map GPIO numbers to IRQ numbers */
 353        int gpio_to_irq(unsigned gpio);
 354
 355        /* map IRQ numbers to GPIO numbers (avoid using this) */
 356        int irq_to_gpio(unsigned irq);
返回值是对应的编号或者是负的错误码。使用未用gpio_direction_input()安装的gpio编号或者不是从
gpio_to_irq()得到的中断号是不会被gpio检查的错误。

gpio_to_irq()返回的中断编号可以传给request_irq()和free_irq()。
irq_to_gpio()返回的gpio编号通常用来调用gpio_get_value(),比如在沿触发的中断中获取引脚的状态。
有些平台不支持这种映射,应该避免调用映射函数。

模拟漏极开路
------------------
gpio可能支持也可能不支持漏极开路输出。如果硬件不支持,可以模拟漏极开路输出的输入或者输出:
LOW:    gpio_direction_output(gpio, 0)  忽略上拉电阻。
HIGH:    gpio_direction_input(gpio) 关闭输出,上拉电阻控制信号。
如果驱动输出高电平但是gpio_get_value(gpio)报告的电平是低电平(经过一定延迟之后),可以断定
其它部分正在信号线上输出低电平。

GPIO 的框架(可选)
===============
GPIO的框架在gpiolib中实现。
如果使能了 debugfs,系统下会出现 /sys/kernel/debug/gpio这个文件。这个文件内
将会列出所有注册在框架内的控制器和当前使用的gpio的状态。

控制器驱动:gpio_chip
-------------------------------
在gpio框架中使用"struct gpio_chip"表示每个控制器的所有信息:
--设置gpio方向的方法
--访问gpio状态的方法
--表示是否会睡眠的标志
--可选的debugfs方法
--诊断用的标签lable(char指针)
当然还有每个实例的独有数据(可能来自dev.platform_data);第一个gpio的编号和管理的gpio数量。

gpiolib支持多个gpio控制器的实例,使用gpiochip_add()来注册,使用gpiochip_remove()注销。
gpio_chip的debugfs方法将会忽略未被申请的gpio。使用gpiochip_is_requested()将会返回NULL(未被申请)或者
lable(已被申请)

平台支持
------------
为了支持gpio框架,必须选择Kconfig中的选项:ARCH_REQUIRE_GPIOLIB 或者 ARCH_WANT_OPTIONAL_GPIOLIB。并且
在 <asm/gpio.h>包含<asm-generic/gpio.h>,还要定义三个函数: gpio_get_value(), gpio_set_value(), and gpio_cansleep().
同时也可以提供ARCH_NR_GPIOS的定义,以反映平台支持的gpio数量。

ARCH_REQUIRE_GPIOLIB表示gpiolib代码总是会编译进内核中。
ARCH_WANT_OPTIONAL_GPIOLIB表示gpiolib默认是关闭的,但是用户可以使能它并把它编译进内核。
如果这两个选项一个都未被选中,gpiolib则不能被用户使能。

通常那三个函数接口可以直接使用框架内的代码(通过gpio_chip来调用):
             #define gpio_get_value        __gpio_get_value
             #define gpio_set_value        __gpio_set_value
             #define gpio_cansleep         __gpio_cansleep


用户空间的sysfs接口(可选)
==================

在sysfs下的路径
---------------------
在/sys/class/gpio下有三种类型的入口:
            --用户空间控制GPIO的接口(第一类)
            --对应具体GPIO
            --GPIO控制器("gpio_chip"的实例)(第三类)
以上接口不包括标准“device”文件和它们的链接。

控制接口(第一类)是只写的:
            /sys/class/gpio/
                        "export" 用户空间通过写入gpio的编号来向内核申请将某个gpio的控制权导出到用户空间
                                    比如“echo 19 > export ”将会为19号gpio创建一个节点“gpio19”,前提是没有内和代码申请了这个端口。
                        “unexport” 和导出的效果相反。
                                    比如“echo 19 > unexport”将会移除“gpio19”这个节点。
GPIO管脚的路径类似于/sys/class/gpio/gpio42/ (for GPIO #42)的形式,并且有以下可读写的属性:
            /sys/class/gpio/gpioN/   (第二类)
                        “direction” 读取结果是“in”或者“out”。也可以往其中写入。写入“out”默认将引脚值初始化为低。
                                    写入“low”或“high”可以初始化作为输出时的初始值。
                                    如果内核不支持或者内核代码不愿意,将不会存在这个属性。
                        “value” 读取结果是0(低电平)或1(高电平)。如果GPIO被配置为输出,这个值是可写的,
                                    任何非零的值都将输出高电平。
                                    如果某个引脚能并且已经被配置为产生中断,可以在它的节点上调用poll(2)并且poll(2)将在中断触发后返回。
                                    如果使用poll(2),设置事件类型为POLLPRI和POLLERR。如果使用sellect(2),将文件描述符加入exceptfds。
                                    在poll(2)返回后,可以使用lseek(2)移动到文件开头读取新的值或者关闭它再重新打开读取新值。
                        “edge”   读取改节点将会得到以下值: "none", "rising", "falling",或者  "both"。写如这些字符串到这个节点中将会选择
                                    唤醒在“value”上的poll(2)的信号沿。
                                    这个文件节点只有在引脚能被配置为输入中断引脚的时候才存在。
                        "active_low" 读取值是0(假)或者1(真)。写入任何非零的值都将反转“value”中读取和写入的值。
                                    已经在使用和之后使用poll(2)的“edge”节点的“rasing”和“falling”值将会受"active_low" 的影响。
                                    (自己的理解:这个值实际是将高变为0,低变为1。也就是实现了逻辑反转。)

GPIO控制器的路径类似于/sys/class/gpio/gpiochip42/(这个控制器的最小端口是42)并且有以下的读写属性:
            /sys/class/gpio/gpiochipN/
                        “base” 和N相同,也就是控制器管理的最小的端口编号。
                        “lable”  诊断使用的标志(并不总是唯一的)
                        “ngpio” 控制器管理的端口数量(端口范围是:N ~ N+ngpio-1)


从内核空间中导出
------------------------
内核可以对已经被 gpio_request()申请的gpio的导出进行明确的管理。
          /* 导出GPIO到用户空间的sysfs,当允许用户空间修改gpio的方向,第二个参数是真 */
           int gpio_export(unsigned gpio, bool direction_may_change);
          /* 撤销GPIO的导出 */
           void gpio_unexport();
          /* 创建到导出GPIO的 sysfs link ,第一个参数是在哪个dev下创建,第二个参数名字,第三个是gpio编号 */
           int gpio_export_link(struct device *dev, const char *name, unsigned gpio)
          /* 改变sysfs中GPIO节点的极性,也就是将高低电平的逻辑反转。可以在export之前和之后进行。之前设置的enabled poll(2)也会相应改变   */
           int gpio_sysfs_set_active_low(unsigned gpio, int value);


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
以上内容是内核文档的内容,下面分析gpio_chip的内容,这是一个控制器。
  48/**
  49 * struct gpio_chip - 一个 GPIO 控制器的抽象
  50 * @label: 诊断用的标签(名字)
  51 * @dev: 可选的设备结构体
  52 * @owner: 拥有者模块指针
  53 * @request: 可选的激活回调函数,就像电源管理的接口一样。可以睡眠
  55 * @free: 可选的释放回调函数,可以睡眠。
  57 * @direction_input: 配置某个偏移对应的端口为输入引脚,可能返回错误。
  58 * @get: 返回相应偏移引脚的值; 如果是输出引脚则返回输出引脚的状态,或者返回零。
  60 * @direction_output: 配置输出引脚的输出值,或者返回错误
  61 * @set: 设置指定引脚的输出值。
  62 * @to_irq: 可选的回调函数,支持非静态的 gpio_to_irq()映射, 不可以睡眠 ;
  64 * @dbg_show: 可选的 debugfs接口; 如果不设置,内核会使用默认函数赋给这个指针。
  67 * @base: gpiogpio_chip所管理的第一个gpio的编号;如果注册期间是负值,将会动态申请一个值。
  69 * @ngpio: GPIO控制器管理的IO数目,最后一个IO的号码是  (base + ngpio - 1).
  71 * @can_sleep: 如果get()/set() 方法会睡眠这个标志必须被设置
  73 * @names:如果设置了这个成员,它必须是大小为 ngpio的字符指针数组,每个成员可以指向相应引脚的特殊的名字,也可以为空。
                         name影响的是exportGPIO到sysfs的名字,如果不设置的话名字就是“gpioN”(N是IO对应的编号)。
                         当然这个成员可以不初始化。

gpio_chip使用offset(0~(ngpio - 1))来分辨不同的引脚。
  */

struct gpio_chip {
  91        const char              *label;
  92        struct device           *dev;
  93        struct module           *owner;
  94
  95        int                     (*request)(struct gpio_chip *chip,
  96                                                unsigned offset);
  97        void                    (*free)(struct gpio_chip *chip,
  98                                                unsigned offset);
  99
 100        int                     (*direction_input)(struct gpio_chip *chip,
 101                                                unsigned offset);
 102        int                     (*get)(struct gpio_chip *chip,
 103                                                unsigned offset);
 104        int                     (*direction_output)(struct gpio_chip *chip,
 105                                                unsigned offset, int value);
 106        int                     (*set_debounce)(struct gpio_chip *chip,
 107                                                unsigned offset, unsigned debounce);
 108
 109        void                    (*set)(struct gpio_chip *chip,
 110                                                unsigned offset, int value);
 111
 112        int                     (*to_irq)(struct gpio_chip *chip,
 113                                                unsigned offset);
 114
 115        void                    (*dbg_show)(struct seq_file *s,
 116                                                struct gpio_chip *chip);
 117        int                     base;
 118        u16                     ngpio;
 119        const char              *const *names;
 120        unsigned                can_sleep:1;
 121        unsigned                exported:1;
 122
 123#if defined(CONFIG_OF_GPIO)
 124        /*
 125         * If CONFIG_OF is enabled, then all GPIO controllers described in the
 126         * device tree automatically may have an OF translation
 127         */
 128        struct device_node *of_node;
 129        int of_gpio_n_cells;
 130        int (*of_xlate)(struct gpio_chip *gc, struct device_node *np,
 131                        const void *gpio_spec, u32 *flags);
 132#endif
 133};

gpio_chip的接口:
extern const char *gpiochip_is_requested(struct gpio_chip *chip, unsigned offset); 如果offet对应的gpio已经被申请了则返回NULL,否则返回lable指针或者“??”


extern int __must_check gpiochip_reserve(int start, int ngpio);
将start开始的ngpio个编号保留
这个函数是在全局范围内预留一段编号,将来供某个gpio_chip使用。


0 0
原创粉丝点击