4. 触摸设备驱动

触摸屏在Linux系统中并不是一个“单一设备”,而是由触摸控制器(硬件)→ 总线接口(I2C/SPI/USB)→ 控制器驱动 → 输入子系统(input subsystem)→ 事件节点(/dev/input/eventX)逐层向上抽象而成的完整链路。本章聚焦最贴近硬件的一层:触摸设备驱动。理解这一层,才能解释为什么“屏幕能亮但触摸没反应”“坐标偏移”“单点正常多点丢失”等典型问题。

一、核心原理:三类总线接口的驱动模型

触摸控制器与SoC之间的物理连接方式决定了驱动形态。I2C最常见,SPI用于高刷新率或大尺寸面板,USB则多见于外接触摸屏或一体机。

  • I2C触摸驱动:控制器作为I2C从设备,驱动通过i2c_driver注册,使用regmap或i2c_smbus_read_i2c_block_data读取坐标寄存器。优点是引脚少、协议简单,缺点是带宽有限,多点高帧率时容易成为瓶颈。
  • SPI触摸驱动:通过spi_driver注册,使用spi_sync传输。SPI没有地址概念,靠CS片选区分设备,适合需要高速上报的场合,但中断脚和复位脚必须正确配置。
  • USB触摸驱动:通常遵循HID协议,由usbhid或hid-multitouch统一处理,厂商私有协议则需编写usb_driver。USB设备支持热插拔,枚举信息可通过lsusb -v查看。

无论哪种总线,最终都要调用输入子系统的上报接口:input_report_abs()上报坐标,input_report_key()上报BTN_TOUCH,最后input_sync()提交一帧。缺少input_sync()会导致事件被缓存而不触发。

二、设备树配置:把硬件描述交给内核

现代Linux驱动要求硬件资源通过设备树(Device Tree)描述,而不是硬编码。一个典型的I2C触摸节点如下:

&i2c3 {
    status = "okay";
    clock-frequency = <400000>;

    touchscreen@38 {
        compatible = "edt,edt-ft5406";
        reg = <0x38>;
        interrupt-parent = <&gpio1>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
        reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
        vcc-supply = <&vdd_3v3_tp>;
        touchscreen-size-x = <1024>;
        touchscreen-size-y = <600>;
        touchscreen-inverted-x;
    };
};

关键属性包括:compatible用于匹配驱动,reg是I2C地址,interrupts描述中断触发方式,reset-gpios控制复位时序,touchscreen-size-x/y定义坐标范围。若这些属性缺失或写错,驱动可能加载成功但无法上报有效坐标。

调试设备树时,可用 dtc -I fs /proc/device-tree 反编译当前生效的设备树,确认内核实际看到的内容,而不是只看源码。

三、中断处理与上报事件流程

触摸驱动通常采用“中断+工作队列”模型:中断上下文只做最少的事,把读取坐标的耗时操作放到线程化中断或工作队列中,避免阻塞系统。

static irqreturn_t ts_irq_handler(int irq, void *dev_id)
{
    struct ts_data *ts = dev_id;
    /* 中断上下文:仅标记并唤醒 */
    disable_irq_nosync(irq);
    schedule_work(&ts->work);
    return IRQ_HANDLED;
}

static void ts_work_handler(struct work_struct *work)
{
    struct ts_data *ts = container_of(work, struct ts_data, work);
    ts_read_coordinates(ts);          /* I2C/SPI 读取 */
    input_report_abs(ts->input, ABS_MT_POSITION_X, ts->x);
    input_report_abs(ts->input, ABS_MT_POSITION_Y, ts->y);
    input_report_key(ts->input, BTN_TOUCH, 1);
    input_sync(ts->input);            /* 提交一帧 */
    enable_irq(ts->irq);
}

上报多点触摸时,必须使用MT协议:先input_mt_slot()选择槽位,再上报ABS_MT_TRACKING_ID和坐标,最后input_mt_sync_frame()与input_sync()。漏掉TRACKING_ID会导致手指抬起后槽位无法释放。

触摸设备驱动数据流与中断处理链路 触摸控制器 FT5406 / GT911 总线接口 I2C / SPI / USB 控制器驱动 probe / irq / work 输入子系统 input_dev / event GPIO 中断 IRQ_TYPE_EDGE_FALLING 中断处理程序 schedule_work() 工作队列 读取坐标寄存器 事件上报(MT协议) input_mt_slot → ABS_MT_TRACKING_ID → ABS_MT_POSITION_X/Y input_report_key(BTN_TOUCH) → input_sync() 用户空间 /dev/input/eventX evtest / libinput / Wayland 调试手段 i2cdetect / evtest

四、调试技巧与典型案例

触摸驱动调试有一套成熟工具链,建议按“总线→中断→事件”顺序排查:

排查层次 工具/命令 判断依据
I2C通信 i2cdetect -y 3 地址0x38是否出现,无则查上拉/供电
驱动加载 dmesg | grep -i touch probe是否成功,有无-EPROBE_DEFER
中断触发 cat /proc/interrupts 触摸时中断计数是否增长
事件上报 evtest /dev/input/event2 是否输出ABS_MT_POSITION_X/Y
坐标校准 libinput debug-events 坐标范围与屏幕分辨率是否匹配
常见误区:只改设备树touchscreen-size-x/y而不检查驱动是否读取该属性;或中断触发方式写成IRQ_TYPE_LEVEL_HIGH而硬件实际是下降沿,导致中断风暴或完全无中断。

典型案例:某平板触摸无反应,i2cdetect能看到0x38,但/proc/interrupts中触摸中断计数始终为0。最终发现设备树中interrupt-parent指向了错误的GPIO控制器,中断被路由到无效引脚。修正后中断正常,evtest立即输出坐标事件。

掌握“总线通信—中断触发—事件上报”这条主线,再配合设备树与调试工具,绝大多数触摸驱动问题都能被快速定位。驱动层的稳定,是上层图形栈与手势识别可靠工作的前提。