Linux下自己完成的ft5x06触摸屏驱动(tiny4412开发板)

触摸屏驱动
a.先来了解下基本知识
1.ft5x6触摸屏驱动通过I2C接口与CPU进行连接,服务于I2C总线,同时触摸屏属于又是输入设备,因此又隶属于输入子系统。
2.ft5x06通过I2C接口连接CPU,直接将数字信号放入内部寄存器,(一些触摸屏管脚发出电压值为模拟信号,需要接ADC转换至数字信号供CPU读取)
3.ft5x06触摸屏的各个寄存器如下图
Linux下自己完成的ft5x06触摸屏驱动(tiny4412开发板)_第1张图片
Linux下自己完成的ft5x06触摸屏驱动(tiny4412开发板)_第2张图片
由寄存器表可以知道,当前按键的个数在02h地址的第四位,每个触摸点的X值的高8位都是在该地址内容的低4位,低8位在下一个地址中(用于之后的原始数据拼凑成X,Y值和触摸点个数值)

b.直接上代码(代码中有很详细的注释)
1.触摸屏驱动程序(文件名:tiny_ts.c)

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


#include 
#include 
#include 
#include 

#define TS_NAME "ft5x0x_ts"
#define FT_FT5X0X_PT_MAX 5 //多点触摸最大值

struct ts_info{
	int x;
	int y;
	int id;
};

struct ts_desc{
	int irq;
	struct i2c_client* ts_cli;
	struct input_dev* ts_input;
	int x_max; //触摸屏最大的X值,用于设置input_dev
	int y_max; //触摸屏最大的Y值, 用于设置input_dev
	int pressure_max; //触摸屏最大的压力值
	int ts_num; //当前触摸的点数
	struct ts_info ts_info[FT_FT5X0X_PT_MAX]; //触摸点信息的数组,最大可产生5个触摸点
	struct work_struct ts_work_queue;
};

static struct ts_desc *ts;



/*I2C读取函数
*client:i2c_lient结构体
*buf:需要读取的地址
*count:需要读取寄存器的个数
*/
static int ts_i2c_read(struct i2c_client* client, char *buf, int count)
{
	/*分析:传参过来buf[0] == 0
	*然后先写,当len = 1时,表示写入buf数组的第一个元素buf[0]
	*写入的第一个元素为0值,意思是告诉寄存器我要从0x00地址开始读取数据了
	*然后再读,读取长度为count,表示我要从我刚才写入进去的0x00地址开始读取
	*读取count字节数据(等于读取了0x00地址到0x00偏移了count-1地址空间的数据)
	*/
	struct i2c_adapter *adapt;
	struct i2c_msg ts_msg[2];
	
	adapt = client->adapter;
	ts_msg[0].addr = client->addr;
	ts_msg[0].flags = 0; //写,告诉触摸屏我要读哪个地址的数据
	ts_msg[0].buf = buf; 
	ts_msg[0].len = 1; //长度为1,表明写入第一个数组的元素buf[0] = 0;
						//代表我要从0地址开始读取数据
	ts_msg[1].addr = client->addr;
	ts_msg[1].flags = I2C_M_RD;
	ts_msg[1].buf = buf; //读取的数据存入的数组
	ts_msg[1].len = 31; //读取从0地址最多偏移30字节的地址全部的数据,存入buf
	
	int ret = i2c_transfer(adapt, ts_msg, sizeof(ts_msg) / sizeof(ts_msg[0]));

	if(ret != 2)
		printk("fail to i2c_transfer read data\n");
	return ret;
	
}

static int ts_i2c_data_analysis(void)
{
	char buf[32] = {0};
	
	//这里31字节是读取0x到0x1E之间所有地址的数据
	int ret = ts_i2c_read(ts->ts_cli, buf, 31);
	if(ret != 2){
		printk("fail to ts_i2c_data_analysis\n");
		return -1;
	}
	ts->ts_num = buf[2] & 0xf; //获取当前触摸点数,触摸点数为0x2地址的低四位

	/*根据触摸屏手册的数据位,进行移位操作,组成每个触摸点的X,Y值*/
	switch(ts->ts_num){
		case 5:
			ts->ts_info[4].x = (short)buf[0x1c] | (((short)(buf[0x1b]) & 0x0f) << 8);
			ts->ts_info[4].y = (short)buf[0x1e] | (((short)(buf[0x1d]) & 0x11) << 8);
			ts->ts_info[4].id = buf[0x1d] >> 4;
		case 4:
			ts->ts_info[3].x = (short)buf[0x16] | (((short)(buf[0x15]) & 0x0f) << 8);
			ts->ts_info[3].y = (short)buf[0x18] | (((short)(buf[0x17]) & 0x11) << 8);
			ts->ts_info[3].id = buf[0x17] >> 4;
		case 3:
			printk("3 point\n");
			ts->ts_info[2].x = (short)buf[0x10] | (((short)(buf[0x0f]) & 0x0f) << 8);
			ts->ts_info[2].y = (short)buf[0x12] | (((short)(buf[0x0b]) & 0x11) << 8);
			ts->ts_info[2].id = buf[0x11] >> 4;
		case 2:
			ts->ts_info[1].x = (short)buf[0x0a] | (((short)(buf[0x09]) & 0x0f) << 8);
			ts->ts_info[1].y = (short)buf[0x0c] | (((short)(buf[0x0b]) & 0x0f) << 8);
			ts->ts_info[1].id = buf[0xb] >> 4;
		case 1:
			ts->ts_info[0].x = (short)buf[0x04] | (((short)(buf[0x03]) & 0x0f) << 8);
			ts->ts_info[0].y = (short)buf[0x06] | (((short)(buf[0x05]) & 0x0f) << 8 );
			ts->ts_info[0].id = buf[0x5] >> 4;
			break;
		default:
			return ret;
			
	}	
	
	return ret;
}

/*中断底半部处理函数(工作队列机制实现)*/
static void work_read_ts_data(struct work_struct *work)
{
	int i = 0;
	if(ts_i2c_data_analysis() < 0){
		printk("fail to work_read_ts_data\n");
		return;
	}

	if(0 == ts->ts_num){ //表明现在没有触摸点,即抬起,中断处理完毕
		input_mt_sync(ts->ts_input);
		input_sync(ts->ts_input);
		return;
	}
	
	for(i = 0; i < ts->ts_num; i++){ //上报多个数据
		input_report_abs(ts->ts_input, ABS_MT_POSITION_X, ts->ts_info[i].x);
		input_report_abs(ts->ts_input, ABS_MT_POSITION_Y, ts->ts_info[i].y);
		input_report_abs(ts->ts_input, ABS_MT_TRACKING_ID, ts->ts_info[i].id);
	}
	input_mt_sync(ts->ts_input); //每个触摸点都需要上报多个,1个可以不需要
	input_sync(ts->ts_input);

}

/*中断处理函数*/
static irqreturn_t ts_irq_handler(int irq, void *dev_id)
{
	schedule_work(& (ts->ts_work_queue));//执行中断底半部函数
	
	return IRQ_HANDLED;
}



static int ts_probe(struct i2c_client *cli, const struct i2c_device_id *idtable)
{
	printk("-------%s--------\n", __FUNCTION__);
	/*这个结构体的内容取自mach-tiny4412.c文件
	static struct ft5x0x_i2c_platform_data ft5x0x_pdata = {
	.gpio_irq		= EXYNOS4_GPX1(6),
	.irq_cfg		= S3C_GPIO_SFN(0xf),
	.screen_max_x	= 800,
	.screen_max_y	= 1280,
	.pressure_max	= 255,
	};*/

	/*1.申请input_event对象,从i2c子系统中获取平台数据用于初始化该对象
	*	(也可以不获得平台数据,直接定义x,y等信息也行)
	* 2.设置input_dev, 设置哪种事件,哪种数据类型,并给数据类型设置范围
	* 3.初始化工作队列(用于触摸时产生中断时的底半部处理)
	* 4.申请中断,注册中断处理顶半部函数
	*/

	//mach-tiny4412.c文件中ft5x6的数据类型在其中定义
	static struct ft5x0x_i2c_platform_data *pdata; 
	
	ts = kzalloc(sizeof(struct ts_desc), GFP_KERNEL);
	if(!ts){
		return -1;
	}
	/*记录一下i2c_client指针,用于I2C收发数据*/
	ts->ts_cli = cli;
	/*从i2c_clien结构体中拿取平台数据*/
	/*在mach-tiny4412.c文件中厂商已经定义了需要的数据*/
	/*也可以不从i2c总线中拿数据,自己设定数据也可以,现在主要是为了移植性好一点*/
	/*采用从i2c总线中拿数据*/
	pdata = cli->dev.platform_data; //数据会放在i2c_client.dev.platfrom_data中
	if(pdata == NULL){
		return -1;
	}
	printk("success platfrom_data\n");
	printk("pdata = %p\n", pdata);
	ts->irq = gpio_to_irq(pdata->gpio_irq);
	ts->x_max = pdata->screen_max_x;
	ts->y_max = pdata->screen_max_y;
	if(ts->x_max != 800 || ts->y_max != 480)
	{
		ts->x_max = 800;
		ts->y_max = 480;
	}
	ts->pressure_max = pdata->pressure_max;
	ts->ts_input = input_allocate_device();
	
	if(NULL == ts->ts_input){
		printk("fail to input_allocate_device\n");
		goto err_input_alloc;
	}
	printk("success platform data\n");

	set_bit(EV_SYN, ts->ts_input->evbit);
	set_bit(EV_ABS, ts->ts_input->evbit);

	set_bit(ABS_MT_PRESSURE, ts->ts_input->absbit);
	set_bit(ABS_MT_POSITION_X, ts->ts_input->absbit);
	set_bit(ABS_MT_POSITION_Y, ts->ts_input->absbit);
	set_bit(ABS_MT_TRACKING_ID, ts->ts_input->absbit); //最大支持几点触摸

	/*设置数据类型的范围*/
	input_set_abs_params(ts->ts_input, ABS_MT_PRESSURE, 0, ts->pressure_max , 0, 0);
	input_set_abs_params(ts->ts_input, ABS_MT_POSITION_X, 0, ts->x_max , 0, 0);
	input_set_abs_params(ts->ts_input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
	input_set_abs_params(ts->ts_input, ABS_MT_TRACKING_ID, 0, FT_FT5X0X_PT_MAX, 0, 0);

	set_bit(INPUT_PROP_DIRECT, ts->ts_input->propbit); //表示上面设置的X,Y值与屏幕实际像素一一对应

	/*下面设置的内容会在sys/class/input...下看到,主要用于给应用程序提示*/
	ts->ts_input->name = "input_ft5x0x_ts"; //该名字会在sys/class/input...下看到输入设备的名字
	ts->ts_input->id.bustype = BUS_I2C; //表明该输入设备隶属于I2C总线
	ts->ts_input->id.product = 0x14;
	ts->ts_input->id.vendor = 0x12;
	ts->ts_input->id.version = 3;

	int ret = input_register_device(ts->ts_input); //设置完成之后然后注册进输入子系统
	if(0 != ret){
		printk("fail to input_register_device\n");
		goto err_input_register;
	}

	/*初始化工作队列,注册中断底半部处理函数*/
	INIT_WORK(& (ts->ts_work_queue), work_read_ts_data);

	/*硬件操作:申请中断*/
	ret = request_irq(ts->irq, ts_irq_handler, IRQ_TYPE_EDGE_BOTH, "ts_irq", NULL);
	if(ret < 0){
		printk("fail to request_irq\n");
		goto err_irq_request;
	}
	
	
	return 0; //正常情况返回0
	
err_irq_request:
	cancel_work_sync(&(ts->ts_work_queue));
	input_unregister_device(ts->ts_input);
err_input_register:
	input_free_device(ts->ts_input);
err_input_alloc:
	kfree(ts);
	return -1;  //异常情况返回-1
}


static int ts_remove(struct i2c_client *cil)
{	
	free_irq(ts->irq, NULL); //销毁中断
	cancel_work_sync(&(ts->ts_work_queue)); //释放工作队列
	input_unregister_device(ts->ts_input); //从input子系统移出input_device对象
	input_free_device(ts->ts_input); //释放资源
	kfree(ts); //释放资源
	return 0;
}


struct i2c_device_id id[] = { //i2c_driver匹配的信息
	[0] = {					//注意:有限匹配id_table中的名字
		.name = TS_NAME,	//其次才会匹配i2c_driver中driver中的名字
		.driver_data = 0,
	},	
	[1] = {
		.name = "ft510x_ts",
		.driver_data = 0,
	},
};

struct i2c_driver ts_driver = { //定义一个i2c_driver对象
	.probe = ts_probe,
	.remove = ts_remove,
	.id_table = id,
	.driver = {
		.name = TS_NAME,
		.owner	= THIS_MODULE,
	},
};

static int __init tiny4412_ft_init(void) //模块入口
{
	printk("-------%s--------\n", __FUNCTION__);
	return i2c_add_driver(&ts_driver); //向i2c总线中添加该设备
}

static void __exit tiny4412_ft_exit(void) //模块出口
{
	i2c_del_driver(&ts_driver); //退出时将该设备移出总线
}

module_init(tiny4412_ft_init);
module_exit(tiny4412_ft_exit);
MODULE_LICENSE("GPL"); //声明协议

2.测试程序(文件名ts_test.c)

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

int main()
{
	int fd;
	int ret = 0;
	struct input_event myevent;

	//设备节点文件根据不同的文件系统会有差异,有的文件系统是/dev/event*
	fd = open("/dev/input/event1", O_RDWR);
	if(fd < 0){
		printf("fail to open!\n");
	}

	while(1)
	{
		ret = read(fd, &myevent, sizeof(myevent));
		if(ret < 0){
			printf("fail to read\n");
			return -1;
		}
		if(myevent.type == EV_ABS){
			if(myevent.code == ABS_MT_POSITION_X){
				printf("x = %d	", myevent.value);
			}
			if(myevent.code == ABS_MT_POSITION_Y){
				printf("y = %d\n", myevent.value);
			}
			if(myevent.code == ABS_MT_TRACKING_ID){
				printf("id = %d\n", myevent.value);
			}
		}
	}

	close(fd);
	return 0;
}

3.Makefile文件

obj-m +=tiny_ts.o

TAPP=ts_test

LMAKE=/home/linux/nfsroot/yingjian/neiheyuan/linux-3.5


module:
	make -C $(LMAKE) M=$(shell pwd) modules
	arm-linux-gcc $(TAPP).c -o $(TAPP)
.PHYON:
clean:
	rm *.mod.c *.o *.ko *.order *.symvers
install:
	cp ./*.ko /home/linux/nfsroot/nfs/home/neoway/ts/
	cp ./$(TAPP) /home/linux/nfsroot/nfs/home/neoway/ts/

我在实现的时候,发现从0x02地址处的第四位读取触摸点的个数时,最大个数为2,并不是5,这一点我也不知道为什么。附上触摸屏触摸点个数寄存器的图, 希望读者解决之后指教
Linux下自己完成的ft5x06触摸屏驱动(tiny4412开发板)_第3张图片
手册上已经说的很清楚最大支持5点触摸,我也不知道为什么我从这个低四位里面读取的最大数是2,应该是5才对啊。

你可能感兴趣的:(Linux内核驱动)