1:概述
按键驱动实现的过程,主要完成:input_dev的申请和注册,按键中断的实现,中断底半部的实现,在中断底半部调用事件上报函数,完成按键键值的上报;如果增加按键防抖,需要实现一个内核定时器。本文主要分析的是内核中的gpio_keys.c的文件,这是内核提供的参考,我们就借鉴这个文件,来实现我们的按键驱动,首先,看看内核中已经实现的和按键驱动有关的数据结构;
2:和按键相关的数据结构
我们的按键驱动是挂在平台设备上的,所以先看和platform_device有关的数据结构;
2.1:gpio_keys_button:描述一个物理按键,包括基本的按键信息
struct gpio_keys_button {
/* Configuration parameters */
unsigned int code; //键值
int gpio; //io口
int active_low; //低电平收否有效
const char *desc; //按键名字
unsigned int type; //事件支持的事件类型,常用EV_KEY
int wakeup; //是否配置这个按键为唤醒源
int debounce_interval; //按键防抖延迟,单位为ms
bool can_disable;
int value; //按键事件为EV_ABS时用
};
2.2:gpio_keys_platgorm_data:平台设备的私有数据结构,用来描述驱动中所有按键的信息,以及一些通用的方法;这些值全部由用户设置
struct gpio_keys_platform_data {
struct gpio_keys_button *buttons; //指向具体的一个按键
int nbuttons; //驱动中按键的个数
unsigned int poll_interval;
unsigned int rep:1; //使能按键的重复上报
int (*enable)(struct device *dev); //使能设备的方法,用户可提供
void (*disable)(struct device *dev); //禁止设备的方法,用户可提供
const char *name; //用户设置的input_dev的名字
};
然后就是和驱动相关的数据结构:
2.3:gpio_button_data:上面看了一个具体的物理案件的结构描述,这个就是驱动中描述具体一个按键的具体结构;
struct gpio_button_data {
struct gpio_keys_button *button; //指向这个按键的物理描述
struct input_dev *input; //指向输入设备结构
struct timer_list timer; //定义一个定时器,用于防抖
struct work_struct work; //工作队列,作为按键中断和定时器中断的底半部
int timer_debounce; //防抖时间
bool disabled; //标志为
};
2.4:上面我们看了驱动中一个按键描述结构,不过一般的设备中存在多个按键,此时,就需要下面的结构,来描述整个驱动中的所有按键;
struct gpio_keys_drvdata {
struct input_dev *input; //输入设备
struct mutex disable_lock; //互斥锁
unsigned int n_buttons; //设备中按键的数量
int (*enable)(struct device *dev); //使能设备函数
void (*disable)(struct device *dev); //禁止设备函数
struct gpio_button_data data[0]; //空指针,不占用结构体内存,这个结构描述一个单独的按键设备
};
以上的结构就是内核提供的按键驱动的数据结构,为什么要贴这些出来,以为数据结构的构建对与内核来说,是整个骨架。驱动的难点也是构建合理科学的数据结构来描述设备,下面,看看具体代码中的实现,会深有体会;
3:gpio_keys.c代码分析
首先来看platform_driver和platform_device的注册:
static const struct dev_pm_ops gpio_keys_pm_ops = {
.suspend = gpio_keys_suspend,
.resume = gpio_keys_resume,
};
#endif
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,
#ifdef CONFIG_PM
.pm = &gpio_keys_pm_ops,
#endif
}
};
static struct gpio_keys_button gpio_buttons[] = {
{
.gpio = EXYNOS4_GPX1(1),
.code = KEY_HOMEPAGE,
.desc = "BUTTON1",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = EXYNOS4_GPX1(2),
.code = KEY_BACK,
.desc = "BUTTON2",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = EXYNOS4_GPX3(3),
.code = KEY_POWER,
.desc = "BUTTON3",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = EXYNOS4_GPX2(0),
.code = KEY_VOLUMEDOWN,
.desc = "BUTTON4",
.active_low = 1,
.wakeup = 0,
},
{
.gpio = EXYNOS4_GPX2(1),
.code = KEY_VOLUMEUP,
.desc = "BUTTON5",
.active_low = 1,
.wakeup = 0,
}
};
static struct gpio_keys_platform_data gpio_button_data = {
.buttons = gpio_buttons,
.nbuttons = ARRAY_SIZE(gpio_buttons),
.rep = 1,
};
static struct platform_device gpio_button_device = {
.name = "gpio-keys",
.id = -1,
.num_resources = 0,
.dev = {
.platform_data = &gpio_button_data,
}
};
static int __init gpio_keys_init(void)
{
platform_device_register(&gpio_button_device);
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);
用户根据具体的需求来初始化按键信息,接下来看probe函数:
这里说明一点,代码中只是申请了一个input_dev实体,上面我们初始化了5个按键,就是说这5个按键共用一个input_dev实体,详看代码就会明白。这个是怎么去实现呢?靠的就是上面提到的几个数据结构;
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; //得到描述按键设备的平台数据结构
struct gpio_keys_drvdata *ddata; //驱动私有数据结构
struct device *dev = &pdev->dev; //得到platform device中的device模型
struct input_dev *input;
int i, error;
int wakeup = 0;
ddata = kzalloc(sizeof(struct gpio_keys_drvdata) + //申请驱动私有数据结构空间
pdata->nbuttons * sizeof(struct gpio_button_data),
GFP_KERNEL);
input = input_allocate_device(); //申请一个输入设备的实体
if (!ddata || !input) {
dev_err(dev, "failed to allocate state\n");
error = -ENOMEM;
goto fail1;
}
ddata->input = input;
ddata->n_buttons = pdata->nbuttons; //从platform device中得到按键总数
ddata->enable = pdata->enable; //从platform device中得到enable函数
ddata->disable = pdata->disable; //从platform device中得到disable函数
mutex_init(&ddata->disable_lock); //初始化互斥锁
platform_set_drvdata(pdev, ddata); //将ddata数据结构存放在pdev的私有数据指针中,在其他函数中方便使用ddata
input_set_drvdata(input, ddata); //将ddata数据结构存放在input的私有数据指针中,在其他函数中方便使用ddata
input->name = pdata->name ? : pdev->name; //保存在platform device中设备的名字,在/sys/class/input/input0下name节点可以访问到
input->phys = "gpio-keys/input0"; //设置设备路径,在/sys/class/input/input0下会出现phys节点
input->dev.parent = &pdev->dev; //input dev是挂接在platform下的,所起其device的父设备是plafrom->dev
input->open = gpio_keys_open; //open event节点的时候会调用这个函数
input->close = gpio_keys_close; //close event节点的时候会调用这个函数
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
/* Enable auto repeat feature of Linux input subsystem */
if (pdata->rep) //设置内核自动上报事件功能
__set_bit(EV_REP, input->evbit);
for (i = 0; i < pdata->nbuttons; i++) { //遍历驱动中的所有按键
struct gpio_keys_button *button = &pdata->buttons[i];
struct gpio_button_data *bdata = &ddata->data[i];
unsigned int type = button->type ?: EV_KEY; //默认设备为按键
bdata->input = input; //将bdata->input指向input实体
bdata->button = button;
error = gpio_keys_setup_key(pdev, bdata, button); //按键初始化,这个函数很关键
if (error)
goto fail2;
if (button->wakeup)
wakeup = 1;
input_set_capability(input, type, button->code); //设置设备支持的支持的键值
}
//创建sysfs节点
error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n",
error);
goto fail2;
}
//注册input设备
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n",
error);
goto fail3;
}
/* get current state of buttons */
//上报当前的按键状态
for (i = 0; i < pdata->nbuttons; i++)
gpio_keys_report_event(&ddata->data[i]);
input_sync(input);
device_init_wakeup(&pdev->dev, wakeup); //设置device是否可以唤醒系统,相当与开关,如果设置,当按键发生中断,唤醒系统?;这个有待验证
return 0;
fail3:
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
fail2:
while (--i >= 0) {
free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
if (ddata->data[i].timer_debounce)
del_timer_sync(&ddata->data[i].timer);
cancel_work_sync(&ddata->data[i].work);
gpio_free(pdata->buttons[i].gpio);
}
platform_set_drvdata(pdev, NULL);
fail1:
input_free_device(input);
kfree(ddata);
return error;
}
接下来看gpio_keys_setup_key函数,这个函数中完成了gpio的配置和中端以及定时器和工作队列的初始化:
static int __devinit gpio_keys_setup_key(struct platform_device *pdev,
struct gpio_button_data *bdata,
struct gpio_keys_button *button)
{
const char *desc = button->desc ? button->desc : "gpio_keys";
struct device *dev = &pdev->dev;
unsigned long irqflags;
int irq, error;
setup_timer(&bdata->timer, gpio_keys_timer, (unsigned long)bdata);
INIT_WORK(&bdata->work, gpio_keys_work_func);
error = gpio_request(button->gpio, desc);
if (error < 0) {
dev_err(dev, "failed to request GPIO %d, error %d\n",
button->gpio, error);
goto fail2;
}
error = gpio_direction_input(button->gpio);
if (error < 0) {
dev_err(dev, "failed to configure"
" direction for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}
if (button->debounce_interval) {
error = gpio_set_debounce(button->gpio,
button->debounce_interval * 1000);
/* use timer if gpiolib doesn't provide debounce */
if (error < 0)
bdata->timer_debounce = button->debounce_interval;
}
irq = gpio_to_irq(button->gpio);
if (irq < 0) {
error = irq;
dev_err(dev, "Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
goto fail3;
}
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
/*
* If platform has specified that the button can be disabled,
* we don't want it to share the interrupt line.
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED;
error = request_any_context_irq(irq, gpio_keys_isr, irqflags, desc, bdata);
if (error < 0) {
dev_err(dev, "Unable to claim irq %d; error %d\n",
irq, error);
goto fail3;
}
return 0;
fail3:
gpio_free(button->gpio);
fail2:
return error;
}
到此,驱动基本就结束了,当一个按键按下,按键中断响应,工作队列调用input_event上报按键事件和键值;
static void gpio_keys_report_event(struct gpio_button_data *bdata)
{
struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned int type = button->type ?: EV_KEY;
int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;
if (type == EV_ABS) {
if (state)
input_event(input, type, button->code, button->value);
} else {
input_event(input, type, button->code, !!state);
}
input_sync(input);
}
static void gpio_keys_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work);
gpio_keys_report_event(bdata);
}
static void gpio_keys_timer(unsigned long _data)
{
struct gpio_button_data *data = (struct gpio_button_data *)_data;
schedule_work(&data->work);
}
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct gpio_keys_button *button = bdata->button;
BUG_ON(irq != gpio_to_irq(button->gpio));
if (bdata->timer_debounce)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(bdata->timer_debounce));
else
schedule_work(&bdata->work);
return IRQ_HANDLED;
}
以上就是简单的按键驱动的流程分析,adb shell进入开发板文件系统,运行getevnt命令,然后按下按键,测试驱动是否工作正常;
4:思考
4.1:上文中提到设置gpio_keys_platform_data->rep,按键可支持重复事件的上报,上报的按键之为2,sync事件的value为1,一般不用这样去设置重复是的上报,现有的内核子系统上报的机制,会提供给应用程序事件类型和事件子类型以及时间,通过这些信息,应用程序完全可以区分按键的段按/长按/双击,毕竟,内核实现的是机制,而不是逻辑;
4.2:对于匹配的时候,驱动中有这几行代码:
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
这几行代码在匹配的过程当中实际不起作用,因为evdev是默认全部匹配的,这样的设置,适用特殊的设备,对匹配条件十分的苛刻时候用到,不过设置这些值,在sysfs中能可以看到,有可能为用户空间会提供一些有用的信息;
以上思考也是推断,实际的情况当中没有接触到,各位看官,有了解的,留言可以讨论;