一、概述
1.1 简介
本文档主要包括LCD模块的驱动流程分析、Framebuffer相关知识、Gralloc等相关内容,以及LCD调试的一些经验和相关bug的分析和讲解。
1.2 开发环境
Android:4.0
Kernel: Linux3.0
Ubuntu:需要 10.04以及之后的版本
Gcc: 4.4.3 toolchain
1.3 硬件平台
Msm8x25,pmic(pm8029)
1.4 操作系统
Android:4.0, Kernel: 3.0
1.5 开发工具
VIM,SourceInsight,JTAG,ADB
二、LCD驱动流程分析
2.1 帧缓冲
2.1.1帧缓冲概念
帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关系物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示。帧缓冲为标准字符设备,主设备号为29,对应于/dev/fbn。
2.1.2 fb_info结构体
帧缓冲设备最关键的一个数据结构体是fb_info结构,为了便于记忆,简称FBI,这个机构体在fb.h文件中定义了。FBI中包括了关于帧缓冲设备属性和操作的完整描述,这个结构体的定义如下所示。
其中fb_ops、fb_var_screeninfo和fb_fix_screeninfo这三个结构极为重要。FBI的成员变量fbops为指向底层操作的函数指针,这些函数是需要驱动程序开发人员编写的,不过高通平台已经定义好这些接口了,我们只需了解下这些接口的功能,不必修改。
fb_var_screeninfo记录用户可修改的显示控制参数,包括屏幕分辨率和每个像素点的比特数。fb_var_screeninfo中的xres定义屏幕一行有多少个点,yres定义屏幕一列有多少个点,bits_per_pixel定义每个点用多少个字节表示。而fb_fix_screeninfo中记录用户不能修改的显示控制器的参数,如屏幕缓冲区的物理地址、长度。当对帧缓冲设备进行映射操作时,就是从fb_fix_screeninfo中取得缓冲区物理地址的。上述结构体都需要在驱动程序中初始化和设置,在后面的流程分析中会作具体的讲解。
2.1.3 帧缓冲设备驱动结构
从上图可以看出,注册framebuffer时需要用到fb_info结构体,fb_info结构体又包含了fb_ops结构体,而fb_ops结构体中的fb_read、fb_write用于应用层对framebuffer的读写操作,fb_mmap用于应用进程和framebuffer之间的内存映射,fb_ioctl用于应用层对framebuffer进行的一些控制操作,具体的操作会在后面的流程分析中讲到。fb_info结构中的fb_check_var和fb_set_par分别用于获取和设置framebuffer的显示参数。
2.2 LCD driver的注册以及framebuffer的建立
在分析LCD的流程时,从底层往上一层层的分析,这样更容易理解驱动层每一层的作用。
2.2.1 LCD驱动的注册以及LCDC device的创建
在注册LCD驱动前需要设置一些参数,包括分辨率大小、bpp、像素时钟频率等如下图,
在probe函数中会执行msm_fb_add_device这个接口,这个接口在msm_fb.c中定义,这个接口的功能就是传递LCD driver的相关参数并根据LCD的类型(这里假设是RGB接口)创建一个LCDC device,此外还会创建一个framebuffer结构体,并将其添加到全局的framebuffer列表fb_list里面。
2.2.2 MDP device的创建
在根据LCD的类型创建新设备时,会去执行lcdc.c中的probe函数,这个接口会创建一个mdp device,然后设置mdp device的一些显示参数以及on和off接口,并将lcdc的pdev结构体的next指针指向mdp device的设备结构体,即mdp device是lcdc device的父节点。
2.2.3 msm_fbdevice的创建
在创建MDP device时,会去执行mdp.c中的probe函数,初始化MDP相关参数并创建msm_fb device,其next指针指向mdp device的设备结构体,即msm_fb device是mdp device的父节点。
2.2.4 fb0的创建
在创建msmfb_device时,会去执行msm_fb.c中的probe函数,此接口中最重要的一个函数就是msm_fb_register,该接口会对前面讲到的fb_info结构体进行填充,设置fb_var_screen和fb_fix_screen结构体的显示参数,包括图像显示格式、可见分辨率、虚拟分辨率、红绿蓝色域的偏移、帧率、虚拟基地址等等一些参数,并将高通平台自带的fb_ops接口填充到fb_info结构体里面,然后调用register_framebuffer来创建fb0 device。至此,fb0的建立已经完成,应用层可以对fb0节点的控制来操作framebuffer缓冲区。
2.2.5 fb设备创建流程图
从上图可清楚的看出从注册LCD驱动到创建framebuffer的流程。
2.3 fb设备的打开及framebuffer的使用
上面分析从LCD驱动的注册到fb0建立的流程,那么fb0创建好后,怎么使用它呢?现在来分析下打开fb0操作framebuffer的流程。
2.3.1 gralloc设备的打开过程
显示模块在初始化时会去通过hw_get_module加载gralloc库,该库存在于/system/lib/hw中,在加载成功gralloc库后,会调用framebuffer_open接口,这个接口最终会被指向framebuffer.cpp文件中的fb_device_open函数。执行fb_device_open时,首先会去打开先前已经加载成功的gralloc库。
Gralloc模块在在文件hardware/libhardware/include/hardware/gralloc.h中定义了一个帮助函数gralloc_open,用来打开gralloc设备。gralloc_open最终会指向gralloc.cpp中的gralloc_device_open函数。
intgralloc_device_open(consthw_module_t* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
gralloc_context_t *dev;
dev = (gralloc_context_t*)malloc(sizeof(*dev));
/* initialize our state here */
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = gralloc_close;
dev->device.alloc = gralloc_alloc;
dev->device.free = gralloc_free;
*device = &dev->device.common;
status = 0;
} else {
status = fb_device_open(module, name, device);
}
return status;
}
这个函数主要是用来创建一个gralloc_context_t结构体,并且对它的成员变量device进行初始化。结构体gralloc_context_t的成员变量device的类型为gralloc_device_t,它用来描述一个gralloc设备。gralloc设备是用来分配和释放图形缓冲区的,这是通过调用它的成员函数alloc和free来实现的。
2.3.2 fb设备的打开过程
在打开gralloc设备后,会去执行fb_device_open来打开fb设备。fb设备使用结构体framebuffer_device_t来描述。结构体framebuffer_device_t是用来描述系统帧缓冲区的信息,它定义在文hardware/libhardware/include/hardware/fb.h。
typedefstructframebuffer_device_t {
structhw_device_t common;
/* flags describing some attributes of the framebuffer */
const uint32_t flags;//记录系统帧缓冲区的标志
/* dimensions of the framebuffer in pixels */
const uint32_t width; //描述设备显示屏的宽度
const uint32_t height; //描述设备显示屏的高度
/* frambuffer stride in pixels */
constint stride; //描述设备显示屏的一行有多少个像素点
/* framebuffer pixel format */
constint format; //描述系统帧缓冲区的像素格式
/* resolution of the framebuffer's display panel in pixel per inch*/
const float xdpi; //描述设备显示屏在宽度上的密度
const float ydpi; //描述设备显示屏在高度上的密度
/* framebuffer's display panel refresh rate in frames per second */
const float fps; //描述设备显示屏的刷新频率,它的单位是帧每秒
/* min swap interval supported by this framebuffer */
constintminSwapInterval; //描述帧缓冲区交换前后两个图形缓冲区的最小时间间隔
/* max swap interval supported by this framebuffer */
constintmaxSwapInterval; //描述帧缓冲区交换前后两个图形缓冲区的最大时间间隔
/* number of framebuffers */
constintnumFramebuffers;
int reserved[7];
int (*setSwapInterval)(structframebuffer_device_t* window,int interval);
int (*setUpdateRect)(structframebuffer_device_t* window,int left, int top, int width, int height);
int (*post)(structframebuffer_device_t* dev, buffer_handle_t buffer);
int (*compositionComplete)(structframebuffer_device_t* dev);
int (*lockBuffer) (structframebuffer_device_t* dev, int);
void (*dump)(structframebuffer_device_t* dev, char *buff, intbuff_len);
int (*enableScreen)(structframebuffer_device_t* dev, int enable);
int (*perform) (structframebuffer_device_t* dev, int event, int value);
} framebuffer_device_t;
Gralloc模块在在文件hardware/libhardware/include/hardware/fb.h中定义了一个帮助函数framebuffer_open,用来打开fb设备。这个接口最终会被指向framebuffer.cpp文件中的fb_device_open函数。
intfb_device_open(hw_module_tconst* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
alloc_device_t* gralloc_device;
status = gralloc_open(module, &gralloc_device);
if (status < 0)
return status;
/* initialize our state here */
fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = fb_close;
dev->device.setSwapInterval = fb_setSwapInterval;
dev->device.post = fb_post;
dev->device.setUpdateRect = 0;
dev->device.compositionComplete = fb_compositionComplete;
dev->device.lockBuffer = fb_lockBuffer;
#if defined(HDMI_DUAL_DISPLAY)
dev->device.perform = fb_perform;
#endif
private_module_t* m = (private_module_t*)module;
status = mapFrameBuffer(m);
if (status >= 0) {
int stride = m->finfo.line_length / (m->info.bits_per_pixel>> 3);
const_cast<uint32_t&>(dev->device.flags) = 0;
const_cast<uint32_t&>(dev->device.width) = m->info.xres;
const_cast<uint32_t&>(dev->device.height) = m->info.yres;
const_cast<int&>(dev->device.stride) = stride;
const_cast<int&>(dev->device.format) = m->fbFormat;
const_cast<float&>(dev->device.xdpi) = m->xdpi;
const_cast<float&>(dev->device.ydpi) = m->ydpi;
const_cast<float&>(dev->device.fps) = m->fps;
const_cast<int&>(dev->device.minSwapInterval) = private_module_t::PRIV_MIN_SWAP_INTERVAL;
const_cast<int&>(dev->device.maxSwapInterval) = private_module_t::PRIV_MAX_SWAP_INTERVAL;
const_cast<int&>(dev->device.numFramebuffers) = m->numBuffers;
if (m->finfo.reserved[0] == 0x5444 &&
m->finfo.reserved[1] == 0x5055) {
dev->device.setUpdateRect = fb_setUpdateRect;
LOGD("UPDATE_ON_DEMAND supported");
}
*device = &dev->device.common;
}
// Close the gralloc module
gralloc_close(gralloc_device);
}
return status;
}
fb_device_open用来创建一个fb_context_t结构体,并且对它的成员变量device进行初始化。结构体fb_context_t的成员变量device的类型为framebuffer_device_t,前面提到,它是用来描述fb设备的。fb设备主要是用来渲染图形缓冲区的,这是通过调用它的成员函数post来实现的。从这里可以看出,函数fb_device_open所打开的fb设备的成员函数post被设置为Gralloc模块中的函数fb_post.。
函数fb_device_open在打开fb设备的过程中,会调用另外一个函数mapFrameBuffer来获得系统帧缓冲区的信息,并且将这些信息保存在参数module所描述的一个private_module_t结构体的各个成员变量中。有了系统帧缓冲区的信息之后,函数fb_device_open接下来就可以对前面所打开的一个fb设备的各个成员变量进行初始化。这些成员变量的含义可以参考前面对结构体framebuffer_device_t的介绍。函数mapFrameBuffer除了用来获得系统帧缓冲区的信息之外,还会将系统帧缓冲区映射到当前进程的地址空间来。
函数mapFrameBuffer实现在文件hardware/libhardware/modules/gralloc/framebuffer.cpp,如下所示:
staticintmapFrameBuffer(structprivate_module_t* module)
{
pthread_mutex_lock(&module->lock);
int err = mapFrameBufferLocked(module);
pthread_mutex_unlock(&module->lock);
return err;
}
这个函数调用了同一个文件中的另外一个函数mapFrameBufferLocked来初始化参数module以及将系统帧缓冲区映射到当前进程的地址空间来。
intmapFrameBufferLocked(structprivate_module_t* module)
{
// already initialized...
if (module->framebuffer) {
return 0;
}
char const * constdevice_template[] = {
"/dev/graphics/fb%u",
"/dev/fb%u",
0 };
intfd = -1;
int i=0;
char name[64];
char property[PROPERTY_VALUE_MAX];
/* 首先在系统中检查是否存在设备文件/dev/graphics/fb0或者/dev/fb0。如果存在的话,那么就调用函数open来打开它,并且将得到的文件描述符保存在变量fd中。这样,接下来函数mapFrameBufferLocked就可以通过文件描述符fd来与内核中的帧缓冲区驱动程序交互*/
while ((fd==-1) &&device_template[i]) {
snprintf(name, 64, device_template[i], 0);
fd = open(name, O_RDWR, 0);
i++;
}
if (fd< 0)
return -errno;
/* 以下几行代码分别通过IO控制命令FBIOGET_FSCREENINFO和FBIOGET_VSCREENINFO来获得系统帧缓冲区的信息,分别保存在fb_fix_screeninfo结构体finfo和fb_var_screeninfo结构体info中*/
structfb_fix_screeninfofinfo;
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
return -errno;
structfb_var_screeninfo info;
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
/*设置设备显示屏的虚拟分辨率,结构体fb_var_screeninfo的成员变量xres和yres用来描述显示屏的可视分辨率,而成员变量xres_virtual和yres_virtual用来描述显示屏的虚拟分辨率。这里保持可视分辨率以及虚拟分辨率的宽度值不变,而将虚拟分辨率的高度值设置为可视分辨率的高度值的NUM_BUFFERS倍。NUM_BUFFERS是一个宏,它的值被定义为2。这样,我们就可以将系统帧缓冲区划分为两个图形缓冲区来使用,即可以通过硬件来实现双缓冲技术。在结构体fb_var_screeninfo中,与显示屏的可视分辨率和虚拟分辨率相关的另外两个成员变量是xoffset和yoffset,它们用来告诉帧缓冲区当前要渲染的图形缓冲区是哪一个*/
info.reserved[0] = 0;
info.reserved[1] = 0;
info.reserved[2] = 0;
info.xoffset = 0;
info.yoffset = 0;
info.activate = FB_ACTIVATE_NOW;
if(info.bits_per_pixel == 32) {
/*
* Explicitly request RGBA_8888
*/
info.bits_per_pixel = 32;
info.red.offset = 24;
info.red.length = 8;
info.green.offset = 16;
info.green.length = 8;
info.blue.offset = 8;
info.blue.length = 8;
info.transp.offset = 0;
info.transp.length = 8;
/* Note: the GL driver does not have a r=8 g=8 b=8 a=0 config, so if we do
* not use the MDP for composition (i.e. hw composition == 0), ask for
* RGBA instead of RGBX. */
if (property_get("debug.sf.hw", property, NULL) > 0 &&atoi(property) == 0)
module->fbFormat = HAL_PIXEL_FORMAT_RGBX_8888;
else if(property_get("debug.composition.type", property, NULL) > 0 && (strncmp(property, "mdp", 3) == 0))
module->fbFormat = HAL_PIXEL_FORMAT_RGBX_8888;
else
module->fbFormat = HAL_PIXEL_FORMAT_RGBA_8888;
} else {
info.bits_per_pixel = 16;
info.red.offset = 11;
info.red.length = 5;
info.green.offset = 5;
info.green.length = 6;
info.blue.offset = 0;
info.blue.length = 5;
info.transp.offset = 0;
info.transp.length = 0;
module->fbFormat = HAL_PIXEL_FORMAT_RGB_565;
}
//adreno needs 4k aligned offsets. Max hole size is 4096-1
int size = roundUpToPageSize(info.yres * info.xres * (info.bits_per_pixel/8));
/*
* Request NUM_BUFFERS screens (at lest 2 for page flipping)
*/
intnumberOfBuffers = (int)(finfo.smem_len/size);
LOGV("num supported framebuffers in kernel = %d", numberOfBuffers);
if (property_get("debug.gr.numframebuffers", property, NULL) > 0) {
intnum = atoi(property);
if ((num>= NUM_FRAMEBUFFERS_MIN) && (num<= NUM_FRAMEBUFFERS_MAX)) {
numberOfBuffers = num;
}
}
if (numberOfBuffers> NUM_FRAMEBUFFERS_MAX)
numberOfBuffers = NUM_FRAMEBUFFERS_MAX;
LOGV("We support %d buffers", numberOfBuffers);
//consider the included hole by 4k alignment
uint32_t line_length = (info.xres * info.bits_per_pixel / 8);
info.yres_virtual = (size * numberOfBuffers) / line_length;
/*通过IO控制命令FBIOPUT_VSCREENINFO来设置设备显示屏的虚拟分辨率以及像素格式,如果设置失败,即调用函数ioctl的返回值等于-1,那么很可能是因为系统帧缓冲区在硬件上不支持双缓冲,因此,接下来的代码就会重新将显示屏的虚拟分辨率的高度值设置为可视分辨率的高度值,并且将变量flags的PAGE_FLIP位置为0 */
uint32_t flags = PAGE_FLIP;
if (ioctl(fd, FBIOPUT_VSCREENINFO, &info) == -1) {
info.yres_virtual = size / line_length;
flags&= ~PAGE_FLIP;
LOGW("FBIOPUT_VSCREENINFO failed, page flipping not supported");
}
/* 另一方面,如果调用函数ioctl成功,但是最终获得的显示屏的虚拟分辨率的高度值小于可视分辨率的高度值的2倍,那么也说明系统帧缓冲区在硬件上不支持双缓冲。在这种情况下,接下来的代码也会重新将显示屏的虚拟分辨率的高度值设置为可视分辨率的高度值,并且将变量flags的PAGE_FLIP位置为0。*/
if (info.yres_virtual< ((size * 2) / line_length) ) {
// we need at least 2 for page-flipping
info.yres_virtual = size / line_length;
flags&= ~PAGE_FLIP;
LOGW("page flipping not supported (yres_virtual=%d, requested=%d)",
info.yres_virtual, info.yres*2);
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
if (int(info.width) <= 0 || int(info.height) <= 0) {
// the driver doesn't return that information
// default to 160 dpi
info.width = ((info.xres * 25.4f)/160.0f + 0.5f);
info.height = ((info.yres * 25.4f)/160.0f + 0.5f);
}
/* 首先计算显示屏的密度,即每英寸有多少个像素点,分别宽度和高度两个维度,分别保存在变量xdpi和ydpi中。注意,fb_var_screeninfo结构体info的成员变量width和height用来描述显示屏的宽度和高度,它们是以毫米(mm)为单位的。*/
floatxdpi = (info.xres * 25.4f) / info.width;
floatydpi = (info.yres * 25.4f) / info.height;
//The reserved[4] field is used to store FPS by the driver.
float fps = info.reserved[4];
/*通过IO控制命令FBIOGET_FSCREENINFO来获得系统帧缓冲区的固定信息,并且保存在fb_fix_screeninfo结构体finfo中,接下来再使用fb_fix_screeninfo结构体finfo以及前面得到的系统帧缓冲区的其它信息来初始化参数module所描述的一个private_module_t结构体*/
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo) == -1)
return -errno;
module->flags = flags;
module->info = info;
module->finfo = finfo;
module->xdpi = xdpi;
module->ydpi = ydpi;
module->fps = fps;
/*表达式info.yres_virtual / info.yres计算的是整个系统帧缓冲区可以划分为多少个图形缓冲区来使用 */
int err;
module->numBuffers = info.yres_virtual / info.yres;
/*bufferMask的值接着被设置为0,表示系统帧缓冲区中的所有图形缓冲区都是处于空闲状态 */
module->bufferMask = 0;
size_tfbSize = roundUpToPageSize(finfo.line_length * info.yres) * module->numBuffers;
module->framebuffer = new private_handle_t(fd, fbSize,
private_handle_t::PRIV_FLAGS_USES_PMEM, BUFFER_TYPE_UI,module->fbFormat, info.xres, info.yres);
/* 系统帧缓冲区是通过调用函数mmap来映射到当前进程的地址空间来的。映射后得到的地址空间使用一个private_handle_t结构体来描述,这个结构体的成员变量base保存的即为系统帧缓冲区在当前进程的地址空间中的起始地址。这样,Gralloc模块以后就可以从这块地址空间中分配图形缓冲区给当前进程使用*/
void* vaddr = mmap(0, fbSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (vaddr == MAP_FAILED) {
LOGE("Error mapping the framebuffer (%s)", strerror(errno));
return -errno;
}
module->framebuffer->base = intptr_t(vaddr);
memset(vaddr, 0, fbSize);
return 0;
}
至此,fb设备的打开过程的分析完成了,系统缓冲区已经被映射到应用层的进程空间了。在fb设备被打开后,应用层就可以通过ioctl接口对底层的framebuffer进行操作了。
2.3.3 fb设备打开流程图
三、LCD调试经验
3.1 移植驱动代码
在调试之前,需要先将LCD的驱动代码移植好,使LCD的接口和平台的接口对应上,并根据硬件原理图,修改gpio的配置,修改上电相关的接口,给LCD加载正确的供电。移植代码包括板:板文件的修改、驱动源文件的添加、Konfig和Makefile的修改、工程mk的修改。代码移植OK后就可以开始准备调试LCD了。
3.2点亮背光
调试LCD时,在效果出来之前首先要保证背光能点亮。目前背光的控制方式有两种:数字脉冲和PWM。现在用得比较多的是PWM方式,PWM提供波形的源也有两种,一种是从PMIC提供的,另一种是从LCD内部出来的,CABC控制方式。
使用PMIC提供的源时,需要硬件上将pmic的PWM输出脚(高通平台是gpio01)连接到LCD的背光控制脚,在系统起来时,调用pwm初始化接口,之后调用pwm设置背光等级的接口就可以设置背光亮度了。
使用CABC控制方式时,只需设置LCD的相关寄存器就可以控制背光的打开和关闭,以及背光亮度的调试,具体设置请参考对应的文档。
3.3点亮LCD
点亮LCD时,首先要给LCD上电(一般情况下,对上电时序没有严格的要求),LCD的上电接口在板文件中实现了。上完电后要使LCD复位,之后再往LCD的寄存器里面写参数,对LCD进行初始化。初始化参数一般是由LCD屏厂提供的,在拿到初始化代码后需要对照spec进行适当的修改,将LCD的极性设置为当前项目平台的极性。当LCD点亮后,可能还会出现显示相关的问题,比如显示区域有偏移,显示的颜色不正确等等,需要对照spec修改相应的寄存器进行优化。
四、问题总结
4.1 LCD相关问题
4.1.1驱动代码移植完后,调试LCD,背光亮了,屏幕没有任何显示,一片漆黑
问题定位:测量LCD的相关脚的电压,是否达到正常工作所需电压;
(1)若工作电压没有达到LCD的正常工作电压
现象分析:LCD上电不成功,没有正常工作;
解决方法:对照硬件原理图,修改软件代码,修改上电接口的参数;
(2) 若工作电压已经达到LCD的正常工作电压
现象分析:LCD正常工作了,但是LCD的极性不对;
解决方法:对照LCD的spec,参考当前平台的其他LCD驱动极性的设置,修改极性相关的寄存器。
4.1.2 屏幕点亮了,但是颜色显示不正确,红色和蓝色互换了
问题定位:LCD有个寄存器是用来设置显示模式的,红色和蓝色互换,说明对应的寄存器设置不正确;
解决方法:对照LCD的spec,修改寄存器配置参数;
4.1.3 LCD屏闪,偶尔还会出现不明显的线条
问题定位:修改代码,提高pclk到一个较高的频率;
(1)若提高pclk后,屏闪现象消失
现象分析:说明此现象是刷新频率较低引起的;
解决方法:将pclk提高到一个合适的较高频率;
(2) 若提高pclk后,屏闪现象仍然存在
现象分析:说明此现象不是刷新频率较低引起的,这种情况是LCD极性设置不正确造成的;
解决方法:对照spec修改LCD的极性;
4.1.4当屏为24位LCD时,24位效果不明显,和18位LCD一样
问题定位:检查LCD驱动配置是否正确,包括LCD寄存器的配置,驱动参数
Bpp和颜色显示模式fb_img_type;以及LCD的gpio配置,是否支持24位显示模式
(1) 若检查上述配置后,发现有些配置不满足要求
现象分析:说明底层驱动的配置不合理,需要修改;
解决方法:将不合理的配置修改为正确的配置,若仍有问题,参考(2);
(2) 若检查上述配置后,所有配置都正确
现象分析:说明底层驱动的配置都是OK的,那么应该跟上层有关;
解决方法:联系软件部的同事,检查并修改应用层的参数设置;
4.2 平台相关问题
4.2.1开机时,在开机logo和开机动画之间会闪一下屏
现象分析:开机时,在kernel起来前是LK在支持LCD的显示,kernel起来后会关掉LK那边的电源和clk,然后打开
kernel这边的电源和clk等,这个时候如果点亮背光的接口的调用比LCD初始化接口的调用早,就会引
起屏幕闪烁一下的现象
解决方法:调整背光接口和LCD初始化接口的调用顺序,在需要的地方加上适当的延时
4.2.2做LCD兼容功能时,读取到的ADC值老是有波动
问题定位:因为LK从共享内存中读取到的ADC值是modem那边读取后存到共享内存里面的,因此首先检查共享
内存的配置和使用是否正常
(1) 若共享内存的设置和读取没有问题
现象分析:说明不是共享内存传值是OK的,那么应该是ADC读取接口的问题,有波动说明可以读取到数据但是
不准确,那么应该是adc通道的初始化没有完成或者不正确引起的;
解决方法:将adc通道的初始化代码放到比较靠前的地方,保证在调用adc接口读取adc值时,adc通道已经被正
确的初始化了;
(2) 若共享内存的设置和读取有问题
解决方法:检查共享内存的申请和使用,注意,目前高通平台支持三个id可以被客户使用,分别是vendor0、
vendor1和vendor2;
4.2.3修改开机logo后,系统起不来,串口没有任何log输出
现象分析:系统启动时,会给变量分配内存,若开机logo太大,有可能在分配开机logo的内存空间时将系统的
某些重要内存区域覆盖掉,这样就会造成系统无法启动了
解决方法:修改开机logo时,要控制转换后logo数组的大小,使数组尽可能的小,这样还可以节省刷logo的时
间,如果logo的背景是黑色的,则需要要显示logo的彩色区域就可以了;
4.2.4开机logo和开机动画之间有一段较长的黑屏时间
现象分析:kernel启动时,会关掉所有的clk,然后重新初始化需要使用的clk,Mdp相关的clk会在kernel启动时被
关掉,而在mdp初始化时才被打开,因此存在一段空白期;
解决方法:在kernel中对mdp相关的clk进行设置,使其不被关闭;
调试小结:
1)调试lcd背光,背光主要分为PMIC自带的和单独的DCDC,如果为PMIC自带的背光,一般平台厂商已经做好,直接调用接口即可,如果为单独的DCDC驱动,则需要用GPIO控制DCDC的EN端
2)确认lcd的模拟电,io电是否正常
3)根据lcd的分辨率,RGB/CPU/MIPI等不同的接口,配置控制寄存器接口
4)根据lcd spec配置PCLK的频率,配置PCLK,VSYNC,HSYNC,DE等控制线的极性
5)使用示波器测试所有clk的波形,确认频率,极性是否符合要求
6)使用示波器测试data线,看是否有数据输出,bpp的设置是否正确
7)如果lcd需要初始化,配置spi的接口,一般分为cpu自带的spi控制器,和gpio模拟的spi。
8)根据lcd spec中的初始化代码进行lcd的初始化
9)用示波器测量lcd的spi clk及数据线,确认是否正常输出
10)正常情况下,此时lcd应该可以点亮。如果没有点亮,按照上述步骤1到9,逐项进行检查测试,重点检查第5项,clk的极性
11)如果lcd点亮,但是花屏。则需要先确认数据格式是否正确,然后确认fb里的数据是否正常,有以下几种方法确认fb里的数据
i)cat /dev/graphics/fb0 > /sdcard/fb0,然后将/sdcard/fb0 >到另一台相同分辨率及相同格式的手机上,看图片显示是否正常 ii)使用irfanview软件显示cat /dev/graphics/fb0出来的raw数据,注意要正确设置分辨率及格式,否则显示花屏 iii)如果adb连接正常,可以使用豌豆莢等软件,查看fb中的数据是否正常
通过以上途径,如果确认fb中的数据正常显示,则很可能为lcd初始化代码的问题,或者clk极性的问题,如果fb数据不正常,则可能为lcd控制寄存器配置不正常导致
小结二
其实点亮lcd很简单必须保证以后几个步骤正确:
1:确认Lcd信息所在文件被编译进去,并且lcd 和board name里面注册一质,倘若这部正确,那么log里面应该有对应分辨率的一段framebuffer同时调到相对应的power_on函数。对于lcdc panel对应文件在lcdc_xx.c,对于mipi panel对应文件在mipi_xx.c(下序列操作)和mipi_xxxx.c(timing pll clk等初始化操作)。
2:仔细检查上电同时测量,同时将28根rgb interface对应gpio设为lcdc func。对于传统的lcd不需要RST操作只需拉高即可,对于mipi和需要下code的RGB panel需要RST高低高操作,这样code才生效。注意一般sleep out(0x11)和display on(0x29)之间需要mdelay(100)左右,貌似这个对于大部分panel是必须的。
3:最后还要确认是否有framebuffer输出,要是改动了display这块的clk很有可能没有buffer输出的,可以通过cat /dev/graphyics/fb0查看有没有输出字符。曾经调试开机logo连续显示时遇到过好几次没有buffer输出导致kernel卡住,屏也不亮按power键没有反映的情况。
4:如果以上操作正常同时序列正确,那么屏幕应该可以点亮。对于遇到的有以下显示问题:
a:屏幕呈花屏状态,说明lcd初始化成功,但是没有rgb刷过来。认真检查之后发现pclk时序不对,由于是新的平台所以设对以后,以后的屏就好办了。
b:RGB pane内容l闪烁通常由pclk设置不对导致还有可能与porch有关。通常wvga 16bit的panel使用24.5M的PCLK,qhd的24bit panel 30M PCLK。至于porch我们可以多替换几组试试或者找FAE发个可以点亮的。一般屏对porch要求不高,几乎都可以点亮的。
c:FPC没有贴好也有可能导致屏幕不亮。
d:rest有问题,一定仔细测量使用示波器看出波形,比如lk下面有时可能就没有控制对。
e:许多kernel里面实现的但是在lk下面由于代码比较少,就不好实现,比如pm上电,vibrator等等,其实在kernel里面归根也是写对应寄存器的,很简单,最好使的办法就是在kernel里面读出来,在在lk里面写进去,这样就好办了。8x平台许多上电我就是这样做的,还有mipi的dsi相关设置clk的REG。
f:屏幕经常唤醒只显示灰色底面,最后查明寄存器没有使能外部升压电路。
g:唤醒屏幕闪白光问题,说白了是背光早亮了,很有可能是下序列mdelay太久,改小点就没有这个问题了。根本原因屏幕初始化序列下慢了。亲身经历的。
h:lcd唤醒闪屏问题,这个是由于每次重新RST下序列过程delay久了导致,适当减少delay时间即可。
i:用厂商给的序列要么屏点不亮要么界面有水波纹,这些通常都是rgb interface polarity导致,需要调整pclk hsync vsync de极性使之符合平台极性。
j:结束开机logo至android动画出现之间好多屏会出现闪屏或者闪白光的情况。原因:在这个时间点kernel会会对屏再次初始化,我们可以软件上屏蔽第一次初始化动作从而解决。