i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序


文章目录

    • 1 Linux自带按键驱动程序源码
    • 2 Linux内核自带按键驱动程序的使用

1 Linux自带按键驱动程序源码 在Linux内核中也集成了按键的驱动程序,要使用的话,需要在内核中进行配置,按照下面路径找到相应的配置:
Device Drivers---> Input device support---> [*]Keyboards---> <*>GPIO Buttons

选中“GPIO Buttons”选项,这样驱动程序就会编译进 Linux 内核中,如图 1.1所示:
i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验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 { 854return platform_driver_register(&gpio_keys_device_driver); 855 } 856 857 static void __exit gpio_keys_exit(void) 858 { 859platform_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 { 691struct device *dev = &pdev->dev; 692const struct gpio_keys_platform_data *pdata = https://www.it610.com/article/dev_get_platdata(dev); 693struct gpio_keys_drvdata *ddata; 694struct input_dev *input; 695size_t size; 696int i, error; 697int wakeup = 0; 698 699if (!pdata) { 700pdata = gpio_keys_get_devtree_pdata(dev); 701if (IS_ERR(pdata)) 702return PTR_ERR(pdata); 703 } ...... 713input = devm_input_allocate_device(dev); 714if (!input) { 715dev_err(dev,"failed to allocate input device\n"); 716return -ENOMEM; 717} 718 719ddata->pdata = https://www.it610.com/article/pdata; 720ddata->input = input; 721mutex_init(&ddata->disable_lock); 722 723platform_set_drvdata(pdev, ddata); 724input_set_drvdata(input, ddata); 725 726input->name = pdata->name ? : pdev->name; 727input->phys = "gpio-keys/input0"; 728input->dev.parent = &pdev->dev; 729input->open = gpio_keys_open; 730input->close = gpio_keys_close; 731 732input->id.bustype = BUS_HOST; 733input->id.vendor = 0x0001; 734input->id.product = 0x0001; 735input->id.version = 0x0100; 736 737/* Enable auto repeat feature of Linux input subsystem */ 738if (pdata->rep) 739__set_bit(EV_REP, input->evbit); 740 741for (i = 0; i < pdata->nbuttons; i++) { 742const struct gpio_keys_button *button = &pdata->buttons[i]; 743struct gpio_button_data *bdata = https://www.it610.com/article/&ddata->data[i]; 744 745error = gpio_keys_setup_key(pdev, input, bdata, button); 746if (error) 747return error; 748 749if (button->wakeup) 750wakeup = 1; 751} ...... 760error = input_register_device(input); 761if (error) { 762dev_err(dev, "Unable to register input device, error: %d\n", 763error); 764goto 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, 438struct input_dev *input, 439struct gpio_button_data *bdata, 440const struct gpio_keys_button *button) 441 { 442const char *desc = button->desc ? button->desc : "gpio_keys"; 443struct device *dev = &pdev->dev; 444irq_handler_t isr; 445unsigned long irqflags; 446int irq; 447int error; 448 449bdata->input = input; 450bdata->button = button; 451spin_lock_init(&bdata->lock); 452 453if (gpio_is_valid(button->gpio)) { 454 455error = devm_gpio_request_one(&pdev->dev, button->gpio, 456GPIOF_IN, desc); 457if (error < 0) { 458dev_err(dev, "Failed to request GPIO %d, error %d\n", 459button->gpio, error); 460return error; ...... 488isr = gpio_keys_gpio_isr; 489irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; 490 491} else { 492if (!button->irq) { 493dev_err(dev, "No IRQ specified\n"); 494return -EINVAL; 495} 496bdata->irq = button->irq; ...... 506 507isr = gpio_keys_irq_isr; 508irqflags = 0; 509} 510 511input_set_capability(input, button->type ?: EV_KEY, button->code); ...... 540return 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 { 394struct gpio_button_data *bdata = https://www.it610.com/article/dev_id; 395const struct gpio_keys_button *button = bdata->button; 396struct input_dev *input = bdata->input; 397unsigned long flags; 398 399BUG_ON(irq != bdata->irq); 400 401spin_lock_irqsave(&bdata->lock, flags); 402 403if (!bdata->key_pressed) { 404if (bdata->button->wakeup) 405pm_wakeup_event(bdata->input->dev.parent, 0); 406 407input_event(input, EV_KEY, button->code, 1); 408input_sync(input); 409 410if (!bdata->release_delay) { 411input_event(input, EV_KEY, button->code, 0); 412input_sync(input); 413goto out; 414} 415 416bdata->key_pressed = true; 417} 418 419if (bdata->release_delay) 420mod_timer(&bdata->release_timer, 421jiffies + msecs_to_jiffies(bdata->release_delay)); 422 out: 423spin_unlock_irqrestore(&bdata->lock, flags); 424return IRQ_HANDLED; 425 }

可以看出我们之前编写的key_input.c驱动文件和Linux内核中自带的驱动文件思路相同,都是申请和初始化input_dev结构体,设置事件,然后向Linux内核注册。最终在按键中断处理函数中上报事件和事件值。
2 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 { 2compatible = "gpio-keys"; 3#address-cells = <1>; 4#size-cells = <0>; 5autorepeat; 6key0 { 7label = "GPIO Key Enter"; 8linux,code = ; 9gpios = <&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所示:
i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序
文章图片

图 2.1 可以看到已经生成了event1这个文件,这个文件就是按键KEY0对应的设备文件,使用hexdump命令查看/dev/input/event1文件,命令如下:
hexdump /dev/input/event1
然后按下开发板上的按键KEY0,终端会出现下面的内容:
i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序
文章图片

图 2.2 终端输出这些内容表示Linux内核中的按键驱动正常工作,如果终端没有输出,按键按下没有反应,可以有下面几点原因:
① 是否使能 Linux 内核 KEY 驱动。
② 设备树中 gpio-keys 节点是否创建成功。
③ 在设备树中是否有其他外设也使用了 KEY 按键对应的 GPIO,但是我们并没有删除掉这些外设信息。
【i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序】i.MX6ULL终结者|i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序
文章图片

    推荐阅读