输入子系统(3):按键驱动实现

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中能可以看到,有可能为用户空间会提供一些有用的信息;

以上思考也是推断,实际的情况当中没有接触到,各位看官,有了解的,留言可以讨论;

你可能感兴趣的:(linux,device,driver)