电容屏FT5x06驱动与设计

电容屏FT5x06驱动与设计

说明书

 

 

 

 

 

 

 

拟 制 人:周亦行

核 准 人:

发布单位:

发布时间:

 

 

 

 

 

 

 

 

 

 

 

 

 

版本变更说明

版本

变更说明

作者

日期

V1.00

初始版本

周亦行

2013年7月15日

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


目录

1.      概述... 1-4

2.      硬件原理图... 2-5

2.1.       外部引脚说明... 2-5

2.2.       硬件框图... 2-5

2.3.       原理图... 2-5

3.      软件设计... 3-6

3.1.       FT5x063-6

3.2.       驱动模块处理流程... 3-8

3.3.       驱动源码分析... 3-9

3.3.1.         初始化阶段... 3-9

3.3.2.         数据处理... 3-12

3.3.3.         低功耗... 3-14

3.3.4.         驱动测试方法以及案例... 3-14

4.      总结... 4-16

5.      附录1:input子系统介绍... 5-17

5.1.       概述... 5-17

5.2.       Inputdriver编写要点... 5-18

5.3.       从触摸屏驱动切入的input子系统。... 5-19

5.4.       总结... 5-22

 

1.     概述

当下,触摸式输入方法深受大众喜爱。电容式触摸屏因为其具备准确,不需用力按压,高灵敏度,通透性好等优点而成为了时代的宠儿。

结构组成上,电容式触控屏可以简单地看成是由四层复合屏构成的屏体:最外层是玻璃保护层,接着是导电层,第三层是不导电的玻璃屏,最内的第四层也是导电层。最内导电层是屏蔽层,起到屏蔽内部电气信号的作用,中间的导电层是整个触控屏的关键部分,四个角或四条边上有直接的引线,负责触控点位置的检测。

硬件原理上,电容式触摸屏在触摸屏四边均镀上狭长的电极,在导电体内形成一个低电压交流电场。在触摸屏幕时,由于人体电场,手指与导体层间会形成一个耦合电容,四边电极发出的电流会流向触点,而电流强弱与手指到电极的距离成正比,位于触摸屏幕后的控制器便会计算电流的比例及强弱,准确算出触摸点的位置。电容触摸屏的双玻璃不但能保护导体及感应器,更有效地防止外在环境因素对触摸屏造成影响,就算屏幕沾有污秽、尘埃或油渍,电容式触摸屏依然能准确算出触摸位置。

软件设计上,电容式触摸屏自带IC进行数据处理。采用主从通信方式与主机进行数据交换。本文侧重从数据交换和主机数据处理来深入了解触摸屏的Linux驱动设计。

2.     硬件原理图

2.1.  外部引脚说明

Ø  I2C/SPI: an interface for data exchange with host

Ø  INT: an interrupt signal to inform the host processor that touchdata is ready for read

Ø  WAKE: an interrupt signal for the host to change F5x06 fromHibernate to Active mode

Ø  /RST: an external low signal reset the chip.

目前国内G870D项目中采用了SPI接口,INT输入和/RST输出信号。

2.2.  硬件框图

图一 硬件框图

2.3.  原理图

图二 原理图

       外部信号跟HOST连接情况:

TP外部信号

核心板引脚

软件描述名

TP-INT

POWER_FAIL_int

MX25_PIN_POWER_FAIL

TP-RST

拓展IO13

拓展IO13

I2C1-SDA

I2C1-SDA

I2C1-SDA

I2C1-SCL

I2C1-SCL

I2C1-SCL

       因为触摸屏功耗较小,硬件上没有考虑低功耗问题,所以WAKE引脚不使用。

3.     软件设计

电容式触摸屏Linux多点触摸驱动总的来说只有两种,Type A和Type B。以下为linux kernel文档的介绍:

The protocol isdivided into two types, depending on the capabilities of the hardware. Fordevices handling anonymous contacts (type A), the protocol describes how tosend the raw data for all contacts to the receiver. For devices capable oftracking identifiable contacts (type B), the protocol describes how to sendupdates for individual contacts via event slots.

For type Adevices, the kernel driver should generate an arbitrary enumeration of the fullset of anonymous contacts currently on the surface. The order in which thepackets appear in the event stream is not important.  Event filtering and finger tracking is leftto user space [3].

For type Bdevices, the kernel driver should associate a slot with each identifiedcontact, and use that slot to propagate changes for the contact.Creation,replacement and destruction of contacts is achieved by modifying theABS_MT_TRACKING_ID of the associated slot. A non-negative tracking id is interpreted as a contact, and the value -1denotes an unused slot. A tracking id not previously present is considered new,and a tracking id no longer present is considered removed.  Since only changes are propagated,the fullstate of each initiated contact has to reside in the receiving end.Uponreceiving an MT event, one simply updates the appropriate attribute of thecurrent slot.

-- Multi-touch (MT) Protocol

由于G870D采用的Linux kernel的input子系统支持Type A类型的多点触摸,因而本文涉及到多点触摸地方也只分析Type A。事实上,公司的触摸屏中间件函数只支持单点,而且多点触摸的上报方式跟单点的上报方式不兼容。所以驱动只预留多点触摸功能,使用单点触摸功能。

单点触摸的上报方式非常简单。只需要上报ABS_X,ABS_Y,BTN_TOUCH和ABS_PRESSURE,最后调用input_sync同步即可。

3.1.  FT5x06

本处只列举本驱动使用到的数据,更多信息请参考《FTS_AN_CTPM_Standard_eng.pdf》。

I2C写数据格式:

       I2C读数据设置地址格式:

       I2C读数据格式:

RegisterMap:

3.2.  驱动模块处理流程

在等待列队里面,接收触摸屏数据和上报应用层

 

激活等待列队

 

 

 

 

3.3.  驱动源码分析

3.3.1.                             初始化阶段

G870D的触摸屏使用I2C进行数据通信,而主板的I2C信息一般都在一个i2c_board_info类型结构体填充。i2c_board_info结构体的原形为:

struct i2c_board_info {                                                                                                                     

    char        type[I2C_NAME_SIZE];

    unsigned short  flags;

    unsigned short  addr;

    void        *platform_data;

    struct dev_archdata*archdata;

    int     irq;

};

G870D里面,相应的结构体为mxc_i2c_board_info。以下为触摸屏的填充信息。

{

.type  ="ft5206IIC_ts",

.addr  = 0x38,

.irq   = IOMUX_TO_IRQ(MX25_PIN_POWER_FAIL),

},

上述填充信息指示了,i2c的client名字,从地址和使用的中断资源。从i2c的协议,可知内核是预知从设备的地址的。其实,从地址是i2c从设备程序自身定的地址,可能跟产商协定。完成i2c设备信息填充之后,便可以通过i2c_add_driver建立触摸屏设备驱动。触摸屏使用的i2c_driver结构体为:

static struct i2c_drivermigor_ts_driver = {

   .driver = {

        .name = "ft5206IIC_ts",                                                                                                              

   }, 

   .probe = migor_ts_probe,

   .remove = migor_ts_remove,

   .suspend = migor_ts_suspend,

   .resume = migor_ts_resume,

   .id_table = migor_ts_id,

};

在i2c_add_driver(&migor_ts_driver)的时候,成功找到client之后就会回调migor_ts_probe函数。migor_ts_probe函数会利用找到的client信息完成触摸屏的后期初始化工作。简单介绍一下

static intmigor_ts_probe(struct i2c_client *client, const struct i2c_device_id *idp)

{

    …

input =input_allocate_device();

    if (!input) {

       dev_err(&client->dev, "Failed to allocate inputdevice.\n");

        error = -ENOMEM;

        goto err1;

    }

 

#ifdef CONFIG_FT5X0X_MULTITOUCH                    //支持多点触摸

   input->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY);

    input->evbit[BIT_WORD(EV_ABS)]|= BIT_MASK(EV_ABS);

 

   input->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);

 

input->absbit[BIT_WORD(ABS_MT_POSITION_X)]

                   |=BIT_MASK(ABS_MT_POSITION_X);

input->absbit[BIT_WORD(ABS_MT_POSITION_Y)]

                   |=BIT_MASK(ABS_MT_POSITION_Y);

input->absbit[BIT_WORD(ABS_MT_TOUCH_MAJOR)]|=BIT_MASK(ABS_MT_TOUCH_MAJOR);

input->absbit[BIT_WORD(ABS_MT_WIDTH_MAJOR)]|=BIT_MASK(ABS_MT_WIDTH_MAJOR);

   input->absbit[BIT_WORD(ABS_PRESSURE)] |=BIT_MASK(ABS_PRESSURE);

 

   input_set_abs_params(input,

                ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);

   input_set_abs_params(input,

                ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0);

   input_set_abs_params(input,

                ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);

   input_set_abs_params(input,

                ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);

#else                                                                             //只用单点触屏

   input->evbit[BIT_WORD(EV_KEY)] |= BIT_MASK(EV_KEY);     // 设置支持事件

   input->evbit[BIT_WORD(EV_ABS)] |= BIT_MASK(EV_ABS);

 

   input->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);

 

   input->absbit[BIT_WORD(ABS_X)] |=BIT_MASK(ABS_X);

   input->absbit[BIT_WORD(ABS_Y)] |=BIT_MASK(ABS_Y);                                                                                       

   input->absbit[BIT_WORD(ABS_PRESSURE)] |=BIT_MASK(ABS_PRESSURE);

 

   input_set_abs_params(input, ABS_X, 0, SCREEN_MAX_X, 0, 0);   // 设置分辨率

   input_set_abs_params(input, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);

   input_set_abs_params(input, ABS_PRESSURE, 0, PRESS_MAX, 0 , 0);

#endif

    input->name =client->name;

    input->id.bustype =BUS_I2C;

    input->dev.parent =&client->dev;

 

    input->open =migor_ts_open;

    input->close =migor_ts_close;

 

    input_set_drvdata(input,priv);

 

    priv->client = client;

    priv->input = input;

   INIT_DELAYED_WORK(&priv->work, migor_ts_poscheck);         // 初始化等待列队

    priv->irq =client->irq;

 

    error =input_register_device(input);                                              //注册输入设备

    if (error)

        goto err1;

 

    error=request_irq(priv->irq, migor_ts_isr, IRQF_TRIGGER_FALLING, client->name,priv);

    if (error) {                                                                                   //申请中断

       dev_err(&client->dev, "Unable to request touchscreenIRQ.\n");

        goto err2;

}

#ifdef TS_WAKEUP

    device_init_wakeup(&client->dev, 1);                       // 设置是否支持唤醒低功耗

#endif

}

从上述程序,可见后期的初始化工作主要做了设置input设备支持的事件和注册input设备;初始化等待列队、申请中断和设置触摸屏是否能唤醒低功耗。到此,完成了初始化的工作。

3.3.2.        数据处理

在初始化的工作中,驱动程序已经申请了中断,初始化了等待列队。从驱动模块处理流程图可知,驱动进入等待数据的环节。当用户触摸屏幕的时候,触摸屏检测到有效数据便会通过TP-INT发出中断信号。HOST端接收到中断信号后,便进入中断服务函数激活等待工作列队。这里是考虑到HOST的处理速度比触摸屏IC快,通过等待工作列队可以保证触摸屏IC有足够时间整理数据。但应该注意到等待工作列队的等待时间会影响HOST反映速度,此值应折中处理。在等待列队里,HOST进行数据处理,然后上报应用层。简单介绍一下工作列队,工作列队归根到底是用内核线程实现的。当内核比较忙碌的时候,有可能导致线程响应慢。分析一下,中断处理程序:

static irqreturn_t migor_ts_isr(intirq, void *dev_id)

{

   struct migor_ts_priv *priv = dev_id;

   disable_irq_nosync(irq);                                           //关闭中断

   // 设置响应速度

   schedule_delayed_work(&priv->work, HZ / 100);             // 唤醒等待工作队列

 

   return IRQ_HANDLED;

}

非常简单,关闭中断(防止在工作列队数据处理的过程中再出现TP-INT中断,必须等待处理完成再使能TP-INT中断),唤醒等待工作队列。继续分析工作队列的程序:

static void migor_ts_poscheck(structwork_struct *work)

{

        …

   buf[0] = 0;                                                      //设置register map偏移

   if (i2c_master_send(priv->client, buf, 1) != 1) {

        dev_err(&priv->client->dev,"Unable to write i2c index\n");

        goto out;

   }

              //读取数据

   if (i2c_master_recv(priv->client, buf, sizeof(buf)) != sizeof(buf)) {

        dev_err(&priv->client->dev,"Unable to read i2c page\n");

        goto out;

   }

             

#ifdef CONFIG_FT5X0X_MULTITOUCH

   for(count = 0; count < (buf[2] & 0x0f); count++)

   {

        xpos = ((buf[3 + count * 6] & 0x03)<< 8 | buf[4 + count * 6]);

        ypos = ((buf[5 + count * 6] & 0x03)<< 8 | buf[6 + count * 6]);

        event_flag = buf[3 + count * 6]>> 6;

 

#ifdef TS_DEBUG

        touch_id = buf[5 + count * 6] >>4;

#endif

       ts_debug("(%x,%x,%d,%d)\n",touch_id,event_flag,xpos,ypos);

        if (event_flag == EVENT_PENDOWN ||event_flag == EVENT_REPEAT) {

            input_report_abs(priv->input,ABS_MT_POSITION_X, xpos);

            input_report_abs(priv->input,ABS_MT_POSITION_Y, ypos);

            input_report_key(priv->input,BTN_TOUCH, 1);

            input_report_abs(priv->input,ABS_PRESSURE, 1);

            input_mt_sync(priv->input);

        } else if (event_flag == EVENT_PENUP) {

            input_report_abs(priv->input,ABS_MT_POSITION_X, xpos);

           input_report_abs(priv->input,ABS_MT_POSITION_Y, ypos);

            input_report_key(priv->input,BTN_TOUCH, 0);

            input_report_abs(priv->input,ABS_PRESSURE, 0);

            input_mt_sync(priv->input);

        }

   }

   input_sync(priv->input);

#else

              //根据ft5x06的数据格式提取数据

   xpos = ((buf[3 + 0 * 6] & 0x03) << 8 | buf[4 + 0 * 6]);    

ypos = ((buf[5 + 0 * 6] & 0x03) << 8 | buf[6 + 0 * 6]);

   event_flag = buf[3 + 0 * 6] >> 6;

 

#ifdef TS_DEBUG

   touch_id = buf[5 + 0 * 6] >> 4;

#endif

   ts_debug("(%x,%x,%d,%d)\n",touch_id,event_flag,xpos,ypos);

   if (event_flag == EVENT_PENDOWN || event_flag == EVENT_REPEAT) {

        input_report_abs(priv->input, ABS_X,xpos);                   // 上报ABS_X

        input_report_abs(priv->input, ABS_Y,ypos);         // 上报ABS_Y

        input_report_key(priv->input,BTN_TOUCH, 1);              // 上报按下事件

        input_report_abs(priv->input,ABS_PRESSURE, 1);

        input_sync(priv->input);

   } else if (event_flag == EVENT_PENUP) {

        input_report_abs(priv->input, ABS_X,xpos);

       input_report_abs(priv->input, ABS_Y,ypos);

        input_report_key(priv->input,BTN_TOUCH, 0);

        input_report_abs(priv->input,ABS_PRESSURE, 0);

        input_sync(priv->input);

   }

#endif

 out:

   enable_irq(priv->irq);                                                            //使能TP-INT中断

}

数据处理的过程也比较简单,首先按照ft5x06的数据格式(前面已经提及)提取数据,然后按照input子系统的格式上报到用户分区即可。还有,前面在中断服务程序里面已经关闭了中断,这里必须使能中断,以便接收下一次的数据。到此数据处理完成。

3.3.3.        低功耗

前面已经说到,触摸屏的低功耗并没做考虑。这里只关注触摸屏能不能唤醒低功耗。触摸屏比较容易导致误唤醒,实际上大多数手持设备禁止触摸屏激活设备。这里只预留唤醒功能,默认不开。程序如下:

static int migor_ts_suspend(structi2c_client *client, pm_message_t mesg)                                                                   

{      

   struct migor_ts_priv *priv = dev_get_drvdata(&client->dev);

       

   if (device_may_wakeup(&client->dev))              // 使能触摸屏中断唤醒

        enable_irq_wake(priv->irq);

       

   return 0;

}

3.3.4.        驱动测试方法以及案例

触摸屏驱动测试这要考虑准确率,响应速度和边缘检测。本驱动只考虑单点触摸的情况,测试程序可以使用tslib。

Tslib的链接:

https://codeload.github.com/kergoth/tslib/zip/master

编译方法:

Cd tslib-master/

./configure--host=arm-none-linux-gnueabi --prefix=(此处添加生成的路径)

Make

安装方法:

将tslib生成的所有文件复制到G870D的/mtd0/tslib/目录,并在/etc/profile文件里面添加如下环境变量重启即可。

export TSLIB_ROOT=/mtd0/tslib

exportTSLIB_PLUGINDIR=${TSLIB_ROOT}/lib/ts

exportTSLIB_CALIBFILE=${TSLIB_ROOT}/pointercal

exportTSLIB_CONFFILE=${TSLIB_ROOT}/etc/ts.conf

exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:${TSLIB_ROOT}/lib

export TSLIB_FBDEVICE=/dev/fb0

export TSLIB_CONSOLEDEVICE=none

exportTSLIB_TSDEVICE=/dev/input/event1

export PATH=$PATH:/mtd0/tslib/bin

测试方法:

1.校准。理论上,电容屏不用校准。但是,考虑到安装的误差,更重要的是tslib需要利用pointercal文件进行点转换,所以还是要进行一次校准。

./ts_calibrate

然后,在目录上提示的地方输入即可。

2.测试

./ts_test

可以进行拖动,划线测试。

3.测试案例

a.快速拖动,查看跟随速度测试响应速度。

b.各种速度进行划线操作,查看是否漏点处理测试驱动的可靠性。

c.长期待机后继续操作,查看是否正常测试稳定性。

d.辐射,静电和湿润等恶劣条件进行操作和老化操作,查看是否正常工作测试抗干扰能力。

e.边缘点测试

4.     总结

因限制于linux内核版本,芯片处理速度和接口兼容的局限,本驱动在设计过程只在驱动上预留了多点触摸的上报。但手势识别一般来说,由硬件支持或者应用层计算完成识别。Ft5x06这块芯片数据格式上写着存在手势支持,但事实上返回的数据为空。所以需要应用层处理。目前来说,小型的pos机无需求。如果开发ATM,商场大屏幕等设备,则添加功能可以提供更好体验。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                

5.     附录1:input子系统介绍

5.1.  概述

输入设备总类繁杂,包括按键,键盘,触摸屏,鼠标,摇杆等等,它们本身都是字符设备,不过内核为了能将这些设备的共性抽象出来,简化驱动的开发,建立了一个Input子系统。Input子系统分为三层,从下至上分别是输入设备驱动层,输入核心层以及输入事件驱动层。下图可以简单说明,input子系统的结构。

核心层已经在linux kernel较好的实现了。因为输入设备存在共性,所以事件驱动理论上可以驱动共享。当无法共享的时候,才会单独实现。一般来说,驱动开发者仅仅只需要关心输入设备驱动层。

二.Input子系统初始化流程分析

子系统初始化有专用的宏接口subsys_initcall。Input子系统初始化入口为:

subsys_initcall(input_init);

以上只是在kernel初始化流程中插入了input_init接口,也就是说input_init是承担了初始化input子系统的主要工作。具体做了那些工作,可以接着分析input_init函数。

static int __init input_init(void)

{

              …

              err= class_register(&input_class);        //注册input_class

              …

              err= input_proc_init();                       // 在/proc文件系统里面建立相应的树结构

              …

              err= register_chrdev(INPUT_MAJOR, "input", &input_fops);  // INPUT_MAJOR为13

                                                                      //建立input子系统主设备号的字符设备。

              …

}

在注释已经指出,input子系统的初始化工作主要有三。注册input_class是出于方便linux kernel归类管理的目的(例如低功耗的处理会利用class链表挂起设备)。在/proc文件系统里面建立相应的树结构是希望将驱动的运行信息通过在用户空间的文件系统反映出来。相比前两点,最关键的是第三点,建立input子系统主设备号的字符设备。这意味所有主设备号为13的节点(包含本文的触摸屏)都通过input_fops结构体的接口来操作。这里详细分析一下input_fops结构体。input_fops结构体的定义:

static const struct file_operationsinput_fops = {

              .owner= THIS_MODULE,

              .open= input_open_file,

};

显然,没有预料的复杂。莫要遗憾,继续分析仅有的open接口input_open_file。

static int input_open_file(structinode *inode, struct file *file)

{

              structinput_handler *handler;

              conststruct file_operations *old_fops, *new_fops = NULL;

              interr;

              …

              handler= input_table[iminor(inode) >> 5];                       //寻找对应的事件驱动

                            //定义staticstruct input_handler *input_table[8];

              if(!handler || !(new_fops = fops_get(handler->fops))) {            // 重新链接接口组

                     err= -ENODEV;

                     gotoout;

              }

              …

                     err = new_fops->open(inode, file);                                  // 利用新的接口重新打开节点

              …

}

必须提及是,事件驱动处理器可以共享,内核预设事件驱动处理器只有8个(从input_table的定义可以证实)。内核利用节点的次设备号来对应,每64个共享一个事件驱动处理器。继续分析上面的源码,容易理解,input_fops只是完成kernel的抽象到现实的意义。输入设备是一个抽象出来的类,现实的设备(例如触摸屏)才是实际的。到这里,应该可以形成一个共识,设备层驱动对应一个事件驱动处理器;一个事件驱动处理器最多可以对应64个设备层驱动。至于里面的联系,后续再分析。

5.2.  Input driver编写要点

1、分配、注册、注销input设备接口

struct input_dev*input_allocate_device(void)

int input_register_device(structinput_dev *dev)

void input_unregister_device(structinput_dev *dev)

2、数据上报、同步接口                                

static inline voidinput_report_key(struct input_dev *dev, unsigned int code, int value)

static inline voidinput_report_rel(struct input_dev *dev, unsigned int code, int value)

static inline void input_report_abs(structinput_dev *dev, unsigned int code, int value)

static inline void input_sync(structinput_dev *dev)

static inline voidinput_mt_sync(struct input_dev *dev)

5.3.  从触摸屏驱动切入的input子系统。

曾记否上文中,触摸屏驱动的初始化工作就是初始化input结构体,然后将其注册。触摸屏input结构体初始化的接口有,input->open =migor_ts_open和input->close= migor_ts_close。重点分析input_register_device(input)的调用。

int input_register_device(structinput_dev *dev)

{                                                                                             

              error = device_add(&dev->dev);                        //添加设备

              list_add_tail(&dev->node,&input_dev_list);              // 添加input到input_dev_list链表

              list_for_each_entry(handler,&input_handler_list, node)

                                                 input_attach_handler(dev,handler);

                                                                                    //匹配事件驱动处理器

}

注册过程重点关注的地方还是只有三个,注释已经指出。实际上linux kernel只有字符设备和块设备。Input设备是一个抽象的设备,注册input设备归根到底是注册一个字符设备。这里必须提及的是input子系统有两个个全局变量,1.输入设备链input_dev_list;2.事件驱动链input_handler_list;input_dev_list负责记录所有的注册的input设备。input_handler_list负责记录所有的事件驱动处理器。注册Input_dev的时候会通过input_handler_list链表寻找合适的input_handler。反之,注册input_handler的时候会通过input_dev_list寻找input_dev。本文属于前面一种情况。这里分析一下查找的过程。

       list_for_each_entry(dev,&input_dev_list, node)

                                          input_attach_handler(dev,handler);

宏list_for_each_entry的作用是依次出列所有input_dev_list元素到dev,然后回调input_attch_handler函数。

static intinput_attach_handler(struct input_dev *dev, struct input_handler *handler)

{

              …

              id= input_match_device(handler->id_table, dev);       // 检查input_handler是否匹配input_dev

              if(!id)

                     return-ENODEV;                                     //不匹配则直接返回

              …

error = handler->connect(handler, dev, id);         // 匹配则调用handler的connect接口

              …

}

这个函数简单的从注释就可以明白。匹配的时候,通过对比handler->id_table和dev相关的信息来完成的。匹配了就会调用connect将两个对象链接起来。首先,关注一下input_match_device。

static const struct input_device_id*input_match_device(const struct input_device_id *id,

                                                 structinput_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;

                     //handler是否兼容dev

                     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;

       }

 

       returnNULL;

}

       不得不说的是,MATCH_BIT这个宏。请看:

#define MATCH_BIT(bit, max)                                \

              for(i = 0; i < BITS_TO_LONGS(max); i++)     \

                     if((id->bit[i] & dev->bit[i]) != id->bit[i]) \

                            break;                                               \

              if(i != BITS_TO_LONGS(max))                             \

                     continue;

       也就是说,if ((id->bit[i] & dev->bit[i]) != id->bit[i])可以看出如果id->bit[i]为0,代表可以肯定兼容;反之,只有id->bit[i]和dev->bit[i]同时为1的时候才兼容。假设匹配成功过,就会如前面所说调用handler->connect。那本文的触摸屏究竟使用那个handler呢?一般来说,没有专门定义的最后都会使用evdev_handler(前面提过可以查看次设备号验证)。显然,本文没有问触摸屏建立专门的事件驱动器,使用evdev_handler事件驱动器。所以handler->connect最终指向的是evdev_connect。分析一下evdev_connect。

static int evdev_connect(structinput_handler *handler, struct input_dev *dev,

                      const struct input_device_id *id)

{

              …

              evdev = kzalloc(sizeof(struct evdev),GFP_KERNEL);              // 分配evdev空间

              if (!evdev)

                     return -ENOMEM;

 

              INIT_LIST_HEAD(&evdev->client_list);                         // 初始化evdev

              spin_lock_init(&evdev->client_lock);

              mutex_init(&evdev->mutex);

              init_waitqueue_head(&evdev->wait);

 

              dev_set_name(&evdev->dev,"event%d", minor);

              evdev->exist = 1;

              evdev->minor = minor;    

 

              evdev->handle.dev =input_get_device(dev);                            //初始化联系体handle

              evdev->handle.name =dev_name(&evdev->dev);

              evdev->handle.handler = handler;

              evdev->handle.private = evdev;

 

              evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE+ minor);

              evdev->dev.class = &input_class;

              evdev->dev.parent = &dev->dev;

              evdev->dev.release = evdev_free;

              device_initialize(&evdev->dev);

 

              error =input_register_handle(&evdev->handle);                //注册handle

              if (error)

                     goto err_free_evdev;

 

              error = evdev_install_chrdev(evdev);                               // 记录evdev到evdev_table表

              if (error)

                     goto err_unregister_handle;

 

              error = device_add(&evdev->dev);                                          // 注册event设备。

              if (error)

                     goto err_cleanup_evdev;

 

              return 0;

}

下面看一下,注册handle的过程。

int input_register_handle(structinput_handle *handle)

{

    …

list_add_tail_rcu(&handle->d_node,&dev->h_list);

    …

    list_add_tail(&handle->h_node,&handler->h_list);

    …

}

比较简单通过链表来记录它联系的handler和dev。到此为止,注册的工作完成。以上述为基础,其他过程,只是简单列举一下。

       a.用户空间的open(“eventX”)      // X代表序号。

              =>evdev_handler->fops-> open

=> evdev_open

=> evdev_open_device

=> input_open_device

=> input->open

=> migor_ts_open                //运行开发者自定义open函数

b.驱动数据上报的过程                                                            

              input_report_abs

ð input_event                   // 调用input core层接口

ð input_handle_event

ð input_pass_event

ð handle->handler->event  // 利用handle需找事件驱动器处理

ð evdev_pass_event

ð  client->buffer[client->head++] = *event;            // 填写缓冲区

client->head &=EVDEV_BUFFER_SIZE - 1;

ð  wake_up_interruptible    // 激活阻塞读操作

c.同步过程与数据上报过程雷同,不做分析。

5.4.  总结

由上文的分析可见,input子系统框架设计思路基本源于驱动设备模型。关键是理解三个数据结构的关系和关系的建立流程。三个数据结构分析是input_handler,input_dev和Input_handle。简单来说,input_handler和input_dev配对存在,input_handle是前两者沟通的渠梁。其建立流程是通过两个全局链表,彼此寻找,匹配成功后通过connect建立渠梁。一般来说,input_handler采用共享的驱动器,驱动开发者重点关心input_dev的建立和数据处理即可。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值