天天看点

MTK平台LCM驱动加载过程-lk阶段

原创文章,转载请注明出处

注:该博客基于MT6739 Android O分析,贴出来的代码会针对性删减,只留重点部分

总体流程

platform_init -> mt_disp_init -> primary_display_init -> disp_lcm_probe

我们先从primary_display_init函数开始

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6739\primary_display.c

int primary_display_init(char *lcm_name)
{
    DISPFUNC()
    ……
    if (pgc->plcm == NULL)
    	pgc->plcm = disp_lcm_probe( lcm_name, LCM_INTERFACE_NOTDEFINED);//disp_lcm_probe:返回一个disp_lcm_handle*的结构体指针,里面存放了找到的lcm驱动的关键参数,后面我们会详细分析
    if (pgc->plcm == NULL) {
    	DISPCHECK("disp_lcm_probe returns null\n");
    	ret = DISP_STATUS_ERROR;
    	goto done;
    } else {
    	DISPCHECK("disp_lcm_probe SUCCESS\n");
    }
    
    lcm_param = disp_lcm_get_params(pgc->plcm);    //获取对应lcm的params
    
    if (lcm_param == NULL) {
    	DISPERR("get lcm params FAILED\n");
    	ret = DISP_STATUS_ERROR;
    	goto done;
    }
    ……
    ret = disp_lcm_init(pgc->plcm);    //走对应lcm的初始化操作
    ……
}
           

这里的pgc稍微有点特别,是个全局结构体指针变量,以下是它的定义。

具体就是申请并初始化了一个display_primary_path_context结构体的内存空间,然后返回指向这个结构体的指针

#define pgc _get_context()

static display_primary_path_context* _get_context(void)
{
    static int is_context_inited = 0;
    static display_primary_path_context g_context;
    if (!is_context_inited) {
    	memset((void*)&g_context, 0, sizeof(display_primary_path_context));
    	is_context_inited = 1;
    }
    
    return &g_context;
}
           

下面重点分析一下disp_lcm_probe函数

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6739\disp_lcm.c

disp_lcm_handle* disp_lcm_probe(char* plcm_name, LCM_INTERFACE_ID lcm_id)
{
    DISPFUNC();
    
    //int ret = 0;
    bool isLCMFound = false;
    bool isLCMConnected = false;
    
    LCM_DRIVER *lcm_drv = NULL;    //LCM_DRIVER lcm操作函数结构体
    LCM_PARAMS *lcm_param = NULL;    //LCM_PARAMS lcm参数结构体
    disp_lcm_handle *plcm = NULL;
    
    if (_lcm_count() == 0) {    //_lcm_count()返回兼容的lcm个数
    	DISPERR("no lcm driver defined in linux kernel driver\n");
    	return NULL;
    } else if (_lcm_count() == 1) {
    	lcm_drv = lcm_driver_list[0];    //lcm_driver_list:指针数组,存放各个lcm的LCM_DRIVER信息
    	isLCMFound = true;    //lcm是否找到标志位
    } else {
    	// in lk, plcm_name should always be NULL
    	if (plcm_name == NULL) {
    		int i = 0;
    		disp_path_handle handle = NULL;
    		disp_lcm_handle hlcm;
    		disp_lcm_handle *plcm = &hlcm;
    		LCM_PARAMS hlcm_param;
    
    		for (i=0; i<_lcm_count(); i++) {    //_lcm_count>1的时候,进入for循环
    			memset((void*)&hlcm, 0, sizeof(disp_lcm_handle));
    			memset((void*)&hlcm_param, 0, sizeof(LCM_PARAMS));
    
    			lcm_drv= lcm_driver_list[i];    //循环获取各个lcm的LCM_DRIVER结构体指针
    			lcm_drv->get_params(&hlcm_param);    //设置对应lcm的各种param参数并存放到hlcm_param中
    			plcm->drv = lcm_drv;    //填充plcm drv成员
    			plcm->params = &hlcm_param;    //填充plcm params成员
    			plcm->lcm_if_id = plcm->params->lcm_if;
    			DISPDBG("we will check lcm: %s\n", lcm_drv->name);
    			if (lcm_id == LCM_INTERFACE_NOTDEFINED ||(lcm_id != LCM_INTERFACE_NOTDEFINED && plcm->lcm_if_id == lcm_id)) {
    				handle = _display_interface_path_init(plcm);    //ddp dsi的一些初始化,后面会分析
    				if (handle == NULL) {
    					DISPERR("_display_interface_path_init returns NULL\n");
    					goto FAIL;
    				}
    
    				if (lcm_drv->init_power) {    //检查lcm驱动中init_power接口是否有实现
    					lcm_drv->init_power();    //如果有实现,先进行init power操作。针对一些对上电有特殊要求的lcm
    				}
    
    				if (lcm_drv->compare_id != NULL) {    //检查lcm驱动中compare_id接口是否有实现,兼容的关键接口,用来判断当前lcm驱动是否与现有硬件匹配
    					if (lcm_drv->compare_id() != 0) {    //调用compare_id接口
    						isLCMFound = true;    //compare_id成功则说明找到了lcm,标志位置为true
    						_display_interface_path_deinit(handle);
    						DISPMSG("we will use lcm: %s\n", lcm_drv->name);
    						break;    //找到lcm之后退出for循环
    					}
    				}
    
    				_display_interface_path_deinit(handle);
    			}
    		}
    
    		if (isLCMFound == false) {    //如果以上循环都匹配不到lcm,则标志位为false
    			DISPERR("we have checked all lcm driver, but no lcm found\n");
    			lcm_drv = lcm_driver_list[0];    //标志位为false时默认加载lcm_driver_list数组中第一个lcm
    			isLCMFound = true;
    		}
    	} else {
    		int i = 0;
    		for (i=0; i<_lcm_count(); i++) {
    			lcm_drv = lcm_driver_list[i];
    			if (!strcmp(lcm_drv->name, plcm_name)) {
    				isLCMFound = true;
    				break;
    			}
    		}
    	}
    }
    
    if (isLCMFound == false) {
    	DISPERR("FATAL ERROR!!!No LCM Driver defined\n");
    	return NULL;
    }
    ……
    return plcm;    //最后将plcm返回
}
           

到这里lcm的驱动就加载完成了,在之后platform_init会继续往下走,显示lk的开机logo。

下来我们来看看上面提到的_display_interface_path_init函数做了哪些工作

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6739\disp_lcm.c

disp_path_handle _display_interface_path_init(disp_lcm_handle *plcm)
{
    DISP_MODULE_ENUM dst_module = DISP_MODULE_NONE;
    disp_ddp_path_config data_config;
    disp_path_handle handle = NULL;
    
    DISPFUNC();
    
    if (plcm == NULL) {
    	DISPERR("plcm is null\n");
    	return NULL;
    }
    
    handle = dpmgr_create_path(DDP_SCENARIO_PRIMARY_RDMA0_DISP, NULL);    //这里应该是根据disp来配置handle的一些结构体成员
    if (handle) {
    	DISPCHECK("dpmgr create path SUCCESS(0x%p)\n", handle);
    } else {
    	DISPCHECK("dpmgr create path FAIL\n");
    	return NULL;
    }
    
    dst_module = _get_dst_module_by_lcm(plcm);    //根据lcm接口类型配置disp的module,这里返回的是DISP_MODULE_DSI0
    dpmgr_path_set_dst_module(handle, dst_module);    //将二维数组module_list_scenario[handle->scenario]中的最后一个数据设置为DISP_MODULE_DSI0
    DISPCHECK("dpmgr set dst module FINISHED(%s)\n", ddp_get_module_name(dst_module));
    
    
    dpmgr_set_lcm_utils(handle, plcm->drv);    //调用ddp_modules_driver[DISP_MODULE_DSI0]->set_lcm_utils,最终调用lcm_drv->set_util_funcs(utils)填充对应lcm驱动中的utils结构体成员
    dpmgr_path_init(handle, CMDQ_DISABLE);    //调用ddp模块驱动的初始化函数
    ……
    return handle;
}
           

以上相关函数位于vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6739\ddp_manager.c和

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt6739\ddp_path.c中

handle = dpmgr_create_path(DDP_SCENARIO_PRIMARY_RDMA0_DISP, NULL);
disp_path_handle dpmgr_create_path(DDP_SCENARIO_ENUM scenario, cmdqRecHandle cmdq_handle)
{
    ……
    memset((void *)(&g_handle), 0, sizeof(ddp_path_handle_t));
    path_handle  = &g_handle;
    if (NULL != path_handle) {
    	path_handle->cmdqhandle = cmdq_handle;
    	path_handle->scenario = scenario;    //将scenario成员设置为DDP_SCENARIO_PRIMARY_RDMA0_DISP
    	path_handle->hwmutexid = acquire_mutex(scenario);
    ……
}
           
dpmgr_path_set_dst_module(handle, dst_module);
int dpmgr_path_set_dst_module(disp_path_handle dp_handle,DISP_MODULE_ENUM dst_module)
{
    ASSERT(dp_handle != NULL);
    ddp_path_handle handle = (ddp_path_handle)dp_handle;
    ASSERT((handle->scenario >= 0  && handle->scenario < DDP_SCENARIO_MAX));
    DISP_LOG_I("set dst module on scenario %s, module %s\n",
               ddp_get_scenario_name(handle->scenario),ddp_get_module_name(dst_module));
    return ddp_set_dst_module(handle->scenario, dst_module);//这里我们知道两个参数分别为DDP_SCENARIO_PRIMARY_RDMA0_DISP和DISP_MODULE_DSI0
}
int ddp_set_dst_module(DDP_SCENARIO_ENUM scenario, DISP_MODULE_ENUM dst_module)
{
    int i = 0;
    
    DDPMSG("ddp_set_dst_module, scenario=%s, dst_module=%s\n",
           ddp_get_scenario_name(scenario), ddp_get_module_name(dst_module));
    if (ddp_find_module_index(scenario, dst_module) > 0) {
    	DDPDBG("%s is already on path\n", ddp_get_module_name(dst_module));
    	return 0;
    }
    i = ddp_get_module_num_l(module_list_scenario[scenario]) - 1;//获取module_list_scenario[DDP_SCENARIO_PRIMARY_RDMA0_DISP]的长度
    ASSERT(i >= 0);
    if (dst_module == DISP_MODULE_DSIDUAL) {    //dst_module==DISP_MODULE_DSI0
    	if (i < (DDP_ENING_NUM - 1)) {
    		module_list_scenario[scenario][i++] = DISP_MODULE_SPLIT0;
    	} else {
    		DDPERR("set dst module over up bound\n");
    		return -1;
    	}
    } else {
    	if (ddp_get_dst_module(scenario) == DISP_MODULE_DSIDUAL) {
    		if (i >= 1) {
    			module_list_scenario[scenario][i--] = -1;
    		} else {
    			DDPERR("set dst module over low bound\n");
    			return -1;
    		}
    	}
    }
    module_list_scenario[scenario][i] = dst_module;    //将module_list_scenario[scenario]最后一个成员设置为DISP_MODULE_DSI0
    if (scenario == DDP_SCENARIO_PRIMARY_ALL)
    	ddp_set_dst_module(DDP_SCENARIO_PRIMARY_DISP, dst_module);
    else if (scenario == DDP_SCENARIO_SUB_ALL)
    	ddp_set_dst_module(DDP_SCENARIO_SUB_RDMA1_DISP, dst_module);
    
    ddp_print_scenario(scenario);
    return 0;
}
           

到这里我们可以知道 

module_list_scenario[DDP_SCENARIO_PRIMARY_RDMA0_DISP][]=
{
    DISP_MODULE_RDMA0, DISP_MODULE_PWM0, DISP_MODULE_DSI0,
    DISP_MODULE_NONE,
}
           
dpmgr_set_lcm_utils(handle, plcm->drv);
int dpmgr_set_lcm_utils(disp_path_handle dp_handle, void *lcm_drv)
{
    unsigned int i=0;
    int module_name;
    ASSERT(dp_handle != NULL);
    ddp_path_handle handle = (ddp_path_handle)dp_handle;
    unsigned int *modules = ddp_get_scenario_list(handle->scenario);
    int module_num = ddp_get_module_num(handle->scenario);
    
    DISP_LOG_V("path set lcm drv handle 0x%p\n",handle);
    for ( i=0; i< module_num; i++) {
    	module_name = modules[i];
    	if (ddp_modules_driver[module_name] != 0) {//ddp_modules_driver[DISP_MODULE_PWM0]=NULL
    		if ((ddp_modules_driver[module_name]->set_lcm_utils!= 0)&&lcm_drv) {//ddp_modules_driver[DISP_MODULE_RDMA0]->set_lcm_utils=NULL
    			DISP_LOG_I("%s set lcm utils\n",ddp_get_module_name(module_name));
    			ddp_modules_driver[module_name]->set_lcm_utils(module_name, lcm_drv); //调用ddp_modules_driver[DISP_MODULE_DSI0]->set_lcm_utils
    		}
    	}
    }
    return 0;
}
           
ddp_modules_driver[module_name]->set_lcm_utils(module_name, lcm_drv);
int ddp_dsi_set_lcm_utils(DISP_MODULE_ENUM module, LCM_DRIVER *lcm_drv)
{
    LCM_UTIL_FUNCS *utils = NULL;
    ……
    utils->set_reset_pin    = lcm_set_reset_pin;    //设置复位脚操作函数
    utils->udelay           = lcm_udelay;    //设置微妙延时函数
    utils->mdelay           = lcm_mdelay;    //设置毫秒延时函数
    
    if (module == DISP_MODULE_DSI0) {    //设置dsi写数据操作接口
    	utils->dsi_set_cmdq = DSI_set_cmdq_wrapper_DSI0;
    	utils->dsi_set_cmdq_V2 = DSI_set_cmdq_V2_Wrapper_DSI0;
    	utils->dsi_set_cmdq_V3 = DSI_set_cmdq_V3_Wrapper_DSI0;
    	utils->dsi_dcs_read_lcm_reg_v2 = DSI_dcs_read_lcm_reg_v2_wrapper_DSI0;
    	utils->dsi_set_cmdq_V22 = DSI_set_cmdq_V2_DSI0;
    	utils->dsi_set_cmdq_V11 = DSI_set_cmdq_V11_wrapper_DSI0;
    }
    ……
    lcm_drv->set_util_funcs(utils);   //调用对应lcm驱动中set_util_funcs接口,将utils传出去
    ……
}
           

到这里整个lk关于lcm驱动的加载过程就都介绍完了,做完上面介绍的流程之后,在跑完lk准备调到kernel之前,lk会通过fdt将加载到的lcm相关信息传给kernel,以便后续的操作

vendor\mediatek\proprietary\bootable\bootloader\lk\app\mt_boot\mt_boot.c

int boot_linux_fdt(void *kernel, unsigned *tags,
		   unsigned machtype,
		   void *ramdisk, unsigned ramdisk_sz)
{
    ……
    ptr = (char *)target_atag_videolfb((unsigned *)buf, FDT_BUFF_SIZE);
    ret = fdt_setprop(fdt, offset, "atag,videolfb", buf, ptr - buf);
    if (ret) {
        assert(0);
        return FALSE;
    }
    ……
}
unsigned *target_atag_videolfb(unsigned *ptr, size_t buf_size)
{
    extern unsigned long long fb_addr_pa_k;
    const char *lcmname = mt_disp_get_lcm_id();
    unsigned *p = ptr;
    char *data = NULL;
    size_t data_cnt = 0;
    size_t data_size = 0;
    
    *p++ = (unsigned)(fb_addr_pa_k & 0xFFFFFFFF);
    data_cnt++;
    *p++ = (unsigned)((fb_addr_pa_k >> 32) & 0xFFFFFFFF);
    data_cnt++;
    *p++ = DISP_IsLcmFound();
    data_cnt++;
    *p++ = mt_disp_get_lcd_time();
    data_cnt++;
    *p++ = DISP_GetVRamSize();
    data_cnt++;
    
    /*
     * ATAG size for ATAG_VIDEOLFB is aligned to a word and the unit of size is 4 bytes.
     */
    data = (char *)p;
    data_size = buf_size - data_cnt * sizeof(unsigned);
    
    strncpy(data, lcmname, data_size - 1);
    data[data_size - 1] = '\0';
    p += (ROUNDUP(strlen(lcmname) + 1, 4) / 4);
    
    dprintf(CRITICAL, "videolfb - fb_base    = 0x%llx\n", fb_addr_pa_k);
    dprintf(CRITICAL, "videolfb - islcmfound = %d\n", DISP_IsLcmFound());
    dprintf(CRITICAL, "videolfb - fps        = %d\n", mt_disp_get_lcd_time());
    dprintf(CRITICAL, "videolfb - vram       = %d\n", DISP_GetVRamSize());
    dprintf(CRITICAL, "videolfb - lcmname    = %s\n", lcmname);
    
    return (unsigned *)p;
}
           

后续kernel会通过fdt获取atag,videolfb这个标签中lcm的lcmname fps等相关信息,这个我们在下一篇讲述

继续阅读