linux按键驱动

1 相关的数据结构和接口函数
1.1 数据结构
struct input_dev {
        void *private;                                //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
        const char *name;                        //提供给用户的输入设备的名称
        const char *phys;                        //提供给编程者的设备节点的名称
        const char *uniq;                        //指定唯一的ID号,就像MAC地址一样
        struct input_id id;                        //输入设备标识ID,用于和事件处理层进行匹配
        unsigned long evbit[NBITS(EV_MAX)];                //位图,记录设备支持的事件类型
        unsigned long keybit[NBITS(KEY_MAX)];                //位图,记录设备支持的按键类型
        unsigned long relbit[NBITS(REL_MAX)];                //位图,记录设备支持的相对坐标
        unsigned long absbit[NBITS(ABS_MAX)];                //位图,记录设备支持的绝对坐标
        unsigned long mscbit[NBITS(MSC_MAX)];        //位图,记录设备支持的其他功能
        unsigned long ledbit[NBITS(LED_MAX)];                //位图,记录设备支持的指示灯
        unsigned long sndbit[NBITS(SND_MAX)];                //位图,记录设备支持的声音或警报
        unsigned long ffbit[NBITS(FF_MAX)];                //位图,记录设备支持的作用力功能
        unsigned long swbit[NBITS(SW_MAX)];                //位图,记录设备支持的开关功能
        unsigned int keycodemax;                //设备支持的最大按键值个数
        unsigned int keycodesize;                //每个按键的字节大小
        void *keycode;                                //指向按键池,即指向按键值数组首地址
        int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);        //修改按键值
        int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);        //获取按键值
        struct ff_device *ff;                        //
        unsigned int repeat_key;                //支持重复按键
        struct timer_list timer;                //设置当有连击时的延时定时器
        int state;                //
        int sync;                //同步事件完成标识,为1说明事件同步完成
        int abs[ABS_MAX + 1];                //记录坐标的值
        int rep[REP_MAX + 1];                //记录重复按键的参数值
        unsigned long key[NBITS(KEY_MAX)];                //位图,按键的状态
        unsigned long led[NBITS(LED_MAX)];                //位图,led的状态
        unsigned long snd[NBITS(SND_MAX)];                //位图,声音的状态
        unsigned long sw[NBITS(SW_MAX)];                        //位图,开关的状态
        int absmax[ABS_MAX + 1];                                        //位图,记录坐标的最大值
        int absmin[ABS_MAX + 1];                                        //位图,记录坐标的最小值
        int absfuzz[ABS_MAX + 1];                                        //位图,记录坐标的分辨率
        int absflat[ABS_MAX + 1];                                        //位图,记录坐标的基准值
        int (*open)(struct input_dev *dev);                        //输入设备打开函数
        void (*close)(struct input_dev *dev);                        //输入设备关闭函数
        int (*flush)(struct input_dev *dev, struct file *file);        //输入设备断开后刷新函数
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);        //事件处理
        struct input_handle *grab;                //
        struct mutex mutex;                //用于open、close函数的连续访问互斥
        unsigned int users;                //
        struct class_device cdev;        //输入设备的类信息
        union {                                //设备结构体
                struct device *parent;
        } dev;
        struct list_head        h_list;        //handle链表
        struct list_head        node;        //input_dev链表
};
这个结构是linux输入子系统的核心,通过这个结构定义子系统支持的事件,支持哪些按键。上报输入事件。
这个结构很庞大,但是我们重点只需要关注下面几个成员
const char *name; 这个是展示给用户的输入设备的名称
const char *phys; 这个是设备节点的名称
这两个名称通过cat /proc/bus/input/devices就可以查看的到。


unsigned long evbit;是该设备支持的事件类型,在input.h里面定义了下面的类型
#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   /*输入声音类事件*/
#define EV_REP   0x14   /* 能产生重复类事件 */
#define EV_FF    0x15
#define EV_PWR   0x16   /* 电源管理类事件 */
#define EV_FF_STATUS  0x17


对于按键就需要支持EV_KEY   ,如果需要长按按键能不断产生按键消息,就需要设置EV_REP 。
定义这些宏是对应的位不是数值,例如我们要设置我们的驱动支持按键和重复按键,就需要这样写
input_dev->evbit[0] = (1< 注意:如果我们设置了EV_REP,那么在驱动上报键按下和释放之间,每隔33毫秒,会发送一次按键。这个间隔时间是定义在input.c的input_register_device函数中的dev->rep[REP_PERIOD] = 33;这里的需要注意的是如果定义了EV_REP,那么我们的扫描间隔一定要少于33ms,否则可能释放了按键后还会收到按键的消息。例如我们设置的间隔时间是100毫秒,那么很可能释放了之后还会收到3个按键消息。
另外对于重复键的设置,在我们的驱动中,扫描和上报按下和释放还是和原来的处理时一样的,重复按键的消息是有input子系统的core来完成的。


unsigned long keybit:这个是设置我们需要支持的按键键值。我们可以用setbit来处理,例如set_bit(KEY_ENTER,  input->keybit);linux的键值定义在input.h头文件中。
keybit通常是在初始化的时候设置的,但是因为某些原因我们需要动态的增加支持的按键,那么在调用了input_register_device之后,再调用setbit设置支持的按键也是没有问题的。但是一定要确保在调用input_register_device之前至少要设置了一个按键。


void *private:这个是用户的私有数据,我们可以把我们自定义的一些控制结构、变量的地址保存用这个指针保存。


在linux2.6.3之后,linux还定义了一个input_polled_dev的结构,这个数据结构支持轮询的方式。结构定义如下:
struct input_polled_dev {
        void *private;
        
        void (*open)(struct input_polled_dev *dev);
        void (*close)(struct input_polled_dev *dev);
        void (*poll)(struct input_polled_dev *dev);
        unsigned int poll_interval; /* msec */
        unsigned int poll_interval_max; /* msec */
        unsigned int poll_interval_min; /* msec */
        
        struct input_dev *input;
        
        /* private: */
        struct delayed_work work;
};
这个结构我们重点关注下面几个成员
void (*poll)(struct input_polled_dev *dev):轮询处理函数的指针,我们在这个函数中完成对按键的检测。
unsigned int poll_interval;轮询的间隔时间,以毫秒为单位
struct input_dev *input;这个就是上面介绍的input_dev结构。


1.2重要的接口函数:
struct input_dev *input_allocate_device(void);
分配一个input_dev的结构,返回这个结构体的指针。


void input_free_device(struct input_dev *dev);
释放用input_allocate_device分配的结构。参数为input_dev的指针


struct input_polled_dev *input_allocate_polled_device(void);
分配一个input_polled_dev的结构,这个函数分配的input_polled_dev的时候,也同时分配了一个input_dev,并且将地址设置给input,所以我们调用完这个函数后不需要再调用*input_allocate_device分配。


void input_free_polled_device(struct input_polled_dev *dev);
释放input_allocate_polled_device分配的数据结构,参数为input_polled_dev的指针。


int  input_register_device(struct input_dev * dev);
注册一个input设备,参数为之前调用 input_allocate_device分配的结构。
执行成功返回0 ,返回值小于0执行失败。


void input_unregister_device(struct input_dev *dev);
释放input_register_device注册的input设备。


int input_register_polled_device(struct input_polled_dev *dev);
注册是一个支持轮询的input设备,成功返回0,小于0执行失败


void input_unregister_polled_device(struct input_polled_dev *dev);
释放input_register_polled_device注册的设备。


void input_report_key(struct input_dev *dev, unsigned int code, int value);
上报按键 ,参数dev为之前注册的input_dev结构 ,code为按键的键值,value为1表示有键按下,0表示释放


void input_sync(struct input_dev *dev);
用于事件同步,它告知事件的接收者驱动已经发出了一个完整的报告。


上面介绍的函数和结构实际上是linux input子系统。所以我们注册其他的Input设备也可以用到上面介绍的资料。


2 示例代码:
2.1 中断方式检测按键的例子
首先我们注册一个平台设备,在probe函数中初始化按键接,注册input设备。当然我们也可以注册一个字符设备,在init函数中直接注册一个input设备也没有问题,这里不属于按键驱动的部分,不展开说明了。
typedef struct
{
        input_dev *input_keypd_dev ;
        struct work_struct work;
        .........
}my_keypad;
static int  my_key_probe(struct platform_device *pdev)
{
        int i;
        int err =0;
        my_keypad *keypad_data;
        input_dev *keypd_dev ;
                .....
        keypad_data= kzalloc(sizeof(my_keypad) ,
        GFP_KERNEL);
        if(!my_keypad )
        {
                err  = -1 ; 
                goto fail0;
        }
        keypd_dev = input_allocate_device();
        if (!keypd_dev) {
                err  = -1 ; 
                goto fail1;
        }
        
        keypd_dev->name = "my-keypad"; 
        keypd_dev->phys = "Keypad/input0";
        keypd_dev->id.bustype = BUS_HOST;
        keypd_dev->id.vendor = 0x0001;
        keypd_dev->id.product = 0x0001;
        keypd_dev->id.version = 0x0100;
        
        keypd_dev->evbit[0] = BIT_MASK(EV_KEY);
             
                .......
        set_bit(KEY_BACK, keypd_dev->keybit); 
        set_bit(KEY_MENU, keypd_dev->keybit);
                .........
        keypad_data->input_keypd_dev = keypd_dev ;
        platform_set_drvdata(pdev, keypad_data);
        INIT_WORK(&(keypad_data->work), keypad_work_func);
        if (request_irq(KEYPAD_IRQNUM, keypad_irq_service, 0, "my_keypad",keypad_data)) 
        {
                err = -EBUSY; 
                goto fail2;
        }
        err = input_register_device(keypd_dev);
        if (err){
         
                goto fail3;
        }
                ............. 
        return 0;
fail3: 
        free_irq(KEYPAD_IRQNUM, NULL);
fail2: 
        input_free_device(keypd_dev);
        
fail1:
        kfree(keypad_data);
fail0:
        return err;
}
int __devexit my_key_remove(struct platform_device *pdev)
{
        my_keypad *keypad_data;
        keypad_data = platform_get_drvdata(pdev);
        .......
        input_unregister_device(keypad_data->input_keypd_dev);
        kfree(keypad_data);
        free_irq(KEYPAD_IRQNUM, NULL);
        ...........
    return 0;
}
static struct platform_driver my_key_driver = {
        .driver         = {
                .name   = "my-keypad",
                .owner  = THIS_MODULE,
        },
        .probe          = my_key_probe,
        .remove         = my_key_remove,
        .suspend        = my_key_suspend,
        .resume         = my_key_resume,
};
int __init my_key_init(void)
{
        .....
        
        res =  platform_driver_register(&my_key_driver );
        if(res)
        {
                printk(KERN_INFO "fail : platrom driver %s (%d) \n", my_key_driver.driver.name, res);
                return res;
        }
        .........
        return 0;
}
void __exit my_key_exit(void)
{
        platform_driver_unregister(&my_key_driver);
}


module_init(my_key_init);
module_exit(my_key_exit);


然后我们再定义中断处理函数和工作队列的函数。
因为我们检测按键往往需要去抖动,需要延时,但是在中断处理函数里面是不允许sleep的,否则会造成系统死掉。所以我们需要定义一个工作队列,将去抖动部分发在这里面处理。
下面是处理函数示例


irqreturn_t keypad_irq_service(int irq, void *para)
{
        my_keypad * keypad_data = (my_keypad *)para  ;
        
        ......
                //检测按键的状态
        schedule_work(&(keypad_data->work));
        return IRQ_HANDLED;
}


void keypad_work_func(struct work_struct *work)
{
        my_keypad * keypad_data=container_of(work, my_keypad , work);
        disable_irq(KEYPAD_IRQNUM);
        //检测按键,获取键值
        if( key press)
        {
                input_report_key(keypad_data->input_keypd_dev, key code, 1);
        }
        else
        {
                input_report_key(keypad_data->input_keypd_dev, key code, 0);
        }
        input_sync(keypad_data->input_keypd_dev);
        enable_irq(KEYPAD_IRQNUM);
}
2.1轮询的方式检测按键
对于有的设备,按键没有接到中断口上,就只能用轮询的方式了,下面是示例
typedef struct
{
        input_polled_dev *input_keypd_dev ;
        ........
}my_keypad;
static int  my_key_probe(struct platform_device *pdev)
{
        int i;
        int err =0;
        my_keypad *keypad_data;
        input_polled_dev *keypd_dev ;
                .....
        keypad_data = kzalloc(sizeof(my_keypad) ,
        GFP_KERNEL);
        if(!my_keypad )
        {
                err  = -1 ; 
                goto fail0;
        }
        keypd_dev = input_allocate_polled_device();
        if (!keypd_dev) {
         
                err  = -1 ; 
                goto fail1;
        }
        keypd_dev->poll = keypad_poll_callback;
                keypd_dev->poll_interval = 15;   //轮询的时间间隔15ms
        keypd_dev->input->name = "my-keypad"; 
        keypd_dev->input->phys = "Keypad/input0";
        keypd_dev->input->id.bustype = BUS_HOST;
        keypd_dev->input->id.vendor = 0x0001;
        keypd_dev->input->id.product = 0x0001;
        keypd_dev->input->id.version = 0x0100;
        


        keypd_dev->input->evbit[0] = BIT_MASK(EV_KEY);
     
        .......
        set_bit(KEY_BACK, keypd_dev->input->keybit); 
        set_bit(KEY_MENU, keypd_dev->input->keybit);
                .........
        keypad_data->input_keypd_dev = keypd_dev ;
        platform_set_drvdata(pdev, keypad_data);
 
        err = input_register_polled_device(keypd_dev);
        if (err){
         
                goto fail2;
        }


        .............
        return 0;
fail2: 
        input_free_polled_device(keypd_dev);


fail1:
        kfree(keypad_data);
fail0:


        return err;
}
int __devexit my_key_remove(struct platform_device *pdev)
{
        my_keypad *keypad_data;
        keypad_data = platform_get_drvdata(pdev);
        .......
        input_unregister_polled_device(keypad_data->input_keypd_dev);
        kfree(keypad_data);
     
        ...........
        return 0;
}
轮询函数定义如下:
void keypad_poll_callback(struct input_polled_dev *dev)
{
        .......
        //检测按键,去抖动。。
        if(有键按下)
        {
                input_report_key(dev->input, key code , 1 );
             
        }
        else
        { 
                input_report_key(dev->input, key code, 0 );     
        }
        input_sync(dev->input);    
}


最后需要注意的是input_polled_dev 只有在linux2.6.3之后才支持的,如果是之前的版本,那就只有自己起一个timer来定时轮询,同样的timer的处理函数中也不能有sleep延时,所以如果去抖动需要延时也需要开一个工作队列。下面是示例代码


首先定义一个timer


struct timer_list key_timer;
参考“中断方式检测按键的例子”
去掉probe函数中的中断部分的代码,改为
init_timer(&(keypad_data->_key_timer));
keypad_data->key_timer.expires = jiffies + 10;//jiffies是当前的毫秒数,这句的意思是10毫秒后timer到时
keypad_data->key_timer.function = key_Timer;
add_timer(&(keypad_data->key_timer));
keypad_data->key_timer.data = (unsigned long) keypad_data;




然后定义一个处理函数key_Timer
void key_Timer(unsigned long data)
{
    my_keypad *keypad_data = (my_keypad *) data;   
    schedule_work(&(keypad_data->work));
}
最后需要注意的是timer到时候不会自动重新载入的,所以我们还需要在工作队列的处理函数keypad_work_func的结尾加上下面的代码:
init_timer(&(keypad_data->_key_timer));
keypad_data->key_timer.expires = jiffies + 10; 
add_timer(&(keypad_data->key_timer));

你可能感兴趣的:(linux驱动)