linux 驱动开发之平台设备驱动设备树 input子系统的使用:按键中断驱动
这一章我们linux input 子系统
在linux中,input子系统有三部分组成:驱动层,输入子系统核心,事件处理层.一个输入时间,比如按键按下通过:驱动->input core->event handler->user space的顺序到达用户空间的应用程序
驱动层:将底层的硬件输入转化为统一的事件形式,向输入核心(input core)汇报
核心层:承上启下,为驱动层提供输入设备接口与操作借口;通知事件处理层进行时间处理;在/proc下产生相应的设备信息.
事件处理层:树要是和用户空间打交道,(Linux中一切皆是文件,由于input驱动设备有fops接口,在/dev下会产生相应的设备文件nod,访问这些文件通过应用程序来完成).
实现input驱动的核心工作就是:向系统报告按键等输入事件(event,通过input_event结构描述),不再关心文件操作接口(不用手动的完成file_operations结构体的实例化).报告事件通过核心层和事件处理层直接到达用户.
一些input驱动相关的函数:
设备注册
注册 int input_regesiter_device(struct input_dev *dev)
注销 void input_unregister_device(struct input_dev *dev)
设置事件
set_bit()告诉input输入系统支持那些设备,哪些按键等
比如
set_bit(EV_KEY,button_dev.evbit)(其中button_dev是struct input_dev类型)
报告事件:
用于报告EV_KEY,EV_REL,EV_ABS事件的函数分别为:
报告按键事件void input_report_key(struct input_dev *dev,unsigned int code,int value)
报告重复事件void input_report_rel (struct input_dev *dev, unsigned int code, int value)
报告绝对坐标事件void input_report_abs (struct input_dev *dev, unsigned int code, int value)
报告结束后,同步事件
input_sync()同步用于告诉input core 子系统报告结束
实例 ,我们做一个按键input驱动,该驱动首先在设备树文件里查找按键的节点,获取按键的属性值,通过中断里开启定时器定时10ms进行按键机械消抖,在定时器里将按键事件汇报给input核,然后编写应用程序实现按键事件的读取
在设备树根/下建立一个key节点,在iomux节点下建立一个pinctl节点,包括compatible属性,按键对应的gpio口,中断,名称等等
设备树文件:
key-myz {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myz-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
interrupt-parent = <&gpio5>;
interrupts = <1 IRQ_TYPE_EDGE_BOTH>;//both_edge
key-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
pinctrl_key: keygrp {
fsl,pins = <
//MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0xF080/* KEY0 */
MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0xF080/* KEY0 */
>;
};
驱动程序:
#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/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
struct key_input
{ //key属性
struct input_dev *input_dev; //input设备
struct device_node *nd; //设备节点
struct timer_list timer; //定时器句柄
irqreturn_t (*handler)(int, void *); //定时器回调函数
int pin; //key的引脚编号
int irqnum; //key对应的中断
int value; //key的值
};
static struct key_input key; //声明一个实例
static irqreturn_t key_handler(int irq, void *dev_id)
{ //中断回调函数
struct key_input *dev = (struct key_input *)dev_id;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); //主要实现了一个10ms的定时任务,用于按键机械防抖
return IRQ_RETVAL(IRQ_HANDLED);
}
static void timer_fun(unsigned long arg)
{ //定时器回调函数
unsigned char value = 0;
struct key_input *dev = (struct key_input *)arg;
value = gpio_get_value(dev->pin); //获取按键值
if (value == 0)
{
input_report_key(dev->input_dev, dev->value, 1); //上报按键的值,如果value == 0的时候就上报1,否则上报0;
input_sync(dev->input_dev); //同步上报的信息
}
else
{
input_report_key(dev->input_dev, dev->value, 0);
input_sync(dev->input_dev);
}
}
static int __init mkey_init(void)
{ //insmod 的时候调用
int res = 0;
printk("key _init\n");
key.input_dev = input_allocate_device(); //申请一个input设备
key.input_dev->name = "key-myz";
key.nd = of_find_node_by_path("/key-myz"); //在设备树根/下找key-myz节点
if (key.nd == NULL)
{
printk("key node is not find \n");
return -EINVAL;
}
else
{
printk("key node is success\n");
}
key.pin = of_get_named_gpio(key.nd, "key-gpio",0); //通过key-myz节点找出""key-gpio"的引脚编号
if (key.pin < 0)
{
printk("can not get key pin\n");
}
else
{
printk("get key pin success ! pin = %d \n",key.pin);
}
res = gpio_request(key.pin, "KEY0"); //通过按键引脚编号申请一个gpio
if(res){
printk("gpio_requested failed res = %d\n",res);
}else{
printk("gpio_requested successed !\n");
}
gpio_direction_input(key.pin); //设置按键引脚为输入
key.irqnum = irq_of_parse_and_map(key.nd, 0); //根据key-myz节点解析一个中断号
printk("irqnumber is %d\n",key.irqnum);
key.handler = key_handler; //中断函数赋值
key.value = KEY_0;
//request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
res = request_irq(key.irqnum, key.handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key-mayunzhi", &key); //根据中断号申请中断并且注册中断回调函数,上升下降沿产生中断
if (res < 0)
{
printk("irq request failed ! res = %d\n",res);
gpio_free(key.pin );
return -EINVAL;
}
else
{
printk("irq request successed\n");
}
init_timer(&key.timer); //初始化定时器
key.timer.function = timer_fun; //???定时器中断函数
key.input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); //按键事件和重复事件赋值
input_set_capability(key.input_dev, EV_KEY, KEY_0); //注册按键事件和重复事件
res = input_register_device(key.input_dev); //注册一个input设备
if (res)
{
printk("input device register failed\n");
return res;
}
else
{
printk("input device register successed\n");
}
return 0;
}
static void __exit mkey_exit(void)
{ //rmmod 的时候调用
printk("key _exit\n");
del_timer_sync(&key.timer);
free_irq(key.irqnum, &key);
input_unregister_device(key.input_dev); //卸载input设备
input_free_device(key.input_dev);
}
module_init(mkey_init);
module_exit(mkey_exit);
MODULE_LICENSE("GPL");
应用程序的编写:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/ioctl.h>
static struct input_event inputevent;
int main(int argc,char *argv[]){
int fd;
int res;
char *filename;
if(argc != 2){
printf("err usage \n");
}
filename = argv[1];
fd = open(filename ,O_RDWR);
if(fd<0){
printf("can not open file :%s\n",filename);
return -1;
}
while(1){
res = read(fd ,&inputevent,sizeof(inputevent));
if(res >0){
switch(inputevent.type){
case EV_KEY:
if(inputevent.code <BTN_MISC){
printf("key %d is %s\n",inputevent.code,inputevent.value?"pressed":"released");
}else{
printf("key %d is %s\n",inputevent.code,inputevent.value?"pressed":"released");
}
break;
case EV_REL:
printf("it is a rel event\n");
break;
}
}else{
printf("read file %s failed\n",filename);
}
}
return 0;
}
makefile 文件内容
KERNELDIR :=/home/mayunzhi/linux/100ask_imx6ull-sdk/Linux-4.9.88
CURRENT_PATH :=$(shell pwd)
ARM-CC :=arm-linux-gnueabihf-gcc
APP :=app
DRV :=key
obj-m :=$(DRV).o
build:kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
rm -f ./app
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
app:
$(ARM-CC) $(APP).c -o $(APP)
mv:
sudo mv ./app *.ko /home/mayunzhi/linux/nfs/rootfs/lib/modules/4.1.15
在驱动文件夹下依次执行make ,make app ,make mv
在kernel根文件目录下 执行 make dtbs
,并且拷贝生成的xxx.dtb文件到tftp文件夹,以便通过tftp启动内核和设备树可执行文件
重启设备,加载设备树,将key.ko app
文件放在开发板文件系统中/lib/module/4.1.15
目录中
cd /lib/module/4.1.15
执行insmod key.ko没有错误的话说就说明加载成功了,然后执行./app,通过应用程序进行解析/dev/input/event2
中的按键事件,也可以执行hexdump /dev/input/event2
来实现原始数据的读取
实验现象
卸载驱动 rmmod key.ko