在上一节里,我们用一个应用程序实现了鼠标的控制,并控制鼠标用相对位移不断的画一个正方形,感觉非常有意思,这一节,我们将通过一个简单按键实例来真正的实现一个input设备驱动程序。
http://blog.csdn.net/morixinguan/article/details/69808832
在写Input驱动之前,我们要了解下这个结构体,在此,我们要包含相应的头文件:
#include <linux/input.h>
我们在这个头文件中找到了以下结构体,它就是input设备的核心:
//用来和应用交互的input设备 struct input_dev { //输入设备的名称 //在/proc/bus/input/devices中产生 const char *name; //硬件相关 const char *phys; const char *uniq; struct input_id id; unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; //支持事件,位图里每一位代表一个事件 //#define EV_MAX 0x1f //#define EV_CNT (EV_MAX+1) unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //支持的按键#define KEY_MAX 0x2ff //#define KEY_CNT (KEY_MAX+1) 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)]; unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; unsigned int hint_events_per_packet; unsigned int keycodemax; unsigned int keycodesize; void *keycode; int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int *old_keycode); int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke); struct ff_device *ff; unsigned int repeat_key; struct timer_list timer; int rep[REP_CNT]; struct input_mt_slot *mt; int mtsize; int slot; int trkid; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; unsigned long led[BITS_TO_LONGS(LED_CNT)]; unsigned long snd[BITS_TO_LONGS(SND_CNT)]; unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //文件操作 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 __rcu *grab; spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; bool sync; //继承于dev struct device dev; struct list_head h_list; struct list_head node; };
我们还好看到input_id这个成员,它也是一个结构体如下:
struct input_id { //总线类型 __u16 bustype; //与厂商有关 __u16 vendor; //产品 __u16 product; //版本 __u16 version; };
写驱动之前,我们还要了解一些最基本的API函数:
(1)static inline void set_bit(int nr, unsigned long *addr)
这个函数主要用来设置位,也就是设置对应的事件,因为上面的keybit,absbit,relbit等等都表示对应事件的位,我们需要哪个事件就需要去设置对应的位,所以需要这个函数:
(2)struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
input_allocate_device这个函数主要用来申请一个input设备,一旦申请成功了,我们就可以去填充上面这个struct input_dev这个结构体了,反之,申请不成功就要释放这个input设备,用input_free_device这个函数,这两个函数总是成对出现的。
(3)int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
input_register_device这个函数用来注册一个input设备,反之则是注销一个input设备,用input_unregister_device这个函数。
(4)
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_KEY, code, !!value); } static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_REL, code, value); } static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value) { input_event(dev, EV_ABS, code, value); }
当然我们还会看到以上这些函数,当然还有其它的,也是类似的,在input.h中可以找到。这些是用来上报键值给上层的。
当然我们上报为具体的事件以后,我们还需要上报一个同步事件,防止底层乱报数据,也就是提高数据上报的准确性。
我们会用到下面这个函数:
static inline void input_sync(struct input_dev *dev) { input_event(dev, EV_SYN, SYN_REPORT, 0); }
暂时我们需要知道的就这么多,接下来开始写代码:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/fb.h> #include <linux/backlight.h> #include <linux/err.h> #include <linux/pwm.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/delay.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/timer.h> /*timer*/ #include <asm/uaccess.h> /*jiffies*/ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/input.h> struct input_dev *dev ; static irqreturn_t irq_fuction(int irq, void *dev_id) { struct input_dev *keydev ; #if 0 if(in_interrupt()){ printk("%s in interrupt handle!\n",__FUNCTION__); } #endif keydev = dev_id ; //上报键值 input_report_key(keydev, KEY_HOME, \ !gpio_get_value(EXYNOS4_GPX3(2))); input_sync(keydev); //上报一个同步事件 printk("irq:%d\n",irq); return IRQ_HANDLED ; } static int __init tiny4412_Key_irq_test_init(void) { int err = 0 ; int irq_num1 = 0; int ret ; struct input_id id ; dev = input_allocate_device(); if(IS_ERR_OR_NULL(dev)){ ret = -ENOMEM ; goto ERR_alloc; } //对input_id的成员进行初始化 dev->name = "tiny4412_home_key" ; dev->phys = "YYX_create_key" ; dev->uniq = "20170410" ; dev->id.bustype = BUS_HOST ; dev->id.vendor = ID_PRODUCT ; dev->id.version = ID_VENDOR ; set_bit(EV_SYN,dev->evbit); //设置为同步事件,这个宏可以在input.h中找到 set_bit(EV_KEY,dev->evbit); //因为是按键,所以要设置成按键事件 set_bit(KEY_HOME,dev->keybit); //设置这个按键表示为KEY_HOME这个键,到时用来上报 ret = input_register_device(dev); //注册input设备 if(IS_ERR_VALUE(ret)) goto ERR_input_reg ; irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));//申请中断号,并注册中断,下降沿触发,设备就是input设备 err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",dev); if(err != 0) goto free_irq_flag ; //以下除了return 0 都为出错处理 return 0 ; ERR_input_reg: input_unregister_device(dev); free_irq_flag: free_irq(irq_num1,(void *)"key1"); ERR_alloc: return ret ; } static void __exit tiny4412_Key_irq_test_exit(void) { //为了简单,我这里不需要验证exit的功能,只要在开机init成功就可以了,日后再完善 int irq_num1 ; printk("irq_key exit\n"); irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); free_irq(irq_num1,dev); } module_init(tiny4412_Key_irq_test_init); module_exit(tiny4412_Key_irq_test_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("YYX"); MODULE_DESCRIPTION("Exynos4 KEY Driver");
编译好,下载到开发板,用cat /proc/bus/input/devices可以查看到以下信息:
我们看到,Bus,vendor,Product,version就是刚刚我们那个input_id里的结构体成员,name,phys,Uniq就是我们input_dev里的成员,Sysfs的这个input节点是由内核分配的,在/dev/input/event4
我们可以用cat /dev/input/event4,按下按键会打印信息,不过这个并不是我们想要看到的结果,我们可以简单的写一个按键事件,因为我是在android系统上做测试,还没有去写这个程序,有兴趣的同学可以自己去写,然后测测,是否读event4这个事件会有值返回,这其实是在中断处理函数打印的中断irq的值。
这节到此为止,往后我们学习完i2c等等驱动以后,会教大家如何来写触摸屏,重力传感器等复杂的驱动程序,敬请期待。