s3c2440的ADC触摸屏驱动——学习笔记

来源:互联网 发布:windows手机版主题 编辑:程序博客网 时间:2024/06/05 16:49

们首先来分析drivers/input/touchsreen/s3c2410_ts.c这个文件:

static struct device_driver s3c2410ts_driver = {
       .name           = "s3c2410-ts",    //在注册这个driver的时候,系统就是以这个结构的.name来查找相同name的设备的。
       .bus            = &platform_bus_type,  
       .probe          = s3c2410ts_probe,
       .remove         = s3c2410ts_remove,
};


int __init s3c2410ts_init(void)
{
return driver_register(&s3c2410ts_driver);
}


void __exit s3c2410ts_exit(void)
{
driver_unregister(&s3c2410ts_driver);
}

(我们可以看出,在模块初始化函数中,直接调用了driver_register(),而不是像往常那样使用平台设备驱动的模型。到底什么时候需要使用这个模型,什么时候不需要呢?而且我们看到很多不是集成在芯片内的设备,也用了平台设备的模型,比如framebuffer,所以我觉得平台设备模型是为了模拟出一条总线,进而调用probe等函数。)


下来要看的当然是探测函数int __init s3c2410ts_probe(struct device *dev)了:
static int __init s3c2410ts_probe(struct device *dev)
{
struct s3c2410_ts_mach_info *info;
info = ( struct s3c2410_ts_mach_info *)dev->platform_data;  //在framebuffer中,也有这个获取platform的方法,但是这个到底是谁赋值的?
      //而且这个struct s3c2410_ts_mach_info *info 结构体的拿来干嘛的?

if (!info)
{
printk(KERN_ERR "Hm... too bad : no platform data for ts\n");
return -EINVAL;
}

#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "Entering s3c2410ts_init\n");
#endif

adc_clock = clk_get(NULL, "adc");
if (!adc_clock) {
printk(KERN_ERR "failed to get adc clock source\n");
return -ENOENT;
}
//clk_use(adc_clock);
clk_enable(adc_clock);

#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
printk(DEBUG_LVL "got and enabled clock\n");
#endif

base_addr=ioremap(S3C2410_PA_ADC,0x20);     //将0x58000000的IO内存映射到自己的地址空间中,跟平台设备驱动相比,少了获取IO内存资源描述  //这一步。
if (base_addr == NULL) {
printk(KERN_ERR "Failed to remap register block\n");
return -ENOMEM;
}


/* Configure GPIOs */
s3c2410_ts_connect(); 


if ((info->presc&0xff) > 0)
writel(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(info->presc&0xFF),\    //设置预分频器的值
    base_addr+S3C2410_ADCCON);
else
writel(0,base_addr+S3C2410_ADCCON);


/* Initialise registers */
if ((info->delay&0xffff) > 0)
writel(info->delay & 0xffff,  base_addr+S3C2410_ADCDLY);   //设置延时时间


writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);    //设置触摸屏为等待中断模式


/* Initialise input stuff */
memset(&ts, 0, sizeof(struct s3c2410ts));           //初始化struct s3c2410ts ts这个结构体
//init_input_dev(&ts.dev);                                  
ts.dev = input_allocate_device();     //生成并初始化一个输入设备,至于为什么要生成一个输入设备,可以先看input子系统相关的知识。
ts.dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
ts.dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT(BTN_TOUCH);
input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0);


sprintf(ts.phys, "ts0");

ts.dev->private = &ts;
ts.dev->name = s3c2410ts_name;
ts.dev->phys = ts.phys;
ts.dev->id.bustype = BUS_RS232;
ts.dev->id.vendor = 0xDEAD;
ts.dev->id.product = 0xBEEF;
ts.dev->id.version = S3C2410TSVERSION;

ts.shift = info->oversampling_shift;

/* Get irqs */
if (request_irq(IRQ_ADC, stylus_action, SA_SAMPLE_RANDOM,
"s3c2410_action", ts.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, SA_SAMPLE_RANDOM,
"s3c2410_action", ts.dev)) {
printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !\n");
iounmap(base_addr);
return -EIO;
}
/*
 *for debug regs
 */
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
unsigned long debug_regs;
debug_regs = 0;
debug_regs = readl(base_addr+S3C2410_ADCCON);
printk("S3C2410_ADCCON:0x%08lx\n",debug_regs);
debug_regs = readl(base_addr+S3C2410_ADCTSC);
printk("S3C2410_ADCTSC:0x%08lx\n",debug_regs);
debug_regs = readl(base_addr+S3C2410_ADCDLY);
printk("S3C2410_ADCDLY:0x%08lx\n",debug_regs);
#endif

printk(KERN_INFO "%s successfully loaded\n", s3c2410ts_name);

/* All went ok, so register to the input system */
input_register_device(ts.dev);

return 0;
}

说明:
我们看到这个结构体:
struct s3c2410_ts_mach_info {
       int             delay;      //要设置的延时
       int             presc;     //要设置的预分频值
       int             oversampling_shift;   //这个值是4,说明要取4次坐标,然后求平均值。
};

s3c2410_ts_connect()这一句主要就是配置4个引脚功能:
static inline void s3c2410_ts_connect(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_XMON);
s3c2410_gpio_cfgpin(S3C2410_GPG13, S3C2410_GPG13_nXPON);
s3c2410_gpio_cfgpin(S3C2410_GPG14, S3C2410_GPG14_YMON);
s3c2410_gpio_cfgpin(S3C2410_GPG15, S3C2410_GPG15_nYPON);
}

也就是
根据下图来配置GPG12~GPG15分别用来读写ADC的XM、XP、YM、YP引脚的值:


有一个全局变量:
struct s3c2410ts {
struct input_dev *dev;   //在跟input子系统打交道要用到的。
long xp;         //要向input子系统报告的X、Y值。
long yp;
int count;     //已经取的坐标次数,当它为4时,才报告。
int shift;       //相当于一个阀值,跟oversampling_shift相同。
char phys[32];
};

static struct s3c2410ts ts;



调用完probe函数后,就处于等中断的过程,下面我们来看两个中断处理函数:
1、在probe中,
request_irq(IRQ_TC, stylus_updown, SA_SAMPLE_RANDOM, "s3c2410_action", ts.dev)) ;
在触摸屏被按下时,会产生IRQ_TC中断,调用下面的函数:

static irqreturn_t stylus_updown(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
int updown;
disable_irq(IRQ_TC);//add by lili 2007-6-19
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = 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);
enable_irq(IRQ_TC);//add by lili 2007-6-19
return IRQ_HANDLED;
}

理解这个中断服务函数,我们先来看一下ADCDAT0跟ADCDAT1这两个寄存器:(ADCDAT1与之相似)。

我们这里用到的,主要就是UPDOWN与XPDATA这两个,分别表示触笔点击/提起状态,X、Y轴坐标值。

然后就是调用touch_timer_fire(0)了:

static void touch_timer_fire(unsigned long data)
{
  unsigned long data0;
  unsigned long data1;
int updown;
int tmp;//add by lili 2006-12-12
  data0 = readl(base_addr+S3C2410_ADCDAT0);
  data1 = 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;
//reset coordinate system add by lili 2006-12-12
#ifdef CONFIG_RESET_COORDINATE_SYSTEM
tmp = ts.xp;
ts.xp = ts.yp;
ts.yp = tmp;
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
 {
 struct timeval tv;
 do_gettimeofday(&tv);
printk("Touch screen info:");//add by lili 2006-12-9
 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);
 }
}

相信看到这里,对寄存器的各种设置我们一定觉得很乱没思路,现在理一遍寄存器的设置与ADC的工作模式:
s3c2440触摸屏接口有4种工作模式:
1、(1号模式我还没用到,用到了再写进来。)

2、等待中断:
这时触摸屏等待信号的到来,当信号到来时,产生INT_TC中断,表示有触摸动作发生。当中断发生后,触摸屏需要转换成其他两种状态来读取触摸点的位置(X,Y)。

3、独立的X/Y位置转换模式:
独立的X/Y位置转换模式分为两种子模式:X位置模式与Y位置模式。X位置模式就是将转换后的X坐标写到ADCDAT0寄存器的XPDATA位中,转换后,触摸屏接口控制器会产生INT_ADC中断。
4、自动X/Y位置转换模式:
该模式下,触摸屏接口控制器自动转换X位置和Y位置,转换后,控制器自动将X、Y坐标写到ADCDAT0与ADCDAT1中,然后产生INT_ADC中断。

寄存器请大家自行对照S3C2440A的手册,这里就不一 一贴出来了。
我们接着看驱动程序是怎么在不同模式之间转换的:

a、在probe函数中,writel(WAIT4INT(0), base_addr+S3C2410_ADCTSC);将模式设置为等待中断模式。
是通过#define WAIT4INT(x)  (((x)<<8) | \
    S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
    S3C2410_ADCTSC_XY_PST(3))
来设置的,也就是说,把ADCTSC设置为0XD3就可以了。

b、调用了probe后,触摸屏就一直处在等待中断模式下,然后当有触摸事件发生后,进入INT_TC的中断服务函数中:
向输入子系统报告当前触摸笔的位置后,就通过下面这句,转换到了4自动转换模式。
writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);

c、上面转换到了自动模式后,触摸屏控制器会自动的将X、Y坐标转换后写入ADCDAT0、ADCDAT1中,并产生INT_ADC中断,进而调用INT_ADC相应的中断处理函数。这样各个模式之间的转换,都比较清晰了吧?

现在我们再来看,INT_ADC的中断服务程序:

static irqreturn_t stylus_action(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned long data0;
unsigned long data1;
disable_irq(IRQ_ADC);//add by lili  2007-06-19
data0 = readl(base_addr+S3C2410_ADCDAT0);
data1 = 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);
}
enable_irq(IRQ_ADC);
return IRQ_HANDLED;
}

说明:

检测如果count<4,主要是为了多次读取触摸屏XY坐标,求平均值。

如果,未到次数,继续启动ADC转换

再次执行stylus_action

如此进行4次

此时,count=4,得到预设次数,执行else后面语句
也就是,修改touch_timer定时器,将其延后一个单位,在下一个定时时刻到来,调用touch_timer指定的函数:
static struct timer_list touch_timer =TIMER_INITIALIZER(touch_timer_fire, 0, 0);
从这里看出,出发的是touch_time_fire这个函数。

同时设置等待手写笔抬起中断。

然后,分两种情况:

(1)如果手写笔还没有抬起,定时器到了,则触发touch_timer_fire函数,执行下面的一段代码:
最要就是求出4次的平均值;向input子系统报告;count等清零;(此时的模式是等待中断模式,等待触摸笔提起)又设置为自动转换模式。
if (updown) {
  if (ts.count != 0) {
  ts.xp >>= ts.shift;  //除以4求四次的平均值。
  ts.yp >>= ts.shift;
//reset coordinate system add by lili 2006-12-12
#ifdef CONFIG_RESET_COORDINATE_SYSTEM
tmp = ts.xp;
ts.xp = ts.yp; 
ts.yp = tmp;
#endif
#ifdef CONFIG_TOUCHSCREEN_S3C2410_DEBUG
  {
  struct timeval tv;
  do_gettimeofday(&tv);
printk("Touch screen info:");//add by lili 2006-12-9
  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);


(2)如果在定时器到之前,手写笔由于抖动,而抬起,那么会触发stylus_updown中断函数,由于此时,updown=0,所以,在此函数中直接返回了。
然后,在定时器到了之后,仍然会触发touch_timer_fire,但是由于updown=0,会执行else后面语句:
就是向输入子系统报告触摸笔提起事件,并回到等待中断模式。
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);
  }



以整个触摸事件的流程画出来是这样的:



ADC的驱动程序到这里就差不多完成了,希望高手如果发现有错误,不吝赐教~


       

















原创粉丝点击