在Linux内核中也集成了按键的驱动程序,要使用的话,需要在内核中进行配置,按照下面路径找到相应的配置:
Device Drivers --->
Input device support --->
[*] Keyboards --->
<*> GPIO Buttons
选中“GPIO Buttons”选项,这样驱动程序就会编译进 Linux 内核中,如图 1.1所示:
保存退出后,在.congfig文件下会出现“CONFIG_KEYBOARD_GPIO=y”。
Linux内核中自带的按键驱动程序路径drivers/input/keyboard/gpio_keys.c,按键驱动程序基于platform框架,使用input子系统实现功能。
gpio_keys.c文件部分代码如下:
673 static const struct of_device_id gpio_keys_of_match[] = {
674 { .compatible = "gpio-keys", },
675 { },
676 };
......
842 static struct platform_driver gpio_keys_device_driver = {
843 .probe = gpio_keys_probe,
844 .remove = gpio_keys_remove,
845 .driver = {
846 .name = "gpio-keys",
847 .pm = &gpio_keys_pm_ops,
848 .of_match_table = of_match_ptr(gpio_keys_of_match),
849 }
850 };
851
852 static int __init gpio_keys_init(void)
853 {
854 return platform_driver_register(&gpio_keys_device_driver);
855 }
856
857 static void __exit gpio_keys_exit(void)
858 {
859 platform_driver_unregister(&gpio_keys_device_driver);
860 }
从上面代码可以看出,这是一个典型的platform框架结构,当和设备匹配成功后gpio_keys_probe函数就会执行,来看一下gpio_keys_probe函数中实现什么:
689 static int gpio_keys_probe(struct platform_device *pdev)
690 {
691 struct device *dev = &pdev->dev;
692 const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
693 struct gpio_keys_drvdata *ddata;
694 struct input_dev *input;
695 size_t size;
696 int i, error;
697 int wakeup = 0;
698
699 if (!pdata) {
700 pdata = gpio_keys_get_devtree_pdata(dev);
701 if (IS_ERR(pdata))
702 return PTR_ERR(pdata);
703 }
......
713 input = devm_input_allocate_device(dev);
714 if (!input) {
715 dev_err(dev, "failed to allocate input device\n");
716 return -ENOMEM;
717 }
718
719 ddata->pdata = pdata;
720 ddata->input = input;
721 mutex_init(&ddata->disable_lock);
722
723 platform_set_drvdata(pdev, ddata);
724 input_set_drvdata(input, ddata);
725
726 input->name = pdata->name ? : pdev->name;
727 input->phys = "gpio-keys/input0";
728 input->dev.parent = &pdev->dev;
729 input->open = gpio_keys_open;
730 input->close = gpio_keys_close;
731
732 input->id.bustype = BUS_HOST;
733 input->id.vendor = 0x0001;
734 input->id.product = 0x0001;
735 input->id.version = 0x0100;
736
737 /* Enable auto repeat feature of Linux input subsystem */
738 if (pdata->rep)
739 __set_bit(EV_REP, input->evbit);
740
741 for (i = 0; i < pdata->nbuttons; i++) {
742 const struct gpio_keys_button *button = &pdata->buttons[i];
743 struct gpio_button_data *bdata = &ddata->data[i];
744
745 error = gpio_keys_setup_key(pdev, input, bdata, button);
746 if (error)
747 return error;
748
749 if (button->wakeup)
750 wakeup = 1;
751 }
......
760 error = input_register_device(input);
761 if (error) {
762 dev_err(dev, "Unable to register input device, error: %d\n",
763 error);
764 goto err_remove_group;
765 }
......
774 }
第 700 行,调用 gpio_keys_get_devtree_pdata 函数从设备树中获取到 KEY 相关的设备节点信息。
第 713 行,使用 devm_input_allocate_device 函数申请 input_dev。
第 726~735,初始化 input_dev。
第 739 行,设置 input_dev 事件,这里设置了 EV_REP 事件。
第 745 行,调用 gpio_keys_setup_key 函数继续设置 KEY,此函数会设置 input_dev 的 EV_KEY 事件已经事件码(也就是 KEY 模拟为哪个按键)。
第 760 行,调用 input_register_device 函数向 Linux 系统注册 input_dev。
我们接下来再来看一下 gpio_keys_setup_key 函数的内容:
437 static int gpio_keys_setup_key(struct platform_device *pdev,
438 struct input_dev *input,
439 struct gpio_button_data *bdata,
440 const struct gpio_keys_button *button)
441 {
442 const char *desc = button->desc ? button->desc : "gpio_keys";
443 struct device *dev = &pdev->dev;
444 irq_handler_t isr;
445 unsigned long irqflags;
446 int irq;
447 int error;
448
449 bdata->input = input;
450 bdata->button = button;
451 spin_lock_init(&bdata->lock);
452
453 if (gpio_is_valid(button->gpio)) {
454
455 error = devm_gpio_request_one(&pdev->dev, button->gpio,
456 GPIOF_IN, desc);
457 if (error < 0) {
458 dev_err(dev, "Failed to request GPIO %d, error %d\n",
459 button->gpio, error);
460 return error;
......
488 isr = gpio_keys_gpio_isr;
489 irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
490
491 } else {
492 if (!button->irq) {
493 dev_err(dev, "No IRQ specified\n");
494 return -EINVAL;
495 }
496 bdata->irq = button->irq;
......
506
507 isr = gpio_keys_irq_isr;
508 irqflags = 0;
509 }
510
511 input_set_capability(input, button->type ?: EV_KEY, button->code);
......
540 return 0;
541 }
第 511 行,调用 input_set_capability 函数设置 EV_KEY 事件以及 KEY 的按键类型,也就是 KEY 作为哪个按键?我们会在设备树里面设置指定的 KEY 作为哪个按键。
一切都准备就绪以后剩下的就是等待按键按下,然后向 Linux 内核上报事件,事件上报是在 gpio_keys_irq_isr 函数中完成的,此函数内容如下:
392 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
393 {
394 struct gpio_button_data *bdata = dev_id;
395 const struct gpio_keys_button *button = bdata->button;
396 struct input_dev *input = bdata->input;
397 unsigned long flags;
398
399 BUG_ON(irq != bdata->irq);
400
401 spin_lock_irqsave(&bdata->lock, flags);
402
403 if (!bdata->key_pressed) {
404 if (bdata->button->wakeup)
405 pm_wakeup_event(bdata->input->dev.parent, 0);
406
407 input_event(input, EV_KEY, button->code, 1);
408 input_sync(input);
409
410 if (!bdata->release_delay) {
411 input_event(input, EV_KEY, button->code, 0);
412 input_sync(input);
413 goto out;
414 }
415
416 bdata->key_pressed = true;
417 }
418
419 if (bdata->release_delay)
420 mod_timer(&bdata->release_timer,
421 jiffies + msecs_to_jiffies(bdata->release_delay));
422 out:
423 spin_unlock_irqrestore(&bdata->lock, flags);
424 return IRQ_HANDLED;
425 }
可以看出我们之前编写的key_input.c驱动文件和Linux内核中自带的驱动文件思路相同,都是申请和初始化input_dev结构体,设置事件,然后向Linux内核注册。最终在按键中断处理函数中上报事件和事件值。
要使用Linux内核中自带的按键驱动程序也很简单,只需要在设备树中添加指定的设备节点就可以了,具体可以参考Documentation/devicetree/bindings/input/gpio-keys.txt文件。
节点内容有已下几个要求:
① 节点名字为“gpio-keys”。
② gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”。
③ 所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述自己:
gpios:KEY 所连接的 GPIO 信息。
interrupts:KEY 所使用 GPIO 中断信息,不是必须的,可以不写。
label:KEY 名字
linux,code:KEY 要模拟的按键,也就是示例代码 58.1.2.4 中的这些按键。
④ 如果按键要支持连按的话要加入 autorepeat。
打开topeet_emmc_4_3.dts文件,根据上面几点要求,创建按键的设备节点,内容如下:
1 gpio-keys {
2 compatible = "gpio-keys";
3 #address-cells = <1>;
4 #size-cells = <0>;
5 autorepeat;
6 key0 {
7 label = "GPIO Key Enter";
8 linux,code = <KEY_ENTER>;
9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
10 };
11 };
第2行,compatible属性值要和驱动中的compatible名称一样。
第5行,autorepeat表示支持按键连按。
第6~10行,设置按键信息,设置按键label名称。第8行,设置按键的功能,将按键设置为KEY_ENTER功能,也就是回车键。最后第9行设置按键所使用的GPIO引脚。
在设备树中添加完按键的设备节点后,重新编译设备树文件,然后重新下载设备树文件,启动Linux系统,查看/dev/input目录,如图 2.1所示:
可以看到已经生成了event1这个文件,这个文件就是按键KEY0对应的设备文件,使用hexdump命令查看/dev/input/event1文件,命令如下:
hexdump /dev/input/event1
然后按下开发板上的按键KEY0,终端会出现下面的内容:
终端输出这些内容表示Linux内核中的按键驱动正常工作,如果终端没有输出,按键按下没有反应,可以有下面几点原因:
① 是否使能 Linux 内核 KEY 驱动。
② 设备树中 gpio-keys 节点是否创建成功。
③ 在设备树中是否有其他外设也使用了 KEY 按键对应的 GPIO,但是我们并没有删除掉这些外设信息。