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. 运行应用程序

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/124338588