自己写驱动程序之触摸屏
这是一个大的课题哦!输入子系统!之前有个输入子系统的按键延时操作没有分析和自己写,所以拿到这里讲,是因为触摸屏也是输入子系统哦!!!
不会肿么办呀!搜一下啦,按照input_register_device查找:
/******************************************************************************************************************
driversmacintoshAdbhid.c
******************************************************************************************************************/
static const u16 adb_to_linux_keycodes[128] = {
/* 0x00 */ KEY_A, /* 30 */
/* 0x01 */ KEY_S, /* 31 */
/* 0x02 */ KEY_D, /* 32 */
/* 0x03 */ KEY_F, /* 33 */
/* 0x04 */ KEY_H, /* 35 */
/* 0x05 */ KEY_G, /* 34 */
/* 0x06 */ KEY_Z, /* 44 */
/* 0x07 */ KEY_X, /* 45 */
/* 0x08 */ KEY_C, /* 46 */
/* 0x09 */ KEY_V, /* 47 */
/* 0x0a */ KEY_102ND, /* 86 */
/* 0x0b */ KEY_B, /* 48 */
/* 0x0c */ KEY_Q, /* 16 */
/* 0x0d */ KEY_W, /* 17 */
/* 0x0e */ KEY_E, /* 18 */
/* 0x0f */ KEY_R, /* 19 */
/* 0x10 */ KEY_Y, /* 21 */
/* 0x11 */ KEY_T, /* 20 */
/* 0x12 */ KEY_1, /* 2 */
/* 0x13 */ KEY_2, /* 3 */
/* 0x14 */ KEY_3, /* 4 */
/* 0x15 */ KEY_4, /* 5 */
/* 0x16 */ KEY_6, /* 7 */
/* 0x17 */ KEY_5, /* 6 */
/* 0x18 */ KEY_EQUAL, /* 13 */
/* 0x19 */ KEY_9, /* 10 */
/* 0x1a */ KEY_7, /* 8 */
/* 0x1b */ KEY_MINUS, /* 12 */
/* 0x1c */ KEY_8, /* 9 */
/* 0x1d */ KEY_0, /* 11 */
/* 0x1e */ KEY_RIGHTBRACE, /* 27 */
/* 0x1f */ KEY_O, /* 24 */
/* 0x20 */ KEY_U, /* 22 */
/* 0x21 */ KEY_LEFTBRACE, /* 26 */
/* 0x22 */ KEY_I, /* 23 */
/* 0x23 */ KEY_P, /* 25 */
/* 0x24 */ KEY_ENTER, /* 28 */
/* 0x25 */ KEY_L, /* 38 */
/* 0x26 */ KEY_J, /* 36 */
/* 0x27 */ KEY_APOSTROPHE, /* 40 */
/* 0x28 */ KEY_K, /* 37 */
/* 0x29 */ KEY_SEMICOLON, /* 39 */
/* 0x2a */ KEY_BACKSLASH, /* 43 */
/* 0x2b */ KEY_COMMA, /* 51 */
/* 0x2c */ KEY_SLASH, /* 53 */
/* 0x2d */ KEY_N, /* 49 */
/* 0x2e */ KEY_M, /* 50 */
/* 0x2f */ KEY_DOT, /* 52 */
/* 0x30 */ KEY_TAB, /* 15 */
/* 0x31 */ KEY_SPACE, /* 57 */
/* 0x32 */ KEY_GRAVE, /* 41 */
/* 0x33 */ KEY_BACKSPACE, /* 14 */
/* 0x34 */ KEY_KPENTER, /* 96 */
/* 0x35 */ KEY_ESC, /* 1 */
/* 0x36 */ KEY_LEFTCTRL, /* 29 */
/* 0x37 */ KEY_LEFTMETA, /* 125 */
/* 0x38 */ KEY_LEFTSHIFT, /* 42 */
/* 0x39 */ KEY_CAPSLOCK, /* 58 */
/* 0x3a */ KEY_LEFTALT, /* 56 */
/* 0x3b */ KEY_LEFT, /* 105 */
/* 0x3c */ KEY_RIGHT, /* 106 */
/* 0x3d */ KEY_DOWN, /* 108 */
/* 0x3e */ KEY_UP, /* 103 */
/* 0x3f */ KEY_FN, /* 0x1d0 */
/* 0x40 */ 0,
/* 0x41 */ KEY_KPDOT, /* 83 */
/* 0x42 */ 0,
/* 0x43 */ KEY_KPASTERISK, /* 55 */
/* 0x44 */ 0,
/* 0x45 */ KEY_KPPLUS, /* 78 */
/* 0x46 */ 0,
/* 0x47 */ KEY_NUMLOCK, /* 69 */
/* 0x48 */ 0,
/* 0x49 */ 0,
/* 0x4a */ 0,
/* 0x4b */ KEY_KPSLASH, /* 98 */
/* 0x4c */ KEY_KPENTER, /* 96 */
/* 0x4d */ 0,
/* 0x4e */ KEY_KPMINUS, /* 74 */
/* 0x4f */ 0,
/* 0x50 */ 0,
/* 0x51 */ KEY_KPEQUAL, /* 117 */
/* 0x52 */ KEY_KP0, /* 82 */
/* 0x53 */ KEY_KP1, /* 79 */
/* 0x54 */ KEY_KP2, /* 80 */
/* 0x55 */ KEY_KP3, /* 81 */
/* 0x56 */ KEY_KP4, /* 75 */
/* 0x57 */ KEY_KP5, /* 76 */
/* 0x58 */ KEY_KP6, /* 77 */
/* 0x59 */ KEY_KP7, /* 71 */
/* 0x5a */ 0,
/* 0x5b */ KEY_KP8, /* 72 */
/* 0x5c */ KEY_KP9, /* 73 */
/* 0x5d */ KEY_YEN, /* 124 */
/* 0x5e */ KEY_RO, /* 89 */
/* 0x5f */ KEY_KPCOMMA, /* 121 */
/* 0x60 */ KEY_F5, /* 63 */
/* 0x61 */ KEY_F6, /* 64 */
/* 0x62 */ KEY_F7, /* 65 */
/* 0x63 */ KEY_F3, /* 61 */
/* 0x64 */ KEY_F8, /* 66 */
/* 0x65 */ KEY_F9, /* 67 */
/* 0x66 */ KEY_HANJA, /* 123 */
/* 0x67 */ KEY_F11, /* 87 */
/* 0x68 */ KEY_HANGEUL, /* 122 */
/* 0x69 */ KEY_SYSRQ, /* 99 */
/* 0x6a */ 0,
/* 0x6b */ KEY_SCROLLLOCK, /* 70 */
/* 0x6c */ 0,
/* 0x6d */ KEY_F10, /* 68 */
/* 0x6e */ KEY_COMPOSE, /* 127 */
/* 0x6f */ KEY_F12, /* 88 */
/* 0x70 */ 0,
/* 0x71 */ KEY_PAUSE, /* 119 */
/* 0x72 */ KEY_INSERT, /* 110 */
/* 0x73 */ KEY_HOME, /* 102 */
/* 0x74 */ KEY_PAGEUP, /* 104 */
/* 0x75 */ KEY_DELETE, /* 111 */
/* 0x76 */ KEY_F4, /* 62 */
/* 0x77 */ KEY_END, /* 107 */
/* 0x78 */ KEY_F2, /* 60 */
/* 0x79 */ KEY_PAGEDOWN, /* 109 */
/* 0x7a */ KEY_F1, /* 59 */
/* 0x7b */ KEY_RIGHTSHIFT, /* 54 */
/* 0x7c */ KEY_RIGHTALT, /* 100 */
/* 0x7d */ KEY_RIGHTCTRL, /* 97 */
/* 0x7e */ KEY_RIGHTMETA, /* 126 */
/* 0x7f */ KEY_POWER, /* 116 */
};
static int adbhid_input_register(int id, int default_id, int original_handler_id, int current_handler_id,
int mouse_kind)
{
struct input_dev *input_dev;
input_dev = input_allocate_device();
switch (original_handler_id)
{
case 0x02: /* Adjustable keyboard button device */
sprintf(hid->name, "ADB adjustable keyboard buttons");
//能产生哪类事件
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
//能产生这类操作里的哪些事件
set_bit(KEY_SOUND, input_dev->keybit);
set_bit(KEY_MUTE, input_dev->keybit);
set_bit(KEY_VOLUMEUP, input_dev->keybit);
set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
break;
case 0x1f: /* Powerbook button device */
sprintf(hid->name, "ADB Powerbook buttons");
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
set_bit(KEY_MUTE, input_dev->keybit);
set_bit(KEY_VOLUMEUP, input_dev->keybit);
set_bit(KEY_VOLUMEDOWN, input_dev->keybit);
set_bit(KEY_BRIGHTNESSUP, input_dev->keybit);
set_bit(KEY_BRIGHTNESSDOWN, input_dev->keybit);
set_bit(KEY_EJECTCD, input_dev->keybit);
set_bit(KEY_SWITCHVIDEOMODE, input_dev->keybit);
set_bit(KEY_KBDILLUMTOGGLE, input_dev->keybit);
set_bit(KEY_KBDILLUMDOWN, input_dev->keybit);
set_bit(KEY_KBDILLUMUP, input_dev->keybit);
break;
}
......
err = input_register_device(input_dev);
fail: input_free_device(input_dev);
return err;
}
static void adbhid_input_unregister(int id)
{
input_unregister_device(adbhid[id]->input);
......
}
查看了很多内核类似代码,发现只有这个简单一些,其他的太复杂了,那就这一个吧!现在查查定时器类似代码:找不到哦,试了很多方法,好吧,看来还得宋老师出场啦!
/*xxx设备结构体*/
struct xxx_dev
{
struct cdev cdev;
...
timer_list xxx_timer; //设备要使用的定时器
};
//xxx驱动中的某函数
xxx_func1(...)
{
struct xxx_dev *dev= filp->private_data;
...
//初始化定时器
init_timer(&dev->xxx_timer);
dev->xxx_timer.function=&xxx_do_timer;
dev->xxx_timer.data=(unsigned long)dev;
//设备结构体指针作为定时器处理函数参数
dev->xxx_timer.expires=jiffies+delay;
//添加(注册)定时器
add_timer(&dev->xxx_timer);
...
}
//xxx驱动中的某函数
xxx_func2(...)
{
...
//删除定时器
del_timer(&dev->xxx_timer);
...
}
//定时器处理函数
static void xxx_do_timer(unsigned long arg)
{
struct xxx_device *dev=(struct xxx_device*)(arg);
...
//调度定时器再执行
dev->xxx_timer.expires=jiffies+delay;
add_timer(&dev->xxx_timer);
...
}
//定时器的到期时间往往是在目前jiffies的基础上添加一个时延,若为Hz,则表示延迟1s。在定时器处理函数中,在做完相应的工作后,往往会延后expires并将定时器再次添加到内核定时器链表,以便定时器能再次被触发。
好啦!关于延时去抖按键的驱动程序如下啦!
/******************************************************************************************************************
buttons.c
******************************************************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#include //POWER Manager
#include
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF(0), KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF(2), KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPG(3), KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPG(11), KEY_LEFTSHIFT},
};
static struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100); //注释1
return IRQ_RETVAL(IRQ_HANDLED);
}
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
static int buttons_init(void)
{
int i;
/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;
/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
/* 3. 注册 */
input_register_device(buttons_dev);
/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);
for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
return 0;
}
static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
测试步骤如下:
2. 如果没有启动QT:
cat /dev/tty1
按:s2,s3,s4
就可以得到ls
或者:
exec 0
好啦!开始自己写驱动啦:
第一步:程序框架
从这个例子driversmacintoshAdbhid.c可以看出框架很简单:
static int buttons_init(void)
{
return 0;
}
static void buttons_exit(void)
{
}
module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL");
第二步:完善初始化函数init():
从driversmacintoshAdbhid.c这个例子可以看出初始化主要四个步骤:
1. 分配一个input_dev结构体
static struct input_dev *buttons_dev;
buttons_dev = input_allocate_device();
2. 能产生哪类事件
我们知道:EV_KEY 0x01 按键事件,EV_REP 0x14 Repeat,我们从该例子中知道它们一般一起出现,所以:
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);
或者:
buttons_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
3. 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
4. 注册
input_register_device(buttons_dev);
第三步:完善注销函数:
input_free_device(buttons_dev);
input_unregister_device(buttons_dev); (这样是错误的!!!!)
结果呢:
所以,它俩顺序不能颠倒:
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
第四步:回归按键思路,先是定时器:
-
在初始化函数init()中初始化定时器
static struct timer_list buttons_timer;
init_timer(&buttons_timer);
buttons_timer.function=&buttons_timer_function;
add_timer(&buttons_timer);
2. 在注销函数中注销定时器
del_timer(&buttons_timer);
第五步:按键中断
首先如何触发定时器启动呢?既然是触发那肯定是中断啦!当触发按键中断时,首先延迟规定时间之后触发定时器中断,进入定时器中断处理函数中再去处理按键,依次来达到去抖。原来按键中断申请函数request_irq()都是在open()函数中,而Input子系统(未深入了解,所以现在只是简单说说我的理解)没有应用程序调用而是将驱动插入之后通过事件event触发得来,举个例子,当你用触摸屏的时候用之前难道要先运行一下应用程序之后才能用嘛?不是的,应该是在你触摸屏幕的时候就会得到触摸屏的响应。所以,我们驱动程序里面木有open()函数,直接在初始化中就可以调用request_irq()函数:
-
在初始化函数init()调用request_irq()函数:
我们知道在之前的buttons驱动程序中, request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)的参数部分是通过结构体来调用的,这次的驱动程序使此函数的参数全部由结构体来调用:
struct pin_desc{
int pin;
char *name;
unsigned int pin;
};
struct pin_desc pin_desc[4]={
{IRQ_EINT0, "S2", S3C2410_GPF(0)},
{IRQ_EINT2, "S3", S3C2410_GPF(2)},
{IRQ_EINT11, "S4", S3C2410_GPF(3)},
{IRQ_EINT19, "S5", S3C2410_GPF(11)},
};
for(i=0;i<4;i++)
{
request_irq(pins_desc[i].irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
}
根据request_irq()的第二个参数可知它要的是中断处理函数:
static struct pin_desc *irq_pd;
static irqreturn_t buttons_irq(int irq, void * _priv)
{
irq_pd=(struct pin_desc *)_priv;
return IRQ_RETVAL(IRQ_HANDLED);
}
中断处理函数中启动定时器:
mod_timer(&buttons_timer, jiffies +HZ/100);
for(i=0;i<4;i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}
2.在注销exit()函数中调用free_irq()函数:
int i;
for(i=0;i<4;i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
第六步:完成 buttons_timer.function=&buttons_timer_function;及定时器处理函数
从注释3中可以看出,input_event函数第二个参数type是判断能产生哪类事件并与其匹配;第三个参数是按键所造成的作用;为了使参数归档在一起,所以放在结构体里定义:
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4]={
{IRQ_EINT0, "S2", S3C2410_GPF(0),KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF(2),KEY_S},
{IRQ_EINT11, "S4", S3C2410_GPF(3),KEY_ENTER},
{IRQ_EINT19, "S5", S3C2410_GPF(11),KEY_LEFTSHIFT},
};
static void buttons_timer_function(unsigned long data)
{
struct pin_desc* pindesc =irq_pd;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval)// 1按下
{
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev); //见注释4
}
else
{
input_event(buttons_dev,EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}
这里在自己复写一遍之后出现了这个问题:
因为我写的是:
static void buttons_timer_function(void)
更改成:
static void buttons_timer_function(unsigned long data)
之后就木有问题啦:
原因在这里!!!
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
**************************************************************************************************************************************************************************************************************************************
注释1:
内核通过函数mod_timer来实现已经激活的定时器超时时间:
mod_timer(&my_timer, jiffies+new_delay);
mod_timer函数也可以操作那些已经初始化,但还没有被激活的定时器,如果定时器没有激活,mod_timer会激活它。如果调用时定时器未被激活,该函数返回0,否则返回1。一旦从mod_timer函数返回,定时器都将被激活而且设置了新的定时值。
如果需要在定时器超时前停止定时器,可以使用del_timer函数:
del_timer(&my_timer);
被激活或未被激活的定时器都可以使用该函数,如果定时器还未被激活,该函数返回0;否则返回1。当删除定时器,必须小心一个潜在的竞争条件。当del_timer返回后,可以保证的只是:定时器不会被再激活,但是多处理器上定时器中断可能已经在其他处理上运行了,所以需要等待可能在其他处理器上运行的定时器处理程序都退出,这时需要使用del_timer_sync函数执行删除工作:
del_timer_sync(&my_timer);
和del_timer函数不同,del_timer_sync数不能在中断上下文中使用。
注释2:
1、为何引入input system?
以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可以实现一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统。
输入子系统引入的好处:
(1)统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
(2)提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
(3)抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。
注:更多详细描述可参见《精通Linux设备驱动程序开发》这本书。
注释3:
在中断函数或者工作队列中调用input_event上报事件:
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)中调用下列函数:
static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = true;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case SYN_MT_REPORT:
dev->sync = false;
disposition = INPUT_PASS_TO_HANDLERS;
break;
}
break;
case EV_KEY:
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
else
input_stop_autorepeat(dev);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
......
}
注释4:
4)input_sync()
用来告诉上层,本次的事件已经完成了.
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
具体见下一篇博文!!!