没说程序之前先上图一张,这个图方便理解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子系统实现触摸屏的主体部分也就结束了。还有好多细节部分和要注意的部分,由于一个脑残的同学让我给他百度论文,思路断了,所以不写了。不过在源码里都有体现了。