本文主要描述了GPIO口的申請和高低電平輸出、輸入,以及中斷的申請和調用等
Linux Kernel 中使用GPIO,不能直接引用和操作GPIO的物理地址,而需要把物理地址映射爲內存中的虛擬地址,然後對映射的虛擬地址進行操作。
大致情況是這樣的,以下通過實際代碼分析linux GPIO的簡單的驅動分析。
一、一種情況的GPIO驅動和IRQ中斷服務申請
1.1、源碼實例:
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <asm/irq.h>
#include <mach/hardware.h>
#include <plat/regs-serial.h>
#include "samsung.h"
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <mach/hardware.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <plat/s5pv210.h>
#include <plat/gpio-cfg.h>
#include <linux/gpio.h>
struct scan_button_irq_desc
{
int irq;
int pin;
int pin_setting;
// int number;
char *name;
};
static struct scan_button_irq_desc scan_button_irq[] =
{
{IRQ_EINT7,S5PV210_GPH0(7),IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"scan_trig"},
};
static irqreturn_t s3c_gpio_scan_isr(int irq,void* dev_id)
{
unsigned int key_status;
u32 cfg;
//void __iomem *base = S5PV210_GPG1DAT;
cfg = readl(S5PV210_GPG1CON);
cfg &= (0xf<<20); //clear gpg1_5 con
cfg |= (1 << 20); //out put cfg
writel(cfg,S5PV210_GPG1CON);
printk("scanmodule pwr ctrl con: %d",readl(S5PV210_GPG1CON));
cfg = readl(S5PV210_GPG1DAT);
cfg |= (1 << 5);
writel(cfg,S5PV210_GPG1DAT);
printk("scanmodule pwr ctrl dat: %d",readl(S5PV210_GPG1DAT));
key_status = (readl(S5PV210_GPH0DAT)) & (1 << 7);
printk("scan_button,keyval:%d : %s\n", GPIO_KEY, key_status?"released":"pressed");
return IRQ_HANDLED;
}
static int s3c_gpio_scan_isr_setup(void)
{
int ret;
~~~~
s3c_gpio_cfgpin(S5PV210_GPH0(7), S3C_GPIO_SPECIAL(0xf)); //配置gpio爲中斷輸出,Eint7
s3c_gpio_setpull(S5PV210_GPH0(7), S3C_GPIO_PULL_UP); //S3C_GPIO_PULL_NONE
set_irq_wake(scan_button_irq[0].irq, 1);
ret = request_irq(scan_button_irq[0].irq, s3c_gpio_scan_isr,
IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING,
scan_button_irq[0].name, (void *)&scan_button_irq);
if (ret) {
printk("request IRQ for scan triger failed\n");
ret = -EIO;
}
~~~~
return ret;
}
static int __init XXX_init(void)
{
~~~~
//中斷申請
ret = s3c_gpio_scan_isr_setup();
if(!ret)
printk("IRQ request successfully\n");
else
printk("IRQ request failed");
~~~~
return 0;
}
static void __exit XXX_exit(void)
{
~~~~
//IRQ srv free
free_irq(scan_button_irq[0].irq,(void *)&scan_button_irq);
~~~~
}
1.2、中斷申請函數原型及分析:
1.2.1、原型
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *,struct pt_regs *),unsigned long flags,
const char *dev_name,
void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
1.2.2、分析
1)、unsigned int irq:所要註冊的中斷號
2)、irqreturn_t (*handler)(int, void *, struct pt_regs *):中斷服務程序的入口地址。
3)、unsigned long flags:與中斷管理有關的位掩碼選項,有三組值:
SA_INTERRUPT : 快速中斷處理程序,當使用它的是後處理器上所有的其他中斷都被禁用;
SA_SHIRQ : 該中斷是在設備之間可共享的;
SA_SAMPLE_RANDOM : 這個位表示產生的中斷能夠有貢獻給 /dev/random和 /dev/urandom 使用的加密池。
4)、const char *dev_name:設備描述,表示那一個設備在使用這個中斷。
5)、void *dev_id:用作共享中斷線的指針. 它是一個獨特的標識, 用在當釋放中斷線時以及可能還被驅動用來指向它自己的私有數據區(來標識哪個設備在中斷) 。這個參數在真正的驅動程序中一般是指向設備數據結構的指針.在調用中斷處理程序的時候它就會傳遞給中斷處理程序的void *dev_id。(這是我的理解)如果中斷沒有被共享, dev_id 可以設置爲 NULL, 但是使用這個項指向設備結構不管如何是個好主意. 我們將在"實現一個處理"一節中看到
dev_id 的一個實際應用。
中斷號的查看可以使用下面的命令:“cat /proc/interrupts”。
# cat /proc/interrupts
CPU0
24: 109 s3c-uart s5pv210-uart
26: 164 s3c-uart s5pv210-uart
35: 0 s5p_vic_eint key pgio
36: 0 s5p_vic_eint pmic Tick
38: 1 s5p_vic_eint rt5625 HP Hotplug
39: 15 s5p_vic_eint scan_trig
45: 0 s5p_vic_eint hpd
58: 1 VIC System timer
59: 0 VIC s3c2410-wdt
61: 271883 VIC rtc-tick
78: 1992 VIC s3c2410-i2c.0
83: 0 VIC s3c2410-i2c.2
87: 0 VIC ehci_hcd:usb1
88: 5180 VIC s3c-udc
90: 4528 VIC mmc0
91: 0 VIC mmc1
92: 0 VIC mmc2
97: 1 VIC s3cfb, s3cfb
101: 0 VIC s3c-fimc0
102: 0 VIC s3c-fimc1
103: 90 VIC s3c-fimc2
104: 0 VIC s3c-jpg
105: 0 VIC s5p-g2d
106: 740 VIC PowerVR
107: 0 VIC s5p-tvout
108: 0 VIC s5p-tvout
109: 57 VIC s3c2410-i2c.1
110: 0 VIC s3c-mfc
111: 0 VIC s5p-tvout
121: 1 VIC s3c-keypad
130: 16 VIC mmc3
131: 0 VIC s5p-cec
137: 0 VIC s3c_action
138: 0 VIC s3c_updown
Err: 0
#
/proc/stat 記錄了幾個關於系統活動的低級統計量, 包括(但是不限於)自系統啓動以來收到的中斷數. stat 的每一行以一個文本字串開始, 是該行的關鍵詞; intr 標誌是我們在找的.
第一個數是所有中斷的總數, 而其他每一個代表一個單個 IRQ 線, 從中斷 0 開始. 所有的計數跨系統中所有處理器而彙總的. 這個快照顯示, 中斷號 4 已使用 1 次, 儘管當前沒有安裝處理. 如果你在測試的驅動請求並釋放中斷在每個打開和關閉循環, 你可能發現 /proc/stat 比 /proc/interrupts 更加有用.
二、GPIO Driver 另外一種申請方法
~~~~
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <plat/gpio-cfg.h>
#include <plat/regs-fb.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-mem.h>
#include <mach/regs-gpio.h>
#include <mach/gpio.h> //s5pv210 base address define
#include <mach/gpio-bank.h>
~~~~
if(gpio_request(S5PV210_GPJ1(3), "GPJ1_3")) //S5PV210_GPJ1(3)爲GPIO的編號
printk("S5PV210_GPJ1(3) ERR!\n");
if(gpio_request(S5PV210_GPJ1(4), "GPJ1_4"))
printk("S5PV210_GPJ1(4) ERR!\n");
if(gpio_request(S5PV210_GPJ1(5), "GPJ1_5"))
printk("S5PV210_GPJ1(5) ERR!\n");
if(gpio_request(S5PV210_GPJ0(5), "GPJ0_5"))
printk("S5PV210_GPJ0(5) ERR!\n");
gpio_direction_output(S5PV210_GPJ1(3), 0); //直接設置爲輸出功能,並且直接輸出對應的高低電平
gpio_direction_output(S5PV210_GPJ1(4), 0);
gpio_direction_output(S5PV210_GPJ1(5), 0);
gpio_direction_output(S5PV210_GPJ0(5), 0);
~~~~
gpio_free(S5PV210_GPJ1(3));
gpio_free(S5PV210_GPJ1(4));
gpio_free(S5PV210_GPJ1(5));
gpio_free(S5PV210_GPJ0(5));
~~~~
gpio_set_value(port_num,0/1) 一般只是在這個GPIO口的寄存器上寫上某個值,至於這個端口是否設置爲輸出,它就管不了!而gpio_direction_output (port_num,0/1),在某個GPIO口寫上某個值之後,還會把這個端口設置爲輸出模式。 因此,有人也許就會建議,把gpio_set_value這個函數直接去掉不用,是否可以,顯然是可以的。 但是爲什麼系統還要用呢, 我個人分析是, 系統開發人員在要結合這兩者來使用,以便提高效率。 一般某個端口設置好了輸入與輸出模式後,最好不要經常變動。 首先要調用gpio_direction_output(),以後要設置高低電平時,直接使用gpio_set_value()就可以了,這樣可以省卻再次調用設置輸出模式的操作,從而提高運行效率!