【北京迅为】i.MX6ULL终结者Linux 电容触摸屏实验实验程序编写

文章目录

    • 1 修改设备树文件
    • 2 编写多点电容触摸驱动

1 修改设备树文件

1、添加FT5426的pinctrl信息
FT5426 触摸芯片用到了 4 个 IO,一个复位 IO、一个中断 IO、I2C2 的 SCL 和 SDA,所以我们需要先在设备树中添加 IO 相关的信息。复位 IO 和中断 IO 是普通的 GPIO,因此这两个 IO可以放到同一个节点下去描述,I2C2 的 SCL 和 SDA 属于 I2C2,因此这两个要放到同一个节点下去描述。首先是复位 IO 和中断 IO,topeet_emmc_4_3.dts 文件里面默认有个名为“pinctrl_tsc”的节点,如果被删除了的话就自行创建,在此节点下添加触摸屏的复位 IO 和中断 IO 信息,修改以后的“pinctrl_tsc”节点内容如下所示:

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

继续添加 I2C2 的 SCL 和 SDA 这两个 IO 信息,topeet_emmc_4_3.dts 里面默认就已经添加了 I2C2 的 IO 信息,这是 NXP 官方添加的,所以不需要我们去修改。找到“pinctrl_i2c1”节点,此节点就是用于描述 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 }; 

最后在检查一下这四个引脚有没有被其他外设使用。如果有的话就需要屏蔽掉。
2、添加FT5426节点
FT5426 这个触摸 IC 挂载 I2C2 下,因此需要向 I2C2 节点下添加一个子节点,此子节点用于描述 FT5426,添加完成以后的 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 
12         ft5426: ft5426@38 { 
13             compatible = "edt,edt-ft5426"; 
14             reg = <0x38>; 
15             pinctrl-names = "default"; 
16             pinctrl-0 = <&pinctrl_tsc>; 
17             interrupt-parent = <&gpio1>; 
18             interrupts = <9 0>; 
19             reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 
20             interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 
21         }; 
22 };

第 12 行,触摸屏所使用的 FT5426 芯片节点,挂载 I2C2 节点下,FT5426 的器件地址为0X38。
第 14 行,reg 属性描述 FT5426 的器件地址为 0x38。
第 16 行,pinctrl-0 属性描述 FT5426 的复位 IO 和中断 IO 所使用的节点为 pinctrl_tsc。
第 17 行,interrupt-parent 属性描述中断 IO 对应的 GPIO 组为 GPIO1。
第 18 行,interrupts 属性描述中断 IO 对应的是 GPIO1 组的 IOI09。
第 19 行,reset-gpios 属性描述复位 IO 对应的 GPIO 为 GPIO5_IO09。
第 20 行,interrupt-gpios 属性描述中断 IO 对应的 GPIO 为 GPIO1_IO09。

2 编写多点电容触摸驱动

本实验例程路径:i.MX6UL终结者光盘资料/06_Linux驱动例程/20_multitouch
创建ft5426.c驱动文件,内容如下:

  1 #include <linux/module.h>
  2 #include <linux/ratelimit.h>
  3 #include <linux/interrupt.h>
  4 #include <linux/input.h>
  5 #include <linux/i2c.h>
  6 #include <linux/uaccess.h>
  7 #include <linux/delay.h>
  8 #include <linux/debugfs.h>
  9 #include <linux/slab.h>
 10 #include <linux/gpio.h>
 11 #include <linux/of_gpio.h>
 12 #include <linux/input/mt.h>
 13 #include <linux/input/touchscreen.h>
 14 #include <linux/input/edt-ft5x06.h>
 15 #include <linux/i2c.h>
 16 
 17 #define MAX_SUPPORT_POINTS              5           /* 5点触摸 */
 18 #define TOUCH_EVENT_DOWN                0x00       /* 按下 */
 19 #define TOUCH_EVENT_UP                  0x01        /* 抬起 */
 20 #define TOUCH_EVENT_ON                  0x02        /* 接触 */
 21 #define TOUCH_EVENT_RESERVED    0x03            /* 保留 */
 22 
 23 /* FT5X06寄存器相关宏定义 */
 24 #define FT5X06_TD_STATUS_REG    0X02            /*  状态寄存器地址 */
 25 #define FT5x06_DEVICE_MODE_REG  0X00          /* 模式寄存器 */
 26 #define FT5426_IDG_MODE_R       0XA4          /* 中断模式 */
 27 #define FT5X06_READLEN             29      /* 要读取的寄存器个数 */
 28 
 29 struct ft5x06_dev {
 30         struct device_node      *nd;            /* 设备节点 */
 31         int irq_pin,reset_pin;                  /* 中断和复位IO */
 32         int irqnum;                           /* 中断号 */
 33         void *private_data;                     /* 私有数据 */
 34         struct input_dev *input;                /* input结构体 */
 35         struct i2c_client *client;              /* I2C客户端 */
 36 };
 37 
 38 static struct ft5x06_dev ft5x06;
 39 
 40 /*
 41  * @description     : 复位FT5X06
 42  * @param - client      : 要操作的i2c
 43  * @param - multidev: 自定义的multitouch设备
 44  * @return          : 0,成功;其他负值,失败
 45  */
 46 static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
 47 {
 48         int ret = 0;
 49 
 50         if (gpio_is_valid(dev->reset_pin)) {            /* 检查IO是否有效 */
 51                 /* 申请复位IO,并且默认输出低电平 */
 52                 ret = devm_gpio_request_one(&client->dev,
 53                                      dev->reset_pin, GPIOF_OUT_INIT_LOW,
 54                                         "edt-ft5x06 reset");
 55                 if (ret) {
 56                         return ret;
 57                 }
 58 
 59                 msleep(5);
 60                 gpio_set_value(dev->reset_pin, 1); /* 输出高电平,停止复位 */
 61                 msleep(300);
 62         }
 63 
 64         return 0;
 65 }
 66 
 67 /*
 68  * @description : 从FT5X06读取多个寄存器数据
 69  * @param - dev:  ft5x06设备
 70  * @param - reg:  要读取的寄存器首地址
 71  * @param - val:  读取到的数据
 72  * @param - len:  要读取的数据长度
 73  * @return              : 操作结果
 74  */
 75 static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
 76 {
 77         int ret;
 78         struct i2c_msg msg[2];
 79         struct i2c_client *client = (struct i2c_client *)dev->client;
 80 
 81         /* msg[0]为发送要读取的首地址 */
 82         msg[0].addr = client->addr;          /* ft5x06地址 */
 83         msg[0].flags = 0;                   /* 标记为发送数据 */
 84         msg[0].buf = &reg;                  /* 读取的首地址 */
 85         msg[0].len = 1;                                     /* reg长度*/
 86 
 87         /* msg[1]读取数据 */
 88         msg[1].addr = client->addr;                     /* ft5x06地址 */
 89         msg[1].flags = I2C_M_RD;                      /* 标记为读取数据*/
 90         msg[1].buf = val;                             /* 读取数据缓冲区 */
 91         msg[1].len = len;                             /* 要读取的数据长度*/
 92 
 93         ret = i2c_transfer(client->adapter, msg, 2);
 94         if(ret == 2) {
 95                 ret = 0;
 96         } else {
 97                 ret = -EREMOTEIO;
 98         }
 99         return ret;
100 }
101 
102 /*
103  * @description : 向ft5x06多个寄存器写入数据
104  * @param - dev:  ft5x06设备
105  * @param - reg:  要写入的寄存器首地址
106  * @param - val:  要写入的数据缓冲区
107  * @param - len:  要写入的数据长度
108  * @return        :   操作结果
109  */
110 static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
111 {
112         u8 b[256];
113         struct i2c_msg msg;
114         struct i2c_client *client = (struct i2c_client *)dev->client;
115 
116         b[0] = reg;                    /* 寄存器首地址 */
117         memcpy(&b[1],buf,len);       /* 将要写入的数据拷贝到数组b里面 */
118 
119         msg.addr = client->addr;        /* ft5x06地址 */
120         msg.flags = 0;                          /* 标记为写数据 */
121 
122         msg.buf = b;                            /* 要写入的数据缓冲区 */
123         msg.len = len + 1;                      /* 要写入的数据长度 */
124 
125         return i2c_transfer(client->adapter, &msg, 1);
126 }
127 
128 /*
129  * @description : 向ft5x06指定寄存器写入指定的值,写一个寄存器
130  * @param - dev:  ft5x06设备
131  * @param - reg:  要写的寄存器
132  * @param - data: 要写入的值
133  * @return   :    无
134  */
135 static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
136 {
137         u8 buf = 0;
138         buf = data;
139         ft5x06_write_regs(dev, reg, &buf, 1);
140 }
141 
142 /*
143  * @description     : FT5X06中断服务函数
144  * @param - irq         : 中断号 
145  * @param - dev_id      : 设备结构。
146  * @return                      : 中断执行结果
147  */
148 static irqreturn_t ft5x06_handler(int irq, void *dev_id)
149 {
150         struct ft5x06_dev *multidata = dev_id;
151 
152         u8 rdbuf[29];
153         int i, type, x, y, id;
154         int offset, tplen;
155         int ret;
156         bool down;
157 
158         offset = 1;     /* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
159         tplen = 6;              /* 一个触摸点有6个寄存器来保存触摸值 */
160 
161         memset(rdbuf, 0, sizeof(rdbuf));                /* 清除 */
162 
163         /* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
164         ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, 
rdbuf, FT5X06_READLEN);
165         if (ret) {
166                 goto fail;
167         }
168 
169         /* 上报每一个触摸点坐标 */
170         for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
171                 u8 *buf = &rdbuf[i * tplen + offset];
172                 
173          /* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
174                  * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
175                  * bit5:4  保留
176                  * bit3:0  X轴触摸点的11~8位。
177                  */
178                 type = buf[0] >> 6;     /* 获取触摸类型 */
179                 if (type == TOUCH_EVENT_RESERVED)
180                         continue;
181                         
182                 /* 我们所使用的触摸屏和FT5X06是反过来的 */
183                 x = ((buf[2] << 8) | buf[3]) & 0x0fff;
184                 y = ((buf[0] << 8) | buf[1]) & 0x0fff;
185 
186          /* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
187                  * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
188                  * bit3:0  Y轴触摸点的11~8位。
189                  */
190                 id = (buf[2] >> 4) & 0x0f;
191                 down = type != TOUCH_EVENT_UP;
192 
193                 input_mt_slot(multidata->input, id);
194           input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
195 
196                 if (!down)
197                         continue;
198                         
199                 input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
200                 input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
201         }
202 
203         input_mt_report_pointer_emulation(multidata->input, true);
204         input_sync(multidata->input);
205 
206 fail:
207         return IRQ_HANDLED;
208         
209 }
210 
211 /*
212  * @description     : FT5x06中断初始化
213  * @param - client      : 要操作的i2c
214  * @param - multidev: 自定义的multitouch设备
215  * @return          : 0,成功;其他负值,失败
216  */
217 static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
218 {
219         int ret = 0;
220 
221         /* 1,申请中断GPIO */
222         if (gpio_is_valid(dev->irq_pin)) {
223                 ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
224                                         GPIOF_IN, "edt-ft5x06 irq");
225                 if (ret) {
226                         dev_err(&client->dev,
227                                 "Failed to request GPIO %d, error %d\n",
228                                 dev->irq_pin, ret);
229                         return ret;
230                 }
231         }
232 
233         /* 2,申请中断,client->irq就是IO中断, */
234         ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
235                       ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
236                        client->name, &ft5x06);
237         if (ret) {
238                 dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
239                 return ret;
240         }
241 
242         return 0;
243 }
244 
245  /*
246   * @description     : i2c驱动的probe函数,当驱动与
247   *                    设备匹配以后此函数就会执行
248   * @param - client  : i2c设备
249   * @param - id      : i2c设备ID
250   * @return          : 0,成功;其他负值,失败
251   */
252 static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
253 {
254         int ret = 0;
255 
256         ft5x06.client = client;
257 
258         /* 1,获取设备树中的中断和复位引脚 */
259       ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
260       ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
261 
262         /* 2,复位FT5x06 */
263         ret = ft5x06_ts_reset(client, &ft5x06);
264         if(ret < 0) {
265                 goto fail;
266         }
267 
268         /* 3,初始化中断 */
269         ret = ft5x06_ts_irq(client, &ft5x06);
270         if(ret < 0) {
271                 goto fail;
272         }
273 
274         /* 4,初始化FT5X06 */
275         ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); /* 进入正常模式*/
276         ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);  /* FT5426中断模式 */
277 
278         /* 5,input设备注册 */
279         ft5x06.input = devm_input_allocate_device(&client->dev);
280         if (!ft5x06.input) {
281                 ret = -ENOMEM;
282                 goto fail;
283         }
284         ft5x06.input->name = client->name;
285         ft5x06.input->id.bustype = BUS_I2C;
286         ft5x06.input->dev.parent = &client->dev;
287 
288         __set_bit(EV_KEY, ft5x06.input->evbit);
289         __set_bit(EV_ABS, ft5x06.input->evbit);
290         __set_bit(BTN_TOUCH, ft5x06.input->keybit);
291 
292         input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
293         input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
294         input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
295         input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);
296         ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
297         if (ret) {
298                 goto fail;
299         }
300 
301         ret = input_register_device(ft5x06.input);
302         if (ret)
303                 goto fail;
304 
305         return 0;
306 
307 fail:
308         return ret;
309 }
310 
311 /*
312  * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
313  * @param - client      : i2c设备
314  * @return          : 0,成功;其他负值,失败
315  */
316 static int ft5x06_ts_remove(struct i2c_client *client)
317 {
318         /* 释放input_dev */
319         input_unregister_device(ft5x06.input);
320         return 0;
321 }
322 
323 
324 /*
325  *  传统驱动匹配表
326  */
327 static const struct i2c_device_id ft5x06_ts_id[] = {
328         { "edt-ft5206", 0, },
329         { "edt-ft5426", 0, },
330         { /* sentinel */ }
331 };
332 
333 /*
334  * 设备树匹配表 
335  */
336 static const struct of_device_id ft5x06_of_match[] = {
337         { .compatible = "edt,edt-ft5206", },
338         { .compatible = "edt,edt-ft5426", },
339         { /* sentinel */ }
340 };
341 
342 /* i2c驱动结构体 */
343 static struct i2c_driver ft5x06_ts_driver = {
344         .driver = {
345                 .owner = THIS_MODULE,
346                 .name = "edt_ft5x06",
347                 .of_match_table = of_match_ptr(ft5x06_of_match),
348         },
349         .id_table = ft5x06_ts_id,
350         .probe    = ft5x06_ts_probe,
351         .remove   = ft5x06_ts_remove,
352 };
353 
354 /*
355  * @description : 驱动入口函数
356  * @param               : 无
357  * @return              : 无
358  */
359 static int __init ft5x06_init(void)
360 {
361         int ret = 0;
362 
363         ret = i2c_add_driver(&ft5x06_ts_driver);
364 
365         return ret;
366 }
367 
368 /*
369  * @description : 驱动出口函数
370  * @param               : 无
371  * @return              : 无
372  */
373 static void __exit ft5x06_exit(void)
374 {
375         i2c_del_driver(&ft5x06_ts_driver);
376 }
377 
378 module_init(ft5x06_init);
379 module_exit(ft5x06_exit);
380 MODULE_LICENSE("GPL");
381 MODULE_AUTHOR("topeet");

第 29~36 行,定义一个设备结构体,存放多点电容触摸设备相关属性信息。
第 38 行,定义一个名为 ft5x06 的全局变量,变量类型就是上面定义的 ft5x06_dev 结构体。
第 46~63 行,ft5x06_ts_reset 函数,用于初始化 FT5426 触摸芯片,其实就是设置 FT5426的复位 IO 为高电平,防止芯片复位。注意在第 52 行使用 devm_gpio_request_one 函数来申请复位 IO,关于“devm_”前缀的作用已经在前面做了详细的讲解。使用“devm_”前缀的API 函数申请的资源不需要我们手动释放,内核会处理,所以这里使用 devm_gpio_request_one函数申请 IO 以后不需要我们在卸载驱动的时候手动去释放此 IO。
第 73~98 行,ft5x06_read_regs 函数,用于连续的读取 FT5426 内部寄存器数据,就是 I2C读取函数。
第 98~124 行,ft5x06_write_regs 函数,用于向 FT5426 寄存器写入连续的数据,也就是 I2C写函数。
第 133~138 行,ft5x06_write_reg 函数,对 ft5x06_write_regs 函数的简单封装,向 FT5426 指定寄存器写入一个数据,用于配置 FT5426。
第 146~207 行,ft5x06_handler 函数,触摸屏中断服务函数,触摸点坐标的上报就是在此函数中完成的。第 162 行通过 ft5x06_read_regs 函数读取 FT5426 的所有触摸点信息寄存器数据,从 0X02 这个地址开始,一共 29 个寄存器。第 168~199 行的 for 循环就是一个一个的上报触摸点坐标数据,使用Type B时序,这个我们已经在前面说了很多次了。最后在202行通过input_sync函数上报 SYN_REPORT 事件。如果理解了前面讲解的 Type B 时序,那么此函数就很好看懂。
第 215~241 行 , ft5x06_ts_irq 函数,初始化 FT5426 的中断 IO ,第 221 行使用
devm_gpio_request_one 函数申请中断 IO。第 232 行使用函数 devm_request_threaded_irq 申请中断,中断处理函数为 ft5x06_handler。
第 250~307 行,当 I2C 设备与驱动匹配以后此函数就会执行,一般在此函数中完成一些初始化工作。我们重点来看一下 277~299 行是关于 input_dev 设备的初始化,第 277~284 行申请并简单的初始化 input_dev。第 286 和 288行设置 input_dev需要上报的事件为 EV_KEY 和 EV_ABS,需要上报的按键码为 BTN_TOUCH。EV_KEY 是按键事件,用于上报触摸屏是否被按下,相当于把触摸屏当做一个按键。EV_ABS 是触摸点坐标数据,BTN_TOUCH 表示将触摸屏的按下和抬起用作 BTN_TOUCH 按键。第 290~293 行调用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。第 294 行调用 input_mt_init_slots 函数初始化 slots,也就是最大触摸点数量,FT5426 是个 5 点电容触摸芯片,因此一共 5 个 slot。最后在299 行调用 input_register_device 函数向系统注册 input_dev。
第 316~321 行,当卸载驱动的时候 ft5x06_ts_remove 函数就会执行,因为前面很多资源我们都是用“devm_”前缀函数来申请的,因此不需要手动释放。此函数只需要调用input_unregister_device 来释放掉前面添加到内核中的 input_dev。
第 322 行~结束,剩下的就是 I2C 驱动框架那一套。

【北京迅为】i.MX6ULL终结者Linux 电容触摸屏实验实验程序编写_第1张图片

你可能感兴趣的:(#,第四部分,Linux驱动开发,linux,开发,嵌入式)