全部博文(478)
分类: 嵌入式
2018-03-05 17:05:39
原文地址:TP驱动分析(上) 作者:jerry20000
--分析Ft0x5x.c
一、硬件电路概述
1,电容触摸屏的工作及坐标定位算法这里不做介绍,网上有很多相关的资料可以查看。一般按TP的触摸IC是否在主板上,可以分为:COB(chip on board)和COF(chip on film)。前者是把触摸IC贴在主板上,后者是把触摸IC帖在FPC上。但是他们的工作原理是一样的,不同的是TP connector的pin数不一样罢了。
2,TP的工作原理
从规格书上可以知道,在CTP和HOST之间有三种通信:
(1)Transfer the data via I2C(通过I2C或者其它接口传输数据给主机)
(2)Send interrupt when there is a valid touch(当有触摸时发送中断给主机)
(3)Host send Wakeup signal to CTPM(主机唤醒TP)
TP检测到有效的触摸以后,计算出具体坐标等数据存放在寄存器中,然后发中断给主控,主控收到中断后让TP通过I2C发送数据过来,接着主控再对这些数据进行处理,以响应对应的操作。如果TP进入休眠模式,则主机可以通过WAKE信号线唤醒TP,使之正常工作。
二,软件代码分析
在linux系统中,TP驱动流程如下:
先进行I2C等初始化,然后TP等待被触摸。如果有触摸事件发生,TP会把这些数据存储在寄存器里;同时产生一个中断给主控,主控接收到中断后调用中断处理函数,中断处理函数向工作队列提交一个任务,最后执行中断服务函数,通过i2c_transfer从TP的寄存器里获取刚才得到的触摸事件的记录;主控接收到数据以后,对从I2C得到的数据进行判断:是按下,弹起,还是move;然后把这些分好类的数据再上报给input子系统;input子系统再对这些数据进行分析,以决定调用哪个事件。
下面从头开始分析代码:一切驱动的入口module_init(),在驱动的最后位置找了下,没看到module_init()函数,倒是看到这个late_initcall(ft5x0x_mod_init)和module_exit(ft5x0x_mod_init)相对应,原来late_initcall就是这个驱动的入口。有关late_initcall()和module_init()函数的区别可以参考此文章:
http://blog.csdn.net/cstk502/article/details/6579231
1,我们接着看ft5x0x_mod_init()内部的主要内容:
(1)首先定义了一个i2c_board_info结构体,以及i2c_adapter和i2c_client指针。
紧接着就是对i2c_board_info结构体和i2c_adapter指针进行填充,这两个结构体的主要功能是为了收集资源。填充了i2c_board_info这个结构体中的addr,irq和type,接着调用i2c_get_adapter(2)函数,初始化了adapter结构体。
(2)client = i2c_new_device(adapter, &info); 这个函数利用i2c_board_info结构体和i2c_adapter指针生成了一个新的i2c_client,并且将这个i2c_client注册到了i2c_device链表中。
(3)i2c_add_driver(&ft5x0x_driver); 这个函数的作用就是将&ft5x0x_driver注册到i2c_driver链表中,在每次注册完i2c_device或者i2c_driver后,都会有一个匹配过程,主要是通过名字匹配,如果匹配成功,则会调用i2c_driver的probe函数。
2,ft5x0x_probe()函数:
接下来我们去看看ft5x0x_driver,这里有几个回调函数,但最重要的是probe函数。我们来看probe都实现了哪些功能。
ft5x0x_probe函数开头有两个非常重要的参数定义:
(1)struct ft5x0x_data *ft5x0x_ts;主要是为了接受i2c_clint中i2c_board_info传来的资源,这个是为了综合整个TP驱动中使用的所有资源,包括中断,输入子系统,工作队列等,应该说,整个驱动最难的地方在于如何设计这个结构体。
(2)主要的工作如下:
i2c_set_clientdata(client, ft5x0x_ts); //将ts和i2c_client进行绑定
err = ft5x0x_chip_init(client);
上面这个函数又调用了i2c_get_clientdata(client);进一步跟踪进去会发现其实是把client->p->driver_data给取出来,放到ft5x0x_ts里,以便进行i2c_set_clientdata(client, ft5x0x_ts);。
ft5x0x_ts->input_dev = input_allocate_device();//用source insight 点进去看原形可知,这个函数其实就是分配内存。有关input_device的原理,可以参见这篇文章:http://blog.csdn.net/wealoong/article/details/7580916
err = input_register_device(ft5x0x_ts->input_dev);// 上一个函数是分配空间,分配完之后就是注册了
3,接下来要分析函数:
INIT_WORK(&ft5x0x_ts->pen_event_work, ft5x0x_work_func);
//设置ts中的任务,即让ft5x0x_ts->pen_event_work指向我们自己定义的函数ft5x0x_work_func
ft5x0x_ts->ts_workqueue=create_singlethread_workqueue(dev_name(&client->dev));
//创建一个工作队列
err = request_irq(_sui_irq_num, fts_ts_irq, 0, "qt602240_ts", ft5x0x_ts);//请求一个中断
(1)在分析它们之前,先简要说明一下INIT_WORK()这个宏。
内核里对这个函数是这样定义的 #define INIT_WORK(_work, _func),可以理解为INIT_WORK会在你定义的_work工作队列里面增加一个工作任务,该任务就是_func。看许多驱动模块的时候会发觉work就是一个工作队列,一般是结构体work_struct,主要的目的就是用来处理中断的。比如在中断里面要做很多事,但是比较耗时,这时就可以把耗时的工作放到工作队列。说白了就是系统延时调度的一个自定义函数。
我们深入到fts_work_func这个函数里。
fts_work_func调用了fts_read_data函数,在fts_work_func函数里实现了收集触摸信息,计算x,y坐标,并把这些信息上报给input系统的功能。
(2)分析中断请求函数:
err = request_irq(_sui_irq_num, fts_ts_irq, 0, "qt602240_ts", ft5x0x_ts);
此函数的原型是:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
在发生对应于第 1个参数 irq 的中断时,则调用第 2 个参数 handler 指定的中断服务函数(也就是把 handler() 中断服务函数注册到内核中 )。
第 3 个参数 flags 指定了快速中断或中断共享等中断处理属性。
第 4 个参数 name 通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟) 文件上,或内核发生中断错误时使用。
第 5 个参数 dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。
返回值:函数运行正常时返回 0 ,否则返回对应错误的负值。
中断服务函数做了哪些事?我们看fts_ts_irq
(3)分析中断服务函数fts_ts_irq:
首先检测中断标志是否清除,如果没有清除,则清除,然后
if(!work_pending(&ft5x0x_ts->pen_event_work)){
queue_work(ft5x0x_ts->ts_workqueue, &ft5x0x_ts->pen_event_work);
}
即先检查ft5x0x_ts->pen_event_work所指的工作是不是在等待状态,如果不是,就调用
queue_work(ft5x0x_ts->ts_workqueue, &ft5x0x_ts->pen_event_work);
queue_work就是调度执行一个任务,这个任务的指针就是ft5x0x_ts->pen_event_work。
由这里INIT_WORK(&ft5x0x_ts->pen_event_work, fts_work_func);
可以发现,ft5x0x_ts->pen_event_work这个任务指针其实就是fts_work_func。
fts_work_func函数上面已经分析过,其实现了收集触摸信息,计算x,y坐标,并把这些信息上报给input系统的功能。
有关fts_work_func这个函数的详细分析,应该可以单独作为一篇或者更多篇文章了。
这里也验证了,中断服务程序一般分为中断上半部和下半部。其中上半部处理比较紧急的事情,这里是清中断标识,下半部处理其它事情。注意,中断上半部要尽量简短,以名影响其它中断,把比较耗时的任务应该放在中断下半部。中断下半部的实现方法有:软中断,tasklet,工作队列,详细可参考:
http://www.cnblogs.com/wang_yb/archive/2013/04/23/3037268.html
这里用到的是工作队列。