高级驱动——(驱动所有按键)

Posted wzfdashuaibi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高级驱动——(驱动所有按键)相关的知识,希望对你有一定的参考价值。

我们用一种更为高级的方法去驱动所有的按键————理论上按键最多可以驱动768个,但是我的板子只有三个按键
所以现在只能驱动三个,但是驱动三个和驱动768个都是一样的。
首先我们要利用起设备树文件了,如果不用设备树的话,你就得一个一个的去驱动,虽然你可以在一个模块上完成所有的按键驱动,但是要是真的驱动768个按键,那么你估计能写上万行代码。你要跟以前的方法一样,每一个按键都要去实例化设备,然后获取中断,地址,…这些信息,然后再处理这些信息。
但是如果你用了设备树,我直接在设备树上写好这些信息,到时候你拿的去用就可以了。
并且你直接整一个for循环,它就自动把768个设备信息给你全部拿过来了,岂不爽哉!!
那就让我们来领略一下设备树的魅力:
————————————————————————————————————————————————
驱动所有按键第一步:
编写设备树:
我们之前也编写过,这里我们详细的讲一下:(设备树就是用来存储设备信息的)
首先是在根节点下创建了一个属于我们的设备叫:key_int_node.
我们要驱动三个按键,所以在这个我们的设备下面创建了三个子设备,代表我这设备由这三个设备共同组成。
要是你有一千个设备那么你也可以吧一千个设备写里面组成你的设备。
好让我们看一下每一个子设备下存储了上面信息

```c
在设备树文件中设置这几个元素:
  key_int_node{
            compatible = "test_key";
            #address-cells = <1>;
            #size-cells = <1>;

            key_int@0 {
                    key_name = "key2_power_eint";     //表示我这个子设备名字叫....自定义
                    key_code = <116>;	              //设置这个子设备的键值,就是代表这个键是用来干嘛的,
                    									//你可以随便设你想让它有上面功能都可以
                    gpio = <&gpx1 1 0>;				//代表这个引脚在电路图上是属于gpx1_1引脚,后面可以读这个引脚的值来获
                    								//取按键有没有按下,就很方便
                    reg = <0x11000C20 0x18>;			//物理地址
                    interrupt-parent = <&gpx1>;			//表示中断继承了gpx1
                    interrupts = <1 0>;					//代表是gpx1_1的中断,后面根据节点获取中断号就是
            };

            key_int@1 {
                    key_name = "key3_vup_eint";
                    key_code = <115>;
                    gpio = <&gpx1 2 0>;
                    reg = <0x11000C20 0x18>;
                    interrupt-parent = <&gpx1>;
                    interrupts = <2 0>;
            };

            key_int@2 {
                    key_name = "key4_vdown_eint";
                    key_code = <114>;
                    gpio = <&gpx3 2 0>;
                    reg = <0x11000C60 0x18>;
                    interrupt-parent = <&gpx3>;
                    interrupts = <2 0>;
            };
	};	
	好了第一步完成,你可以根据芯片手册,把所有的按键信息写进去

——————————————————————————————————————
驱动所有按键第二步:
接下来我们就要编写设备模块代码了
1)首先就是日常搭框架
2)然后就是想办法从设备树上把这些我们写的文件信息拿过来。

我们采用了我通用的设备信息结构体,
然后我用一个数组去实例化它,那么我只要一个数组的名字我就可以存储并且找到所有的设备信息了。

struct  key_desc{
	char *name;
	int key_code;
	int irqno;
	int gpio_num;
	void *regbase;
	struct device_node *cnp;	
};

struct key_desc *all_dev[KEY_NOM];

那我们有这样一个结构体数组了,那我还是没有从设备树上获取到信息啊。
别急——我们写一个函数,直接对着设备树就是 —拿来吧你
我们需要一个中介去连接我们的设备树,和用来存储信息的结构体:这个中介就是: struct device_node *cnp; 这是一个设备节点在结构体中已经定义了
首先我们像获取中断号一样,从设备树上获取到节点,但是这个节点,不是我们要的儿子节点。所有我们得获取到子节点点,并且,
要一个设备结构体数组对应一个子节点信息:

void get_allirqno(void){
	//从设备树上获取设备节点
	struct device_node *np = of_find_node_by_path("/key_int_node");
	if(np == NULL){
		printk("find_node error\\n");
		return 0;
	}
	struct  device_node*lcnp;   //用来记录节点的,到时候再赋给每一个的all_dev的cnp
	
	//要获取的节点的上一个节点,这样就可以通过上一个节点的位置获取到我要获取的节点的位置
	 struct device_node *prev = NULL;
	int i=0;
	
	do{
		
			if(lcnp != NULL){
				all_dev[i++].cnp = lcnp;//将当前的节点记录下来
			}

			prev = lcnp; //把当前的设置位prev

		}while(of_get_next_child(np, prev) != NULL);
							
	
}

这样我们就可以通过设备结构体数组中的子节点与设备树建立了连接。

——————————————————————————————————————————————————
接下来是驱动第三步:
完成模块装载入口
我们之前在模块装载路口做了三件事:
1,分配一个input device对象
2, 初始化input device对象
3,注册input device对象
我们现在也是做这三件事;
1)实例化一个输入设备对象,并发分配空间,和部分初始化
struct input_dev *inputdev; //实例化一个输入设备对象
inputdev = input_allocate_device(void);
2)可以添加设备信息
3)调用我们的获取信息的函数————因为我们接下来要用到这些在设备树上的信息

4)初始化input device对象
这里需要初始化俩个:
一个是按键类型
//初始化inputdevice对象—设为按键类型信息
_set_bit(EV_KEY, inputdev->evbit);
还有就是按键的具体信息:
但是不要忘了,我们有很多很多的按键,所以我们要利用我们之前的设备结构体数组中的子节点
然后利用for循环,把设备树上的其它对应信息读到对应的设备结构体数组中去
我们还要
1)设置键值具体信息:所以要拿到对应的键值
of_property_read_u32(cnp,“key_code”, &code
2)申请对应中断:所以要拿到中断号
irqno = irq_of_parse_and_map(cnp, 0)
3)我们还可以打印设备名字用来区分设备
of_property_read_string(cnp, “key_name”, &key_name);

这样我们就完成了,设备的初始化以及中断申请
4)注册设备:
我们申请了设备(由很多子设备组成的大设备)就要注册进去,让内核帮我们匹配
input_register_device(inputdev);
——————————————————————————————
接下来是驱动第三步:
那我们拿到数据了,怎么把按键的值传出去呢?
,我们写在中断处理函数里面,但是我有几百个设备,我只用了一个中断处理函数我怎么区分你按的是什么中断?
首先我们在申请中断的时候就会把,设备信息结构体指针传个中断处理函数。
我们直接可以区分不同的键值。
并且我们还可以直接根据设备树上的gpionum直接去读引脚的数据判断,是否被按下————nice
int gpionum = of_get_named_gpio(pdesc->cnp, “gpio”, 0);
//直接通过gpio获取按键状态
int value = gpio_get_value(gpionum);
——————————————————总的代码————————————————————

#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>

#include <asm/io.h>


#define KEY_NUMS 3

#define KEY_NOM 3
#define IRQFLAGS IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING


struct input_dev *inputdev;  //实例化一个输入设备对象

struct  key_desc{
	char *name;
	int key_code;
	int irqno;
	int gpio_num;
	void *regbase;
	struct device_node *cnp;	
};

struct key_desc all_dev[KEY_NOM];


irqreturn_t  input_key_irq_handler(int irqno, void *devid){
	
	//区分不同的按键
	struct key_desc *pdesc = (struct key_desc *)devid;

	int gpionum = of_get_named_gpio(pdesc->cnp, "gpio", 0);


	//直接通过gpio获取按键状态
	int value = gpio_get_value(gpionum);

	if(value){//抬起
		
		input_report_key(inputdev, pdesc->key_code, 0);
		input_sync(inputdev);//上报数据结束
			
	}else{
		input_report_key(inputdev, pdesc->key_code, 1);
		input_sync(inputdev);//上报数据结束
}


return IRQ_HANDLED;

}




void get_allirqno(void){
	//从设备树上获取设备节点
	struct device_node *np = of_find_node_by_path("/key_int_node");
	if(np == NULL){
		printk("find_node error\\n");
		return 0;
	}
	struct  device_node*lcnp;   //用来记录节点的,到时候再赋给每一个的all_dev的cnp
	
	//要获取的节点的上一个节点,这样就可以通过上一个节点的位置获取到我要获取的节点的位置
	 struct device_node *prev = NULL;
	int i=0;
	
	do{
		
			if(lcnp != NULL){
				all_dev[i++].cnp = lcnp;//将当前的节点记录下来
			}

			prev = lcnp; //把当前的设置位prev

		}while(of_get_next_child(np, prev) != NULL);
							
	
}


static int __init akey_dev_init(void){
	//编写输入子系统代码
	/*
		1,分配一个input device对象
		2, 初始化input  device对象
		3,注册input device对象
	*/
	//1、给实例化的输入设备对象分配空间并部分初始化
	inputdev = input_allocate_device(void)if(inputdev == NULL)
	{
		printk(KERN_ERR "input_allocate_device error\\n");
		return -ENOMEM;
	}
	//添加设备信息/sys/class/input/eventx/device/
	//自定义
	inputdev->name = "input key";
	inputdev->phys = "key/input/input0";
	inputdev->uniq = "simple key0 for 4412";
	inputdev->id.bustype = BUS_HOST;
	inputdev->id.vendor =0x1234 ;
	inputdev->id.product = 0x8888;
	inputdev->id.version = 0x0001;

	get_allirqno();
	
	//初始化inputdevice对象---设为按键类型信息
	_set_bit(EV_KEY, inputdev->evbit);

	//利用设备树多个获取信息
	int i;
	for(i=0;i<KEY_NOM;++i){
		
	struct device_node *cnp = all_dev[i].cnp;   //这个cnp记录了每个设备的节点

	//获取键值
	int code;
	of_property_read_u32(cnp,"key_code", &code);
	_set_bit(code, inputdev->keybit); //将从设备树上获取的键值设置位键值数据code
	 all_dev[i].key_code = code; //把code信息记录到每个子设备的结构体中


	//获取中断号
	int irqno;
	irqno = irq_of_parse_and_map(cnp, 0);
	all_dev->irqno = irqno;

	//获取按键name
	char *key_name ;
	of_property_read_string(cnp, "key_name",  &key_name);
	all_dev[i].name = key_name;


	//申请中断
		ret = request_irq(irqno, input_key_irq_handler, irqflags, 
					key_name, &all_dev[i]);
		if(ret != 0)
		{
			printk("request_irq error\\n");
			goto err_1;
			}
	
	}


	//注册设备
	ret = input_register_device(inputdev);
	if(ret != 0)
	{
		printk(KERN_ERR "input_register_device error\\n");
		goto err_0;
	}

	return 0;

err_1:
	input_unregister_device(inputdev);
err_0:
	input_free_device(inputdev);

}



static int __exit akey_dev_exit(void){

	int i;
	for(i=0; i<KEY_NOM; i++)
		free_irq(all_dev[i].irqno, &all_dev[i]);
		
	input_unregister_device(inputdev);
	input_free_device(inputdev);

}


module_init(akey_dev_init);
module_exit(akey_dev_exit);
MODULE_LICENSE("GPL");

—————————————————————应用程序代码———————————————————————

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(void)
{
	int fd;
	int ret;
	struct input_event event;
	
	fd = open("/dev/input/event1", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}


	while(1)
	{
		ret = read(fd, &event, sizeof(struct input_event));
		if(ret < 0)
		{
			perror("read");
			exit(1);
		}

		if(event.type == EV_KEY){

		
			switch(event.code){
				case KEY_POWER:
					if(event.value){ //按下
						printf("__APP_USER__ :  power pressed\\n");

					}else{
						printf("__APP_USER__ :  power up\\n");
					}
					break;
				case KEY_VOLUMEDOWN:
					if(event.value){ //按下
						printf("__APP_USER__ :  vollum dowm  pressed\\n");

					}else{
						printf("__APP_USER__ :  vollum dowm up\\n");
					}
					break;
				case KEY_VOLUMEUP:
					if(event.value){ //按下
						printf("__APP_USER__ :  vollum up  pressed\\n");

					}else{
						printf("__APP_USER__ :  vollum up up\\n");
					}
					break;
				default:

					printf("error");
			}

		}
		
	}


	close(fd);

	return 0;
}

以上是关于高级驱动——(驱动所有按键)的主要内容,如果未能解决你的问题,请参考以下文章

字符设备驱动按键异步通知

6.驱动支持多按键操作

OK6410按键驱动程序

第四季-专题12-按键驱动程序设计

Linux——Linux驱动之使用输入子系统设计按键驱动实战(输入子系统基本概念代码获取上报信息相关函数解析)

涂鸦智能烧水壶软件实现之TS02N触摸按键驱动