一、修改设备树
用以前的,这里不用修改
二、编写驱动
keyinput.c
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/input.h>
/**
* file name:keyinput
* date: 2021-09-01 08:59
* version:1.0
* author:luatao
* describe:keyinput device drive
*/
#define GPIOKEY_CNT 1 /* 设备号个数 */
#define GPIOKEY_NAME "keyinput" /* 设备名*/
/* 定义按键值 */
#define KEY0VALUE 0x01 /* 按键值 */
#define INVAKEY 0x00 /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断IO描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号*/
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int , void *); /* 中断服务函数 */
};
/* 设备结构体 自定义 */
struct gpiokey_dev{
dev_t devid; /*设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类*/
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
struct input_dev * inputdev; /* input结构体 */
};
/* 定义一个设备结构体 */
struct gpiokey_dev gpiokey; /* key 设备 */
/* 定义定时器和回调函数 */
static void timer_function(struct timer_list *);
static DEFINE_TIMER(Timer, timer_function); // Timer:是一个定时器对象 timer_function:回调函数
/* 中断服务函数 开启定时器 延时 10ms 定时器用于按键消抖*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct gpiokey_dev *dev = (struct gpiokey_dev *)dev_id;
dev->curkeynum = 0; // 当前按键号
mod_timer(&Timer, jiffies + msecs_to_jiffies(10)); /* 10 ms定时 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定时器服务函数 用于按键*/
void timer_function(struct timer_list * arg)
{
unsigned char value; // 按键值
unsigned char num; // 当前按键号
struct irq_keydesc *keydesc; /* 中断描述 */
num = gpiokey.curkeynum; // 当前按键号
keydesc = &gpiokey.irqkeydesc[num]; // 当前按键号的中断描述
value = gpio_get_value(keydesc->gpio); // 读取按键值
if(value == 0){ // 按键按下
/* 上报按键值 */
input_report_key(gpiokey.inputdev, keydesc->value, 1); /* 最后一个参数表示按下还是松开 1 为按下 0为松开*/
}else{ // 按键松开
/* 上报按键值 */
input_report_key(gpiokey.inputdev, keydesc->value, 0); /* 最后一个参数表示按下还是松开 1 为按下 0为松开*/
}
input_sync(gpiokey.inputdev); /* 同步,表示上报完成 */
}
/* 驱动入口函数 */
static int __init mkey_init(void)
{
int ret,i = 0; // 返回值
/* 获取设备数中的属性数据 */
/* 1. 获取设备节点 /key*/
gpiokey.nd = of_find_node_by_path("/key"); // 通过绝对路径查找设备节点
if(gpiokey.nd == NULL){
printk("key node no find!\r\n");
return -EINVAL; /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
}
/* 2. 获取设备树中的gpio属性 得到key所使用的gpio编号 */
/* 可能有多个按键 这里统一处理 兼容性更好 */
for(i = 0; i< KEY_NUM; i++){
gpiokey.irqkeydesc[i].gpio = of_get_named_gpio(gpiokey.nd, "key-gpio", i);
if(gpiokey.irqkeydesc[i].gpio < 0 ){
printk("can't get key-gpio%d\r\n",i);
return -EINVAL; /* 无效参数 不知道这个返回值是啥意思,我觉得返回一个负数就可以,这个值是23,不知道有没有处理*/
}
printk("key-gpio%d num = %d \r\n", i,gpiokey.irqkeydesc[i].gpio); // 打印获取的key-gpio属性值
}
/* 初始化key所使用的IO 并且设置为中断模式 */
for(i = 0; i< KEY_NUM; i++) {
memset(gpiokey.irqkeydesc[i].name, 0, sizeof(gpiokey.irqkeydesc[i].name)); // 缓冲区清零
sprintf(gpiokey.irqkeydesc[i].name, "key%d", i); // 组合名字
gpio_request(gpiokey.irqkeydesc[i].gpio, gpiokey.irqkeydesc[i].name); // 申请IO
gpio_direction_input(gpiokey.irqkeydesc[i].gpio); // 设置为输入模式
gpiokey.irqkeydesc[i].irqnum = irq_of_parse_and_map(gpiokey.nd, i); // 获取中断号
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
/* 打印按键信息 */
printk("key%d:gpio=%d, irqnum=%d\r\n", i, gpiokey.irqkeydesc[i].gpio, gpiokey.irqkeydesc[i].irqnum);
}
/* 申请中断 */
gpiokey.irqkeydesc[0].handler = key0_handler; // 中断处理函数
gpiokey.irqkeydesc[0].value = KEY_0; // 按键值
for(i = 0 ; i < KEY_NUM; i++){
ret = request_irq(gpiokey.irqkeydesc[i].irqnum, gpiokey.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, gpiokey.irqkeydesc[i].name, &gpiokey);
if(ret < 0){
printk("irq %d request failed!\r\n", gpiokey.irqkeydesc[i].irqnum); // 中断号申请失败
return -EFAULT;
}
}
/* 申请input_dev */
gpiokey.inputdev = input_allocate_device(); //
gpiokey.inputdev->name = GPIOKEY_NAME;
/* 下面是三种初始化input_dev的方法 设置产生那些事件 */
#if 0
/* 初始化input_dev,设置产生哪些事件 */
__set_bit(EV_KEY, gpiokey.inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件,比如按下去不放开,就会一直输出信息 */
/* 初始化input_dev,设置产生哪些按键 */
__set_bit(KEY_0, gpiokey.inputdev->keybit);
#endif
#if 0
gpiokey.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
gpiokey.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif
/* 设置事件 和 事件值*/
gpiokey.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); /* 按键事件 和 重复事件 */
input_set_capability(gpiokey.inputdev, EV_KEY, KEY_0); /* EV_KEY: 按键事件 KEY_0:按键值 为11*/
/* 注册输入设备 */
ret = input_register_device(gpiokey.inputdev);
if(ret){
printk("register input device failed!\'r\n");
return ret;
}
return 0;
}
/* 驱动出口函数 */
static void __exit mkey_exit(void)
{
unsigned char i = 0;
/* 释放IO */
for(i = 0; i < KEY_NUM; i++){
gpio_free(gpiokey.irqkeydesc[i].gpio);
}
/* 删除定时器 */
del_timer_sync(&Timer);
/* 释放中断 */
for(i = 0; i < KEY_NUM; i++){
free_irq(gpiokey.irqkeydesc[i].irqnum, &gpiokey);
}
/* 释放input_dev*/
input_unregister_device(gpiokey.inputdev);
input_free_device(gpiokey.inputdev);
printk("key drive unregsister ok !\r\n");
}
/* 加载驱动入口和出口函数 */
module_init(mkey_init);
module_exit(mkey_exit);
/* LICENSE 和 AUTHOR 信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luatao");
三、编写应用程序
keyinputApp.c
#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>
/**
* file name:keyinputApp
* date: 2021-09-01 09:07
* version:1.0
* author:luatao
* describe:input系统测试APP
* 执行命令:./keyinputApp 读取按键值
*/
/* 定义一个input_event变量 存放输入事件信息 */
static struct input_event inputevent;
/* 主程序 */
int main(int argc, char *argv[])
{
char *filename; // 可执行文件名
int fd,ret =0 ; // fd: 文件句柄 ret:函数操作返回值
/* 先判断输入的参数 */
if(argc != 2){ // 本身文件名带1个 执行文件1个
printf("parameter error!\r\n");
return -1;
}
/* 分析参数 ,提取有用的信息 */
filename = argv[1]; // 可执行文件名
/* 打开key文件 */
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 < BTN_MISC){ /* 是键盘键值 */
printf("key %d %s \r\n",inputevent.code, inputevent.value ? "press" : "release"); // 输入按键代码 并且判断按键是按下还是松开
}else{
printf("button %d %s \r\n",inputevent.code, inputevent.value ? "press" : "release"); // 输入按键代码 并且判断按键是按下还是松开
}
break;
/* 其他类型事件 根据需要自行处理 */
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
}
}else{ // 数据无效
printf("read data error\r\n");
}
}
/* 关闭文件 */
ret = close(fd);
if(ret < 0){
printf("can't close file %s \r\n", filename);
return -1;
}
return 0;
}
四、运行测试
驱动模块加载成功以后再来看一下/dev/input 目录下有哪些文件,结果如图
这里面就是我们的输入设备,经过我的简单测试,
这里的event1就是我们的屏幕的触摸,因为我的板子上没有这个,所以运行之后一直报错
event2就是我们编写的按键的输入设备,
打印效果是这样的
每按下一次按键,都会打印一次按下,一次释放,一直按下,就会一直打印按下。
event0经过测试是我们lradc的按键,其中有4个按键。运行之后是这样的
按下不同的按键,对应的按键值不一样。
这个lradc的驱动是sun4i-lradc-keys.c 如下图
五、关于按键值(补充知识)
全局搜索kernel,搜索KEY_0,可以得到
点开
所有的按键值描述都有
可以看到lradc描述的按键值
我们以KEY_VOLUMEUP为例
在input-event-codes.h中搜索
可以看到和我们按键按下的值是一样的。
其他的也是一样的。