以下几幅图是最近的一段时间对自己模块LCM的一些总结,目前只是完成了一部分,而且描述的不是特别到位,后期会不断更新和修改的。
第一幅是LCM的总体移植框架和组件图。介绍了LCM驱动的分部和调试屏的步骤和要点。
分lk和kernel两个部分,具体哪些.c,如何添加一块新屏,大体的步骤和方法。
第二幅图是屏的初始化流程图,分为lk和kernel两部分。Lk部分如下所示:
系统起来的时候会调用 bootable/bootloader/lk/app/aboot/aboot.c, 在这个函数里面的 aboot_init()函数里调用显示屏初始化函数target_display_init(),在这个函数里面调用gcdb_display_init()。
在这个函数中有个相当重要的函数就是oem_panel_select(),定义在oem_panel.c中,它根据高通私有代码传进来的hw_id 来决定你到底使用哪一款LCD,并把panel相关数据信息填充到数据结构中--init_panel_data(),gcdb_display_init()中执行完 oem_panel_select()确定使用的panel和panel相关信息后,通过函数指针赋值了一系列与此panel相关的功能函数,包括pll,背光,clk,供电相关的初始化和使能函数,以便在下面进行调用。
同时, 开始执行 一系列屏初始化流程。第一步,上电,执行pdata->power_func,打开一些ldo,使能一些系统电源,拉高一些gpio, 使得屏得到供电,并且在里面对屏做reset,调用的函数是target_panel_reset(),在里面对reset引脚做高低高操作。然后使能时钟,调用pdata->clk_func,接着做一些使能pll,分配framebuffer,通过fbcon_setup(&(panel->fb))函数设置记录显示的分辨率和显示地址等全局指针。接下来进入msm_display_config()函数针对LVDS_PANEL,MIPI_VIDEO_PANEL,MIPI_CMD_PANEL等类型进行dsi和mipi配置。
经过一些列dsi参数置最后调用 mdss_dsi_panel_initialize(),使用mdss_dsi_cmds_tx()向lcm的driver ic发送了初始化代码。并判断,如果 target_panel_auto_detect_enabled()打开了话,就使用高通平台的lcm read_id函数mdss_dsi_read_panel_signature()进行id读取。
完成这一系列操作以后执行msm_display_on(),点亮屏,最后一个阶段调用pdata->bl_func(1)打开背光。这样整个LK的lcm初始化就完成了。LK的初始化流程图如下所示。
接下来是kernel阶段LCM的初始化过程。
kernel阶段,首先会在驱动中注册三个平台设备,msm_fb,mdp,mipi_dsi。mdp模块是高通内部的显示控制芯片,mipi_dsi是传输协议层设备,msm_fb是高通显卡的驱动设备。
首先执行的是mdp驱动的probe,进行硬件资源包括时钟,中断,iommu,总线,DMA的初始化,注册一些接口以供fb设备使用。mdp初始化以后初始化dsi控制器,在mdss_dsi_ctrl_probe函数中,调用了一个函数mdss_ds_config_panel,这个函数中,通过名字匹配去获取panel设备节点,如果没有匹配成功就去调用msm-mtp.dtsi设定的预制panel device node。
接下来执行函数dsi_panel_device_register()注册panel_device设备节点。在这个注册函数中,确定了是DSI_VIDEO_MODE还是MIPI_CMD_MODE,获取了一些资源如gpio,regulatot,dsi-lane相关的信息,重要的是它注册了一个event_handler:mdss_dsi_event_handler,这个事件处理函数会接受其他层传进来的各种event事件包括点亮,熄灭屏等等。也是通过这个event_handler,dsi驱动层与fb层,mdp层进行通信。
然后执行mdss_dsi_panel_init函数进行panel的初始化。这个函数通过解析dtsi文件获取了屏的名字,分辨率,物理尺寸,dsi类型,porch参数,色彩格式,初始化指令,背光模式等一些列参数,并且把它们一一填充到pinfo这个结构体中,并且注册了一些on off ,设置背光,选择模式等操作函数。接下来是mdss_fb设备的初始化,在mdss_fb_probe函数中,通过调用mfd->mdp.init_fnc去初始化mdp,通过lcd_backlight_registered()去注册背光驱动。
在系统开机动画起来的时候,会调用函数mdss_fb_open打开fb控制器,在里面会执行mdss_fb_blank_sub()并传入FB_BLANK_UNBLANK event参数,执行mdss_fb_blank_unblank()函数。这个函数中,通过调用mfd->mdp.on_fnc()执行了mdp驱动中的on_fnc函数,就是mdp3_ctrl_on()函数,该函数向之前dsi驱动中的event handler发送三个事件MDSS_EVENT_LINK_READY,MDSS_EVENT_UNBLANK,MDSS_EVENT_PANEL_ON调用mdss_dsi_panel驱动中mdss_dsi_on()。
在里面执行了配置dsi,上电,reset,发送指令一些列操作从而将屏点亮。这样kernel部分的屏初始化就完成了。
下图就是KERNEL阶段的初始化流程图。
第四幅图,是点亮屏熄灭屏从上层到底层的数据传输图。
如果要灭屏,上层会调用PowerManagerService提供的方法。整个过程的调用关系是goToSleep->goToSleepInternal-> updatePowerStateLocked->updateDisplayPowerStateLocked->
requestPowerState->sendUpdatePowerStateLocked->mHandler.sendMessage(msg)-> updatePowerState->animateScreenStateChange->setScreenState->scheduleScreenUpdate->
postScreenUpdateThreadSafe->mPhotonicModulator.setState->mLock.notifyAll-> mBlanker.requestDisplayState->initPowerManagement->callbacks.onDisplayStateChange->
setHalAutoSuspendModeLocked->nativeSetAutoSuspend
最后到nativeSetAutoSuspend()这个本地方法,是在
frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp
调用的,调用里面的autosuspend_enable(),这个函数会往/sys/power/state节点中写入
mem。
这个节点,是在kernel/kernel/power/main.c当中创建的sysfs文件节点,一旦往里面写入值就会调用相应的store函数。灭屏,上层写入mem,调用了store当中的suspend函数,于是系统就会走suspend流程。传递到屏这边,在msm_fb系统中调用suspend函数,
经过一些列调用,走到mdss_fb_blank_blank(),调用mfd->mdp.off_fnc,调用mdp_ctrl.c
的mdp_ctrl_off函数,于是panel->set_backlight(panel, 0)关闭背光,通过发送
MDSS_EVENT_BLANK 给dsi驱动中的event_handler,调用mdss_dsi_blank()执行一系列下电灭屏的操作。这样整个灭屏流程就执行完了。
亮屏的话,同样是从PowerManagerService提供方法wake up,最后就调用jni的autosuspend_disable(),往/sys/power/state节点中写入on,最后调用suspend流程点亮屏。
流程图如下所示。
第五幅图,总结了背光调节从上层到底层是如何传输的。
当用户在手机的setting-display-backlights调节背光时,系统将会调用
packages\apps\Settings\src\com\android\settings\BrightnessPreference.java去实现。在这里面设置亮度调节范围0-255,通过powermanagerservice.java的接口setBrightness实现服务。这个方法定义在LightService.java中,调用setBrightness()--->setLightLocked()--->setLight_native()。
这个本地方法在frameworks\base\services\jni\com_android_server_LightsService.cpp下实现。在这个文件下,init_native函数中,调用hw_get_module(LIGHTS_HARDWARE_MODULE_ID, (hw_module_t const**)&module)获取 lights硬件抽象层模块lights.default.so并加载,调用hall层函数devices->lights[light]->set_light(devices->lights[light],&state)设置亮度。hall层的代码是hardware/qcom/display/liblight/lights.c。
在这里面实现了open_lights,close_lights,rgb_to_brightness等函数。最关键是,调节亮度时候,会调用其中的set_light_backlight(),通过write_int往/sys/class/leds/lcd-backlight/brightness写值。这个文件节点在kernel层创建。kernel层,在Mdss_dsi_panel.c文件里,有一个背光控制函数static void mdss_dsi_panel_bl_ctrl(struct mdss_panel_data *pdata,u32 bl_level), 参数背光值就是通过这个bl_level进行调节的。
经过一层层调用,实际上在kernel/drivers/leds/Led-class.c中创建的sysfs文件节点/sys/class/leds/lcd-backlight/brightness,并且在store函数中,把当前亮度值存到led_cdev->brightness变量中, 一层层传到驱动的bl_level。这样,就形成了上层到底层的背光调节值的传递。
以上几幅图,总体上概括了LCM驱动部分以及一些上层的关系,流程。这样的整理对自己的模块理解和熟悉是相当有帮助的。随着工作的深入,我会不断补充和完善这个文档,希望能够在实际工作中取得良好的效果。