0%

sc7731 lcd流程分析

前言

本系列文章以分享笔者以前的学习和工作内容为主,旨在查缺补漏,如有错误和不足之处,请读者不吝指正。

本文基于Android 4.4代码分析sc7731 lcd的基本流程。

显示系统

这里暂时以7710芯片手册为例,多媒体显示系统包含 LCDC 和 DISPC,其中 LCDC 仅支持 MCU 接口类型,支持 2 路显示;而 DISPC 同时支持 DBI 和 DPI 接口类型。

lcd

  • LCDC

支持最多6个图层的Alaha Blending,RGB888数据格式到RGB565/RGB666数据格式的Dithering等功能。

lcdc

  • LCM

LCM接收AHB总线或者LCDC数据,并按照MCU接口格式按照固定的时序传输到显示模组上。
lcm

MCU支持8080和6080两种传输格式,两者的区别只是在读写控制上。

8080

  • DISPC

这里可以看出2种路线,一种直接由LCDC DBI经由DISPC DBI输出;另一种直接由AXI Domain到DISPC DBI/DPI输出。所以如果使用MIPI接口显示模组,是不需要经过LCDC显示模块的,直接由DISPC模块控制。

DISPC

uboot

lcd相关源码位于 drivers/video/sprdfb,代码文件截图如下:

uboot source code

可以看到代码兼容了多款 Soc,Makefile 中也包含了多个平台的配置。

头文件 include/configs/sp7731gea.h,里面定义了 CONFIG_SC8830

1
2
3
#define CONFIG_SC8830
#define CONFIG_DSIH_VERSION_1P21A
#define CONFIG_FB_LCD_NT35516_MIPI

这里值得一提的是7731和8830的AP相同,只是Modem存在差异,前者支持WCMDA,后者支持TD_SCMDA。

1
2
3
4
COBJS-$(CONFIG_SC8830)+= sprdfb_main.o sprdfb_panel.o sprdfb_dispc.o \
sprdfb_mcu.o sprdfb_rgb.o sprdfb_mipi.o \
sprdfb_i2c.o sprdfb_spi.o sprdfb_dsi.o \
sprdfb_chip_common.o sprdfb_chip_8830.o

能够看出该平台的显示系统支持多种lcd接口,包括mcu、rgb、mipi、i2c、spi。

这里的mipi dsi有2个不同版本的IP核,参考头文件中的定义,这里使用的是dsi_1_21a中的代码。

下面以 lcd_nt35516_mipi.c 为例,分析 uboot lcd 的整个流程。

lcd 模组配置

  1. drivers/video/sprdfb/lcd/Makefile 包含编译该屏驱动的配置

    1
    COBJS-$(CONFIG_FB_LCD_NT35516_MIPI) += lcd_nt35516_mipi.o
  2. drivers/video/sprdfb/sprdfb_panel.c 中包含该屏的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    extern struct panel_spec lcd_nt35516_mipi_spec;
    static struct panel_cfg panel_cfg[] = {
    ...
    #ifdef CONFIG_FB_LCD_NT35516_MIPI
    {
    .lcd_id = 0x16,
    .panel = &lcd_nt35516_mipi_spec,
    },
    #endif
    ...
    }
  3. drivers/video/sprdfb/lcd/lcd_nt35516_mipi.c 中详细描述了该模组的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    struct panel_spec lcd_nt35516_mipi_spec = {
    //.cap = PANEL_CAP_NOT_TEAR_SYNC,
    #ifdef CONFIG_FB_LOW_RES_SIMU
    .display_width = 480,
    .display_height= 854,
    #endif
    .width = 540,
    .height = 960,
    .fps = 60,
    .type = LCD_MODE_DSI,
    .direction = LCD_DIRECT_NORMAL,
    .info = {
    .mipi = &lcd_nt35516_mipi_info //(1) mipi lcd模组的常规配置
    },
    .ops = &lcd_nt35516_mipi_operations, //(2) mipi lcd的操作集合
    };
    static struct info_mipi lcd_nt35516_mipi_info = {
    .work_mode = SPRDFB_MIPI_MODE_VIDEO,
    .video_bus_width = 24, /*18,16*/
    .lan_number = 3,
    .phy_feq = 500*1000,
    .h_sync_pol = SPRDFB_POLARITY_POS,
    .v_sync_pol = SPRDFB_POLARITY_POS,
    .de_pol = SPRDFB_POLARITY_POS,
    .te_pol = SPRDFB_POLARITY_POS,
    .color_mode_pol = SPRDFB_POLARITY_NEG,
    .shut_down_pol = SPRDFB_POLARITY_NEG,
    .timing = &lcd_nt35516_mipi_timing, //(3) mipi lcd的时序proch配置
    .ops = NULL,
    };
    static struct panel_operations lcd_nt35516_mipi_operations = {
    .panel_init = nt35516_mipi_init, //包含mipi屏大量初始化代码
    .panel_readid = nt35516_readid, //mipi屏的读取id操作
    };
    static struct timing_rgb lcd_nt35516_mipi_timing = {
    .hfp = 20, /* unit: pixel */
    .hbp = 20,
    .hsync = 20,//4,
    .vfp = 10, /*unit: line*/
    .vbp = 10,
    .vsync = 6,
    };

lcd 模组初始化流程

系统上电执行芯片 RomCode,完成 DDR 和外部存储器等常用外设的初始化;

之后读取外部存储器中的 uboot 并跳转执行,优先完成板级初始化 board_init_fboard_init_r,其中 board_init_r 中调用了stdio_init。代码流程图如下:

sprdfb_lcd_init

我们把重点聚焦在 sprdfb_probe 的实现上,下面剔除了部分无关代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ drivers/video/sprdfb/sprdfb_main.c  

static int sprdfb_probe(void * lcdbase)
{
struct sprdfb_device *dev = &s_sprdfb_dev;
set_backlight(0); //(1)
dev->ctrl = &sprdfb_dispc_ctrl; //(2)
dev->ctrl->early_init(dev); //(3)
if (0 != sprdfb_panel_probe(dev)) { //(4)
sprdfb_panel_remove(dev);
dev->ctrl->uninit(dev);
printf("sprdfb: failed to probe\n");
return -EFAULT;
}
dev->smem_start = ((uint32_t)lcdbase);
dev->ctrl->init(dev); //(5)
return 0;
}
  • (1) 关闭pwm控制
  • (2) 获取display controller的接口,定义在 sprdfb_dispc.c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ drivers/video/sprdfb/sprdfb_dispc.c

    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,
    .update_clk = dispc_update_clock,
    };
  • (3) 调用sprdfb_dispc_early_init,配置相关的clk,使能dispc module
  • (4) sprdfb_panel_probe 会读取和保存 lcd 模组的 id,并对该模组完成初始化。
  • (5) sprdfb_dispc_init 会完成显示系统 dithering 和 osd layer 的设置。

需要特别注意 uboot lcd 初始化流程中会保存 lcd_baselcd_id,通过命令行参数传递,以供 kernel 阶段继续使用。

lcd 刷新显示流程

看到这里读者可能会有疑问,uboot logo 是什么时候显示的呢?

下面开始分析lcd refresh流程,这里要从 board_init_r 中的do_cboot代码分析。

  1. normal_mode
    代码定义了多种启动方式:normal、recovery、fastboot、charge、watchdog等,这里直接看normal_boot的代码。
    首先初始化马达硬件,完成开机震动,然后调用 vlx_nand_boot,这里的代码取决有是 nand 方案还是 emmc 方案,这里假设是 emmc 启动。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @ property/normal_mode.c

    void normal_mode(void) {
    vibrator_hw_init();
    set_vibrator(1);
    #if BOOT_NATIVE_LINUX
    vlx_nand_boot(BOOT_PART, CONFIG_BOOTARGS, BACKLIGHT_ON);
    #else
    vlx_nand_boot(BOOT_PART, NULL, BACKLIGHT_ON);
    #endif
    }
  2. vlx_nand_boot

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ property/normal_emc_mode.c

    void vlx_nand_boot(char * kernel_pname, char * cmdline, int backlight_set) {
    _boot_display_logo(dev, backlight_set); //(1)
    set_vibrator(FALSE);
    _boot_load_kernel_ramdisk_image(dev, kernel_pname, hdr);
    _boot_secure_check();
    sipc_addr_reset();
    vlx_entry();
    }
  • 调用_boot_display_logo,读取logo分区图片内容,并调用lcd_display_logo,重点关注。

  • 关闭马达;

  • 从存储器读取kernel、ramdisk、dtb image,保存相关内存地址。

  • secure boot校验流程,根据产品定义,可能会对DSP、Modem、SIMLOCK等image进行校验。

  • sipc 内存区域清零,这部分由展讯自己实现,用于AP和Modem之间的通信交换,后面有文章会涉及。

  • 跳转到内核启动

  1. _boot_display_logo
    从logo分区中读取图片信息,存放到缓存中,调用lcd_display_logo刷新显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    LOCAL __inline void _boot_display_logo(block_dev_desc_t *dev, int backlight_set)
    {
    size_t size;
    #if defined(CONFIG_LCD_720P) || defined(CONFIG_LCD_HD) //LiWei add CONFIG_LCD_HD
    size = 1<<20;
    #else
    size = 1<<19;
    #endif
    uint8 * bmp_img = malloc(size);
    if(!bmp_img){
    debugf("%s: malloc for splash image failed!\n",__FUNCTION__);
    return;
    }
    if(!_boot_partition_read(dev, L"logo", 0, size, bmp_img))
    {
    debugf("%s: read logo partition failed!\n",__FUNCTION__);
    goto end;
    }
    lcd_display_logo(backlight_set,(ulong)bmp_img,size);
    end:
    free(bmp_img);
    return;
    }
  2. lcd_display_logo
    因为是bmp格式图片,需要调用 lcd_display_bitmap 进行格式转换后才能在lcd上显示;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void lcd_display_logo(int backlight_set,ulong bmp_img,size_t size)
    {
    #define mdelay(t) ({unsigned long msec=(t); while (msec--) { udelay(1000);}})
    if(backlight_set == BACKLIGHT_ON){
    lcd_display_bitmap((ulong)bmp_img, 0, 0);
    Dcache_CleanRegion((unsigned int)(lcd_base), size<<1);
    lcd_display();
    mdelay(50);
    set_backlight(255);
    } else {
    Dcache_CleanRegion((unsigned int)(lcd_base), size<<1);
    lcd_display();
    }
    }
  3. lcd_display
    参考前面的内容,refresh函数指针等同于sprdfb_dispc_refresh,定义在 drivers/video/sprdfb_dispc.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @ drivers/video/sprdfb/sprdfb_main.c
    void lcd_display(void)
    {
    real_refresh(&s_sprdfb_dev);
    }
    static int real_refresh(struct sprdfb_device *dev)
    {
    dev->ctrl->refresh(dev);
    return 0;
    }
  4. sprdfb_dispc_refresh
    这里针对mipi lcd模组,可以简单理解为cmd mode和video mode不同方式刷新显示,这里不再详细分析操作寄存器的部分。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @ drivers/video/sprdfb_dispc.c
    static int32_t sprdfb_dispc_refresh (struct sprdfb_device *dev)
    {
    if(SPRDFB_PANEL_IF_DPI == dev->panel_if_type) { //cmd mode
    dispc_set_bits((1<<5), DISPC_DPI_CTRL);
    if(is_first_frame){
    udelay(30);
    dispc_clear_bits((1<<4), DISPC_DPI_CTRL);
    dispc_set_bits((1 << 4), DISPC_CTRL);
    is_first_frame = 0;
    }
    } else { //video mode
    dispc_set_bits((1 << 4), DISPC_CTRL);
    ...
    dispc_set_bits((1<<0), DISPC_INT_CLR);
    }
    }

至此,uboot lcd的初始化和刷新显示已经分析完成了。

kernel

lcd 相关代码如下图:
sprdfb kernel code

和 uboot 代码架构基本相似,下面以 lcd_nt35516_mipi.c为例进行分析

lcd模组配置

驱动文件放置路径: drivers/video/sprdfb/lcd/lcd_nt35516_mipi.c

  • drivers/video/sprdfb/lcd/Makefile
    包含编译该屏驱动的配置

    1
    obj-$(CONFIG_FB_LCD_NT35516_MIPI)  += lcd_nt35516_mipi.o=
  • drivers/video/sprdfb/Kconfig

    1
    2
    3
    4
    config FB_LCD_NT35516_MIPI
    boolean "support NT35516 mipi panel"
    depends on FB_SC8825 || FB_SCX35 || FB_SCX30G
    default n
  • arch/arm/configs/sp7731gea-dt_defconfig

    1
    CONFIG_FB_LCD_NT35516_MIPI=y
  • drivers/video/sprdfb/lcd/lcd_nt35516_mipi.c
    这个文件配置了该模组的基本信息和操作方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    static struct panel_operations lcd_nt35516_mipi_operations = {
    .panel_init = nt35516_mipi_init,
    .panel_readid = nt35516_readid,
    .panel_enter_sleep = nt35516_enter_sleep,
    .panel_esd_check = nt35516_check_esd,
    };

    static struct timing_rgb lcd_nt35516_mipi_timing = {
    .hfp = 20, /* unit: pixel */
    .hbp = 20,
    .hsync = 20,//4,
    .vfp = 10, /*unit: line*/
    .vbp = 10,
    .vsync = 6,
    };

    static struct info_mipi lcd_nt35516_mipi_info = {
    .work_mode = SPRDFB_MIPI_MODE_VIDEO,
    .video_bus_width = 24, /*18,16*/
    .lan_number = 3,
    .phy_feq = 500*1000,
    .h_sync_pol = SPRDFB_POLARITY_POS,
    .v_sync_pol = SPRDFB_POLARITY_POS,
    .de_pol = SPRDFB_POLARITY_POS,
    .te_pol = SPRDFB_POLARITY_POS,
    .color_mode_pol = SPRDFB_POLARITY_NEG,
    .shut_down_pol = SPRDFB_POLARITY_NEG,
    .timing = &lcd_nt35516_mipi_timing,
    .ops = NULL,
    };

    struct panel_spec lcd_nt35516_mipi_spec = {
    #ifdef CONFIG_FB_LOW_RES_SIMU
    .display_width = 480,
    .display_height = 854,
    #endif
    .width = 540,
    .height = 960,
    .fps = 60,
    .type = LCD_MODE_DSI,
    .direction = LCD_DIRECT_NORMAL,
    .is_clean_lcd = true,
    .info = {
    .mipi = &lcd_nt35516_mipi_info
    },
    .ops = &lcd_nt35516_mipi_operations,
    };

    struct panel_cfg lcd_nt35516_mipi = {
    /* this panel can only be main lcd */
    .dev_id = SPRDFB_MAINLCD_ID,
    .lcd_id = 0x16,
    .lcd_name = "lcd_nt35516_mipi",
    .panel = &lcd_nt35516_mipi_spec,
    };

    static int __init lcd_nt35516_mipi_init(void)
    {
    return sprdfb_panel_register(&lcd_nt35516_mipi);
    }

    subsys_initcall(lcd_nt35516_mipi_init);

subsys_initcall 是一个内核中的一个宏,定义特殊的init段,在内核启动过程中会依次调用这些函数,基本和module_init类似,但优先级更高。

sprdfb_panel_register 定义在 drivers/video/sprdfb/sprdfb_panel.c,会把该模组对应的 panel_cfg 添加到维护的全局链表panel_list_main 中。

lcd 模组初始化流程

  • arch/arm/configs/sp7731gea-native_defconfig

    1
    CONFIG_MACH_SP7731GEA=y
  • arch/arm/boot/dts/Makefile

    1
    2
    3
    4
    5
    arch/arm/boot/dts/sprd-scx35_sp7731gea.dts
    226:dtb-$(CONFIG_MACH_SP7731GEA) += sprd-scx35_sp7731gea.dtb
    227:dtb-$(CONFIG_MACH_SP7731GEA_LM) += sprd-scx35_sp7731gea_lm.dtb
    228:dtb-$(CONFIG_MACH_SP7731GEA_FWVGA) += sprd-scx35_sp7731gea_fwvga.dtb
    229:dtb-$(CONFIG_MACH_SP7731GEAOPENPHONE) += sprd-scx35_sp7731geaopenphone.dtb
  • sprd-scx35_sp7731gea.dts
    其中lcd controller配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    aliases {
    serial0 = &uart0;
    serial1 = &uart1;
    serial2 = &uart2;
    serial3 = &uart3;
    i2c0 = &i2c0;
    i2c1 = &i2c1;
    i2c2 = &i2c2;
    i2c3 = &i2c3;
    lcd0 = &fb0;
    spi0 = &spi0;
    spi1 = &spi1;
    spi2 = &spi2;
    hwspinlock0 = &hwspinlock0;
    hwspinlock1 = &hwspinlock1;
    };

    fb0: fb@20800000 {
    compatible = "sprd,sprdfb";
    reg = <0xf5122000 0x1000>,<0xf5146000 0x1000>;
    interrupts = <0 46 0x0>,<0 48 0x0>, <0 49 0x0>;
    clock-names = "dispc_clk_parent", "dispc_dbi_clk_parent", "dispc_dpi_clk_parent", "dispc_emc_clk_parent", "dispc_clk", "dispc_dbi_clk", "dispc_dpi_clk", "dispc_emc_clk", "fb_spi_clock", "fb_spi_clock_parent";
    clocks = <&clk_256m>, <&clk_256m>, <&clk_384m>, <&clk_aon_apb>, <&clk_dispc0>, <&clk_dispc0_dbi>, <&clk_dispc0_dpi>, <&clk_disp_emc>, <&clk_spi2>, <&ext_26m>;
    clock-src = <256000000 256000000 384000000>;
    dpi_clk_div = <7>;
    sprd,fb_use_reservemem;
    sprd,fb_mem = <0x9F311000 0x5EF000>;
    };
  • drivers/video/sprdfb/sprdfb_main.c
    与之相匹配的lcd控制器驱动位于 drivers/video/sprdfb/sprdfb_main.c,当设备节点device_node被解析为platform_device注册到系统时,会和这个驱动进行匹配。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #ifdef CONFIG_OF
    static const struct of_device_id sprdfb_dt_ids[] = {
    { .compatible = "sprd,sprdfb", },
    {}
    };
    #endif
    static struct platform_driver sprdfb_driver = {
    .probe = sprdfb_probe,
    #ifndef CONFIG_HAS_EARLYSUSPEND
    .suspend = sprdfb_suspend,
    .resume = sprdfb_resume,
    #endif
    .remove = sprdfb_remove,
    .driver = {
    .name = "sprd_fb",
    .owner = THIS_MODULE,
    #ifdef CONFIG_OF
    .of_match_table = of_match_ptr(sprdfb_dt_ids),
    #endif
    },
    };
    static int __init sprdfb_init(void)
    {
    return platform_driver_register(&sprdfb_driver);
    }

驱动加载流程大致如下图:
sprd kernel lcd probe

sprdfb_probe 函数的定义如下,简单分析下代码流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static int sprdfb_probe(struct platform_device *pdev)
{
struct fb_info *fb = NULL;
struct sprdfb_device *dev = NULL;
struct resource r;

fb = framebuffer_alloc(sizeof(struct sprdfb_device), &pdev->dev); //(1)分布帧缓冲使用空间
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"); //(2)读取设备树中lcd id
printk("sprdfb: [%s] id = %d\n", __FUNCTION__, dev->dev_id);
#else
dev->dev_id = pdev->id;
#endif
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; //(3)
#ifdef CONFIG_OF
if(0 != of_address_to_resource(pdev->dev.of_node, 0, &r)){ //(4)
printk(KERN_ERR "sprdfb: sprdfb_probe fail. (can't get register base address)\n");
goto err0;
}
g_dispc_base_addr = r.start;
printk("sprdfb: set g_dispc_base_addr = 0x%x\n", g_dispc_base_addr);
#endif
dev->logo_buffer_addr_v = 0;
if(sprdfb_panel_get(dev)){ //(5)
dev->panel_ready = true;
dev->ctrl->logo_proc(dev); //(6)
}else{
dev->panel_ready = false;
}
dev->ctrl->early_init(dev); //(7)
if(!dev->panel_ready){
if (!sprdfb_panel_probe(dev)) {
ret = -EIO;
goto cleanup;
}
}
ret = setup_fb_mem(dev, pdev); //(8)
setup_fb_info(dev); //(9)
/* register framebuffer device */
ret = register_framebuffer(fb); //(10)
platform_set_drvdata(pdev, dev);
sprdfb_create_sysfs(dev);
dev->ctrl->init(dev); //(11)
return 0;
}
  • (1) 为lcd控制器分配 framebuffer 结构体 fb_info;

  • (2) 读取设备树中lcd id,从前面设备树的信息lcd0来看,id = 0;

  • (3) sprdfb_dispc_ctrl 保存为Soc显示控制器的操作接口;

  • (4) 获取设备树中配置的寄存器地址信息。

  • (5) sprdfb_panel_get 会调用adapt_panel_from_uboot遍历模组链表 panel_list_main 中 panel_cfg,将lcd_id 和 uboot中读取的模组芯片id进行对比,如果匹配则返回对应的panel_cfg;如果没有匹配,会再后续的流程中调用sprdfb_panel_probe重新加载模组驱动。这期间会设置 DISPC 的dsi硬件配置。

    1
    2
    3
    4
    5
    6
    7
    static struct panel_spec *adapt_panel_from_readid(struct sprdfb_device *dev)
    {
    struct panel_cfg *cfg;
    struct list_head *panel_list;
    int id;

    }
  • (6) sprdfb_dispc_logo_proc 按照panel_cfg的尺寸申请缓存区,并从uboot lcd_base中拷贝logo数据;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    void sprdfb_dispc_logo_proc(struct sprdfb_device *dev)
    {
    uint32_t kernel_fb_size = 0;
    uint32_t logo_src_v = 0;
    uint32_t logo_dst_v = 0;//use the second frame buffer ,virtual
    uint32_t logo_dst_p = 0;//use the second frame buffer ,physical
    uint32_t logo_size = 0;// should be rgb565

    if(dev == NULL) {
    printk("sprdfb: %s[%d]: dev == NULL, return without process logo!!\n",__func__,__LINE__);
    return;
    }

    if(lcd_base_from_uboot == 0) {
    printk("sprdfb: %s[%d]: lcd_base_from_uboot == 0, return without process logo!!\n",__func__,__LINE__);
    return;
    }
    if(SPRDFB_PANEL_IF_DPI != dev->panel_if_type)
    return;

    logo_size = dev->panel->width * dev->panel->height * 2;// should be rgb565

    dev->logo_buffer_size = logo_size;
    dev->logo_buffer_addr_v = __get_free_pages(GFP_ATOMIC | __GFP_ZERO , get_order(logo_size));

    logo_dst_v = dev->logo_buffer_addr_v;
    logo_dst_p = __pa(dev->logo_buffer_addr_v);

    logo_src_v = (uint32_t)ioremap(lcd_base_from_uboot, logo_size);

    memcpy(logo_dst_v, logo_src_v, logo_size);
    dma_sync_single_for_device(dev, logo_dst_p, logo_size, DMA_TO_DEVICE);

    iounmap(logo_src_v);

    dispc_write(logo_dst_p, DISPC_OSD_BASE_ADDR);
    sprdfb_dispc_refresh_logo(dev);
    }

    static int32_t sprdfb_dispc_refresh_logo (struct sprdfb_device *dev)
    {
    dispc_clear_bits(0x1f, DISPC_INT_EN);//disable all interrupt
    dispc_set_bits(0x1f, DISPC_INT_CLR);// clear all interruption
    dispc_set_bits(BIT(5), DISPC_DPI_CTRL);//update

    //wait for update- done interruption
    for(i=0; i<500; i++) {
    if(!(dispc_read(DISPC_INT_RAW) & (0x10))){
    udelay(1000);
    } else {
    break;
    }
    }
    if(i >= 500) {
    printk("sprdfb: [%s] wait dispc done int time out!! (0x%x)\n", __func__, dispc_read(DISPC_INT_RAW));
    }
    dispc_set_bits((1<<5), DISPC_INT_CLR);
    return 0;
    }
  • (7) sprdfb_dispc_early_init 恢复使能 DISPC 模组;

  • (8) setup_fb_mem 解析设备树“sprd,fb_mem”配置 fb_info screen_base等参数

  • (9) setup_fb_info 进一步配置 fb_info,设置固定参数和可变参数;

  • (10) register_framebuffer 向内核注册帧缓冲区;

  • (11) sprdfb_dispc_init 设置 DISPC 的dithering 和 osd layer blending功能。

总结

针对lcd驱动调试的一些总结:

  • 背光不亮

通常直接排查硬件,检查背光电压,pwm波形输出等

  • 白屏显示

1.优先检查电压,一般液晶模组在完成初始化后,VGH/VGL经过charge pump,基本能够达到正负15v左右。

2.读取模组id,优先检查硬件接口是否有误,然后排查软件接口的设置,可通过示波器辅助快速定位。

3.检查是否初始化代码有误,存在模组代码不匹配的情况。

  • 显示异常

1.优先排查硬件,曾经有遇到过BB的VDD输出2.8V,DVDD输出1.8v,但是模组内部将VDD/DVDD相连,直接导致初始化升压不够,显示异常。

2.检查分辨率、像素、时序配置和proch设定是否存在问题。

3.画面撕裂问题,一般需要开启TE,BB会在每帧数据开始前发送信号同步。

4.画面翻转问题,一般修改模组初始化代码,修改显示刷新方向解决。