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)用于存储触摸屏的物理属性(分辨率、坐标方向、翻转状态等),由驱动初始化,供触摸屏核心层使用,统一坐标上报格式,实现坐标校准和方向适配。

touchscreen_properties结构体(内核源码/include/linux/input/touchscreen.h)
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管理多个触摸点,实现触摸点的跟踪和识别。

input_mt_slot结构体(内核源码/include/linux/input/mt.h)
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)管理所有多点触摸槽位,存储多点触摸的全局状态,由多点触摸核心层自动维护,驱动无需手动初始化。

input_mt结构体(内核源码/include/linux/input/mt.h)
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

重要

寄存器说明在芯片数据手册中有十分详细的说明。

GT928的寄存器相较GT911地址在寄存器地址末尾多了连续的5个触摸点寄存器,其他部分和GT911是一致的,以下以GT911进行说明。

截取手册关键说明并结合本实验情况整理得:

上电时序及通信地址设置

../_images/subsystem_touch_driver_0.jpg

GT911从电源上电到复位完成包括4个阶段:

  • 第1阶段:电源上电

必须先给AVDD(模拟电源)上电,稳定后,VDDIO(IO电源)必须在T1 < 100ms内完成上电。

  • 第2阶段:电源稳定等待

VDDIO上电完成后,需等待T2 > 10ms,让电源纹波完全稳定,再操作Reset和INT引脚,避免误复位。

  • 第3、4阶段:I2C通信地址设置

../_images/subsystem_touch_driver_1.jpg

如果需要设定地址为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引脚保持高电平,不再复位。

实时命令寄存器

../_images/subsystem_touch_driver_2.jpg

该寄存器用于触发芯片执行指定的实时控制命令,其中需关注的是

  • bit2位:软件复位,触发芯片执行内部软件复位。

  • bit5位:关屏,让芯片进入低功耗关屏模式,停止触摸扫描。

配置信息寄存器

../_images/subsystem_touch_driver_3.jpg

配置信息寄存器范围从0x8047到0x8100,其中需要关注的寄存器是:

  • 0x8048~0x8049寄存器,分别保存X轴坐标输出最大值低8位和高8位。

  • 0x804A~0x804B寄存器,分别保存Y轴坐标输出最大值低8位和高8位。

  • 0x804C寄存器,其bit3~bit0位确定最大触摸点数量。

  • 0x804D寄存器,其bit1~bit0位用于设置中断触发类型。

坐标信息寄存器

../_images/subsystem_touch_driver_4.jpg

坐标信息寄存器范围从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

从内核源码中找到对应的设备树插件源码如下:

设备树插件(位于内核源码/arch/arm64/boot/dts/rockchip/overlay/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
    /* 省略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 = <&gt911_dsi0>;

        __overlay__ {
            status = "okay";
            nvmem = <&eeprom0>;
            nvmem-names = "eeprom";
        };
    };
};

可以确认LubanCat2-V3板卡dsi0接口使用的i2c为i2c1,默认触摸节点为gt911_dsi0。

16.3.2.2. 确认板卡DSI接口触摸相关引脚

查看板卡原理图,以LubanCat2-V3板卡dsi0接口为例:

../_images/subsystem_touch_driver_5.jpg

可以确认,i2c的确为i2c1,中断INT引脚为GPIO0_B5,复位RST引脚为GPIO0_B6。

提示

如果对设备树比较熟悉也可以直接通过查看主设备树及其相关dtsi设备树找到默认触摸节点,确定相关引脚。

16.3.2.3. 修改默认MIPI屏幕插件适配本实验

以rk3568-lubancat-2-dsi0-in-vp1-generic-overlay.dts为例,修改后的设备树源码如下:

设备树插件(位于内核源码/arch/arm64/boot/dts/rockchip/overlay/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 = <&gt9xx_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 = <&gt9xx_pin>;
            //      touchscreen-inverted-x = <1>;
            //      touchscreen-swapped-x-y = <1>;
            // };
        };
    };

    fragment@7 {
        target = <&gt911_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硬件初始化、输入设备注册、触摸数据读取、最终实现多点触摸事件上报。

核心定义与数据结构

核心定义与数据结构(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/* 触摸硬件参数宏定义 */
#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,匹配对应的芯片配置参数,实现多芯片适配。

芯片ID匹配函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 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 &gt911_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上电和设置通信地址时序,确保芯片正常启动。

GPIO配置与芯片复位函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/* 设置芯片通讯地址后中断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;
}

关键说明:

  1. 从设备树获取GPIO配置:

  • 第63行:获取中断GPIO(设备树中irq-gpios配置),输入模式。

  • 第75行:获取复位GPIO(设备树中reset-gpios配置),输入模式。

需注意 ,gpiod系列获取GPIO的API会自动拼接“-gpios”后缀完成属性匹配,因此调用时只需传入属性前缀即可,无需写完整属性名。

  1. 芯片硬件复位,确认通信地址:

  • 第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地址选择。

  1. 设置芯片通讯地址后中断GPIO初始化为输入:

  • 第8行:设置中断GPIO为输出模式,输出低电平,初始化引脚状态。

  • 第11行:延时50ms,确保引脚电平稳定。

  • 第13行:恢复中断GPIO为输入模式,等待芯片触发中断。

I2C通信连通性测试

测试I2C通信是否正常,确认芯片正常工作。

I2C通信连通性测试(位于linux_driver/34_touch_driver/touch_goodix.c)
 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和固件版本读取

芯片ID和固件版本读取(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 读取芯片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与版本信息,用于调试验证。

触摸数据读取函数

批量读取触摸坐标数据,判断数据就绪状态,提取有效触摸点数量,读取全部有效触摸点数据。

触摸数据读取函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 读取触摸输入报告数据 */
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、触摸大小,上报到输入子系统,支持坐标翻转。

单个触摸点事件上报函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 上报单个触摸点事件到输入子系统 */
static 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行:上报触摸宽度,与主尺寸一致,提升兼容性。

触摸事件处理函数

中断触发后,调用该函数读取所有触摸点数据,批量上报事件,完成一帧触摸数据的处理。

触摸事件处理函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 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行:发送同步事件,通知系统处理本次触摸数据。

中断服务函数

触摸中断触发后,内核调用该函数,触发触摸事件处理。

中断服务函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 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,告知内核中断已处理。

中断申请与释放函数

申请线程化中断,释放中断资源,确保中断配置正确。

中断申请与释放函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 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函数读取芯片内置配置寄存器数据,提取中断触发类型、最大触摸点数、分辨率等核心参数,适配硬件实际配置。

芯片配置读取函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* 读取芯片内部配置参数 */
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函数配置输入设备属性、注册输入设备、初始化多点触摸槽位,适配输入子系统要求。

输入设备配置函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* 配置输入设备与中断参数 */
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总线。

probe函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* 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函数,释放资源。

remove函数(位于linux_driver/34_touch_driver/touch_goodix.c)
 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总线。

驱动注册(位于linux_driver/34_touch_driver/touch_goodix.c)
 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即可。

Makefile(位于linux_driver/34_touch_driver/Makefile)
 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设备树插件参考下图:

../_images/subsystem_touch_driver_6.jpg

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”按键切换为画线模式,可在屏幕画线,效果如下:

../_images/subsystem_touch_driver_7.jpg

提示

如果触摸点出现轴对称或者翻转,需确认设备树的touchscreen-inverted-x/y、touchscreen-swapped-x-y配置。

16.3.8. 实验注意事项

  • 硬件接线:接线前务必断电,严格按照快速使用手册连接板卡和屏幕,避免带电连接或者接错接口烧毁屏幕。

  • 设备树配置:设备树插件的compatible属性必须与驱动代码中of_device_id匹配表完全一致,大小写、字符均不可偏差;中断触发类型需与驱动默认配置、硬件实际输出极性匹配,坐标翻转配置(touchscreen-inverted-x/y)需根据屏幕安装方向调整,否则会出现触摸无响应、坐标错乱。

  • 芯片复位时序:驱动中的复位函数(goodix_reset)严格遵循GT911/GT928复位和通信地址设置时序,不可随意修改延时参数,否则芯片无法正常初始化,导致触摸无响应。

  • 实验操作:实验过程中避免触碰芯片引脚和板卡接口,防止静电或接触不良导致的硬件故障。