微波EDA网,见证研发工程师的成长!
首页 > 研发问答 > 手机设计讨论 > MTK手机平台交流 > LCD 驱动过程详解

LCD 驱动过程详解

时间:10-02 整理:3721RD 点击:

mtk 平台LCD 驱动的整个过程的讲解



MTK_LCD_Driver

代码的路是在/mediatek/source/kernel/driver/vedio/mtkfb.c

module_init(mtkfb_init);模块初始化函数

int__init mtkfb_init(void)

{

int r = 0;


MSG_FUNC_ENTER();



/* Register the driver withLDM */


if(platform_driver_register(&mtkfb_driver)) {//以platform方式进行注册mtkfb driver

PRNERR("failed toregister mtkfb driver\n");

r = -ENODEV;

goto exit;

}


#ifdefCONFIG_HAS_EARLYSUSPEND

register_early_suspend(&mtkfb_early_suspend_handler);//LCD是以erly_suspend的方式注册的,这个涉及到power_management的内容

#endif


DBG_Init();


exit:

MSG_FUNC_LEAVE();

return r;

知道设备模型的人应该知道platformbus总线的match函数的规则是device和driver的名字必须相同,当name匹配一样的时候,我们就会调用driver里面的probe函数,这个函数是LCDdriver的核心入口函数。

进入LCDprobe世界。

staticint mtkfb_probe(struct device *dev)

{

struct platform_device *pdev;

struct mtkfb_device *fbdev= NULL;

struct fb_info *fbi;

int init_state;

int r = 0;

char*p = NULL;

MSG_FUNC_ENTER();


printk("%s,%s\n", __func__, saved_command_line);

p= strstr(saved_command_line, "fps=");

if(p== NULL){

lcd_fps= 6000;

printk("[FBdriver]can not get fps fROM uboot\n");

}

else{

p+= 4;

lcd_fps= SIMple_strtol(p, NULL, 10);

}


…........................

…........................


r = register_framebuffer(fbi);

if (r != 0) {

PRNERR("register_framebufferfailed\n");

goto cleanup;

}


fbdev->state =MTKFB_ACTIVE;


MSG(INFO, "MTKframebuffer initialized vram=%lu\n", fbdev->fb_size_in_byte);


MSG_FUNC_LEAVE();

return 0;


cleanup:

mtkfb_free_resources(fbdev,init_state);


MSG_FUNC_LEAVE();

return r;

}

这个函数比较长,下面我们一一对这个Probe函数进行讲解。

1、开始driver接受从uboot中传递过来的参数,saved_command_line变量,进行参数的取值。

2、DISP_IsContextInited这个函数判断LCM_params、disp_drv、lcm_drv是不是都进行初始化了,如果都初始化了,那么就返回TRUE如果有一个没有进行初始化的话,那么就返回FALSE。其实这里在uboot中已经进行了初始化了,我们这里假设这里还没有初始化,如果是FALSE的话,那么就会调用mtkfb_find_lcm_driver这个function函数。

mtkfb_find_lcm_driver:

BOOLmtkfb_find_lcm_driver(void)

{

BOOLret = FALSE;

char*p, *q;


p= strstr(saved_command_line,"lcm=");//这里我们会找出”lcm=”这个字符串在saved_command_line中第一次出现的位置,取出这个指针。

if(p== NULL)

{

//we can't find lcm string in the command line, the uboot should be oldversion

returnDISP_SelectDevice(NULL);

}


p+= 4;

if((p- saved_command_line) > strlen(saved_command_line+1))

{

ret= FALSE;

gotodone;

}


printk("%s,%s\n", __func__, p);

q= p;

while(*q!= ' ' && *q != '\0')

q++;


mEMSet((void*)mtkfb_lcm_name,0, sizeof(mtkfb_lcm_name));

strncpy((char*)mtkfb_lcm_name,(const char*)p, (int)(q-p));//这里会找出lcm的名字,接下来会将这个Name股指给mtkfb_lcm_name这个变量,下面会将这个变量传递给mtkfb_lcm_name这个函数。


printk("%s,%s\n", __func__, mtkfb_lcm_name);

if(DISP_SelectDevice(mtkfb_lcm_name))

ret= TRUE;


done:

returnret;

}

continuetrace code:DISP_SelectDevice(mtkfb_lcm_name)

DISP_SelectDevice(mtkfb_lcm_name):

BOOLDISP_SelectDevice(const char* lcm_name)

{

LCD_STATUSret;


ret= LCD_Init();

printk("retof LCD_Init() = %d\n", ret);


lcm_drv= disp_drv_get_lcm_driver(lcm_name);

if(NULL == lcm_drv)

{

printk("%s,disp_drv_get_lcm_driver() returns NULL\n", __func__);

returnFALSE;

}


disp_dump_lcm_parameters(lcm_params);

returndisp_drv_init_context();

}

上面的函数还是比较复杂的,我们下面会进行一一的讲解:

2.1:LCD_Init

LCD_STATUSLCD_Init(void)

{

LCD_STATUS ret =LCD_STATUS_OK;


memset(&_lcdContext, 0,sizeof(_lcdContext));//我们可以发现-lcdContext这个变量是一个数组,而且是static类型的,C语言中我们定义了这种类型的变量的话,那么就会为这个变量分配一个地址


// LCD controller would NOTreset register as default values

// Do it by SW here

//

ResetBackupedLCDRegisterValues();这里我们会为上面的_lcdContext.regBackup里面的值进行赋值,我们所用的是LCD_OUTREG32,函数进行赋值的,开始我一直以为这个变量根本就没有赋值啊,我往哪里的地址写呢,换位思考下,我们定义了一个变量后就肯定有地址,向地址写值就是将这个变量所指向的指针写值,也就是赋值。regs->SERIAL_CFG;regs->PARALLEL_CFG[0];regs->PARALLEL_CFG[1];regs->PARALLEL_CFG[2]


ret =LCD_PowerOn();//通过配置regiter的值将LCD打开,这里就不具体纠结这个细节了,要深入的话,可以自己去看下。


LCD_OUTREG32(&LCD_REG->SYNC_LCM_SIZE,0x00010001);

LCD_OUTREG32(&LCD_REG->SYNC_CNT,0x1);


ASSERT(ret == LCD_STATUS_OK);


#ifENABLE_LCD_INTERRUPT

if(request_irq(MT6577_LCD_IRQ_ID,

_LCD_InterruptHandler,IRQF_TRIGGER_LOW, "mtklcd", NULL) < 0)//申请LCD的中断处理函数,当有新的数据需要刷新到屏上面的时候,我们就会调用这个中断处理函数,关于这个中断处理函数我们下面会讲解。这里我一直很奇怪的是这个interrupt到底做了什么事情,接下来我会联系lcm_update函数进行统一讲解,这样我就能够将所有的流程串起来了。

{

DBI_LOG("[LCD][ERROR]fail to request LCD irq\n");

return LCD_STATUS_ERROR;

}

// mt65xx_irq_unmask(MT6577_LCD_IRQ_ID);//下面是设置一些寄存器的值

// enable_irq(MT6577_LCD_IRQ_ID);

init_waitqueue_head(&_lcd_wait_queue);

LCD_REG->INT_ENABLE.COMPLETED= 1;

// LCD_REG->INT_ENABLE.REG_COMPLETED= 1;

LCD_REG->INT_ENABLE.CMDQ_COMPLETED= 1;

LCD_REG->INT_ENABLE.HTT= 1;

LCD_REG->INT_ENABLE.SYNC =1;

#endif


return LCD_STATUS_OK;

}

2.2:disp_drv_get_lcm_driver(lcm_name)

disp_drv_get_lcm_driver(lcm_name):这会将我们在uboot中得到的Lcm的名字传递过来。

constLCM_DRIVER *disp_drv_get_lcm_driver(const char *lcm_name)

{

LCM_DRIVER*lcm = NULL;

printk("[LCMAuto Detect], we have %d lcm drivers built in\n", lcm_count);

printk("[LCMAuto Detect], try to find driver for [%s]\n",

(lcm_name==NULL)?"unknown":lcm_name);


if(lcm_count==1)//进行判断Lcm_count的值,这个值是通过计算lcm_driver_list里面大小进行判断的,如果我们需要新添加一个新的lcm进去的话,那么就需要在这个数组里面添加新的IC厂商的lcm,添加代码的路径是在/meidatek/custom/common/kernel/lcm/mt65xx_lcm_list.c里面进行添加

{

//we need to verify whether the lcm is connected

//even there is only one lcm type defined

lcm= lcm_driver_list[0];//如果这个lcm_driver_list中只有一个Lcm的话,那么就默认的就只取地一个就可以了

lcm->set_util_funcs(&lcm_utils);//没一个lcm结构里面都自己对应的定义成员,这里会调用lcm->set_util_funcs函数,传递进去的参数是在disp_drv.c里面定义好的结构,这个结构是mediatek的自己实现的display_driver:/mediatek/source/kernel/driver/video/disp_drv.c,我们调用lcm.set_util_funcs函数就是完成将display_driver里面的结构赋值给lcm_util这个static结构。

lcm->get_params(&s_lcm_params);//调用Lcm.get_params函数将s_lcm_params这个变量进行初始化,所赋的值,我们都都在每一个lcm进行参数的初始化。


lcm_params= &s_lcm_params;

lcm_drv= lcm;


{

isLCMFound= TRUE;

}


printk("[LCMSpecified]\t[%s]\n", (lcm->name==NULL)?"unknown":lcm->name);


gotodone;

}

else

{

inti;


for(i= 0;i < lcm_count;i++)

{

lcm_params= &s_lcm_params;

lcm= lcm_driver_list;


printk("[LCMAuto Detect] [%d] - [%s]\t", i,(lcm->name==NULL)?"unknown":lcm->name);


lcm->set_util_funcs(&lcm_utils);

memset((void*)lcm_params,0, sizeof(LCM_PARAMS));

lcm->get_params(lcm_params);//上面的函数和count等于1的status是一样的,参考上面就可以了。


disp_drv_init_ctrl_if();//初始化dispaly的controlinterface有串口和并口等

disp_drv_set_driving_current(lcm_params);//从函数的命名定义上面,我们是在设置lcd的电流,但是我根据地址查询mediatek的socdatasheet并没有找到相关的定义。

disp_drv_init_io_pad(lcm_params);//查看datasheet是说在设置这个register就可以设置pin脚的值


if(lcm_name!= NULL)

{

if(!strcmp(lcm_name,lcm->name))//将我们从uboot中得到的lcm_name和我们没一个的lcm里面的name进行对比,如果一样的话,那么就代表我们已经找到了我们使用的Lcm

{

printk("\t\t[success]\n");

isLCMFound= TRUE;

lcm_drv= lcm;


gotodone;

}

else

{

printk("\t\t[fail]\n");

}

}

else

{

if(LCM_TYPE_DSI== lcm_params->type){

init_dsi(FALSE);//初始化dsi这种模式

}


if(lcm->compare_id!= NULL &&lcm->compare_id())如果发现我们的传递的lcm_name是NULL;并且Lcm_list又不止一个的话,那么就会调用没一个lcm的compare_id函数,这个函数是我们lcm里面实现好的,我这里用的是r61408这个IC屏,我们就看看这个lcm的compare_id函数是如何实现的。我们R61408这个IC里面直接是读取IC里面的devicecode register进行和这个LCM默认的值进行对比而得到的,当然不同deIC可能compare_id函数实现也不一样,只要满足一个判断的标准就可以了。

{

printk("\t\t[success]\n");

isLCMFound= TRUE;

lcm_drv= lcm;


gotodone;

}

else

{


if(LCM_TYPE_DSI== lcm_params->type)

DSI_Deinit();

printk("\t\t[fail]\n");

}

}

}

#ifdefHQ_PROJECT_A75

/*HQ ynn 2012-06-29 modified for no lcd poweron*/

if(FALSE== isLCMFound)// ynn

{

lcm= lcm_driver_list[0];

lcm->set_util_funcs(&lcm_utils);

lcm->get_params(&s_lcm_params);


lcm_params= &s_lcm_params;

lcm_drv= lcm;

isLCMFound= TRUE;

}

#endif


}

done:

returnlcm_drv;

}//当找到这个lcm的话,就将找到的lcm赋值给lc_drv;将获得的s_lcm_params赋值给lcm_params,并将isLCMFound这个标志变量设置为TRUE,代表我已经找到了LCM。

2.3:disp_dump_lcm_parameters(lcm_params)

disp_dump_lcm_parameters(lcm_params):将获得的lcm_params进行打印出来

2.4:disp_drv_init_context

disp_drv_init_context:


staticBOOL disp_drv_init_context(void)

{

if(disp_drv != NULL && lcm_drv != NULL){

returnTRUE;

}


if(!isLCMFound)

DISP_DetectDevice();//进而判断我们有没有找到设备,如果没有的话,调用这个函数再进一次找设备的过程,和上面的selectDevice函数是一样的


disp_drv_init_ctrl_if();//和上面的一样是在初始化control接口


switch(lcm_params->type)//下面是在根据我们的lcm的类型,取出disp_drv的值,这里我们会将我们属于哪一种模式的Lcm就将DISP_DRIVER这个结构赋值过去,这个代码的路径是在/mediatek/source/kernel/drivers/video/这个路径下面有每一种模式的结构的定义。

{

caseLCM_TYPE_DBI : disp_drv = DISP_GetDriverDBI(); break;

caseLCM_TYPE_DPI : disp_drv = DISP_GetDriverDPI(); break;

caseLCM_TYPE_DSI : disp_drv = DISP_GetDriverDSI(); break;

default: ASSERT(0);

}


if(!disp_drv) return FALSE;


returnTRUE;

}

到这里的话,我们的DISP_SelectDevice这个函数就已经讲解完了。那么我们的mtk_find_lcm_driver这个函数也就讲解完了。

在这里我们就返回到mtkfb.c中的Probe函数了,讲解下面的内容。

接下来:

MTK_FB_XRES = DISP_GetScreenWidth();

MTK_FB_YRES =DISP_GetScreenHeight();

fb_xres_update= MTK_FB_XRES;

fb_yres_update= MTK_FB_YRES;


printk("[MTKFB]XRES=%d, YRES=%d\n", MTK_FB_XRES, MTK_FB_YRES);


MTK_FB_BPP =DISP_GetScreenBpp();

MTK_FB_PAGES =DISP_GetPages();

init_waitqueue_head(&screen_update_wq);

上面是获得mtkfb的参数和创建等待队列。

screen_update_task= kthread_create(

screen_update_kthread,NULL, "screen_update_kthread");//创建一个线程,这个线程根据名字可以大概的猜测出来是用来update屏幕的内容的。

L既然看到了这个线程的话,那么就看下这个线程到底在干嘛吧?

screen_update_kthread:屏幕刷新的线程

screen_update_kthread

staticint screen_update_kthread(void *data)

{

structsched_param param = { .sched_priority = RTPM_PRIO_SCRN_UPDATE};//设置线程的优先级,下面会用到

sched_setscheduler(current,SCHED_RR, ¶m);设置当前线程的优先级别


for( ;; ) {

wait_event_interruptible(screen_update_wq,atoMIC_read(&has_pending_update));//这里是一个for的无限循环,我们将当前线程进入screen_update_wq等待队列,这个时候我们就会进入睡眠,既然有进入睡眠的函数,那么就有地方将他唤醒,在下面我们会讲解唤醒的函数。并且设置autio机制,只能被一个线程占有。

MTKFB_LOG("wqwakeup\n");

mtkfb_update_screen_impl();一旦这个线程被唤醒的话,那么就会调用这个函数。


atomic_set(&has_pending_update,0);

if (kthread_should_stop())

break;

}


return 0;

}

3.1:mtkfb_update_screen_impl

mtkfb_update_screen_impl

staticvoid mtkfb_update_screen_impl(void)

{

BOOLdown_sem = FALSE;

MTKFB_FUNC();


//printk("\n\n\n\n mtkfb_update_screen_impl in init current->pid\n\n\n\n",current->pid);


if(down_interruptible(&sem_overlay_buffer)){//尝试获得信号量,如果获得不成功的话,那么就进入睡眠

printk("[FB Driver]can't get semaphore in mtkfb_update_screen_impl()\n");

}

else{

down_sem= TRUE;

sem_overlay_buffer_cnt--;

}


#ifdefined(MTK_M4U_SUPPORT)

{

unsigned int i;

/// check if the MVAaddress is invalid, turn off the layer

for(i=0;i<FB_LAYER;i++){

if(LCD_IsLayerEnable((LCD_LAYER_ID)i)){

if(!MTKFB_SearchMVA(LCD_LayerGetAddress(i))){

LCD_LayerEnable((LCD_LAYER_ID)i,FALSE);

}

}

}

}

#endif


DISP_CHECK_RET(DISP_UpdateScreen(0,0, fb_xres_update, fb_yres_update));//调用DISP_UpdateScreen函数进行update


if(down_sem){

sem_overlay_buffer_cnt++;

up(&sem_overlay_buffer);

}

}

3.1.2:DISP_STATUSDISP_UpdateScreen

DISP_STATUSDISP_UpdateScreen(UINT32 x, UINT32 y, UINT32 width, UINT32 height)

{

DISP_LOG("updatescreen, (%d,%d),(%d,%d)\n", x, y, width, height);

if(down_interruptible(&sem_update_screen)) {

printk("ERROR:Can't get sem_update_screen in DISP_UpdateScreen()\n");

returnDISP_STATUS_ERROR;

}

#ifdefined(MTK_LCD_HW_3D_SUPPORT)//判断我们的屏幕是否支持3D效果

LCD_CHECK_RET(DISP_Set3DPWM(DISP_Is3DEnabled(), DISP_is3DLandscapeMode() ));

#endif

//if LCM is powered down, LCD would never recieve the TE signal

//

if(is_lcm_in_suspend_mode || is_engine_in_suspend_mode) gotoEnd;//判断有没有进入suspend状态下


LCD_CHECK_RET(LCD_WaitForNotBusy());

if(lcm_params->type==LCM_TYPE_DSI&& lcm_params->dsi.mode == CMD_MODE)

DSI_CHECK_RET(DSI_WaitForNotBusy());


if(lcm_drv->update) {//调用lcm_drv.update函数进行更新参数,在下面会进行讲解

lcm_drv->update(x,y, width, height);

}


LCD_CHECK_RET(LCD_SetRoiWindow(x,y, width, height));//设置屏幕的显示窗口的大小


LCD_CHECK_RET(LCD_FBSetStartCoord(x,y));


if(-1 != direct_link_layer) {

//MT6516IDP_EnableDirectLink(); // FIXME

}else {

disp_drv->update_screen();//调用对应的displaydriver的update_screen函数在下面会进行讲解。

}


End:

up(&sem_update_screen);


returnDISP_STATUS_OK;

}

3.1.2.1:lcm_update

.update = lcm_update,

staticvoid lcm_update(unsigned int x, unsigned int y,

unsignedint width, unsigned int height)

{

unsignedint x0 = x;

unsignedint y0 = y;

unsignedint x1 = x0 + width - 1;

unsignedint y1 = y0 + height - 1;


unsignedchar x0_MSB = ((x0>>8)&0xFF);

unsignedchar x0_LSB = (x0&0xFF);

unsignedchar x1_MSB = ((x1>>8)&0xFF);

unsignedchar x1_LSB = (x1&0xFF);

unsignedchar y0_MSB = ((y0>>8)&0xFF);

unsignedchar y0_LSB = (y0&0xFF);

unsignedchar y1_MSB = ((y1>>8)&0xFF);

unsignedchar y1_LSB = (y1&0xFF);


unsignedint data_array[16];


data_array[0]=0x00053902;

data_array[1]=(x1_MSB<<24)|(x0_LSB<<16)|(x0_MSB<<8)|0x2a;

data_array[2]=(x1_LSB);

data_array[3]=0x00053902;

data_array[4]=(y1_MSB<<24)|(y0_LSB<<16)|(y0_MSB<<8)|0x2b;

data_array[5]=(y1_LSB);

data_array[6]=0x002c3909;

// data_array[6]=0x002c3901;

//上面是设置传输数据的信息,这些地址是写死的,当有数据的时候,我们就会触发DMA中断,DMA直接将数据放到目的地,我们就会将数据显示到LCD上面

dsi_set_cmdq(data_array,7, 0);//最终调用到dsi_set_cmdq函数


}

看下dsi_set_cmdq函数的实现:

#definedsi_set_cmdq(pdata, queue_size,force_update) lcm_util.dsi_set_cmdq(pdata, queue_size, force_update)

调用的是lcm_util.dsi_set_cmdq函数。

在disp_drv.c里面

.dsi_set_cmdq =(void (*)(unsigned int *, unsigned int, unsigned char))DSI_set_cmdq

上面的kthread_create函数只是在创建一个新的线程,但是这个线程不会立刻执行,需要调用wake_up_process函数,这个线程才会真正的进行执行,但是当执行的时候,我们会执行wait_event_interruptible(screen_update_wq,atomic_read(&has_pending_update));这个函数,如果atomic_read(&has_pending_update)这个contion不是true的话,那么就会将当前的线程加入screen_update_wq这个等待队列。所以这个线程又会进入睡眠了,不知道你们有没有想到如果进入睡眠的话,那么是谁在唤醒这个线程呢?对了就是我们在进面申请的中断,这个中断处理函数会进行唤醒这个线程将LCD进行update数据的动作。

4、LCD中断和Lcm_update函数之间的衔接

4.1:在上面的代码中我们讲解了kthread_create函数创建的线程在干嘛?下面继续我们的代码执行过程。

wake_up_process(screen_update_task);


{

///registerLCD complete interrupt callback

DISP_INTERRUPT_CALLBACK_STRUCTcbStruct;

cbStruct.pFunc= mtkfb_lcd_complete_interrupt;//为cbStruct.pFunc成员进行赋值,下面会使用这个里面的成员

cbStruct.pParam= NULL;

///regster callback

if(DISP_STATUS_OK !=DISP_SetInterruptCallback(DISP_LCD_TRANSFER_COMPLETE_INT,&cbStruct))//4.1.1会进行讲解这个函数在干嘛?,我们会将傻瓜你买你的cdStruct变量传递给这个函数

{

ASSERT(0);

}

}

4.1.1:DISP_SetInterruptCallback(DISP_LCD_TRANSFER_COMPLETE_INT,&cbStruct)

DISP_STATUSDISP_SetInterruptCallback(DISP_INTERRUPT_EVENTS eventID,DISP_INTERRUPT_CALLBACK_STRUCT *PCBStruct)

{

UINT32 offset;

ASSERT(pCBStruct != NULL);


disp_drv_init_context();//这个函数我们在前面的代码中已经讲解过了,这里就不再讲解了


if(eventID >=DISP_LCD_INTERRUPT_EVENTS_START && eventID <=DISP_LCD_INTERRUPT_EVENTS_END )

//我们会根据我们设置的id的不同。而进行不同的case的执行,我只讲解一种case,执行流程是一样的

{

///register callback

offset = eventID -DISP_LCD_INTERRUPT_EVENTS_START;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;//根据我们的不同的Id,我们为DISP_CallbackArray数组里面的成员赋值

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


LCD_CHECK_RET(LCD_SetInterruptCallback(_—DISP_InterruptCallbackProxy));//看下这个函数在做什么?LCD_STATUSLCD_SetInterruptCallback(void (*pCB)(DISP_INTERRUPT_EVENTS))

{ lcdContext.pIntCallback= pCB;


returnLCD_STATUS_OK;

}

//上面其实是将——DISP_InterruptCallbackProxy函数赋值给lcdContext.pIntCallback函数,这个函数我们会在LCD的中断处理函数中进行调用,下面我们看下这个函数到底在干嘛

LCD_CHECK_RET(LCD_EnableInterrupt(eventID));

}

else if(eventID >=DISP_DSI_INTERRUPT_EVENTS_START && eventID <=DISP_DSI_INTERRUPT_EVENTS_END )

{

///register callback

offset = eventID -DISP_DSI_INTERRUPT_EVENTS_START + DISP_LCD_INTERRUPT_EVENTS_NUMBER;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


DSI_CHECK_RET(DSI_SetInterruptCallback(_DISP_InterruptCallbackProxy));

DSI_CHECK_RET(DSI_EnableInterrupt(eventID));//根据我们的event的类型不同,我们去控制我们的LCD的寄存器

}

else if(eventID >=DISP_DPI_INTERRUPT_EVENTS_START && eventID <=DISP_DPI_INTERRUPT_EVENTS_END )

{

offset = eventID -DISP_DPI_INTERRUPT_EVENTS_START + DISP_LCD_INTERRUPT_EVENTS_NUMBER +DISP_DSI_INTERRUPT_EVENTS_NUMBER;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


DPI_CHECK_RET(DPI_SetInterruptCallback(_DISP_InterruptCallbackProxy));

DPI_CHECK_RET(DPI_EnableInterrupt(eventID));

}

else

{

DISP_LOG("Invalidevent id: %d\n", eventID);

ASSERT(0);

return DISP_STATUS_ERROR; ///TODO: error log

}

return DISP_STATUS_OK;

}

4.1.1.1:_DISP_InterruptCallbackProxy

staticvoid _DISP_InterruptCallbackProxy(DISP_INTERRUPT_EVENTS eventID)

{

UINT32 offset;


if(eventID >=DISP_LCD_INTERRUPT_EVENTS_START && eventID <=DISP_LCD_INTERRUPT_EVENTS_END )

{

offset = eventID -DISP_LCD_INTERRUPT_EVENTS_START;

if(DISP_CallbackArray[offset].pFunc)

{

DISP_CallbackArray[offset].pFunc(DISP_CallbackArray[offset].pParam);

}

}

…...................

…..................

}

看到没有,我们会根据Id的值从DISP_CallbackArray数组中取对应的pFun函数,这个函数也就是cbStruct.pFunc =mtkfb_lcd_complete_interrupt;

cbStruct.pParam = NULL;

以上的函数和参数,再执行这个函数。

4.1.1.2:mtkfb_lcd_complete_interrupt

mtkfb_lcd_complete_interrupt:

staticvoid mtkfb_lcd_complete_interrupt(void *param)

{

if(atomic_read(&has_pending_update))

{

wake_up_interruptible(&screen_update_wq);//这里会进行唤醒我们在上面创建更新LCD的线程,

}


#ifdefined(MTK_HDMI_SUPPORT)

hdmi_source_buffer_switch();

if(is_hdmi_active())

{

hdmi_update();

}

#endif

到这里还没有将我们的Interrupt函数联系以来。下面来联系:

5:LCD的中断处理函数

我们再次回到/mediatek/platform/mt6577/kernel/driver/video/lcd_drv.c里面

request_irq(MT6577_LCD_IRQ_ID,_LCD_InterruptHandler,IRQF_TRIGGER_LOW, "mtklcd", NULL)

回顾下中断处理函数:

staticirqreturn_t _LCD_InterruptHandler(int irq, void *dev_id)

{

LCD_REG_INTERRUPT status =LCD_REG->INT_STATUS;


if (status.COMPLETED)

{

#ifdefCONFIG_MTPROF_APPLAUNCH // eng enable, user disable

LOG_PRINT(android_LOG_INFO,"AppLaunch", "LCD frame buffer update done !\n");

#endif

wake_up_interruptible(&_lcd_wait_queue);


if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_TRANSFER_COMPLETE_INT);


DBG_OnLcdDone();

}


if (status.SYNC)// this is TEmode 0 interrupt

{

if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_SYNC_INT);


DBG_OnTeDelayDone();

lcd_esd_check= false;

}

#if0 //TE mode 1

if(status.HTT)

{

if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_HTT_INT);//看到没有,我们这里就会调用pINtCallback函数,也就是我们在上面的mtkfb_lcd_complete_interrupt函数,只有调用了这个函数,那么我们的lcd更新画面的线程才会被调用


DBG_OnTeDelayDone();

}

#endif

LCD_OUTREG32(&LCD_REG->INT_STATUS,0);

return IRQ_HANDLED;

}

#endif

到目前为止,LCD的代码的执行的整个过程就已经讲解完了。


本文转自
http://blog.csdn.net/sunweizhong1024/article/details/8447915

路过,留着

多谢

学习学习。

此贴不错,

学习学习。

帖子不错,大家

很详细啊,无私的奉献

mark。

好东西,学习。

mtk 平台LCD 驱动的整个过程的讲解



MTK_LCD_Driver

代码的路是在/mediatek/source/kernel/driver/vedio/mtkfb.c

module_init(mtkfb_init);模块初始化函数

int__init mtkfb_init(void)

{

int r = 0;


MSG_FUNC_ENTER();



/* Register the driver withLDM */


if(platform_driver_register(&mtkfb_driver)) {//以platform方式进行注册mtkfb driver

PRNERR("failed toregister mtkfb driver\n");

r = -ENODEV;

goto exit;

}


#ifdefCONFIG_HAS_EARLYSUSPEND

register_early_suspend(&mtkfb_early_suspend_handler);//LCD是以erly_suspend的方式注册的,这个涉及到power_management的内容

#endif


DBG_Init();


exit:

MSG_FUNC_LEAVE();

return r;

知道设备模型的人应该知道platformbus总线的match函数的规则是device和driver的名字必须相同,当name匹配一样的时候,我们就会调用driver里面的probe函数,这个函数是LCDdriver的核心入口函数。

进入LCDprobe世界。

staticint mtkfb_probe(struct device *dev)

{

struct platform_device *pdev;

struct mtkfb_device *fbdev= NULL;

struct fb_info *fbi;

int init_state;

int r = 0;

char*p = NULL;

MSG_FUNC_ENTER();


printk("%s,%s\n", __func__, saved_command_line);

p= strstr(saved_command_line, "fps=");

if(p== NULL){

lcd_fps= 6000;

printk("[FBdriver]can not get fps fROM uboot\n");

}

else{

p+= 4;

lcd_fps= SIMple_strtol(p, NULL, 10);

}


…........................

…........................


r = register_framebuffer(fbi);

if (r != 0) {

PRNERR("register_framebufferfailed\n");

goto cleanup;

}


fbdev->state =MTKFB_ACTIVE;


MSG(INFO, "MTKframebuffer initialized vram=%lu\n", fbdev->fb_size_in_byte);


MSG_FUNC_LEAVE();

return 0;


cleanup:

mtkfb_free_resources(fbdev,init_state);


MSG_FUNC_LEAVE();

return r;

}

这个函数比较长,下面我们一一对这个Probe函数进行讲解。

1、开始driver接受从uboot中传递过来的参数,saved_command_line变量,进行参数的取值。

2、DISP_IsContextInited这个函数判断LCM_params、disp_drv、lcm_drv是不是都进行初始化了,如果都初始化了,那么就返回TRUE如果有一个没有进行初始化的话,那么就返回FALSE。其实这里在uboot中已经进行了初始化了,我们这里假设这里还没有初始化,如果是FALSE的话,那么就会调用mtkfb_find_lcm_driver这个function函数。

mtkfb_find_lcm_driver:

BOOLmtkfb_find_lcm_driver(void)

{

BOOLret = FALSE;

char*p, *q;


p= strstr(saved_command_line,"lcm=");//这里我们会找出”lcm=”这个字符串在saved_command_line中第一次出现的位置,取出这个指针。

if(p== NULL)

{

//we can't find lcm string in the command line, the uboot should be oldversion

returnDISP_SelectDevice(NULL);

}


p+= 4;

if((p- saved_command_line) > strlen(saved_command_line+1))

{

ret= FALSE;

gotodone;

}


printk("%s,%s\n", __func__, p);

q= p;

while(*q!= ' ' && *q != '\0')

q++;


mEMSet((void*)mtkfb_lcm_name,0, sizeof(mtkfb_lcm_name));

strncpy((char*)mtkfb_lcm_name,(const char*)p, (int)(q-p));//这里会找出lcm的名字,接下来会将这个Name股指给mtkfb_lcm_name这个变量,下面会将这个变量传递给mtkfb_lcm_name这个函数。


printk("%s,%s\n", __func__, mtkfb_lcm_name);

if(DISP_SelectDevice(mtkfb_lcm_name))

ret= TRUE;


done:

returnret;

}

continuetrace code:DISP_SelectDevice(mtkfb_lcm_name)

DISP_SelectDevice(mtkfb_lcm_name):

BOOLDISP_SelectDevice(const char* lcm_name)

{

LCD_STATUSret;


ret= LCD_Init();

printk("retof LCD_Init() = %d\n", ret);


lcm_drv= disp_drv_get_lcm_driver(lcm_name);

if(NULL == lcm_drv)

{

printk("%s,disp_drv_get_lcm_driver() returns NULL\n", __func__);

returnFALSE;

}


disp_dump_lcm_parameters(lcm_params);

returndisp_drv_init_context();

}

上面的函数还是比较复杂的,我们下面会进行一一的讲解:

2.1:LCD_Init

LCD_STATUSLCD_Init(void)

{

LCD_STATUS ret =LCD_STATUS_OK;


memset(&_lcdContext, 0,sizeof(_lcdContext));//我们可以发现-lcdContext这个变量是一个数组,而且是static类型的,C语言中我们定义了这种类型的变量的话,那么就会为这个变量分配一个地址


// LCD controller would NOTreset register as default values

// Do it by SW here

//

ResetBackupedLCDRegisterValues();这里我们会为上面的_lcdContext.regBackup里面的值进行赋值,我们所用的是LCD_OUTREG32,函数进行赋值的,开始我一直以为这个变量根本就没有赋值啊,我往哪里的地址写呢,换位思考下,我们定义了一个变量后就肯定有地址,向地址写值就是将这个变量所指向的指针写值,也就是赋值。regs->SERIAL_CFG;regs->PARALLEL_CFG[0];regs->PARALLEL_CFG[1];regs->PARALLEL_CFG[2]


ret =LCD_PowerOn();//通过配置regiter的值将LCD打开,这里就不具体纠结这个细节了,要深入的话,可以自己去看下。


LCD_OUTREG32(&LCD_REG->SYNC_LCM_SIZE,0x00010001);

LCD_OUTREG32(&LCD_REG->SYNC_CNT,0x1);


ASSERT(ret == LCD_STATUS_OK);


#ifENABLE_LCD_INTERRUPT

if(request_irq(MT6577_LCD_IRQ_ID,

_LCD_InterruptHandler,IRQF_TRIGGER_LOW, "mtklcd", NULL) < 0)//申请LCD的中断处理函数,当有新的数据需要刷新到屏上面的时候,我们就会调用这个中断处理函数,关于这个中断处理函数我们下面会讲解。这里我一直很奇怪的是这个interrupt到底做了什么事情,接下来我会联系lcm_update函数进行统一讲解,这样我就能够将所有的流程串起来了。

{

DBI_LOG("[LCD][ERROR]fail to request LCD irq\n");

return LCD_STATUS_ERROR;

}

// mt65xx_irq_unmask(MT6577_LCD_IRQ_ID);//下面是设置一些寄存器的值

// enable_irq(MT6577_LCD_IRQ_ID);

init_waitqueue_head(&_lcd_wait_queue);

LCD_REG->INT_ENABLE.COMPLETED= 1;

// LCD_REG->INT_ENABLE.REG_COMPLETED= 1;

LCD_REG->INT_ENABLE.CMDQ_COMPLETED= 1;

LCD_REG->INT_ENABLE.HTT= 1;

LCD_REG->INT_ENABLE.SYNC =1;

#endif


return LCD_STATUS_OK;

}

2.2:disp_drv_get_lcm_driver(lcm_name)

disp_drv_get_lcm_driver(lcm_name):这会将我们在uboot中得到的Lcm的名字传递过来。

constLCM_DRIVER *disp_drv_get_lcm_driver(const char *lcm_name)

{

LCM_DRIVER*lcm = NULL;

printk("[LCMAuto Detect], we have %d lcm drivers built in\n", lcm_count);

printk("[LCMAuto Detect], try to find driver for [%s]\n",

(lcm_name==NULL)?"unknown":lcm_name);


if(lcm_count==1)//进行判断Lcm_count的值,这个值是通过计算lcm_driver_list里面大小进行判断的,如果我们需要新添加一个新的lcm进去的话,那么就需要在这个数组里面添加新的IC厂商的lcm,添加代码的路径是在/meidatek/custom/common/kernel/lcm/mt65xx_lcm_list.c里面进行添加

{

//we need to verify whether the lcm is connected

//even there is only one lcm type defined

lcm= lcm_driver_list[0];//如果这个lcm_driver_list中只有一个Lcm的话,那么就默认的就只取地一个就可以了

lcm->set_util_funcs(&lcm_utils);//没一个lcm结构里面都自己对应的定义成员,这里会调用lcm->set_util_funcs函数,传递进去的参数是在disp_drv.c里面定义好的结构,这个结构是mediatek的自己实现的display_driver:/mediatek/source/kernel/driver/video/disp_drv.c,我们调用lcm.set_util_funcs函数就是完成将display_driver里面的结构赋值给lcm_util这个static结构。

lcm->get_params(&s_lcm_params);//调用Lcm.get_params函数将s_lcm_params这个变量进行初始化,所赋的值,我们都都在每一个lcm进行参数的初始化。


lcm_params= &s_lcm_params;

lcm_drv= lcm;


{

isLCMFound= TRUE;

}


printk("[LCMSpecified]\t[%s]\n", (lcm->name==NULL)?"unknown":lcm->name);


gotodone;

}

else

{

inti;


for(i= 0;i < lcm_count;i++)

{

lcm_params= &s_lcm_params;

lcm= lcm_driver_list;


printk("[LCMAuto Detect] [%d] - [%s]\t", i,(lcm->name==NULL)?"unknown":lcm->name);


lcm->set_util_funcs(&lcm_utils);

memset((void*)lcm_params,0, sizeof(LCM_PARAMS));

lcm->get_params(lcm_params);//上面的函数和count等于1的status是一样的,参考上面就可以了。


disp_drv_init_ctrl_if();//初始化dispaly的controlinterface有串口和并口等

disp_drv_set_driving_current(lcm_params);//从函数的命名定义上面,我们是在设置lcd的电流,但是我根据地址查询mediatek的socdatasheet并没有找到相关的定义。

disp_drv_init_io_pad(lcm_params);//查看datasheet是说在设置这个register就可以设置pin脚的值


if(lcm_name!= NULL)

{

if(!strcmp(lcm_name,lcm->name))//将我们从uboot中得到的lcm_name和我们没一个的lcm里面的name进行对比,如果一样的话,那么就代表我们已经找到了我们使用的Lcm

{

printk("\t\t[success]\n");

isLCMFound= TRUE;

lcm_drv= lcm;


gotodone;

}

else

{

printk("\t\t[fail]\n");

}

}

else

{

if(LCM_TYPE_DSI== lcm_params->type){

init_dsi(FALSE);//初始化dsi这种模式

}


if(lcm->compare_id!= NULL &&lcm->compare_id())如果发现我们的传递的lcm_name是NULL;并且Lcm_list又不止一个的话,那么就会调用没一个lcm的compare_id函数,这个函数是我们lcm里面实现好的,我这里用的是r61408这个IC屏,我们就看看这个lcm的compare_id函数是如何实现的。我们R61408这个IC里面直接是读取IC里面的devicecode register进行和这个LCM默认的值进行对比而得到的,当然不同deIC可能compare_id函数实现也不一样,只要满足一个判断的标准就可以了。

{

printk("\t\t[success]\n");

isLCMFound= TRUE;

lcm_drv= lcm;


gotodone;

}

else

{


if(LCM_TYPE_DSI== lcm_params->type)

DSI_Deinit();

printk("\t\t[fail]\n");

}

}

}

#ifdefHQ_PROJECT_A75

/*HQ ynn 2012-06-29 modified for no lcd poweron*/

if(FALSE== isLCMFound)// ynn

{

lcm= lcm_driver_list[0];

lcm->set_util_funcs(&lcm_utils);

lcm->get_params(&s_lcm_params);


lcm_params= &s_lcm_params;

lcm_drv= lcm;

isLCMFound= TRUE;

}

#endif


}

done:

returnlcm_drv;

}//当找到这个lcm的话,就将找到的lcm赋值给lc_drv;将获得的s_lcm_params赋值给lcm_params,并将isLCMFound这个标志变量设置为TRUE,代表我已经找到了LCM。

2.3:disp_dump_lcm_parameters(lcm_params)

disp_dump_lcm_parameters(lcm_params):将获得的lcm_params进行打印出来

2.4:disp_drv_init_context

disp_drv_init_context:


staticBOOL disp_drv_init_context(void)

{

if(disp_drv != NULL && lcm_drv != NULL){

returnTRUE;

}


if(!isLCMFound)

DISP_DetectDevice();//进而判断我们有没有找到设备,如果没有的话,调用这个函数再进一次找设备的过程,和上面的selectDevice函数是一样的


disp_drv_init_ctrl_if();//和上面的一样是在初始化control接口


switch(lcm_params->type)//下面是在根据我们的lcm的类型,取出disp_drv的值,这里我们会将我们属于哪一种模式的Lcm就将DISP_DRIVER这个结构赋值过去,这个代码的路径是在/mediatek/source/kernel/drivers/video/这个路径下面有每一种模式的结构的定义。

{

caseLCM_TYPE_DBI : disp_drv = DISP_GetDriverDBI(); break;

caseLCM_TYPE_DPI : disp_drv = DISP_GetDriverDPI(); break;

caseLCM_TYPE_DSI : disp_drv = DISP_GetDriverDSI(); break;

default: ASSERT(0);

}


if(!disp_drv) return FALSE;


returnTRUE;

}

到这里的话,我们的DISP_SelectDevice这个函数就已经讲解完了。那么我们的mtk_find_lcm_driver这个函数也就讲解完了。

在这里我们就返回到mtkfb.c中的Probe函数了,讲解下面的内容。

接下来:

MTK_FB_XRES = DISP_GetScreenWidth();

MTK_FB_YRES =DISP_GetScreenHeight();

fb_xres_update= MTK_FB_XRES;

fb_yres_update= MTK_FB_YRES;


printk("[MTKFB]XRES=%d, YRES=%d\n", MTK_FB_XRES, MTK_FB_YRES);


MTK_FB_BPP =DISP_GetScreenBpp();

MTK_FB_PAGES =DISP_GetPages();

init_waitqueue_head(&screen_update_wq);

上面是获得mtkfb的参数和创建等待队列。

screen_update_task= kthread_create(

screen_update_kthread,NULL, "screen_update_kthread");//创建一个线程,这个线程根据名字可以大概的猜测出来是用来update屏幕的内容的。

L既然看到了这个线程的话,那么就看下这个线程到底在干嘛吧?

screen_update_kthread:屏幕刷新的线程

screen_update_kthread

staticint screen_update_kthread(void *data)

{

structsched_param param = { .sched_priority = RTPM_PRIO_SCRN_UPDATE};//设置线程的优先级,下面会用到

sched_setscheduler(current,SCHED_RR, ¶m);设置当前线程的优先级别


for( ;; ) {

wait_event_interruptible(screen_update_wq,atoMIC_read(&has_pending_update));//这里是一个for的无限循环,我们将当前线程进入screen_update_wq等待队列,这个时候我们就会进入睡眠,既然有进入睡眠的函数,那么就有地方将他唤醒,在下面我们会讲解唤醒的函数。并且设置autio机制,只能被一个线程占有。

MTKFB_LOG("wqwakeup\n");

mtkfb_update_screen_impl();一旦这个线程被唤醒的话,那么就会调用这个函数。


atomic_set(&has_pending_update,0);

if (kthread_should_stop())

break;

}


return 0;

}

3.1:mtkfb_update_screen_impl

mtkfb_update_screen_impl

staticvoid mtkfb_update_screen_impl(void)

{

BOOLdown_sem = FALSE;

MTKFB_FUNC();


//printk("\n\n\n\n mtkfb_update_screen_impl in init current->pid\n\n\n\n",current->pid);


if(down_interruptible(&sem_overlay_buffer)){//尝试获得信号量,如果获得不成功的话,那么就进入睡眠

printk("[FB Driver]can't get semaphore in mtkfb_update_screen_impl()\n");

}

else{

down_sem= TRUE;

sem_overlay_buffer_cnt--;

}


#ifdefined(MTK_M4U_SUPPORT)

{

unsigned int i;

/// check if the MVAaddress is invalid, turn off the layer

for(i=0;i<FB_LAYER;i++){

if(LCD_IsLayerEnable((LCD_LAYER_ID)i)){

if(!MTKFB_SearchMVA(LCD_LayerGetAddress(i))){

LCD_LayerEnable((LCD_LAYER_ID)i,FALSE);

}

}

}

}

#endif


DISP_CHECK_RET(DISP_UpdateScreen(0,0, fb_xres_update, fb_yres_update));//调用DISP_UpdateScreen函数进行update


if(down_sem){

sem_overlay_buffer_cnt++;

up(&sem_overlay_buffer);

}

}

3.1.2:DISP_STATUSDISP_UpdateScreen

DISP_STATUSDISP_UpdateScreen(UINT32 x, UINT32 y, UINT32 width, UINT32 height)

{

DISP_LOG("updatescreen, (%d,%d),(%d,%d)\n", x, y, width, height);

if(down_interruptible(&sem_update_screen)) {

printk("ERROR:Can't get sem_update_screen in DISP_UpdateScreen()\n");

returnDISP_STATUS_ERROR;

}

#ifdefined(MTK_LCD_HW_3D_SUPPORT)//判断我们的屏幕是否支持3D效果

LCD_CHECK_RET(DISP_Set3DPWM(DISP_Is3DEnabled(), DISP_is3DLandscapeMode() ));

#endif

//if LCM is powered down, LCD would never recieve the TE signal

//

if(is_lcm_in_suspend_mode || is_engine_in_suspend_mode) gotoEnd;//判断有没有进入suspend状态下


LCD_CHECK_RET(LCD_WaitForNotBusy());

if(lcm_params->type==LCM_TYPE_DSI&& lcm_params->dsi.mode == CMD_MODE)

DSI_CHECK_RET(DSI_WaitForNotBusy());


if(lcm_drv->update) {//调用lcm_drv.update函数进行更新参数,在下面会进行讲解

lcm_drv->update(x,y, width, height);

}


LCD_CHECK_RET(LCD_SetRoiWindow(x,y, width, height));//设置屏幕的显示窗口的大小


LCD_CHECK_RET(LCD_FBSetStartCoord(x,y));


if(-1 != direct_link_layer) {

//MT6516IDP_EnableDirectLink(); // FIXME

}else {

disp_drv->update_screen();//调用对应的displaydriver的update_screen函数在下面会进行讲解。

}


End:

up(&sem_update_screen);


returnDISP_STATUS_OK;

}

3.1.2.1:lcm_update

.update = lcm_update,

staticvoid lcm_update(unsigned int x, unsigned int y,

unsignedint width, unsigned int height)

{

unsignedint x0 = x;

unsignedint y0 = y;

unsignedint x1 = x0 + width - 1;

unsignedint y1 = y0 + height - 1;


unsignedchar x0_MSB = ((x0>>8)&0xFF);

unsignedchar x0_LSB = (x0&0xFF);

unsignedchar x1_MSB = ((x1>>8)&0xFF);

unsignedchar x1_LSB = (x1&0xFF);

unsignedchar y0_MSB = ((y0>>8)&0xFF);

unsignedchar y0_LSB = (y0&0xFF);

unsignedchar y1_MSB = ((y1>>8)&0xFF);

unsignedchar y1_LSB = (y1&0xFF);


unsignedint data_array[16];


data_array[0]=0x00053902;

data_array[1]=(x1_MSB<<24)|(x0_LSB<<16)|(x0_MSB<<8)|0x2a;

data_array[2]=(x1_LSB);

data_array[3]=0x00053902;

data_array[4]=(y1_MSB<<24)|(y0_LSB<<16)|(y0_MSB<<8)|0x2b;

data_array[5]=(y1_LSB);

data_array[6]=0x002c3909;

// data_array[6]=0x002c3901;

//上面是设置传输数据的信息,这些地址是写死的,当有数据的时候,我们就会触发DMA中断,DMA直接将数据放到目的地,我们就会将数据显示到LCD上面

dsi_set_cmdq(data_array,7, 0);//最终调用到dsi_set_cmdq函数


}

看下dsi_set_cmdq函数的实现:

#definedsi_set_cmdq(pdata, queue_size,force_update) lcm_util.dsi_set_cmdq(pdata, queue_size, force_update)

调用的是lcm_util.dsi_set_cmdq函数。

在disp_drv.c里面

.dsi_set_cmdq =(void (*)(unsigned int *, unsigned int, unsigned char))DSI_set_cmdq

上面的kthread_create函数只是在创建一个新的线程,但是这个线程不会立刻执行,需要调用wake_up_process函数,这个线程才会真正的进行执行,但是当执行的时候,我们会执行wait_event_interruptible(screen_update_wq,atomic_read(&has_pending_update));这个函数,如果atomic_read(&has_pending_update)这个contion不是true的话,那么就会将当前的线程加入screen_update_wq这个等待队列。所以这个线程又会进入睡眠了,不知道你们有没有想到如果进入睡眠的话,那么是谁在唤醒这个线程呢?对了就是我们在进面申请的中断,这个中断处理函数会进行唤醒这个线程将LCD进行update数据的动作。

4、LCD中断和Lcm_update函数之间的衔接

4.1:在上面的代码中我们讲解了kthread_create函数创建的线程在干嘛?下面继续我们的代码执行过程。

wake_up_process(screen_update_task);


{

///registerLCD complete interrupt callback

DISP_INTERRUPT_CALLBACK_STRUCTcbStruct;

cbStruct.pFunc= mtkfb_lcd_complete_interrupt;//为cbStruct.pFunc成员进行赋值,下面会使用这个里面的成员

cbStruct.pParam= NULL;

///regster callback

if(DISP_STATUS_OK !=DISP_SetInterruptCallback(DISP_LCD_TRANSFER_COMPLETE_INT,&cbStruct))//4.1.1会进行讲解这个函数在干嘛?,我们会将傻瓜你买你的cdStruct变量传递给这个函数

{

ASSERT(0);

}

}

4.1.1:DISP_SetInterruptCallback(DISP_LCD_TRANSFER_COMPLETE_INT,&cbStruct)

DISP_STATUSDISP_SetInterruptCallback(DISP_INTERRUPT_EVENTS eventID,DISP_INTERRUPT_CALLBACK_STRUCT *PCBStruct)

{

UINT32 offset;

ASSERT(pCBStruct != NULL);


disp_drv_init_context();//这个函数我们在前面的代码中已经讲解过了,这里就不再讲解了


if(eventID >=DISP_LCD_INTERRUPT_EVENTS_START && eventID <=DISP_LCD_INTERRUPT_EVENTS_END )

//我们会根据我们设置的id的不同。而进行不同的case的执行,我只讲解一种case,执行流程是一样的

{

///register callback

offset = eventID -DISP_LCD_INTERRUPT_EVENTS_START;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;//根据我们的不同的Id,我们为DISP_CallbackArray数组里面的成员赋值

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


LCD_CHECK_RET(LCD_SetInterruptCallback(_—DISP_InterruptCallbackProxy));//看下这个函数在做什么?LCD_STATUSLCD_SetInterruptCallback(void (*pCB)(DISP_INTERRUPT_EVENTS))

{ lcdContext.pIntCallback= pCB;


returnLCD_STATUS_OK;

}

//上面其实是将——DISP_InterruptCallbackProxy函数赋值给lcdContext.pIntCallback函数,这个函数我们会在LCD的中断处理函数中进行调用,下面我们看下这个函数到底在干嘛

LCD_CHECK_RET(LCD_EnableInterrupt(eventID));

}

else if(eventID >=DISP_DSI_INTERRUPT_EVENTS_START && eventID <=DISP_DSI_INTERRUPT_EVENTS_END )

{

///register callback

offset = eventID -DISP_DSI_INTERRUPT_EVENTS_START + DISP_LCD_INTERRUPT_EVENTS_NUMBER;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


DSI_CHECK_RET(DSI_SetInterruptCallback(_DISP_InterruptCallbackProxy));

DSI_CHECK_RET(DSI_EnableInterrupt(eventID));//根据我们的event的类型不同,我们去控制我们的LCD的寄存器

}

else if(eventID >=DISP_DPI_INTERRUPT_EVENTS_START && eventID <=DISP_DPI_INTERRUPT_EVENTS_END )

{

offset = eventID -DISP_DPI_INTERRUPT_EVENTS_START + DISP_LCD_INTERRUPT_EVENTS_NUMBER +DISP_DSI_INTERRUPT_EVENTS_NUMBER;

DISP_CallbackArray[offset].pFunc= pCBStruct->pFunc;

DISP_CallbackArray[offset].pParam= pCBStruct->pParam;


DPI_CHECK_RET(DPI_SetInterruptCallback(_DISP_InterruptCallbackProxy));

DPI_CHECK_RET(DPI_EnableInterrupt(eventID));

}

else

{

DISP_LOG("Invalidevent id: %d\n", eventID);

ASSERT(0);

return DISP_STATUS_ERROR; ///TODO: error log

}

return DISP_STATUS_OK;

}

4.1.1.1:_DISP_InterruptCallbackProxy

staticvoid _DISP_InterruptCallbackProxy(DISP_INTERRUPT_EVENTS eventID)

{

UINT32 offset;


if(eventID >=DISP_LCD_INTERRUPT_EVENTS_START && eventID <=DISP_LCD_INTERRUPT_EVENTS_END )

{

offset = eventID -DISP_LCD_INTERRUPT_EVENTS_START;

if(DISP_CallbackArray[offset].pFunc)

{

DISP_CallbackArray[offset].pFunc(DISP_CallbackArray[offset].pParam);

}

}

…...................

…..................

}

看到没有,我们会根据Id的值从DISP_CallbackArray数组中取对应的pFun函数,这个函数也就是cbStruct.pFunc =mtkfb_lcd_complete_interrupt;

cbStruct.pParam = NULL;

以上的函数和参数,再执行这个函数。

4.1.1.2:mtkfb_lcd_complete_interrupt

mtkfb_lcd_complete_interrupt:

staticvoid mtkfb_lcd_complete_interrupt(void *param)

{

if(atomic_read(&has_pending_update))

{

wake_up_interruptible(&screen_update_wq);//这里会进行唤醒我们在上面创建更新LCD的线程,

}


#ifdefined(MTK_HDMI_SUPPORT)

hdmi_source_buffer_switch();

if(is_hdmi_active())

{

hdmi_update();

}

#endif

到这里还没有将我们的Interrupt函数联系以来。下面来联系:

5:LCD的中断处理函数

我们再次回到/mediatek/platform/mt6577/kernel/driver/video/lcd_drv.c里面

request_irq(MT6577_LCD_IRQ_ID,_LCD_InterruptHandler,IRQF_TRIGGER_LOW, "mtklcd", NULL)

回顾下中断处理函数:

staticirqreturn_t _LCD_InterruptHandler(int irq, void *dev_id)

{

LCD_REG_INTERRUPT status =LCD_REG->INT_STATUS;


if (status.COMPLETED)

{

#ifdefCONFIG_MTPROF_APPLAUNCH // eng enable, user disable

LOG_PRINT(android_LOG_INFO,"AppLaunch", "LCD frame buffer update done !\n");

#endif

wake_up_interruptible(&_lcd_wait_queue);


if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_TRANSFER_COMPLETE_INT);


DBG_OnLcdDone();

}


if (status.SYNC)// this is TEmode 0 interrupt

{

if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_SYNC_INT);


DBG_OnTeDelayDone();

lcd_esd_check= false;

}

#if0 //TE mode 1

if(status.HTT)

{

if(_lcdContext.pIntCallback)

_lcdContext.pIntCallback(DISP_LCD_HTT_INT);//看到没有,我们这里就会调用pINtCallback函数,也就是我们在上面的mtkfb_lcd_complete_interrupt函数,只有调用了这个函数,那么我们的lcd更新画面的线程才会被调用


DBG_OnTeDelayDone();

}

#endif

LCD_OUTREG32(&LCD_REG->INT_STATUS,0);

return IRQ_HANDLED;

}

#endif

到目前为止,LCD的代码的执行的整个过程就已经讲解完了。


本文转自
http://blog.csdn.net/sunweizhong1024/article/details/8447915

路过,留着

多谢

学习学习。

此贴不错,

学习学习。

帖子不错,大家

很详细啊,无私的奉献

mark。

好东西,学习。

Copyright © 2017-2020 微波EDA网 版权所有

网站地图

Top