HDMI: High Definition Multimedia Interface,高清多媒体接口,是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号。HDMI有4种类型的接口,分别为 type A, type B, type C和type D,下面我们来看一下这4种接口的定义:
Pin | type A | type B | type C | type D |
1 | TMDS Data2+ | TMDS Data2+ | TMDS Data2 Shield | Hot Plug Detect |
2 | TMDS Data2 Shield | TMDS Data2 Shield | TMDS Data2+ | Utility |
3 | TMDS Data2– | TMDS Data2– | TMDS Data2– | TMDS Data2+ |
4 | TMDS Data1+ | TMDS Data1+ | TMDS Data1 Shield | TMDS Data2 Shield |
5 | TMDS Data1 Shield | TMDS Data1 Shield | TMDS Data1+ | TMDS Data2- |
6 | TMDS Data1– | TMDS Data1– | TMDS Data1– | TMDS Data1+ |
7 | TMDS Data0+ | TMDS Data0+ | TMDS Data0 Shield | TMDS Data1 Shield |
8 | TMDS Data0 Shield | TMDS Data0 Shield | TMDS Data0+ | TMDS Data1- |
9 | TMDS Data0– | TMDS Data0– | TMDS Data0– | TMDS Data0+ |
10 | TMDS Clock+ | TMDS Clock+ | TMDS Clock Shield | TMDS Data0 Shield |
11 | TMDS Clock Shield | TMDS Clock Shield | TMDS Clock+ | TMDS Data0- |
12 | TMDS Clock– | TMDS Clock– | TMDS Clock– | TMDS Clock+ |
13 | CEC | TMDS Data5+ | DDC/CEC Ground | TMDS Clock Shield |
14 | Reserved(N.C. on device) | TMDS Data5 Shield | CEC | TMDS Clock- |
15 | SCL | TMDS Data5- | SCL | CEC |
16 | SDA | TMDS Data4+ | SDA | DDC/CEC Ground |
17 | DDC/CEC Ground | TMDS Data4 Shield | Reserved(N.C. on device) | SCL |
18 | +5V Power | TMDS Data4- | +5V Power | SDA |
19 | Hot Plug Detect | TMDS Data3+ | Hot Plug Detect | +5V Power |
20 | ---- | TMDS Data3 Shield | ---- | ---- |
21 | ---- | TMDS Data3- | ---- | ---- |
22 | ---- | CEC | ---- | ---- |
23 | ---- | Reserved(N.C. on device) | ---- | ---- |
24 | ---- | Reserved(N.C. on device) | ---- | ---- |
25 | ---- | SCL | ---- | ---- |
26 | ---- | SDA | ---- | ---- |
27 | ---- | DDC/CEC Ground | ---- | ---- |
28 | ---- | +5V Power | ---- | ---- |
29 | ---- | Hot Plug Detect | ---- | ---- |
这里我们用的是type A的接口,电路图如下:
从上面可以看出HDMI采用差分数据的形式进行数据传输,有3组数据差分和一组时钟差分,类似的形式比如CAN, USB等都是通过差分的形式进行数据交互的,采用这种形式的好处是抗干扰能力强,而且速度快,比较新的HDMI 2.1最大传输速率可以达到42.6G bit/s,我们这里用到的全志的CPU是HDMI 1.4,理论上最大支持速率到8.16G bit/s,这个其实已经很快了,allwinner官方的datasheet上说可以支持30 fps的4K画质播放.下边是不同HDMI版本的区别:
接下来我们分析一下HDMI的硬件框架,上面的电路图上pin脚可以分为4部分:
第一部分:4组差分线,数据交互等工作.
第二部分:一组I2C总线,这里称为DDC,主要进行CPU和显示设备之间HDCP数据密钥交换和获取EDID的接口.
第三部分:CEC线,在HDMI上有多个显示器时,如果显示器都支持CEC,那么一个遥控就可以控制其它设备.
第四部分:HHPD线,热插拔检测引脚,驱动程序中会根据这个引脚的变化做拔插相关工作.
上面还有两个概念需要说一下:
HDCP: 高带宽数据内容保护,目的是对数据加密,保护知识产权.
EDID: 包含显示器及其性能参数.
allwinner H3上有集成HDMI控制器,大概的一个框架图如下:
从上面框架可以看出,支持HDMI和TV两种形式,datasheet上了解到,这款芯片支持同时输出HDMI和TV,这个的TV指的是CVBS,那么,我们可以通过这个功能实现双屏,说到双屏,这里简单提一下,双屏主要分为3种显示形式: 双屏同显, 双屏异显,双屏合显,当然,单屏也可以实现类似效果,这里只说一下双屏,实现双屏的形式有很多种方式,比如DSI + HDMI, DSI + CVBS,DSI + SPI,...有很多形式,当然,最好的方法其实是两个屏幕用同一组接口,通过片选或者地址选择,双屏同显看起来比较容易,双屏在公交车上也有用到,公交车的广告机屏1播放传媒视频,屏2播放PPT式广告,手机上比如努比亚,ZTE等都有推出双屏的手机.
好了,回到正题,继续说HDMI,接下来说一下软件部分:
本次使用的是Android进行验证,源码方面是7.0, Android N的代码,kernel使用的是Linux-4.4,和上次camera那一节使用的Linux-3.4代码上做了一些升级,HDMI这一部分还是有很大变动的,无论是HDMI专有部分还是通用部分,都进行了代码优化和变动,不过大致的框架还是差不多的,代码方面依然可以去参考Linux-3.4的,这里贴一下代码路径:
Linux-3.4在线看代码: https://github.com/orangepi-xunlong/orangepi_h3_linux
Android N全部压缩源代码: https://pan.baidu.com/s/1o9HIBii#list/path=%2F
这里简单说一下全志平台Android N的编译步骤,首先从百度云下载7.0的全部压缩包H3-sdk7.0-2017-11-03.tar.gzaa到*.gzan,然后在Ubuntu执行:
cat H3-sdk7.0-2017-11-03.tar.gza* > H3-Android7.0.tar.gz
tar -xvf H3-Android7.0.tar.gz
就可以解压得到Android和kernel源码,然后根据我之前写的编译环境搭建,安装以下Android源码的编译依赖,不过要注意,Android 7.0需要openjdk-8-jdk,不是6或者7,如果安装错了,则编译报错,如果有安装多个openjdk则根据我之前写的编译环境搭建,做一下切换即可.安装完编译依赖环境之后,上述所说的解压文件会得到两个文件夹,一个是android,另一个是lichee,我们进入lichee,做如下配置:
phoebus@Linux:~/xshark/Android/AndroidN/lichee$ ./build.sh config
Welcome to mkscript setup progress
All available chips:
0. sun50iw1p1
1. sun50iw2p1
2. sun50iw6p1
3. sun8iw11p1
4. sun8iw12p1
5. sun8iw6p1
6. sun8iw7p1
7. sun8iw8p1
8. sun9iw1p1
Choice: 6
All available platforms:
0. android
1. dragonboard
2. linux
3. camdroid
Choice: 0
All available business:
0. dolphin
1. secure
2. karaok
Choice: 0
LICHEE_BUSINESS=dolphin
using kernel 'linux-3.10':
All available kernel:
0. linux-4.4
Choice: 0
然后会自动编译,如果没有自动编译,则直接运行./build.sh即可,等待编译完成,然后,进入android目录执行:
source ./build/envsetup.sh
lunch 28
extract-bsp
make -j8 && pack
因为我用的是H3,所以选择lunch 28,其它CPU根据实际情况选择编译对应版本的Android.下面主要说一下HDMI驱动的实现.在全志平台,video子系统这部分可以分为两部分,一部分是通用代码,另一部分是具体控制器专用驱动.这里用的H3只集成了HDMI和CVBS,所以目录结构如下:
通用部分代码量还是不少的,这里暂时主题是具体驱动实现,所以,暂时不讨论通用部分,CVBS使用的很少了,这里也先不说,重点说一下HDMI目录,目录结构如下:
可以看出来sun8iw11和sun50iw1工程,全志只是提供了动态库,并没有给出源码,应该是这部分涉及到一些全志的特色技术,但是不得不说,全志这种做法已经违反了GPL协议,GNU组织没有告全志,可能是觉得这个vendor应该太小了,来一趟还不够油钱,用<<惠子相梁>>形容一下.
因为这里用的是H3,代号是sun8iw7p1,这里正好这部分是开源的,所以动态库,对本次没有影响.我们先来看一下,这个目录的makefile:
obj-$(CONFIG_HDMI_DISP2_SUNXI) += hdmi.o
hdmi-y := drv_hdmi.o hdmi_core.o hdmi_edid.o
ifeq ($(CONFIG_ARM64),y)
$(shell cp $(obj)/libhdmi_sun50iw1 $(obj)/libhdmi_sun50iw1.a)
hdmi-$(CONFIG_ARCH_SUN50IW1P1) += libhdmi_sun50iw1.a
hdmi-$(CONFIG_ARCH_SUN50IW2P1) += libhdmi_sun50iw1.a
endif
ifeq ($(CONFIG_ARM),y)
$(shell cp $(obj)/libhdmi_sun8iw11 $(obj)/libhdmi_sun8iw11.a)
hdmi-$(CONFIG_ARCH_SUN50IW1P1) += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN50IW2P1) += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN8IW11) += libhdmi_sun8iw11.a
hdmi-$(CONFIG_ARCH_SUN8IW12) += hdmi_bsp_sun8iw12.o
hdmi-$(CONFIG_ARCH_SUN8IW7) += hdmi_bsp_sun8iw7.o
endif
可以看出,这个目录的文件编译之后,会链接成一个hdmi.o,这个文件包括:drv_hdmi.c, hdmi_core.c, hdmi_edid.c,然后这里是sun8iw7p1,所以,还会包含:hdmi_bsp_sun8iw7.c,这就是HDMI功能实现的驱动代码,代码量不是很多,3K多行,它们的组织关系是, hdmi_bsp_sun8iw7.c和hdmi_edid.c为hdmi_core.c提供API, hdmi_core.c为drv_hdmi.c提供API,可以看出,主要实现就是在drv_hdmi.c中,这个文件是重点, 然后,需要我们实现的是hdmi_bsp_sun8iw7.c,不过这个全志已经实现了,所以,这里主要就是两个文件是我们比较关注的.因为时间比较紧,所以没有画这个驱动的框架图,这里先直接贴一下这两个文件的代码:
hdmi_bsp_sun8iw7.c
/*
* Allwinner SoCs hdmi driver.
*
* Copyright (C) 2017 Allwinner.
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include "hdmi_bsp.h"
#include "hdmi_core.h"
static unsigned int hdmi_base_addr;
static unsigned int hdmi_version;
/*static struct audio_para glb_audio;*/
static unsigned int tmp_rcal_100, tmp_rcal_200;
static unsigned int rcal_flag;
#define get_bvalue(addr) (*((volatile unsigned char *)(addr)))
#define put_bvalue(addr, v) \
(*((volatile unsigned char *)(addr)) = (unsigned char)(v))
#define get_wvalue(addr) (*((volatile unsigned long *)(addr)))
#define put_wvalue(addr, v) \
(*((volatile unsigned long *)(addr)) = (unsigned long)(v))
static int hdmi_phy_set(struct video_para *video);
struct para_tab
{
unsigned int para[19];
};
struct pcm_sf
{
unsigned int sf;
unsigned char cs_sf;
};
static struct para_tab ptbl[] = {
{
{6, 1, 1, 1, 5, 3, 0, 1, 4, 0, 0, 160, 20, 38, 124, 240, 22, 0, 0}},
{
{21, 11, 1, 1, 5, 3, 1, 1, 2, 0, 0, 160, 32, 24, 126, 32, 24, 0, 0}},
{
{2, 11, 0, 0, 2, 6, 1, 0, 9, 0, 0, 208, 138, 16, 62, 224, 45, 0, 0}},
{
{17, 11, 0, 0, 2, 5, 2, 0, 5, 0, 0, 208, 144, 12, 64, 64, 49, 0, 0}},
{
{19, 4, 0, 96, 5, 5, 2, 2, 5, 1, 0, 0, 188, 184, 40, 208, 30, 1, 1}},
{
{4, 4, 0, 96, 5, 5, 2, 1, 5, 0, 0, 0, 114, 110, 40, 208, 30, 1, 1}},
{
{20, 4, 0, 97, 7, 5, 4, 2, 2, 2, 0, 128, 208, 16, 44, 56, 22, 1, 1}},
{
{5, 4, 0, 97, 7, 5, 4, 1, 2, 0, 0, 128, 24, 88, 44, 56, 22, 1, 1}},
{
{31, 2, 0, 96, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45, 1, 1}},
{
{16, 2, 0, 96, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45, 1, 1}},
{
{32, 4, 0, 96, 7, 5, 4, 3, 4, 2, 0, 128, 62, 126, 44, 56, 45, 1, 1}},
{
{33, 4, 0, 0, 7, 5, 4, 2, 4, 2, 0, 128, 208, 16, 44, 56, 45, 1, 1}},
{
{34, 4, 0, 0, 7, 5, 4, 1, 4, 0, 0, 128, 24, 88, 44, 56, 45, 1, 1}},
{
{160, 2, 0, 96, 7, 5, 8, 3, 4, 2, 0, 128, 62, 126, 44, 157, 45, 1, 1}},
{
{147, 2, 0, 96, 5, 5, 5, 2, 5, 1, 0, 0, 188, 184, 40, 190, 30, 1, 1}},
{
{132, 2, 0, 96, 5, 5, 5, 1, 5, 0, 0, 0, 114, 110, 40, 160, 30, 1, 1}},
{
{257, 1, 0, 96, 15, 10, 8, 2, 8, 0, 0, 0, 48, 176, 88, 112, 90, 1, 1}},
{
{258, 1, 0, 96, 15, 10, 8, 5, 8, 4, 0, 0, 160, 32, 88, 112, 90, 1, 1}},
{
{259, 1, 0, 96, 15, 10, 8, 6, 8, 4, 0, 0, 124, 252, 88, 112, 90, 1, 1}},
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
};
static unsigned char ca_table[64] = {
0x00, 0x11, 0x01, 0x13, 0x02, 0x31, 0x03, 0x33, 0x04, 0x15, 0x05,
0x17, 0x06, 0x35, 0x07, 0x37, 0x08, 0x55, 0x09, 0x57, 0x0a, 0x75,
0x0b, 0x77, 0x0c, 0x5d, 0x0d, 0x5f, 0x0e, 0x7d, 0x0f, 0x7f, 0x10,
0xdd, 0x11, 0xdf, 0x12, 0xfd, 0x13, 0xff, 0x14, 0x99, 0x15, 0x9b,
0x16, 0xb9, 0x17, 0xbb, 0x18, 0x9d, 0x19, 0x9f, 0x1a, 0xbd, 0x1b,
0xbf, 0x1c, 0xdd, 0x1d, 0xdf, 0x1e, 0xfd, 0x1f, 0xff,
};
static struct pcm_sf sf[10] = {
{22050, 0x04},
{44100, 0x00},
{88200, 0x08},
{176400, 0x0c},
{24000, 0x06},
{48000, 0x02},
{96000, 0x0a},
{192000, 0x0e},
{32000, 0x03},
{768000, 0x09},
};
static unsigned int n_t