Linux内核中按键驱动分为:较为复杂的【矩阵键盘keyboard Matrix】和简单的【按键驱动】。
【Matrix矩阵键盘】一般适用于多行多列多按键的情况,源码文件为:drivers/input/keyboard/matrix_keypad.c
【GPIO按键驱动】一般适用于少量按键,一个GPIO口对应一个按键的情况,源码文件为:driers/input/keyboard/gpio_keys.c
本次在imx6ul中实现矩阵键盘功能:Linux内核版本为4.1.15,cpu为imx6ul。
以下对矩阵键盘原理、源码、调试步骤等逐一进行分析,简单的单按键就暂时不写了。
一、功能实现(代码)
具体开发流程如下:
1.1 添加编译选项
内核源码根目录下执行make menuconfig,选中如下几个选项来添加Matrix相关编译选项:
Device Drivers --->
Input device support --->
<*> Event debugging (选中)
[*] Keyboards --->
<*> GPIO Buttons(选中)
<*> GPIO driven matrix keypad support(选中)
<*> IMX keypad support(选中)
选中后,save保存,exit退出,使用【make zImage】编译生成新的zImage,路径为:【arch/arm/boot/】
lsy@ubuntu18:~/linux-4.1.15$ ls arch/arm/boot/
bootp compressed dts Image install.sh Makefile zImage
将新的zImage拷贝并烧写到板卡中,重启板卡,然后正式开始进行开发工作。
备注:如果觉得编译整个zImage麻烦,也可以使用【make modules】单独编译模块,将drivers/input/keyboard/matrix_keypad.c文件编译生成对应的.ko模块,然后使用insmod或者modprob加载到板卡内核中来进行调试也行。
1.2 添加设备树节点
此处以【8行*3列】矩阵键盘为例
一定注意:Linux内核中定义矩阵键盘必须且只能为:【行GPIO输入,列GPIO输出】!!!不能搞错!!!
1.2.1 添加kpp节点
备注:本示例中的linux,keymap按键映射以较为容易理解、且直观的方式实现,内核驱动源码中的示例以及网上很多demo中写法都是形如0x02010058的写法,这样不太直观,不好理解,后面会对这种写法含义做出说明,两种写法都是可以的。
/*lsy*/
&kpp {
compatible = "gpio-matrix-keypad";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
debounce-delay-ms = <20>; /*防反跳延时,即:去抖延时*/
col-scan-delay-us = <400>; /*列扫描延时*/
/*8行*/
row-gpios = <&gpio2 11 GPIO_ACTIVE_LOW
&gpio2 12 GPIO_ACTIVE_LOW
&gpio2 10 GPIO_ACTIVE_LOW
&gpio2 8 GPIO_ACTIVE_LOW
&gpio2 9 GPIO_ACTIVE_LOW
&gpio2 13 GPIO_ACTIVE_LOW
&gpio2 14 GPIO_ACTIVE_LOW
&gpio2 15 GPIO_ACTIVE_LOW
>;
/*3列*/
col-gpios = <&gpio2 16 GPIO_ACTIVE_LOW
&gpio2 17 GPIO_ACTIVE_LOW
&gpio2 19 GPIO_ACTIVE_LOW
>;
/* 举例释义:MATRIX_KEY(0x2, 0x1, KEY_X)表示将【第2行1列的按键】映射为【KEY_X】 */
linux,keymap = <
/*row0*/
MATRIX_KEY(0x0, 0x0, KEY_LEFT)
MATRIX_KEY(0x0, 0x1, KEY_DOWN)
/* row1 */
MATRIX_KEY(0x1, 0x0, KEY_UP)
MATRIX_KEY(0x1, 0x1, KEY_RIGHT)
/* row2 */
MATRIX_KEY(0x2, 0x0, KEY_M)
MATRIX_KEY(0x2, 0x1, KEY_X)
MATRIX_KEY(0x2, 0x2, KEY_ENTER)
/*row3*/
MATRIX_KEY(0x3, 0x0, KEY_N)
MATRIX_KEY(0x3, 0x1, KEY_O)
MATRIX_KEY(0x3, 0x2, KEY_I)
/*row4*/
MATRIX_KEY(0x4, 0x0, KEY_7)
MATRIX_KEY(0x4, 0x1, KEY_8)
MATRIX_KEY(0x4, 0x2, KEY_9)
/*row5*/
MATRIX_KEY(0x5, 0x0, KEY_4)
MATRIX_KEY(0x5, 0x1, KEY_5)
MATRIX_KEY(0x5, 0x2, KEY_6)
/*row6*/
MATRIX_KEY(0x6, 0x0, KEY_1)
MATRIX_KEY(0x6, 0x1, KEY_2)
MATRIX_KEY(0x6, 0x2, KEY_3)
/*row7*/
MATRIX_KEY(0x7, 0x0, KEY_MINUS)
MATRIX_KEY(0x7, 0x1, KEY_0)
MATRIX_KEY(0x7, 0x2, KEY_DOT)
>;
gpio-activelow;
status = "okay";
};
1.2.2 在iomuxc中添加pinctrl_复用节点
&iomuxc {
......
/*lsy*/
pinctrl_key: kppgrp{
fsl,pins = <
/* row 8行 */
MX6UL_PAD_ENET2_TX_DATA0__GPIO2_IO11 0xb0b1
MX6UL_PAD_ENET2_TX_DATA1__GPIO2_IO12 0xb0b1
MX6UL_PAD_ENET2_RX_EN__GPIO2_IO10 0xb0b1
MX6UL_PAD_ENET2_RX_DATA0__GPIO2_IO08 0xb0b1
MX6UL_PAD_ENET2_RX_DATA1__GPIO2_IO09 0xb0b1
MX6UL_PAD_ENET2_TX_EN__GPIO2_IO13 0xb0b1
MX6UL_PAD_ENET2_TX_CLK__GPIO2_IO14 0xb0b1
MX6UL_PAD_ENET2_RX_ER__GPIO2_IO15 0xb0b1
/* col 3列 */
MX6UL_PAD_SD1_CMD__GPIO2_IO16 0x70a1
MX6UL_PAD_SD1_CLK__GPIO2_IO17 0x70a1
MX6UL_PAD_SD1_DATA1__GPIO2_IO19 0x70a1
>;
};
.......
};
1.2.3 屏蔽其他设备节点占用的键盘GPIO
非常重要:dts中所有键盘使用的GPIO在其他设备节点要是被占用,要全部屏蔽掉,方法此处不再赘述。
1.3 内核源码中按键映射写法
网上很多demo,包括驱动源码中的demo按键映射写法是如下这样写的,这样写也是可以的,只是没有上述代码看起来直观,一眼就能看出来映射的哪个按键而已。
linux中的按键映射文件为:include/uapi/linux/input.h文件,该文件中有键盘所有按键对应的十进制数,查到以后需要换算成十六进制,然后写成下述格式。
linux,keymap = <
/*row0*/
0x00000069 /*Qt.Key_Left -> HEX:69 -> DEC:105 -> KEY_LEFT */
0x0001006C /*Qt.Key_Down -> HEX:6C -> DEC:108 -> KEY_DOWN */
/*row1*/
0x01000067 /*Qt.Key_Up -> HEX:67 -> DEC:103 -> KEY_UP */
0x0101006A /*Qt.Key_Right -> HEX:6A -> DEC:106 -> KEY_RIGHT */
/*row2*/
0x0200004D /*Qt.Key_M -> HEX:4D -> DEC:77 -> KEY_KP6 */
0x02010058 /*Qt.Key_X -> HEX:58 -> DEC:88 -> KEY_F12 */
0x0202001C /*Qt.Key_Return-> HEX:1C -> DEC:28 -> KEY_ENTER */
/*row3*/
0x0300004E /*Qt.Key_N -> HEX:4E -> DEC:78 -> KEY_KPPLUS*/
0x0301004F /*Qt.Key_O -> HEX:4F -> DEC:79 -> KEY_KP1 */
0x03020049 /*Qt.Key_I -> HEX:49 -> DEC:73 -> KEY_KP9 */
/*row4*/
0x04000008 /*Qt.Key_7 -> HEX:08 -> DEC: 8 -> KEY_7 */
0x04010009 /*Qt.Key_8 -> HEX:09 -> DEC: 9 -> KEY_8 */
0X0402000A /*Qt.Key_9 -> HEX:0A -> DEC:10 -> KEY_9 */
/*row5*/
0x05000005 /*Qt.Key_4 -> HEX:05 -> DEC: 5 -> KEY_4 */
0x05010006 /*Qt.Key_5 -> HEX:06 -> DEC: 6 -> KEY_5 */
0x05020007 /*Qt.Key_6 -> HEX:07 -> DEC: 7 -> KEY_6 */
/*row6*/
0x06000002 /*Qt.Key_1 -> HEX:02 -> DEC: 2 -> KEY_1 */
0x06010003 /*Qt.Key_2 -> HEX:03 -> DEC: 3 -> KEY_2 */
0x06020004 /*Qt.Key_3 -> HEX:04 -> DEC: 4 -> KEY_3 */
/*row7*/
0x0700002D /*Qt.Key_Minux -> HEX:2D -> DEC:45 -> KEY_X */
0x0701000B /*Qt.Key_0 -> HEX:0B -> DEC:11 -> KEY_0 */
0x0702002E /*Qt.Key_Period-> HEX:2E -> DEC:46 -> KEY_C */
>;
二、硬件电路
2.1 上拉模式:输入GPIO硬件设计为上拉
原电路图包含其他功能,此处不贴原图了,就上个示意图就好,每个交叉口代表一个按键。
按键原理此处不再赘述,只说大概(matrix_keypad.c源码中的键盘原理分析见后面)。
流程大致如下:
- 1、行:全部设置为输入,且上拉,则默认总是检测到高电平。
- 2、列:从左到右,逐列输出低电平(每次只能有一列是低电平)
- 3、行:从上到下,逐行检测哪行出现低电平。
- 4、检测到低电平的行,对应此时激活的列,就可以判断哪个按键导通(按下)了。
备注:
Linux内核本身自带的Matrix机制比这个稍微复杂一些,但是:检测当前每个按键的状态(按下还是弹起)逻辑跟这个是一样的,后面会详细讲到。
大致来讲:
Linux内核定义2个数组,分别用于保存:【上一次按键状态last_key_state[]】和【本次按键状态new_state[]】 ,通过对比两次按键状态发现状态改变的按键,并逐一上报内核。(按键按下或弹起都算状态改变)
2.2 下拉模式:输入GPIO硬件设计为下拉
另外,上图电路图中行(输入)为上拉,如果电路硬件设计为下拉的话,则设备树中可以做如下修改:
一、修改默认电平:
将设备树节点中行和列的【GPIO_ACTIVE_LOW】全部修改为【GPIO_ACTIVE_HIGH】
将【gpio-activelow;】修改为【gpio-activehigh;】
二、调整去抖延时和列扫描延时
debounce-delay-ms = <100>; /*防反跳延时,即:去抖延时*/
col-scan-delay-us = <2000>; /*列扫描延时*/
三、调试步骤
3.1 调试工具推荐
调试按键的时候,/dev/input/路径下:event0是矩阵按键设备,event1是普通按键设备,event2 是电阻屏触摸设备
3.1.1 hexdump命令
使用hexdump命令调试,按键按下以后产生的数据,以及含义解析如下:
root@imx6ulevk:~# hexdump /dev/input/event0
/* 事件序号 tv_sec tv_usec type code value */
0000000 ef7a 59c3 16e6 0006 0004 0004 0000 0000
0000010 ef7a 59c3 16e6 0006 0001 0069 0001 0000 <---------0x69按键按下
0000020 ef7a 59c3 16e6 0006 0000 0000 0000 0000
0000030 ef7a 59c3 c3f9 0007 0004 0004 0000 0000
0000040 ef7a 59c3 c3f9 0007 0001 0069 0000 0000 <---------0x69按键弹起
0000050 ef7a 59c3 c3f9 0007 0000 0000 0000 0000
这里只说右边4列含义:
右起第4列【type列】:事件类型,0001表示产生了按键事件,其他类型见【附加知识小节:Linux内核中的事件类型】
右起第3列【code列】:表示十六进制编码,如果是按键事件的话,则对应key的十六进制值,在内核源码include/uapi/linux/input.h中可以查到每个按键的十进制编码。
最右边2列【value列】:按键的值,0001 0000表示按下,0000 0000表示弹起
3.1.2 类hexdump工具源码(极力推荐,使用起来很方便)
该工具使用起来非常人性化,不像上面那样,每个按键,键值啥的十六进制、十进制还要转换,而且很难看。
-
工具源码见另一篇博客:hexdump调试小工具——获取event事件信息、键盘按键信息等
或github链接:【https://github.com/lishiyuan/tools_hexdump】
-
使用方法为:
将源码进行交叉编译,生成可执行文件read_linux_key_value(名字看自己喜好了),然后拷贝到板卡,使用命令
//Linux中交叉编译 arm-linux-gnueabihf-gcc -o read_linux_key_value xxx.c //拷贝read_linux_key_value到板卡执行下面命令, ./read_linux_key_value 0 <----0表示设备event0
-
该工具使用效果如下:
root@imx6ulevk:~# ./read_linux_key_value 0 /dev/input/event0 evdev version: 1.0.1 name: 20b8000.kpp features: unknown keys/buttons reserved repeat /dev/input/event0: open, fd = 3 Thu Sep 21 17:27:47 2017.869136 type 0x0004; code 0x0004; value 0x00000000; Misc Thu Sep 21 17:27:47 2017.869136 type 0x0001; code 0x0069; value 0x00000001; Key 105 (0x69) press Thu Sep 21 17:27:47 2017.869136 type 0x0000; code 0x0000; value 0x00000000; Thu Sep 21 17:27:47 2017.998944 type 0x0004; code 0x0004; value 0x00000000; Misc Thu Sep 21 17:27:47 2017.998944 type 0x0001; code 0x0069; value 0x00000000; Key 105 (0x69) release Thu Sep 21 17:27:47 2017.998944 type 0x0000; code 0x0000; value 0x00000000;
是不是很爽!
3.2 调试流程
3.2.1 大致调试流程如下图
备注:具体流程分析见下一小节
3.2.2 调试流程具体步骤描述如下
-
先将每个GPIO分别设置为普通IO口,验证输入输出均可控(不管是采用裸机方式还是GPIO export shell命令的方式都行,这里不再赘述方法)。
如果发现有GPIO不可控,则排查dts中是否有别的地方复用了该引脚,要全部屏蔽掉,或者检查硬件电路设计是否无误。
-
板卡【/dev/input/】目录下是否正确生成event0设备:
若否,则首先排查自己添加的kpp节点中【行列属性】以及【linux,keymap属性】中行列描述是否正确;
如果kpp正确,则排查板卡【/sys/firmware/devicetree/base】目录下设备kpp节点是否符合预期,自己的加的kpp是否生效:
备注:
【/sys/firmware/devicetree/base】目录就是整个dts,按照最顶层设备树文件imx6ul.dtsi中的设备节点逐个展开,整个dts的节点以及属性全部展现在sysfs中。此外,还有一个软连接也是指向该路径的【ls /proc/device-tree】。
具体可以查阅该博客【转:致驱动工程师的一封信】。
按照imx6ul设备树描述,追到最顶层设备树【imx6ul.dtsi】文件中,发现kpp节点描述有如下层级关系:
//imx6ul-14x14-evk.dts中引用了imx6ul.dtsi //追到imx6ul.dtsi中,该文件结构如下: / { ....... soc { ....... aips1: aips-bus@02000000 { ....... kpp: kpp@020b8000 { ....... } ....... } } }
因此在板卡【/sys/firmware/devicetree/base/soc/aips-bus@02000000】目录下查阅该节点是否包含自己定义的节点信息,如果否,则说明自己定义的节点必定有问题,都未生效,功能肯定不对。
-
使用上面提到【hexdump /dev/input/event0】命令或【类hexdump工具】来进行功能验证,验证方法见上一小节【调试工具推荐】,如果发现按键数据不对,继续后续步骤
-
观察按键有无反应,用示波器抓波是否存在波形,分析波形是否符合预期;
-
阅读内核源码,在内核源码中增加log,打印输出,查看上报的数据是否正确;
备注:源码路径为【drivers/input/keyboard/matrix_keypad.c】,通常情况下,源码是无需更改的,除非自己要定制特殊功能!毕竟经过全世界各种大佬们的验证,肯定比自己牛x多了,出现逻辑问题的几率非常非常小。一般情况最多就是在源码中增加一些printk打印变量log,观测value,帮自己分析问题,定位问题。
-
具体问题具体分析,见下一节【调试过程中遇到的坑】
3.3 源码中添加调试log小技巧
-
在源码c文件中添加log的时候,这里推荐一个小技巧,在文件顶部定义如下宏:
//#define lsydebug(fmt,args...) printk(KERN_ERR "[lsy]:%s:%d:" fmt,__func__,__LINE__,##args) #define lsydebug(fmt,args...)
这样代码中添加log的时候可以像这样:
...... memset(new_state, 0, sizeof(new_state)); lsydebug("row = pdata->num_row_gpios = %d\r\n", pdata->num_row_gpios); lsydebug("col = pdata->num_col_gpios = %d\r\n", pdata->num_col_gpios); ......
当调试完毕以后,直接保留#define lsydebug(fmt,args…)即可,调试log也不会出现了,不用一个个全删除,其他好处自行体会。
-
备注:或者也可以这样:
//#define DEBUG 1 #ifdef DEBUG #define lsydebug(fmt,args...) printk(KERN_ERR "[lsy]:%s:%d:" fmt,__func__,__LINE__,##args) #else #define lsydebug(fmt,args...) #endif
四、调试过程中遇到的坑
-
通常情况,源码matrix_keypad.c无需做修改,仅仅在调试的时候需要增加一些log信息用于观测变量值而已。也就是说配置内核自带matrix矩阵键盘功能只需要配置dts即可。
配置完dts后,如果无误,会在板卡/dev/input/目录下生成event0设备,如果发现该目录下并未生成该设备,则有很大一种可能性是:dts中配置节点行列有误,导致内核无法识别matrix,所以未生成event0设备,排查思路如下:
- 查看dts中节点【row-gpios、col-gpios行列描述】与【linux,keymap属性行列描述】是否正确,具体规则见后面几条。
- 查看【/sys/firmware/devicetree/base/soc/aips-bus@02000000】目录下查阅kpp节点是否包含自己定义的节点信息,若否,则必有问题,节点都未生效,功能肯定不对。
-
设备树中:行、列一定不能反!Linux内核规定matrix功能:行必须为输入,列必须为输出。
row-gpios成员为行,
col-gpios成员为列。
-
设备树中:linux,keymap属性键值映射,行列也不能反,反了就乱。
正确格式如下两种任选其一都行(推荐第一种,容易理解,且更加直观):
(1)MATRIX_KEY(0x3, 0x1, KEY_O):表示将第3行,第1列按键映射为KEY_O,
(2)0x03020069:表示将第3行,第2列按键映射为0x69对应的按键。按键键值可以在include/uapi/linux/input.h中找到,需要注意:该文件中#define键值为十进制。比如0x69对应十进制为105,在文件中查得105对应按键:KEY_LEFT
0x 00 00 0069 十六进制 行序号 列序号 按键十六进制键值 -
上述编译选项menuconfig中该打开的功能必须打开。
-
设备树中:延时一定要调整合适,否则可能会出现按下一个按键,整行按键都被上报到event设备中。
debounce-delay-ms = <20>; /*防反跳延时,即:去抖延时*/ col-scan-delay-us = <400>; /*列扫描延时*/
-
设备树中:kpp节点中如果定义了属性:gpio-activelow,就是告诉软件层面【低电平有效】,激活端口时,必须设置为低电平。应用在函数【__activate_col()】中。
五、Matrix内核源码逻辑分析
源码路径为【drivers/input/keyboard/matrix_keypad.c】,通常情况下,源码是无需更改的,除非自己要定制特殊功能!
此处对源码逻辑、扫描原理、以及代码中部分重要函数进行分析:
5.1 Matrix整体功能逻辑图
后续补充
5.2 键盘matrix_keypad_scan()函数扫描原理
5.2.1 键盘示意图
为了简单,以【3行,4列】键盘为例:
5.2.2 键盘和保存GPIO状态的数组定义
键盘原理中,列GPIO必须配置为输出,行GPIO必须配置为输入,注意一定更不能搞反!!
以【3行,4列】键盘为例,其中:
-
列GPIO:输出,4列
行GPIO:输入,3行
-
内核源码中定义了2个数组:【旧状态】数组last_key_state[],和【新状态】数组new_state[],都是u32类型(整型int)。
其中:
数组 含义 【旧状态数组 last_key_state[]】 用来保存上一次每行GPIO的电平状态 【新状态数组 new_state[]】 用来保存此次每行GPIO的电平状态 【数组成员】 键盘有m列,则数组就都有m个成员(数组角标为col)
一个数组成员,对应一列
如:
new_state[0] 用于保存第0列电平状态
new_state[1] 用于保存第1列电平状态
new_state[2] 用于保存第2列电平状态
new_state[3] 用于保存第3列电平状态
last_key_state[col]同理【数组每个成员bit位】 对应键盘n行,每个bit位的0/1代表每一行的状态(成员bit为row)
一个bit,对应一行
如:
每个成员的 bit0 用于保存第0行输入电平状态
每个成员的 bit1 用于保存第1行输入电平状态
每个成员的 bit2 用于保存第2行输入电平状态
last_key_state[col]每个bit位同理查找对应按键原理 这样,
1、函数外层for循环—>先匹配到数组成员(列),
2、然后函数函数内层for循环—>通过移位方式,匹配到某个bit位(行),
3、则自然而然的查找到【某列-某行】的按键。此例子中:
last和new数组分别都有4个成员(因为有4列),每个成员有效位为bit0、bit1、bit2(共3个有效bit,因为有3行)。
每个成员有效bit为代表了对应行的输入电平状态。
如下示意图:每一列就是一个u32位的变量,此例子中低3位有效,表示有效行3行。
LAST:上一个状态 last_key_state[0] last_key_state[1] last_key_state[2] last_key_state[3] col0 col1 col2 col3 row0 -- bit[0] bit[0] bit[0] bit[0] row1 -- bit[1] bit[1] bit[1] bit[1] row2 -- bit[2] bit[2] bit[2] bit[2] NEW:当前状态 new_state[0] new_state[1] new_state[2] new_state[3] col0 col1 col2 col3 row0 -- bit[0] bit[0] bit[0] bit[0] row1 -- bit[1] bit[1] bit[1] bit[1] row2 -- bit[2] bit[2] bit[2] bit[2]
5.2.3 matrix_keypad_scan() 函数源码解析
该函数源码大体结构如下:
-
主要是2个双层for循环:
-
第一个双层for循环用于获取当前所有按键状态,保存在new_state[]矩阵中;
-
第二个双层for循环将new_state[]和last_key_state[]做比较,匹配每一个状态改变的按键,并将每个按键状态都上报input子系统。
-
-
备注:
第二个双层for循环中有一行异或操作需要注意:
bits_changed = keypad->last_key_state[col] ^ new_state[col];
不要把bits_changed的含义理解错了:
注意亦或的含义:不同为1,所以该条语句执行完以后,bits_changed对应bit是1的话,仅仅是标识哪一行电平状态改变了,并不是代表该行电平!!!该行电平在new_state[col]的对应bit位保存!!! -
下面是matrix_keypad_scan()函数的源码,我在里面加了一些注释如下:
/* * This gets the keys from keyboard and reports it to input subsystem */ static void matrix_keypad_scan(struct work_struct *work) { struct matrix_keypad *keypad = container_of(work, struct matrix_keypad, work.work); struct input_dev *input_dev = keypad->input_dev; const unsigned short *keycodes = input_dev->keycode; const struct matrix_keypad_platform_data *pdata = keypad->pdata; uint32_t new_state[MATRIX_MAX_COLS]; int row, col, code; dbg("Etnry scan!\r\n"); /* de-activate all columns for scanning */ activate_all_cols(pdata, false); /*反激活全部列,扫描结束后再全部激活*/ memset(new_state, 0, sizeof(new_state)); /*先清0当前按键状态矩阵,避免检测错误*/ /* assert each column and read the row status out */ /* 一、该双层for循环:按顺序依次改变每列电平并获取对应行电平,保存在new矩阵中,后面和last矩阵对比判断是否状态改变 */ for (col = 0; col < pdata->num_col_gpios; col++) { activate_col(pdata, col, true); /*激活某列*/ for (row = 0; row < pdata->num_row_gpios; row++) { new_state[col] |= row_asserted(pdata, row) ? (1 << row) : 0; /*获取每一行的电平,存放在对应bit*/ } activate_col(pdata, col, false); /*反激活某列*/ } /* 二、该双层for循环:将new和last作比较,每匹配到一个状态改变的按键,就向event设备发送一次数据*/ for (col = 0; col < pdata->num_col_gpios; col++) { uint32_t bits_changed; /* * 先直接对整个变量(全部行状态)异或操作, * 如果任意一个bit(即:任意一行)状态改变,bits_changed必不为0 * 说明至少有一行状态有改变 */ bits_changed = keypad->last_key_state[col] ^ new_state[col]; if (bits_changed == 0) /*若所有行均无变化,则继续下一列*/ continue; for (row = 0; row < pdata->num_row_gpios; row++) { if ((bits_changed & (1 << row)) == 0) /*通过row的移位,逐个判断每个bit位(每行)的状态是否有变化*/ continue; /*若当前行无变化,则继续下一行*/ /*每碰到一个有变化的bit(即:变化行),则开始上报内核*/ code = MATRIX_SCAN_CODE(row, col, keypad->row_shift); /*确定变化的行列,就取出dts中定义的code*/ input_event(input_dev, EV_MSC, MSC_SCAN, code); /*向输入子系统上报输入设备产生的事件*/ input_report_key(input_dev, keycodes[code], new_state[col] & (1 << row)); /*上报按键事件*/ } } input_sync(input_dev); /*通知接收者,一个报告上报完毕。至此通过双层for循环,上报了所有按下的按键*/ memcpy(keypad->last_key_state, new_state, sizeof(new_state)); /*一次全键盘扫描完毕,当前状态覆盖上次状态*/ activate_all_cols(pdata, true); /*扫描结束,激活全部列*/ /* Enable IRQs again */ spin_lock_irq(&keypad->lock); keypad->scan_pending = false; enable_row_irqs(keypad); spin_unlock_irq(&keypad->lock); }
5.3 其他几个重要函数
5.3.1 matrix_keypad_parse_dt()函数源码解析
-
该函数作用为:解析dts中矩阵键盘的配置
#ifdef CONFIG_OF /*解析dts中矩阵键盘的配置*/ static struct matrix_keypad_platform_data *matrix_keypad_parse_dt(struct device *dev) { struct matrix_keypad_platform_data *pdata; struct device_node *np = dev->of_node; unsigned int *gpios; int i, nrow, ncol; if (!np) { dev_err(dev, "device lacks DT data\n"); return ERR_PTR(-ENODEV); } pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { dev_err(dev, "could not allocate memory for platform data\n"); return ERR_PTR(-ENOMEM); } pdata->num_row_gpios = nrow = of_gpio_named_count(np, "row-gpios"); /*解析获取行、列总数量*/ pdata->num_col_gpios = ncol = of_gpio_named_count(np, "col-gpios"); if (nrow <= 0 || ncol <= 0) { dev_err(dev, "number of keypad rows/columns not specified\n"); return ERR_PTR(-EINVAL); } if (of_get_property(np, "linux,no-autorepeat", NULL)) pdata->no_autorepeat = true; if (of_get_property(np, "linux,wakeup", NULL)) pdata->wakeup = true; if (of_get_property(np, "gpio-activelow", NULL)) /*如果定义了该变量,表示软件层面为低电平有效,则激活端口时必须设置为低电平,具体见函数__activate_col()*/ pdata->active_low = true; of_property_read_u32(np, "debounce-delay-ms", &pdata->debounce_ms); /*消抖延时*/ of_property_read_u32(np, "col-scan-delay-us", &pdata->col_scan_delay_us); gpios = devm_kzalloc(dev, sizeof(unsigned int) * (pdata->num_row_gpios + pdata->num_col_gpios), GFP_KERNEL); if (!gpios) { dev_err(dev, "could not allocate memory for gpios\n"); return ERR_PTR(-ENOMEM); } for (i = 0; i < pdata->num_row_gpios; i++) /*解析获取行GPIO*/ gpios[i] = of_get_named_gpio(np, "row-gpios", i); for (i = 0; i < pdata->num_col_gpios; i++) /*解析获取列GPIO*/ gpios[pdata->num_row_gpios + i] = of_get_named_gpio(np, "col-gpios", i); pdata->row_gpios = gpios; pdata->col_gpios = &gpios[pdata->num_row_gpios]; return pdata; } #else static inline struct matrix_keypad_platform_data *matrix_keypad_parse_dt(struct device *dev) { dev_err(dev, "no platform data defined\n"); return ERR_PTR(-EINVAL); } #endif
5.3.2 matrix_keypad_init_gpio()函数源码解析
-
该函数作用为:后续补充
六、附加知识
6.1 Linux内核中的事件类型
-
输入事件类型:可选的事件类型定义在 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 /* LED */ #define EV_SND 0x12 /* sound(声音) */ #define EV_REP 0x14 /* 重复事件 */ #define EV_FF 0x15 /* 压力事件 */ #define EV_PWR 0x16 /* 电源事件 */ #define EV_FF_STATUS 0x17 /* 压力状态事件 */
至此,结束!