您当前的位置: 首页 >  嵌入式

正点原子

暂无认证

  • 0浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【正点原子Linux连载】第五十八章 Linux INPUT子系统实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

正点原子 发布时间:2021-09-23 15:12:05 ,浏览量:0

1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子Linux感兴趣的同学可以加群讨论:935446741 4)关注正点原子公众号,获取最新资料更新 在这里插入图片描述 第五十八章 Linux INPUT子系统实验

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

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

图58.1.1.1 input子系统结构图 图58.1.1.1中左边就是最底层的具体设备,比如按键、USB键盘/鼠标等,中间部分属于Linux内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出input子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下: 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。 事件层:主要和用户空间进行交互。 58.1.2 input驱动编写流程 input核心层会向Linux内核注册一个字符设备,大家找到drivers/input/input.c这个文件,input.c就是input输入子系统的核心层,此文件里面有如下所示代码: 示例代码58.1.2.1 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 class\n");
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所示:

在这里插入图片描述

图58.1.2.1 input类 第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文件中,定义如下(有省略):

示例代码58.1.2.2 input_dev结构体
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)];	/*LED相关的位图    */
135     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound有关的位图 */
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文件中,事件类型如下:
示例代码58.1.2.3 事件类型
#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    /* 压力状态事件 	*/
比如本章我们要使用到按键,那么就需要注册EV_KEY事件,如果要使用连按功能的话还需要注册EV_REP事件。
继续回到示例代码58.1.2.2中,第129行~137行的evbit、keybit、relbit等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit,keybit就是按键事件使用的位图,Linux内核定义了很多按键值,这些按键值定义在include/uapi/linux/input.h文件中,按键值如下:
示例代码58.1.2.4 按键值
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注册过程示例代码如下所示:

示例代码58.1.2.5 input_dev注册流程
1  struct input_dev *inputdev;      /* input结构体变量 */
2  
3  /* 驱动入口函数 */
4  static int __init xxx_init(void)
5  {
6   	......
7   	inputdev = input_allocate_device(); 	/* 申请input_dev 		*/
8   	inputdev->name = "test_inputdev";  		/* 设置input_dev名字 	*/
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  	/* 注册input_dev */
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);  	/* 注销input_dev 	*/
36  	input_free_device(inputdev);        		/* 删除input_dev 	*/
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函数,此函数内容如下: 例代码58.1.2.6 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。 返回值:无。 综上所述,按键的上报事件的参考代码如下所示:

示例代码58.1.2.7 事件上报参考代码
1  /* 用于按键消抖的定时器服务函数 */
2  void timer_function(unsigned long arg)
3  {
4   	unsigned char value;
5   
6   	value = gpio_get_value(keydesc->gpio);  	/* 读取IO值 	*/
7   	if(value == 0){                         		/* 按下按键 		*/
8       		/* 上报按键值 */
9       		input_report_key(inputdev, KEY_0, 1);	/* 最后一个参数1,按下 */
10      	input_sync(inputdev);               		/*  同步事件 	*/
11  	} else {                                			/* 按键松开 		*/
12      	input_report_key(inputdev, KEY_0, 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这个按键没有按下或松开了。 58.1.3 input_event结构体 Linux内核使用input_event这个结构体来表示所有的输入事件,input_envent结构体定义在include/uapi/linux/input.h文件中,结构体内容如下:

示例代码58.1.3.1 input_event结构体
24 struct input_event {
25  	struct timeval time;
26  	__u16 type;
27  	__u16 code;
28  	__s32 value;
29 };
我们依次来看一下input_event结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为timeval结构体类型,timeval结构体定义如下:
示例代码58.1.3.2 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驱动。

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 
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  /***************************************************************
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描述结构体 */
39  struct irq_keydesc {
40      int gpio;                         	/* gpio 			*/
41      int irqnum;                        	/* 中断号     		*/
42      unsigned char value;            	/* 按键对应的键值 	*/
43      char name[10];                   	/* 名字 			*/
44      irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
45  };
46  
47  /* keyinput设备结构体 */
48  struct keyinput_dev{
49      dev_t devid;            	/* 设备号     		*/
50      struct cdev cdev;       	/* cdev     		*/
51      struct class *class;    	/* 类      			*/
52      struct device *device;  	/* 设备    			*/
53      struct device_node  *nd; /* 设备节点 		*/
54      struct timer_list timer;	/* 定义一个定时器	*/
55      struct irq_keydesc irqkeydesc[KEY_NUM]; 	/* 按键描述数组 */
56      unsigned char curkeynum;                		/* 当前的按键号 	*/
57      struct input_dev *inputdev;     				/* input结构体	*/
58  };
59  
60  struct keyinput_dev keyinputdev;    /* key input设备 */
61  
62  /* @description  		: 中断服务函数,开启定时器,延时10ms,
63   *                   	  定时器用于按键消抖。
64   * @param - irq     	: 中断号 
65   * @param - dev_id  	: 设备结构。
66   * @return          	: 中断执行结果
67   */
68  static irqreturn_t key0_handler(int irq, void *dev_id)
69  {
70      struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;
71  
72      dev->curkeynum = 0;
73      dev->timer.data = (volatile long)dev_id;
74      mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); 
75      return IRQ_RETVAL(IRQ_HANDLED);
76  }
77  
78  /* @description 	: 定时器服务函数,用于按键消抖,定时器到了以后
79   *                	  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
80   * @param - arg 	: 设备结构变量
81   * @return      	: 无
82   */
83  void timer_function(unsigned long arg)
84  {
85      unsigned char value;
86      unsigned char num;
87      struct irq_keydesc *keydesc;
88      struct 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值 	*/
93      if(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  */
110 static int keyio_init(void)
111 {
112     unsigned char i = 0;
113     char name[10];
114     int ret = 0;
115     
116     keyinputdev.nd = of_find_node_by_path("/key");
117     if (keyinputdev.nd== NULL){
118         printk("key node not find!\r\n");
119         return -EINVAL;
120     } 
121 
122     /* 提取GPIO */
123     for (i = 0; i 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);
177     if (ret) {
178         printk("register input device failed!\r\n");
179         return ret;
180     }
181     return 0;
182 }
183 
184 /*
185  * @description 	: 驱动入口函数
186  * @param       	: 无
187  * @return      	: 无
188  */
189 static int __init keyinput_init(void)
190 {
191     keyio_init();
192     return 0;
193 }
194 
195 /*
196  * @description 	: 驱动出口函数
197  * @param       	: 无
198  * @return      	: 无
199  */
200 static void __exit keyinput_exit(void)
201 {
202     unsigned int i = 0;
203     /* 删除定时器 */
204     del_timer_sync(&keyinputdev.timer); 
205         
206     /* 释放中断 */
207     for (i = 0; i  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所示: 在这里插入图片描述

图58.5.1.1 内核自带KEY驱动使能选项 选中以后就会在.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文件代码段
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函数内容如下(为了篇幅有缩减):
示例代码58.5.1.2 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 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函数,此函数内容如下:

示例代码58.5.1.3 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 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函数中完成的,此函数内容如下:
示例代码58.5.1.4 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。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。

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 = ;
4      #size-cells = ;
5      autorepeat;
6      key0 {
7          label = "GPIO Key Enter";
8          linux,code = ;
9          gpios = ;
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 /dev/input目录文件 从图58.5.2.1可以看出存在event1这个文件,这个文件就是KEY对应的设备文件,使用hexdump命令来查看/dev/input/event1文件,输入如下命令: hexdump /dev/input/event1 然后按下ALPHA开发板上的按键,终端输出图58.5.2.2所示内容: 在这里插入图片描述

图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。

关注
打赏
1665308814
查看更多评论
0.0384s