展讯平台android display驱动分析
本文以展讯tshak平台的display controller为硬件模型,来说明Android底层显示驱动的实现原理。该硬件模型较基础和简单,目前大多SOC平台的硬件设计都变得越来越复杂,实现功能也越来越全面,但基于此平台的设计更方便理解。
1 display控制器
下图的结构即是显示控制器的模块组成,它内部集成了lcd显示相关的控制器:lcdc和dispc,其中lcdc仅支持MCU接口类型(DBI),而dispc支持DBI和DPI接口类型。
图片最上面的实现是lcdc,直接通过DBI进行了输出。
图片中间较复杂的内容是dispc,图像数据流流向
ddr sdram ——> dispc ——> panel,dispc通过axi总线连接ddr controller并获取ddr sdram中的图像数据,经由dispc处理后通过dbi/dpi等interface将图像输出给屏幕。
图片最下面是寄存器配置相关,一般寄存器配置在SOC内部通过ahb总线。
blending
展讯平台dispc支持两层图像显示: image 层/OSD 层,image 层支持的图像格式: YUV422/YUV420/YUV400/RGB888/RGB565/RGB666/RGB555/PACK data,OSD 层支持的图像格式: RGB888/RGB565/RGB666/RGB555。下图是dispc内部blending的功能示意。
video和camera的图像一般是YUV格式的,UI的图像是RGB的。
dithering
支持RGB888->RGB 666, RGB888->RGB 565。
输出接口
- DBI接口, 就是通常所讲的MCU模式。LCD panel内部一般需要一块sram,刷新操作由lcd panel完成。控制简单方便,无需时钟和同步信号。
DPI接口,就是RGB模式。LCD panel内部一般不需要sram,刷新操作由外部输入的vsync完成。数据线和控制线分离显示数据直接写屏,速度快。
dpi时序图如下面两幅图所示。mipi mode
手机平台目前最常用的方案,内容较多,在此不详述,可参考专业文档。
…
为方便理解输出接口,下面附一张屏幕显示驱动芯片ili9486的结构图,它与dispc的输出接口相连。它支持MCU,SPI,RGB,MIPI各种接口。
2 display驱动
首先show一下display驱动的结构图,由三个层次组成,分别为fb, dispc和panel。
static int sprdfb_probe(struct platform_device *pdev)
{
struct fb_info *fb = NULL;
struct sprdfb_device *dev = NULL;
int ret = 0;
//fb中结构体fb_info的申请和分配
fb = framebuffer_alloc(sizeof(struct sprdfb_device), &pdev->dev);
dev = fb->par;
dev->fb = fb;
#ifdef CONFIG_OF
dev->of_dev = &(pdev->dev);
dev->dev_id = of_alias_get_id(pdev->dev.of_node, "lcd");
#else
dev->dev_id = pdev->id;
#endif
if((SPRDFB_MAINLCD_ID != dev->dev_id) &&(SPRDFB_SUBLCD_ID != dev->dev_id)){
printk(KERN_ERR "sprdfb: sprdfb_probe fail. (unsupported device id)\n");
goto err0;
}
switch(SPRDFB_IN_DATA_TYPE){
case SPRD_IN_DATA_TYPE_ABGR888:
dev->bpp = 32;
break;
case SPRD_IN_DATA_TYPE_BGR565:
dev->bpp = 16;
break;
default:
dev->bpp = 32;
break;
}
if(SPRDFB_MAINLCD_ID == dev->dev_id){
dev->ctrl = &sprdfb_dispc_ctrl; //sprd dispc控制器
#ifdef CONFIG_OF
if(0 != of_address_to_resource(pdev->dev.of_node, 0, &r)){
printk(KERN_ERR "sprdfb: sprdfb_probe fail. (can't get register base address)\n");
goto err0;
}
//寄存器
g_dispc_base_addr = (unsigned long)ioremap_nocache(r.start,
resource_size(&r));
if(!g_dispc_base_addr)
BUG();
printk("sprdfb: set g_dispc_base_addr = %ld\n", g_dispc_base_addr);
#endif
}
dev->frame_count = 0;
dev->logo_buffer_addr_v = 0;
dev->capability = sprdfb_config_capability();
if(sprdfb_panel_get(dev)){
#if defined(CONFIG_FB_LCD_OLED_BACKLIGHT)
if (dev->panel->ops->panel_dimming_init)
dev->panel->ops->panel_dimming_init(dev->panel, &pdev->dev);
#endif
dev->panel_ready = true;
//一般显示相关驱动在uboot阶段已进行过初始化,所以此处可以直接使用
dev->ctrl->logo_proc(dev);
}else{
dev->panel_ready = false;
}
dev->ctrl->early_init(dev); //sprd dispc控制器的early_init,其中有vsync中断的申请
if(!dev->panel_ready){
if (!sprdfb_panel_probe(dev)) { //panel注册
ret = -EIO;
goto cleanup;
}
}
ret = setup_fb_mem(dev, pdev); //申请fb的memory,一般在dts中有reservemem
if (ret) {
goto cleanup;
}
//配置fb_info成员参数,除了变量信息外,还会设置ops信息,其中最常用的就是mmap函数,将
//framebuffer的物理内存映射给usersapce使用
setup_fb_info(dev);
/* register framebuffer device */
ret = register_framebuffer(fb); //fb设备注册
if (ret) {
printk(KERN_ERR "sprdfb: sprdfb_probe register framebuffer fail.\n");
goto cleanup;
}
platform_set_drvdata(pdev, dev);
sprdfb_create_sysfs(dev);
dev->ctrl->init(dev); //sprd dispc控制器的init
...
return 0;
}
struct display_ctrl sprdfb_dispc_ctrl = {
.name = "dispc",
.early_init = sprdfb_dispc_early_init,
.init = sprdfb_dispc_init,
.uninit = sprdfb_dispc_uninit,
.refresh = sprdfb_dispc_refresh,
.logo_proc = sprdfb_dispc_logo_proc,
.update_clk = dispc_update_clk_intf,
.is_refresh_done = sprdfb_is_refresh_done,
...
};
3 调试问题
帧率
dpi_clk的计算方法如下:
dpi_clk=(witdh+hfp+hbp+hsync)* (height+vfp+vbp+vsync)*fps
例如需配置帧率的话需要根据配置的dpi_clock,对除width/height这类图像固有参数以外参数进行配置。
dpi_clk=64M,如需配置720p的显示帧率为60的话,可使用后面对应的可变参数(720+4+80+4)*(1280+18+17+5)*60= 64M
图像问题
memory dump & screencap
…