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