linux input子系统下驱动编写

Linux input 子系统

介绍

按键,鼠标,键盘,触摸屏等都属于(input)设备,linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上用了input框架,用户只需要负责上报输入事件,比如按键值,坐标等信息,input核心层负责处理这些事件。

框架
硬件输入设备内核空间驱动层/核心层/事件层用户空间
按键GPIOinput core设备访问节点
USB键盘/鼠标USB HIDinput core设备访问节点
触摸屏等等touchinput core设备访问节点

可以看出input子系统用到了驱动分层模型,我们编写驱动的时候只需要关注中间的驱动层,核心层和事件层。

驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容

核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理

事件层:主要和用户空间进行交互

input驱动编写流程

input核心层会向linux内核注册一个字符设备

/* input核心层重点代码,可以在drivers/input/input.c这个文件阅读核心层代码 */
struct class input_class = {
    .name		= "input",
    .devnode	= "input_devnode",
};

static int __init input_init(void){
    int err;
    
    /* 注册了一个input类 系统启动后可以在/sys/class 目录下找到一个input子目录 */
    err = class_register(&input_class);
    if(err){
        pr_err("unable to register input_dev class\n");
        return err;
    }
    
    err = input_proc_init();
    if(err){
        goto fail1;
    }
    
    /* 注册一个字符设备,主设备号为INPUT_MAJOR(13) 
    *  也就是说所有input子系统处理输入设备的时候就不用去注册字符设备,只需要向系统注册一个input_device即可
    */
    err = register_chrdev_region(MKDEV(INPUT_MAJOR,0),INPUT_MAX_CHAR_DEVICES,"input");
    if(err){
        pr_err("unable to register char major %d",INPUT_MAJOR);
        goto fail2;
    }
    
    return 0;
    
fail2:input_proc_exit();
fail1:class_unregister(&input_class);
    return err;
}
注册 input_dev

在使用input子系统的时候只需要注册一个input设备即可

/* input_dev结构体表示input设备,定义在include/linux/input.h中 */
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)];/* 事件类型的位图 */
    /* 如果要用到按键,就需要注册EV_KEY事件,如果需要使用连按功能还需要注册EN_REP事件 */
    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)];		/* sount有关的位图 */
    unsigned long ffbit[BITS_TO_LONGS9(FF_CNT)];			/* 压力反馈的位图 */
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];			/* 开关状态的位图 */
    
    bool dervers_managed;
};

/* 这里简单说明一个demo 我们将某个开发板上的按键作为USB键盘按键上的某一个按键 
*  那么编写input设备驱动的时候需要先申请一个Input_dev结构体变量,使用以下函数
*/
struct input_dev *input_allocate_device(void);

/* 注销设备需要使用以下函数 */
void input_free_device(struct input_dev *dev);

/* 除此之外,我们还可以通过devm_input_allocate_device 函数来申请input_dev 变量 但是这个函数在设备卸载的时候(加载失败)会自动释放,不需要手动释放 */

/* 申请完后就要初始化这个input_dev 初始化内容为事件类型(evbit)和事件值(keybit),然后使用下列
*  函数向input子系统核心层注册
*/
int input_register_device(struct input_dev *dev);

/* 注销input驱动 */
void input_unregister_device(struct input_dev *dev);

/* 总结 */
struct input_dev *inputdev;
static int __init xxx_init(void){
    inputdev = devm_input_allocate_device();
    inputdev->name = "xxx_inputdev";
    
    /* 第一种设置事件和事件值的方法 */
    __set_bit(EV_KEY,inputdev->evbit);
    __set_bit(EV_REP,inputdev->evbit);
    __set_bit(KEY_0,inputdev->keybit);
    
    /* 第二种设置事件和事件值的方法 */
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
    
    /* 第三种设置事件和事件值的方法 */
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev,EN_KEY,KEY_0);
    
    
    input_register_device(inputdev);
    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void){
    input_unregister_device(input_dev);
    input_free_device(inputdev);
}
上报输入事件

注册好input_dev以后还需要上报输入事件,比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给input子系统核心层,不同事件的的上报API函数不同

/* 常用的事件上报API函数 针对所有事件
* dev:	需要上报的input设备
* type: 上报的事件类型 比如EV_KEY
* code:	事件码,我们注册的按键值 KEY_0
* value:事件值,比如1表示按下  0表示按键松开
*/
void input_event(struct input_dev		*dev,
                 unsigned int			type,
                 unsigned int 			code,
                 int					value
                );

/* 针对按键上报的函数 */
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);

/* 当我们上报事件后还需要使用input_sync来告知input子系统上报结束,进行同步操作*/
void input_sync(struct input_dev *dev);

/* 总结 */
void timer_function(unsigned long arg){
    unsigned char value;
    
    value = gpio_get_value(keydesc->gpio);
    if(0 == value){
        input_reprot_key(inputdev,KEY_0,1);
        input_sync(inputdev);
    }
    else{
        input_report_key(inputdev,KEY_0,0);
        input_sync(inputdev);
    }
}

input_event 结构体
/* linux内核使用input_event结构体表示所有的输入事件 include/uapi/linux/input.h */

/*
*time: 事件发生的时间
*type: 事件类型
*code: 事件码
*value:值
*/
struct input_event{
    struct timeval time;
    __u16	type;
    __u16	code;
    __u16	value;
};
platform 框架下需要添加的内容
struct mykey_dev{
    struct input_dev *idev;
    struct timer_list timer;
    int gpio;
    int irq;
}

static int mykey_probe(struct platform_device *pdev){
    struct mykey_dev *key;
    struct input_dev *idev;
    int ret;
    
    dev_info(&pdev->dev,"Key device and driver matched successfully!\n");
    
    /* key指针分配内存 */
    key = devm_kazlloc(&pdev->dev,sizeof(struct mykey_dev),GFP_KERNEL);
    if(!key)
        return -ENOMEM;
    
    platform_set_drvdate(pdev,key);		//设置私有数据
    /* 初始化按键
    *  这个函数主要是从设备树获取按键硬件信息
    *  申请GPIO devm_gpio_request(); 设置GPIO模式  获取中断号 获取中断触发类型
    *  申请中断
    */
    ret = mykey_init(pdev);
    if(ret)
        return -ENOMEM;
    
    /* 初始化定时器 */
    init_timer(&key->timer);
    key->timer.function = key_timer_function;
    key->timer.data		= (unsigned long)key;
    
    /* input_dev 初始化 */
    idev = devm_input_allocate_device(&pdev->dev);
    if(idev){
        return -ENOMEM;
    }
    
    key->idev = idev;
    idev->name = "mykey";
    
    __set_bit(EV_KEY,idev->evbit);
    __set_bit(EV_REP,idev->evbit);
    __set_bit(PS_KEY0_CODE,idev->keybit);
    
    return input_register_device(idev);
}

static int mykey_remove(struct platform_device *pdev){
    struct mykey_dev *key = platform_get_drvdata(pdev);
    
    /* 删除定时器 */
    del_timer_sync(&key->timer);
    
    /* 卸载按键设备 */
    input_unregister_device(key->idev);
    
    return 0;
}

/* 匹配列表 */
static const struct of_device_id key_of_match[] = {
    {.compatible = "mizar,key"},
    {/* Sentinel */}
};

/* platform 驱动结构体 */
static struct platform_driver mykey_driver = {
    .driver = {
        .name				= "zynq-key";
        .of_match_table		= key_of_match,
    },
    .probe				= mykey_probe;
    .remove				= mykey_remove,
};

module_platform_driver(mykey_driver);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值