目錄
msm_fb_alloc()&fbcon_setup(&(panel->fb));
一、整體流程概述
首先從aboot_init開始,去走LCD的初始化流程,在aboot中判斷是哪種存儲方式和啓動方式,然後進入LCD在LK的唯一初始化入口——target_display_init。然後會調用對應的gcdb_display_init方法。判斷是哪個屏,並初始化對應的參數,以及對其進行上電,最終點亮屏幕。與此同時在點亮之後,會在aboot中將對應的屏幕信息寫入到pbuf中,以便kernel能夠獲取到對應的屏幕信息,然後加載參數並點亮。如下是簡單的流程圖:
二、詳細流程分析
1、LK中LCD的唯一入口
首先我們從aboot_init方法開始,看如何一步步啓動LCD:
void aboot_init(const struct app_descriptor *app)
{
... ...
target_display_init(device.display_panel);
... ...
}
aboot中省略了系統系統的前期準備以及相關的啓動模式,這部分後續文章在進行分析,當前繼續分析LCD啓動相關。
這裏就可以看到我們的入口方法:target_display_init()。
-
target_display_init()
根據不同的平臺,此處調用的target_display_init()也是不同的,我們以MSM8909爲例。此處調用的是msm8909目錄下的Target_display.c文件。我們開始看下target_display_init()的調用流程:
void target_display_init(const char *panel_name)
{
... ...
if (target_splash_disable())//如果splash_disable的宏開啓,則LK階段不再初始化LCD,參數等的初始化均在kernel階段進行
return;
do {
target_force_cont_splash_disable(false);
ret = gcdb_display_init(oem.panel, MDP_REV_305, MIPI_FB_ADDR);
if (!ret || ret == ERR_NOT_SUPPORTED) {
break;
} else {
target_force_cont_splash_disable(true);
msm_display_off();
}
} while (++panel_loop <= oem_panel_max_auto_detect_panels());
... ...
}
-
gcdb_display_init()
target_display_init方法中最關鍵的部分即do...while結構。這裏會對LCD繼續做初始化,即gcdb_display_init方法。該方法在gcdb_display.c (bootable\bootloader\lk\dev\gcdb\display),我們先來看下該方法的實現:
int gcdb_display_init(const char *panel_name, uint32_t rev, void *base)
{
int ret = NO_ERROR;
int pan_type;
dsi_video_mode_phy_db.pll_type = DSI_PLL_TYPE_28NM;
//通過讀取對應的ID或者mipi回讀判斷是哪塊屏,並將屏幕信息保存在pinfo結構體中
pan_type = oem_panel_select(panel_name, &panelstruct, &(panel.panel_info),
&dsi_video_mode_phy_db);
if (pan_type == PANEL_TYPE_DSI) {
if (update_dsi_display_config())
goto error_gcdb_display_init;
target_dsi_phy_config(&dsi_video_mode_phy_db);
mdss_dsi_check_swap_status();
mdss_dsi_set_pll_src();
if (dsi_panel_init(&(panel.panel_info), &panelstruct)) {
dprintf(CRITICAL, "DSI panel init failed!\n");
ret = ERROR;
goto error_gcdb_display_init;
}
panel.panel_info.mipi.mdss_dsi_phy_db = &dsi_video_mode_phy_db;
panel.pll_clk_func = mdss_dsi_panel_clock;
panel.dfps_func = mdss_dsi_mipi_dfps_config;
panel.power_func = mdss_dsi_panel_power;
panel.pre_init_func = mdss_dsi_panel_pre_init;
panel.bl_func = mdss_dsi_bl_enable;
panel.dsi2HDMI_config = mdss_dsi2HDMI_config;
/*
* Reserve fb memory to store pll codes and pass
* pll codes values to kernel.
*/
panel.panel_info.dfps.dfps_fb_base = base;
base += DFPS_PLL_CODES_SIZE;
panel.fb.base = base;
dprintf(SPEW, "dfps base=0x%p,d, fb_base=0x%p!\n",
panel.panel_info.dfps.dfps_fb_base, base);
panel.fb.width = panel.panel_info.xres;
panel.fb.height = panel.panel_info.yres;
panel.fb.stride = panel.panel_info.xres;
panel.fb.bpp = panel.panel_info.bpp;
panel.fb.format = panel.panel_info.mipi.dst_format;
} else if (pan_type == PANEL_TYPE_EDP) {
mdss_edp_panel_init(&(panel.panel_info));
/* prepare func is set up at edp_panel_init */
panel.clk_func = mdss_edp_panel_clock;
panel.power_func = mdss_edp_panel_power;
panel.bl_func = mdss_edp_bl_enable;
panel.fb.format = FB_FORMAT_RGB888;
} else {
dprintf(CRITICAL, "Target panel init not found!\n");
ret = ERR_NOT_SUPPORTED;
goto error_gcdb_display_init;
}
panel.fb.base = base;
panel.mdp_rev = rev;
//通過獲取到的屏幕信息繼續進行初始化
ret = msm_display_init(&panel);
error_gcdb_display_init:
display_enable = ret ? 0 : 1;
return ret;
}
可以看到該方法主要有四個動作:
① 選取對應的屏幕:dsi_panel_init()
pan_type = oem_panel_select(panel_name, &panelstruct, &(panel.panel_info),
&dsi_video_mode_phy_db);
② 將初始化信息保存在pinfo中:dsi_panel_init()
dsi_panel_init(&(panel.panel_info), &panelstruct)
③ 根據panel type初始化panel結構體
④ 根據上述信息進一步初始化LCD:msm_display_init()
ret = msm_display_init(&panel);
2、屏幕識別&獲取初始化參數信息
-
oem_panel_select()
該方法同樣根據平臺不同會各自選取對應的執行文件,我們同樣使用MSM8909對應的Oem_panel.c。此方法會根據硬件ID或者回讀MIPI的方法來識別不同的屏。然後將識別到的屏幕信息保存在panelstruct結構體中。
int oem_panel_select(const char *panel_name, struct panel_struct *panelstruct,
struct msm_panel_info *pinfo,
struct mdss_dsi_phy_ctrl *phy_db)
{
uint32_t hw_id = board_hardware_id();
uint32_t platform_subtype = board_hardware_subtype();
int32_t panel_override_id;
if (panel_name) {
panel_override_id = panel_name_to_id(supp_panels,
ARRAY_SIZE(supp_panels), panel_name);
if (panel_override_id < 0) {
dprintf(CRITICAL, "Not able to search the panel:%s\n",
panel_name);
} else if (panel_override_id < UNKNOWN_PANEL) {
/* panel override using fastboot oem command */
panel_id = panel_override_id;
dprintf(INFO, "OEM panel override:%s\n",
panel_name);
goto panel_init;
}
}
if((panel_id=switch_panel_id())==UNKNOWN_PANEL)//獲取屏幕ID
{
return PANEL_TYPE_UNKNOWN;
}
special_panel = panel_id;
panel_init:
phy_db->regulator_mode = DSI_PHY_REGULATOR_LDO_MODE;
return init_panel_data(panelstruct, pinfo, phy_db);//獲取配置參數
}
① 首先我們來看下如何識別不同的LCD:switch_panel_id()
/*
* switch panel id by id0 and id1
*/
int switch_panel_id(void)
{
int lcd_id0;
int lcd_id1;
gpio_tlmm_config(panel_id0.pin_id, 0,
panel_id0.pin_direction, panel_id0.pin_pull,
panel_id0.pin_strength, panel_id0.pin_state);
gpio_tlmm_config(panel_id1.pin_id, 0,
panel_id1.pin_direction, panel_id1.pin_pull,
panel_id1.pin_strength, panel_id1.pin_state);
//mdelay(10);
lcd_id1=1;
lcd_id0=1;
lcd_id1=gpio_status(panel_id1.pin_id);//獲取gpio狀態
lcd_id0=gpio_status(panel_id0.pin_id);
dprintf(INFO,"lancelot lcd_id0=%d.\n",lcd_id0);
dprintf(INFO,"lancelot lcd_id1=%d.\n",lcd_id1);
if((lcd_id1 == 1)&&(lcd_id0 == 0)){
return JD9365_STARRY_VIDEO_PANEL;
}else{
return NT35521Z_INX101_VIDEO_PANEL;
}
}
此處給出的是通過兩個硬件ID腳的狀態組合進行識別的。即ID0和ID1。分別有高、低兩種狀態。兩個ID腳的組合可以實現四種狀態(00,01,10,11),也就是說我們足夠識別四種屏。當然硬件上首先要能夠進行區分。
② 區分panel id後,我們就可以根據panel id獲取不同的配置參數信息:init_panel_data()
static int init_panel_data(struct panel_struct *panelstruct,
struct msm_panel_info *pinfo,
struct mdss_dsi_phy_ctrl *phy_db)
{
int pan_type = PANEL_TYPE_DSI;//panel type
switch (panel_id) {
... ...
case NT35521Z_INX101_VIDEO_PANEL:
panelstruct->paneldata = &nt35521z_inx101_video_panel_data;
panelstruct->panelres = &nt35521z_inx101_video_panel_res;
panelstruct->color = &nt35521z_inx101_video_color ;
panelstruct->videopanel = &nt35521z_inx101_video_video_panel ;
panelstruct->commandpanel = &nt35521z_inx101_video_command_panel ;
panelstruct->state = &nt35521z_inx101_video_state;
panelstruct->laneconfig = &nt35521z_inx101_video_lane_config;
panelstruct->paneltiminginfo
= &nt35521z_inx101_video_timing_info;
panelstruct->panelresetseq
= &nt35521z_inx101_video_reset_seq;
panelstruct->backlightinfo = &nt35521z_inx101_video_backlight;
pinfo->mipi.panel_on_cmds
= nt35521z_inx101_video_on_command;
pinfo->mipi.num_of_panel_on_cmds
= NT35521Z_INX101_VIDEO_ON_COMMAND;
pinfo->mipi.panel_off_cmds
= nt35521z_inx101_video_off_command;
pinfo->mipi.num_of_panel_off_cmds
= NT35521Z_INX101_VIDEO_OFF_COMMAND;
memcpy(phy_db->timing,
nt35521z_inx101_video_timings, TIMING_SIZE);
break;
... ...
}
return pan_type;
}
init_panel_data()初始化panel數據,主要初始化panel_struct結構體數據,然後返回panel類型PANEL_TYPE_DSI(還有EDP和HDMI格式)。據對應的panel_id值將對應的屏幕參數賦值給panelstruct結構體。此結構體對應/dsi-panel-xxx-video.dtsi文件,此結構體主要成員如下:
struct panel_struct {
struct panel_config *paneldata;//基本參數信息
struct panel_resolution *panelres;//Panel的分辨率、時序參數、極性等數據
struct color_info *color;
struct videopanel_info *videopanel;
struct commandpanel_info *commandpanel;
struct command_state *state;
struct lane_configuration *laneconfig;//這裏可看出只支持到4lane,支持的lane對應的state爲1,否則爲0
struct panel_timing *paneltiminginfo;
struct panel_reset_sequence *panelresetseq;//對應設備樹文件的qcom,mdss-dsi-reset-sequence =<1 20>, <0 20>, <1 20>;這裏可讓pin狀態及演示多久後再修改pin狀態。
struct backlight *backlightinfo;
struct fb_compression fbcinfo;
struct topology_config *config;
};
struct backlight {
uint16_t bl_interface_type; //背光控制方式
uint16_t bl_min_level;//背光最小值
uint16_t bl_max_level;//背光最大值
uint16_t bl_step;//間隔值
uint16_t bl_pmic_controltype;
char *bl_pmic_model;
};
-
dsi_panel_init
panel_display.c (bootable\bootloader\lk\dev\gcdb\display)
根據panel_info和panelstruct將對應屏幕的參數初始化到pinfo中。
int dsi_panel_init(struct msm_panel_info *pinfo,
struct panel_struct *pstruct)
{
int ret = NO_ERROR;
/* Resolution setting*/
pinfo->xres = pstruct->panelres->panel_width;
pinfo->yres = pstruct->panelres->panel_height;
pinfo->lcdc.h_back_porch = pstruct->panelres->hback_porch;
pinfo->lcdc.h_front_porch = pstruct->panelres->hfront_porch;
pinfo->lcdc.h_pulse_width = pstruct->panelres->hpulse_width;
pinfo->lcdc.v_back_porch = pstruct->panelres->vback_porch;
pinfo->lcdc.v_front_porch = pstruct->panelres->vfront_porch;
pinfo->lcdc.v_pulse_width = pstruct->panelres->vpulse_width;
pinfo->lcdc.hsync_skew = pstruct->panelres->hsync_skew;
pinfo->border_top = pstruct->panelres->vtop_border;
pinfo->border_bottom = pstruct->panelres->vbottom_border;
pinfo->border_left = pstruct->panelres->hleft_border;
pinfo->border_right = pstruct->panelres->hright_border;
... ...
}
-
初始化panel結構體
初始化panel全局變量的其他結構體成員
panel.panel_info.mipi.mdss_dsi_phy_db = &dsi_video_mode_phy_db;
panel.pll_clk_func = mdss_dsi_panel_clock;
panel.dfps_func = mdss_dsi_mipi_dfps_config;
panel.power_func = mdss_dsi_panel_power;
panel.pre_init_func = mdss_dsi_panel_pre_init;
panel.bl_func = mdss_dsi_bl_enable;
panel.dsi2HDMI_config = mdss_dsi2HDMI_config;
/*
* Reserve fb memory to store pll codes and pass
* pll codes values to kernel.
*/
panel.panel_info.dfps.dfps_fb_base = base;
base += DFPS_PLL_CODES_SIZE;
panel.fb.base = base;
dprintf(SPEW, "dfps base=0x%p,d, fb_base=0x%p!\n",
panel.panel_info.dfps.dfps_fb_base, base);
panel.fb.width = panel.panel_info.xres;
panel.fb.height = panel.panel_info.yres;
panel.fb.stride = panel.panel_info.xres;
panel.fb.bpp = panel.panel_info.bpp;
panel.fb.format = panel.panel_info.mipi.dst_format;
3、屏幕進一步初始化及點亮
獲取到對應的屏幕信息以及參數後,會調用msm_display_init方法,進一步的爲屏幕的點亮做初始化的動作。實際在該方法中就開始對屏幕進行上電、第一幀顯示動畫、背光燈的進行控制。
int msm_display_init(struct msm_fb_panel_data *pdata)
{
int ret = NO_ERROR;
... ...
/* Turn on panel 給panel上電*/
if (pdata->power_func)
ret = pdata->power_func(1, &(panel->panel_info));
... ...
/* Enable clock 使能CLK*/
if (pdata->clk_func)
ret = pdata->clk_func(1, &(panel->panel_info));
... ...
//調用calculate_clock_config(pinfo)計算時鐘配置和調用target_panel_clock(enable, pinfo)配置目標panel的時鐘。
if (pdata->pll_clk_func)
ret = pdata->pll_clk_func(1, &(panel->panel_info));
... ...
//爲幀緩衝器(frame buffer)分配內存。
ret = msm_fb_alloc(&(panel->fb));
if (ret)
goto msm_display_init_out;
fbcon_setup(&(panel->fb));
display_image_on_screen();//調用fetch_image_from_partition()從splash分區獲取lk logo圖片,如果splash分區沒有滿足要求的數據,就顯示默認的logo。
... ...
ret = msm_display_config();//根據pinfo->type,比如我們這裏是MIPI_VIDEO_PANEL來配置msm平臺display,配置時還需要根據MDP(MobileDisplay processor)的版本來調用對應的config函數
if (ret)
goto msm_display_init_out;
ret = msm_display_on();
if (ret)
goto msm_display_init_out;
if (pdata->post_power_func)
ret = pdata->post_power_func(1);
if (ret)
goto msm_display_init_out;
/* Turn on backlight */
if (pdata->bl_func)
ret = pdata->bl_func(1);
if (ret)
goto msm_display_init_out;
msm_display_init_out:
return ret;
}
通過上述方法可以看到,在這裏就可以看到第一幀畫面了。我們來拆分下msm_display_init方法,看他分別作了哪些動作。
-
mdss_dsi_panel_power()
static int mdss_dsi_panel_power(uint8_t enable,
struct msm_panel_info *pinfo)
{
int ret = NO_ERROR;
if (enable) {
ret = target_ldo_ctrl(enable, pinfo);
if (ret) {
dprintf(CRITICAL, "LDO control enable failed\n");
return ret;
}
... ...
/* Panel Reset */
if (!panelstruct.paneldata->panel_lp11_init) {
ret = mdss_dsi_panel_reset(enable);
if (ret) {
dprintf(CRITICAL, "panel reset failed\n");
return ret;
}
}
dprintf(SPEW, "Panel power on done\n");
} else {
/* Disable panel and ldo */
ret = mdss_dsi_panel_reset(enable);
if (ret) {
dprintf(CRITICAL, "panel reset disable failed\n");
return ret;
}
ret = target_ldo_ctrl(enable, pinfo);
if (ret) {
dprintf(CRITICAL, "ldo control disable failed\n");
return ret;
}
dprintf(SPEW, "Panel power off done\n");
}
return ret;
}
target_ldo_ctrl()--->regulator_enable()給L2、L6和L17供電。另外是否會進行reset,取決於我們在dtsi中給lp11附的值。
-
mdss_dsi_panel_clock
調用calculate_clock_config(pinfo)計算時鐘配置和調用target_panel_clock(enable, pinfo)配置目標panel的時鐘。
static uint32_t mdss_dsi_panel_clock(uint8_t enable,
struct msm_panel_info *pinfo)
{
uint32_t ret = NO_ERROR;
ret = calculate_clock_config(pinfo);
if (ret)
dprintf(CRITICAL, "Clock calculation failed\n");
else
ret = target_panel_clock(enable, pinfo);
return ret;
}
-
msm_fb_alloc()&fbcon_setup(&(panel->fb));
爲幀緩衝器(frame buffer)分配內存。
-
display_image_on_screen()
調用fetch_image_from_partition()從splash分區獲取lk logo圖片,如果splash分區沒有滿足要求的數據,就顯示默認的logo。
void display_image_on_screen(void)
{
#if DISPLAY_TYPE_MIPI
int fetch_image_from_partition();
if (fetch_image_from_partition() < 0) {
display_default_image_on_screen();
} else {
/* data has been put into the right place */
fbcon_flush();
}
#else
display_default_image_on_screen();
#endif
}
-
msm_display_config
① mdss_dsi_phy_init()
如果有兩個MIPI DSI接口MIPI_DSI0和MIPI_DSI1就調用兩次mdss_dsi_phy_init(),msm8909只有MIPI_DSI0,MSM8994等有兩個DSI接口。
② mdss_dsi_host_init()
初始化DSI接口的host控制器。
③ mdss_dsi_panel_pre_init()
根據lp11的值判定是否走reset
case MIPI_VIDEO_PANEL:
dprintf(INFO, "Config MIPI_VIDEO_PANEL.\n");
mdp_rev = mdp_get_revision();
if (mdp_rev == MDP_REV_50 || mdp_rev == MDP_REV_304 ||
mdp_rev == MDP_REV_305)
ret = mdss_dsi_config(panel);
else
ret = mipi_config(panel);
if (ret)
goto msm_display_config_out;
if (pinfo->early_config)
ret = pinfo->early_config((void *)pinfo);
ret = mdp_dsi_video_config(pinfo, &(panel->fb));
if (ret)
goto msm_display_config_out;
break;
-
msm_display_on()
int msm_display_on()
{
... ...
case MIPI_VIDEO_PANEL:
dprintf(INFO, "Turn on MIPI_VIDEO_PANEL.\n");
ret = mdp_dsi_video_on(pinfo);
if (ret)
goto msm_display_on_out;
ret = mdss_dsi_post_on(panel);
if (ret)
goto msm_display_on_out;
ret = mipi_dsi_on(pinfo);
if (ret)
goto msm_display_on_out;
break;
... ...
}
① 調用mdp_dsi_video_on()使能DSI VIDEO
② mdss_dsi_post_on()使用初始化命令來初始化panel,對應qcom,mdss-dsi-on-command部分。
int mdss_dsi_post_on(struct msm_fb_panel_data *panel)
{
int ret = 0;
struct msm_panel_info *pinfo = &(panel->panel_info);
if (pinfo->mipi.cmds_post_tg) {
ret = mdss_dsi_panel_initialize(&pinfo->mipi, pinfo->mipi.broadcast);
if (ret) {
dprintf(CRITICAL, "dsi panel init error\n");
}
}
return ret;
}
③ mipi_dsi_on()
int mipi_dsi_on(struct msm_panel_info *pinfo)
{
int ret = NO_ERROR;
unsigned long ReadValue;
unsigned long count = 0;
ReadValue = readl(pinfo->mipi.ctl_base + INT_CTRL) & 0x00010000;
mdelay(10);
while (ReadValue != 0x00010000) {
ReadValue = readl(pinfo->mipi.ctl_base + INT_CTRL) & 0x00010000;
count++;
if (count > 0xffff) {
dprintf(CRITICAL, "Video lane test failed\n");
return ERROR;
}
}
dprintf(INFO, "Video lane tested successfully\n");
return ret;
}
-
mdss_dsi_bl_enable
對應調用mdss_dsi_bl_enable()--->panel_backlight_ctrl(enable)--->target_backlight_ctrl(panelstruct.backlightinfo,enable)
可知根據panelstruct.backlightinfo來進行背光控制,而panelstruct.backlightinfo在oem_panel_select()--->init_panel_data()被賦值
panelstruct->backlightinfo =&xxx_video_backlight;
static int mdss_dsi_bl_enable(uint8_t enable)
{
int ret = NO_ERROR;
ret = panel_backlight_ctrl(enable);
if (ret)
dprintf(CRITICAL, "Backlight %s failed\n", enable ? "enable" :
"disable");
return ret;
}
至此,屏幕已經顯示第一幀畫面,且背光也是正常亮起。lk的顯示已經完成。
其實LK還有一步很重要的工作,就是把我們識別到的屏幕信息傳遞給kernel,它是如何傳遞的呢?下一篇小文檔將會單獨講解該信息的傳遞。