Linux驱动之INPUT设备驱动

这篇具有很好参考价值的文章主要介绍了Linux驱动之INPUT设备驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、开发环境

二、编写按键input设备的注册与事件上报

        2.1 修改设备树文件

                1 添加 pinctrl 节点

                2、添加 KEY 设备节点

                3、检查 PIN 是否被其他外设使用

        2.2 驱动程序编写

        2.3 测试APP编写

        2.4 运行测试

三、Linux内核自带按键input设备驱动

        3.1 自带按键驱动程序源码简析

        3.2 自带按键驱动程序的使用

        3.3 运行测试


        上一章已经了解了input子系统的大体框架和input设备的注册以及对应事件的上报流程,现在就写一个简单的input设备驱动实验来更加深入的理解input子系统。

        本章将分别采用以下两种方法来进行按键input设备驱动的实验:

  • 1、编写按键input设备的注册与事件上报

  • 2、Linux内核自带按键input设备驱动

一、开发环境

  • CPU:IMX6ULL

  • 内核版本:Linux-5.19

二、编写按键input设备的注册与事件上报

2.1 修改设备树文件

1 添加 pinctrl 节点

        I.MX6U-ALPHA开发板上的 KEY 使用了 UART1_CTS_B这个 PIN,打开 imx6ul-14x14-evk.dtsi ,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:

pinctrl_key: keygrp {
    fsl,pins = <
        MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* key0 */
    >;
};

        第 3 行,将 GPIO_IO18 这个 PIN 复用为 GPIO1_IO18。

2、添加 KEY 设备节点

        在根节点“/”下创建 KEY 节点,节点名为“key”,节点内容如下:

key {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "imx6ull-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
    status = "okay";
};

        第 6 行, pinctrl-0 属性设置 KEY 所使用的 PIN 对应的 pinctrl 节点。

        第 7 行, key-gpio 属性指定了 KEY 所使用的 GPIO。

3、检查 PIN 是否被其他外设使用

        本次实验中按键使用的 PIN 为 UART1_CTS_B,因此先检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

        设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-toto.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如下所示:

/ # ls /proc/device-tree/
#address-cells      clock-osc           pmu
#size-cells         compatible          regulator-can-3v3
aliases             cpus                regulator-peri-3v3
backlight-display   dts_led             regulator-sd1-vmmc
beep                key                 soc
chosen              memory@80000000     sound-wm8960
clock-cli           model               spi4
clock-di0           name                timer
clock-di1           panel

2.2 驱动程序编写

        设备树准备好以后就可以编写驱动程序了,在 input_key.c 里面输入如下内容:

/*
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * @Description: 
 * @Version: 1.0
 * @Autor: Seven
 * @Date: 2023-09-17 13:19:32
 * @LastEditors: Seven
 * @LastEditTime: 2023-09-17 16:57:23
 */
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>

#define KEYINPUT_CNT    1           /* 设备数量 */
#define KEYINPUT_NAME   "inputkey"  /* 设备名字 */
#define KEY_VALUE       0x1         /* 按键值 */
#define KEY_INVALID     0xFF        /* 无效的按键值 */
#define KEY_NUM         1           /* 按键数量 */

/* 定义按键中断操作结构体 */
struct irq_keydesc {
    int gpio;           /* 中断使用的gpio */
    int irqnum;         /* 中断号 */
    unsigned char value;/* 按键对应的键值 */
    char name[10];      /* 中断名 */
    irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/* inputkey设备信息 */
struct inputkey_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *dev_nd;
    struct timer_list timer;
    struct irq_keydesc irq_desc[KEY_NUM];
    unsigned char cur_keynum;
    struct input_dev *inputdev;
};

/* 定义 key 输入设备 */
struct inputkey_dev inputdev;

/*
 * @Brief   中断服务函数,开启定时器,
 *          延时10ms用于按键消抖
 * @Param   irq:中断号
 * @Param   dev_id:设备结构
 * @Note    NOne
 * @RetVal  中断执行结果
 */
static irqreturn_t key_handler(int irq, void *dev_id)
{
    struct inputkey_dev *dev = (struct inputkey_dev *)dev_id;

    dev->cur_keynum = 0;
    // dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * @Brief   定时器服务函数,用于按键消抖,
 *          定时器到再次读取按键值,按键还处于按下则按键有效
 * @Param   t:设备结构体
 * @Note    NOne
 * @RetVal  NOne
 */
void timer_function(struct timer_list *t)
{
    unsigned char value, num;
    struct irq_keydesc *keydesc;
    struct inputkey_dev *dev = from_timer(dev, t, timer);

    num = dev->cur_keynum;
    keydesc = &dev->irq_desc[num];
    
    /* 读取IO值 */
    value = gpio_get_value(keydesc->gpio);
    /* 按键按下 */
    if(value == 0) {
        /* 上报按键值 */
        input_report_key(dev->inputdev, keydesc->value, 1);
        input_sync(dev->inputdev);
    } else { /* 按键松开 */
        input_report_key(dev->inputdev, keydesc->value, 0);
        input_sync(dev->inputdev);
    }
}

/*
 * @Brief   按键IO初始化
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    char name[10];
    int ret = 0;

    inputdev.dev_nd = of_find_node_by_path("/key");
    if (!inputdev.dev_nd) {
        printk(KERN_ERR "key node not found\r");
        return -EINVAL;
    }

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        inputdev.irq_desc[i].gpio = 
                of_get_named_gpio(inputdev.dev_nd, "key-gpio", i);
        if (inputdev.irq_desc[i].gpio < 0) {
            printk(KERN_ERR "can't get key:%d\n", i);
        }
    }

    /* 初始化key所使用的IO, 并设置中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(inputdev.irq_desc[i].name, 0, sizeof(name));
        sprintf(inputdev.irq_desc[i].name, "KEY%d", i);
        gpio_request(inputdev.irq_desc[i].gpio, 
                        inputdev.irq_desc[i].name);
        gpio_direction_input(inputdev.irq_desc[i].gpio);
        inputdev.irq_desc[i].irqnum = 
                        irq_of_parse_and_map(inputdev.dev_nd, i);
    }

    /* 申请中断 */
    inputdev.irq_desc[0].handler = key_handler;
    inputdev.irq_desc[0].value = KEY_0;

    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(inputdev.irq_desc[i].irqnum,
                        inputdev.irq_desc[i].handler,
                        IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                        inputdev.irq_desc[i].name, &inputdev);
        if (ret < 0) {
            printk(KERN_ERR "irq %d request failed\n", 
                            inputdev.irq_desc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 6.初始化timer */
    inputdev.timer.expires = jiffies + msecs_to_jiffies(10);
    timer_setup(&inputdev.timer, timer_function, 0);

    /* 申请input_dev */
    inputdev.inputdev = input_allocate_device();
    inputdev.inputdev->name = KEYINPUT_NAME;

    inputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) 
                                | BIT_MASK(EV_REP);
    input_set_capability(inputdev.inputdev, EV_KEY, KEY_0);

    /* 注册input设备*/
    ret = input_register_device(inputdev.inputdev);
    if (ret) {
        printk(KERN_ERR "register input device failed\n");
        return ret;
    }

    return 0;
}

/*
 * @Brief   驱动入口函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static int __init inputkey_init(void)
{
    keyio_init();

    return 0;
}

/*
 * @Brief   驱动出口函数
 * @Param   None
 * @Note    NOne
 * @RetVal  NOne
 */
static void __exit inputkey_exit(void)
{
    unsigned int i = 0;

    /* 删除定时器 */
    del_timer_sync(&inputdev.timer);

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(inputdev.irq_desc[i].irqnum, &inputdev);
    }

    /* 释放IO */
    for (i = 0; i < KEY_NUM; i++) {
        gpio_free(inputdev.irq_desc[i].gpio);
    }

    /* 释放input设备 */
    input_unregister_device(inputdev.inputdev);
    input_free_device(inputdev.inputdev);
}


module_init(inputkey_init);
module_exit(inputkey_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");

2.3 测试APP编写

        input_key_app.c 测试程序具体代码如下:

/*
 * Copyright © toto Co., Ltd. 1998-2029. All rights reserved.
 * @Description: 
 * @Version: 1.0
 * @Autor: Seven
 * @Date: 2023-09-17 16:10:12
 * @LastEditors: Seven
 * @LastEditTime: 2023-09-17 21:12:01
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/input.h>

/* 定义一个input_event变量,存放输入事件信息 */
static struct input_event inputevt;

/*
 * @Brief   main 主程序
 * @Param   argc:
 * @Param   argv:
 * @Note    NOne
 * @RetVal  0-成功;其他-失败
 */
int main(int argc, char *argv[])
{
    int fd, err;
    char *filename;

    if (argc != 2) {
        printf("argc != 3\n");
        return -1;
    }

    filename = argv[1];

    /*打开驱动文件*/
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("open filename:%d failed\n", filename);
        return -1;
    }

    while (1) {
        err = read(fd, &inputevt, sizeof(inputevt));
        if (err <= 0) {
            printf("read inputevt failed\n");
            continue;
        }

        switch (inputevt.type)
        {
        case EV_KEY:
            printf("key %d %s\n", inputevt.code, 
                        inputevt.value ? "down" : "up");
            break;
        case EV_REL:
            break;
        case EV_ABS:
            break;
        case EV_MSC:
            break;
        case EV_SW:
            break;
        default:
            break;
        }
    }

    /*关闭文件*/
    close(fd);

    return 0;
}

2.4 运行测试

        开发板上电,将input_key.ko 和 input_key_app 这两个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,在加载input_key.ko之前,先来看一下/dev/input 目录下都有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 19 19:30 .
drwxr-xr-x    6 0        0             2880 Jan 19 19:25 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0

        从上面可以看出,当前/dev/input 目录只有 event0 这一个文件。接下来输入如下命令加载 input_key.ko 这个驱动模块。

/ # insmod /lib/modules/5.19.0-g794a2f7be62d-dirty/input_key.ko 
[  310.665956] input_key: loading out-of-tree module taints kernel.
[  310.678432] input: inputkey as /devices/virtual/input/input2

        当驱动模块加载成功以后再来看一下/dev/input 目录下有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 19 19:30 .
drwxr-xr-x    6 0        0             2880 Jan 19 19:25 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-------    1 0        0          13,  65 Jan 19 19:30 event1

        从上面可以看出,多了一个 event1 文件,因此/dev/input/event1 就是注册的驱动所对应的设备文件。 input_key_app 就是通过读取/dev/input/event1 这个文件来获取输入事件信息的,输入如下测试命令:

./input_key_app /dev/input/event1

        然后按下开发板上的 KEY 按键,结果如下所示:

/home/app # ./input_key_app /dev/input/event1
key 11 down
key 11 up
key 11 down
key 11 up
key 11 down
key 11 up
key 11 down
key 11 up

        从上面可以看出,当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。

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

hexdump /dev/input/event1

        然后按下按键,终端输出如下所示信息:

/ # hexdump /dev/input/event1
0000000 079e 0019 8806 0004 0001 000b 0001 0000
0000010 079e 0019 8806 0004 0000 0000 0000 0000
0000020 079e 0019 6be6 0007 0001 000b 0000 0000
0000030 079e 0019 6be6 0007 0000 0000 0000 0000
0000040 079f 0019 2861 0003 0001 000b 0001 0000
0000050 079f 0019 2861 0003 0000 0000 0000 0000
0000060 079f 0019 d5a2 0004 0001 000b 0000 0000
0000070 079f 0019 d5a2 0004 0000 0000 0000 0000
0000080 07a0 0019 69a9 0000 0001 000b 0001 0000
0000090 07a0 0019 69a9 0000 0000 0000 0000 0000
00000a0 07a0 0019 14f8 0002 0001 000b 0000 0000
00000b0 07a0 0019 14f8 0002 0000 0000 0000 0000

        上面就是 input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

/*****************input_event 类型********************/
/* 编号 */    /* tv_sec */    /* tv_usec */    /* type */    /* code */    /* value */

0000000       079e 0019       8806 0004        0001         000b           0001 0000
0000010       079e 0019       8806 0004        0000         0000           0000 0000
0000020       079e 0019       6be6 0007        0001         000b           0000 0000
0000030       079e 0019       6be6 0007        0000         0000           0000 0000

        type 为事件类型,查看示例代码 58.1.2.3 可知, EV_KEY 事件值为 1, EV_SYN 事件值为0。因此第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件。

        code 为事件编码,也就是按键号,查看示例代码 58.1.2.4 可以, KEY_0 这个按键编号为 11,对应的十六进制为 0xb,因此第1 行表示 KEY_0 这个按键事件,最后的 value 就是按键值,为 1 表示按下,为 0 的话表示松开。

综上所述,"hexdump /dev/input/event1" 中的原始事件值含义如下:

  • 第 1 行,按键(KEY_0)按下事件。

  • 第 2 行, EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。

  • 第 3 行,按键(KEY_0)松开事件。

  • 第 4 行, EV_SYN 同步事件,和第 2 行一样。

三、Linux内核自带按键input设备驱动

3.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 内核中【默认是选中的】,如下图所示:

 文章来源地址https://www.toymoban.com/news/detail-733120.html

linux驱动input,linux,driver,linux,按键驱动,imx6ull,linux驱动,input设备驱动,key按键驱动,gpio_keys驱动

        选中以后就会在.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 文件中找到如下所示内容:

static const struct of_device_id gpio_keys_of_match[] = {
    { .compatible = "gpio-keys", },
    { },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);

static int gpio_keys_probe(struct platform_device *pdev)
......
static struct platform_driver gpio_keys_device_driver = {
    .probe      = gpio_keys_probe,
    .shutdown   = gpio_keys_shutdown,
    .driver     = {
        .name   = "gpio-keys",
        .pm = &gpio_keys_pm_ops,
        .of_match_table = gpio_keys_of_match,
        .dev_groups = gpio_keys_groups,
    }
};

static int __init gpio_keys_init(void)
{
    return platform_driver_register(&gpio_keys_device_driver);
}

static void __exit gpio_keys_exit(void)
{
    platform_driver_unregister(&gpio_keys_device_driver);
}

late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");

        从上面的代码可以看出,这就是一个标准的 platform 驱动框架,如果要使用设备树来描述 KEY 设备信息的话,设备节点的 compatible 属性值要设置为“gpio-keys”。当设备和驱动匹配以后 gpio_keys_probe 函数就会执行, gpio_keys_probe 函数内容如下:

static int gpio_keys_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
    struct fwnode_handle *child = NULL;
    struct gpio_keys_drvdata *ddata;
    struct input_dev *input;
    int i, error;
    int wakeup = 0;

    if (!pdata) {
        pdata = gpio_keys_get_devtree_pdata(dev);
        if (IS_ERR(pdata))
            return PTR_ERR(pdata);
    }
    ......
    input = devm_input_allocate_device(dev);
    if (!input) {
        dev_err(dev, "failed to allocate input device\n");
        return -ENOMEM;
    }

    ddata->pdata = pdata;
    ddata->input = input;
    mutex_init(&ddata->disable_lock);

    platform_set_drvdata(pdev, ddata);
    input_set_drvdata(input, ddata);

    input->name = pdata->name ? : pdev->name;
    input->phys = "gpio-keys/input0";
    input->dev.parent = dev;
    input->open = gpio_keys_open;
    input->close = gpio_keys_close;

    input->id.bustype = BUS_HOST;
    input->id.vendor = 0x0001;
    input->id.product = 0x0001;
    input->id.version = 0x0100;

    input->keycode = ddata->keymap;
    input->keycodesize = sizeof(ddata->keymap[0]);
    input->keycodemax = pdata->nbuttons;

    /* Enable auto repeat feature of Linux input subsystem */
    if (pdata->rep)
        __set_bit(EV_REP, input->evbit);

    for (i = 0; i < pdata->nbuttons; i++) {
        const struct gpio_keys_button *button = &pdata->buttons[i];

        if (!dev_get_platdata(dev)) {
            child = device_get_next_child_node(dev, child);
            if (!child) {
                dev_err(dev,
                    "missing child device node for entry %d\n",
                    i);
                return -EINVAL;
            }
        }

        error = gpio_keys_setup_key(pdev, input, ddata,
                        button, i, child);
        if (error) {
            fwnode_handle_put(child);
            return error;
        }

        if (button->wakeup)
            wakeup = 1;
    }

    fwnode_handle_put(child);

    error = input_register_device(input);
    if (error) {
        dev_err(dev, "Unable to register input device, error: %d\n",
            error);
        return error;
    }

    device_init_wakeup(dev, wakeup);

    return 0;
}

大致可以总结如下:

  • 调用 gpio_keys_get_devtree_pdata 函数从设备树中获取到 KEY 相关的设备节点信息。

  • 调用 devm_input_allocate_device 函数申请 input_dev。

  • 初始化 input_dev。

  • 设置 input_dev 事件,这里设置了 EV_REP 事件。

  • 调用 gpio_keys_setup_key 函数继续设置 KEY,此函数会设置 input_dev 的EV_KEY 事件已经事件码(也就是 KEY 模拟为哪个按键)。

  • 调用 input_register_device 函数向 Linux 系统注册 input_dev。

接下来再来看一下 gpio_keys_setup_key 函数,此函数内容如下:

static int gpio_keys_setup_key(struct platform_device *pdev,
                struct input_dev *input,
                struct gpio_keys_drvdata *ddata,
                const struct gpio_keys_button *button,
                int idx,
                struct fwnode_handle *child)
{
    const char *desc = button->desc ? button->desc : "gpio_keys";
    struct device *dev = &pdev->dev;
    struct gpio_button_data *bdata = &ddata->data[idx];
    irq_handler_t isr;
    unsigned long irqflags;
    int irq;
    int error;

    bdata->input = input;
    bdata->button = button;
    spin_lock_init(&bdata->lock);

    if (child) {
        bdata->gpiod = devm_fwnode_gpiod_get(dev, child,
                             NULL, GPIOD_IN, desc);
        if (IS_ERR(bdata->gpiod)) {
            error = PTR_ERR(bdata->gpiod);
            if (error == -ENOENT) {
                /*
                 * GPIO is optional, we may be dealing with
                 * purely interrupt-driven setup.
                 */
                bdata->gpiod = NULL;
            } else {
                if (error != -EPROBE_DEFER)
                    dev_err(dev, "failed to get gpio: %d\n",
                        error);
                return error;
            }
        }
    } else if (gpio_is_valid(button->gpio)) {
        /*
         * Legacy GPIO number, so request the GPIO here and
         * convert it to descriptor.
         */
        unsigned flags = GPIOF_IN;

        if (button->active_low)
            flags |= GPIOF_ACTIVE_LOW;

        error = devm_gpio_request_one(dev, button->gpio, flags, desc);
        if (error < 0) {
            dev_err(dev, "Failed to request GPIO %d, error %d\n",
                button->gpio, error);
            return error;
        }

        bdata->gpiod = gpio_to_desc(button->gpio);
        if (!bdata->gpiod)
            return -EINVAL;
        .......
        INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);

        hrtimer_init(&bdata->debounce_timer,
                 CLOCK_REALTIME, HRTIMER_MODE_REL);
        bdata->debounce_timer.function = gpio_keys_debounce_timer;

        isr = gpio_keys_gpio_isr;
        irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
        ......
        bdata->irq = button->irq;

        if (button->type && button->type != EV_KEY) {
            dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
            return -EINVAL;
        }

        bdata->release_delay = button->debounce_interval;
        hrtimer_init(&bdata->release_timer,
                 CLOCK_REALTIME, HRTIMER_MODE_REL_HARD);
        bdata->release_timer.function = gpio_keys_irq_timer;

        isr = gpio_keys_irq_isr;
        irqflags = 0;

        /*
         * For IRQ buttons, there is no interrupt for release.
         * So we don't need to reconfigure the trigger type for wakeup.
         */
    }

    bdata->code = &ddata->keymap[idx];
    *bdata->code = button->code;
    input_set_capability(input, button->type ?: EV_KEY, *bdata->code);
    ......
    return 0;
}

        调用 input_set_capability 函数设置 EV_KEY 事件以及 KEY 的按键类型,也就是 KEY 作为哪个按键?我们会在设备树里面设置指定的 KEY 作为哪个按键。

        一切都准备就绪以后剩下的就是等待按键按下,然后向 Linux 内核上报事件,事件上报是在 gpio_keys_irq_isr 函数中完成的,此函数内容如下:

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
    struct gpio_button_data *bdata = dev_id;
    struct input_dev *input = bdata->input;
    unsigned long flags;

    BUG_ON(irq != bdata->irq);

    spin_lock_irqsave(&bdata->lock, flags);

    if (!bdata->key_pressed) {
        if (bdata->button->wakeup)
            pm_wakeup_event(bdata->input->dev.parent, 0);

        input_event(input, EV_KEY, *bdata->code, 1);
        input_sync(input);

        if (!bdata->release_delay) {
            input_event(input, EV_KEY, *bdata->code, 0);
            input_sync(input);
            goto out;
        }

        bdata->key_pressed = true;
    }

    if (bdata->release_delay)
        hrtimer_start(&bdata->release_timer,
                  ms_to_ktime(bdata->release_delay),
                  HRTIMER_MODE_REL_HARD);
out:
    spin_unlock_irqrestore(&bdata->lock, flags);
    return IRQ_HANDLED;
}

        gpio_keys_irq_isr 是按键中断处理函数,调用 "input_event" 向 Linux 系统上报 EV_KEY 事件,表示按键按下。调用 "input_sync" 使用 input_sync 函数向系统上报 EV_REP 同步事件。

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

3.2 自带按键驱动程序的使用

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

  • ①、节点名字为“gpio-keys”。

  • ②、 gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”。

  • ③、所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述:
    • gpios: KEY 所连接的 GPIO 信息

    • interrupts: KEY 所使用 GPIO 中断信息,不是必须的,可以不写

    • label: KEY 名字

    • linux,code: KEY 要模拟的按键。

  • ④、如果按键要支持连按的话要加入 autorepeat。

        打开 imx6ul-14x14-evk.dtsi ,根据上面的要求创建对应的设备节点,设备节点内容如下所示:

gpio-keys {
    compatible = "gpio-keys";
    #address-cells = <1>;
    #size-cells = <0>;
    autorepeat;
    
    key0 {
        labal = "GPIO Key Enter";
        linux,code = <KEY_ENTER>;
        gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
    };
};
  • autorepeat 表示按键支持连按。

  • ALPHA 开发板 KEY 按键信息,名字设置为“GPIO Key Enter”,这里我们将开发板上的 KEY 按键设置为“EKY_ENTER”这个按键键值为28,也就是回车键,效果和键盘上的回车键一样。

  • 设置 KEY 所使用的 IO 为 GPIO1_IO18,一定要检查一下设备树看看此 GPIO 有没有被用到其他外设上,如果有的话要删除掉相关代码!

3.3 运行测试

        重新编译设备树,然后用新编译出来的 设备树dtb 启动 Linux 系统,系统启动以后查看/dev/input 目录,看看都有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 20 00:34 .
drwxr-xr-x    6 0        0             2880 Jan 20 00:34 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-rw----    1 0        0          13,  65 Jan 20 00:34 event1

从上面可以看出存在 event1 这个文件,这个文件就是 KEY 对应的设备文件,使用hexdump 命令来查看/dev/input/event1 文件,输入如下命令:

hexdump /dev/input/event1

然后按下 ALPHA 开发板上的按键,终端输出如下所示内容:

/ # hexdump /dev/input/event1
0000000 157f 0019 80d4 000d 0001 001c 0001 0000
0000010 157f 0019 80d4 000d 0000 0000 0000 0000
0000020 1580 0019 001c 0001 0001 001c 0000 0000
0000030 1580 0019 001c 0001 0000 0000 0000 0000
0000040 1580 0019 f8a0 000b 0001 001c 0001 0000
0000050 1580 0019 f8a0 000b 0000 0000 0000 0000
0000060 1580 0019 0aef 000f 0001 001c 0000 0000
0000070 1580 0019 0aef 000f 0000 0000 0000 0000
0000080 1581 0019 7e19 0009 0001 001c 0001 0000
0000090 1581 0019 7e19 0009 0000 0000 0000 0000
00000a0 1581 0019 30e4 000c 0001 001c 0000 0000
00000b0 1581 0019 30e4 000c 0000 0000 0000 0000

        按下 KEY 按键以后会在终端上输出如上所示的信息,表示 Linux 内核的按键驱动工作正常。至于上述中内容的含义就参照前面2.4节中的介绍,进行分析。


         关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。

 

到了这里,关于Linux驱动之INPUT设备驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux设备驱动程序(一)——设备驱动简介

    这一部分主要是用来介绍 Linux 设备驱动程序的一些基本概念,包括:Linux 设备驱动程序的作用、内核功能的划分、设备和模块的分类以及版本编号。 设备驱动程序就像一个个的“黑盒子”,使某个特定硬件响应一个定义良好的内部编程接口,这些操作完全隐藏了设备的工作

    2024年02月05日
    浏览(87)
  • linux设备驱动(5)--设备树

    代码学习资料来源于: 第6.1讲 Linux设备树详解-什么是设备树?_哔哩哔哩_bilibili 仅用于个人学习/复习,侵联删 在linux内核3.x版本之后,linux内核开始使用设备树, 设备树描述开发板上的硬件信息 。 如上图所示,树的主干就是系统总线,IIC控制器,GPIO控制器,SPI控制器等都

    2024年02月11日
    浏览(37)
  • Linux设备驱动之SPI驱动

    Linux下SPI驱动分成两部分:主机驱动和设备驱动。 主机驱动:         主机侧SPI控制器使用 struct spi_master 描述,该结构体中包含了SPI控制器的序号(很多SoC中存在多个SPI控制器),片选数量,SPI信息传输的速率,配置SPI模式的函数指针(4种模式),实现数据传输的函数指针

    2023年04月11日
    浏览(55)
  • 4、Linux驱动开发:设备-设备号&设备号注册

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(56)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(50)
  • Linux设备驱动——第三章字符驱动

    当对幸福的憧憬过于急切,那痛苦就在人的心灵深处升起。——加缪 本章的目的是编写一个完整的字符设备驱动。我们开发一个字符驱动是因为这一类适合大部分简单的硬件设备。字符驱动也比块驱动易于理解。本章的最终目的是编写一个模块化的字符驱动,但是我们不会在

    2024年02月08日
    浏览(82)
  • Linux 驱动学习笔记 ——(1)字符设备驱动

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南》学习笔记 字符设备是 Linux 驱动中最基本的一类设备驱动,字节设备就是按照字节流来读写的设备,常见的字符设备包括:LED、蜂鸣器、按键、I2C 以及 SPI 等。 Linux 中一切皆文件,字符设备驱动加载成功后会在 /dev 目录下生成相

    2024年02月08日
    浏览(55)
  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(53)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(70)
  • Linux设备驱动模型(二)

    基于linux-3.14.16 设备模型(LDM)包括,总线、驱动、设备 以i2c总线为例,下面基本表现出了注册一个总线的过程。 1、定义一个总线bus_type,填充几个回调 其中几个比较重要 , match,总线设备和总线驱动的匹配规则 probe,总线设备和总线驱动匹配后将会执行的回调 2、调用b

    2024年02月05日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包