本文档在《全志T7 Display驱动简介.docx》基础上进行补充说明,主要的目的是想了解系统总共有多少个图像层可以给应用程序使用,好让以后应用程序如何同叠加图像层来高效显示画面。
DST配置
下面说明比较旧,仅供参考:
Display驱动入口
驱动的入口文件是dev_disp.c,入口函数disp_module_init():
static int __init disp_module_init(void)
{
int ret = 0, err;
//1、注册一个/dev/disp的字符设备
alloc_chrdev_region(&devid, 0, 1, "disp");//动态分配设备编号
my_cdev = cdev_alloc();//分配字符设备
cdev_init(my_cdev, &disp_fops);//字符设备的初始化
my_cdev->owner = THIS_MODULE;
err = cdev_add(my_cdev, devid, 1);
//2、注册一个/sys/disp的类
disp_class = class_create(THIS_MODULE, "disp");//将在sys目录下创建disp目录
display_dev = device_create(disp_class, NULL, devid, NULL, "disp");
//3、注册Disp驱动
ret = platform_driver_register(&disp_driver);//注册Display驱动
dispdbg_init();//创建一些用于调试的目录文件
return ret;
}
这里我们截取一些重要的信息:
DISP_DEVICE_NUM等于3,说明系统有3个LCD控制器。在disp2\disp\de\lowlevel_v2x\de_feat.h中可以修改一些宏进行配置:
上面代码创建了一个Kobj目录,dev_disp.c文件中有三分之一的代码是为了实现disp_attribute_group。
Display初始化
接下来重点分析disp_init():
static s32 disp_init(struct platform_device *pdev)
{
(1)初始化disp_drv_info g_disp_drv,调用parser_disp_init_para()解析DST信息保存到g_disp_drv.disp_init
(2)调用bsp_disp_init()对Display进行初始化
(3)调用lcd_init()进行LCD驱动初始化
(4)调用fb_init()完成Framebuffer驱动初始化
(5)调用composer_init(),初始化渲染器
(6)start_process(),开始输出显示画面
}
bsp_disp_init()–>bsp_disp_set_print_level() 设置是否需要打印display驱动信息
(1)调用调用parser_disp_init_para()解析DST信息保存到g_disp_drv.disp_init。g_disp_drv.disp_init是结构体disp_init_para变量,结构体解释参考《全志T7 Display驱动相关结构体》。DST配置说明本项目使用LCD控制器输出显示画面,分辨率720P50,Framebuffer则使用双缓冲区方式来处理。
此外还会初始化g_disp_drv.para及g_disp_drv.para.boot_info,g_disp_drv.para.boot_info是disp_bootloader_info结构体变量,看名字觉得是和uboot阶段初始化Display的信息,在DST有设备信息“boot_disp”:
解析这个设备信息,然后判断是否需要让kernel初始化Display的参数和uboot的保持一致(本项目保持一致),从而觉得是否重新初始化g_disp_drv.disp_init。
(2)调用bsp_disp_init()对显示控制器初始化,参数是g_disp_drv.para
- 初始化全局变量gdisp中的init_para参数;
- 初始化显示控制器DE的默认参数;
- 挂接中断处理中tasklet处理函数;
- 设置显示打印等级;
- 初始化显示控制器DE的抽象图层驱动;
- 初始化显示控制器DE的lcd设备抽象驱动;
- 初始化显示控制器DE的管理层驱动;
- 初始化显示控制器DE的enhance驱动;
- 初始化显示控制器DE的背光控制驱动;
- 初始化显示控制器DE的capture驱动;
- 将管理层驱动与抽象图层、lcd设备驱动、enhance驱动、背光控制驱动、capture驱动关联起来。
第20行和第21行,初始化gdisp.init_para,有:
第23行,调用disp_init_feat()获得DE模块的参数信息:
用文字描述一下DE模块:DE模块可支持DE_NUM个(2个)屏输出控制器(RT-Mixer),支持DEVICE_NUM种(3种)输出设备(RGB、LVDS、DSI)。DE模块的两个RT-Mixer :DISP0支持4个通道、DISP1支持2个通道,即DE总共支持6个通道,每个通道支持4个layer(也就是图层)、都支持Scaler。下面的图是这段话的理解
其中两个RT-Mixer在代码里面使用DISP0和DISP1表示,TCON0用LCD0表示,TCON1用LCD1表示,外挂CVBS即TV out,根据sun8iw17_de_features.is_support_wb,只有RT-Mixer 0支持WB功能。
上面两个图不一定是T7的,只能参考。它表示RT-Mixer0有4个输入通道,其中DMA0是Video通道,可输入YUV格式,然后经过OVL_V(overlay)和Scaler,最后通过Proc1模块转为RGB格式。
RT-Mixer2只有两个输入通道。
下面的函数比较复杂,我们分几个小节来说明。分析这些函数前,有必要给出“显示控制器驱动架构”:
Manager抽象层:disp_manager.c
显示设备驱动:Disp_lcd.c disp_init_hdmi.c disp_init_tv_para.c disp_init_edp.c
smbl背光驱动:Disp_smart_backlight.c
enhance驱动:disp_enhance.c
Capture驱动:disp_capture.c
显示图层驱动:disp_layer.c(我提取出来的)
1)disp_init_al()
初始化显示控制器DE的抽象图层驱动
2)disp_init_lcd()
初始化显示控制器DE的lcd设备抽象驱动
这个函数大部分代码都在初始化全局变量lcds,它是disp_device结构体变量指针,disp_device结构体描述了一个lcd设备信息,包括lcd时序、一些可被调用的函数。DE驱动的其他文档可以通过disp_get_lcd()函数获得lcd设备信息,然后调用它里面的函数。
调用disp_lcd_init()从DST中获得LCD时序,保存在lcds->timings,并初始化背光PWM。
3)disp_init_mgr()
初始化显示控制器DE的图层管理器驱动。
struct disp_manager是管理抽象层的数据结构体,一个RT-Mixer由一个disp_manager管理,一个disp_manager“对象”包含一个LCD设备驱动、smbl驱动、enhance驱动、capture驱动结构体指针和一个layer列表,以及这个管理层驱动提供给外部使用的操作接口。而DE驱动的设备结构体disp_drv_info中包含有struct disp_manager类型的成员:
disp_init_mgr()函数大部分代码都在初始化struct disp_manager变量mgrs,DE其他文档可以通过disp_get_layer_manager()函数获得mgrs。
disp_init_mgr()函数最后调用了disp_init_lyr(),初始化显示图层,即为2个Disp设备的每个通道(4个+2个)的图层(4个)分配一个disp_layer,然后初始化它,所以最多可以分配24个图层。代码中图层以disp,channel,layer_id三个索引唯一确定(disp:0/1,chanel:0/1/2/3,layer_id:0/1/2/3),结构体disp_layer代表一个layer,也需要三个索引:
注:原来T7 的layer代码是放在disp_manager.c,为了配合“显示控制器驱动架构”和代码清晰,我把layer相关代码移到disp_layer.c,修改disp_layer.c时后要修改一下disp_manager.c,否则没有重新编译disp_layer.c。
4)disp_init_enhance()
初始化显示控制器DE的enhance驱动
5)disp_init_smbl()
初始化显示控制器DE的背光控制驱动
6)disp_init_capture()
初始化显示控制器DE的capture驱动。Capture功能表面意思就是截图,对应DE里面的Write-Back模块,有了硬件的支持,类似Android的Miracast就很容易实现。
7)disp_init_connections()
将管理层驱动与抽象图层、lcd设备驱动、enhance驱动、背光控制驱动、capture驱动关联起来。这个函数所有代码都类似下面代码段:
执行完成后得到:
对LCD设备有类似结果:
因一个disp_manager管理一个RT-Mixer,执行完该函数后, disp_manager将包含有LCD设备、smbl、enhance、capture以及这个 RT-Mixer所支持的layers,即下面结构体的成员变量被初始化了:
到此bsp_disp_init()分析完成。
LCD初始化
disp_init ()–>lcd_init()对LCD模块进行初始化:
sunxi_disp_get_source_ops()函数为g_lcd_drv.src_ops设置一些函数指针,如下图。其中有关于背光使能控制和LCD PWM相关的函数,我们留意一下,后面分析背光调节时来看看这些函数。
lcd_set_panel_funs()函数目的是将系统预先调试好的液晶屏的一些函数添加到LCD,这类函数有5个:
cfg_panel_info获取一些额外的配置参数,例如Gamma参数;
cfg_open_flow和cfg_close_flow一般与上电或掉电的时序有关;
lcd_user_defined_func其他操作
set_bright设置LCD亮度值,不是PWM背光亮度,DSI接口的屏需要设置,RGB的不需要。
系统默认已经添加了多个液晶屏:
在DST中要求使用了“default_lcd”,刚好对应上面数组的default_panel。
根据项目的使用情况,我们不需要兼容那么多屏,所以可以把其他注释掉。
注:对于方案开发商(类似全志)来说,根据不同客户的需求,会调试多种规格的液晶屏,这些液晶屏除了时序参数需要配置,还有一些特殊的配置或操作,比如有些屏虽然点亮了,但是色彩方面需要调试外部Gamma参数使得屏幕色彩符合客户需求;又比如有些液晶屏需要上电时序的要求。这些信息不能都在DST配置,否则DST就变得太复杂了,所以它们被添加到代码里面。这里的代码设计应该只是考虑到兼容多一些屏,所以lcd_set_panel_funs()函数采用遍历的方式把所有屏的func都调用了,对本项目来说是反而变累赘了。
Framebuffer初始化
disp_init ()–>fb_init ()对Framebuffer初始化。
s32 fb_init(struct platform_device *pdev)
{
(1)、创建vsync_thread
(2)、初始化8个fb_info
(3)、调用display_fb_request(),修改fb_info并更新对应layer的配置
(4)、调用register_framebuffer(),向系统注册8个fb设备
}
在display_fb_request()中看到熟悉的影子:screen_win、crop、size、alpha,参考《Sunxi_display2模块使用文档.pdf》:
display_fb_request()–>fb_map_video_memory()为Framebuffer分配内存空间,info->screen_base虚拟地址的,对应物理地址是info->fix.smem_start,大小info->fix.smem_len
display_fb_request()–>b_map_kernel_logo(),这个函数读取开机静态logo的bmp图片,然后把图片数据拷贝到Framebuffer,即显示logo画面。
display_fb_request()函数最后会调用disp_manager.c的disp_mgr_set_layer_config(),更新对应layer的配置,因下面的配置:
所以默认情况下,使用disp0 channel1 layer0显示静态logo。
/dev/disp的字符设备
字符串设备的操作方法是disp_fops:
实际上只有disp_ioctl()和disp_mmap()两个函数有具体实现。
ioctl可参考:《Sunxi_display2模块使用文档.pdf》
sysfs 接口
static int __init disp_module_init(void)
{
//1、注册一个/dev/disp的字符设备
//2、注册一个/sys/disp的类
disp_class = class_create(THIS_MODULE, "disp");//将在sys目录下创建disp目录
display_dev = device_create(disp_class, NULL, devid, NULL, "disp");
//3、注册Disp驱动
}
sysfs接口的使用例子可参考:《Sunxi_display2模块使用文档.pdf》