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 结构体的各个成员的介绍如下:
l 在LCM_UTIL_FUNCS这个类型中,主要定义了一些接口函数,这些接口函数是mtk提供给lcm驱动开发者使用的;
l lcm_compare_id函数中主要是通过读寄存器获取硬件的id号判断和此驱动支持的硬件lcm是否一致,如果一致就选择这个驱动。实现了驱动和设备正确匹配。在后面分析lcm的设备模型中会讲到。
l lcm_get_params函数中主要是一些参数定义,例如屏的分辨率,屏的接口类型等;
l 下面三个函数主要和上电时序有关
// for power-onsequence refinement void (*init_power)(void); void (*suspend_power)(void); void(*resume_power)(void);
l 下面几个函数和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来匹配。
设备
在文件Mt_devs.c (arch\arm\mach-mt6572)中定义了LCM设备和资源,代码如下:
驱动
驱动文件出了MTK抽象出来的具体设备驱动文件外,这里指的是挂在platform上的驱动文件。Mtkfb.c (drivers\misc\mediatek\video\mt6572) 。
驱动的名字和设备匹配后,调用驱动的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))。
下面对其进行分析:
BOOL mtkfb_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()函数中会有以下的函数调用流程:
在函数platform_early_init()函数里进行一系列的硬件初始化,看代码(精简):
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。