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_KEY)|(1<<EV_REP );
注意:如果我们设置了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;
}
.............
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;
}
.............
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));