触摸屏的使用场合越来越多,从手机、平板到蜂巢取货的屏幕等,到处充斥着触摸屏。触摸屏也从原来的电阻触摸屏发展到了很流行的电容触摸屏。
4.3寸的触摸IC是gt9147,在看视频的时候,一定要注意。设备树和驱动是和视频不一样的。
1Linux下电容触摸屏驱动框架
1.1 多点触摸(MT)协议
电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:
IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。
触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。
老版本的 linux 内核是不支持多点电容触摸的(Multi-touch,简称 MT), MT 协议是后面加入的,因此如果使用 2.x 版本 linux 内核的话可能找不到 MT 协议。 MT 协议被分为两种类型, Type A 和 TypeB,这两种类型的区别如下:
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息, FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。
触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有ABS_MT 事件是用于多点触摸的, ABS_MT 事件定义在文件 include/uapi/linux/input.h 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15在 上 面 这 些 众 多 的 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()函数来隔离不同的触摸点数据信息,此函数原型如下所示:
1 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()函数原型如下所示:
1 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。
1.2Type A 触摸点信息上报时序
对于 Type A 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:
1
2
3
4
5
6
7 ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
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 函数实现。
1.3Type B 触摸点信息上报时序
对于 Type B 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:
1
2
3
4
5
6
7
8
9 ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
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 行,和第 14 行类似,只是换成了上报触摸点 0 的(X,Y)坐标信息。- 第 9 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 input_sync函数来完成。
当一个触摸点移除以后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示:
1
2 ABS_MT_TRACKING_ID -1
SYN_REPORT
第 1 行,当一个触摸点(SLOT)移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的第三个参数 active 设置为 false 即可,不需要用户手动去设置-1。
第 2 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。
1.4多点触摸所使用到的 API 函数
我们知道 linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的,本小节我们来看一下一些常见的 API 函数。
1.4.1 input_mt_init_slots
input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,此函数定义在文件 drivers/input/input-mt.c 中,函数原型如下所示:
1
2
3 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 如下所示:
1
2
3
4
5可以采用‘|’运算来同时设置多个 flags 标识。
返回值: 0,成功;负值,失败。
1.4.2 input_mt_slot
input_mt_slot 函数用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数定义在文件 include/linux/input/mt.h 中,函数原型如下所示:
1
2 void input_mt_slot(struct input_dev *dev,
int slot)
- dev: MT 设备对应的 input_dev。
- slot:当前发送的是哪个 slot 的坐标信息,也就是哪个触摸点。
- 返回值:无。
1.4.3 input_mt_report_slot_state
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 中,此函数原型如下所示:
1
2
3 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.4.4 input_report_abs
input_report_abs 函数,Type A 和 Type B 类型都使用此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报 。 此 函 数 定 义 在 文 件include/linux/input.h 中,函数原型如下所示:
1
2
3 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.4.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中,函数原型如下:
1
2 void input_mt_report_pointer_emulation(struct input_dev *dev,
bool use_count)
- dev: MT 设备对应的 input_dev。
- use_count: true,有效的触摸点数量; false,追踪到的触摸点数量多于当前上报的数量。
- 返回值:无。
2 多点电容触摸驱动框架
首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意一下几点:
①、多点电容触摸芯片的接口,一般都为I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT协议上报坐标信息。
2.1 I2C 驱动框架
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 static const struct i2c_device_id xxx_ts_id[] = {
{ "xxx", 0, },
{ /* sentinel */ }
};
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx", },
{ /* sentinel */ }
};
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(xxx_of_match),
},
.id_table = xxx_ts_id,
.probe = xxx_ts_probe,
.remove = xxx_ts_remove,
};
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_ts_driver);
return ret;
}
static void __exit xxx_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
2.2 初始化触摸 IC、中断和 input 子系统
初始化操作都是在 xxx_ts_probe 函数中完成
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 static int xxx_ts_probe(struct i2c_client *client, const struct
i2c_device_id *id)
{
struct input_dev *input;
/* 1、初始化 I2C */
......
/* 2,申请中断, */
devm_request_threaded_irq(&client->dev, client->irq, NULL,
xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &xxx);
......
/* 3, input 设备申请与初始化 */
input = devm_input_allocate_device(&client->dev);
input->name = client->name;
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
......
/* 4,初始化 input 和 MT */
__set_bit(EV_ABS, input->evbit);
__set_bit(BTN_TOUCH, input->keybit);
input_set_abs_params(input, ABS_X, 0, width, 0, 0);
input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0);
input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);
......
/* 5,注册 input_dev */
input_register_device(input);
......
}
2.3上报坐标信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 static irqreturn_t xxx_handler(int irq, void *dev_id)
{
int num; /* 触摸点数量 */
int x[n], y[n]; /* 保存坐标值 */
/* 1、从触摸芯片获取各个触摸点坐标值 */
......
/* 2、上报每一个触摸点坐标 */
for (i = 0; i < num; i++) {
input_mt_slot(input, id);
input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
input_report_abs(input, ABS_MT_POSITION_X, x[i]);
input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
}
......
input_sync(input);
......
return IRQ_HANDLED;
}
3 实验程序
正点原子4.3寸的触摸驱动IC是GT9147,和视频略有区别.
3.1修改设备树
硬件连接:
在&iomuxc节点下添加:
1
2
3
4
5
6 pinctrl_tsc: tscgrp {
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 /* TSC_RST */
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x10B0 /* TSC_INT */
>;
};检查引脚是否被占用,被占用则注释掉。
还需要检查引脚做gpio的时候,
在I2C2节点下添加gt9147
1
2
3
4
5
6
7
8
9
10
11 gt9147: gt9147@14 {
compatible = "cbus,gt9147";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc>;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
status = "okay";
};
3.2驱动程序
跟着正点原子视频学的话,在4.3寸的触摸ICgt9147会出现一直中断的情况,这时候只需要
在printk函数前面加上一句,向这个寄存器写入0。
1
2
3
4
5
6 static irqreturn_t gt9147_handler(int irq,void *dev_id)
{
gt9147_write_reg(>9147,GT_GSTID_REG,0x00);
printk("gt9147_handler\r\n");
return IRQ_HANDLED;
}
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
//struct
struct gt9147_dev
{
struct device_node *nd;
int irq_pin,reset_pin;
int irq_num;
int irqtype;
void *private;
struct i2c_client *client;
struct input_dev *input; /* input结构体 */
int max_x; /* 最大横坐标 */
int max_y; /* 最大纵坐标 */
};
struct gt9147_dev gt9147;
const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */
static int gt9147_read_regs(struct gt9147_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;
/* GT9147寄存器长度为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 = ®data[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;
}
static s32 gt9147_write_regs(struct gt9147_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; /* gt9147地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 2; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
//复位gt9147
static int gt9147_ts_reset(struct i2c_client *client,struct gt9147_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_HIGH,
"gt9147 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_HIGH,
"gt9147 int");
if (ret) {
return ret;
}
}
/* 4、初始化GT9147,要严格按照GT9147时序要求 */
gpio_set_value(dev->reset_pin, 0); /* 复位GT9147 */
msleep(10);
gpio_set_value(dev->reset_pin, 1); /* 停止复位GT9147 */
msleep(10);
gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */
msleep(50);
gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */
return 0;
}
static irqreturn_t gt9147_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 gt9147_dev *dev = dev_id;
ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1);
if (data == 0x00) { /* 没有触摸数据,直接返回 */
goto fail;
} else { /* 统计触摸点数据 */
touch_num = data & 0x0f;
}
/* 由于GT9147没有硬件检测每个触摸点按下和抬起,因此每个触摸点的抬起和按
* 下不好处理,尝试过一些方法,但是效果都不好,因此这里暂时使用单点触摸
*/
if(touch_num) { /* 单点触摸按下 */
gt9147_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);
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);
}
} 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);
input_sync(dev->input);
data = 0x00; /* 向0X814E寄存器写0 */
gt9147_write_regs(dev, GT_GSTID_REG, &data, 1);
fail:
return IRQ_HANDLED;
}
static int gt9147_ts_irq(struct i2c_client *client,struct gt9147_dev *dev)
{
int ret = 0;
/* 2,申请中断,client->irq就是IO中断, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
gt9147_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, >9147);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}
return 0;
}
static int gt9147_read_firmware(struct i2c_client *client, struct gt9147_dev *dev)
{
int ret = 0, version = 0;
u16 id = 0;
u8 data[7]={0};
char id_str[5];
ret = gt9147_read_regs(dev, GT_PID_REG, data, 6);
if (ret) {
dev_err(&client->dev, "Unable to read PID.\n");
return ret;
}
memcpy(id_str, data, 4);
id_str[4] = 0;
if (kstrtou16(id_str, 10, &id))
id = 0x1001;
version = get_unaligned_le16(&data[4]);
dev_info(&client->dev, "ID %d, version: %04x\n", id, version);
switch (id) { /* 由于不同的芯片配置寄存器地址不一样需要判断一下 */
case 1151:
case 1158:
case 5663:
case 5688: /* 读取固件里面的配置信息 */
ret = gt9147_read_regs(dev, GT_1xx_CFGS_REG, data, 7);
break;
default:
ret = gt9147_read_regs(dev, GT_9xx_CFGS_REG, data, 7);
break;
}
if (ret) {
dev_err(&client->dev, "Unable to read Firmware.\n");
return ret;
}
dev->max_x = (data[2] << 8) + data[1];
dev->max_y = (data[4] << 8) + data[3];
dev->irqtype = data[6] & 0x3;
printk("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x\r\n", dev->max_x, dev->max_y, dev->irqtype);
return 0;
}
static int gt9147_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
u8 data;
int ret = 0;
gt9147.client = client;
gt9147.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios",0);
gt9147.reset_pin = of_get_named_gpio(client->dev.of_node,"reset-gpios",0);
ret = gt9147_ts_reset(client,>9147);
if (ret < 0)
{
printk("gt9147_ts_reset fail!!!\r\n");
goto fail;
}
/* 3,初始化GT9147 */
data = 0x02;
gt9147_write_regs(>9147, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x0;
gt9147_write_regs(>9147, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);
/* 4,初始化GT9147,读取固件 */
ret = gt9147_read_firmware(client, >9147);
if(ret != 0) {
printk("Fail !!! check !!\r\n");
goto fail;
}
/* 5.input设备注册 */
gt9147.input = devm_input_allocate_device(&client->dev);
if (!gt9147.input) {
ret = -ENOMEM;
goto fail;
}
gt9147.input->name = client->name;
gt9147.input->id.bustype = BUS_I2C;
gt9147.input->dev.parent = &client->dev;
__set_bit(EV_KEY, gt9147.input->evbit);
__set_bit(EV_ABS, gt9147.input->evbit);
__set_bit(BTN_TOUCH, gt9147.input->keybit);
input_set_abs_params(gt9147.input, ABS_X, 0, gt9147.max_x, 0, 0);
input_set_abs_params(gt9147.input, ABS_Y, 0, gt9147.max_y, 0, 0);
input_set_abs_params(gt9147.input, ABS_MT_POSITION_X,0, gt9147.max_x, 0, 0);
input_set_abs_params(gt9147.input, ABS_MT_POSITION_Y,0, gt9147.max_y, 0, 0);
ret = input_mt_init_slots(gt9147.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(gt9147.input);
if (ret)
goto fail;
ret = gt9147_ts_irq(client, >9147);
if(ret < 0) {
printk("gt9147_ts_irq fail!!!\r\n");
goto fail;
}
printk("gt9147_i2c_probe ok!\n");
return 0;
fail:
return ret;
}
static int gt9147_i2c_remove(struct i2c_client *client)
{
input_unregister_device(gt9147.input);
printk("gt9147_i2c_remove\n");
return 0;
}
//传统的匹配表
static const struct i2c_device_id gt9147_id[] = {
{ "cbus,gt9147", 0 },
{ }
};
//设备树匹配表
static const struct of_device_id gt9147_of_match[] = {
{ .compatible = "cbus,gt9147"},
{ }
};
static struct i2c_driver gt9147_driver = {
.probe = gt9147_i2c_probe,
.remove = gt9147_i2c_remove,
.driver = {
.name = "gt9147",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(gt9147_of_match),
},
.id_table = gt9147_id, //未使用设备树
};
static int __init gt9147_init(void)
{
int ret = 0;
ret = i2c_add_driver (>9147_driver);
printk("gt9147_init\n");
return ret;
}
static void __exit gt9147_exit(void)
{
i2c_del_driver(>9147_driver);
}
module_init(gt9147_init);
module_exit(gt9147_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("cbus");
3.3 驱动测试
驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动就会生成/dev/input/event2 这个文件
输入如下命令查看 event2,也就是多点电容触摸屏上报的原始数据:
1 hexdump /dev/input/event2这里就不分析了。裸机里面有讲。
4 tslib移植与使用
tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有时候也需要进行校准。最主要的是 tslib 提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。最新版本的 tslib 已经支持了多点电容触摸屏,因此可以通过 tslib 来直观的测试多点电容触摸屏驱动,这个要比观看 eventX 原始数据方便的多。
修改 tslib 源码所属用户
1 sudo chown zwl:zwl tslib-1.21 -R
编译 tslib 的时候需要先在 ubuntu 中安装一些文件,防止编译 tslib 过程中出错,命令如下所示:
1
2
3 sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
接下来输入如下命令配置并编译 tslib:
1
2
3
4
5 cd tslib-1.21/ //进入 tslib 源码目录
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/home/zwl/linux/tool/tslib
make //编译
make install //安装
将所有文件拷贝到开发板的根文件系统中,命令如下:
1 sudo cp * -rf /home/zuozhongkai/linux/nfs/rootfs
使用 ts_test_mt 这个软件来测试触摸屏工作是否正常,以及多点触摸是否有效,执行如下所示命令:
1 ts_test_mt此命令会打开一个触摸测试界面
5.将驱动加入内核
写好的驱动程序放到drivers/input/touchscreen,然后修改Makefile,编译。
重启开发版,会看到
此时,input里面已经变成event1了。就需要修改/etc/profile,保存,重启。
输入te_test_mt。即可绘图测试。