GT9xx触摸屏驱动移植:从I2C、设备树到input多点触控上报
简介面向嵌入式Linux驱动开发者的全志R16平台GT9XX系列触摸屏驱动源码包聚焦电容触摸控制器在内核中的适配与调试。内容围绕Goodix GT9XX芯片展开涉及I2C/SPI通信初始化、中断注册、触点数据解析、input子系统事件上报、电源管理以及固件更新等关键环节并兼顾全志平台中断控制器、时钟与设备树配置的移植要点适合具备一定内核基础、希望打通TP驱动从硬件到应用链路的工程师参考。压缩包共25个文件、约483KB以4个C源文件、2个头文件和Makefile、Kconfig为主同时保留.o、.ko、.cmd等编译中间件与模块产物便于对照构建流程与排查编译问题。已有1156人学习下载。包内代码按驱动主体、固件更新与工具辅助等模块拆分目录组织紧凑读者可从中获得较完整的驱动代码框架、模块编译组织方式与GT9XX调通思路用于项目移植、触摸坐标校准、异常复位分析与性能优化时作参考。1. GT9xx 触摸屏驱动的落地边界它到底接管了哪几层这份 GT9xx 系列触摸屏驱动面向某国产 SoC 平台的 Linux BSP核心不是写一个能读坐标的 demo而是把 tp 驱动完整接进 input 子系统设备树声明 I2C 地址和 INT/RESET 引脚probe 里做上电复位、读配置、注册 input_dev中断线程里按 protocol B 上报多点触控。适合正在调触摸屏 bring-up 的 BSP 工程师、驱动新手以及遇到坐标跳变、中断风暴、触摸无响应要排查的人。它不是通用 HAL换平台要改 DTS、引脚和电源名但寄存器解析和上报框架可以直接复用。先把它当成一条从硬件引脚到/dev/input/eventX的完整链路来看后面每一步才有落点。2. 先立住 input 子系统与 I2C 时序GT9xx 的数据通路怎么选2.1 GT9xx 的三根线I2C 传数据INT 报事件RESET 定状态GT9xx 这类触摸控制器硬件上通常就三组关键信号I2C 的 SCL/SDA 负责读写寄存器INT 引脚在触摸状态变化时通知主控RESET 引脚负责上电复位和地址选择。I2C 是慢速控制通道不适合在中断里长时间占用所以常见做法是 INT 触发中断上半部只确认事件真正的寄存器读取放到线程化中断或工作队列里。这样做的原因是 GT9xx 一次触摸可能连续上报多个点读 0x814E 状态寄存器、再读 0x8150 起始的触点数据全程走 I2C放在硬中断上下文里容易把系统拖出硬实时问题。I2C 地址常见是 7 位 0x5D 或 0x14具体由复位时 INT 引脚电平决定。很多翻车不是驱动写错而是 DTS 里把 8 位地址 0xBA 填进reg或者复位时序没给够芯片进了另一个地址。I2C 频率建议先降到 100kHz 验证确认通路后再升到 400kHz。上拉电阻、走线长度、电源时序都会影响 ACK先用 i2c-tools 扫到地址再谈 probe。# 确认 I2C 控制器编号和地址bus 1 只是示例按实际板子改 i2cdetect -y 1 # 读 GT9xx 产品 ID寄存器 0x8140读 4 字节 # w20x5d 表示向 0x5d 写 2 字节寄存器地址r4 表示随后读 4 字节 i2ctransfer -f -y 1 w20x5d 0x81 0x40 r4 # 读配置表版本寄存器 0x8047读 1 字节 i2ctransfer -f -y 1 w20x5d 0x80 0x47 r1上面第一条命令只验证总线上有没有设备响应第二条才是验证寄存器通路。i2ctransfer的w20x5d是写两个字节的寄存器地址r4是重复起始后读四个字节。读出来如果是全0x00或全0xFF先别改驱动回头查 RESET 时序和 INT 电平。产品 ID 和配置版本能稳定读出来才说明 I2C 层通了。2.2 为什么用 protocol Bslot 和 tracking_id 是多点触控的骨架GT9xx 支持多点触控input 子系统里对应的是 MT protocol B。protocol A 是每次上报所有触点驱动自己维护状态protocol B 用 slot 绑定触点每个 slot 有独立的ABS_MT_TRACKING_ID手指按下时分配 id抬起时把 tracking_id 置为 -1。GT9xx 的触点数据里带 track id天然适合 B 协议。常见做法是input_mt_init_slots()初始化最大触点数触摸线程里用input_mt_slot()切槽input_mt_report_slot_state()报按下/抬起再填ABS_MT_POSITION_X/Y和ABS_MT_TOUCH_MAJOR最后input_mt_sync_frame()加input_sync()收尾。选 B 协议的理由很实际上层手势库、Qt、Android 的 InputReader 都按 slot 取点A 协议需要额外兼容层B 协议下每个 slot 的 id 稳定滑动手势不会因为触点顺序变化而断。参数上要设ABS_MT_POSITION_X的 min/maxmax 来自配置表 0x8048 和 0x804A 的 X/Y 分辨率不要拍脑袋写 1920。ABS_MT_TOUCH_MAJOR对应触点面积GT9xx 的 size 字段可以直接映射范围按 0 到 255 或 0 到配置值。slot 数量不要超过芯片配置的触摸点数否则会出现空 slot 上报。2.3 设备树先把硬件描述清楚驱动只做翻译设备树不是形式主义它决定 probe 时拿到的client-irq、GPIO 编号、电源名和 I2C 地址。常见写法是把触摸屏挂在 I2C 控制器下reg用 7 位地址interrupt-parent指向 GPIO 中断控制器interrupts写引脚号和触发方式reset-gpios和int-gpios按实际原理图填。电源名要和 regulator 节点一致否则devm_regulator_get_optional()拿不到上电时序就会缺一步。i2c1 { status okay; clock-frequency 400000; gt9xx: touchscreen5d { compatible vendor,gt9xx; reg 0x5d; interrupt-parent pio; interrupts 7 1 IRQ_TYPE_EDGE_FALLING; reset-gpios pio 7 0 GPIO_ACTIVE_LOW; int-gpios pio 7 1 GPIO_ACTIVE_LOW; vdd-supply reg_tp_3v3; iovdd-supply reg_tp_1v8; touchscreen-size-x 1024; touchscreen-size-y 600; touchscreen-swapped-x-y; status okay; }; };这段 DTS 里reg 0x5d是 7 位地址不是 0xBAinterrupts的触发方式要和硬件一致GT9xx 的 INT 一般是下降沿有效reset-gpios用GPIO_ACTIVE_LOW表示低电平复位驱动里gpiod_set_value_cansleep(reset, 1)是拉低。touchscreen-size-x/y可以先按屏幕分辨率填但最终要以配置表读出的 max_x/max_y 为准。touchscreen-swapped-x-y这类通用属性用于旋转不要一上来就在代码里写死坐标变换。3. 从设备树到 probe把 GT9xx 挂到国产 SoC 的 I2C 总线上3.1 probe 的第一件事不是读坐标而是把电和复位管好I2C 驱动 probe 时设备树已经帮你把client建好但芯片不一定已经上电。顺序通常是拿 regulator、拿 GPIO、上电、拉复位、等芯片启动、读产品 ID。很多“I2C 能扫到但读寄存器失败”的案例都是 probe 里只做了i2c_get_clientdata忘了给vdd和iovdd上电或者复位释放后延时不够。GT9xx 的复位时序不用追求某个神秘数字但要有明确的拉低、释放、等待窗口常见做法是拉低 10ms、释放后等 50 到 100ms再读寄存器。static int gt9xx_probe(struct i2c_client *client) { struct gt9xx_data *ts; int ret; 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); ts-vdd devm_regulator_get_optional(ts-dev, vdd); ts-iovdd devm_regulator_get_optional(ts-dev, iovdd); if (IS_ERR(ts-vdd) || IS_ERR(ts-iovdd)) return -EPROBE_DEFER; ts-reset_gpio devm_gpiod_get_optional(ts-dev, reset, GPIOD_OUT_HIGH); if (IS_ERR(ts-reset_gpio)) return PTR_ERR(ts-reset_gpio); if (ts-vdd) regulator_enable(ts-vdd); if (ts-iovdd) regulator_enable(ts-iovdd); msleep(10); if (ts-reset_gpio) { gpiod_set_value_cansleep(ts-reset_gpio, 1); /* 拉低复位 */ msleep(10); gpiod_set_value_cansleep(ts-reset_gpio, 0); /* 释放复位 */ msleep(60); } ret gt9xx_read_id(ts); if (ret) { dev_err(ts-dev, read gt9xx id failed: %d\n, ret); return ret; } return gt9xx_register_input(ts); }这段 probe 的重点是错误路径要能回滚。regulator 用devm_regulator_get_optional()是因为有些板子只给一路电源拿不到不应该直接失败但-EPROBE_DEFER要保留电源节点可能晚于触摸屏加载。GPIOD_OUT_HIGH结合 DTS 的GPIO_ACTIVE_LOW表示初始状态为无效电平也就是不复位。msleep(60)是给芯片内部固件启动的时间具体值可以按手册调整但不要省。gt9xx_read_id()失败时不要继续注册 input否则上层会看到一个永远没事件的设备。3.2 读产品 ID 和配置表确认 I2C 不是“假通”I2C 能 ACK 不等于寄存器能读。GT9xx 的产品 ID 在 0x8140配置表版本在 0x8047分辨率在 0x8048 到 0x804B。读产品 ID 可以判断芯片是否在正常工作模式读配置表可以拿到 max_x、max_y、触摸点数。常见做法是 probe 时读 4 字节产品 ID再读 1 字节配置版本如果产品 ID 全 0 或全 FF直接返回-ENODEV。配置表不一定要在 probe 里全读但分辨率建议读出来后面注册 input 时用。static int gt9xx_i2c_read(struct gt9xx_data *ts, u16 reg, u8 *buf, int len) { u8 reg_buf[2] { reg 8, reg 0xff }; struct i2c_msg msgs[2] { { .addr ts-client-addr, .flags 0, .len 2, .buf reg_buf }, { .addr ts-client-addr, .flags I2C_M_RD, .len len, .buf buf }, }; int ret i2c_transfer(ts-client-adapter, msgs, 2); return ret 2 ? 0 : (ret 0 ? ret : -EIO); } static int gt9xx_read_id(struct gt9xx_data *ts) { u8 id[4]; u8 cfg_ver; int ret; ret gt9xx_i2c_read(ts, 0x8140, id, sizeof(id)); if (ret) return ret; if ((id[0] 0x00 id[1] 0x00) || (id[0] 0xff id[1] 0xff)) { dev_err(ts-dev, invalid gt9xx id: %02x %02x\n, id[0], id[1]); return -ENODEV; } ret gt9xx_i2c_read(ts, 0x8047, cfg_ver, 1); if (ret) return ret; dev_info(ts-dev, gt9xx id%02x%02x cfg_ver%02x\n, id[0], id[1], cfg_ver); return 0; }gt9xx_i2c_read()用两个i2c_msg组成“写寄存器地址 读数据”的复合传输i2c_transfer()返回 2 表示两条消息都成功。reg_buf是栈上变量i2c_transfer()同步执行生命周期没问题。产品 ID 的判断只是兜底更严格的做法是对照芯片手册的 ID 范围。配置版本读出来可以打印后续如果触摸参数不对先看这个版本和配置文件是否匹配。3.3 注册 input_dev把能力集和 slot 数量一次设对input 设备注册要在中断申请之前或之后都行但建议先注册 input再申请中断避免中断先来、input 还没准备好。能力集要设ABS_MT_POSITION_X/Y、ABS_MT_TOUCH_MAJOR如果要上报压力可以加ABS_MT_PRESSURE。input_mt_init_slots()的 slot 数量取配置表里的触摸点数常见是 5 或 10不要固定写 10。INPUT_MT_DIRECT表示直接触摸设备上层会按屏幕坐标处理。static int gt9xx_register_input(struct gt9xx_data *ts) { int ret; ts-input devm_input_allocate_device(ts-dev); if (!ts-input) return -ENOMEM; ts-input-name gt9xx_ts; ts-input-id.bustype BUS_I2C; ts-input-dev.parent ts-dev; input_set_abs_params(ts-input, ABS_MT_POSITION_X, 0, ts-max_x, 0, 0); input_set_abs_params(ts-input, ABS_MT_POSITION_Y, 0, ts-max_y, 0, 0); input_set_abs_params(ts-input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); ret input_mt_init_slots(ts-input, ts-max_touch, INPUT_MT_DIRECT); if (ret) return ret; ret input_register_device(ts-input); if (ret) return ret; return 0; }ts-max_x和ts-max_y应该来自配置表 0x8048 到 0x804B而不是 DTS 里的屏幕分辨率。屏幕分辨率和触摸有效区可能不一致尤其是带黑边的屏。input_mt_init_slots()返回负数要直接退出不能忽略。input_register_device()之后设备节点才会出现后面用evtest验证时看的是这个节点。4. 中断、坐标解析与 protocol B 上报手指动作如何变成 input 事件4.1 中断上半部只做确认I2C 读取放到线程里GT9xx 的 INT 是电平/边沿信号触摸状态变化时拉低主机读走数据后写 0 到 0x814E 清除。硬中断里不能做长 I2C 传输常见做法是devm_request_threaded_irq()primary handler 返回IRQ_WAKE_THREADthread handler 里读状态和坐标。触发方式用IRQF_TRIGGER_FALLING | IRQF_ONESHOTIRQF_ONESHOT保证线程处理完之前中断线不被重复触发避免中断风暴。如果硬件是电平触发DTS 和代码要一致否则会出现“读一次进一次、读不完”的现象。static irqreturn_t gt9xx_irq_primary(int irq, void *dev_id) { struct gt9xx_data *ts dev_id; if (gpiod_get_value_cansleep(ts-int_gpio) 0) return IRQ_WAKE_THREAD; return IRQ_NONE; } static int gt9xx_request_irq(struct gt9xx_data *ts) { return devm_request_threaded_irq(ts-dev, ts-client-irq, gt9xx_irq_primary, gt9xx_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, gt9xx_ts, ts); }gt9xx_irq_primary()只读一次 INT 引脚电平低电平才唤醒线程高电平返回IRQ_NONE。这一步能过滤掉抖动和误触发。IRQF_ONESHOT很重要线程没处理完之前不会再次进入中断。ts-client-irq来自 DTS 的interrupts如果 probe 里拿到 0说明 DTS 中断映射没配好先别怀疑芯片。4.2 读 0x814E 和 0x8150状态位、触点数、8 字节触点GT9xx 的状态寄存器 0x814E 最高位是 buffer ready低 4 位是当前触点数。读到 ready 后从 0x8150 开始每个触点 8 字节第 1 字节是 track id第 2 到 3 字节是 X 低/高第 4 到 5 字节是 Y 低/高第 6 到 7 字节是 size 低/高第 8 字节保留。坐标按 16 位拼实际有效位宽以配置表为准。读完所有触点后必须向 0x814E 写 0 清状态否则下一次中断不会来或者一直重复上报同一帧。static irqreturn_t gt9xx_irq_thread(int irq, void *dev_id) { struct gt9xx_data *ts dev_id; u8 status; u8 zero 0; int ret, i, num; ret gt9xx_i2c_read(ts, 0x814E, status, 1); if (ret) return IRQ_HANDLED; if (!(status 0x80)) return IRQ_HANDLED; num status 0x0f; if (num ts-max_touch) num ts-max_touch; for (i 0; i ts-max_touch; i) { u8 point[8]; int id, x, y, w; if (i num) { input_mt_slot(ts-input, i); input_mt_report_slot_state(ts-input, MT_TOOL_FINGER, false); continue; } ret gt9xx_i2c_read(ts, 0x8150 i * 8, point, 8); if (ret) continue; id point[0] 0x0f; x point[1] | (point[2] 8); y point[3] | (point[4] 8); w point[5] | (point[6] 8); 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, w); } input_mt_sync_frame(ts-input); input_sync(ts-input); gt9xx_i2c_write(ts, 0x814E, zero, 1); return IRQ_HANDLED; }这段代码里status 0x80判断有没有新数据status 0x0f取触点数。循环用ts-max_touch而不是num是为了把未按下的 slot 显式上报抬起避免上层残留触点。id取低 4 位如果芯片上报的 id 超出 slot 数量要做取模或丢弃不能直接越界。input_mt_sync_frame()会处理 slot 状态input_sync()提交事件帧。最后写 0 清状态这一步漏了就是血泪教训看起来能摸但第二次触摸没反应。4.3 坐标映射、旋转和边缘抖动别在中断里写死坐标映射分两层硬件配置表给的是触摸有效区DTS 给的是屏幕方向。常见旋转用touchscreen-swapped-x-y、touchscreen-inverted-x、touchscreen-inverted-y描述驱动里读这些属性再换算。不要在中断线程里写if (board xxx)板子一换就翻车。边缘抖动可以用clamp限制到 0 到 max再用简单的滑动窗口或input_set_abs_params的 fuzz 参数过滤。fuzz 设 2 到 4 个像素能过滤掉一部分 ADC 噪声但不要设太大否则细小的手势会丢。static void gt9xx_map_point(struct gt9xx_data *ts, int *x, int *y) { if (ts-swap_xy) { int tmp *x; *x *y; *y tmp; } if (ts-invert_x) *x ts-max_x - *x; if (ts-invert_y) *y ts-max_y - *y; *x clamp(*x, 0, ts-max_x); *y clamp(*y, 0, ts-max_y); }swap_xy要在invert之前做顺序反了旋转方向会不对。max_x/max_y用配置表读出的值不要用屏幕分辨率。clamp放在最后防止旋转后越界。如果坐标在边缘来回跳先看evtest里的原始值再决定是加 fuzz 还是改配置表。不要一上来就在驱动里加平均滤波触摸响应会变慢。5. 避坑与排查GT9xx 在国产 SoC 板子上最容易翻车的五个点5.1 上电、复位与 INT 电平的坑现象i2cdetect能看到 0x5D但 probe 读 0x8140 全是 0xFF或者读几次后 I2C 超时。原因复位时序不对或者复位时 INT 电平把芯片带进了另一个 I2C 地址也有可能是vdd和iovdd上电顺序不对芯片内部还没启动。解决先用示波器看 RESET 和 INT 的上电波形确认复位拉低至少 10ms、释放后等 50ms 以上DTS 里reset-gpios的极性要和原理图一致如果芯片支持地址选择复位期间把 INT 拉到确定电平。不要只靠msleep猜读产品 ID 成功才算过。现象冷启动时触摸可用热重启后 probe 失败。原因电源没有完全掉电芯片还在上一次的状态里复位释放太快。解决在 probe 里先关一次 regulator 再开或者加长复位低电平时间确认reset-gpios初始状态不是释放。热重启的坑很隐蔽建议在压力测试里加 reboot 循环。5.2 I2C 读写与配置表的坑现象i2c_transfer返回-ENXIO或-EIO但地址扫描正常。原因DTS 的reg写成了 8 位地址 0xBAI2C 框架按 7 位地址发当然不通或者 I2C 频率 400kHz 下上升沿太慢ACK 不稳定。解决reg 0x5d不是 0xBA先把clock-frequency降到 100000 验证再逐级升检查 SDA/SCL 上拉常见是 2.2k 到 4.7k具体看总线电容。i2c-tools 的i2cdetect只能证明地址有 ACK不能证明寄存器可读。现象读配置表分辨率max_x/max_y 和屏幕分辨率对不上坐标只在左上角一小块。原因配置表里的 X/Y 输出最大值不是屏幕分辨率而是触摸有效区或者读错了寄存器偏移0x8048 是 X 低字节0x8049 是 X 高字节0x804A/0x804B 是 Y。解决用i2ctransfer连续读 0x8047 起 8 字节按小端拼出 max_x/max_y注册 input 时用这个值不要用 DTS 的touchscreen-size-x/y硬顶。换屏后一定要重读配置表。5.3 中断与 input 上报的坑现象触摸能用但手指抬起后evtest里一直有触点或者第二次触摸没反应。原因读完 0x814E 没有写 0 清状态芯片认为 buffer 还没被取走或者未按下的 slot 没有上报TRACKING_ID -1。解决每次线程结束前向 0x814E 写 0循环覆盖所有 slot对i num的 slot 调用input_mt_report_slot_state(..., false)确认调用了input_mt_sync_frame()。这三个动作缺一个都会出现“幽灵触点”。现象中断风暴CPU 占用飙高dmesg里能看到中断计数疯涨。原因触发方式配成了电平触发但线程里没有清中断源或者IRQF_ONESHOT没加线程还没读完又来中断。解决DTS 用IRQ_TYPE_EDGE_FALLING代码用IRQF_TRIGGER_FALLING | IRQF_ONESHOTprimary handler 里读 INT 电平高电平返回IRQ_NONE线程里确保读状态并写 0。如果硬件是低电平保持要和硬件同事确认能否改成边沿。现象坐标方向反了或者旋转 90 度后 X/Y 对调但方向不对。原因touchscreen-swapped-x-y和touchscreen-inverted-x/y的组合顺序错了或者 max_x/max_y 用错。解决先在evtest里看原始坐标确认是交换还是反相驱动里先 swap 再 invertmax 值用配置表的值。不要在代码里写死x 1024 - x换屏就废。6. 进阶验证与固化用 evtest、i2c-tools 和压力测试把驱动压稳6.1 三层验证从总线到事件节点我一般分三层验证不跳步。第一层用i2cdetect和i2ctransfer确认地址和产品 ID第二层用evtest看原始事件确认 slot、tracking_id、坐标范围第三层用压力脚本连续画圈、多点按压、反复插拔观察dmesg有没有 I2C 超时和中断异常。三层都过再谈交付。下面这张表是我常用的判据验证层命令/工具看什么不通过先查I2C 通路i2cdetect、i2ctransfer地址 ACK、产品 ID 非全 0/FF地址、复位、上拉input 事件evtest /dev/input/eventXslot、tracking_id、ABS_MT 坐标状态清 0、slot 上报稳定性while循环 dmesg -wI2C 超时、中断计数、丢点中断触发方式、电源# 找到触摸屏事件节点名字通常是 gt9xx_ts evtest # 只看指定节点按 CtrlC 退出 evtest /dev/input/event2 # 压力测试连续读事件观察有没有卡死 timeout 60 cat /dev/input/event2 /dev/nullevtest里重点看ABS_MT_SLOT和ABS_MT_TRACKING_ID手指按下时 tracking_id 应该是一个非负值抬起时变成 -1。如果只有ABS_MT_POSITION_X/Y没有 slot说明 protocol B 没配对。timeout 60 cat是最粗糙的压力测试能暴露中断风暴和 I2C 卡死但不会模拟多点手势正式测试还是要用上层应用画圈。6.2 把参数固化DTS 管硬件配置文件管触摸参数DTS 里只放硬件事实I2C 地址、中断触发方式、GPIO 极性、电源名、屏幕方向。触摸配置表里的 max_x/max_y、触摸点数、固件版本放在配置文件或驱动内置数组里不要混进 DTS。这样换屏时只换配置文件换板时只改 DTS边界清楚。我吃过一次亏板子换屏后只改了touchscreen-size-x忘了改配置表里的分辨率坐标在右边缘被截断查了半天才发现 max_x 还是旧值。从那以后我每次 bring-up 都先跑i2ctransfer读 0x8048再跑evtest看原始值最后才动映射。希望帮到你。本文还有配套的精品资源点击获取