1. 项目概述从零到一搞定i.MX6ULL的电容触摸驱动搞嵌入式Linux驱动开发特别是做带屏的工控、物联网设备电容触摸屏几乎是标配。最近在基于NXP的i.MX6ULL这颗经典的工业级处理器做项目屏幕驱动点亮了但手指戳上去没反应这体验可就大打折扣了。所以给i.MX6ULL适配电容触摸驱动就成了一个必须啃下来的硬骨头。这个实践分上下两篇上篇我们重点解决驱动框架的搭建、设备树的配置以及最核心的I2C通信和中断处理。很多人觉得驱动开发玄而又玄其实拆解开来就是按照Linux内核定好的“规矩”把硬件的能力“告诉”系统并处理好硬件“喊话”中断的过程。我会结合我实际在i.MX6ULL-ALPHA开发板上调试GT911这款常见触摸IC的经验把每一步的原理、代码和踩过的坑都讲清楚目标是让你看完就能动手在自己的板子上把触摸功能调通。2. 驱动框架选择与设备树关键配置解析2.1 为什么选择Input子系统与I2C总线电容触摸屏驱动在Linux内核中通常基于Input子系统和I2C总线来构建。这不是随便选的而是由硬件特性和软件框架共同决定的。首先从硬件角度看绝大多数电容触摸屏控制器Touch IC比如我们用的GT911都是通过I2C接口与主控CPU通信的。I2C协议简单引脚占用少只需SCL和SDA两根线非常适合这种中低速、小数据量的外设。所以我们的驱动自然要挂载到Linux的I2C总线框架下成为一个I2C客户端设备驱动。其次从功能上看触摸屏是典型的人机交互输入设备。Linux内核为此抽象出了Input子系统专门用来统一管理键盘、鼠标、触摸屏、游戏手柄等各类输入设备。它为上层的应用如GUI、Qt提供了一套简单、统一的访问接口比如在/dev/input/下生成事件设备节点同时为底层驱动定义了标准的注册、上报事件的流程。我们的驱动核心任务就是作为Input子系统的一个“生产者”在检测到触摸事件时按照Input子系统规定的格式比如ABS_MT事件将触摸点的坐标、压力等信息“上报”给它。选择这个框架意味着我们不用自己造轮子去处理设备节点、数据读取线程、事件队列等复杂问题内核已经帮我们做好了。我们只需要聚焦于“如何与GT911芯片通信”以及“如何把读到的原始数据转换成标准输入事件”。这大大降低了开发难度。2.2 设备树Device Tree配置详解设备树是现代ARM Linux驱动的“硬件描述文件”它代替了以前杂乱的板级文件以结构化的文本形式描述了板子上有什么硬件、它们如何连接、以及关键参数是什么。为GT911配置设备树是驱动能正确工作的第一步。假设我们的GT911芯片连接在i.MX6ULL的I2C1总线上从机地址是0x5D这是GT911的默认地址之一具体看ADDR引脚的电平中断引脚连接到了GPIO1_IO09上。我们需要在设备树源文件.dts或.dtsi中找到对应的I2C1控制器节点通常是i2c1然后在其中添加一个子节点来描述我们的触摸芯片。i2c1 { clock-frequency 100000; /* I2C总线速度100kHz */ pinctrl-names default; pinctrl-0 pinctrl_i2c1; /* 引脚复用配置需在pinctrl节点中定义 */ status okay; /* 电容触摸芯片 GT911 */ gt911: touchscreen5d { compatible goodix,gt911; // 驱动匹配的关键字 reg 0x5d; // I2C从机地址 pinctrl-names default; pinctrl-0 pinctrl_tsc; // 触摸屏中断、复位引脚复用配置 interrupt-parent gpio1; // 中断所属的GPIO控制器 interrupts 9 IRQ_TYPE_EDGE_FALLING; // 中断引脚编号和触发方式 reset-gpios gpio1 8 GPIO_ACTIVE_LOW; // 复位引脚低电平有效 irq-gpios gpio1 9 GPIO_ACTIVE_LOW; // 中断引脚低电平有效 /* 以下为GT911特有或可选的属性 */ touchscreen-size-x 800; // 屏幕X方向分辨率 touchscreen-size-y 480; // 屏幕Y方向分辨率 // 有些驱动可能需要配置最大触摸点数 // goodix,max-touch-number 5; }; };这里有几个关键点需要解释compatible属性这是最重要的属性内核通过它来找到匹配的驱动程序。“goodix,gt911”是一个标准的匹配字符串内核中Goodix GT911的驱动会声明自己兼容这个字符串。如果你的内核版本较旧或没有内置驱动可能需要自己编写驱动并声明同样的compatible。reg属性指定I2C设备的7位地址。0x5d就是写入到I2C总线上的地址。务必确认硬件原理图上ADDR引脚的电平它决定了地址是0x5d还是0x14。中断配置(interrupt-parent,interrupts)GT911通常在检测到触摸或有坐标更新时会通过INT引脚产生一个中断信号通知主控。这里我们将其配置为下降沿触发(IRQ_TYPE_EDGE_FALLING)。配置好这里驱动中才能正确申请到中断服务函数。GPIO描述符(reset-gpios,irq-gpios)这是较新的、推荐的方式使用-gpios后缀的属性来描述GPIO。驱动中可以通过标准函数如devm_gpiod_get来获取并控制这些GPIO。复位引脚用于在上电或异常时对GT911进行硬件复位。屏幕参数(touchscreen-size-x/y)这些信息用于将GT911读出的原始坐标值映射到屏幕的实际像素坐标上。必须根据你的显示屏分辨率正确设置。注意pinctrl_i2c1和pinctrl_tsc这两个引脚控制组的配置必须在你板级的Pinctrl节点中正确定义将对应的引脚复用为I2C1功能和GPIO功能。这是很多新手容易遗漏导致驱动probe失败的地方。2.3 驱动代码骨架probe与remove驱动加载时内核会遍历设备树为每个compatible匹配的设备节点调用对应驱动的probe函数。这是驱动初始化的主战场。#include linux/i2c.h #include linux/input.h #include linux/interrupt.h #include linux/gpio/consumer.h #include linux/module.h struct gt911_data { struct i2c_client *client; struct input_dev *input_dev; struct gpio_desc *reset_gpio; struct gpio_desc *irq_gpio; int irq; }; static int gt911_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev client-dev; struct gt911_data *ts; struct input_dev *input_dev; int error; /* 1. 分配驱动私有数据结构 */ ts devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; ts-client client; i2c_set_clientdata(client, ts); /* 2. 获取设备树中配置的GPIO */ ts-reset_gpio devm_gpiod_get(dev, reset, GPIOD_OUT_LOW); if (IS_ERR(ts-reset_gpio)) { dev_err(dev, Failed to get reset GPIO\n); return PTR_ERR(ts-reset_gpio); } ts-irq_gpio devm_gpiod_get(dev, irq, GPIOD_IN); if (IS_ERR(ts-irq_gpio)) { dev_err(dev, Failed to get IRQ GPIO\n); return PTR_ERR(ts-irq_gpio); } /* 3. 硬件复位GT911 */ gpiod_set_value_cansleep(ts-reset_gpio, 0); msleep(20); // 保持低电平一段时间 gpiod_set_value_cansleep(ts-reset_gpio, 1); msleep(100); // 等待芯片稳定 /* 4. 分配并设置Input设备 */ input_dev devm_input_allocate_device(dev); if (!input_dev) return -ENOMEM; ts-input_dev input_dev; input_dev-name GT911 Touchscreen; input_dev-id.bustype BUS_I2C; input_dev-dev.parent dev; /* 5. 设置Input设备能上报的事件类型和坐标范围 */ __set_bit(EV_ABS, input_dev-evbit); __set_bit(EV_KEY, input_dev-evbit); __set_bit(BTN_TOUCH, input_dev-keybit); /* 对于多点触摸使用ABS_MT协议 */ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 800, 0, 0); input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 480, 0, 0); // 如果需要上报触摸面积或压力还需设置 ABS_MT_TOUCH_MAJOR, ABS_MT_PRESSURE 等 input_mt_init_slots(input_dev, 5, INPUT_MT_DIRECT); // 假设支持5点触控 /* 6. 注册Input设备 */ error input_register_device(ts-input_dev); if (error) { dev_err(dev, Failed to register input device: %d\n, error); return error; } /* 7. 配置并申请中断 */ ts-irq gpiod_to_irq(ts-irq_gpio); if (ts-irq 0) { dev_err(dev, Unable to get irq number for GPIO\n); return ts-irq; } error devm_request_threaded_irq(dev, ts-irq, NULL, gt911_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client-name, ts); if (error) { dev_err(dev, Failed to request IRQ %d: %d\n, ts-irq, error); return error; } dev_info(dev, GT911 touchscreen initialized\n); return 0; } static int gt911_remove(struct i2c_client *client) { /* 由于使用了devm_系列函数大部分资源会自动释放 */ dev_info(client-dev, GT911 touchscreen removed\n); return 0; } static const struct of_device_id gt911_of_match[] { { .compatible goodix,gt911 }, { } }; MODULE_DEVICE_TABLE(of, gt911_of_match); static struct i2c_driver gt911_driver { .driver { .name gt911, .of_match_table gt911_of_match, }, .probe gt911_probe, .remove gt911_remove, // .id_table 可省略因为优先使用of_match_table }; module_i2c_driver(gt911_driver);在probe函数中我们完成了驱动生命周期的初始化分配内存、获取硬件资源GPIO、复位硬件、初始化并注册Input设备、最后申请中断。使用devm_Managed Device Resource前缀的函数如devm_kzalloc,devm_gpiod_get,devm_request_threaded_irq是最佳实践它们会自动在设备注销或驱动卸载时释放资源极大地避免了内存泄漏和资源未释放的bug。3. I2C通信与芯片初始化流程3.1 I2C读写函数封装与寄存器访问驱动与GT911的所有交互都通过I2C读写其内部寄存器完成。内核提供了i2c_smbus_read_byte_data和i2c_smbus_write_byte_data等函数用于读写8位寄存器地址和8位数据。对于GT911我们通常需要读写16位的寄存器地址因此需要稍作封装。static int gt911_i2c_read_reg(struct i2c_client *client, u16 reg, u8 *buf, int len) { struct i2c_msg msgs[2]; u8 reg_buf[2]; int ret; /* 将16位寄存器地址转换为大端字节序根据GT911数据手册 */ reg_buf[0] (reg 8) 0xFF; // 高字节在前 reg_buf[1] reg 0xFF; // 低字节在后 /* 消息1写入寄存器地址 */ msgs[0].addr client-addr; msgs[0].flags 0; // 写标志 msgs[0].len 2; msgs[0].buf reg_buf; /* 消息2读取数据 */ msgs[1].addr client-addr; msgs[1].flags I2C_M_RD; // 读标志 msgs[1].len len; msgs[1].buf buf; ret i2c_transfer(client-adapter, msgs, ARRAY_SIZE(msgs)); if (ret ARRAY_SIZE(msgs)) return 0; else if (ret 0) return ret; else return -EIO; } static int gt911_i2c_write_reg(struct i2c_client *client, u16 reg, const u8 *buf, int len) { u8 *tx_buf; int ret; tx_buf kmalloc(len 2, GFP_KERNEL); if (!tx_buf) return -ENOMEM; tx_buf[0] (reg 8) 0xFF; tx_buf[1] reg 0xFF; memcpy(tx_buf[2], buf, len); ret i2c_master_send(client, tx_buf, len 2); kfree(tx_buf); if (ret len 2) return 0; else if (ret 0) return ret; else return -EIO; }这里的关键在于寄存器地址的字节序。一定要仔细查阅GT911的数据手册很多I2C设备是大端字节序Big-Endian即高字节在前低字节在后。我们的封装函数体现了这一点。如果顺序弄反你读写的将是完全错误的寄存器导致驱动无法工作。3.2 上电初始化与配置校验在硬件复位后GT911可能处于一种未知状态。一个健壮的驱动应该在probe函数中或者在首次中断到来时对芯片进行基本的初始化和状态检查。static int gt911_init(struct gt911_data *ts) { struct i2c_client *client ts-client; u8 buf[4]; u16 pid; int error; /* 1. 读取产品ID例如0x3131即“11”的ASCII代表GT911 */ error gt911_i2c_read_reg(client, GT911_REG_ID, buf, 4); if (error) { dev_err(client-dev, Failed to read ID register\n); return error; } pid (buf[0] 8) | buf[1]; // 合并两个字节 if (pid ! GT911_PRODUCT_ID) { dev_err(client-dev, Product ID mismatch: 0x%04X (expected 0x%04X)\n, pid, GT911_PRODUCT_ID); return -ENODEV; // 不是我们要的芯片 } dev_info(client-dev, Found GT911, product ID: 0x%04X\n, pid); /* 2. 读取配置版本检查是否需要更新固件配置 */ error gt911_i2c_read_reg(client, GT911_REG_CONFIG_VERSION, buf, 2); if (error) return error; dev_dbg(client-dev, Config version: %d.%d\n, buf[0], buf[1]); /* 3. 检查芯片状态寄存器确保其已准备就绪 */ error gt911_i2c_read_reg(client, GT911_REG_STATUS, buf, 1); if (error) return error; if ((buf[0] 0x80) 0) { // 最高位为0表示缓冲状态无效 dev_warn(client-dev, Chip not ready, status: 0x%02X\n, buf[0]); // 可以尝试再次软复位或等待 } /* 4. 可选向芯片写入预定义的配置参数 */ // 如果芯片的配置是空的或者我们需要覆盖默认配置如交换XY坐标、调整增益 // 可以在这里将一个配置数组写入到GT911的配置寄存器区域。 // 这通常需要从设备树读取参数或使用一个预定义的配置块。 // error gt911_i2c_write_reg(client, GT911_REG_CONFIG_START, config_data, config_len); // if (error) { // dev_err(client-dev, Failed to write config\n); // return error; // } // msleep(50); // 等待配置生效 // gpiod_set_value_cansleep(ts-reset_gpio, 0); // msleep(20); // gpiod_set_value_cansleep(ts-reset_gpio, 1); // 复位使新配置生效 // msleep(100); return 0; }这个初始化过程完成了“握手”确认我们通过读取一个已知的寄存器如产品ID来确认I2C通信链路是通的并且对面的芯片确实是GT911。这能有效排除I2C地址错误、接线问题等硬件故障。检查状态寄存器可以了解芯片是否已经完成自检并准备好上报数据。实操心得在驱动开发早期强烈建议在probe函数中加入类似的初始化检查并用dev_info或printk打印出关键寄存器的值。这比盲目调试中断和坐标数据要高效得多。如果连ID都读不对后面的所有工作都是徒劳。4. 中断服务函数与原始数据读取4.1 中断处理函数的设计要点当手指触摸屏幕时GT911会拉低INT引脚产生中断。我们的中断服务函数ISR需要快速响应读取触摸数据然后上报给Input子系统。static irqreturn_t gt911_irq_handler(int irq, void *dev_id) { struct gt911_data *ts dev_id; struct i2c_client *client ts-client; u8 status_reg; u8 touch_data[1 8 * 5]; // 状态最多5个触摸点数据 int num_touches, i, error; /* 1. 读取状态寄存器判断是否有有效数据 */ error gt911_i2c_read_reg(client, GT911_REG_STATUS, status_reg, 1); if (error) { dev_err_ratelimited(client-dev, Failed to read status: %d\n, error); goto out; } /* 2. 检查最高位Buffer Ready Flag */ if (!(status_reg 0x80)) { /* 没有新的触摸数据可能是误中断或已处理完 */ goto out; } /* 3. 读取触摸点数量低4位 */ num_touches status_reg 0x0F; if (num_touches 5 || num_touches 0) { // 假设最大支持5点 dev_warn_ratelimited(client-dev, Invalid touch number: %d\n, num_touches); /* 仍然需要读取数据来清除缓冲区否则芯片会一直产生中断 */ num_touches 1; } /* 4. 读取所有触摸点数据 */ error gt911_i2c_read_reg(client, GT911_REG_DATA, touch_data, 1 num_touches * 8); if (error) { dev_err_ratelimited(client-dev, Failed to read touch data: %d\n, error); goto out; } /* 5. 解析并上报每个触摸点 */ for (i 0; i num_touches; i) { u8 *data touch_data[1 i * 8]; // 跳过状态字节 u16 x, y; u8 id, event_flag; id data[0] 0x0F; // 触摸点ID event_flag (data[0] 6) 0x03; // 事件按下、抬起、移动 /* 坐标数据通常是两个字节需要组合 */ x (data[1] 8) | data[2]; y (data[3] 8) | data[4]; /* 上报触摸点信息 */ input_mt_slot(ts-input_dev, id); input_mt_report_slot_state(ts-input_dev, MT_TOOL_FINGER, (event_flag ! TOUCH_EVENT_UP)); if (event_flag ! TOUCH_EVENT_UP) { /* 对于按下和移动事件上报坐标 */ input_report_abs(ts-input_dev, ABS_MT_POSITION_X, x); input_report_abs(ts-input_dev, ABS_MT_POSITION_Y, y); // 如果需要还可以上报压力、面积等 data[5], data[6]... } } /* 6. 同步并上报所有事件 */ input_mt_sync_frame(ts-input_dev); // 关键标记一帧数据结束 input_sync(ts-input_dev); // 关键通知Input子系统数据包完整 /* 7. 清除芯片的中断状态写入0到状态寄存器 */ status_reg 0x00; gt911_i2c_write_reg(client, GT911_REG_STATUS, status_reg, 1); out: return IRQ_HANDLED; }这个中断处理函数是驱动的核心逻辑。有几个关键细节状态寄存器检查必须先读状态寄存器确认有新的数据(0x80位为1)。否则可能是噪声干扰引起的误中断。数据读取长度根据触摸点数量动态计算需要读取的数据长度。GT911的数据格式通常是1字节状态 N个触摸点数据包每个包8字节包含ID、事件、X、Y、压力等。Input MT协议上报input_mt_slot报告当前处理的是哪个触摸点通过ID区分。input_mt_report_slot_state报告该触摸点的状态存在/不存在。input_report_abs报告该触摸点的绝对坐标。input_mt_sync_frame至关重要它告诉Input子系统当前这一帧一次中断周期的所有触摸点信息已经上报完毕。没有这一步上层可能无法正确识别多点触摸。input_sync标记一个完整的输入事件包结束。清除中断标志读取数据后必须向状态寄存器写入特定值通常是0来告知GT911我们已经处理完数据否则它会认为数据未被读取可能持续拉低中断线或不再上报新事件。具体值需查阅数据手册。4.2 数据处理与坐标映射从GT911读出的X和Y坐标是原始值范围是0到某个最大值比如4095取决于芯片的分辨率。我们需要将其映射到屏幕的实际像素坐标上。static void gt911_report_abs(struct gt911_data *ts, u16 raw_x, u16 raw_y) { struct input_dev *input ts-input_dev; int screen_x, screen_y; u32 max_x, max_y; /* 从Input设备获取我们之前设置的坐标范围 */ max_x input_abs_get_max(input, ABS_MT_POSITION_X); max_y input_abs_get_max(input, ABS_MT_POSITION_Y); /* 简单的线性映射。注意可能需要根据屏幕方向调整映射关系 */ screen_x (int)((raw_x * (long)max_x) / GT911_MAX_RAW_X); screen_y (int)((raw_y * (long)max_y) / GT911_MAX_RAW_Y); /* 边界检查 */ screen_x clamp(screen_x, 0, max_x); screen_y clamp(screen_y, 0, max_y); input_report_abs(input, ABS_MT_POSITION_X, screen_x); input_report_abs(input, ABS_MT_POSITION_Y, screen_y); }这里的GT911_MAX_RAW_X和GT911_MAX_RAW_Y需要根据GT911数据手册确定。有时触摸屏的坐标系和显示屏的坐标系方向是反的比如镜像或旋转了180度这时就需要在映射公式中进行调整例如screen_x max_x - (raw_x * max_x / GT911_MAX_RAW_X)。更复杂的校准如非线性补偿、边缘校正通常不在驱动层做而是由上层应用或中间件如TSLIB通过读取原始值并应用校准矩阵来完成。驱动层只需提供稳定的原始数据或简单映射后的数据即可。5. 调试技巧与常见问题排查实录驱动开发离不开调试。即使代码逻辑看起来完美硬件也可能给你“惊喜”。下面是我在调试GT911驱动时遇到的一些典型问题及解决方法。5.1 问题排查速查表现象可能原因排查步骤与解决方法驱动加载成功但/dev/input下无事件节点1. Input设备注册失败。2. Probe函数中途出错返回。1. 检查dmesg看input_register_device是否报错。2. 在probe函数每个可能返回错误的地方加dev_err打印定位失败点。3. 确认内核配置已启用CONFIG_INPUT及相关子选项。有事件节点但cat /dev/input/eventX无输出1. 中断未成功申请或未触发。2. I2C通信失败读不到数据。3. 芯片未正确初始化/复位。1. 检查/proc/interrupts看你的中断号是否有触发计数。2. 用i2cdetect工具扫描I2C总线看能否看到0x5d或0x14地址的设备。3. 在中断处理函数开头加printk看是否进入。4. 在probe中调用初始化函数并打印产品ID确认I2C读写正常。5. 用示波器或逻辑分析仪检查I2C总线和INT中断引脚波形。触摸有反应但坐标完全错乱1. 坐标映射公式错误。2. X/Y坐标字节序弄反。3. 屏幕物理方向与坐标轴不匹配。1. 在中断函数中打印原始坐标值(raw_x, raw_y)观察其变化范围是否合理0~4095左右。2. 检查组合坐标的代码x (data[1]8)|data[2]确保高低字节顺序与数据手册一致。3. 尝试交换X和Y的映射或对坐标进行max - value的反向处理。只能单点触控无法多点1. Input设备的多点触摸配置错误。2. 未正确使用input_mt_sync_frame。3. 芯片配置或固件不支持多点。1. 确认input_mt_init_slots的第二个参数最大点数设置正确且大于1。2.确保在报告完所有触摸点后调用了input_mt_sync_frame。3. 打印每个触摸点的ID看芯片是否上报了不同的ID。触摸不灵敏、跳点、飞线1. 电源噪声或干扰。2. I2C上拉电阻不合适。3. 触摸屏本身或贴合问题。4. 中断处理函数耗时太长丢失数据。1. 检查触摸屏供电是否稳定用万用表量。2. I2C总线的SCL/SDA建议接4.7kΩ上拉电阻到3.3V。3. 确保触摸屏与LCD贴合良好无气泡。4. 优化中断处理函数只做必要的读取和上报复杂处理放到工作队列workqueue中。系统运行一段时间后触摸失灵1. 驱动存在资源泄漏未使用devm_函数。2. 中断风暴或死锁。3. 芯片进入异常状态。1. 检查驱动代码确保所有资源申请都有对应的释放或使用devm_系列函数。2. 在中断处理函数中如果I2C通信失败一定要正确清除芯片的中断状态防止它不断产生中断。3. 增加看门狗或定时器定期检查芯片状态必要时进行软复位。5.2 实用调试命令与工具I2C工具集i2c-tools包是必备的。i2cdetect -l列出所有I2C总线。i2cdetect -y 1扫描I2C1总线上的设备能看到0x5d或0x14吗。i2cget/i2cset直接读写I2C寄存器用于手动验证通信和配置。Input事件查看cat /proc/bus/input/devices查看所有已注册的Input设备找到你的GT911 Touchscreen记下eventX编号。hexdump /dev/input/eventX或使用evtest工具实时查看上报的原始输入事件这是判断驱动是否正常工作的终极手段。你能看到ABS_MT事件流吗内核日志dmesg -w实时查看内核打印信息在你的驱动关键位置probe, init, irq handler加入dev_dbg或dev_info能让你清晰地了解驱动执行流程。硬件调试万用表测量INT和RST引脚电平示波器或逻辑分析仪抓取I2C和中断波形是解决硬件相关问题的杀手锏。可以直观地看到通信是否成功、中断是否产生。5.3 一个典型的调试流程当我拿到一块新板子触摸不工作时我的排查顺序通常是确认硬件连接核对原理图确认I2C和中断引脚连接正确电源已接通。检查设备树pinctrl配置是否正确compatible字符串是否匹配GPIO号有没有写错编译后的dtb文件是否正确烧录验证I2C通信加载驱动前先用i2cdetect扫描确认从机地址能被看到。如果看不到检查I2C总线是否使能、上拉电阻、电平是否匹配i.MX6ULL是3.3V。加载驱动看日志insmod驱动模块观察dmesg输出。Probe成功了吗产品ID读出来正确吗Input设备注册成功了吗检查中断驱动加载后查看/proc/interrupts对应的中断号是否已注册触摸屏幕时该中断的计数是否增加查看Input事件找到对应的eventX用evtest监听。触摸屏幕时是否有事件输出如果没有回到中断函数加打印。如果有事件但坐标不对则打印原始坐标进行映射逻辑调试。上层应用测试用简单的Qt程序或ts_test来自tslib测试触摸功能是否正常。这个过程基本能覆盖90%以上的问题。驱动开发就是这样需要耐心地从硬件到软件从底层到上层一层一层地验证和排查。