基于展讯sc7731 - Android 5.1 代码分析浏览。将屏蔽细节,把握整体,并且不涉及其他设备和LCD的交互。
以下对sc7731 lcd大体流程进行简要说明。
第一,lcd 的两个阶段
1. 在uboot引起系统阶段,大约1~5秒左右,需要打印一个厂商log。这里对驱动要求非常简单,只要能打印log即可. (下面皆以lcd_ili9486s1_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 //在 [email protected]/video/sprdfb/Sprdfb_dispc.c 以及 [email protected]/video/sprdfb/Sprdfb_lcdc.c 里被调用 14 void sprdfb_panel_resume(struct sprdfb_device *dev, bool from_deep_sleep); 15 16 //睡眠接口,与lcd 睡眠唤醒机制相关 17 //在[email protected]/video/sprdfb/Sprdfb_dispc.c 以及 [email protected]/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,在[email protected]/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 //[email protected]/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 //[email protected]/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