1.1 概述
近期需要把 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的基礎分析://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, &s5ptv_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(&s5ptv_status.tvout_lock);
s5ptv_early_suspend.suspend = s5p_tvout_early_suspend;
s5ptv_early_suspend.resume = s5p_tvout_late_resume;
s5ptv_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; //added yqf, suspend
before LCD
register_early_suspend(&s5ptv_early_suspend);
suspend_status = 0;
#endif
#ifdef CONFIG_TV_FB
#ifndef CONFIG_USER_ALLOC_TVOUT
clk_enable(s5ptv_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 相關引腳配置,進行必要的調試工作,增加自己的工作量。