linux系统LCD驱动(三):mtk lcd驱动lcm的加载以及初始化

上一篇博文(linux系统LCD驱动(二):mtk lcd驱动fb_info初始化)https://blog.csdn.net/Ian22l/article/details/105929192 提到mtkfb_probe除了进行fb_info的初始化外,还进行lcm的初始化以及lcm底层驱动的接口获取。

static int mtkfb_probe(struct platform_device *pdev)
{
	......
	is_lcm_inited = disp_lcm_get_init_flag();

	primary_display_init(mtkfb_find_lcm_driver(), lcd_fps, is_lcm_inited);
	......
}

一:获取lcm驱动名称
一般来说,mtk平台的lcm驱动lk启动的时候就已经加载并对lcm初始化一次了,因为在lk启动阶段主要用来显示logo或者充电显示等等。lk初始化完成后,会往内核传递lcm的名字以及初始化状态,disp_lcm_get_init_flag函数就是获取是否lk初始化状态,已经完成lk初始化这里就返回true,否则返回false.这里获取lk初始化状态的原因是因为如果lk进行了初始化,那么内核就不再进行初始化,如果再进行初始化,系统启动的时候就很容易出现白屏。但还是要获取lcm驱动的其他接口,例如supend以及resume.,主要在系统休眠唤醒的时候调用到。

unsigned int disp_lcm_get_init_flag(void)
{
	unsigned int lcm_init_flag = 0;
	lcm_init_flag = *((unsigned int *)(g_fb_rsv_mem_vbase + LCM_INIT_FLAG_ADDR_OFF));
	DISPMSG("Disp LCM init flag: 0x%x!\n", lcm_init_flag);
	return (lcm_init_flag == LCM_INIT_READY) ? 1 : 0;
}

mtkfb_find_lcm_driver获取lk传递到内核的参数,当中包含这lcm模块的名字,在内核加载lcd驱动的时候获取该名字。

char *mtkfb_find_lcm_driver(void)
{
	_parse_tag_videolfb();
	DISPMSG("%s, %s\n", __func__, mtkfb_lcm_name);
	return mtkfb_lcm_name;
}

二:内核加载lcm驱动以及初始化
primary_display_init函数会进行很多lcd控制器的一些初始化以及lcm驱动的加载,并且根据is_lcm_inited的状态是否要对lcm进行上电初始化。

int primary_display_init(char *lcm_name, unsigned int lcm_fps,
	int is_lcm_inited)
{
	......
	/* Part1: LCM */
	//通过lcm名字去加载lcm底层驱动
	pgc->plcm = disp_lcm_probe(lcm_name, LCM_INTERFACE_NOTDEFINED,
		is_lcm_inited);
	if (unlikely(pgc->plcm == NULL)) {
		DISPDBG("disp_lcm_probe returns null\n");
		ret = DISP_STATUS_ERROR;
		goto done;
	} else {
		DISPCHECK("disp_lcm_probe SUCCESS\n");
	}
.......
	/*根据is_lcm_inited判断是否已经lk初始化*/
	if (is_lcm_inited) {//初始化后就什么都不执行
		/* ??? why need */
		/* no need lcm power on,because lk power on lcm */
		/* ret = disp_lcm_init(pgc->plcm, 0);	*/
	} else {//没有初始化则在内核进行初始化
		/* lcm not inited:
		 * 1. fpga no lk(verify done);
		 * 2. evb no lk(need verify)
		 */
		if (use_cmdq) {
			/* make sure dsi configuration done before lcm init */
			_cmdq_flush_config_handle(1, NULL, 0);
			_cmdq_reset_config_handle();
		}

		/*pgc->plcm是上面disp_lcm_probe根据名字加载到的lcm驱动*/
		ret = disp_lcm_init(pgc->plcm, 1);
	}

}

三:lcm驱动的加载
从上面看出lcm驱动是通过disp_lcm_probe加载,我们看下它是如何加载到我们想要的lcm驱动的。代码如下

struct disp_lcm_handle *disp_lcm_probe(char *plcm_name,
	enum LCM_INTERFACE_ID lcm_id, int is_lcm_inited)
{
	int lcmindex = 0;
	bool isLCMFound = false;
	bool isLCMInited = false;

	/*获取驱动列表中支持的lcm驱动,一般来说会有多个lcm驱动*/
	if (_lcm_count() == 0) {
		DISPERR("no lcm driver defined in linux kernel driver\n");
		return NULL;
	} else if (_lcm_count() == 1) {
		if (plcm_name == NULL) {
			lcm_drv = lcm_driver_list[0];

			isLCMFound = true;
			isLCMInited = false;
			DISPCHECK("LCM Name NULL\n");
		} else {
			lcm_drv = lcm_driver_list[0];
			if (strcmp(lcm_drv->name, plcm_name)) {
				DISPERR(
					"FATAL ERROR!!!LCM Driver defined in kernel(%s) is different with LK(%s)\n",
				    lcm_drv->name, plcm_name);
				return NULL;
			}

			isLCMInited = true;
			isLCMFound = true;
		}

		if (!is_lcm_inited) {
			isLCMFound = true;
			isLCMInited = false;
			DISPCHECK("LCM not init\n");
		}

		lcmindex = 0;
	} else {//多个lcm模组驱动代码逻辑执行段
		if (plcm_name == NULL) {
			/* TODO: we need to detect all the lcm driver */
		} else {
			int i = 0;
			//根据lcm_name在lcm_driver_list中一一查找匹配
			for (i = 0; i < _lcm_count(); i++) {
				lcm_drv = lcm_driver_list[i];
				if (!strcmp(lcm_drv->name, plcm_name)) {
					isLCMFound = true;
					isLCMInited = true;
					lcmindex = i;
					break;
				}
			}
			if (!isLCMFound) {
				DISPERR(
					"FATAL ERROR: can't found lcm driver:%s in linux kernel driver\n",
				    plcm_name);
			} else if (!is_lcm_inited) {
				isLCMInited = false;
				DISPCHECK("LCM not init\n");
			}
		}
		/* TODO: */
	}
......
	plcm = kzalloc(sizeof(uint8_t *) *
			sizeof(struct disp_lcm_handle), GFP_KERNEL);
		lcm_param = kzalloc(sizeof(uint8_t *) * sizeof(struct LCM_PARAMS),
			GFP_KERNEL);
		if (plcm && lcm_param) {
			plcm->params = lcm_param;
			plcm->drv = lcm_drv;//将找到的lcm驱动赋值到plcm->drv中,那么返回的plcm变量则包含lcm_drv的访问地址
			plcm->is_inited = isLCMInited;
			plcm->index = lcmindex;
		} else {
			DISPERR("FATAL ERROR!!!kzalloc plcm and plcm->params failed\n");
			goto FAIL;
		}
.....

	if ((lcm_id == LCM_INTERFACE_NOTDEFINED) || lcm_id == plcm->lcm_if_id) {
		plcm->lcm_original_width = plcm->params->width;
		plcm->lcm_original_height = plcm->params->height;
		_dump_lcm_info(plcm);
		return plcm;//返回值
	}
}

从上面代码看出disp_lcm_probe就是根据lcm_name名字从lcm_driver_list数组表当中寻找lcm驱动进行匹配。并将找到的lcm驱动赋值到plcm->drv中,那么返回的plcm变量则包含lcm_drv的访问地址,这样就能访问lcm驱动的接口了。而lcm是从lcm_driver_list中寻找匹配的,我们看下lcm_driver_list的定义以及初始化。
代码路径:drivers/misc/mediatek/lcm/xxx_lcm_list.c(xxx代表某个具体平台)

struct LCM_DRIVER *lcm_driver_list[] = {
#ifdef CONFIG_ATC_AOSP_ENHANCEMENT
#if defined(LCM_DRIVER_COMMON)
	&lcm_driver_common_lcm_drv,
#endif
#if defined(SN65DSI83_BP101WX1_206_1280_800)
	&sn65dsi83_bp101wx1_206_1280_800_lcm_drv,
#endif
#endif

#if defined(NT35521_HD_DSI_VDO_TRULY_RT5081)
	&nt35521_hd_dsi_vdo_truly_rt5081_lcm_drv,
#endif

};

从上面看出lcm_driver_list是一个LCM_DRIVER类型数组,LCM_DRIVER结构体包含了lcm芯片的各种硬件操作结合。

struct LCM_DRIVER {
	const char *name;
	void (*set_util_funcs)(const struct LCM_UTIL_FUNCS *util);
	void (*get_params)(struct LCM_PARAMS *params);

	void (*init)(void);
	void (*suspend)(void);
	void (*resume)(void);

	/* for power-on sequence refinement */
	void (*init_power)(void);
	void (*suspend_power)(void);
	void (*resume_power)(void);

	void (*update)(unsigned int x, unsigned int y, unsigned int width,
			unsigned int height);
	unsigned int (*compare_id)(void);
	void (*parse_dts)(const struct LCM_DTS *DTS,
			unsigned char force_update);

	/* /////////////////////////CABC backlight related function */
	void (*set_backlight)(unsigned int level);
	void (*set_backlight_cmdq)(void *handle, unsigned int level);
	void (*set_pwm)(unsigned int divider);
	unsigned int (*get_pwm)(unsigned int divider);
	void (*set_backlight_mode)(unsigned int mode);
	/* ///////////////////////// */

	int (*adjust_fps)(void *cmdq, int fps, struct LCM_PARAMS *params);
	void (*validate_roi)(int *x, int *y, int *width, int *height);

	void (*scale)(void *handle, enum LCM_SCALE_TYPE scale);
	void (*setroi)(int x, int y, int width, int height, void *handle);
	/* ///////////ESD_RECOVERY////////////////////// */
	unsigned int (*esd_check)(void);
	unsigned int (*esd_recover)(void);
	unsigned int (*check_status)(void);
	unsigned int (*ata_check)(unsigned char *buffer);
	void (*read_fb)(unsigned char *buffer);
	int (*ioctl)(enum LCM_DRV_IOCTL_CMD cmd, unsigned int data);
	/* /////////////////////////////////////////////// */

	void (*enter_idle)(void);
	void (*exit_idle)(void);
	void (*change_fps)(unsigned int mode);

	/* //switch mode */
	void *(*switch_mode)(int mode);
	void (*set_cmd)(void *handle, int *mode, unsigned int cmd_num);
	void (*set_lcm_cmd)(void *handle, unsigned int *lcm_cmd,
		unsigned int *lcm_count, unsigned int *lcm_value);
	/* /////////////PWM///////////////////////////// */
	void (*set_pwm_for_mix)(int enable);

	void (*aod)(int enter);
};

当调试或者适配具体的lcm芯片的时候只需要定义一个一个struct LCM_DRIVER对象进行接口的初始化并且到lcm_driver_list当中便可。这跟我们的lcd的fb_info一样的思路。例如sn65dsi83_bp101wx1_206_1280_800_lcm_drv。

struct LCM_DRIVER sn65dsi83_bp101wx1_206_1280_800_lcm_drv = {
	.name = "sn65dsi83_bp101wx1_206_1280_800",
	.set_util_funcs = lcm_set_util_funcs,
	.get_params = lcm_get_params,
	.init = lcm_init,
	.suspend = lcm_suspend,
	.resume = lcm_resume,
	.compare_id = NULL,
	.init_power = NULL,
	.resume_power = NULL,
	.suspend_power = NULL,
	.esd_check = NULL,
	.set_backlight = NULL,
	.ata_check = NULL,
	.update = NULL,
	.switch_mode = NULL,
};

//lcm硬件上电时序初始化
static void lcm_init(void)
{
	LCM_DRIVER_INFO("enter\n");

	SET_RESET_PIN(0);
	MDELAY(15);
	SET_RESET_PIN(1);
	MDELAY(1);
	SET_RESET_PIN(0);
	MDELAY(10);
	SET_RESET_PIN(1);
	MDELAY(10);

	/*for lvds panel*/
	lvds_push_table(init_setting, sizeof(init_setting) / sizeof(struct LCM_setting_table));
	/*for mipi panel*/
	/*push_table(init_setting, sizeof(init_setting) / sizeof(struct LCM_setting_table), 1);*/

	lcm_gpio_output(LCM_POWER_EN_PIN, 1);
	LCM_DRIVER_INFO("leave\n");
}

四:lcm的初始化
上面的代码片段:

ret = disp_lcm_init(pgc->plcm, 1);//lcm的初始化

虽然说lk如果进行了初始化那么内核就不会进行初始化,但是我们还是来看lcm的初始化调用流程吧。

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;

	/*这里通过调用lcm的驱动接口进行init_power*/
	if (lcm_drv->init_power) {
		if (!disp_lcm_is_inited(plcm) || force) {
			DISPMSG("lcm init power()\n");
			lcm_drv->init_power();
		}
	}

	/*这里通过调用lcm的驱动接口进行init*/
	if (lcm_drv->init) {
		if (!disp_lcm_is_inited(plcm) || force) {
			DISPMSG("lcm init()\n");
			lcm_drv->init();
		}
	} 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;
}

其实很简单,这里其实就是调用了我们lcm驱动接口当中init_power以及init进行初始化。

你可能感兴趣的:(linux编程,mtk)