从零开始之驱动开发、linux驱动(七十一、电容触摸屏驱动)

一、电容式触摸屏检测原理

基本原理是利用充电时间检测电容大小,从而通过检测出电容值的变化来获知触摸信号。电容屏的最上层是玻璃(不会像电阻屏那样形变),核心层部分也是由ITO材料构成的,这些导电材料在屏幕里构成了人眼看不见的静电网,静电网由多行X轴电极和多列Y轴电极构成,两个电极之间会形成电容。触摸屏工作时,X轴电极发出AC交流信号,而交流信号能穿过电容,即通过Y轴能感应出该信号,当交流电穿越时电容会有充放电过程,检测该充电时间可获知电容量。若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作(这就是为什么它被称为电容式触摸屏以及绝缘体触摸没有反应的原因)。

下图为Vc 跟随时间变化情况,可以看出在无触摸情况下,电压变化较快;而在有触摸时,总的电容量增大了,电压变化缓慢一些。

电容屏 ITO 层的结构见下图,这是比较常见的形式,电极由多个菱形导体组成,生产时使用蚀刻工艺在 ITO 层生成这样的结构。

X 轴电极与 Y 轴电极在交叉处形成电容,即这两组电极构成了电容的两极,这样的结构覆盖了整个电容屏,每个电容单元在触摸屏中都有其特定的物理位置,即电容的位置就是它在触摸屏的 XY 坐标。检测触摸的坐标时,第 1 条 X 轴的电极发出激励信号,而所有Y 轴的电极同时接收信号,通过检测充电时间可检测出各个 Y 轴与第 1 条 X 轴相交的各个互电容的大小,各个 X 轴依次发出激励信号,重复上述步骤,即可得到整个触摸屏二维平面的所有电容大小。当手指接近时,会导致局部电容改变,根据得到的触摸屏电容量变化的二维数据表,可以得知每个触摸点的坐标,因此电容触摸屏支持多点触控。其实电容触摸屏可看作是多个电容按键组合而成,就像机械按键中独立按键和矩阵按键的关系一样,甚至电容触摸屏的坐标扫描方式与矩阵按键都是很相似的。
 

二、 电容触摸屏控制芯片

相对来说,电容屏的坐标检测比电阻屏的要复杂,因而它也有专用芯片用于检测过程,下面我们以本章重点讲述的电容屏使用的触控芯片gslx680为例进行说明。

因为这个厂家的datasheet很简单,没有提供具体的寄存器,所以只能根据官方给的驱动程序来判定。

没办法,这样也能分析下去。

后面找到一个网站可以看一下,有一些寄存器的说明。

http://linux-sunxi.org/GSL1680

00~07是一些固件信息寄存器

首先看一下这个芯片的基本电路图。

接下来看一下基本的原理框图。

除了前面所说的X轴的多条激励信号和Y轴的多条检测信号之外,我们这里主要关心的就是如何获取信息。这里厂家提供了两组信号。

  • 一个是IRQ的中断信号,表示有按键按下。
  • 另一个就是提供了一个IIC总线的接口,用来获取坐标数据或配置寄存器。

GSL1680 由单电源供电, 范围从 2.6V〜3.3V。 GSL1680 有片内上电复位(POR) 电路。上电后, GSL1680 在 5 毫秒内进入正常工作模式。为了使 POR 正常工作, VDD 必须下降到 1.6V 以下。

芯片经历一个上电复位后,便通过 IRQ 引脚向主机发出中断信号。 中断标志表明是上电复位消息。该功能可用于检测任何意外断电突破事件,因此允许主机采取任何必要的措施, 例如重新配置 GSL1680。

GSL1680 还具有一个 RESET 引脚。 RESET 引脚置低至少 100 ns 器件回到复位状态。释放 RESET 后GSL1680 在 5 毫秒内进入正常工作模式。 RESET 引脚内部有上拉电阻,因此可以悬空。

软件复位命令可以用来通过 I2C 复位芯片(参考 GSL1680 应用指南)。软件复位仅需要 1毫秒。软件复位或 RESET 引脚复位事件将不会触发上电复位中断。
 

通讯协议
GSL1680 使用 I2C 兼容接口进行通信。 GSL1680 不推荐查询方式,因为它只有在数据包发生更新时才会拉高 IRQ 线, 提示新的数据包到来。
 

I2C 兼容地址

GSL1680 支持单个 I2C 兼容的设备地址, 0x40 的。 该地址左移一位,形成的 SLA + W
或 SLA + R 的与 I2C 兼容的地址。

IRQ 中断
IRQ 是高电平有效的中断输出引脚,用来提醒主机,新的数据或上电复位事件。这样减少I2C 兼容总线的浪费。
主机应该始终只在中断到来时才读取消息。避免查询方式。 如果查询不可避免,建议同一数据读两次。两次读取结果一致则数据有效。避免数据更新和读取同时发生引起的数据错误
 

三、驱动分析

开始之前,先看一下我们主控的原理图

触摸屏的接口接在主控的i2c1上。

中断引脚接在GPH0_7上。

复位引脚接在GPH0_6上。

因为触摸屏驱动芯片gslx680属于一个i2c的从设备,所以我们需要先写一个设备节点。

&i2c1 {
        status = "okay";

        gslX680@40 {
        compatible = "GSLX680";
        reg = <0x40>;
        screen_max_x = <1024>;
        screen_max_y = <600>;
        /*
         * 通过下面这个interrupts和interrupt-parent来指定中断
         * 也可以通过引脚指定gpio,然后在驱动函数中,通过引脚反推出中断
         * 这里两个都给出
         */
        interrupt-parent = <&gph0>;
        interrupts = <7 1>;    /* gph0的中断7,上升沿触发 */
        touch-gpio = <&gph0 7 1>;    
        reset-gpio = <&gph0 6 1>; /* gph0的6口,默认高电平,低电平睡眠 */
    };  
};

上面这个应该很清除,设备地址是0x40,所对应屏的x方向分辨率为1024,y方向为600。

现在看一下驱动程序。

官方提供的历程中提供了一个gsl_point_id的已经编译但未连接的文件。

因为我不是这个芯片原厂的客户,所以详细的这个芯片的一手资料我拿不到。

所以这个gsl_point_id文件的具体功能我不清楚。但我经过分析代码后,发现这个应该是一个配置相关的文件。

也就是可以通过这个gsl_point_id来更该或更新他们的固件。

可以看一下相关的内容。

#ifdef	GSL_NOID_VERSION
struct gsl_touch_info
{
	int x[10];
	int y[10];
	int id[10];
	int finger_num;
};

extern unsigned int gsl_mask_tiaoping(void);
extern unsigned int gsl_version_id(void);
extern void gsl_alg_id_main(struct gsl_touch_info *cinfo);
extern void gsl_DataInit(int *ret);
unsigned int gsl_config_data_id[] =
{
	0x7b1bd8,  
	0x200,
	0,0,
	0,
	0,0,0,
	0,0,0,0,0,0,0,0xd887666b,


	0x900,0x5,0xa000f,0xa000f,0x2580400,0,0x5100,0x8e00,
	0,0x320014,0,0,0,0,0,0,
	0x8,0x4000,0x1000,0x10410005,0x10450008,0,0,0x1010101,
	0x1b6db688,0x190,0xc00014,0xb3001e,0xa60028,0x9a0032,0xc00014,0xb3001e,
	0xa60028,0x9a0032,0xc00014,0xb3001e,0xa60028,0x9a0032,0xc00014,0xb3001e,
	0xa60028,0x9a0032,0x804000,0x90040,0x90001,0,0,0,
	0,0,0,0x14012c,0xa003c,0xa0078,0x400,0x1081,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,

	0,//key_map
	0x3200384,0x64,0x503e8,//0
	0,0,0,//1
	0,0,0,//2
	0,0,0,//3
	0,0,0,//4
	0,0,0,//5
	0,0,0,//6
	0,0,0,//7

	0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,


	0x220,
	0,0,0,0,0,0,0,0,
	0x10203,0x4050607,0x8090a0b,0xc0d0e0f,0x10111213,0x14151617,0x18191a1b,0x1c1d1e1f,
	0x20212223,0x24252627,0x28292a2b,0x2c2d2e2f,0x30313233,0x34353637,0x38393a3b,0x3c3d3e3f,
	0x10203,0x4050607,0x8090a0b,0xc0d0e0f,0x10111213,0x14151617,0x18191a1b,0x1c1d1e1f,
	0x20212223,0x24252627,0x28292a2b,0x2c2d2e2f,0x30313233,0x34353637,0x38393a3b,0x3c3d3e3f,

	0x10203,0x4050607,0x8090a0b,0xc0d0e0f,0x10111213,0x14151617,0x18191a1b,0x1c1d1e1f,
	0x20212223,0x24252627,0x28292a2b,0x2c2d2e2f,0x30313233,0x34353637,0x38393a3b,0x3c3d3e3f,

	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,

	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,

	0x10203,0x4050607,0x8090a0b,0xc0d0e0f,0x10111213,0x14151617,0x18191a1b,0x1c1d1e1f,
	0x20212223,0x24252627,0x28292a2b,0x2c2d2e2f,0x30313233,0x34353637,0x38393a3b,0x3c3d3e3f,

	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,


	0x3,
	0x101,0,0x100,0,
	0x20,0x10,0x8,0x4,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,

	0x4,0,0,0,0,0,0,0,
	0x28003c0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,
};
#endif

主要就是上面的这个配置数据id。

当然前提是要定义下面的这个宏。

#define	GSL_NOID_VERSION
extern unsigned int gsl_mask_tiaoping(void);
extern unsigned int gsl_version_id(void);
extern void gsl_alg_id_main(struct gsl_touch_info *cinfo);
extern void gsl_DataInit(int *ret);

同时可以看到这个宏中定义的函数名称。

可以看到,调频,版本id,主要的算法id,初始化数据。

基本 可以推算出gsl_point_id里面就是这几个函数的实现。

这几个函数应该比他们出厂芯片自带的算法或一些参数的准确度更高。这个数据电容触摸屏的难点,也属于商业机密,所以芯片厂家为了修复原来算法的缺陷,用了一个编译好,未连接的二进制文件来弥补之前的缺陷。

当然如果我们不定义GSL_NOID_VERSION这个宏,也就是使用最初芯片出场就自带的算法,起始也是可是使用的。就是效果没有修复的的有效。

经过我的验证,就算没定义上面的这个GSL_NOID_VERSION,也是可以的使用的。

所以前面的代码分析,我都默认不分析GSL_NOID_VERSION这个宏所包含的内容。

从入口函数开始:

#define GSLX680_I2C_NAME 	"GSLX680"



unsigned char root_config[32] = "root";	//设置为linux环境


static const struct i2c_device_id gsl_ts_id[] = {
	{GSLX680_I2C_NAME, 0},
	{}
};
MODULE_DEVICE_TABLE(i2c, gsl_ts_id);

static struct of_device_id goodix_ts_dt_ids[] = {
	{ .compatible = "GSLX680" },
	{ }
};

static struct i2c_driver gsl_ts_driver = {
	.driver = {
		.name = GSLX680_I2C_NAME,
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(goodix_ts_dt_ids),
	},
	.probe		= gsl_ts_probe,
	.remove		= gsl_ts_remove,
	.id_table	= gsl_ts_id,
};


static int __init gsl_ts_init(void)
{
	int ret;

	if(strcasecmp(root_config, "default") == 0)
	{
		printk("Initial gslx680 Touch Driver\n");
        /*
         * 安卓平台 
         */
		REPORT_DATA_ANDROID_4_0 = 1;
		is_linux = 0;
		MAX_FINGERS = 10;
	}
	else
	{
		printk("Initial gslx680 linux Touch Driver\n");
        /*
         * linux平台
         */
		REPORT_DATA_ANDROID_4_0 = 0;
		is_linux = 1;
		MAX_FINGERS = 1;
	}

	ret = i2c_add_driver(&gsl_ts_driver);
	return ret;
}
static void __exit gsl_ts_exit(void)
{
	i2c_del_driver(&gsl_ts_driver);
	return;
}

module_init(gsl_ts_init);
module_exit(gsl_ts_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GSLX680 touchscreen controller driver");
MODULE_AUTHOR("Guan Yuwei, [email protected]");
MODULE_ALIAS("platform:gsl_ts");

这里可以看到通过修改root_config字符数组的默认值来,选择使用的操作系统环境。

如果是"root",表示是linux环境,如果是default表示是安卓4.0版本。

同时,安卓平台设置了可以10指触摸,linux下最多单指触摸。

同时因为在设备树文件定义了i2c1    okay,所在注册i2c_adaptor的时候,会把这个adaptor里面的设备节点(在这里也就是gslx680转换成一个client)

	of_i2c_register_devices(adap);

在执行module_init函数,注册gsl_ts_driver时,i2c总线会遍历这个adaptor上的client,如果client的中compatible和驱动中of_device_id 里面的compatible名字相同那就表示匹配。

具体的i2c驱动内容我之前有过多章节的详细分析。

接下来就会执行probe函数。

这里我们就看probe函数。

probe函数比较长,我们会分开看。

static int  gsl_ts_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct gsl_ts *ts;
	int rc;
	struct device_node *np = client->dev.of_node;
	enum of_gpio_flags rst_flags;
	unsigned long irq_flags;
	int ret = 0;

	printk("GSLX680 Enter %s\n", __func__);
	/*检测触摸屏设备*/
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C functionality not supported\n");
		return -ENODEV;
	}

	/*分配一个struct gsl_ts结构体,为了后续调用函数方便*/
	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;

	/*把client嵌入到gsl_ts结构体中*/
	ts->client = client;
	/*把gsl_ts嵌入到client结构体中*/
	i2c_set_clientdata(client, ts);
	ts->device_id = 0;

	/*从设备树获取触摸中断引脚*/
	ts->irq_pin = of_get_named_gpio_flags(np, "touch-gpio", 0, (enum of_gpio_flags *)&irq_flags);
	
	/*从设备树获取复位中断引脚*/
	ts->rst_pin = of_get_named_gpio_flags(np, "reset-gpio", 0, &rst_flags);
	
	/*检测复位引脚是否有效,如果有效申请一个GPIO口设定为设备树指定的电平*/
	if (gpio_is_valid(ts->rst_pin)) {
		ts->rst_val = (rst_flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
		ret = devm_gpio_request_one(&client->dev, ts->rst_pin, (rst_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, "goodix reset pin");
		if (ret != 0) {
			dev_err(&client->dev, "goodix gpio_request error\n");
			return -EIO;
		}
		gpio_direction_output(ts->rst_pin, 0);
		gpio_set_value(ts->rst_pin, 1);
		msleep(20);
	} else {
		dev_info(&client->dev, "reset pin invalid\n");
	}

	gts = ts;

	/*对触摸屏软件进行初始化工作:中断以及input设备*/
	rc = gslX680_ts_init(client, ts);
	if (rc < 0) {
		dev_err(&client->dev, "GSLX680 init failed\n");
		goto error_mutex_destroy;
	}
	gsl_client = client;

	/*复位一次触摸屏*/
	gslX680_init();
	
	/*芯片初始化*/
	if(init_chip(ts->client) < 0)
		return -1;

	//获取中断号,并且申请中断
	ts->irq=gpio_to_irq(ts->irq_pin);
	if (ts->irq)
	{
		rc=  request_irq(ts->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts);
		if (rc != 0) {
			printk(KERN_ALERT "Cannot allocate ts INT!ERRNO:%d\n", ret);
			goto error_req_irq_fail;
		}
	}

#ifdef GSL_MONITOR   //如果定义了监听
	printk( "gsl_ts_probe () : queue gsl_monitor_workqueue\n");

	//初始化一个延时监听队列
	INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker);
	
	//创建一个监听队列
	gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue");
	
	//监听函数
	queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 1000);
#endif
	device_enable_async_suspend(&client->dev);  //
	
	INIT_WORK(&ts->resume_work, gs_ts_work_resume);  //

	printk("[GSLX680] End %s\n", __func__);

	return 0;

error_req_irq_fail:
    free_irq(ts->irq, ts);

error_mutex_destroy:
	input_free_device(ts->input);
	kfree(ts);
	return rc;
}

1.检测这个adapter是否具有i2c功能。

	/*检测触摸屏设备*/
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C functionality not supported\n");
		return -ENODEV;
	}

	/*分配一个struct gsl_ts结构体,为了后续调用函数方便*/
	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;

	/*把client嵌入到gsl_ts结构体中*/
	ts->client = client;
	/*把gsl_ts嵌入到client结构体中*/
	i2c_set_clientdata(client, ts);
	ts->device_id = 0;

申请一个触摸屏的结构体,包含的这款触摸屏用到的所有东西。(用到再说)

struct gsl_ts {
	struct i2c_client *client;
	struct input_dev *input;
	struct work_struct work;
	struct workqueue_struct *wq;
	struct gsl_ts_data *dd;
	u8 *touch_data;
	u8 device_id;
	int irq;
	int irq_pin;
	int rst_pin;
	int rst_val;
	struct work_struct	resume_work;
};

2.获取设备树指定的资源


	/*从设备树获取触摸中断引脚*/
	ts->irq_pin = of_get_named_gpio_flags(np, "touch-gpio", 0, (enum of_gpio_flags *)&irq_flags);
	
	/*从设备树获取复位中断引脚*/
	ts->rst_pin = of_get_named_gpio_flags(np, "reset-gpio", 0, &rst_flags);
	
	/*检测复位引脚是否有效,如果有效申请一个GPIO口设定为设备树指定的电平*/
	if (gpio_is_valid(ts->rst_pin)) {
		ts->rst_val = (rst_flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
		ret = devm_gpio_request_one(&client->dev, ts->rst_pin, (rst_flags & OF_GPIO_ACTIVE_LOW) ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, "goodix reset pin");
		if (ret != 0) {
			dev_err(&client->dev, "goodix gpio_request error\n");
			return -EIO;
		}
		gpio_direction_output(ts->rst_pin, 0);
		gpio_set_value(ts->rst_pin, 1);
		msleep(20);
	} else {
		dev_info(&client->dev, "reset pin invalid\n");
	}

这里获取了我们指定的中断引脚和复位引脚。

因为手册中说了,复位引脚是拉低复位。

而上面申请gpio,配置完之后是拉高的,即没进行复位。

gpio_set_value(ts->rst_pin, 1);

这里要注意,获取的只是中断引脚,而不是中断号。

3.初始化触摸屏

	gts = ts;

	/*对触摸屏软件进行初始化工作:中断以及input设备*/
	rc = gslX680_ts_init(client, ts);
	if (rc < 0) {
		dev_err(&client->dev, "GSLX680 init failed\n");
		goto error_mutex_destroy;
	}
	gsl_client = client;

其中这里也把两个全局变量给初始化了。方便后面在本文件的其它函数使用。

static struct gsl_ts *gts;
static struct i2c_client *gsl_client = NULL;


4.接下来看一下一个比较重要的点,就是注册一个输入设备。

再次之前我们需要了解一些触摸屏的基本数据。

#define GSL_DATA_REG		0x80
#define GSL_STATUS_REG		0xe0
#define GSL_PAGE_REG		0xf0
#define PRESS_MAX    		255



/*触摸屏数据结构体*/
static struct gsl_ts_data devices[] = {
	{
		.x_index = 6,                //x_index表示数据是从6位置偏移的1.5个字节
		.y_index = 4,                //y_index表示数据是从4位置偏移的两个字节
		.z_index = 5,                //z_index未使用,有些触摸屏和压力有关系所以要z
		.id_index = 7,               //上面x_index使用了6位置偏移1字节+7位置偏移的低四位,高四位表示id,而id在多点触摸中,同一个id持续上报数据,表示手指未松开
		.data_reg = GSL_DATA_REG,    //数据寄存器起始地址
		.status_reg = GSL_STATUS_REG,//状态寄存器地址
		.update_data = 0x4,          //未使用
		.touch_bytes = 4,            //一个按键需要4字节表示
		.touch_meta_data = 4,        //触摸元数据4字节,这个也就是表示当前按下的数量
		.finger_size = 70,           //目前未使用
	},
};

这里我们可以通过对比,发现寄存器也都是完全相同的。

0x80寄存也就是元寄存器,表示按下的键数。

0x84~0xab表示其他10个按键的值。

同时对比上面的,以第一个按键为例0x84 0x85寄存器放的是Y坐标数据。而0x86 以及0x87的第四位放的是X坐标数据。0x87的高四位放的是id数据。

0xe0是状态寄存器。

当然触摸帧的格式,在前面给的开源网址也有说明,我简单摘录如下:

每组坐标的四个字节包含X和Y值,也包含手指。
前两个字节以小端格式包含12个低位中的X坐标。另外两个字节以小端格式包含12个低位中的Y坐标。
Y坐标中的4个高位包含触摸的手指标识符。
例:
假设用户用一根手指触摸屏幕。寄存器0x80将包含1,寄存器0x84至0x87将包含X和Y坐标,并且手指标识符将为1。
现在,用户在不移除第一根手指的情况下用第二根手指触摸屏幕。寄存器0x80将包含2.寄存器0x84至0x87将包含第一次触摸的X和Y坐标,其中的手指标识符将为1.寄存器0x88至0x8B将包含第二次触摸和手指的X和Y坐标其中的标识符为2。
现在,用户移除第一个手指,保留第二个手指。寄存器0x80将包含1.寄存器0x84至0x87将包含X和Y坐标,但手指标识符将为2,因为这是保留在屏幕中的手指。


/*触摸屏数据结构体*/
static struct gsl_ts_data devices[] = {
	{
		.x_index = 6,                //x_index表示数据是从6位置偏移的1.5个字节
		.y_index = 4,                //y_index表示数据是从4位置偏移的两个字节
		.z_index = 5,                //z_index未使用,有些触摸屏和压力有关系所以要z
		.id_index = 7,               //上面x_index使用了6位置偏移1字节+7位置偏移的低四位,高四位表示id,而id在多点触摸中,同一个id持续上报数据,表示手指未松开
		.data_reg = GSL_DATA_REG,    //数据寄存器起始地址
		.status_reg = GSL_STATUS_REG,//状态寄存器地址
		.update_data = 0x4,          //未使用
		.touch_bytes = 4,            //一个按键需要4字节表示
		.touch_meta_data = 4,        //触摸元数据4字节,这个也就是表示当前按下的数量
		.finger_size = 70,           //目前未使用
	},
};

static int gslX680_ts_init(struct i2c_client *client, struct gsl_ts *ts)
{
	struct input_dev *input_device;
	int rc = 0;

	printk("[GSLX680] Enter %s\n", __func__);

	//获取触摸屏结构体gsl_ts_data ,这个device_id我们默认始终是 0
	ts->dd = &devices[ts->device_id];  

	//这是第一个触摸屏设备
	if (ts->device_id == 0) {
		ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data; //获取触摸屏数据的大小
		ts->dd->touch_index = 0;  //设置该触摸屏的序列号为1
	}

	/*分配触摸数据空间大小*/
	ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
	if (!ts->touch_data) {
		pr_err("%s: Unable to allocate memory\n", __func__);
		return -ENOMEM;
	}

	input_device = input_allocate_device();
	if (!input_device) {
		rc = -ENOMEM;
		goto error_alloc_dev;
	}

	/*初始化input设备*/
	ts->input = input_device; 
	input_device->name = GSLX680_I2C_NAME;
	input_device->id.bustype = BUS_I2C;
	input_device->dev.parent = &client->dev;
	input_set_drvdata(input_device, ts);

	set_bit(EV_ABS, input_device->evbit);

	if(is_linux > 0)
	{
		set_bit(BTN_TOUCH, input_device->keybit);
	 	set_bit(EV_ABS, input_device->evbit);//设置触摸屏支持上报绝对位移类型
		set_bit(EV_KEY, input_device->evbit);//设置触摸屏支持上报绝对位移类型
		//设置触摸屏支持上报绝对位x值
		input_set_abs_params(input_device, ABS_X, 0, SCREEN_MAX_X, 0, 0);
		//设置触摸屏支持上报绝对位y值
		input_set_abs_params(input_device, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);
	}
	else
	{
		input_set_abs_params(input_device,ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);
		input_set_abs_params(input_device,ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0);
		input_set_abs_params(input_device,ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);
		input_set_abs_params(input_device, ABS_MT_PRESSURE, 0, 255, 0, 0);
	}
#ifdef HAVE_TOUCH_KEY  
	input_device->evbit[0] = BIT_MASK(EV_KEY);  //上报按键类型
	for (i = 0; i < MAX_KEY_NUM; i++)
		set_bit(key_array[i], input_device->keybit);
#endif

	ts->irq = client->irq;

	//创建一个工作队列
	ts->wq = create_singlethread_workqueue("kworkqueue_ts");  
	if (!ts->wq) {
		dev_err(&client->dev, "Could not create workqueue\n");
		goto error_wq_create;
	}
	
	//冲洗工作队列,即清理工作队列
	flush_workqueue(ts->wq);

	//往工作队列增加一个任务,该任务为执行gslX680_ts_worker函数
	INIT_WORK(&ts->work, gslX680_ts_worker);

	rc = input_register_device(input_device);  //注册input设备
	if (rc)
		goto error_unreg_device;

	return 0;

error_unreg_device:
	destroy_workqueue(ts->wq);
error_wq_create:
	input_free_device(input_device);
error_alloc_dev:
	kfree(ts->touch_data);
	return rc;
}


上面已经注释的很清楚了。

上面的我们先看下面这个数据缓冲区的大小计算,元数据4个字节。剩下就是触摸数据了,如果我们想要支持多点触摸,那么我们就需要指定最大手指个数MAX_FINGERS,每个触摸为4字节。如果我们想要10指触摸,那么就需要44个字节空间。

	//这是第一个触摸屏设备
	if (ts->device_id == 0) {
		ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data; //获取触摸屏数据的大小
		ts->dd->touch_index = 0;  //设置该触摸屏的序列号为1
	}

	/*分配触摸数据空间大小*/
	ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
	if (!ts->touch_data) {
		pr_err("%s: Unable to allocate memory\n", __func__);
		return -ENOMEM;
	}

接下来就是一些输入子系统的知识了。

  • 申请一个输入设备
  • 初始化输入设备的参数,绑定输入设备到触摸屏设备等
  • 设置输入设备的能力(绝对位移,触摸)
  • 注册输入设备

因为安卓的输入和linux这边有点小的区别,所以做了判断。我们这里只看linux下的就可以。

    input_device = input_allocate_device();
	if (!input_device) {
		rc = -ENOMEM;
		goto error_alloc_dev;
	}

	/*初始化input设备*/
	ts->input = input_device; 
	input_device->name = GSLX680_I2C_NAME;
	input_device->id.bustype = BUS_I2C;
	input_device->dev.parent = &client->dev;
	input_set_drvdata(input_device, ts);

	set_bit(EV_ABS, input_device->evbit);

	if(is_linux > 0)
	{
		set_bit(BTN_TOUCH, input_device->keybit);
	 	set_bit(EV_ABS, input_device->evbit);//设置触摸屏支持上报绝对位移类型
		set_bit(EV_KEY, input_device->evbit);//设置触摸屏支持上报绝对位移类型
		//设置触摸屏支持上报绝对位x值
		input_set_abs_params(input_device, ABS_X, 0, SCREEN_MAX_X, 0, 0);
		//设置触摸屏支持上报绝对位y值
		input_set_abs_params(input_device, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);
	}
	else
	{
		input_set_abs_params(input_device,ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);
		input_set_abs_params(input_device,ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0);
		input_set_abs_params(input_device,ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);
		input_set_abs_params(input_device, ABS_MT_PRESSURE, 0, 255, 0, 0);
	}

        .......

	rc = input_register_device(input_device);  //注册input设备
	if (rc)
		goto error_unreg_device;

接下来就是创建一个工作队列了(为什么要创建工作队列?)

因为i2c是一个很慢的总线,发生触摸事件后,会要读数据,如果在中断函数中读数据,会降低系统的实时性,效率也会很低。

而创建一个内核线程,在启动的情况下读数据,执行完了之后自己会睡眠。而且也不影响实时性。

	ts->irq = client->irq;

	//创建一个工作队列
	ts->wq = create_singlethread_workqueue("kworkqueue_ts");  
	if (!ts->wq) {
		dev_err(&client->dev, "Could not create workqueue\n");
		goto error_wq_create;
	}
	
	//清理工作队列
	flush_workqueue(ts->wq);

	//往工作队列增加一个任务,该任务为执行gslX680_ts_worker函数
	INIT_WORK(&ts->work, gslX680_ts_worker);

这里我们注意    ts->irq = client->irq其实是无效的。因为目前我们还没获取中断号(我们给的是一个gpio号)

5.复位芯片,初始化触摸屏芯片

	/*复位一次触摸屏*/
	gslX680_init();
	
	/*芯片初始化*/
	if(init_chip(ts->client) < 0)
		return -1;

手册上说的,超过100ns就可以复位,这里厂家给的50ms。

static int gslX680_init(void)
{
	/* reset pin */
	gpio_request(gts->rst_pin, "reset-gpio");
	msleep(5);
	if (gpio_is_valid(gts->rst_pin)) {
		gpio_set_value(gts->rst_pin,0);        //拉低复位引脚
	}
	msleep(50);
	if (gpio_is_valid(gts->rst_pin)) {
		gpio_set_value(gts->rst_pin,1);        //拉高复位引脚
	}
	msleep(5);

	return 0;
}

初始化芯片,可以看到又复位了一次芯片。检查芯片是不是在线,即对0xf0寄存器先写,后读,判断数据是否前后一致。

之后执行了一清除寄存器,安装固件,开机的过程。

当然这些都是

static int init_chip(struct i2c_client *client)
{
	int rc;
	
	/*复位操作一次*/
	gpio_set_value(gts->rst_pin,0);
	msleep(20); 	
	gpio_set_value(gts->rst_pin,1);
	msleep(20); 

	/*检测I2C设备,即触摸屏设备*/
	rc = test_i2c(client);
	if(rc < 0)
		return -1;

	//芯片初始具体化工作,对寄存器的配置可以查看芯片文档
	clr_reg(client);
	reset_chip(client);
	gsl_load_fw(client);
	startup_chip(client);
	reset_chip(client);
	startup_chip(client);
	return 0;
}


static int test_i2c(struct i2c_client *client)
{
	u8 buf;

	buf = 0x12;
	if(gsl_ts_write(client, 0xf0, &buf, 1) < 0)
		return -1;

	buf = 0x00;
	if(gsl_ts_read(client, 0xf0, &buf, 1) < 0)
		return -1;

	if(buf == 0x12)
		return 0;
	return -1;
}

原始的驱动应该很简单,就是一些写操作,后面应该厂家查出 了一些bug,所以又增加了一些修复功能。因为修复功能不开源,所以我们也看不到。

static void startup_chip(struct i2c_client *client) //启动设备开始工作
{
	u8 tmp = 0x00;

#ifdef GSL_NOID_VERSION
	gsl_DataInit(gsl_config_data_id);
#endif
	gsl_ts_write(client, 0xe0, &tmp, 1);
}

static void reset_chip(struct i2c_client *client)  //设备复位操作
{
	u8 tmp = 0x88;
	u8 buf[4] = {0x00};

	gsl_ts_write(client, 0xe0, &tmp, sizeof(tmp));
	msleep(20);
	tmp = 0x04;
	gsl_ts_write(client, 0xe4, &tmp, sizeof(tmp));
	msleep(10);
	gsl_ts_write(client, 0xbc, buf, sizeof(buf));
	msleep(10);
}

static void clr_reg(struct i2c_client *client)
{
	u8 write_buf[4]	= {0};

	write_buf[0] = 0x88;
	gsl_ts_write(client, 0xe0, &write_buf[0], 1);
	write_buf[0] = 0x03;
	gsl_ts_write(client, 0x80, &write_buf[0], 1);
	write_buf[0] = 0x04;
	gsl_ts_write(client, 0xe4, &write_buf[0], 1);
	write_buf[0] = 0x00;
	gsl_ts_write(client, 0xe0, &write_buf[0], 1);
}

整个过程也是和下面的这个过程一样的。

该芯片需要先加载固件才能工作。此固件似乎特定于每个设备型号,因此必须在您的设备中找到它。

因为这个固件确实很长,所以我在这里就列出一部分。


static  struct fw_data GSLX680_FW[] = {

{0xf0,0x2},
{0x00,0x00000000},
{0x04,0x00000000},
{0x08,0x00000000},
{0x0c,0x00000000},
{0x10,0x00000000},
{0x14,0x00000000},
{0x18,0x00000000},
{0x1c,0x00000000},
{0x20,0x00000000},
{0x24,0x00000000},
{0x28,0x00000000},
{0x2c,0x00000000},
{0x30,0x00000000},
{0x34,0x00000000},
{0x38,0x00000000},
{0x3c,0x00000000},
{0x40,0x00000000},
{0x44,0x00000000},
{0x48,0x00000000},
{0x4c,0x00000000},
{0x50,0x00000000},
{0x54,0x00000000},
{0x58,0x00000000},
{0x5c,0x00000000},
{0x60,0x00000000},
{0x64,0xf801001a},
{0x68,0x00066414},
{0x6c,0x1001020a},
{0x70,0x00000fff},
{0x74,0x00000000},
{0x78,0x00000000},
{0x7c,0x0a0f0a0f},
{0xf0,0x3},
{0x00,0xb188ac02},
{0x04,0x00000000},
{0x08,0x00000000},
{0x0c,0x00000000},
{0x10,0x00000000},
{0x14,0x00000000},
{0x18,0x00000000},
{0x1c,0x00000000},
{0x20,0x00000000},
{0x24,0x00005100},
{0x28,0x00008e00},
{0x2c,0x00000000},
{0x30,0x00000000},
{0x34,0x00000000},
{0x38,0x00000000},
{0x3c,0x00000000},
{0x40,0x00000000},
{0x44,0x00000000},
{0x48,0x00000000},
{0x4c,0x00000000},
{0x50,0x00000000},
{0x54,0x00000000},
{0x58,0x00000000},
{0x5c,0x00000000},
{0x60,0x00000000},
{0x64,0x1a0ac00a},
{0x68,0x00000002},
{0x6c,0x0000000f},
{0x70,0x00000000},
{0x74,0xffffffff},
{0x78,0xffffffec},
{0x7c,0x00000000},
{0xf0,0x4},
{0x00,0x00000000},
{0x04,0x0001660b},
{0x08,0x00000064},
{0x0c,0x00000000},
{0x10,0xfe0cff06},
{0x14,0x00000000},
{0x18,0x00000000},
{0x1c,0x00000000},
{0x20,0x00000000},
{0x24,0x00000000},
{0x28,0x00000000},
{0x2c,0x00000000},
{0x30,0x00010000},
{0x34,0x00000fff},
{0x38,0x0000000a},
{0x3c,0x00000258},
{0x40,0x00000000},
{0x44,0x04020a00},
{0x48,0x0014012c},
{0x4c,0x9a000000},
{0x50,0x00000000},
{0x54,0x00010203},
{0x58,0x04050607},
{0x5c,0x08090a0b},
{0x60,0x0c0d0e0f},
{0x64,0x10111213},
{0x68,0x14151617},
{0x6c,0x18191a1b},
{0x70,0x1c1d1e1f},
{0x74,0x0014000a},
{0x78,0x80808080},
{0x7c,0xcba981f4},
{0xf0,0x5},
{0x00,0x00000000},
{0x04,0x00000005},
{0x08,0x000000b4},
{0x0c,0x4d4d3346},
{0x10,0x0000000a},
{0x14,0x00000000},
{0x18,0x00000fff},
{0x1c,0x10410005},
{0x20,0x10450008},
{0x24,0x00000000},
{0x28,0x00000000},
{0x2c,0x00000400},
{0x30,0x80808080},
{0x34,0x80808080},
{0x38,0x80808080},
{0x3c,0x80808080},
{0x40,0x80808080},
{0x44,0x80808080},
{0x48,0x80808080},
{0x4c,0x80808000},
{0x50,0xffffffff},
{0x54,0x00000000},
{0x58,0x00000000},
{0x5c,0x00000000},
{0x60,0x00000000},
{0x64,0x00000000},
{0x68,0x00000000},
{0x6c,0x00000000},
{0x70,0x00000000},
{0x74,0x00000220},
{0x78,0x0000000f},
{0x7c,0x0000000a},
{0xf0,0x6},
{0x00,0x0000000f},
{0x04,0x00000000},
{0x08,0x0000000a},
{0x0c,0x04030402},
{0x10,0x00000032},
{0x14,0x1414140a},
{0x18,0x00000000},
{0x1c,0x00000001},
{0x20,0x00002904},
{0x24,0x00000258},
{0x28,0x00000400},
{0x2c,0xf8010015},
{0x30,0xf8010005},
{0x34,0x00000005},
{0x38,0x00000001},
{0x3c,0x00000fff},
{0x40,0x80000000},
{0x44,0x001a001a},
{0x48,0x00000fff},
{0x4c,0x04040402},
{0x50,0x00010000},
{0x54,0x00000190},
{0x58,0x00004000},
{0x5c,0x1b6db688},
{0x60,0x20100804},
{0x64,0x00000000},
{0x68,0x00000000},
{0x6c,0x00000000},
{0x70,0x00000000},
{0x74,0x000000d2},
{0x78,0x000a003c},
{0x7c,0x00000000},
{0xf0,0x7},
{0x00,0x01040007},
{0x04,0x03060209},
{0x08,0x0508040a},
{0x0c,0x07110610},
{0x10,0x09130812},
{0x14,0x00123456},
{0x18,0x00000000},
{0x1c,0x000a0078},
{0x20,0x00001081},
{0x24,0xff080010},
{0x28,0xff080120},
{0x2c,0xff080140},
{0x30,0xff080160},
{0x34,0x000000fa},
{0x38,0x000000d8},
{0x3c,0x000000b7},
{0x40,0x00000000},
{0x44,0x00500064},
{0x48,0x00000900},
{0x4c,0x320f0f03},
{0x50,0x00000000},
{0x54,0x00000004},
{0x58,0x00020000},
{0x5c,0x00090003},
{0x60,0x000d000a},
{0x64,0x000e000e},
{0x68,0x00020000},
{0x6c,0x00060002},
{0x70,0x00030001},
{0x74,0x00000000},
{0x78,0x00012345},
{0x7c,0x006789ab},
{0xf0,0x8},
{0x00,0x026f01f3},
{0x04,0x028f21f4},
{0x08,0x22af21f5},
{0x0c,0x22cf21f6},
{0x10,0x22ef21f7},
{0x14,0x430f41f8},
{0x18,0x432f41f9},
{0x1c,0x734f01fa},
{0x20,0x01f401f5},
{0x24,0x01f601f7},
{0x28,0x01f801f9},
{0x2c,0x01fa0000},
{0x30,0x00000000},
{0x34,0x00000000},
{0x38,0x00000000},
{0x3c,0x00000000},
{0x40,0x01030507},
{0x44,0x09000000},
{0x48,0x00000000},
{0x4c,0x02040608},
{0x50,0x0a000000},
{0x54,0x00000000},
{0x58,0x00040003},
{0x5c,0x00000008},
{0x60,0x00000190},
{0x64,0x00030201},
{0x68,0x000a0804},
{0x6c,0x006600cd},
{0x70,0x000000cd},
{0x74,0x00000074},
{0x78,0x00000000},
{0x7c,0x0000000a},
{0xf0,0x9},
{0x00,0xff080094},
{0x04,0x00070011},
{0x08,0xff080090},
{0x0c,0x00040000},
{0x10,0xff080068},
{0x14,0x00030000},
{0x18,0xff080064},
{0x1c,0x01002582},
{0x20,0xff080060},
{0x24,0x00000000},
{0x28,0xff08004c},
{0x2c,0x00197fff},
{0x30,0xfffffff0},
{0x34,0x00000000},
{0x38,0xfffffff0},
{0x3c,0x00000000},
{0x40,0xfffffff0},
{0x44,0x00000000},
{0x48,0xfffffff0},
{0x4c,0x00000000},
{0x50,0xfffffff0},
{0x54,0x00000000},
{0x58,0xfffffff0},
{0x5c,0x00000000},
{0x60,0xfffffff0},
{0x64,0x00000000},
{0x68,0xfffffff0},
{0x6c,0x00000000},
{0x70,0xfffffff0},
{0x74,0x00000000},
{0x78,0xfffffff0},
{0x7c,0x00000000},


.....

};

由于发送几个字节将它们存储在相关寄存器中,理论上代码必须只设置0x00作为目标寄存器,并在一次传输中发送该块的所有字节。不幸的是,I2C传输的大小通常有限,因此建议不要构建非常大的数据包,将每个数据块发送到较小的部分。

static void gsl_load_fw(struct i2c_client *client)
{
	u8 buf[DMA_TRANS_LEN*4 + 1] = {0};
	u8 send_flag = 1;
	u8 *cur = buf + 1;
	u32 source_line = 0;
	u32 source_len;
	struct fw_data *ptr_fw;

	//printk("=============gsl_load_fw start==============\n");

	ptr_fw = GSLX680_FW;
	source_len = ARRAY_SIZE(GSLX680_FW);

	for (source_line = 0; source_line < source_len; source_line++)
	{
		/* init page trans, set the page val */
		if (GSL_PAGE_REG == ptr_fw[source_line].offset)
		{
			fw2buf(cur, &ptr_fw[source_line].val);
			gsl_write_interface(client, GSL_PAGE_REG, buf, 4);
			send_flag = 1;
		}
		else
		{
			if (1 == send_flag % (DMA_TRANS_LEN < 0x20 ? DMA_TRANS_LEN : 0x20))
	    			buf[0] = (u8)ptr_fw[source_line].offset;

			fw2buf(cur, &ptr_fw[source_line].val);
			cur += 4;

			if (0 == send_flag % (DMA_TRANS_LEN < 0x20 ? DMA_TRANS_LEN : 0x20))
			{
	    			gsl_write_interface(client, buf[0], buf, cur - buf - 1);
	    			cur = buf + 1;
			}

			send_flag++;
		}
	}

	printk("=============gsl_load_fw end==============\n");

}

6.申请中断

	
	//获取中断号,并且申请中断
	ts->irq=gpio_to_irq(ts->irq_pin);
	if (ts->irq)
	{
		rc=  request_irq(ts->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts);
		if (rc != 0) {
			printk(KERN_ALERT "Cannot allocate ts INT!ERRNO:%d\n", ret);
			goto error_req_irq_fail;
		}
	}

	
	INIT_WORK(&ts->resume_work, gs_ts_work_resume);  //

这里因为是外部中断,所以可以通过gpio编号,反推出irq号,这部分内容可以在pinctrl驱动部分查看。

这里申请的中断是一个上升沿触发的中断。

也就是只要按下就会发生一次中断。如果不松开就不再继续触发中断。所以按下后的操作需要我们来在中断函数中处理了。

最后就是初始化了一个resume主要用于电源管理。

接下来我们来看一下触摸屏驱动部分的重点,

static irqreturn_t gsl_ts_irq(int irq, void *dev_id)
{
	struct gsl_ts *ts = dev_id;

	disable_irq_nosync(ts->irq);  //禁用中断

	if (!work_pending(&ts->work))  //判断等待队列是否挂起
	{
		queue_work(ts->wq, &ts->work);  //调度任务
	}

	return IRQ_HANDLED;

}

这里比较简单,但很重要。就是有按键后,启动一次工作队列。

当然,工作队列我们前面已经初始化好了。

鉴于一些原因,我们把GSL_NOID_VERSION和GSL_MONITOR宏包含的内容暂时先去掉不分析,后面再分析

/*获取数据并且上报数据*/
static void gslX680_ts_worker(struct work_struct *work)
{
	struct gsl_ts *ts = container_of(work, struct gsl_ts,work);
	int rc, i;
	u8 id, touches;
	u16 x, y;



//	printk("=====gslX680_ts_worker=====\n");



	rc = gsl_ts_read(ts->client, 0x80, &ts->touch_data[0], 4);
	if (rc < 0)
	{
		dev_err(&ts->client->dev, "read failed\n");
		goto schedule;
	}
	touches = ts->touch_data[ts->dd->touch_index];  //获取上报几个触摸点的数据

	//获取数据,最多获取九个点的数据
	if(touches > 0)
		gsl_ts_read(ts->client, 0x84, &ts->touch_data[4], 4);
	if(touches > 1)
		gsl_ts_read(ts->client, 0x88, &ts->touch_data[8], 4);
	if(touches > 2)
		gsl_ts_read(ts->client, 0x8c, &ts->touch_data[12], 4);
	if(touches > 3)
		gsl_ts_read(ts->client, 0x90, &ts->touch_data[16], 4);
	if(touches > 4)
		gsl_ts_read(ts->client, 0x94, &ts->touch_data[20], 4);
	if(touches > 5)
		gsl_ts_read(ts->client, 0x98, &ts->touch_data[24], 4);
	if(touches > 6)
		gsl_ts_read(ts->client, 0x9c, &ts->touch_data[28], 4);
	if(touches > 7)
		gsl_ts_read(ts->client, 0xa0, &ts->touch_data[32], 4);
	if(touches > 8)
		gsl_ts_read(ts->client, 0xa4, &ts->touch_data[36], 4);
	if(touches > 9)
		gsl_ts_read(ts->client, 0xa8, &ts->touch_data[40], 4);

	printk("-----touches: %d -----\n", touches);

    printk("\n");
	/*对上报标号和上报状态清零*/
	for(i = 1; i <= MAX_CONTACTS; i ++)
	{
		if(touches == 0)
			id_sign[i] = 0;
		id_state_flag[i] = 0;
	}

	/*判断是是否为指尖触摸*/
	for(i= 0;i < (touches > MAX_FINGERS ? MAX_FINGERS : touches);i ++)
	{

		/*加载上报的数据*/
		x = join_bytes( ( ts->touch_data[ts->dd->x_index  + 4 * i + 1] & 0xf),
				ts->touch_data[ts->dd->x_index + 4 * i]);
		y = join_bytes(ts->touch_data[ts->dd->y_index + 4 * i + 1],
				ts->touch_data[ts->dd->y_index + 4 * i ]);
		id = ts->touch_data[ts->dd->id_index + 4 * i] >> 4;


		if(1 <=id && id <= MAX_CONTACTS)
		{
		#ifdef FILTER_POINT
			filter_point(x, y ,id);  //进行数据过滤
		#else
			record_point(x, y , id);  //保存数据
		#endif
			report_data(ts, x_new, y_new, 10, id); //设置报表上报数据
			id_state_flag[id] = 1;
		}
	}
	for(i = 1; i <= MAX_CONTACTS; i ++)  //检测每个上报点是否设定完毕,并且上报数据
	{
		if( (0 == touches) || ((0 != id_state_old_flag[i]) && (0 == id_state_flag[i])) )
		{
			if(REPORT_DATA_ANDROID_4_0 > 0)
			{
				input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 0);
				input_mt_sync(ts->input);
			}
			if(is_linux > 0)
			{
				report_data(ts, x_new, y_new, 0, i); 
			}
			id_sign[i]=0;
		}
		id_state_old_flag[i] = id_state_flag[i];
	}
	if(0 == touches)   //如果是指尖触摸
	{
		if(REPORT_DATA_ANDROID_4_0 > 0)
			input_mt_sync(ts->input);
	}
	input_sync(ts->input);  //同步上报时间

schedule:

	enable_irq(ts->irq);

}

正式分析上面的读数据之前我们先看一下基本读写函数。

static int gsl_ts_read(struct i2c_client *client, u8 addr, u8 *pdata, unsigned int datalen)
{
	int ret = 0;
	int i = 0;

	if (datalen > 126)
	{
		printk("%s too big datalen = %d!\n", __func__, datalen);
		return -1;
	}
	for(i=0; i<datalen; i++){
		ret = gsl_ts_readbyte(client, addr+i, pdata+i);
		if(ret < 0)
			return ret;
	}
	return ret;
}

int gsl_ts_readbyte(struct i2c_client *client, u8 addr, u8 *pdata)
{
	int ret = 0;
	ret = gsl_ts_write(client, addr, NULL, 0);
	if (ret < 0)
	{
		printk("%s set data address fail!\n", __func__);
		return ret;
	}

	return i2c_master_recv(client, pdata, 1);
}


static int gsl_ts_write(struct i2c_client *client, u8 addr, u8 *pdata, int datalen)
{
	int ret = 0;
	u8 tmp_buf[128];
	unsigned int bytelen = 0;
	if (datalen > 125)
	{
		printk("%s too big datalen = %d!\n", __func__, datalen);
		return -1;
	}

	tmp_buf[0] = addr;
	bytelen++;

	if (datalen != 0 && pdata != NULL)
	{
		memcpy(&tmp_buf[bytelen], pdata, datalen);
		bytelen += datalen;
	}

	ret = i2c_master_send(client, tmp_buf, bytelen);
	return ret;
}

可以看到,基本上都是i2c那边提供放入的接口函数。

正式开始分析:

	rc = gsl_ts_read(ts->client, 0x80, &ts->touch_data[0], 4);
	if (rc < 0)
	{
		dev_err(&ts->client->dev, "read failed\n");
		goto schedule;
	}
	touches = ts->touch_data[ts->dd->touch_index];  //获取上报几个触摸点的数据

读0x80寄存器,读四个字节,也就是获取按键个数。

	rc = gsl_ts_read(ts->client, 0x80, &ts->touch_data[0], 4);

获取点的个数,以为前面我们初始化的touch_index为0,即读的0x80寄存器的值就是点的个数(读了四个字节,我们只使用第一个字节)

	touches = ts->touch_data[ts->dd->touch_index];  //获取上报几个触摸点的数据

因为这款触摸屏支持10点触摸,所以根据点的个数。读取按键值。

前面我们也已经知道了x轴的坐标是从6开始,y轴坐标是从4开始的。两个坐标合起来加上id总共是4个字节。每多一个触摸点,坐标位置向后推四个字节。

第一个按键的寄存器地址是0x84开始

	//获取数据,最多获取10个点的数据
	if(touches > 0)
		gsl_ts_read(ts->client, 0x84, &ts->touch_data[4], 4);
	if(touches > 1)
		gsl_ts_read(ts->client, 0x88, &ts->touch_data[8], 4);
	if(touches > 2)
		gsl_ts_read(ts->client, 0x8c, &ts->touch_data[12], 4);
	if(touches > 3)
		gsl_ts_read(ts->client, 0x90, &ts->touch_data[16], 4);
	if(touches > 4)
		gsl_ts_read(ts->client, 0x94, &ts->touch_data[20], 4);
	if(touches > 5)
		gsl_ts_read(ts->client, 0x98, &ts->touch_data[24], 4);
	if(touches > 6)
		gsl_ts_read(ts->client, 0x9c, &ts->touch_data[28], 4);
	if(touches > 7)
		gsl_ts_read(ts->client, 0xa0, &ts->touch_data[32], 4);
	if(touches > 8)
		gsl_ts_read(ts->client, 0xa4, &ts->touch_data[36], 4);
	if(touches > 9)
		gsl_ts_read(ts->client, 0xa8, &ts->touch_data[40], 4);

每个手指触摸按下,如果不松开的话,坐标会变,但id是不会变的。

这里先清掉id,当然如何目前有按键的话只清id_state_flag

	/*对上报标号和上报状态清零*/
	for(i = 1; i <= MAX_CONTACTS; i ++)
	{
		if(touches == 0)
			id_sign[i] = 0;
		id_state_flag[i] = 0;
	}

前面我们知道了按键按下,x的坐标存在6、7字节上。id存在第7字节的高四位,y坐标存在4、5字节。

下面就是对已知触摸的位置进行坐标解析。

当然,这里还对按键数据做了一些过滤操作。

	/*判断是是否为指尖触摸*/
	for(i= 0;i < (touches > MAX_FINGERS ? MAX_FINGERS : touches);i ++)
	{

		/*加载上报的数据*/
		x = join_bytes( ( ts->touch_data[ts->dd->x_index  + 4 * i + 1] & 0xf),
				ts->touch_data[ts->dd->x_index + 4 * i]);
		y = join_bytes(ts->touch_data[ts->dd->y_index + 4 * i + 1],
				ts->touch_data[ts->dd->y_index + 4 * i ]);
		id = ts->touch_data[ts->dd->id_index + 4 * i] >> 4;

		if(1 <=id && id <= MAX_CONTACTS)
		{
		#ifdef FILTER_POINT
			filter_point(x, y ,id);  //进行数据过滤
		#else
			record_point(x, y , id);  //保存数据
		#endif
			report_data(ts, x_new, y_new, 10, id); //设置报表上报数据
			id_state_flag[id] = 1;
		}
	}

上报按键值,这里就是常规的输入子系统的上报流程了。

当然,压力为我们可以不用管,因为这块触摸屏是没触摸压力传感器的。

/*上报位移数据*/
static void report_data(struct gsl_ts *ts, u16 x, u16 y, u8 pressure, u8 id)
{
	swap(x, y);

	if(x > SCREEN_MAX_X || y > SCREEN_MAX_Y)
	{
	#ifdef HAVE_TOUCH_KEY
		report_key(ts,x,y);
	#endif
		return;
	}
	if(is_linux > 0)
	{
		input_report_abs(ts->input, ABS_X, x); 
	 	input_report_abs(ts->input, ABS_Y, y); //设置触摸屏支持上报绝对位移事件y轴
		if(pressure != 0)
			input_report_key(ts->input, BTN_TOUCH, 1);
		else
			input_report_key(ts->input, BTN_TOUCH, 0);
		input_sync(ts->input);
	}
	else
	{
		input_report_abs(ts->input, ABS_MT_PRESSURE, id);
		input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 1);
		input_report_abs(ts->input, ABS_MT_POSITION_X, x);
		input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
		input_mt_sync(ts->input);
	}
}

其它的就都属于输入子系统的知识了我们这里暂时就不再看了。

因为我这边没这个触摸屏的完整的手册,所以优化部分我这里给出一些思路。

1.触摸屏坐标

通常触摸屏是装载在lcd显示器上的,所以一般我们会设置触摸屏的x轴的最大长度为触摸屏x轴长度。触摸屏的y轴的最大长度为触摸屏y轴长度。

但实际上,触摸屏的分辨率可以从它的手册或原理图看到。

就我们这个最大分辨率为14 * 9,可见是比屏幕分分辨率要小很多的。

当然我们手指也在触摸也是足够了。 芯片内部会通过一定的算法来让一些水平移动的触摸更接近是一条平滑的曲线。

当然正常情况下触摸屏会驱动程序会在初始化阶段,给到两个寄存器用来让我们设置我们屏幕的分辨率。之后读回来的触摸屏x和y轴的坐标都是和我们按下屏幕的x和y的坐标对应的。

但这个厂家的因为我没收据手册,所以只能用一种比较笨的方式。

比如这个触摸屏是一个7寸的触摸屏,默认厂家会以一种触摸屏尺寸为x和y坐标值。

比如7寸常见的有800*480,800*600,1024*600等。

假设厂家默认所在屏的分辨率为800*600,我们的实际的1024*600,那么驱动程序中就要对堵回来的x坐标进行一个比例参数调整。

如何知道厂家默认的分辨率?

1.查数据手册

2.驱动程序打印坐标。

驱动程序打印坐标,我们分别按下四个角的按键,查看打印的值。

比如我这里

可以看到x轴的最大不超过945,y轴的最大621。

可以猜测这个屏幕默认尺寸可能是1024*768的。

当然也可以认为是945*621的分辨率。

接下来如果我们实际的屏幕分辨率如果接近,则可以认为是不用转换的。

如果差距比较大,那就要按照我们的屏幕和触摸屏默认尺寸计算一个比例参数。之后对读出来的值进行转换。

2.参数滤波

从前面的几个按键中我们也可以看到,实际出来的坐标可能会偏差很大 。多以我们需要进行滤波操作。

常用的就是读多次,取平均值。

但这里很明显比如下面的第一个的y坐标是一个很大的无效值。会对平均值进行很大干扰。

工作中通常会使用多种滤波的结合,下面我给出一种解决方案。

假设我们一次快速按键,可以触发5次数据读取(下图就是我的一次快读按键,9次打印)

所以5次数据读取,进行一次事件上报是可行的。

  1. 去除无效坐标
  2. 去掉最大值和最小值
  3. 取平均值

对5次数据进行上面三种操作后,通常情况下数据就会非常的平稳。

当然有时候5次按键,如果有超过2个按键是无效值,我们可以认为这次按键无效,等等进行优化处理。

测试:

我们这里使用tslib来进行测试

tslib在github有仓库,所以我们在这里拉取

https://github.com/libts/tslib

执行下面这个脚本配置,检查一下本地库

./autogen.sh 

结果出现了下面告警,找不到相关库。

configure.ac:64: error: possibly undefined macro: AC_PROG_LIBTOOL
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.

搜索后说是缺少libtool

apt-get install libtool

我这边因为更新出现问题,也就是找不到下载包

Reading package lists... Error!
E: Encountered a section with no Package: header
E: Problem with MergeList /var/lib/apt/lists/ftp.sjtu.edu.cn_ubuntu_dists_precise-security_restricted_binary-i386_Packages
E: The package lists or status file could not be parsed or opened.

所以删掉了之前的源,重新更新了源

  sudo rm /var/lib/apt/lists/* -vf
 sudo apt-get update

之后配置,编译,安装

CC=arm-none-linux-gnueabi-gcc ./configure --prefix=$(PWD)/tslib_tmp --host=arm-none-linux-gnueabi 
make

make install

把tslib_tmp目录下所有的文件都拷贝到根文件系统中

cp *    /xxxx/xx/   -rfd

把所有的使用到的东西导出到环境变量

export TSLIB_TSDEVICE=/dev/input/event0
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

TSLIB_TSDEVICE        	//触摸屏设备文件名。 
TSLIB_CALIBFILE        	//校准的数据文件,由ts_calibrate校准程序生成(电阻屏使用,电容无所谓)。 
SLIB_CONFFILE        	//配置文件名。 
TSLIB_PLUGINDIR         //插件目录 
TSLIB_CONSOLEDEVICE 	//控制台设备文件名 
TSLIB_FBDEVICE        	//输出设备名

当前我这里最开始触摸屏调的还是不好,是有跳帧的,后面画了两个小时优化了一下算法,基本上是可以写字之类了。

如果想要优化的更好,那就需要更升入的学习触摸屏的基本知识,比如触摸屏芯片中的算法是怎么输出的,这样知此知彼才能做更好的产品。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/89967840