0%

Linux驱动学习日记(29) Linux 多点电容触摸

多点电容触摸简介

  我手上使用的屏幕是 TFT LCD + 触摸屏组合起来的,底下是 LCD 面板,上面是触摸面板,将两个封装到一起就成了带有触摸屏的 LCD 屏幕。电容触摸屏也需要一个驱动 IC,驱动 IC 一般会提供一个 I2C 接口给主控制器,主控制器可以通过 I2C 接口来读取驱动 IC 的数据。我手上使用的触摸控制 IC 是 GT1151。

多点触摸(MT)协议

  我们已经知道以下几点:
  ①、电容触摸屏是 I2C 接口,需要触摸 IC。
  ②、触摸 IC 提供了中断信号引脚 (INT),可以通过中断来获取触摸信息。
  ③、电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
  结合以上几点我们可以得出电容触摸屏驱动起始就是以以下几种 Linux 驱动框架组合起来的:
  ①、I2C 设备驱动,因为电容触摸 IC 基本都是 I2C 接口,因此大框架就是 I2C 设备驱动。
  ②、通过中断引脚向 Linux 内核上报触摸信息,因此需要用到 Linux 的中断驱动框架。坐标的上报在中断服务函数中完成。
  ③、触摸屏的坐标信息、屏幕按下和抬起信息属于 Linux 的 input 子系统,因此向 Linux 内核上报触摸屏坐标信息就得用 input 子系统。

  接下来我们来学习 input 子系统下的多点电容触摸协议。
  触摸点的信息通过一系列的 ABS_MT 事件上报给 Linux 内核,只有 ABS_MT 事件是用于多点出没的,ABS_MT 时间定义在文件 include/dt-bindings/input/linux-event-codes.h 中,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define ABS_MT_SLOT     0x2f     /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#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。对于能区分触摸点的设备,使用 ABS_MT_TRACKING_ID 来区分触摸点。

  对于能区分触摸点的设备,我们上报触摸点信息的时候需要通过 input_mt_slot() 函数区分是哪一个触摸点,其原型如下

1
2
3
4
static inline void input_mt_slot(struct input_dev *dev, int slot)
{
input_event(dev, EV_ABS, ABS_MT_SLOT, slot);
}

  此函数有两个参数,第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是哪个触摸点信息。input_mt_slot() 函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点的数据。

  不管是什么类型的设备,最终都要调用 input_sync 函数来表示多点触摸信息传输完成,告诉接收者处理之前累积的所有信息,并准备好下一次接收。对于能区分触摸点的设备,其使用 slot 协议区分具体的触摸点,slot 需要用到 ABS_MT_TRACKING_ID 信息,这个 ID 需要硬件提供,这个设备需要给每个识别出的触摸点分配一个 slot,后面使用这个 slot 上报信息。设备驱动给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息,可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果在也不存在就表示删除了。

  对于能区分触摸点的设备驱动,发送触摸点信息的时序如下,

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_MY_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

  首先上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标是前要先用 input_mt_slot 函数上报当前触摸点 SLOT,触摸点的 SLOT 就是触摸点 ID,需要由触摸 IC 提供。接下来根据要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID,通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换和删除。具体用到的函数就是 input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数第三个参数要设置为 true,Linux 内核会自动分配一个 ABS_MT_TRACKING_ID 的值。然后就是上报触摸点的 X、Y 坐标。最后需要上报 SYN_REPORT 事件以保证同步。

  当一个触摸点移除后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示

1
2
ABS_MT_TRACKING_ID      -1
SYN_REPORT

  ili210x.c 文件上报触摸坐标信息函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata)
{
struct input_dev *input = priv->input;
int i;
bool contact = false, touch;
unsigned int x = 0, y = 0, z = 0;

for (i = 0; i < priv->chip->max_touches; i++) {
touch = priv->chip->parse_touch_data(touchdata, i, &x, &y, &z);

input_mt_slot(input, i);
if (input_mt_report_slot_state(input, MT_TOOL_FINGER, touch)) {
touchscreen_report_pos(input, &priv->prop, x, y, true);
if (priv->chip->has_pressure_reg)
input_report_abs(input, ABS_MT_PRESSURE, z);
contact = true;
}
}

input_mt_report_pointer_emulation(input, false);
input_sync(input);

return contact;
}

  我们后续编写代码也是按照这个框架来编写。

多点触摸所使用的 API 函数

  1、input_mt_init_slots 函数
  input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,此函数原型如下所示

1
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 信息。返回值为 0 成功。

  2、input_mt_slot
  此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数原型如下:

1
2
3
4
static inline void input_mt_slot(struct input_dev *dev, int slot)
{
input_event(dev, EV_ABS, ABS_MT_SLOT, slot);
}

  其中,dev 就是 MT 设备对应的 input_dev,slot 就是当前发送的触摸点信息。

  3、input_mt_report_slot_state
  此函数用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE 事件,ABS_MT_TRACKING_ID 事件给 slot 关联一个 ABS_MT_TRACKING_ID,ABS_MT_TOOL_TYPE 事件指定触摸类型。

1
bool input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active)

  其中,dev 表示 MT 设备对应的 input_dev。tool_type 表示触摸类型,可以选择手指、笔或手掌等等。active 为 true 时就表示连续触摸,input 子系统内核会自动分配一个 ABS_MT_TRACKING_ID 给 slot。false 表示触摸点抬起。

  4、input_report_abs
  所有触摸设备都使用此函数来上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 实现坐标信息上报。其函数原型如下:

1
2
3
4
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}

  5、input_mt_report_pointer_emulation 函数
  若追踪到的触摸点数量大于当前上报的数量,驱动程序用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数,然后调用 input_mt_report_pointer_emulation 函数将 use_count 参数设置为 false,否则将 use_count 参数设置为 true,表示当前的触摸点数量。函数原型如下:

1
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count);

  其中,dev 就是 MT 设备对应的 input_dev,use_count true 表示有效的触摸点数量,false 表示追踪到的触摸点数量大于当前上报的数量。

多点电容触摸驱动框架

  综上所述,多点电容触摸框架需要以下几个内容:
  1、主框架为 I2C。
  2、Linux 中多用中断来上报触摸点坐标信息,使用到中断框架。
  3、input 子系统框架
  4、在中断处理中使用 Linux 的 MT 协议上报坐标信息。

  接下来我们逐个回顾框架如何编写

  1、I2C 驱动框架
  驱动总体采用 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
static const struct i2c_device_id xxx_ts_id[] = {};

static const struct of_device_id xxx_of_match[] = {};

static struct i2c_driver xxx_tx_driver = {
.owner = THIS_MODULE,
...
};

static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_ts_driver);
return ret;
}

static int __exit xxx_exit(void)
{
i2c_del_driver(&xxx_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hcw");

  当设备树中触摸 IC 的设备节点和驱动匹配以后,probe 函数就会执行,可以在此函数中初始化触摸 IC、中断、input 子系统等等。

  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
static int xxx_tx_probe(struct i2c_client &client, const struct i2c_device_id *id)
{
struct input_dev *dev;

/* 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);
······

/* 5、注册 input_dev */
input_register_device(input);
}

  首先初始化触摸芯片,包括芯片的相关 IO,比如复位、中断等 IO 引脚,然后就是芯片本身的初始化,配置触摸芯片相关的寄存器。接下来就是申请中断、input dev,接着设置 input_dev 的上报事件。

  值得注意的是,这里申请中断使用的函数是 devm_request_threaded_irq,这里的意思是将此中断线程化,可以保证高优先级的任务能被优先处理。devm_ 前缀的意思是让 Linux 自动释放资源。

  3、上报坐标信息
  最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择发送时序,对于能区分多个触摸点的设备参考框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static irqreturn_t xxx_handler(int irq, void *dev_id)
{
int num;
int x[n],y[n];

/* 读取触摸点 */
······

/* 上报每一个触摸点坐标 */
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;
}

硬件分析

  我手上的触摸芯片为 GT1151,这是一颗支持多点触控(最高5点)的电容触摸屏控制器,它通过最常用的 I2C总线 与主控芯片进行通信。除此之外,还有两根非常重要的GPIO线:
  复位引脚(RST): 用于对触摸芯片进行硬件复位。通常由主控控制,在驱动初始化时,先拉低再拉高,完成一次复位操作。
  中断引脚(INT): 这是触摸芯片主动“通知”主控的关键。当有触摸事件(按下、移动、抬起)发生时,GT1151会通过这个引脚向主控发送一个中断信号。采用中断方式,而不是让主控不停地去查询(轮询),可以极大地节省CPU资源,降低系统功耗。

  查询底板原理图,可以得知 39、38、36、35 pin 对应的就是四根线,对应关系如下

触摸屏引脚 对应板级编号 MX引脚
CT_RST A48 SNVS_TAMPERD9
CT_INT B15 GPIO_9
I2C2_SDA B32 UART5_RXD
I2C2_SCL B31 UART5_TXD
图 1 原理图

程序编写

修改设备树

  打开 imx6ull-hcw-emmc.dtsi,找到 iomuxc 节点,添加以下 pinctrl 组

1
2
3
4
5
6
7
8
9
10
11
pinctrl_gt1151_int: gt1151intgrp{
fsl,pins = <
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080
>;
};

pinctrl_gt1151_reset: gt1151resetgrp{
fsl,pins = <
MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};

  接下来,在 i2c2 节点下添加设备,如下

1
2
3
4
5
6
7
8
9
10
11
gt1151@14{
compatible = "hcw,gt1151";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gt1151_int
&pinctrl_gt1151_reset>;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};

  使用新编译好的设备树启动 Linux 内核。

编写代码

  创建 .c 文件,写入以下代码

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
/*
* @Author: 胡城玮
* @FilePath: gt1151.c
* @Date: 2026-03-24
* @Description:
* @Version: 0.1
*/

#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>
#include <asm/unaligned.h>

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

#define GT_GSTID_REG 0X814E /* GT1151当前检测到的触摸情况 */
#define GT_TP1_REG 0X8150 /* 第一个触摸点数据地址 */
#define GT_TP2_REG 0X8158 /* 第二个触摸点数据地址 */
#define GT_TP3_REG 0X8160 /* 第三个触摸点数据地址 */
#define GT_TP4_REG 0X8168 /* 第四个触摸点数据地址 */
#define GT_TP5_REG 0X8170 /* 第五个触摸点数据地址 */
#define MAX_SUPPORT_POINTS 5 /* 最多5点电容触摸 */

const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */

struct gt1151_dev{
int irq_pin, reset_pin;
int irqnum;
int irqtype;
int max_x;
int max_y;
void *private_data;
struct input_dev *input;
struct i2c_client *client;
};

static struct gt1151_dev gt1151dev;

/**
* @description: 硬件复位
* @param {i2c_client} *client
* @param {gt1151_dev} *dev
* @return {*}
*/
static int gt1151_dev_init(struct i2c_client *client, struct gt1151_dev *dev)
{
int ret = 0;
if (gpio_is_valid(dev->reset_pin))
{
ret = devm_gpio_request_one(&client->dev, dev->reset_pin,
GPIOF_OUT_INIT_HIGH, "gt1151 reset");
if(ret)
{
return ret;
}
}

if (gpio_is_valid(dev->irq_pin))
{
ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
GPIOF_OUT_INIT_HIGH, "gt1151 int");
if(ret)
{
return ret;
}
}

gpio_set_value(dev->reset_pin, 0); /* 复位GT1151 */
msleep(10);
gpio_set_value(dev->reset_pin, 1); /* 停止复位GT1151 */
msleep(10);
gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */
msleep(50);
gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */

return 0;
}

/**
* @description: 读寄存器
* @param {gt1151_dev} *dev
* @param {u16} reg
* @param {u8} *buf
* @param {int} len
* @return {*}
*/
static int gt1151_read_regs(struct gt1151_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;

regdata[0] = reg >> 8;
regdata[1] = reg & 0xff;

msg[0].addr = client->addr;
msg[0].buf = &regdata[0];
msg[0].flags = !I2C_M_RD;
msg[0].len = 2;

msg[1].addr = client->addr;
msg[1].buf = buf;
msg[1].flags = I2C_M_RD;
msg[1].len = len;

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

/**
* @description: 写寄存器
* @param {gt1151_dev} *dev
* @param {u16} reg
* @param {u8} *buf
* @param {int} len
* @return {*}
*/
static int gt1151_write_regs(struct gt1151_dev *dev, u16 reg, u8 *buf, int len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;

b[0] = reg >> 8;
b[1] = reg & 0xff;

memcpy(&b[2], buf, len);

msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 2;

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

static irqreturn_t gt1151_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 gt1151_dev *dev = dev_id;

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

if(touch_num) { /* 单点触摸按下 */
gt1151_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, false);
input_sync(dev->input);

data = 0x00;
gt1151_write_regs(dev, GT_GSTID_REG, &data, 1);
failed:
return IRQ_HANDLED;
}

static int gt1151_read_firmware(struct i2c_client *client, struct gt1151_dev *dev)
{
int ret = 0,version = 0;
u8 data[7] = {0};
gt1151_read_regs(dev, GT_1xx_CFGS_REG, data, 7);
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", dev->max_x, dev->max_y, dev->irqtype);

return 0;
}

static const struct of_device_id gt1151_of_match[] = {
{.compatible = "hcw,gt1151"},
{}
};

MODULE_DEVICE_TABLE(of, gt1151_of_match);

static int gt1151_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("matched!!\r\n");
int ret = 0;
u8 data = 0;
gt1151dev.client = client;
/* 初始化设备 */

// 获取设备信息
gt1151dev.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
gt1151dev.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

// 复位 gt1151
ret = gt1151_dev_init(client, &gt1151dev);
if(ret < 0)
goto failed;

data = 0x02;
gt1151_write_regs(&gt1151dev, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x0;
gt1151_write_regs(&gt1151dev, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);

// 读取硬件信息
ret = gt1151_read_firmware(client, &gt1151dev);
if(ret < 0)
goto failed;

// input 设备注册
gt1151dev.input = devm_input_allocate_device(&client->dev);
if (!gt1151dev.input) {
ret = -ENOMEM;
goto failed;
}

gt1151dev.input->name = client->name; // 获取设备名
gt1151dev.input->id.bustype = BUS_I2C; // 输入设备来自 IIC 总线
gt1151dev.input->dev.parent = &client->dev; // 设置父设备为 I2C 设备

__set_bit(EV_KEY, gt1151dev.input->evbit);
__set_bit(EV_ABS, gt1151dev.input->evbit); // 设置支持的事件类型,按下和绝对坐标
__set_bit(BTN_TOUCH, gt1151dev.input->keybit); // 设置支持的按键

/* 配置 input 设备的坐标能力和多点触摸能力 */
input_set_abs_params(gt1151dev.input, ABS_X, 0, gt1151dev.max_x, 0, 0);
input_set_abs_params(gt1151dev.input, ABS_Y, 0, gt1151dev.max_y, 0, 0);
input_set_abs_params(gt1151dev.input, ABS_MT_POSITION_X,0, gt1151dev.max_x, 0, 0);
input_set_abs_params(gt1151dev.input, ABS_MT_POSITION_Y,0, gt1151dev.max_y, 0, 0);
ret = input_mt_init_slots(gt1151dev.input, MAX_SUPPORT_POINTS, 0);
if (ret)
goto failed;


ret = input_register_device(gt1151dev.input);
if (ret)
goto failed;

ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, gt1151_irq_handler,
irq_table[gt1151dev.irqtype] | IRQF_ONESHOT, client->name, &gt1151dev);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}
failed:

return ret;
}
static void gt1151_i2c_remove(struct i2c_client *client){
/* 进行资源释放 */
}

static struct i2c_driver gt1151_i2c_driver = {
.probe = gt1151_i2c_probe,
.remove = gt1151_i2c_remove,
.driver = {
.name = "gt1151_drv",
.owner = THIS_MODULE,
.of_match_table = gt1151_of_match
}
};

static int __init gt1151_init(void)
{
int ret = 0;
ret = i2c_add_driver(&gt1151_i2c_driver);
return ret;
}

static void __exit gt1151_exit(void)
{
i2c_del_driver(&gt1151_i2c_driver);
}

module_init(gt1151_init);
module_exit(gt1151_exit);
MODULE_AUTHOR("hcw");
MODULE_LICENSE("GPL");

  由于内核中已经存在了 gtxxx 的驱动,所以我们暂时先将设备树和驱动程序中的设备名改为 “hcw,gt1”。装载模块,现在我们可以读取到触摸屏上传的数据了

图 2 input数据