本文主要描述了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()就可以了,这样可以省却再次调用设置输出模式的操作,从而提高运行效率!