欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux 驱动开发之平台设备驱动设备树 input子系统的使用:按键中断驱动

程序员文章站 2022-07-14 09:46:30
...

这一章我们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来实现原始数据的读取

实验现象linux 驱动开发之平台设备驱动设备树 input子系统的使用:按键中断驱动
linux 驱动开发之平台设备驱动设备树 input子系统的使用:按键中断驱动
卸载驱动 rmmod key.ko

相关标签: linux driver