kernel(十九)触摸屏FT5x06

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/82500307

FT5x06原厂芯片资料下载https://download.csdn.net/download/jerrygou/10653027

FT5x06驱动完整代码下载https://download.csdn.net/download/jerrygou/10653022

一、电容触控芯片FT5x06

TQ210的电容触摸屏控制芯片是FT5x06。
           FT5x06引出了6根脚,分别是VCC、GND、I2CSDA、I2CSCL、INT和RESET,虽然INT脚不是必须的,但是开发高效省资源的触屏驱动程序往往都采用中断方式,下面是GT811的引脚图:

使用万能表实际测量了一下触控模块的各个引脚,实际线序是GND、SDA、SDL、INT、RESET和VDD。

LXpwmTout3连接到了FT5x06的RESET脚上,然后与GPIO0_3链接,XEINT14连接到FT5x06的INT脚上,与GPIO1_6链接,因此,需要将GPIO0_3配置为终端输入引脚,GPIO1_6配置为输出引脚。

二、 I2C驱动编写

I2C驱动也是基于总线结构的,不过分为两种,一种是Legacy方式,另一种是New Style方式,其中,Legacy方式在新内核中已经不支持了,不过韦东山老师的视频中还是分析的Legacy方式,New Style方式你可以自己用Source Insight追踪分析一下,具体的可以参考下面的代码。

ft5x06_touch.c

/*
 * driver/input/touchscreen/ft5x06_touch.c
 */
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/input.h>
#include <linux/device.h>
#include <linux/hrtimer.h>


#define FT5X06_I2C_BUS		(2)
#define FT5X06_I2C_ADDRESS	(0x38)

#define SCREEN_MAX_WIDTH	(800)
#define SCREEN_MAX_HEIGHT	(480)

#define TOUCH_MAX_WIDTH	    (800)
#define TOUCH_MAX_HEIGHT    (480)

#define K_BACK				(KEY_BACK & KEY_MAX)
#define K_MENU				(KEY_MENU & KEY_MAX)
#define K_HOME				(KEY_HOME & KEY_MAX)
#define K_SEARCH			(KEY_SEARCH & KEY_MAX)

struct point_node
{
	unsigned int x;
	unsigned int y;
};

struct ft5x06_ts_data {
	struct workqueue_struct * wq;
	struct work_struct work;
	struct i2c_client * client;
	struct input_dev * input_dev;

	struct point_node node[5];
};

typedef enum
{
	ERR_OK,
	ERR_MODE,
    ERR_READID,
    ERR_ERASE,
    ERR_STATUS,
    ERR_ECC,
    ERR_DL_ERASE_FAIL,
    ERR_DL_PROGRAM_FAIL,
    ERR_DL_VERIFY_FAIL
} E_UPGRADE_ERR_TYPE;

typedef unsigned char					FTS_BYTE;
typedef unsigned short					FTS_WORD;
typedef unsigned int					FTS_DWRD;
typedef unsigned char					FTS_BOOL;

#define FTS_NULL						0x0
#define FTS_TRUE						0x1
#define FTS_FALSE						0x0

#define FTS_PACKET_LENGTH				128

static int ft5x06_read_coordinates(struct i2c_client * client, uint8_t * buf, uint8_t len)
{
	struct i2c_msg msgs[2];
	uint8_t msgbuf[1] = { 0x0 };
	int ret;

	msgs[0].flags = !I2C_M_RD;
	msgs[0].addr = client->addr;
	msgs[0].len = 1;
	msgs[0].buf = &msgbuf[0];

	msgs[1].flags = I2C_M_RD;
	msgs[1].addr = client->addr;
	msgs[1].len = len;
	msgs[1].buf = &buf[0];

	ret = i2c_transfer(client->adapter, msgs, 2);
	return ret;
}

static void ft5x06_ts_work_func(struct work_struct * work)
{
	struct ft5x06_ts_data * ts = container_of(work, struct ft5x06_ts_data, work);
	uint8_t buf[32] = { 0 };
	unsigned int X, Y;
	unsigned int x, y, event, id;
	int i;

	ft5x06_read_coordinates(ts->client, buf, 32);

	for(i = 0; i < 5; i++)
	{
		X = (buf[i*6 + 3])<<8 | buf[i*6 + 4];
		Y = (buf[i*6 + 5])<<8 | buf[i*6 + 6];

		x = X & 0xfff;
		y = Y & 0xfff;
	
		event = (X >> 14) & 0x3;    //触摸点所发生的事件
		id = (Y >> 12) & 0xf;       //触摸点ID

		/* Remove Muti finger */
         if(id >= 0 && id <= 4) //五点触摸
		//if(id == 0)   //一点触摸
		{
			ts->node[id].x = x;
			ts->node[id].y = y;
            printk("[%d]event: event = %4d\n", id, event);

			if((event == 0) || (event == 0x02)) //按下或接触
			{
                //上报触屏事件
				input_report_abs(ts->input_dev, ABS_X, ts->node[id].x); //X坐标
				input_report_abs(ts->input_dev, ABS_Y, ts->node[id].y); //Y坐标
				input_report_abs(ts->input_dev, ABS_Z, 0);
				input_report_abs(ts->input_dev, ABS_PRESSURE, 1);
				input_sync(ts->input_dev);

				//printk("[%d]down: x = %4d, y = %4d\n", id, ts->node[id].x, ts->node[id].y);
			}
			else if(event == 0x01)  //抬起
			{
				//input_report_abs(ts->input_dev, ABS_X, ts->node[id].x);
				//input_report_abs(ts->input_dev, ABS_Y, ts->node[id].y);
				input_report_abs(ts->input_dev, ABS_Z, 0);
				//input_report_key(ts->input_dev, BTN_TOUCH, 0);
				input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
				input_sync(ts->input_dev);

				//printk("[%d]up: x = %4d, y = %4d\n", id, ts->node[id].x, ts->node[id].y);
			}
		}
	}

	input_sync(ts->input_dev);  //同步用于告诉input core子系统报告结束
	enable_irq(ts->client->irq);
}

static irqreturn_t ft5x06_ts_irq_handler(int irq, void * dev_id)
{
	struct ft5x06_ts_data * ts = dev_id;

	disable_irq_nosync(ts->client->irq);
	queue_work(ts->wq, &ts->work);

	return IRQ_HANDLED;
}

static int ft5x06_ts_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
	int ret;
	struct ft5x06_ts_data * ts;

	//检测适配器是否支持I2C_FUNC_I2C通讯方式
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
		return -ENODEV;

	ts = kzalloc(sizeof(*ts), GFP_KERNEL);	//(注:GFP_KERNEL是内核内存分配时最常用的,无内存可用时可引起休眠)
	if(!ts)
		return -ENOMEM;

	ts->wq = create_workqueue("ft5x06_wq");	//创建工作queue
	if(!ts->wq)
	{
		kfree(ts);
		return -ENOMEM;
	}

	INIT_WORK(&ts->work, ft5x06_ts_work_func);	//创建工作,关联工作函数
	ts->client = client;
	i2c_set_clientdata(client, ts);

	ts->input_dev = input_allocate_device();	//分配一个设备结构体,并初始化
	if (ts->input_dev == NULL)
	{
		destroy_workqueue(ts->wq);	//释放工作queue
		kfree(ts);
		return -ENOMEM;
	}

	ts->input_dev->name = client->name;
	ts->input_dev->phys = "input/ts";
	ts->input_dev->id.bustype = BUS_I2C;
	ts->input_dev->id.vendor = 0xDEAD;
	ts->input_dev->id.product = 0xBEEF;
	ts->input_dev->id.version = 10427;

    set_bit(EV_SYN, ts->input_dev->evbit);
    set_bit(EV_KEY, ts->input_dev->evbit);
    set_bit(EV_ABS, ts->input_dev->evbit);
    set_bit(ABS_X,  ts->input_dev->absbit);
    set_bit(ABS_Y,  ts->input_dev->absbit);
    set_bit(ABS_Z,  ts->input_dev->absbit);
    set_bit(BTN_TOUCH, ts->input_dev->keybit);
	set_bit(BTN_2, ts->input_dev->keybit);

    set_bit(K_BACK, ts->input_dev->keybit);
    set_bit(K_MENU, ts->input_dev->keybit);
    set_bit(K_HOME, ts->input_dev->keybit);
    set_bit(K_SEARCH, ts->input_dev->keybit);

	//设置范围参数
    input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
	//表示:X轴范围是0到SCREEN_MAX_WIDTH,数据误差是-4到+4,中心平滑位置是0 .
    input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_WIDTH, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_HEIGHT, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 1, 0, 0);

    ret = input_register_device(ts->input_dev);	//注册到输入型子系统
	if(ret)
	{
		destroy_workqueue(ts->wq);
		kfree(ts);
		return ret;
	}	

	irq_set_irq_type(client->irq, IRQ_TYPE_EDGE_FALLING);	//下降沿触发
	ret = request_irq(client->irq, ft5x06_ts_irq_handler, IRQ_TYPE_EDGE_FALLING, client->name, ts);
	if(ret)
	{
		destroy_workqueue(ts->wq);
		kfree(ts);
		return ret;
	}
    
	return 0;
}

static int ft5x06_ts_remove(struct i2c_client *client)
{
	struct ft5x06_ts_data * ts = i2c_get_clientdata(client);
	printk("==ft5x06_ts_remove=\n");
	
	free_irq(client->irq, ts);
	i2c_set_clientdata(client, NULL);
	input_unregister_device(ts->input_dev);
	if(ts->input_dev)
		kfree(ts->input_dev);
	kfree(ts);

	return 0;
}

static const struct i2c_device_id ft5x06_ts_id[] = {
	{ "Louis210-ts", 0},
	{ }
};

MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);

static struct i2c_driver ft5x06_ts_driver = {
	.driver        = {
		.name    = "Louis210_ts",
		.owner = THIS_MODULE,
	},
	.probe		= ft5x06_ts_probe,
	.remove		= ft5x06_ts_remove,
	.id_table	= ft5x06_ts_id,
};

module_i2c_driver(ft5x06_ts_driver);

MODULE_AUTHOR("LouisGou");
MODULE_DESCRIPTION("Louis210 ft5x06 Touchscreen Driver");
MODULE_LICENSE("GPL");

这并不是完整的代码,一方面是没有做异常处理,另一方面是没有上报消息,只是简单的驱动了TQ210的触摸屏部分,如果您需要拿去自己略作修改即可使用。

三、 注册TS的I2C模块设备

注册TS的I2C模块很简单,在Linux内核文件arch/arm/mach-s5pv210/mach-Louis210.c文件的I2C通道2结构体中加入TS的I2C地址,也就是0x53,添加后如下

void Louis210_ft5x06_cfg_gpio(struct platform_device *dev)
{
	int err;

	s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2,
			      S3C_GPIO_SFN(2), S3C_GPIO_PULL_UP);

	err = gpio_request(S5PV210_GPH1(6), "GPH1");
	if (err)
		printk(KERN_ERR "#### failed to GPH1(6) for gt801 interrupt\n");

	s3c_gpio_setpull(S5PV210_GPH1(6), S3C_GPIO_PULL_NONE);
	s3c_gpio_cfgpin(S5PV210_GPH1(6),S3C_GPIO_SFN(0xf));
	gpio_free(S5PV210_GPH1(6));
	
	err = gpio_request(S5PV210_GPD0(3), "GPD0");
	if (err)
		printk(KERN_ERR "#### failed to GPH1(6) for gt801 interrupt\n");

	s3c_gpio_setpull(S5PV210_GPD0(3), S3C_GPIO_PULL_NONE);
	s3c_gpio_cfgpin(S5PV210_GPD0(3),S3C_GPIO_SFN(0x1));
	gpio_direction_output(S5PV210_GPD0(3), 0);
	gpio_free(S5PV210_GPD0(3));
}

static struct s3c2410_platform_i2c Louis_ft5x06_pdata __initdata = {
   .flags        = 0,
   .slave_addr    = 0xaa>>1,
   .frequency    = 1000*100,
   .sda_delay    =  10,
   .cfg_gpio = Louis210_ft5x06_cfg_gpio,
};

static struct i2c_board_info smdkv210_i2c_devs2[] __initdata = {
	/* To Be Updated */
	{ I2C_BOARD_INFO("Louis210-ts", 0x38),
	  .irq = IRQ_EINT(14),
	  .platform_data  = &Louis_ft5x06_pdata,	
	},
};

运行结果:

四、tslib测试教程(ubuntu)

编译好的tslib文件https://download.csdn.net/download/jerrygou/10640507

1. 安装git

sudo apt-get install git

2. 下载最新的tslib

git clone https://github.com/kergoth/tslib

3. 安装auto

sudo apt-get install autoconf  automake  libtool

4. 编译tslib(配置成make.sh脚本

#!/bin/sh
./autogen.sh 
mkdir tmp
echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install

如果报错:

../src/.libs/libts.so: undefined reference to `EVIOCGPROP'
collect2: ld returned 1 exit status
Makefile:579: recipe for target 'ts_test' failed

修改tslib.h文件,添加

#define EVIOCGPROP(len)        _IOC(_IOC_READ, 'E', 0x09, len)        /* get device properties */

5. 安装tslib

cd tmp
cp * /nfsroot/rootfs -rfd

6. 配置tslib

修改 /etc/ts.conf
将行
# module_raw input
改为:
module_raw input
(实际上就是去掉高行的#号和第一个空格)

7. 配置tslib运行环境变量(添加至/rootfs/etc/profile文件中

export TSLIB_TSDEVICE=/dev/input/event0  //这里需要根据自己的event位置进行修改,新内核在/dev/input/event*
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

8. 校正(电容屏实际上不需要校正,仅为了测试触屏驱动

运行ts_calibrate,并根据提示进行校正
如果报错 ts_setup: No such file or directory等类似错误,这是因为上一步环境变量配置失败,需要重新设置

9. 自由画图

运行ts_test,点击draw按钮,可以自由画图,效果如下图。

五、小结

本文中列举的代码是简单的实现了触摸坐标获取,没有实现触摸消息上报等操作,这些操作需要自己来实现。

六、触摸屏驱动测试

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <signal.h>
#include <linux/fb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>

int main(int argc, char** argv) 
{
    int tsfd;
    int key_value, count;
    struct input_event ev_key;
    
    printf("hello tq210\n");
    
    tsfd = open("/dev/input/event0", O_RDWR);
    if (tsfd == -1) {
        perror("open device error");
        exit(-1);
    }
    printf("open device %d\n", tsfd);
    
    for(;;){
        count = read(tsfd, &ev_key, sizeof(struct input_event));
        
        if(EV_KEY == ev_key.type)
            printf("type:EV_KEY, code:0x%x, value:0x%x\n", ev_key.type, ev_key.code, ev_key.value);
        else if(EV_ABS == ev_key.type)
            printf("type:EV_ABS, code:0x%x, value:0x%x\n", ev_key.type, ev_key.code, ev_key.value);
        else if(EV_SYN == ev_key.type)
            printf("[ ... syn event]\n");
            
    }

    close(tsfd);
    return 0;
}

七、报错

运行报错:tslib: Selected device is not a touchscreen (must support ABS and KEY event types)

两种情况:一种内核未加入触摸屏的支持,另一种情况是将TSLIB_TSDEVICE=/dev/input/event0 
改为 TSLIB_TSDEVICE=/dev/input/event1
 
解决办法:
1)内核加入触摸屏的支持
内核和文件系统编译时加入触摸屏支持:
一、内核配置支持触摸屏
 
Device Drivers  --->
    Input device support  --->
        (800) Horizontal screen resolution
        (480) Vertical screen resolution
        <*> Event interface
        [*] Touchscreens --->
            <*> Louis210 FT5216 touchscreen driver 

猜你喜欢

转载自blog.csdn.net/JerryGou/article/details/82500307