栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux INPUT 子系统实验

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux INPUT 子系统实验

目录
  • input 子系统
    • input 子系统简
    • input 驱动编写流程
    • input_event 结构体
  • 硬件原理图分析
  • 实验程序编写
    • 修改设备树文件
    • 按键input 驱动程序编写
    • 编写测试APP
  • 运行测试
    • 编译驱动程序和测试APP
    • 运行测试
  • Linux 自带按键驱动程序的使用
    • 自带按键驱动程序源码简析
    • 自带按键驱动程序的使用

按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息,input 核心层负责处理这些事件。
本章我们就来学习一下Linux 内核中的input 子系统。

input 子系统 input 子系统简

input 就是输入的意思,因此input 子系统就是管理输入的子系统,和pinctrl、gpio 子系统一样,都是Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心
应用层的事情,我们只需要按照要求上报这些输入事件即可。为此input 子系统分为input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图58.1.1.1 所示:

图58.1.1.1 中左边就是最底层的具体设备,比如按键、USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。

input 驱动编写流程

input 核心层会向Linux 内核注册一个字符设备,大家找到drivers/input/input.c 这个文件,input.c 就是input 输入子系统的核心层,此文件里面有如下所示代码:

1767 struct class input_class = {
1768 .name = "input",
1769 .devnode = input_devnode,
1770 };
......
2414 static int __init input_init(void)
2415 {
2416 int err;
2417
2418 err = class_register(&input_class);
2419 if (err) {
2420 pr_err("unable to register input_dev classn");
2421 return err;
2422 }
2423
2424 err = input_proc_init();
2425 if (err)
2426 goto fail1;
2427
2428 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2429 INPUT_MAX_CHAR_DEVICES, "input");
2430 if (err) {
2431 pr_err("unable to register char major %d", INPUT_MAJOR);
2432 goto fail2;
2433 }
2434
2435 return 0;
2436
2437 fail2: input_proc_exit();
2438 fail1: class_unregister(&input_class);
2439 return err;
2440 }

第2418 行,注册一个input 类,这样系统启动以后就会在/sys/class 目录下有一个input 子目录,如图58.1.2.1 所示:

第2428~2429 行,注册一个字符设备,主设备号为INPUT_MAJOR,INPUT_MAJOR 定义在include/uapi/linux/major.h 文件中,定义如下:

#define INPUT_MAJOR 13

因此,input 子系统的所有设备主设备号都为13,我们在使用input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个input_device 即可。
1、注册input_dev
在使用input 子系统的时候我们只需要注册一个input 设备即可,input_dev 结构体表示input设备,此结构体定义在include/linux/input.h 文件中,定义如下(有省略):

121 struct input_dev {
122 const char *name;
123 const char *phys;
124 const char *uniq;
125 struct input_id id;
126
127 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
128
129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; 
130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; 
131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; 
132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 
133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; 
134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; 
135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; 
137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; 
......
189 bool devres_managed;
190 };

第129 行,evbit 表示输入事件类型,可选的事件类型定义在include/uapi/linux/input.h 文件中,事件类型如下:

#define EV_SYN 0x00 
#define EV_KEY 0x01 
#define EV_REL 0x02 
#define EV_ABS 0x03 
#define EV_MSC 0x04 
#define EV_SW 0x05 
#define EV_LED 0x11 
#define EV_SND 0x12 
#define EV_REP 0x14 
#define EV_FF 0x15 
#define EV_PWR 0x16 
#define EV_FF_STATUS 0x17 

比如本章我们要使用到按键,那么就需要注册EV_KEY 事件,如果要使用连按功能的话还需要注册EV_REP 事件。

继续回到示例代码58.1.2.2 中,第129 行~137 行的evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在include/uapi/linux/input.h 文件中,按键值如下:

215 #define KEY_RESERVED 0
216 #define KEY_ESC 1
217 #define KEY_1 2
218 #define KEY_2 3
219 #define KEY_3 4
220 #define KEY_4 5
221 #define KEY_5 6
222 #define KEY_6 7
223 #define KEY_7 8
224 #define KEY_8 9
225 #define KEY_9 10
226 #define KEY_0 11
......
794 #define BTN_TRIGGER_HAPPY39 0x2e6
795 #define BTN_TRIGGER_HAPPY40 0x2e7

我们可以将开发板上的按键值设置为示例代码58.1.2.4 中的任意一个,比如我们本章实验会将I.MX6U-ALPHA 开发板上的KEY 按键值设置为KEY_0。

在编写input 设备驱动的时候我们需要先申请一个input_dev 结构体变量,使用input_allocate_device 函数来申请一个input_dev,此函数原型如下所示:

struct input_dev *input_allocate_device(void)

函数参数和返回值含义如下:
参数:无。
返回值:申请到的input_dev。
如果要注销的input 设备的话需要使用input_free_device 函数来释放掉前面申请到的input_dev,input_free_device 函数原型如下:

void input_free_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要释放的input_dev。
返回值:无。
申请好一个input_dev 以后就需要初始化这个input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。input_dev 初始化完成以后就需要向Linux 内核注册input_dev了,需要用到input_register_device 函数,此函数原型如下:

int input_register_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:要注册的input_dev 。
返回值:0,input_dev 注册成功;负值,input_dev 注册失败。
同样的,注销input 驱动的时候也需要使用input_unregister_device 函数来注销掉前面注册的input_dev,input_unregister_device 函数原型如下:

void input_unregister_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:要注销的input_dev 。
返回值:无。
综上所述,input_dev 注册过程如下:

①、使用input_allocate_device 函数申请一个input_dev。
②、初始化input_dev 的事件类型以及事件值。
③、使用input_register_device 函数向Linux 系统注册前面初始化好的input_dev。
④、卸载input 驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev,然后使用input_free_device 函数释放掉前面申请的input_dev。input_dev 注册过程示例代码如下所示:

1 struct input_dev *inputdev; 
2
3 
4 static int __init xxx_init(void)
5 {
6 ......
7 inputdev = input_allocate_device(); 
8 inputdev->name = "test_inputdev"; 
9
10 
11 __set_bit(EV_KEY, inputdev->evbit); 
12 __set_bit(EV_REP, inputdev->evbit); 
13 __set_bit(KEY_0, inputdev->keybit); 
14 
15
16 
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
19 
20
21 
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 
25
26 
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
31
32 
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); 
36 input_free_device(inputdev); 
37 }

第1 行,定义一个input_dev 结构体指针变量。

第4~ 30 行,驱动入口函数,在此函数中完成input_dev 的申请、设置、注册等工作。第7行调用input_allocate_device 函数申请一个input_dev。第10~23 行都是设置input 设备事件和按键值,这里用了三种方法来设置事件和按键值。第27 行调用input_register_device 函数向Linux内核注册inputdev。

第33~37 行,驱动出口函数,第35 行调用input_unregister_device 函数注销前面注册的input_dev,第36 行调用input_free_device 函数删除前面申请的input_dev。

2、上报输入事件
当我们向Linux 内核注册好input_dev 以后还不能高枕无忧的使用input 设备,input 设备都是具有输入功能的,但是具体是什么样的输入值Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给Linux 内核,这样Linux 内核才
能获取到正确的输入值。不同的事件,其上报事件的API 函数不同,我们依次来看一下一些常用的事件上报API 函数。

首先是input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下:

void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value)

函数参数和返回值含义如下:
dev:需要上报的input_dev。
type: 上报的事件类型,比如EV_KEY。
code:事件码,也就是我们注册的按键值,比如KEY_0、KEY_1 等等。
value:事件值,比如1 表示按键按下,0 表示按键松开。
返回值:无。
input_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了input_event 函数。比如上报按键所使用的input_report_key 函数,此函数内容如下:

static inline void input_report_key(struct input_dev *dev,
unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}

从示例代码58.1.2.6 可以看出,input_report_key 函数的本质就是input_event 函数,如果要上报按键事件的话还是建议大家使用input_report_key 函数。
同样的还有一些其他的事件上报函数,这些函数如下所示:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

当我们上报事件以后还需要使用input_sync 函数来告诉Linux 内核input 子系统上报结束,input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

void input_sync(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要上报同步事件的input_dev。
返回值:无。
综上所述,按键的上报事件的参考代码如下所示:

1 
2 void timer_function(unsigned long arg)
3 {
4 unsigned char value;
5
6 value = gpio_get_value(keydesc->gpio); 
7 if(value == 0){ 
8 
9 input_report_key(inputdev, KEY_0, 1); 
10 input_sync(inputdev); 
11 } else { 
12 input_report_key(inputdev, KEY_0, 0); 
13 input_sync(inputdev); 
14 }
15 }

第6 行,获取按键值,判断按键是否按下。

第9~10 行,如果按键值为0 那么表示按键被按下了,如果按键按下的话就要使用input_report_key 函数向Linux 系统上报按键值,比如向Linux 系统通知KEY_0 这个按键按下了。
第12~13 行,如果按键值为1 的话就表示按键没有按下,是松开的。向Linux 系统通知KEY_0 这个按键没有按下或松开了。

input_event 结构体

Linux 内核使用input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };

我们依次来看一下input_event 结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为timeval 结构体类型,timeval 结构体定义如下:

1 typedef long __kernel_long_t;
2 typedef __kernel_long_t __kernel_time_t;
3 typedef __kernel_long_t __kernel_suseconds_t;
4
5 struct timeval {
6 __kernel_time_t tv_sec; 
7 __kernel_suseconds_t tv_usec; 
8 };

从示例代码58.1.3.2 可以看出,tv_sec 和tv_usec 这两个成员变量都为long 类型,也就是32位,这个一定要记住,后面我们分析event 事件上报数据的时候要用到。
type:事件类型,比如EV_KEY,表示此次事件为按键事件,此成员变量为16 位。
code:事件码,比如在EV_KEY 事件中code 就表示具体的按键码,如:KEY_0、KEY_1等等这些按键。此成员变量为16 位。
value:值,比如EV_KEY 事件中value 就是按键值,表示按键有没有被按下,如果为1 的话说明按键按下,如果为0 的话说明按键没有被按下或者按键松开了。
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照input_event 结构体呈现给用户的,用户应用程序可以通过input_event 来获取到具体的输入事件或相关的值,比如按键值等。关于input 子系统就讲解到这里,接下来我们就以开发板上的KEY0 按键为例,讲解一下如何编写input 驱动。

硬件原理图分析

本章实验硬件原理图参考15.2 小节即可。

实验程序编写

本实验对应的例程路径为:开发板光盘-> 2、Linux 驱动例程-> 20_input。

修改设备树文件

直接使用49.3.1 小节创建的key 节点即可。

按键input 驱动程序编写

新建名为“20_input”的文件夹,然后在20_input 文件夹里面创建vscode 工程,工作区命名为“keyinput”。工程创建好以后新建keyinput.c 文件,在keyinput.c 里面输入如下内容:

1 #include 
2 #include 
3 #include 
4 #include 
5 #include 
6 #include 
7 #include 
8 #include 
9 #include 
10 #include 
11 #include 
12 #include 
13 #include 
14 #include 
15 #include 
16 #include 
17 #include 
18 #include 
19 #include 
20 #include 
21 #include 
22 
32 #define KEYINPUT_CNT 1 
33 #define KEYINPUT_NAME "keyinput" 
34 #define KEY0VALUE 0x01 
35 #define INVAKEY 0xFF 
36 #define KEY_NUM 1 
37
38 
39 struct irq_keydesc {
40 int gpio; 
41 int irqnum; 
42 unsigned char value; 
43 char name[10]; 
44 irqreturn_t (*handler)(int, void *); 
45 };
46
47 
48 struct keyinput_dev{
49 dev_t devid; 

keyinput.c 文件内容其实就是实验“13_irq”中的imx6uirq.c 文件中修改而来的,只是将其中与字符设备有关的内容进行了删除,加入了input_dev 相关的内容,我们简单来分析一下示例代码58.3.2.1 中的程序。

第57 行,在设备结构体中定义一个input_dev 指针变量。

第93~102 行,在按键消抖定时器处理函数中上报输入事件,也就是使用input_report_key函数上报按键事件以及按键值,最后使用input_sync 函数上报一个同步事件,这一步一定得做!

第156~180 行,使用input_allocate_device 函数申请input_dev,然后设置相应的事件以及事件码(也就是KEY 模拟成那个按键,这里我们设置为KEY_0)。最后使用input_register_device函数向Linux 内核注册input_dev。

第211~212 行,当注销input 设备驱动的时候使用input_unregister_device 函数注销掉前面注册的input_dev,最后使用input_free_device 函数释放掉前面申请的input_dev。

编写测试APP

新建keyinputApp.c 文件,然后在里面输入如下所示内容:

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include 
10 #include 
11 #include 
12 #include 
13 #include 
14 #include 
15 
     
0000000 0c41 0000 d7cd 000c 0001 000b 0001 0000
0000010 0c41 0000 d7cd 000c 0000 0000 0000 0000
0000020 0c42 0000 54bb 0000 0001 000b 0000 0000
0000030 0c42 0000 54bb 0000 0000 0000 0000 0000

type 为事件类型,查看示例代码58.1.2.3 可知,EV_KEY 事件值为1,EV_SYN 事件值为0。因此第1 行表示EV_KEY 事件,第2 行表示EV_SYN 事件。code 为事件编码,也就是按键号,查看示例代码58.1.2.4 可以,KEY_0 这个按键编号为11,对应的十六进制为0xb,因此第1 行表示KEY_0 这个按键事件,最后的value 就是按键值,为1 表示按下,为0 的话表示松开。

综上所述,示例代码58.4.2.1 中的原始事件值含义如下:
第1 行,按键(KEY_0)按下事件。
第2 行,EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个EV_SYN 事件。
第3 行,按键(KEY_0)松开事件。
第4 行,EV_SYN 同步事件,和第2 行一样。

Linux 自带按键驱动程序的使用 自带按键驱动程序源码简析

Linux 内核也自带了KEY 驱动,如果要使用内核自带的KEY 驱动的话需要配置Linux 内核,不过Linux 内核一般默认已经使能了KEY 驱动,但是我们还是要检查一下。按照如下路径找到相应的配置选项:

-> Device Drivers
	-> Input device support
		-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
			-> Keyboards (INPUT_KEYBOARD [=y])
				->GPIO Buttons

选中“GPIO Buttons”选项,将其编译进Linux 内核中,如图58.5.1.1 所示:


选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”这一行,Linux 内核就会根据这一行来将KEY 驱动文件编译进Linux 内核。Linux 内核自带的KEY 驱动文件为drivers/input/keyboard/gpio_keys.c,gpio_keys.c 采用了platform 驱动框架,在KEY 驱动上使用了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 }

从示例代码58.5.1.1 可以看出,这就是一个标准的platform 驱动框架,如果要使用设备树来描述KEY 设备信息的话,设备节点的compatible 属性值要设置为“gpio-keys”。当设备和驱动匹配以后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 devicen");
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";

第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 %dn",
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 specifiedn");
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 }

gpio_keys_irq_isr 是按键中断处理函数,第407 行向Linux 系统上报EV_KEY 事件,表示按键按下。第408 行使用input_sync 函数向系统上报EV_REP 同步事件。

综上所述,Linux 内核自带的gpio_keys.c 驱动文件思路和我们前面编写的keyinput.c 驱动文件基本一致。都是申请和初始化input_dev,设置事件,向Linux 内核注册input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。

自带按键驱动程序的使用

要使用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。
打开imx6ull-alientek-emmc.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 = ;
9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
10 };
11 };

第5 行,autorepeat 表示按键支持连按。
第6~10 行,ALPHA 开发板KEY 按键信息,名字设置为“GPIO Key Enter”,这里我们将开发板上的KEY 按键设置为“EKY_ENTER”这个按键,也就是回车键,效果和键盘上的回车键一样。后面学习LCD 驱动的时候需要用到此按键,因为Linux 内核设计的10 分钟以后LCD关闭,也就是黑屏,就跟我们用电脑或者手机一样,一定时间以后关闭屏幕。这里将开发板上的KEY 按键注册为回车键,当LCD 黑屏以后直接按一下KEY 按键即可唤醒屏幕,就跟当电
脑熄屏以后按下回车键即可重新打开屏幕一样。

最后设置KEY 所使用的IO 为GPIO1_IO18,一定要检查一下设备树看看此GPIO 有没有被用到其他外设上,如果有的话要删除掉相关代码!

重新编译设备树,然后用新编译出来的imx6ull-alientek-emmc.dtb 启动Linux 系统,系统启动以后查看/dev/input 目录,看看都有哪些文件,结果如图58.5.2.1 所示:

从图58.5.2.1 可以看出存在event1 这个文件,这个文件就是KEY 对应的设备文件,使用hexdump 命令来查看/dev/input/event1 文件,输入如下命令:

hexdump /dev/input/event1

然后按下ALPHA 开发板上的按键,终端输出图58.5.2.2 所示内容:

如果按下KEY 按键以后会在终端上输出图58.5.2.2 所示的信息那么就表示Linux 内核的按键驱动工作正常。至于图58.5.2.2 中内容的含义大家就自行分析,这个已经在58.4.2 小节详细的分析过了,这里就不再讲解了。
大家如果发现按下KEY 按键以后没有反应,那么请检查一下三方面:
①、是否使能Linux 内核KEY 驱动。
②、设备树中gpio-keys 节点是否创建成功。
③、在设备树中是否有其他外设也使用了KEY 按键对应的GPIO,但是我们并没有删除掉这些外设信息。检查Linux 启动log 信息,看看是否有类似下面这条信息:

gpio-keys gpio_keys:Failed to request GPIO 18, error -16

上述信息表示GPIO 18 申请失败,失败的原因就是有其他的外设正在使用此GPIO。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/460749.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号