static struct clk *adc_clock;
static int __init s3c2410ts_init(void)
{
struct input_dev *input_dev;
/*获得ADC时钟*/
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);/*使能时钟*/
/*将ADC的IO端口占用的这段IO空间映射到
*内存的虚拟地址,S3C2410_PA_ADC(0x58000000)是
*ADC控制器的基地址,0x20是虚拟地址长度大小 */
base_addr=ioremap(S3C2410_PA_ADC,0x20);
if (base_addr == NULL) {
printk(KERN_ERR "Failed to remap register block\n");
return -ENOMEM;
}
/* Configure GPIOs */
s3c2410_ts_connect();
/*A/D 转换器预分频器使能预分频值 255+1=256*/
iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\
base_addr+S3C2410_ADCCON);
/*ADC启动延时寄存器,延时值*/
iowrite32(0xffff, base_addr+S3C2410_ADCDLY);
/*bit[8]:0 检测笔尖落下中断信号
*bit[7]: 1 = YM输出驱动器使能
*bit[6]: 1 = YP输出驱动器使能
*bit[5]: 0 = XM输出驱动器禁止
*bit[4]: 1 = XP输出驱动器使能
*bit[1:0]:3 = 等待中断模式*/
iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
/* 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字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,
BIT宏实际就是对1进行位操作*/
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);/*X绝对位移事件*/
input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);/*压力事件*/
dev->name = s3c2410ts_name;/*设备名称*/
/*id结构中这几个成员是输入子系统中
*用于判断处理器是否能支持该设备,
*event处理器能支持所有设备,所以这里
*不设置也无所谓(默认为0)*/
dev->id.bustype = BUS_RS232;/*总线类型*/
dev->id.vendor = 0xDEAD;/*经销商ID号*/
dev->id.product = 0xBEEF; /*产品ID号*/
dev->id.version = S3C2410TSVERSION; /*版本ID号*/
/* Get irqs */
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 loaded\n", s3c2410ts_name);
/* All went ok, so register to the input system */
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);
}
在驱动加载部分,主要做的事情是:启用ADC所需要的时钟、映射IO口、初始化寄存器、申请中断、初始化输入设备、将输入设备注册到输入子系统。设置触摸屏为等待笔尖落下的模式。
当笔尖落下时产生触摸屏中断。
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
int updown;/*触摸笔的状态是按下还是抬起*/
/*ADC资源可以获取,即上锁*/
if (down_trylock(&ADC_LOCK) == 0) {
OwnADC = 1;
/*base_addr是ADC映射到内存空间的虚拟基地址
*加上S3C2410_ADCDAT0就是寄存器ADCDAT0的虚拟
*地址,下面两句的作用读取寄存器的状态*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*判断触摸笔的状态,只要读取寄存器ADCDAT0/1
*的bit[15]:1表示抬起,0为按下 */
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {/*为1表示触摸笔按下状态*/
touch_timer_fire(0);/*调用touch_timer_fire函数来启动ADC转换*/
} else {
/*如果是抬起状态,就结束了这一
*次的操作,所以就释放ADC资源的占有*/
OwnADC = 0;
up(&ADC_LOCK);
}
}
return IRQ_HANDLED;
}
进入中断处理函数先上锁获得ADC资源使用权,读取寄存器的值判断此时笔尖是否真的处于落下状态。如果是就调用定时器处理函数启动ADC进行XY坐标的转换。否则就释放ADC资源
touch_timer_fire定时器处理函数
static void touch_timer_fire(unsigned long data)
{
unsigned long data0;
unsigned long data1;
int updown;
/*读取AD转换后的状态值*/
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);
/*启动ADC转换*/
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);
/*如果触摸屏抬起释放ADC资源的占有*/
if (OwnADC) {
OwnADC = 0;
up(&ADC_LOCK);
}
}
}
读取寄存器的值判断此时笔尖是否处于落下状态且ADC转换已经完成。如果是就向内核笔尖落下和xy绝对位移值上报事件;如果处于落下状态但ADC未启动则启动ADC转换;如果笔尖处于抬起状态就上报笔记抬起事件。
当ADC转换完成后产生ADC中断,进入ADC中断服务程序
static irqreturn_t stylus_action(int irq, void *dev_id)
{
unsigned long data0;
unsigned long data1;
if (OwnADC) {/*读取这一次AD转换后的坐标值*/
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;/*10AD,与上0x3FF取出寄存器低10位数据*/
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
count++;/*AD转换计数加1*/
/*计数小于4,再次启动ADC转换*/
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 {
/*否则,启动1个时间滴答的定时器,
*这时就会去执行定时器服务程序
*上报事件和数据
*/
mod_timer(&touch_timer, jiffies+1);
/*设置为等待笔尖抬起*/
iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
}
}
return IRQ_HANDLED;
}
完成4次ADC转换结果的读取后,启动定时器并关闭ADC的转换。定时时间到执行定时器处理函数
总结转换这个的过程
1.入口设置触摸屏为等待中断模式,等待触摸笔被按下
2.如果触摸笔按下则进入触摸屏中断处理函数stylus_updown。获取获取ADC_LOCK后判断触摸屏状态为按下,则调用touch_timer_fire启动ADC转换。
3.当ADC转换启动后,触发ADC中断即进入adc_irq,如果这一次转换的次数小于4,则重新启动ADC进行转换,如果4次完毕后,启动1个时间滴答的定时器,停止ADC转换,设置触摸屏为等待笔尖抬起。
4.如果1个时间滴答到来则进入定时器服务程序touch_timer_fire,判断触摸屏仍然处于按下状态则上报事件和转换的数据。然后将原来读取的数据清楚并重启ADC转换,重复第(2)步(这一步处理笔尖长按和滑动的情况)
5.如果触摸抬起了,则上报释放事件,并将触摸屏重新设置为等待中断状态。
补充:
id结构中这几个成员是输入子系统中 用于判断处理器是否能支持该设备, event处理器能支持所有设备,所以这里
不设置也无所谓(默认为0) 。下面是有设置和没设置的实验截图。cat proc/bus/input/devices 可以看到相关的设置
dev->name = s3c2410ts_name;/*设备名称*/
dev->id.bustype = BUS_RS232;/*总线类型*/
dev->id.vendor = 0xDEAD;/*经销商ID号*/
dev->id.product = 0xBEEF; /*产品ID号*/
dev->id.version = S3C2410TSVERSION; /*版本ID号*/
有设置时与源码一一对应
问:为什么可以支持所有输入设备呢?
要回答这个问题,我们得先回去复习一下输入子系统中事件处理器与设备是怎样匹配的。先来看看匹配函数
static const struct input_device_id *input_match_device(const struct input_device_id *id,
struct input_dev *dev)
{
int i;
for (; id->flags || id->driver_info; id++) {
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
return NULL;
}
可以看到如果时间处理器中设置flag的,就会逐一比较输入设备中设置的id结构成员bustype、vendor、product、
version值是否跟时间处理器input_device_id结构中对应成员的值相同。只要其中有一个不同就比较下一个处理器的
接下来我们再来看看evdev_handler处理器能支持什么什么设备。在drivers/input/evdev.c源码中有这么一段,对照上面的匹配函数我们很容易知道evdev_handler处理器能支持所有输入设备。因此输入设备中我们不设置bustype、vendor、product、version也行。
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
使用tslib库进行测试,参考