中断处理程序是管理硬件的驱动程序的组成部分,
每一设备都有相关的驱动程序,驱动程序可以通过request_irq()函数注册一个中断处理程序,
并且激活给定的中断号,来处理指定的中断. 同时需要芯片对应的IO口支持中断
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <asm/gpio.h>
#include <plat/gpio_keys.h>
// 建立该结构体用于把按键的相关信息封装起来传递到中断服务函数中
struct gpio_button_data {
struct example_gpio_keys_button *button;
struct input_dev *input;
};
// 建立结构体用于保存数据 在卸载驱动模块的时候释放相关的内存
struct gpio_keys_drvdata {
struct input_dev *input;
struct gpio_button_data data[0];
};
static int gpio_get_value(unsigned long pin){
// 读取按键值
return example_gpio_getpin(pin);
}
static int gpio_init(struct example_gpio_keys_button *button){
example_setpin_as_gpio(button->gpio);
example_gpio_cfgpin(button->gpio, button->dir); // set AK_GPIO_DIR_INPUT
example_gpio_intpol(button->gpio, button->int_pol); // default=AK_GPIO_INT_HIGHLEVEL
return 0;
}
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct example_gpio_keys_button *button = bdata->button;
int gpio_level;
gpio_level = gpio_get_value(button->gpio);
// 修改中断条件 否则会一直处于触发状态导致该中断函数一直被调用
// (本按键驱动为高电平触发中断)
if (gpio_level){
revert_irq_polarity(irq, 0);
}
if (!gpio_level){
revert_irq_polarity(irq, 1);
}
int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;
printk("%s[EV_KEY,%d,%d]\n", __func__, button->code, !!state);
return IRQ_HANDLED;
}
static struct example_gpio_keys_button exp_buttons[] = {
[0] = {
.code = 0x1001,
.gpio = EXP_GPIO_03, // 3号按键
.active_low = 0,
.desc = "test-key01",
.debounce_interval = 10,
.wakeup = 1,
.pullup = -1,
.pulldown = -1,
.dir = EXP_GPIO_DIR_INPUT, // 设置为输入口
.int_pol = EXP_GPIO_INT_HIGHLEVEL, // 中断方向 需要进行循环变化
},
};
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_drvdata *ddata;
struct input_dev *input;
struct exp_gpio_keys_platform_data *pdata = pdev->dev.platform_data;
int i = 0;
int error;
pdata->buttons = exp_buttons;
pdata->nbuttons = ARRAY_SIZE(exp_buttons);
ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
input = input_allocate_device();
if (!ddata || !input) {
error = -ENOMEM;
goto fail1;
}
platform_set_drvdata(pdev, ddata);
input->name = pdev->name;
input->phys = "gpio-keys/input0";
input->dev.parent = &pdev->dev;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
ddata->input = input;
struct example_gpio_keys_button *button = &exp_buttons[0];
struct gpio_button_data *bdata = &ddata->data[0];
int irq;
bdata->input = input;
bdata->button = button;
error = gpio_init(button);
if (error < 0) {
exp_gpio_free(button->gpio);
goto fail2;
}
irq = exp_gpio_to_irq(button->gpio); // 获取按键对应的中断号
if (irq < 0) {
error = irq;
exp_gpio_free(button->gpio);
goto fail2;
}
void* dev_id = bdata;
error = request_irq(irq, gpio_keys_isr, IRQF_TRIGGER_HIGH, "gpio_keys", dev_id);
if (error) {
exp_gpio_free(button->gpio);
goto fail2;
}
// 设置输入设备可以上报哪些输入事件(按键/触摸/LED/...)
input_set_capability(input, EV_KEY, button->code);
error = input_register_device(input);
if (error) {
goto fail2;
}
// 唤醒能力, 提供所有的wakeup信息。
device_init_wakeup(&pdev->dev, 1);
return 0;
fail2:
while (--i >= 0) {
free_irq(exp_gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
exp_gpio_free(pdata->buttons[i].gpio);
}
platform_set_drvdata(pdev, NULL);
fail1:
input_free_device(input);
kfree(ddata);
return error;
}
static int __devexit gpio_keys_remove(struct platform_device *pdev){
struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdev);
struct input_dev *input = ddata->input;
struct exp_gpio_keys_platform_data *pdata = pdev->dev.platform_data;
int i;
device_init_wakeup(&pdev->dev, 0);
for (i = 0; i < pdata->nbuttons; i++) {
int irq = exp_gpio_to_irq(pdata->buttons[i].gpio);
free_irq(irq, &ddata->data[i]);
exp_gpio_free(pdata->buttons[i].gpio);
}
input_unregister_device(input);
return 0;
}
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = __devexit_p(gpio_keys_remove),
.driver = {
.name = "gpio_keys",
.owner = THIS_MODULE,
}
};
static int __init gpio_keys_init(void){
return platform_driver_register(&gpio_keys_device_driver);
}
static void __exit gpio_keys_exit(void){
platform_driver_unregister(&gpio_keys_device_driver);
}
module_init(gpio_keys_init);
module_exit(gpio_keys_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mr.linux.debug");
MODULE_DESCRIPTION("Demo gpio-irq-key for kernel module");
相关函数说明
input_allocate_device | 分配一个设备结构体,并初始化成员 作为input子系统的核心函数 |
request_irq | 注册中断服务函数 (中断号,中断服务函数,高/低电平触发,"gpio_keys",传递给中断函数的参数) |
input_set_capability | 设置输入设备可以上报哪些输入事件 (本例程上报的是按键事件EV_KEY, 还有EV_ABS/EV_LED等) |
input_register_device | 该函数将input_dev结构体注册到输入子系统 (输入子系统核心[input core]提供的函数) |
free_irq | 对应request_irq (释放相关的中断任务) |
platform_set_drvdata | 把相关的附加数据保存到设备信息的结构体中 可以在其他位置或者线程使用(platform_get_drvdata)进行读取出来 |
platform_get_drvdata | 读取 platform_set_drvdata 保存到设备信息中的附加数据 platform_get_drvdata(struct platform_device *) |
需要注意的地方
1 查询芯片对应的IO口是否具备中断功能(是否有对应的中断号)
2 需要在中断服务函数中修改中断条件(修改中断电平),否则中断服务函数会在满足条件下一直调用
3 由于中断处理程序需要占用CPU的时间,为了CPU尽快恢复中断现场,有耗时操作,建议打包成任务,排到内核的工作队列中进行处理
相应的一些宏定义
EV_SYN | 表示设备支持所有的事件 |
EV_KEY | 键盘或者按键,表示一个键码 |
EV_REL | 鼠标设备,表示一个相对的光标位置结果 |
EV_ABS | 手写板产生的值,其是一个绝对整数值 |
EV_LED | LED灯设备 |
EV_SND | 蜂鸣器,输入声音 |
EV_REP | 允许重复按键类型 |
IRQF_SHARED | 表示共享相同的中断号,很多人可以占用这个中断号 |
IRQF_TRIGGER_HIGH | 高电平中断 |