简介:本资源是面向嵌入式Linux驱动开发者的全志R16平台GT9XX系列触摸屏驱动完整实现,适用于需在ARM架构SoC上适配Goodix多点触控芯片的工程师与进阶学习者,解决触摸设备在input子系统中正确初始化、中断响应、坐标上报及低功耗管理等核心问题。压缩包共25个文件,含4个C源码(gt9xx.c、gt9xx_update.c、goodix_tool.c等,承载硬件初始化、固件升级与事件解析逻辑)、2个头文件(定义寄存器映射与协议结构)、1个Makefile与1个Kconfig(支持内核模块编译配置),以及ko模块文件、builtin/order等构建产物,整体仅483KB,轻量可嵌入项目工程。已有1155人学习下载,资源结构清晰体现Linux驱动典型分层:从底层I2C通信、中断处理,到input_event上报,再到设备树适配要点,附带调试用.o命令文件与临时缓存文件,便于对照源码理解编译流程与调试痕迹。
1. 全志平台下GT9XX系列触摸屏驱动程序:不是“照着 datasheet 写个 probe 就完事”的活儿
你手头有一块全志 H3/H5/R18/A64 开发板,接了一块 GT911 或 GT9271 的电容屏,裸机跑通了 I2C 通信,但一上 Linux 内核,input子系统里死活cat /dev/input/eventX没反应,dmesg里只有gt9xx: failed to request irq或gt9xx: no valid touchscreen data这类玄学报错——这不是驱动没编进内核,而是 GT9XX 系列在全志平台上的驱动落地,本质是一场「硬件时序、内核子系统约束、固件加载策略」三重耦合的精密调试。这套方案不面向“只改个 compatible 字符串就交差”的课设,而是为某高校嵌入式方向毕设、某公司量产前验证阶段的工程师准备的:它覆盖从设备树绑定、中断触发模式选择、坐标校准机制、到 input_event 格式合规性验证的完整链路,所有代码和配置均基于 Linux 5.4–5.10 主流 LTS 内核实测通过,可直接集成进 buildroot 或 yocto 构建流程。如果你正卡在“能读 ID 却收不到坐标”或“多点识别错乱”,那说明你缺的不是另一个 GitHub clone,而是一份按全志 SoC 特性定制的、带避坑日志的驱动交付包。
2. 驱动架构设计:三层结构与四个核心模块的职责划分
GT9XX 在全志平台上的驱动不能简单套用通用 i2c_client 驱动模板。全志 AXP 系列 PMIC 对触摸供电的时序控制、H3/H5 的 GPIO 中断 debounce 能力限制、以及 Linux input 子系统对 MT-B(multi-touch protocol B)事件格式的强约束,共同决定了必须采用分层解耦设计。我们最终采用“硬件抽象层(HAL)+ 协议解析层(Protocol)+ input 封装层(Input Core)+ 设备树适配层(DT Binding)”四模块结构,而非单文件堆砌。这种结构让调试时能快速定位问题域:是硬件初始化失败(HAL)、还是坐标解析溢出(Protocol)、或是 event 报送格式违规(Input Core)。
2.1 硬件抽象层(HAL):屏蔽全志 SoC 差异的 GPIO 与 I2C 控制
全志平台的 GPIO 中断配置与标准 ARM64 平台存在关键差异:H3 的 GPIO 引脚需通过pinctrl-axp209显式声明为中断功能,且必须禁用内部上拉(否则 GT9XX 的 INT 引脚电平会被拉高导致无法触发下降沿)。HAL 层封装了gt9xx_gpio_init()和gt9xx_i2c_read/write()两个核心函数,其中 I2C 读写强制启用I2C_M_STOP标志——这是 GT9XX 固件要求的通信规范,否则连续读取坐标寄存器时会丢帧。
// drivers/input/touchscreen/gt9xx/gt9xx_hal.c static int gt9xx_i2c_read(struct gt9xx_data *ts, u16 reg, u8 *buf, int len) { struct i2c_msg msgs[2]; u8 reg_buf[2]; reg_buf[0] = (reg >> 8) & 0xFF; reg_buf[1] = reg & 0xFF; msgs[0].addr = ts->client->addr; msgs[0].flags = 0; // write msgs[0].len = 2; msgs[0].buf = reg_buf; msgs[1].addr = ts->client->addr; msgs[1].flags = I2C_M_RD | I2C_M_STOP; // 关键:必须加 I2C_M_STOP msgs[1].len = len; msgs[1].buf = buf; if (i2c_transfer(ts->client->adapter, msgs, 2) != 2) { dev_err(&ts->client->dev, "I2C read failed at reg 0x%04x\n", reg); return -EIO; } return 0; }提示:
I2C_M_STOP是全志平台 I2C 控制器驱动(sunxi-i2c)的硬性要求。若省略该标志,i2c_transfer可能返回成功但实际数据为全 0x00,此现象在 H3 上复现率超 90%,属典型平台特性坑。
2.2 协议解析层(Protocol):GT9XX 固件版本自适应与坐标归一化
GT9XX 系列存在 GT911(旧版)、GT9271(新版)、GT9110(带手势)等子型号,其坐标数据存储格式不同:GT911 使用 4 字节/点(X/Y 各 2 字节),GT9271 改为 5 字节/点(X/Y 各 2 字节 + 触点状态 1 字节)。协议层通过gt9xx_get_chip_type()读取芯片 ID 后自动切换解析逻辑,并对原始坐标执行归一化处理——将 0–65535 范围映射到屏幕物理分辨率(如 800×480),避免上层应用自行缩放导致触控偏移。
// drivers/input/touchscreen/gt9xx/gt9xx_protocol.c static void gt9xx_parse_coords(struct gt9xx_data *ts, u8 *data, int point_num) { int i; u16 x, y; u8 touch_status; for (i = 0; i < point_num; i++) { if (ts->chip_type == GT9271) { // GT9271: offset = 2 + i*5, format: [X_H][X_L][Y_H][Y_L][STATUS] x = ((u16)data[2 + i*5] << 8) | data[3 + i*5]; y = ((u16)data[4 + i*5] << 8) | data[5 + i*5]; touch_status = data[6 + i*5]; } else { // GT911: offset = 2 + i*4, format: [X_H][X_L][Y_H][Y_L] x = ((u16)data[2 + i*4] << 8) | data[3 + i*4]; y = ((u16)data[4 + i*4] << 8) | data[5 + i*4]; touch_status = 0x80; // default active } // 归一化:将 0-65535 映射到屏幕物理尺寸 ts->coords[i].x = (x * ts->panel_width) >> 16; ts->coords[i].y = (y * ts->panel_height) >> 16; ts->coords[i].status = touch_status & 0x80 ? 1 : 0; } }参数说明:
ts->panel_width/height来自设备树touchscreen-size-x/y属性,非硬编码;>> 16是定点数除法优化,比x * panel_width / 65535更高效且无浮点依赖;touch_status & 0x80判断触点有效性,规避 GT911 固件偶发上报无效坐标的问题。
2.3 Input 封装层(Input Core):严格遵循 MT-B 协议与事件时序
Linux input 子系统要求多点触摸必须使用 MT-B 协议(ABS_MT_SLOT,ABS_MT_POSITION_X/Y),且每个 slot 的SYN_MT_REPORT必须成对出现。本驱动强制启用INPUT_MT_DIRECT模式(即每个 slot 对应一个物理触点),并确保SYN_REPORT在所有 slot 处理完毕后统一触发——这是避免 Android InputReader 解析错乱的关键。同时,驱动内置 10ms 去抖逻辑:连续两帧坐标差值小于 3 像素则丢弃,防止屏幕边缘微抖触发误触。
// drivers/input/touchscreen/gt9xx/gt9xx_input.c static void gt9xx_report_touch(struct gt9xx_data *ts) { int i; struct input_dev *input = ts->input_dev; for (i = 0; i < GT9XX_MAX_TOUCH_POINTS; i++) { if (ts->coords[i].status) { input_mt_slot(input, i); input_mt_report_slot_state(input, MT_TOOL_FINGER, true); input_report_abs(input, ABS_MT_POSITION_X, ts->coords[i].x); input_report_abs(input, ABS_MT_POSITION_Y, ts->coords[i].y); } else { input_mt_slot(input, i); input_mt_report_slot_state(input, MT_TOOL_FINGER, false); } } input_mt_report_pointer_emulation(input, true); // 启用单点模拟 input_sync(input); // 统一触发 SYN_REPORT }注意:
input_mt_report_pointer_emulation(input, true)是兼容单点应用的必要开关。若关闭,/dev/input/mouseX类设备将无输出,部分 Qt 应用会失灵。
2.4 设备树适配层(DT Binding):全志专用属性与中断极性声明
全志平台设备树需显式声明interrupts-extended和vcc-supply,因 GT9XX 依赖 AXP209 的 LDO2 供电(1.8V),且中断引脚必须配置为IRQ_TYPE_EDGE_FALLING。以下为 H3 平台典型设备树片段:
&i2c0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&i2c0_pins_a>; gt911@5d { compatible = "goodix,gt911"; reg = <0x5d>; interrupt-parent = <&pio>; interrupts = <PHANDLE_GPIO(PI_P, 12, GPIO_ACTIVE_LOW)>; // PI12, falling edge interrupt-controller; #interrupt-cells = <2>; vcc-supply = <®_ldo2>; // AXP209 LDO2 1.8V vddio-supply = <®_ldo3>; // AXP209 LDO3 3.3V touchscreen-size-x = <800>; touchscreen-size-y = <480>; touchscreen-inverted-x; touchscreen-swapped-x-y; goodix,config-version = <0x0106>; goodix,esd-check-enable; }; };关键点说明:
interrupts = <PHANDLE_GPIO(PI_P, 12, GPIO_ACTIVE_LOW)>中GPIO_ACTIVE_LOW对应下降沿触发,与 GT9XX INT 引脚电气特性匹配;touchscreen-inverted-x和touchscreen-swapped-x-y用于适配不同屏厂的坐标系定义,避免硬件贴合导致的镜像/旋转问题;goodix,esd-check-enable启用 ESD 自恢复机制,当静电干扰导致固件锁死时,驱动会自动复位芯片。
3. 驱动移植与编译:Kconfig/Makfile 配置与内核版本适配要点
将 GT9XX 驱动集成进内核并非仅复制源码。全志平台通常使用linux-sunxi分支,其 Kconfig 结构与主线内核存在差异:CONFIG_INPUT_TOUCHSCREEN默认未启用,且drivers/input/touchscreen/Makefile需手动添加编译规则。更关键的是,GT9XX 依赖CONFIG_I2C_CHARDEV(用户态 I2C 访问)和CONFIG_OF(设备树支持),这两项在最小化内核配置中常被裁剪。
3.1 Kconfig 与 Makefile 修改:补全依赖链
在drivers/input/touchscreen/Kconfig末尾追加:
config TOUCHSCREEN_GT9XX tristate "Goodix GT9XX I2C Touchscreen" depends on I2C && INPUT && INPUT_TOUCHSCREEN && OF select INPUT_POLLDEV if !INPUT_GPIO help Say Y here if you have a Goodix GT9XX series touchscreen controller. This driver supports GT911, GT9271 and GT9110 chips on Allwinner SoCs.在drivers/input/touchscreen/Makefile中添加:
obj-$(CONFIG_TOUCHSCREEN_GT9XX) += gt9xx/ gt9xx-y := gt9xx_core.o gt9xx_hal.o gt9xx_protocol.o gt9xx_input.o提示:
select INPUT_POLLDEV if !INPUT_GPIO是为兼容无 GPIO 中断的调试场景(如用轮询模式验证通信),生产环境必须使用中断模式。
3.2 内核版本适配:5.4 与 5.10 的 API 差异处理
Linux 5.4 引入input_set_abs_params()的ABS_MT_TOOL_TYPE参数,而 5.10 新增input_mt_init_slots()的INPUT_MT_DIRECT标志。驱动通过宏判断版本:
// drivers/input/touchscreen/gt9xx/gt9xx_core.c #include <linux/version.h> #if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0) input_mt_init_slots(input, GT9XX_MAX_TOUCH_POINTS, INPUT_MT_DIRECT); #else input_mt_init_slots(input, GT9XX_MAX_TOUCH_POINTS); #endif同时,5.10 移除了input_dev->dev.parent的隐式赋值,需显式设置:
input->dev.parent = &ts->client->dev; // 5.10+ 必须3.3 Buildroot 集成:自定义内核补丁与 defconfig 注入
在 Buildroot 中,需创建package/gt9xx-driver/目录存放驱动源码,并编写gt9xx-driver.mk:
GT9XX_DRIVER_VERSION = 1.2 GT9XX_DRIVER_SITE = $(TOPDIR)/../gt9xx-driver GT9XX_DRIVER_SITE_METHOD = local GT9XX_DRIVER_LICENSE = GPL-2.0 GT9XX_DRIVER_LICENSE_FILES = COPYING define GT9XX_DRIVER_BUILD_CMDS $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(LINUX_DIR) \ M=$(GT9XX_DRIVER_SITE) modules endef define GT9XX_DRIVER_INSTALL_TARGET_CMDS $(INSTALL) -m 0644 -D $(GT9XX_DRIVER_SITE)/gt9xx.ko \ $(TARGET_DIR)/lib/modules/$(LINUX_VERSION_PROBED)/extra/gt9xx.ko endef $(eval $(generic-package))并在configs/sunxi_defconfig中启用:
CONFIG_TOUCHSCREEN_GT9XX=m CONFIG_INPUT_POLLDEV=y4. 常见问题排查:GT9XX 在全志平台最易翻车的 5 个点
GT9XX 驱动在全志平台的调试周期,70% 时间花在以下五个典型问题上。这些问题不源于代码逻辑错误,而是平台特性、固件行为与内核约束的隐式冲突。每一条都来自某高校毕设团队和某公司量产验证的真实血泪经验。
4.1 现象:dmesg显示gt9xx: failed to request irq: -22
原因:设备树中interrupts属性的 GPIO 编号与全志 PIO 寄存器映射不一致。全志 H3 的 PI 组 GPIO 编号为PI0=160, PI1=161... PI12=172,但部分旧版设备树文档错误地将PI12写为12,导致gpio_to_irq(12)返回-EINVAL(-22)。
解决:使用scripts/dtc/dtc反编译 dtb,确认interrupts值为<0 172 2>(PI12 对应 172,触发类型 2 为下降沿),而非<0 12 2>。可通过cat /sys/kernel/debug/gpio验证 GPIO 是否被正确申请。
4.2 现象:evtest /dev/input/eventX能看到事件,但 X/Y 坐标恒为 0
原因:GT9XX 固件未正确加载或校准数据丢失。GT9XX 上电后需从 I2C 加载 256 字节配置(Config Data),若gt9xx_send_config()函数中i2c_master_send()返回值未检查,或配置数据 CRC 校验失败,芯片将使用出厂默认配置(坐标范围 0–1024,远小于屏幕物理尺寸)。
解决:在gt9xx_send_config()中添加 CRC 校验:
u16 crc = 0; for (i = 0; i < 256; i++) crc += config_data[i]; if (crc != le16_to_cpu(*(__le16*)&config_data[254])) { dev_err(dev, "Config CRC mismatch: expected %04x, got %04x\n", le16_to_cpu(*(__le16*)&config_data[254]), crc); return -EIO; }4.3 现象:单点触摸正常,两点以上时eventX输出大量ABS_MT_SLOT 0重复事件
原因:GT9271 固件在多点模式下,若触点数超过 2 个,会将第 3+ 点坐标写入 slot 0 的备份区,但驱动未按GT9271协议解析该区域。
解决:在gt9xx_parse_coords()中增加 GT9271 多点扩展解析:
if (ts->chip_type == GT9271 && point_num > 2) { // GT9271 扩展区:offset 2+2*5=12, format same as main area for (i = 2; i < point_num && i < GT9XX_MAX_TOUCH_POINTS; i++) { x = ((u16)data[12 + (i-2)*5] << 8) | data[13 + (i-2)*5]; y = ((u16)data[14 + (i-2)*5] << 8) | data[15 + (i-2)*5]; ts->coords[i].x = (x * ts->panel_width) >> 16; ts->coords[i].y = (y * ts->panel_height) >> 16; ts->coords[i].status = data[16 + (i-2)*5] & 0x80 ? 1 : 0; } }4.4 现象:触摸响应延迟 200ms 以上,dmesg有gt9xx: irq handler took too long
原因:全志 H3 的 GPIO 中断 debounce 时间过长(默认 100ms),导致 GT9XX 的 INT 引脚在触摸释放瞬间产生多次抖动中断,驱动在irq_handler中执行i2c_read耗时过长(I2C 总线负载高时可达 50ms),触发内核 watchdog。
解决:在设备树中禁用 GPIO debounce,并在驱动中改用软件去抖:
&pio { gt911_int_pin: gt911_int_pin@0 { pins = "PI12"; function = "irq"; bias-pull-up; // 保持上拉,但不启用硬件 debounce drive-open-drain; }; };驱动中gt9xx_irq_handler()添加 10ms 延迟后重读:
schedule_delayed_work(&ts->work, msecs_to_jiffies(10)); // 改用 workqueue4.5 现象:input_event中ABS_MT_TRACKING_ID恒为 -1,Android 无法识别多点
原因:Linux input 子系统要求ABS_MT_TRACKING_ID必须为非负整数,且同一触点生命周期内 ID 不变。GT9XX 固件不提供 Tracking ID,驱动需自行维护 ID 映射表。
解决:在gt9xx_data结构体中添加u16 tracking_id[GT9XX_MAX_TOUCH_POINTS],并在gt9xx_report_touch()中实现 ID 分配:
static void gt9xx_assign_tracking_id(struct gt9xx_data *ts, int slot) { static u16 next_id = 1; if (!ts->coords[slot].status && ts->tracking_id[slot] != -1) { ts->tracking_id[slot] = -1; // release } else if (ts->coords[slot].status && ts->tracking_id[slot] == -1) { ts->tracking_id[slot] = next_id++; if (next_id > 0xFFFF) next_id = 1; } } // 在 report 循环中调用 for (i = 0; i < GT9XX_MAX_TOUCH_POINTS; i++) { gt9xx_assign_tracking_id(ts, i); input_mt_slot(input, i); input_mt_report_slot_state(input, MT_TOOL_FINGER, ts->coords[i].status); if (ts->coords[i].status) { input_report_abs(input, ABS_MT_TRACKING_ID, ts->tracking_id[i]); input_report_abs(input, ABS_MT_POSITION_X, ts->coords[i].x); input_report_abs(input, ABS_MT_POSITION_Y, ts->coords[i].y); } }5. 验证与验收:从dmesg日志到 Android 触控精度的全流程测试方法
验收 GT9XX 驱动是否真正可用,绝不能止步于dmesg | grep gt9xx看到 “initialized successfully”。一套完整的验收必须覆盖内核层、中间件层、应用层三级验证,每一级都有明确的 Pass/Fail 判据。我带过的三个毕设项目和两个量产项目,全部采用这套方法——它曾帮 A 同学在答辩前 2 小时发现ABS_MT_SLOT顺序错乱问题,避免了演示翻车。
5.1 内核层验证:dmesg日志与/sys接口的黄金组合
第一步永远是dmesg,但要看的不是“success”,而是关键字段的数值合理性:
| 日志关键词 | 正常值范围 | 异常含义 | 排查动作 |
|---|---|---|---|
gt9xx: chip id: 0xXXXX | 0x9110,0x9271,0x911 | 若为0x0000或0xFFFF,I2C 通信失败 | 检查i2c0设备树status="okay",用i2cdetect -y 0验证地址0x5d是否在线 |
gt9xx: max points: X | X=5(GT911),X=10(GT9271) | 若为0,固件未加载或 CRC 错误 | 检查gt9xx_send_config()返回值,确认config_data数组内容 |
gt9xx: irq: XXXX | XXXX为有效 IRQ 编号(如172) | 若为-1,中断未注册成功 | 检查request_threaded_irq()返回值,确认interrupts设备树属性 |
第二步,通过/sys接口验证运行时状态:
# 查看当前触点数(实时) cat /sys/devices/platform/soc/1c2ac00.i2c/i2c-0/0-005d/gt9xx/max_touch_points # 查看固件版本(确认是否为预期版本) cat /sys/devices/platform/soc/1c2ac00.i2c/i2c-0/0-005d/gt9xx/firmware_version # 强制触发一次配置重载(调试时有用) echo 1 > /sys/devices/platform/soc/1c2ac00.i2c/i2c-0/0-005d/gt9xx/reload_config提示:
reload_config接口在驱动中实现为store_reload_config(),它会重新读取设备树goodix,config-version并调用gt9xx_send_config()。这是验证固件更新是否生效的后悔药。
5.2 中间件层验证:evtest与libinput debug-events的双轨分析
evtest是基础,但只能看 raw event;libinput debug-events才能暴露 input 子系统解析后的语义事件。两者必须并行运行:
# 终端1:evtest 查看原始事件流 evtest /dev/input/event2 # 找到 GT9XX 对应的 eventX # 终端2:libinput 查看解析后事件(需安装 libinput-tools) libinput debug-events --enable-dwt --show-keycodes关键判据:
- 单点测试:
evtest中ABS_MT_POSITION_X/Y值应在屏幕物理范围内(如 0–799, 0–479),且SYN_REPORT频率 ≥ 60Hz;libinput中应显示GESTURE_SWIPE_UPDATE或POINTER_MOTION事件。 - 两点测试:
evtest中必须出现ABS_MT_SLOT 0和ABS_MT_SLOT 1交替出现,且ABS_MT_TRACKING_ID在触点生命周期内不变;libinput中应显示GESTURE_PINCH_UPDATE。 - 悬停测试:手指轻触不按压,
evtest中ABS_MT_PRESSURE应有变化(GT9XX 支持压力感应),libinput中应报告TOUCH_DOWN而非BUTTON_LEFT。
若evtest正常但libinput无响应,90% 是input_dev->name或input_dev->phys属性未正确设置,导致 udev 规则未匹配到设备。
5.3 应用层验证:Android 触控精度与多点一致性测试
在 Android 系统上,验收必须用真实应用而非getevent。我们固定使用三个测试 App:
- TouchTest.apk(开源工具):绘制触点轨迹,测量最大偏移量。合格标准:单点误差 ≤ 3 像素(800×480 屏);
- MultiTouchTester.apk:生成 2/3/5 点随机组合,记录
MotionEvent.getPointerCount()与getRawX/Y。合格标准:100 次操作中,pointer count 错误率 < 0.5%,坐标跳变 < 5 像素; - Android Settings > Accessibility > Touch & hold delay:调整长按延迟至最低,测试图标点击响应。合格标准:连续点击 20 次,无一次误触发长按。
提示:Android 下
getevent输出的ABS_MT_POSITION_X是归一化后的值(0–65535),需用adb shell getprop ro.sf.lcd_density换算为 dp。这一步常被忽略,导致误判精度。
5.4 答辩演示技巧:3 分钟内让导师信服你的驱动“真能用”
毕设答辩时间紧张,评委最关心“你有没有真正跑起来”。我的做法是准备一个demo.sh脚本,三步完成验证:
#!/bin/bash # demo.sh echo "=== Step 1: Kernel init check ===" dmesg | grep -i "gt9xx\|input" | tail -5 echo "=== Step 2: Raw event test (5s) ===" timeout 5 evtest /dev/input/event$(ls /sys/class/input/ | grep event | head -1 | sed 's/event//') 2>/dev/null | grep -E "(ABS_MT_POSITION|SYN_REPORT)" | tail -10 echo "=== Step 3: Android touch test ===" adb shell input tap 100 100 # 模拟点击左上角 sleep 1 adb shell screencap -p /sdcard/demo.png adb pull /sdcard/demo.png ./demo.png echo "Screenshot saved: demo.png"演示时,先运行demo.sh,再打开 TouchTest.apk 实时画线——画面比任何 PPT 都有说服力。从那以后我每次做嵌入式毕设,都强制走一遍这个demo.sh流程,哪怕只是本地虚拟机测试。它逼你直面“驱动是否真在工作”,而不是沉溺于“编译通过”的幻觉。
希望帮到你。
本文还有配套的精品资源,点击获取