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

原文地址:https://www.cnblogs.com/chineseboy/p/5092554.html