1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
58.2 硬件原理图分析
本章实验硬件原理图参考15.2小节即可。
58.3实验程序编写
本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->20_input。
58.3.1 修改设备树文件
直接使用49.3.1小节创建的key节点即可。
58.3.2 按键input驱动程序编写
新建名为“20_input”的文件夹,然后在20_input文件夹里面创建vscode工程,工作区命名为“keyinput”。工程创建好以后新建keyinput.c文件,在keyinput.c里面输入如下内容:
示例代码58.3.2.1 keyinput.c文件代码段
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/input.h>
15 #include <linux/semaphore.h>
16 #include <linux/timer.h>
17 #include <linux/of_irq.h>
18 #include <linux/irq.h>
19 #include <asm/mach/map.h>
20 #include <asm/uaccess.h>
21 #include <asm/io.h>
22/***************************************************************
23 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
24文件名 : keyinput.c
25作者 : 左忠凯
26版本 : V1.0
27描述 : Linux按键input子系统实验
28其他 : 无
29论坛 : www.openedv.com
30日志 : 初版V1.0 2019/8/21 左忠凯创建
31 ***************************************************************/
32 #define KEYINPUT_CNT 1 /* 设备号个数 */
33 #define KEYINPUT_NAME "keyinput" /* 名字 */
34 #define KEY0VALUE 0X01/* KEY0按键值 */
35 #define INVAKEY 0XFF/* 无效的按键值 */
36 #define KEY_NUM 1 /* 按键数量 */
37
38/* 中断IO描述结构体 */
39struct irq_keydesc {
40int gpio; /* gpio */
41int irqnum; /* 中断号 */
42unsignedchar value; /* 按键对应的键值 */
43char name[10]; /* 名字 */
44 irqreturn_t (*handler)(int,void*);/* 中断服务函数 */
45};
46
47/* keyinput设备结构体 */
48struct keyinput_dev{
49 dev_t devid; /* 设备号 */
50struct cdev cdev; /* cdev */
51struct class *class; /* 类 */
52struct device *device; /* 设备 */
53struct device_node *nd;/* 设备节点 */
54struct timer_list timer; /* 定义一个定时器 */
55struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
56unsignedchar curkeynum; /* 当前的按键号 */
57struct input_dev *inputdev; /* input结构体 */
58};
59
60struct keyinput_dev keyinputdev;/* key input设备 */
61
62/* @description : 中断服务函数,开启定时器,延时10ms,
63 * 定时器用于按键消抖。
64 * @param - irq : 中断号
65 * @param - dev_id : 设备结构。
66 * @return : 中断执行结果
67 */
68static irqreturn_t key0_handler(int irq,void*dev_id)
69{
70struct keyinput_dev *dev =(struct keyinput_dev *)dev_id;
71
72 dev->curkeynum =0;
73 dev->timer.data =(volatilelong)dev_id;
74 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
75return IRQ_RETVAL(IRQ_HANDLED);
76}
77
78/* @description : 定时器服务函数,用于按键消抖,定时器到了以后
79 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
80 * @param - arg : 设备结构变量
81 * @return : 无
82 */
83void timer_function(unsignedlong arg)
84{
85unsignedchar value;
86unsignedchar num;
87struct irq_keydesc *keydesc;
88struct keyinput_dev *dev =(struct keyinput_dev *)arg;
89
90 num = dev->curkeynum;
91 keydesc =&dev->irqkeydesc[num];
92 value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
93if(value ==0){ /* 按下按键 */
94/* 上报按键值 */
95//input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
96 input_report_key(dev->inputdev, keydesc->value,1);/*1,按下*/
97 input_sync(dev->inputdev);
98}else{ /* 按键松开 */
99//input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
100 input_report_key(dev->inputdev, keydesc->value,0);
101 input_sync(dev->inputdev);
102}
103}
104
105/*
106 * @description : 按键IO初始化
107 * @param : 无
108 * @return : 无
109 */
110staticint keyio_init(void)
111{
112unsignedchar i =0;
113char name[10];
114int ret =0;
115
116 keyinputdev.nd = of_find_node_by_path("/key");
117if(keyinputdev.nd==NULL){
118 printk("key node not find!");
119return-EINVAL;
120}
121
122/* 提取GPIO */
123for(i =0; i < KEY_NUM; i++){
124 keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd,"key-gpio", i);
125if(keyinputdev.irqkeydesc[i].gpio <0){
126 printk("can't get key%d", i);
127}
128}
129
130/* 初始化key所使用的IO,并且设置成中断模式 */
131for(i =0; i < KEY_NUM; i++){
132 memset(keyinputdev.irqkeydesc[i].name,0,sizeof(name));
133 sprintf(keyinputdev.irqkeydesc[i].name,"KEY%d", i);
134 gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
135 gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);
136 keyinputdev.irqkeydesc[i].irqnum =
irq_of_parse_and_map(keyinputdev.nd, i);
137}
138/* 申请中断 */
139 keyinputdev.irqkeydesc[0].handler = key0_handler;
140 keyinputdev.irqkeydesc[0].value = KEY_0;
141
142for(i =0; i < KEY_NUM; i++){
143 ret = request_irq(keyinputdev.irqkeydesc[i].irqnum,
keyinputdev.irqkeydesc[i].handler,
144 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
keyinputdev.irqkeydesc[i].name,&keyinputdev);
145if(ret <0){
146 printk("irq %d request failed!",
keyinputdev.irqkeydesc[i].irqnum);
147return-EFAULT;
148}
149}
150
151/* 创建定时器 */
152 init_timer(&keyinputdev.timer);
153 keyinputdev.timer.function = timer_function;
154
155/* 申请input_dev */
156 keyinputdev.inputdev = input_allocate_device();
157 keyinputdev.inputdev->name = KEYINPUT_NAME;
158 #if0
159/* 初始化input_dev,设置产生哪些事件 */
160 __set_bit(EV_KEY, keyinputdev.inputdev->evbit);/*按键事件 */
161 __set_bit(EV_REP, keyinputdev.inputdev->evbit);/* 重复事件 */
162
163/* 初始化input_dev,设置产生哪些按键 */
164 __set_bit(KEY_0, keyinputdev.inputdev->keybit);
165 #endif
166
167 #if0
168 keyinputdev.inputdev->evbit[0]= BIT_MASK(EV_KEY)|
BIT_MASK(EV_REP);
169 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)]|=
BIT_MASK(KEY_0);
170 #endif
171
172 keyinputdev.inputdev->evbit[0]= BIT_MASK(EV_KEY)|
BIT_MASK(EV_REP);
173 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
174
175/* 注册输入设备 */
176 ret = input_register_device(keyinputdev.inputdev);
177if(ret){
178 printk("register input device failed!");
179return ret;
180}
181return0;
182}
183
184/*
185 * @description : 驱动入口函数
186 * @param : 无
187 * @return : 无
188 */
189staticint __init keyinput_init(void)
190{
191 keyio_init();
192return0;
193}
194
195/*
196 * @description : 驱动出口函数
197 * @param : 无
198 * @return : 无
199 */
200staticvoid __exit keyinput_exit(void)
201{
202unsigned i =0;
203/* 删除定时器 */
204 del_timer_sync(&keyinputdev.timer);
205
206/* 释放中断 */
207for(i =0; i < KEY_NUM; i++){
208 free_irq(keyinputdev.irqkeydesc[i].irqnum,&keyinputdev);
209}
210/* 释放input_dev */
211 input_unregister_device(keyinputdev.inputdev);
212 input_free_device(keyinputdev.inputdev);
213}
214
215 module_init(keyinput_init);
216 module_exit(keyinput_exit);
217 MODULE_LICENSE("GPL");
218 MODULE_AUTHOR("zuozhongkai");
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。
58.3.3 编写测试APP
新建keyinputApp.c文件,然后在里面输入如下所示内容:
示例代码58.3.3.1 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 <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <linux/input.h>
15/***************************************************************
16 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
17文件名 : keyinputApp.c
18作者 : 左忠凯
19版本 : V1.0
20描述 : input子系统测试APP。
21其他 : 无
22使用方法 :./keyinputApp /dev/input/event1
23论坛 : www.openedv.com
24日志 : 初版V1.0 2019/8/26 左忠凯创建
25 ***************************************************************/
26
27/* 定义一个input_event变量,存放输入事件信息 */
28staticstruct input_event inputevent;
29
30/*
31 * @description : main主程序
32 * @param - argc : argv数组元素个数
33 * @param - argv : 具体参数
34 * @return : 0 成功;其他失败
35 */
36int main(int argc,char*argv[])
37{
38 int fd;
39 int err =0;
40 char*filename;
41
42 filename = argv[1];
43
44 if(argc !=2){
45 printf("Error Usage!");
46 return-1;
47 }
48
49 fd = open(filename, O_RDWR);
50 if(fd <0){
51 printf("Can't open file %s", filename);
52 return-1;
53 }
54
55 while(1){
56 err = read(fd,&inputevent,sizeof(inputevent));
57 if(err >0){/* 读取数据成功 */
58 switch(inputevent.type){
59 case EV_KEY:
60 if(inputevent.code < BTN_MISC){/* 键盘键值 */
61 printf("key %d %s", inputevent.code,
inputevent.value ?"press":"release");
62 }else{
63 printf("button %d %s", inputevent.code,
inputevent.value ?"press":"release");
64 }
65 break;
66
67 /* 其他类型的事件,自行处理 */
68 case EV_REL:
69 break;
70 case EV_ABS:
71 break;
72 case EV_MSC:
73 break;
74 case EV_SW:
75 break;
76 }
77 }else{
78 printf("读取数据失败");
79 }
80 }
81 return0;
82}
第58.1.3小节已经说过了,Linux内核会使用input_event结构体来表示输入事件,所以我们要获取按键输入信息,那么必须借助于input_event结构体。第28行定义了一个inputevent变量,此变量为input_event结构体类型。
第56行,当我们向Linux内核成功注册input_dev设备以后,会在/dev/input目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX就是对应的input设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的。使用read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照input_event结构体组织起来。获取到输入事件以后(input_event结构体类型)使用switch case语句来判断事件类型,本章实验我们设置的事件类型为EV_KEY,因此只需要处理EV_KEY事件即可。比如获取按键编号(KEY_0的编号为11)、获取按键状态,按下还是松开的?
58.4 运行测试
58.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“keyinput.o”,Makefile内容如下所示:
示例代码58.4.1.1 Makefile文件
1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := keyinput.o
......
11 clean:
12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为“keyinput.o”。
输入如下命令编译出驱动模块文件:
make-j32
编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyinputApp.c这个测试程序:
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
编译成功以后就会生成keyinputApp这个应用程序。
58.4.2 运行测试
将上一小节编译出来keyinput.ko和keyinputApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中。在加载keyinput.ko驱动模块之前,先看一下/dev/input目录下都有哪些文件,结果如图58.4.2.1所示:
从图58.4.2.1可以看出,当前/dev/input目录只有event0和mice这两个文件。接下来输入如下命令加载keyinput.ko这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块
当驱动模块加载成功以后再来看一下/dev/input目录下有哪些文件,结果如图58.4.2.2所示:
从图58.4.2.2可以看出,多了一个event1文件,因此/dev/input/event1就是我们注册的驱动所对应的设备文件。keyinputApp就是通过读取/dev/input/event1这个文件来获取输入事件信息的,输入如下测试命令:
./keyinputApp /dev/input/event1
然后按下开发板上的KEY按键,结果如图58.4.2.3所示:
从图58.4.2.3可以看出,当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在Linux内核中KEY_0为11。
另外,我们也可以不用keyinputApp来测试驱动,可以直接使用hexdump命令来查看/dev/input/event1文件内容,输入如下命令:
hexdump /dev/input/event1
然后按下按键,终端输出如图58.4.2.4所示信息:
图58.4.2.4就是input_event类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:
示例代码58.4.2.1 input_event类型的原始事件值
/*****************input_event类型********************/
/* 编号 */ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000 0c410000 d7cd 000c 0001 000b 0001 0000
0000010 0c41 0000 d7cd000c 00000000 00000000
0000020 0c420000 54bb0000 0001 000b 00000000
0000030 0c420000 54bb0000 00000000 00000000
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行一样。
58.5 Linux自带按键驱动程序的使用
58.5.1 自带按键驱动程序源码简析
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文件中找到如下所示内容:
示例代码58.5.1.1 gpio_keys文件代码段
673staticconststruct of_device_id gpio_keys_of_match[]={
674{.compatible ="gpio-keys",},
675{},
676};
......
842staticstruct 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
852staticint __init gpio_keys_init(void)
853{
854return platform_driver_register(&gpio_keys_device_driver);
855}
856
857staticvoid __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函数内容如下(为了篇幅有缩减):
示例代码58.5.1.2 gpio_keys_probe函数代码段
689staticint gpio_keys_probe(struct platform_device *pdev)
690{
691struct device *dev =&pdev->dev;
692conststruct gpio_keys_platform_data *pdata =
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){
700 pdata = gpio_keys_get_devtree_pdata(dev);
701if(IS_ERR(pdata))
702return PTR_ERR(pdata);
703}
......
713 input = devm_input_allocate_device(dev);
714if(!input){
715 dev_err(dev,"failed to allocate input device");
716return-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 */
738if(pdata->rep)
739 __set_bit(EV_REP, input->evbit);
740
741for(i =0; i < pdata->nbuttons; i++){
742conststruct gpio_keys_button *button =&pdata->buttons[i];
743struct gpio_button_data *bdata =&ddata->data[i];
744
745 error = gpio_keys_setup_key(pdev, input, bdata, button);
746if(error)
747return error;
748
749if(button->wakeup)
750 wakeup =1;
751}
......
760 error = input_register_device(input);
761if(error){
762 dev_err(dev,"Unable to register input device, error: %d",
763 error);
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函数,此函数内容如下:
示例代码58.5.1.3 gpio_keys_setup_key函数代码段
437staticint gpio_keys_setup_key(struct platform_device *pdev,
438struct input_dev *input,
439struct gpio_button_data *bdata,
440conststruct gpio_keys_button *button)
441{
442constchar*desc = button->desc ? button->desc :"gpio_keys";
443struct device *dev =&pdev->dev;
444 irq_handler_t isr;
445unsignedlong irqflags;
446int irq;
447int error;
448
449 bdata->input = input;
450 bdata->button = button;
451 spin_lock_init(&bdata->lock);
452
453if(gpio_is_valid(button->gpio)){
454
455 error = devm_gpio_request_one(&pdev->dev, button->gpio,
456 GPIOF_IN, desc);
457if(error <0){
458 dev_err(dev,"Failed to request GPIO %d, error %d",
459 button->gpio, error);
460return error;
......
488 isr = gpio_keys_gpio_isr;
489 irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
490
491}else{
492if(!button->irq){
493 dev_err(dev,"No IRQ specified");
494return-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);
......
540return0;
541}
第511行,调用input_set_capability函数设置EV_KEY事件以及KEY的按键类型,也就是KEY作为哪个按键?我们会在设备树里面设置指定的KEY作为哪个按键。
一切都准备就绪以后剩下的就是等待按键按下,然后向Linux内核上报事件,事件上报是在gpio_keys_irq_isr函数中完成的,此函数内容如下:
示例代码58.5.1.4 gpio_keys_irq_isr函数代码段
392static irqreturn_t gpio_keys_irq_isr(int irq,void*dev_id)
393{
394struct gpio_button_data *bdata = dev_id;
395conststruct gpio_keys_button *button = bdata->button;
396struct input_dev *input = bdata->input;
397unsignedlong flags;
398
399 BUG_ON(irq != bdata->irq);
400
401 spin_lock_irqsave(&bdata->lock, flags);
402
403if(!bdata->key_pressed){
404if(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
410if(!bdata->release_delay){
411 input_event(input, EV_KEY, button->code,0);
412 input_sync(input);
413goto out;
414}
415
416 bdata->key_pressed = true;
417}
418
419if(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);
424return 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。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。
58.5.2 自带按键驱动程序的使用
要使用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,根据上面的要求创建对应的设备节点,设备节点内容如下所示:
示例代码58.5.2.1 gpio-keys节点内容
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};
第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:Failedtorequest GPIO 18, error -16
上述信息表示GPIO 18申请失败,失败的原因就是有其他的外设正在使用此GPIO。