HDMI(High-DefinitionMultimediaInterface)又被称为高清晰度多媒体接口,是首个支持在单线缆上传输,不经过压缩的全数字高清晰度、多声道音频和智能格式与控制命令数据的数字接口。HDMI接口由SiliconImage美国晶像公司倡导,联合索尼、日立、松下、飞利浦、汤姆逊、东芝等八家著名的消费类电子制造商联合成立的工作组共同开发的。
MobileHigh-Definition Link (MHL) 移动终端高清影音标准接口,是一种连接便携式消费电子装置的影音标准接口,MHL仅使用一条信号电缆,通过标准HDMI输入接口即可呈现于高清电视上。它运用了现有的MicroUSB接口,不论是手机、数码相机、数字摄影机皆可将完整的媒体内容直接传输到电视上且不损伤影片高分辨率的效果。
HDMI驱动涉及的驱动:hdmitx虚拟Platform驱动、FB_Buffer驱动、Mhal驱动、dispsys驱动、(DSI、DBI、DPI)总线驱动、LCM驱动。
Hdmitx虚拟驱动主要是为了管理Mhl和HDMIIC驱动而创建的驱动,MDMI和MHL驱动主要提供一些接口,IC只要实现这些接口,我们就可以调用这些接口进行操作。Dispsys驱动,我感觉是MTK自己的和PlatformDisp_controner驱动(MTK讲平台相关的代码独立开来,方便开发,这里只是我个人的理解)。
接下来,我主要讲解的是HDMI(MHL)驱动整个触发过程以及如何上报Event给上层。
mediatek/kernel/drivers/hdmitx/
staticint __init hdmi_init(void)
{
intret = 0;
printk("[hdmi]%s\n",__func__);
if(platform_driver_register(&hdmi_driver))
{
printk("[hdmi]failedto register mtkfb driver\n");
return-1;
}
…………………………………………………………………………………………………
init_hdmi_mmp_events();
if(!hdmi_drv_init_context())
{
printk("[hdmi]%s,hdmi_drv_init_context fail\n", __func__);
returnHDMI_STATUS_NOT_IMPLEMENTED;
}
p->output_mode= hdmi_params->output_mode;
hdmi_drv->init();
HDMI_LOG("Outputmode is %s\n",(hdmi_params->output_mode==HDMI_OUTPUT_MODE_DPI_BYPASS)?"HDMI_OUTPUT_MODE_DPI_BYPASS":"HDMI_OUTPUT_MODE_LCD_MIRROR");
…………………………………………………………………………………….
HDMI_DBG_Init();
hdmi_switch_data.name= "hdmi";
hdmi_switch_data.index= 0;
hdmi_switch_data.state= NO_DEVICE;
//for support hdmi hotplug, inform AP the event
ret=switch_dev_register(&hdmi_switch_data);
……………………………………………………………………………………………………………….
}
module_init(hdmi_init);
上面是hdmi虚拟模块驱动加载函数。上面会Platform方式进行注册一个设备,对PlatformBus 了解的应该不难(bus的匹配规则是名字相同,一旦名字匹配成功就会调用Driverprobe函数)。下面看下这个probe函数到底做了哪些事情。
staticint hdmi_probe(struct platform_device *pdev)
{
intret = 0;
structclass_device *class_dev = NULL;
。。。。。。。。。。。。。。。。。。。
printk("[hdmi]alloc_chrdev_regionfail\n");
return-1;
}
/*For character driver register to system, device number binded to fileoperations */
hdmi_cdev= cdev_alloc();
hdmi_cdev->owner= THIS_MODULE;
hdmi_cdev->ops= &hdmi_fops;
ret= cdev_add(hdmi_cdev, hdmi_devno, 1);
。。。。。。。。。。。。。。。。。。。。。。。。。。。
if(!hdmi_drv_init_context())
{
printk("[hdmi]%s, hdmi_drv_init_context fail\n",__func__);
returnHDMI_STATUS_NOT_IMPLEMENTED;
}
init_waitqueue_head(&hdmi_video_mode_wq);
INIT_LIST_HEAD(&hdmi_video_mode_buffer_list);
init_waitqueue_head(&hdmi_update_wq);
hdmi_update_task= kthread_create(hdmi_update_kthread, NULL, "hdmi_update_kthread");
wake_up_process(hdmi_update_task);
init_waitqueue_head(&hdmi_overlay_config_wq);
hdmi_overlay_config_task= kthread_create(hdmi_overlay_config_kthread, NULL,"hdmi_overlay_config_kthread");
wake_up_process(hdmi_overlay_config_task);
init_waitqueue_head(&hdmi_rdma_config_wq);
init_waitqueue_head(®_update_wq);
init_waitqueue_head(&dst_reg_update_wq);
return0;
}
Probe函数主要是创建了HDMI的设备、调用hdmi_drv_init_context获得具体的MHL(HDMI)IC实现的函数列表接口、创建HDMI更新线程以及Overlayconfig 线程。
staticBOOL hdmi_drv_init_context(void)
{
staticconst HDMI_UTIL_FUNCS hdmi_utils =
{
.udelay = hdmi_udelay,
.mdelay = hdmi_mdelay,
.state_callback =hdmi_state_callback,
};
if(hdmi_drv != NULL)
returnTRUE;
hdmi_drv= (HDMI_DRIVER*)HDMI_GetDriver();
if(NULL == hdmi_drv) return FALSE;
hdmi_drv->set_util_funcs(&hdmi_utils);
hdmi_drv->get_params(hdmi_params);
returnTRUE;
}
上面完成了获得具体的MHL(HDMI)IC的驱动接口HDMI_DRIVER、Hdmi_utils赋值给hdmi_drv。HDMI_GetDriver这个函数是全局类型的,我们实现了哪个IC驱动,就会调用实现的HDMI_GetDriver函数。
我们使用的Sii8338这个IC,厂商会实现HDMI_DRIVER这个Functionlist。
mediatek/kernel/drivers/mhl/linux_driver/hdmi_drv.c
constHDMI_DRIVER* HDMI_GetDriver(void)
{
staticconst HDMI_DRIVER HDMI_DRV =
{
.set_util_funcs= hdmi_drv_set_util_funcs,
.get_params = hdmi_drv_get_params,
.init = hdmi_drv_init,
.enter = hdmi_drv_enter,
.exit = hdmi_drv_exit,
.suspend = hdmi_drv_suspend,
.resume = hdmi_drv_resume,
.video_config = hdmi_drv_video_config,
.audio_config = hdmi_drv_audio_config,
.video_enable = hdmi_drv_video_enable,
.audio_enable = hdmi_drv_audio_enable,
.power_on = hdmi_drv_power_on,
.power_off = hdmi_drv_power_off,
.get_state = hdmi_drv_get_state,
.log_enable = hdmi_drv_log_enable
};
HDMI_FUNC();
return&HDMI_DRV;
}
下面的线程更新函数在Hdmi虚拟Platform驱动Probe函数里面进行了创建,通过原子变量、workqueue实现的。
staticint hdmi_update_kthread(void *data)
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
for(;; ) {
wait_event_interruptible(hdmi_update_wq,atomic_read(&hdmi_update_event));
atomic_set(&hdmi_update_event,0);
hdmi_update_impl();
if(kthread_should_stop())
break;
}
return0;
}
上面创建的线程是一个for无限循环,当hdmi_update_event为False的时候当前线程就会进入睡眠,当为True的时候就会唤醒这个线程调用hdmi_update_impl函数进行Update。这里我们要关注的是这个线程是什么时候唤醒的(这里留个悬念,在下面会进行讲解)。同样的hdmi_overlay_config_kthread这个线程执行的流程和这个差不多,这里就不再多详述。
在上面说的悬念,就是Hdmiupdate 线程是在什么时候唤醒的呢?
staticvoid mtkfb_lcd_complete_interrupt(void *param)
{
if(atomic_read(&has_pending_update))
{
wake_up_interruptible(&screen_update_wq);
}
#ifdefined(MTK_HDMI_SUPPORT)
hdmi_source_buffer_switch();
if(is_hdmi_active())
{
hdmi_update();
}
#endif
}
上面的是LCD的中断回调处理函数。MTK_HDMI_SUPPORT这个宏在控制HDMI的部分。这个宏是在ProjectConfig.mk里面在控制的。
mediatek/config/mt6589_phone_k89v2/ProjectConfig.mk
上面的MTK_HDMI_SUPPORT=yes说明是在支持HDMI的,下面的CUSTOM_KERNEL_HDMI是在配置我们所使用的HDMIIC 驱动的目录。目前我们使用的是Sii8338这个IC,在驱动的Makefile目录里面进行了判断:mediatek/kernel/drivers/mhl/Makefile
include$(MTK_PATH_BUILD)/common.mk
ifeq($(CUSTOM_KERNEL_HDMI),Sii8338)
obj-y+= $(call all-subdir-src-or-makefile)
endif
obj-y+= dummy.o
再次回到FB_buffer的中断处理函数中来;
hdmi_source_buffer_switch:
voidhdmi_source_buffer_switch(void)
{
//printk("lcdwrite buffer:%d\n", hdmi_buffer_lcdw_id);
RET_VOID_IF_NOLOG(IS_HDMI_NOT_ON());
RET_VOID_IF_NOLOG(p->output_mode== HDMI_OUTPUT_MODE_DPI_BYPASS);
RET_VOID_IF_NOLOG(IS_HDMI_IN_VIDEO_MODE());
if(p->lcm_is_video_mode)
{
atomic_set(&hdmi_overlay_config_event,1);
wake_up_interruptible(&hdmi_overlay_config_wq);
//DISP_Config_Overlay_to_Memory(temp_mva_w+ p->lcm_width * p->lcm_height * 3 * hdmi_buffer_lcdw_id_tmp,1);
}
else
{
hdmi_buffer_lcdw_id= (hdmi_buffer_lcdw_id+1)%hdmi_params->intermediat_buffer_num;
DISP_Config_Overlay_to_Memory(temp_mva_w+ p->lcm_width * p->lcm_height * 3 * hdmi_buffer_lcdw_id, 1);
}
}
当我们支持HDMI的时候,我们会执行上面的函数,根据现在的LCM的模式是vedio还是cmd模式执行不同的case,当是vedio模式的时候就会将hdmi_overlay_config_event置为1,从而调用上面的更新函数hdmi_overlay_config_kthread,如果不是vedio模式就会直接配置overlay的配置Memory的函数。
接下来会执行:
/*Will be called in LCD Interrupt handler to check whether HDMI isactived */
boolis_hdmi_active(void)
{
returnIS_HDMI_ON();
}
这里会判断我们有没有HDMI的插入。IS_HDMI_ON函数的实现如下:
#defineIS_HDMI_ON() (HDMI_POWER_STATE_ON == atomic_read(&p->state))
上面是判断&p->state的值,如果为True的时候,我们就会执行hdmi_update()函数进行Update到外置显示器上面。
我们现在Trace下这个state是什么时候置为True的,我们用逆推得方法找出在哪里置为True的。下面的执行流程全部是逆方向的。
#defineSET_HDMI_ON() atomic_set(&p->state,HDMI_POWER_STATE_ON)
TraceSET_HDMI_ON在哪里调用
/*static*/void hdmi_resume(void)
{
SET_HDMI_ON();
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
hdmi_dpi_power_switch(true);
hdmi_drv->resume();
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
Trace下SET_HDMI_ON在哪里调用
/*HDMI Driver state callback function */
voidhdmi_state_callback(HDMI_STATE state)
{
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
switch(state)
。。。。。。。。。。。。。。。。。。
case HDMI_STATE_ACTIVE:
{
hdmi_resume();
代码路径是:mediatek/kernel/drivers/mhl/component/mhl_tx/si_mhl_tx.c.这里会根据根据传递的State参数进行调用hdmi_state_callback
void SiiMhlTxNotifyDsHpdChange(uint8_t dsHpdStatus )
{
//xuecheng, notify for cable plug in
hdmi_state_callback(HDMI_STATE_NO_DEVICE);
}
else
{
//xuecheng, notify for cable plug in
hdmi_state_callback(HDMI_STATE_ACTIVE);
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
staticvoid Int1Isr(void)
{………………………………………………………
TX_DEBUG_PRINT(("Drv:Downstream HPD changed to: %02X\n", (int) cbusStatus));
SiiMhlTxNotifyDsHpdChange(status );
voidSiiMhlTxDeviceIsr( void )
{
…………………………………………………………………………………….
MhlCbusIsr();
Int5Isr();
Int1Isr();
Int2Isr();
}
下面的Hdmi_drv_init函数是HDMIIC 驱动提供的接口函数列表里面的初始化函数。是注册一个中断,当有HDMI插入的时候,就会触发这个中断触发函数。将状态报告给上层。
staticint hdmi_drv_init(void)
{
halStatus= HalOpenI2cDevice(MHL_PART_NAME, MHL_DRIVER_NAME);
//msleep(200);
#ifdefSiI8338DRIVER_INTERRUPT_MODE
halStatus= HalInstallIrqHandler(SiiMhlTxDeviceIsr);
以上就是HDMI整个触发的过程,那么上层是如何知道我们的HDMI的状态的呢?
上层一般是通过设备文件和底层进行交互的,在这里是通过开关文件进行交互的,根据开关文件的状态的不同,上层进行不同的操作。
hdmi_switch_data.name= "hdmi";
hdmi_switch_data.index= 0;
hdmi_switch_data.state= NO_DEVICE;
//for support hdmi hotplug, inform AP the event
ret= switch_dev_register(&hdmi_switch_data);
上面是我们在驱动里面创建的开关设备文件。我们会根据HDMI的不同状态去改变它的状态的。
当有HDMI插入的时候,我们会执行下面的代码:
caseHDMI_STATE_ACTIVE:
{
hdmi_resume();
//forceupdate screen
DISP_UpdateScreen(0,0, DISP_GetScreenWidth(), DISP_GetScreenHeight());
if(HDMI_OUTPUT_MODE_LCD_MIRROR == p->output_mode)
{
msleep(1000);
}
switch_set_state(&hdmi_switch_data,HDMI_STATE_ACTIVE);
HDMI的状态一共有以下几种状态:
typedefenum{
HDMI_STATE_NO_DEVICE,
HDMI_STATE_ACTIVE,
#ifdefined(MTK_MT8193_HDMI_SUPPORT)
HDMI_STATE_PLUGIN_ONLY,
HDMI_STATE_EDID_UPDATE,
HDMI_STATE_CEC_UPDATE
#endif
}HDMI_STATE;
上面是通过改变/sys/class/switch/hdmi/state的值,上层通过监听它的值来判断有没有HDMI的插入。