Linux驱动_多点电容触摸

一丶Linux下多点电容触摸驱动框架

① 电容触摸屏IC是FT5426,为IIC协议芯片,因此需要编写IIC驱动

② 触摸IC会发出中断信号,并在中断服务函数中上报信息,因此需要编写中断框架

③ 触摸屏向Linux内核上报的信息都属于Input子系统,因此需要编写Input子系统

④ 上报的信息需要满足Input子系统的多点电容触摸协议

二、多点电容触摸协议

        在Documentation/input/multitouch-protocol.txt。

        MT协议主要是分为2种type A和type B。

        type B适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过slot更新某一个触摸点的信息, FT5426就属于此类型。

        在中断函数中我们需要将触摸点的信息上报给Linux内核,在协议文档中介绍了,触摸点的信息通过ABS_MT事件上传给内核的。

        相关事件的宏定义如下:

#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_POSITION_X ABS_MT_POSITION_Y 表示触摸点的X、Y坐标。ABS_MT_SLOT 用来上报触摸点的ID,除此之外,对于我们使用的满足type B类型的IC芯片,还需要使用ABS_MT_TRACKING_ID 关联每一个SLOT信息。

总结来说:对于FT5426芯片的电容屏,上传一个点的信息需要包括四个参数:

ABS_MT_SLOT    //通过input_mt_report_slot_state 函数

ABS_MT_POSITION_X

ABS_MT_POSITION_Y        //通过input_report_abs 函数

ABS_MT_TRACKING_ID       //通过input_mt_report_slot_state 函数

三、 多点触摸使用的API函数

        1、 input_mt_init_slots 函数

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

函数参数和返回值含义如下:

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

void input_mt_slot(struct input_dev *dev,
                    int slot)

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

        3、 input_mt_report_slot_state 函数

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(手掌),对于多点电容触摸屏来说一般都是手指。
        activetrue,连续触摸, input 子系统内核会自动分配一个 ABS_MT_TRACKING_ID 给 slot。
        false,触摸点抬起,表示某个触摸点无效了, input 子系统内核会分配一个-1 给 slot,表示触摸点溢出。
        返回值:无。

        4、 input_report_abs 函数

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

四、搭建驱动框架

        前面介绍了本次驱动需要搭载IIC驱动和中断驱动框架,搭建完毕如下所示:

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


struct ft5x06_dev {
	struct device_node *nd;	//设备节点
	int irq_pin,reset_pin;	//中断引脚,复位引脚
	int irq_num;	//中断号
	void *private_data;
	struct i2c_client *client;	//i2c设备
};

static struct ft5x06_dev ft5x06;

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 = ®					/* 读取的首地址 */
	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;
}


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);
}


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);
}


/*复位FT5X06*/
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;
}

/*中断处理函数*/
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{

	printk("ft5x06_handler\r\n");
	return IRQ_HANDLED;
}

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;
}

static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret = 0;
	printk("ft5426 init\n");

	ft5x06.client = client;	//保存i2c分配的client信息

	/*从设备树获取信息*/
	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);

	ft5x06_ts_reset(ft5x06.client, &ft5x06);
	ft5x06_ts_irq(ft5x06.client, &ft5x06);
	return 0;

}


static int ft5x06_ts_remove(struct i2c_client *client)
{	
	printk("ft5426 exit\n");
	return 0;
}


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

/*
 * 设备树匹配表 
 */
static const struct of_device_id ft5x06_of_match[] = {
	{ .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,
};


static int __init ft5x06_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ft5x06_ts_driver);

	return ret;
}


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("ZYC");

        在驱动框架中利用设备树匹配的方式,完成了IIC设备的申请,触摸IC复位引脚和中断引脚的申请,并且申请方式为devm_gpio_request_one,通过此方式申请的IO不需要自己释放,由系统释放。

        现在加载驱动以后,触碰屏幕就会触发中断处理函数。验证如下:

Linux驱动_多点电容触摸_第1张图片

五、 完善多点电容触摸驱动 

        

#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;	//中断引脚,复位引脚

	int irq_num;	//中断号

	void *private_data;

	struct i2c_client *client;	//i2c设备

	struct input_dev *input;	//input设备

};



static struct ft5x06_dev ft5x06;



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 = ®					/* 读取的首地址 */

	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;

}





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);

}





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);

}





/*复位FT5X06*/

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;

}



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;



}



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;

}





static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)

{

	int ret = 0;

	printk("ft5426 init\n");



	ft5x06.client = client;	//保存i2c分配的client信息



	/*从设备树获取信息*/

	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);



	ft5x06_ts_reset(ft5x06.client, &ft5x06);

	ft5x06_ts_irq(ft5x06.client, &ft5x06);



	/* 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);	//初始化slot信息槽

	if (ret) {

		goto fail;

	}



	ret = input_register_device(ft5x06.input);

	if (ret)

		goto fail;



	return 0;



fail:

	return ret;



}





static int ft5x06_ts_remove(struct i2c_client *client)

{	

	printk("ft5426 exit\n");

	return 0;

}





/*

 *  传统驱动匹配表

 */ 

static const struct i2c_device_id ft5x06_ts_id[] = {

	{ "edt-ft5426", 0, },

	{ /* sentinel */ }

};



/*

 * 设备树匹配表 

 */

static const struct of_device_id ft5x06_of_match[] = {

	{ .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,

};





static int __init ft5x06_init(void)

{

	int ret = 0;



	ret = i2c_add_driver(&ft5x06_ts_driver);



	return ret;

}





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("ZYC");



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