初级会员
- 积分
- 120
- 金钱
- 120
- 注册时间
- 2019-11-3
- 在线时间
- 25 小时
|
好久没有写过单独的驱动了,今天使用正点原子的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);
}
}
这个应用程序看一眼就好,不要当真,哈哈。
如果有什么问题,可以指出,一起学习。原子哥的例程非常棒,点赞。
|
|