在本实验中使用按键KEY0设备。所以设备树节点不用修改,直接使用前面章节创建的key节点即可。
1 编写驱动程序
本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/17_key_input
创建key_input.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 #define KEYINPUT_CNT 1 /* 设备号个数 */
24 #define KEYINPUT_NAME "keyinput" /* 名字 */
25 #define KEY0VALUE 0X01 /* KEY0按键值 */
26 #define INVAKEY 0XFF /* 无效的按键值 */
27 #define KEY_NUM 1 /* 按键数量 */
28
29 /* 中断IO描述结构体 */
30 struct irq_keydesc {
31 int gpio; /* gpio */
32 int irqnum; /* 中断号*/
33 unsigned char value; /* 按键对应的键值 */
34 char name[10]; /* 名字 */
35 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
36 };
37
38 /* keyinput设备结构体 */
39 struct keyinput_dev{
40 dev_t devid; /* 设备号*/
41 struct cdev cdev; /* cdev*/
42 struct class *class; /* 类 */
43 struct device *device; /* 设备 */
44 struct device_node *nd; /* 设备节点 */
45 struct timer_list timer;/* 定义一个定时器*/
46 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
47 unsigned char curkeynum; /* 当前的按键号 */
48 struct input_dev *inputdev; /* input结构体 */
49 };
50
51 struct keyinput_dev keyinputdev; /* key input设备 */
52
53 /* @description : 中断服务函数,开启定时器,延时10ms,
54 * 定时器用于按键消抖。
55 * @param - irq : 中断号
56 * @param - dev_id : 设备结构。
57 * @return : 中断执行结果
58 */
59 static irqreturn_t key0_handler(int irq, void *dev_id)
60 {
61 struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;
62
63 dev->curkeynum = 0;
64 dev->timer.data = (volatile long)dev_id;
65 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定时 */
66 return IRQ_RETVAL(IRQ_HANDLED);
67 }
68
69 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
70 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
71 * @param - arg : 设备结构变量
72 * @return : 无
73 */
74 void timer_function(unsigned long arg)
75 {
76 unsigned char value;
77 unsigned char num;
78 struct irq_keydesc *keydesc;
79 struct keyinput_dev *dev = (struct keyinput_dev *)arg;
80
81 num = dev->curkeynum;
82 keydesc = &dev->irqkeydesc[num];
83 value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
84 if(value == 0){
/* 按下按键 */
85 /* 上报按键值 */
86 //input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
87 input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个
参数表示按下还是松开,1为按下,0为松开 */
88 input_sync(dev->inputdev);
89 } else {
/* 按键松开 */
90 //input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
91 input_report_key(dev->inputdev, keydesc->value, 0);
92 input_sync(dev->inputdev);
93 }
94 }
95
96 /*
97 * @description : 按键IO初始化
98 * @param : 无
99 * @return : 无
100 */
101 static int keyio_init(void)
102 {
103 unsigned char i = 0;
104 char name[10];
105 int ret = 0;
106
107 keyinputdev.nd = of_find_node_by_path("/key");
108 if (keyinputdev.nd== NULL){
109 printk("key node not find!\r\n");
110 return -EINVAL;
111 }
112
113 /* 提取GPIO */
114 for (i = 0; i < KEY_NUM; i++) {
115 keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,
"key-gpio", i);
116 if (keyinputdev.irqkeydesc[i].gpio < 0) {
117 printk("can't get key%d\r\n", i);
118 }
119 }
120
121 /* 初始化key所使用的IO,并且设置成中断模式 */
122 for (i = 0; i < KEY_NUM; i++) {
123 memset(keyinputdev.irqkeydesc[i].name, 0,
sizeof(name)); /* 缓冲区清零 */
124 sprintf(keyinputdev.irqkeydesc[i].name,
"KEY%d", i); /* 组合名字 */
125 gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
126 gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);
127 keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);
128 }
129 /* 申请中断 */
130 keyinputdev.irqkeydesc[0].handler = key0_handler;
131 keyinputdev.irqkeydesc[0].value = KEY_0;
132
133 for (i = 0; i < KEY_NUM; i++) {
134 ret = request_irq(keyinputdev.irqkeydesc[i].irqnum,
keyinputdev.irqkeydesc[i].handler,
135 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
keyinputdev.irqkeydesc[i].name, &keyinputdev);
136 if(ret < 0){
137 printk("irq %d request failed!\r\n",
keyinputdev.irqkeydesc[i].irqnum);
138 return -EFAULT;
139 }
140 }
141
142 /* 创建定时器 */
143 init_timer(&keyinputdev.timer);
144 keyinputdev.timer.function = timer_function;
145
146 /* 申请input_dev */
147 keyinputdev.inputdev = input_allocate_device();
148 keyinputdev.inputdev->name = KEYINPUT_NAME;
149 #if 0
150 /* 初始化input_dev,设置产生哪些事件 */
151 __set_bit(EV_KEY, keyinputdev.inputdev->evbit); /* 设置产生按键事件*/
152 __set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件,比如按
下去不放开,就会一直输出信息 */
153
154 /* 初始化input_dev,设置产生哪些按键 */
155 __set_bit(KEY_0, keyinputdev.inputdev->keybit);
156 #endif
157
158 #if 0
159 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
160 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
161 #endif
162
163 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
164 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
165
166 /* 注册输入设备 */
167 ret = input_register_device(keyinputdev.inputdev);
168 if (ret) {
169 printk("register input device failed!\r\n");
170 return ret;
171 }
172 return 0;
173 }
174
175 /*
176 * @description : 驱动入口函数
177 * @param : 无
178 * @return : 无
179 */
180 static int __init keyinput_init(void)
181 {
182 keyio_init();
183 return 0;
184 }
185
186 /*
187 * @description : 驱动出口函数
188 * @param : 无
189 * @return : 无
190 */
191 static void __exit keyinput_exit(void)
192 {
193 unsigned int i = 0;
194 /* 删除定时器 */
195 del_timer_sync(&keyinputdev.timer); /* 删除定时器 */
196
197 /* 释放中断 */
198 for (i = 0; i < KEY_NUM; i++) {
199 free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
200 }
201 /* 释放input_dev */
202 input_unregister_device(keyinputdev.inputdev);
203 input_free_device(keyinputdev.inputdev);
204 }
205
206 module_init(keyinput_init);
207 module_exit(keyinput_exit);
208 MODULE_LICENSE("GPL");
209 MODULE_AUTHOR("topeet");
第 48 行,在设备结构体中定义一个 input_dev 指针变量。
第 83~93 行,在按键消抖定时器处理函数中上报输入事件,也就是使用 input_report_key函数上报按键事件以及按键值,最后使用 input_sync 函数上报一个同步事件,这是必须的一步!
第 147~172 行,使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码(也就是 KEY 模拟成那个按键,这里我们设置为 KEY_0)。最后使用 input_register_device函数向 Linux 内核注册 input_dev。
第 202、203 行,当注销 input 设备驱动的时候使用 input_unregister_device 函数注销掉前面注册的 input_dev,最后使用 input_free_device 函数释放掉前面申请的 input_dev。
2 应用测试程序
创建key_input_test.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 /* 定义一个input_event变量,存放输入事件信息 */
17 static struct input_event inputevent;
18
19 /*
20 * @description : main主程序
21 * @param - argc : argv数组元素个数
22 * @param - argv : 具体参数
23 * @return : 0 成功;其他 失败
24 */
25 int main(int argc, char *argv[])
26 {
27 int fd;
28 int err = 0;
29 char *filename;
30
31 filename = argv[1];
32
33 if(argc != 2) {
34 printf("Error Usage!\r\n");
35 return -1;
36 }
37
38 fd = open(filename, O_RDWR);
39 if (fd < 0) {
40 printf("Can't open file %s\r\n", filename);
41 return -1;
42 }
43
44 while (1) {
45 err = read(fd, &inputevent, sizeof(inputevent));
46 if (err > 0) {
/* 读取数据成功 */
47 switch (inputevent.type) {
48
49
50 case EV_KEY:
51 if (inputevent.code < BTN_MISC) {
/* 键盘键值 */
52 printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
53 } else {
54 printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
55 }
56 break;
57
58 /* 其他类型的事件,自行处理 */
59 case EV_REL:
60 break;
61 case EV_ABS:
62 break;
63 case EV_MSC:
64 break;
65 case EV_SW:
66 break;
67 }
68 } else {
69 printf("读取数据失败\r\n");
70 }
71 }
72 return 0;
73 }
第17行,定义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,然后根据事件值来判断按键是按下的还是松开的。