利用驱动的中断机制,可以捕获按键按下时产生的中断事件,然后将信息上报给输入子系统,由子系统将数据交给用户。将不同按键设备的信息,描述到设备树中,驱动程序遍历每一个节点即设备对象;利用中断子系统实现对多个按键产生的数据,进行上报。

重点是驱动代码的设计处理,通过设备树节点将设备信息读取到结构体数组成员中,兼容多个硬件设备,体现到代码重用的思想。如果要添加一个按键设备,只要在原有的基础上添加一个设备树节点和改变结构体数组长度即可。

 

关于设备树的定义规则和获取方法

请参考笔者另一篇文章:

https://blog.csdn.net/feit2417/article/details/84585989

 

关于 /sys/class/input 目录

sysfs文件系统,系统所有输入设备都会出现在这个目录。用户查看驱动可以通过 /sys/目录来查看

 

关于 /dev/input

/dev 是对用户层提供的设备节点,给用户的使用的

 

驱动代码

将不同的按键描述到设备树中,通过get_all_child_from_node遍历到所有的按键设备,并且存储到一个自定义的结构体对象中,完成分配注册输入设备对象。申请中断资源(request_irq)时,指定将产生中断的设备信息传参给中断处理函数,当中断处理拿到设备的信息是,分析按键的类型和键值,将数据上报给输入子系统。

设备信息: 设备树 --> 设备结构体对象 --> (当按键动作产生中断)传递给中断处理方法 --> 上报设备信息

//simple_input_drv.c
#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 <linux/of_address.h>
#include <linux/platform_device.h>
#include <asm/io.h>


#define KEY_NUMS 3


irqreturn_t input_key_irq_handler(int irqno, void *devid);	//中断入口
void get_all_child_from_node(void);

//设计一个对象出来
struct key_desc{
	char *name;
	int irqno;
	int key_code;
	int gpionum;
	void *reg_base;
	struct device_node *cnp;// 可以随时去获取节点各个信息
};

struct key_desc all_key[KEY_NUMS];

struct input_dev *inputdev;


static int __init input_drv_init(void)
{
	printk("-------%s-------------\n", __FUNCTION__);

	int ret;
	//分配 input device 对象
	inputdev = input_allocate_device();
	if(NULL == inputdev)
	{
		printk(KERN_ERR "input_allocate_device error\n");
		return -ENOMEM;
	}

	//遍历设备节点 并存放到结构体all_key[i]中
	get_all_child_from_node();

	//添加设备信息--/sys/class/input/eventx/device/
	inputdev->name = "simple 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;

	//初始化 input device 对象
	__set_bit(EV_KEY,  inputdev->evbit);

	int i;
	for(i=0; i<KEY_NUMS; i++)
	{
		//设置keybit,支持哪些按键
		// 按键值从设备设备树
		int code;
		struct device_node *cnp = all_key[i].cnp;
		
		of_property_read_u32(cnp,"key_code", &code);
		__set_bit(code, inputdev->keybit);
		all_key[i].key_code = code; //先记录下来


		int irqno;
		irqno = irq_of_parse_and_map(cnp, 0);
		all_key[i].irqno = irqno;//先记录下来

		char *key_name ;
		of_property_read_string(cnp, "key_name",  &key_name);
		all_key[i].name = key_name;

		printk("name = %s, code = %d,irqno = %d\n",
						key_name, code,irqno);	

		int irqflags = IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING;
		
		ret = request_irq(irqno, input_key_irq_handler, irqflags, 
					key_name, &all_key[i]);
		if(ret != 0)
		{
			printk("request_irq error\n");
			goto err_0;
		}
		
	}


	//注册 input device 对象
	ret = input_register_device(inputdev);
	if(0 != ret)
	{
		printk(KERN_ERR "input_allocate_device error\n");
		goto err_0;
	}


	return 0;


	err_1:
		input_unregister_device(inputdev);

	err_0:
		input_free_device(inputdev);
		return ret;
}

static void __exit input_drv_exit(void)
{
	printk("-------%s-------------\n", __FUNCTION__);
	int i;
	for(i=0; i<KEY_NUMS; i++)
		free_irq(all_key[i].irqno, &all_key[i]); //释放中断资源
		
	input_unregister_device(inputdev);	//将input device 对象注销
	input_free_device(inputdev);		//释放input device 对象

}

irqreturn_t  input_key_irq_handler(int irqno, void *devid)
{
	printk("-------%s-------------\n", __FUNCTION__);

	//区分不同的按键
	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_all_child_from_node(void)
{
	// 获取到设备树中到节点
	struct device_node *np = of_find_node_by_path("/key_int_node");
	if(np){
		printk("find node ok\n");
	}else{
		printk("find node failed\n");
	}

	struct device_node *cnp;
	struct device_node *prev = NULL;

	int i = 0;

	do{
			//获取到子节点
			cnp = of_get_next_child(np, prev);
			if(cnp != NULL){
				all_key[i++].cnp = cnp;//将当前的节点记录下来
			}

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

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

}


module_init(input_drv_init);
module_exit(input_drv_exit);

MODULE_LICENSE("GPL");

 

 

应用程序

用户读取设备节点,读取到的是指定的input_event类型的数据包。然后分析键值,并打印键值。

//simple_input_test.c
#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__ :  KEY_VOLUMEDOWN pressed\n");

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

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

		}
		
	}


	close(fd);

	return 0;
}

 

结果展示