一、输入子系统情景回忆ING......
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。下面用图形来描述一下这三者的关系吧!

二、S3C2440触摸屏接口预热ING......
S3C2440提供的触摸屏接口有4种处理模式,分别是:正常转换模式、单独的X/Y位置转换模式、自动X/Y位置转换模式
和等待中断模式,对于在每种模式下工作的要求,请详细查看数据手册的描述。本驱动实例将采用自动X/Y位置转换
模式和等待中断模式。
三、tq2440_ts.c源码分析
1.入口函数,为了方便分析,清晰架构,这里省去了返回值的判断,最详细的还请参考后面的源码。
- static int __init tq2440ts_init(void)
- {
- struct input_dev *input_dev;
-
-
-
-
- adc_clock = clk_get(NULL, "adc");
- clk_enable(adc_clock);
-
-
- base_addr=ioremap(S3C2410_PA_ADC,0x20);
-
-
- iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON);
-
-
- iowrite32(0xffff, base_addr+S3C2410_ADCDLY);
-
-
- iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
-
-
- input_dev = input_allocate_device();
-
-
- dev = input_dev;
-
-
- dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
-
-
- dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
-
-
- 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->name = tq2440ts_name;
- dev->id.bustype = BUS_RS232;
- dev->id.vendor = 0xDEAD;
- dev->id.product = 0xBEEF;
- dev->id.version = S3C2410TSVERSION;
-
-
-
-
- request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, tq2440ts_name, dev)
- request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, tq2440ts_name, dev)
-
- printk(KERN_INFO "%s successfully loaded\n", tq2440ts_name);
-
-
- input_register_device(dev);
- return 0;
- }
驱动加载函数tq2440ts_init主要做了以下一些事情:获取ADC时钟,使能ADC时钟,映射ADC的IO地址,使能预分频、设ADCDLY寄存器,进入等待按下中断模式,分配一个input_dev结构体,初始化input_dev结构体成员,如:支持哪类事件、支持这类事件的哪些事件,申请ADC、TC中断,最后注册一个触摸屏输入设备。万事具备,只欠东风。加载函数准备好一切条件后,就当你触摸触摸屏了,当触摸屏被触摸后,即被按下后,会进入触摸屏中断处理函数stylus_updown
-
- 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;
-
-
- data0 = ioread32(base_addr+S3C2410_ADCDAT0);
- data1 = ioread32(base_addr+S3C2410_ADCDAT1);
- updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
-
- if (updown)
- {
-
-
-
- touch_timer_fire(0);
- }
- else
- {
-
- OwnADC = 0;
- up(&ADC_LOCK);
- }
- }
- return IRQ_HANDLED;
- }
stylus_updown函数首先获得ADC资源,因为在ADC驱动里也有可能使用了ADC资源,然后获得触摸屏状态,判断触摸屏是被按下还是被抬起,如果是被按下,那么调用touch_timer_fire函数启动ADC转换;如果是抬起状态,就结束这次的操作,并释放ADC资源的占用。
touch_timer_fire函数分析
- 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;
- xp = yp;
- yp = tmp;
-
-
- xp >>= 2;
- yp >>= 2;
-
-
- input_report_abs(dev, ABS_X, xp);
- input_report_abs(dev, ABS_Y, yp);
-
-
- 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);
-
-
- iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
- }
- else
- {
-
- count = 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);
- }
- }
- }
touch_timer_fire
函数首先读出触摸屏状态,如果是按下状态,并且ADC已经转换,就报告事件和数据;如果是按下状态,但ADC还没开始转换,就启动ADC转换;如果是抬起状态,报告事件后,将触摸屏重设置为等待按下中断模式;如果触摸屏抬起,意味着这一次的操作结束,所以应该释放ADC资源的占用。
当ADC转换完成后触发ADC中断,就会进入ADC中断处理函数stylus_action
-
- 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;
- yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
-
-
- count++;
-
- if (count < (1<<2))
- {
-
- 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);
- }
- }
- return IRQ_HANDLED;
- }
在stylus_action函数里,首先读取ADC转换后的数据,判断是否连续转换够4次,如果没有则重新启动ADC转换;否则,启动1个时间滴答的定时器,这就会去执行touch_timer_fire定时器超时函数的上报事件和数据。
在本驱动的定时器的定义和初始化与以往不一样,这里使用了简易的方法
-
- static struct timer_list touch_timer =
- TIMER_INITIALIZER(touch_timer_fire, 0, 0);
在这里总结一下触摸的工作流程:
(1)如果触摸屏感觉到触摸,则触发触摸屏中断即进入stylus_updown,获取ADC_LOCK后判断触摸屏状态为按下,
则调用touch_timer_fire启动ADC转换;
(2)当ADC转换启动后,触发ADC中断即进入stylus_action,如果这一次转换的次数小于4,则重新启动ADC进行转换,
如果4次完毕后,启动1个时间滴答的定时器,停止ADC转换,也就是说在这个时间滴答内,ADC转换是停止的;
(3)这里为什么要在1个时间滴答到来之前停止ADC的转换呢?这是为了防止屏幕抖动。
(4)如果1个时间滴答到来则进入定时器服务程序touch_timer_fire,判断触摸屏仍然处于按下状态则上报事件和
转换的数据,并重启ADC转换,重复第(2)步;
(5)如果触摸抬起了,则上报释放事件,并将触摸屏重新设置为等待中断状态。
触摸屏驱动参考源码
- #include <linux/errno.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/input.h>
- #include <linux/init.h>
- #include <linux/serio.h>
- #include <linux/delay.h>
- #include <linux/platform_device.h>
- #include <linux/clk.h>
- #include <asm/io.h>
- #include <asm/irq.h>
-
- #include <plat/regs-adc.h>
- #include <mach/regs-gpio.h>
-
-
- #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))
-
- static char *tq2440ts_name = "TQ2440 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 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;
- xp = yp;
- yp = tmp;
-
-
- xp >>= 2;
- yp >>= 2;
-
-
- input_report_abs(dev, ABS_X, xp);
- input_report_abs(dev, ABS_Y, yp);
-
-
- 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);
-
-
- iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
- }
- else
- {
-
- count = 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;
-
-
- data0 = ioread32(base_addr+S3C2410_ADCDAT0);
- data1 = ioread32(base_addr+S3C2410_ADCDAT1);
- updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
-
- if (updown)
- {
-
-
-
- touch_timer_fire(0);
- }
- else
- {
-
- OwnADC = 0;
- up(&ADC_LOCK);
- }
- }
- return IRQ_HANDLED;
- }
-
-
-
- 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;
- yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
-
-
- count++;
-
- if (count < (1<<2))
- {
-
- 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);
- }
- }
- return IRQ_HANDLED;
- }
-
- static struct clk *adc_clock;
-
- static int __init tq2440ts_init(void)
- {
- struct input_dev *input_dev;
-
-
-
-
- adc_clock = clk_get(NULL, "adc");
- if (!adc_clock)
- {
- printk(KERN_ERR "failed to get adc clock source\n");
- return -ENOENT;
- }
- clk_enable(adc_clock);
-
-
- base_addr=ioremap(S3C2410_PA_ADC,0x20);
- if (base_addr == NULL)
- {
- printk(KERN_ERR "Failed to remap register block\n");
- return -ENOMEM;
- }
-
-
-
-
-
- iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON);
-
-
- iowrite32(0xffff, base_addr+S3C2410_ADCDLY);
-
-
- iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
-
-
- input_dev = input_allocate_device();
-
- if (!input_dev)
- {
- printk(KERN_ERR "Unable to allocate the input device !!\n");
- return -ENOMEM;
- }
-
-
- dev = input_dev;
-
-
- dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
-
-
- dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
-
-
- 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->name = tq2440ts_name;
- dev->id.bustype = BUS_RS232;
- dev->id.vendor = 0xDEAD;
- dev->id.product = 0xBEEF;
- dev->id.version = S3C2410TSVERSION;
-
-
-
-
-
- if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, tq2440ts_name, dev))
- {
- printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");
- iounmap(base_addr);
- return -EIO;
- }
- if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, tq2440ts_name, dev))
- {
- printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");
- iounmap(base_addr);
- return -EIO;
- }
-
- printk(KERN_INFO "%s successfully loaded\n", tq2440ts_name);
-
-
- input_register_device(dev);
-
- return 0;
- }
-
- static void __exit tq2440ts_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(tq2440ts_init);
- module_exit(tq2440ts_exit);
测试,使用tslib库来测试,关于如何测试请参考韦东山视频的“第16课第3节 触摸屏驱动程序之使用TSLIB测试_P”,在这里再说显得有点多余。
2014-01-20补充
- [WJ2440]# cat proc/bus/input/devices
- I: Bus=0013 Vendor=dead Product=beef Version=0101
- N: Name="TQ2440 TouchScreen"
- P: Phys=
- S: Sysfs=/devices/virtual/input/input0
- U: Uniq=
- H: Handlers=event0
- B: EV=b
- B: KEY=0
- B: ABS=1000003
与源码里的信息是一致的
- dev->name = tq2440ts_name;
- dev->id.bustype = BUS_RS232;
- dev->id.vendor = 0xDEAD;
- dev->id.product = 0xBEEF;
- dev->id.version = S3C2410TSVERSION;