概述
1.电容触摸屏驱动框架
1.1 MT协议讲解
①、电容触摸屏是 IIC 接口的,需要触摸 IC,以正点原子的 ATK7016 为例,其所使用的触摸屏控制 IC 为 FT5426,因此所谓的电容触摸驱动就是 IIC 设备驱动。
②、触摸 IC 提供了中断信号引脚(INT),可以通过中断来获取触摸信息。
③、电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
④、电容触摸屏不需要校准,当然了,这只是理论上的,如果电容触摸屏质量比较差,或者触摸玻璃和 TFT 之间没有完全对齐,那么也是需要校准的。
根据以上几个知识点,我们可以得出电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:
①、IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
②、通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。
MT 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个
触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有ABS_MT 事件是用于多点触摸的,ABS_MT 事件定义在文件 include/uapi/linux/input.h 中,相关事件如下所示:
在 上 面 这 些 众 多 的 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()函数来隔离不同的触摸点数据信息,此函数原型如下所示:
不管是哪个类型的设备,最终都要调用 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。
1.2 Type A触摸点信息上报时序
对于 Type A 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:
第 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 函数实现。
我们在编写 TypeA 类型的多点触摸驱动的时候就需要按照示例代码 64.1.2.1 中的时序上报坐标信息。Linux 内核里面也有 Type A 类型的多点触摸驱动,找到 st2332.c 这个驱动文件,路径为 drivers/input/touchscreen/st1232.c,找到 st1232_ts_irq_handler 函数,此函数里面就是上报触摸点坐标信息的。
第 111 行,获取所有触摸点信息。
第 116~125 行,按照 Type A 类型轮流上报所有的触摸点坐标信息,第 121 和 122 行分别上报触摸点的(X,Y)轴坐标,也就是 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。每上报完一个触摸点坐标,都要在第 123 行调用 input_mt_sync 函数上报一个 SYN_MT_REPORT信息。
第 142 行,每上报完一轮触摸点信息就调用一次 input_sync 函数,也就是发送一个SYN_REPORT 事件。
1.3 Type B触摸点信息上报时序
第 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 行,和第 1~4 行类似,只是换成了上报触摸点 0 的(X,Y)坐标信息
第 9 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 input_sync函数来完成。
当一个触摸点移除以后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示:
第 1 行,当一个触摸点(SLOT)移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的第三个参数 active 设置为 false 即可,不需要用户手动去设置-1。
第 2 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。
当要编写 Type B 类型的多点触摸驱动的时候就需要按照示例代码 64.1.3.1 中的时序上报坐标信息。Linux 内核里面有大量的 Type B 类型的多点触摸驱动程序,我们可以参考这些现成的驱动程序来编写自己的驱动代码。这里就以 ili210x 这个触摸驱动 IC 为例,看看是 Type B 类型是如何上报触摸点坐标信息的。找到 ili210x.c 这 个 驱 动 文 件 , 路 径 为drivers/input/touchscreen/ili210x.c,找到 ili210x_report_events 函数,此函数就是用于上报 ili210x触摸坐标信息的,函数内容如下所示:
static void ili210x_report_events(struct input_dev *input, const struct touchdata *touchdata)
{
int i;
bool touch;
unsigned int x, y;
const struct finger *finger;
for (i = 0; i < MAX_TOUCHES; i++) {
//上 报 ABS_MT_SLOT 事件
input_mt_slot(input, i);
finger = &touchdata->finger[i];
touch = touchdata->status & (1 << i);
//input_mt_report_slot_state 函数上报ABS_MT_TRACKING_ID 事件,也就是给 SLOT 关联一个 ABS_MT_TRACKING_ID
//touch是true
input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
if (touch) {
x = finger->x_low | (finger->x_high << 8);
y = finger->y_low | (finger->y_high << 8);
//上报 x y坐标
input_report_abs(input, ABS_MT_POSITION_X, x);
input_report_abs(input, ABS_MT_POSITION_Y, y);
}
}
//input_sync 函数上报 SYN_REPORT 事件
input_mt_report_pointer_emulation(input, false);
input_sync(input);
}
1.4 MT其他事件使用
ABS_MT_TOOL_TYPE 事件用于上报触摸工具类型,很多内核驱动都不能区分出触摸设备类型,是手指还是触摸笔?这种情况下,这个事件可以忽略掉。目前的协议支持MT_TOOL_FINGER(手指)、MT_TOOL_PEN(笔)和 MT_TOOL_PALM(手掌)这三种触摸设备类型 , 于 Type B 类 型 , 此 事 件 由 input 子系统内核处理。如果驱动程序需要上报ABS_MT_TOOL_TYPE 事件,那么可以使用 input_mt_report_slot_state 函数来完成此工作。
1.5 MT用到的API函数
根据前面的讲解,我们知道 linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的,本小节我们来看一下一些常见的 API 函数。
input_mt_init_slots 函数
input_mt_slot 函数
input_mt_report_slot_state 函数
input_report_abs 函数
input_mt_report_pointer_emulation 函数
1.6 多点电容触摸驱动框架
①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。
根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:
I2C 驱动框架
初始化触摸 IC、中断和 input 子系统
第 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,算是低速外设。不断的产生中断、读取触摸信息、上报信息会导致处理器在触摸中断上花费大量的时间,但是触摸相对来说不是那么重要的事件,因此可以将触摸中断线程化。如果你觉得触摸中断很重要,那么就可以不将其进行线程化处理。总之,要不要将一个中断进行线程化处理是需要自己根据实际情况去衡量的。linux 内核自带的 goodix.c(汇顶科技)、mms114.c(MELFAS 公司)、zforce_ts.c(zForce 公司)等多点电容触摸 IC 驱动程序都采用了中断线程化,当然也有一些驱动没有采用中断线程化。
③、最后来看一下“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。
上报坐标信息
最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择使用 TypeA 还是 Type B 时序。由于大多数的设备都是 Type B 类型,因此这里就以 Type B类型为例讲解一下上报过程,参考驱动框架如下所示:
进入中断处理程序以后首先肯定是从触摸 IC 里面读取触摸坐标以及触摸点数量,假设触摸点数量保存到 num 变量,触摸点坐标存放到 x,y 数组里面。
第 11~16 行,循环上报每一个触摸点坐标,一定要按照 Type B 类型的时序进行,这个已经在 64.1.3 小节进行详细的讲解,这里就不再赘述了。
第 19 行,每一轮触摸点坐标上报完毕以后就调用一次 input_sync 函数发送一个SYN_REPORT 事件。
2.实验程序编写
2.1 设备树
&i2c2 {
clock_frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
codec: wm8960@1a {
compatible = "wlf,wm8960";
reg = <0x1a>;
clocks = <&clks IMX6UL_CLK_SAI2>;
clock-names = "mclk";
wlf,shared-lrclk;
};
ov5640: ov5640@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_csi1>;
clocks = <&clks IMX6UL_CLK_CSI>;
clock-names = "csi_mclk";
pwn-gpios = <&gpio_spi 6 1>;
rst-gpios = <&gpio_spi 5 0>;
csi_id = <0>;
mclk = <24000000>;
mclk_source = <0>;
status = "okay";
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi1_ep>;
};
};
};
/* shaozheming 2022/02/14 添加触摸屏设备 */
ft5426: ft5426@38 {
compatible = "edt,edt-ft5426";
reg = <0x38>; /* 触摸屏所使用的 FT5426 芯片节点,挂载 I2C2 节点下,FT5426 的器件地址为0X38 */
pinctrl-names = "default";
/* 复位 IO 和中断 IO 所使用的节点为 pinctrl_tsc和 pinctrl_tsc_reset */
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
};
2.2 驱动代码
#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h> /* 异步通知 */
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#define MAX_SUPPORT_POINTS 5 /* 5 点触摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 抬起 */
#define TOUCH_EVENT_ON 0x02 /* 接触 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */
/* FT5X06 寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG 0X02 /* 状态寄存器地址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中断模式 */
#define FT5X06_READLEN 29 /* 要读取的寄存器个数 */
struct ft5x06_dev {
struct device_node *nd; /* 设备节点 */
int irq_pin,reset_pin; /* 中断和复位 IO */
int irqnum; /* 中断号 */
void *private_data; /* 私有数据 */
struct input_dev *input; /* input 结构体 */
struct i2c_client *client; /* I2C 客户端,设备 */
};
static struct ft5x06_dev ft5x06;
/*
* @description : 复位 FT5X06
* @param - client : 要操作的 i2c
* @param – multidev : 自定义的 multitouch 设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
if (gpio_is_valid(dev->reset_pin)) { /* 检查 IO 是否有效 */
/* 申请复位 IO,并且默认输出低电平 */
ret = devm_gpio_request_one(&client->dev,
dev->reset_pin,
GPIOF_OUT_INIT_LOW,
"edt-ft5x06 reset"); /* label自己定义 */
if (ret) {
return ret;
}
msleep(5);
gpio_set_value(dev->reset_pin, 1); /* 输出高电平,停止复位 */
msleep(300);
}
return 0;
}
/*
* @description : 从 FT5X06 读取多个寄存器数据
* @param – dev : ft5x06 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* ft5x06地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ft5x06地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向 ft5x06 多个寄存器写入数据
* @param – dev : ft5x06 设备
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1], buf, len); /* 将要写入的数据拷贝到数组 b 里面 */
msg.addr = client->addr; /* ft5x06 地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 向 ft5x06 指定寄存器写入指定的值,写一个寄存器
* @param – dev : ft5x06 设备
* @param – reg : 要写的寄存器
* @param – data : 要写入的值
* @return : 无
*/
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ft5x06_write_regs(dev, reg, &buf, 1);
}
/*
* @description : FT5X06 中断服务函数
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
struct ft5x06_dev *multidata = dev_id;
u8 rdbuf[29];
int i, type, x, y, id;
int offset, tplen;
int ret;
bool down;
offset = 1; /* 偏移 1,也就是 0X02+1=0x03,从 0X03 开始是触摸值 */
tplen = 6; /* 一个触摸点有 6 个寄存器来保存触摸值 */
memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */
/* 读取 FT5X06 触摸点坐标从 0X02 寄存器开始,连续读取 29 个寄存器 */
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
if (ret) {
goto fail;
}
/* 上报每一个触摸点坐标 */
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];
/* 以第一个触摸点为例,寄存器 TOUCH1_XH(地址 0X03),各位描述如下:
* bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件
* bit5:4 保留
* bit3:0 X 轴触摸点的 11~8 位。
*/
type = buf[0] >> 6; /* 获取触摸类型 */
if (type == TOUCH_EVENT_RESERVED)
continue;
/* 我们所使用的触摸屏和 FT5X06 是反过来的 */
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;
/* 以第一个触摸点为例,寄存器 TOUCH1_YH(地址 0X05),各位描述如下:
* bit7:4 Touch ID 触摸 ID,表示是哪个触摸点
* bit3:0 Y 轴触摸点的 11~8 位。
*/
id = (buf[2] >> 4) & 0x0f;
down = type != TOUCH_EVENT_UP;
input_mt_slot(multidata->input, id);
input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
if (!down)
continue;
input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
}
input_mt_report_pointer_emulation(multidata->input, true);
input_sync(multidata->input);
fail:
return IRQ_HANDLED;
}
/*
* @description : FT5x06 中断初始化
* @param - client : 要操作的 i2c
* @param – multidev : 自定义的 multitouch 设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
/* 1,申请中断 GPIO */
if (gpio_is_valid(dev->irq_pin)) {
ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
GPIOF_IN, "edt-ft5x06 irq");
if (ret) {
dev_err(&client->dev, "Failed to request GPIO %d, error %dn",
dev->irq_pin, ret);
return ret;
}
}
/* 2,申请中断,client->irq 就是 IO 中断, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
ft5x06_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &ft5x06);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.n");
return ret;
}
return 0;
}
/*
* @description : i2c 驱动的 probe 函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : i2c 设备
* @param - id : i2c 设备 ID
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
ft5x06.client = client;
/* 1,获取设备树中的中断和复位引脚 */
ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
/* 2,复位 FT5x06 */
ret = ft5x06_ts_reset(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 3,初始化中断 */
ret = ft5x06_ts_irq(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 4,初始化 FT5X06 */
ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);
ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);
/* 5,input 设备注册 */
ft5x06.input = devm_input_allocate_device(&client->dev);
if (!ft5x06.input) {
ret = -ENOMEM;
goto fail;
}
ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;
__set_bit(EV_KEY, ft5x06.input->evbit);
__set_bit(EV_ABS, ft5x06.input->evbit);
__set_bit(BTN_TOUCH, ft5x06.input->keybit);
input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y, 0, 600, 0, 0);
ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(ft5x06.input);
if (ret)
goto fail;
return 0;
fail:
return ret;
}
/*
* @description : i2c 驱动的 remove 函数,移除 i2c 驱动的时候此函数会执行
* @param – client : i2c 设备
* @return : 0,成功;其他负值,失败
*/
static int ft5x06_ts_remove(struct i2c_client *client)
{
/* 释放 input_dev */
input_unregister_device(ft5x06.input);
return 0;
}
/*
* 传统驱动匹配表
*/
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "edt-ft5206", 0, },
{ "edt-ft5426", 0, },
{ /* sentinel */ }
};
/*
* 设备树匹配表
*/
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", },
{ .compatible = "edt,edt-ft5426", },
{ /* sentinel */ }
};
/* i2c 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(ft5x06_of_match),
},
.id_table = ft5x06_ts_id,
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ft5x06_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ft5x06_ts_driver);
return ret;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");
3.tslib移植使用
tslib 是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用 tslib 进行校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有时候也需要进行校准。最主要的是 tslib 提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。最新版本的 tslib 已经支持了多点电容触摸屏,因此可以通过 tslib 来直观的测试多点电容触摸屏驱动,这个要比观看 eventX 原始数据方便的多。
4.内核自带驱动
最后
以上就是负责丝袜为你收集整理的Linux 多点电容触摸屏实验的全部内容,希望文章能够帮你解决Linux 多点电容触摸屏实验所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复