正点原子imx6ull-mini-Linux驱动之Linux 多点电容触摸屏实验(24)

触摸屏的使用场合越来越多,从手机、平板到蜂巢取货的屏幕等,到处充斥着触摸屏。触 摸屏也从原来的电阻触摸屏发展到了很流行的电容触摸屏,我们在第二十八章裸机实验中已经 讲解了如何编写电容触摸屏驱动。本章我们就来学习一下如何在 linux 下编写多点电容触摸屏 驱动。

1:Linux 下电容触摸屏驱动框架简介

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/multitouch-protocol.txt。

老版本的 linux 内核是不支持多点电容触摸的(Multi-touch,简称 MT),MT 协议是后面加入 的,因此如果使用 2.x 版本 linux 内核的话可能找不到 MT 协议。MT 协议被分为两种类型,Type A 和 TypeB,这两种类型的区别如下:

TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使 用中非常少!)。

Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个 触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。 触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有 ABS_MT 事件是用于多点触摸的,ABS_MT 事件定义在文件 include/uapi/linux/input.h 中,相关 事件如下所示:

852 #define ABS_MT_SLOT 0x2f /* MT slot being modified */
853 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
854 #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
855 #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
856 #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
857 #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
858 #define ABS_MT_POSITION_X 0x35 /* Center X touch position */
859 #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
860 #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
861 #define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
862 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
863 #define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
864 #define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
865 #define ABS_MT_TOOL_X 0x3c /* Center X tool position */
866 #define ABS_MT_TOOL_Y 0x3d /* 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 事件来区分触摸点。

对于 TypeA 类型的设备,通过 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 需要硬件提供, 或者通过原始数据计算出来。对于 TypeA 设备,内核驱动需要一次性将触摸屏上所有的触摸点 信息全部上报,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和手指 (触摸点)跟踪是在内核空间处理的。

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。

1.2:Type A 触摸点信息上报时序

对于 Type A 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:

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 函数实现。 我们在编写 TypeA 类型的多点触摸驱动的时候就需要按照示例代码 64.1.2.1 中的时序上报 坐标信息。Linux 内核里面也有 Type A 类型的多点触摸驱动,找到 st2332.c 这个驱动文件,路 径为 drivers/input/touchscreen/st1232.c,找到 st1232_ts_irq_handler 函数,此函数里面就是上报触 摸点坐标信息的。

103 static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
104 {
......
111 ret = st1232_ts_read_data(ts);
112 if (ret < 0)
113 goto end;
114
115 /* multi touch protocol */
116 for (i = 0; i < MAX_FINGERS; i++) {
117 if (!finger[i].is_valid)
118 continue;
119
120 input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
121 input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
122 input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
123 input_mt_sync(input_dev);
124 count++;
125 }
......
140
141 /* SYN_REPORT */
142 input_sync(input_dev);
143
144 end:
145 return 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 事件

1.3:Type B 触摸点信息上报时序

对于 Type B 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:

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 来处理, 时序如下所示:

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 触摸坐标信息的,函数内容如下所示:

78 static void ili210x_report_events(struct input_dev *input,
79 const struct touchdata *touchdata)
80 {
81 int i;
82 bool touch;
83 unsigned int x, y;
84 const struct finger *finger;
85 
86 for (i = 0; i < MAX_TOUCHES; i++) {
87 input_mt_slot(input, i);
88 
89 finger = &touchdata->finger[i];
90 
91 touch = touchdata->status & (1 << i);
92 input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
93 if (touch) {
94 x = finger->x_low | (finger->x_high << 8);
95 y = finger->y_low | (finger->y_high << 8);
96 
97 input_report_abs(input, ABS_MT_POSITION_X, x);
98 input_report_abs(input, ABS_MT_POSITION_Y, y);
99 }
100 }
101
102 input_mt_report_pointer_emulation(input, false);
103 input_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 事件。

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 的不同,分为 TypeA 和 Type B 两种类型,不同的类型其上报时序不同,目前使用最多的 是 Type B 类型。接下来我们就根据前面学习过的 MT 协议来编写一个多点电容触摸驱动程序, 本章节所使用的触摸屏是正点原子的 ATK7084(7 寸 800*480)和 ATK7016(7 寸 1024*600)这两款 触摸屏,这两款触摸屏都使用 FT5426 这款触摸 IC,因此驱动程序是完全通用的。

1.5:多点触摸所使用到的 API 函数

根据前面的讲解,我们知道 linux 下的多点触摸协议其实就是通过不同的事件来上报触摸 点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的,本小节我们来看一下 一些常见的 API 函数。

1.5.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_UNUSED0x0004 /* 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,成功;负值,失败。

1.5.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 的坐标信息,也就是哪个触摸点。

返回值:无。

1.5.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,表示触摸 点溢出。 返回值:无。

1.5.4:input_report_abs 函数

TypeA 和 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 轴坐标数据值。 返回值:无。

1.5.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,追踪到的触摸点数量多于当前上报的数量。

返回值:无。

1.6:多点电容触摸驱动框架

多点电容触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据 前面的分析,我们在编写驱动的时候需要注意一下几点:

①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。

②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。

③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架

④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。 根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:

1.6.1:I2C 驱动框架

驱动总体采用 I2C 框架,参考框架代码如下所示:

1 /* 设备树匹配表 */
2 static const struct i2c_device_id xxx_ts_id[] = {
3 { "xxx", 0, },
4 { /* sentinel */ }
5 };
6 
7 /* 设备树匹配表 */
8 static 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 {
32 int ret = 0;
33
34 ret = i2c_add_driver(&xxx_ts_driver);
35
36 return ret;
37 }
38
39 /*
40 * @description : 驱动出口函数
41 * @param : 无
42 * @return : 无
43 */
44 static void __exit xxx_exit(void)
45 {
46 i2c_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 子系统等。

1.6.2:初始化触摸 IC、中断和 input 子系统

初始化操作都是在 xxx_ts_probe 函数中完成,参考框架如下所示(以下代码中步骤顺序可以 自行调整,不一定按照示例框架来):

1 static int xxx_ts_probe(struct i2c_client *client, const struct
i2c_device_id *id)
2 {
3 struct input_dev *input;
4 
5 /* 1、初始化 I2C */
6 ......
7 
8 /* 2,申请中断, */
9 devm_request_threaded_irq(&client->dev, client->irq, NULL,
10 xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
11 client->name, &xxx);
12 ......
13
14 /* 3,input 设备申请与初始化 */
15 input = devm_input_allocate_device(&client->dev);
16
17 input->name = client->name;
18 input->id.bustype = BUS_I2C;
19 input->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
26 input_set_abs_params(input, ABS_X, 0, width, 0, 0);
27 input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
28 input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
29 input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
30 input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);
31 ......
32 
33 /* 5,注册 input_dev */
34 input_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。

1.6.3:上报坐标信息

最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型 选择使用 TypeA 还是 Type B 时序。由于大多数的设备都是 Type B 类型,因此这里就以 Type B 类型为例讲解一下上报过程,参考驱动框架如下所示:

1 static irqreturn_t xxx_handler(int irq, void *dev_id)
2 {
3 
4 int num; /* 触摸点数量 */
5 int x[n], y[n]; /* 保存坐标值 */
6 
7 /* 1、从触摸芯片获取各个触摸点坐标值 */
8 ......
9 
10 /* 2、上报每一个触摸点坐标 */
11 for (i = 0; i < num; i++) {
12 input_mt_slot(input, id);
13 input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
14 input_report_abs(input, ABS_MT_POSITION_X, x[i]);
15 input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
16 }
17 ......
18 
19 input_sync(input);
20 ......
21 
22 return IRQ_HANDLED;
23 }

进入中断处理程序以后首先肯定是从触摸 IC 里面读取触摸坐标以及触摸点数量,假设触 摸点数量保存到 num 变量,触摸点坐标存放到 x,y 数组里面。

第 11~16 行,循环上报每一个触摸点坐标,一定要按照 Type B 类型的时序进行,这个已经 在 64.1.3 小节进行详细的讲解,这里就不再赘述了。

第 19 行,每一轮触摸点坐标上报完毕以后就调用一次 input_sync 函数发送一个 SYN_REPORT 事件。 关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动 程序。

2:试验程序编写

本试验以正点原子的 ATK7084(7 寸 800*480 分辨率)和 ATK7016(7 寸 1024*600 分辨率)这两款屏幕所使用的 FT5426 触摸芯片为例,讲解如何编写多点电容触摸驱动。关于 FT5426 这颗 触摸芯片的详细内容就不再赘述了,因为已经在裸机篇的 28.1 小节进行了详细的讲解。本实验 对应的例程路径为:开发板光盘-> 2、Linux 驱动例程-> 23_multitouch。

2.1:修改设备树

2.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”节点内容如下所示:

1 pinctrl_tsc: tscgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
4 >;
5 };

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

1 pinctrl_tsc_reset: tsc_reset {
2 fsl,pins = <
3 MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
4 >;
5 };

继续添加 I2C2 的 SCL 和 SDA 这两个 IO 信息,imx6ull-alientek-emmc.dts 里面默认就已经 添加了 I2C2 的 IO 信息,这是 NXP 官方添加的,所以不需要我们去修改。找到“pinctrl_i2c2” 节点,此节点就是用于描述 I2C2 的 IO 信息,节点内容如下所示:

1 pinctrl_i2c2: i2c2grp {
2 fsl,pins = <
3 MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
4 MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
5 >;
6 };

最后,一定要检查一下设备树,确保触摸屏所使用的 IO 没有被其他的外设使用,如果有的 话就需要将其屏蔽掉,保证只有触摸屏用到了这四个 IO。

2.1.2:添加 FT5426 节点

FT5426 这个触摸 IC 挂载 I2C2 下,因此需要向 I2C2 节点下添加一个子节点,此子节点用 于描述 FT5426,添加完成以后的 I2C2 节点内容如下所示(省略掉其他挂载到 I2C2 下的设备):

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

2.2:编写多点电容触摸驱动

新建名为“23_multitouch”的文件夹,然后在 23_multitouch 文件夹里面创建 vscode 工程, 工作区命名为“multitouch”。工程创建好以后新建 ft5x06.c 这个驱动文件,在里面输入如下所示 内容(限于篇幅原因,部分内容省略掉了,完整的内容请查看驱动源码):

#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ft5x06.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: FT5X06,包括FT5206、FT5426等触摸屏驱动程序
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/12/23 左忠凯创建个
***************************************************************/

#define MAX_SUPPORT_POINTS		5			/* 5点触摸 	*/
#define TOUCH_EVENT_DOWN		0x00		/* 按下 	*/
#define TOUCH_EVENT_UP			0x01		/* 抬起 	*/
#define TOUCH_EVENT_ON			0x02		/* 接触 	*/
#define TOUCH_EVENT_RESERVED	0x03		/* 保留 	*/

/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG	0X02		/*	状态寄存器地址 		*/
#define FT5x06_DEVICE_MODE_REG	0X00 		/* 模式寄存器 			*/
#define FT5426_IDG_MODE_REG		0XA4		/* 中断模式				*/
#define FT5X06_READLEN			29			/* 要读取的寄存器个数 	*/

struct ft5x06_dev {
	struct device_node	*nd; 				/* 设备节点 		*/
	int irq_pin,reset_pin;					/* 中断和复位IO		*/
	int irqnum;								/* 中断号    		*/
	void *private_data;						/* 私有数据 		*/
	struct input_dev *input;				/* input结构体 		*/
	struct i2c_client *client;				/* I2C客户端 		*/
};

static struct ft5x06_dev ft5x06;

/*
 * @description     : 复位FT5X06
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	if (gpio_is_valid(dev->reset_pin)) {  		/* 检查IO是否有效 */
		/* 申请复位IO,并且默认输出低电平 */
		ret = devm_gpio_request_one(&client->dev,	
					dev->reset_pin, GPIOF_OUT_INIT_LOW,
					"edt-ft5x06 reset");
		if (ret) {
			return ret;
		}

		msleep(5);
		gpio_set_value(dev->reset_pin, 1);	/* 输出高电平,停止复位 */
		msleep(300);
	}

	return 0;
}

/*
 * @description	: 从FT5X06读取多个寄存器数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->client;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ft5x06地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ft5x06地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ft5x06多个寄存器写入数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->client;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ft5x06地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 向ft5x06指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ft5x06设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ft5x06_write_regs(dev, reg, &buf, 1);
}

/*
 * @description     : FT5X06中断服务函数
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
	struct ft5x06_dev *multidata = dev_id;

	u8 rdbuf[29];
	int i, type, x, y, id;
	int offset, tplen;
	int ret;
	bool down;

	offset = 1; 	/* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
	tplen = 6;		/* 一个触摸点有6个寄存器来保存触摸值 */

	memset(rdbuf, 0, sizeof(rdbuf));		/* 清除 */

	/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
	ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
	if (ret) {
		goto fail;
	}

	/* 上报每一个触摸点坐标 */
	for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
		u8 *buf = &rdbuf[i * tplen + offset];

		/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
		 * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
		 * bit5:4  保留
		 * bit3:0  X轴触摸点的11~8位。
		 */
		type = buf[0] >> 6;     /* 获取触摸类型 */
		if (type == TOUCH_EVENT_RESERVED)
			continue;
 
		/* 我们所使用的触摸屏和FT5X06是反过来的 */
		x = ((buf[2] << 8) | buf[3]) & 0x0fff;
		y = ((buf[0] << 8) | buf[1]) & 0x0fff;
		
		/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
		 * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
		 * bit3:0  Y轴触摸点的11~8位。
		 */
		id = (buf[2] >> 4) & 0x0f;
		down = type != TOUCH_EVENT_UP;

		input_mt_slot(multidata->input, id);
		input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);

		if (!down)
			continue;

		input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
		input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
	}

	input_mt_report_pointer_emulation(multidata->input, true);
	input_sync(multidata->input);

fail:
	return IRQ_HANDLED;

}

/*
 * @description     : FT5x06中断初始化
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	/* 1,申请中断GPIO */
	if (gpio_is_valid(dev->irq_pin)) {
		ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
					GPIOF_IN, "edt-ft5x06 irq");
		if (ret) {
			dev_err(&client->dev,
				"Failed to request GPIO %d, error %d\n",
				dev->irq_pin, ret);
			return ret;
		}
	}

	/* 2,申请中断,client->irq就是IO中断, */
	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
					ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
					client->name, &ft5x06);
	if (ret) {
		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
		return ret;
	}

	return 0;
}

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret = 0;

	ft5x06.client = client;

	/* 1,获取设备树中的中断和复位引脚 */
	ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
	ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

	/* 2,复位FT5x06 */
	ret = ft5x06_ts_reset(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 3,初始化中断 */
	ret = ft5x06_ts_irq(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 4,初始化FT5X06 */
	ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); 	/* 进入正常模式 	*/
	ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); 		/* FT5426中断模式	*/

	/* 5,input设备注册 */
	ft5x06.input = devm_input_allocate_device(&client->dev);
	if (!ft5x06.input) {
		ret = -ENOMEM;
		goto fail;
	}
	ft5x06.input->name = client->name;
	ft5x06.input->id.bustype = BUS_I2C;
	ft5x06.input->dev.parent = &client->dev;

	__set_bit(EV_KEY, ft5x06.input->evbit);
	__set_bit(EV_ABS, ft5x06.input->evbit);
	__set_bit(BTN_TOUCH, ft5x06.input->keybit);

	input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);	     
	ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
	if (ret) {
		goto fail;
	}

	ret = input_register_device(ft5x06.input);
	if (ret)
		goto fail;

	return 0;

fail:
	return ret;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_remove(struct i2c_client *client)
{	
	/* 释放input_dev */
	input_unregister_device(ft5x06.input);
	return 0;
}


/*
 *  传统驱动匹配表
 */ 
static const struct i2c_device_id ft5x06_ts_id[] = {
	{ "edt-ft5206", 0, },
	{ "edt-ft5426", 0, },
	{ /* sentinel */ }
};

/*
 * 设备树匹配表 
 */
static const struct of_device_id ft5x06_of_match[] = {
	{ .compatible = "edt,edt-ft5206", },
	{ .compatible = "edt,edt-ft5426", },
	{ /* sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ft5x06_ts_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "edt_ft5x06",
		.of_match_table = of_match_ptr(ft5x06_of_match),
	},
	.id_table = ft5x06_ts_id,
	.probe    = ft5x06_ts_probe,
	.remove   = ft5x06_ts_remove,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ft5x06_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ft5x06_ts_driver);

	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ft5x06_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
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。

第 260~317 行,当 I2C 设备与驱动匹配以后此函数就会执行,一般在此函数中完成一些初 始化工作。我们重点来看一下 287~309 行是关于 input_dev 设备的初始化,

第 287~294 行申请 并简单的初始化 input_dev,这个我们在第五十八章已经讲解过了。

第 296 和 298行设置 input_dev 需要上报的事件为 EV_KEY 和 EV_ABS,需要上报的按键码为 BTN_TOUCH。EV_KEY 是按 键事件,用于上报触摸屏是否被按下,相当于把触摸屏当做一个按键。EV_ABS 是触摸点坐标 数据,BTN_TOUCH 表示将触摸屏的按下和抬起用作 BTN_TOUCH 按键。

第 300~303 行调用 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 驱动框架那一套,已经在六十一章中进行了详细的讲解。

3:编译驱动程序

编写 Makefile 文件

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)

obj-m := ft5x06.o 

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译成功以后就会生成一个名为“ft5x06.ko”的驱动模块文件。

4:运行测试

编译设备树,然后使用新的设备树启动 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 所示信息输入:

驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动 就会在我所使用的 ALPHA 开发板平台下就会生成/dev/input/event2 这个文件,如图 64.4.2.2 所 示:

不同的平台 event 序号不同,也可能是 event3,event4 等,一切以实际情况为准!输入如下 命令查看 event2,也就是多点电容触摸屏上报的原始数据:

 

hexdump /dev/input/event2

现在用一根手指触摸屏幕的右上角,然后再抬起,理论坐标值为(1023,0),但是由于触摸误 差的原因,大概率不会是绝对的(1023,0),应该是在此值附近的一个触摸坐标值,实际的上报数 据如图 64.4.2.3 所示:

图 64.4.2.3 上报的信息是按照 input_event 类型呈现的,这个同样在第 58.4.2 小节做了详细 的介绍,这里我们重点来分析一下,在多点电容触摸屏上其所代表的具体含义,将图 64.4.2.3 中 的数据进行整理,结果如下所示:

/* 编号 */ /* 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=0,说明接下来上报的是第 一个触摸点坐标。

第 2 行 , type 为 0x3 , 说 明 是 一 个 EV_ABS 事 件 , code 为 0x39 , 也 就 是 ABS_MT_TRACKING_ID ,这一行就是 input_mt_report_slot_state 函 数 上 报 ABS_MT_TRACKING_ID 事件。value=5 说明给 SLOT0 分配的 ID 为 5。

第 3 行,type 为 0x3,是一个 EV_ABS 事件,code 为 0x35,为 ABS_MT_POSITION_X, 这一行就是 input_report_abs 函数上报的 ABS_MT_POSITION_X 事件,也就是触摸点的 X 轴坐 标。value=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=0x17=23,说明 Y 轴坐标为 23,由此可以看出本次触摸的坐标为(1004,23), 处于屏幕右上角区域。

第 5 行,type 为 0x1,是一个 EV_KEY 事件,code=0x14a,为 BTN_TOUCH,value=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=0xffffffff=-1,说明触摸点离开了屏幕。

第 10 行,type 为 0x1,是一个 EV_KEY 事件,code=0x14a,为 BTN_TOUCH,value=0x0 表示手指离开触摸屏,也就是触摸屏没有被按下了。

第 11 行,type 为 0x0,是一个 EV_SYN 事件,由 input_sync 函数上报。 以上就是一个触摸点的坐标上报过程,和我们前面讲解的 Type B 类型设备一致。

5:将驱动添加到内核中

前面我们一直将触摸驱动编译为模块,每次系统启动以后在手动加载驱动模块,这样很不方便。当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加 载驱动,不需要我们再手动 modprobe 了。本节我们就来学习一下如何将 ft5x06.c 添加到 linux 内核里面,步骤如下所示:

5.1:将驱动文件放到合适的位

首先肯定是在内核源码中找个合适的位置将 ft5x06.c 放进去,ft5x06.c 是个触摸屏驱动,因 此我们需要查找一下 linux 内核里面触摸屏驱动放到了哪个目录下。linux 内核里面将触摸屏驱 动放到了 drivers/input/touchscreen 目录下,因此我们要将 ft5x06.c 拷贝到此目录下,命令如下:

cp ft5x06.c (内核源码目录)/drivers/input/touchscreen/ -f

5.2:修改对应的 Makefile

修改 drivers/input/touchscreen 目录下的 Makefile,在最下面添加下面一行:

obj-y += ft5x06.o

完成以后如图 64.4.3.1 所示:

修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。如果驱动添加成功的 话系统启动的时候就会输出如图 64.4.3.2 所示的信息:

从图 64.4.3.2 可以看出,触摸屏驱动已经启动了,这个时候就会自动生成/dev/input/evenvtX。 在本实验中将触摸屏驱动添加到 linux 内核里面以后触摸屏对应的是 event1,而不是前面编译 为模块对应的 event2,这一点一定要注意!输入如下命令,查看驱动工作是否正常:

hexdump /dev/input/event1 //查看触摸屏原始数据上报信息

结果如图 64.4.3.3 所示

6:tslib 移植与使用

6.1:tslib 移植

tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行 校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有 时候也需要进行校准。最主要的是 tslib 提供了一些其他软件,我们可以通过这些软件来测试触 摸屏工作是否正常。最新版本的 tslib 已经支持了多点电容触摸屏,因此可以通过 tslib 来直观的 测试多点电容触摸屏驱动,这个要比观看 eventX 原始数据方便的多。 tslib 的移植很简单,步骤如下:

6.1.1:获取 tslib 源码

首先肯定是获取 tslib 的源码,git 地址为 https://github.com/kergoth/tslib,目前最新的版本是 1.21。tslib源码已经放到开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》tslib-1.21.tar.bz2。 将压缩包发送到 ubuntu 中并解压,得到名为“tslib-1.21”的目录,此目录下就是 tslib 源码。

6.1.2:修改 tslib 源码所属用户

修改解压得到的 tslib-1.21 目录所属用户为当前用户,这一步一定要做!否则在稍后的编译 中会遇到各种问题。我当前 ubuntu 的登录用户名为“zuozhongkai”,那么修改命令如下:

sudo chown zuozhongkai:zuozhongkai tslib-1.21 -R

6.1.3:ubuntu 工具安装

编译 tslib 的时候需要先在 ubuntu 中安装一些文件,防止编译 tslib 过程中出错,命令如下 所示:

sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool

6.1.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 所示:

bin 目录下是可执行文件,包括 tslib 的测试工具。etc 目录下是 tslib 的配置文件,lib 目录 下是相关的库文件。将图 64.5.1.1 中的所有文件拷贝到开发板的根文件系统中,命令如下:

sudo cp * -rf /home/zuozhongkai/linux/nfs/rootfs

6.1.5:配置 tslib

打开/etc/ts.conf 文件,找到下面这一行:

module_raw input

如果上面这句前面有“#”的话就删除掉“#”。 打开/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。 全部配置好以后重启开发板,然后就可以进行测试了。

6.2:tslib 测试

电容屏可以不用校准,如果是电阻屏就要先进行校准!校准的话输入如下命令:

ts_calibrate

校准完成以后如果不满意,或者不小心对电容屏做了校准,那么直接删除掉/etc/pointercal 文件即可。 最后我们使用 ts_test_mt 这个软件来测试触摸屏工作是否正常,以及多点触摸是否有效, 执行如下所示命令:

ts_test_mt

此命令会打开一个触摸测试界面,如图 64.5.2.1 所示:

在图 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 条线,说明 5 点电容触摸工作正常。这 5 跳线都是白 色的,图 64.5.2.2 中由于拍照并处理的原因,导致 5 条线开起来不是白色的。

7:使用内核自带的驱动

Linux 内核已经集成了很多电容触摸 IC 的驱动文件,比如本章实验我们所使用 FT5426, 本节我们就来学习一下,如何使用 Linux 内核自带的多点电容触摸驱动。在使用之前要先将前 面我们自己添加到内核的 ft5x06.c 这 个 文 件 从 内 核 中 去 除 掉 , 只 需 要 修 改 drivers/input/touchscreen/Makefile 这个文件即可,将下面这一行删除掉:

obj-y += ft5x06.o

内核自带的 FT5426 的驱动文件为 drivers/input/touchscreen/edt-ft5x06.c,此驱动文件不仅仅 能够驱动 FT5426,FT5206、FT5406 这些都可以驱动。按照如下步骤来操作,学习如何使用此 驱动。

7.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 即可。感兴趣的可以对比一下两个 文件的差异,看一下我们修改了什么地方。

7.2:使能内核自带的 FT5X06 驱动

edt-ft5x06.c 这个驱动默认是没有使能的,我们需要配置 Linux 内核,使能此驱动,通过图 形化配置界面即可完成配置。进入 linux 内核源码目录,输入如下所示命令打开图形化配置界 面:

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

配置好以后重新编译 linux 内核,生成 zImage,但是还不能直接用,要修改设备树。

7.3:修改设备树

修改我们在 64.3.1 中编写的 ft5426 这个设备节点,需要在里面添加 compatible 属性,添加 的内容就要参考 edt-ft5x06.c 文件了,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 节点内容如下所示:

1 ft5426: ft5426@38 {
2 compatible = "edt,edt-ft5426","edt,edt-ft5406";
3 reg = <0x38>;
4 pinctrl-names = "default";
5 pinctrl-0 = <&pinctrl_tsc>;
6 interrupt-parent = <&gpio1>;
7 interrupts = <9 0>;
8 reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 
9 interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
10 };

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

直接运行 ts_test_mt 来测试触摸屏是否可以使用。 至此,关于 Linux 下的多点电容触摸驱动就结束了,重点就是掌握 linux 下的触摸屏上报 时序,大多数都是 Type B 类型。

 8:4.3 寸屏触摸驱动实验

正点原子有两款 4.3 寸电容触摸屏,分辨率分别为 800*480 和 480*272,这两款电容触摸 屏的触摸驱动 IC 都是 GT9147,因此本质上就是编写 GT9147 驱动。原理和方法基本和前面讲 的 7 寸屏所使用的 FT5426 一样,这里我们只简单讲解一下 GT9147 的驱动编写步骤。

8.1:修改设备树 pinctrl_tsc 节点内容

pinctrl_tsc 节点用于保存触摸屏的中断和复位引脚配置信息,GT9147 和 FT5426 的这两个 IO 配置略有不同,因此需要对 pinctrl_tsc 节点进行修改,修改后的 pinctrl_tsc 节点内容如下所 示:

1 pinctrl_tsc: tscgrp {
2 fsl,pins = <
3 /* 4.3 寸 RGB 屏幕,GT9147 */
4 MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 /* TSC_RST */
5 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x10B0 /* TSC_INT */
6 >;

第 4 和第 5 行就是修改后的 GT9147 复位和中断 IO 配置。

8.2:在设备树的 i2c2 节点下添加 gt9147 子节点

我们还需要在 i2c2 节点下添加 GT9147 的子节点,内容如下:

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

8.3:修改设备树的 lcdif 节点

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

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

设备树修改完成以后重新编译设备树并用新的设备树启动,检查一下 LCD 是否驱动成 功,如果不成功的话检查 lcdif 节点配置。

8.4:编译 GT9147 驱动文件

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

 本文仅在记录学习正点原子imx6ull-mini开发板的过程,不做他用。 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值