i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件

Posted Mculover666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件相关的知识,希望对你有一定的参考价值。

本系列文章所编写的驱动源码仓库,欢迎Star:https://github.com/Mculover666/linux_driver_study

一、编写驱动

1. 编写模块

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/timer.h>

struct key_dev 
    struct device_node *node;   /*!< 设备树节点         */
    int gpio;                   /*!< key使用的gpio编号  */
    int debounce_interval;      /*!< 去抖时长           */
    struct timer_list  timer;   /*!< 用于软件消抖        */
    struct input_dev   *input;  /*!< 输入设备           */

    int irq;                    /*!< 中断号             */
;

static struct key_dev key;

static irqreturn_t key0_handler(int irq, void *dev_id)

    struct key_dev *dev = (struct key_dev *)dev_id;
    
    // 启动软件定时器, 消抖时间后再去读取
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->debounce_interval));
    dev->timer.data = (volatile long)dev_id;

    return IRQ_RETVAL(IRQ_HANDLED);


static void timer_handler(unsigned long arg)

    int val;
    struct key_dev *dev = (struct key_dev *)arg;

    val = gpio_get_value(dev->gpio);
    if (val == 0) 
    
     else 
    
    


static int key_init(void)

    int ret;
    uint32_t debounce_interval = 0;

    // 获取设备树节点
    key.node = of_find_node_by_name(NULL, "key0");
    if (!key.node) 
        printk("key0 node find fail!\\n");
        return -1;
    

    // 提取gpio
    key.gpio = of_get_named_gpio(key.node, "key-gpio", 0);
    if (key.gpio < 0) 
        printk("find key-gpio propname fail!\\n");
        return -1;
    

    // 初始化gpio
    ret = gpio_request(key.gpio, "key-gpio");
    if (ret < 0) 
        printk("gpio request fail!\\n");
        return -1;
    
    gpio_direction_input(key.gpio);

    // 解析设备树,获取去抖时长
    if (of_property_read_u32(key.node, "debounce-interval", &debounce_interval) < 0) 
        printk("find debounce-interval fail!\\n");
        goto error;
    

    // 设置去抖时长
    if (debounce_interval) 
        ret = gpio_set_debounce(key.gpio, key.debounce_interval * 1000);
        if (ret < 0) 
            printk("gpio_set_debounce not support, use soft timer!\\n");
            
            // 设置软件定时器用于消抖
            init_timer(&key.timer);
            key.timer.function = timer_handler;
            key.debounce_interval = debounce_interval;
        
    

    // 获取中断号
    key.irq = gpio_to_irq(key.gpio);
    if (key.irq < 0) 
        printk("gpio_to_irq fail!\\n");
        goto error;
    

    // 申请中断
    ret = request_irq(key.irq, key0_handler, IRQF_TRIGGER_FALLING, "key_irq", &key);
    if (ret < 0) 
        printk("irq request fail, ret is %d!\\n", ret);
        goto error;
    

    return 0;

error:
    gpio_free(key.gpio);
    return -1;


static void key_deinit(void)

    // 释放中断
    free_irq(key.irq, &key);

    // 释放gpio
    gpio_free(key.gpio);

    // 删除定时器
    del_timer(&key.timer);


static int __init key_module_init(void)

    return key_init();


static void __exit key_module_exit(void)

    key_deinit();


module_init(key_module_init);
module_exit(key_module_exit);

MODULE_AUTHOR("Mculover666");
MODULE_LICENSE("GPL");

2. 输入设备

引入头文件:

#include <linux/input.h>

(1)添加全局变量:

struct key_dev 
    struct device_node *node;   /*!< 设备树节点         */
    int gpio;                   /*!< key使用的gpio编号  */
    int debounce_interval;      /*!< 去抖时长           */
    struct timer_list  timer;   /*!< 用于软件消抖        */
    struct input_dev   *input;  /*!< 输入设备           */

    int irq;                    /*!< 中断号             */
;

(2)分配/注册输入设备
在按键初始化函数中,编写以下代码。

// 分配输入设备
key.input = input_allocate_device();
if (!key.input) 
    printk("irq input_allocate_device fail!\\n");
    goto error;

为了使用 setbit 函数,引入头文件:

#include <asm/bitops.h>

编写初始化输入设备的代码:

// 初始化输入设备
key.input->name = "keyinput";
set_bit(EV_KEY, key.input->evbit);  // 按键事件
set_bit(EV_REP, key.input->evbit);  // 重复事件
set_bit(KEY_0, key.input->keybit);  // 设置产生哪些按键 

注册输入设备:

// 注册输入设备
ret = input_register_device(key.input);
if (ret < 0) 
    printk("input_register_device fail!\\n");
    goto error;
   

(3)注销/释放输入设备
在按键去初始化函数中,添加输入设备的注销与释放。

static void key_deinit(void)

    // 注销输入设备
    input_unregister_device(key.input);

    // 释放输入设备
    input_free_device(key.input);

    // 释放中断
    free_irq(key.irq, &key);

    // 释放gpio
    gpio_free(key.gpio);

    // 删除定时器
    del_timer(&key.timer);

3. 上报事件

在消抖定时器超时后,上报事件值:

static void timer_handler(unsigned long arg)

    int val;
    struct key_dev *dev = (struct key_dev *)arg;
    static int i = 0;

    // 上报输入事件
    val = gpio_get_value(dev->gpio);
    printk("--->report, i = %d\\n", i++);
    if (val == 0) 
        input_report_key(dev->input, KEY_0, 1);
        input_sync(dev->input);
        input_report_key(dev->input, KEY_0, 0);
        input_sync(dev->input);
    

在上报按键按下之后,要再上报按键松开,不然Linux内核就会一直以为按键处于按下状态!

4. 编译模块

KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull
obj-m = input_key.o

build: kernel_module

kernel_module:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

二、编写应用程序

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <linux/input.h>
#include <string.h>

static struct input_event event;

int main(int argc, char *argv[])

    int fd;
    int ret;

    if (argc != 2) 
        printf("usage: ./test_input_key [device]\\n");
        return -1;
    

    fd = open(argv[1], O_RDWR);
    if (fd < 0) 
        printf("open file %s fail!", argv[1]);
        return -1;
    

    while (1) 
        memset(&event, 0, sizeof(event));
        ret = read(fd, &event, sizeof(event));
        switch (event.type) 
            case EV_SYN:
                break;
            case EV_KEY:
                if (event.code < BTN_MISC)     // 键盘键值
                    printf("key %d %s\\n", event.code, event.value ? "press" : "release");
                 else 
                    printf("btn %d %s\\n", event.code, event.value ? "press" : "release");
                
                break;
            case EV_REL:
                break;
            case EV_ABS:
                break;
            case EV_MSC:
                break;
            case EV_SW:
                break;
        
    

三、测试

1. 加载模块,查看输入设备节点

加载模块之前,查看设备节点,加载模块之后再次查看设备节点:

可以看到新增的设备为 /dev/event4

2. 运行应用程序

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件的主要内容,如果未能解决你的问题,请参考以下文章

i.MX6ULL驱动开发 | 25 - 基于Linux自带的KEY驱动检测按键

i.MX6ULL驱动开发 | 18 - 使用中断方式检测按键

i.MX6ULL驱动开发 | 18 - 使用中断方式检测按键

i.MX6ULL驱动开发 | 20 - Linux input 子系统

i.MX6ULL驱动开发 | 20 - Linux input 子系统

i.MX6ULL驱动开发 | 09 -基于Linux自带的LED驱动点亮LED