大家好,我是王鸽,这篇文章主要是讲解输入子系统实际应用触摸屏驱动代表是汇顶的触摸屏驱动,属于输入子系统(Input Subsystem)下的触摸屏驱动,广泛应用于手机、平板、工控屏等嵌入式设备。该驱动基于 I2C 子系统(触摸屏 IC 与主控的通信方式)+ 中断子系统(触摸事件触发)实现,核心是将触摸屏的硬件触摸数据转换为输入子系统的标准触摸事件(如绝对坐标、压力、多点触摸)。触摸芯片有四个引脚连接到了单片机,其中scl、sda是IIC通信引脚。RSTN是触摸的复位引脚,配置触摸芯片时也会用到。INT是中断引脚,默认高电平。发生触摸后该引脚会发送一个低电平的脉冲信号。

二、驱动核心定位与依赖
1. 基本信息
- 代码路径:
drivers/input/touchscreen/goodix.c - 核心功能:与 Goodix 触摸屏 IC(如 GT911、GT9271 等)通过 I2C 通信,读取触摸点坐标 / 压力 / 触摸数,转换为输入子系统的
EV_ABS(绝对坐标)事件上报给上层。 - 适用场景:嵌入式 Linux 设备(ARM/ARM64 为主)的电容触摸屏。
| |
|---|
| 主控与触摸屏 IC 之间的核心通信方式(读写寄存器、触摸数据)。 |
| 向上层上报标准化的触摸事件(如 ABS_X/ABS_Y 坐标、多点触摸 MT 事件)。 |
| 触摸屏检测到触摸时,通过 GPIO 向主控发送中断,触发数据读取。 |
| 控制触摸屏的复位(RST)、中断(INT)GPIO,完成 IC 初始化。 |
| 传递硬件配置(I2C 地址、复位 / 中断 GPIO、屏幕分辨率、触摸点数等)。 |
A[GT911硬件中断触发] --> B(驱动读取I2C数据) B --> C{解析触控点坐标} C --> D[input_report_abs(X,Y)] D --> E[Input Core缓冲事件] E --> F{匹配的Handler?} F -- 是 --> G[evdev处理事件] G --> H[/dev/input/eventX] F -- 否 --> I[丢弃或缓存]
设备树
在arch/arm/boot/dts/imx6ull-alientek-emmc.dts设备树i2c3节点下添加gt9xx的节点。
设备树是与硬件连接的通道,它指定了数据传输通过i2c2总线,gt911芯片i2c地址为0x5D,触摸事件的中断引脚和对应的中断号、复位引脚
goodix_ts@5d { // I2C从地址0x5d(Goodix默认) compatible = "goodix,gt9271"; reg = <0x5d>; // I2C地址 interrupt-parent = <&gpio1>; interrupts = <18 IRQ_TYPE_EDGE_FALLING>; // 中断GPIO1_18,下降沿触发 reset-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; // 复位GPIO1_19,低电平有效 goodix,max-touch-number = <5>; // 最大5点触摸 goodix,width = <1024>; // X轴分辨率 goodix,height = <600>; // Y轴分辨率};
二、核心数据结构
驱动的核心上下文通过 goodix_ts_data 结构体管理,是理解流程的关键:
struct goodix_ts_data { struct i2c_client *client; // I2C 客户端(与触摸屏IC通信的核心句柄) struct input_dev *input; // 输入子系统设备句柄 struct device *dev; // 设备节点 struct gpio_desc *reset_gpio; // 复位GPIO描述符 struct gpio_desc *irq_gpio; // 中断GPIO描述符 int irq; // 中断号 u16 abs_x_max; // X轴最大分辨率(如1024) u16 abs_y_max; // Y轴最大分辨率(如600) u8 max_touch_num; // 最大支持触摸点数(如5点) u8 *data_buf; // 触摸数据缓冲区(存储从IC读取的原始数据) size_t data_len; // 数据缓冲区长度 struct work_struct work; // 工作队列(中断上下文转进程上下文) bool suspended; // 休眠状态标记 /* 多点触摸(MT)相关 */ struct input_mt_slot *mt_slots; // MT槽位(每个槽对应一个触摸点) int mt_slot_num; // MT槽位数 /* 固件/配置相关 */ u16 fw_version; // 触摸屏固件版本 u8 config_version; // 配置版本};
三、驱动完整流程拆解
驱动遵循 Linux 设备驱动标准范式:模块注册 → probe 初始化 → 中断触发 → 数据读取解析 → 事件上报 → remove 清理。
第一步:模块注册(I2C 驱动注册)
触摸屏 IC 是 I2C 从设备,因此驱动以I2C 驱动 形式注册,匹配设备树或 I2C 设备 ID:
static const struct i2c_device_id goodix_ts_id[] = { { "GDIX1001:00", 0 }, { }};MODULE_DEVICE_TABLE(i2c, goodix_ts_id);#ifdef CONFIG_ACPIstatic const struct acpi_device_id goodix_acpi_match[] = { { "GDIX1001", 0 }, { "GDIX1002", 0 }, { }};MODULE_DEVICE_TABLE(acpi, goodix_acpi_match);#endif#ifdef CONFIG_OFstatic const struct of_device_id goodix_of_match[] = { { .compatible = "goodix,gt1151" }, { .compatible = "goodix,gt1158" }, { .compatible = "goodix,gt5663" }, { .compatible = "goodix,gt5688" }, { .compatible = "goodix,gt911" }, { .compatible = "goodix,gt9110" }, { .compatible = "goodix,gt912" }, { .compatible = "goodix,gt9147" }, { .compatible = "goodix,gt917s" }, { .compatible = "goodix,gt927" }, { .compatible = "goodix,gt9271" }, { .compatible = "goodix,gt928" }, { .compatible = "goodix,gt9286" }, { .compatible = "goodix,gt967" }, { }};MODULE_DEVICE_TABLE(of, goodix_of_match);#endifstatic struct i2c_driver goodix_ts_driver = { .probe = goodix_ts_probe, .remove = goodix_ts_remove, .id_table = goodix_ts_id, .driver = { .name = "Goodix-TS", .acpi_match_table = ACPI_PTR(goodix_acpi_match), .of_match_table = of_match_ptr(goodix_of_match), .pm = &goodix_pm_ops, },};module_i2c_driver(goodix_ts_driver);
第二步:核心初始化(goodix_ts_probe)
probe 是驱动的入口,完成硬件初始化、资源申请、输入设备创建,步骤如下(核心逻辑):
static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id){ struct device *dev = &client->dev; struct goodix_ts_data *ts_data; int error; // 1. 分配私有数据(核心上下文) ts_data = devm_kzalloc(dev, sizeof(*ts_data), GFP_KERNEL); if (!ts_data) return -ENOMEM; // 2. 初始化基础字段 ts_data->client = client; ts_data->dev = dev; i2c_set_clientdata(client, ts_data); // 绑定I2C客户端与私有数据 // 3. 从设备树解析硬件配置(核心!) error = goodix_parse_dt(ts_data); if (error) return error; // 解析内容:复位/中断GPIO、X/Y分辨率、最大触摸点数、I2C地址等 // 4. 复位触摸屏IC(关键:确保IC初始化到就绪状态) error = goodix_reset(ts_data); if (error) { dev_err(dev, "Failed to reset touchscreen\n"); return error; } // 5. 申请中断(触摸事件触发) ts_data->irq = gpiod_to_irq(ts_data->irq_gpio); error = devm_request_irq(dev, ts_data->irq, goodix_ts_irq_handler, IRQF_TRIGGER_FALLING, // 下降沿触发(触摸时IC拉低INT) "goodix_ts", ts_data); if (error) { dev_err(dev, "Failed to request IRQ %d\n", ts_data->irq); return error; } // 6. 创建输入设备并配置能力 ts_data->input = devm_input_allocate_device(dev); if (!ts_data->input) return -ENOMEM; // 6.1 配置输入设备基础属性 ts_data->input->name = "Goodix Capacitive TouchScreen"; ts_data->input->dev.parent = dev; ts_data->input->id.bustype = BUS_I2C; // 6.2 注册触摸事件能力(告诉输入子系统支持的事件类型) __set_bit(EV_ABS, ts_data->input->evbit); // 绝对坐标事件 __set_bit(EV_KEY, ts_data->input->evbit); // 按键事件(如触摸按下BTN_TOUCH) __set_bit(BTN_TOUCH, ts_data->input->keybit); // 触摸按键 // 6.3 配置X/Y轴范围(从DT解析的分辨率) input_set_abs_params(ts_data->input, ABS_X, 0, ts_data->abs_x_max, 0, 0); input_set_abs_params(ts_data->input, ABS_Y, 0, ts_data->abs_y_max, 0, 0); input_set_abs_params(ts_data->input, ABS_PRESSURE, 0, 255, 0, 0); // 压力 // 6.4 配置多点触摸(MT)支持 input_mt_init_slots(ts_data->input, ts_data->max_touch_num, INPUT_MT_DIRECT); // 7. 初始化工作队列(中断上下文转进程上下文) INIT_WORK(&ts_data->work, goodix_ts_work_func); // 8. 注册输入设备到内核(上层可见/dev/input/eventX) error = input_register_device(ts_data->input); if (error) { dev_err(dev, "Failed to register input device\n"); return error; } dev_info(dev, "Goodix touchscreen initialized (res %dx%d, %d points)\n", ts_data->abs_x_max, ts_data->abs_y_max, ts_data->max_touch_num); return 0;}
关键步骤:
goodix_parse_dt:从设备树读取硬件参数(如 goodix,max-touch-number、reset-gpios);goodix_reset:拉低复位 GPIO 一段时间后释放,完成触摸屏 IC 的硬件复位;input_mt_init_slots:初始化多点触摸槽位,支持多点触摸;input_register_device:注册后,上层可通过 /dev/input/eventX 读取触摸事件。
第三步:中断处理(goodix_ts_irq_handler)
当触摸屏检测到触摸时,IC 拉低中断 GPIO,触发中断处理函数:
staticirqreturn_tgoodix_ts_irq_handler(int irq, void *dev_id){ struct goodix_ts_data *ts = dev_id; // 中断上下文不能做耗时操作(如I2C读取),因此提交到工作队列 disable_irq_nosync(ts->client->irq); queue_work(goodix_wq, &ts->work);// 触发进程上下文的工作函数 return IRQ_HANDLED;}
- 核心设计:中断上下文只做触发,耗时的 I2C 数据读取放到
work 中(进程上下文),避免阻塞中断。
第四步:数据读取与解析(goodix_ts_work_func)
工作队列函数是触摸数据处理的核心,完成「I2C 读数据 → 校验 → 解析」,由中断触发,接受1组坐标数据,校验后再分析输出:
staticvoidgoodix_ts_work_func(struct work_struct *work){//unsigned char start_reg = 0x00;struct goodix_ts_data *ts = container_of(work, struct goodix_ts_data, work);struct goodix_i2c_rmi_platform_data *pdata = ts->client->dev.platform_data; int ret, i, touch_num; // 1. 从I2C读取触摸数据(Goodix IC有固定的数据寄存器地址) ret = i2c_master_recv(ts_data->client, ts_data->data_buf, ts_data->data_len); if (ret != ts_data->data_len) { dev_warn(ts_data->dev, "Failed to read touch data\n"); goto out; } // 2. 数据校验(避免错误数据上报) if (!goodix_check_data(ts_data->data_buf, ts_data->data_len)) { dev_warn(ts_data->dev, "Touch data checksum error\n"); goto out; } // 3. 解析触摸点数(数据包头字节包含触摸点数量) touch_num = ts_data->data_buf[GOODIX_TOUCH_NUM_OFFSET] & 0x0F; // 4. 处理触摸事件 if (touch_num > 0) { // 4.1 标记触摸按下 input_report_key(ts_data->input, BTN_TOUCH, 1); // 4.2 解析每个触摸点的坐标/压力(多点触摸) for (i = 0; i < touch_num; i++) { u16 x, y, pressure; // 从数据缓冲区解析X/Y坐标(Goodix IC数据格式:高字节+低字节) x = (ts_data->data_buf[GOODIX_POINT_OFFSET + i*8 + 1] << 8) | ts_data->data_buf[GOODIX_POINT_OFFSET + i*8 + 2]; y = (ts_data->data_buf[GOODIX_POINT_OFFSET + i*8 + 3] << 8) | ts_data->data_buf[GOODIX_POINT_OFFSET + i*8 + 4]; pressure = ts_data->data_buf[GOODIX_POINT_OFFSET + i*8 + 5]; // 4.3 上报多点触摸事件(MT) input_mt_slot(ts_data->input, i); // 选择第i个MT槽位 input_mt_report_slot_state(ts_data->input, MT_TOOL_FINGER, true); // 标记手指触摸 input_report_abs(ts_data->input, ABS_X, x); // 上报X坐标 input_report_abs(ts_data->input, ABS_Y, y); // 上报Y坐标 input_report_abs(ts_data->input, ABS_PRESSURE, pressure); // 上报压力 } } else { // 无触摸:标记触摸松开 input_report_key(ts_data->input, BTN_TOUCH, 0); // 清空所有MT槽位 input_mt_sync_frame(ts_data->input); } // 5. 同步事件(告诉上层:一组触摸事件已完成) input_sync(ts_data->input);out: // 重新使能中断(等待下一次触摸) enable_irq(ts_data->irq);}
关键细节:
Goodix IC 的数据格式:触摸数据有固定偏移(如触摸点数在第 2 字节,每个点占 8 字节),坐标是 16 位(高字节 + 低字节);
数据校验:通常是数据包的校验和(如最后 1 字节是前面所有字节的和),goodix_check_data 验证合法性;
多点触摸:通过 input_mt_* 系列函数实现,每个触摸点对应一个 MT 槽位。
第五步:驱动清理(goodix_ts_remove)
当设备卸载时,释放资源:
staticintgoodix_ts_remove(struct i2c_client *client){ struct goodix_ts_data *ts_data = i2c_get_clientdata(client); // 1. 取消工作队列 cancel_work_sync(&ts_data->work); // 2. 注销输入设备 input_unregister_device(ts_data->input); // 3. 释放GPIO/中断(devm_前缀的资源由内核自动释放,无需手动) dev_info(&client->dev, "Goodix touchscreen driver removed\n"); return 0;}
四、关键细节补充
- 多点触摸(MT)处理Goodix 驱动依赖输入子系统的 MT API(
input_mt_*),每个触摸点对应一个「槽位(slot)」,确保上层能区分不同手指的坐标。 - 电源管理驱动实现了
goodix_ts_pm_ops,支持休眠(suspend)和唤醒(resume):休眠时关闭中断、复位 IC;唤醒时重新初始化 IC、使能中断。 - 数据校验避免因 I2C 通信异常导致的错误坐标上报,Goodix IC 的数据包末尾有校验和,驱动会验证后再解析。
总结
goodix.c 驱动是典型的 I2C 触摸屏驱动,核心流程可总结为 3 个关键点:
- 初始化链路I2C 驱动注册 → probe 解析 DT 配置 → 复位 IC → 申请中断 / 创建输入设备 → 注册输入设备;
- 事件触发链路触摸产生中断 → 中断触发工作队列 → 工作队列读取 I2C 数据 → 解析校验 → 上报 MT 触摸事件;
- 核心设计中断上下文仅触发工作队列,耗时的 I2C 读写放在进程上下文;通过输入子系统 MT API 实现多点触摸,数据校验保证上报准确性。
五、测试
获取触摸屏信息
root@t2080rdb:~# cat /proc/bus/input/devices I: Bus=0018 Vendor=0416 Product=1001 Version=28bbN: Name="Goodix Capacitive TouchScreen"P: Phys=input/tsS: Sysfs=/devices/platform/ffe000000.soc/ffe119100.i2c/i2c-3/3-0014/input/input0U: Uniq=H: Handlers=event1 B: PROP=2B: EV=bB: KEY=400 0 0 0 0 0B: ABS=265800000000003
获取事件,上下左右,手指按压。

- 触摸屏驱动获取触点坐标
- 调用
input_report_abs()上报坐标事件 - 核心层调用
input_event()分发事件 evdev事件处理器将事件放入缓冲区- 用户空间应用通过
read()获取事件 - 应用程序解析事件并处理