天天看点

mini2440触摸屏驱动分析

1.s3c2440触摸屏工作模式

  1.1进入分离的X/Y方向转换模式,会自动先转换x坐标产生中断,再转换y坐标产生中断,并将转换结果分别存入ADCDAT0和ADCDAT1两个寄存器中

  1.2进入自动X/Y方向转换模式,顺序转换xy坐标并将转换结果分别存入ADCDAT0和ADCDAT1两个寄存器中,然后产生一个中断表明转换结束。

  1.3等待中断模式(设置寄存器ADCTSC=0xd3即可)

  1.4待机模式

2.触摸屏使用过程:    2.1初始化设置触摸屏为等待中断模式(等待触摸笔落下)。按下产生中断(IRQ_TC)    2.2在触摸屏中断处理函数里启动ADC转换xy坐标值      2.3ADC转换结束产生ADC中断    2.4在ADC中断处理函数里上报事件,启动定时器    2.5如果定时时间到,再次启动ADC转换xy坐标,处理长按滑动的情况    2.6松开 优化措施: 1.设置ADCDLY为最大值,让电压稳定之后再发出IRQ中断 2.如果ADC完成时判断触摸笔是否松开,如果是则丢弃本次结果 3.多次测量求平均值 4.使用定时器处理长按滑动的情况

下图是电压测量的原理。图中XP、XM、YP、YM接地接3.3v或不接就可以通过设置ADC的寄存器某些位来实现。

mini2440触摸屏驱动分析

3.回顾输入子系统

mini2440触摸屏驱动分析

我们看到输入子系统体系结构大概包括三部分: drivers、input core 、handlers,其中 input core、handlers是由内核来实现的,程序员要做的就是利用 input core提供的接口函数来实现drivers。下面我们来具体说一下这三层的功能: 驱动层(drivers):将底层的硬件输入转化为统一事件形式,向输入核心层(input core)汇报。 输入核心层(input core):为驱动层提供输入设备注册与操作接口,如 input_register_device;通知事件处理层对事件进行处理;在 /proc下产生相应的设备信息; 事件处理层(handlers):主要作用是和用户空间交互,我们知道 linux在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供 fops接口,以及在/dev下生成相应的设备文件节点,而在输入子系统中,这些工作由事件处理层来完成。

上图展示了输入子系统的操作。此子系统包括一前一后运行的两类驱动:输入事件(event)驱动和输入设备(device)驱动。在子系统中,事件驱动是标准的,对所有的输入类都是可以用的 (接口) ,所以你更可能的是实现输入设备驱动,对各硬件寄存器的操作和提交的输入事件。输入事件(event)驱动负责和应用程序的接口,而输入设备(device)驱动负责和底层输入设备的通信。 问:怎么写符合输入子系统框架的驱动程序? 答: 3.1 分配一个input_dev结构体      input_allocate_device(); 3.2 设置      设置支持什么类型事件      设置该类事件中的哪些事件 3.3 注册      input_register_device() 3.4 硬件相关的代码,比如在中断服务程序里上报事件 4. s3c2410_ts.c触摸屏驱动分析 看驱动先从入口函数开始。

static struct clk *adc_clock;  
static int __init s3c2410ts_init(void)  
{  
    struct input_dev *input_dev;  
        /*获得ADC时钟*/  
    adc_clock = clk_get(NULL, "adc");  
    if (!adc_clock) {  
        printk(KERN_ERR "failed to get adc clock source\n");  
        return -ENOENT;  
    }  
    clk_enable(adc_clock);/*使能时钟*/  
  
      /*将ADC的IO端口占用的这段IO空间映射到  
       *内存的虚拟地址,S3C2410_PA_ADC(0x58000000)是  
       *ADC控制器的基地址,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 */  
    s3c2410_ts_connect();  
  
    /*A/D 转换器预分频器使能预分频值 255+1=256*/  
    iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),\  
             base_addr+S3C2410_ADCCON);  
    /*ADC启动延时寄存器,延时值*/  
    iowrite32(0xffff,  base_addr+S3C2410_ADCDLY);  
      
    /*bit[8]:0 检测笔尖落下中断信号  
        *bit[7]:   1 = YM输出驱动器使能  
        *bit[6]:   1 = YP输出驱动器使能  
        *bit[5]:   0 = XM输出驱动器禁止  
        *bit[4]:   1 = XP输出驱动器使能  
        *bit[1:0]:3 = 等待中断模式*/  
    iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);  
  
    /* Initialise input stuff */  
    input_dev = input_allocate_device();/*申请*/  
  
    if (!input_dev) {  
        printk(KERN_ERR "Unable to allocate the input device !!\n");  
        return -ENOMEM;  
    }  
  
    dev = input_dev;  
    /*即给输入设备结构体input_dev的成员设置值。   
    evbit字段用于描述支持的事件,这里支持同步事件、按键事件、绝对坐标事件,   
    BIT宏实际就是对1进行位操作*/   
    dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);  
    /*支持按键类中的触摸屏点击*/  
    dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);    
    input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);/*X绝对位移事件*/  
    input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);  
    input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);/*压力事件*/  
  
    dev->name = s3c2410ts_name;/*设备名称*/  
    /*id结构中这几个成员是输入子系统中  
    *用于判断处理器是否能支持该设备,  
    *event处理器能支持所有设备,所以这里  
    *不设置也无所谓(默认为0)*/  
    dev->id.bustype = BUS_RS232;/*总线类型*/    
    dev->id.vendor = 0xDEAD;/*经销商ID号*/    
    dev->id.product = 0xBEEF; /*产品ID号*/   
    dev->id.version = S3C2410TSVERSION; /*版本ID号*/    
     
    /* Get irqs */  
    if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM,  
        "s3c2410_action", 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, 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 */  
    input_register_device(dev);/*注册*/   
    return 0;  
}  
  
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_unregister_device(dev);  
    iounmap(base_addr);  
}  
      

在驱动加载部分,主要做的事情是:启用ADC所需要的时钟、映射IO口、初始化寄存器、申请中断、初始化输入设备、将输入设备注册到输入子系统。设置触摸屏为等待笔尖落下的模式。 当笔尖落下时产生触摸屏中断。

static irqreturn_t stylus_updown(int irq, void *dev_id)  
{  
    unsigned long data0;  
    unsigned long data1;  
    int updown;/*触摸笔的状态是按下还是抬起*/  
         /*ADC资源可以获取,即上锁*/  
    if (down_trylock(&ADC_LOCK) == 0) {  
        OwnADC = 1;  
        /*base_addr是ADC映射到内存空间的虚拟基地址  
         *加上S3C2410_ADCDAT0就是寄存器ADCDAT0的虚拟  
         *地址,下面两句的作用读取寄存器的状态*/  
        data0 = ioread32(base_addr+S3C2410_ADCDAT0);  
        data1 = ioread32(base_addr+S3C2410_ADCDAT1);  
        /*判断触摸笔的状态,只要读取寄存器ADCDAT0/1  
        *的bit[15]:1表示抬起,0为按下  */  
        updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));  
  
        if (updown) {/*为1表示触摸笔按下状态*/  
            touch_timer_fire(0);/*调用touch_timer_fire函数来启动ADC转换*/  
        } else {  
            /*如果是抬起状态,就结束了这一  
              *次的操作,所以就释放ADC资源的占有*/   
            OwnADC = 0;  
            up(&ADC_LOCK);  
        }  
    }  
  
    return IRQ_HANDLED;  
}  
  
           

进入中断处理函数先上锁获得ADC资源使用权,读取寄存器的值判断此时笔尖是否真的处于落下状态。如果是就调用定时器处理函数启动ADC进行XY坐标的转换。否则就释放ADC资源     touch_timer_fire定时器处理函数

static void touch_timer_fire(unsigned long data)  
{  
    unsigned long data0;  
    unsigned long data1;  
    int updown;  
        /*读取AD转换后的状态值*/  
    data0 = ioread32(base_addr+S3C2410_ADCDAT0);  
    data1 = ioread32(base_addr+S3C2410_ADCDAT1);  
        /*判断触摸笔此时的状态*/  
    updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));  
  
    if (updown) {  
        if (count != 0) {  
            long tmp;                                                                                             tmp = xp;  
            xp = yp;  
            yp = tmp;  
                                                                                                   
                        xp >>= 2;  
                        yp >>= 2;  
  
            input_report_abs(dev, ABS_X, xp);/*上报事件*/  
            input_report_abs(dev, ABS_Y, yp);  
  
            input_report_key(dev, BTN_TOUCH, 1);  
            input_report_abs(dev, ABS_PRESSURE, 1);  
            input_sync(dev);  
        }  
        xp = 0;  
        yp = 0;  
        count = 0;  
               /*设置触摸屏的模式为自动转换模式*/  
        iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);  
               /*启动ADC转换*/  
        iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);  
    } else {    /*否则是抬起状态*/  
        count = 0;  
        input_report_key(dev, BTN_TOUCH, 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);  
        }  
    }  
}  
           

读取寄存器的值判断此时笔尖是否处于落下状态且ADC转换已经完成。如果是就向内核笔尖落下和xy绝对位移值上报事件;如果处于落下状态但ADC未启动则启动ADC转换;如果笔尖处于抬起状态就上报笔记抬起事件。 当ADC转换完成后产生ADC中断,进入ADC中断服务程序

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);  
  
        xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;/*10AD,与上0x3FF取出寄存器低10位数据*/  
        yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;  
        count++;/*AD转换计数加1*/  
  
        /*计数小于4,再次启动ADC转换*/  
        if (count < (1<<2)) {  
            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 {  
            /*否则,启动1个时间滴答的定时器,  
            *这时就会去执行定时器服务程序  
            *上报事件和数据  
            */  
            mod_timer(&touch_timer, jiffies+1);  
            /*设置为等待笔尖抬起*/  
            iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);             
        }  
    }  
  
    return IRQ_HANDLED;  
}  
           

完成4次ADC转换结果的读取后,启动定时器并关闭ADC的转换。定时时间到执行定时器处理函数 总结转换这个的过程 1.入口设置触摸屏为等待中断模式,等待触摸笔被按下 2.如果触摸笔按下则进入触摸屏中断处理函数stylus_updown。获取获取ADC_LOCK后判断触摸屏状态为按下,则调用touch_timer_fire启动ADC转换。 3.当ADC转换启动后,触发ADC中断即进入adc_irq,如果这一次转换的次数小于4,则重新启动ADC进行转换,如果4次完毕后,启动1个时间滴答的定时器,停止ADC转换,设置触摸屏为等待笔尖抬起。 4.如果1个时间滴答到来则进入定时器服务程序touch_timer_fire,判断触摸屏仍然处于按下状态则上报事件和转换的数据。然后将原来读取的数据清楚并重启ADC转换,重复第(2)步(这一步处理笔尖长按和滑动的情况) 5.如果触摸抬起了,则上报释放事件,并将触摸屏重新设置为等待中断状态。 补充: id结构中这几个成员是输入子系统中  用于判断处理器是否能支持该设备,  event处理器能支持所有设备,所以这里   不设置也无所谓(默认为0) 。下面是有设置和没设置的实验截图。cat proc/bus/input/devices 可以看到相关的设置

dev->name = s3c2410ts_name;/*设备名称*/
	dev->id.bustype = BUS_RS232;/*总线类型*/  
	dev->id.vendor = 0xDEAD;/*经销商ID号*/  
	dev->id.product = 0xBEEF; /*产品ID号*/ 
	dev->id.version = S3C2410TSVERSION; /*版本ID号*/  
           
mini2440触摸屏驱动分析

有设置时与源码一一对应

mini2440触摸屏驱动分析

问:为什么可以支持所有输入设备呢?      要回答这个问题,我们得先回去复习一下输入子系统中事件处理器与设备是怎样匹配的。先来看看匹配函数

static const struct input_device_id *input_match_device(const struct input_device_id *id,
                                                        struct input_dev *dev)
{
        int i;

        for (; id->flags || id->driver_info; id++) {
                if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
                        if (id->bustype != dev->id.bustype)
                                continue;
                if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
                        if (id->vendor != dev->id.vendor)
                                continue;
                if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
                        if (id->product != dev->id.product)
                                continue;
                if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
                        if (id->version != dev->id.version)
                                continue;

                MATCH_BIT(evbit,  EV_MAX);
                MATCH_BIT(keybit, KEY_MAX);
                MATCH_BIT(relbit, REL_MAX);
                MATCH_BIT(absbit, ABS_MAX);
                MATCH_BIT(mscbit, MSC_MAX);
                MATCH_BIT(ledbit, LED_MAX);
                MATCH_BIT(sndbit, SND_MAX);
                MATCH_BIT(ffbit,  FF_MAX);
                MATCH_BIT(swbit,  SW_MAX);
                return id;
        }
        return NULL;
}
           

   可以看到如果时间处理器中设置flag的,就会逐一比较输入设备中设置的id结构成员bustype、vendor、product、 version值是否跟时间处理器input_device_id结构中对应成员的值相同。只要其中有一个不同就比较下一个处理器的 接下来我们再来看看evdev_handler处理器能支持什么什么设备。在drivers/input/evdev.c源码中有这么一段,对照上面的匹配函数我们很容易知道evdev_handler处理器能支持所有输入设备。因此输入设备中我们不设置bustype、vendor、product、version也行。

static const struct input_device_id evdev_ids[] = {
        { .driver_info = 1 },   /* Matches all devices */
        { },                    /* Terminating zero entry */
};
           

使用tslib库进行测试,参考 http://blog.csdn.net/h_fire/article/details/24317521