没说程序之前先上图一张,这个图方便理解input子系统下的触摸屏的实现。
关于input子系统的实现在前几篇博客中已经实现了input子系统的按键驱动。
下面开始说触摸屏的驱动程序。
在说之前先结合上面的图说一下几个主要的函数的调用情况。
个中断函数的调用次序:
先是stylus_updown TC中断处理函数,
如果要是压下则调用touch_timer_fire来启动ADC转换,
在ADC转换结束之后调用stylus_action。
程序中前面定义的宏和变量:
/* For ts.dev.id.version */
#define S3C2410TSVERSION 0x0101
/*定义一个WAIT4INT宏,该宏将对ADC触摸屏控制寄存器进行操作
S3C2410_ADCTSC_YM_SEN这些宏都定义在regs-adc.h中*/
#define WAIT4INT(x) (((x)<<8) | \
S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
S3C2410_ADCTSC_XY_PST(3))
/*定义一个AUTOPST宏,将ADC触摸屏控制寄存器设置成自动转换模式*/
#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 *s3c2410ts_name = "s3c2410 TouchScreen";
/*定义一个输入设备来表示我们的触摸屏设备*/
static struct input_dev *dev;
/*用于记录转换后的X坐标值和Y坐标值*/
static long xp;
static long yp;
/*用于计数对触摸屏压下或抬起时模拟输入转换的次数*/
static int count;
/*定义一个外部的信号量ADC_LOCK,因为ADC_LOCK在ADC驱动程序中已申明
这样就能保证ADC资源在ADC驱动和触摸屏驱动中进行互斥访问*/
extern struct semaphore ADC_LOCK;
/*做为一个标签,只有对触摸屏操作后才对X和Y坐标进行转换*/
static int OwnADC = 0;
static void __iomem *base_addr;
老规矩:从init走你
static int __init s3c2410ts_init(void)
{
struct input_dev *input_dev;
/*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,
因为ADC的转换频率跟时钟有关。 系统的一些时钟定义
在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
adc_clock = clk_get(NULL, "adc");
/*普通的错误处理*/
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
/*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/
clk_enable(adc_clock);
/*
将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。
注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,
S3C2410_PA_ADC是ADC控制器的基地址,定义在mach-s3c2410/include/mach/map.h中,
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 */
/*初始化ADC控制寄存器和ADC触摸屏控制寄存器*/
s3c2410_ts_connect();
//不懂这是做什么。
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);
/* Initialise input stuff */
/*给输入设备申请空间,input_allocate_device定义在input.h中*/
input_dev = input_allocate_device();
if (!input_dev) {
printk(KERN_ERR "Unable to allocate the input device !!\n");
return -ENOMEM;
}
/*初始化input子系统的dev结构体*/
dev = input_dev;
/*下面初始化输入设备,即给输入设备结构体input_dev的成员设置值。
evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,
BIT宏实际就是对1进行位操作,定义在linux/bitops.h中*/
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
/*keybit字段用于描述按键的类型,在input.h中定义了很多,这里用BTN_TOUCH类型来表示触摸屏的点击*/
dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);
/*对于触摸屏来说,使用的是绝对坐标系统。这里设置该坐标系统中X和Y坐标的最小值和最大值(0-1023范围)
ABS_X和ABS_Y就表示X坐标和Y坐标,ABS_PRESSURE就表示触摸屏是按下还是抬起状态*/
input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
//ABS_PRESSURE表示触摸屏是按下还是抬起状态。
input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);
/*以下是设置触摸屏输入设备的身份信息,直接在这里写死。
这些信息可以在驱动挂载后在/proc/bus/input/devices中查看到*/
dev->name = s3c2410ts_name;
dev->id.bustype = BUS_RS232;
dev->id.vendor = 0xDEAD;
dev->id.product = 0xBEEF;
dev->id.version = S3C2410TSVERSION;
/* Get irqs */
/*申请ADC中断,AD转换完成后触发。这里使用共享中断IRQF_SHARED是因为该中断号在ADC驱动中也使用了,
最后一个参数1是随便给的一个值,因为如果不给值设为NULL的话,中断就申请不成功*/
if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,
"s3c2410_action", 1)) {
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 */
/*!!!!把触摸屏设备的描述结构体dev注册进input子系统中*/
input_register_device(dev);
return 0;
}
这段函数的主要功能是:启用ADC所需要的时钟、映射IO口、初始化寄存器、申请中断、初始化输入设备、将输入设备注册到输入子系统。同时注意这句话:/*!!!!把触摸屏设备的描述结构体dev注册进input子系统中*/input_register_device(dev);很帮助真正的理解。还有别忘了根本:/*初始化input子系统的dev结构体*/。同时,/*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。 系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/adc_clock = clk_get(NULL, "adc");不跑裸机好多年,时钟感觉距离好远!
exit退出函数:
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设备。
input_unregister_device(dev);
/*释放虚拟地址映射空间*/
iounmap(base_addr);
}
关于先释放input设备,还是先释放地址我觉得还是源程序对。
下面开始说几个中断函数:
/*TC中断的处理函数*/
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
/*用于记录这一次AD转换后的值*/
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);
/*记录这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
if (updown) {
//如果要是压下则调用touch_timer_fire来启动ADC转换。
touch_timer_fire(0);
} else {
/*如果是抬起状态,就结束了这一次的操作,所以就释放ADC资源的占有*/
OwnADC = 0;
up(&ADC_LOCK);
}
}
return IRQ_HANDLED;
}
关键点在这里:if (updown) {//如果要是压下则调用touch_timer_fire来启动ADC转换。touch_timer_fire(0);
touch_timer_fire函数:
//AD转换函数
static void touch_timer_fire(unsigned long data)
{
/*用于记录这一次AD转换后的值*/
unsigned long data0;
unsigned long data1;
/*用于记录触摸屏操作状态是按下还是抬起*/
int updown;
//在未进行判定之前先读取一次状态。
data0 = ioread32(base_addr+S3C2410_ADCDAT0);
data1 = ioread32(base_addr+S3C2410_ADCDAT1);
/*判断这一次对触摸屏是压下还是抬起,该状态保存在数据寄存器的第15位,所以与上S3C2410_ADCDAT0_UPDOWN*/
updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));
//开始判断触摸屏的状态。
if (updown) {
/*如果状态是按下,并且ADC已经转换了就报告事件和数据*/
if (count != 0) {
long tmp;
tmp = xp;
xp = yp;
yp = tmp;
/*取4次平均值*/
xp >>= 2;
yp >>= 2;
/*
上报坐标
报告X,Y的绝对坐标值
*/
input_report_abs(dev, ABS_X, xp);
input_report_abs(dev, ABS_Y, yp);
/*报告触摸屏的状态,1表明触摸屏被按下*/
input_report_key(dev, BTN_TOUCH, 1);
/*报告按键事件,键值为1(代表触摸屏对应的按键被按下)*/
input_report_abs(dev, ABS_PRESSURE, 1);
/*结束报告*/
input_sync(dev);
}
//归零
xp = 0;
yp = 0;
count = 0;
/* 启动AD 转换*/
//设置为自动转换模式
iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
//启动AD转换
iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
} else {
count = 0;
/*报告按键事件,键值为0(代表触摸屏对应的按键被释放)*/
input_report_key(dev, BTN_TOUCH, 0);
/*报告触摸屏的状态,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);
}
}
}
/* 启动AD 转换*///设置为自动转换模式 iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);//启动AD转换 iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
这里先留一个疑问。我们来看最后一个中断函数:
/*ADC中断服务程序,AD转换完成后触发执行*/
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);
/*记录这一次通过AD转换后的X坐标值和Y坐标值,根据数据手册可知,X和Y坐标转换数值
分别保存在数据寄存器0和1的第0-9位,所以这里与上S3C2410_ADCDAT0_XPDATA_MASK就是取0-9位的值*/
xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
/*计数这一次AD转换的次数*/
count++;
//这里说如果转换次数小于4,则重新启动转换。为什么?
if (count < (1<<2)) {
/* 再一次启动AD转换*/
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;
}
到这里整个的input子系统实现触摸屏的主体部分也就结束了。还有好多细节部分和要注意的部分,由于一个脑残的同学让我给他百度论文,思路断了,所以不写了。不过在源码里都有体现了。