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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章