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终结者Linux INPUT子系统实验linux自带按键驱动程序_第1张图片

图 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内核注册。最终在按键中断处理函数中上报事件和事件值。

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 { 
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所示:
在这里插入图片描述

图 2.1

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

图 2.2

终端输出这些内容表示Linux内核中的按键驱动正常工作,如果终端没有输出,按键按下没有反应,可以有下面几点原因:
① 是否使能 Linux 内核 KEY 驱动。
② 设备树中 gpio-keys 节点是否创建成功。
③ 在设备树中是否有其他外设也使用了 KEY 按键对应的 GPIO,但是我们并没有删除掉这些外设信息。

i.MX6ULL终结者Linux INPUT子系统实验linux自带按键驱动程序_第3张图片

你可能感兴趣的:(i.MX6ULL终结者,#,第四部分,Linux驱动开发,嵌入式,linux,开发)