imx6ul添加keyboard Matrix矩阵键盘




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工具源码(极力推荐,使用起来很方便)

该工具使用起来非常人性化,不像上面那样,每个按键,键值啥的十六进制、十进制还要转换,而且很难看。

  1. 工具源码见另一篇博客:hexdump调试小工具——获取event事件信息、键盘按键信息等

    或github链接:【https://github.com/lishiyuan/tools_hexdump】

  2. 使用方法为:

    将源码进行交叉编译,生成可执行文件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
    
  3. 该工具使用效果如下:

    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 调试流程具体步骤描述如下

  1. 先将每个GPIO分别设置为普通IO口,验证输入输出均可控(不管是采用裸机方式还是GPIO export shell命令的方式都行,这里不再赘述方法)。

    如果发现有GPIO不可控,则排查dts中是否有别的地方复用了该引脚,要全部屏蔽掉,或者检查硬件电路设计是否无误。

  2. 板卡【/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】目录下查阅该节点是否包含自己定义的节点信息,如果否,则说明自己定义的节点必定有问题,都未生效,功能肯定不对。

  3. 使用上面提到【hexdump /dev/input/event0】命令或【类hexdump工具】来进行功能验证,验证方法见上一小节【调试工具推荐】,如果发现按键数据不对,继续后续步骤

  4. 观察按键有无反应,用示波器抓波是否存在波形,分析波形是否符合预期;

  5. 阅读内核源码,在内核源码中增加log,打印输出,查看上报的数据是否正确;

    备注:源码路径为【drivers/input/keyboard/matrix_keypad.c】,通常情况下,源码是无需更改的,除非自己要定制特殊功能!毕竟经过全世界各种大佬们的验证,肯定比自己牛x多了,出现逻辑问题的几率非常非常小。一般情况最多就是在源码中增加一些printk打印变量log,观测value,帮自己分析问题,定位问题。

  6. 具体问题具体分析,见下一节【调试过程中遇到的坑】

3.3 源码中添加调试log小技巧

  1. 在源码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也不会出现了,不用一个个全删除,其他好处自行体会。

  2. 备注:或者也可以这样:

    //#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
    



四、调试过程中遇到的坑

  1. 通常情况,源码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节点是否包含自己定义的节点信息,若否,则必有问题,节点都未生效,功能肯定不对。
  2. 设备树中:行、列一定不能反!Linux内核规定matrix功能:行必须为输入,列必须为输出

    row-gpios成员为行,

    col-gpios成员为列。

  3. 设备树中: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

    0x00000069
    十六进制行序号列序号按键十六进制键值
  4. 上述编译选项menuconfig中该打开的功能必须打开。

  5. 设备树中:延时一定要调整合适,否则可能会出现按下一个按键,整行按键都被上报到event设备中。

    debounce-delay-ms = <20>;   /*防反跳延时,即:去抖延时*/
    col-scan-delay-us = <400>;  /*列扫描延时*/
    
  6. 设备树中: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列】键盘为例,其中:

  1. 列GPIO:输出,4列

    行GPIO:输入,3行

  2. 内核源码中定义了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() 函数源码解析

该函数源码大体结构如下:

  1. 主要是2个双层for循环:

    • 第一个双层for循环用于获取当前所有按键状态,保存在new_state[]矩阵中;

    • 第二个双层for循环将new_state[]和last_key_state[]做比较,匹配每一个状态改变的按键,并将每个按键状态都上报input子系统。

  2. 备注:

    第二个双层for循环中有一行异或操作需要注意:

      bits_changed = keypad->last_key_state[col] ^ new_state[col];
    

    不要把bits_changed的含义理解错了:
    注意亦或的含义:不同为1,所以该条语句执行完以后,bits_changed对应bit是1的话,仅仅是标识哪一行电平状态改变了,并不是代表该行电平!!!该行电平在new_state[col]的对应bit位保存!!!

  3. 下面是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()函数源码解析

  1. 该函数作用为:解析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()函数源码解析

  1. 该函数作用为:后续补充

    
    

六、附加知识

6.1 Linux内核中的事件类型

  1. 输入事件类型:可选的事件类型定义在 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 /* 压力状态事件 */
    



至此,结束!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值