MTK平台TP驱动框架解析

一,TP驱动代码的组成

MTK平台TP驱动主要包括两处文件:

1,kernel-3.18\drivers\input\touchscreen\mediatek\mtk_tpd.c

平台设备驱动,主要为了兼容多个不同的TP驱动IC,它会去遍历每一个编译并添加进 tpd_driver_list 中的IC驱动,直到其中一个初始化成功。

2,kernel-3.18\drivers\input\touchscreen\mediatek\XXX

XXX文件为具体的TP驱动文件,如:汇鼎,思立微,敦泰 等等,一般由IC厂提供,然后移植到各平台的代码中(移植方法后面会讲到),主要实现了对IC的上电和初始化,注册输入设备,获取并上报数据等。

二,mtk_tpd.c分析

首先在装载入口函数 tpd_device_init 中创建了一个工作队列,调用 tpd_init_work_callback 函数,用于注册平台总线。

static struct platform_driver tpd_driver = {
	.remove = tpd_remove,
	.shutdown = NULL,
	.probe = tpd_probe,
	.driver = {
			.name = TPD_DEVICE,
			.pm = &tpd_pm_ops,
			.owner = THIS_MODULE,
			.of_match_table = touch_of_match,	//匹配设备树
	},
};

/* called when loaded into kernel */
static void tpd_init_work_callback(struct work_struct *work)
{
	TPD_DEBUG("MediaTek touch panel driver init\n");
	if (platform_driver_register(&tpd_driver) != 0)	//注册平台总线驱动tpd_driver
		TPD_DMESG("unable to register touch panel driver.\n");
}

int tpd_device_init(void)
{
	int res = 0;
    
        //创建并初始化一个工作队列,调用tpd_init_workqueue回调函数
	tpd_init_workqueue = create_singlethread_workqueue("mtk-tpd");	
	INIT_WORK(&tpd_init_work, tpd_init_work_callback);
	
	res = queue_work(tpd_init_workqueue, &tpd_init_work);	//将设备入队
	if (!res)
		pr_err("tpd : touch device init failed res:%d\n", res);
	return 0;
}
EXPORT_SYMBOL(tpd_device_init);

与设备树中的device匹配成功后,调用 tpd_probe 方法

static const struct file_operations tpd_fops = {
/* .owner = THIS_MODULE, */
	.open = tpd_misc_open,		//tp杂类设备open
	.release = tpd_misc_release,//tp杂类设备注销
	.unlocked_ioctl = tpd_unlocked_ioctl,//使用ioctl操作tpd杂类设备
#ifdef CONFIG_COMPAT
	.compat_ioctl = tpd_compat_ioctl,//调用tpd_unlocked_ioctl中的TPD_GET_FILTER_PARA命令
#endif
};

static struct miscdevice tpd_misc_device = {
	.minor = MISC_DYNAMIC_MINOR,	//次设备号
	.name = "touch",		//设备名称
	.fops = &tpd_fops,		//杂类设备操作集合
};

/* touch panel probe */
static int tpd_probe(struct platform_device *pdev)
{
	int touch_type = 1;	/* 0:R-touch, 1: Cap-touch */	//选择电阻屏或电容屏
	int i = 0;
#ifndef CONFIG_CUSTOM_LCM_X
#ifdef CONFIG_LCM_WIDTH
	unsigned long tpd_res_x = 0, tpd_res_y = 0;
	int ret = 0;
#endif
#endif

	TPD_DMESG("enter %s, %d\n", __func__, __LINE__);

	if (misc_register(&tpd_misc_device))	//把tpd作为misc杂类设备进行注册,其实就是一个封装过的字符设备
		pr_err("mtk_tpd: tpd_misc_device register failed\n");
	tpd_get_gpio_info(pdev);		//获取gpio配置信息
	tpd = kmalloc(sizeof(struct tpd_device), GFP_KERNEL);	//分配一个mtk_tpd_device
	if (tpd == NULL)
		return -ENOMEM;
	memset(tpd, 0, sizeof(struct tpd_device));

	/* allocate input device */
	tpd->dev = input_allocate_device();	//分配一个input设备
	if (tpd->dev == NULL) {
		kfree(tpd);
		return -ENOMEM;
	}
.............................

在 tpd_probe 方法中,先选择触摸屏的种类和一些其他参数信息,然后把tpd作为misc设备进行注册,之后会从设备树获取gpio配置信息

int tpd_get_gpio_info(struct platform_device *pdev)
{
	int ret;

	TPD_DEBUG("[tpd %d] mt_tpd_pinctrl+++++++++++++++++\n", pdev->id);
	pinctrl1 = devm_pinctrl_get(&pdev->dev)
	if (IS_ERR(pinctrl1)) {
		ret = PTR_ERR(pinctrl1);
		dev_err(&pdev->dev, "fwq Cannot find touch pinctrl1!\n");
		return ret;
	}
	pins_default = pinctrl_lookup_state(pinctrl1, "default");//通过gpio子系统的函数,获取引脚的状态
..............................
	TPD_DEBUG("[tpd%d] mt_tpd_pinctrl----------\n", pdev->id);
	return 0;
}

回到tpd_probe中,之后会分配一个input设备,并进行初始化,然后将属性设置为 INPUT_PROP_DIRECT,即为触摸屏

	/* allocate input device */
	tpd->dev = input_allocate_device();	//分配一个input设备
	if (tpd->dev == NULL) {
		kfree(tpd);
		return -ENOMEM;
	}
	TPD_RES_X = simple_strtoul(LCM_WIDTH, NULL, 0);   //获取屏的宽度和高度
	TPD_RES_Y = simple_strtoul(LCM_HEIGHT, NULL, 0);
	if (2560 == TPD_RES_X)
		TPD_RES_X = 2048;
	if (1600 == TPD_RES_Y)
		TPD_RES_Y = 1536;
	pr_debug("mtk_tpd: TPD_RES_X = %lu, TPD_RES_Y = %lu\n", TPD_RES_X, TPD_RES_Y);

	tpd_mode = TPD_MODE_NORMAL;		//设置tp的模式
	tpd_mode_axis = 0;
	tpd_mode_min = TPD_RES_Y / 2;
	tpd_mode_max = TPD_RES_Y;
	tpd_mode_keypad_tolerance = TPD_RES_X * TPD_RES_X / 1600;
	/* struct input_dev dev initialization and registration */	
	tpd->dev->name = TPD_DEVICE;		//设置输入子系统必备的参数
	set_bit(EV_ABS, tpd->dev->evbit);		// 设置为绝对事件
	set_bit(EV_KEY, tpd->dev->evbit);		// 设置为按键事件
	set_bit(ABS_X, tpd->dev->absbit);		// 设置绝对x
	set_bit(ABS_Y, tpd->dev->absbit);		// 设置绝对y
	set_bit(ABS_PRESSURE, tpd->dev->absbit);// 设置绝对触摸压力值
#if !defined(CONFIG_MTK_S3320) && !defined(CONFIG_MTK_S3320_47)\
	&& !defined(CONFIG_MTK_S3320_50) && !defined(CONFIG_MTK_MIT200) \
	&& !defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) && !defined(CONFIG_MTK_S7020) \
	&& !defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50)
	set_bit(BTN_TOUCH, tpd->dev->keybit);
#endif /* CONFIG_MTK_S3320 */
	set_bit(INPUT_PROP_DIRECT, tpd->dev->propbit);	//设置输入属性,INPUT_PROP_DIRECT为触摸屏

	/* save dev for regulator_get() before tpd_local_init() */
	tpd->tpd_dev = &pdev->dev;	//保存设置的值到tpd->tpd_dev结构体中,初始化tpd

然后会遍历mtk的 tpd_driver_list 里面的所有的驱动,当名字不为null时,调用具体TP驱动的 tpd_local_init 函数。

	/* save dev for regulator_get() before tpd_local_init() */
	tpd->tpd_dev = &pdev->dev;//保存设置的值到tpd->tpd_dev结构体中,初始化tpd
	for (i = 1; i < TP_DRV_MAX_COUNT; i++) {
		/* add tpd driver into list */
		if (tpd_driver_list[i].tpd_device_name != NULL) {//这里是在遍历mtk的tpd_driver_list里面的所有的驱动,
                //判断名字是否为NULL,每一个moduletouch IC 驱动都会添加到这个静态数组里面
			tpd_driver_list[i].tpd_local_init();//调用具体TP驱动的tpd_local_init函数,
			/* msleep(1); */
			if (tpd_load_status == 1) {//这里我们会判断我们所遍历的每一个moduleIC驱动的初始化函数。
				//成功的话就会将tpd_load_status置1,所以我们就是通过这个值判断是哪一个驱动的
				TPD_DMESG("[mtk-tpd]tpd_probe, tpd_driver_name=%s\n",
					  tpd_driver_list[i].tpd_device_name);
				g_tpd_drv = &tpd_driver_list[i];
				break;
			}
		}
	}
	if (g_tpd_drv == NULL) {
		if (tpd_driver_list[0].tpd_device_name != NULL) {
			g_tpd_drv = &tpd_driver_list[0];
			/* touch_type:0: r-touch, 1: C-touch */
			touch_type = 0;
			g_tpd_drv->tpd_local_init();
			TPD_DMESG("[mtk-tpd]Generic touch panel driver\n");
		} else {
			TPD_DMESG("[mtk-tpd]cap touch and Generic touch both are not loaded!!\n");
			return 0;
		}
	}
	touch_resume_workqueue = create_singlethread_workqueue("touch_resume");
	//创建一个工作队列并初始化,调用touch_resume_workqueue_callback来处理resume
        INIT_WORK(&touch_resume_work, touch_resume_workqueue_callback);	
	/* use fb_notifier */
	tpd_fb_notifier.notifier_call = tpd_fb_notifier_callback;
	if (fb_register_client(&tpd_fb_notifier))		//注册fb驱动
		TPD_DMESG("register fb_notifier fail!\n");
	/* TPD_TYPE_CAPACITIVE handle */

.............................................

        if (input_register_device(tpd->dev))	//注册input设备
		TPD_DMESG("input_register_device failed.(tpd)\n");
	else
		tpd_register_flag = 1;
	if (g_tpd_drv->tpd_have_button)
		tpd_button_init();

	if (g_tpd_drv->attrs.num)
		tpd_create_attributes(&pdev->dev, &g_tpd_drv->attrs);		//创建tpd的属性

	return 0;
}

按下power亮屏时,为了不卡住亮屏的时间,touch创建了一个工作队列用来处理 resume,最后注册fb驱动和input设备

static int tpd_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
	struct fb_event *evdata = NULL;
	int blank;
	int err = 0;

	TPD_DEBUG("tpd_fb_notifier_callback\n");

	evdata = data;
	/* If we aren't interested in this event, skip it immediately ... */
	if (event != FB_EVENT_BLANK)
		return 0;
	/*
		按下power亮屏,为了不卡住亮屏的时间,touch创建了一个工作队列用来处理resume
		touchresume时间尽量要短,防止lcm亮了,touch短时间内不能使用
	*/
	blank = *(int *)evdata->data;
	TPD_DMESG("fb_notify(blank=%d)\n", blank);
	switch (blank) {
	case FB_BLANK_UNBLANK:
		TPD_DMESG("LCD ON Notify\n");
		if (g_tpd_drv && tpd_suspend_flag) {
			err = queue_work(touch_resume_workqueue, &touch_resume_work);
			if (!err) {
				TPD_DMESG("start touch_resume_workqueue failed\n");
				return err;
			}
		}
		break;
	case FB_BLANK_POWERDOWN:		//如果按下power按键灭屏,touch会run_supend,相当于挂起,休眠。
		TPD_DMESG("LCD OFF Notify\n");
		if (g_tpd_drv && !tpd_suspend_flag) {
			err = cancel_work_sync(&touch_resume_work);
			if (!err)
				TPD_DMESG("cancel touch_resume_workqueue err = %d\n", err);
			g_tpd_drv->suspend(NULL);
		}
		tpd_suspend_flag = 1;
		break;
	default:
		break;
	}
	return 0;
}

三,具体的触摸IC驱动文件(以gslx680.c为例)

首先,在 gslx680.c 文件的 tpd_driver_init 函数中,先获取dts中的配置信息,然后将 tpd_device_driver 添加到 tpd_driver_lis 数组里面,然后在 tpd_probe 方法中会回调 tpd_local_init,此过程正是上面讲到的 tpd_probe 里面 for 循环调用的过程。

/* called when loaded into kernel */
static int __init tpd_driver_init(void) {
	printk("Sileadinc gslX680 touch panel driver init\n");

	tpd_get_dts_info();		//获得dts中的配置信息

	if(tpd_driver_add(&tpd_device_driver) < 0)	//调用tpd_driver_add添加添加一个驱动
							//其实就是将这个驱动添加到静态数组tpd_driver_lis里面。
		printk("add gslX680 driver failed\n");
#ifdef TPD_PROXIMITY
if(item_integer("sensor.tp.proximity",0)==1)
	tp_alsps_init();//by lai
#endif
	return 0;
}

在 tpd_driver_init 函数中可以添加判断系统是否开机,如果是关机充电进入内核,则返回一个错误码,这样可以在关机充电时不加载,防止关机充电时唤醒屏幕出现卡顿

在 tpd_local_init 函数中,先初始化ic并上电,然后注册i2c设备驱动,之后会调用tpd_i2c_probe

struct i2c_driver tpd_i2c_driver = {
	.driver = {
		.name = TPD_DEVICE, 
		.owner = THIS_MODULE,
		.of_match_table = tpd_of_match,
	},
	.probe = tpd_i2c_probe,
	.remove = tpd_i2c_remove,
	.id_table = tpd_i2c_id,
	.detect = tpd_i2c_detect,
};

int tpd_local_init(void)
{
	printk("%s\n", __func__);

	init_tp_power(tpd->tpd_dev, TP_POWER);	//初始化ic并上电
	tp_power_on(1);	

	if(i2c_add_driver(&tpd_i2c_driver) != 0) {// 对 tpd_i2c_driver 进行注册
		printk("%s() unable to add i2c driver.\n", __func__);
		return -1;
	}

	if (tpd_load_status == 0) {
		printk("add error touch panel driver.\n");
		i2c_del_driver(&tpd_i2c_driver);	
		return -1;
	}

................

	tpd_type_cap = 1;

	printk("%s successfully\n", __func__);
	return 0;
}

tpd_i2c_probe 中,会对IC进行复位操作,然后测试i2c是否连接成功,之后会创建一个内核线程,用于采集触摸数据

static int tpd_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int err = 0;
	
#ifdef TPD_PROXIMITY
	struct hwmsen_object obj_ps;
#endif
	
	printk("%s\n", __func__);

	i2c_client = client;

	/*
		复位ic
	*/
	tpd_gpio_output(GTP_RST_PORT, 0);
	msleep(100);

	tpd_gpio_output(GTP_RST_PORT, 1);

	tpd_gpio_output(GTP_INT_PORT, 0);
	tpd_gpio_as_int(GTP_INT_PORT);
	msleep(50);

	mutex_init(&gsl_i2c_lock);
	
	if(test_i2c(client) < 0)		//测试I2C是否连接成功
	{
		printk("%s: i2c test error, so quit!!!\n", __func__);
		return -ENODEV;
	}

		//创建一个内核中运行的线程,
		//这个线程主要是实现触摸事件发生时,采集触摸点数
        thread = kthread_run(touch_event_handler, 0, TPD_DEVICE);
	if (IS_ERR(thread)) {
		err = PTR_ERR(thread);
		TPD_DMESG(TPD_DEVICE " failed to create kernel thread: %d\n", err);
	}

	printk("%s successfully\n", __func__);

	tpd_load_status = 1;

	return 0;
}

在线程执行函数 touch_event_handler 中,会申请中断,然后在一个do while循环里面设置线程休眠,当有触摸事件时会产生中断,在中断处理函数 tpd_eint_interrupt_handler 中将线程唤醒,然后执行 report_data_handle 函数获取并上报数据

//触摸中断事件处理函数
static irqreturn_t tpd_eint_interrupt_handler(int irq, void *dev_id)
{
	TPD_DEBUG_PRINT_INT;

	eint_flag = 1;
	tpd_flag=1; 	//将标志位置位
	wake_up_interruptible(&waiter);	// 唤醒等待队列
	return IRQ_HANDLED;
}

static int touch_event_handler(void *unused)		//触摸事件 采集线程
{
	struct sched_param param = { .sched_priority = 4 };
	sched_setscheduler(current, SCHED_RR, &param);	
        // 调度策略和调度参数分别设置为param指向的sched_param结构中指定的policy和参数
	
	gsl_post_init();    //中断申请

	do
	{
		set_current_state(TASK_INTERRUPTIBLE);	//设置当前线程睡眠
		if (tpd_flag == 0)		// 设置等待队列等待事件,tpd_flag 会在中断处理函数中置位
		{
			DEFINE_WAIT(wait);
			prepare_to_wait(&waiter, &wait, TASK_INTERRUPTIBLsE);
			schedule();		//让进程让出调度
			finish_wait(&waiter, &wait);
		}
		// wait_event_interruptible(waiter, tpd_flag != 0);
		tpd_flag = 0;
		TPD_DEBUG_SET_TIME;
		set_current_state(TASK_RUNNING);// 标记当前线程正执行
		print_info("===touch_event_handler, task running===\n");

		eint_flag = 0;
		report_data_handle();    //获取并上报数据
		
	} while (!kthread_should_stop());
	
	return 0;
}

具体的触摸IC驱动就介绍到此,不同的IC厂家的代码基本都大同小异。

四,MTK平台TP驱动的移植和调试

首先,将触摸IC厂提供的驱动文件XXX放入路径:kernel-3.18\drivers\input\touchscreen\mediatek

并在Makefile中加入编译规则,以 GSLX688 为例

obj-$(CONFIG_TOUCHSCREEN_GSLX688)	+=  GSLX688/

上述添加方法使用宏控制的目的是为了方便选择编译触摸IC的驱动,需在Kconfig和项目的xxxdeconfig文件加入宏 CONFIG_TOUCHSCREEN_GSLX688 的配置,也可以不加宏控制直接编译,如下

obj-y	+=  GSLX688/

之后配置dts文件,此处需查看原理图,确定触摸IC使用的I2C号,各引脚的GPIO号和中断号,在相应的I2C节点中加入配置

然后就是编译了,运气好可能一次性就编译通过了,但是大多数情况第一次都是会报错的,因为你拿到的驱动代码基本上是在MTK其他平台copy下来的,此处只有根据报错信息去做相应的修改,报错基本上是一些头文件和一些接口等 差异导致的

下面简单介绍一些TP的调试手段

1,如果触摸IC能正常工作,只是触摸点位不对等问题,找FAE更改峰位参数解决。

2,IC不能正常工作的情况

首先检查硬件是否有问题,如焊接是否有虚焊,供电是否正常,pin脚是否匹配等

其次是检查中断触发是否正常,触摸TP的时候用示波器测量中断脚,看是否有中断触发,还需检查触摸IC的IO电压是否和平台的pin脚电压相匹配(举例:中断触发的电压为1.8v,而中断脚需3.3v才能触发,此时IC端虽然有中断电平输出,但主控无法识别并产生中断事件),并确定触发方式是否正确,然后检查I2C通讯是否成功,查看串口log或使用adb命令:getevent 检查系统是否有接收到上报的数据。

TP的移植和调试相对简单,一般问题都出在中断和I2C上,很好解决,小编就介绍到此,非常感谢阅读。

发布了6 篇原创文章 · 获赞 29 · 访问量 1048

猜你喜欢

转载自blog.csdn.net/dthua888/article/details/102652455