Linux 多点电容触摸屏实验

一、 Linux 下电容触摸屏驱动框架简介

1、多点触摸(MT)协议详解

电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:
①、IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
②、通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。

MT(Multi-touch,简称 MT) 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:
TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

ABS_MT 事件定义在文件 include/uapi/linux/input.h 中,相关事件如下所示:

#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_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 事件来区分触摸点。

对于 TypeA 类型的设备,通过 input_mt_sync()函数来隔离不同的触摸点数据信息,此函数原型如下所示:

void input_mt_sync(struct input_dev *dev)

此函数只要一个参数,类型为 input_dev,用于指定具体的 input_dev 设备。input_mt_sync()函数会触发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据。

对于 Type B 类型的设备,上报触摸点信息的时候需要通过 input_mt_slot()函数区分是哪一个触摸点,input_mt_slot()函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int slot)

此函数有两个参数,第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是哪个触摸点信息。input_mt_slot()函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据。

TypeA 设备,内核驱动需要一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和手指(触摸点)跟踪是在内核空间处理的。

Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果再也不存在了就表示删除了。

Type B 和 Type A 相比最大的区别就是 Type B 可以区分出触摸点, 因此可以减少发送到用户空间的数据。

2、Type A 触摸点信息上报时序

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

ABS_MT_POSITION_X x[0]	//上报第一个触摸点的 X 坐标数据,通过input_report_abs 函数实现
ABS_MT_POSITION_Y y[0]	//上报第一个触摸点的 Y 坐标数据
SYN_MT_REPORT			//上报 SYN_MT_REPORT 事件,通过调用 input_mt_sync 函数来实现
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT

具体实现代码如下:

static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
{
	......
	ret = st1232_ts_read_data(ts);//获取所有触摸点信息
	if (ret < 0)
		goto end;

	/* multi touch protocol */
	for (i = 0; i < MAX_FINGERS; i++) 
	{
		if (!finger[i].is_valid)
		continue;
	
		input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
		input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
		input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
		input_mt_sync(input_dev);
		count++;
	}
	......
	
	/* SYN_REPORT */
	input_sync(input_dev);	//发送一个SYN_REPORT 事件
	end:
	return IRQ_HANDLED;
}

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

ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

第 1 行,上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID,需要由触摸 IC 提供。
第 2 行,根据 Type B 的要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID,通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换或删除。具体用到的函数就是 input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数的第三个参数active 要设置为 true,linux 内核会自动分配一个 ABS_MT_TRACKING_ID 值,不需要用户去指定具体的 ABS_MT_TRACKING_ID 值。
第 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 行,当一个触摸点(SLOT)移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的第三个参数 active 设置为 false 即可,不需要用户手动去设置-1。
第 2 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。

用于上报 ili210x触摸坐标信息的,函数内容如下所示:

static void ili210x_report_events(struct input_dev *input,const struct touchdata *touchdata)
{
	int i;
	bool touch;
	unsigned int x, y;
	const struct finger *finger;

	for (i = 0; i < MAX_TOUCHES; i++) 		//实现所有触摸点上报
	{
		input_mt_slot(input, i);			//上 报 ABS_MT_SLOT 事件

		finger = &touchdata->finger[i];
	 
		touch = touchdata->status & (1 << i);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);//上报ABS_MT_TRACKING_ID 事件,也就是给 SLOT 关联一个 ABS_MT_TRACKING_ID
		if (touch) 
		{
			x = finger->x_low | (finger->x_high << 8);
			y = finger->y_low | (finger->y_high << 8);
	
			input_report_abs(input, ABS_MT_POSITION_X, x);
			input_report_abs(input, ABS_MT_POSITION_Y, y);
		}
	}

	input_mt_report_pointer_emulation(input, false);
	input_sync(input);	//上报 SYN_REPORT 事件
}

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

4.1、input_mt_init_slots 函数

input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,此函数定义在文件 drivers/input/input-mt.c 中,函数原型如下所示:

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 /* pointer device, e.g. trackpad */
#define INPUT_MT_DIRECT 			0x0002 /* direct device, e.g. touchscreen */
#define INPUT_MT_DROP_UNUSED		0x0004 /* drop contacts not seen in frame */
#define INPUT_MT_TRACK 				0x0008 /* use in-kernel tracking */
#define INPUT_MT_SEMI_MT 			0x0010 /* semi-mt device, finger count handled manually */

可以采用‘|’运算来同时设置多个 flags 标识。
返回值:0,成功;负值,失败。

4.2、input_mt_slot 函数

此函数用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数定义在文件 include/linux/input/mt.h 中,函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int slot)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
slot:当前发送的是哪个 slot 的坐标信息,也就是哪个触摸点。
返回值:无。

4.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 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )。 此 函 数 定 义 在 文 件
drivers/input/input-mt.c 中,此函数原型如下所示:

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.4、input_report_abs 函数

TypeA 和 Type B 类型都使用此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报 。 此 函 数 定 义 在 文 件include/linux/input.h 中,函数原型如下所示:

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 轴坐标数据值。
返回值:无。

4.5、input_mt_report_pointer_emulation 函数

如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数量,然后调用 input_mt_report_pointer_emulation 函数将use_count 参数设置为 false。否则的话将 use_count 参数设置为 true,表示当前的触摸点数量(此函数会获取到具体的触摸点数量,不需要用户给出),此函数定义在文件 drivers/input/input-mt.c中,函数原型如下:

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量。
返回值:无。

5、多点电容触摸驱动框架

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

5.1、I2C 驱动框架

/* 设备匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {
	{ "xxx", 0, },
	{ /* sentinel */ }
};

/* 设备树匹配表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx", },
	{ /* sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "edt_ft5x06",
		.of_match_table = of_match_ptr(xxx_of_match),
	},
	.id_table = xxx_ts_id,
	.probe = xxx_ts_probe,
	.remove = xxx_ts_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init xxx_init(void)
{
	int ret = 0;
	
	ret = i2c_add_driver(&xxx_ts_driver);
	
	return ret;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit xxx_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

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

static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct input_dev *input;

	/* 1、初始化 I2C */
	......

	/* 2,申请中断, */
	devm_request_threaded_irq(&client->dev, client->irq, NULL,xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, &xxx);		//中断线程化,防止程序一直进入中断占用CPU,这里单独给他个线程,使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。
	......

	/* 3,input 设备申请与初始化 */
	input = devm_input_allocate_device(&client->dev);	//申请 input_dev,因为多点电容触摸属于 input 子系统。

	input->name = client->name;
	input->id.bustype = BUS_I2C;
	input->dev.parent = &client->dev;
	......

	/* 4,初始化 input 和 MT */
	__set_bit(EV_ABS, input->evbit);	//设置 input_dev 需要上报的事件为 EV_ABS 和 BTN_TOUCH
	__set_bit(BTN_TOUCH, input->keybit);

	input_set_abs_params(input, ABS_X, 0, width, 0, 0);
	input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
	input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);		//初始化多点电容触摸的 slots
	......
 
	/* 5,注册 input_dev */
	input_register_device(input);				//数系统注册前面申请到的 input_dev
	......
}

5.3、上报坐标信息

static irqreturn_t xxx_handler(int irq, void *dev_id)
{

	int num; /* 触摸点数量 */
	int x[n], y[n]; /* 保存坐标值 */
	
	/* 1、从触摸芯片获取各个触摸点坐标值 */
	......
	
	/* 2、上报每一个触摸点坐标 */
	for (i = 0; i < num; i++) {
		input_mt_slot(input, id);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
		input_report_abs(input, ABS_MT_POSITION_X, x[i]);
		input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
	}
	......
	 
	input_sync(input);
	......
	 
	return IRQ_HANDLED;
}

二、试验程序编写

1、修改设备树

1.1、添加 FT5426 所使用的 IO
FT5426 触摸芯片用到了 4 个 IO,一个复位 IO、一个中断 IO、I2C2 的 SCL 和 SDA,所以我们需要先在设备树中添加 IO 相关的信息。

触摸屏的中断引脚信息,修改以后的“pinctrl_tsc”节点内容如下所示:

pinctrl_tsc: tscgrp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
	>;
};

复位引脚配置信息即可,如下所示:

pinctrl_tsc_reset: tsc_reset {
	fsl,pins = <
		MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
	>;
};

找到“pinctrl_i2c2”节点,此节点就是用于描述 I2C2 的 IO 信息,节点内容如下所示:

pinctrl_i2c2: i2c2grp {
	fsl,pins = <
		MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
		MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
	>;
};

确保触摸屏所使用的 IO 没有被其他的外设使用,如果有的话就需要将其屏蔽掉,保证只有触摸屏用到了这四个 IO。

2、添加 FT5426 节点

&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";
	/****************************/
	/* 省略掉其他的设备节点 */
	/****************************/
	 
	/* zuozhongkai FT5406/FT5426 */
	ft5426: ft5426@38 {			//触摸屏所使用的 FT5426 芯片节点,挂载 I2C2 节点下,FT5426 的器件地址为0X38。
		compatible = "edt,edt-ft5426";
		reg = <0x38>;			//reg 属性描述 FT5426 的器件地址为 0x38。
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc
				&pinctrl_tsc_reset >;
		interrupt-parent = <&gpio1>;	//中断 IO 对应的 GPIO 组为 GPIO1
		interrupts = <9 0>;				//述中断 IO 对应的是 GPIO1 组的 IOI09
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 		//复位 IO 对应的 GPIO 为 GPIO5_IO09
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;	//中断 IO 对应的 GPIO 为 GPIO1_IO09
	};
};

3、编写多点电容触摸驱动

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_SUPPORT_POINTS		5			/* 5点触摸 	*/
#define TOUCH_EVENT_DOWN		0x00		/* 按下 	*/
#define TOUCH_EVENT_UP			0x01		/* 抬起 	*/
#define TOUCH_EVENT_ON			0x02		/* 接触 	*/
#define TOUCH_EVENT_RESERVED	0x03		/* 保留 	*/

/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG	0X02		/*	状态寄存器地址 		*/
#define FT5x06_DEVICE_MODE_REG	0X00 		/* 模式寄存器 			*/
#define FT5426_IDG_MODE_REG		0XA4		/* 中断模式				*/
#define FT5X06_READLEN			29			/* 要读取的寄存器个数 	*/

struct ft5x06_dev {
	struct device_node	*nd; 				/* 设备节点 		*/
	int irq_pin,reset_pin;					/* 中断和复位IO		*/
	int irqnum;								/* 中断号    		*/
	void *private_data;						/* 私有数据 		*/
	struct input_dev *input;				/* input结构体 		*/
	struct i2c_client *client;				/* I2C客户端 		*/
};

static struct ft5x06_dev ft5x06;

/*
 * @description     : 复位FT5X06
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	if (gpio_is_valid(dev->reset_pin)) {  		/* 检查IO是否有效 */
		/* 申请复位IO,并且默认输出低电平 */
		ret = devm_gpio_request_one(&client->dev,	
					dev->reset_pin, GPIOF_OUT_INIT_LOW,
					"edt-ft5x06 reset");
		if (ret) {
			return ret;
		}

		msleep(5);
		gpio_set_value(dev->reset_pin, 1);	/* 输出高电平,停止复位 */
		msleep(300);
	}

	return 0;
}

/*
 * @description	: 从FT5X06读取多个寄存器数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->client;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ft5x06地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ft5x06地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ft5x06多个寄存器写入数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->client;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ft5x06地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 向ft5x06指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ft5x06设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ft5x06_write_regs(dev, reg, &buf, 1);
}

/*
 * @description     : FT5X06中断服务函数
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
	struct ft5x06_dev *multidata = dev_id;

	u8 rdbuf[29];
	int i, type, x, y, id;
	int offset, tplen;
	int ret;
	bool down;

	offset = 1; 	/* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
	tplen = 6;		/* 一个触摸点有6个寄存器来保存触摸值 */

	memset(rdbuf, 0, sizeof(rdbuf));		/* 清除 */

	/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
	ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
	if (ret) {
		goto fail;
	}

	/* 上报每一个触摸点坐标 */
	for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
		u8 *buf = &rdbuf[i * tplen + offset];

		/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
		 * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
		 * bit5:4  保留
		 * bit3:0  X轴触摸点的11~8位。
		 */
		type = buf[0] >> 6;     /* 获取触摸类型 */
		if (type == TOUCH_EVENT_RESERVED)
			continue;
 
		/* 我们所使用的触摸屏和FT5X06是反过来的 */
		x = ((buf[2] << 8) | buf[3]) & 0x0fff;
		y = ((buf[0] << 8) | buf[1]) & 0x0fff;
		
		/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
		 * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
		 * bit3:0  Y轴触摸点的11~8位。
		 */
		id = (buf[2] >> 4) & 0x0f;
		down = type != TOUCH_EVENT_UP;

		input_mt_slot(multidata->input, id);
		input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);

		if (!down)
			continue;

		input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
		input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
	}

	input_mt_report_pointer_emulation(multidata->input, true);
	input_sync(multidata->input);

fail:
	return IRQ_HANDLED;

}

/*
 * @description     : FT5x06中断初始化
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	/* 1,申请中断GPIO */
	if (gpio_is_valid(dev->irq_pin)) {
		ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
					GPIOF_IN, "edt-ft5x06 irq");
		if (ret) {
			dev_err(&client->dev,
				"Failed to request GPIO %d, error %d\n",
				dev->irq_pin, ret);
			return ret;
		}
	}

	/* 2,申请中断,client->irq就是IO中断, */
	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
					ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
					client->name, &ft5x06);
	if (ret) {
		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
		return ret;
	}

	return 0;
}

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret = 0;

	ft5x06.client = client;

	/* 1,获取设备树中的中断和复位引脚 */
	ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
	ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

	/* 2,复位FT5x06 */
	ret = ft5x06_ts_reset(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 3,初始化中断 */
	ret = ft5x06_ts_irq(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 4,初始化FT5X06 */
	ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); 	/* 进入正常模式 	*/
	ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); 		/* FT5426中断模式	*/

	/* 5,input设备注册 */
	ft5x06.input = devm_input_allocate_device(&client->dev);
	if (!ft5x06.input) {
		ret = -ENOMEM;
		goto fail;
	}
	ft5x06.input->name = client->name;
	ft5x06.input->id.bustype = BUS_I2C;
	ft5x06.input->dev.parent = &client->dev;

	__set_bit(EV_KEY, ft5x06.input->evbit);
	__set_bit(EV_ABS, ft5x06.input->evbit);
	__set_bit(BTN_TOUCH, ft5x06.input->keybit);

	input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);	     
	ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
	if (ret) {
		goto fail;
	}

	ret = input_register_device(ft5x06.input);
	if (ret)
		goto fail;

	return 0;

fail:
	return ret;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_remove(struct i2c_client *client)
{	
	/* 释放input_dev */
	input_unregister_device(ft5x06.input);
	return 0;
}


/*
 *  传统驱动匹配表
 */ 
static const struct i2c_device_id ft5x06_ts_id[] = {
	{ "edt-ft5206", 0, },
	{ "edt-ft5426", 0, },
	{ /* sentinel */ }
};

/*
 * 设备树匹配表 
 */
static const struct of_device_id ft5x06_of_match[] = {
	{ .compatible = "edt,edt-ft5206", },
	{ .compatible = "edt,edt-ft5426", },
	{ /* sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ft5x06_ts_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "edt_ft5x06",
		.of_match_table = of_match_ptr(ft5x06_of_match),
	},
	.id_table = ft5x06_ts_id,
	.probe    = ft5x06_ts_probe,
	.remove   = ft5x06_ts_remove,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ft5x06_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ft5x06_ts_driver);

	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ft5x06_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

运行测试

depmod //第一次加载驱动的时候需要运行此命令
modprobe ft5x06.ko //加载驱动模块

驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动就会在我所使用的 ALPHA 开发板平台下就会生成/dev/input/event2 这个文件

在这里插入图片描述
上报数据的格式
Linux 多点电容触摸屏实验_第1张图片
多点电容触摸信息含义

/* 编号 */ /* tv_sec */	 	/* tv_usec */ /* type */ /* code */ /* value */
0000000 	02bb 0000 		9459 0007 		0003 	002f 		0000 0000
0000010 	02bb 0000 		9459 0007 		0003 	0039 		0005 0000
0000020 	02bb 0000 		9459 0007 		0003 	0035 		03ec 0000
0000030	 	02bb 0000 		9459 0007 		0003 	0036 		0017 0000
0000040 	02bb 0000 		9459 0007 		0001 	014a 		0001 0000
0000050 	02bb 0000 		9459 0007 		0003 	0000 		03ec 0000
0000060 	02bb 0000 		9459 0007 		0003 	0001 		0017 0000
0000070 	02bb 0000 		9459 0007 		0000 	0000 		0000 0000
0000080 	02bb 0000 		e5f8 0008 		0003 	0039 		ffff ffff
0000090 	02bb 0000 		e5f8 0008 		0001 	014a 		0000 0000
00000a0 	02bb 0000 		e5f8 0008 		0000 	0000 		0000 0000

第 1 行,type 为 0x3,说明是一个 EV_ABS 事件,code 为 0x2f,为 ABS_MT_SLOT,因此这一行就是 input_mt_slot 函数上报的 ABS_MT_SLOT 事件。value=0,说明接下来上报的是第一个触摸点坐标。

第 2 行 , type 为 0x3 , 说 明 是 一 个 EV_ABS 事 件 , code 为 0x39 , 也 就 是ABS_MT_TRACKING_ID ,这一行就是 input_mt_report_slot_state 函 数 上 报ABS_MT_TRACKING_ID 事件。value=5 说明给 SLOT0 分配的 ID 为 5。

第 3 行,type 为 0x3,是一个 EV_ABS 事件,code 为 0x35,为 ABS_MT_POSITION_X,这一行就是 input_report_abs 函数上报的 ABS_MT_POSITION_X 事件,也就是触摸点的 X 轴坐标。value=0x03ec=1004,说明触摸点 X 轴坐标为 1004,属于屏幕右上角区域。

第 4 行,type 为 0x3,是一个 EV_ABS 事件,code 为 0x36,为 ABS_MT_POSITION_Y,这一行就是 input_mt_report_slot_state 函数上报的 ABS_MT_POSITION_Y 事件,也就是触摸点的 Y 轴坐标。value=0x17=23,说明 Y 轴坐标为 23,由此可以看出本次触摸的坐标为(1004,23),处于屏幕右上角区域。

第 5 行,type 为 0x1,是一个 EV_KEY 事件,code=0x14a,为 BTN_TOUCH,value=0x1 表示触摸屏被按下。

三、tslib 移植与使用(测试触摸屏的软件)

你可能感兴趣的:(Linux,驱动以及裸机,linux,驱动开发)