|
本帖最后由 正点原子 于 2021-7-6 18:52 编辑
1)实验平台:正点原子STM32MP157开发板
2) 章节摘自【正点原子】《STM32MP157嵌入式Linux驱动开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=629270721801
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyzmp157.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32MP157技术交流群:691905614
第三十八章 Linux INPUT子系统实验
按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input框架,用户只需要负责上报输入事件,比如按键值、坐标等信息,input核心层负责处理这些事件。本章我们就来学习一下Linux内核中的input子系统。
38.1 input子系统
38.1.1 input子系统简介
input就是输入的意思,因此input子系统就是管理输入的子系统,和pinctrl、gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点,input子系统框架如图38.1.1.1所示:
图38.1.1.1 input子系统结构图
图38.1.1.1中左边就是最底层的具体设备,比如按键、USB键盘/鼠标等,中间部分属于Linux内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出input子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
38.1.2 input驱动编写流程
input核心层会向Linux内核注册一个字符设备,大家找到drivers/input/input.c这个文件,input.c就是input输入子系统的核心层,此文件里面有如下所示代码:
示例代码38.1.2.1 input核心层创建字符设备过程
- 1797 struct class input_class = {
- 1798 .name = "input",
- 1799 .devnode = input_devnode,
- 1800 };
- ......
- 2494 static int __init input_init(void)
- 2495 {
- 2496 int err;
- 2497
- 2498 err = class_register(&input_class);
- 2499 if (err) {
- 2500 pr_err("unable to register input_dev class\n");
- 2501 return err;
- 2502 }
- 2503
- 2504 err = input_proc_init();
- 2505 if (err)
- 2506 goto fail1;
- 2507
- 2508 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
- 2509 INPUT_MAX_CHAR_DEVICES, "input");
- 2510 if (err) {
- 2511 pr_err("unable to register char major %d", INPUT_MAJOR);
- 2512 goto fail2;
- 2513 }
- 2514
- 2515 return 0;
- 2516
- 2517 fail2: input_proc_exit();
- 2518 fail1: class_unregister(&input_class);
- 2519 return err;
- 2520 }
复制代码
第2498行,注册一个input类,这样系统启动以后就会在/sys/class目录下有一个input子目录,如图38.1.2.1所示:
图38.1.2.1 input类
第2508~2509行,注册一个字符设备,主设备号为INPUT_MAJOR,INPUT_MAJOR定义在include/uapi/linux/major.h文件中,定义如下:
因此,input子系统的所有设备主设备号都为13,我们在使用input子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个input_device即可。
1、注册input_dev
在使用input子系统的时候我们只需要注册一个input设备即可,input_dev结构体表示input设备,此结构体定义在include/linux/input.h文件中,定义如下(有省略):
示例代码 38.1.2.2 input_dev 结构体
- 131 struct input_dev {
- 132 const char *name;
- 133 const char *phys;
- 134 const char *uniq;
- 135 struct input_id id;
- 136
- 137 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
- 138
- 139 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
- 140 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
- 141 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图*/
- 142 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
- 143 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
- 144 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
- 145 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* sound 有关的位*/
- 146 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
- 147 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
- 148
- ......
- 201 bool devres_managed;
- 202
- 203 ktime_t timestamp[INPUT_CLK_MAX];
- 204 };
复制代码
第139行,evbit表示输入事件类型,可选的事件类型定义在include/uapi/linux/input.h文件中,事件类型如下:
示例代码38.1.2.3 事件类型
- 38 #define EV_SYN 0x00 /* 同步事件 */
- 39 #define EV_KEY 0x01 /* 按键事件 */
- 40 #define EV_REL 0x02 /* 相对坐标事件 */
- 41 #define EV_ABS 0x03 /* 绝对坐标事件 */
- 42 #define EV_MSC 0x04 /* 杂项(其他)事件 */
- 43 #define EV_SW 0x05 /* 开关事件 */
- 44 #define EV_LED 0x11 /* LED */
- 45 #define EV_SND 0x12 /* sound(声音) */
- 46 #define EV_REP 0x14 /* 重复事件 */
- 47 #define EV_FF 0x15 /* 压力事件 */
- 48 #define EV_PWR 0x16 /* 电源事件 */
- 49 #define EV_FF_STATUS 0x17 /* 压力状态事件 */
复制代码
比如本章我们要使用到按键,那么就需要注册EV_KEY事件,如果要使用连按功能的话还需要注册EV_REP事件。
继续回到示例代码38.1.2.2中,第139行~147行的evbit、keybit、relbit等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit,keybit就是按键事件使用的位图,Linux内核定义了很多按键值,这些按键值定义在include/uapi/linux/input-event-codes.h文件中,按键值如下:
示例代码 38.1.2.4 按键值
- 75 #define KEY_RESERVED 0
- 76 #define KEY_ESC 1
- 77 #define KEY_1 2
- 78 #define KEY_2 3
- 79 #define KEY_3 4
- 80 #define KEY_4 5
- 81 #define KEY_5 6
- 82 #define KEY_6 7
- 83 #define KEY_7 8
- 84 #define KEY_8 9
- 85 #define KEY_9 10
- 86 #define KEY_0 11
- .....
- 692 #define BTN_TRIGGER_HAPPY39 0x2e6
- 693 #define BTN_TRIGGER_HAPPY40 0x2e7
复制代码
我们可以将开发板上的按键值设置为示例代码38.1.2.4中的任意一个,比如我们本章实验会将STM32MP1开发板上的KEY0按键值设置为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注册过程示例代码如下所示:
示例代码38.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函数,此函数内容如下:
例代码38.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);
- }
复制代码
从示例代码38.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。
返回值:无。
综上所述,按键的上报事件的参考代码如下所示:
示例代码38.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这个按键没有按下或松开了。
38.1.3 input_event结构体
Linux内核使用input_event这个结构体来表示所有的输入事件,input_envent结构体定义在include/uapi/linux/input.h文件中,结构体内容如下:
示例代码 38.1.3.1 input_event 结构体
- 28 struct input_event {
- 29 #if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64))
- && !defined(__KERNEL__)
- 30 struct timeval time;
- 31 #define input_event_sec time.tv_sec
- 32 #define input_event_usec time.tv_usec
- 33 #else
- 34 __kernel_ulong_t __sec;
- 35 #if defined(__sparc__) && defined(__arch64__)
- 36 unsigned int __usec;
- 37 unsigned int __pad;
- 38 #else
- 39 __kernel_ulong_t __usec;
- 40 #endif
- 41 #define input_event_sec __sec
- 42 #define input_event_usec __usec
- 43 #endif
- 44 __u16 type;
- 45 __u16 code;
- 46 __s32 value;
- 47 };
复制代码
我们依次来看一下input_event结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为timeval结构体类型,timeval结构体定义如下:
示例代码38.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 };
复制代码
从示例代码38.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驱动。
38.2 硬件原理图分析
本章实验硬件原理图参考29.2小节即可。
38.3 实验程序编写
本实验对应的例程路径为:开发板光盘1、程序源码2、Linux驱动例程20_input。
38.3.1 修改设备树文件
1、创建按键所使用的引脚pinctrl子节点
首先在stm32mp15-pinctrl.dtsi文件中创建按键对应的pinctrl子节点,内容如下:
示例代码38.3.1.1 key_pins_a节点
- 1 key_pins_a: key_pins-0 {
- 2 pins1 {
- 3 pinmux = <STM32_PINMUX('G', 3, GPIO)>, /* KEY0 */
- 4 <STM32_PINMUX('H', 7, GPIO)>; /* KEY1 */
- 5 bias-pull-up;
- 6 slew-rate = <0>;
- 7 };
- 8
- 9 pins2 {
- 10 pinmux = <STM32_PINMUX('A', 0, GPIO)>; /* WK_UP */
- 11 bias-pull-down;
- 12 slew-rate = <0>;
- 13 };
- 14 };
复制代码
这里我们将STM32MP1开发板上的三个按键:KEY0、KEY1和WK_UP都设置了,虽然本小节我们只使用到KEY0。由于KEY0和KEY1这两个按键是低电平有效(按下以后为低电平),WK_UP是高电平有效(按下以后为高电平)。所以这里分开初始化,其中第2~7行初始化KEY0和KEY1使用的PG3和PH7这两个引脚,第9~13行初始化WK_UP使用的PA0引脚。
2、创建按键所使用的引脚pinctrl子节点
接下就是创建按键设备节点,我们直接在29.3.1小节创建的key节点上修改即可,修改完成以后如下所示:
示例代码38.3.1.2 key节点
- 1 key {
- 2 compatible = "alientek,key";
- 3 status = "okay";
- 4 pinctrl-names = "default";
- 5 pinctrl-0 = <&key_pins_a>;
- 6 key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
- 7 interrupt-parent = <&gpiog>;
- 8 interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
- 9 };
复制代码
38.3.2 按键input驱动程序编写
新建名为“20_input”的文件夹,然后在20_input文件夹里面创建vscode工程,工作区命名为“keyinput”。工程创建好以后新建keyinput.c文件,在keyinput.c里面输入如下内容:
示例代码 38.3.2.1 keyinput.c 文件代码段
- 1 #include <linux/module.h>
- 2 #include <linux/errno.h>
- 3 #include <linux/of.h>
- 4 #include <linux/platform_device.h>
- 5 #include <linux/of_gpio.h>
- 6 #include <linux/input.h>
- 7 #include <linux/timer.h>
- 8 #include <linux/of_irq.h>
- 9 #include <linux/interrupt.h>
- 10
- 11 #define KEYINPUT_NAME "keyinput" /* 名字 */
- 12
- 13 /* key设备结构体 */
- 14 struct key_dev{
- 15 struct input_dev *idev; /* 按键对应的input_dev指针 */
- 16 struct timer_list timer; /* 消抖定时器 */
- 17 int gpio_key; /* 按键对应的GPIO编号 */
- 18 int irq_key; /* 按键对应的中断号 */
- 19 };
- 20
- 21 static struct key_dev key; /* 按键设备 */
- 22
- 23 /*
- 24 * @description : 按键中断服务函数
- 25 * @param – irq : 触发该中断事件对应的中断号
- 26 * @param – arg : arg参数可以在申请中断的时候进行配置
- 27 * @return : 中断执行结果
- 28 */
- 29 static irqreturn_t key_interrupt(int irq, void *dev_id)
- 30 {
- 31 if(key.irq_key != irq)
- 32 return IRQ_NONE;
- 33
- 34 /* 按键防抖处理,开启定时器延时15ms */
- 35 disable_irq_nosync(irq); /* 禁止按键中断 */
- 36 mod_timer(&key.timer, jiffies + msecs_to_jiffies(15));
- 37
- 38 return IRQ_HANDLED;
- 39 }
- 40
- 41 /*
- 42 * @description : 按键初始化函数
- 43 * @param – nd : device_node设备指针
- 44 * @return : 成功返回0,失败返回负数
- 45 */
- 46 static int key_gpio_init(struct device_node *nd)
- 47 {
- 48 int ret;
- 49 unsigned long irq_flags;
- 50
- 51 /* 从设备树中获取GPIO */
- 52 key.gpio_key = of_get_named_gpio(nd, "key-gpio", 0);
- 53 if(!gpio_is_valid(key.gpio_key)) {
- 54 printk("key:Failed to get key-gpio\n");
- 55 return -EINVAL;
- 56 }
- 57
- 58 /* 申请使用GPIO */
- 59 ret = gpio_request(key.gpio_key, "KEY0");
- 60 if (ret) {
- 61 printk(KERN_ERR "key: Failed to request key-gpio\n");
- 62 return ret;
- 63 }
- 64
- 65 /* 将GPIO设置为输入模式 */
- 66 gpio_direction_input(key.gpio_key);
- 67
- 68 /* 获取GPIO对应的中断号 */
- 69 key.irq_key = irq_of_parse_and_map(nd, 0);
- 70 if(!key.irq_key){
- 71 return -EINVAL;
- 72 }
- 73
- 74 /* 获取设备树中指定的中断触发类型 */
- 75 irq_flags = irq_get_trigger_type(key.irq_key);
- 76 if (IRQF_TRIGGER_NONE == irq_flags)
- 77 irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
- 78
- 79 /* 申请中断 */
- 80 ret = request_irq(key.irq_key, key_interrupt, irq_flags,
- "Key0_IRQ", NULL);
- 81 if (ret) {
- 82 gpio_free(key.gpio_key);
- 83 return ret;
- 84 }
- 85
- 86 return 0;
- 87 }
- 88
- 89 /*
- 90 * @description : 定时器服务函数,用于按键消抖,定时时间到了以后
- 91 * 再读取按键值,根据按键的状态上报相应的事件
- 92 * @param – arg : arg参数就是定时器的结构体
- 93 * @return : 无
- 94 */
- 95 static void key_timer_function(struct timer_list *arg)
- 96 {
- 97 int val;
- 98
- 99 /* 读取按键值并上报按键事件 */
- 100 val = gpio_get_value(key.gpio_key);
- 101 input_report_key(key.idev, KEY_0, !val);
- 102 input_sync(key.idev);
- 103
- 104 enable_irq(key.irq_key);
- 105 }
- 106
- 107 /*
- 108 * @description : platform驱动的probe函数,当驱动与设备匹配成功
- 109 * 以后此函数会被执行
- 110 * @param – pdev : platform设备指针
- 111 * @return : 0,成功;其他负值,失败
- 112 */
- 113 static int atk_key_probe(struct platform_device *pdev)
- 114 {
- 115 int ret;
- 116
- 117 /* 初始化GPIO */
- 118 ret = key_gpio_init(pdev->dev.of_node);
- 119 if(ret < 0)
- 120 return ret;
- 121
- 122 /* 初始化定时器 */
- 123 timer_setup(&key.timer, key_timer_function, 0);
- 124
- 125 /* 申请input_dev */
- 126 key.idev = input_allocate_device();
- 127 key.idev->name = KEYINPUT_NAME;
- 128
- 129 #if 0
- 130 /* 初始化input_dev,设置产生哪些事件 */
- 131 __set_bit(EV_KEY, key.idev->evbit); /* 设置产生按键事件 */
- 132 __set_bit(EV_REP, key.idev->evbit); /* 重复事件,比如按下去不放开,
- 就会一直输出信息 */
- 133
- 134 /* 初始化input_dev,设置产生哪些按键 */
- 135 __set_bit(KEY_0, key.idev->keybit);
- 136 #endif
- 137
- 138 #if 0
- 139 key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
- 140 key.idev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
- 141 #endif
- 142
- 143 key.idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
- 144 input_set_capability(key.idev, EV_KEY, KEY_0);
- 145
- 146 /* 注册输入设备 */
- 147 ret = input_register_device(key.idev);
- 148 if (ret) {
- 149 printk("register input device failed!\r\n");
- 150 goto free_gpio;
- 151 }
- 152
- 153 return 0;
- 154 free_gpio:
- 155 free_irq(key.irq_key,NULL);
- 156 gpio_free(key.gpio_key);
- 157 del_timer_sync(&key.timer);
- 158 return -EIO;
- 159
- 160 }
- 161
- 162 /*
- 163 * @description : platform驱动的remove函数,当platform驱动模块卸载
- 164 * 时此函数会被执行
- 165 * @param – dev : platform设备指针
- 166 * @return : 0,成功;其他负值,失败
- 167 */
- 168 static int atk_key_remove(struct platform_device *pdev)
- 169 {
- 170 free_irq(key.irq_key,NULL); /* 释放中断号 */
- 171 gpio_free(key.gpio_key); /* 释放GPIO */
- 172 del_timer_sync(&key.timer); /* 删除timer */
- 173 input_unregister_device(key.idev); /* 释放input_dev */
- 174
- 175 return 0;
- 176 }
- 177
- 178 static const struct of_device_id key_of_match[] = {
- 179 {.compatible = "alientek,key"},
- 180 {/* Sentinel */}
- 181 };
- 182
- 183 static struct platform_driver atk_key_driver = {
- 184 .driver = {
- 185 .name = "stm32mp1-key",
- 186 .of_match_table = key_of_match,
- 187 },
- 188 .probe = atk_key_probe,
- 189 .remove = atk_key_remove,
- 190 };
- 191
- 192 module_platform_driver(atk_key_driver);
- 193
- 194 MODULE_LICENSE("GPL");
- 195 MODULE_AUTHOR("ALIENTEK");
- 196 MODULE_INFO(intree, "Y");
复制代码
在本程序中用到了定时器和中断相关的API函数,这些内容在前面章节都已经给大家介绍过了,而本章的重点知识点是linux下的input子系统,那下面我们将对keyinput.c代码进行讲解。
第14~19行,自定义的按键设备结构体struct key_dev,用于描述一个按键设备,其中的成员变量包括一个input_dev指针变量,定时器timer、GPIO以及中断号。
第29~39行,按键中断处理函数key_interrupt,当按键按下或松开的时候都会触发,也就是上升沿和下降沿都会触发此中断。key_interrupt函数中的操作也很简单,调用disable_irq_nosync函数先禁止中断,然后使用mod_timer打开定时器,定时时长为15ms。
第46~87行,按键的GPIO初始化。
第95~105行,定时器服务函数key_timer_function,使用到定时器的目的主要是为了使用软件的方式进行按键消抖处理,在key_timer_function函数中,我们使用gpio_get_value获取按键GPIO的电平状态,使用input_report_key函数上报按键事件;在val变量前加了一个取反,因为按键GPIO的电平状态,使用input_report_key函数上报按键事件;在val变量前加了一个取反,因为按键按下的情况下,获取到的val为0;按键松开的情况下,获取到的val为1,但是input子系统框架规定按键按下上报1,按键松开上报0,所以这里需要进行取反操作。事件上报完成之后使用input_sync函数同步事件,表示此事件已上报完成,input子系统核心层就会进行相关的处理。114enable_irq函数使能中断,因为在按键中断发生的时候我们会关闭中断,等事件处理完成之后再打开。
第113~160行,platform驱动的probe函数atk_key_probe,其中第 136~161 行,使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码(也就是 KEY 模拟成那个按键,这里我们设置为 KEY_0)。最后使用 input_register_device函数向 Linux 内核注册 input_dev。
第168~176行,platform驱动的remove函数mykey_remove,在该函数中先释放GPIO在使用del_timer_sync删除定时器并且调用input_unregister_device卸载按键设备。
38.3.3 编写测试APP
新建keyinputApp.c文件,然后在里面输入如下所示内容:
示例代码 38.3.3.1 keyinputApp.c 文件代码段
- 1 #include <stdio.h>
- 2 #include <unistd.h>
- 3 #include <sys/types.h>
- 4 #include <sys/stat.h>
- 5 #include <fcntl.h>
- 6 #include <stdlib.h>
- 7 #include <string.h>
- 8 #include <linux/input.h>
- 9
- 10 /*
- 11 * @description : main主程序
- 12 * @param – argc : argv数组元素个数
- 13 * @param – argv : 具体参数
- 14 * @return : 0 成功;其他 失败
- 15 */
- 16 int main(int argc, char *argv[])
- 17 {
- 18 int fd, ret;
- 19 struct input_event ev;
- 20
- 21 if(2 != argc) {
- 22 printf("Usage:\n"
- 23 "\t./keyinputApp /dev/input/eventX @ Open Key\n"
- 24 );
- 25 return -1;
- 26 }
- 27
- 28 /* 打开设备 */
- 29 fd = open(argv[1], O_RDWR);
- 30 if(0 > fd) {
- 31 printf("Error: file %s open failed!\r\n", argv[1]);
- 32 return -1;
- 33 }
- 34
- 35 /* 读取按键数据 */
- 36 for ( ; ; ) {
- 37
- 38 ret = read(fd, &ev, sizeof(struct input_event));
- 39 if (ret) {
- 40 switch (ev.type) {
- 41 case EV_KEY: /* 按键事件 */
- 42 if (KEY_0 == ev.code) { /* 判断是不是KEY_0按键 */
- 43 if (ev.value) /* 按键按下 */
- 44 printf("Key0 Press\n");
- 45 else /* 按键松开 */
- 46 printf("Key0 Release\n");
- 47 }
- 48 break;
- 49
- 50 /* 其他类型的事件,自行处理 */
- 51 case EV_REL:
- 52 break;
- 53 case EV_ABS:
- 54 break;
- 55 case EV_MSC:
- 56 break;
- 57 case EV_SW:
- 58 break;
- 59 };
- 60 }
- 61 else {
- 62 printf("Error: file %s read failed!\r\n", argv[1]);
- 63 goto out;
- 64 }
- 65 }
- 66
- 67 out:
- 68 /* 关闭设备 */
- 69 close(fd);
- 70 return 0;
- 71 }
复制代码
第38.1.3小节已经说过了,Linux内核会使用input_event结构体来表示输入事件,所以我们要获取按键输入信息,那么必须借助于input_event结构体。第19行定义了一个input_event类型变量ev。
第36~65行,当我们向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,因此只需要处理EV_KEY事件即可。比如获取按键编号(KEY_0的编号为11)、获取按键状态,按下还是松开的?
38.4 运行测试
38.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“keyinput.o”,Makefile内容如下所示:
示例代码38.4.1.1 Makefile文件
- 1 KERNELDIR := /home/zuozhongkai/linux/my_linux/linux-5.4.31
- ......
- 4 obj-m := keyinput.o
- ......
- 11 clean:
- 12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
复制代码
第4行,设置obj-m变量的值为“keyinput.o”。
输入如下命令编译出驱动模块文件:
编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试keyinputApp.c这个测试程序:
- arm-none-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
复制代码
编译成功以后就会生成keyinputApp这个应用程序。
38.4.2 运行测试
将上一小节编译出来keyinput.ko和keyinputApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中。在加载keyinput.ko驱动模块之前,先看一下/dev/input目录下都有哪些文件,结果如图38.4.2.1所示:
图38.4.2.1 /dev/input目录
从图38.4.2.1可以看出,在/dev目录下根本就不存在input文件夹,原因在于此时系统中并没有加载过input输入类设备,所以这个文件夹不存在。接下来输入如下命令加载keyinput.ko这个驱动模块。
- depmod //第一次加载驱动的时候需要运行此命令
- modprobe keyinput.ko //加载驱动模块
复制代码
当驱动模块加载成功以后再来看一下/dev/input目录下有哪些文件,结果如图38.4.2.2所示:
图38.4.2.2 加载驱动以后的/dev/input目录
从图38.4.2.2可以看出,在/dev/input目录下生成了一个event0文件,这其实就是我们注册的驱动所对应的设备文件。keyinputApp就是通过读取/dev/input/event0这个文件来获取输入事件信息的,输入如下测试命令:
- ./keyinputApp /dev/input/event0
复制代码
然后按下开发板上的KEY按键,结果如图38.4.2.3所示:
图38.4.2.3 测试结果
从图38.4.2.3可以看出,当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在Linux内核中KEY_0为11。
另外,我们也可以不用keyinputApp来测试驱动,可以直接使用hexdump命令来查看/dev/input/event0文件内容,输入如下命令:
图38.4.2.4 原始数据值
图38.4.2.4就是input_event类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:
type为事件类型,查看示例代码38.1.2.3可知,EV_KEY事件值为1,EV_SYN事件值为0。因此第1行表示EV_KEY事件,第2行表示EV_SYN事件。code为事件编码,也就是按键号,查看示例代码38.1.2.4可以,KEY_0这个按键编号为11,对应的十六进制为0xb,因此第1行表示KEY_0这个按键事件,最后的value就是按键值,为1表示按下,为0的话表示松开。综上所述,示例代码38.4.2.1中的原始事件值含义如下:
第1行,按键(KEY_0)按下事件。
第2行,EV_SYN同步事件,因为每次上报按键事件以后都要同步的上报一个EV_SYN事件。
第3行,按键(KEY_0)松开事件。
第4行,EV_SYN同步事件,和第2行一样。
38.5 Linux自带按键驱动程序的使用
38.5.1 自带按键驱动程序源码简析
Linux内核也自带了KEY驱动,如果要使用内核自带的KEY驱动的话需要配置Linux内核,不过Linux内核一般默认已经使能了KEY驱动,但是我们还是要检查一下。按照如下路径找到相应的配置选项:
- Device Drivers
- Input device support
- Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
- Keyboards (INPUT_KEYBOARD [=y])
- GPIO Buttons
复制代码
选中“GPIO Buttons”选项,将其编译进Linux内核中,如图38.5.1.1所示:
图38.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文件中找到如下所示内容:
示例代码38.5.1.1 gpio_keys文件代码段
- 758 static const struct of_device_id gpio_keys_of_match[] = {
- 759 { .compatible = "gpio-keys", },
- 760 { },
- 761 };
- ......
- 1012 static struct platform_driver gpio_keys_device_driver = {
- 1013 .probe = gpio_keys_probe,
- 1014 .shutdown = gpio_keys_shutdown,
- 1015 .driver = {
- 1016 .name = "gpio-keys",
- 1017 .pm = &gpio_keys_pm_ops,
- 1018 .of_match_table = gpio_keys_of_match,
- 1019 .dev_groups = gpio_keys_groups,
- 1020 }
- 1021 };
- 1022
- 1023 static int __init gpio_keys_init(void)
- 1024 {
- 1025 return platform_driver_register(&gpio_keys_device_driver);
- 1026 }
- 1027
- 1028 static void __exit gpio_keys_exit(void)
- 1029 {
- 1030 platform_driver_unregister(&gpio_keys_device_driver);
- 1031 }
复制代码
从示例代码38.5.1.1可以看出,这就是一个标准的platform驱动框架,如果要使用设备树来描述KEY设备信息的话,设备节点的compatible属性值要设置为“gpio-keys”。当设备和驱动匹配以后gpio_keys_probe函数就会执行,gpio_keys_probe函数内容如下(为了篇幅有缩减):
示例代码 38.5.1.2 gpio_keys_probe 函数代码段
- <font size="2">764 static int gpio_keys_probe(struct platform_device *pdev)
- 765 {
- 766 struct device *dev = &pdev->dev;
- 767 const struct gpio_keys_platform_data *pdata =
- dev_get_platdata(dev);
- 768 struct fwnode_handle *child = NULL;
- 769 struct gpio_keys_drvdata *ddata;
- 770 struct input_dev *input;
- 771 int i, error;
- 772 int wakeup = 0;
- 773
- 774 if (!pdata) {
- 775 pdata = gpio_keys_get_devtree_pdata(dev);
- 776 if (IS_ERR(pdata))
- 777 return PTR_ERR(pdata);
- 778 }
- ......
- 793 input = devm_input_allocate_device(dev);
- 794 if (!input) {
- 795 dev_err(dev, "failed to allocate input device\n");
- 796 return -ENOMEM;
- 797 }
- .....
- 806 input->name = pdata->name ? : pdev->name;
- 807 input->phys = "gpio-keys/input0";
- 808 input->dev.parent = dev;
- 809 input->open = gpio_keys_open;
- 810 input->close = gpio_keys_close;
- 811
- 812 input->id.bustype = BUS_HOST;
- 813 input->id.vendor = 0x0001;
- 814 input->id.product = 0x0001;
- 815 input->id.version = 0x0100;
- .....
- 821 /* Enable auto repeat feature of Linux input subsystem */
- 822 if (pdata->rep)
- 823 __set_bit(EV_REP, input->evbit);
- 824
- 825 for (i = 0; i < pdata->nbuttons; i++) {
- 826 const struct gpio_keys_button *button =
- &pdata->buttons;
- 827
- 828 if (!dev_get_platdata(dev)) {
- 829 child = device_get_next_child_node(dev, child);
- 830 if (!child) {
- 831 dev_err(dev,
- 832 "missing child device node for entry %d\n",
- 833 i);
- 834 return -EINVAL;
- 835 }
- 836 }
- 837
- 838 error = gpio_keys_setup_key(pdev, input, ddata,
- 839 button, i, child);
- 840 if (error) {
- 841 fwnode_handle_put(child);
- 842 return error;
- 843 }
- 844
- 845 if (button->wakeup)
- 846 wakeup = 1;
- 847 }
- 848
- 849 fwnode_handle_put(child);
- 850
- 851 error = input_register_device(input);
- 852 if (error) {
- 853 dev_err(dev, "Unable to register input device, error: %d\n",
- 854 error);
- 855 return error;
- 856 }
- 857
- 858 device_init_wakeup(dev, wakeup);
- 859
- 860 return 0;
- 861 }</font>
复制代码
第775行,调用gpio_keys_get_devtree_pdata函数从设备树中获取到KEY相关的设备节点信息。
第793行,使用devm_input_allocate_device函数申请input_dev。
第806~815,初始化input_dev。
第823行,设置input_dev事件,这里设置了EV_REP事件。
第838行,调用gpio_keys_setup_key函数继续设置KEY,此函数会设置input_dev的EV_KEY事件以及事件码(也就是KEY模拟哪个按键)。
第851行,调用input_register_device函数向Linux系统注册input_dev。
我们接下来再来看一下gpio_keys_setup_key函数,此函数内容如下:
示例代码 38.5.1.3 gpio_keys_setup_key 函数代码段
- 477 static int gpio_keys_setup_key(struct platform_device *pdev,
- 478 struct input_dev *input,
- 479 struct gpio_keys_drvdata *ddata,
- 480 const struct gpio_keys_button *button,
- 481 int idx,
- 482 struct fwnode_handle *child)
- 483 {
- 484 const char *desc = button->desc ? button->desc : "gpio_keys";
- 485 struct device *dev = &pdev->dev;
- 486 struct gpio_button_data *bdata = &ddata->data[idx];
- 487 irq_handler_t isr;
- 488 unsigned long irqflags;
- 489 int irq;
- 490 int error;
- 491
- 492 bdata->input = input;
- 493 bdata->button = button;
- 494 spin_lock_init(&bdata->lock);
- 495
- .....
- 612 bdata->code = &ddata->keymap[idx];
- 613 *bdata->code = button->code;
- 614 input_set_capability(input, button->type ?: EV_KEY, *bdata->code);
- 615
- .....
- 641
- 642 return 0;
- 643 }
复制代码
第614行,调用input_set_capability函数设置EV_KEY事件以及KEY的按键类型,也就是KEY作为哪个按键?我们会在设备树里面设置指定的KEY作为哪个按键。
一切都准备就绪以后剩下的就是等待按键按下,然后向Linux内核上报事件,事件上报是在gpio_keys_irq_isr函数中完成的,此函数内容如下:
示例代码 38.5.1.4 gpio_keys_irq_isr 函数代码段
- 433 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
- 434 {
- 435 struct gpio_button_data *bdata = dev_id;
- 436 struct input_dev *input = bdata->input;
- 437 unsigned long flags;
- 438
- 439 BUG_ON(irq != bdata->irq);
- 440
- 441 spin_lock_irqsave(&bdata->lock, flags);
- 442
- 443 if (!bdata->key_pressed) {
- 444 if (bdata->button->wakeup)
- 445 pm_wakeup_event(bdata->input->dev.parent, 0);
- 446
- 447 input_event(input, EV_KEY, *bdata->code, 1);
- 448 input_sync(input);
- 449
- 450 if (!bdata->release_delay) {
- 451 input_event(input, EV_KEY, *bdata->code, 0);
- 452 input_sync(input);
- 453 goto out;
- 454 }
- 455
- 456 bdata->key_pressed = true;
- 457 }
- 458
- 459 if (bdata->release_delay)
- 460 mod_timer(&bdata->release_timer,
- 461 jiffies + msecs_to_jiffies(bdata->release_delay));
- 462 out:
- 463 spin_unlock_irqrestore(&bdata->lock, flags);
- 464 return IRQ_HANDLED;
- 465 }
复制代码
gpio_keys_irq_isr是按键中断处理函数,第447行向Linux系统上报EV_KEY事件,表示按键按下。第448行使用input_sync函数向系统上报EV_REP同步事件。
综上所述,Linux内核自带的gpio_keys.c驱动文件思路和我们前面编写的keyinput.c驱动文件基本一致。都是申请和初始化input_dev,设置事件,向Linux内核注册input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。
38.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要模拟的按键,也就是示例代码38.1.2.4中的这些按键。
④、如果按键要支持连按的话要加入autorepeat。
这里我们将开发板上的三个按键都用起来,KEY0、KEY1和WKUP分别模拟为键盘上的:L、S和Enter(回车)健。
打开stm32mp157d-atk.dts,先添加一个头文件“dt-bindings/input/input.h”此文件就是“linux,code”属性的按键宏定义,如图38.5.2.1所示:
图38.5.2.1 添加头文件
头文件添加完成以后再根据上面的要求创建对应的设备节点,设备节点内容如下所示:
示例代码 38.5.2.1 gpio-keys 节点内容
- 1 gpio-keys {
- 2 compatible = "gpio-keys";
- 3 pinctrl-names = "default";
- 4 pinctrl-0 = <&key_pins_a>;
- 5 autorepeat;
- 6
- 7 key0 {
- 8 label = "GPIO Key L";
- 9 linux,code = <KEY_L>;
- 10 gpios = <&gpiog 3 GPIO_ACTIVE_LOW>;
- 11 };
- 12
- 13 key1 {
- 14 label = "GPIO Key S";
- 15 linux,code = <KEY_S>;
- 16 gpios = <&gpioh 7 GPIO_ACTIVE_LOW>;
- 17 };
- 18
- 19 wkup {
- 20 label = "GPIO Key Enter";
- 21 linux,code = <KEY_ENTER>;
- 22 gpios = <&gpioa 0 GPIO_ACTIVE_HIGH>;
- 23 gpio-key,wakeup;
- 24 };
- 25 };
复制代码
第5行,autorepeat表示按键支持连按。
第7~11行,STM32MP1开发板KEY0按键信息,名字设置为“GPIO Key L”,这里我们将开发板上的KEY0按键设置为“EKY_L”这个按键,也就是‘L’键,效果和键盘上的‘L’键一样。
第13~17行,KEY1按键设置,模拟键盘上的‘S’键。
第19~24行,WKUP按键,模拟键盘上的‘回车’键,注意,WKUP按键连接的PA0引脚,查看原理图可以知道,WKUP按下去以后是高电平,因此这里设置高电平有效。
最后一定要检查一下设备树看看这些引脚有没有被用到其他外设上,如果有的话要删除掉相关代码!
重新编译设备树,然后用新编译出来的stm32mp157d-atk.dtb启动Linux系统,系统启动以后查看/dev/input目录,看看都有哪些文件,结果如图38.5.2.1所示:
图38.5.2.1 /dev/input目录文件
从图38.5.2.1可以看出存在event0这个文件,这个文件就是KEY对应的设备文件,使用hexdump命令来查看/dev/input/event0文件,输入如下命令:
- hexdump /dev/input/event0
复制代码
然后按下STM32MP1开发板上的按键,终端输出图38.5.2.2所示内容:
图38.5.2.2 按键信息
如果按下KEY按键以后会在终端上输出图38.5.2.2所示的信息那么就表示Linux内核的按键驱动工作正常。至于图38.5.2.2中内容的含义大家就自行分析,这个已经在38.4.2小节详细的分析过了,这里就不再讲解了。
大家如果发现按下KEY按键以后没有反应,那么请检查一下三方面:
①、是否使能Linux内核KEY驱动。
②、设备树中gpio-keys节点是否创建成功。
③、在设备树中是否有其他外设也使用了KEY按键对应的GPIO,但是我们并没有删除掉这些外设信息。检查Linux启动log信息,看看是否有类似下面这条信息:
- gpio-keys gpio-keys: failed to get gpio: -16
复制代码
上述信息表示GPIO申请失败,失败的原因就是有其他的外设正在使用此GPIO。
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
该献的血还是要献的。你不献他不献。难道让我去献? --- 出自坛友:lovejp1981
|