本文主要包含的章节:
一、前期的准备工作
二、Linux输入子系统的简单介绍
三、基于输入子系统的按键驱动的实现
一、前期的准备工作
1、基本的开发环境
交叉开发环境 : Ubuntu12.04
Linux内核版本 : Linux-3.0.86
GUI系统 : Qtopia2.2.0
开发板 : 友善之臂的Tiny4412(Cortex-A9)
2、内核的配置
由于默认的内核在初始化时已经将按键对应的GPIO口添加到平台设备当中,每当内核启动的时候这几个按键都会自动去请求中断,导致中断被占用。所以在编写自己的按键输入子系统驱动之前,要注释掉这个平台设备。
这个平台设备对应的代码在内核中位置是:arch\arm\mach-exynos\mach-tiny4412.c,对应的行数为 :2968,代码如下所示:
static struct platform_device tiny4412_input_device = {
.name = GPIO_EVENT_DEV_NAME,
.id = 0,
.dev = {
.platform_data = &tiny4412_input_data,
},
};
对这个文件进行修改,将 mach-tiny4412.c的3315行注释掉即可。
二、Linux输入子系统的简单介绍
1、输入子系统
常见的输入设备有按键、键盘、鼠标、触摸屏等,它们的工作原理虽然都各不相同,但是工作机制却是相似的。一般来讲都是通过触发中断,然后让CPU来读取键值、坐标值等。显然,在这些工作中,只有中断、读键值、坐标值等是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的VFS(Virtual File System)接口对输入设备是通用的。所以,内核把这些输入事件相似的地方给抽象了出来,设计出了输入子系统。
2、核心函数
核心函数在内核的位置:drivers\input\input.c 和 include\linux\input.h
2.1 分配、释放一个输入设备
struct input_dev *input_allocate_device(void)
void input_free_device(struct input_dev *dev)
2.2 注册、注销输入设备
int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
2.3 报告输入事件的接口
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
/* 报告键值 */
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
/* 报告相对坐标 */
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
/* 报告绝对坐标 */
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
/* 报告同步事件 */
static inline void input_sync(struct input_dev *dev)
3、输入子系统驱动的一般编写步骤
* XEINT27----GPX3_3
* XEINT28----GPX3_4
3.2 按键输入子系统驱动的编写
3.2.1 分配一个名为buttons_dev的input_dev结构体
buttons_dev = input_allocate_device();
if(!buttons_dev)
{
printk("input_allocate_device error!\n");
return -ENOMEM;
}
3.2.2 设置buttons_dev结构体
/* 2.1、支持哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2、支持该类事件中的那些事件 */
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
}
3.2.3 注册这个结构体
input_register_device(buttons_dev);
3.2.4 硬件相关的操作
/* 4、硬件相关的操作
* 为每个按键申请一个中断,共用中断处理函数
* 按键触发方式为双边沿触发
*/
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
irq = gpio_to_irq(buttons_desc[i].gpio);
request_irq(irq, yl_buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, buttons_desc[i].name, (void*)&buttons_desc[i]);
}
在硬件操作的核心是请求中断,中断的核心是 yl_buttons_irq 这个函数,这个函数的代码如下所示:
/* 按键中断处理程序 */
static irqreturn_t yl_buttons_irq(int irq, void *devid)
{
struct yl_buttons_desc *buttons_desc = (struct yl_buttons_desc *)devid;
int pinval = gpio_get_value(buttons_desc->gpio);
if(pinval == 1) /* 判断按键是按下还是松开 */
{
/* 松开 */
input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 1);
input_sync(buttons_dev);
}
return IRQ_HANDLED;
}
当发生按键中断时将触发按键中断程序,根据获得的对应按键的值来确定按键是按下还是松开,然后分别进行事件上报和事件同步。
/* 包含的头文件 */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 输入子系统需要的头文件 */
#include
/** 按键映射:
* XEINT26 ---- GPX3_2
* XEINT27 ---- GPX3_3
* XEINT28 ---- GPX3_4
* XEINT29 ---- GPX3_5
*/
/* 定义一个结构体用来对输入按键进行描述 */
struct yl_buttons_desc{
int gpio; // 表示对于的按键的引脚
char *name; // 表示对应的按键请求中断时的中断名
int key_code; // 表示按键在输入子系统中对应的键值
};
/* 定义一个描述按键的数组 */
static struct yl_buttons_desc buttons_desc[] = {
{EXYNOS4_GPX3(2), "yl_buttons_L", KEY_L},
{EXYNOS4_GPX3(3), "yl_buttons_S", KEY_S},
{EXYNOS4_GPX3(4), "yl_buttons_ENTER", KEY_ENTER},
{EXYNOS4_GPX3(5), "yl_buttons_LEFTSHIFT", KEY_LEFTSHIFT},
};
/* 定义一个输入子系统的结构体指针变量 */
static struct input_dev *buttons_dev;
/* 按键中断处理程序 */
static irqreturn_t yl_buttons_irq(int irq, void *devid)
{
struct yl_buttons_desc *buttons_desc = (struct yl_buttons_desc *)devid;
int pinval = gpio_get_value(buttons_desc->gpio);
if(pinval == 1) /* 判断按键是按下还是松开 */
{
/* 松开 */
input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, buttons_desc->key_code, 1);
input_sync(buttons_dev);
}
return IRQ_HANDLED;
}
/* 入口函数 */
static int __init yl_buttons_init(void)
{
int irq;
int i;
/* 1、分配一个input_dev结构体 */
buttons_dev = input_allocate_device();
if(!buttons_dev)
{
printk("input_allocate_device error!\n");
return -ENOMEM;
}
/* 2、设置input_dev结构体 */
/* 2.1、支持哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2、支持该类事件中的那些事件 */
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
set_bit(buttons_desc[i].key_code, buttons_dev->keybit);
}
/* 3、注册input_dev结构体 */
input_register_device(buttons_dev);
/* 4、硬件相关的操作
* 为每个按键申请一个中断,共用中断处理函数
* 按键触发方式为双边沿触发
*/
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
irq = gpio_to_irq(buttons_desc[i].gpio);
request_irq(irq, yl_buttons_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, buttons_desc[i].name, (void*)&buttons_desc[i]);
}
return 0;
}
/* 出口函数 */
static void __exit yl_buttons_exit(void)
{
int irq;
int i;
/* 释放申请的按键中断 */
for(i = 0; i < sizeof(buttons_desc)/sizeof(buttons_desc[0]); i++)
{
irq = gpio_to_irq(buttons_desc[i].gpio);
free_irq(irq, (void*)&buttons_desc[i]);
}
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(yl_buttons_init);
module_exit(yl_buttons_exit);
MODULE_LICENSE("GPL");