使用input子系统完成键值上报

好久没有写过单独的驱动了,今天使用正点原子的alpha开发板,写了一个关于使用input子系统完成按键键值上报的驱动。

先理一下整个驱动的思路:

1.先在设备树里配置按键的信息。会将添加的信息生成一个platform_device。

2.使用platform_device设备模型作为整个框架。(根据设备树中的compatible,与驱动中的of_device_id中的compatible进行匹配,匹配成功则执行probe函数)。

3.在probe函数中完成内存分配,解析设备树中有关按键的信息(GPIO,IRQ等);完成input_dev的初始化,注册input子系统。此驱动参考正点原子的历程还使用了timer进行消抖,因此还需要初始化timer等。

4.在中断函数中完成对timer的定时,在timer_function中进行按键键值的上报。

5.在remove中释放资源。

大致思路就是这样的,我觉得最开始接触linux驱动时,最大的困难就是不知道怎么构建设备结构体,不知道设备结构体中到底应该加进去哪些成员。分享一下我的想法(可能不太对):就拿此驱动来说,首先应该放心的加,把最应该添加的先加进去,后面觉得哪里不合适再做修改,首先次驱动是一个input驱动,需要input_dev;由于使用到设备树需要到device_node;估计后面会与platform_device关联,需要添加platform_device;可以先加这些这样的话设备结构体就是下面这个样子:

struct key_input_dev_st {
    struct device *dev;    //用于存放platform中的dev
    struct device_node *nd;    //存放设备树节点
    struct platform_device *pdev;    //指向platform设备

    struct input_dev *input_dev;
};

还没有按键,按键就是对应的GPIO和IRQ,本想着直接放进这个设备结构体里也没有问题,因为这个开发板只有一个按键,但是想到有可能会用到多个按键呀,如果每个按键都搞一个这个设备结构体,那这样就需要注册很多input设备,因为一个input_dev对应的就是一个input设备。那这样的话就搞个button的数组(后面感觉数组不太方便就用来指针但是原理是一样的)用来存放对应的GPIO等信息(有参考linux中的gpio_key的驱动和正点原子的驱动)。然后设备结构体就成了下面这个样子:

struct button_st {
    int gpio;
    unsigned int irq;
    int value;
    char name[10];

    irqreturn_t (*handler)(int, void *); 
};

struct key_input_dev_st {
    struct device *dev;
    struct device_node *nd;
    struct platform_device *pdev;

    struct input_dev *input_dev;
    struct timer_list timer;
    int cur_irq;
    int cnt;
    struct button_st *button;
};

其中button_st中的value为键值,在设备树中linux,code中指定;name是设备树中的lable值;handler为中断服务函数;设备树信息如下,key中有几个节点,就对应有几个按键。

key{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "mykey";    //根据此值进行与驱动匹配
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_gpio_keys>;

	key_power{
		lable = "key_power";
		linux,code = <116>;
		key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpio1>;
		interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
		status = "okay";
	};
};

key_input_dev_st中的timer为定时器,用于消抖(来源正点原子);cur_irq:当前触发中断的中断号,最开始没有这个成员,在写驱动的过程中发现要用到,后面加上的;cnt:button的数量,此成员同样是发现要用到到底有几个key时加上的;button:按键数组,有几个按键就会存在几个button_st结构体,button指向这些按键结构体的首地址。(可能所有的设备结构体都是边写驱动边完善的,不知道大佬写驱动是不是也是这么干)

至此第一步已经完成,开始第二步。

platform设备模型的结构如下

static const struct of_device_id mykey_of_match[] = {
    {.compatible = "mykey"},
    {},    //如果希望其他属性值也可以匹配此驱动,可以在后面添加如
//  {.compatible = "hiskey"},    只要在设备树中的对应的值设置为同样就可以
};

static struct platform_driver mykey_device_driver = {
    .probe = mykey_probe,    //匹配成功会调用此函数
    .remove = mykey_remove,    //驱动卸载时会调用此函数
    .driver = {
        .name = "mykey",
        .of_match_table = of_match_ptr(mykey_of_match),    //匹配信息

    },
};


static int __init key_input_init(void)
{
    return platform_driver_register(&mykey_device_driver);
}

static void __exit key_input_exit(void)
{
    platform_driver_unregister(&mykey_device_driver);
}

module_init(key_input_init);
module_exit(key_input_exit);

驱动与设备树中对应的compatible的值一样就会匹配成功,驱动和设备匹配成功就会调用mykey_probe函数。贴代码之前先说一下此函数执行的大致内容;为设备结构体分配内存,填充设备结构体;为button分配内存,获取button的信息;申请gpio和irq;分配input设备,配置input设备(key事件,开关事件还是绝对事件),如果是key事件要上报的键值是多少;注册input设备;将设备结构体放入driver private_data中为了在remove中可以获取到。详细代码如下:

static int mykey_probe(struct platform_device *pdev)
{
    int ret = 0;
    int i = 0;
    int button_cnt;

    struct device *dev = &pdev->dev;
    struct key_input_dev_st *key_input_dev;
    struct device_node *node = dev->of_node;    //找到对应的节点,此时节点应该是key
    struct button_st *btn = NULL;
    struct input_dev *input = NULL;

    printk("%s:dev:%s\n", __func__, pdev->name);

    key_input_dev = kzalloc(sizeof(*key_input_dev), GFP_KERNEL);//为设备结构体分配内存
    if (!key_input_dev) {
        pr_err("key input dev alloc err\n"); 
        return -ENOMEM;
    }

    button_cnt = of_get_child_count(node);    //获取子节点的数量,子节点的数量就是key的数量,获取数量的目的是为了后面分配内存的大小,以及申请gpio和IRQ的时候知道有几个
    if (button_cnt <= 0) {
        pr_err("Not find key in dts, please check\n");
        ret = -ENODEV;
        goto out;
    }
    pr_info("button_cnt : %d\n", button_cnt);

    key_input_dev->pdev = pdev;
    key_input_dev->dev = dev;
    key_input_dev->nd = node;
    key_input_dev->cnt = button_cnt;
    
    btn = kzalloc(
             sizeof(struct button_st) * button_cnt,
             GFP_KERNEL);        //为button分配内存
    if (!btn) {
        ret = -ENOMEM;
        pr_err("button alloc is failed\n");
        goto out;
    }

    key_input_dev->button = btn;    
    
    ret = of_prase_button_info(btn, node);    //从设备树中获取信息
    if (ret < 0) {
        pr_err("prase button info err\n");
        goto out;
    }

    for(i = 0;i < button_cnt; i++) {
        if (gpio_is_valid(btn[i].gpio)) { 
            if(gpio_request(btn[i].gpio,btn[i].name)) {    //申请gpio
                pr_err("request gpio %s:%d err\n", btn[i].name, btn[i].gpio);
                goto gpio_irq_free;
            }

            gpio_direction_input(btn[i].gpio);
        }

        ret = request_irq(btn[i].irq, btn[i].handler, 
                            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                            btn[i].name, key_input_dev);    //申请irq,将所有按键的中断服务函数全部设置为key_input_handler;可能有人会问,多个按键时怎么区分哪个按键触发的中断呢,别忘了还有irq(中断号)呢。传进去设备结构体,触发中断时内核会将设备结构体作为参数传过去。
        if (ret < 0) {
            pr_err("request irq:%s err %d\n", btn[i].name, ret);
            goto gpio_irq_free;
        }
        pr_info("request success irq:%d\n", btn[i].irq);
    }

    
    input = input_allocate_device();    //申请input设备
    if (!input) {
        ret = -ENOMEM;
        pr_err("%s input allocate dev err\n", __func__);
        goto gpio_irq_free;
    }

    input->name = pdev->name;        //设置inputname
    key_input_dev->input_dev = input;

    input->evbit[0] = BIT_MASK(EV_KEY);
    for(i = 0;i < button_cnt; i++) {
        input_set_capability(input, EV_KEY, btn[i].value);    //设置键值
    }

    ret = input_register_device(input);    //注册input设备
    if (ret < 0) {
        pr_err("input register device failed\n");
        goto input_register_err;
    }

    init_timer(&key_input_dev->timer);    //初始化定时器
    key_input_dev->timer.function = key_timer_function;    //设置定时器触发时函数
    
    platform_set_drvdata(pdev, key_input_dev);        //等于pdev->dev->driver_data = key_input_dev;

    return 0;



input_register_err:
    input_free_device(input);
gpio_irq_free:
    while(i) {
        gpio_free(btn[i].gpio);
        free_irq(btn[i].irq, key_input_dev);
        i -= 1;
    }
out:
    kfree(key_input_dev);
    return ret;
}

of_prase_button_info(btn, node)函数如下:

static int of_prase_button_info(struct button_st *button, struct device_node *node)
{
    int i = 0;
    int ret = 0;
    const char *str;
    struct device_node *child = NULL;

    if (!button || !node)
        return -1;

    child = of_get_next_child(node, child);//获取到子节点
    if (!child)
        return -1;
    
    for_each_child_of_node(node, child) {    //遍历子节点
        button[i].gpio = of_get_named_gpio(child, "key-gpio", 0); //获取gpio
        pr_info("%s button[%d] gpio:%d\n", __func__, i, button[i].gpio);

        ret = of_property_read_string(child, "lable", &str);     //获取lable值
        if (ret < 0)
            pr_err("%s get lable err\n", __func__);
        else
            sprintf(button[i].name,"%s", str);    //将lable值设置为button name
        pr_info("%s button name:%s\n", __func__, button[i].name);

        button[i].irq = irq_of_parse_and_map(child, 0);    //获取irq
        if (button[i].irq == NO_IRQ) {
            pr_info("%s get irq err\n", __func__);
            return -1;
        }

        ret = of_property_read_u32(child, "linux,code", &button[i].value);  //获取键值
        if (ret < 0) {
            pr_err("%s button code get err\n", __func__);
            return -1;
        } else
            pr_info("%s button code:%d\n", __func__, button[i].value);

        button[i].handler = key_input_handler;//将所有按键的中断服务函数设置为key_input_handler
        i++;
    }

    return 0;

}

至此probe的工作已经完成,应该初始化的已经初始化,应该注册的都已经注册。现在按下按键应该就会触发一下中断,接下来进行第三步:

编写中断服务函数:

static irqreturn_t key_input_handler(int irq, void *dev_id)
{
    struct key_input_dev_st *key_input_dev = (struct key_input_dev_st *)dev_id;

    key_input_dev->cur_irq = irq;

    key_input_dev->timer.data = (volatile long)dev_id;

    mod_timer(&key_input_dev->timer, jiffies + msecs_to_jiffies(10));
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

中断服务函数中不能做非常耗时的操作,因此出现了中断下半部分,这个也可以这样做,但是感觉原子哥这样用也挺好,就没有使用中断下半部分了,作用是一样的,但是中断下半部分好像没有使用timer这个说法。

dev_id:为申请中断时放进来的设备结构体,这样就可以得到设备结构体了。

把irq存到cur_irq中,继续将设备结构体放入timer的参数中,这样可以在触发timerfunction时找到设备结构体。

设置延时;退出;

中断函数中比较简单,只是保留一下irq,下面看第四部分timer_function

void key_timer_function(unsigned long arg)
{
    struct key_input_dev_st *key_input_dev = (struct key_input_dev_st *)arg;
    struct button_st *btn = key_input_dev->button;
    int irq = 0;
    int i = 0;
    int value;

    irq = key_input_dev->cur_irq;

    for (i = 0; i < key_input_dev->cnt; i++)
        if (irq == key_input_dev->button[i].irq)
            break;

    if (i == key_input_dev->cnt) {
        pr_err("don`t understand the irq\n");
        return;
    }

    value = gpio_get_value(btn[i].gpio);
    if (value) {
        input_report_key(key_input_dev->input_dev, btn[i].value, 0);
        input_sync(key_input_dev->input_dev);
    } else {
        input_report_key(key_input_dev->input_dev, btn[i].value, 1);
        input_sync(key_input_dev->input_dev);
    }


    pr_info("This is timer function! %d\n", value);
}

首先获取触发的irq, 找到irq属于哪个按键;获取对应GPIO的状态,根据按键的状态进行上报按下还是抬起,此驱动设置的是按下上报1:表示按下;抬起上报0:表示抬起;

至此驱动已经完成了大部分,按下按键和抬起按键就会上报对应的值。

第五步:

注销驱动时回收对应的资源

static int mykey_remove(struct platform_device *pdev)
{
    struct key_input_dev_st *key_input_dev = platform_get_drvdata(pdev);
    int i = key_input_dev->cnt;
    
    while (i) {
        i -= 1;
        gpio_free(key_input_dev->button[i].gpio);
        free_irq(key_input_dev->button[i].irq, key_input_dev);
    }


    del_timer_sync(&key_input_dev->timer);

    input_unregister_device(key_input_dev->input_dev);

    input_free_device(key_input_dev->input_dev);

    kfree(key_input_dev->button);

    kfree(key_input_dev);

    printk("%s dev:%s\n", __func__, pdev->name);

    return 0;
}

将申请的内存释放掉,很重要!!!!!!先取出probe函数中放进private_data的设备结构体,现在知道为什么要在probe中set_data了吧,当然你也可以设置一个全局的设备结构体,但是不推荐(画重点),因为有些设备结构体的数据可以会比较多,占用的空间大,一个驱动分配的空间是有限的,设备结构体都占完了,代码往哪放,就会出现很大问题,而且很难调试出来(一位大佬在讲课的时候说的经验,看了linux里的代码也都是这么做的)。但是使用分配内存的方式就不会出现这种情况,内存不够直接报错,容易发现是内存问题。

这样就可以写一个应用程序读 /dev/input/eventx 看到上报的键值了

下面是我写的一个极其简单的应用程序

#include "stdio.h"
#include "stdlib.h"
#include "fcntl.h"

#include <linux/input.h>

int main(void)
{
    int ret;
    int fd;

    struct input_event inputvalue;

    fd = open("/dev/input/event1", O_RDWR);
    if (fd < 0) {
        printf("open event1 failed %d\n", fd);
        exit(1);
    }

    while(1) {
        ret = read(fd, &inputvalue, sizeof(inputvalue));
        if (ret < 0) {
            printf("read failed\n");
            exit(1);
        }

        printf("input type:%d code:%d value:%d\n", inputvalue.type, inputvalue.code, inputvalue.value);
    }
}

这个应用程序看一眼就好,不要当真,哈哈。如果有什么问题,可以指出,一起学习。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux input子系统是用来处理输入设备的一个子系统。它提供了一种标准的接口来处理各种输入设备,包括触摸屏。在Linux中,触摸屏驱动IC通常使用ft5X06。 要在Linux中使用触摸屏,首先需要分配一个input_dev结构体,可以使用input_allocate_device()函数来动态分配这个结构体。当不再需要这个input_dev结构体时,可以使用input_free_device()函数释放它。 在注册和注销输入子系统时,需要调用相应的函数来完成这些操作。具体的实现细节可以参考Linux的文档。 使用触摸屏时,可以使用input_mt_sync()函数来同步输入事件。这个函数的形参是一个input_dev结构体,用于指定要同步的设备。 总结来说,Linux input子系统提供了一种标准的接口来处理输入设备,包括触摸屏。使用触摸屏时,需要分配和释放input_dev结构体,并且可以使用input_mt_sync()函数来同步输入事件。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Linux下输入子系统上报触摸屏坐标](https://blog.csdn.net/weixin_44453694/article/details/126906896)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值