第1章:Linux内核触摸驱动基础
各位同学,欢迎来到我们的实战课程。今天咱们聊聊触摸驱动的根基——Input子系统框架、触摸屏协议,还有设备树配置和中断处理。这些东西,说白了就是触摸驱动的地基。地基打不牢,后面移植起来会非常痛苦。
我记得刚入行那会儿,接手一个老项目的触摸屏移植。当时对Input子系统一知半解,结果中断处理写错了,导致触摸上报时断时续。嗯,那几天加班到凌晨的滋味,至今难忘。所以这一章,我会把关键点掰开揉碎了讲。
1.1 Input子系统框架:三层架构
Linux的Input子系统,其实就三层:
- 核心层(Core):负责设备注册、事件分发。你写驱动时基本不用碰它。
- 事件处理层(Handler):比如evdev、mousedev。把内核事件转成用户空间能读的数据。
- 设备驱动层(Driver):这才是我们写代码的地方。直接跟硬件打交道。
我个人的习惯是,先搞清楚数据流:手指按下 → 硬件中断 → 驱动读取坐标 → 上报给核心层 → 通过evdev传给应用。你想想看,这个链条上任何一个环节出问题,触摸就不灵了。
核心要点:驱动层只需要调用input_report_key()和input_report_abs()这类函数,剩下的核心层帮你搞定。别自己瞎折腾。
1.2 触摸屏协议:单点 vs 多点
触摸协议分两种,这个必须分清楚。搞混了,上报的数据应用层根本看不懂。
| 协议类型 | 特点 | 适用场景 |
|---|---|---|
| 单点触摸(Single Touch) | 一次只报一个点,用BTN_TOUCH + ABS_X/ABS_Y | 老式电阻屏、简单按键 |
| 多点触摸(MT Protocol B) | 支持多个手指,用ABS_MT_POSITION_X/Y + 槽位管理 | 电容屏、手势识别 |
我建议你直接上Protocol B。为什么?现在市面上99%的触摸屏都是多点的。Protocol A虽然也能报多点,但槽位管理不清晰,应用层处理起来容易乱。
举个例子,Protocol B的上报流程:
// 手指按下
input_mt_slot(input_dev, 0); // 槽位0
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
input_report_abs(input_dev, ABS_MT_POSITION_X, x);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
// 第二根手指
input_mt_slot(input_dev, 1);
input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, true);
input_report_abs(input_dev, ABS_MT_POSITION_X, x2);
input_report_abs(input_dev, ABS_MT_POSITION_Y, y2);
// 同步上报
input_sync(input_dev);
避坑指南:我曾经在同步上报前忘了调用input_mt_sync_frame(),结果两根手指的位置老是串。后来查了半天才发现,Protocol B要求每个帧开始前必须调用这个函数。
1.3 设备树配置:让内核认识你的触摸屏
设备树,说白了就是告诉内核:你的触摸屏接在哪个I2C总线上,用哪个中断引脚,电源怎么控制。我见过太多人设备树写错了,驱动死活加载不上。
一个典型的触摸屏设备树节点长这样:
&i2c3 {
status = "okay";
touchscreen@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <1024>;
touchscreen-size-y = <600>;
};
};
这里有几个关键点:
- compatible:必须跟驱动里的of_match_table匹配。拼错了驱动不认。
- reg:I2C地址。别跟其他设备冲突。
- interrupts:中断号和触发方式。我建议用边沿触发,电平触发容易出问题。
注意:我曾经遇到一个坑——设备树里忘了配reset-gpios,结果触摸屏上电后没复位,初始化一直失败。后来加上复位引脚,一切正常。所以电源管理和复位引脚,一定要配全。
1.4 中断处理:触摸响应的关键
触摸屏的中断处理,是驱动里最核心的部分。处理不好,要么丢点,要么响应慢。
标准做法是:
- 在probe函数里注册中断:
devm_request_threaded_irq() - 中断服务函数里只做一件事:唤醒工作队列或中断线程
- 在中断线程或工作队列里读取数据、上报事件
为什么不能直接在中断里读数据?因为I2C读取是慢速操作,在中断上下文里做会阻塞系统。我个人的经验是,用devm_request_threaded_irq()的线程化中断,既简单又安全。
static irqreturn_t touch_irq_handler(int irq, void *dev_id) {
struct touch_device *ts = dev_id;
// 这里只唤醒线程,不做实际工作
return IRQ_WAKE_THREAD;
}
static irqreturn_t touch_irq_thread(int irq, void *dev_id) {
struct touch_device *ts = dev_id;
// 读取触摸数据
touch_read_data(ts);
// 上报事件
touch_report_events(ts);
return IRQ_HANDLED;
}
小技巧:我习惯在中断线程里加一个超时保护。如果连续读取失败超过3次,就复位触摸屏。这个机制帮我解决过不少硬件偶发死锁的问题。
1.5 实战中的常见问题
最后,分享几个我踩过的坑:
- 中断号冲突:设备树里配的中断号,跟其他设备共用了一个引脚。用
cat /proc/interrupts检查一下。 - 坐标范围不对:设备树里配的touchscreen-size-x/y,跟实际硬件不匹配。导致触摸位置偏移。
- 上报频率太低:中断处理里加了太多延时。触摸响应慢得像蜗牛。
嗯,这一章的内容就到这里。记住,触摸驱动移植,Input子系统框架是骨架,协议是血肉,设备树是身份证,中断处理是灵魂。四者缺一不可。
下一章,咱们会深入I2C通信和具体的触摸芯片初始化流程。到时候见。