LINUX|【正点原子Linux连载】第六十四章 Linux 多点电容触摸屏实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新
LINUX|【正点原子Linux连载】第六十四章 Linux 多点电容触摸屏实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
文章图片

第六十四章 Linux 多点电容触摸屏实验
触摸屏的使用场合越来越多,从手机、平板到蜂巢取货的屏幕等,到处充斥着触摸屏。触摸屏也从原来的电阻触摸屏发展到了很流行的电容触摸屏,我们在第二十八章裸机实验中已经讲解了如何编写电容触摸屏驱动。本章我们就来学习一下如何在linux下编写多点电容触摸屏驱动。
64.1 Linux下电容触摸屏驱动框架简介
64.1.1 多点触摸(MT)协议详解
电容触摸驱动的基本原理我们已经在《第二十八章 多点电容触摸屏实验》中进行了详细的讲解,回顾一下几个重要的知识点:
①、电容触摸屏是IIC接口的,需要触摸IC,以正点原子的ATK7016为例,其所使用的触摸屏控制IC为FT5426,因此所谓的电容触摸驱动就是IIC设备驱动。
②、触摸IC提供了中断信号引脚(INT),可以通过中断来获取触摸信息。
③、电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
④、电容触摸屏不需要校准,当然了,这只是理论上的,如果电容触摸屏质量比较差,或者触摸玻璃和TFT之间没有完全对齐,那么也是需要校准的。
根据以上几个知识点,我们可以得出电容触摸屏驱动其实就是以下几种linux驱动框架的组合:
①、IIC设备驱动,因为电容触摸IC基本都是IIC接口的,因此大框架就是IIC设备驱动。
②、通过中断引脚(INT)向linux内核上报触摸信息,因此需要用到linux中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于linux的input子系统,因此向linux内核上报触摸屏坐标信息就得使用input子系统。只是,我们得按照linux内核规定的规则来上报坐标信息。
经过简单的分析,我们发现IIC驱动、中断驱动、input子系统我们都已经在前面学过了,唯独没学过的就是input子系统下的多点电容触摸协议,这个才是我们本章学习的重点。linux内核中有一份文档详细的讲解了多点电容触摸屏协议,文档路径为:Documentation/input/multi-touch-protocol.txt。
老版本的linux内核是不支持多点电容触摸的(Multi-touch,简称MT),MT协议是后面加入的,因此如果使用2.x版本linux内核的话可能找不到MT协议。MT协议被分为两种类型,Type A和TypeB,这两种类型的区别如下:
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过slot更新某一个触摸点的信息,FT5426就属于此类型,一般的多点电容触摸屏IC都有此能力。
触摸点的信息通过一系列的ABS_MT事件(有的资料也叫消息)上报给linux内核,只有ABS_MT事件是用于多点触摸的,ABS_MT事件定义在文件include/uapi/linux/input.h中,相关事件如下所示:

示例代码64.1.1.1 ABS_MT事件 852 #define ABS_MT_SLOT0x2f /* MT slot being modified */ 853 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ 854 #define ABS_MT_TOUCH_MINOR0x31 /* Minor axis (omit if circular) */ 855 #define ABS_MT_WIDTH_MAJOR0x32 /* Major axis of approaching ellipse */ 856 #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ 857 #define ABS_MT_ORIENTATION0x34 /* Ellipse orientation */ 858 #define ABS_MT_POSITION_X0x35 /* Center X touch position */ 859 #define ABS_MT_POSITION_Y0x36 /* Center Y touch position */ 860 #define ABS_MT_TOOL_TYPE0x37 /* Type of touching device */ 861 #define ABS_MT_BLOB_ID0x38 /* Group a set of packets as a blob */ 862 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ 863 #define ABS_MT_PRESSURE0x3a /* Pressure on contact area */ 864 #define ABS_MT_DISTANCE0x3b /* Contact hover distance */ 865 #define ABS_MT_TOOL_X0x3c /* Center X tool position */ 866 #define ABS_MT_TOOL_Y0x3d /* Center Y tool position */

在上面这些众多的ABS_MT事件中,我们最常用的就是ABS_MT_SLOT、ABS_MT_POSITION_X、ABS_MT_POSITION_Y和ABS_MT_TRACKING_ID。其中ABS_MT_POSITION_X和ABS_MT_POSITION_Y用来上报触摸点的(X,Y)坐标信息,ABS_MT_SLOT用来上报触摸点ID,对于Type B类型的设备,需要用到ABS_MT_TRACKING_ID事件来区分触摸点。

【LINUX|【正点原子Linux连载】第六十四章 Linux 多点电容触摸屏实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0】对于Type A类型的设备,通过input_mt_sync()函数来隔离不同的触摸点数据信息,此函数原型如下所示:
void input_mt_sync(struct input_dev *dev)
此函数只要一个参数,类型为input_dev,用于指定具体的input_dev设备。input_mt_sync()函数会触发SYN_MT_REPORT事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据。
对于Type B类型的设备,上报触摸点信息的时候需要通过input_mt_slot()函数区分是哪一个触摸点,input_mt_slot()函数原型如下所示:
void input_mt_slot(struct input_dev *dev, int slot)
此函数有两个参数,第一个参数是input_dev设备,第二个参数slot用于指定当前上报的是哪个触摸点信息。input_mt_slot()函数会触发ABS_MT_SLOT事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据。
不管是哪个类型的设备,最终都要调用input_sync()函数来标识多点触摸信息传输完成,告诉接收者处理之前累计的所有消息,并且准备好下一次接收。Type B和Type A相比最大的区别就是Type B可以区分出触摸点, 因此可以减少发送到用户空间的数据。Type B使用slot协议区分具体的触摸点,slot需要用到ABS_MT_TRACKING_ID消息,这个ID需要硬件提供,或者通过原始数据计算出来。对于Type A设备,内核驱动需要一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和手指(触摸点)跟踪是在内核空间处理的。
Type B设备驱动需要给每个识别出来的触摸点分配一个slot,后面使用这个slot来上报触摸点信息。可以通过slot的ABS_MT_TRACKING_ID来新增、替换或删除触摸点。一个非负数的ID表示一个有效的触摸点,-1这个ID表示未使用slot。一个以前不存在的ID表示这是一个新加的触摸点,一个ID如果再也不存在了就表示删除了。
有些设备识别或追踪的触摸点信息要比他上报的多,这些设备驱动应该给硬件上报的每个触摸点分配一个Type B的slot。一旦检测到某一个slot关联的触摸点ID发生了变化,驱动就应该改变这个slot的ABS_MT_TRACKING_ID,使这个slot失效。如果硬件设备追踪到了比他正在上报的还要多的触摸点,那么驱动程序应该发送BTN_TOOL_*TAP消息,并且调用input_mt_report_pointer_emulation()函数,将此函数的第二个参数use_count设置为false。
64.1.2 Type A触摸点信息上报时序
对于Type A类型的设备,发送触摸点信息的时序如下所示,这里以2个触摸点为例:
示例代码64.1.2.1 Type A触摸点数据上报时序 1 ABS_MT_POSITION_X x[0] 2 ABS_MT_POSITION_Y y[0] 3 SYN_MT_REPORT 4 ABS_MT_POSITION_X x[1] 5 ABS_MT_POSITION_Y y[1] 6 SYN_MT_REPORT 7 SYN_REPORT

第1行,通过ABS_MT_POSITION_X事件上报第一个触摸点的X坐标数据,通过input_report_abs函数实现,下面同理。 第2行,通过ABS_MT_POSITION_Y事件上报第一个触摸点的Y坐标数据。 第3行,上报SYN_MT_REPORT事件,通过调用input_mt_sync函数来实现。 第4行,通过ABS_MT_POSITION_X事件上报第二个触摸点的X坐标数据。 第5行,通过ABS_MT_POSITION_Y事件上报第二个触摸点的Y坐标数据。 第6行,上报SYN_MT_REPORT事件,通过调用input_mt_sync函数来实现。 第7行,上报SYN_REPORT事件,通过调用input_sync函数实现。 我们在编写Type A类型的多点触摸驱动的时候就需要按照示例代码64.1.2.1中的时序上报坐标信息。Linux内核里面也有Type A类型的多点触摸驱动,找到st2332.c这个驱动文件,路径为drivers/input/touchscreen/st1232.c,找到st1232_ts_irq_handler函数,此函数里面就是上报触摸点坐标信息的。

示例代码64.1.2.2 st1232_ts_irq_handler函数代码段 103 static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) 104 { ...... 111ret = st1232_ts_read_data(ts); 112if (ret < 0) 113goto end; 114 115/* multi touch protocol */ 116for (i = 0; i < MAX_FINGERS; i++) { 117if (!finger[i].is_valid) 118continue; 119 120input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t); 121input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x); 122input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y); 123input_mt_sync(input_dev); 124count++; 125} ...... 140 141/* SYN_REPORT */ 142input_sync(input_dev); 143 144 end: 145return IRQ_HANDLED; 146 }

第111行,获取所有触摸点信息。 第116~125行,按照Type A类型轮流上报所有的触摸点坐标信息,第121和122行分别上报触摸点的(X,Y)轴坐标,也就是ABS_MT_POSITION_X和ABS_MT_POSITION_Y事件。每上报完一个触摸点坐标,都要在第123行调用input_mt_sync函数上报一个SYN_MT_REPORT信息。 第142行,每上报完一轮触摸点信息就调用一次input_sync函数,也就是发送一个SYN_REPORT事件

64.1.3 Type B触摸点信息上报时序
对于Type B类型的设备,发送触摸点信息的时序如下所示,这里以2个触摸点为例:
示例代码64.1.3.1 Type B触摸点数据上报时序 1 ABS_MT_SLOT 0 2 ABS_MT_TRACKING_ID 45 3 ABS_MT_POSITION_X x[0] 4 ABS_MT_POSITION_Y y[0] 5 ABS_MT_SLOT 1 6 ABS_MT_TRACKING_ID 46 7 ABS_MT_POSITION_X x[1] 8 ABS_MT_POSITION_Y y[1] 9 SYN_REPORT

第1行,上报ABS_MT_SLOT事件,也就是触摸点对应的SLOT。每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID,需要由触摸IC提供。 第2行,根据Type B的要求,每个SLOT必须关联一个ABS_MT_TRACKING_ID,通过修改SLOT关联的ABS_MT_TRACKING_ID来完成对触摸点的添加、替换或删除。具体用到的函数就是input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数的第三个参数active要设置为true,linux内核会自动分配一个ABS_MT_TRACKING_ID值,不需要用户去指定具体的ABS_MT_TRACKING_ID值。 第3行,上报触摸点0的X轴坐标,使用函数input_report_abs来完成。 第4行,上报触摸点0的Y轴坐标,使用函数input_report_abs来完成。 第5~8行,和第1~4行类似,只是换成了上报触摸点0的(X,Y)坐标信息 第9行,当所有的触摸点坐标都上传完毕以后就得发送SYN_REPORT事件,使用input_sync函数来完成。 当一个触摸点移除以后,同样需要通过SLOT关联的ABS_MT_TRACKING_ID来处理,时序如下所示:

示例代码64.1.3.2 Type B触摸点移除时序 1 ABS_MT_TRACKING_ID -1 2 SYN_REPORT

第1行,当一个触摸点(SLOT)移除以后,需要通过ABS_MT_TRACKING_ID事件发送一个-1给内核。方法很简单,同样使用input_mt_report_slot_state函数来完成,只需要将此函数的第三个参数active设置为false即可,不需要用户手动去设置-1。 第2行,当所有的触摸点坐标都上传完毕以后就得发送SYN_REPORT事件。

当要编写Type B类型的多点触摸驱动的时候就需要按照示例代码64.1.3.1中的时序上报坐标信息。Linux内核里面有大量的Type B类型的多点触摸驱动程序,我们可以参考这些现成的驱动程序来编写自己的驱动代码。这里就以ili210x这个触摸驱动IC为例,看看是Type B类型是如何上报触摸点坐标信息的。找到ili210x.c这个驱动文件,路径为drivers/input/touchscreen/ili210x.c,找到ili210x_report_events函数,此函数就是用于上报ili210x触摸坐标信息的,函数内容如下所示:
示例代码64.1.3.3 ili210x_report_events函数代码段 78static void ili210x_report_events(struct input_dev *input, 79const struct touchdata *touchdata) 80{ 81int i; 82bool touch; 83unsigned int x, y; 84const struct finger *finger; 85 86for (i = 0; i < MAX_TOUCHES; i++) { 87input_mt_slot(input, i); 88 89finger = &touchdata->finger[i]; 90 91touch = touchdata->status & (1 << i); 92input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); 93if (touch) { 94x = finger->x_low | (finger->x_high << 8); 95y = finger->y_low | (finger->y_high << 8); 96 97input_report_abs(input, ABS_MT_POSITION_X, x); 98input_report_abs(input, ABS_MT_POSITION_Y, y); 99} 100} 101 102input_mt_report_pointer_emulation(input, false); 103input_sync(input); 104 }

第86~100行,使用for循环实现上报所有的触摸点坐标,第87行调用input_mt_slot函数上报ABS_MT_SLOT事件。第92行调用input_mt_report_slot_state函数上报ABS_MT_TRACKING_ID事件,也就是给SLOT关联一个ABS_MT_TRACKING_ID。第97和98行使用input_report_abs函数上报触摸点对应的(X,Y)坐标值。 第103行,使用input_sync函数上报SYN_REPORT事件。

64.1.4 MT其他事件的使用
在示例代码64.1.1.1中给出了Linux所支持的所有ABS_MT事件,大家可以根据实际需求将这些事件组成各种事件组合。最简单的组合就是ABS_MT_POSITION_X和ABS_MT_POSITION_Y,可以通过在这两个事件上报触摸点,如果设备支持的话,还可以使用ABS_MT_TOUCH_MAJOR和ABS_MT_WIDTH_MAJOR这两个消息上报触摸面积信息,关于其他ABS_MT事件的具体含义大家可以查看Linux内核中的multi-touch-protocol.txt文档,这里我们重点补充一下ABS_MT_TOOL_TYPE事件。
ABS_MT_TOOL_TYPE事件用于上报触摸工具类型,很多内核驱动都不能区分出触摸设备类型,是手指还是触摸笔?这种情况下,这个事件可以忽略掉。目前的协议支持MT_TOOL_FINGER(手指)、MT_TOOL_PEN(笔)和MT_TOOL_PALM(手掌)这三种触摸设备类型,于Type B类型,此事件由input子系统内核处理。如果驱动程序需要上报ABS_MT_TOOL_TYPE事件,那么可以使用input_mt_report_slot_state函数来完成此工作。
关于Linux系统下的多点触摸(MT)协议就讲解到这里,简单总结一下,MT协议隶属于linux的input子系统,驱动通过大量的ABS_MT事件向linux内核上报多点触摸坐标数据。根据触摸IC的不同,分为Type A和Type B两种类型,不同的类型其上报时序不同,目前使用最多的是Type B类型。接下来我们就根据前面学习过的MT协议来编写一个多点电容触摸驱动程序,本章节所使用的触摸屏是正点原子的ATK7084(7寸800480)和ATK7016(7寸1024600)这两款触摸屏,这两款触摸屏都使用FT5426这款触摸IC,因此驱动程序是完全通用的。
64.1.5 多点触摸所使用到的API函数
根据前面的讲解,我们知道linux下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过Linux内核提供的对应API函数实现的,本小节我们来看一下一些常见的API函数。
1、input_mt_init_slots函数
input_mt_init_slots函数用于初始化MT的输入slots,编写MT驱动的时候必须先调用此函数初始化slots,此函数定义在文件drivers/input/input-mt.c中,函数原型如下所示:
int input_mt_init_slots( struct input_dev dev,
unsigned int num_slots,
unsigned int flags)
函数参数和返回值含义如下:
dev: MT设备对应的input_dev,因为MT设备隶属于input_dev。
num_slots:设备要使用的SLOT数量,也就是触摸点的数量。
flags:其他一些flags信息,可设置的flags如下所示:
#define INPUT_MT_POINTER 0x0001 /
pointer device, e.g. trackpad /
#define INPUT_MT_DIRECT 0x0002 /
direct device, e.g. touchscreen /
#define INPUT_MT_DROP_UNUSED 0x0004 /
drop contacts not seen in frame /
#define INPUT_MT_TRACK 0x0008 /
use in-kernel tracking /
#define INPUT_MT_SEMI_MT 0x0010 /
semi-mt device, finger count handled manually */
可以采用‘|’运算来同时设置多个flags标识。
返回值:0,成功;负值,失败。
2、input_mt_slot函数
此函数用于Type B类型,此函数用于产生ABS_MT_SLOT事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数定义在文件include/linux/input/mt.h中,函数原型如下所示:
void input_mt_slot(struct input_dev *dev,
int slot)
函数参数和返回值含义如下:
dev: MT设备对应的input_dev。
slot:当前发送的是哪个slot的坐标信息,也就是哪个触摸点。
返回值:无。
3、input_mt_report_slot_state函数
此函数用于Type B类型,用于产生ABS_MT_TRACKING_ID和ABS_MT_TOOL_TYPE事件,ABS_MT_TRACKING_ID事件给slot关联一个ABS_MT_TRACKING_ID,ABS_MT_TOOL_TYPE事件指定触摸类型(是笔还是手指等)。此函数定义在文件drivers/input/input-mt.c中,此函数原型如下所示:
void input_mt_report_slot_state( struct input_dev *dev,
unsigned int tool_type,
bool active)
函数参数和返回值含义如下:
dev: MT设备对应的input_dev。
tool_type:触摸类型,可以选择MT_TOOL_FINGER(手指)、MT_TOOL_PEN(笔)或MT_TOOL_PALM(手掌),对于多点电容触摸屏来说一般都是手指。
active:true,连续触摸,input子系统内核会自动分配一个ABS_MT_TRACKING_ID给slot。false,触摸点抬起,表示某个触摸点无效了,input子系统内核会分配一个-1给slot,表示触摸点溢出。
返回值:无。
4、input_report_abs函数
Type A和Type B类型都使用此函数上报触摸点坐标信息,通过ABS_MT_POSITION_X和ABS_MT_POSITION_Y事件实现X和Y轴坐标信息上报。此函数定义在文件include/linux/input.h中,函数原型如下所示:
void input_report_abs( struct input_dev *dev,
unsigned int code,
int value)
函数参数和返回值含义如下:
dev: MT设备对应的input_dev。
code:要上报的是什么数据,可以设置为ABS_MT_POSITION_X或ABS_MT_POSITION_Y,也就是X轴或者Y轴坐标数据。
value:具体的X轴或Y轴坐标数据值。
返回值:无。
5、input_mt_report_pointer_emulation函数
如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用BTN_TOOL_TAP事件来通知用户空间当前追踪到的触摸点总数量,然后调用input_mt_report_pointer_emulation函数将use_count参数设置为false。否则的话将use_count参数设置为true,表示当前的触摸点数量(此函数会获取到具体的触摸点数量,不需要用户给出),此函数定义在文件drivers/input/input-mt.c中,函数原型如下:
void input_mt_report_pointer_emulation(struct input_dev *dev,
bool use_count)
函数参数和返回值含义如下:
dev: MT设备对应的input_dev。
use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量。
返回值:无。
64.1.6 多点电容触摸驱动框架
前面几小节已经详细的讲解了linux下多点触摸屏驱动原理,本小节我们来梳理一下linux下多点电容触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意一下几点:
①、多点电容触摸芯片的接口,一般都为I2C接口,因此驱动主框架肯定是I2C。
②、linux里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于input子系统,因此还要用到input子系统框架。
④、在中断处理程序中按照linux的MT协议上报坐标信息。
根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:
1、I2C驱动框架
驱动总体采用I2C框架,参考框架代码如下所示:
示例代码64.1.6.1 多点电容触摸驱动I2C驱动框架 1/* 设备树匹配表 */ 2static const struct i2c_device_id xxx_ts_id[] = { 3{ "xxx", 0, }, 4{ /* sentinel */ } 5}; 6 7/* 设备树匹配表 */ 8static const struct of_device_id xxx_of_match[] = { 9{ .compatible = "xxx", }, 10{ /* sentinel */ } 11 }; 12 13 /* i2c驱动结构体 */ 14 static struct i2c_driver ft5x06_ts_driver = { 15.driver = { 16.owner = THIS_MODULE, 17.name = "edt_ft5x06", 18.of_match_table = of_match_ptr(xxx_of_match), 19}, 20.id_table = xxx_ts_id, 21.probe= xxx_ts_probe, 22.remove= xxx_ts_remove, 23 }; 24 25 /* 26* @description: 驱动入口函数 27* @param: 无 28* @return: 无 29*/ 30 static int __init xxx_init(void) 31 { 32int ret = 0; 33 34ret = i2c_add_driver(&xxx_ts_driver); 35 36return ret; 37 } 38 39 /* 40* @description: 驱动出口函数 41* @param: 无 42* @return: 无 43*/ 44 static void __exit xxx_exit(void) 45 { 46i2c_del_driver(&ft5x06_ts_driver); 47 } 48 49 module_init(xxx_init); 50 module_exit(xxx_exit); 51 MODULE_LICENSE("GPL"); 52 MODULE_AUTHOR("zuozhongkai");

I2C驱动框架已经在六十一章进行了详细的讲解,这里就不再赘述了。当设备树中触摸IC的设备节点和驱动匹配以后,示例代码64.1.6.1中第21行的xxx_ts_probe函数就会执行,我们可以在此函数中初始化触摸IC,中断和input子系统等。 2、初始化触摸IC、中断和input子系统 初始化操作都是在xxx_ts_probe函数中完成,参考框架如下所示(以下代码中步骤顺序可以自行调整,不一定按照示例框架来):

示例代码64.1.6.2 xxx_ts_probe驱动框架 1static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) 2{ 3struct input_dev *input; 4 5/* 1、初始化I2C*/ 6...... 7 8/* 2,申请中断, */ 9devm_request_threaded_irq(&client->dev, client->irq, NULL, 10xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 11client->name, &xxx); 12...... 13 14/* 3,input设备申请与初始化*/ 15input = devm_input_allocate_device(&client->dev); 16 17input->name = client->name; 18input->id.bustype = BUS_I2C; 19input->dev.parent = &client->dev; 20...... 21 22/* 4,初始化input和MT*/ 23__set_bit(EV_ABS, input->evbit); 24__set_bit(BTN_TOUCH, input->keybit); 25 26input_set_abs_params(input, ABS_X, 0, width, 0, 0); 27input_set_abs_params(input, ABS_Y, 0, height, 0, 0); 28input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0); 29input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 30input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0); 31...... 32 33/* 5,注册input_dev*/ 34input_register_device(input); 35...... 36 }

第5~7行,首先肯定是初始化触摸芯片,包括芯片的相关IO,比如复位、中断等IO引脚,然后就是芯片本身的初始化,也就是配置触摸芯片的相关寄存器。

第9行,因为一般触摸芯片都是通过中断来向系统上报触摸点坐标信息的,因此我们需要初始化中断,这里又和第五十一章内容结合起来了。大家可能会发现第9行并没有使用request_irq函数申请中断,而是采用了devm_request_threaded_irq这个函数,为什么使用这个函数呢?是不是request_irq函数不能使用?答案肯定不是的,这里用request_irq函数是绝对没问题的。那为何要用devm_request_threaded_irq呢?这里我们就简单的介绍一下这个API函数,devm_request_threaded_irq函数特点如下:
①、用于申请中断,作用和request_irq函数类似。
②、此函数的作用是中断线程化,大家如果直接在网上搜索“devm_request_threaded_irq”会发现相关解释很少。但是大家去搜索request_threaded_irq函数就会有很多讲解的博客和帖子,这两个函数在名字上的差别就是前者比后者多了个“devm_”前缀,“devm_”前缀稍后讲解。大家应该注意到了“request_threaded_irq”相比“request_irq”多了个threaded函数,也就是线程的意思。那么为什么要中断线程化呢?我们都是知道硬件中断具有最高优先级,不论什么时候只要硬件中断发生,那么内核都会终止当前正在执行的操作,转而去执行中断处理程序(不考虑关闭中断和中断优先级的情况),如果中断非常频繁的话那么内核将会频繁的执行中断处理程序,导致任务得不到及时的处理。中断线程化以后中断将作为内核线程运行,而且也可以被赋予不同的优先级,任务的优先级可能比中断线程的优先级高,这样做的目的就是保证高优先级的任务能被优先处理。大家可能会疑问,前面不是说可以将比较耗时的中断放到下半部(bottom half)处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争。
要注意,并不是所有的中断都可以被线程化,重要的中断就不能这么操作。对于触摸屏而言只要手指放到屏幕上,它可能就会一直产生中断(视具体芯片而定,FT5426是这样的),中断处理程序里面需要通过I2C读取触摸信息并上报给内核,I2C的速度最大只有400KHz,算是低速外设。不断的产生中断、读取触摸信息、上报信息会导致处理器在触摸中断上花费大量的时间,但是触摸相对来说不是那么重要的事件,因此可以将触摸中断线程化。如果你觉得触摸中断很重要,那么就可以不将其进行线程化处理。总之,要不要将一个中断进行线程化处理是需要自己根据实际情况去衡量的。linux内核自带的goodix.c(汇顶科技)、mms114.c(MELFAS公司)、zforce_ts.c(zForce公司)等多点电容触摸IC驱动程序都采用了中断线程化,当然也有一些驱动没有采用中断线程化。
③、最后来看一下“devm_”前缀,在linux内核中有很多的申请资源类的API函数都有对应的“devm_”前缀版本。比如devm_request_irq和request_irq这两个函数,这两个函数都是申请中断的,我们使用request_irq函数申请中断的时候,如果驱动初始化失败的话就要调用free_irq函数对申请成功的irq进行释放,卸载驱动的时候也需要我们手动调用free_irq来释放irq。假如我们的驱动里面申请了很多资源,比如:gpio、irq、input_dev,那么就需要添加很多goto语句对其做处理,当这样的标签多了以后代码看起来就不整洁了。“devm_”函数就是为了处理这种情况而诞生的,“devm_”函数最大的作用就是:
使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。
如果我们使用devm_request_threaded_irq函数来申请中断,那么就不需要我们再调用free_irq函数对其进行释放。大家可以注意一下,带有“devm_”前缀的都是一些和设备资源管理有关的函数。关于“devm_”函数的实现原理这里就不做详细的讲解了,我们的重点在于学会如何使用这些API函数,感兴趣的可以查阅一些其他文档或者帖子来看一下“devm_”函数的实现原理。
第15行,接下来就是申请input_dev,因为多点电容触摸属于input子系统。这里同样使用devm_input_allocate_device函数来申请input_dev,也就是我们前面讲解的input_allocate_device函数加“devm_”前缀版本。申请到input_dev以后还需要对其进行初始化操作。
第23~24行,设置input_dev需要上报的事件为EV_ABS和BTN_TOUCH,因为多点电容屏的触摸坐标为绝对值,因此需要上报EV_ABS事件。触摸屏有按下和抬起之分,因此需要上报BTN_TOUCH按键。
第26~29行,调用input_set_abs_params函数设置EV_ABS事件需要上报ABS_X、ABS_Y、ABS_MT_POSITION_X和ABS_MT_POSITION_Y。单点触摸需要上报ABS_X和ABS_Y,对于多点触摸需要上报ABS_MT_POSITION_X和ABS_MT_POSITION_Y。
第30行,调用input_mt_init_slots函数初始化多点电容触摸的slots。
第34行,调用input_register_device函数系统注册前面申请到的input_dev。
3、上报坐标信息
最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择使用Type A还是Type B时序。由于大多数的设备都是Type B类型,因此这里就以Type B类型为例讲解一下上报过程,参考驱动框架如下所示:
示例代码64.1.6.3 xxx_handler中断处理程序 1static irqreturn_t xxx_handler(int irq, void *dev_id) 2{ 3 4int num; /* 触摸点数量 */ 5int x[n], y[n]; /* 保存坐标值 */ 6 7/* 1、从触摸芯片获取各个触摸点坐标值 */ 8...... 9 10/* 2、上报每一个触摸点坐标 */ 11for (i = 0; i < num; i++) { 12input_mt_slot(input, id); 13input_mt_report_slot_state(input, MT_TOOL_FINGER, true); 14input_report_abs(input, ABS_MT_POSITION_X, x[i]); 15input_report_abs(input, ABS_MT_POSITION_Y, y[i]); 16} 17...... 18 19input_sync(input); 20...... 21 22return IRQ_HANDLED; 23 }

进入中断处理程序以后首先肯定是从触摸IC里面读取触摸坐标以及触摸点数量,假设触摸点数量保存到num变量,触摸点坐标存放到x,y数组里面。 第11~16行,循环上报每一个触摸点坐标,一定要按照Type B类型的时序进行,这个已经在64.1.3小节进行详细的讲解,这里就不再赘述了。 第19行,每一轮触摸点坐标上报完毕以后就调用一次input_sync函数发送一个SYN_REPORT事件。 关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动程序。

64.2 硬件原理图分析
本章实验硬件原理图参考28.2小节即可。
64.3 试验程序编写
本试验以正点原子的ATK7084(7寸800480分辨率)和ATK7016(7寸1024600分辨率)这两款屏幕所使用的FT5426触摸芯片为例,讲解如何编写多点电容触摸驱动。关于FT5426这颗触摸芯片的详细内容就不再赘述了,因为已经在裸机篇的28.1小节进行了详细的讲解。本实验对应的例程路径为:开发板光盘-> 2、Linux驱动例程-> 23_multitouch。
64.3.1 修改设备树
1、添加FT5426所使用的IO
FT5426触摸芯片用到了4个IO,一个复位IO、一个中断IO、I2C2的SCL和SDA,所以我们需要先在设备树中添加IO相关的信息。复位IO和中断IO是普通的GPIO,因此这两个IO可以放到同一个节点下去描述,I2C2的SCL和SDA属于I2C2,因此这两个要放到同一个节点下去描述。首先是复位IO和中断IO,imx6ull-alientek-emmc.dts文件里面默认有个名为“pinctrl_tsc”的节点,如果被删除了的话就自行创建,在此节点下添加触摸屏的中断引脚信息,修改以后的“pinctrl_tsc”节点内容如下所示:
示例代码64.3.1.1 pinctrl_tsc节点信息 1 pinctrl_tsc: tscgrp { 2fsl,pins = < 3MX6UL_PAD_GPIO1_IO09__GPIO1_IO090xF080/* TSC_INT */ 4>; 5 };

触摸屏复位引脚使用的是SNVS_TAMPER9,因此复位引脚信息要添加到iomuxc_snvs节点下,在iomuxc_snvs节点新建一个名为pinctrl_tsc_reset的子节点,然后在此子节点里面输入复位引脚配置信息即可,如下所示:

示例代码64.3.3.2 pinctrl_tsc_reset子节点内容 1 pinctrl_tsc_reset: tsc_reset { 2fsl,pins = < 3MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 4>; 5 };

继续添加I2C2的SCL和SDA这两个IO信息,imx6ull-alientek-emmc.dts里面默认就已经添加了I2C2的IO信息,这是NXP官方添加的,所以不需要我们去修改。找到“pinctrl_i2c2”节点,此节点就是用于描述I2C2的IO信息,节点内容如下所示:
示例代码64.3.1.3 pinctrl_i2c2节点信息 1 pinctrl_i2c2: i2c2grp { 2fsl,pins = < 3MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0 4MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0 5>; 6 };

最后,一定要检查一下设备树,确保触摸屏所使用的IO没有被其他的外设使用,如果有的话就需要将其屏蔽掉,保证只有触摸屏用到了这四个IO。 2、添加FT5426节点 FT5426这个触摸IC挂载I2C2下,因此需要向I2C2节点下添加一个子节点,此子节点用于描述FT5426,添加完成以后的I2C2节点内容如下所示(省略掉其他挂载到I2C2下的设备):

示例代码64.3.1.4 ft5426节点信息 1&i2c2 { 2clock_frequency = <100000>; 3pinctrl-names = "default"; 4pinctrl-0 = <&pinctrl_i2c2>; 5status = "okay"; 6 7/****************************/ 8/* 省略掉其他的设备节点*/ 9/****************************/ 10 11/* zuozhongkai FT5406/FT5426 */ 12ft5426: ft5426@38 { 13compatible = "edt,edt-ft5426"; 14reg = <0x38>; 15pinctrl-names = "default"; 16pinctrl-0 = <&pinctrl_tsc 17&pinctrl_tsc_reset >; 18interrupt-parent = <&gpio1>; 19interrupts = <9 0>; 20reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 21interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 22}; 23 };

第12行,触摸屏所使用的FT5426芯片节点,挂载I2C2节点下,FT5426的器件地址为0X38。 第14行,reg属性描述FT5426的器件地址为0x38。 第16和17行,pinctrl-0属性描述FT5426的复位IO和中断IO所使用的节点为pinctrl_tsc和pinctrl_tsc_reset。 第18行,interrupt-parent属性描述中断IO对应的GPIO组为GPIO1。 第19行,interrupts属性描述中断IO对应的是GPIO1组的IOI09。 第20行,reset-gpios属性描述复位IO对应的GPIO为GPIO5_IO09。 第21行,interrupt-gpios属性描述中断IO对应的GPIO为GPIO1_IO09。

64.3.2 编写多点电容触摸驱动
新建名为“23_multitouch”的文件夹,然后在23_multitouch文件夹里面创建vscode工程,工作区命名为“multitouch”。工程创建好以后新建ft5x06.c这个驱动文件,在里面输入如下所示内容(限于篇幅原因,部分内容省略掉了,完整的内容请查看驱动源码):
示例代码64.3.2.1 ft5x06.c文件内容(有省略) 1#include 2#include ...... 15#include 16/*************************************************************** 17Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 18文件名: ft5x06.c 19作者: 左忠凯 20版本: V1.0 21描述: FT5X06,包括FT5206、FT5426等触摸屏驱动程序 22其他: 无 23论坛: www.openedv.com 24日志: 初版V1.0 2019/12/23 左忠凯创建个 25***************************************************************/ 26 27#define MAX_SUPPORT_POINTS5/* 5点触摸*/ 28#define TOUCH_EVENT_DOWN0x00/* 按下*/ 29#define TOUCH_EVENT_UP0x01/* 抬起*/ 30#define TOUCH_EVENT_ON0x02/* 接触*/ 31#define TOUCH_EVENT_RESERVED0x03/* 保留*/ 32 33/* FT5X06寄存器相关宏定义 */ 34#define FT5X06_TD_STATUS_REG0X02/*状态寄存器地址*/ 35#define FT5x06_DEVICE_MODE_REG 0X00/* 模式寄存器*/ 36#define FT5426_IDG_MODE_REG0XA4/* 中断模式*/ 37#define FT5X06_READLEN29/* 要读取的寄存器个数*/ 38 39struct ft5x06_dev { 40struct device_node*nd; /* 设备节点*/ 41int irq_pin,reset_pin; /* 中断和复位IO*/ 42int irqnum; /* 中断号*/ 43void *private_data; /* 私有数据*/ 44struct input_dev *input; /* input结构体*/ 45struct i2c_client *client; /* I2C客户端*/ 46}; 47 48static struct ft5x06_dev ft5x06; 49 50/* 51* @description: 复位FT5X06 52* @param - client: 要操作的i2c 53* @param – multidev : 自定义的multitouch设备 54* @return: 0,成功; 其他负值,失败 55*/ 56static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev) 57{ 58int ret = 0; 59 60if (gpio_is_valid(dev->reset_pin)) {/* 检查IO是否有效 */ 61/* 申请复位IO,并且默认输出低电平 */ 62ret = devm_gpio_request_one(&client->dev, 63dev->reset_pin, GPIOF_OUT_INIT_LOW, 64"edt-ft5x06 reset"); 65if (ret) { 66return ret; 67} 68msleep(5); 69gpio_set_value(dev->reset_pin, 1); /* 输出高电平,停止复位 */ 70msleep(300); 71} 72return 0; 73} 74 75/* 76* @description : 从FT5X06读取多个寄存器数据 77* @param – dev : ft5x06设备 78* @param – reg : 要读取的寄存器首地址 79* @param – val : 读取到的数据 80* @param – len : 要读取的数据长度 81* @return: 操作结果 82*/ 83static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len) 84{ 85int ret; 86struct i2c_msg msg[2]; 87struct i2c_client *client = (struct i2c_client *)dev->client; ...... 101ret = i2c_transfer(client->adapter, msg, 2); 102if(ret == 2) { 103ret = 0; 104} else { 105ret = -EREMOTEIO; 106} 107return ret; 108 } 109 110 /* 111* @description: 向ft5x06多个寄存器写入数据 112* @param – dev : ft5x06设备 113* @param – reg : 要写入的寄存器首地址 114* @param – val : 要写入的数据缓冲区 115* @param – len : 要写入的数据长度 116* @return: 操作结果 117*/ 118 static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len) 119 { 120u8 b[256]; 121struct i2c_msg msg; 122struct i2c_client *client = (struct i2c_client *)dev->client; 123 124b[0] = reg; /* 寄存器首地址*/ 125memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面*/ 126 127msg.addr = client->addr; /* ft5x06地址*/ 128msg.flags = 0; /* 标记为写数据*/ 129 130msg.buf = b; /* 要写入的数据缓冲区*/ 131msg.len = len + 1; /* 要写入的数据长度*/ 132 133return i2c_transfer(client->adapter, &msg, 1); 134 } 135 136 /* 137* @description: 向ft5x06指定寄存器写入指定的值,写一个寄存器 138* @param – dev : ft5x06设备 139* @param – reg : 要写的寄存器 140* @param – data : 要写入的值 141* @return: 无 142*/ 143 static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data) 144 { 145u8 buf = 0; 146buf = data; 147ft5x06_write_regs(dev, reg, &buf, 1); 148 } 149 150 /* 151* @description: FT5X06中断服务函数 152* @param - irq: 中断号 153* @param - dev_id: 设备结构。 154* @return: 中断执行结果 155*/ 156 static irqreturn_t ft5x06_handler(int irq, void *dev_id) 157 { 158struct ft5x06_dev *multidata = https://www.it610.com/article/dev_id; 159 160u8 rdbuf[29]; 161int i, type, x, y, id; 162int offset, tplen; 163int ret; 164bool down; 165 166offset = 1; /* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */ 167tplen = 6; /* 一个触摸点有6个寄存器来保存触摸值 */ 168 169memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */ 170 171/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */ 172ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN); 173if (ret) { 174goto fail; 175} 176 177/* 上报每一个触摸点坐标 */ 178for (i = 0; i < MAX_SUPPORT_POINTS; i++) { 179u8 *buf = &rdbuf[i * tplen + offset]; 180 181/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下: 182* bit7:6Event flag0:按下 1:释放 2:接触 3:没有事件 183* bit5:4保留 184* bit3:0X轴触摸点的11~8位。 185*/ 186type = buf[0]>> 6; /* 获取触摸类型 */ 187if (type == TOUCH_EVENT_RESERVED) 188continue; 189 190/* 我们所使用的触摸屏和FT5X06是反过来的 */ 191x = ((buf[2] << 8) | buf[3]) & 0x0fff; 192y = ((buf[0] << 8) | buf[1]) & 0x0fff; 193 194/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下: 195* bit7:4Touch ID触摸ID,表示是哪个触摸点 196* bit3:0Y轴触摸点的11~8位。 197*/ 198id = (buf[2] >> 4) & 0x0f; 199down = type != TOUCH_EVENT_UP; 200 201input_mt_slot(multidata->input, id); 202input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down); 203 204if (!down) 205continue; 206 207input_report_abs(multidata->input, ABS_MT_POSITION_X, x); 208input_report_abs(multidata->input, ABS_MT_POSITION_Y, y); 209} 210 211input_mt_report_pointer_emulation(multidata->input, true); 212input_sync(multidata->input); 213 214 fail: 215return IRQ_HANDLED; 216 217 } 218 219 /* 220* @description: FT5x06中断初始化 221* @param - client: 要操作的i2c 222* @param – multidev : 自定义的multitouch设备 223* @return: 0,成功; 其他负值,失败 224*/ 225 static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev) 226 { 227int ret = 0; 228 229/* 1,申请中断GPIO */ 230if (gpio_is_valid(dev->irq_pin)) { 231ret = devm_gpio_request_one(&client->dev, dev->irq_pin, 232GPIOF_IN, "edt-ft5x06 irq"); 233if (ret) { 234dev_err(&client->dev, 235"Failed to request GPIO %d, error %d\n", 236dev->irq_pin, ret); 237return ret; 238} 239} 240 241/* 2,申请中断,client->irq就是IO中断, */ 242ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, 243ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 244client->name, &ft5x06); 245if (ret) { 246dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); 247return ret; 248} 249 250return 0; 251 } 252 253/* 254* @description: i2c驱动的probe函数,当驱动与 255*设备匹配以后此函数就会执行 256* @param - client: i2c设备 257* @param - id: i2c设备ID 258* @return: 0,成功; 其他负值,失败 259*/ 260 static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) 261 { 262int ret = 0; 263 264ft5x06.client = client; 265 266/* 1,获取设备树中的中断和复位引脚 */ 267ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0); 268ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0); 269 270/* 2,复位FT5x06 */ 271ret = ft5x06_ts_reset(client, &ft5x06); 272if(ret < 0) { 273goto fail; 274} 275 276/* 3,初始化中断 */ 277ret = ft5x06_ts_irq(client, &ft5x06); 278if(ret < 0) { 279goto fail; 280} 281 282/* 4,初始化FT5X06 */ 283ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); 284ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); 285 286/* 5,input设备注册 */ 287ft5x06.input = devm_input_allocate_device(&client->dev); 288if (!ft5x06.input) { 289ret = -ENOMEM; 290goto fail; 291} 292ft5x06.input->name = client->name; 293ft5x06.input->id.bustype = BUS_I2C; 294ft5x06.input->dev.parent = &client->dev; 295 296__set_bit(EV_KEY, ft5x06.input->evbit); 297__set_bit(EV_ABS, ft5x06.input->evbit); 298__set_bit(BTN_TOUCH, ft5x06.input->keybit); 299 300input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0); 301input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0); 302input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X, 0, 1024, 0, 0); 303input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y, 0, 600, 0, 0); 304ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0); 305if (ret) { 306goto fail; 307} 308 309ret = input_register_device(ft5x06.input); 310if (ret) 311goto fail; 312 313return 0; 314 315 fail: 316return ret; 317 } 318 319 /* 320* @description: i2c驱动的remove函数,移除i2c驱动的时候此函数会执行 321* @param – client : i2c设备 322* @return: 0,成功; 其他负值,失败 323*/ 324 static int ft5x06_ts_remove(struct i2c_client *client) 325 { 326/* 释放input_dev */ 327input_unregister_device(ft5x06.input); 328return 0; 329 } 330 331 332 /* 333*传统驱动匹配表 334*/ 335 static const struct i2c_device_id ft5x06_ts_id[] = { 336{ "edt-ft5206", 0, }, 337{ "edt-ft5426", 0, }, 338{ /* sentinel */ } 339 }; 340 341 /* 342* 设备树匹配表 343*/ 344 static const struct of_device_id ft5x06_of_match[] = { 345{ .compatible = "edt,edt-ft5206", }, 346{ .compatible = "edt,edt-ft5426", }, 347{ /* sentinel */ } 348 }; 349 350 /* i2c驱动结构体 */ 351 static struct i2c_driver ft5x06_ts_driver = { 352.driver = { 353.owner = THIS_MODULE, 354.name = "edt_ft5x06", 355.of_match_table = of_match_ptr(ft5x06_of_match), 356}, 357.id_table = ft5x06_ts_id, 358.probe= ft5x06_ts_probe, 359.remove= ft5x06_ts_remove, 360 }; 361 362 /* 363* @description: 驱动入口函数 364* @param: 无 365* @return: 无 366*/ 367 static int __init ft5x06_init(void) 368 { 369int ret = 0; 370 371ret = i2c_add_driver(&ft5x06_ts_driver); 372 373return ret; 374 } 375 376 /* 377* @description: 驱动出口函数 378* @param: 无 379* @return: 无 380*/ 381 static void __exit ft5x06_exit(void) 382 { 383i2c_del_driver(&ft5x06_ts_driver); 384 } 385 386 module_init(ft5x06_init); 387 module_exit(ft5x06_exit); 388 MODULE_LICENSE("GPL"); 389 MODULE_AUTHOR("zuozhongkai");

第39~46行,定义一个设备结构体,存放多点电容触摸设备相关属性信息。 第48行,定义一个名为ft5x06的全局变量,变量类型就是上面定义的ft5x06_dev结构体。 第56~73行,ft5x06_ts_reset函数,用于初始化FT5426触摸芯片,其实就是设置FT5426的复位IO为高电平,防止芯片复位。注意在第62行使用devm_gpio_request_one函数来申请复位IO,关于“devm_”前缀的作用已经在64.1.6小节做了详细的讲解。使用“devm_”前缀的API函数申请的资源不需要我们手动释放,内核会处理,所以这里使用devm_gpio_request_one函数申请IO以后不需要我们在卸载驱动的时候手动去释放此IO。 第83~108行,ft5x06_read_regs函数,用于连续的读取FT5426内部寄存器数据,就是I2C读取函数,在六十一章有详细的讲解。 第118~134行,ft5x06_write_regs函数,用于向FT5426寄存器写入连续的数据,也就是I2C写函数,同样在六十一章有详细的讲解。 第143~148行,ft5x06_write_reg函数,对ft5x06_write_regs函数的简单封装,向FT5426指定寄存器写入一个数据,用于配置FT5426。 第156~217行,ft5x06_handler函数,触摸屏中断服务函数,触摸点坐标的上报就是在此函数中完成的。第172行通过ft5x06_read_regs函数读取FT5426的所有触摸点信息寄存器数据,从0X02这个地址开始,一共29个寄存器。第178~209行的for循环就是一个一个的上报触摸点坐标数据,使用Type B时序,这个我们已经在前面说了很多次了。最后在212行通过input_sync函数上报SYN_REPORT事件。如果理解了前面讲解的Type B时序,那么此函数就很好看懂。 第225~251行,ft5x06_ts_irq函数,初始化FT5426的中断IO,第231行使用devm_gpio_request_one函数申请中断IO。第242行使用函数devm_request_threaded_irq申请中断,中断处理函数为ft5x06_handler。

第260317行,当I2C设备与驱动匹配以后此函数就会执行,一般在此函数中完成一些初始化工作。我们重点来看一下287309行是关于input_dev设备的初始化,第287294行申请并简单的初始化input_dev,这个我们在第五十八章已经讲解过了。第296和298行设置input_dev需要上报的事件为EV_KEY和EV_ABS,需要上报的按键码为BTN_TOUCH。EV_KEY是按键事件,用于上报触摸屏是否被按下,相当于把触摸屏当做一个按键。EV_ABS是触摸点坐标数据,BTN_TOUCH表示将触摸屏的按下和抬起用作BTN_TOUCH按键。第300303行调用input_set_abs_params函数设置EV_ABS事件需要上报ABS_X、ABS_Y、ABS_MT_POSITION_X和ABS_MT_POSITION_Y。单点触摸需要上报ABS_X和ABS_Y,对于多点触摸需要上报ABS_MT_POSITION_X和ABS_MT_POSITION_Y。第304行调用input_mt_init_slots函数初始化slots,也就是最大触摸点数量,FT5426是个5点电容触摸芯片,因此一共5个slot。最后在309行调用input_register_device函数向系统注册input_dev。
第324~329行,当卸载驱动的时候ft5x06_ts_remove函数就会执行,因为前面很多资源我们都是用“devm_”前缀函数来申请的,因此不需要手动释放。此函数只需要调用input_unregister_device来释放掉前面添加到内核中的input_dev。
第330行~结束,剩下的就是I2C驱动框架那一套,已经在六十一章中进行了详细的讲解。
64.4 运行测试
64.4.1 编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为“icm20608.o”,Makefile内容如下所示:
示例代码64.4.1.1 Makefile文件 1KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ...... 4obj-m := ft5x06.o ...... 11 clean: 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4行,设置obj-m变量的值为“ft5x06.o”。 输入如下命令编译出驱动模块文件:

make -j32
编译成功以后就会生成一个名为“ft5x06.ko”的驱动模块文件。
64.4.2 运行测试
编译设备树,然后使用新的设备树启动linux内核。
多点电容触摸屏测试不需要编写专门的APP,将上一小节编译出来ft5x06.ko拷贝到rootfs/lib/modules/4.1.15目录中,启动开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载ft5x06.ko这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe ft5x06.ko //加载驱动模块
当驱动模块加载成功以后会有如图64.4.2.1所示信息输入:
图64.4.2.1 驱动加载过程
驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动就会在我所使用的ALPHA开发板平台下就会生成/dev/input/event2这个文件,如图64.4.2.2所示:
图64.4.2.2 电容屏对应的event设备
不同的平台event序号不同,也可能是event3,event4等,一切以实际情况为准!输入如下命令查看event2,也就是多点电容触摸屏上报的原始数据:
hexdump /dev/input/event2
现在用一根手指触摸屏幕的右上角,然后再抬起,理论坐标值为(1023,0),但是由于触摸误差的原因,大概率不会是绝对的(1023,0),应该是在此值附近的一个触摸坐标值,实际的上报数据如图64.4.2.3所示:
图64.4.2.3 上报的原始数据
图64.4.2.3上报的信息是按照input_event类型呈现的,这个同样在第58.4.2小节做了详细的介绍,这里我们重点来分析一下,在多点电容触摸屏上其所代表的具体含义,将图64.4.2.3中的数据进行整理,结果如下所示:
示例代码64.4.2.1 多点电容触摸信息含义
/input_event类型***/
/* 编号 / / tv_sec / / tv_usec / / type / / code / / value */
0000000 02bb 0000 9459 0007 0003 002f 0000 0000
0000010 02bb 0000 9459 0007 0003 0039 0005 0000
0000020 02bb 0000 9459 0007 0003 0035 03ec 0000
0000030 02bb 0000 9459 0007 0003 0036 0017 0000
0000040 02bb 0000 9459 0007 0001 014a 0001 0000
0000050 02bb 0000 9459 0007 0003 0000 03ec 0000
0000060 02bb 0000 9459 0007 0003 0001 0017 0000
0000070 02bb 0000 9459 0007 0000 0000 0000 0000
0000080 02bb 0000 e5f8 0008 0003 0039 ffff ffff
0000090 02bb 0000 e5f8 0008 0001 014a 0000 0000
00000a0 02bb 0000 e5f8 0008 0000 0000 0000 0000
第1行,type为0x3,说明是一个EV_ABS事件,code为0x2f,为ABS_MT_SLOT,因此这一行就是input_mt_slot函数上报的ABS_MT_SLOT事件。value=https://www.it610.com/article/0,说明接下来上报的是第一个触摸点坐标。
第2行,type为0x3,说明是一个EV_ABS事件,code为0x39,也就是ABS_MT_TRACKING_ID,这一行就是input_mt_report_slot_state函数上报ABS_MT_TRACKING_ID事件。value=https://www.it610.com/article/5说明给SLOT0分配的ID为5。
第3行,type为0x3,是一个EV_ABS事件,code为0x35,为ABS_MT_POSITION_X,这一行就是input_report_abs函数上报的ABS_MT_POSITION_X事件,也就是触摸点的X轴坐标。value=https://www.it610.com/article/0x03ec=1004,说明触摸点X轴坐标为1004,属于屏幕右上角区域。
第4行,type为0x3,是一个EV_ABS事件,code为0x36,为ABS_MT_POSITION_Y,这一行就是input_mt_report_slot_state函数上报的ABS_MT_POSITION_Y事件,也就是触摸点的Y轴坐标。value=https://www.it610.com/article/0x17=23,说明Y轴坐标为23,由此可以看出本次触摸的坐标为(1004,23),处于屏幕右上角区域。
第5行,type为0x1,是一个EV_KEY事件,code=0x14a,为BTN_TOUCH,value=https://www.it610.com/article/0x1表示触摸屏被按下。
第6行,type为0x3,是一个EV_ABS事件,code为0x0,为ABS_X,用于单点触摸的时候上报X轴坐标。在这里和ABS_MT_POSITION_X相同,value也为0x3f0=1008。ABS_X是由input_mt_report_pointer_emulation函数上报的。
第7行,type为0x3,是一个EV_ABS事件,code为0x1,为ABS_Y,用于单点触摸的时候上报Y轴坐标。在这里和ABS_MT_POSITION_Y相同,value也为0x29=41。ABS_Y是由input_mt_report_pointer_emulation函数上报的。
第8行,type为0x0,是一个EV_SYN事件,由input_sync函数上报。
第9行,type为0x3,是一个EV_ABS事件,code为0x39,也就是ABS_MT_TRACKING_ID,value=https://www.it610.com/article/0xffffffff=-1,说明触摸点离开了屏幕。
第10行,type为0x1,是一个EV_KEY事件,code=0x14a,为BTN_TOUCH,value=https://www.it610.com/article/0x0表示手指离开触摸屏,也就是触摸屏没有被按下了。
第11行,type为0x0,是一个EV_SYN事件,由input_sync函数上报。
以上就是一个触摸点的坐标上报过程,和我们前面讲解的Type B类型设备一致。
64.4.3 将驱动添加到内核中
前面我们一直将触摸驱动编译为模块,每次系统启动以后在手动加载驱动模块,这样很不方便。当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加载驱动,不需要我们再手动modprobe了。本节我们就来学习一下如何将ft5x06.c添加到linux内核里面,步骤如下所示:
1、将驱动文件放到合适的位置
首先肯定是在内核源码中找个合适的位置将ft5x06.c放进去,ft5x06.c是个触摸屏驱动,因此我们需要查找一下linux内核里面触摸屏驱动放到了哪个目录下。linux内核里面将触摸屏驱动放到了drivers/input/touchscreen目录下,因此我们要将ft5x06.c拷贝到此目录下,命令如下:
cp ft5x06.c (内核源码目录)/drivers/input/touchscreen/ -f
2、修改对应的Makefile
修改drivers/input/touchscreen目录下的Makefile,在最下面添加下面一行:
示例代码64.4.3.1 drivers/input/touchscreen/Makefile添加的内容
obj-y += ft5x06.o
完成以后如图64.4.3.1所示:
图64.4.3.1 修改后的Makefile
修改完成以后重新编译linux内核,然后用新的zImage启动开发板。如果驱动添加成功的话系统启动的时候就会输出如图64.4.3.2所示的信息:
图64.4.3.2 启动信息
从图64.4.3.2可以看出,触摸屏驱动已经启动了,这个时候就会自动生成/dev/input/evenvtX。在本实验中将触摸屏驱动添加到linux内核里面以后触摸屏对应的是event1,而不是前面编译为模块对应的event2,这一点一定要注意!输入如下命令,查看驱动工作是否正常:
hexdump /dev/input/event1 //查看触摸屏原始数据上报信息
结果如图64.4.3.3所示:
图64.4.3.3 上报的原始数据
可以看出,坐标数据上报正常,说明驱动工作没问题。
64.5 tslib移植与使用
64.5.1 tslib移植
tslib是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用tslib进行校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有时候也需要进行校准。最主要的是tslib提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。最新版本的tslib已经支持了多点电容触摸屏,因此可以通过tslib来直观的测试多点电容触摸屏驱动,这个要比观看eventX原始数据方便的多。
tslib的移植很简单,步骤如下:
1、获取tslib源码
首先肯定是获取tslib的源码,git地址为https://github.com/kergoth/tslib,目前最新的版本是1.21。tslib源码已经放到开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》tslib-1.21.tar.bz2。将压缩包发送到ubuntu中并解压,得到名为“tslib-1.21”的目录,此目录下就是tslib源码。
2、修改tslib源码所属用户
修改解压得到的tslib-1.21目录所属用户为当前用户,这一步一定要做!否则在稍后的编译中会遇到各种问题。我当前ubuntu的登录用户名为“zuozhongkai”,那么修改命令如下:
sudo chown zuozhongkai:zuozhongkai tslib-1.21 -R
3、ubuntu工具安装
编译tslib的时候需要先在ubuntu中安装一些文件,防止编译tslib过程中出错,命令如下所示:
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
4、编译tslib
首先在ubuntu中创建一个名为“tslib”的目录存放编译结果,比如我们创建的tslib目录全路径为:/home/zuozhongkai/linux/IMX6ULL/tool/tslib。
接下来输入如下命令配置并编译tslib:
cd tslib-1.21/ //进入tslib源码目录
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/tslib
make //编译
make install //安装
注意,在使用./configure配置tslib的时候“–host”参数指定编译器,“–prefix”参数指定编译完成以后的tslib文件安装到哪里,这里肯定是安装到我们刚刚创建的“tslib”目录下。
完成以后tslib目录下的内容如图64.5.1.1所示:
图64.5.1.1 编译得到的文件
bin目录下是可执行文件,包括tslib的测试工具。etc目录下是tslib的配置文件,lib目录下是相关的库文件。将图64.5.1.1中的所有文件拷贝到开发板的根文件系统中,命令如下:
sudo cp * -rf /home/zuozhongkai/linux/nfs/rootfs
5、配置tslib
打开/etc/ts.conf文件,找到下面这一行:
module_raw input
如果上面这句前面有“#”的话就删除掉“#”。
打开/etc/profile文件,在里面加入如下内容:
示例代码64.5.1.1 /etc/profile文件添加的内容
1 export TSLIB_TSDEVICE=/dev/input/event1
2 export TSLIB_CALIBFILE=/etc/pointercal
3 export TSLIB_CONFFILE=/etc/ts.conf
4 export TSLIB_PLUGINDIR=/lib/ts
5 export TSLIB_CONSOLEDEVICE=none
6 export TSLIB_FBDEVICE=/dev/fb0
第1行,TSLIB_TSDEVICE表示触摸设备文件,这里设置为/dev/input/event1,这个要根据具体情况设置,如果你的触摸设备文件为event2那么就应该设置为/dev/input/event2,以此类推。
第2行,TSLIB_CALIBFILE表示校准文件,如果进行屏幕校准的话校准结果就保存在这个文件中,这里设置校准文件为/etc/pointercal,此文件可以不存在,校准的时候会自动生成。
第3行,TSLIB_CONFFILE表示触摸配置文件,文件为/etc/ts.conf,此文件在移植tslib的时候会生成。
第4行,TSLIB_PLUGINDIR表示tslib插件目录位置,目录为/lib/ts。
第5行,TSLIB_CONSOLEDEVICE表示控制台设置,这里不设置,因此为none。
第6行,TSLIB_FBDEVICE表示FB设备,也就是屏幕,根据实际情况配置,我的屏幕文件为/dev/fb0,因此这里设置为/dev/fb0。
全部配置好以后重启开发板,然后就可以进行测试了。
64.5.2 tslib测试
电容屏可以不用校准,如果是电阻屏就要先进行校准!校准的话输入如下命令:
ts_calibrate
校准完成以后如果不满意,或者不小心对电容屏做了校准,那么直接删除掉/etc/pointercal文件即可。
最后我们使用ts_test_mt这个软件来测试触摸屏工作是否正常,以及多点触摸是否有效,执行如下所示命令:
ts_test_mt
此命令会打开一个触摸测试界面,如图64.5.2.1所示:
图64.5.2.1 ts_test_mt界面
在图64.5.2.1上有三个按钮“Drag”、“Draw”和“Quit”,这三个按钮的功能如下:
Drag:拖拽按钮,默认就是此功能,大家可以看到屏幕中间有一个十字光标,我们可以通过触摸屏幕来拖拽此光标。一个触摸点一个十字光标,对于5点电容触摸屏,如果5个手指都放到屏幕上,那么就有5个光标,一个手指一个。
Draw:绘制按钮,按下此按钮我们就可以在屏幕上进行简单的绘制,可以通过此功能检测多点触摸工作是否正常。
Quit:退出按钮,退出ts_test_mt测试软件。
点击“Draw”按钮,使用绘制功能,5个手指一起划过屏幕,如果多点电容屏工作正常的话就会在屏幕上留下5条线,如图64.5.2.2所示:
图64.5.2.2 5点触摸测试
从图64.5.2.2可以看出,屏幕上有5条线,说明5点电容触摸工作正常。这5跳线都是白色的,图64.5.2.2中由于拍照并处理的原因,导致5条线开起来不是白色的。
64.6 使用内核自带的驱动
Linux内核已经集成了很多电容触摸IC的驱动文件,比如本章实验我们所使用FT5426,本节我们就来学习一下,如何使用Linux内核自带的多点电容触摸驱动。在使用之前要先将前面我们自己添加到内核的ft5x06.c这个文件从内核中去除掉,只需要修改drivers/input/touchscreen/Makefile这个文件即可,将下面这一行删除掉:
obj-y += ft5x06.o
内核自带的FT5426的驱动文件为drivers/input/touchscreen/edt-ft5x06.c,此驱动文件不仅仅能够驱动FT5426,FT5206、FT5406这些都可以驱动。按照如下步骤来操作,学习如何使用此驱动。
1、修改edt-ft5x06.c
edt-ft5x06.c直接使用的话是不行的,需要对其做修改,由于此文件太大,这里就不一一指出来如何修改了。大家可以直接参考我们已经修改好的edt-ft5x06.c,修改好的edt-ft5x06.c放到了开发板光盘中,路径为:1、例程源码/2、Linux驱动例程/23_multitouch/edt-ft5x06.c,直接用我们提供的edt-ft5x06.c文件替换掉内核自带的edt-ft5x06.c即可。感兴趣的可以对比一下两个文件的差异,看一下我们修改了什么地方。
2、使能内核自带的FT5X06驱动
edt-ft5x06.c这个驱动默认是没有使能的,我们需要配置Linux内核,使能此驱动,通过图形化配置界面即可完成配置。进入linux内核源码目录,输入如下所示命令打开图形化配置界面:
make menuconfig
配置路径如下:
Location:
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, …) (INPUT [=y])
-> Touchscreens (INPUT_TOUCHSCREEN [=y])
-> <*> EDT FocalTech FT5x06 I2C Touchscreen support
配置如图64.6.1所示:
图64.6.1 使能内核自带的FT5X06驱动
配置好以后重新编译linux内核,生成zImage,但是还不能直接用,要修改设备树。
3、修改设备树
修改我们在64.3.1中编写的ft5426这个设备节点,需要在里面添加compatible属性,添加的内容就要参考edt-ft5x06.c文件了,edt-ft5x06.c所支持的compatible属性列表如下所示:
示例代码64.6.1 edt-ft5x06.c的compatible兼容属性列表 static const struct of_device_id edt_ft5x06_of_match[] = { { .compatible = "edt,edt-ft5206", }, { .compatible = "edt,edt-ft5306", }, { .compatible = "edt,edt-ft5406", }, { /* sentinel */ } };

可以看出,edt-ft5x06.c文件默认支持的compatible属性只要三个“edt,edt-ft5206”、“edt,edt-ft5306”和“edt,edt-ft5406”。我们可以修改设备树中的ft5426节点,在compatible属性值添加一条“edt,edt-ft5406”(示例代码64.6.1中三选一即可)。或者修改示例代码64.6.1中的edt_ft5x06_of_match表,在里面添加一条:

{ .compatible = “edt,edt-ft5426”, }
总之一句话,让ft5426这个设备和edt-ft5x06.c这个驱动匹配起来!这里我选择修改设备树中的ft5426这个节点,修改后的ft5426节点内容如下所示:
示例代码64.6.2 ft5426节点内容 1ft5426: ft5426@38 { 2compatible = "edt,edt-ft5426","edt,edt-ft5406"; 3reg = <0x38>; 4pinctrl-names = "default"; 5pinctrl-0 = <&pinctrl_tsc>; 6interrupt-parent = <&gpio1>; 7interrupts = <9 0>; 8reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 9interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 10 };

第2行,添加一条“edt,edt-ft5406”兼容性值。 修改完成以后重新编译设备树,然后使用新得到的.dtb和zImage文件启动linux内核。如果一切正常的话系统启动的时候就会输出如图64.6.2所示信息:

图64.6.2 触摸屏log信息
直接运行ts_test_mt来测试触摸屏是否可以使用。
至此,关于Linux下的多点电容触摸驱动就结束了,重点就是掌握linux下的触摸屏上报时序,大多数都是Type B类型。
64.7 4.3寸屏触摸驱动实验
正点原子有两款4.3寸电容触摸屏,分辨率分别为800480和480272,这两款电容触摸屏的触摸驱动IC都是GT9147,因此本质上就是编写GT9147驱动。原理和方法基本和前面讲的7寸屏所使用的FT5426一样,这里我们只简单讲解一下GT9147的驱动编写步骤。
1、修改设备树pinctrl_tsc节点内容
pinctrl_tsc节点用于保存触摸屏的中断和复位引脚配置信息,GT9147和FT5426的这两个IO配置略有不同,因此需要对pinctrl_tsc节点进行修改,修改后的pinctrl_tsc节点内容如下所示:
示例代码64.7.1 修改后的pinctrl_tsc节点 1 pinctrl_tsc: tscgrp { 2fsl,pins = < 3/* 4.3寸RGB屏幕,GT9147 */ 4MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO090x10B0/* TSC_RST */ 5MX6UL_PAD_GPIO1_IO09__GPIO1_IO090x10B0/* TSC_INT */ 6>;

第4和第5行就是修改后的GT9147复位和中断IO配置。 2、在设备树的i2c2节点下添加gt9147子节点 我们还需要在i2c2节点下添加GT9147的子节点,内容如下:

示例代码64.7.2 gt9147子节点内容 1gt9147:gt9147@14 { 2compatible = "goodix,gt9147", "goodix,gt9xx"; 3reg = <0x14>; 4pinctrl-names = "default"; 5pinctrl-0 = <&pinctrl_tsc>; 6interrupt-parent = <&gpio1>; 7interrupts = <9 0>; 8reset-gpios= <&gpio5 9 GPIO_ACTIVE_LOW>; 9interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 10status = "okay"; 11 };

3、修改设备树的lcdif节点 不同的屏幕其配置参数也不同,因此需要修改lcdif节点,添加对应屏幕的参数,4.3寸800*480和480*272这两款屏幕对应的lcdif节点分别如下所示:

//示例代码64.7.3 glcdif节点内容 1/* 4.3寸480*272 */ 2&lcdif { 3pinctrl-names = "default"; 4pinctrl-0 = <&pinctrl_lcdif_dat 5&pinctrl_lcdif_ctrl>; 6 7display = <&display0>; 8status = "okay"; 9 10display0: display { 11bits-per-pixel = <24>; 12bus-width = <24>; 13 14display-timings { 15native-mode = <&timing0>; 16timing0: timing0 { 17clock-frequency = <9000000>; 18hactive = <480>; 19vactive = <272>; 20hfront-porch = <5>; 21hback-porch = <40>; 22hsync-len = <1>; 23vback-porch = <8>; 24vfront-porch = <8>; 25vsync-len = <1>; 26 27hsync-active = <0>; 28vsync-active = <0>; 29de-active = <1>; 30pixelclk-active = <0>; 31}; 32}; 33}; 34 }; 35 36 /* 4.3寸800*480 */ 37 &lcdif { 38pinctrl-names = "default"; 39pinctrl-0 = <&pinctrl_lcdif_dat 40&pinctrl_lcdif_ctrl>; 41 42display = <&display0>; 43status = "okay"; 44 45display0: display { 46bits-per-pixel = <24>; 47bus-width = <24>; 48 49display-timings { 50native-mode = <&timing0>; 51timing0: timing0 { 52clock-frequency = <31000000>; 53hactive = <800>; 54vactive = <480>; 55hfront-porch = <40>; 56hback-porch = <88>; 57hsync-len = <48>; 58vback-porch = <32>; 59vfront-porch = <13>; 60vsync-len = <3>; 61 62hsync-active = <0>; 63vsync-active = <0>; 64de-active = <1>; 65pixelclk-active = <0>; 66}; 67}; 68}; 69 };

设备树修改完成以后重新编译设备树并用新的设备树启动,检查一下LCD是否驱动成功,如果不成功的话检查lcdif节点配置。 4、编译GT9147驱动文件 GT9147的驱动编写这里就不再详细的讲解了,基本和FT5426一样,除了芯片配置的不同。GT9147驱动已经写好并放到了本章教程的驱动文件夹中,驱动文件名字为:23_multitouch->gt9147.c,大家自行编译即可。编译完成以后加载得到的gt9147.ko驱动文件,然后使用tslib进行测试,,这里就不给大家演示了。 注意,gt9147.c里面的驱动是单点触摸的,因为GT9147没有硬件检测每个触摸点的按下和抬起,因此在上报数据的时候不好处理。尝试过一些其他的处理方法,但是效果都不理想,因此改为了单点触摸。

    推荐阅读