
简介面向嵌入式Linux驱动开发者的全志R16平台GT9XX系列触摸屏驱动资料聚焦I2C通信、中断处理、触点数据解析、input子系统事件上报及电源管理等核心环节适合具备一定内核基础、需要适配汇顶触摸芯片的工程师参考。压缩包共25个文件约483KB以C源码、头文件、Makefile与Kconfig等构建配置为主并含编译中间产物、内核模块及调试工具相关代码便于直接对照驱动架构与编译流程。已有1156人学习下载。包内结构覆盖驱动主体、固件更新与工具辅助代码配合构建脚本可快速梳理模块加载、事件注册和数据读取链路编译产物与配置项也有助于排查版本匹配、模块加载失败与触点上报异常为R16平台触摸适配提供可复用的参考框架。1. 板子亮着屏手指按上去毫无反应手头这块全志方案的板子HDMI 亮着、屏幕背光也正常唯独手指点上去一点反应都没有。串口里dmesg停在gt9xx: probe of 1-005d failed with error -5这一行后面什么都没有。这种场面我遇到过不止一次硬件焊好了、内核也编进去了结果卡在驱动这一层屏幕变成了只可远观的大号显示器。全志平台下 GT9xx 系列触摸屏驱动要解决的就是让这一段打通——把 I2C 上挂着的触摸芯片接进 Linux input 子系统上报成标准的EV_ABS/EV_KEY事件。它面向拿着全志方案板、需要点亮或移植 TP 的嵌入式开发者尤其是从零开始接一块 GT911、GT9271 模组的人。2. 数据怎么走从 I2C 寄存器到 input 子系统的完整链路GT9xx 这类芯片本身不带屏幕它只是把电容变化算成坐标塞进自己内部的一块寄存器里等人来读。驱动干的事非常明确通过 I2C 定期或按中断把坐标取回来翻译成 Linux 输入子系统能懂的struct input_event剩下的交给 evdev。理解这条链路是全志平台上能否把 TP 点亮的关键中间任何一跳断了表现出来都是“按屏幕没反应”。2.1 input 子系统的三级结构设备、handler、evdev很多人一上来就盯着input_report_abs这几个函数看其实先要弄清楚输入子系统里到底有谁。它是个典型的生产者/消费者模型分三层输入设备层驱动通过input_allocate_device()申请一个struct input_dev填上name、id.bustype、支持的事件位图evbit、absbit、keybit然后input_register_device()注册。输入核心层input_event()是最底层的事件投递入口核心会按事件类型分发给所有绑定了对应事件的 handler。事件处理器层evdev.c是内核里最常用的 handler它把每个input_dev映射成/dev/input/eventX应用层用read()读到一串struct input_event。GT9xx 驱动只负责最上面一层。我要在 probe 里做的就是把ABS_MT_POSITION_X、ABS_MT_POSITION_Y、ABS_MT_TOUCH_MAJOR这三个绝对坐标事件声明出去再告诉核心最多支持几个触点。声明少了应用层读到的坐标永远是 0声明错了范围手指划到边上坐标就被裁掉。常见做法是在设备树里读touchscreen-size-x和touchscreen-size-y赋给input_set_abs_params()的 max 参数保证上报范围和面板分辨率一致。一个典型的上报序列长这样/* 一个触点按下先占 slot再填坐标 */ input_mt_slot(input, id); /* 选择第 id 个 slot */ input_mt_report_slot_state(input, MT_TOOL_FINGER, true); input_report_abs(input, ABS_MT_POSITION_X, x); input_report_abs(input, ABS_MT_POSITION_Y, y); input_report_abs(input, ABS_MT_TOUCH_MAJOR, size); /* 一轮结束让核心把这一帧打包推给 evdev */ input_mt_sync_frame(input); input_sync(input);input_mt_slot()和input_mt_report_slot_state()是 MT 协议 B 的入口后面 4.2 会详细展开input_mt_sync_frame()会一次性把本帧所有 slot 的变化同步出去漏掉这一句evdev 那头会一直等不到完整帧表现出来就是按了没反应。input_sync()则是把整个事件流打上SYN_REPORT分隔符应用层拿到这个标记才会认为“这一批事件读完了”。2.2 GT9xx 的寄存器地图读哪几个地址就够了GT9xx 内部寄存器是 16 位地址、8 位数据I2C 上先发两字节地址再读数据。全志平台普遍用 400kHz 的 I2C 速率普通读一次也就几百微秒完全够用。驱动真正要访问的寄存器不多先记住下面这几个名称地址长度作用产品 ID0x81404 字节用于确认 I2C 通不通、识别具体型号状态寄存器0x814E1 字节bit7 坐标缓冲区就绪低 4 位 当前触点数第 1 个触点数据0x814F8 字节触点 ID、X、Y、尺寸都在这 8 字节里配置区0x8047186 字节分辨率、触点上限、刷新率等参数命令寄存器0x80401 字节写 2 触发软复位写 0 结束触点的 8 字节格式是固定的前 7 个字节依次是byte0: bit3..0 track id byte1-2: X 坐标小端 byte3-4: Y 坐标小端 byte5-6: 触摸面积 / 压力 byte7: 保留一个最容易翻车的地方是状态寄存器必须手动清零。GT9xx 不会自己清0x814E你读完一次坐标之后如果不写回 0芯片就不会再产生新的中断表现出来的现象是“按下第一下能响应第二下开始就死了”。这条我后面在避坑章节还会再提一次。读一次坐标的原始字节流大致是这样/* 先读 1 字节状态判断有没有新数据 */ u8 status; gt9xx_i2c_read(ts, GT9XX_REG_STATUS, status, 1); if (status 0x80) { int touch_num status 0x0f; /* 有多少点读多少字节每个点 8 字节 */ gt9xx_i2c_read(ts, GT9XX_REG_POINT1, buf, touch_num * 8); /* ... 解析 buf上报 input 事件 ... */ gt9xx_i2c_write(ts, GT9XX_REG_STATUS, 0x00, 1); /* 清状态 */ }status 0x80是缓冲区就绪位只有它为 1 时读出来的坐标才是新的一帧status 0x0f是触点数量GT911 最多 5 点GT9271 最多 10 点这个值会随手指抬起下降。把这两个位分开看是排查“坐标一直不动”和“触点数量错误”的第一手段。3. 板级适配全志平台的 DTS 节点与上电复位时序驱动本身写得再漂亮如果设备树里 I2C 地址、中断引脚、GPIO 编号对不上probe 一样会挂。全志平台在这一块的惯例很固定I2C 挂到i2cN节点下GPIO 用pio或r_pio的 pinctrl 语法中断和复位脚在子节点里各占一行。这一章把 DTS 写完下一章的 probe 才有东西可读。3.1 DTS 节点I2C 子节点与 pinctrl 写法全志的 pinctrl 是按端口组命名的PG8、PG9这种写法在 DTS 里直接拆成pio 7 8和pio 7 9端口号从 PA 开始数A0、B1……G6、G 组的第 8 号引脚就是7 8。中断和复位两只脚都要先在pio里声明子节点再引用这样做的好处是引脚复用、上下拉、驱动强度都能在 pinctrl 里一次性写清楚。pio { gt911_int: gt911_int_pin { pins PG8; function gpio_in; bias-pull-up; /* 中断脚上拉避免悬空误触发 */ }; gt911_rst: gt911_rst_pin { pins PG9; function gpio_out; drive-strength 10; }; }; i2c1 { status okay; clock-frequency 400000; /* 400kHz 是 GT9xx 的常用速率 */ gt911: touchscreen5d { compatible gt9xx,gt911; reg 0x5d; /* 0x5d 或 0x14见 3.2 */ interrupt-parent pio; interrupts 7 8 IRQ_TYPE_EDGE_FALLING; pinctrl-names default; pinctrl-0 gt911_int gt911_rst; reset-gpios pio 7 9 GPIO_ACTIVE_LOW; irq-gpios pio 7 8 GPIO_ACTIVE_HIGH; VDD-supply reg_dcdc1; AVDD-supply reg_aldo1; touchscreen-size-x 1024; touchscreen-size-y 600; status okay; }; };几个参数值得单独说清楚reg要和实际模组的 I2C 地址一致这是 probe 阶段能不能匹配上的第一道关卡。interrupts里最后一项必须是IRQ_TYPE_EDGE_FALLINGGT9xx 的中断脚是“有数据就拉低一段时间再放掉”是下降沿信号配成 LEVEL 会导致中断风暴一发不可收拾。reset-gpios用GPIO_ACTIVE_LOW因为芯片的复位脚是低电平有效写 0 才是复位。touchscreen-size-x/y会直接映射到input_set_abs_params()的上限写错了手指划到边上坐标就被截断表现出来是“屏幕右边一块点不到”。pinctrl-0里两只脚要摆在一起因为上电阶段这两只脚的时序是耦合的分开配容易出现 pinctrl 抢占。全志的 pinctrl 驱动是按组注册的同一组内的多个引脚会在pinctrl_select_state()里被同时应用没有先后问题但中断脚的上拉必须在申请中断之前生效否则第一次下降沿可能就丢。3.2 上电、复位、地址选择0x5D 还是 0x14GT9xx 的 I2C 地址是由复位释放那一刻 INT 引脚的电平决定的INT 拉高常见是0x14INT 拉低常见是0x5D具体要看模组厂手册里写的是哪一种。地址不是芯片本身固定的同一批模组做出来都一样换一批就可能变。我一般先用i2cdetect -y 1扫一遍看扫描结果里是5d还是14再决定 DTS 里的reg填什么比瞎猜准。复位时序是另一个高频翻车点。GT9xx 的芯片手册里对 VDD 和 AVDD 的上电顺序有要求常见做法是先 VDD 后 AVDD中间至少隔 1~2ms等两只电源都稳定之后再拉复位脚复位保持至少 10ms 再释放。全志平台上如果编译了CONFIG_REGULATOR用regulator_enable()打开两路电源之后必须msleep()否则电阻还没稳I2C 通信就是概率性失败。static void gt9xx_reset(struct gt9xx_ts *ts) { /* 上电顺序VDD 先AVDD 后中间留 2ms避免电源打架 */ regulator_enable(ts-vdd); msleep(2); regulator_enable(ts-avdd); msleep(10); /* 复位脚拉低 10ms 以上保证芯片进到复位状态 */ gpiod_set_value_cansleep(ts-reset_gpio, 1); /* ACTIVE_LOW写 1 输出低 */ msleep(12); /* 关键一步释放复位之前决定 I2C 地址拉低走 0x5D拉高走 0x14 */ gpiod_set_value_cansleep(ts-irq_gpio, 0); msleep(1); gpiod_set_value_cansleep(ts-reset_gpio, 0); /* 释放复位 */ msleep(60); /* 芯片初始化需要时间 */ /* 释放之后把 INT 脚切成输入交给中断控制器 */ gpiod_direction_input(ts-irq_gpio); }这段代码里有三个参数需要按实际情况改两路电源之间的msleep(2)有的模组要求 VDD 稳定 5ms 以上再开 AVDD实测不到 2ms 就 100% 挂。复位保持时间 12ms手册里写的是最小 10ms多给 2ms 留余量。复位释放到第一次读寄存器之间的msleep(60)GT9xx 固件启动需要时间短于 50ms 读0x8140会返回全 0。gpiod_direction_input()这一步也容易被漏掉。如果在请求中断之前 INT 脚还挂在输出模式中断控制器看到的电平永远是“被别人按住的”request_threaded_irq()会返回-EBUSY日志里一行probe of 1-005d failed with error -16。4. 核心代码拆解probe 初始化和中断里的 MT 协议 B前两章把数据链路和板级配置讲完了这一章直接看代码。GT9xx 驱动在 Linux 内核里属于典型的 I2C 客户端驱动probe 按“解析 DT → 申请 input_dev → 申请中断”三步走中断里按 MT 协议 B 上报。整份驱动不算gt9xx_update.c里的固件升级逻辑核心函数不到 500 行难点主要在初始化的顺序和中断里的状态清理。4.1 probe 函数先申请 input_dev 还是先请求中断顺序很重要我习惯先把 input_dev 申请、注册好再请求中断。理由是中断一旦打开随时可能来数据如果此时input_dev还是空指针input_mt_slot()会直接空指针解引用空指针的 panic 日志里你连是哪一行都定位不到只能看到一个Unable to handle kernel NULL pointer dereference。static int gt9xx_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct gt9xx_ts *ts; struct input_dev *input; int ret, ver; ts devm_kzalloc(client-dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; ts-client client; ts-dev client-dev; i2c_set_clientdata(client, ts); /* 1. 解析设备树里的 GPIO 与电源省略错误分支 */ ts-reset_gpio devm_gpiod_get(ts-dev, reset, GPIOD_OUT_HIGH); ts-irq_gpio devm_gpiod_get(ts-dev, irq, GPIOD_OUT_LOW); /* 2. 按 3.2 的时序上电和复位 */ gt9xx_reset(ts); /* 3. 读产品 ID确认 I2C 通信正常 */ ver gt9xx_i2c_read(ts, GT9XX_REG_ID, ts-id_buf, 4); if (ver 0) { dev_err(ts-dev, read product id failed: %d\n, ver); return ver; } dev_info(ts-dev, gt9xx id %c%c%c%c\n, ts-id_buf[0], ts-id_buf[1], ts-id_buf[2], ts-id_buf[3]); /* 4. 分配并配置 input_dev这一步必须在请求中断之前完成 */ input devm_input_allocate_device(ts-dev); if (!input) return -ENOMEM; input-name gt9xx-ts; input-id.bustype BUS_I2C; input-id.vendor 0x0001; input-id.product 0x0001; input-dev.parent ts-dev; ts-max_x 1024; /* 从 DT 读 touchscreen-size-x 覆盖 */ ts-max_y 600; /* 从 DT 读 touchscreen-size-y 覆盖 */ ts-max_touch 5; input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts-max_x, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts-max_y, 0, 0); input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); input_mt_init_slots(input, ts-max_touch, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); ret input_register_device(input); if (ret) { dev_err(ts-dev, register input failed: %d\n, ret); return ret; } ts-input input; /* 5. 最后一步才请求中断 */ ret devm_request_threaded_irq(ts-dev, client-irq, NULL, gt9xx_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_FALLING, client-name, ts); if (ret) { dev_err(ts-dev, request irq failed: %d\n, ret); return ret; } return 0; }几个参数和顺序需要重点解释input_mt_init_slots()的第三个参数INPUT_MT_DROP_UNUSED很关键它让核心在每帧结束时把“本帧没有出现、但上一帧还按着”的 slot 自动置为抬起状态。少了它抬手指那一刻 evdev 会一直以为还有手指按着屏幕上就是一个点不掉。input_set_abs_params()的最后两个参数是fuzz和flat一般填 0 就行如果现场发现小抖动造成坐标乱跳可以把fuzz设成 2~3相当于给核心层面做一次去抖。devm_request_threaded_irq()用 threading 模式IRQF_ONESHOT是必须的因为中断处理里要读 I2C而 I2C 控制器本身可能睡眠上下文不能是硬中断。input_register_device()放在请求中断之前是为了避免“中断来了input_dev 还没注册完”这种时序竞争。读产品 ID 那段dev_info打出来的字符串在主控串口里非常显眼GT911 是911、GT9271 是9271。现场调试的时候probe失败第一件事就是看这一行有没有出现出现了说明 I2C 通了后面再挂就是引脚或者中断的问题没出现就直接往 I2C 通信、地址、上电时序这三点去查。4.2 中断处理MT 协议 B 的 slot 分配与状态清理中断处理是 GT9xx 驱动的核心。函数体分三段读状态、按触点数读坐标、按 MT 协议 B 逐点上报。MT 协议 B 是 Linux 输入子系统里最常用的多点触控协议和协议 A 最大的区别是每个触点有固定的 slotinput_mt_slot()传入的那个 id核心靠这个 id 跟踪“哪根手指从哪到哪”。static irqreturn_t gt9xx_irq_handler(int irq, void *data) { struct gt9xx_ts *ts data; u8 status 0; u8 point[8 * 10]; /* 最多 10 个点每个点 8 字节 */ int ret, i, touch_num; /* 1. 读状态寄存器没有就绪位直接返回避免误触发 */ ret gt9xx_i2c_read(ts, GT9XX_REG_STATUS, status, 1); if (ret 0 || !(status 0x80)) return IRQ_HANDLED; touch_num status 0x0f; if (touch_num ts-max_touch) touch_num ts-max_touch; /* 2. 一次 I2C 把所有触点数据读回来减少总线事务 */ if (touch_num) { ret gt9xx_i2c_read(ts, GT9XX_REG_POINT1, point, touch_num * 8); if (ret 0) goto out_clear; } /* 3. 按 MT 协议 B 逐点上报 */ for (i 0; i touch_num; i) { u8 *p point[i * 8]; int id p[0] 0x0f; int x p[1] | (p[2] 8); int y p[3] | (p[4] 8); int area p[5] | (p[6] 8); if (x ts-max_x) x ts-max_x; if (y ts-max_y) y ts-max_y; input_mt_slot(ts-input, id); input_mt_report_slot_state(ts-input, MT_TOOL_FINGER, true); input_report_abs(ts-input, ABS_MT_POSITION_X, x); input_report_abs(ts-input, ABS_MT_POSITION_Y, y); input_report_abs(ts-input, ABS_MT_TOUCH_MAJOR, area); } /* 4. 结束一帧。DROP_UNUSED 已经处理了抬起的 slot */ input_mt_sync_frame(ts-input); input_sync(ts-input); out_clear: /* 5. 无论成败都要清状态否则芯片不再产生新中断 */ gt9xx_i2c_write(ts, GT9XX_REG_STATUS, 0x00, 1); return IRQ_HANDLED; }几个容易忽略的点读坐标一定要一次读完。gt9xx_i2c_read()内部是i2c_transfer()每次调用都有起始、地址、重复起始这一套开销10 个点逐个读是 10 次事务一次读是 1 次。GT911 在 5 点触摸的时候一次中断读 40 字节绰绰有余没必要拆开。out_clear这段无论有没有touch_num都要执行。抬手指的那一帧touch_num 0但状态寄存器那位 0x80 仍然是 1不清掉芯片就不产生下一次中断现象是“按一下之后屏幕再也不响应了”。坐标截断if (x ts-max_x)别省。芯片在边缘偶尔会报出超过面板分辨率的坐标MT 协议 B 的 slot 是允许把越界值传上去的应用层拿到之后画出来的光标会飞出窗口。input_mt_report_slot_state()的第二个参数是MT_TOOL_FINGER如果模组支持手套模式或者手写笔可以换成MT_TOOL_PEN但大多数电容屏没这个要求默认指头就行。中断里不要加printk。I2C 读一次几百微秒加一行printk会让中断处理时间上到毫秒级多指快速划动时会丢帧表现出来是画线断断续续。要调试就用trace_printk或者后文说的 sysfs 接口。5. 避坑排查四条把驱动跑挂过的现场记录GT9xx 驱动的代码量不大但现场把它点亮的难度往往全在“为什么 probe 挂了”和“为什么按上去不响应”这两个问题上。这一章我把这几年踩过的四类坑按现象、原因、解决整理出来每条都是我自己板子上真实撞过的不是从手册里抄的。5.1 现象i2cdetect扫不到 0x5D 或 0x14原因地址不是芯片固定的是复位释放那一刻 INT 引脚电平决定的。INT 拉高对应0x14拉低对应0x5D。全志平台默认的 pinctrlbias-pull-up会让 INT 在复位期间被拉高如果手册里要求的是0x5D地址就会对不上i2cdetect一个设备都扫不到。解决先确认模组手册里写的是哪一组地址然后调整gt9xx_reset()里在释放复位前对irq_gpio的输出值。如果拿不准就在释放复位前把 INT 强制拉低 1ms走0x5D还不行就拉高走0x14。我用过的模块里0x5D占多数但换一批货就得重新核对一遍。5.2 现象probe 报-16-EBUSY明明 DTS 写对了原因request_threaded_irq()返回-EBUSY一般是中断线被占用了。全志平台上常见两种抢占一是 DTS 里interrupts配的 GPIO 号算错跟别外设重复二是复位时序里忘了把 INT 脚从输出模式切回输入gpiod_direction_input()没执行就等于自己按着自己的中断脚。解决先cat /proc/interrupts看对应中断号上是不是已经挂了一个名字不相干的 handler是的话改 DTS 里的 GPIO 编号不是的话核对gt9xx_reset()最后有没有gpiod_direction_input(ts-irq_gpio)。另外 DTS 里interrupts的写法是pio 7 8 IRQ_TYPE_EDGE_FALLING端口号从 A 开始数G 组是 7别按 PA 直接记成 6。5.3 现象按下第一下能响应第二下开始就死了原因状态寄存器0x814E没清。GT9xx 的坐标缓冲区是“读走之后要还回去”的读完坐标如果不写 0 到0x814E芯片就认为上一次数据没人取不再产生下一次下降沿中断。日志里看着一切正常探针也挂着但evtest里就是没有事件。解决检查中断处理函数末尾是不是和 5.2 的代码一样out_clear这段无条件执行gt9xx_i2c_write(ts, GT9XX_REG_STATUS, 0x00, 1)。如果用了goto跳转注意不要让touch_num 0的分支跳过了清理语句。我一般习惯把清理放在函数最后、return之前的唯一一个标签里中间所有return都改成goto out_clear这样不会漏。5.4 现象手指抬起后屏幕上还留一个点一直不消失原因MT 协议 B 里抬手指的那一刻芯片会把对应的 slot 从坐标缓冲区里移走但内核那边的input_dev还认为这个 slot 是按下的。如果驱动在input_mt_init_slots()的时候没带INPUT_MT_DROP_UNUSEDinput_mt_sync_frame()就不知道本帧没出现的 slot 应该被置为抬起。解决两种写法一种是在input_mt_init_slots()的第三个参数里加INPUT_MT_DROP_UNUSED推荐干净另一种是在中断里手动维护一张“上一帧哪些 slot 是按着的”位图本帧结束时对比一下缺的用input_mt_slot()input_mt_report_slot_state(..., false)补上。前者是主流做法后者只在内核版本太老、INPUT_MT_DROP_UNUSED还不存在时才需要。提示遇到“按了没反应”的时候先在串口里exec 3/dev/input/eventX; cat 3 | hexdump -C看有没有事件字节流。有事件说明驱动没问题往应用层查没事件再回到驱动里查 I2C 通信和中断。6. 二次开发坐标校准和自定义手势怎么接进去驱动点亮之后多数人接下来要做两件事把坐标校准做对以及在触摸上报的基础上加自定义手势。前者关系到用户点哪跟屏幕显示哪的对齐后者关系到产品差异化。这一章从这两点出发讲一下在现有 GT9xx 驱动上做二次开发的路径以及怎么验证改动没把原有功能搞坏。6.1 坐标校准三条路按场景选坐标不对齐有两个常见来源一是屏幕方向装反了二是面板分辨率跟芯片上报的坐标范围不一致。前者用设备树的三个布尔属性就能解决属性作用适用场景touchscreen-inverted-xX 轴左右翻转屏幕装反左右镜像touchscreen-inverted-yY 轴上下翻转屏幕装反上下颠倒touchscreen-swapped-x-yX/Y 交换屏幕旋转 90 度后者则需要在input_set_abs_params()里把 max 设成实际面板分辨率。GT9xx 芯片默认上报的坐标范围是它在配置区0x8047开始的 186 字节里配好的和面板分辨率不一定一致。我一般是在 probe 里读一次配置区把 X/Y 输出最大值打出来如果不是面板分辨率就用input_set_abs_params()做一次线性缩放在驱动里完成省得每次都要重新烧配置。/* 读芯片配置区拿到芯片自己认为的 X/Y 范围 */ u8 cfg[186]; gt9xx_i2c_read(ts, GT9XX_REG_CONFIG, cfg, sizeof(cfg)); int chip_x cfg[1] | (cfg[2] 8); /* 0x8048-0x8049 */ int chip_y cfg[3] | (cfg[4] 8); /* 0x804A-0x804B */ /* 上报之前做一次缩放把芯片坐标映射到面板坐标 */ x (u64)x * ts-max_x / chip_x; y (u64)y * ts-max_y / chip_y;这段逻辑的好处是驱动和模组解耦换一块配置不同的模组不用改代码。代价是多了一次乘除不过 5 个点每帧一次根本感知不到。6.2 自定义手势在中断里加还是在 evdev 上加手势识别有两种加法各自适合不同场景在驱动中断里加识别“双指下滑打开通知栏”“三指捏合截屏”这类固定手势直接在gt9xx_irq_handler()的input_sync()之前插入判断识别到就另外上报一个KEY_*事件。优点是响应快、功耗低缺点是改一次要重新编译内核且手势逻辑跟着驱动走不通用。在应用层加用evdev读事件流在用户态做手势识别。优点是改起来方便、可以热更新缺点是多一次上下文切换灵敏度稍差。我一般推荐应用层做除非是开机就要响应、或者系统里没有用户态可用的场景。手势识别本身不难难点在于识别要和原有触点上报并存不能因为识别手势把正常触点事件给吞了。比较稳的做法是“读取但不消费”evdev读到的事件继续往下传只是额外跑一套手势状态机识别到之后单独调用一次uinput注入一个按键事件。6.3 验证改完必须走一遍的回归清单改完坐标校准或手势光看“能点”是不够的必须跑一遍下面这几条否则老问题回来你可能要几天后才发现evtest /dev/input/eventX看单点按下、移动、抬起事件序列齐全SYN_REPORT出现在每帧结尾。两指同时按下、交错抬起两个 slot 的 id 不串最后一个抬起的 slot 之后 evdev 没有虚点。四个边角都点一遍坐标不越界条件允许的话再把屏幕旋转 180 度重测。连续快速划动 20 秒dmesg里没有i2c timeout、也没有irq nobody cared。断电重启 10 次每次都能在dmesg里看到gt9xx id 911那一行不掉概率。我第一次做 GT9xx 驱动移植的时候只测了单点就交付结果用户第一件事就是两指缩放触点 id 全串了回滚了一版。从那以后我每次改完触摸相关的代码都强制走一遍上面这五条尤其是第 2 条。触摸这种交互类驱动单点正常不代表多点正常一次改动的回归范围比想象中大得多。希望帮到你。本文还有配套的精品资源点击获取