前言
写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。
一、实现流程
事件处理层不用我们管了,- -是暂时能力有限管不了。写写设备层的程序就好了。
软件设计流程:
- 分配一个Input_dev结构体
- 初始化 Input_dev 结构体:设置 支持哪一类事件,该类事件里的那些事件
- 注册输入设备
- 申请按键中断
- 实现中断处理函数(事件上报)
二、驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <cfg_type.h>
#define KEYDEV_CNT 1 /* 设备号个数 */
#define KEYDEV_NAME "keyinput" /* 设备名字 */
#define KEY_NUM 4 /* 按键的个数 */
struct keyinput_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
unsigned int key_value[KEY_NUM];/* 键值 */
unsigned int releasekey; /* 标记完成一次完整的按键 */
spinlock_t lock; /* 自旋锁 */
unsigned int curkeynum; /* 当前的按键号 */
struct timer_list timer; /* 定义一个定时器 */
struct input_dev *inputdev; /* 定义input结构体 */
};
struct keyinput_dev keyinputdev;
struct key_irq_info{
unsigned int gpio; /* gpio标号 */
char *name; /* 名字 */
int irqnum; /* 中断号 */
unsigned int key_code; /* 按键值 */
irqreturn_t (*irq_handler_t)(int, void*); /* 中断服务函数 */
};
static irqreturn_t key_irqhandler(int irq, void *dev); //先声明中断处理函数
/* GPIOB31,GPIOB30,GPIOA28,GPIOB9 */
const struct key_irq_info key_irq_info_tbl[KEY_NUM] = {
{
.gpio = PAD_GPIO_B+31,
.name = "gpiob31",
.irqnum = IRQ_GPIO_B_START+31,
.key_code = KEY_A,
.irq_handler_t = key_irqhandler,
},
{
.gpio = PAD_GPIO_B+30,
.name = "gpiob30",
.irqnum = IRQ_GPIO_B_START+30,
.key_code = KEY_B,
.irq_handler_t = key_irqhandler,
},
{
.gpio = PAD_GPIO_A+28,
.name = "gpioa28",
.irqnum = IRQ_GPIO_A_START+28,
.key_code = KEY_C,
.irq_handler_t = key_irqhandler,
},
{
.gpio = PAD_GPIO_B+9,
.name = "gpiob9",
.irqnum = IRQ_GPIO_B_START+9,
.key_code = KEY_D,
.irq_handler_t = key_irqhandler,
},
};
/* 按键中断服务函数 */
static irqreturn_t key_irqhandler(int irq, void *dev)
{
struct keyinput_dev *dev_t = (struct keyinput_dev *)dev;
/* 判断是哪个按键动作了 */
switch(irq){
case IRQ_GPIO_B_START+31:
dev_t->curkeynum = 0;
break;
case IRQ_GPIO_B_START+30:
dev_t->curkeynum = 1;
break;
case IRQ_GPIO_A_START+28:
dev_t->curkeynum = 2;
break;
case IRQ_GPIO_B_START+9:
dev_t->curkeynum = 3;
break;
default:
break;
}
dev_t->timer.data = (volatile long)dev; /* 传递给 function 的参数 */
mod_timer(&dev_t->timer, jiffies + msecs_to_jiffies(10)); /* 启动一个10ms的定时器 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数 */
void timer_function(unsigned long arg)
{
unsigned int num;
unsigned int value;
struct keyinput_dev *dev_t = (struct keyinput_dev *)arg;
num = dev_t->curkeynum;
value = gpio_get_value(key_irq_info_tbl[num].gpio);
/* 上报按键值 */
if(value == 0){
/* 按下 */
input_report_key(dev_t->inputdev, key_irq_info_tbl[num].key_code, 1);
input_sync(dev_t->inputdev);
}else{
/* 弹起 */
input_report_key(dev_t->inputdev, key_irq_info_tbl[num].key_code, 0);
input_sync(dev_t->inputdev);
}
}
/* 入口函数 */
static int __init keyinput_init(void)
{
int ret;
int i;
/* 初始化自旋锁 */
spin_lock_init(&keyinputdev.lock);
/* 初始化定时器 */
init_timer(&keyinputdev.timer);
keyinputdev.timer.function = timer_function;
/* 申请GPIO */
for(i=0; i<KEY_NUM; i++) //先释放
gpio_free(key_irq_info_tbl[i].gpio);
for (i=0;i<KEY_NUM;i++){
ret = gpio_request(key_irq_info_tbl[i].gpio, key_irq_info_tbl[i].name);
if(ret < 0){
printk(KERN_EMERG "kernel gpio_request failed!\r\n");
return -EIO;
}
}
/* 为4个GPIO申请中断 */
for (i=0;i<KEY_NUM;i++){
ret = request_irq(key_irq_info_tbl[i].irqnum,
key_irq_info_tbl[i].irq_handler_t,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, //双边沿触发
key_irq_info_tbl[i].name,
&keyinputdev);
if(ret < 0){
printk(KERN_EMERG "request %s irq failed!\r\n",key_irq_info_tbl[i].name);
return -EFAULT;
}
}
/* 申请input_dev */
keyinputdev.inputdev = input_allocate_device();
keyinputdev.inputdev->name = KEYDEV_NAME;
/* 初始化 input_dev 结构体 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); /* 按键及重复事件类型 */
keyinputdev.inputdev->keybit[BIT_WORD(KEY_A)] |= BIT_MASK(KEY_A); /* 事件码 */
keyinputdev.inputdev->keybit[BIT_WORD(KEY_B)] |= BIT_MASK(KEY_B);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_C)] |= BIT_MASK(KEY_C);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_D)] |= BIT_MASK(KEY_D);
/* 注册输入设备 */
ret = input_register_device(keyinputdev.inputdev);
if (ret) {
printk(KERN_EMERG "register input device failed!\r\n");
return ret;
}
return 0;
}
/* 出口函数 */
static void __exit keyinput_exit(void)
{
int i;
/* 释放GPIO */
for(i=0; i<4; i++)
gpio_free(key_irq_info_tbl[i].gpio);
/* 删除定时器 */
del_timer_sync(&keyinputdev.timer);
/* 释放中断 */
for(i=0; i<4; i++)
free_irq(key_irq_info_tbl[i].irqnum, &keyinputdev);
/* 注销输入设备 */
input_unregister_device(keyinputdev.inputdev);
/* 释放 input_dev */
input_free_device(keyinputdev.inputdev);
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzj");
三、应用程序
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <linux/input.h>
/* 定义一个 input_event 变量,存放输入事件信息 */
static struct input_event inputevent;
int main(int argc , char *argv[])
{
int fd, ret, i;
char *filename;
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
/* 循环读按键 */
while(1)
{
ret = read(fd, &inputevent, sizeof(inputevent));
if(ret > 0){
switch(inputevent.type){
case EV_KEY:
if(inputevent.code == KEY_A){
printf("KEY_A %s\r\n",inputevent.value? "down":"up");
}
if(inputevent.code == KEY_B){
printf("KEY_B %s\r\n",inputevent.value? "down":"up");
}
if(inputevent.code == KEY_C){
printf("KEY_C %s\r\n",inputevent.value? "down":"up");
}
if(inputevent.code == KEY_D){
printf("KEY_D %s\r\n",inputevent.value? "down":"up");
}
break;
default:
break;
}
}
}
/* 关闭设备 */
ret = close(fd);
if(ret < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
四、测试结果
设备注册成功
查看事件处理层的event类设备,命令如下:
通过name字段找到刚才注册成功的输入设备,可以看到其匹配到的handler为event5 。
那么应用层对应打开/dev/input/event5设备节点就可以进行测试了。