欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十六章 电容屏触摸驱动实验

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十六章 电容屏触摸驱动实验

2024/10/25 11:33:52 来源:https://blog.csdn.net/BeiJingXunWei/article/details/140776821  浏览:    关键词:【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十六章 电容屏触摸驱动实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


六十六电容屏触摸驱动实验

如今触摸屏的使用越来越广泛,从手机、平板到蜂巢取货等场合,都是用了触摸屏,触摸屏的使用非

常便捷高效。在本章就来学习一下如何在 Linux 下编写电容触摸屏驱动。我们自己写一个触摸芯片ft5x06的驱动,实际上这个驱动也是可以在触摸芯片ft5426上来使用的,这两个触摸芯片是兼容的,通过这个触摸驱动实验,我们可以将以前学习过的知识进行框架的搭建,对以前学习的知识进行复习。

本章内容对应视频讲解链接(在线观看):

FT5X06触摸驱动实验 (一)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=48

FT5X06触摸驱动实验(二)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=49

触摸校准实验  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=50

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\022-电容屏触摸驱动实验\42touchscreen”路径下。

66.1 Linux 下电容触摸屏驱动框架

66.1.1 多点触摸协议详解

电容触摸屏驱动其实就是一下几种 linux 驱动框架的组合:

① IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。

② 通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。

③ 触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。

在上面的驱动框架组合中我们发现 I2C 驱动、中断驱动、input 子系统都已经学习了解过了,还没有学习过 input 子系统下的多点电容触摸协议,这个就是本章学习的重点,linux 内核中有一份文档详细的讲解了多点电容触摸屏协议,文档路径为:Documentation/input/multitouch-protocol.txt

MT 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:

TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。

Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有 ABS_MT 事件

是用于多点触摸的,ABS_MT 事件定义在文件 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 事件来区分触摸点。

对于 TypeA 类型的设备,通过 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 需要硬件提供,或者通过原始数据计算出来。对于 TypeA 设备,

内核驱动需要一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺

序不重要,因为事件的过滤和手指(触摸点)跟踪是在内核空间处理的。

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。

66.1.2 多点电容触摸驱动框架

本小节我们来梳理一下 linux 下多点电容

触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意以下几点:

① 多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。

② 当我们手点击屏幕的时候,首先屏幕会发生触摸中断,然后我们就要在中断服务函数里面启动中断下文,因为我们要做的事情非常耗时间,所以我们要在中断下文里面做这些事情 。linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。那么我们要使用中断下文,我们之前讲了实现中断下文的两种方法,第一种是tasklet,第二种是工作队列,我们这里要使用哪一种呢?因为我们在使用i2c_transfer进行读写的时候可能会发生休眠,所以说我们要用工作队列。

③ 多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。

④ 在中断下文里面使用i2c读数据并且通过输入子系统上报数据。

如下图所示,可以形象的了解这一过程。

根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:

1 、I2C 驱动框架

驱动总体采用 I2C 框架,参考框架代码如下所示:

/* 设备树匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {{"xxx",0,},{/* sentinel */}};/* 设备树匹配表 */
static const struct of_device_id xxx_of_match[] = {{.compatible = "xxx",},{/* sentinel */}};/* i2c 驱动结构体 */
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,
};/** @description : 驱动入口函数* @param : 无* @return : 无*/
static int __init xxx_init(void)
{int ret = 0;ret = i2c_add_driver(&xxx_ts_driver);return ret;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
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("topeet");

 当设备树中触摸 IC 的设备节点和驱动匹配以后,第 21 行的 xxx_ts_probe 函数就会执行,我们可以

在此函数中初始化触摸 IC,中断和 input 子系统等。

2初始化触摸 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 函数,也就是线程的意思。那么为什么要中

断线程化呢?我们都是知道硬件中断具有最高优先级,不论什么时候只要硬件中断发生,那么内核都会终

止当前正在执行的操作,转而去执行中断处理程序(不考虑关闭中断和中断优先级的情况),如果中断非常频

繁的话那么内核将会频繁地执行中断处理程序,导致任务得不到及时的处理。中断线程化以后中断将作为

内核线程运行,而且也可以被赋予不同的优先级,任务的优先级可能比中断线程的优先级高,这样做的目

的就是保证高优先级的任务能被优先处理。大家可能会疑问,前面不是说可以将比较耗时的中断放到下半

部(bottom half)处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较

耗时的下半部与进程进行公平竞争。要注意,并不是所有的中断都可以被线程化,重要的中断就不能这么操作。对于触摸屏而言只要手指放到屏幕上,它可能就会一直产生中断(视具体芯片而定,FT5426 是这样的),中断处理程序里面需要通过 I2C读取触摸信息并上报给内核,I2C 的速度最大只有 400KHz,算是低速外设。不断的产生中断、读取触摸信息、上报信息会导致处理器在触摸中断上花费大量的时间,但是触摸相对来说不是那么重要的事件,因此可以将触摸中断线程化。如果你觉得触摸中断很重要,那么就可以不将其进行线程化处理。总之,要不要将一个中断进行线程化处理是需要自己根据实际情况去衡量的。

③ 最后来看一下“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。

3 、上报坐标信息

最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择使用

TypeA 还是 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 事件。

关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动程序。

66.2 电容屏触摸驱动实验

66.2.1 硬件原理图

在本实验中使用迅为的 7 寸LVDS屏为例,使用的是 FT5426 触摸芯片。

从原理图中得知,7 寸屏使用 I2C1,触摸屏复位引脚为LCD_RST_H,中断引脚为 TOUCH_INT_L_3V3。

66.2.2 编写触摸驱动

我们拷贝第39章编写的驱动代码和Makefile,在此基础上进行修改。

因为我们要写的是触摸芯片ft5x06的驱动,先来了解一下ft5x06的模式设置,打开触摸芯片ft5x06的数据手册,如下图所示,我们可以找到对应寄存器的地址。

 

完整的代码如下所示: 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/workqueue.h>#define DEVICE_MODE 0x00
#define ID_G_MODE 0xa4
int irq;
static struct device_node *ft5x06_device_node;
static struct input_dev *ft5x06_dev;
static struct i2c_client *ft5x06_client;
static int ft5x06_read_reg(u8 reg_addr);
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
static void ft5x06_func(struct work_struct *work);
DECLARE_WORK(ft5x06_work, ft5x06_func);
//写寄存器函数
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{u8 buff[256];struct i2c_msg msgs[] = {[0] = {.addr = ft5x06_client->addr,.flags = 0,.len = len + 1,.buf = buff,}};buff[0] = reg_addr;memcpy(&buff[1], &data, len);i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
//读寄存器函数
static int ft5x06_read_reg(u8 reg_addr)
{u8 data;struct i2c_msg msgs[] = {[0] = {.addr = ft5x06_client->addr,.flags = 0,.len = sizeof(reg_addr),.buf = &reg_addr,},[1] = {.addr = ft5x06_client->addr,.flags = 1,.len = sizeof(data),.buf = &data,},};i2c_transfer(ft5x06_client->adapter, msgs, 2);return data;
}static void ft5x06_func(struct work_struct *work)
{int TOUCH1_XH, TOUCH1_XL, x;int TOUCH1_YH, TOUCH1_YL, y;int TD_STATUS;//读取TOUCH1_XH寄存器的值TOUCH1_XH = ft5x06_read_reg(0x03);//读取TOUCH1_XL寄存器的值TOUCH1_XL = ft5x06_read_reg(0x04);//获取X的坐标值x = ((TOUCH1_XH << 8) | TOUCH1_XL) & 0x0fff;//读取TOUCH1_YH寄存器的值TOUCH1_YH = ft5x06_read_reg(0x05);//读取TOUCH1_YL寄存器的值TOUCH1_YL = ft5x06_read_reg(0x06);//获取Y的坐标值y = ((TOUCH1_YH << 8) | TOUCH1_YL) & 0x0fff;//读取寄存器TD_STATUS的值TD_STATUS = ft5x06_read_reg(0x02);TD_STATUS = TD_STATUS & 0xf; //获取有没有手指在屏幕上if (TD_STATUS == 0){ //判断有没有手指按上,如果有的话就要上报按下去的事件,没有就要上报抬手事件input_report_key(ft5x06_dev, BTN_TOUCH, 0);input_sync(ft5x06_dev);}else{input_report_key(ft5x06_dev, BTN_TOUCH, 1);input_report_abs(ft5x06_dev, ABS_X, x);input_report_abs(ft5x06_dev, ABS_Y, y);input_sync(ft5x06_dev);}
}
//中断处理函数
static irqreturn_t ft5x06_handler(int irq, void *args)
{//printk("This is ft5x06_handler\n");//调度工作队列schedule_work(&ft5x06_work);return IRQ_RETVAL(IRQ_HANDLED);
}
/* i2c 驱动的 probe 函数 */
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{int ret;int ft5x05_irq_gpio;int ft5x05_reset_gpio;printk("This is ft5x06_probe\n");//因为我们要在别的函数里面用到这个client,所以我们要把他复制出来。ft5x06_client = client; //获得触摸芯片的节点ft5x06_device_node = of_find_node_by_path("/i2c@ff110000/ft5x06@38");if (ft5x06_device_node == NULL){printk("of_find_node_by_path is error\n");return -1;}//打印节点的名字printk("ft5x06_device_node is %s\n", ft5x06_device_node->name);//获得中断引脚的GPIO标号ft5x05_irq_gpio = of_get_named_gpio(ft5x06_device_node, "irq-gpios", 0);if (ft5x05_irq_gpio < 0){printk("ft5x05_irq_gpio of_get_named_gpio is error\n");return -2;}//获得复位引脚的GPIO标号ft5x05_reset_gpio = of_get_named_gpio(ft5x06_device_node, "reset-gpios", 0);if (ft5x05_reset_gpio < 0){printk("ft5x05_reset_gpio of_get_named_gpio is error\n");return -3;}// 打印中断引脚的GPIO标号printk("ft5x05_irq_gpio is %d\n", ft5x05_irq_gpio);//打印复位引脚的GPIO标号printk("ft5x05_reset_gpio is %d\n", ft5x05_reset_gpio);//在申请中断之前将GPIO释放掉gpio_free(ft5x05_irq_gpio);//申请中断引脚的GPIOret = gpio_request(ft5x05_irq_gpio, "touch-gpio");if (ret < 0){printk("gpio_request is error\n");return -4;}gpio_free(ft5x05_reset_gpio);//申请复位引脚的GPIOret = gpio_request(ft5x05_reset_gpio, "reset_gpio");if (ret < 0){printk("ft5x05_reset_gpio is error\n");return -5;}//把中断的引脚设置为输入gpio_direction_input(ft5x05_irq_gpio);//设置复位引脚的方向为输出,然后输出高电平,停止复位gpio_direction_output(ft5x05_reset_gpio, 0);msleep(5);gpio_set_value(ft5x05_reset_gpio, 1);//获得中断号irq = gpio_to_irq(ft5x05_irq_gpio);//申请中断ret = request_irq(irq, ft5x06_handler, IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, "ft5x06_irq", NULL);if (ret < 0){printk("request_irq is error\n");goto error_request_irq;}//设置工作模式为正常模式ft5x06_write_reg(DEVICE_MODE, 0, 1);ft5x06_write_reg(ID_G_MODE, 1, 1);//申请一个 input_dev输入设备ft5x06_dev = input_allocate_device();//设置 input_dev 名字ft5x06_dev->name = "ft5x06_input_test";__set_bit(EV_KEY, ft5x06_dev->evbit);     //支持按键事件__set_bit(EV_ABS, ft5x06_dev->evbit);     //支持绝对坐标事件__set_bit(BTN_TOUCH, ft5x06_dev->keybit); //支持按键检测__set_bit(ABS_X, ft5x06_dev->absbit); //支持X坐标__set_bit(ABS_Y, ft5x06_dev->absbit); //支持Y坐标//__set_bit(ABS_PRESSURE,ft5x06_dev-> keybit);//支持压力检测input_set_abs_params(ft5x06_dev, ABS_X, 0, 1024, 0, 0); //设置X坐标值的范围input_set_abs_params(ft5x06_dev, ABS_Y, 0, 600, 0, 0);  //设置Y坐标值的范围//input_set_abs_params(ft5x06_dev, ABS_PRESSURE, 0, 255, 0,0);//设置压力值的范围//向 Linux内核注册 input_devret = input_register_device(ft5x06_dev);if (ret < 0){printk("input_register_device is error\n");goto error_input_register;}return 0;error_request_irq:free_irq(irq, NULL);error_input_register:free_irq(irq, NULL);input_unregister_device(ft5x06_dev);input_free_device(ft5x06_dev);return ret;
}
/* i2c 驱动的 remove 函数 */
int ft5x06_remove(struct i2c_client *client)
{printk("This is ft5x06_remove\n");return 0;
}
// 无设备树的时候匹配 ID 表
static const struct i2c_device_id ft5x06_id_ts[] = {{"xxx",0,},{/* sentinel */}};
//与设备树的 compatible 匹配
static const struct of_device_id ft5x06_id[] = {{.compatible = "edt,ft5x0x_ts",0,},{.compatible = "edt,ft5x0x_ts",0,},{.compatible = "edt,ft5x0x_ts",0,},{/* sentinel */}};//定义一个i2c_driver的结构体
static struct i2c_driver ft5x06_driver = {.driver = {.owner = THIS_MODULE,.name = "ft5x06_test",.of_match_table = ft5x06_id,},.probe = ft5x06_probe,.remove = ft5x06_remove,.id_table = ft5x06_id_ts};
/* 驱动入口函数 */
static int ft5x06_driver_init(void)
{int ret = 0;ret = i2c_add_driver(&ft5x06_driver);if (ret < 0){printk("i2c_add_driver is error\n");return ret;}return 0;
}
/* 驱动出口函数 */
static void ft5x06_driver_exit(void)
{printk("This is ft5x06_driver_exit\n");free_irq(irq, NULL);input_unregister_device(ft5x06_dev);input_free_device(ft5x06_dev);i2c_del_driver(&ft5x06_driver);
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

我们参考第三十七章 Linux内核模块将刚刚编写的驱动代码编译为驱动模块,

我们进入共享目录并且加载驱动模块,共享目录的搭建参考第二十九章节 NFS服务器的搭建和使用,如下图所示:

我们输入以下命令,查看输入设备节点并且获取event3的输出信息,我们按下触摸屏,上报的数据包如下所示:

 ls /dev/input/event3

 hexdump /dev/input/event3

 数据整理:

我们来分析一下数据包的信息

0d8a 50f9 0000 0000 7454 0007 0000 0000 0001 014a 0001 0000

type  0001

code  014a

value 0001

EV_KEY事件  

BTN_TOUCH

按下

0d8a 50f9 0000 0000 7454 0007 0000 0000 0003 0000 031d 0000 

type   0003

code  0000

value 031d 对应的10进制是 797

EV_ABS事件

ABS_X

X坐标值797

x的坐标值的范围是0到800,797是符合的数据(3399上实验用的屏幕为7寸lvds,分辨率为800*1280)

0d8a 50f9 0000 0000 7454 0007 0000 0000 0003 0001 04dc 0000

type   0003

code 0001

value 04dc 对应的10进制是1244

EV_ABS事件  

ABS_Y

y坐标值 1244

x的坐标值的范围是0到1280,1244是符合的数据(3399上实验用的屏幕为7寸lvds,分辨率为800*1280)

0d8a 50f9 0000 0000 7454 0007 0000 0000 0000 0000 0000 0000

type  0000

 code 0000

 value 0000

EV_SYN事件  

0d8a 50f9 0000 0000 7454 0007 0000 0000 0000 0000 0000 0000

0d8a 50f9 0000 0000 b915 0008 0000 0000 0001 014a 0000 0000

type  0001

code  014a

value 0000

EV_KEY事件  

BTN_TOUCH

抬起

0d8a 50f9 0000 0000 b915 0008 0000 0000 0000 0000 0000 0000 

type  0001

code  014a

value 0000

EV_SYN事件