GT928 TP驱动跟读及虚拟按键上报解析

  • 目前公司TP常用一套代码。MTK 平台使用.ko形式加载,所以跟读一下加深理解。
static struct i2c_driver tpd_i2c_driver = {
	.driver = {
		.of_match_table = of_match_ptr(gt9xx_dt_match),
	},
	.probe = tpd_i2c_probe,
	.remove = tpd_i2c_remove,
	.detect = tpd_i2c_detect,
	.driver.name = "gt9xx",
	.id_table = tpd_i2c_id,
	.address_list = (const unsigned short *)forces,
};

static int __init tpd_driver_init(void)
{
	print_info("gt9xx touch panel driver init\n");

	if (i2c_add_driver(&tpd_i2c_driver) != 0) {
		print_info("unable to add i2c driver.\n");
		return -1;
	}

	return 0;
}

static void __exit tpd_driver_exit(void)
{
	print_info("gt9xx touch panel driver exit\n");

	i2c_del_driver(&tpd_i2c_driver);
}

使用i2c_add_driver();去加载i2c驱动(由于同一套代码需要兼容很多的屏和TP,导致TP IC需要兼容不同厂商的不同IC)如果i2c地址一样并且使用i2c_add_driver()成功注册了i2c设备并且probe不报错,那么这时候兼容就会有问题(比如gt2xx厂商给的驱动是在module_init调用一个自定义函数去实现goodix_i2c_bus_init,然后注册platform_driver_register(&goodix_ts_driver))。这样就算是给了不同的屏加载驱动的时候platform_driver_register 的probe会报错,但是goodix_i2c_bus_init 调用的i2c_add_driver注册的设备probe就不会报错,那么sys/class/i2c-0/0-005d这个设备就会注册成功,导致后面的驱动i2c_add_driver()失败。)

eg:

static int __init goodix_ts_core_init(void)
{
	int ret;

	ts_info("Core layer init:%s", GOODIX_DRIVER_VERSION);
#ifdef CONFIG_TOUCHSCREEN_GOODIX_BRL_SPI
	ret = goodix_spi_bus_init();
#else
	ret = goodix_i2c_bus_init();
#endif
	if (ret) {
		ts_err("failed add bus driver");
		return ret;
	}
	return platform_driver_register(&goodix_ts_driver);
}
----->
int goodix_i2c_bus_init(void)
{
	ts_info("Goodix i2c driver init");
	return i2c_add_driver(&goodix_i2c_driver);
}

解决办法就是先加载那个i2c probe会失败的那个,但是解决中又遇到了,reset时序的问题,如果先加载i2c probe会失败的那个后面的时序会对不上。(由于是采用.ko形式存在内核中,如果probe失败执行rmmod那么下一个驱动就可以成功这样就可以写一个shell脚本,在驱动使用DEVICE_ATTR(attr*)show 一个全局变量 shell中使用cat 去读对应节点的值,如果为0就是驱动还没加载完的等一下,如果为1 就是这个TP就是我的驱动的你不用rmmod 如果为-1就是执行rmmod 要在init.rc里面去执行这个.sh脚本)

接下来看GT928.c的驱动:

在这里插入图片描述
** 1.gt9xx_get_gpio_info()以及init_powerup();是获取pinctl的gpio并设置gpio状态 **
使用了devm_pinctrl_get(dev);
pinctrl_lookup_state
pinctrl_select_state
正常是要使用devm_pinctrl_put(dev)去释放gpio资源的,也可以不使用因为驱动报错之后会自动回收gpio资源。
** 2. gtp_i2c_read(0x8140寄存器,并将读取的值放在test_buf[3~12里面根据表格就是读的product_ID])
在这里插入图片描述
** 3 tp_init_param(test_data, 16); **
初始话tp参数,这里使用了module_param() 从insmod里面获取参数

insmod /vendor/lib/modules/gt9xx_driver.ko screen_inch=${ro.hw.lcm.inch:-0}

就是获取到TP是多少寸的。

void tp_init_param(u8 *buf, int len)
{
	struct fb_info *fb_info = registered_fb[0];
	char *p = virtual_keys;  //指针P指向定义的虚拟按键buf

	p += sprintf(p, "# GT9xx %s' :%02X %02X %02X %02X %02X, %02X %02X, %02X %02X, %02X.\n", screen_inch,
					buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12]);

	if (fb_info) {
		pr_err("GT9xx get LCM size %dx%d\n", fb_info->var.xres, fb_info->var.yres);
	}
	/*
	* 通过之前gtp_i2c_read读取的data[0] data[1] 寄存器里的值判断该TP的信号去设置虚拟按键区域的大小 buf[3~12]
	* __stringify函数的作用是 C 的宏,它在编译时将表达式转换为字符串文本。调试包含变量或常量值的输出
	* 这样就会通过sprintf把数据打印并存储到*P中。
	*/
	if(buf[3] == 0x32 && buf[4] == 0x37 && buf[5] == 0x31 && buf[6] == 0x40 && buf[7] == 0x10 && buf[8] == 0x31 \
		&& buf[9] == 0x4 && buf[10] == 0x58 && buf[11] == 0x2 && buf[12] == 0x0)    //NO 1
	{
		p += sprintf(p, "# build %s at %d\n", __TIME__, __LINE__);
		p += sprintf(p,

		/*
		* 虚拟按键填充规则:当然,在这里tpd keys这个定义key的数组和定义区域的tpd keys dim要准确的填充才可以的。具体的填充的规则如下:
		*	每一个虚拟按键有六个参数:
		*	1、0x01: A version code. Must always be Ox91.
		*	2、<Linux key code>: The Linux key code of the virtual key.
		*	3、<centerx>: The X pixel coordinate of the center of the virtual key.
		*	4、<centerY>: The Y pixel coordinate of the center of the virtual key.
		*	5、<width>: The width of the virtual key in pixels.
		*	6、<height>: The height of the virtual key in pixels.6
		*   EV_KEY:KEY_CLOSECD:1065:107:50:40
		* 	EV_KEY:通过_set_bit去设置input数据类型为按键
		*	KEY_CLOSECD:键值都在内核定义好了,内核上报之后会进入对应的上层进行处理。
		*   centerx: 按键中心X轴坐标  : centerY 按键中心Y轴坐标  : width 按键宽 :height 按键长 (通过这四个值的数据确定一个虚拟按键在TP上的位置)
		*   
		*/
            __stringify(EV_KEY) ":" __stringify(KEY_CLOSECD)    ":1065:107:50:40\n"
            __stringify(EV_KEY) ":" __stringify(KEY_HOMEPAGE)   ":1065:155:50:40\n"
            __stringify(EV_KEY) ":" __stringify(KEY_BACK)       ":1065:238:50:40\n"
            __stringify(EV_KEY) ":" __stringify(KEY_VOLUMEUP)   ":1065:320:50:40\n"
            __stringify(EV_KEY) ":" __stringify(KEY_VOLUMEDOWN) ":1065:380:50:40\n");
	}

这里会设置虚拟按键的区域以及键值

** 4、注册设备以及文件系统 **
kobject_create_and_add()
sysfs_create_group()
input_allocate_device
** 5、设置input 结构体的一些成员 **
gtp_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;
这表示这个设备支持SYN KEY ABS设备 同步 键盘 绝对坐标事件

#if GTP_HAVE_TOUCH_KEY
	static const u16 touch_key_array[] = {KEY_F3, KEY_F4, KEY_F5, KEY_F6};
	#define GTP_MAX_KEY_NUM	 (sizeof(touch_key_array)/sizeof(touch_key_array[0]))
#endif

#if GTP_HAVE_TOUCH_KEY
	for (index = 0; index < GTP_MAX_KEY_NUM; index++) {
		input_set_capability(gtp_dev, EV_KEY, touch_key_array[index]);
	}
#endif

== 设置输入设备的功能函数。这里有一个疑问?为什么touch_key_array[] = {KEY_F3, KEY_F4, KEY_F5, KEY_F6};写死了为const,而在为什么又在3 tp_init_param()函数中写出KEY_CLOSECD等key值???==
填充dev结构体的一些成员信息

	/* 设置绝对轴输入事件的范围参数: 最小值 最大值 模糊筛选:0 平坦过滤:0 */
	input_set_abs_params(gtp_dev, ABS_X, 0, screen_size[0], 0, 0);
	input_set_abs_params(gtp_dev, ABS_Y, 0, screen_size[1], 0, 0);
	input_set_abs_params(gtp_dev, ABS_PRESSURE, 0, 255, 0, 0);
	input_set_abs_params(gtp_dev, ABS_MT_POSITION_X, 0, screen_size[0], 0, 0);
	input_set_abs_params(gtp_dev, ABS_MT_POSITION_Y, 0, screen_size[1], 0, 0);
	input_set_abs_params(gtp_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
	input_set_abs_params(gtp_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
	//input_set_abs_params(gtp_dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
	input_set_abs_params(gtp_dev, ABS_MT_TRACKING_ID, 0, GTP_MAX_TOUCH, 0, 0);

	sprintf(phys, "input/ts");
	gtp_dev->name = "TS_GT9xx";
	gtp_dev->phys = phys;
	gtp_dev->id.bustype = BUS_I2C;
	gtp_dev->id.vendor = 0xDEAD;
	gtp_dev->id.product = 0xBEEF;
	gtp_dev->id.version = 10427;

	ret = input_register_device(gtp_dev);  //注册Input设备
	if (ret) {
		pr_err("Register %s input device failed", gtp_dev->name);
		return -ENODEV;
	}
	fb_register_client(&pm_event_notifier);

** 创建内核线程,监听中断事件 **

	thread = kthread_run(touch_event_handler, 0, TPD_DEVICE);  //开辟内核多线程,用于事件监听上报

	if (IS_ERR(thread)) {
		err = PTR_ERR(thread);
		print_info(TPD_DEVICE " failed to create kernel thread: %d", err);
		return -1; 
	}
	tpd_irq_registration(); //注册中断

首先执行touch_event_handler函数

static int touch_event_handler(void *unused)
{
	struct sched_param param = { .sched_priority = 4 };
	sched_setscheduler(current, SCHED_RR, &param);

	do {
		set_current_state(TASK_INTERRUPTIBLE);
		wait_event_interruptible(waiter, tpd_flag || kthread_should_stop());
		set_current_state(TASK_RUNNING);

		if (tpd_flag)
			report_data_handle();
		tpd_flag = 0;

	} while (!kthread_should_stop());

	return 0;
}

它会阻塞在wait_event_interruptible()函数这里等待又事件上报
参数:等待队列头waiter
tpd_flag || kthread_should_stop() 当这个变为true时开始执行下一步。
一直阻塞在这里直到tpd_irq_registration中断注册函数调用。

static int tpd_irq_registration(void)
{
	struct device_node *node = NULL;
	int ret = 0;
	u32 ints[2] = { 0, 0 };
	const int32_t irqtype = override_irq_type > 0 ? override_irq_type : irq_type;

	node = of_find_compatible_node(NULL, NULL, "mediatek,cap_touch");

	if (node) {
		of_property_read_u32_array(node, "debounce", ints, ARRAY_SIZE(ints));
		print_info("debounce = %d %d\n", ints[0],ints[1]);

		gpio_set_debounce(ints[0], ints[1]);

		/*touch_irq = gpio_to_irq(tpd_int_gpio_number);*/
		touch_irq = irq_of_parse_and_map(node, 0);//40
		print_info("---cgx--#####touch_irq number %d\n", touch_irq);
		if (irqtype == GT_INT_TRIGGER_RISING) {/* EINTF_TRIGGER */
			print_info("GT_INT_TRIGGER_RISING\n");
			ret = request_irq(touch_irq, tpd_interrupt_handler, IRQF_TRIGGER_RISING, TPD_DEVICE, NULL);
			if (ret > 0)
				print_info("tpd request_irq IRQ LINE NOT AVAILABLE!.");
		} else {
			print_info("GT_INT_TRIGGER_FAILING\n");
			ret = request_irq(touch_irq, tpd_interrupt_handler, IRQF_TRIGGER_FALLING, TPD_DEVICE, NULL);
			if (ret > 0)
				print_info("tpd request_irq IRQ LINE NOT AVAILABLE!.");
		}
	} else {
		print_info("tpd request_irq can not find touch eint device node!.");
	}

	return ret;
}

核心使用了request_irq(handle…,rising) 上升沿触发。
看中断处理函数

static irqreturn_t tpd_interrupt_handler(int irq, void *dev_id)
{
	tpd_flag = 1;
	wake_up_interruptible(&waiter);
	return IRQ_HANDLED;
}
wake_up_interruptible是 Linux 内核中的一个函数,用于唤醒在给定等待队列上等待的所有进程,
并将它们标记为中断。此函数通常与 结合使用,使调用进程进入休眠状态,直到满足给定条件。
当满足条件时,内核将调用以唤醒等待该条件的任何进程。wait_event_interruptible  wake_up_interruptible

这时候线程就不阻塞了。然后走到
report_data_handle

static void report_data_handle(void)
{
	u8	end_cmd[3] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF, 0};
	u8	point_data[2 + 1 + 8 * GTP_MAX_TOUCH + 1] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF};  //触摸数据寄存器0x814E  {0x81,0x4E}
	u8	touch_num = 0;
	u8	finger = 0;
	static u8 pre_touch = 0;
	static u8 pre_key = 0;
	static u8 need_calibrate = 0;
	u8	key_value = 0;
	u8 *coor_data = NULL;
	s32 id = 0;
	s32 i  = 0;
	s32 ret = -1;

	mdelay(5);

	ret = gtp_i2c_read(i2c_client, point_data, 12);  
	if (ret < 0) {
		pr_err("I2C transfer error. errno:%d\n ", ret);
		msleep_interruptible(800);
		init_powerup();
		msleep_interruptible(200);
		return;
	}

	finger = point_data[GTP_ADDR_LENGTH];

	if ((finger & 0x80) == 0) {
		goto exit_work_func;
	}

	touch_num = finger & 0x0f;

	// print_info("====touch_num = %d====\n", touch_num);

	if (touch_num > GTP_MAX_TOUCH) {
		goto exit_work_func;
	}

	if (touch_num > 1) {
		 //如果是多点触摸就 读ponit 2的寄存器值 0x814E +10 =0x8158 8158~815B 存储着xy的坐标值 最多支持5点触摸,即最多有point5的寄存器存储触摸数据
		u8 buf[8 * GTP_MAX_TOUCH] = {(GTP_READ_COOR_ADDR + 10) >> 8, (GTP_READ_COOR_ADDR + 10) & 0xff}; 
		ret = gtp_i2c_read(i2c_client, buf, 2 + 8 * (touch_num - 1));
		memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));
	}

#if GTP_HAVE_TOUCH_KEY
	key_value = point_data[3 + 8 * touch_num];

	if (key_value || pre_key)
	{
		for (i = 0; i < GTP_MAX_KEY_NUM; i++)
		{
			input_report_key(gtp_dev, touch_key_array[i], key_value & (0x01<<i));    
		}
		touch_num = 0;
		pre_touch = 0;
	}
#endif

	pre_key = key_value;

	if (pre_touch || touch_num) {
		uint32_t pos = 0;
		uint32_t touch_index = 0;

		coor_data = &point_data[3];
		if (touch_num)
		{
			id = coor_data[pos] & 0x0F;
			touch_index |= (0x01<<id);
		}

		for (i = 0; i < GTP_MAX_TOUCH; i++) {
			if (touch_index & (0x01<<i))
			{
				s32 input_x  = coor_data[pos + 1] | coor_data[pos + 2] << 8;
				s32 input_y  = coor_data[pos + 3] | coor_data[pos + 4] << 8;
				s32 input_w  = coor_data[pos + 5] | coor_data[pos + 6] << 8;

				bool report = true;
				const bool inscreen = (input_x < screen_size[0]) && (input_y < screen_size[1]);
				const bool down = !(pre_touch & (0x01 << i));  // 按下操作



				// 只有按下时才需要判断是否需要校正
				if (down) {
					if (inscreen)
						need_calibrate |= (1 << i);
					else
						need_calibrate &= ~(1 << i);
				}

				if (need_calibrate & (1 << i)) {
					calibrate(&input_x, &input_y);
				} else {
					report = !inscreen;
				}

//

				if (report)
					tpd_down(input_x, input_y, input_w, id);
				pre_touch |= 0x01 << i;

				pos += 8;
				id = coor_data[pos] & 0x0F;
				touch_index |= (0x01<<id);
			}
			else // if (pre_touch & (0x01 << i))
			{
				tpd_up(i);
				pre_touch &= ~(0x01 << i);
				need_calibrate &= ~(1 << i);
			}
		}
	}

	input_sync(gtp_dev);

exit_work_func:

	if (!gtp_rawdiff_mode) {
		ret = gtp_i2c_write(i2c_client, end_cmd, 3);
		if (ret < 0) {
			pr_err("I2C write end_cmd  error!");
		}
	}

}

这里核心去读取寄存器里面的值,会判断是否支持多点触摸以及是否又触摸按键。
读的寄存器列表
在这里插入图片描述
这里可以看出这最多支持5点触摸。

		for (i = 0; i < GTP_MAX_TOUCH; i++) {
			if (touch_index & (0x01<<i))
			{
				s32 input_x  = coor_data[pos + 1] | coor_data[pos + 2] << 8;
				s32 input_y  = coor_data[pos + 3] | coor_data[pos + 4] << 8;
				s32 input_w  = coor_data[pos + 5] | coor_data[pos + 6] << 8;

				bool report = true;
				const bool inscreen = (input_x < screen_size[0]) && (input_y < screen_size[1]);
				const bool down = !(pre_touch & (0x01 << i));  // 按下操作

这里把取出来的值给到一个变量,后面进行report_ads(…)
自此TP中断就完了。
还有一个按键上报。

#if GTP_HAVE_TOUCH_KEY
	key_value = point_data[3 + 8 * touch_num];

	if (key_value || pre_key)
	{
		for (i = 0; i < GTP_MAX_KEY_NUM; i++)
		{
			input_report_key(gtp_dev, touch_key_array[i], key_value & (0x01<<i));    
		}
		touch_num = 0;
		pre_touch = 0;
	}
#endif

为什么这里上报的键值为F3~F5 实际应该为 KEY_CLOSECD,KEY_HOMEPAGE,KEY_BACK,KEY_VOLUMEUP,KEY_VOLUMEDOWN吧?
附上android input框架
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值