例程代码路径:ELF 1开发板资料包3-例程源码3-2 驱动例程源码8_input子系统keyboard
下面以控制开发板上的K2为例进行讲解。
修改设备树
(一)查看原理图和引脚复用表格,可以得到K2由GPIO5_2控制,所以我们需要配置GPIO5_2引脚为输入,而且能够在用户空间能够获取按键事件。
(二)在设备树arch/arm/boot/dts/imx6ull-elf1-emmc.dts中添加keyboard节点和引脚复用,并检查设备树中是否有其它的地方也用到了此引脚,如果用到了就需要将其屏蔽掉,避免复用冲突。
添加节点:
keyboard {
compatible = "keyboard"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_keyboard>; gpios = <&gpio5 2 GPIO_ACTIVE_LOW>; }; |
添加后效果如下:
添加复用,因为GPIO5_IO2为SNVS引脚,所以需要在iomux_snvs中添加:
pinctrl_keyboard: keyboardgrp {
fsl,pins = < MX6ULL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x17059 >; }; |
添加后效果如下:
(三)编译设备树
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs |
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码my_keyboard.c编写
(一)头文件引用
include <linux/init.h>
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/device.h> //包含设备节点相关的头文件 #include <linux/gpio.h> //包含gpio操作函数的相关头文件 #include <linux/of_gpio.h> #include <linux/of.h> #include <linux/input.h> #include <linux/interrupt.h> |
(二)创建相关宏定义
struct device_node *node; //设备树节点
int gpio; //gpio编号 struct keyboard { struct input_dev *input_dev; int irq; }; struct input_dev *input_dev; |
(三)定义platform_driver类型结构体
static struct platform_driver my_platform_driver = {
.driver = { .name = "my_keyboard_driver", .owner = THIS_MODULE, .of_match_table = of_platform_match, }, .probe = keyboard_probe, .remove = my_platform_remove, }; |
(四)定义of_platform_match,用来与设备树中的compatible匹配,匹配成功后才会进入到probe函数中
static const struct of_device_id of_platform_match[] = {
{ .compatible = "keyboard", }, {}, }; |
(五)probe函数的实现
static int keyboard_probe(struct platform_device *pdev)
{ struct keyboard *keyboard; int ret; node = of_find_node_by_name(NULL,"keyboard"); gpio = of_get_named_gpio(node, "gpios", 0); //为keyboard结构体申请内存 keyboard = devm_kzalloc(&pdev->dev,sizeof(*keyboard),GFP_KERNEL); if(!keyboard) return -ENOMEM; // 分配input设备结构 input_dev = input_allocate_device(); if(!input_dev) return -ENOMEM; keyboard->input_dev = input_dev; gpio_free(gpio); //申请GPIO编号 if (gpio_request(gpio, "keyboard")) { printk("request %s gpio%d faile n", "keyboard",gpio); return -1; } //设置为输入 gpio_direction_input(gpio); keyboard->irq = gpio_to_irq(gpio); // 设置支持按键事件 set_bit(EV_KEY, input_dev->evbit); // 设置支持按键类型为KEY_ENTER set_bit(KEY_ENTER, input_dev->keybit); // 注册输入设备 ret = input_register_device(input_dev); if(ret) return ret; //申请中断 ret = devm_request_irq(&pdev->dev, keyboard->irq, keyboard_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "keyboard_irq", keyboard); if (ret) { input_unregister_device(input_dev); return ret; } // 将输入设备与平台设备关联 platform_set_drvdata(pdev, input_dev); printk(KERN_INFO "my_platform_probe: Platform device probedn"); return 0; } |
可以看到probe函数中进行了一些列的初始化操作:
(1)获取设备树中的gpio引脚编号;
(2)分配input设备结构;
(3)申请GPIO编号并配置为输入模式;
(4)设置按键事件和类型;
(5)注册输入设备;
(6)申请中断;
(7)将输入设备与平台设备关联。
(六)中断服务函数实现
static irqreturn_t keyboard_irq_handler(int irq, void *dev_id)
{ struct keyboard *keyboard = dev_id; int value = gpio_get_value(gpio); input_report_key(keyboard->input_dev, KEY_ENTER, !value); input_sync(keyboard->input_dev); return IRQ_HANDLED; } |
中断服务函数中读取了引脚状态,然后通过input_report_key()和input_sync()函数将事件上报给用户空间。
(1)input_report_key()原型:
void input_report_key(struct input_dev *dev, unsigned int code, int value); |
参数说明:
dev:指向 struct input_dev 的指针,表示目标输入设备。
code:无符号整数,表示按键码(键值)。
value:整数,表示按键状态。0表示按键释放,1表示按键按下,2表示按键重复。
input_report_key() 函数将按键事件报告给输入设备 dev,并指定按键码和按键状态。该函数会更新 input_dev 结构体中相应按键的状态信息,并触发输入子系统将事件传递到用户空间。
(2)input_sync()原型:
void input_sync(struct input_dev *dev); |
参数说明:
dev:指向 struct input_dev的指针,表示目标输入设备。
input_sync()函数用于通知输入子系统当前输入设备的事件已经全部报告完毕,需要立即将事件传递到用户空间。在调用 input_report_*() 系列函数报告完输入事件后,应当调用input_sync()函数以确保事件被及时处理。
完整的驱动my_keyboard.c示例源码
#include <linux/init.h>
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/device.h> //包含设备节点相关的头文件 #include <linux/gpio.h> //包含gpio操作函数的相关头文件 #include <linux/of_gpio.h> #include <linux/of.h> #include <linux/input.h> #include <linux/interrupt.h> struct device_node *node; //设备树节点 int gpio; //gpio编号 struct keyboard { struct input_dev *input_dev; int irq; }; struct input_dev *input_dev; static irqreturn_t keyboard_irq_handler(int irq, void *dev_id) { struct keyboard *keyboard = dev_id; int value = gpio_get_value(gpio); printk("------------------n"); input_report_key(keyboard->input_dev, KEY_ENTER, !value); input_sync(keyboard->input_dev); return IRQ_HANDLED; } static int keyboard_probe(struct platform_device *pdev) { struct keyboard *keyboard; int ret; node = of_find_node_by_name(NULL,"keyboard");  gpio = of_get_named_gpio(node, "gpios", 0);  keyboard = devm_kzalloc(&pdev->dev,sizeof(*keyboard),GFP_KERNEL);  if(!keyboard)   return -ENOMEM;  input_dev = input_allocate_device();  if(!input_dev)   return -ENOMEM;  keyboard->input_dev = input_dev;  gpio_free(gpio);  printk("-------------gpio=%d---------n",gpio);  if (gpio_request(gpio, "keyboard")) { printk("request %s gpio%d faile n", "keyboard",gpio); return -1; }  //设置为输入  gpio_direction_input(gpio);  keyboard->irq = gpio_to_irq(gpio);  // 设置支持按键事件  set_bit(EV_KEY, input_dev->evbit);  // 设置支持按键类型为KEY_ENTER  set_bit(KEY_ENTER, input_dev->keybit);    // 注册输入设备  ret = input_register_device(input_dev);  if(ret)   return ret;  ret = devm_request_irq(&pdev->dev, keyboard->irq, keyboard_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "keyboard_irq", keyboard);  if (ret) {   input_unregister_device(input_dev);   return ret;  }  // 将输入设备与平台设备关联  platform_set_drvdata(pdev, input_dev); printk(KERN_INFO "my_platform_probe: Platform device probedn"); return 0; } static int my_platform_remove(struct platform_device *pdev) {  input_unregister_device(input_dev);  input_free_device(input_dev); printk(KERN_INFO "my_platform_remove: Platform device removedn"); return 0; } static const struct of_device_id of_platform_match[] = { { .compatible = "keyboard", }, {}, }; static struct platform_driver my_platform_driver = { .driver = { .name = "my_keyboard_driver", .owner = THIS_MODULE, .of_match_table = of_platform_match, }, .probe = keyboard_probe, .remove = my_platform_remove, }; module_platform_driver(my_platform_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Platform Driver Example"); |
编译
复制7.8.3驱动中的Makefile文件,将其中的elf-aht20.o修改为my_keyboard.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/08_input子系统/keyboard$ make |
将编译生成的my_keyboard.ko模块拷贝到开发板。
测试
root@ELF1:~# insmod my_keyboard.ko
-------------gpio=130--------- input: Unspecified device as /devices/virtual/input/input4 my_platform_probe: Platform device probed |
加载驱动后,打印信息中可以看到,已经进入到了probe函数,输出如下命令查看对应的event事件:
root@ELF1:~# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000 N: Name="20cc000.snvs:snvs-powerkey" P: Phys=snvs-pwrkey/input0 S: Sysfs=/devices/platform/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0 U: Uniq= H: Handlers=kbd event0 B: PROP=0 B: EV=3 B: KEY=100000 0 0 0 I: Bus=0019 Vendor=0000 Product=0000 Version=0000 N: Name="iMX6UL TouchScreen Controller" P: Phys= S: Sysfs=/devices/platform/soc/2000000.aips-bus/2040000.tsc/input/input1 U: Uniq= H: Handlers=mouse0 event1 B: PROP=0 B: EV=b B: KEY=400 0 0 0 0 0 0 0 0 0 0 B: ABS=3 I: Bus=0000 Vendor=0000 Product=0000 Version=0000 N: Name="" P: Phys= S: Sysfs=/devices/virtual/input/input2 U: Uniq= H: Handlers=kbd event2 B: PROP=0 B: EV=3 B: KEY=10000000 |
可以看到生成的是event2的节点。接下来使用evtest检查这个节点,按下抬起K2按键,会上报对应的键值和事件:
root@ELF1:~# evtest /dev/input/event2
Input driver version is 1.0.1 Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0 Input device name: "Unknown" Supported events: Event type 0 (EV_SYN) Event type 1 (EV_KEY) Event code 28 (KEY_ENTER) Properties: Testing ... (interrupt to exit) ------------------ Event: time 1691609102.952275, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609102.952275, -------------- SYN_REPORT ------------ ------------------ ------------------ Event: time 1691609103.106850, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609103.106850, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609103.682438, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609103.682438, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609103.831401, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609103.831401, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609104.223788, type 1 (EV_KEY), code 28 (KEY_ENTER), value 1 Event: time 1691609104.223788, -------------- SYN_REPORT ------------ ------------------ Event: time 1691609104.430191, type 1 (EV_KEY), code 28 (KEY_ENTER), value 0 Event: time 1691609104.430191, -------------- SYN_REPORT ------------ root@ELF1:~# rmmod my_keyboard.ko my_platform_remove: Platform device removed |