正点原子嵌入式linux驱动开发——Linux INPUT子系统

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

input子系统

input子系统简介

input子系统就是管理输入的子系统,和pinctrl、gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,只需要按照要求上报这些输入事件即可。为此input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点,input子系统框
架如下图所示:
input子系统结构图
上图中左边就是最底层的具体设备,比如按键、USB键盘 /鼠标等,中间部分属于Linux内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出input子系统用到了前面讲解的驱动分层模型,编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:

  • 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
  • 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行
    处理。
  • 事件层:主要和用户空间进行交互。

input驱动编写流程

input核心层会向Linux内核注册一个字符设备,找到drivers/input/input.c这个文件,input.c就是input输入子系统的核心层,此文件里面有如下所示代码:
input核心层创建字符设备
第2498行,注册一个input类,这样系统启动以后就会在/sys/class目录下有一个input子
目录,如下图所示:
input类
第2508-2509行,注册一个字符设备,主设备号为INPUT_MAJOR,INPUT_MAJOR定义
在include/uapi/linux/major.h文件中,定义如下:

#define INPUT_MAJOR 13

因此,input子系统的所有设备主设备号都为13,在使用input子系统处理输入设备
的时候就不需要去注册字符设备了,只需要向系统注册一个input_device即可

注册input_dev

在使用input子系统的时候只需要注册一个input设备即可,input_dev结构体表示input设备,此结构体定义在include/linux/input.h文件中,定义如下 (有省略):
input_dev结构体
第139行,evbit表示输入事件类型,可选的事件类型定义在include/uapi/linux/input.h文件
中,事件类型如下:
事件类型
比如本章要使用到按键,那么就需要注册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中的任意一个,比如本章实验会将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注册过程如下:

  1. 使用input_allocate_device函数申请一个input_dev。
  2. 初始化input_dev的事件类型以及事件值。
  3. 使用input_register_device函数向Linux系统注册前面初始化好的input_dev。
  4. 卸载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。

上报输入事件

当向Linux内核注册好input_dev以后需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给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函数,此函数内容如下:
input_report_key函数
从示例代码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这个按键没有按下或松开了。

input_event结构体

Linux内核使用input_event这个结构体来表示 所有的输入事件,input_envent结构体定义在include/uapi/linux/input.h文件中,结构体内容如下:
input_event结构体
依次来看一下input_event结构体中的各个成员变量:

  • time:时间,也就是此事件发生的时间,为timeval结构体类型,timeval结构体定义如下:
    timeval结构体
    从示例代码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驱动

硬件原理图分析

就是之前的按键原理图。

实验程序编写

修改设备树文件

创建按键引脚的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和KEY1这两个按键是低电平有效(按下以后为低电平),WK_UP是高电平有效(按下以后为高电平)。所以这里分开初始化,其中第2-7行初始化KEY0和KEY1使用的PG3和PH7这两个引脚,第9-13行初始化WK_UP使用的PA0引脚。

创建按键引脚的设备节点

接下就是创建按键设备节点,可以直接在之前的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 };

按键input驱动程序编写

按键结构体key_dev中,需要input_dev结构体指针*idev;需要timer_list结构体的timer来辅助按键消抖;需要int类型的gpio_key表示按键的GPIO编号以及int类型的irq_key表示按键对应的中断号。

编写按键的中断服务函数static irqreturn_t的函数key_interrupt,里面需要先disable_irq_nosync禁止中断,然后通过mod_timer激活定时器来消抖。

编写key_gpio_init来初始化按键,里面就是常规操作了,先of_get_named_gpio从设备树获取按键的GPIO,然后gpio_request申请GPIO,设置输入gpio_direction_input,通过irq_of_parse_and_map来获取对应的中断号,之后irq_get_trigger_type获取中断触发类型,并request_irq申请中断。

编写定时器的中断服务函数完成按键消抖,key_timer_function中,gpio_get_value获取当前按键值后,input_report_key上报案件时间,并通过input_sync来同步,最后enable_irq开启中断。

编写platform驱动的probe函数atk_key_probe,完成GPIO的初始化,调用key_gpio_init初始化,然后timer_setup初始化定时器;input子系统需要input_allocate_device申请设备,然后设置key.idev->name名字,通过__set_bit来设置事件以及触发的按键值(也可以通过BIT_MASK设置,效果一样),最后input_register_device注册input设备。

编写platform驱动的remove函数atk_key_remove,需要free_irq释放中断号,gpio_free释放GPIO,del_timer_sync删除timer,最后input_unregister_device释放input设备。

platform还需要编写一个of_device_id结构体的列表key_of_match[],设置.compatible属性,保持与设备树中节点的compatible一致。

然后设置platform_driver结构体的atk_key_driver这个platform驱动,设置.driver中的.name(设备树中名字)以及.of_match_table;然后设置.probe以及.remove函数。

最后module_platform_driver(atk_key_driver)即可。

编写测试APP

传入的argc就2个;open(argv[1], O_RDWR)之后,在for死循环中read读取设备,通过传入的值判断具体的按键情况,最后写一个close就可以了。

运行测试

编译驱动程序

Makefile还是老样子,改一下obj-m为keyinput.o然后“make”就可以了。

编译测试APP

可以通过如下命令编译:

arm-none-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp

运行测试

将上一小节编译出来keyinput.ko和keyinputApp这两个文件拷贝到rootfs/lib/modules/5.4.31目录中,重启开发板,进入到目录lib/modules/5.4.31中。在加载 keyinput.ko驱动模块之前,先看一下/dev/input目录下都有哪些文件,结果如下图所示:
/dev/input目录
可以从上图看出,当前在/dev目录下不存在input文件夹,因为此时系统中并没有加载过input输入类设备,所以这个文件夹不存在。接下来输入如下命令加载keyinput.ko这个驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载设备模块

当驱动模块加载成功以后再来看一下/dev/input目录下有哪些文件,结果如下图所示:
加载驱动后/dev/input目录
从上图可以看出,在/dev/input目录下生成了一个event0文件,这其实就是注册的驱动所对应的设备文件。keyinputApp就是通过读取/dev/input/event0这个文件来获取输入事
件信息的,输入如下测试命令:

./keyinputApp /dev/input/event0

之后按下KEY0按键即可被检测到。

另外,也可以不用keyinputApp来测试驱动,可以直接使用hexdump命令来查看/dev/input/event0文件内容,输入如下命令:

hexdump /dev/input/event0

可以得到如下结果:
原始数据值

上图就是input_event类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:
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行一样。

Linux自带按键驱动程序使用

自带按键驱动程序源码解析

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内核中,如下图所示:
内核自带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文件中找到如下所示内容:
gpio_keys文件代码段
从示例代码38.5.1.1可以看出,这就是一个标准的platform驱动框架,如果要使用设备树来描述KEY设备信息的话,设备节点的compatible属性值要设置为“gpio-keys”。当设备和驱动匹配以后gpio_keys_probe函数就会执行,gpio_keys_probe函数内容如下(为了篇幅有缩减):

示例代码 38.5.1.2 gpio_keys_probe 函数代码段 
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[i]; 
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 }

第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函数,此函数内容如下:
gpio_keys_setup_key函数
第614行,调用input_set_capability函数设置EV_KEY事件以及KEY的按键类型,也就是KEY作为哪个按键?会在设备树里面设置指定的KEY作为哪个按键。

一切都准备就绪以后剩下的就是等待按键按下,然后向Linux内核上报事件,事件上报是在gpio_keys_irq_isr函数中完成的,此函数内容如下:
gpio_keys_irq_isr函数
gpio_keys_irq_isr是按键中断处理函数,第447行向Linux系统上报EV_KEY事件,表示按键按下。第448行使用input_sync函数向系统上报EV_REP同步事件。

综上所述,Linux内核自带的gpio_keys.c驱动文件思路和前面编写的keyinput.c驱动文件基本一致。都是申请和初始化input_dev,设置事件,向Linux内核注册input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值

自带按键驱动程序的使用

要使用Linux内核自带的按键驱动程序很简单,只需要根据Documentation/devicetree/bindings/input/gpio-keys.txt这个文件在设备树中添加指定的设备节点
即可,节点要求如下:

  1. 节点名字为“gpio-keys”。
  2. gpio-keys节点的compatible属性值一定要设置为“gpio-keys”。
  3. 所有的KEY都是gpio-keys的子节点,每个子节点可以用如下属性描述自己:
    • gpios:KEY所连接的GPIO信息。
    • interrupts:KEY所使用GPIO中断信息,不是必须的,可以不写。
    • label:KEY名字。
    • linux,code:KEY要模拟的按键,也就是示例代码38.1.2.4中的这些按键。
  4. 如果按键要支持连按的话要加入autorepeat。

这里将开发板上的三个按键都用起来,KEY0、KEY1和WKUP分别模拟为键盘上的:L、S和Enter(回车)健。

打开stm32mp157d-atk.dts,先添加一个头文件“dt-bindings/input/input.h”此文件就是“linux,code”属性的按键宏定义,如下图所示:
添加头文件
头文件添加完成以后再根据上面的要求创建对应的设备节点,设备节点内容如下所示:

示例代码 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目录,看看都有哪些文件,结果如下图所示:
/dev/input目录文件
从上图可以看出存在event0这个文件,这个文件就是KEY对应的设备文件,使用hexdump命令来查看/dev/input/event0文件,输入如下命令:

hexdump /dev/input/event0

然后按下STM32MP1开发板上的按键,终端输出如下图所示内容:
按键信息
如果按下KEY按键以后会在终端上输出上图所示的信息那么就表示Linux内核的按键驱动工作正常。

如果按键没有反应,可以检查一下如下3方面:

  1. 是否使能Linux内核KEY驱动。
  2. 设备树中gpio-keys节点是否创建成功。
  3. 在设备树中是否有其他外设也使用了KEY按键对应的GPIO,但是并没有删除掉这些外设信息。检查Linux启动log信息,看看是否有类似下面这条信息,如果有表明GPIO申请失败,有其他外设使用此GPIO:
gpio-keys gpio-keys: failed to get gpio: -16

总结

input子系统可以驱动输入的外设,使用起来就是platform驱动加上input自己的驱动内容。

自行添加input驱动

编写驱动前,需要先在stm32mp15-pinctrl.dtsi文件中创建对应的pinctrl子节点,并在stm32mp157d-atk.dts设备树中添加对应节点。

驱动程序中,初始化就是gpio的初始化写法,没什么花头,最多就是跟本篇笔记中一样加一个中断的初始化。

然后是定时器服务函数,这里在gpio_get_value之后获取状态,需要input_report_key和input_sync上报按键事件,并enable_irq开启中断。

probe函数就是调用GPIO的初始化,然后timer_setup初始化定时器;关键就是input_allocate_device然后__set_bit设置事件,最后input_register_devize注册输入设备

remove函数中,区别就是加上input_unregister_device释放input设备

最后module_platform_driver加上接好的platform_driver结构体的驱动。

Linux内核自带

如果是Linux内核相关,则先打开图形化界面然后去使能;比如本次的按键输入,使能之后,在stm32mp157d-atk.dts这个设备树文件中,首先需要添加头文件"dt-bindings/input/input.h",然后添加对应的GPIO节点,之后就可以使用了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/107286.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++——类和对象(上)

1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 例如手洗衣服 C是基于面向对象的&#xff0c;关注的是对象&#xff0c;将一件事情拆分成不同的对象&#xff0c;靠对象之间…

计算机视觉的相机选型

#你一般什么时候会用到GPT?# 目前市面上的工业相机大多是基于CCD&#xff08;ChargeCoupled Device&#xff09;或CMOS&#xff08;Complementary Metal Oxide Semiconductor&#xff09;芯片的相机。一般CCD制造工艺更加复杂&#xff0c;也会更贵一点&#xff01; 1、CCD工…

底层全部重构,小米澎湃OS完整系统架构公布

上周&#xff0c;雷军发文称小米全新操作系统澎湃 OS 正式版已完成封包&#xff0c;将逐步接替 MIUI。而后&#xff0c;又有网友曝光小米澎湃 OS 界面。 今日&#xff0c;雷军再度发表长文预热小米澎湃 OS&#xff0c;正式公布了完整系统架构。 据介绍&#xff0c;从架构设计之…

统计学习方法 决策树

文章目录 统计学习方法 决策树决策树模型与学习特征选择决策树的生成ID3 算法C4.5 的生成算法 决策树的剪枝CART 算法CART 回归树的生成CART 分类树的生成CART 剪枝 统计学习方法 决策树 阅读李航的《统计学习方法》时&#xff0c;关于决策树的笔记。 决策树模型与学习 决策…

归结原理、归结演绎推理

主要内容 归结演绎推理范式子句与子句集将谓词公式转化为子句集命题逻辑鲁宾逊归结原理 归结演绎推理 定理证明的实质是对前提P和结论Q证明P →Q的永真性应用反证法&#xff0c;欲证明P →Q&#xff0c;只要证明 P∧~Q 等价于 F鲁宾逊归结原理对机械化推理有重大突破鲁宾逊归…

【Ansible自动化运维工具 1】Ansible常用模块详解(附各模块应用实例和Ansible环境安装部署)

Ansible常用模块 一、Ansible1.1 简介1.2 工作原理1.3 Ansible的特性1.3.1 特性一&#xff1a;Agentless&#xff0c;即无Agent的存在1.3.2 特性二&#xff1a;幂等性 1.4 Ansible的基本组件 二、Ansible环境安装部署2.1 安装ansible2.2 查看基本信息2.3 配置远程主机清单 三、…

windows PC virtualBox 配置

效果&#xff1a; oracle vitualbox 可以访问通PC主机&#xff0c;可以访问外网: 注意&#xff0c;如果docker0网络地址&#xff0c;和PC主机的网络地址冲突了&#xff0c;需要变更docker的网络地址&#xff1a; root/home/mysqlPcap/anti-tamper $ cat /etc/docker/daemon.js…

C语言找出一个二维数组中的鞍点,即该位置上的元素在该行上最大,在该列上最小,也可能没有鞍点

完整代码&#xff1a; /*找出一个二维数组中的鞍点&#xff0c;即该位置上的元素在该行上最大&#xff0c;在该列上最小&#xff0c;也可能 没有鞍点*/ #include<stdio.h> #include<stdlib.h> int main(){int n0;printf("请输入矩阵的行数:");scanf(&qu…

牛客网刷题-(7)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

168. Excel表列名称

168. Excel表列名称 Java代码&#xff1a; 26进制&#xff0c;但是每个进制是从1开始的&#xff0c;不是从0开始&#xff1b;因此要计算要构建从0开始的求余&#xff01; class Solution {public String convertToTitle(int cn) {StringBuilder sb new StringBuilder();whi…

matlab中类的分别之handle类和value类——matlab无法修改类属性值的可能原因

写在之前&#xff08;吐槽&#xff09; 最近由于变化了一些工作方向&#xff0c;开始需要使用matlab进行开发&#xff0c;哎哟喂&#xff0c;matlab使用的我想吐&#xff0c;那个matlab编辑器又没代码提示&#xff0c;又没彩色&#xff0c;我只好用vscode进行代码编辑&#xf…

计算机网络【CN】介质访问控制

信道划分介质访问控制 FDMTDMWDMCDM【掌握eg即可】 随机介质访问控制 CSMA 1-坚持CSMA 非坚持CSMA p-坚持CSMA 空闲时 立即发送数据 立即发送数据 以概率P发送数据&#xff0c;以概率1-p推迟到下一个时隙 忙碌时 继续坚持侦听 放弃侦听&#xff0c;等待一个随机的时…

Centos使用war文件部署jenkins

部署jenkins所需要的jdk环境如下&#xff1a; 这里下载官网最新的版本&#xff1a; 选择jenkins2.414.3版本&#xff0c;所以jdk环境最低得是java11 安装java11环境 这里直接安装open-jdk yum -y install java-11-openjdk.x86_64 java-11-openjdk-devel.x86_64下载jenkins最新…

12种常见的恶意软件类型与防范建议

1、病毒 病毒是迄今为止最常见的恶意软件类型之一。它是一种能够感染、破坏计算机设备&#xff0c;并在其运行系统上自我复制的程序。由于病毒是自我复制的&#xff0c;一旦安装并运行&#xff0c;它们就可以在同一网络上自动从一台设备传播到另一台设备&#xff0c;无需人为干…

ARM汇编指令之数据操作指令

数据搬移指令&#xff1a;立即数&#xff1a;在待判断的32位数&#xff08;以十六进制展开&#xff09;中&#xff0c;寻找一个0~255&#xff08;即0x00~0xff&#xff09;之间的数值&#xff0c;然后将这个数值循环右移偶数个位置&#xff0c;可以得到待判断的数&#xff0c;即…

【期中复习】深度学习

文章目录 机器&#xff08;深度&#xff09;学习的四大核心要素为什么深度学习&#xff0c;不增加网络宽度黑盒模型的问题计算图线性神经网络梯度下降学习率优化方法softmax函数用于多分类交叉熵线性回归与softmax回归的对比为什么需要非线性激活函数感知机线性回归、softmax回…

2023版 STM32实战12 IIC总线读写AT24C02

IIC简述 一个多主从的串行总线&#xff0c;又叫I2C&#xff0c;是由飞利浦公司发明的通讯总线 IIC特点 -1- 串行(逐bit传输) -2- 同步(共用时钟线) -3- 半双工(收发不同进行) -4- 总线上的任何设备都可以是主机 开发使用习惯和理解 -1- 通过地址寻址 -2- 数据线的…

「网络编程」数据链路层协议_ 以太网协议学习

「前言」文章内容是数据链路层以太网协议的讲解。 「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 一、以太网协议简介二、以太网帧格式&#xff08;报头&#xff09;三、MTU对上层协议的影响四、ARP协议4.1 ARP协议的作用4.2 ARP协议报头 一、以太网协…

基于单片机的IC卡门禁系统设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、主要研究内容及总体设计方案1.1 系统方案设计1.2系统工作原理 二、硬件设计2.1 主控电路 三、软件设计3.2主程序设计实物附录1 原理图附录2 源程序清单 四、 结论五、 文章目录 概要 本论文重点通过对射频技术…

小米14系列, OPPO Find N3安装谷歌服务框架,安装Play商店,Google

10月26号小米发布了新款手机小米14,那么很多大家需求问是否支持谷歌服务框架,是否支持Google Play商店gms。因为毕竟小米公司现在安装的系统是HyperOS澎湃OS。但是我拿到手机之后会发现还是开机初始界面会显示power by android,证明这一点他还是支持安装谷歌,包括最近一段时间发…