作者:华清远见讲师
近期需要把 Android4.4 KitKat 的 HDMI 高清输出功能移植到 fs4412 开发平台,查阅了相关资料,经过一段时间的研究、调试,终于成功输出 1080P 的图像到电视,当然也支持720P 的输出了,这里笔者记录移植过程及注意事项,方便有相同需求的客户作为技术参考。
1.1.1 HDMI 基础知识
HDMI(High-DefinitionMultimedia Interface)又被称为高清晰度多媒体接口,是首个支持在单线缆上传输,不经过压缩的全数字高清晰度、多声道音频和智能格式与控制命令数据的数字接口。HDMI 接口由 Silicon Image 美国晶像公司倡导,联合索尼、日立、松下、飞利浦、汤姆逊、东芝等八家著名的消费类电子制造商联合成立的工作组共同开发的。
1.1.2 HDMI 传输原理
HDMI 采用 TMDS (TimeMinimized Differential Signal)最小化传输差分信号传输技术, TMDS 是一种微分信号机制,采用的是差分传动方式 ,是一种利用 2 个引脚间电压差来传送信号的技术。每一个标准的 HDMI 连接,都包含了 3 个用于传输数据的 TMDS 传输通道,还有 1 个独立的 TMDS 时钟通道,以保证传输时所需的统一时序。在一个时钟周期内,每个 TMDS 通道都能传送 10bit 的数据流。而这 10bit 数据,可以由若干种不同的编码格式构成。
所用到的术语:
HDMI 把视频信号分为 R、G、B、H、V 五种信号用 TMDS 技术编码。
TMDS:这三个通道传输 R、G、B 三原色,HV 编码在 B 信号通道里面传输,R、G 的多余位置用来传输音频信号。
DDC :即显示数据通道,用来向视频接收装置发送配置信息和数据格式信息,接收装置读取这些 E-EDID(增强扩展显示识别数据)的信息。
CEC:即消费电子控制通道,通过这条通道可以控制视听设备的工作。
1.1.3 HDMI 数据容量
HDMI 电路中的时钟频率,在 1.0 版本规定为 25MHz-165MHz 之间,也就是说一个 TMDS通道每秒最多能传输 165MHz×10bit=1.65Gbit 的数据,3 个 TMDS 通道一秒就可以传输1.65×3=4.95Gbit 的数据,再加上控制数据,用标准方法表示就是 4.96Gbps 的带宽;若传输信号的比率小于 25MHz,HDMI 会采用自动循环技术填补码率,将信号的码率提升到 25MHz 的水平。
如果用像素点来表示,那就是一秒可以传输显示 1.65G 个像素点(一个完整的像素点信息由 R/G/B
三原色信息构成)所需要的数据量。
在 1.3 版本规格中,TMDS 连接带宽从原来最高 165MHz 提升到 340MHz,数据传输率也从 4.96Gbps 提升到了 10.2Gbps,可以支持支持更高数据量的高清数字流量,如果采用 Type B 型双路 TMDS 连接,则可以在此基础上再提升一倍系统带宽。
1.1.4 HDMI 数据传输
HDMI 输入的源编码格式包括视频像素数据(8 位)、控制数据(2 位)和数据包(4 位)。其中数据包中包含有音频数据和辅助信息数据。数据传输过程可以分成三个部分:视频数据传输期、岛屿数据传输期和控制数据传输期。
视频数据传输期:HDMI 数据线上传送视频像素信号,视频信号经过编码,生成 3 路(即 3 个TMDS 数据信息通道,每路 8 位)共 24 位的视频数据流,输入到 HDMI 发射器中。24 位像素的视频信号通过 TMDS 通道传输,将每通道 8 位的信号编码转换为 10 位,在每个 10 位像素时钟周期传送一个最小化的信号序列,视频信号被调制为 TMDS 数据信号传送出去,最后到接受器中接收。
1.1.5 HDMI 音频功能
传统的数字音频信号的传输主要依靠两种途径:同轴电缆和光纤传输。
同轴电缆传输数字音频信号是一种非常成熟且高质量的方式。这种接口标准对设备端的硬件要求较低,但是在传输高频信号时,容易发生比较大的衰减,影响到最终音质。
光纤对设备接收、发射端的同步时许要求非常严格,在技术上比同轴要难于实现,但是光纤技术在长距离传输方面的优势非常明显,不会出现同轴电缆长距离衰减过大的问题,因此也得到了很多有距离限制以及新装修用户的青睐。
HDMI 技术则综合了以上两者的优点:物理层采用成熟的电缆连接。HDMI 理论上可以实现最高 20 米的无损耗数字音频信号传播,那些对距离有要求的用户也能较好接受。
1.1.6 HDMI 接口类型
常见的 HDMI 类型有 A、B、C 三种类型。其中 A 型是标准的 19 针 HDMI 接口,普及率最高;B 型接口尺寸稍大,但是有 29 个引脚,可以提供双 TMDS 传输通道。而 C 型接口和 A 型接口性能一致,但是体积较小,更加适合紧凑型便携设备使用。接口 A、接口 B、接口 C
注: fs4412开发板HDMI采用的是TYPE C接口
1.1.7 HDMI 特点
1、更好的抗干扰性能,能实现最长 20 米的无增益传输。
2、针对大尺寸数字平板电视分辨率进行优化,兼容性好。
3、支持 EDID 和 DDC2B 标准,设备之间可以智能选择最佳匹配的连接方式。
4、拥有强大的版权保护机制(HDCP),有效防止盗版现象。
5、支持 24bit 色深处理,(RGB、YCbCr4-4-4、YCbCr4-2-2)。
6、接口体积小,各种设备都能轻松安装。
7、一根线缆实现数字音频、视频信号同步传输,有效降低使用成本和繁杂程度。
8、完全兼容 DVI 接口标准,用户不用担心新旧系统不匹配。
9、支持热插拔技术。
移植环境:
1 fs4412开发平台
2 kernel 3.0.15 version
3 Android4.4.4
4 Ubuntu12.04 64BIt 开发环境
注:笔者移植过程中查询了 HDMI 相关的一些技术资料,在此感谢 CSDN 博主对 HDMI的基础分析:http://blog.csdn.net/xubin341719/article/details/7713450,以上 HDMI 基本概念描述转载自此博客。
1.2 硬件相关部分 ---查找硬件原理图
下图为 fs4412 开发板底板 HDMI 接口引脚定义:
原理图结合 HDMI 接口定义标准我们可以获知:
1 TMDS_D0-、TMDS_D0+, TMDS_D1- 、TMDS_D1+, TMDS_D2- 、TMDS_D2+ 三对数据线用于传输视频和音频及控制信号;
2 TMDS_CLK+、TMDS_CLK-为 HDMI 传输提供时钟源;
3 SCL,SDA 为 I2C 控制信号,用于 EDID 协议传输,主要用于板卡与 HDMI 显示设备之间进行协商,比如查询 HDMI 显示设备支持的最大分辨率,板卡设置 HDMI 输出分辨率等等均通过 I2C 总线传输,fs4412 开发平台采用 I2C0 传输 EDID。 I2C 作为 HDMI 的 DDC 通道,用于设备之间的沟通。
4 HDMI_HPD 引脚用于产生热插拔中断信号,CPU 端通过此信号可以知道有 HDMI 设备插入或者拔出,驱动程序会处理中断,告知到 Android 层。
5 CEC引脚用于 HDMI 的高级客户定制功能,用于传输厂商自定义的命令,属于 HDMI 的拓展功能,比如 HDMI 发送端设备可以通过 CEC 引脚告知 HDMI 显示设备随同发送设备开机,关机等操作。
1.3 Kernel
1.3.1 概述
fs4412 开发板采用的内核是 Linux 3.0.15 版本,我们这边没有三星官方的关于 HDMI的 PortingGuid, 只能是自己根据 Exynos4412 的 Datasheet 结合三星提供的内核代码进行分析。
我们先看一下 Exynos4412 Datasheet 中关于 HDMI 功能的属性支持:
The features of HDMI are:
Complies with HDMI 1.4 (3D feature), HDCP 1.1, and DVI 1.0
The video formats that HDMI supports are:
480p 59.94 Hz/60 Hz, 576p @ 50 Hz
720p @ 50 Hz/59.94 Hz/60 Hz
1080i @ 50 Hz/59.94 Hz/60 Hz
1080p @ 50 Hz/59.94 Hz/60 Hz
Supports other various formats up to 148.5 MHz Pixel Clock
Supports Color Format: 4:4:4 RGB/YCbCr
Supports 8-bit precision per color only
Supports CEC function
Contains an Integrated HDCP Encryption Engine for video/ audio content protection
Does not include DDC. There is a dedicated Inter-Integrated Circuit (I2C) for DDC in Exynos 4412 SCP
可以知道 HDMI 控制器支持 HDMI1.4,HDCP1.1,DVI1.0 规范,另外支持最高 1080P 60HZ的显示,当然也支持最常见的 480P ,720P,1080i 输出,另外 HDMI 输出的颜色格式可是 RGB的也可以是 YUV 的,这个可以通过软件界面进行输出设置。
DDC 全文为 Display Data Channel,用于 HDMI 设备之间的协议沟通,Exynos4412 内部没有专用的 DDC 控制器,而是采用 I2C 总线完成这部分工作,驱动部分使用 I2C 传输控制命令到显示终端设备。
系统框图:
HDMI 的视频数据是通过 MIXER 输入到 HDMI CORE 核心,然后通过 PHY 发送出去,MIXER 是视频混合器,用于图层的混合。音频数据源有两路,一路是 SPDIF 总线输入,另外一路是 I2S 音频总线输入,我们的开发板采用的是 I2S 的音频源,故音频输入是通过 I2S 传输到HDMI CORE 的。
另外需要注意一下 HDMI PHY,Exynos4412 集成了 HDMI PHY,PHY 用于产生 pixel 时钟和TMDS 时钟,我们不再需要额外的 PHY 芯片,这样可以省去 PCB 布线,当然还有 cost,软件通过 CPU 内部的专用的 I2C 总线配置 Phy 寄存器,针对 Phy 进行控制,比如开启,关闭 PHY电源等等。
HDMI 功能在 Exynos4412 平台中属于 TVOUT 子系统的一部分,图形图像数据可以输出到 TV显示设备,也可以输出到 HDMI 显示设备 : 如图,VideoProcessor 硬件模块从内存获取到 YUV420 格式的图像数据进行裁剪,及空间色彩转换,然后把数据传输到 MIXER 硬件模块,Dataheet 是这样解释 VideoProcessor 的功能定义:
Video Processor (VP) is responsible for video scaling, de-interlacing, and video post processing of TV-out data path. VP reads reconstructed YCbCr 4:2:0 video sequences from DRAM. It then processes the sequence, and sends it to on-the- fly Mixer。
MIXER 模块主要是对VideoProcessor输入的图形,视频,背景进行混合叠加,形成完成的窗口显示,然后把数据传输到TVENC进行编码,数模转换,输出到TV设备,或者把数据传输到HDMI模块,由HDMI的PHY把数据传输到HDMI接收显示设备。
Mixer overlaps or blends the input data such as graphic, video, background and sends the resulting data to the TVOUT module. The TVOUT module generates all the video control signals
如果您对MIXER的功能作用不是很清楚,可以看一下Datasheet中对MIXER混合器描述的图例: 通过上面的解释,我们知道内存数据是如何显示到HDMI设备上的,这样对HDMI就有了一个框架性的认识和理解,方便我们分析 Linux HDMI驱动结构,及驱动模块在HDMI使用中扮演怎么样的角色。
1.3.2 内核代码
这里我们把 HDMI 驱动相关划分为两部分,一部分是驱动文件,驱动文件实现了HDMI 的驱动架构。另外一部分是板级支持文件,与 fs4412 开发板相关的文件。
1.3.2.1 驱动文件
首先我们看一下 HDMI 驱动相关文件夹,因为 HDMI 属于 TVOUT 子系统的一部分,那么HDMI 驱动是离不开 TV 输出系统的:
路径:drivers/media/video/Samsung/tvout
TVOUT 文件夹为 TVOUT 子系统的驱动,HDMI 驱动就位于其中:
这里我们看到了 s5p_tvout_hpd.c 关于 HDMI 热插拔事件相关驱动,该驱动文件会产生/dev/HPD 设备节点,用户态软件会打开设备节点,用于监控 HDMI 设备的插入和拔出。 s5p_mixer_ctrl.c 视频混合器,s5p_vp_ctrl.c 图形裁剪驱动,这些文件是 TV 驱动调用接口文件,实际的实现位于 hw_if 文件夹下面,hw_if 文件夹下面的这些文件会操作最底层的寄存器配置: 比如 hdmi.c 文件包含对 HDMI 控制寄存器,状态寄存器,及其他功能寄存器的配置,当然也包含了对 HDMI PHY 配置:
void s5p_hdmi_reg_enable(bool en)
{
u8 reg;
reg = readb(hdmi_base + S5P_HDMI_CON_0);
if (en)
reg |= S5P_HDMI_EN;
else
reg &= ~(S5P_HDMI_EN | S5P_HDMI_ASP_EN);
writeb(reg, hdmi_base + S5P_HDMI_CON_0);
if (!en) {
do {
reg = readb(hdmi_base + S5P_HDMI_CON_0);
} while (reg & S5P_HDMI_EN);
}
}
s32 s5p_hdmi_phy_config( enum phy_freq freq, enum s5p_hdmi_color_depth cd)
{
s32 index;
s32 size;
u8 buffer[32] = {0, };
u8 reg;
int loop =0;
switch (cd) {
case HDMI_CD_24:
index = 0;
break;
case HDMI_CD_30:
index = 1;
break;
case HDMI_CD_36:
index = 2;
break;
default:
return -1;
}
buffer[0] = PHY_REG_MODE_SET_DONE;
buffer[1] = 0x00;
if (s5p_hdmi_i2c_phy_write(PHY_I2C_ADDRESS, 2, buffer) != 0) {
tvout_err("s5p_hdmi_i2c_phy_write failed.\n");
return -1;
}
writeb(0x5, i2c_hdmi_phy_base + HDMI_I2C_LC);
size = sizeof(phy_config[freq][index])
/ sizeof(phy_config[freq][index][0]);
memcpy(buffer, phy_config[freq][index], sizeof(buffer));
if (s5p_hdmi_i2c_phy_write(PHY_I2C_ADDRESS, size, buffer) != 0)
return -1;
#ifdef CONFIG_HDMI_PHY_32N
buffer[0] = PHY_REG_MODE_SET_DONE;
buffer[1] = 0x80;
if (s5p_hdmi_i2c_phy_write(PHY_I2C_ADDRESS, 2, buffer) != 0) {
tvout_err("s5p_hdmi_i2c_phy_write failed.\n");
return -1;
}
#else
buffer[0] = 0x01;
if (s5p_hdmi_i2c_phy_write(PHY_I2C_ADDRESS, 1, buffer) != 0) {
tvout_err("s5p_hdmi_i2c_phy_write failed.\n");
return -1;
}
#endif
s5p_hdmi_print_phy_config();
#ifndef CONFIG_HDMI_PHY_32N
s5p_hdmi_reg_core_reset();
#endif
#ifdef CONFIG_HDMI_PHY_32N
do {
reg = readb(hdmi_base + S5P_HDMI_PHY_STATUS0);
} while (!(reg & S5P_HDMI_PHY_STATUS_READY));
#else
do {
reg = readb(hdmi_base + S5P_HDMI_PHY_STATUS);
mdelay(5);
loop++;
if(loop==100) return -1; //added yqf, for robust
} while (!(reg & S5P_HDMI_PHY_STATUS_READY));
#endif
writeb(I2C_CLK_PEND_INT, i2c_hdmi_phy_base + HDMI_I2C_CON);
writeb(I2C_IDLE, i2c_hdmi_phy_base + HDMI_I2C_STAT);
return 0;
}
Mixer.c 文件包含了对视频混合器的底层配置:
void s5p_mixer_start(void)
{
writel((readl(mixer_base + S5P_MXR_STATUS) | S5P_MXR_STATUS_RUN),
mixer_base + S5P_MXR_STATUS);
}
void s5p_mixer_stop(void)
{
u32 reg = readl(mixer_base + S5P_MXR_STATUS);
reg &= ~S5P_MXR_STATUS_RUN;
writel(reg, mixer_base + S5P_MXR_STATUS);
do {
reg = readl(mixer_base + S5P_MXR_STATUS);
} while (!(reg & S5P_MXR_STATUS_IDLE_MODE));
}
这些驱动文件中当然有一个主文件,作为驱动的入口文件,他就是 s5p_tvout.c 文件, 该文件提供了驱动注册函数,另外构建了 VideoProcessor 对象,MIXER 视频混淆器对象还有非常重要的 V4L2 用户态调用接口,用户态程序是通过 V4L2 接口控制 HDMI 的输出的。
static int __devinit s5p_tvout_probe(struct platform_device *pdev)
{
s5p_tvout_pm_runtime_enable(&pdev->dev);
#if defined(CONFIG_S5P_SYSMMU_TV) && defined(CONFIG_VCM)
if (s5p_tvout_vcm_create_unified() < 0)
goto err;
if (s5p_tvout_vcm_init() < 0)
goto err;
#elif defined(CONFIG_S5P_SYSMMU_TV) && defined(CONFIG_S5P_VMEM)
s5p_sysmmu_enable(&pdev->dev);
printk("sysmmu on\n");
s5p_sysmmu_set_tablebase_pgd(&pdev->dev, __pa(swapper_pg_dir));
#endif
#ifndef CONFIG_TC4_EVT //yqf
tv_regulator_vdd18 = regulator_get(NULL, "vdd18_mipi");
if (IS_ERR(tv_regulator_vdd18)) {
printk("%s: failed to get %s\n", __func__, "vdd18_mipi");
goto err_regulator;
}
//regulator_enable(tv_regulator_vdd18);
tv_regulator_vdd10 = regulator_get(NULL, "vdd10_mipi");
if (IS_ERR(tv_regulator_vdd10)) {
printk("%s: failed to get %s\n", __func__, "vdd10_mipi");
goto err_regulator;
}
//regulator_enable(tv_regulator_vdd10);
#endif
if (s5p_tvout_clk_get(pdev, &s7pxv_status) < 0)
goto err;
if (s5p_vp_ctrl_constructor(pdev) < 0)
goto err;
/* s5p_mixer_ctrl_constructor must be called
before s5p_tvif_ctrl_constructor */
if (s5p_mixer_ctrl_constructor(pdev) < 0)
goto err;
if (s5p_tvif_ctrl_constructor(pdev) < 0)
goto err;
if (s5p_tvout_v4l2_constructor(pdev) < 0)
goto err;
#ifdef CONFIG_HAS_EARLYSUSPEND
spin_lock_init(&s7pxv_status.tvout_lock);
s7pxv_early_suspend.suspend = s5p_tvout_early_suspend;
s7pxv_early_suspend.resume = s5p_tvout_late_resume;
s7pxv_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; //added yqf, suspend
before LCD
register_early_suspend(&s7pxv_early_suspend);
suspend_status = 0;
#endif
#ifdef CONFIG_TV_FB
#ifndef CONFIG_USER_ALLOC_TVOUT
clk_enable(s7pxv_status.i2c_phy_clk); //added yqf
s5p_hdmi_phy_power(true);
if (s5p_tvif_ctrl_start(TVOUT_720P_60, TVOUT_HDMI) < 0) //yqf, back later
goto err;
#endif
/* prepare memory */
if (s5p_tvout_fb_alloc_framebuffer(&pdev->dev))
goto err;
if (s5p_tvout_fb_register_framebuffer(&pdev->dev))
goto err;
#endif
on_stop_process = false;
on_start_process = false;
return 0;
#ifndef CONFIG_TC4_EVT
err_regulator:
regulator_put(tv_regulator_vdd18);
regulator_put(tv_regulator_vdd10);
#endif
err:
return -ENODEV;
}
static const struct dev_pm_ops s5p_tvout_pm_ops = {
.suspend = s5p_tvout_suspend,
.resume = s5p_tvout_resume,
.runtime_suspend = s5p_tvout_runtime_suspend,
.runtime_resume = s5p_tvout_runtime_resume
};
static struct platform_driver s5p_tvout_driver = {
.probe = s5p_tvout_probe,
.remove = s5p_tvout_remove,
.driver = {
.name = "s5p-tvout",
.owner = THIS_MODULE,
.pm = &s5p_tvout_pm_ops
},
};
static char banner[] __initdata =
KERN_INFO "S5P TVOUT Driver v3.0 (c) 2010 Samsung Electronics\n";
static int __init s5p_tvout_init(void)
{
int ret;
printk(banner);
ret = platform_driver_register(&s5p_tvout_driver);
if (ret) {
printk(KERN_ERR "Platform Device Register Failed %d\n", ret);
return -1;
}
#ifdef CONFIG_PM
tvout_resume_wq = create_freezable_workqueue("tvout resume work");
if (!tvout_resume_wq) {
printk(KERN_ERR "Platform Device Register Failed %d\n", ret);
platform_driver_unregister(&s5p_tvout_driver);
return -1;
}
INIT_WORK(&tvout_resume_work, (work_func_t) s5p_tvout_resume_work);
#endif
return 0;
}
static void __exit s5p_tvout_exit(void)
{
#ifdef CONFIG_HAS_EARLYSUSPEND
mutex_destroy(&s5p_tvout_mutex);
#endif
platform_driver_unregister(&s5p_tvout_driver);
}
late_initcall(s5p_tvout_init);
module_exit(s5p_tvout_exit);
1.3.2.2 板级相关文件
文件列表:
arch/arm/mach-exynos/mach-fs4412.c
arch/arm/plat-s5p/dev-tvout.c
arch/arm/mach-exynos/setup-tvout.c
mach-fs4412.c 文件为 fs4412 开发板的入口文件,该文件定义了 fs4412 开发板的所有板载资源,当然也包括 HDMI 相关的设备:
static struct platform_device *smdk4x12_devices[] __initdata = {
…...
#ifdef CONFIG_VIDEO_TVOUT
&s5p_device_tvout,
&s5p_device_cec,
&s5p_device_hpd,
#endif
……..
}
static void __init smdk4x12_machine_init(void)
{
……
#if defined(CONFIG_VIDEO_TVOUT)
s5p_hdmi_hpd_set_platdata(&hdmi_hpd_data);
s5p_hdmi_cec_set_platdata(&hdmi_cec_data);
#ifdef CONFIG_EXYNOS_DEV_PD
s5p_device_tvout.dev.parent = &exynos4_device_pd[PD_TV].dev;
exynos4_device_pd[PD_TV].dev.parent= &exynos4_device_pd[PD_LCD0].dev;
#endif
…….
}
dev-tvout.c 文件定义了 HDMI 系统相关的设备,如 VideoProcess,Mixer 视频混淆器占
用的系统资源,如寄存器地址,中断:
/* TVOUT interface */
static struct resource s5p_tvout_resources[] = {
[0] = {
.start = S5P_PA_TVENC,
.end = S5P_PA_TVENC + S5P_SZ_TVENC - 1,
.flags = IORESOURCE_MEM,
.name = "s5p-sdo"
},
[1] = {
.start = S5P_PA_VP,
.end = S5P_PA_VP + S5P_SZ_VP - 1,
.flags = IORESOURCE_MEM,
.name = "s5p-vp"
},
[2] = {
.start = S5P_PA_MIXER,
.end = S5P_PA_MIXER + S5P_SZ_MIXER - 1,
.flags = IORESOURCE_MEM,
.name = "s5p-mixer"
},
[3] = {
.start = S5P_PA_HDMI,
.end = S5P_PA_HDMI + S5P_SZ_HDMI - 1,
.flags = IORESOURCE_MEM,
.name = "s5p-hdmi"
},
[4] = {
.start = S5P_I2C_HDMI_PHY,
.end = S5P_I2C_HDMI_PHY + S5P_I2C_HDMI_SZ_PHY - 1,
.flags = IORESOURCE_MEM,
.name = "s5p-i2c-hdmi-phy"
},
[5] = {
.start = IRQ_MIXER,
.end = IRQ_MIXER,
.flags = IORESOURCE_IRQ,
.name = "s5p-mixer"
},
[6] = {
.start = IRQ_HDMI,
.end = IRQ_HDMI,
.flags = IORESOURCE_IRQ,
.name = "s5p-hdmi"
},
[7] = {
.start = IRQ_TVENC,
.end = IRQ_TVENC,
.flags = IORESOURCE_IRQ,
.name = "s5p-sdo"
},
};
struct platform_device s5p_device_tvout = {
.name = "s5p-tvout",
.id = -1,
.num_resources = ARRAY_SIZE(s5p_tvout_resources),
.resource = s5p_tvout_resources,
};
EXPORT_SYMBOL(s5p_device_tvout);
当然也包括设备节点 /dev/HPD 所使用的资源:
/* HPD */
static struct resource s5p_hpd_resources[] = {
[0] = {
.start = IRQ_TVOUT_HPD,
.end = IRQ_TVOUT_HPD,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s5p_device_hpd = {
.name = "s5p-tvout-hpd",
.id = -1,
.num_resources = ARRAY_SIZE(s5p_hpd_resources),
.resource = s5p_hpd_resources,
};
EXPORT_SYMBOL(s5p_device_hpd);
■ setup-tvout.c 文件主要提供了 GPIO 引脚功能配置:
void s5p_int_src_hdmi_hpd(struct platform_device *pdev)
{
s3c_gpio_cfgpin(EXYNOS4_GPX3(7), S3C_GPIO_SFN(0x3));
s3c_gpio_setpull(EXYNOS4_GPX3(7), S3C_GPIO_PULL_DOWN);
}
void s5p_int_src_ext_hpd(struct platform_device *pdev)
{
s3c_gpio_cfgpin(EXYNOS4_GPX3(7), S3C_GPIO_SFN(0xf));
s3c_gpio_setpull(EXYNOS4_GPX3(7), S3C_GPIO_PULL_DOWN);
}
int s5p_hpd_read_gpio(struct platform_device *pdev)
{
return gpio_get_value(EXYNOS4_GPX3(7));
}
void s5p_cec_cfg_gpio(struct platform_device *pdev)
{
s3c_gpio_cfgpin(EXYNOS4_GPX3(6), S3C_GPIO_SFN(0x3));
s3c_gpio_setpull(EXYNOS4_GPX3(6), S3C_GPIO_PULL_NONE);
}
setup-tvout.c 文件提供的功能函数会被其他文件的函数体调用。
1.3.2.3 内核配置
我们以 POP 核心的内核配置为例来说明:
cp config_for_android_pop .config
make menuconfig->Device Drivers->Multimedia support->video capture adapters
Samsung TVOUT Driver使能该驱动选项,然后同样打开 HDMI CEC ,HDMIHPD,HDMI
14A Driver, HDMI PHY, TVOUT frame buffer driver support, Support pre allocate frame
buffer memory.
如果需要获得更多的调试信息,需要使能 TVOUT driver debug message, 这样内核会输出 HDMI 的调试信息到调试串口,为我们学习 HDMI 驱动提供有力的帮助,HDMI 功能调试完成后需要关闭该选项,因为频繁的打印调试信息会影响到系统的性能,严重的情况下会导致 HDMI 输出界面出现卡顿的现象。
1.4 Android 空间
Android4.4 系统是支持 HDMI 输出显示的,主要体现 Androd 框架层的支持,及用户设置界面关于 HDMI 参数设置。
下面我们看一下 Android 的图形显示系统框架:
HDMI 的输出是由 SurfaceFlinger 控制 Mali Display(HW Composer)输出的,我们会从HDMI HAL 层代码看到 HW Composer 硬件合成器输出图像到 HDMI 显示设备,硬件合成器是Honeycomb(android 发布版本)引入的一个 HAL,SurfaceFlinger 使用它,利用硬件资源来加速 Surface 的合成,比如 3D GPU 和 2D 的图形引擎。 Android 的 Framwork 层已经支持 HDMI 的输出显示,我们重点关注的是 HDMI HAL 层的实现代码,这也是我们 Porting 工作最重要的部分。
1.4.1 HAL 层移植
首先我们看一下 HDMI HAL 层相关文件夹:
hardware/samsung_slsi/exynos4/libhdmi
hardware/samsung_slsi/exynos4/libhwc
libhdmi 文件夹是我们重点关注的对象,里面共有三个子文件夹:
hardware/samsung_slsi/exynos4/libhdmi/ libhdmiservice
hardware/samsung_slsi/exynos4/libhdmi/ libsForhdmi
hardware/samsung_slsi/exynos4/libhdmi/ SecHdmi
libhdmiservice: 该文件夹会编译形成 libTVOut.so, libhdmiclient.so 库文件。
libhdmiservice 文件夹提供了 SecHdmiClient 类的实现,及 SecHdmiClient 类对象的创建函数SecHdmiClient::getInstance(),硬件合成器 libhwc 会调用 SecHdmiClient::getInstance()函数创建全局唯一的 SecHdmiClient 对象,使用对象指针 mHdmiClient 指向该对象。
libhwc 文件夹的关键文件 hwc.cpp,也是硬件合成器 libhwc 的 HAL 层文件,该文件会通过对象指针调用 SecHdmiClient 的接口函数,比如使能 HDMI,设置 HDMI 的分辨率等等:
hwc.cpp 调用 SecHdmiClient 类接口函数的相关代码片段:
#if defined(BOARD_USES_HDMI)
android::SecHdmiClient *mHdmiClient = android::SecHdmiClient::getInstance();
if (skip_hdmi_rendering == 1)
return 0;
if (contents == NULL) {
// Don‘t display unnecessary image
mHdmiClient->setHdmiEnable(0);
return 0;
} else {
mHdmiClient->setHdmiEnable(1);
}
#ifdef SUPPORT_AUTO_UI_ROTATE
#if 0 //yqf, move to FramebufferNativeWindow
cur = &list->hwLayers[0];
//LOGE("%s, cur->tran:%d \n",__func__,cur->transform); //added yqf
if (cur->transform == HAL_TRANSFORM_ROT_90 )//added yqf for test
mHdmiClient->setHdmiRotate(90, ctx->num_of_hwc_layer);
else if(cur->transform == HAL_TRANSFORM_ROT_270)
mHdmiClient->setHdmiRotate(270, ctx->num_of_hwc_layer);
else if(cur->transform == HAL_TRANSFORM_ROT_180)
mHdmiClient->setHdmiRotate(180, ctx->num_of_hwc_layer);
else /*if(cur->transform == HAL_TRANSFORM_ROT_0)*/
mHdmiClient->setHdmiRotate(0, ctx->num_of_hwc_layer);
#endif
#endif
libhdmi 文件夹里面包含了三个字文件夹 libcec,libddc,libedid,通过名字我们也就知道这个文件夹负责与 HDMI 显示设备进行 I2C 通信,查询 HDMI 显示设备的显示能力,设置 HDMI显示设备的分辨率。
EDID: Extended display identification data,简称 EDID,是指屏幕分辨率的信息,包括厂商名称与序号。一般 EDID 存在于显示器的 PROM 或 EEPROM 内。一般如要读取 EDID 都是透过I2C,slave address 是 0x50。
EDID 的获取是通过 DDC 进行的,DDC 就是开发板与 HDMI 显示设备之间进行通信的通道,HDMI 发送端设备会通过 DDC 读取显示器的 EDID 信息,然后根据 EDID 信息判断显示能力等
显示参数。
查看开发板的硬件原理图,我们可以知道 HDMI 使用 I2C0 与显示设备通信,这里我们需
要正确设置 I2C 通道:
#define DEV_NAME "/dev/i2c-0"
int DDCOpen()
{
int ret = 1;
// check already open??
if (ref_cnt > 0) {
ref_cnt++;
return 1;
}
// open
if ((ddc_fd = open(DEV_NAME,O_RDWR)) < 0) {
LOGE("%s: Cannot open I2C_DDC : %s",__func__, DEV_NAME);
ret = 0;
}
ref_cnt++;
return ret;
}
HDMI 的 CEC 属于 HDMI 的扩展功能,我们没有使用到,这里不再解释。
SecHdmi 文件夹提供最底层的 V4L2 的调用。
HDMI 的设备操作是通过 Kernel HDMI 驱动提供的设备节点来进行的,HDMI 相关的设备
节点有:
/dev/video16 Graphics0 层设备节点
/dev/video17 Graphics1 层设备节点
/dev/video20 Video 层设备节点
/dev/graphics/fb0 frambuffer 设备节点
/dev/HPD HDMI 热插拔检测设备节点
SecHdmi 文件夹实现这些设备节点的打开,控制,关闭操作。
SecHdmi 文件夹对外提供 SecHdmi 类对象调用接口,libhdmiservice 文件夹会调用该对象的接口函数,用于底层设备节点的控制。
SecHdmi 文件夹也用于调用 libsForhdmi 文件夹通过的 EDID 接口,来获取 HDMI 显示设备的显示能力,及设置显示设备的分辨率。
libhwc 模块及 libhdmi 子模块调用关系:
另外 Android4.4 提供了 libhdmiservice_jni.so 库文件,该库文件提供了 Java 界面 HDMI参数设置的接口实现,libhdmiservice_jni.so 文件最后也会调用到 libsForhdmi 层用于控制HDMI 显示设备参数。
1.4.2 HDMI 参数设置:
Android4.4 Setting 界面可以控制 HDMI 输出参数:
1.4.3 HDMI 编译选项
如果需要 Android4.4 编译生成的镜像支持 HDMI 显示,那么编译 Android 源代码前必须
配置好 HDMI 相关的宏定义:
配置文件: device/samsung/smdk4x12/BoardConfig.mk
添加以下宏定义:
BOARD_USES_HDMI_SUBTITLES := true
BOARD_USES_HDMI := true
BOARD_HDMI_STD := STD_720P
BOARD_HDMI_DDC_CH := DDC_CH_I2C_0
BOARD_USES_FIMGAPI := true
BOARD_USES_HDMI_EDID := true
BOARD_USES_HDMI_JUMPER := false
这样我们的 Android4.4 即可支持 HDMI 显示,且默认输出 720P 分辨率,DDC 采用 I2C0
进行通信,支持 HDMI 显示设备 EDID 信息的获取与配置。
1.5 总结
以上作为 fs4412 开发平台移植 HDMI 功能的过程总结,Android4.4版本的 Kernel 及 Android 层代码均包含 Porting 后的代码,也就是 HDMI 正常工作的代码,方便大家学习和产品研发.
HDMI 功能支持音视频同步输出,这里我们重点讲解的是 HDMI 的 Porting 工作,如果对音视频同步输出有研究的朋友,可以自己阅读相关的代码,fs4412 开发板的 HDMI 是支持1080P,720P 分辨率,同样支持音视频同步输出。
如果您在实际的项目中需要 HDMI 功能,请参考我们的原理图设计硬件,尽量使用相同的 HDMI 资源,这样您只需要关注硬件部分,驱动使用我们移植好的即可,否则需要您修改HDMI 相关引脚配置,进行必要的调试工作,增加自己的工作量。