16. Linux内核触摸驱动¶
在嵌入式Linux系统中,触摸屏作为人机交互的核心输入设备,广泛应用于智能手机、平板电脑、工业控制终端、车载设备等场景。 与传统的键盘、鼠标输入不同,触摸屏具备直观、便捷、小巧的特点,能够实现“所见即所得”的操作体验,是嵌入式设备走向轻量化、智能化的关键输入载体。
Linux内核为各类触摸屏设备(电阻式、电容式、USB式)抽象封装了标准化的触摸驱动子系统,核心由input子系统、触摸屏核心层(touchscreen core)和多点触摸核心层(input-mt core)组成。 该子系统屏蔽了不同类型、不同厂商触摸屏的硬件差异,统一了驱动开发接口和应用层交互方式,驱动开发者只需适配底层硬件的触摸数据读取、中断处理逻辑, 无需重复编写上层输入事件上报、多点触摸管理、设备节点创建等通用代码,大幅降低触摸驱动的开发难度和移植成本。
16.1. 触摸屏基础知识¶
根据触摸检测原理和通信方式,嵌入式系统中常用的触摸屏主要分为三类:电阻式触摸屏、电容式触摸屏、USB触摸屏。 三类触摸屏的工作原理、特点、适用场景差异较大,驱动适配逻辑也有所不同,以下分别讲解。
16.1.1. 电阻式触摸屏¶
16.1.1.1. 工作原理¶
电阻式触摸屏的核心是“分层电阻膜+压力感应”,由上下两层透明电阻膜(ITO膜)组成,中间填充绝缘隔离点。当用户用手指或触摸笔按压屏幕时,上下两层电阻膜接触,形成导通回路,通过检测接触点的电压分压,计算出触摸坐标。
电阻屏的坐标检测分为“X轴检测”和“Y轴检测”:
X轴检测:给上层电阻膜施加固定电压,下层膜接地,接触点的电压与X轴坐标成正比,通过ADC采集电压值,换算为X轴坐标;
Y轴检测:与X轴相反,给下层电阻膜施加固定电压,上层膜接地,采集接触点电压换算为Y轴坐标。
具体可参考: 电阻式触摸屏检测原理
16.1.1.2. 优缺点¶
优点:结构简单、成本低廉、抗干扰能力强、支持触摸笔/手指等多种触摸介质,适用于工业控制、户外设备等恶劣环境。
缺点:仅支持单点触摸,触摸精度较低,容易出现漂移,需要定期校准,触摸时需要施加一定压力,体验较差。
16.1.2. 电容式触摸屏¶
16.1.2.1. 工作原理¶
电容式触摸屏基于“电容感应”原理,屏幕表面覆盖一层透明电容层,当用户手指(导体)触摸屏幕时,手指与电容层形成耦合电容,导致电容层的电容量发生变化。触摸屏控制芯片通过检测电容量的变化,定位触摸点的坐标。
电容屏分为“表面电容式”和“投射电容式”,嵌入式设备中主要采用投射电容式(支持多点触摸):
表面电容式:仅支持单点触摸,通过屏幕四个角的电极检测电容变化,定位精度较低;
投射电容式:在电容层蚀刻出矩阵式电极(X轴、Y轴交叉电极),每个交叉点形成一个电容单元,触摸时可同时检测多个电容单元的变化,实现多点触摸。
具体可参考: 电容式触摸屏检测原理
16.1.2.2. 优缺点¶
优点:支持多点触摸,触摸精度高、响应速度快,无需施加压力,触摸体验好,适用于智能手机、平板电脑等消费类设备。
缺点:成本较高,抗干扰能力弱,易受水、金属等导体影响,不支持普通绝缘体触摸笔。
16.1.3. USB触摸屏¶
16.1.3.1. 工作原理¶
USB触摸屏本质是“电容/电阻触摸屏+USB接口控制芯片”,其触摸检测原理与普通电容/电阻屏一致,核心差异在于通信方式:普通触摸屏通过I2C/SPI总线与主控芯片通信,而USB触摸屏通过USB总线与主控通信,符合USB HID(Human Interface Device,人机接口设备)协议,被Linux内核识别为HID设备。
USB触摸屏的控制芯片会将触摸数据(坐标、触摸状态)封装为HID报告,通过USB总线发送给主控,Linux内核通过HID子系统解析HID报告,提取触摸数据,上报给input子系统。
16.1.3.2. 优缺点¶
优点:即插即用,无需单独适配底层硬件(控制芯片已集成USB HID协议),驱动开发难度低,通用性强。
缺点:响应速度略低于I2C/SPI接口触摸屏,依赖USB总线供电,功耗较高。
16.2. 触摸驱动核心层详解¶
Linux内核触摸驱动的核心是触摸屏核心层(touchscreen core)和多点触摸核心层(input-mt core),两者均依赖input子系统,分别负责“触摸屏标准化管理”和“多点触摸事件管理”,是所有触摸驱动的基础。
16.2.1. 触摸屏核心层(touchscreen core)¶
触摸屏核心层的核心作用是抽象触摸屏的共性功能,屏蔽不同类型触摸屏的硬件差异,提供统一的触摸屏驱动接口和坐标校准机制,简化驱动开发。
16.2.1.1. 核心结构体¶
16.2.1.1.1. 触摸屏物理属性结构体¶
触摸屏物理属性结构体(struct touchscreen_properties)用于存储触摸屏的物理属性(分辨率、坐标方向、翻转状态等),由驱动初始化,供触摸屏核心层使用,统一坐标上报格式,实现坐标校准和方向适配。
1 2 3 4 5 6 7 | struct touchscreen_properties {
unsigned int max_x; /* X轴最大分辨率 */
unsigned int max_y; /* Y轴最大分辨率 */
bool invert_x; /* X轴是否翻转:true翻转,false正常 */
bool invert_y; /* Y轴是否翻转:true翻转,false正常 */
bool swap_x_y; /* X/Y轴是否交换:true交换,false正常 */
};
|
16.2.1.2. 核心API函数¶
16.2.1.2.1. 触摸屏属性解析函数(touchscreen_parse_properties)¶
touchscreen_parse_properties函数用于从设备树中解析触摸屏的属性(分辨率、轴翻转、轴交换),自动填充struct touchscreen_properties结构体,无需驱动手动赋值,简化驱动开发。
函数原型:
1 2 | void touchscreen_parse_properties(struct input_dev *input, bool multitouch,
struct touchscreen_properties *prop);
|
参数说明:
dev:指向当前触摸驱动的input_dev结构体指针;
multitouch:是否支持多点触摸,true表示多点触摸,false表示单点触摸;
prop:指向struct touchscreen_properties结构体指针,用于存储解析后的触摸屏属性。
16.2.1.2.2. 触摸坐标上报函数(touchscreen_report_pos)¶
touchscreen_report_pos函数用于标准化的触摸坐标上报函数,自动根据struct touchscreen_properties结构体的配置(轴翻转、轴交换),修正原始触摸坐标,然后上报给input子系统,无需驱动手动处理坐标修正。
函数原型:
1 2 3 4 | void touchscreen_report_pos(struct input_dev *input,
const struct touchscreen_properties *prop,
unsigned int x, unsigned int y,
bool multitouch);
|
参数说明:
dev:输入设备结构体指针,指向当前触摸驱动的input_dev;
prop:触摸屏属性结构体指针,包含坐标修正相关参数;
x:从硬件读取的原始X轴坐标;
y:从硬件读取的原始Y轴坐标;
multitouch:是否为多点触摸事件,true表示多点,false表示单点。
16.2.2. 多点触摸核心层(input-mt core)¶
input-mt core(MT:Multi-Touch)的核心作用是管理多点触摸事件,实现触摸点的识别、跟踪、槽位管理,统一多点触摸事件的上报格式,适配电容式触摸屏等支持多点触摸的设备。
16.2.2.1. 核心结构体¶
16.2.2.1.1. 多点触摸槽位结构体¶
多点触摸槽位结构体(struct input_mt_slot)用于存储单个触摸点的状态信息,每个触摸点对应一个slot(槽位),input-mt core通过slot管理多个触摸点,实现触摸点的跟踪和识别。
1 2 3 4 5 | struct input_mt_slot {
int abs[ABS_MT_LAST - ABS_MT_FIRST + 1]; /* 存储该触摸点的绝对坐标(X、Y)、压力等参数,适配MT坐标范围 */
unsigned int frame; /* 该触摸点所属的帧编号,用于同步一帧触摸数据 */
unsigned int key; /* 触摸点关联的按键标识,可选,用于绑定触摸与按键事件 */
};
|
16.2.2.1.2. 多点触摸全局管理结构体¶
多点触摸全局管理结构体(struct input_mt)管理所有多点触摸槽位,存储多点触摸的全局状态,由多点触摸核心层自动维护,驱动无需手动初始化。
1 2 3 4 5 6 7 8 9 | struct input_mt {
int trkid; /* 触摸点跟踪ID,用于关联相邻帧的触摸点,实现触摸跟踪 */
int num_slots; /* 最大槽位数量,即最大触摸点数量 */
int slot; /* 当前活跃的槽位索引 */
unsigned int flags; /* 多点触摸控制标志,用于配置MT工作模式,如跟踪、上报方式 */
unsigned int frame; /* 当前帧编号,用于同步一帧触摸数据 */
int *red; /* 触摸点冗余数据指针,用于存储额外触摸相关信息 */
struct input_mt_slot slots[];/* 柔性数组成员,存储所有多点触摸槽位,对应各个触摸点 */
};
|
16.2.2.2. 核心API函数¶
16.2.2.2.1. 多点触摸槽位初始化函数(input_mt_init_slots)¶
input_mt_init_slots函数用于初始化多点触摸槽位,分配槽位内存,设置最大触摸点数量,是多点触摸驱动的必备初始化步骤。
函数原型:
1 2 3 | int input_mt_init_slots(struct input_dev *dev,
unsigned int num_slots,
unsigned int flags);
|
参数说明:
dev:输入设备结构体指针,指向当前触摸驱动的input_dev;
num_slots:最大槽位数量,即驱动支持的最大触摸点数量;
flags:多点触摸模式标志,常用取值:
INPUT_MT_DIRECT:直接上报模式,每个触摸点对应一个槽位,适用于大多数电容屏;
INPUT_MT_DROP_UNUSED:自动释放未使用的槽位,节省资源;
INPUT_MT_TRACK:开启触摸点跟踪,自动关联相邻帧的触摸点。
返回值:
成功:返回0,槽位初始化完成;
失败:返回负整数错误码。
16.2.2.2.2. 触摸点槽位绑定函数(input_mt_slot)¶
input_mt_slot函数用于将当前触摸点绑定到指定的槽位,告知多点触摸核心层当前上报的触摸数据属于哪个触摸点,是多点触摸事件上报的第一步。
函数原型:
1 | void input_mt_slot(struct input_dev *dev, int slot);
|
参数说明:
dev:输入设备结构体指针;
slot:槽位索引,对应触摸点ID,由驱动从硬件读取。
16.2.2.2.3. 触摸点状态上报函数(input_mt_report_slot_state)¶
input_mt_report_slot_state函数用于上报当前槽位对应的触摸点状态(按下/抬起),告知多点触摸核心层该触摸点是否有效。
函数原型:
1 2 3 | void input_mt_report_slot_state(struct input_dev *dev,
unsigned int tool_type,
bool active);
|
参数说明:
dev:输入设备结构体指针;
tool_type:触摸工具类型,常用MT_TOOL_FINGER(手指触摸)、MT_TOOL_PEN(笔触摸);
active:触摸点状态,true表示触摸点按下(有效),false表示触摸点抬起(无效)。
16.2.2.2.4. 多点触摸帧同步函数(input_mt_sync_frame)¶
input_mt_sync_frame函数用于标记一帧多点触摸数据上报完成,同步所有触摸点的事件,告知input子系统当前帧的触摸数据已全部上报,应用层可读取完整的一帧数据。
函数原型:
1 | void input_mt_sync_frame(struct input_dev *dev);
|
参数说明:
dev:输入设备结构体指针。
16.2.3. 触摸驱动通用API函数¶
input_set_capability函数用于用于设置输入设备的能力,告知Linux内核当前输入设备支持的事件类型(如EV_ABS、EV_KEY)和具体事件编码(如ABS_MT_POSITION_X),是触摸驱动中初始化input_dev的关键函数,替代传统的set_bit操作,简化设备能力配置流程。
函数原型:
1 2 3 | int input_set_capability(struct input_dev *dev,
unsigned int type,
unsigned int code);
|
参数说明:
dev:输入设备结构体指针,指向当前触摸驱动的input_dev;
type:输入事件类型,触摸驱动常用EV_ABS(绝对坐标事件)、EV_KEY(按键事件);
code:具体事件编码,如EV_ABS类型对应的ABS_MT_POSITION_X(多点触摸X轴坐标)、ABS_MT_POSITION_Y(多点触摸Y轴坐标),EV_KEY类型对应的BTN_TOUCH(触摸按键)。
返回值:
成功:返回0,设备能力设置完成;
失败:返回负整数错误码。
input_set_abs_params函数用于设置输入设备绝对坐标(如触摸X、Y轴)的参数范围(最大、最小、模糊值、平坦值),用于定义触摸坐标的有效范围,过滤无效坐标和微小抖动。
函数原型:
1 2 3 | void input_set_abs_params(struct input_dev *dev,
unsigned int axis,
int min, int max, int fuzz, int flat);
|
参数说明:
dev:输入设备结构体指针;
axis:绝对坐标轴,如ABS_MT_TOUCH_MAJOR(触摸主尺寸,反映触摸接触面积大小)、ABS_MT_WIDTH_MAJOR(触摸宽度,辅助判断触摸姿态);
min:该轴的最小取值;
max:该轴的最大取值;
fuzz:模糊值,用于过滤微小抖动,如1~2像素;
flat:平坦值,用于过滤微小变化,如0。
注解
由于目前嵌入式Linux设备大部分场合使用的都是电容触摸屏,很难再遇到电阻触摸屏,因此对电阻触摸驱动不做相应实验,对电阻触摸感兴趣的可参考内核源码/drivers/input/touchscreen/ads7846.c自行研究,对应屏幕 野火3.2寸电阻触摸屏 和 树莓派3.5寸电阻触摸屏 的电阻触控芯片XPT2046。
16.3. 触摸驱动实验¶
本实验基于I2C子系统 + Regmap API + 输入子系统的架构实现对GT911/GT928电容触摸芯片的驱动开发,支持多点触摸、坐标上报、中断触发功能,实验整体逻辑为“设备树插件配置->驱动适配加载->输入设备注册->硬件初始化->触摸数据读取与事件上报”。
本章的示例代码目录为: linux_driver/34_touch_driver
目前野火5.5寸、7寸MIPI屏幕使用的触摸芯片为GT911,10.1寸MIPI屏幕使用的触摸芯片为GT928,实际以 野火【MIPI接口电容触摸屏_5.5_7_10.1寸】模块 说明为主。
16.3.1. GT911/GT928电容触摸芯片简介¶
GT911与GT928电容触摸芯片均为汇顶(Goodix)的电容触控芯片,I2C接口兼容、驱动可通用适配,核心差异在屏幕尺寸、触摸点数、通道规模、封装等,两者对比如下:
芯片 |
GT911 |
GT928 |
|---|---|---|
产品定位 |
小/中小尺寸触摸屏 |
中大尺寸触摸屏 |
最大支持尺寸 |
4~8寸 |
≤10.1寸 |
触摸点数 |
5点触控 |
10点触控 |
Tx/Rx通道 |
26Tx x 14Rx |
32Tx x 24Rx |
封装 |
QFN52(6x6mm) |
QFN68(8x8mm) |
触摸扫描频率 |
100Hz |
100Hz |
通信接口 |
I2C |
I2C |
GT911数据手册: GT911_Datasheet
GT928数据手册: GT928_Datasheet
重要
寄存器说明在芯片数据手册中有十分详细的说明。
GT928的寄存器相较GT911地址在寄存器地址末尾多了连续的5个触摸点寄存器,其他部分和GT911是一致的,以下以GT911进行说明。
截取手册关键说明并结合本实验情况整理得:
上电时序及通信地址设置
GT911从电源上电到复位完成包括4个阶段:
第1阶段:电源上电
必须先给AVDD(模拟电源)上电,稳定后,VDDIO(IO电源)必须在T1 < 100ms内完成上电。
第2阶段:电源稳定等待
VDDIO上电完成后,需等待T2 > 10ms,让电源纹波完全稳定,再操作Reset和INT引脚,避免误复位。
第3、4阶段:I2C通信地址设置
如果需要设定地址为0x28/0x29,则T3阶段Reset引脚保持低电平,INT引脚在T3阶段开始时保持低电平,到T3阶段结束前转为高电平且保持时间大于100us;T4阶段Reset引脚转变为高电平,INT引脚保持高电平且保持时间大于5ms。
如果需要设定地址为0xBA/0xBB,则T3阶段Reset引脚保持低电平,INT引脚在T3阶段开始时保持低电平,到T3阶段结束前低电平保持时间需大于100us;T4阶段Reset引脚转变为高电平,INT引脚保持低电平且保持时间大于5ms。
设置地址完全后INT引脚可设置为低电平一段时间再设置为输入模式,等待芯片触发中断,Reset引脚保持高电平,不再复位。
实时命令寄存器
该寄存器用于触发芯片执行指定的实时控制命令,其中需关注的是
bit2位:软件复位,触发芯片执行内部软件复位。
bit5位:关屏,让芯片进入低功耗关屏模式,停止触摸扫描。
配置信息寄存器
配置信息寄存器范围从0x8047到0x8100,其中需要关注的寄存器是:
0x8048~0x8049寄存器,分别保存X轴坐标输出最大值低8位和高8位。
0x804A~0x804B寄存器,分别保存Y轴坐标输出最大值低8位和高8位。
0x804C寄存器,其bit3~bit0位确定最大触摸点数量。
0x804D寄存器,其bit1~bit0位用于设置中断触发类型。
坐标信息寄存器
坐标信息寄存器范围从0x8140开始,GT911芯片到0x8177结束,GT928芯片从0x819F结束,因为GT928比GT911多5个触摸点数据寄存器,其中需要关注的寄存器是:
0x8140~0x8143寄存器:保存芯片的ID,如911、928。
0x8144~0x8145寄存器:保存芯片的固件版本号。
0x814E寄存器:bit7位确认缓冲区是否就绪,为1表示就绪才能读触摸点数据,bit3~bit0位确认此次需要处理的触摸点数量。
0x814F~0x8156寄存器:第一个触摸点数据,包括触摸点ID、X轴和Y轴坐标低8位和高8位,触摸大小的低8位和高8位,触摸点数据共8字节。
0x8157~0x815E寄存器:第二个触摸点数据,包括触摸点ID、X轴和Y轴坐标低8位和高8位,触摸大小的低8位和高8位,触摸点数据共8字节。
16.3.2. 设备树插件详解¶
本实验设备树插件 是不确定的 ,因为想要MIPI屏幕显示需要通过开启板卡对应的MIPI设备树插件,而MIPI设备树插件默认已经有触摸配置,如果不修改触摸配置就会直接匹配内核现有的触摸驱动,导致无法再匹配我们编写的触摸驱动, 因此, 需要在现有的MIPI设备树插件基础上进行修改 。
16.3.2.1. 确认板卡使用的MIPI设备树插件¶
板卡配套的快速使用手册的MIPI屏幕章节有对各板卡的MIPI设备树插件做详细说明:
以使用LubanCat2-V3板卡 + 野火新5.5寸MIPI屏幕(EBF410125V1R0)为例进行说明,该屏幕带EEPROM,支持自动加载通用MIPI设备树插件进行显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #查看配置文件可用MIPI屏幕插件
grep -E 'dsi|gsdt' /boot/uEnv/uEnv.txt
#以LubanCat2-V3板卡为例,4.19内核镜像信息打印如下
#dsi0-vp1
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-1080p-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-rpi-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-7.0-1024x600-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-10.1-800x1280-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-800x1280-10.1inch-s8001280b1060b-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dtbo
#dsi1-vp0
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-1080p-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-rpi-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-7.0-1024x600-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-10.1-800x1280-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-800x1280-10.1inch-s8001280b1060b-overlay.dtbo
#dtoverlay=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-generic-overlay.dtbo
enable_gsdt_auto_load=1
gsdt_plugin0=/dtb/overlay/rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dtbo
gsdt_plugin1=/dtb/overlay/rk3568-lubancat-2-dsi1-in-vp0-generic-overlay.dtbo
|
其中默认被注释的是专用MIPI屏幕插件,一个插件对应一个屏幕,而gsdt_plugin插件是通用MIPI屏幕插件,支持全部带EEPROM的屏幕,其中gsdt_plugin0对应dsi0接口,gsdt_plugin1对应dsi1接口。
因此,如果LubanCat2-V3板卡使用野火新5.5寸MIPI屏幕(EBF410125V1R0,带EEPROM)连接dsi0接口,那么实际使用的MIPI屏幕插件就是rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dtbo
从内核源码中找到对应的设备树插件源码如下:
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 | /* 省略MIPI配置...*/
fragment@6 {
target = <&i2c1>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
eeprom0: eeprom@51 {
compatible = "atmel,24c64";
reg = <0x51>;
label = "eeprom";
};
};
};
fragment@7 {
target = <>911_dsi0>;
__overlay__ {
status = "okay";
nvmem = <&eeprom0>;
nvmem-names = "eeprom";
};
};
};
|
可以确认LubanCat2-V3板卡dsi0接口使用的i2c为i2c1,默认触摸节点为gt911_dsi0。
16.3.2.2. 确认板卡DSI接口触摸相关引脚¶
查看板卡原理图,以LubanCat2-V3板卡dsi0接口为例:
可以确认,i2c的确为i2c1,中断INT引脚为GPIO0_B5,复位RST引脚为GPIO0_B6。
提示
如果对设备树比较熟悉也可以直接通过查看主设备树及其相关dtsi设备树找到默认触摸节点,确定相关引脚。
16.3.2.3. 修改默认MIPI屏幕插件适配本实验¶
以rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dts为例,修改后的设备树源码如下:
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 | /* 省略MIPI配置...*/
fragment@6 {
target = <&i2c1>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
eeprom0: eeprom@51 {
compatible = "atmel,24c64";
reg = <0x51>;
label = "eeprom";
};
gt911_test: gt911-test@5d { // 5.5寸和7寸MIPI屏幕触摸节点
status = "okay";
compatible = "fire,GT911";
reg = <0x5d>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PB5 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
irq-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <>9xx_pin>;
touchscreen-inverted-x = <1>;
touchscreen-inverted-y = <1>;
};
// gt928_test: gt928-test@5d { // 10.1寸MIPI屏幕触摸节点
// status = "okay";
// compatible = "fire,GT928";
// reg = <0x5d>;
// interrupt-parent = <&gpio0>;
// interrupts = <RK_PB5 IRQ_TYPE_EDGE_FALLING>;
// reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
// irq-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;
// pinctrl-names = "default";
// pinctrl-0 = <>9xx_pin>;
// touchscreen-inverted-x = <1>;
// touchscreen-swapped-x-y = <1>;
// };
};
};
fragment@7 {
target = <>911_dsi0>;
__overlay__ {
status = "disabled";
nvmem = <&eeprom0>;
nvmem-names = "eeprom";
};
};
fragment@8 {
target = <&pinctrl>;
__overlay__ {
goodix {
gt9xx_pin: gt9xx-pin {
rockchip,pins =
<0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>,
<0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
};
};
};
|
关键说明:
gt911_test: gt911-test@5d:触摸芯片从设备节点名称,@后0x5d为GT911的I2C通信7位从地址(上电时序T3、T4阶段INT引脚拉低的地址,0xBA是8位地址,右移一位得7位地址0x5D);
compatible = “fire,GT911”:驱动匹配的核心标识,需与I2C驱动中of_match_table的属性完全一致,否则驱动无法匹配设备;
interrupts 中断配置:绑定GPIO0_PB5为触摸中断引脚,配置为下降沿触发(屏厂默认触摸固件配置为了下降沿触发),与硬件中断输出匹配;
reset-gpios/irq-gpios:分别指定复位引脚(GPIO0_PB6,低电平有效)和中断引脚(GPIO0_PB5,空闲高电平),与引脚配置节点对应;
gt9xx_pin 引脚配置:将GPIO0_PB5、GPIO0_PB6配置为GPIO功能,开启上拉电阻,保证引脚电平稳定,避免干扰导致的触摸异常;
touchscreen-inverted-x/y:配置X/Y轴坐标翻转(1为翻转,0为不翻转),根据实际触摸屏幕安装方向调整,解决坐标颠倒问题;
gt911_dsi0节点disabled:关闭gt911_dsi0节点,避免默认匹配内核自带的触摸驱动,无法再匹配我们自己编写的驱动。
对于x、y轴翻转、轴交换配置,需在调试阶段根据触摸屏实际情况而配置,不是所有屏幕都需要翻转或交换,除了自行调试也可参考 野火MIPI屏幕EEPROM固件仓库 的配置进行设置。
如野火新5.5寸MIPI屏幕(EBF410125V1R0)对应的ebf410125v1_firmware.c中有以下定义:
1 2 3 4 5 | static struct touchscreen_properties prop = {
.invert_x = true,
.invert_y = true,
.swap_x_y = false,
};
|
invert_x和invert_y为true代表需设置touchscreen-inverted-x = <1>; 和 touchscreen-inverted-y = <1>;
swap_x_y为false代表需touchscreen-swapped-x-y = <0>; ,也可以省略。
16.3.3. 驱动代码详解¶
驱动基于I2C子系统、输入子系统、Regmap API开发,核心功能是GT911/GT928硬件初始化、输入设备注册、触摸数据读取、最终实现多点触摸事件上报。
核心定义与数据结构
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 | /* 触摸硬件参数宏定义 */
#define GOODIX_MAX_HEIGHT 4096 /* 触摸屏最大Y轴分辨率 */
#define GOODIX_MAX_WIDTH 4096 /* 触摸屏最大X轴分辨率 */
#define GOODIX_CONTACT_SIZE 8 /* 单个触摸点数据长度 */
#define GOODIX_MAX_CONTACTS 10 /* 驱动支持的最大触摸点数 */
/* 芯片配置数据长度宏定义 */
#define GOODIX_CONFIG_911_LENGTH 186 /* GT911/GT928的配置信息寄存器长度 */
/* 芯片寄存器地址宏定义 */
#define GOODIX_REG_COMMAND 0x8040 /* 命令控制寄存器地址 */
#define GOODIX_READ_COOR_ADDR 0x814E /* 状态位寄存器地址 */
#define GOODIX_GT9X_REG_CONFIG_DATA 0x8047 /* GT911/GT928配置信息寄存器开始地址 */
#define GOODIX_REG_ID 0x8140 /* GT911/GT928坐标信息寄存器开始地址 */
/* 触摸数据状态位宏定义 */
#define GOODIX_BUFFER_STATUS_READY BIT(7) /* 数据缓冲区就绪状态位 */
#define GOODIX_BUFFER_STATUS_TIMEOUT 20 /* 数据就绪等待超时时间 */
/* 配置数据偏移宏定义 */
#define RESOLUTION_LOC 1 /* 分辨率参数在配置区的偏移位置 */
#define MAX_CONTACTS_LOC 5 /* 最大触摸点数在配置区的偏移位置 */
#define TRIGGER_LOC 6 /* 中断触发类型在配置区的偏移位置 */
/* 中断触发类型数组 */
static const unsigned long goodix_irq_flags[] = {
IRQ_TYPE_EDGE_RISING, /* 索引0:上升沿触发 */
IRQ_TYPE_EDGE_FALLING, /* 索引1:下降沿触发 */
IRQ_TYPE_LEVEL_LOW, /* 索引2:低电平触发 */
IRQ_TYPE_LEVEL_HIGH /* 索引3:高电平触发 */
};
#define GOODIX_INT_TRIGGER 1 /* 默认中断触发类型索引,索引1下降沿触发 */
/* 芯片配置数据结构体 */
struct goodix_chip_data {
u16 config_addr; /* 配置信息寄存器的起始地址 */
int config_len; /* 配置信息寄存器的总长度 */
};
/* 触摸驱动核心数据结构体 */
struct goodix_ts_data {
struct i2c_client *client; /* I2C客户端设备结构体 */
struct regmap *regmap; /* Regmap操作句柄 */
struct input_dev *input_dev; /* 输入设备结构体 */
const struct goodix_chip_data *chip; /* 指向当前芯片的配置参数结构体 */
struct touchscreen_properties prop; /* 触摸屏物理属性结构体 */
unsigned int max_touch_num; /* 设备支持的最大触摸点数量 */
unsigned int int_trigger_type; /* 中断触发类型 */
struct gpio_desc *gpiod_int; /* 中断GPIO描述符 */
struct gpio_desc *gpiod_rst; /* 复位GPIO描述符 */
u16 id; /* 芯片型号ID */
u16 version; /* 芯片固件版本号 */
unsigned long irq_flags; /* 中断注册标志位 */
};
/* Regmap配置结构体 */
static const struct regmap_config goodix_regmap_config = {
.reg_bits = 16, /* 寄存器地址位宽:16位 */
.val_bits = 8, /* 数据位宽:8位 */
.write_flag_mask = 0, /* 写操作无额外标志位 */
.read_flag_mask = 0, /* 读操作无额外标志位 */
.fast_io = true, /* 开启快速I/O模式,满足触摸高实时性需求 */
};
/*
* GT911/GT928芯片专用配置参数
* 两款芯片寄存器地址、配置长度完全一致,共用该结构体
*/
static const struct goodix_chip_data gt911_chip_data = {
.config_addr = GOODIX_GT9X_REG_CONFIG_DATA, /* 配置信息寄存器起始地址 */
.config_len = GOODIX_CONFIG_911_LENGTH, /* 配置信息寄存器数据长度 */
};
|
关键说明:
第2-23行:触摸硬件参数宏定义,定义芯片最大分辨率、触摸点数、数据长度等基础参数;
第26-33行:中断触发类型数组,对应芯片配置的4种触发模式,与驱动默认配置对应;
第36-39行:芯片配置数据结构体,存储不同芯片的配置信息寄存器地址和数据长度;
第42-55行:触摸驱动核心数据结构体,统一管理所有设备资源和驱动状态;
第58-64行:Regmap配置结构体,适配GT911/GT928 16位寄存器地址、8位数据位宽;
第70-73行:GT911/GT928芯片专用配置参数,与芯片寄存器规格匹配。
芯片ID匹配函数
goodix_get_chip_data函数根据读取到的芯片ID,匹配对应的芯片配置参数,实现多芯片适配。
1 2 3 4 5 6 7 8 9 10 11 | /* 根据芯片ID获取对应配置参数 */
static const struct goodix_chip_data *goodix_get_chip_data(u16 id)
{
switch (id) {
case 911: /* 匹配GT911芯片ID */
case 928: /* 匹配GT928芯片ID */
return >911_chip_data; /* 返回共用配置参数 */
default: /* 不支持的芯片ID */
return NULL; /* 返回空指针标识不支持 */
}
}
|
关键说明:
根据芯片ID获取对应配置参数,支持GT911、GT928芯片复用,其余芯片ID默认不支持。
提示
实际上GT911、GT9271、GT9110、GT9111、GT927、GT928的配置信息寄存器起始地址和数据长度都是一样的,可使用同一驱动,本实验驱动并没有过多列出,而GT1151、GT5688等地址和长度不一致,则需单独在goodix_chip_data作区分。
GPIO配置与芯片复位函数
实现中断和复位GPIO初始化、芯片硬件复位,严格遵循GT911/GT928上电和设置通信地址时序,确保芯片正常启动。
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 | /* 设置芯片通讯地址后中断GPIO初始化为输入 */
static int goodix_int_sync(struct goodix_ts_data *ts)
{
/* 函数执行错误码 */
int error;
/* 设置中断GPIO为输出模式,输出低电平 */
error = gpiod_direction_output(ts->gpiod_int, 0);
if (error)
return error;
msleep(50);
/* 恢复中断GPIO为输入模式,等待芯片触发中断 */
error = gpiod_direction_input(ts->gpiod_int);
if (error)
return error;
return 0;
}
/* 芯片硬件复位,确认通信地址 */
static int goodix_reset(struct goodix_ts_data *ts)
{
/* 函数执行错误码 */
int error;
/* 开始I2C从地址选择:复位GPIO置低 */
error = gpiod_direction_output(ts->gpiod_rst, 0);
if (error)
return error;
msleep(20); /* 延时20ms,满足芯片时序要求T2(>10ms) */
/* 设置INT电平选择I2C地址: HIGH=0x14, LOW=0x5d */
error = gpiod_direction_output(ts->gpiod_int, ts->client->addr == 0x14);
if (error)
return error;
usleep_range(100, 2000); /* 延时100us-2ms,满足芯片时序要求T3(>100us) */
/* 释放复位:复位GPIO置高 */
error = gpiod_direction_output(ts->gpiod_rst, 1);
if (error)
return error;
usleep_range(6000, 10000); /* 延时6-10ms,满足芯片时序要求T4(>5ms) */
/* 复位GPIO恢复输入模式,结束I2C地址选择 */
error = gpiod_direction_input(ts->gpiod_rst);
if (error)
return error;
/* 执行中断GPIO初始化 */
error = goodix_int_sync(ts);
if (error)
return error;
return 0;
}
/* 从设备树获取GPIO配置 */
static int goodix_get_gpio_config(struct goodix_ts_data *ts)
{
int error; /* 函数执行错误码 */
struct device *dev; /* 设备结构体指针 */
struct gpio_desc *gpiod; /* GPIO描述符临时指针 */
if (!ts->client) /* I2C客户端为空则参数错误 */
return -EINVAL;
dev = &ts->client->dev; /* 获取设备结构体 */
/* 获取中断GPIO */
gpiod = devm_gpiod_get(dev, "irq", GPIOD_IN);
if (IS_ERR(gpiod)) {
error = PTR_ERR(gpiod);
if (error != -EPROBE_DEFER) {
dev_err(dev, "Failed to get %s GPIO: %d\n",
"irq", error);
}
return error;
}
ts->gpiod_int = gpiod; /* 保存中断GPIO描述符 */
/* 获取复位GPIO */
gpiod = devm_gpiod_get(dev, "reset", GPIOD_IN);
if (IS_ERR(gpiod)) {
error = PTR_ERR(gpiod);
if (error != -EPROBE_DEFER) {
dev_err(dev, "Failed to get %s GPIO: %d\n",
"reset", error);
}
return error;
}
ts->gpiod_rst = gpiod; /* 保存复位GPIO描述符 */
return 0;
}
|
关键说明:
从设备树获取GPIO配置:
第63行:获取中断GPIO(设备树中irq-gpios配置),输入模式。
第75行:获取复位GPIO(设备树中reset-gpios配置),输入模式。
需注意 ,gpiod系列获取GPIO的API会自动拼接“-gpios”后缀完成属性匹配,因此调用时只需传入属性前缀即可,无需写完整属性名。
芯片硬件复位,确认通信地址:
第26行:拉低复位GPIO,开始I2C地址选择。
第29行:延时20ms,满足芯片时序要求T2>10ms。
第32行:设置INT引脚电平选择I2C地址,和设备树节点配置的地址比较,控制INT引脚电平从而设置7位地址,当设备树节点为0x5d时,电平为低电平,从而设置地址0x5d(0x5d为0xBA右移一位),本实验用0x5d。
第35行:延时100us-2ms,满足时序T3>100us。
第38行:拉高复位GPIO,释放复位,开始T4阶段。
第41行:延时6-10ms,满足时序T4>5ms。
第44行:复位GPIO恢复输入模式,结束I2C地址选择。
设置芯片通讯地址后中断GPIO初始化为输入:
第8行:设置中断GPIO为输出模式,输出低电平,初始化引脚状态。
第11行:延时50ms,确保引脚电平稳定。
第13行:恢复中断GPIO为输入模式,等待芯片触发中断。
I2C通信连通性测试
测试I2C通信是否正常,确认芯片正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* I2C通信连通性测试 */
static int goodix_i2c_test(struct goodix_ts_data *ts)
{
int retry = 0; /* 重试次数计数器 */
int error; /* 函数执行错误码 */
unsigned int test; /* 测试读取的寄存器值 */
/* 最多重试2次I2C读取 */
while (retry++ < 2) {
/* 读取0x8140芯片ID寄存器,测试通信 */
error = regmap_read(ts->regmap, GOODIX_REG_ID, &test);
if (!error)
return 0;
/* 读取失败则打印错误并延时重试 */
dev_err(&ts->client->dev, "I2C test failed attempt %d: %d\n",
retry, error);
msleep(20); /* 延时20ms后重试 */
}
return error;
}
|
关键说明:
第9行:最多重试2次I2C读取,避免单次通信失败误判。
第11行:读取芯片ID寄存器(0x8140),测试I2C通信。
芯片ID和固件版本读取
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 | /* 读取芯片ID与固件版本信息 */
static int goodix_read_version(struct goodix_ts_data *ts)
{
int error; /* 函数执行错误码 */
u8 buf[6]; /* 版本数据缓冲区 */
char id_str[5]; /* 芯片ID字符串缓冲区,4字节ID + 结束符 */
/* 读取ID与版本寄存器数据,0x8140~0x8145 */
error = regmap_bulk_read(ts->regmap, GOODIX_REG_ID, buf, sizeof(buf));
if (error) {
dev_err(&ts->client->dev, "Read version failed: %d\n", error);
return error;
}
/* 复制前4字节为ID字符串,0x8140~0x8143 */
memcpy(id_str, buf, 4);
id_str[4] = 0; /* 添加字符串结束符 */
/* 将ID字符串转换为16位无符号整型 */
if (kstrtou16(id_str, 10, &ts->id)) {
ts->id = 0x1001; /* 转换失败则使用默认ID */
}
/* 小端模式读取固件版本号,0x8144~0x8145 */
ts->version = get_unaligned_le16(&buf[4]);
/* 打印芯片ID与版本信息 */
dev_info(&ts->client->dev, "ID %d, version: %04x\n", ts->id, ts->version);
return 0;
}
|
关键说明:
第9行:读取芯片ID与版本寄存器(0x8140~0x8145),共6字节数据。
第16-17行:复制前4字节为ID字符串(芯片ID存储在0x8140~0x8143)。
第20行:将ID字符串转换为16位无符号整型,存储到私有数据。
第25行:小端模式读取固件版本号(存储在0x8144~0x8145)。
第28行:打印芯片ID与版本信息,用于调试验证。
触摸数据读取函数
批量读取触摸坐标数据,判断数据就绪状态,提取有效触摸点数量,读取全部有效触摸点数据。
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 | /* 读取触摸输入报告数据 */
static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data)
{
unsigned long max_timeout; /* 数据等待超时的jiffies值 */
int touch_num; /* 有效触摸点数量 */
int error; /* 函数执行错误码 */
/* 计算超时时间:当前jiffies + 20ms转换的jiffies值 */
max_timeout = jiffies + msecs_to_jiffies(GOODIX_BUFFER_STATUS_TIMEOUT);
do {
/* 通过regmap批量读取触摸坐标数据,状态位寄存器+第1个触摸点数据,0x814E~0x8156 */
error = regmap_bulk_read(ts->regmap, GOODIX_READ_COOR_ADDR,
data, GOODIX_CONTACT_SIZE + 1);
if (error) {
dev_err(&ts->client->dev, "Regmap read error: %d\n", error);
return error;
}
/* 判断数据缓冲区是否就绪,0x814E第7位为1表示就绪 */
if (data[0] & GOODIX_BUFFER_STATUS_READY) {
touch_num = data[0] & 0x0f; /* 提取低4位获取触摸点数量 */
if (touch_num > ts->max_touch_num) /* 触摸点数量超阈值则协议错误 */
return -EPROTO;
/* 触摸点数量大于1时,读取剩余触摸点数据 */
if (touch_num > 1) {
data += 1 + GOODIX_CONTACT_SIZE; /* 缓冲区指针偏移,跳过已经存入的状态位+第1个触摸点数据 */
error = regmap_bulk_read(ts->regmap,
GOODIX_READ_COOR_ADDR + 1 + GOODIX_CONTACT_SIZE, /* 地址偏移状态位寄存器+第1个触摸点数据,从第二个触摸点开始读 */
data, GOODIX_CONTACT_SIZE * (touch_num - 1)); /* 读取长度为一个触摸点长度 * 剩余的触摸点数 */
if (error)
return error;
}
return touch_num; /* 返回有效触摸点数量 */
}
usleep_range(1000, 2000); /* 未就绪则休眠1-2ms后重试 */
} while (time_before(jiffies, max_timeout)); /* 未超时则继续轮询 */
/* 超时未读到数据,返回0个触摸点 */
return 0;
}
|
关键说明:
第9行:计算超时时间(当前jiffies + 20ms转换的jiffies值),避免无限等待。
第12-13行:批量读取触摸坐标数据(状态位寄存器+第1个触摸点数据,共9字节)。
第19行:判断数据缓冲区是否就绪(第0字节的状态位寄存器第7位为1表示就绪)。
第24行:触摸点数量>1时,读取剩余触摸点数据。
第25行:缓冲区指针偏移,跳过已读取的状态位寄存器 + 第1个触摸点数据。
第26-28行:读取剩余触摸点数据,地址偏移对应位置。
第32行:返回有效触摸点数量。
第38行:超时未读到数据,返回0个触摸点。
单个触摸点事件上报函数
解析单个触摸点的坐标、ID、触摸大小,上报到输入子系统,支持坐标翻转。
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 void goodix_ts_report_touch(struct goodix_ts_data *ts, u8 *coor_data)
{
/* 提取触摸点ID(低4位) */
int id = coor_data[0] & 0x0F;
/* 小端模式读取16位X轴坐标 */
int input_x = get_unaligned_le16(&coor_data[1]);
/* 小端模式读取16位Y轴坐标 */
int input_y = get_unaligned_le16(&coor_data[3]);
/* 小端模式读取16位触摸大小 */
int input_w = get_unaligned_le16(&coor_data[5]);
/* 绑定当前触摸点到对应MT槽位 */
input_mt_slot(ts->input_dev, id);
/* 上报槽位状态:当前为手指触摸,状态有效 */
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
/* 按触摸屏属性上报坐标,支持方向翻转 */
touchscreen_report_pos(ts->input_dev, &ts->prop,
input_x, input_y, true);
/* 上报触摸主尺寸 */
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
/* 上报触摸宽度 */
input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, input_w);
}
|
关键说明:
第5行:提取触摸点ID(第0字节低4位),用于区分不同触摸点。
第7行:小端模式读取16位X轴坐标(第1-2字节)。
第9行:小端模式读取16位Y轴坐标(第3-4字节)。
第11行:小端模式读取16位触摸大小(第5-6字节)。
第14行:绑定当前触摸点到对应MT槽位,支持多点触摸区分。
第16行:上报槽位状态,标记当前为手指触摸,状态有效。
第18-19行:按触摸屏属性上报坐标,自动适配X/Y轴翻转配置。
第21行:上报触摸主尺寸。
第23行:上报触摸宽度,与主尺寸一致,提升兼容性。
触摸事件处理函数
中断触发后,调用该函数读取所有触摸点数据,批量上报事件,完成一帧触摸数据的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* 处理所有触摸事件:读取数据并批量上报 */
static void goodix_process_events(struct goodix_ts_data *ts)
{
/* 定义触摸数据缓冲区:状态位寄存器+最大触摸点*单触摸点长度 */
u8 point_data[1 + GOODIX_CONTACT_SIZE * GOODIX_MAX_CONTACTS];
int touch_num; /* 有效触摸点数量 */
int i; /* 循环遍历变量 */
/* 读取所有触摸点数据 */
touch_num = goodix_ts_read_input_report(ts, point_data);
if (touch_num < 0)
return;
/* 循环上报所有有效触摸点事件 */
for (i = 0; i < touch_num; i++) {
goodix_ts_report_touch(ts, &point_data[1 + GOODIX_CONTACT_SIZE * i]);
}
/* 同步MT帧,标记一帧触摸数据上报完成 */
input_mt_sync_frame(ts->input_dev);
/* 发送同步事件,通知系统处理本次触摸数据 */
input_sync(ts->input_dev);
}
|
关键说明:
第5行:定义触摸数据缓冲区(状态位寄存器 + 最大触摸点数 * 一个触摸点数据,共81字节)。
第10行:读取所有触摸点数据,获取有效触摸点数量。
第15-17行:循环上报所有有效触摸点事件。
第19行:同步MT帧,标记一帧触摸数据上报完成。
第21行:发送同步事件,通知系统处理本次触摸数据。
中断服务函数
触摸中断触发后,内核调用该函数,触发触摸事件处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* 触摸中断处理函数 */
static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
{
/* 转换私有数据为驱动核心结构体 */
struct goodix_ts_data *ts = dev_id;
/* 调用事件处理函数,读取并上报触摸数据 */
goodix_process_events(ts);
/* 向0x814E状态位寄存器写入0,清除缓冲区状态标志和触摸点数量等 */
if (regmap_write(ts->regmap, GOODIX_READ_COOR_ADDR, 0) < 0) {
dev_err(&ts->client->dev, "Regmap write end_cmd error\n");
}
return IRQ_HANDLED; /* 告知内核中断已处理 */
}
|
关键说明:
第8行:调用事件处理函数,读取并上报触摸数据。
第10-12行:清除缓冲区状态标志,避免重复触发中断。
第13行:返回IRQ_HANDLED,告知内核中断已处理。
中断申请与释放函数
申请线程化中断,释放中断资源,确保中断配置正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* 释放触摸中断资源 */
static void goodix_free_irq(struct goodix_ts_data *ts)
{
/* 释放设备树管理的中断资源 */
devm_free_irq(&ts->client->dev, ts->client->irq, ts);
}
/* 申请触摸中断资源 */
static int goodix_request_irq(struct goodix_ts_data *ts)
{
/* 申请线程化中断:无顶半部分,底半部分为中断处理函数 */
return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
NULL, goodix_ts_irq_handler,
ts->irq_flags, ts->client->name, ts);
}
|
关键说明:
第5行:释放设备树管理的中断资源,避免资源泄漏。
第12-14行:申请线程化中断,底半部分为中断处理函数。
芯片配置读取函数
goodix_read_config函数读取芯片内置配置寄存器数据,提取中断触发类型、最大触摸点数、分辨率等核心参数,适配硬件实际配置。
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 | /* 读取芯片内部配置参数 */
static void goodix_read_config(struct goodix_ts_data *ts)
{
u8 config[GOODIX_CONFIG_911_LENGTH]; /* 配置数据缓冲区 */
int x_max, y_max; /* X/Y轴最大分辨率 */
int error; /* 函数执行错误码 */
/* 批量读取芯片配置数据,此处读取0x8047~0x8100,配置信息寄存器全部读取完 */
error = regmap_bulk_read(ts->regmap, ts->chip->config_addr,
config, ts->chip->config_len);
if (error) { /* 读取失败则使用默认参数 */
dev_warn(&ts->client->dev, "Error reading config: %d\n",
error);
ts->int_trigger_type = GOODIX_INT_TRIGGER; /* 默认中断触发类型 */
ts->max_touch_num = GOODIX_MAX_CONTACTS; /* 默认最大触摸点 */
return;
}
/* 从配置区提取中断触发类型,0x804D低2位 */
ts->int_trigger_type = config[TRIGGER_LOC] & 0x03;
/* 从配置区提取最大触摸点数量,0x804C低4位 */
ts->max_touch_num = config[MAX_CONTACTS_LOC] & 0x0f;
/* 小端模式读取X轴坐标输出最大值,0x8048~0x8049 */
x_max = get_unaligned_le16(&config[RESOLUTION_LOC]);
/* 小端模式读取Y轴坐标输出最大值,0x804A~0x804B */
y_max = get_unaligned_le16(&config[RESOLUTION_LOC + 2]);
if (x_max && y_max) { /* 坐标输出最大值有效则设置输入子系统坐标范围 */
input_abs_set_max(ts->input_dev, ABS_MT_POSITION_X, x_max - 1);
input_abs_set_max(ts->input_dev, ABS_MT_POSITION_Y, y_max - 1);
}
}
|
关键说明:
第9行:批量读取芯片配置数据,将配置信息寄存器全部读取完。
第19行:0x804D寄存器低2位保存INT触发方式,0x804D寄存器是配置信息寄存器第7个寄存器,所以从config[TRIGGER_LOC]=config[6]中获取。
第21行:0x804C寄存器低4位保存最大触摸点数量,0x804C寄存器是配置信息寄存器第6个寄存器,所以从config[MAX_CONTACTS_LOC]=config[5]中获取。
第23行:0x8048~0x8049寄存器保存X轴坐标输出最大值,0x8048寄存器是配置信息寄存器第2个寄存器,所以从config[RESOLUTION_LOC]=config[1]中获取,读取2字节。
第25行:0x804A~0x804B寄存器保存Y轴坐标输出最大值,0x804A寄存器是配置信息寄存器第4个寄存器,所以从config[RESOLUTION_LOC+2]=config[3]中获取,读取2字节。
第27行:x_max-1,因为坐标范围为0~x_max-1,与芯片输出的坐标值范围保持一致,避免坐标上报错误。
输入设备配置函数
goodix_configure_dev函数配置输入设备属性、注册输入设备、初始化多点触摸槽位,适配输入子系统要求。
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 | /* 配置输入设备与中断参数 */
static int goodix_configure_dev(struct goodix_ts_data *ts)
{
/* 函数执行错误码 */
int error;
/* 初始化默认参数 */
ts->int_trigger_type = GOODIX_INT_TRIGGER; /* 触发模式索引 */
ts->max_touch_num = GOODIX_MAX_CONTACTS; /* 默认最大触摸点 */
/* 分配设备树管理的输入设备结构体 */
ts->input_dev = devm_input_allocate_device(&ts->client->dev);
if (!ts->input_dev) {
dev_err(&ts->client->dev, "Failed to allocate input device.");
return -ENOMEM;
}
/* 设置输入设备基本信息 */
ts->input_dev->name = "Goodix Capacitive TouchScreen"; /* 设备名称 */
ts->input_dev->phys = "input/ts"; /* 设备物理描述 */
ts->input_dev->id.bustype = BUS_I2C; /* 总线类型:I2C */
ts->input_dev->id.vendor = 0x0416; /* 厂商ID */
ts->input_dev->id.product = ts->id; /* 芯片ID */
ts->input_dev->id.version = ts->version; /* 设备版本 */
/* 设置输入设备支持的事件:多点触摸X/Y坐标 */
input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X);
input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y);
/* 设置触摸宽度绝对参数范围 */
input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
/* 设置触摸压力绝对参数范围 */
input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
/* 读取芯片硬件配置参数 */
goodix_read_config(ts);
/* 解析触摸屏物理属性,方向、翻转等 */
touchscreen_parse_properties(ts->input_dev, true, &ts->prop);
/* 配置无效则使用默认分辨率 */
if (!ts->prop.max_x || !ts->prop.max_y || !ts->max_touch_num) {
dev_err(&ts->client->dev, "Invalid config, using defaults\n");
ts->prop.max_x = GOODIX_MAX_WIDTH - 1; /* 默认X轴最大坐标 */
ts->prop.max_y = GOODIX_MAX_HEIGHT - 1; /* 默认Y轴最大坐标 */
/* 设置输入子系统坐标范围 */
input_abs_set_max(ts->input_dev, ABS_MT_POSITION_X, ts->prop.max_x);
input_abs_set_max(ts->input_dev, ABS_MT_POSITION_Y, ts->prop.max_y);
}
/* 初始化多点触摸槽位,支持直接上报与丢弃未使用槽位 */
error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,
INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
if (error) {
dev_err(&ts->client->dev, "Failed to initialize MT slots: %d", error);
return error;
}
/* 注册输入设备到系统输入子系统 */
error = input_register_device(ts->input_dev);
if (error) {
dev_err(&ts->client->dev, "Failed to register input device: %d", error);
return error;
}
/* 组合中断标志:触发类型 + 单次触发模式 */
ts->irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT;
/* 申请中断资源 */
error = goodix_request_irq(ts);
if (error) {
dev_err(&ts->client->dev, "Request IRQ failed: %d\n", error);
return error;
}
return 0;
}
|
关键说明:
第8-9行:初始化默认参数(中断触发类型、最大触摸点数)。
第12行:分配设备树管理的输入设备结构体。
第19-24行:设置输入设备基本信息,用于系统识别。
第27-28行:设置输入设备支持的事件(多点触摸X/Y坐标)。
第30-32行:设置触摸宽度和触摸压力绝对参数范围(0-255)。
第35行:读取芯片硬件配置参数(分辨率、触摸点数等)。
第37行:解析触摸屏物理属性(自动解析设备树配置的touchscreen-inverted-x/y配置)。
第40-57行:配置无效则使用默认分辨率,避免设备异常。
第50行:初始化多点触摸槽位,支持直接上报与丢弃未使用槽位。
第58行:注册输入设备到系统输入子系统,完成设备注册。
第65行:组合中断标志(触发类型 + 单次触发模式)。
第68行:申请中断资源,完成中断配置。
probe函数
设备树与驱动compatible匹配成功后自动执行probe函数,完成驱动初始化、资源分配、设备注册,最后注册驱动到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 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 | /* I2C驱动探测函数 */
static int goodix_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 驱动核心数据结构体指针 */
struct goodix_ts_data *ts;
/* 函数执行错误码 */
int error;
/* 分配设备树管理的核心数据内存 */
ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
/* 绑定I2C客户端设备 */
ts->client = client;
/* 设置I2C客户端私有数据 */
i2c_set_clientdata(client, ts);
/* 初始化I2C设备的Regmap句柄 */
ts->regmap = devm_regmap_init_i2c(client, &goodix_regmap_config);
if (IS_ERR(ts->regmap)) {
dev_err(&client->dev, "Regmap init failed: %ld\n", PTR_ERR(ts->regmap));
return PTR_ERR(ts->regmap);
}
/* 获取中断、复位引脚 */
error = goodix_get_gpio_config(ts);
if (error)
return error;
/* 存在GPIO引脚则执行芯片硬件复位并设置通信地址 */
if (ts->gpiod_int && ts->gpiod_rst) {
error = goodix_reset(ts);
if (error) {
dev_err(&client->dev, "Controller reset failed.\n");
return error;
}
}
/* 测试I2C通信是否正常 */
error = goodix_i2c_test(ts);
if (error) {
dev_err(&client->dev, "I2C communication failure: %d\n", error);
return error;
}
/* 读取芯片ID与版本信息 */
error = goodix_read_version(ts);
if (error) {
dev_err(&client->dev, "Read version failed.\n");
return error;
}
/* 根据芯片ID获取对应配置参数 */
ts->chip = goodix_get_chip_data(ts->id);
if (!ts->chip) {
dev_err(&client->dev, "Unsupported chip ID: %d\n", ts->id);
return -ENODEV;
}
/* 配置输入设备与中断 */
error = goodix_configure_dev(ts);
if (error)
return error;
return 0;
}
|
关键说明:
第11行:分配设备树管理的核心数据内存,初始化清零。
第16行:绑定I2C客户端设备。
第18行:设置I2C客户端私有数据。
第21行:初始化I2C设备的Regmap句柄,用于寄存器读写。
第27行:获取中断、复位引脚配置,从设备树读取GPIO信息。
第33-34行:存在GPIO引脚则执行芯片硬件复位,配置I2C地址。
第42行:测试I2C通信是否正常,确保总线连通。
第49行:读取芯片ID与版本信息,确认芯片型号。
第55行:根据芯片ID获取对应配置参数,不支持则返回错误。
第63行:配置输入设备与中断,完成驱动初始化。
remove函数
驱动卸载时执行remove函数,释放资源。
1 2 3 4 5 6 7 8 9 10 11 12 | /* I2C驱动移除函数 */
static int goodix_ts_remove(struct i2c_client *client)
{
/* 获取I2C客户端私有数据 */
struct goodix_ts_data *ts = i2c_get_clientdata(client);
/* 存在GPIO引脚则释放中断资源 */
if (ts->gpiod_int && ts->gpiod_rst) {
goodix_free_irq(ts);
}
return 0;
}
|
关键说明:
第8-10行:存在GPIO引脚则释放中断资源,避免资源泄漏。
驱动注册
定义设备树匹配表,注册驱动到I2C总线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* 定义设备树匹配表 */
static const struct of_device_id goodix_of_match[] = {
{ .compatible = "fire,GT911" },
{ .compatible = "fire,GT928" },
{ }
};
/* 声明设备树匹配表,供内核自动匹配设备 */
MODULE_DEVICE_TABLE(of, goodix_of_match);
/* I2C驱动结构体定义,注册驱动到I2C总线 */
static struct i2c_driver goodix_ts_driver = {
.probe = goodix_ts_probe,
.remove = goodix_ts_remove,
.driver = {
.name = "goodix",
.of_match_table = of_match_ptr(goodix_of_match),
},
};
/* 简化模块入口/出口:注册/注销I2C驱动 */
module_i2c_driver(goodix_ts_driver);
|
16.3.4. Makefile说明¶
本节实验使用的Makefile如下所示,编写该Makefile时,只需要根据实际情况修改变量KERNEL_DIR、obj-m和test_app即可。
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 | #指定内核路径,可以是相对路径或绝对路径
KERNEL_DIR=../../kernel/
# KERNEL_DIR=/home/guest/LubanCat_Linux_Generic_Full_SDK/kernel-6.1
# KERNEL_DIR=/home/guest/LubanCat_Linux_rk3588_SDK/kernel
#指定目标架构为arm64
ARCH=arm64
#指定交叉编译工具链的前缀
CROSS_COMPILE=aarch64-linux-gnu-
# CROSS_COMPILE=/home/guest/LubanCat_Linux_Generic_Full_SDK/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
#导出为环境变量
export ARCH CROSS_COMPILE
#指定要编译的内核模块目标文件
obj-m := touch_goodix.o
#all :默认目标,执行时会编译驱动模块
#$(MAKE) :调用make工具
#-C $(KERNEL_DIR) :指定的内核源码目录
#M=$(CURDIR) :模块的源码位于当前目录
#modules :编译模块
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
.PHONE:clean
#清理编译生成的文件
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
|
16.3.5. 编译设备树和驱动¶
16.3.5.1. 编译设备树¶
修改内核自带的MIPI设备树插件后,在内核源码顶层目录执行以下命令编译设备树插件:
1 2 3 | #这里以rk356x系列4.19.232内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs
|
提示
其余系列板卡参考 使用内核的构建脚本编译设备树插件 章节进行编译。
16.3.5.2. 加载设备树¶
编译出来的设备树插件位于 内核源码/arch/arm64/boot/dts/rockchip/overlay/ 目录,与MIPI设备树插件同名,但后缀为dtbo。
将设备树插件先传到板卡,再拷贝到板卡的 /boot/dtb/overlay/ 目录下。
1 2 3 4 | #先传输到板卡
#再拷贝到板卡的/boot/dtb/overlay/目录下,以LubanCat2的通用MIPI设备树插件为例
sudo cp -f rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dtbo /boot/dtb/overlay/
|
默认通用MIPI设备树插件会在uboot启动过程中识别到屏幕的EEPROM地址自动进行加载,不需要再手动配置。如果使用专用MIPI设备树插件需删除 /boot/uEnv/uEnv.txt 专用MIPI设备树插件前面的#实现加载,并且将enable_gsdt_auto_load的值改为0,防止自动加载通用MIPI设备树插件。
如果使用专用MIPI设备树插件参考下图:
16.3.5.3. 编译驱动¶
在实验目录下输入 make 即可编译驱动,编译得到内核模块touch_goodix.ko。
16.3.6. 屏幕接线说明¶
请自行参考板卡对应的快速使用手册进行连接,避免接错接口。
16.3.7. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
16.3.7.1. 实验操作¶
使用以下命令加载驱动,加载驱动前需确保MIPI屏幕已经连接到板卡:
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 | #先确认设备地址存在,使用i2cdetect命令,其中1表示是i2c1,需要根据实际使用的i2c修改
sudo i2cdetect -a -y 1
#信息打印如下,可以看到14地址或5d地址
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- 14 -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- UU -- -- -- -- -- -- -- 59 -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#加载驱动
sudo insmod touch_goodix.ko
#信息输出如下
[ 337.426992] goodix 1-005d: ID 911, version: 1060
[ 337.446017] input: Goodix Capacitive TouchScreen as /devices/platform/fe5a0000.i2c/i2c-1/1-005d/input/input3
#再次查看设备地址
sudo i2cdetect -a -y 1
#信息打印如下,14地址消失,5d地址有设备且被驱动使用显示UU
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- UU -- -- -- -- -- -- -- 59 -- -- -- UU -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
可以从驱动加载信息打印看到,当前芯片的ID为911,固件版本为1060,系统将触摸注册到了input3;从i2cdetect打印信息可以看到,原先的0x14地址(0x28右移1位的7位地址)消失,被初始化为了0x5d,和驱动设置的通信地址一致。
可以通过evtest命令监控触摸事件上报:
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 | #监控系统事件上报
sudo evtest
#信息打印如下,其中event3就是新注册触摸事件,名字和驱动设置的一致
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: fdd70030.pwm
/dev/input/event1: rk805 pwrkey
/dev/input/event2: rk-headset
/dev/input/event3: Goodix Capacitive TouchScreen
Select the device event number [0-3]:
#输入3选择event3,打印信息如下
Select the device event number [0-3]: 3
Input driver version is 1.0.1
Input device ID: bus 0x18 vendor 0x416 product 0x38f version 0x1060 //I2C总线类型为0x18,vendor 0x416:汇顶官方厂商ID
Input device name: "Goodix Capacitive TouchScreen" //设备名称
Supported events:
Event type 0 (EV_SYN) //事件同步分隔符,内核用来打包上报事件
Event type 1 (EV_KEY) //按键事件
Event code 330 (BTN_TOUCH) //触摸按下/抬起标志,1=按下,0=抬起
Event type 3 (EV_ABS) //绝对坐标事件
Event code 0 (ABS_X) //单点兼容触摸坐标X
Value 0
Min 0
Max 1079 //5.5寸屏幕分辨率1080x1920,X轴0~1079共1080点
Event code 1 (ABS_Y) //单点兼容触摸坐标Y
Value 0
Min 0
Max 1919 //5.5寸屏幕分辨率1080x1920,Y轴0~1919共1920点
Event code 47 (ABS_MT_SLOT) //多点触控(MT)支持
Value 0
Min 0
Max 9 //实际硬件只支持5点触控
Event code 48 (ABS_MT_TOUCH_MAJOR) //触摸压力绝对参数范围
Value 0
Min 0
Max 255
Event code 50 (ABS_MT_WIDTH_MAJOR) //触摸宽度绝对参数范围
Value 0
Min 0
Max 255
Event code 53 (ABS_MT_POSITION_X) //多点触摸坐标X
Value 0
Min 0
Max 1079
Event code 54 (ABS_MT_POSITION_Y) //多点触摸坐标Y
Value 0
Min 0
Max 1919
Event code 57 (ABS_MT_TRACKING_ID) //触摸点唯一ID
Value 0
Min 0
Max 65535
Properties:
Property type 1 (INPUT_PROP_DIRECT) //代表这是直接触摸屏幕,不是触摸板
Testing ... (interrupt to exit)
#点击一次屏幕的打印信息
Event: time 1779068675.604767, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 0 //标记第1个触摸点,ID=0
Event: time 1779068675.604767, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 420 //多点触摸坐标:X=420,Y=723
Event: time 1779068675.604767, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 723
Event: time 1779068675.604767, type 3 (EV_ABS), code 48 (ABS_MT_TOUCH_MAJOR), value 36 //触摸压力36
Event: time 1779068675.604767, type 3 (EV_ABS), code 50 (ABS_MT_WIDTH_MAJOR), value 36 //触摸宽度36
Event: time 1779068675.604767, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1 //触摸按下,有效触摸
Event: time 1779068675.604767, type 3 (EV_ABS), code 0 (ABS_X), value 420 //单点兼容坐标X=420
Event: time 1779068675.604767, type 3 (EV_ABS), code 1 (ABS_Y), value 723 //单点兼容坐标Y=723
Event: time 1779068675.604767, -------------- SYN_REPORT ------------ //内核同步,一帧触摸数据上报完成
Event: time 1779068675.689611, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value -1 //释放触摸点,ID=-1 表示无效/抬起
Event: time 1779068675.689611, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 0 //触摸抬起,触摸结束
Event: time 1779068675.689611, -------------- SYN_REPORT ------------ //同步完成
|
除了evtest也可以使用ts_test测试触摸:
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 | #更新apt数据
sudo apt update
#安装libts-bin
sudo apt install libts-bin
#如果是带桌面的镜像需关闭桌面并重启
sudo systemctl set-default multi-user.target
sudo reboot
###如需恢复桌面,请后续测试完再执行以下命令恢复
sudo systemctl set-default graphical.target
sudo reboot
#加载驱动
sudo insmod touch_goodix.ko
#信息输出如下,注册事件为event3
[ 337.426992] goodix 1-005d: ID 911, version: 1060
[ 337.446017] input: Goodix Capacitive TouchScreen as /devices/platform/fe5a0000.i2c/i2c-1/1-005d/input/input3
#环境变量指定触摸设备事件为event3,根据实际事件编号而定
export TSLIB_TSDEVICE=/dev/input/event3
#执行测试
sudo ts_test
|
ts_test运行后屏幕会出现测试界面,默认是触摸跟随(第一个Dray按键对应的功能),滑动屏幕默认有光标跟随,可自行测试。
点击界面中间的“Draw”按键切换为画线模式,可在屏幕画线,效果如下:
提示
如果触摸点出现轴对称或者翻转,需确认设备树的touchscreen-inverted-x/y、touchscreen-swapped-x-y配置。
16.3.8. 实验注意事项¶
硬件接线:接线前务必断电,严格按照快速使用手册连接板卡和屏幕,避免带电连接或者接错接口烧毁屏幕。
设备树配置:设备树插件的compatible属性必须与驱动代码中of_device_id匹配表完全一致,大小写、字符均不可偏差;中断触发类型需与驱动默认配置、硬件实际输出极性匹配,坐标翻转配置(touchscreen-inverted-x/y)需根据屏幕安装方向调整,否则会出现触摸无响应、坐标错乱。
芯片复位时序:驱动中的复位函数(goodix_reset)严格遵循GT911/GT928复位和通信地址设置时序,不可随意修改延时参数,否则芯片无法正常初始化,导致触摸无响应。
实验操作:实验过程中避免触碰芯片引脚和板卡接口,防止静电或接触不良导致的硬件故障。