BeagleBone Black GPIO IRQ 驅動

最近打算把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使用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章