MTK平台的LCM防静电(esd-check)机制

MTK平台的LCM防静电(esd-check)机制

🚀这片博客主要分享给在MTK平台调试显示屏(LCD/LCM)的同行同仁们,如果你想要了解这个平台的防静电机制,不烦来看看我分享的这部分内容对你有没有帮助?如有帮助,还请顺手点赞、收藏或转发,原创不易,确实手都敲废了~,OK废话不说了,接下来开始上干货!👇

一、熟悉你配置LCM关于静电部分的配置内容

一般你的LCM驱动配置文件基本上都是放在:alps/kernel-(kernel版本)/driver/misc/mediatek/lcm/下对应驱动配置文件,这里以**8057_fhd_dsi_vdo_ctc_cw_xq**2.c里面的配置内容做举例讲解.

在你的LCM驱动配置文件中,会有这个函数lcm_get_params(struct LCM_PARAMS *params) ,在这个函数封装了这个屏的很多属性,比如接口类型type,接口mode,porch参数等等,当对于静电来说,主要还是这几行code:

	params->type = LCM_TYPE_DSI;	//这是屏的接口类型
	params->dsi.mode = BURST_VDO_MODE; //这是屏的传输模式,比较常用的是CMD_MODE和VDO_MODE;
	params->dsi.esd_check_enable = 1;	//这是esd-check的开关标志,1是打开,0是关闭;
	params->dsi.customization_esd_check_enable = 1; //这是esd-check模式的配置,0对应着TE侦测,1对应着读LCM寄存器;
	params->dsi.lcm_esd_check_table[0].cmd = 0x0D; //这是读寄存器的寄存器值,你要读LCM的哪个寄存器就在这里配置,我这里是读0DH,所以配置的0x0D;
	params->dsi.lcm_esd_check_table[0].count = 1; //这是配置读寄存器返回的位数,一般MTK平台每次只能返回一位短包值
	params->dsi.lcm_esd_check_table[0].para_list[0] = 0x01;	/*这是你预设的0D寄存器的返回值,如果预设的是读LCM的寄存器模式,在esd-check的过程中,平台会获取这个值,然后拿去与从LCM中读出来的值做匹配(compare),如果匹配失败,就会check失败,进而触发esd-recovery;如果匹配成功,就会等待进入下一次esd-check。*/

咨询MTK那边了解到,如果采用读LCM寄存器的模式的话,要想读多个寄存器是有一定难度的,目前他们平台最多能够支持读3个寄存器,但是从MT6765平台的表现来看,读多个寄存器有时候也会翻车的,我就遇上过,读一个寄存器可以成功,但是读一个以上就会在开机进入kernel阶段后连续快速五次触发esd-check,导致esd线程挂掉。
但是如果你想配置读2个或者3个寄存器也是可以的,这里简单举个栗子:

	params->dsi.lcm_esd_check_table[0].cmd = 0x0D;//读0DH
	params->dsi.lcm_esd_check_table[0].count = 1;
	params->dsi.lcm_esd_check_table[0].para_list[0] = 0x01;
	
	params->dsi.lcm_esd_check_table[1].cmd = 0x0C;//读0CH
	params->dsi.lcm_esd_check_table[1].count = 1;
	params->dsi.lcm_esd_check_table[1].para_list[0] = 0x00;

	params->dsi.lcm_esd_check_table[2].cmd = 0x0B;//读0BH
	params->dsi.lcm_esd_check_table[2].count = 1;
	params->dsi.lcm_esd_check_table[2].para_list[0] = 0x00;

二、介绍一下MTK平台esd-check的大致框架

MTK平台kernel中关于LCM的平台框架基本上都在这个目录下:alps/kernel-(kernel版本)/driver/misc/mediatek/video/MT****(平台)/,所以esd-check的框架也是在这下面,这里我就拿MT6765来介绍一下,我在研究这个esd模块时所采用的工具是sourceinsight 4,个人觉得对分析问题非常顺手方便,这里安利给大家。

esd的核心线程叫做primary_display_check_recovery_worker_kthread(void * data),它在alps/kernel-(kernel版本)/driver/misc/mediatek/video/MT****(平台)/videox/disp_recovery.c中进行定义,原函数定义如下:

static int primary_display_check_recovery_worker_kthread(void *data)
{
	struct sched_param param = {.sched_priority = 87 };
	int ret = 0;
	int i = 0;
	int esd_try_cnt = 5; /* 20; */
	int recovery_done = 0;
	DISPFUNC();
	sched_setscheduler(current, SCHED_RR, &param);//轮转方式对这个线程进行实时调度策略
	while (1) {
		msleep(2000); /* 2s *//*用msleep的方式进行等待,大概时间是2s左右,当有更高优先级的中断响应时就会等待,所以是“大概2s”*/
		ret = wait_event_interruptible(_check_task_wq,atomic_read(&_check_task_wakeup));//中断开始唤醒执行esd-check任务,就是执行后面的程序
		if (ret < 0) {
			printk("[ESD]check thread waked up accidently\n"); /*如果esd-check线程属于意外唤醒,则认为是异常,会跳出此次检测,不执行后面程序,等待下一轮while循环*/
			continue;
		}
		_primary_path_switch_dst_lock();
		/* 1. esd check & recovery */
		if (!esd_check_enable) {  /*关联primary_display_esd_check_enable,在此函数中进行赋值,如果esd_check_enable =0表示没有使能esd-check,跳出此次检查*/
			_primary_path_switch_dst_unlock();
			continue;
		}
		primary_display_manual_lock();
		/* thread relase CPU, when display is slept */
		if (primary_get_state() == DISP_SLEPT) { //如果是睡眠模式,就会跳出此次检查并释放掉CPU资源
			primary_display_manual_unlock();
			_primary_path_switch_dst_unlock();
			primary_display_wait_not_state(DISP_SLEPT,
				MAX_SCHEDULE_TIMEOUT);
			continue;
		}
		primary_display_manual_unlock();
		i = 0; /* repeat */
		do {
			ret = primary_display_esd_check();//esd check的主导函数
			if (!ret) /* success */
				break;	//如果check成功,就会跳出while循环,不会往下继续走esd-recovery
			DISPERR(
				"[ESD]esd check fail, will do esd recovery. try=%d\n",
				i);
			primary_display_esd_recovery();//check失败,就会走到这里执行esd_recovery的主导函数			
			recovery_done = 1;//标志位,后面会用到
		} while (++i < esd_try_cnt); //esd_try_cnt定义数值为5,即连续进行5次check和recovery
		if (ret == 1) {
			DISPERR(
				"[ESD]LCM recover fail. Try time:%d. Disable esd check\n",esd_try_cnt);
			primary_display_esd_check_enable(0);//关闭esd-check的使能,esd-check后面不在生效了
		} else if (recovery_done == 1) {
			printk("[ESD]esd recovery success\n");
			recovery_done = 0;
		}
		esd_checking = 0;//表示esd-check执行完毕了,不是正在进行中
		_primary_path_switch_dst_unlock();
		/* 2. other check & recovery *//*这里预留了其他check和recovery的接口*/
		if (kthread_should_stop())
			break;
	}
	return 0;
}

上面基本上就是整个esd-check的大致框架了,要进一步分析需要进里面的功能函数去看看,这里等会再进去看看,先回头再看一眼这个esd核心线程在哪里进行创建的?在一个叫做primary_display_check_recovery_init(void)的函数中进行创建,其函数原型如下:

void primary_display_check_recovery_init(void)
{
	/* primary display check thread init */
	primary_display_check_task =
		kthread_create(primary_display_check_recovery_worker_kthread ,/*创建esd核心线程并且初始化*/
			       NULL, "disp_check");
	init_waitqueue_head(&_check_task_wq);//用于整个esd流程

	if (disp_helper_get_option(DISP_OPT_ESD_CHECK_RECOVERY)) {/*判断是否有设定esd-check和recovery模块*/
		wake_up_process(primary_display_check_task);
		if (_need_do_esd_check()) {/*这个是esd-check的总开关,根据LCM配置文件中的dsi.customization_esd_check_enable参数来决定开/关*/
			/* esd check init */
			init_waitqueue_head(&esd_ext_te_wq);//初始化TE侦测
			primary_display_requset_eint();//获取dtsi中关于TE引脚的配置,并且注册TE中断
			set_esd_check_mode(GPIO_EINT_MODE);/*设置esd-check的mode为GPIO_EINT_MODE mode,GPIO_EINT_MODE定义为0,匹配TE模式配置0*/
			primary_display_esd_check_enable(1);/*esd_check的使能开关*/
		}
	}
}

可能会有人对disp_helper_get_option()这个函数会有疑惑,不知道它到底是干啥的,😅其实笔者一开始也不确定这个函数的功能,只是结合它的用法隐约觉得有点像是一个判断是否存在某个功能模块的函数,放一下这个函数的原型:

int disp_helper_get_option(enum DISP_HELPER_OPT option)
{
	int ret = 0;

	if (option >= DISP_OPT_NUM) {
		DISPWARN("%s: option invalid %d\n", __func__, option);
		return -1;
	}
	switch (option) {
	case DISP_OPT_MIPITX_ON_CHIP:
		{
			if (_is_normal_stage())
				return 1;
			else if (_is_bringup_stage())
				return 1;
			else if (_is_early_porting_stage())
				return 0;

			DISPWARN("%s,get option MIPITX fail\n", __FILE__);
			return -1;
		}
	case DISP_OPT_FAKE_LCM_X:
		{
			int x = 0;

#ifdef CONFIG_CUSTOM_LCM_X
			ret = kstrtoint(CONFIG_CUSTOM_LCM_X, 0, &x);
			if (ret) {
				pr_info("%s error to parse x: %s\n",
					__func__, CONFIG_CUSTOM_LCM_X);
				x = 0;
			}
#endif
			return x;
		}
	case DISP_OPT_FAKE_LCM_Y:
		{
			int y = 0;
#ifdef CONFIG_CUSTOM_LCM_Y
			ret = kstrtoint(CONFIG_CUSTOM_LCM_Y, 0, &y);
			if (ret) {
				pr_info("%s error to parse x: %s\n",
					__func__, CONFIG_CUSTOM_LCM_Y);
				y = 0;
			}
#endif
			return y;
		}
	case DISP_OPT_FAKE_LCM_WIDTH:
		{
			int w = primary_display_get_virtual_width();

			if (w == 0)
				w = DISP_GetScreenWidth();
			return w;
		}
	case DISP_OPT_FAKE_LCM_HEIGHT:
		{
			int h = primary_display_get_virtual_height();

			if (h == 0)
				h = DISP_GetScreenHeight();
			return h;
		}
	case DISP_OPT_NO_LK:
		{
			return 1;
		}
	case DISP_OPT_PERFORMANCE_DEBUG:
		{
			if (_is_normal_stage())
				return 0;
			else if (_is_bringup_stage())
				return 0;
			else if (_is_early_porting_stage())
				return 0;
		}
	case DISP_OPT_SWITCH_DST_MODE:
		{
			if (_is_normal_stage())
				return 0;
			else if (_is_bringup_stage())
				return 0;
			else if (_is_early_porting_stage())
				return 0;
			else
				return 0;
		}
	default:
		{
			unsigned int i;

			for (i = 0; i < ARRAY_SIZE(help_info); i++) {
				if (help_info[i].opt == option)
					return help_info[i].val;
			}
			return 0;
		}
	}
	return ret;
}

我们可以看到在这个地方调用的传参是"DISP_OPT_ESD_CHECK_RECOVERY",而这个"DISP_OPT_ESD_CHECK_RECOVERY"最终会执行到函数的最后去与help_info这个结构体的子成员去匹配,help_info这个结构体在同级目录下的disp_helper.c文件中定义:

static struct {
	enum DISP_HELPER_OPT opt;
	unsigned int val;
	const char *desc;
} help_info[] = {

然后点进去查看help_info结构体的初始化发现这个位置是这么定义的:

{DISP_OPT_ESD_CHECK_RECOVERY, 0, "DISP_OPT_ESD_CHECK_RECOVERY"},

当时看到这里value值设的是0,感觉有点不对,是0 的话那return help_info[i].val 不就返回的是0吗?那就往下走不下去了,然后继续抓着这个DISP_OPT_ESD_CHECK_RECOVERY关键字搜索,最终找到在disp_helper.c文件里面有一个叫disp_helper_option_init(void)的初始化函数,在这里面调用了disp_helper_set_option(DISP_OPT_ESD_CHECK_RECOVERY, 1);好,到这里就清楚了,那最后return help_info[i].val返回值就是一个有效值,所以我才隐约觉得有点像是一个匹配某个功能模块的函数,当人只是个人猜测😢,如若有同仁清楚的话,敬请在评论区指正,谢谢!

好,回到primary_display_check_recovery_init(void)这个地方继续往下看,_need_do_esd_check函数的原型:

static unsigned int _need_do_esd_check(void) 
{
	int ret = 0;

#ifdef CONFIG_OF
	if ((primary_get_lcm()->params->dsi.esd_check_enable == 1) &&
		(islcmconnected == 1))
		ret = 1;
#else
	if (primary_get_lcm()->params->dsi.esd_check_enable == 1)
		ret = 1;
#endif
	return ret;
}

这里的dsi.esd_check_enable的值不正是你LCM配置文件中的dsi.esd_check_enable吗?好,后头继续往下看primary_display_requset_eint(),其函数原型如下:

void primary_display_requset_eint(void)
{
	struct LCM_PARAMS *params;
	struct device_node *node;
	u32 ints[2] = { 0, 0 };

	params = primary_get_lcm()->params;/*从你LCM配置文件里面去获取lcm_get_params的属性*/
	if (params->dsi.customization_esd_check_enable == 0) {
		node = of_find_compatible_node(NULL, NULL,
				"mediatek, DSI_TE-eint");//获取dtsi中te的配置信息
		if (!node) {
			DISPERR(
				"[ESD][%s] can't find DSI_TE eint compatible node\n",
				    __func__);
			return;
		}

		/* 1.register irq handler */
		of_property_read_u32_array(node, "debounce",
					   ints, ARRAY_SIZE(ints));

		te_irq = irq_of_parse_and_map(node, 0);
		if (request_irq(te_irq, _esd_check_ext_te_irq_handler,
				IRQF_TRIGGER_RISING, "DSI_TE-eint", NULL)) { /*注册TE中断函数*/
			DISPERR("[ESD]EINT IRQ LINE NOT AVAILABLE!\n");
			return;
		}
		/* 2.disable irq */
		disable_irq(te_irq);
		/* 3.set DSI_TE GPIO to TE MODE */
		disp_dts_gpio_select_state(DTS_GPIO_STATE_TE_MODE_TE);
	}
}

好,再看看set_esd_check_mode(),其函数原型很简洁:

void set_esd_check_mode(unsigned int mode)
{
	esd_check_mode = mode;
}

那在初始化这个地方就相当于默认初始化esd_check_mode为GPIO_EINT_MODE了,也就是TE侦测的模式。
继续往下看看primary_display_esd_check_enable这个使能函数,其函数原型定义如下:

void primary_display_esd_check_enable(int enable)
{
	if (_need_do_esd_check()) { //总开关打开了后才去判断enable的值
		if (enable) {
			esd_check_enable = 1;		//给出标志位状态,使能成功
			printk("[ESD]enable esd check\n");
			atomic_set(&_check_task_wakeup, 1);
			wake_up_interruptible(&_check_task_wq);/*启动唤醒esd-check的检测功能*/
		} else {
			esd_check_enable = 0;	//给出标志位状态,使能失败,关闭esd-check
			atomic_set(&_check_task_wakeup, 0);
			printk("[ESD]disable esd check\n");
		}
	} else {
		printk("[ESD]do not support esd check\n");
	}
}

好,至此,esd-check的大致框架和初始化部分讲解完毕。

三、介绍一下MTK平台esd-check是如何进行“check”

平台的esd-check主导函数是int primary_display_esd_check(void),结果返回0表示屏没有被静电打坏,结果返回1表示屏被静电打出异常了,来一起看一下primary_display_esd_check函数的原型:

/**
 * ESD CHECK FUNCTION
 * return 1: esd check fail
 * return 0: esd check pass
 */
int primary_display_esd_check(void)
{
	int ret = 0;
	unsigned int mode;
	mmp_event mmp_te = ddp_mmp_get_events()->esd_extte;
	mmp_event mmp_rd = ddp_mmp_get_events()->esd_rdlcm;
	mmp_event mmp_chk = ddp_mmp_get_events()->esd_check_t;
	struct LCM_PARAMS *params;

	dprec_logger_start(DPREC_LOGGER_ESD_CHECK, 0, 0);
	mmprofile_log_ex(mmp_chk, MMPROFILE_FLAG_START, 0, 0);
	printk("[ESD]ESD check begin\n");

	primary_display_manual_lock();
	if (primary_get_state() == DISP_SLEPT) { /*判断显示屏的状态,如果是睡眠状态slept,那就会跳出check*/
		mmprofile_log_ex(mmp_chk, MMPROFILE_FLAG_PULSE, 1, 0);
		printk("[ESD]Primary DISP slept. Skip esd check\n");
		primary_display_manual_unlock();
		goto done;
	}
	primary_display_manual_unlock();

	/*  Esd Check : EXT TE *//*首先优先执行判断LCM驱动配置文件中配置的esd-check模式是否为TE模式,是的话就会优先执行te侦测*/
	params = primary_get_lcm()->params;   /*获取LCM驱动配置文件中配置的 lcm_get_params 获取属性*/
	if (params->dsi.customization_esd_check_enable == 0) {
		/* use te for esd check */
		mmprofile_log_ex(mmp_te, MMPROFILE_FLAG_START, 0, 0);
		if (primary_display_is_video_mode()) {/*判断显示输出模式是video mode/cmd mode*/
			mode = get_esd_check_mode();	//这里获取一下esd-check mode
			if (mode == GPIO_EINT_MODE) {
				ret = do_esd_check_eint();	//执行TE侦测的主导函数
				if (_can_switch_check_mode())//判断是否可以切换check模式
					set_esd_check_mode(GPIO_DSI_MODE);/*把esd_check_mode设为GPIO_DSI_MODE,GPIO_DSI_MODE定义为1,匹配读寄存器模式*/
			} else {
				ret = do_esd_check_read();//执行读寄存器模式去esd-check
				if (_can_switch_check_mode())
					set_esd_check_mode(GPIO_EINT_MODE);
			}
		} else
			ret = do_esd_check_eint();
		mmprofile_log_ex(mmp_te, MMPROFILE_FLAG_END, 0, ret);
		goto done;
	}
	/*  Esd Check : Read from lcm */
	mmprofile_log_ex(mmp_rd, MMPROFILE_FLAG_START,
			 0, primary_display_cmdq_enabled());
	if (primary_display_cmdq_enabled() == 0) {
		printk("[ESD]not support cpu read do esd check\n");
		mmprofile_log_ex(mmp_rd, MMPROFILE_FLAG_END, 0, ret);
		goto done;//跳转到done,check完毕,返回ret值
	}
	mmprofile_log_ex(mmp_rd, MMPROFILE_FLAG_PULSE,
			 0, primary_display_is_video_mode());
	/* only cmd mode read & with disable mmsys clk will kick *//*这个地方不清楚原因,如若有同仁知道还请不惜赐教,谢谢!*/
	if (disp_helper_get_option(DISP_OPT_IDLEMGR_ENTER_ULPS) &&
	    !primary_display_is_video_mode())
		primary_display_idlemgr_kick((char *)__func__, 1);

	ret = do_esd_check_read();
	mmprofile_log_ex(mmp_rd, MMPROFILE_FLAG_END, 0, ret);
done:
	printk("[ESD]ESD check end, ret = %d\n", ret);
	mmprofile_log_ex(mmp_chk, MMPROFILE_FLAG_END, 0, ret);
	dprec_logger_done(DPREC_LOGGER_ESD_CHECK, 0, 0);
	return ret;
}

primary_display_is_video_mode()函数会去匹配是哪种传输模式,如果是BURST_VDO_MODE则会返回1,是CMD_MODE则会返回0.
从check的机制来看,首先会优先根据LCM配置文件中去匹配配置的模式是否是TE侦测模式,TE侦测的主导函数是do_esd_check_eint(),来一起看一下它的函数原型:

int do_esd_check_eint(void)
{
	int ret = 0;

	primary_display_switch_esd_mode(GPIO_EINT_MODE);

	if (wait_event_interruptible_timeout(esd_ext_te_wq,
		atomic_read(&esd_ext_te_event), HZ / 2) > 0)     /*如果HZ/2之内没有收到终端信息,则说明esd打坏了显示,从平台定义来看,HZ等于1sec,不是指刷新帧率Hz,那假如帧率出现异常,是否可以在这里做修改呢?*/
		ret = 0; /* esd check pass */
	else
		ret = 1; /* esd check fail */

	atomic_set(&esd_ext_te_event, 0);

	primary_display_switch_esd_mode(GPIO_DSI_MODE);

	return ret;
}

void primary_display_switch_esd_mode(int mode)
{
	if (mode == GPIO_EINT_MODE) {
		/* Enable TE EINT */
		enable_irq(te_irq);//使能一下te中断

	} else if (mode == GPIO_DSI_MODE) {
		/* Disable TE EINT */
		disable_irq(te_irq);//关闭te中断
	}
}

这里可以看出TE侦测其实就是在开始前会使能一下TE的中断,然后如果大概半秒中内没接收到屏那边传过来得中断信号,就会去return 一个1,意味着屏幕异常了,会继续往下走到goto done,check完毕, 需要返回check结果,触发esd-recovery。

好,继续往下看一下_can_switch_check_mode()这个函数,它的函数原型定义如下:

unsigned int _can_switch_check_mode(void)
{
	int ret = 0;
	struct LCM_PARAMS *params;

	params = primary_get_lcm()->params;
	if (params->dsi.customization_esd_check_enable == 0 &&
	    params->dsi.lcm_esd_check_table[0].cmd != 0)
		ret = 1;
	return ret;
}

函数的功能主要是根据LCM配置文件信息去选择是否需要进行模式切换,如果配置的是TE侦测模式并且有配置读寄存器模式的寄存器值,则会返回1,从primary_display_esd_check函数调用的地方看会继续往下走,_can_switch_check_mode返回1则会重新配置一下esd_check_mode,配置的模式是1,匹配读寄存器模式,那下次循环到这个地方的时候就会根据mode来读寄存器的方式去check:do_esd_check_read()。
我们进一步看看do_esd_check_read()函数到底是如何从LCM中读取寄存器的,先放一下它的函数原型:

int do_esd_check_read(void)
{
	int ret = 0;
	struct cmdqRecStruct *qhandle;
	disp_path_handle phandle = primary_get_dpmgr_handle();

	/* 0.create esd check cmdq */
	ret = cmdqRecCreate(CMDQ_SCENARIO_DISP_ESD_CHECK, &qhandle);
	if (ret) {
		DISPERR("%s:%d, create cmdq handle fail!ret=%d\n",
			__func__, __LINE__, ret);
		return -1;
	}
	cmdqRecReset(qhandle);

	primary_display_manual_lock();
	dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_ALLC_SLOT, 0);/*LCM配置文件中获取需要读的寄存器个数,最多三位,获取到了个数了后用cmdq去对这些寄存器分配槽位*/
	primary_display_manual_unlock();

	/* 1.use cmdq to read from lcm */
	if (primary_display_is_video_mode())
		ret = _esd_check_config_handle_vdo(qhandle);
	else
		ret = _esd_check_config_handle_cmd(qhandle);

	primary_display_manual_lock();

	if (ret == 1) {	/* cmdq fail */
		if (need_wait_esd_eof()) {
			/* Need set esd check eof synctoken to
			 * let trigger loop go.
			 */
			cmdqCoreSetEvent(CMDQ_SYNC_TOKEN_ESD_EOF);
		}
		/* do dsi reset */
		dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_DSI_RESET, 0);
		goto destroy_cmdq;
	}

	/* 2.check data(*cpu check now) */
	ret = dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_CHECK_CMP, 0);
	if (ret){
		printk("[esd]esd check fail, dpmgr_path_build_cmdq return ret = %d\n");
		ret = 1; /* esd check fail */
		
		}

destroy_cmdq:
	dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_FREE_SLOT, 0);

	primary_display_manual_unlock();

	/* 3.destroy esd config thread */
	cmdqRecDestroy(qhandle);
	printk("[esd] do_esd_check_read return ret =%d \n",ret);
	return ret;
}

我们理一下这个函数的逻辑:

  1. 首先创建用于 esd check的cmdq工具,cmdq具体是什么呢?笔者也不是很清楚,只是把它当做是用于能够对LCM寄存器进行读和写的一种软工具,如果想要操作LCM内部的寄存器,就可以用cmdq去操作,如若有同仁知道这个cmdq具体的功能,还请不惜赐教!
  2. 用dpmgr_path_build_cmdq函数去从LCM配置文件中获取需要读的寄存器个数,最多三位,获取到了个数了后用cmdq去对这些寄存器分配槽位。
  3. 用_esd_check_config_handle_vdo()函数去从LCM中读取寄存器,并且保存下来,是否读取成功会返回ret值,ret=0表示读取成功,ret=1表示读取失败,假如读取失败就会去判断是否esd-check功能关闭了,关闭了则会设置防静电检查eof同步令牌让触发循环,然后reset一下dsi
  4. 用dpmgr_path_build_cmdq去check 读到的数据与预设的数据是否匹配,如果不匹配返回一个非零值
  5. 释放掉cmdq资源,read的寄存器执行完毕

好,理完这个函数的大致逻辑之后,再逐一进入函数去查看其功能和具体实现方法,先看dpmgr_path_build_cmdq这个函数,其函数原型定义如下:

int dpmgr_path_build_cmdq(disp_path_handle dp_handle,
	void *trigger_loop_handle, enum CMDQ_STATE state, int reverse)
{
	int ret = 0;
	int i = 0;
	int module_name, module_num;
	struct ddp_path_handle *handle;
	int *modules;
	struct DDP_MODULE_DRIVER *mod_drv;

	if (!dp_handle) {
		ASSERT(0);
		return -1;
	}

	handle = (struct ddp_path_handle *)dp_handle;
	modules = ddp_get_scenario_list(handle->scenario);/*感觉像是在获取场景模块列表*/
	module_num = ddp_get_module_num(handle->scenario);/*感觉像是在获取这些场景的数目*/

	if (reverse) {
		for (i = module_num - 1; i >= 0; i--) {
			module_name = modules[i];
			mod_drv = ddp_get_module_driver(module_name);/*像是在注册模块驱动*/
			if (mod_drv && mod_drv->build_cmdq)/*如果这些模块驱动里面有build_cmdq工具的话就会走进来,对应ddp_dsi.c中的DDP_MODULE_DRIVER结构体*/
				ret = mod_drv->build_cmdq(module_name,/*调用build_cmdq映射的函数去执行这些场景命令*/
					trigger_loop_handle, state);
		}
	} else {
		for (i = 0; i < module_num; i++) {
			module_name = modules[i];
			
			mod_drv = ddp_get_module_driver(module_name);
			if (mod_drv && mod_drv->build_cmdq) {
				ret = mod_drv->build_cmdq(module_name,
					trigger_loop_handle, state);
			}
		}
	}
	return ret;
}

感觉这个函数最重要的就是传参最后两位,最后一位决定轮循的顺序,然后去匹配场景命令,搜索了一下这些场景命令主要在ddp_dsi_build_cmdq(enum DISP_MODULE_ENUM module,void *cmdq_trigger_handle, enum CMDQ_STATE state)函数中,匹配到对应的场景命令后就会用cmdq或者dsi去执行具体的功能,很复杂看着就头疼,由于这个函数实在太长了就不去贴函数原型了…

简单梳理一下后再回到do_esd_check_read去看一下dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_ALLC_SLOT, 0);这一行代码是在干啥,贴一下假如匹配到了CMDQ_ESD_ALLC_SLOT后,会走那些代码:

 else if (state == CMDQ_ESD_ALLC_SLOT) {
		/* create 3 slot */
		unsigned int h = 0, n = 0;

		n = DSI_esd_check_num(dsi_params);/*从LCM配置文件中获取需要读寄存器的个数*/
		for (h = 0; h < 4; h++)
			cmdqBackupAllocateSlot(&hSlot[h], n);/*然后根据个数来用cmdq备份分配槽位,dsi通过cmdq工具去从LCM中读取数据,读到后会放到这些槽位里面*/
	} 

再次回到do_esd_check_read函数往下看一下_esd_check_config_handle_vdo函数是怎么从LCM中获取参数的,其函数原型如下:

/**
 * For Vdo Mode Read LCM Check
 * Config cmdq_handle_config_esd
 * return value: 0:success, 1:fail
 */
int _esd_check_config_handle_vdo(struct cmdqRecStruct *qhandle)
{
	int ret = 0;
	disp_path_handle phandle = primary_get_dpmgr_handle();

	/* 1.reset */
	cmdqRecReset(qhandle);
	/*set esd check read timeout 200ms*/
	/*remove to dts*/
	/*cmdq_task_set_timeout(qhandle, 200);*/
	/* wait stream eof first */
	/* cmdqRecWait(qhandle, CMDQ_EVENT_DISP_RDMA0_EOF); */
	cmdqRecWait(qhandle, CMDQ_EVENT_MUTEX0_STREAM_EOF);

	primary_display_manual_lock();

	esd_checking = 1;

	/* 2.stop dsi vdo mode */
	dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_STOP_VDO_MODE, 0);

	/* 3.write instruction(read from lcm) *//*写指令,然后等待LCM返回的包*/
	dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_CHECK_READ, 0);/*主要从这里去匹配CMDQ_ESD_CHECK_READ,然后dsi通过cmdq去读*/

	/* 4.start dsi vdo mode */
	dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_START_VDO_MODE, 0);
	cmdqRecClearEventToken(qhandle, CMDQ_EVENT_MUTEX0_STREAM_EOF);
	/* cmdqRecClearEventToken(qhandle, CMDQ_EVENT_DISP_RDMA0_EOF); */
	/* 5.trigger path */
	dpmgr_path_trigger(phandle, qhandle, CMDQ_ENABLE);

	/* mutex sof wait*/
	ddp_mutex_set_sof_wait(dpmgr_path_get_mutex(phandle), qhandle, 0);

	primary_display_manual_unlock();

	/* 6.flush instruction */
	dprec_logger_start(DPREC_LOGGER_ESD_CMDQ, 0, 0);
	ret = cmdqRecFlush(qhandle);
	dprec_logger_done(DPREC_LOGGER_ESD_CMDQ, 0, 0);

	printk("[ESD]_esd_check_config_handle_vdo ret=%d\n", ret);

	if (ret)
		ret = 1;
	return ret;
}

从第3步可以看出,主要是运用dpmgr_path_build_cmdq去匹配CMDQ_ESD_CHECK_READ,然后执行,一起看下假如匹配到了CMDQ_ESD_CHECK_READ会有哪些流程:

 else if (state == CMDQ_ESD_CHECK_READ) {
		/* enable dsi interrupt: RD_RDY/CMD_DONE (need do this here?) */
		DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,
			      DSI_REG[dsi_i]->DSI_INTEN, RD_RDY, 1);
		DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_INT_ENABLE_REG,
			      DSI_REG[dsi_i]->DSI_INTEN, CMD_DONE, 1);

		for (i = 0; i < 3; i++) {
			if (dsi_params->lcm_esd_check_table[i].cmd == 0)
				break;

			/* 0. send read lcm command(short packet) */
			t0.CONFG = 0x04; /* /BTA */
			t0.Data0 = dsi_params->lcm_esd_check_table[i].cmd;
			t0.Data_ID =
				(t0.Data0 < 0xB0) ?
				DSI_DCS_READ_PACKET_ID :
				DSI_GERNERIC_READ_LONG_PACKET_ID;
			t0.Data1 = 0;

			t1.CONFG = 0x00;
			t1.Data_ID = 0x37;
			t1.Data0 = dsi_params->lcm_esd_check_table[i].count;
			t1.Data1 = 0;

			/* write DSI CMDQ */
			DSI_OUTREG32(cmdq_trigger_handle,
				&DSI_CMDQ_REG[dsi_i]->data[0], AS_UINT32(&t1));
			DSI_OUTREG32(cmdq_trigger_handle,
				&DSI_CMDQ_REG[dsi_i]->data[1], AS_UINT32(&t0));
			DSI_OUTREG32(cmdq_trigger_handle,
				&DSI_REG[dsi_i]->DSI_CMDQ_SIZE, 2);

			/* start DSI */
			DSI_OUTREG32(cmdq_trigger_handle,
				&DSI_REG[dsi_i]->DSI_START, 0);
			DSI_OUTREG32(cmdq_trigger_handle,
				&DSI_REG[dsi_i]->DSI_START, 1);

			/* 1. wait DSI RD_RDY(must clear,
			 * in case of cpu RD_RDY interrupt handler)
			 */
			if (dsi_i == 0) {
				DSI_POLLREG32(cmdq_trigger_handle,
					&DSI_REG[dsi_i]->DSI_INTSTA,
					0x00000001, 0x1);
				DSI_OUTREGBIT(cmdq_trigger_handle,
					struct DSI_INT_STATUS_REG,
					DSI_REG[dsi_i]->DSI_INTSTA,
					RD_RDY, 0x00000000);
			}
			/* 2. save RX data *//*将从屏端返回的包保存到槽位里面hSlot,DSI_RX_DATA0------>hSlot*/
			if (hSlot[0] && hSlot[1] && hSlot[2] && hSlot[3]) {
				DSI_BACKUPREG32(cmdq_trigger_handle,
					hSlot[0], i,
					&DSI_REG[0]->DSI_RX_DATA0);
				DSI_BACKUPREG32(cmdq_trigger_handle,
					hSlot[1], i,
					&DSI_REG[0]->DSI_RX_DATA1);
				DSI_BACKUPREG32(cmdq_trigger_handle,
					hSlot[2], i,
					&DSI_REG[0]->DSI_RX_DATA2);
				DSI_BACKUPREG32(cmdq_trigger_handle,
					hSlot[3], i,
					&DSI_REG[0]->DSI_RX_DATA3);
			}
			/* 3. write RX_RACK */
			DSI_OUTREGBIT(cmdq_trigger_handle, struct DSI_RACK_REG,
				DSI_REG[dsi_i]->DSI_RACK, DSI_RACK, 1);

			/* 4. polling not busy(no need clear) */
			if (dsi_i == 0) {
				DSI_POLLREG32(cmdq_trigger_handle,
					&DSI_REG[dsi_i]->DSI_INTSTA,
					0x80000000, 0);
			}
			/* loop: 0~4 */
		}

简单梳理一下从LCM端读取的步骤:

  1. /send read lcm command(short packet)/,下发读LCM的短包指令

  2. /* write DSI CMDQ */,用dsi写cmdq指令

  3. /* start DSI */ dsi 开始连通屏,并把前面的cmdq指令发给屏

  4. /* save RX data *//将从屏端返回的包保存到槽位里面hSlot,DSI_RX_DATA0------>hSlot/

  5. /*write RX_RACK */这里不清楚是在干啥,难道是类似返回一个什么握手标志???

  6. /* polling not busy(no need clear) */

只能大概根据平台端的简单注释猜测在干啥,如若有同仁知道,还请不惜赐教!

回到do_esd_check_read,看一下"check"的核心:dpmgr_path_build_cmdq(phandle, qhandle, CMDQ_ESD_CHECK_CMP, 0);,这一步简单来讲就是将上面从LCM屏返回的包进行拆分,然后拿LCM配置文件中预设的值进行逐一匹配,如果匹配失败就返回ret 1.

 else if (state == CMDQ_ESD_CHECK_CMP) {
		struct LCM_esd_check_item *lcm_esd_tb;
		/* cmp just once and only 1 return value */
		for (i = 0; i < 3; i++) {
			if (dsi_params->lcm_esd_check_table[i].cmd == 0)
				break;

			/* read data */
			if (hSlot[0] && hSlot[1] && hSlot[2] && hSlot[3]) {/*把备份在槽位内的包进行拆分出来,放到read_data0中区*/
				/* read from slot */
				cmdqBackupReadSlot(hSlot[0], i,
					(uint32_t *)&read_data0);
				cmdqBackupReadSlot(hSlot[1], i,
					(uint32_t *)&read_data1);
				cmdqBackupReadSlot(hSlot[2], i,
					(uint32_t *)&read_data2);
				cmdqBackupReadSlot(hSlot[3], i,
					(uint32_t *)&read_data3);
			} else if (i == 0) {/*假如没有在槽位里面,就去dsi中获取,将DSI_RX_DATA放到read_data*/
				/* read from dsi, support only one cmd read */
				DSI_OUTREG32(NULL, &read_data0, AS_UINT32(
					&DSI_REG[dsi_i]->DSI_RX_DATA0));
				DSI_OUTREG32(NULL, &read_data1, AS_UINT32(
					&DSI_REG[dsi_i]->DSI_RX_DATA1));
				DSI_OUTREG32(NULL, &read_data2, AS_UINT32(
					&DSI_REG[dsi_i]->DSI_RX_DATA2));
				DSI_OUTREG32(NULL, &read_data3, AS_UINT32(
					&DSI_REG[dsi_i]->DSI_RX_DATA3));
			}

			lcm_esd_tb = &dsi_params->lcm_esd_check_table[i];/*获取LCM中配置的寄存器预设值*/

			DISPDBG("[DSI]enter cmp read_data0 byte0~1=0x%x~0x%x\n",
				read_data0.byte0, read_data0.byte1);
			DISPDBG("[DSI]enter cmp read_data0 byte2~3=0x%x~0x%x\n",
				read_data0.byte2, read_data0.byte3);
			DISPDBG("[DSI]enter cmp read_data1 byte0~1=0x%x~0x%x\n",
				read_data1.byte0, read_data1.byte1);
			DISPDBG("[DSI]enter cmp read_data1 byte2~3=0x%x~0x%x\n",
				read_data1.byte2, read_data1.byte3);
			DISPDBG("[DSI]enter cmp read_data2 byte0~1=0x%x~0x%x\n",
				read_data2.byte0, read_data2.byte1);
			DISPDBG("[DSI]enter cmp read_data2 byte2~3=0x%x~0x%x\n",
				read_data2.byte2, read_data2.byte3);
			DISPDBG("[DSI]enter cmp read_data3 byte0~1=0x%x~0x%x\n",
				read_data3.byte0, read_data3.byte1);
			DISPDBG("[DSI]enter cmp read_data3 byte2~3=0x%x~0x%x\n",
				read_data3.byte2, read_data3.byte3);

			DISPDBG("[DSI]enter cmp check_tab cmd=0x%x,cnt=0x%x\n",
				lcm_esd_tb->cmd, lcm_esd_tb->count);/*把获取到的LCM中配置的寄存器预设值打印出来*/
			printk
	("[DSI][esd]para_list[0]=0x%x,para_list[1]=0x%x, para_list[2]=0x%x\n",
				lcm_esd_tb->para_list[0],
				lcm_esd_tb->para_list[1],
				lcm_esd_tb->para_list[2]);
			DISPDBG("[DSI]enter cmp DSI+0x200=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x200));
			DISPDBG("[DSI]enter cmp DSI+0x204=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x204));
			DISPDBG("[DSI]enter cmp DSI+0x60=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x60));
			DISPDBG("[DSI]enter cmp DSI+0x74=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x74));
			DISPDBG("[DSI]enter cmp DSI+0x88=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x88));
			DISPDBG("[DSI]enter cmp DSI+0x0c=0x%x\n",
				AS_UINT32(DISPSYS_DSI0_BASE + 0x0c));

			packet_type = read_data0.byte0;
			/* 0x02: acknowledge & error report */
			/* 0x11: generic short read response(1 byte return) */
			/* 0x12: generic short read response(2 byte return) */
			/* 0x1a: generic long read response */
			/* 0x1c: dcs long read response */
			/* 0x21: dcs short read response(1 byte return) */
			/* 0x22: dcs short read response(2 byte return) */
			if (packet_type == 0x1A || packet_type == 0x1C) { /*返回的包判断是不是长包*/
				recv_data_cnt = read_data0.byte1
					+ read_data0.byte2 * 16;

				if (recv_data_cnt > RT_MAX_NUM) {
					DISPDBG
			("DSI read long packet data exceeds 10 bytes\n");
					recv_data_cnt = RT_MAX_NUM;
				}
				if (recv_data_cnt > lcm_esd_tb->count)
					recv_data_cnt = lcm_esd_tb->count;

				DISPCHECK("DSI read long packet size: %d\n",
					recv_data_cnt);
				if (recv_data_cnt <= 4) {
					memcpy((void *)buffer,
					(void *)&read_data1, recv_data_cnt);
				} else if (recv_data_cnt <= 8) {
					memcpy((void *)buffer,
					(void *)&read_data1, 4);
					memcpy((void *)(buffer + 4),
					(void *)&read_data2, recv_data_cnt - 4);
				} else {
					memcpy((void *)buffer,
						(void *)&read_data1, 4);
					memcpy((void *)(buffer + 4),
						(void *)&read_data2, 4);
					memcpy((void *)(buffer + 8),
					(void *)&read_data3, recv_data_cnt - 8);
				}

			} else if (packet_type == 0x11 || packet_type == 0x21) {//短包操作,返回一个byte
				recv_data_cnt = 1;
				memcpy((void *)buffer,
				(void *)&read_data0.byte1, recv_data_cnt);

			} else if (packet_type == 0x12 || packet_type == 0x22) {//短包操作,返回两个byte
				recv_data_cnt = 2;
				if (recv_data_cnt > lcm_esd_tb->count)
					recv_data_cnt = lcm_esd_tb->count;

				memcpy((void *)buffer,
				(void *)&read_data0.byte1, recv_data_cnt);

			} else if (packet_type == 0x02) {//未知的错误包
				DISPCHECK
					("read return type is 0x02, re-read\n");
			} else {
				DISPCHECK
			("read return type is non-recognite, type = 0x%x\n",
					packet_type);
			}
			DISPDBG("[DSI]packet_type~recv_data_cnt = 0x%x~0x%x\n",
				packet_type, recv_data_cnt);
			/*do read data cmp*//*这里开始逐一匹配了*/
			for (j = 0; j < lcm_esd_tb->count; j++) {

				printk("buffer[%d]=0x%x\n", j, buffer[j]);
				if (buffer[j] != lcm_esd_tb->para_list[j]) {//出现不匹配的情况
					printk
			("buffer[%d]0x%x != lcm_esd_tb->para_list[%d]0x%x\n",
				j, buffer[j], j, lcm_esd_tb->para_list[j]);

					ret |= 1;/*esd failed*/
					
					break;
				}
				ret |= 0;/*esd pass*/
				printk("[DSI][esd]cmp pass cnt = %d\n", j);
			}

			if (ret)/*esd failed*/
				break;
		}

至此,read寄存器的功能就完毕了,最后会返回出去ret的值,回到primary_display_check_recovery_worker_kthread总线程这里,往下看:

i = 0; /* repeat */
		do {
			ret = primary_display_esd_check();//esd check的主导函数
			if (!ret) /* success */
				break;	/*如果check成功,就会跳出while循环,不会往下继续走esd-recovery*/

			DISPERR(
				"[ESD]esd check fail, will do esd recovery. try=%d\n",
				i);
			primary_display_esd_recovery();/*check失败,就会走到这里执行esd_recovery的主导函数*/
			
			recovery_done = 1;//标志位,后面会用到
		} while (++i < esd_try_cnt); /*esd_try_cnt定义数值为5,即连续进行5次check和recovery*/

如果ret为0表示chek一切正常,没有触发esd,就会跳出本次while循环,不会往下走到esd-recovery的地方,但是如果返回是非零值(一般是1),就会继续往下走,走到primary_display_esd_recovery()进行esd-recovery,接下来一起看看是怎么进行recovery的吧!

四、介绍一下MTK平台esd-check是如何进行"recovery"

recovery的过程非常复杂,不仅有MTK平台端还包括屏端的,先放一下函数的原型:

/* ESD RECOVERY */
int primary_display_esd_recovery(void)
{
	enum DISP_STATUS ret = DISP_STATUS_OK;
	struct LCM_PARAMS *lcm_param = NULL;
	mmp_event mmp_r = ddp_mmp_get_events()->esd_recovery_t;

	DISPFUNC();
	dprec_logger_start(DPREC_LOGGER_ESD_RECOVERY, 0, 0);
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_START, 0, 0);
	printk("[ESD]ESD recovery begin\n");

	primary_display_manual_lock();
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE,
		       primary_display_is_video_mode(), 1);

	lcm_param = disp_lcm_get_params(primary_get_lcm());
	if (primary_get_state() == DISP_SLEPT) {
		printk("[ESD]Primary DISP is slept, skip esd recovery\n");
		goto done;
	}

	/* In video mode, recovery don't need kick and blocking flush *//*这个流程不是很清楚,如若有同仁,还请不惜赐教!*/
	if (!primary_display_is_video_mode()) {
		primary_display_idlemgr_kick((char *)__func__, 0);
		mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 2);

		/* blocking flush before stop trigger loop */
		_blocking_flush();
	}

	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 3);

	printk("[ESD]display cmdq trigger loop stop[begin]\n");
	_cmdq_stop_trigger_loop();/*显示CMDQ触发循环停止,需要先停止cmdq去发场景命令,不然会一直轮训工作*/
	printk("[ESD]display cmdq trigger loop stop[end]\n");

	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 4);

	printk("[ESD]stop dpmgr path[begin]\n");
	dpmgr_path_stop(primary_get_dpmgr_handle(), CMDQ_DISABLE);/*关闭了dpmgr的path,也是cmdq不能工作了*/
	printk("[ESD]stop dpmgr path[end]\n");
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 0xff);

	if (dpmgr_path_is_busy(primary_get_dpmgr_handle())) {
		printk("[ESD]primary display path is busy after stop\n");
		dpmgr_wait_event_timeout(primary_get_dpmgr_handle(),
			DISP_PATH_EVENT_FRAME_DONE, HZ * 1);/*等待最后一帧数据刷完*/
		printk("[ESD]wait frame done ret:%d\n", ret);
	}
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 5);

	printk("[ESD]reset display path[begin]\n");
	dpmgr_path_reset(primary_get_dpmgr_handle(), CMDQ_DISABLE);/*拉显示path的 reset*/
	printk("[ESD]reset display path[end]\n");

	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 6);

	printk("[POWER]lcm suspend[begin]\n");
	/*after dsi_stop, we should enable the dsi basic irq.*/
	dsi_basic_irq_enable(DISP_MODULE_DSI0, NULL);/*原来用的是DISP_MODULE_DSI0这个module*/
	disp_lcm_suspend(primary_get_lcm());/*先使LCM进入lcm_suspend(灭屏睡眠),在进行下电(suspend_power)*/
	printk("[POWER]lcm suspend[end]\n");

	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 7);

	printk("[ESD]dsi power reset[begine]\n");
	dpmgr_path_dsi_power_off(primary_get_dpmgr_handle(), NULL);/*dsi有进行下电和上电*/
	dpmgr_path_dsi_power_on(primary_get_dpmgr_handle(), NULL);
	if (!primary_display_is_video_mode())
		dpmgr_path_ioctl(primary_get_dpmgr_handle(), NULL,
				DDP_DSI_ENABLE_TE, NULL);
	printk("[ESD]dsi power reset[end]\n");



	printk("[ESD]lcm recover[begin]\n");
	disp_lcm_esd_recover(primary_get_lcm());/*走lcm_init_power再走lcm_init*/
	printk("[ESD]lcm recover[end]\n");
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 8);

	printk("[ESD]start dpmgr path[begin]\n");
	if (disp_partial_is_support()) {
		struct disp_ddp_path_config *data_config =
			dpmgr_path_get_last_config(primary_get_dpmgr_handle());

		primary_display_config_full_roi(data_config,
			primary_get_dpmgr_handle(), NULL);
	}
	dpmgr_path_start(primary_get_dpmgr_handle(), CMDQ_DISABLE);
	printk("[ESD]start dpmgr path[end]\n");

	if (dpmgr_path_is_busy(primary_get_dpmgr_handle())) {
		DISPERR("[ESD]Main display busy before triggering SOF\n");
		ret = -1;
		/* goto done; */
	}

	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 9);
	printk("[ESD]start cmdq trigger loop[begin]\n");
	_cmdq_start_trigger_loop();
	printk("[ESD]start cmdq trigger loop[end]\n");
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 10);
	if (primary_display_is_video_mode()) {
		/*
		 * for video mode, we need to force trigger here
		 * for cmd mode, just set DPREC_EVENT_CMDQ_SET_EVENT_ALLOW
		 * when trigger loop start
		 */
		dpmgr_path_trigger(primary_get_dpmgr_handle(), NULL,
			CMDQ_DISABLE);

	}
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_PULSE, 0, 11);

	/*
	 * (in suspend) when we stop trigger loop
	 * if no other thread is running, cmdq may disable its clock
	 * all cmdq event will be cleared after suspend
	 */
	cmdqCoreSetEvent(CMDQ_EVENT_DISP_WDMA0_EOF);

	/* set dirty to trigger one frame -- cmd mode */
	if (!primary_display_is_video_mode()) {
		cmdqCoreSetEvent(CMDQ_SYNC_TOKEN_CONFIG_DIRTY);
		mdelay(40);
	}

done:
	primary_display_manual_unlock();
	printk("[ESD]ESD recovery end\n");
	mmprofile_log_ex(mmp_r, MMPROFILE_FLAG_END, 0, 0);
	dprec_logger_done(DPREC_LOGGER_ESD_RECOVERY, 0, 0);
	return ret;
}

在代码中基本上都有具体的注释,这里就不在赘述了,需要强调一下LCM的一个恢复流程,因为我之前在网上看到的一些分析,大多都没有把LCM是如何进行恢复的讲清楚,有些人就简单的提了一下是重写初始化,但是这是不对的!从这上面的流程来看,关于屏的恢复首先是进入lcm_suspend,进行灭屏睡眠,然后再进入lcm_suspend_power进行vsp/vsn下电,再等到dsi进行下电和上电了之后,lcm才开始进行恢复,首先是进入lcm_init_power拉上电时序,给vsp/vsn上电,上电玩了之后才是进入lcm_init进行拉LCD reset和push 初始化代码,这样就完成了屏端的esd-recovery,这也是为什么我们在打静电的时候特别是屏朝下看到屏幕被静电打黑屏了后过一会儿又自动重新亮起来的一个现象,如果你发现自动重新亮起来了之后还是异常显示,建议排查一下lcm_suspend_power这个函数中有没有做下电操作。

为了讲清楚我们先看一下disp_lcm_suspend是怎么调用lcm_suspend和lcm_suspend_power,函数原型如下:

int disp_lcm_suspend(struct disp_lcm_handle *plcm)
{
	struct LCM_DRIVER *lcm_drv = NULL;
	DISPFUNC();
	if (_is_lcm_inited(plcm)) {
		lcm_drv = plcm->drv;
		if (lcm_drv->suspend) {/*这里判断一下LCM配置文件中是否有这个lcm_suspend函数,有的话就调用*/
			lcm_drv->suspend();
		} else {
			DISPERR("FATAL ERROR, lcm_drv->suspend is null\n");
			return -1;
		}
		if (lcm_drv->suspend_power)/*这里判断一下LCM配置文件中是否有这个lcm_suspend_power函数,有的话就调用*/
			lcm_drv->suspend_power();
		return 0;
	}
	DISPERR("lcm_drv is null\n");
	return -1;
}

可能有些刚开始调试的小伙伴不是很清楚,为什么这里执行lcm_drv->suspend();就是调用我们LCM配置文件中的lcm_suspend函数呢?你再看看你LCM驱动配置文件中最后面部分是不是大概有这些?

struct LCM_DRIVER **8057_fhd_dsi_vdo_ctc_cw_xq**2_lcm_drv = {
	.name = "**8057_fhd_dsi_vdo_ctc_cw_xq**2",
	.set_util_funcs = lcm_set_util_funcs,
	.get_params = lcm_get_params,
	.init = lcm_init,
	.suspend = lcm_suspend,
	.resume = lcm_resume,
	.compare_id = lcm_compare_id,
	.init_power = lcm_init_power,
	.resume_power = lcm_resume_power,
	.suspend_power = lcm_suspend_power,
	.set_backlight_cmdq = lcm_setbacklight_cmdq,
	.ata_check = lcm_ata_check,
#ifdef CONFIG_MTK_HIGH_FRAME_RATE
	/*DynFPS*/
	.dfps_send_lcm_cmd = lcm_dfps_inform_lcm,
	.dfps_need_send_cmd = lcm_dfps_need_inform_lcm,
#endif
};

回到刚才说的LCM恢复流程上,我们看看disp_lcm_esd_recover是怎么先走lcm_init_power再走lcm_init的,其函数原型如下:

int disp_lcm_esd_recover(struct disp_lcm_handle *plcm)
{
	struct LCM_DRIVER *lcm_drv = NULL;
	DISPFUNC();
	if (_is_lcm_inited(plcm)) {
		lcm_drv = plcm->drv;
		if (lcm_drv->esd_recover) {/*如果你LCM配置文件中写了客制化的esd_recover函数,这里就会优先执行你客制化的esd_recovery方案,如果没有写就会走下面的disp_lcm_init*/
			lcm_drv->esd_recover();
			DISPDBG("use customzie ESD recovery\n");
		} else {
			disp_lcm_init(plcm, 1);
            printk("[lcm] disp_lcm.c: disp_lcm_esd_recover/ disp_lcm_init \n");
		}
		return 0;
	}
	DISPERR("lcm_drv is null\n");
	return -1;
}
int disp_lcm_init(struct disp_lcm_handle *plcm, int force)
{
	struct LCM_DRIVER *lcm_drv = NULL;

	DISPFUNC();

	if (!_is_lcm_inited(plcm)) {
		DISPERR("plcm is null\n");
		return -1;
	}
	lcm_drv = plcm->drv;
	if (lcm_drv->init_power) {
		if (!disp_lcm_is_inited(plcm) || force) {
			printk("callback to lcm init power()\n");
			lcm_drv->init_power();/*调用LCM配置文件中的lcm_init_power进行上电*/
		}
	}
	if (lcm_drv->init) {
		if (!disp_lcm_is_inited(plcm) || force) {
			printk("callback to lcm init()\n");
			lcm_drv->init();/*调用LCM配置文件中的lcm_init进行拉LCD reset和push 初始化diamante*/
		}
	} else {
		DISPERR("FATAL ERROR, lcm_drv->init is null\n");
		return -1;
	}
	/* ddp_dsi_start(DISP_MODULE_DSI0, NULL); */
	/* DSI_BIST_Pattern_Test(DISP_MODULE_DSI0,NULL,true, 0x00ffff00); */
	return 0;
}

😂终于写完啦,花了2口气才写完,原创也太不容易了~l💯

五、个人经验

刚入行手机研发2年多,碰到过好几次关于esd难解的情况,不过还好都挺过来了🙈,给同行的建议就是多沟通,实在自己搞不定的时候可以多向IC 的FAE 或者Support请教,也可以向平台端的Support咨询,其实静电问题终究是硬件设计的一个缺陷导致的,软件实在改不动的时候也需要让硬件的兄弟们想想办法,看看一般是哪个位置打出的异常或者复现概率高,然后通过硬件来改改看,比如加个导电布或者挪一挪线路和什么电阻器件之类的也是可以的;另外需要提醒一点的就是,有时候打静电的手法、环境和手机的整机状态都会影响到手机的静电效果的,建议在上报问题前先把这些可能因素排除掉,否则会浪费很多调试资源;最后提醒的就是:千万不要让解静电问题耽搁项目调试计划,如果一周两周都搞不定的话就需要及时向项目团队进行预警了,沟通讨论看看是是否放弃这个屏的调试或者协调其他资源介入一起解决。

以上就是个人的一个全部知识共享,这里面肯定有很多不足甚至是错误理解的地方,希望同仁们在看到了之后及时在评论区中点评指正,避免再次误导他人。也欢迎同行的朋友们私信交流技术上的问题,希望能够互帮互助,如果这边文章对您有帮助,也请点赞收藏或转发!至此,感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值