zoukankan      html  css  js  c++  java
  • sc7731 Android 5.1 LCD驱动简明笔记之一

    基于展讯sc7731 - Android 5.1 代码分析浏览。将屏蔽细节,把握整体,并且不涉及其他设备和LCD的交互。

    以下对sc7731 lcd大体流程进行简要说明。

    第一,lcd 的两个阶段
    1. 在uboot引导系统阶段,大约1~5秒左右,需要打印一个厂商log。这里对驱动要求非常简单,只要能打印log即可. (下面皆以lcd_ili9486e_mipi.c为例)
      驱动文件放置路径: u-boot64/drivers/video/sprdfb/lcd/

     添加新屏时需要修改的文件分别为:
    (1) u-boot64/drivers/video/sprdfb/lcd/Makefile 在该文件中添加编译该屏驱动的宏控

    1 obj-$(CONFIG_FB_LCD_ILI9806E_MIPI)  += lcd_ili9806e_mipi.o 

    (2) u-boot64/drivers/video/sprdfb/sprdfb_panel.c 在该文件中的 static struct panel_cfg panel_cfg[] 表中,注册该LCD 驱动里面的句柄

    1 extern struct panel_spec lcd_ili9806e_mipi_spec;
    2 static struct panel_cfg panel_cfg[] = {
    3 #if defined(CONFIG_FB_LCD_ILI9806E_MIPI)
    4     {
    5     .lcd_id = 0x04,
    6     .panel = &lcd_ili9806e_mipi_spec,
    7     }
    8 #endif
    9 }        

    (3) special/u-boot64/include/configs/sp7731gea.h 在该文件中 #define 对应的宏控 --> 该处表示,该设备uboot 可以使用该LCD

    1 #define CONFIG_FB_LCD_ILI9806E_MIPI

    2. 系统启动完毕后,lcd 将担负着人机对话的接口。这里的驱动代码以及处理机制,较之uboot目录里的要复杂许多,也精细许多。一切的故事将由此展开。
    驱动文件放置路径: kernel/drivers/video/sprdfb/lcd/
    添加新屏时需要修改的文件分别为:
    (1) kernel/drivers/video/sprdfb/lcd/Makefile 在该文件中添加编译该屏驱动的宏控

    1 obj-$(CONFIG_FB_LCD_ILI9806E_MIPI) += lcd_ili9806e_mipi.o

    (2) kernel/drivers/video/sprdfb/Kconfig 在该文件中把新屏链接到相关panel(该处修改时,注意语法层次缩进,否则会报错)

    1     config FB_LCD_ILI9806E_MIPI                                                                                                
    2          boolean "support ili9806e mipi panel"
    3          depends on FB_SC8825 || FB_SCX35 || FB_SCX30G || FB_SCX35L
    4          default n

    (3)special/kernel/arch/arm/configs/sp7731gea-dt_defconfig 在该文件中定义相关宏控("#" 表示注释,宏控定义必须顶最左边写)

    1 CONFIG_FB_LCD_ILI9806E_MIPI=y

    说明: 如果一份代码下有多个工程甚至是多个工单的话,可能会产生不同的配置,这个时候就需要做一个所谓的差异化special 配置。展讯就这样做的。

    第二,LCD 驱动框架流程
    1. uboot 代码流程

    1      stdio_init()                         @u-boot64/common/stdio.c
    2      drv_lcd_init()                       @u-boot64/common/lcd.c
    3      lcd_init()                           @u-boot64/common/lcd.c
    4      lcd_ctrl_init()                      @u-boot64/drivers/video/sprdfb/sprdfb_main.c
    5      sprdfb_probe()                       @u-boot64/drivers/video/sprdfb/sprdfb_panel.c
    6      sprdfb_panel_probe()                 @u-boot64/drivers/video/sprdfb/sprdfb_panel.c
    7      adapt_panel_from_readid()            @u-boot64/drivers/video/sprdfb/sprdfb_panel.c
    8      struct panel_cfg panel_cfg[]         @u-boot64/drivers/video/sprdfb/sprdfb_panel.c

    说明: 在uboot里面,lcd 驱动会把自己的句柄注册到 sprdfb_panel 的 panel_cfg[]表中,然后uboot启动的时候,会通过以上流程去读取LCD ID,并传送给kernel。

    2. kernel 代码结构(不涉及代码详细流程)
    按照比较统一的观点是,lcd 在kernel里面的处理分为 framebuffer file ops 、framebuffer driver、lcd driver。这三部分依次形成调用关系。
    但是按照个人观点,在以上三部分中,应该还有个panel,它介于framebuffer 和 具体lcd驱动代码之间,属于一个接口过渡层。

    (1) framebuffer file ops
    该部分代码位于7731_5.1/kernel/drivers/video/fbmem.c 文件中。
    它的作用就是向VFS层提供文件操作接口,实现 struct file_operations 结构体。换句话说,就是向用户空间提供framebuffer 驱动操作接口。
    当然,也是必须的,它肯定会调用framebuffer 驱动的一些接口。
    至于怎么调,一方面是通过hook的方式,一方面也会使用 file_fb_info()这个接口

     1     static const struct file_operations fb_fops = {
     2     .owner =    THIS_MODULE,
     3     .read =        fb_read,
     4     .write =    fb_write,
     5     .unlocked_ioctl = fb_ioctl,
     6     #ifdef CONFIG_COMPAT
     7     .compat_ioctl = fb_compat_ioctl,
     8     #endif
     9     .mmap =        fb_mmap,
    10     .open =        fb_open,
    11     .release =    fb_release,
    12     #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
    13     .get_unmapped_area = get_fb_unmapped_area,
    14     #endif
    15     #ifdef CONFIG_FB_DEFERRED_IO
    16     .fsync =    fb_deferred_io_fsync,
    17     #endif
    18     .llseek =    default_llseek,
    19     };

    (2) framebuffer 驱动
    该部分代码位于7731_5.1/kernel/drivers/video/sprdfb/sprdfb_main.c文件中,其中 sprdfb_probe() 探针函数是最关键的。
    这是LCD 的一个核心,一切的实现都在这里开始。向上,给fb_fops提供调用,以便实现用户接口;向下,通过lcd panel,操作具体的硬件。
    总结下展讯该探针函数大概的处理:

     1 static int sprdfb_probe(struct platform_device *pdev)
     2 {
     3     struct fb_info *fb = NULL;
     4     
     5     //分配帧缓冲使用的内存空间
     6     fb = framebuffer_alloc(sizeof(struct sprdfb_device), &pdev->dev);
     7     
     8     //...
     9     
    10     //检查设备ID
    11     if((SPRDFB_MAINLCD_ID != dev->dev_id) &&(SPRDFB_SUBLCD_ID != dev->dev_id)){
    12         //...
    13     }
    14     
    15     //LCD 硬件主控制操作函数 control ops
    16     if(SPRDFB_MAINLCD_ID == dev->dev_id) {
    17         dev->ctrl = &sprdfb_dispc_ctrl;
    18     }else {
    19         dev->ctrl = &sprdfb_lcdc_ctrl;
    20     }
    21     
    22     //设置/获取 帧缓冲区mem各种参数 --LCD 硬件上固定的参数 ?
    23     ret = setup_fb_mem(dev, pdev);
    24     
    25     //帧缓冲区显示参数的设置 ---用户可修改的参数 ? 
    26     setup_fb_info(dev);    
    27 
    28     //注册帧缓冲区到系统
    29     ret = register_framebuffer(fb);
    30     
    31     //利用帧缓冲的资源初始化平台驱动结构体
    32     platform_set_drvdata(pdev, dev);
    33     
    34     //创建sysfs 文件系统
    35     sprdfb_create_sysfs(dev);
    36         
    37     //对LCD控制器硬件的初始化
    38     dev->ctrl->init(dev);
    39 
    40     //注册睡眠唤醒机制
    41     register_early_suspend(&dev->early_suspend);
    42     
    43     //....
    44     
    45     return 0;
    46 }

    以上需要关注的是:
    Control ops: 会牵涉到底层硬件操作接口的调用(想了想,硬件操作接口操作和硬件操作,还是选择了前者。硬件的的操作,是lcd 驱动去做的)
    lcd 硬件固定参数: 这个是lcd的物理尺寸,无法更改的。
    lcd 可修改参数: 这个可修改是在硬件物理尺寸的基础上来操作的。比如一张图片的大小,或者显示的范围(不一定准确,大概意思差不多).

    (3) lcd panel过渡层
    该部分代码位于7731_5.1/kernel/drivers/video/sprdfb/sprdfb_panel.c 文件中。
    主要介于framebuffer 和 具体的lcd 具体驱动之间。可以简单的看做,是具体lcd 驱动的一个封装,然后把这些封装提供给framebuffer层使用。
    我之所以叫它为过渡层,是因为这里纯粹就是一些函数接口的封装,不涉及kernel的模块机制。也就是不会modue_init到编译链接脚本。
    以下是该过渡层提供给lcd驱动和framebuffer驱动的接口列表:

     1 //每一个LCD 驱动,都通过该接口加入到一个驱动链表里(这里是仅仅向系统加入一个驱动,不会去匹配硬件的,匹配硬件的操作是在framebuffer 驱动里面完成的)
     2 //换句话说,该接口就是具体的LCD 驱动调用,比如在 ili9806e 的lcd 驱动文件 lcd_ili9806e_mipi.c 会调用该接口
     3 int sprdfb_panel_register(struct panel_cfg *cfg); 
     4 
     5 //移除一个lcd 驱动
     6 //这里的移除,并非是把一个LCD驱动从链表移除,而是进行一种disable的操作
     7 //该接口将被 sprdfb_remove @drivers/video/sprdfb/sprdfb_main.c 调用
     8 void sprdfb_panel_remove(struct sprdfb_device *dev);
     9 
    10 
    11 
    12 //唤醒接口,与lcd 睡眠唤醒机制相关
    13 //在 sprdfb_dispc_resume@drivers/video/sprdfb/Sprdfb_dispc.c 以及 sprdfb_lcdc_resume@drivers/video/sprdfb/Sprdfb_lcdc.c 里被调用
    14 void sprdfb_panel_resume(struct sprdfb_device *dev, bool from_deep_sleep);
    15 
    16 //睡眠接口,与lcd 睡眠唤醒机制相关
    17 //在sprdfb_dispc_suspend@drivers/video/sprdfb/Sprdfb_dispc.c 以及 sprdfb_lcdc_suspend@drivers/video/sprdfb/Sprdfb_lcdc.c 里被调用
    18 void sprdfb_panel_suspend(struct sprdfb_device *dev);
    19 
    20 
    21 //检查ESD硬件接口, 在framebuffer 里使用, @drivers/video/sprdfb/sprdfb_main.c
    22 uint32_t sprdfb_panel_ESD_check(struct sprdfb_device *dev);
    23 
    24 //改变fps,在sprdfb_dispc_chg_clk@drivers/video/sprdfb/Sprdfb_dispc.c 里使用
    25 void sprdfb_panel_change_fps(struct sprdfb_device *dev, int fps_level);
    26 
    27 
    28 //以下4个接口与图形刷新有关系,在drivers/video/sprdfb/Sprdfb_dispc.c 和 drivers/video/sprdfb/Sprdfb_lcdc.c 都会使用到
    29 void sprdfb_panel_after_refresh(struct sprdfb_device *dev);
    30 void sprdfb_panel_before_refresh(struct sprdfb_device *dev);
    31 void sprdfb_panel_invalidate(struct panel_spec *self);
    32 void sprdfb_panel_invalidate_rect(struct panel_spec *self,uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
    33 
    34 
    35 //注册framebuffer驱动之前,会检查kernel里面的device ID 和 uboot里面传上来的device ID 是否相同。若不相同,则需要调用该接口,重新准备lcd panel 层
    36 //sprdfb_probe@drivers/video/sprdfb/sprdfb_main.c 调用
    37 bool sprdfb_panel_probe(struct sprdfb_device *dev); //static struct panel_spec *adapt_panel_from_readid(struct sprdfb_device *dev);
    38 //匹配kernel中device ID 和 uboot里面传上来的device ID。如果匹配失败,则会导致前面 sprdfb_panel_probe() 接口被调用
    39 //sprdfb_probe@drivers/video/sprdfb/sprdfb_main.c 调用
    40 bool sprdfb_panel_get(struct sprdfb_device *dev);  //static struct panel_spec *adapt_panel_from_uboot(uint16_t dev_id);
    41 
    42 
    43 //检查lcd panel 是否有效
    44 int panel_ready(struct sprdfb_device *dev);

    (4) lcd 驱动
    这一层,就完全是同lcd硬件打交道了。那么,不同厂商不同型号的lcd,其驱动代码处理细节都是不同的。当然,处理流程肯定是一样的。
    这些代码展讯都放在了 7731_5.1/kernel/drivers/video/sprdfb/lcd/ 的目录下。至于如何添加新屏,前面已经详细写过。
    每个lcd的驱动,都会通过lcd panel层提供的sprdfb_panel_register()接口,把自己添加到一个panel_list_main 或者 panel_list_sub链表中去。至于细节,暂未分析。


    第三,用户空间对framebuffer 驱动的调用
    framebuffer驱动已经通过fb_ops 向用户空间提供了文件操作接口。
    如果愿意,可以直接使用UNIX的文件编程接口 open/read/write/ioctl也行。当然,对于android这样如此复杂的系统,简单的这样操作,就只有实验价值而已。并且,这里的接口还涉及到c/c++与java的本地交互。实现一个操作库的可行性更高。
    在Android里面,提供了一个操作framebuffer 驱动的库,名字叫 gralloc。其最终实现,当然还是会使用open等基础接口,不过其架构、效率、机制都非常的优秀。
    在这里,有一个高大上的名字:HAL层。

    1. gralloc 库
    (1) 基础说明
    该部分文件一般放在了 7731_5.1/hardware/libhardware/modules/gralloc/ 目录下。不过展讯在 vendor/sprd/open-source/libs/gralloc/ 目录下又搞了一份。
    该库经过编译后,会生成一个动态的 gralloc.default.so 库文件,该库文件会放在 system/lib/hw/ 目录下,系统会通过特定的函数去读取该.so 并提出相应的信息。展讯的名字叫: gralloc.sc8830.so
    以上.so文件放置的路径以及.so文件的名字,都可以通过 7731_5.1/hardware/libhardware/modules/gralloc/Android.mk (展讯: 7731_5.1/vendor/sprd/open-source/libs/gralloc/utgard/Android.mk)进行修改。

    1 #指定.so 放置的路径
    2 LOCAL_MODULE_RELATIVE_PATH := hw 
    3 
    4 #生成.so模块的名字
    5 LOCAL_MODULE := gralloc.default

    (2) gralloc 代码入口:
    7731_5.1/hardware/libhardware/modules/gralloc/gralloc.cpp 是gralloc 库的核心文件。
    入口: HAL_MODULE_INFO_SYM 是每个HAL模块都必须实现的一个宏,其中最重要的就是base成员。在这里定义了获取module、注册、注销、锁定缓冲区的操作接口。
    模块ID: GRALLOC_HARDWARE_MODULE_ID 通过此ID来标示该模块.
    关键性的函数接口: gralloc_device_open();

    2. 调用 gralloc 模块
    调用gralloc 库的地方比较多:

    1 7731_5.1/frameworks/native/libs/ui/GraphicBufferAllocator.cpp
    2 7731_5.1/frameworks/native/libs/ui/GraphicBufferMapper.cpp
    3 7731_5.1/frameworks/native/libs/ui/FramebufferNativeWindow.cpp
    4 7731_5.1/frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp
    5 7731_5.1/frameworks/native/opengl/libagl/egl.cpp
    6 7731_5.1/frameworks/native/opengl/libagl/texture.cpp
    7 ....

    但是都会通过一个统一的接口来调用: hw_get_module()@hardware/libhardware/hardware.c
    所有的HAL调用,应该都是使用该接口。而对于各模块的区别,就是使用模块ID来区别的,比如: GRALLOC_HARDWARE_MODULE_ID

    (over)
    2015-12-31

  • 相关阅读:
    5分钟GET我使用Github 5 年总结的这些骚操作!
    这个项目可以让你在几分钟快速了解某个编程语言
    vulnstack靶机实战01
    PHP代码审计04之strpos函数使用不当
    【T1543.003】利用 ACL 隐藏恶意 Windows 服务
    Baolu CSV Data Set Config
    两种方式简单免杀ew
    mssql dba注入点写shell
    jeecms写shell的两种方法
    三种方式获取SSMS连接密码
  • 原文地址:https://www.cnblogs.com/chineseboy/p/5092554.html
Copyright © 2011-2022 走看看