MTK LCM
1.Linux設備模型
站在BSP的角度來看,整個系統可以由三部分組成:設備、總線、驅動。
Linux kernel有一些總線,比如USB、I2C等。對於每一個總線都會有一些設備和驅動掛在上面。驅動服務於匹配的設備,使Linux正確的操作硬件設備。當一個設備或者驅動註冊到特定的總線上的時候就會觸發總線匹配函數,比如一個設備註冊到了總線,所有的該總線的驅動都會被枚舉,判斷是不是可以服務於新添加的設備(一般通過name來匹配),反之亦然。
如果總線匹配成功,就會調用驅動的probe函數,檢查指定的硬件確實存在,然後確定是否所需的資源都能夠從系統申請。
事實上,設備或者驅動能夠正確的合作,在probe之後,模塊初始化順序決定於probe的執行順序,可以由BSP函數中註冊設備的順序控制。MT6572平臺,L版本的BSP文件放在kernel/arch/arm/mach-mt6572/mt_devs.c,mt_board_init()函數控制着probe的順序。
2.LCM設備模型
對於方案公司的驅動開發人員來說,對於LCM的工作主要是在Mediatek的代碼架構下進行兼容和優化。和其他的所有的模塊一樣,Mediatek的軟件架構儘可能的把所有的無需客製化的代碼劃分出來,從而減少對下游開發人員的工作量。Mediatek封裝了一個結構體給開發人員,包含了所有可能需要剋制化的函數指針,對於不同的IC,只需要對應實現相應的函數就可以了。
LCM_DRIVER st7796s_hvga_dsi_ivo_txd_lcm_drv =
{
.name = "st7796s_hvga_dsi_ivo_txd",
.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,
//.esd_check = lcm_esd_check,
//.esd_recover = lcm_esd_recover,
#if (LCM_DSI_CMD_MODE)
.update = lcm_update,
#endif
};
LCM_DRIVER 結構體的各個成員的介紹如下:
- 在LCM_UTIL_FUNCS這個類型中,主要定義了一些接口函數,這些接口函數是mtk提供給lcm驅動開發者使用的;
- lcm_compare_id函數中主要是通過讀寄存器獲取硬件的id號判斷和此驅動支持的硬件lcm是否一致,如果一致就選擇這個驅動。實現了驅動和設備正確匹配。在後面分析lcm的設備模型中會講到。
- lcm_get_params函數中主要是一些參數定義,例如屏的分辨率,屏的接口類型等;
- 下面三個函數主要和上電時序有關
// for power-onsequence refinement
void (*init_power)(void);
void (*suspend_power)(void);
void(*resume_power)(void);
下面幾個函數和ESD有關
/////////////ESD_RECOVERY//////////////////////
unsigned int(*esd_check)(void);
unsigned int (*esd_recover)(void);
unsigned int(*check_status)(void);
unsigned int(*ata_check)(unsigned char *buffer);
言歸正傳,LCM的設備模型也是遵守設備總線驅動結構的,只不過在這個基礎上MTK又做了一些工作,封裝出LCM_DRIVER 結構體。
總線:platform虛擬總線,關在該總線的設備和驅動通過name來匹配。
2.1 設備
文件在/drivers/misc/mediatek/mach/mt6735/mt_devs.c
中定義了LCM設備和資源,代碼如下:
static struct platform_device mt6575_device_fb = {
.name = "mtkfb",
.id = 0,
.num_resources = ARRAY_SIZE(resource_fb),
.resource = resource_fb,
.dev = {
.dma_mask = &mtkfb_dmamask,
.coherent_dma_mask = 0xffffffff,
},
};
retval = platform_device_register(&mt6575_device_fb);
printk("[%s]: mt6575_device_fb, retval=%d \n!", __func__, retval);
if (retval != 0) {
return retval;
}
2.2 驅動
驅動文件出了MTK抽象出來的具體設備驅動文件外,這裏指的是掛在platform上的驅動文件,/drivers/misc/mediatek/videox/mt6735/mtkfb.c
static struct platform_driver mtkfb_driver =
{
.driver = {
.name = MTKFB_DRIVER,
#ifdef CONFIG_PM
.pm = &mtkfb_pm_ops,
#endif
.bus = &platform_bus_type,
.probe = mtkfb_probe,
.remove = mtkfb_remove,
.suspend = mtkfb_suspend,
.resume = mtkfb_resume,
.shutdown = mtkfb_shutdown,
.of_match_table = mtkfb_of_ids,
},
};
// 註冊
if (platform_driver_register(&mtkfb_driver))
{
PRNERR("failed to register mtkfb driver\n");
r = -ENODEV;
goto exit;
}
驅動的名字和設備匹配後,調用驅動的probe進行探測設備,並完成資源申請,sysfs文件系統操作等。最終在/dev/下生成設備文件節點。系統通過uevent通知udev,udev會收集在sysfs/class下面的文件信息,自動創建文件節點。mtk_fb_probe函數主要做的工作如下:
Called by LDM binding to probe andattach a new device.
Initialization sequence:
1.allocate system fb_info structure select panel type according to machine type
2.init LCD panel
3.init LCD controller and LCD DMA
4.init system fb_info structure
5.init gfx DMA
6.enable LCD panel
start LCD frame transfer
7.register system fb_info structure
比較關鍵的函數是mtkfb_find_lcm_driver,這個函數調用具體設備驅動裏面的函數和數據(例如:St7796s_hvga_dsi_ivo_txd.c (drivers\misc\mediatek\lcm\st7796s_hvga_dsi_ivo_txd))。
下面對其進行分析:
BOOLmtkfb_find_lcm_driver(void)
{
p = strstr(saved_command_line, "lcm=");
if(p== NULL)
{
//we can't find lcm string in the command line, the uboot should be old version
returnDISP_SelectDevice(NULL);
}
if(DISP_SelectDevice(mtkfb_lcm_name))
ret= TRUE;
done:
returnret;
}
p =strstr(saved_command_line, “lcm=”);獲取從lk(uboot)傳入的cmdline中的"lcm="數據,爲LCM設備的名稱。衆所周知,uboot把控制權交給kernel之後會傳入一些參數,其中cmdline爲uboot傳入kernel參數命令行。可以通過如下指令獲得:cat /proc/cmdline .
proc/cmdline :
console=tty0 console=ttyMT0,921600n1 root=/dev/ram vmalloc=496Mslub_max_order=0 slub_debug=O lcm=1-st7796s_hvga_dsi_ivo_txd fps=5480 vram=4194304bootprof.pl_t=1830 bootprof.lk_t=1628 printk.disable_uart=0ddebug_query=“file mediatek +p ; file gpu =_” boot_reason=0androidboot.serialno=0123456789ABCDEF androidboot.bootreason=power_key
獲得LCM名稱以後就會調用if(DISP_SelectDevice(mtkfb_lcm_name)) 函數根據mtkfb_lcm_name 從LCM_DRIVER鏈表中獲取對應的結構體指針,然後就傳入了上面介紹的MTK客製化的一些LCM函數。
問題來了,uboot(LK)是怎麼知道LCM的名字的呢?在LK中也會有和kernel具體LCM驅動相同的設備驅動文件如:
St7796s_hvga_dsi_ivo_txd.c(dev\lcm\st7796s_hvga_dsi_ivo_txd)。
LK也會把一系列LCM_DRIVER放入鏈表中,然後通過調用LCM_DRIVER裏面的lcm_compare_id函數進行ID匹配,如果讀到硬件的id和此驅動支持的id匹配,則選擇此驅動。下面是對LK代碼中LCM驅動的選擇流程分析。
首先是LK總體代碼執行的流程圖:
在Kmain()函數中會有以下的函數調用流程:
void platform_early_init(void)
{
//preloader won't reach max speed. It will done by LK.
if (g_boot_arg->boot_mode != DOWNLOAD_BOOT)
{
mtk_set_arm_clock();//設置時鐘
}
/* initialize the uart */
uart_init_early();//串口初始化
platform_init_interrupts();//中斷
platform_early_init_timer();//時鐘
mt_gpio_set_default();//gpio
mt_i2c_init();//i2c
clk_init();//clk
mtk_wdt_init();//看門狗
isink0_init(); //turnon PMIC6329 isink0
mt_disp_init((void *)g_fb_base);//LCM
#ifdef CONFIG_CFB_CONSOLE
drv_video_init();
#endif
#if defined(TARGET_S4)
pmic_init();//pmu
#endif
}
mt_disp_init((void *)g_fb_base);函數會調用
DISP_CHECK_RET(DISP_Init((UINT32)lcdbase, (UINT32)lcdbase, FALSE));
DISP_Init函數在Disp_drv.c(platform\mt6572) 中定義,然後繼續調用disp_drv_init_context()函數:
if(!isLCMFound) DISP_DetectDevice();
lcm_drv = disp_drv_get_lcm_driver(NULL);
判斷LCM_DRIVER鏈表中lcm驅動的數量,如果爲1個就直接拿來用給設備,不需要調用compare_id函數,如果多個就需要匹配硬件id號了。
if(lcm_count ==1)
{
// we need to verify whether the lcm is connected
// even there is only one lcm type defined
lcm = lcm_driver_list[0];
lcm->set_util_funcs(&lcm_utils);
lcm->get_params(&s_lcm_params);
u4IndexOfLCMList = 0;
lcm_params = &s_lcm_params;
lcm_drv = lcm;
isLCMFound= TRUE;
}
for(i = 0;i < lcm_count;i++)
{
……
if(lcm->compare_id != NULL && lcm->compare_id())
{
printk("\t\t[success]\n");
isLCMFound = TRUE;
lcm_drv = lcm;
u4IndexOfLCMList = i;
goto done;
}
}
驅動找到會把LCD_DRIVER結構體指針賦值到全局變量lcm_drv中。通過調用文件Mt_boot.c (app\mt_boot)中的intboot_linux_from_storage(void)函數
strlen += sprintf(commanline, "%s lcm=%1d-%s",
commanline,DISP_IsLcmFound(), mt_disp_get_lcm_id());
strlen += sprintf(commanline, "%s fps=%1d", commanline,mt_disp_get_lcd_time());
strlen += sprintf(commanline, "%s vram=%1d", commanline,DISP_GetVRamSize());
將lcm相關的信息,寫到cmdline中,傳遞給kernel.