本文主要简述S5pv210处理器的 HDMI 接口在 Linux 3.0.8 内核下的驱动框架。
现在三星的主流处理器基本都支持HDMI,使用HDMI也有段时间了,却一直不知道它是怎么工作的,只知道linux和android下都会有一个HDMI-service的用户服务程序。然后底层会有HDMI驱动。知道HDMI 和framebuffer有点关系,却不知道两者是如何联系在一起的。从知道HDMI以来就觉得它神秘,出于好奇,决定揭开它的面纱一探真容。按照我的思路从下面四个方面并依照源码简单剖析一下Samsung S5pv210 处理器HDMI 在linux3.0.8下的驱动框架。
• 1.1 何为HDMI,HDMI总线协议发展概况
• 1.2 HDMI驱动框架
• 1.3 从源码,看HDMI设备驱动框架
• 1.4 从“疑问???”猜答案。(透过HDMI-service剖析HDMI与Framebuffer的关系。)
1.1 何为HDMI,HDMI总线协议发展概况
高清晰度多媒体接口(英文:HighDefinition Multimedia Interface,HDMI)是一种数字化视频/音频接口技术,是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号,最高数据传输速度为5Gbps。下面给出的是HDMI typeA型的公母头,另外HDMI 还有typeB typeC 两种类型的插头。
HDMI不仅可以满足1080P的分辨率,还能支持DVDAudio等数字音频格式,支持八声道96kHz或立体声192kHz数码音频传送,可以传送无压缩的音频信号及视频信号。 HDMI的设备具有“即插即用”的特点 。从下面的图片我们看一下个版本HDMI的差别
在s5pv210处理器上的HDMI模块是1.3版,Exyson4412上用的是1.4版。4412在hdmi上的优势从上面的图就一目了然了。从数据手册上可以得到s5pv210处理器HDMI的基本参数:
1.2 HDMI驱动框架
在介绍HDMI的驱动框架之前,首先需要了解一下HDCP,HPD,DDC 这几个新名词指的是什么,否则直接深入源码只会搞得晕头转向,然后就会觉得HDMI这玩意高深的不得了。其实它不怎么深。。。^_^
• HDCP: HDCP的全称是High-bandwidthDigital Content Protection,也就是“高带宽数字内容保护”。
• DDC: HDCP数据秘钥在CPU和显示设备间的交换以及EDID(EDID中包含有关显示器及其性能的参数)要通过hdmi 接口的两个DDC(IIC总线)引脚实现.(实质是实现一个IIC设备驱动)
• HDP:Hot PlugDetection,在HDMI的一对联接中,为热插拔的实现而设计的。简单地说,当发送端接入接受端时,接受端会回应HPD信号给发送端,进而发送端会启动DDC通道,而读取接受端EDID的信息,然后进行HDCP的交互,如果双方认证成功,则视频、音频正常工作,否则联接失败,不同系统会有不同的处理。
HDCP,HPD,DDC 这几个新名词清楚了,那就得想想它们作为HDMI的一个组件(这个词不清楚用的是否恰当)之间有什么样的联系呢?其实上面说的已经差不多了,我再简单总结下:
HDCP起到一个数字内容保护的作用,它的秘钥交换需要用到IIC总线,也就是HDMI的DDC通道。另外HDMI还需要从显示设备获得显示相关参数(EDID),比如分辨率等显示信息。这个信息也是通过IIC总线交互的,走的也是DDC通道。HPD就不用多说了,HDMI热插拔后的“工作”全靠它了。Linux驱动需要分别实现这几个组件的驱动,这几个组件的驱动相互配合共支撑实现了HDMI 驱动。下面就先把HDMI框架画出来。
这里还需要介绍一下CEC:可以简单理解 当您有很多HDMI设备通过HDMI线,切换器或者分配器连在一起的时候,如果所有的HDMI产品都支持CEC功能,那么可以利用其中一台的遥控器可以去控制其他的设备. 这就是CEC功能。210也提供了HDMI cec的驱动源码,并且生成了供给用户空间操作的设备文件(它是作为混杂设备被注册进内核),从上面的框图也可以看到,CEC驱动的实现不同于HDCP、HPD、DDC 等组件的驱动向内核空间提供了函数接口。CEC驱动中仅实现了file_operation操作方法,供给用户对设备文件操作时调用。并没有对内核空间暴露函数接口。
再简单说明下框架:在linux 3.0.8中HDMI作为TV_OUT的一部分,TV_OUT驱动注册时,HDMI作为TV_OUT的一个子系统被初始化。【当TV_OUT的探针函数(static int __devinit s5p_tv_probe(struct platform_device *pdev))被执行时,会调用HDMI相关初始化函数s5p_hdmi_probe(pdev,3, 4);】。并且同时会注册一个符合V4L2标准的设备,因此用户空间对HDMI设备(video14)的基本操作如设置分辨等操作符合V4L2标准操作(这个在后面还会根据源码深入分析)。而CEC HDCP DDC HPD作为HDMI的组件,他们的驱动实现即为HDMI驱动的实现提供函数接口(HDCP DDC HPD 这三个组件的驱动会互相暴露函数调用),也为用户空间提供了操作这个组件的方法(HPD CEC这两个组件的驱动向用户空间提供了操作方法)。
通过上面的框图可以看到,我们在用户空间可以看到的跟HDMI相关的设备文件只有三个,分别是CEC HPD video14 这三个设备文件,根据驱动中实现的方法应用程序可以通过这三个设备文件分别是实现HDMI CEC功能操作、读取热插拔HDMI设备状态、对HDMI设备操作。这三个设备文件是如何生成的,他们提供了哪些操作方法呢?看下面的分析----------
1.3 透过源码看HDMI设备驱动框架的实现
HDMI的这几个组件都有自己的驱动源文件,下面就分开看看各组件驱动的实现及其在HDMI主体驱动中的作用
HDMI DDC 驱动:
先来看看HDMI DDC驱动,DDC驱动实际上实现了一个标准IIC设备的驱动。
DDC设备注册:
DDC设备结构,在板级初始化时被注册进内核mach-XXX210.c(我用的是tiny210 mach-mini210.c)
这是DDC设备结构体,其中包含了DDC设备IIC总线上的地址信息。在mini210_machine_init(void) 函数中会调用:
i2c_register_board_info(1,mini210_i2c_devs1,ARRAY_SIZE(mini210_i2c_devs1));函数将这个结构注册进内核。如下图所示:
再看一下DDC驱动注册:
这是DDC驱动的结构体。在内核启动后start_kernel -->rest_init() -->kernel_init() --> do_basic_setup() -->do_initcalls() 执行到do_initcalls函数时所有带有__init标示的函数被顺序执行这里ddc_init函数就被执行了,将上面的驱动结构注册进内核如下图:
若IIC总线上设备、驱动匹配,则ddc_probe函数被执行。ddc_probe函数内容如下:
ddc_probe函数被执行后会获得IIC设备资源,主要是IIC设备地址信息。
DDC驱动中实现了两个方法,这两个方法不暴露给用户,暂且称为“内核API”(这种说法不太准确,为了方便后面暂且这么叫).驱动将这两个方法,提供给HDMI 的HDCP驱动调用(HDCP 秘钥的交换需要用到DDC组件)。这两个方法的实现如下:
DDC驱动内容大致就这么多
HDMI HDCP 驱动:
下面了解一下HDCP驱动:
HDCP驱动完全没有给用户空间提供操作接口,对用户空间来说感觉不到它的存在,但是驱动提供了“内核API”供HDMI 驱动及其它组件调用。对外暴露的函数列表如下:
static ints5p_hdcp_is_reset(void) //函数功能:判断HDCP的自旋锁是否已经被获取如果被获取返回1,否则返回0.函数未被调用。
static bools5p_set_hpd_detection(bool detection, bool hdcp_enabled,struct i2c_client*client)// 函数功能:检测HPD引脚状态,函数未被调用。
int s5p_hdcp_init(void)//函数功能:初始化HDCP
bool s5p_start_hdcp(void)// 函数功能:开启HDCP功能,并从显示设备获得秘钥。
bool s5p_stop_hdcp(void)// 函数功能:关闭HDCP功能
ints5p_hdcp_encrypt_stop(bool on)// 函数功能:当HDMI接口拔下时,会执行这个函数,停止HDCP加密
int s5p_hdmi_set_dvi(boolen)// 函数功能:设置开启/关闭DVI 标志
voids5p_hdmi_set_audio(bool en)// 函数功能:设置开启/关闭 PCM流无损音频输出 标志
ints5p_hdmi_audio_enable(bool en)// 函数功能:开启音频输出
ints5p_hdmi_set_mute(bool en) // 函数功能:设置声音图像消隐 开启/关闭 标志
ints5p_hdmi_get_mute(void) // 函数功能:获得声音图像消隐 开启/关闭 标志状态
voids5p_hdmi_mute_en(bool en) // 函数功能:开启声音图像消隐
顺便提一下什么是声音图像消隐:简单来说就是显示设备的声音至于静音状态、图像至于黑屏状态。比如HDMI输出设备在切换分辨率、Color Space、开关机等操作时,显示设备可能会看到一些过度花屏的现象,所以,HDMI输出设备在进行相应操作之前,发送给显示设备一个AVMute信号,让显示设备至于黑屏静音状态,等待HDMI输出设备切换好后,再发送一个Clear AVMute信号,让显示设备再开机,这样,切换过程中的花屏现象就会被屏蔽掉。
HDCP驱动大致就提供了这些接口函数,供HDMI驱动的其它组件调用。
HDMI HPD 驱动:
然后再介绍一下HPD驱动:
HPD驱动使用了Platform&misc框架。 设备结构注册到内核,驱动结构体注册到内核,设备驱动匹配后,HPD 的探针函数中(probe)获得设备资源,并注册一个混杂设备,这时会在根文件系统生成设备文件HPD,用户可以通过驱动所支持的方法,操作设备文件,从而获得HDMI HPD引脚线的状态。这段文字用源码的形式展现如下:
Mach-mini210.c文件中,HPD设备结构的定义如下
作为板级平台设备,上面的结构包含在mini210_devices数组中
系统平台设备初始化(mini210_machine_init(void) ----àplatform_add_devices)时,将设备结构s5p_device_hpd注册进内核。
下面的代码在函数 s5p_hpd_init 中将驱动结构 s5p_hpd_driver 注册进内核
当platform的设备和驱动匹配后执行probe探针函数
探针函数s5p_hpd_probe 中注册将HPD注册为一个混杂设备,设备结构体如下
可以看到这个驱动中的file_operations结构实现了 s5p_hpd_open、s5p_hpd_release 、s5p_hpd_read 、s5p_hpd_poll 这几个方法。用户可以通过read 系统调用实现读取HPD引脚的状态。
另外HPD驱动中还有三个函数(方法):
int s5p_hpd_get_state(void)
int s5p_hpd_set_hdmiint(void)
int s5p_hpd_set_eint(void)
这三个操作方法,作为Kernel API 供给HDMI驱动核心调用,分别用于获取当前HDMI接口状态、上电初始化HDMI HPD 、及上电初始化HPD引脚中断。
HDMI 主设备驱动
下面就要进入HDMI驱动最精彩的部分了,HDMI 设备的注册:HDMI作为TV_OUT的一部分,设备资源包含在TV_OUT设备资源结构中如下图数组中下标为3、4的元素:
下面是TV_OUT的设备结构体:
它作为板级平台设备,被下面的数组包含
在mini210_machine_init函数中 的 platform函数被调用时注册进内核
下面是TV_OUT的驱动结构体
在s5p_tv_init函数中 s5p_tv_driver 被注册进内核
当platform的设备和驱动匹配时执行探针函数(probe)s5p_tv_probe
这个probe函数中与HDMI相关的s5p_hdmi_probe 函数被调用,完成HDMI设备的初始化。
紧接着,在probe函数中,下面红框内调用video_device_register 函数,将HDMI设备注册,
函数会获得s5p_tvout这个结构,
并会被注册为遵循V4L2框架的 video设备,对HDMI的操作函数嵌入到v4l2框架。,然后会在根文件生成名为 video14 的设备文件(.minor = TVOUT_MINOR_TVOUT, 这个元素中TVOUT_MINOR_TVOUT宏展开为14 )。
而对video14(HDMI)设备的操作函数最终通过V4L2框架封装暴露给用户空间,实现的操作方法如下:
符合V4L2框架的ioctl操作
到此为止我们已经可以通过/dev/video14 设备文件,按照V4L2规范对HDMI进行操作了。
HDMI CEC驱动 暂略后面会补上
1.4疑问???
到现在为止,我们应该还有一个疑问,用户空间是如何使用HDMI设备的,由此可以引出四个问题:
• 在用户空间如何配置HDMI?
• HDMI显示数据从哪来?
• HDMI显示和Framebuffer有何关联?
从android的HDMI-service 我们可以看到,在Service中打开HDMI 设备:(/device/samsung/proprietary/libhdmi/SecHdmi.cpp)
并且会在Overlay的初始化过程将HDMI显示与Framebuffer关联起来。在overlay.cpp 中会在系统启动时对HDMI进行初始化,将帧缓冲内存映射到HDMI 显示缓冲区中。源码中的buffer指针指向帧缓冲区
经过上述方法,实现了帧缓冲区向HDMI接口的映射。我们在应用程序中,只需要向帧缓冲区中写入图像数据就会通过HDMI输出到显示设备上(HDMI-service正常工作做的前提下,HDMI-service在系统启动后被启动,用来初始化HDMI基本参数,如分辨率等)。
映射完成后 framebuffer与HDMI 的关系如下图:
至此HDMI驱动框架介绍完毕。由于网上基本搜不到210 HDMI的linux驱动相关信息,以上内容都是通过读源码分析总结的,可能会有一些认识错误,还请大家在阅读后及时指正,谢谢。