Linux 多点电容触摸屏

Linux 多点电容触摸屏

电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:
①、IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
②、通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。
老版本的 linux 内核是不支持多点电容触摸的(Multi-touch,简称 MT),MT 协议是后面加入的,因此如果使用 2.x 版本 linux 内核的话可能找不到 MT 协议。MT 协议被分为两种类型,TypeA 和 TypeB。接下来我们要讲的是TypeB,对于TypeA就不做过多赘述了。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。
触摸点的信息通过一系列的 ABS_MT 事件上报给 linux 内核,它的相关事件如下所示:

#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */

在 上 面 这 些 众 多 的 ABS_MT 事 件 中 , 我 们 最 常 用 的 就 是 ABS_MT_SLOT 、ABS_MT_POSITION_X 、 ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID 。其中ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用 来 上报 触 摸点 的 (X,Y) 坐 标 信息 ,
ABS_MT_SLOT 用 来 上 报 触 摸 点 ID ,对于 Type B 类 型 的 设 备 , 需 要 用 到ABS_MT_TRACKING_ID 事件来区分触摸点。
对于 Type B 类型的设备,上报触摸点信息的时候需要通过 input_mt_slot()函数区分是哪一个触摸点。
第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是
哪个触摸点信息。input_mt_slot()函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据。
不管是哪个类型的设备,最终都要调用 input_sync()函数来标识多点触摸信息传输完成,告诉接收者处理之前累计的所有消息,并且准备好下一次接收。
Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果再也不存在了就表示删除了。

Type B 触摸点信息上报时序

对于 Type B 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:

1 ABS_MT_SLOT 0 
2 ABS_MT_TRACKING_ID 45
3 ABS_MT_POSITION_X x[0] 
4 ABS_MT_POSITION_Y y[0] 
5 ABS_MT_SLOT 1 
6 ABS_MT_TRACKING_ID 46
7 ABS_MT_POSITION_X x[1] 
8 ABS_MT_POSITION_Y y[1] 
9 SYN_REPORT

第 1 行,上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID。
第 2 行,Type B 要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID,通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换或删除。具体用到的函数就是 input_mt_report_slot_state。
第 3 行,上报触摸点 0 的 X 轴坐标,使用函数 input_report_abs 来完成。
第 4 行,上报触摸点 0 的 Y 轴坐标,使用函数 input_report_abs 来完成。
第 5-8行,和第 1~4行类似,只是换成了上报触摸点 0 的(X,Y)坐标信息。
第 9 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 input_sync函数来完成。
当一个触摸点移除以后,也需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示:

ABS_MT_TRACKING_ID -1
SYN_REPORT

第1行,当一个触摸点移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成。
第2行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。

多点触摸所使用到的 API 函数

1、input_mt_init_slots 函数
input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,内容如下:

int input_mt_init_slots( struct input_dev *dev, 
					unsigned int num_slots,
					unsigned int flags)

dev: MT 设备对应的 input_dev,因为 MT 设备隶属于 input_dev。num_slots:设备要使用的 SLOT 数量,也就是触摸点的数量。
flags:其他一些 flags 信息,可设置的 flags 如下所示:

#define INPUT_MT_POINTER 		0x0001 
#define INPUT_MT_DIRECT 		0x0002  
#define INPUT_MT_DROP_UNUSED 	0x0004 
#define INPUT_MT_TRACK 			0x0008 
#define INPUT_MT_SEMI_MT 		0x0010 

可以采用‘|’运算来同时设置多个 flags 标识。
返回值:0,成功;负值,失败。
2、input_mt_slot 函数
此函数用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,原型如下:

void input_mt_slot(struct input_dev *dev, int slot)

dev: MT 设备对应的 input_dev。
slot:当前发送的是哪个 slot 的坐标信息,也就是哪个触摸点。
3、input_mt_report_slot_state 函数
此函数用于 Type B 类型,用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件, ABS_MT_TRACKING_ID 事 件 给 slot 关联一个 ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 ),此函数原型如下所示:

void input_mt_report_slot_state( struct input_dev *dev,
						unsigned int tool_type, 
						bool active)

dev: MT 设备对应的 input_dev。
tool_type:触摸类型,可以选择 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(笔)或MT_TOOL_PALM(手掌),对于多点电容触摸屏来说一般都是手指。
active:true,连续触摸,input 子系统内核会自动分配一个ABS_MT_TRACKING_ID 给 slot。false,触摸点抬起,表示某个触摸点无效了,input 子系统内核会分配一个-1 给 slot,表示触摸点溢出。
4、input_report_abs 函数
Type B 类型使用此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报 。函数原型如下:

void input_report_abs( struct input_dev *dev, unsigned int code, int value)

dev: MT 设备对应的 input_dev。
code:要上报的是什么数据,可以设置为 ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,也就是 X 轴或者 Y 轴坐标数据。
value:具体的 X 轴或 Y 轴坐标数据值。
5、input_mt_report_pointer_emulation 函数
如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数量,然后调用 input_mt_report_pointer_emulation 函数将use_count 参数设置为 false。否则的话将 use_count 参数设置为 true,表示当前的触摸点数量(此函数会获取到具体的触摸点数量,不需要用户给出)。函数原型如下:

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

dev: MT 设备对应的 input_dev。
use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量。

多点电容触摸驱动框架

根据前面的分析,我们在编写驱动的时候需要注意一下几点:
①、多点电容触摸芯片的接口基本为 I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般通过中断来上报触摸点坐标信息,因此需要用到中断架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。
根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:
1、I2C 驱动框架

1 /* 设备树匹配表 */
2 static const struct i2c_device_id xxx_ts_id[] = { 3 { "xxx", 0, },
4 { /* sentinel */ } 5 };
6 
7 /* 设备树匹配表 */
8 static const struct of_device_id xxx_of_match[] = { 9 { .compatible = "xxx", },
10 { /* sentinel */ }
11 };
12
13 /* i2c 驱动结构体 */
14 static struct i2c_driver ft5x06_ts_driver = {
15 .driver = {
16 .owner = THIS_MODULE,
17 .name = "edt_ft5x06",
18 .of_match_table = of_match_ptr(xxx_of_match),
19 },
20 .id_table = xxx_ts_id,
21 .probe = xxx_ts_probe,
22 .remove = xxx_ts_remove,
23 };
24
25 /*
26 * @description : 驱动入口函数
27 * @param : 无
28 * @return : 无
29 */
30 static int __init xxx_init(void)
31 {
32 int ret = 0;
33
34 ret = i2c_add_driver(&xxx_ts_driver);
35
36 return ret;
37 }
38
39 /*
40 * @description : 驱动出口函数
41 * @param : 无
42 * @return : 无
43 */
44 static void __exit xxx_exit(void)
45 {
46 i2c_del_driver(&ft5x06_ts_driver);
47 }
48
49 module_init(xxx_init);
50 module_exit(xxx_exit);
51 MODULE_LICENSE("GPL");
52 MODULE_AUTHOR("zb");

2、初始化触摸 IC、中断和 input 子系统

1 static int xxx_ts_probe(struct i2c_client *client, const struct
i2c_device_id *id) 
2 { 
3 struct input_dev *input;
4
5 /* 1、初始化 I2C */
6 ......
7 
8 /* 2,申请中断, */
9 devm_request_threaded_irq(&client->dev, client->irq, NULL,
10 xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
11 client->name, &xxx);
12 ......
13
14 /* 3,input 设备申请与初始化 */
15 input = devm_input_allocate_device(&client->dev);
16
17 input->name = client->name;
18 input->id.bustype = BUS_I2C;
19 input->dev.parent = &client->dev;
20 ......
21 
22 /* 4,初始化 input 和 MT */
23 __set_bit(EV_ABS, input->evbit);
24 __set_bit(BTN_TOUCH, input->keybit);
25
26 input_set_abs_params(input, ABS_X, 0, width, 0, 0);
27 input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
28 input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
29 input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
30 input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);
31 ......
32 
33 /* 5,注册 input_dev */
34 input_register_device(input);
35 ......
36 }

第 5~7 行,首先是初始化触摸芯片,包括芯片的相关 IO,比如复位、中断等 IO 引脚,然后就是芯片本身的初始化,也就是配置触摸芯片的相关寄存器。
第 9 行,因为一般触摸芯片都是通过中断来向系统上报触摸点坐标信息的,因此我们需要初始化中断大家可能会发现第 9 行并没有使用request_irq 函数申请中断,而是采用了 devm_request_threaded_irq 这个函数,为什么使用这个函数呢?是不是 request_irq 函数不能使用?答案肯定不是的,这里用 request_irq 函数是绝对没问题的。那为何要用 devm_request_threaded_irq 呢?这里我们就简单的介绍一下这个 API 函数,devm_request_threaded_irq 函数特点如下:
1.用于申请中断,作用和 request_irq 函数类似。
2.此函数的作用是中断线程化
3.最重要一点使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。
第 15 行,接下来就是申请 input_dev,因为多点电容触摸属于 input 子系统。这里同样使用devm_input_allocate_device 函数来申请 input_dev,也就是我们前面讲解的 input_allocate_device函数加“devm_”前缀版本。申请到 input_dev 以后还需要对其进行初始化操作。
第 23~24 行,设置 input_dev 需要上报的事件为 EV_ABS 和 BTN_TOUCH,因为多点电容屏的触摸坐标为绝对值,因此需要上报 EV_ABS 事件。触摸屏有按下和抬起之分,因此需要上报 BTN_TOUCH 按键。
第 26~29 行,调用 input_set_abs_params 函数设置 EV_ABS 事件需要上报 ABS_X、ABS_Y、ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。单点触摸需要上报 ABS_X 和 ABS_Y,对于多点触摸需要上报 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。
3、上报坐标信息

1 static irqreturn_t xxx_handler(int irq, void *dev_id) 2 { 3 
4 int num; /* 触摸点数量 */
5 int x[n], y[n]; /* 保存坐标值 */
6 
7 /* 1、从触摸芯片获取各个触摸点坐标值 */
8 ......
9 
10 /* 2、上报每一个触摸点坐标 */
11 for (i = 0; i < num; i++) {
12 input_mt_slot(input, id);
13 input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
14 input_report_abs(input, ABS_MT_POSITION_X, x[i]);
15 input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
16 }
17 ......
18 
19 input_sync(input);
20 ......
21 
22 return IRQ_HANDLED;
23 }

进入中断处理程序以后首先肯定是从触摸 IC 里面读取触摸坐标以及触摸点数量,假设触摸点数量保存到 num 变量,触摸点坐标存放到 x,y 数组里面。
第 11~16 行,循环上报每一个触摸点坐标,一定要按照 Type B 类型的时序进行,这个已经在前面进行详细的讲解,这里就不再赘述了。
第 19 行,每一轮触摸点坐标上报完毕以后就调用一次 input_sync 函数发送一个SYN_REPORT 事件。

你可能感兴趣的:(linux,驱动开发,arm开发)