基于 i.MX6ULL-Linux的触摸屏(GT911)驱动

基于 i.MX6ULL-Linux的触摸屏(GT911)驱动

H_Haozi Lv2

A 前情提要

这里需要用到的前置知识有: Linux驱动基础,IIC子系统,INPUT子系统,Pinctrl和GPIO子系统,以及中断驱动
其中 IIC 用来读取触摸屏的信息,INPUT用来向linux内核上报信息,当屏幕被按下触发中断

B GT911 大致介绍

GT911、GT928、GT9147都属于GT9系列非单层多点触控芯片,他们支持的触控点数不同(GT928支持10个点、GT911支持5个点)、驱动和感应通道也可能不同。可是他们的寄存器和IIC通讯时序是相同的,也就是说驱动程序是基本兼容的。

GT911初始化的时候有两种方法选择IIC地址

  1. 把RST、INT拉低,延时10ms,把INT拉高,延时100us,把RST拉高,就可以把IIC地址设为0x28/0x29,七位地址就是 0X14

  2. 把RST、INT拉低,延时10ms,把RST拉高,就可以把IIC地址设为0xBA/0xBB 七位地址就是 0X5D

下面我们选择第二种方法来初始化屏幕,设置完地址之后就需要把INT设置为悬空输出模式,RST保持拉高,默认通过 上升沿 触发中断,然后来读取触摸信息

C 设备树修改

主要是设置复位引脚,中断引脚和IIC引脚,下面给出修改部分,同时要注意屏蔽掉使用这四个引脚的其它驱动,防止其它驱动操作了这四个引脚发生错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**************reset引脚部分****************/
&iomuxc_snvs {
pinctrl-names = "default_snvs";
pinctrl-0 = <&pinctrl_hog_2>;
imx6ul-evk {

/*省略不相关部分*/

//Mouse MT ret
pinctrl_tsc_reset: tsc_reset{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};

/*省略不相关部分*/
}
}

/**************int引脚部分****************/

&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;

/*省略不相关部分*/

//Mouse INT
pinctrl_tsc: tscgrp{
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x79
>;
};

/*省略不相关部分*/
}

/**************IIC引脚部分****************/


&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;

imx6ul-evk {

/*省略不相关部分*/

pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};

/*省略不相关部分*/

}
}

/**************GPIO节点部分****************/

&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";

/*省略不相关部分*/

gt911:gt911@5d {
compatible = "goodix,gt911";
reg = <0x5d>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 1>; /* 使用的是9号中断 因为引脚是9 触发方式是1 上升沿触发 */
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; /* 4*32 + 9 = 128+9 = 137 */
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; /* 9 */
goodix,cfg-group0 = [
41 20 03 E0 01 05 BD 00 01 0F
14 0F 5F 32 03 05 00 00 00 00
00 00 00 18 1A 1C 14 89 29 0B
3A 38 8F 04 00 00 01 21 02 1D
00 01 00 00 00 00 00 00 00 00
00 28 50 94 C5 02 08 00 00 04
80 2A 00 80 31 00 83 38 00 8C
41 00 9B 4A 00 9A 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 16 14 12 10 0E 0C 0A 08
06 04 02 FF FF FF 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 02 04 06 08 0A 0F 10
12 16 18 1C 1D 1E 1F 20 21 22
FF FF FF FF FF FF FF FF 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 B7 01];
status = "okay";
};

/*省略不相关部分*/
}

D 驱动编写

驱动主要涉及 IIC框架,测试中断,INPUT子系统框架,测试上报数据,测试屏幕

下面先给出完整代码,包括单点触摸和多点触摸(其中因为gt911不支持硬件检测每个触摸点的按下和抬起,所以多点效果并不好,可以通过驱动文件中最上面的宏MORE_GT911选择使用多点或单点)

大致说一下代码流程:

  1. 首先通过IIC的匹配表(module_i2c_driver(gt911_i2c_driver);),如果匹配成功,进入gt911_probe()函数
  2. 首先获取设备树种的中断和复位引脚,然后根据gt911的复位和初始化时序,来初始化屏幕
  3. 这时候可以先初始化中断,然后便能通过中断中添加调试信息来判断是否初始化成功,但这里有一点要注意的,gt911要求触发中断之后,需要主要清除中断标志位,才能正常响应下一次中断( data = 0x00; ret = gt911_write_regs(dev, GT_GSTID_REG, &data, 1)),所以要提前写好IIC的read和write函数,成功之后当你按屏幕后,将会有调试信息输出
  4. 确认中断无误后,可以在probe中添加INPUT子系统的初始化,以及对屏幕的软复位,同时将中断的初始化放到probe函数最后
  5. 最重要的一步就是在中断函数中添加对触摸信息的上报同步,这里要注意上报的顺序,其中中断使用的是中断线程化

下面再具体说说驱动中的几个重点:

  • 上报流程

这里先简单介绍一下多点触摸(MT)协议,通常有两种: TypeA(适用于触摸点不能被区分和追踪的硬件) 和 TypeB(适用于有硬件追踪并能区分触摸点的触摸设备) 两种

下面具体说说TypeB 的上报时序(这里以两个触摸点来表示)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ABS_MT_SLOT 0              //触摸点的SLOT 即触摸点ID 需要由触摸IC提供
ABS_MT_TRACKING_ID 45 //完成对触摸点的添加、替换或删除,通过input_mt_report_slot_state实现
ABS_MT_POSITION_X x[0] //触摸点0的X坐标,通过input_report_abs函数来完成
ABS_MT_POSITION_Y y[0] //触摸点0的Y坐标
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

/*根据 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 值 */

在初始化INPUT子系统的时候,MT也有一个初始化slots的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 ret = input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0); //多点触控需初始化(MY)槽
if (ret) {
goto fail;
}

ret = input_register_device(gt911.input); //注册input设备

//下面是上报流程的部分代码
if(touch_num) { /* 单点触摸按下 */
gt911_read_regs(dev, GT_TP1_REG, touch_data, 5);
id = touch_data[0] & 0x0F;
if(id == 0) {
input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);
input_mt_slot(dev->input, id); //上报id
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true); //按下
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x); //x
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y); //y
}
} else if(touch_num == 0){ /* 单点触摸释放 */
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); //松开
}
input_mt_report_pointer_emulation(dev->input, true); //若追踪的触摸点数量多于当前上报的数量,那么置设置为false
input_sync(dev->input); //通知内核一次完整的事件上报已经结束

在触摸屏中我们使用到了三个事件如下

1
2
3
__set_bit(EV_KEY, gt911.input->evbit);  //按键事件
__set_bit(EV_ABS, gt911.input->evbit); //绝对坐标事件
__set_bit(BTN_TOUCH, gt911.input->keybit); //触摸按键

通过中断,上报IIC读取到的数据到Linux内核完成触摸屏的驱动

  • 中断线程化

这里主要介绍 devm_request_threaded_irq 函数,作用是申请中断与request_irq类似

1.其中的threaded是线程的意思,因为硬件中断具有很高的优先级,但是如果中断频繁进入就会打断其它任务,使得其他任务可能得不到及时的处理。这里有人会问那为什么不使用中断的下半部,因为中断下半部的优先级仍旧很高(基本高于普通线程)

特性维度 软中断 Tasklet 工作队列 中断线程化
本质/关系 传统下半部核心机制,基于中断上下文 基于软中断实现,更易用 传统下半部机制,运行于内核线程上下文 中断下半部的一种实现形式,但采用线程模型
执行上下文 中断上下文(软中断上下文) 中断上下文(软中断上下文) 进程上下文(普通内核线程) 进程上下文(专用实时内核线程)
是否可阻塞/睡眠
优先级 最高(在中断上下文执行) 较高 较低(普通线程调度) 可调度的实时优先级(通常高于普通工作队列)
并发特性 同一种类可在多CPU上并行,需重入设计 同一tasklet在多个CPU上串行执行 可配置,但同一工作队列中的任务可能串行执行 每个中断有独立线程,可绑定到特定CPU
编程复杂度 高(需考虑重入和并发安全) 中(自动序列化简化编程) 低(类似普通内核编程) 低(类似普通线程编程)
适用场景 高频、实时性要求极高的场景(如网络包处理) 简单的设备中断处理,需要避免并发问题 耗时操作,需要睡眠或阻塞的场景(如文件I/O) 复杂的驱动处理,需要实时性保证和可睡眠能力

当然也不是所有的中断都可以被线程化,比如那些重要的中断,只是因为屏幕的触摸很频繁,且不是那么重要,所以可以线程化

GT911 通过 I2C 总线与主控芯片通信,在中断处理函数中需要读取触摸数据。I2C 传输本身并不是一个原子操作,在访问过程中,如果控制器繁忙,可能会导致等待。在中断上下文(软中断运行环境)中,这种等待(即使是极短的)是绝对禁止的,因为它会阻塞整个系统的中断响应。而中断线程化在进程上下文中运行,​允许睡眠和阻塞,所以这里中断线程化更优

2.devm的意思是使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理

  • 维护数据

这里可以通过三种方式维护:

  1. 数据存储位置 触摸屏中读取的数据通常存储在驱动的私有数据结构体中,在这里可以分配一个缓冲区来保存坐标状态等信息

  2. 防止多核访问冲突 这里可以使用linux内核的锁机制 自旋锁(适用于短时间的临界区保护) 互斥锁(适用于需要睡眠的场景) 或者 在中断上下文禁用中断

  3. 数据更新与同步 在中断处理函数中读取触摸数据后,需要及时更新到缓冲区,并通过同步机制通知其他线程或内核子系统(input_sync(dev->input))

因为之前我们选择的是进程上下文(中断线程化或者工作队列,允许睡眠),所以可以选择互斥锁
当然,如果选择使用中断上下文(比如中断下半部或者tasklet,不运行睡眠),可以选择自旋锁

  • 驱动代码

下面直接贴源码了(基于前面写的设备树和imx6ullm板子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
/*
* gt911驱动
* obj:gt911_mouse.ko
*/

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/debugfs.h>
#include <linux/delay.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/i2c.h>

#define GT_CTRL_REG 0X8040 /* gt911控制寄存器 */
#define GT_MODSW_REG 0X804D /* gt911模式切换寄存器 */
#define GT_CFGS_REG 0X8047 /* gt911配置起始地址寄存器 */
#define GT_CHECK_REG 0X80FF /* gt911校验和寄存器 */
#define GT_PID_REG 0X8140 /* gt911产品ID寄存器 */

#define GT_GSTID_REG 0X814E /* gt911当前检测到的触摸情况 */
#define GT_TP1_REG 0X814F /* 第一个触摸点数据地址 */
#define GT_TP2_REG 0X8157 /* 第二个触摸点数据地址 */
#define GT_TP3_REG 0X815F /* 第三个触摸点数据地址 */
#define GT_TP4_REG 0X8167 /* 第四个触摸点数据地址 */
#define GT_TP5_REG 0X816F /* 第五个触摸点数据地址 */
#define MAX_SUPPORT_POINTS 3 /* 最多5点电容触摸 */
#define MORE_GT911 0 /* 是否支持多点 */

#define GT911_TP_DATA_SIZE 8 // 每个触摸点数据长度
#define BUFFER_SIZE (GT911_TP_DATA_SIZE * MAX_SUPPORT_POINTS) // 读取寄存器时的缓存大小

struct gt911_dev {
int irq_pin,reset_pin; /* 中断和复位IO */
int irqnum; /* 中断号 */
void *private_data; /* 私有数据 */
struct input_dev *input; /* input结构体 */
struct i2c_client *client; /* I2C客户端 */

};
struct gt911_dev gt911;

const unsigned char gt911_CT[]=
{
0x48,0xe0,0x01,0x10,0x01,0x05,0x0d,0x00,0x01,0x08,
0x28,0x05,0x50,0x32,0x03,0x05,0x00,0x00,0xff,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x89,0x28,0x0a,
0x17,0x15,0x31,0x0d,0x00,0x00,0x02,0x9b,0x03,0x25,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,
0x00,0x0f,0x94,0x94,0xc5,0x02,0x07,0x00,0x00,0x04,
0x8d,0x13,0x00,0x5c,0x1e,0x00,0x3c,0x30,0x00,0x29,
0x4c,0x00,0x1e,0x78,0x00,0x1e,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,
0x18,0x1a,0x00,0x00,0x00,0x00,0x1f,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x00,0x02,0x04,0x05,0x06,0x08,0x0a,0x0c,
0x0e,0x1d,0x1e,0x1f,0x20,0x22,0x24,0x28,0x29,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,
};

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

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

/* 申请中断IO*/
if (gpio_is_valid(dev->irq_pin)) {
/* 申请复位IO,并且默认输出低电平 */
ret = devm_gpio_request_one(&client->dev,
dev->irq_pin, GPIOF_OUT_INIT_LOW,
"gt911 irq");
if (ret) {
return ret;
}
}

/* 4、初始化gt911,要严格按照gt911时序要求 */
gpio_set_value(dev->reset_pin, 0); /* 复位gt911 */
msleep(10);
gpio_set_value(dev->reset_pin, 1); /* 停止复位gt911 */
msleep(10);
gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */
msleep(50);
gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */

return 0;
}

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

/* gt911寄存器长度为2个字节 */
regdata[0] = reg >> 8;
regdata[1] = reg & 0xFF;

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

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

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

/*
* @description : 向gt911多个寄存器写入数据
* @param - dev: gt911设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 gt911_write_regs(struct gt911_dev *dev, u16 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;

b[0] = reg >> 8; /* 寄存器首地址低8位 */
b[1] = reg & 0XFF; /* 寄存器首地址高8位 */
memcpy(&b[2],buf,len); /* 将要写入的数据拷贝到数组b里面 */

msg.addr = client->addr; /* gt911地址 */
msg.flags = 0; /* 标记为写数据 */

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

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

#if MORE_GT911 == 0
static irqreturn_t gt911_irq_handler(int irq, void *dev_id)
{
int touch_num = 0;
int input_x, input_y;
int id = 0;
int ret = 0;
u8 data;
u8 touch_data[5];
struct gt911_dev *dev = dev_id;

ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1);
if (data == 0x00) { /* 没有触摸数据,直接返回 */
goto fail;
} else { /* 统计触摸点数据 */
touch_num = data & 0x0f;
}

/* 由于 gt911没有硬件检测每个触摸点按下和抬起,因此每个触摸点的抬起和按
* 下不好处理,尝试过一些方法,但是效果都不好,因此这里暂时使用单点触摸
* 下面有一个支持多点触摸的,但是连续性不好
*/
if(touch_num) { /* 单点触摸按下 */
gt911_read_regs(dev, GT_TP1_REG, touch_data, 5);
id = touch_data[0] & 0x0F;
if(id == 0) {
input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);

input_mt_slot(dev->input, id); //上报id
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true); //按下
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x); //x
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y); //y
}
} else if(touch_num == 0){ /* 单点触摸释放 */
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); //松开
}

input_mt_report_pointer_emulation(dev->input, true); //若追踪的触摸点数量多于当前上报的数量,那么设置设置为fase
input_sync(dev->input); //同步使用

data = 0x00; /* 向0X814E寄存器写0 */
gt911_write_regs(dev, GT_GSTID_REG, &data, 1);

fail:
return IRQ_HANDLED;
}

#else

//支持多点触摸的触摸屏中断函数
static irqreturn_t gt911_irq_handler(int irq, void *dev_id)
{
struct gt911_dev *dev = dev_id;
int input_x, input_y;
int i,id = 0;
int ret = 0;
u8 data;
int touch_num = 0; // 触摸点数量
u8 buf[BUFFER_SIZE]; // 触摸数据缓冲区
u16 reported_ids = 0; // 用于记录本轮已上报的ID,用于判断哪些点抬起
static u16 last_reported_ids = 0; // 上一轮已上报的ID

//1,判断是否有触摸屏数据
ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1);
if (data == 0x00) //没有数据直接返回
{
goto clear_irq;
}
else
{
touch_num = data & 0x0f; //统计触摸点个数
}

//2,读取所有触摸点的数据
if(touch_num>0)
{
ret = gt911_read_regs(dev, GT_TP1_REG, buf, sizeof(buf));
if(ret<0)
{
goto clear_irq;
}
}
if(touch_num>MAX_SUPPORT_POINTS)touch_num=MAX_SUPPORT_POINTS;
//3,上报有效的触摸点 按下或移动
for(i=0;i<touch_num;i++)
{
int pos = i*GT911_TP_DATA_SIZE; //当前位置
id = buf[pos] & 0x0f; //计算坐标
input_x = (buf[pos + 1] | (buf[pos + 2] << 8)) & 0x0fff;
input_y = (buf[pos + 3] | (buf[pos + 4] << 8)) & 0x0fff;

reported_ids |= (1 << id); //标记该id在本轮被上报了

//上报当前点数据
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);
}

/* 4. 处理已抬起的触摸点 */
for (i = 0; i < MAX_SUPPORT_POINTS; i++)
{
// 如果这个点上一轮存在,但本轮不存在,说明已抬起
if ((last_reported_ids & (1 << i)) && !(reported_ids & (1 << i)))
{
input_mt_slot(dev->input, i);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);
}
}

/* 5. 更新状态并同步 */
last_reported_ids = reported_ids;
input_mt_report_pointer_emulation(dev->input, true);
input_sync(dev->input);

clear_irq:
/* 清理中断状态(保持硬件能继续产生中断) */
data = 0x00;
ret = gt911_write_regs(dev, GT_GSTID_REG, &data, 1);
if (ret < 0)
{
printk(KERN_ERR "GT911: Failed to clear interrupt status\n");
}
return IRQ_HANDLED;
}

#endif

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

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

return 0;
}

/*
* @description : 发送gt911配置参数
* @param - client: i2c_client
* @param - mode: 0,参数不保存到flash
* 1,参数保存到flash
* @return : 无
*/
void gt911_send_cfg(struct gt911_dev *dev, unsigned char mode)
{
unsigned char buf[2];
unsigned int i = 0;

buf[0] = 0;
buf[1] = mode; /* 是否写入到gt911 FLASH? 即是否掉电保存 */
for(i = 0; i < (sizeof(gt911_CT)); i++) /* 计算校验和 */
buf[0] += gt911_CT[i];
buf[0] = (~buf[0]) + 1;

/* 发送寄存器配置 */
gt911_write_regs(dev, GT_CFGS_REG, (u8 *)gt911_CT, sizeof(gt911_CT));
gt911_write_regs(dev, GT_CHECK_REG, buf, 2);/* 写入校验和,配置更新标记 */
}

int gt911_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
u8 data, ret;

gt911.client = client;

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

printk("irq:%d reset:%d\r\n",gt911.irq_pin,gt911.reset_pin); //9 137

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

/* 3,初始化gt911 */
data = 0x02;
gt911_write_regs(&gt911, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x0;
gt911_write_regs(&gt911, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);

/* 4,初始化gt911,烧写固件
gt911_read_regs(&gt911, GT_CFGS_REG, &data, 1);
printk("gt911 ID =%#X\r\n", data);
if(data < gt911_CT[0]) {
gt911_send_cfg(&gt911, 0); 芯片内置固件已能够使用,无需下载固件name
} */

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

__set_bit(EV_KEY, gt911.input->evbit); //按键事件
__set_bit(EV_ABS, gt911.input->evbit); //绝对坐标事件
__set_bit(BTN_TOUCH, gt911.input->keybit); //触摸按键

//单点触控
input_set_abs_params(gt911.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(gt911.input, ABS_Y, 0, 600, 0, 0);

//多点触控(最多5点)
input_set_abs_params(gt911.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
input_set_abs_params(gt911.input, ABS_MT_POSITION_Y,0, 600, 0, 0);

ret = input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0); //多点触控需初始化(MY)槽
if (ret) {
goto fail;
}

ret = input_register_device(gt911.input); //注册input设备
if (ret)
goto fail;

// /* 6,最后初始化中断 */
ret = gt911_ts_irq(client, &gt911);
if(ret < 0) {
goto fail;
}
return 0;

fail:
return ret;
}

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

/*
* 传统驱动匹配表
*/
const struct i2c_device_id gt911_id_table[] = {
{ "goodix,gt911", 0, },
{ /* sentinel */ }
};

/*
* 设备树匹配表
*/
const struct of_device_id gt911_of_match_table[] = {
{.compatible = "goodix,gt911" },
{ /* sentinel */ }
};

/* i2c驱动结构体 */
struct i2c_driver gt911_i2c_driver = {
.driver = {
.name = "gt911",
.owner = THIS_MODULE,
.of_match_table = gt911_of_match_table,
},
.id_table = gt911_id_table,
.probe = gt911_probe,
.remove = gt911_remove,
};

module_i2c_driver(gt911_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mouse_H");

最后可以将驱动添加到内核中,这样开发板启动就会自动启动gt911的驱动

E 驱动测试

测试的方法有很多,首先可以使用 hexdump /dev/input/event2 来获取中断中上报的数据,注意观察自己是event几,否则可能没有信息,如果成功有数据上报,那么可以选择继续用专业的测试工具来测试
这里介绍一下tslib工具,大家可以在 https://github.com/kergoth/tslib 这里获取工具源码,解压安装编译下载后添加到开发板中,便可以实现手写测试屏幕的灵敏度或者偏移,具体教程可以自行在网上查找资料(比如正点原子I.MX6U 嵌入式 Linux 驱动开发指南 V1.81 第1566页)
下面有展示一下使用该工具的效果图:

参考文献

  1. 【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81

留言

有问题请指出,你可以选择以下方式:

  1. 在下方评论区留言
  2. 邮箱留言
  • Title: 基于 i.MX6ULL-Linux的触摸屏(GT911)驱动
  • Author: H_Haozi
  • Created at : 2025-09-26 13:22:14
  • Updated at : 2025-10-12 15:52:09
  • Link: https://redefine.ohevan.com/2025/09/26/embedded_imx6ull_gt911/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments