OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 5768|回复: 8

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

[复制链接]

5

主题

21

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2019-11-3
在线时间
25 小时
发表于 2020-3-15 23:15:09 | 显示全部楼层 |阅读模式
好久没有写过单独的驱动了,今天使用正点原子的alpha开发板,写了一个关于使用input子系统完成按键键值上报的驱动。原子的历程没有使用platform设备,我这里使用了platform设备,但大致思路是一样的

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

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);
    }
}
这个应用程序看一眼就好,不要当真,哈哈。
如果有什么问题,可以指出,一起学习。原子哥的例程非常棒,点赞。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

88

主题

7377

帖子

5

精华

资深版主

Rank: 8Rank: 8

积分
14980
金钱
14980
注册时间
2013-11-13
在线时间
1823 小时
发表于 2020-3-17 08:54:54 | 显示全部楼层
回复 支持 反对

使用道具 举报

2

主题

712

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2178
金钱
2178
注册时间
2018-8-27
在线时间
258 小时
发表于 2020-3-17 09:26:01 | 显示全部楼层
可以  可以喔!
森罗万象
回复 支持 反对

使用道具 举报

14

主题

83

帖子

0

精华

初级会员

Rank: 2

积分
167
金钱
167
注册时间
2019-7-11
在线时间
42 小时
发表于 2020-3-17 10:03:38 | 显示全部楼层
厉害了,小哥哥
回复 支持 反对

使用道具 举报

9

主题

767

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
5274
金钱
5274
注册时间
2019-9-25
在线时间
433 小时
发表于 2020-3-17 10:37:03 | 显示全部楼层
顶!来多一些这些帖子分享
想思考的时候,有时还可以用屁股,QQ 1252699831
回复 支持 反对

使用道具 举报

5

主题

21

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2019-11-3
在线时间
25 小时
 楼主| 发表于 2020-3-17 22:39:04 | 显示全部楼层
zuozhongkai 发表于 2020-3-17 08:54
厉害厉害,一起学习

有些代码就是参考你们的例程,例程很好
回复 支持 反对

使用道具 举报

20

主题

122

帖子

0

精华

高级会员

Rank: 4

积分
635
金钱
635
注册时间
2014-6-20
在线时间
168 小时
发表于 2020-5-28 19:43:45 | 显示全部楼层
赞 ~~~~~自己研究了下,和你做的差不多.不过当时想着万一几个按键同时按下的情况,所以给每个按键的中断消抖申请了独立的定时器,
        for (i = 0; i < MYKEY_CNT; i++) {
                /* 创建定时器 */
                init_timer(&my_key.timer[i]);
                my_key.timer[i].function = timer_function;
        }

所以当进入中断后,需要mod_timer不同的定时器:  为此要获取当前的按键序号,即逻辑上的第几个按键.所以在中断处理函数中这样写了:

static irqreturn_t key_handler(int irq, void *my_key_dev)
{
        int i;
        int irq_valid = MY_FALSE;
        int curr_key_id;

        struct gpiokey_dev *dev = (struct gpiokey_dev*)my_key_dev;

        for(i=0; i<MYKEY_CNT; i++)
        {
                if(dev->keyinfo[i].irqnum == irq)
                {
                        curr_key_id = i;
                        irq_valid = MY_TRUE;       
                        break;
                }
        }
        if(irq_valid == MY_FALSE)
        {
                return -EFAULT;
        }
        printk("key_handler[%d] start!\r\n", curr_key_id);
        dev->curr_key_id = curr_key_id;
        dev->timer[curr_key_id].data = (volatile long)my_key_dev;

        mod_timer(&dev->timer[curr_key_id], jiffies + msecs_to_jiffies(10));        /* 10ms定时 */
        return IRQ_RETVAL(IRQ_HANDLED);
}

不过我这个for循环写的感觉好蠢,哈哈哈


想听听你是怎么考虑多按键同时触发这种情况的
回复 支持 反对

使用道具 举报

7

主题

76

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
244
金钱
244
注册时间
2016-4-11
在线时间
54 小时
发表于 2021-5-13 20:21:07 | 显示全部楼层
Fliger 发表于 2020-5-28 19:43
赞 ~~~~~自己研究了下,和你做的差不多.不过当时想着万一几个按键同时按下的情况,所以给每个按键的中断消抖 ...

最近在搞几个按键同事按下的情况,有点意思,研究中
回复 支持 反对

使用道具 举报

86

主题

982

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1846
金钱
1846
注册时间
2013-4-15
在线时间
163 小时
发表于 2023-9-25 09:43:45 | 显示全部楼层
楼主,问个问题,我看你的按键对应的IO初始化为 上升沿/下降沿 触发的,而且在定时器中断服务函数里面上报的是EV_KEY事件,请问,为啥在当按键按下不放的时候,为啥在应用程序里面可以一直检测到按键按下?定时器服务函数里面你也没有上报 “重复”事件
合肥-文盲
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-25 01:37

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表