前提知识:
内核定时器用于控制某个函数(定时器处理函数)在未来某个时间特定执行。内核定时器注册的函数只会执行一次
内核定时器被组织成双向链表,使用struct_timer_list结构来描述
struct timer_list { struct list_head entry;/*链表*/ unsigned long expires;/*超时的jiffies值*/ void (*function)(unsigned long);/*超时处理函数*/ unsigned long data;/*超时函数参数*/ struct tvec_base *base;/*内核使用*/ }
初始化定时器队列结构:
void init_timer(struct timer_list *timer);
添加定时器:
void add_timer(struct timer_list *timer);
删除定时器:
int del_timer(struct timer_list *timer); 在定时器超时前将它删除。(超时后会自动删除)
修改定时器:
int mod_timer(struct timer_list *timer,unsigned long expires);
使用示例:
struct timer_list timer; void timer_fuction(int para);
init_timer(&timer);
timer.data = 5;
timer.expires = jiffies +(20*HZ);
timer.function = timer_function;
add_timer(&timer);
还可以使用TIMER_INITIALIZER(_function, _expires, _data)宏迅速创建timer
static struct timer_list touch_timer = TIMER_INITIALIZER(timer_function, 0, 0);
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
将Input设备注册到input子系统中;
在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
设置设备所支持的事件类型和取值范围:
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS); /* 设置按键类型为触摸 */ dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH); /* 设置abs事件参数取值范围 */ input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
Linux中输入设备的事件类型有(这里只列出了常用的一些,更多请看linux/input.h中):
EV_SYN 0x00 同步事件 EV_KEY 0x01 按键事件 EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移) EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置) EV_MSC 0x04 其它 EV_LED 0x11 LED EV_SND 0x12 声音 EV_REP 0x14 Repeat EV_FF 0x15 力反馈
用于提交较常用的事件类型给输入子系统的函数有:
void input_report_key(struct input_dev *dev, unsigned int code, int 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); //提交绝对坐标事件的函数
注意,在提交输入设备的事件后必须用下列方法使事件同步,让它告知input系统,设备驱动已经发出了一个完整的报告,对于触摸屏,如果不报告同步,可能导致xy坐标分离:
void input_sync(struct input_dev *dev)
触摸屏驱动工作流程图:
需要注意的是ADC工作模式的切换:先使用waiting for interrupt mode,检测触摸中断。然后进入Auto X/Y Position conversion mode,产生ADC中断后获得坐标值。 驱动源代码:
#include #include #include #include #include #include #include #include #include #include #include #include #include
#include #include
/* For ts.dev.id.version */
#define S3C2410TSVERSION 0x0101
#define WAIT4INT(x) (((x)<<8) |
S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN |
S3C2410_ADCTSC_XY_PST(3))
#define AUTOPST (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN |
S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0)) //S3C2410_ADCTSC_XY_PST(0) 设置无操作模式
static char *s3c2410ts_name = “s3c2410 TouchScreen”;
static struct input_dev *dev;
static long xp;
static long yp;
static int count;
extern struct semaphore ADC_LOCK;
static int OwnADC = 0;
static void __iomem *base_addr;
static inline void s3c2410_ts_connect(void)
{ s3c2410_gpio_cfgpin(S3C2410_GPG(12), S3C2410_GPG12_XMON);
s3c2410_gpio_cfgpin(S3C2410_GPG(13), S3C2410_GPG13_nXPON);
s3c2410_gpio_cfgpin(S3C2410_GPG(14), S3C2410_GPG14_YMON);
s3c2410_gpio_cfgpin(S3C2410_GPG(15), S3C2410_GPG15_nYPON);
}
static void touch_timer_fire(unsigned long data)
{ unsigned long data0;
unsigned long data1;
int updown;
data0 = ioread32(base_addr + S3C2410_ADCDAT0);
data1 = ioread32(base_addr + S3C2410_ADCDAT1);
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN))
&& (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) { //如果触摸屏按下
if (count != 0) {
long tmp;
tmp = xp; //我猜测MINI2440 的触摸屏以横屏为正方向
xp = yp; //所以这里需要交换xy
yp = tmp;
xp >>= 2; //取算术平均值
yp >>= 2;
/* 调试信息 */
#ifdef CONFIG_TOUCHSCREEN_MY2440_DEBUG
struct timeval tv;
do_gettimeofday(&tv);
printk(KERN_DEBUG “T: %06d, X: %03ld, Y: %03ldn”,
(int) tv.tv_usec, xp, yp);
#endif
/* 上报坐标值 */
input_report_abs(dev, ABS_X, xp);
input_report_abs(dev, ABS_Y, yp);
/* 上报触摸状态为按下,压力为1 */
input_report_key(dev, BTN_TOUCH, 1);
input_report_abs(dev, ABS_PRESSURE, 1);
input_sync(dev);
}
xp = 0;
yp = 0;
count = 0;
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
base_addr + S3C2410_ADCTSC); //关闭XP上拉,开启xy自动转换
iowrite32(ioread32(base_addr + S3C2410_ADCCON) |
S3C2410_ADCCON_ENABLE_START,
base_addr + S3C2410_ADCCON); //启动ADC
} else {
count = 0;
/* 上报触摸状态为抬起,压力为0 */
input_report_key(dev, BTN_TOUCH, 0);
input_report_abs(dev, ABS_PRESSURE, 0);
input_sync(dev);
iowrite32(WAIT4INT(0), base_addr + S3C2410_ADCTSC);
if (OwnADC) {
OwnADC = 0;
up(&ADC_LOCK);
}
}
}
static struct timer_list touch_timer =
TIMER_INITIALIZER(touch_timer_fire, 0, 0);
/* 触摸中断处理函数 */
static irqreturn_t stylus_updown(int irq, void *dev_id)
{ unsigned long data0;
unsigned long data1;
int updown;
if (down_trylock(&ADC_LOCK) == 0) { //获取信号量
OwnADC = 1; //标记获得ADC
data0 = ioread32(base_addr + S3C2410_ADCDAT0);
data1 = ioread32(base_addr + S3C2410_ADCDAT1);
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN)); //通过DAT寄存器15位,判断是否真的按下了,按下时为1
if (updown) { //如果按下
touch_timer_fire(0);
} else {
OwnADC = 0;
up(&ADC_LOCK); //释放信号量
}
}
return IRQ_HANDLED;
}
/* ADC中断处理函数 */
static irqreturn_t stylus_action(int irq, void *dev_id)
{ unsigned long data0;
unsigned long data1;
if (OwnADC) {
data0 = ioread32(base_addr + S3C2410_ADCDAT0);
data1 = ioread32(base_addr + S3C2410_ADCDAT1);
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK; //data0 & 0x3ff 取出x坐标值
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
count++;
if (count < (1 << 2)) { //count < 4 采样次数少于4次,再次启动ADC采样
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST,
base_addr + S3C2410_ADCTSC);
iowrite32(ioread32(base_addr + S3C2410_ADCCON) |
S3C2410_ADCCON_ENABLE_START,
base_addr + S3C2410_ADCCON);
} else {
mod_timer(&touch_timer, jiffies + 1);
iowrite32(WAIT4INT(1), base_addr + S3C2410_ADCTSC); //进入等待中断模式,设置为等待Pen up
}
}
return IRQ_HANDLED;
}
static struct clk *adc_clock;
static int __init s3c2410ts_init(void)
{ struct input_dev *input_dev;
adc_clock = clk_get(NULL, “adc”);
if (!adc_clock) {
printk(KERN_ERR “failed to get adc clock sourcen”);
return -ENOENT;
}
clk_enable(adc_clock);
base_addr = ioremap(S3C2410_PA_ADC, 0x20);
if (base_addr == NULL) {
printk(KERN_ERR “Failed to remap register blockn”);
return -ENOMEM;
}
/* Configure GPIOs */
s3c2410_ts_connect();
iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),
base_addr + S3C2410_ADCCON);
iowrite32(0xffff, base_addr + S3C2410_ADCDLY); //设置等待中断模式下长生INT_TC等待时间间隔
iowrite32(WAIT4INT(0), base_addr + S3C2410_ADCTSC); //设置等待中断模式,Pen Down 中断
/* Initialise input stuff */
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR
“Unable to allocate the input device !!n”);
return -ENOMEM;
}
dev = input_dev;
/* 设置input设备支持事件类型:同步事件,按键事件,绝对坐标变化 */
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
/* 设置按键类型为触摸 */
dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
/* 设置abs事件参数取值范围 */
input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
/* 设置dev信息 */
dev->name = s3c2410ts_name;
dev->id.bustype = BUS_RS232;
dev->id.vendor = 0xDEAD;
dev->id.product = 0xBEEF;
dev->id.version = S3C2410TSVERSION;
/* 注册irq */
if (request_irq
(IRQ_ADC, stylus_action, IRQF_SHARED | IRQF_SAMPLE_RANDOM,
“s3c2410_action”, dev)) {
printk(KERN_ERR
“s3c2410_ts.c: Could not allocate ts IRQ_ADC !n”);
iounmap(base_addr);
return -EIO;
}
if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,
“s3c2410_action”, dev)) {
printk(KERN_ERR
“s3c2410_ts.c: Could not allocate ts IRQ_TC !n”);
iounmap(base_addr);
return -EIO;
}
printk(KERN_INFO “%s successfully loadedn”, s3c2410ts_name);
/* 注册input设备 */
input_register_device(dev);
return 0;
}
static void __exit s3c2410ts_exit(void)
{ disable_irq(IRQ_ADC);
disable_irq(IRQ_TC);
free_irq(IRQ_TC, dev);
free_irq(IRQ_ADC, dev);
if (adc_clock) {
clk_disable(adc_clock);
clk_put(adc_clock);
adc_clock = NULL;
}
input_unregister_device(dev);
iounmap(base_addr);
}
module_init(s3c2410ts_init);
module_exit(s3c2410ts_exit);