上一篇博文(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进行初始化。