一、概述
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进行设置,使其不被关闭;