42.Linux INPUT 子系统驱动
对于输入设备,本质上是字符设备,linux内核为其进一步封装,名为Input框架,因此 input 子系统就是管理输入的子系统
和 pinctrl、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。
input 子系统框

驱动层前面使用设备树进行描述设备
用户空间只需要给出输入事件即可
因此我们主要关注中间的驱动层、核心层和事件层
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
input核心层会帮我注册input这么一个字符设备
核心层位于drivers\input\input.c中
体现在,input子系统已经帮我们创建了这个input类
c
ls /sys/class/input/
event0 input0 mice
/dev/input # ls -l
total 0
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 event0
crw-rw---- 1 0 0 13, 63 Jan 1 00:00 mice
主设备号 次设备号
input与我们前面开发很相似,如入口函数input_init
c
static int __init input_init(void)
{
int err;
err = class_register(&input_class); //创建类
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input"); //注册设备 主设备号为INPUT_MAJOR 13
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
因此input子系统已经帮我们把框架搭建好了,现在只需要调用他给个api函数即可
1.input_dev描述input的结构体
c
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
//下面这几个比较重要
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */ //EV_CNT表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件中
//evbit存放不同事件对应的值。
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
注册 EV_KEY
EV_CNT可选include\uapi\linux\input.h
c
/*
* Event types
*/
#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 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
比如本章我们要使用到按键,那么就需要注册 EV_KEY 事件,如果要使用连按功能的话还需要注册 EV_REP 事件。
确认键值
evbit存放不同事件对应的值。 需要我们设置按键对应的键值
c
/*
* Keys and buttons
*
* Most of the keys/buttons are modeled after USB HUT 1.12
* (see http://www.usb.org/developers/hidpage).
* Abbreviations in the comments:
* AC - Application Control
* AL - Application Launch Button
* SC - System Control
*/
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
...
我们可以将开发板上的按键值设置为其中的任意一个,比如我们本章实验会将 I.MX6U-ALPHA 开发板上的 KEY 按键值设置为 KEY_0 11
1.申请 input_dev
在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:
c
struct input_dev *input_allocate_device(void)
返回值: 申请到的 input_dev。
如果要注销的 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_dev, input_free_device 函数原型如下:
c
void input_free_device(struct input_dev *dev)
dev:需要释放的 input_dev。
2.初始化 input_dev
申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数,此函数原型如下:
c
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 函数原型如下:
c
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。
模板如下:
c
struct input_dev *inputdev; /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
......
inputdev = input_allocate_device(); /* 申请 input_dev */
inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
/*********第一种设置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
/************************************************/
/*********第二种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=BIT_MASK(KEY_0);
/************************************************/
/*********第三种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/
/* 注册 input_dev */
input_register_device(inputdev);
......
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev); /* 注销 input_dev */
input_free_device(inputdev); /* 删除 input_dev */
}
2、上报输入事件
按键按下后需要上报事件,比如对于按键而言就是按键中断服务函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值
不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。
上报通用事件
首先是 input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下:
c
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 函数,此函数内容如下:
c
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
上报按键事件的话还是建议大家使用 input_report_key 函数
同样的还有一些其他的事件上报函数,这些函数如下所示:
c
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 函数本质是上报一个同步事件,此函数原型如下所示:
c
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
dev:需要上报同步事件的 input_dev
模板
按键的上报事件的参考代码如下所示:
c
/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
unsigned char value;
value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 判断按键是否按下*/
if(value == 0){ /* 按下按键 */
/* 上报按键值 */
input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1, 按下 */
input_sync(inputdev); /* 同步事件 */
} else { /* 按键松开 */
input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0, 松开 */
input_sync(inputdev); /* 同步事件 */
}
}
如果按键值为 0 那么表示按键被按下了,如果按键按下的话就要使用input_report_key 函数向 Linux 系统上报按键值,比如向 Linux 系统通知 KEY_0 这个按键按下了
如果按键值为 1 的话就表示按键没有按下,是松开的。向 Linux 系统通知KEY_0 这个按键没有按下或松开了。
input_dev上面表示了 一个具体的输入设备
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*
/
设备节点信息:
...
gpioinput {
compatible = "alientek,gpioInput";
pinctrl-name = "default";
pinctrl-0 = <&pinctrl_gpioInput>;
//MX6UL_PAD_UART1_CTS_B__GPIO1_IO18
cd-gpioinput = <&gpio1 18 GPIO_ACTIVE_HIGH>;
state = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
...
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
...
pinctrl_gpioInput: gpio_inputgrp {
fsl,pin = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080
>;
};
...
}; */
#define INPUT_CNT 1
#define INPUT_NAME "key_input_dev"
#define KEY_NUM 1
#define KEY_VALUE 0x01
//中断结构体
struct irq_key{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[20]; //名字
irqreturn_t (*handler) (int,void *); //中断处理函数
};
//设备结构体
struct key_input_dev{
struct device_node *nd;
int gpio_id;
struct irq_key irqkey[KEY_NUM];
struct timer_list timer;
atomic_t key_value;
atomic_t release;
unsigned char curkeynum;
struct input_dev *inputdev;
};
struct key_input_dev irq;
//中断处理函数 按键0 - key0_handler
static irqreturn_t key0_handler(int irq, void *arg){
struct key_input_dev *dev = arg;
dev->curkeynum = 0;
dev->timer.data = (volatile long)arg;
mod_timer(&dev->timer,jiffies+msecs_to_jiffies(15)); //开启定时器
return 0;
}
//定时器完成函数
static void timer_fun(unsigned long arg){
int value = 0;
unsigned char num; //标记当前是哪个按键
struct key_input_dev *dev = arg;
num = dev->curkeynum;
value = gpio_get_value(dev->irqkey[0].gpio); //前面申请的gpio来获取按键值
if (value == 0) //按下
{
// 上报按键值
input_event(dev->inputdev,EV_KEY,KEY_0,1);
}else if (value == 1)//释放
{
input_event(dev->inputdev,EV_KEY,KEY_0,0);
}
input_sync(dev->inputdev);
}
//按键初始化
static int keyinput_init(struct key_input_dev *dev){
int ret = 0;
int i = 0;
//获取设备节点
dev->nd = of_find_node_by_path("/gpioinput");
if (!dev->nd)
{
ret = -EINVAL;
printk("failed find nd!");
goto fail_nd;
}
printk("success find nd!");
//按键的中断信息
for ( i = 0; i < KEY_NUM; i++)
{
dev->irqkey[i].gpio= of_get_named_gpio(dev->nd,"cd-gpioinput",0);
if (dev->irqkey[i].gpio< 0)
{
printk("can't find gpio_id\r\n");
ret = -EINVAL;
return ret;
}
printk("success find gpio_id = %d\r\n",dev->irqkey[i].gpio);
}
for ( i = 0; i < KEY_NUM; i++)
{
memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name,"key %d",i);
gpio_request(irq.irqkey[i].gpio,dev->irqkey[i].name);
gpio_direction_input(irq.irqkey[i].gpio);
//获取中断号 两种方式
dev->irqkey[i].irqnum = gpio_to_irq(irq.irqkey[i].gpio);
// dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);
}
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY_0;
//中断初始化
for ( i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irqkey[i].irqnum,key0_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,dev->irqkey[i].name,&irq);
if (ret)
{
printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
goto fail_irq;
}
}
//初始化定时器
init_timer(&irq.timer);
irq.timer.function = timer_fun;
//后面在中断中使用mod_timer开启定时器
return 0;
fail_irq:
//如果1234都申请成功了,第5个失败了,就要将前四个全部释放掉。
for ( i = 0; i < KEY_NUM; i++)
{
gpio_free(irq.irqkey[i].gpio);
}
fail_nd:
return ret;
}
static int __init key_input_init(void){
int ret = 0;
ret = keyinput_init(&irq);
if (ret<0)
{
goto fail_init;
}
//注册input_dev
//申请inputdev
irq.inputdev = input_allocate_device();
if (!irq.inputdev)
{
ret = -EINVAL;
goto fail_init;
}
//初始化inputdev
irq.inputdev->name = INPUT_NAME;
__set_bit(EV_KEY,irq.inputdev->evbit); //按键事件 把EV_KEY写入evbit
__set_bit(EV_REP,irq.inputdev->evbit); //重复事件 把EV_REP写入evbit
__set_bit(KEY_0, irq.inputdev->keybit); /*设置产生哪些按键值 按键值为KEY_0*/
//注册input_dev
ret = input_register_device(irq.inputdev);
if (ret)
{
goto fail_input_register;
}
return 0;
fail_input_register:
input_free_device(irq.inputdev);
fail_init:
return ret;
}
static void __exit key_input_exit(void){
int i;
//删除定时器
del_timer_sync(&irq.timer);
//释放中断
for ( i = 0; i < KEY_NUM; i++)
{
free_irq(irq.irqkey[i].irqnum,&irq);
}
//释放IO
for ( i = 0; i < KEY_NUM; i++)
{
gpio_free(irq.irqkey[i].gpio);
}
//注销input_dev;
input_unregister_device(irq.inputdev);
input_free_device(irq.inputdev);
gpio_free(irq.gpio_id);
}
module_init(key_input_init);
module_exit(key_input_exit);
MODULE_LICENSE("GPL");
驱动写好之后
可以先查看是否成功
c
modprobe input.ko
success find nd!success find gpio_id = 18
input: key_input_dev as /devices/virtual/input/input1
会自动创建/dev/event1
使用
c
hexdump /dev/input/event1
按下按键,会输出相应的信息。这些信息分析看3:
3.输入事件结构体
input_event 结构体 表示所有的输入事件
c
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:时间,也就是此事件发生的时间,为 timeval 结构体类型, timeval 结构体定义在time.h如下:
c
struct timeval {
__kernel_time_t tv_sec; /* seconds */ //__kernel_time_t就是long
__kernel_suseconds_t tv_usec; /* microseconds */
//__kernel_suseconds_t也是long类型
};
input_event 结构体完整如下
c
struct input_event {
struct timeval time{
long tv_sec; //32位表示秒
long tv_usec; //32位表示微秒
};
__u16 type; //16位的事件类型
__u16 code; //16位的事件码 如: KEY_0、 KEY_1等等这些按键。此成员变量为 16 位。
__s32 value; //32位value: 值,比如 EV_KEY 事件中 value 就是按键值表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了
};
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等。
makefile
编写好驱动以后hexdump /dev/input/event1,按下按键打印的信息如下:
编号 tv_sec tv_usec type code value
0000000 |eed2 0000|9572 000b|0001|000b|0001 0000| EV_KEY事件 value=1表示按下
0000010 |eed2 0000|9572 000b|0000|0000|0000 0000| EV_SYN事件 只表示同步
0000020 |eed2 0000|df57 000d|0001|000b|0000 0000| EV_KEY事件 value=0表示释放
0000030 |eed2 0000|df57 000d|0000|0000|0000 0000| EV_SYN事件 只表示同步
0000040 |eed3 0000|b361 0006|0001|000b|0001 0000|
0000050 |eed3 0000|b361 0006|0000|0000|0000 0000|
0000060 |eed3 0000|6111 0008|0001|000b|0000 0000|
0000070 |eed3 0000|6111 0008|0000|0000|0000 0000|
0000080 |eed6 0000|7285 0009|0001|000b|0001 0000|
0000090 |eed6 0000|7285 0009|0000|0000|0000 0000|
11
00003f0 f4b1 0000 bbc4 0002 0000 0000 0001 0000 EV_KEY事件 value=1表示按下
0000400 f4b1 0000 5803 0003 0001 000b 0002 0000 EV_KEY事件 value=2表示按下第二次
0000410 f4b1 0000 5803 0003 0000 0000 0001 0000 重复
0000420 f4b1 0000 f440 0003 0001 000b 0002 0000
0000430 f4b1 0000 f440 0003 0000 0000 0001 0000
0000440 f4b1 0000 907c 0004 0001 000b 0002 0000
0000460 f4b1 0000 908c 0004 0001 000b 0000 0000 EV_KEY事件按键抬起
0000470 f4b1 0000 908c 0004 0000 0000 0000 0000 同步信号。
按键驱动对应的文件就是/dev/input/eventX(x=0,1,2,3...)
应用程序通过读取/dev/input/event1来检查输入驱动input1的按键情况,也就是按键有没有被按下。
我们通过/dev/input/event1读取到的信息是input_event结构体形式的数据
因此我只需要在应用程序中定义一个
c
#include "linux/input.h"
//input_event结构体变量
static struct input_event inputevent;
while (1)
{
ret = read(fd,&inputevent,sizeof(inputevent));
if (ret> 0 )
{ //数据读取成功
switch (inputevent.type)
{
case EV_KEY:
printf("EV_KEY事件\r\n");
break;
case EV_SYN:
printf("EV_SYN事件\r\n");
break;
case EV_REL:
/* code */
break;
case EV_REP:
/* code */
break;
default:
break;
}
}else{
printf("数据读取失败\r\n");
}
}
完善
c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "linux/input.h"
//input_event结构体变量
static struct input_event inputevent;
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
unsigned char data;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1)
{
ret = read(fd,&inputevent,sizeof(inputevent));
if (ret> 0 )
{ //数据读取成功
switch (inputevent.type)
{
case EV_KEY:
if (inputevent.code < BTN_MISC) //说明时按键
{
printf("Key %d %s \r\n",inputevent.code,inputevent.value ? "press":"release");
}else{ //说明为Button
printf("Button %d %s \r\n",inputevent.code,inputevent.value ? "press":"release");
}
break;
case EV_SYN:
printf("EV_SYN事件\r\n");
break;
case EV_REL:
/* code */
break;
case EV_REP:
/* code */
break;
default:
break;
}
}else{
printf("数据读取失败\r\n");
}
}
close(fd);
return ret;
}
c
Key 11 press
EV_SYN事件
Key 11 release
EV_SYN事件
Key 11 press
EV_SYN事件
Key 11 release
EV_SYN事件
Key 11 press
EV_SYN事件
Key 11 release
EV_SYN事件
将驱动中的KEY_0改成BTN_0
则会
c
Button 256 press
EV_SYN事件
Button 256 release
EV_SYN事件
Button 256 press
EV_SYN事件
Button 256 release
EV_SYN事件
Button 256 press
EV_SYN事件
Button 256 release
使用linux内核中自带的button
c
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons
在选项中输出?则会看到作用的config配置项
如果是能了Keyboards (INPUT_KEYBOARD [=y])
那么在.config中会使能
c
CONFIG_INPUT_KEYBOARD=y //表示编译进内核中
->GPIO Buttons使能后
c
CONFIG_KEYBOARD_GPIO=y
此时在drivers/input/keyboard 下面的Makefile则会根据规则则会编译该目录下的文件到内核中
c
obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o
编译的文件路径为drivers/input/keyboard/gpio_keys.c 因此就找到了GPIO Buttons的驱动文件gpio_keys.c
gpio_keys.c
最后的内容为
c
static const struct of_device_id gpio_keys_of_match[] = {
{ .compatible = "gpio-keys", },
{ },
};
...
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.pm = &gpio_keys_pm_ops,
.of_match_table = of_match_ptr(gpio_keys_of_match),
}
};
static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}
static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
}
late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");
可以看到系统自带的key是一个标准的platform驱动
且driver->driver.name为"gpio-keys",匹配表上的compatible也为"gpio-keys"
我就需要去设备树中寻找compatible为gpio-keys的设备。没有则需要创建
创建文档帮助在绑定文档中Documentation\devicetree\bindings\input\gpio-keys.txt
绑定设备树示例
c
gpio_keys { //节点名字无要求
compatible = "gpio-keys"; //必须
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
button@21 { //每一个节点作为子节点
label = "GPIO Key UP";
linux,code = <103>;
gpios = <&gpio1 0 1>;
};
button@22 {
label = "GPIO Key DOWN";
linux,code = <108>;
interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
};
c
gpio_keys: gpio_keys@0 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key1@1 {
label = "USER-KEY1";
linux,code = <114>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; //按下是低电平有效
gpio-key,wakeup;
};
};
...
pinctrl_gpio_keys: gpio-keys {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x80000000
>;
};
使用
c
hexdump /dev/input/event1
配表上的compatible也为"gpio-keys"
我就需要去设备树中寻找compatible为gpio-keys的设备。没有则需要创建
创建文档帮助在绑定文档中Documentation\devicetree\bindings\input\gpio-keys.txt
绑定设备树示例
c
gpio_keys { //节点名字无要求
compatible = "gpio-keys"; //必须
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
button@21 { //每一个节点作为子节点
label = "GPIO Key UP";
linux,code = <103>;
gpios = <&gpio1 0 1>;
};
button@22 {
label = "GPIO Key DOWN";
linux,code = <108>;
interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>;
};
c
gpio_keys: gpio_keys@0 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key1@1 {
label = "USER-KEY1";
linux,code = <114>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; //按下是低电平有效
gpio-key,wakeup;
};
};
...
pinctrl_gpio_keys: gpio-keys {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x80000000
>;
};
使用
c
hexdump /dev/input/event1
就可以使用系统自带的驱动key