s3c2440触摸屏驱动分析(LINUX2.6)(1)

来源:互联网 发布:淘宝查号的二维码 编辑:程序博客网 时间:2024/05/01 04:46

 本人初次写内核方面的文章,关于触摸屏的驱动只是作为一个引子来深入探讨LINUX的INPUT子系统。这边文章主要先研究触摸屏的中断。

以下是对代码linux-2.6.25.8/drivers/input/touchscreen文件夹下的EmbedSky_ts.c程序的分析(关于此内核是天嵌公司ARM9开发板对应的内核)

  这里我们要关注的是以下三个程序段touch_timer_fire,stylus_updown,stylus_action第一个是后两者的子集,换句话说就是后两者分别调用前者的。后两者是在EmbedSky_ts_probe里面事先注册的,代码如下:

   if (request_irq(IRQ_ADC, stylus_action, SA_SAMPLE_RANDOM, "s3c2410_action", ts.dev))
 {
  printk(KERN_ERR "EmbedSky_ts.c: Could not allocate ts IRQ_ADC !/n");
  iounmap(base_addr);
  return -EIO;
 }
 if (request_irq(IRQ_TC, stylus_updown, SA_SAMPLE_RANDOM,
   "s3c2410_action", ts.dev)) {
  printk(KERN_ERR "EmbedSky_ts.c: Could not allocate ts IRQ_TC !/n");
  iounmap(base_addr);
  return -EIO;
 }

由中断号IRQ_ADC,IRQ_TC可以看得出,stylus_action是ADC转换的中断服务程序,stylus_updown是处理触摸的中断服务程序。

当你按下触摸屏的时候,首先会引发中断服务程序stylus_updown,以下是它的代码:

static irqreturn_t stylus_updown(int irq, void *dev_id, struct pt_regs *regs)
{
 unsigned long data0;
 unsigned long data1;
 int updown;

 data1 = readl(base_addr+S3C2410_ADCDAT0);
 data0 = readl(base_addr+S3C2410_ADCDAT1);

 updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

 /* TODO we should never get an interrupt with updown set while
  * the timer is running, but maybe we ought to verify that the
  * timer isn't running anyways. */

 if (updown)
  touch_timer_fire(0);

 return IRQ_HANDLED;
}

data1,data0分别存放着寄存器ADCDAT0,ADCDAT1的值,updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));这个语句判断的就是data0,data1最高位是否为"1"(为0时代表按下触摸屏)。此时updown=1调用touch_timer_fire函数,其代码如下:

static void touch_timer_fire(unsigned long data)
{
   unsigned long data0;
   unsigned long data1;
 int updown;

   data1 = readl(base_addr+S3C2410_ADCDAT0);
   data0 = readl(base_addr+S3C2410_ADCDAT1);

  updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

  if (updown) {
   if (ts.count != 0) {
    ts.xp >>= ts.shift;
    ts.yp >>= ts.shift;

#ifdef CONFIG_TOUCHSCREEN_EmbedSky_DEBUG
    {
     struct timeval tv;
     do_gettimeofday(&tv);
    printk("Touch screen info:"); 
     printk(DEBUG_LVL "T: %06d, X: %03ld, Y: %03ld/n", (int)tv.tv_usec, ts.xp, ts.yp);
    }
#endif

    input_report_abs(ts.dev, ABS_X, ts.xp);
    input_report_abs(ts.dev, ABS_Y, ts.yp);

    input_report_key(ts.dev, BTN_TOUCH, 1);
    input_report_abs(ts.dev, ABS_PRESSURE, 1);
    input_sync(ts.dev);
   }

   ts.xp = 0;
   ts.yp = 0;
   ts.count = 0;

   writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
   writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
  } else {
   ts.count = 0;

   input_report_key(ts.dev, BTN_TOUCH, 0);
   input_report_abs(ts.dev, ABS_PRESSURE, 0);
   input_sync(ts.dev);

   writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);
  }
}

代码比较长,我们一点点来分析。

  由上可知,代码会执行"if (updown)",再加上由于此时ts.count=0,则直接执行以下几行代码后直接跳出,代码如下:

   ts.xp = 0;
   ts.yp = 0;
   ts.count = 0;

   writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
   writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);

重点是在AUTOPST,这样设置后,触摸屏处于自动X/Y位置转换模式,同时S3C2410_ADCCON_ENABLE_START这样设置以后,使AD开始转换数据,每次转换后会产生中断IRQ_ADC,接着就执行中断服务程序stylus_action。以下是它的代码:

 unsigned long data0;
 unsigned long data1;

 data1 = readl(base_addr+S3C2410_ADCDAT0);
 data0 = readl(base_addr+S3C2410_ADCDAT1);

 ts.xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
 ts.yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
 ts.count++;

 if (ts.count < (1<<ts.shift)) {
  writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
  writel(readl(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
 } else {
  mod_timer(&touch_timer, jiffies+1);
  writel(WAIT4INT(1), base_addr+S3C2410_ADCTSC);
 }

 return IRQ_HANDLED;
}
可以看出ts.xp与ts.yp是存放每次累加的变量,累加的次数是ts.count < (1<<ts.shift),当ts.xp与ts.yp两者的填满的时候就会执行ELSE的后两条语句,前一条语句是为了去抖动而设置的,后一条语句是设置为等待抬起中断的。

我们先来分析为什么第一条语句是去抖动的:

假设ADC转换已经使ts.xp与ts.yp两者缓冲器填满后,触摸笔还未抬起,同时定时器到时了。这时会调用touch_timer_fire函数。

updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));由于此时触摸笔处于按下的状态,则updown=1。这时会执行if (updown)里面的语句,又因为ts.count != 0,所以会执行if (ts.count != 0)里面的语句。

ts.xp >>= ts.shift;
ts.yp >>= ts.shift;以上两者都是在求均值。

input_report_abs(ts.dev, ABS_X, ts.xp);
input_report_abs(ts.dev, ABS_Y, ts.yp);

input_report_key(ts.dev, BTN_TOUCH, 1);
input_report_abs(ts.dev, ABS_PRESSURE, 1);
input_sync(ts.dev);
向INPUT子系统汇报相应的事件。说到这里大概明白为什么去抖动了吧。如果你过快抬起笔,或是抖动了,必然会引起 stylus_updown中断服务程序的执行,自己细看里面的代码会发现,程序并不会调用touch_timer_fire。这时就无法完成向INPUT子系统汇报相应事件了,但是只要有定时器在,一定时间后还是会调用touch_timer_fire中断服务程序的。汇报工作完成后,这时会等待笔的抬起中断,只要笔抬起后,同时定时器满后又调用touch_timer_fire程序,此时不做if (updown)里面的语句,而是做else里面的语句。

   ts.count = 0;

   input_report_key(ts.dev, BTN_TOUCH, 0);//通知INPUT子系统触摸屏没有被按下了

   input_report_abs(ts.dev, ABS_PRESSURE, 0);//通知INPUT子系统触摸笔抬起事件
   input_sync(ts.dev);//等待INPUT子系统处理完毕

   writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);//再次等待触摸笔按下的中断

看到这里会发现整个笔按下数据转换和抬起笔的过程了。