(Linux驱动学习 - 11).Input 子系统

目录

一.Input 子系统的定义

二.注册 Input 设备有关函数

1.input_dev 结构体

2.申请一个 Input 结构体变量 - input_allocate_device

3.释放 input_dev 结构体变量 - input_free_device

4.向 Linux 内核注册一个 input_dev  - input_register_device

5.注销 Input 驱动 - input_unregister_device

三.上报事件相关函数

1.上报指定的事件以及对应的值 - input_event

2.告诉 Linux 内核 input 子系统上报结束 - input_sync

3.input_event 结构体

四. Input 子系统下的按键输入事件代码

1.设备树

(1).流程图

(2).代码部分

2.驱动部分

(1).流程图

(2).代码部分

3.应用程序部分

(1).流程图

(2).代码部分

五.使用Linux自带的按键驱动程序

1.Linux自带的 KEY 驱动文件

2.设备树

(1).流程图

(2).设备树代码

(3).重新编译设备树


一.Input 子系统的定义

        input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统
一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等
等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,
鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心
应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动
层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。

        可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
        驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
        核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行
处理。
        事件层:主要和用户空间进行交互。

二.注册 Input 设备有关函数

1.input_dev 结构体

struct input_dev 
{
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];         /* 事件类型的位图 */
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];       /* 按键值的位图 */
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];       /* 相对坐标的位图 */
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];       /* 绝对坐标的位图 */
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];       /* 杂项事件的位图 */
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];       /*LED 相关的位图 */
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];       /* sound 有关的位图 */
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];         /* 压力反馈的位图 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];         /*开关状态的位图 */

    ......

    bool devres_managed;
};

其中 evbit 表示输入事件类型,可选择以下事件

#define EV_SYN 0x00         /* 同步事件 */
#define EV_KEY 0x01         /* 按键事件 */
#define EV_REL 0x02         /* 相对坐标事件 */
#define EV_ABS 0x03         /* 绝对坐标事件 */
#define EV_MSC 0x04         /* 杂项(其他)事件 */
#define EV_SW 0x05          /* 开关事件 */
#define EV_LED 0x11         /* LED */
#define EV_SND 0x12         /* sound(声音) */
#define EV_REP 0x14         /* 重复事件 */
#define EV_FF 0x15          /* 压力事件 */
#define EV_PWR 0x16         /* 电源事件 */
#define EV_FF_STATUS 0x17   /* 压力状态事件 */

2.申请一个 Input 结构体变量 - input_allocate_device

/**
 * @description:        申请一个 input_dev 结构体变量
 * @param -         :   无
 * @return          :   申请到的 input_dev
 */
struct input_dev *input_allocate_device(void)


3.释放 input_dev 结构体变量 - input_free_device

/**
 * @description:        释放 input_dev 结构体变量
 * @param - dev     :   要释放的 input_dev 
 * @return          :   无
 */
void input_free_device(struct input_dev *dev)

4.向 Linux 内核注册一个 input_dev  - input_register_device

/**
 * @description:        向 Linux 注册 input_dev
 * @param - dev     :   要注册的 input_dev
 * @return          :   注册成功返回(0),失败返回(负值)
 */
int input_register_device(struct input_dev *dev)

5.注销 Input 驱动 - input_unregister_device

/**
 * @description:        注销 input_dev
 * @param - dev     :   要注销的 input_dev
 * @return          :   无
 */
void input_unregister_device(struct input_dev *dev)

三.上报事件相关函数

1.上报指定的事件以及对应的值 - input_event

/**
 * @description:            上报指定的事件以及对应的值
 * @param - dev     :       需要上报的 input_dev
 * @param - type    :       上报的事件类型,比如 EV_KEY
 * @param - code    :       事件码,比如注册的按键值,KEY_0 , KEY_1 等
 * @param - value   :       事件值,比如 1 表示按下 , 0 表示按键松开
 */
void input_events(struct input_dev *dev,unsigned int type,unsigned int code,int value)

其他上报事件的函数,都是内部调用了 input_events 函数

/**
 * @description:            上报按键事件
 */
static inline void input_report_key(struct input_dev *dev,unsigned int code,int value)
{
    input_event(dev,EV_KEY,code,!!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)


void input_report_ff_status(struct input_dev *dev,unsigned int code,int value)


void input_report_switch(struct input_dev *dev,unsigned int code,int value)


void input_mt_sync(struct input_dev *dev)

2.告诉 Linux 内核 input 子系统上报结束 - input_sync

/**
 * @description:            告诉 Linux 内核 input 子系统上报结束
 * @param - dev     :       需要上报同步事件的 input_dev
 * @return          :       无
 */
void input_sync(struct input_dev *dev)

3.input_event 结构体

struct input_event
{
    struct timeval time;            //事件发生的时间
    __u16 type;                     //事件类型
    __u16 code;                     //事件码
    __s32 value;                    //值,比如 EV_KEY 事件中, value 就是按键值
};

四. Input 子系统下的按键输入事件代码

1.设备树

(1).流程图

(2).代码部分

2.驱动部分

(1).流程图

(2).代码部分

#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define KEYINPUT_CNT    1               /* 设备号个数 */
#define KEYINPUT_NAME   "keyinput"      /* 名字 */
#define KEY0VALUE       0X01            /* KEY0按键值 */
#define INVAKEY         0XFF            /* 无效的按键值 */
#define KEY_NUM         1               /* 按键数量 */


/* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                           /* gpio */
    int irq_num;                        /* 中断号 */
    unsigned char value;                /* 按键对应的值 */
    char name[10];                      /* 名字 */
    irqreturn_t (*handler)(int,void *); /* 中断服务函数 */
};



/* keyinput 设备结构体 */
struct keyinput_dev
{
    dev_t devid;                                /* 设备号 */
    struct cdev cdev;                           /* cdev */
    struct class *class;                        /* 类 */
    struct device *device;                      /* 设备 */
    struct device_node *nd;                     /* 设备结点 */
    struct timer_list timer;                    /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM];     /* 按键描述数组 */
    unsigned char curkeynum;                    /* 当前的按键号 */

    struct input_dev *inputdev;                 /* input 结构体 */
};


/* key input 设备 */
struct keyinput_dev keyinputdev;     



/**
 * @description:            中断服务函数,开启定时器,延时 10ms 定时器用于按键消抖
 */
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}


/**
 * @description:            定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,如果按键还是处于按下状态就表示按键有效
 * @param - arg     :       设备结构变量
 * @return          :       无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    value = gpio_get_value(keydesc->gpio);      //读取 IO 值

    if(0 == value)
    {
        /* 上报按键值 */
        input_report_key(dev->inputdev,keydesc->value,1);
        input_sync(dev->inputdev);
    }
    else
    {
        /* 上报按键值 */
        input_report_key(dev->inputdev,keydesc->value,0);
        input_sync(dev->inputdev);
    }
}




/**
 * @description:            按键 IO 初始化
 * @param           :       无
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    char name[10];
    int ret;

    keyinputdev.nd = of_find_node_by_path("/key");
    if(NULL == keyinputdev.nd)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }


    /* 1.获取每一个 GPIO 的编号 */
    for(i = 0; i < KEY_NUM ; i++)
    {
        keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd,"key-gpio",i);
        if(0 > keyinputdev.irqkeydesc[i].gpio)
        {
            printk("can not get key %d \r\n",i);
        }
    }


    /* 2.初始化 key 所使用的 IO , 并且设置成中断模式 */
    for(i = 0;i < KEY_NUM;i++)
    {
        /* (1).申请 GPIO */
        memset(keyinputdev.irqkeydesc[i].name,0,sizeof(name));
        sprintf(keyinputdev.irqkeydesc[i].name,"KEY%d",i);
        gpio_request(keyinputdev.irqkeydesc[i].gpio,keyinputdev.irqkeydesc[i].name);

        /* (2).设置 GPIO 为输入 */
        gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);

        /* (3).获取中断号 */
        keyinputdev.irqkeydesc[i].irq_num = irq_of_parse_and_map(keyinputdev.nd,i);
    }

    /* 3.申请中断 */
    keyinputdev.irqkeydesc[0].handler = key0_handler;
    keyinputdev.irqkeydesc[0].value = KEY0VALUE;
    for(i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(keyinputdev.irqkeydesc[i].irq_num,keyinputdev.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,keyinputdev.irqkeydesc[i].name,&keyinputdev);

        if(0 > ret)
        {
            printk("irq %d request failed!\r\n",keyinputdev.irqkeydesc[i].irq_num);
            return -EFAULT;
        }
    }


    /* 4.创建定时器 */
    init_timer(&keyinputdev.timer);
    keyinputdev.timer.function = timer_function;


    /* 5.申请 input_dev */
    //(1).申请 input 结构体变量
    keyinputdev.inputdev = input_allocate_device();
    //(2).初始化 input_dev ,设置产生哪些事件
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev,EV_KEY,KEY_0);


    /* 6.注册输入设备 */
    ret = input_register_device(keyinputdev.inputdev);
    if(ret)
    {
        printk("register input device failed!\r\n");
        return ret;
    }


    return 0;
}



/**
 * @description:            驱动入口函数
 * @param -         :       无
 * @return          :       无
 */
static int __init keyinput_init(void)
{
    keyio_init();

    return 0;
}



/**
 * @description:            驱动出口函数
 */
static void __exit keyinput_exit(void)
{
    unsigned int i = 0;

    /* 1.删除定时器 */
    del_timer_sync(&keyinputdev.timer);

    /* 2.释放中断 */
    for(i = 0; i < KEY_NUM; i++)
    {
        free_irq(keyinputdev.irqkeydesc[i].irq_num,&keyinputdev);
    }

    /* 3.释放 IO */
    for(i = 0;i < KEY_NUM;i++)
    {
       gpio_free(keyinputdev.irqkeydesc[i].gpio); 
    }


    /* 4.注销与释放 input_dev */
    input_unregister_device(keyinputdev.inputdev);
    input_free_device(keyinputdev.inputdev);
    
}


module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");

3.应用程序部分

(1).流程图

(2).代码部分

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>



/* 1.定义一个 input_event 变量,用于存放输入事件信息 */
static struct input_event inputevent;


int main(int argc, char  *argv[])
{
    int ret,fd;
    char *filename;

    if(argc != 2)
    {
        printf("Usage : ./%s <dev_path>",argv[0]);
        return -1;
    }

    filename = argv[1];


    fd = open(filename,O_RDWR);
    if(0 > fd)
    {
        perror("open dev error");
        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");
                    }
                    ret = 0;
                    break;

                /* 其他事件...... */
            }
        }
        else
            printf("读取失败\n");
    }

    return 0;
}

五.使用Linux自带的按键驱动程序

1.Linux自带的 KEY 驱动文件

        Linux 内核自带的 KEY 驱动文件为 drivers/input/keyboard/gpio_keys.c ,其中采用了 platform 框架,在 KEY 驱动上使用了 input 子系统实现。

        所以,使用 Linux 自带的 KEY 驱动文件,只需在设备树中注册节点时,注意节点的 compatibel 要与 驱动中匹配列表的 compatible 一致。

Linux自带的驱动如下:

static const struct of_device_id gpio_keys_of_match[] = 
{
    { .compatible = "gpio-keys", },
    { },
};

static struct platform_driver gpio_keys_device_driver = 
{
    .probe = gpio_keys_probe,
    .remove = gpio_keys_remove,
    .driver = 
    {
        .name = "gpio-keys",            //设备树中的 compatible 属性值要与此处一致
        .pm = &gpio_keys_pm_ops,
        .of_match_table = of_match_ptr(gpio_keys_of_match),
    }
};

static int __init gpio_keys_init(void)
{
return platform_driver_register(&gpio_keys_device_driver);
}

static void __exit gpio_keys_exit(void)
{
platform_driver_unregister(&gpio_keys_device_driver);
}

2.设备树

       要使用Linux自带的 KEY 驱动,就要使得 KEY 设备树节点中 compatible 属性要与 Linux 自带KEY 驱动中的一致。

(1).流程图

(2).设备树代码

       

(3).重新编译设备树

        重新编译设备树,查看 /dev/input/ 下有没有新增节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值