现在的位置: 首页 > 综合 > 正文

Android 触摸屏驱动代码分析(ADC 类型触摸屏 CPU:s3c-s5pc100)

2013年08月13日 ⁄ 综合 ⁄ 共 10966字 ⁄ 字号 评论关闭

 

Android 2.1 farsight version for s5pc100

 

 

File Name: s3c-ts.c

1           简介

1.1          本例基于s5pc100开发板,触摸屏与CPU直接使用ADC连接。下次再找个I2C的驱动分析一下(比如:tsc2007.c)。

 

接口如下:

 

1.2          相关寄存器设置请看《S5PC100_UM_REV104.PDF》第1669页,REGISTER DESCRIPTION.

1.3          总体上触摸屏驱动程序的工作流程如下:

 

Step1:在Probe里注册一个Input设备,并注册TSADC Pen Down Interrupt和TSADC EOC (End of conversion) Interrupt。

Step2:触摸笔按下,响应中断,打开AD转换

Step3:响应AD转换结束中断,记录转换结果。

Step4:如果没超过最大连续AD转换次数,再次转换。

Step5:如果超过最大连续AD转换次数,则计算坐标上报事件。打开Timer。

Step6:Timer到期,检查按下状态。

Step7:如果按下打开AD转换,转换结束进入Step3

Step8:如果抬起状态,上报抬起事件。

 

1.4          源代码如下:.

 

2           初始化

static struct platform_driver s3c_ts_driver = {

       .probe          = s3c_ts_probe,

       .remove         = s3c_ts_remove,

       .suspend        = s3c_ts_suspend,

       .resume         = s3c_ts_resume,

       .driver                  = {

                  .owner     = THIS_MODULE,

                   .name       = "s3c-ts",

         },

};

 

static char banner[] __initdata = KERN_INFO "S3C Touchscreen driver, (c) 2008 Samsung Electronics\n";//一个常量信息,放在init.data文件中,内核启动结束后释放。

 

static int __init s3c_ts_init(void)

{

         printk(banner);

         return platform_driver_register(&s3c_ts_driver);//注册一个名为s3c-ts的驱动程序

}

 

static void __exit s3c_ts_exit(void)

{

         platform_driver_unregister(&s3c_ts_driver); //注销一个名为s3c-ts的驱动程序

}

 

module_init(s3c_ts_init);//入口宏

module_exit(s3c_ts_exit);//出口宏

 

MODULE_AUTHOR("Samsung AP");

MODULE_DESCRIPTION("S3C touchscreen driver");

MODULE_LICENSE("GPL");

 

 

3           探测函数s3c_ts_probe

 

3.1          res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

获得资源并保存在res变量中。获得了flags= IORESOURCE_MEM,索引为0的资源。

资源定义在如下文件中:arch\arm\plat-s3c\Dev-ts.c

注意除了ts外很多资源定义在arch\arm\plat-s5pc1xx\devs.c中

/* Touch srcreen */

static struct resource s3c_ts_resource[] = {

      [0] = {

               .start = S3C_PA_ADC,

               .end   = S3C_PA_ADC + SZ_4K - 1,

               .flags = IORESOURCE_MEM,

      },

      [1] = {

               .start = IRQ_PENDN,

               .end   = IRQ_PENDN,

               .flags = IORESOURCE_IRQ,

      },

      [2] = {

               .start = IRQ_ADC,

               .end   = IRQ_ADC,

               .flags = IORESOURCE_IRQ,

      }

     

};

3.2           ts_mem = request_mem_region(res->start, size, pdev->name);

申请内存区域。申请之后,才能开始ioremap()或者ioremap_nocache()来映射,映射后的变量才能使用。

3.3          ts_base = ioremap(res->start, size);

将io映射到变量ts_base,以后访问这个变量相当于访问ts对应的io地址了。

 

3.4          ts_clock = clk_get(&pdev->dev, "adc");

得到adc的时钟。adc时钟定义在arch/arm/plat-s5pc1xx/clock.c中

static struct clk init_clocks[] = {

}, {

               .name                = "adc",

               .id              = -1,

               .parent              = &clk_p,

               .enable              = s5pc1xx_clk_d15_ctrl,

               .ctrlbit      = S5P_CLKGATE_D15_TSADC,

      }, {

 

3.5          s3c_ts_cfg = s3c_ts_get_platdata(&pdev->dev); //得到dev的platdata,如果为空则返回s3c_ts_default_cfg的数据。

 

3.6          寄存器初始化

 

if ((s3c_ts_cfg->presc&0xff) > 0)//cfg是否设置了proscale,

                writel(S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(s3c_ts_cfg->presc&0xFF),\

                                  ts_base+S3C_ADCCON); // 如果设置了,则设置到寄存器

      else

                writel(0, ts_base+S3C_ADCCON);

 

      /* Initialise registers */

      if ((s3c_ts_cfg->delay&0xffff) > 0)//是否设置了延时

                writel(s3c_ts_cfg->delay & 0xffff, ts_base+S3C_ADCDLY);

 

      if (s3c_ts_cfg->resol_bit==12) {//设置AD模式,10bit 或12bit

                switch(s3c_ts_cfg->s3c_adc_con) {

                case ADC_TYPE_2://6410 或s5pc100

                         writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT, ts_base+S3C_ADCCON);

                         break;

 

                case ADC_TYPE_1://2410

                         writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT_1, ts_base+S3C_ADCCON);

                         break;

                        

                default:

                         dev_err(dev, "Touchscreen over this type of AP isn't supported !\n");

                         break;

                }

      }

 

3.7          writel(WAIT4INT(0), ts_base+S3C_ADCTSC); // S3C_ADCTSC 寄存器清零,此时应该不会发生TouchScreen引起的AD转换。

 

3.8          input_dev = input_allocate_device();

使用input子系统的一般流程为:input_allocate_device()申请一个input_dev设备——>初始化该input_dev——>input_register_device()向子系统注册该设备——>中断时input_event()向子系统报告事件

 

3.9          初始化input_dev(即:ts->dev)

初始化的内容各个TS差不多。

注意:BTN_TOUCH 这个事件的相应是必须的。这是Andorid特有的要求。

ts->dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

 

3.10       register_early_suspend(&ts->early_suspend);

注册一个early_suspend函数,这个函数会在power management的时候用到。

 

3.11       ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

获得资源并保存在ts_irq变量中。获得了flags= IORESOURCE_IRQ,索引为0的资源。

资源定义在如下文件中:arch\arm\plat-s3c\Dev-ts.c

(详见本节开头)

 

3.12       ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts);

注册一个中断响应函数。其中:ts_irq->start 等于IRQ_PENDN

此函数在触摸屏按下或者抬起的时候发生。

详见arch/arm/plat-s5pc1xx/ include/plat/irqs.h

#define IRQ_PENDN               S5PC1XX_IRQ_VIC2(24)

#define IRQ_TC                       IRQ_PENDN

 

3.13       ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1);

获得资源并保存在ts_irq变量中。获得了flags= IORESOURCE_IRQ,索引为1的资源。

资源定义在如下文件中:arch\arm\plat-s3c\Dev-ts.c

(详见本节开头)

 

3.14       ret = request_irq(ts_irq->start, stylus_action, IRQF_SAMPLE_RANDOM, "s3c_action", ts);

注册一个中断响应函数。其中:ts_irq->start 等于IRQ_ADC

当触摸屏按下后发生滑动的时候发生。

3.15       ret = input_register_device(ts->dev);

注册一个input 设备

 

4           s3c_ts_remove

s3c_ts_probe的反过程。注销设备,释放资源。

 

5           static irqreturn_t stylus_updown(int irqno, void *param)

5.1          读取按下的状态

data0 = readl(ts_base+S3C_ADCDAT0);

data1 = readl(ts_base+S3C_ADCDAT1);

 

//判断data0,data1最高位是否仍为"0",为“0”表示触摸笔状态保持为down

updown = (!(data0 & S3C_ADCDAT0_UPDOWN)) && (!(data1 & S3C_ADCDAT1_UPDOWN));

#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG

       printk(KERN_INFO "   %c\n", updown ? 'D' : 'U');

#endif

 

如果updown是1表示按下。

 

5.2          判断按下的状态

if (updown)

touch_timer_fire(0);

 

5.3          如果是6410或者s5pc100则,关闭中断(ADCCLRINT 和ADCCLRINTPNDNUP)

if(ts->s3c_adc_con==ADC_TYPE_2) {

__raw_writel(0x0, ts_base+S3C_ADCCLRWK);

__raw_writel(0x0, ts_base+S3C_ADCCLRINT);

}

 

6           static irqreturn_t stylus_action(int irqno, void *param)

unsigned long data0;

unsigned long data1;

 

         data0 = readl(ts_base+S3C_ADCDAT0);//读取数据

         data1 = readl(ts_base+S3C_ADCDAT1);

 

         if(ts->resol_bit==12) {//根据类型设置不同的掩码,将数据保存在yp,xp变量中

#if defined(CONFIG_SMDK6410_REV10) || defined(CONFIG_TOUCHSCREEN_NEW)

         ts->yp += S3C_ADCDAT0_XPDATA_MASK_12BIT - (data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT);

         ts->xp += S3C_ADCDAT1_YPDATA_MASK_12BIT - (data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT);

#else

         ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT;

         ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT;

#endif

         }

         else {//这里是10bit

#if defined(CONFIG_SMDK6410_REV10) || defined(CONFIG_TOUCHSCREEN_NEW)

                   ts->yp += S3C_ADCDAT0_XPDATA_MASK - (data0 & S3C_ADCDAT0_XPDATA_MASK);

                   ts->xp += S3C_ADCDAT1_YPDATA_MASK - (data1 & S3C_ADCDAT1_YPDATA_MASK);

#else

                   ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK;

                   ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK;

#endif      

         }

 

         ts->count++;//累计AD转化次数

         if (ts->count < (1<<ts->shift)) {//如果未到达次数限制,通过设置寄存器开始AD转化

                   writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);

                   writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);

         } else {

#ifdef CONFIG_FB_S3C_INNOLUX430

                            ts->yp = S3C_ADCDAT1_YPDATA_MASK_12BIT * ts->count - ts->yp;

#endif

                   mod_timer(&touch_timer, jiffies+1);//设置一个Timer

                   writel(WAIT4INT(1), ts_base+S3C_ADCTSC);//TS 的AD转化进入等待状态

         }

 

         if(ts->s3c_adc_con==ADC_TYPE_2) {//6410,S5pc100清理中断

                   __raw_writel(0x0, ts_base+S3C_ADCCLRWK);

                 __raw_writel(0x0, ts_base+S3C_ADCCLRINT);

         }

        

         return IRQ_HANDLED;

 

7           static void touch_timer_fire(unsigned long data)

7.1          校准变量

a0=205,a1=-4999,a2=64552724,a3=6326,a4=-12,a5=-37526976,a6=65536;

这是本驱动所用的变量。

Android要求对获得的xy数据进行校准,代码如下:(相关解释见下面备注)

ts->xp=(long) ((a2+(a0*x)+(a1*y))/a6);

    ts->yp=(long) ((a5+(a3*x)+(a4*y))/a6);

 

备注:

Android的校准主要有如下两种方案:

方案一:移植TSLIB,通过TSLIB产生 pointercal 校准参数文件。

方案二:在驱动程序中校准。

校准公式解释如下:

(XL, YL是显示屏坐标,XT, YT是触摸屏坐标,)
XL = XT*A+YT*B+C
YL = XT*D+YT*E+F
由于具体计算是希望是整数运算,所以实际中保存的ABCDEF为整数,而增加一个参数Div
XL = (XT*A+YT*B+C) / Div
YL = (YT*D+YT*E+F) / Div
TSLIB把以上的7个参数 ABCDEF Div 保存在 pointercal 文件中。
不校准的数据: A=1, B=0, C=0, D=0, E=1, F=0, Div=1
A    B    C    D    E    F    Div
-411    37818    -3636780    -51325    39    47065584    65536

7.2          判断是按下或者抬起

data0 = readl(ts_base+S3C_ADCDAT0);

data1 = readl(ts_base+S3C_ADCDAT1);

 

updown = (!(data0 & S3C_ADCDAT0_UPDOWN)) && (!(data1 & S3C_ADCDAT1_UPDOWN));

 

      updown==1表示按下

 

7.3          如果累计AD转换次数大于零,计算坐标并上报事件

x=(int) ts->xp;

y=(int) ts->yp;

 

ts->xp=(long) ((a2+(a0*x)+(a1*y))/a6);//计算校准

ts->yp=(long) ((a5+(a3*x)+(a4*y))/a6);

 

if(ts->xp!=ts->xp_old || ts->yp!=ts->yp_old)//如果和上一次的坐标不同

{

input_report_abs(ts->dev, ABS_X, ts->xp);//上报坐标

input_report_abs(ts->dev, ABS_Y, ts->yp);

input_report_abs(ts->dev, ABS_Z, 0);

input_report_key(ts->dev, BTN_TOUCH, 1);

input_sync(ts->dev);

}

ts->xp_old=ts->xp;//记录新的坐标点,供下次比对

ts->yp_old=ts->yp;

 

7.4          如果累计AD转换次数为零,则开启AD转换

//设置ADC开启转换

writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);

writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);

 

7.5          如果不是按下,上报抬起事件

ts->count = 0;//计数器清空

#ifdef ANDROID_TS

input_report_abs(ts->dev, ABS_X, ts->xp_old);//上报位置

input_report_abs(ts->dev, ABS_Y, ts->yp_old);

input_report_abs(ts->dev, ABS_Z, 0);

#endif

 

input_report_key(ts->dev, BTN_TOUCH, 0);//上报抬起事件

#ifndef ANDROID_TS

input_report_abs(ts->dev, ABS_PRESSURE, 0);

#endif

input_sync(ts->dev);

 

writel(WAIT4INT(0), ts_base+S3C_ADCTSC);//清除ADC中断

 

 

备注:

1.        oversampling_shift 防止采样过度。

2.       关于中断类型的解释如下:

/*

 * These flags used only by the kernel as part of the

 * irq handling routines.

 *

 * IRQF_DISABLED - keep irqs disabled when calling the action handler

 * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator

 * IRQF_SHARED - allow sharing the irq among several devices

 * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur

 * IRQF_TIMER - Flag to mark this interrupt as timer interrupt

 * IRQF_PERCPU - Interrupt is per cpu

 * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing

 * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is

 *                registered first in an shared interrupt is considered for

 *                performance reasons)

 */

#define IRQF_DISABLED            0x00000020

#define IRQF_SAMPLE_RANDOM    0x00000040

#define IRQF_SHARED               0x00000080

#define IRQF_PROBE_SHARED         0x00000100

#define IRQF_TIMER                  0x00000200

#define IRQF_PERCPU                0x00000400

#define IRQF_NOBALANCING  0x00000800

#define IRQF_IRQPOLL               0x00001000

3.       adc type defination

File name: arch\arm\plat-s3c\include\plat\ts.h

enum s3c_adc_type {

ADC_TYPE_0,

ADC_TYPE_1, //S3C2416, S3C2450

ADC_TYPE_2, //S3C64XX, S5PC1XX

};

 

4.       s3c_ts_mach_info type defination

 

//用于设置触摸屏信息

struct s3c_ts_mach_info {

int delay; //AD转换延时

int presc; //分频

int oversampling_shift; // 采样的数据

int resol_bit; //转换精度

enum s3c_adc_type s3c_adc_con;

};

 

struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = {

                .delay =            10000,

                .presc =            49,

                .oversampling_shift =     2,

                   .resol_bit =               10

};

 

//采集触摸屏信息

struct s3c_ts_info {

struct input_dev *dev;

long xp; //x方向位置

long yp; //y方向位置

int count; //累加xp或yp数据的次数

int shift;

char phys[32];

int resol_bit; //转换精度

enum s3c_adc_type s3c_adc_con;

};

 

抱歉!评论已关闭.