电容触摸驱动的基本原理我们已经在裸机中进行了详细的讲解,回顾一下几个重要的知识点:
①、电容触摸屏是 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 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。
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 事件来区分触摸点。
对于 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。
对于 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 函数实现。
我们在编写 Type A 类型的多点触摸驱动的时候就需要按照示例代码中的时序上报坐标信息。
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 事件
对于 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 类型的多点触摸驱动的时候就需要按照示例代码中的时序上报坐标信息。
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 事件。
在示例代码中给出了 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,因此驱动程序是完全通用的。
根据前面的讲解,我们知道 linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的,本小节我们来看一下一些常见的 API 函数。
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,成功;负值,失败。
此函数用于 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 的坐标信息,也就是哪个触摸点。
返回值:无。
此函数用于 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,表示触摸点溢出。
返回值:无。
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 轴坐标数据值。
返回值:无。
如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用 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,追踪到的触摸点数量多于当前上报的数量。
返回值:无。
我们来梳理一下 linux下多点电容触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意一下几点:
①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。
根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:
驱动总体采用 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");
当设备树中触摸 IC的设备节点和驱动匹配以后,示例代码中第 21 行的 xxx_ts_probe 函数就会执行,我们可以在此函数中初始化触摸 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 函数,也就是线程的意思。那么为什么要中断线程化呢?我们都是知道硬件中断具有最高优先级,不论什么时候只要硬件中断发生,那么内核都会终止当前正在执行的操作,转而去执行中断处理程序(不考虑关闭中断和中断优先级的情况),如果中断非常频繁的话那么内核将会频繁的执行中断处理程序,导致任务得不到及时的处理。中断线程化以后中断将作为内核线程运行,而且也可以被赋予不同的优先级,任务的优先级可能比中断线程的优先级高,这样做的目的就是保证高优先级的任务能被优先处理。大家可能会疑问,前面不是说可以将比较耗时的中断放到下半部(bottomhalf)处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争。要注意,并不是所有的中断都可以被线程化,重要的中断就不能这么操作。对于触摸屏而言只要手指放到屏幕上,它可能就会一直产生中断(视具体芯片而定,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。
最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择使用 Type A 还是 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 类型的时序进行。
第 19 行,每一轮触摸点坐标上报完毕以后就调用一次 input_sync 函数发送一个SYN_REPORT 事件。
关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动程序。
本试验以正点原子的 ATK7084(7 寸 800480 分辨率)和 ATK7016(7 寸 1024600 分辨率)这两款屏幕所使用的 FT5426 触摸芯片为例,讲解如何编写多点电容触摸驱动。
FT5426 触摸芯片用到了 4 个 IO,一个复位 IO、一个中断 IO、I2C2 的 SCL 和 SDA,所以我们需要先在设备树中添加 IO 相关的信息。
复位 IO 和中断 IO 是普通的 GPIO,因此这两个 IO可以放到同一个节点下去描述,I2C2 的 SCL 和 SDA 属于 I2C2,因此这两个要放到同一个节点下去描述。
首先是复位 IO 和中断 IO,imx6ull-luatao-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 的子节点,然后在此子节点里面输入复位引脚配置信息即可,如下所示:
pinctrl_tsc_reset: tsc_reset {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};
继续添加 I2C2 的 SCL 和 SDA 这两个 IO 信息,imx6ull-luatao-emmc.dts 里面默认就已经添加了 I2C2 的 IO 信息,这是 NXP 官方添加的,所以不需要我们去修改。找到“pinctrl_i2c2”节点,此节点就是用于描述 I2C2 的 IO 信息,节点内容如下所示:
最后,一定要检查一下设备树,确保触摸屏所使用的 IO 没有被其他的外设使用,如果有的话就需要将其屏蔽掉,保证只有触摸屏用到了这四个 IO。
FT5426 这个触摸 IC 挂载 I2C2 下,因此需要向 I2C2 节点下添加一个子节点,此子节点用于描述 FT5426,添加完成以后的 I2C2 节点内容如下所示(省略掉其他挂载到 I2C2 下的设备):
/* 触摸 */
/* FT5406/FT5426 */
ft5426: ft5426@38 {
compatible = "edt,edt-ft5426";
reg = <0x38>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
第 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。
新建名为“23_multitouch”的文件夹,然后在 23_multitouch 文件夹里面创建 vscode 工程,工作区命名为“multitouch”。工程创建好以后新建 ft5x06.c 这个驱动文件,在里面输入如下所示内容:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/***************************************************************
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 = ® /* 读取的首地址 */
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_”前缀的作用已经在小节做了详细的讲解。使用“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 驱动框架那一套。
depmod //第一次加载驱动的时候需要运行此命令
modprobe ft5x06.ko //加载驱动模块
当驱动模块加载成功以后会有如图
驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动就会在我所使用的 ALPHA 开发板平台下就会生成/dev/input/event2 这个文件,如图所示
不同的平台 event 序号不同,也可能是 event3,event4 等,一切以实际情况为准!输入如下命令查看 event2,也就是多点电容触摸屏上报的原始数据:
hexdump /dev/input/event2
现在用一根手指触摸屏幕的右上角,然后再抬起,理论坐标值为(1023,0),但是由于触摸误差的原因,大概率不会是绝对的(1023,0),应该是在此值附近的一个触摸坐标值,实际的上报数据如图所示:
上报的信息是按照 input_event 类型呈现的,这个同样在小节做了详细的介绍,这里我们重点来分析一下,在多点电容触摸屏上其所代表的具体含义,将图中的数据进行整理,结果如下所示:
第 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 类型设备一致。
前面我们一直将触摸驱动编译为模块,每次系统启动以后在手动加载驱动模块,这样很不方便。当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加载驱动,不需要我们再手动 modprobe 了。本节我们就来学习一下如何将 ft5x06.c 添加到 linux内核里面,步骤如下所示:
首先肯定是在内核源码中找个合适的位置将 ft5x06.c 放进去,ft5x06.c 是个触摸屏驱动,因此我们需要查找一下 linux 内核里面触摸屏驱动放到了哪个目录下。
linux 内核里面将触摸屏驱动放到了 drivers/input/touchscreen 目录下,因此我们要将 ft5x06.c 拷贝到此目录下,命令如下:
cp ft5x06.c (内核源码目录)/drivers/input/touchscreen/ -f
修改 drivers/input/touchscreen 目录下的 Makefile,在最下面添加下面一行:
obj-y += ft5x06.o
修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。如果驱动添加成功的话系统启动的时候就会输出如图
可以看出,触摸屏驱动已经启动了,这个时候就会自动生成/dev/input/evenvtX。
在本实验中将触摸屏驱动添加到 linux 内核里面以后触摸屏对应的是 event1,而不是前面编译为模块对应的 event2,这一点一定要注意!输入如下命令,查看驱动工作是否正常:
hexdump /dev/input/event1 //查看触摸屏原始数据上报信息
tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有时候也需要进行校准。最主要的是 tslib 提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。
最新版本的 tslib 已经支持了多点电容触摸屏,因此可以通过 tslib 来直观的测试多点电容触摸屏驱动,这个要比观看 eventX 原始数据方便的多。
tslib 的移植很简单,步骤如下:
首先肯定是获取 tslib 的源码,git 地址为https://github.com/kergoth/tslib,目前最新的版本是1.21。tslib源码已经放到开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》tslib-1.21.tar.bz2。
将压缩包发送到 ubuntu 中并解压,得到名为“tslib-1.21”的目录,此目录下就是 tslib 源码。
修改解压得到的 tslib-1.21 目录所属用户为当前用户,这一步一定要做!否则在稍后的编译中会遇到各种问题。我当前 ubuntu 的登录用户名为“luatao”,那么修改命令如下:
sudo chown luatao:luatao tslib-1.21 -R
编译 tslib 的时候需要先在 ubuntu 中安装一些文件,防止编译 tslib 过程中出错,命令如下所示:
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
首先在 ubuntu 中创建一个名为“tslib”的目录存放编译结果,比如我们创建的 tslib 目录全路径为:/home/luatao/linux/tool/tslib
接下来输入如下命令配置并编译 tslib:
cd tslib-1.21/ //进入 tslib 源码目录
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/home/luatao/linux/tool/tslib
make //编译
make install //安装
注意,在使用./configure 配置 tslib 的时候“–host”参数指定编译器,“–prefix”参数指定编译完成以后的 tslib 文件安装到哪里,这里肯定是安装到我们刚刚创建的“tslib”目录下。完成以后 tslib 目录下的内容如图所示:
bin 目录下是可执行文件,包括 tslib 的测试工具。etc 目录下是 tslib 的配置文件,lib 目录下是相关的库文件。将图中的所有文件拷贝到开发板的根文件系统中,命令如下:
sudo cp * -rf /home/luatao/linux/nfs/rootfs
在文件系统根目录下打开/etc/ts.conf 文件,找到下面这一行:
module_raw input
打开/etc/profile 文件,在里面加入如下内容:
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。
全部配置好以后重启开发板,然后就可以进行测试了。
电容屏可以不用校准,如果是电阻屏就要先进行校准!校准的话输入如下命令:
ts_calibrate
校准完成以后如果不满意,或者不小心对电容屏做了校准,那么直接删除掉/etc/pointercal文件即可。
最后我们使用 ts_test_mt 这个软件来测试触摸屏工作是否正常,以及多点触摸是否有效,执行如下所示命令:
ts_test_mt
此命令会打开一个触摸测试界面
在图上有三个按钮“Drag”、“Draw”和“Quit”,这三个按钮的功能如下:
Drag: :拖拽按钮,默认就是此功能,大家可以看到屏幕中间有一个十字光标,我们可以通过触摸屏幕来拖拽此光标。一个触摸点一个十字光标,对于 5 点电容触摸屏,如果 5 个手指都放到屏幕上,那么就有 5 个光标,一个手指一个。
Draw: :绘制按钮,按下此按钮我们就可以在屏幕上进行简单的绘制,可以通过此功能检测多点触摸工作是否正常。
Quit :退出按钮,退出 ts_test_mt 测试软件。
暂不写
暂不写