Linux驱动学习--HDMI开发(二)HDMI驱动源码分析(RK平台)

目录

一、引言

二、驱动框架

------> dts节点

------> HDMI DDC 驱动

------> HDMI HDCP驱动

------> HDMI CEC驱动

------> HDMI 主设备驱动

一、引言

前一篇文章简单介绍了一下HDMI的整体结构,从硬件到协议,可以看到,确实包含了很多东西,这篇文章,就从驱动源码的角度,来分析一下HDMI

二、驱动框架

整体框架分析

先来回顾一下几个名词
HDCP: HDCP的全称是High-bandwidthDigital Content Protection,也就是“高带宽数字内容保护”。

1、DDC: HDCP数据秘钥在CPU和显示设备间的交换以及EDID(EDID中包含有关显示器及其性能的参数)要通过hdmi 接口的两个DDC(IIC总线)引脚实现.(实质是实现一个IIC设备驱动)

2、HDP:Hot PlugDetection,在HDMI的一对联接中,为热插拔的实现而设计的。简单地说,当发送端接入接受端时,接受端会回应HPD信号给发送端,进而发送端会启动DDC通道,而读取接受端EDID的信息,然后进行HDCP的交互,如果双方认证成功,则视频、音频正常工作,否则联接失败,不同系统会有不同的处理。

HDCP起到一个数字内容保护的作用,它的秘钥交换需要用到IIC总线,也就是HDMI的DDC通道。另外HDMI还需要从显示设备获得显示相关参数(EDID),比如分辨率等显示信息。这个信息也是通过IIC总线交互的,走的也是DDC通道。HPD就不用多说了,HDMI热插拔后的“工作”全靠它了。Linux驱动需要分别实现这几个组件的驱动,这几个组件的驱动相互配合共支撑实现了HDMI 驱动。

这里还需要介绍一下CEC:可以简单理解 当您有很多HDMI设备通过HDMI线,切换器或者分配器连在一起的时候,如果所有的HDMI产品都支持CEC功能,那么可以利用其中一台的遥控器可以去控制其他的设备. 这就是CEC功能。210也提供了HDMI cec的驱动源码,并且生成了供给用户空间操作的设备文件(它是作为混杂设备被注册进内核),从上面的框图也可以看到,CEC驱动的实现不同于HDCP、HPD、DDC 等组件的驱动向内核空间提供了函数接口。CEC驱动中仅实现了file_operation操作方法,供给用户对设备文件操作时调用。并没有对内核空间暴露函数接口。

在RK平台下,在dts中定义了相关的hdmi节点后,就会调用HDMI相关初始化函数rockchip_hdmiv2_probe。

通过上面的框图可以看到,我们在用户空间可以看到的跟HDMI相关的设备文件只有三个,分别是CEC HPD video14 这三个设备文件,根据驱动中实现的方法应用程序可以通过这三个设备文件分别是实现HDMI CEC功能操作、读取热插拔HDMI设备状态、对HDMI设备操作。这三个设备文件是如何生成的,他们提供了哪些操作方法呢?看下面的分析----------

HDMI dts节点

        hdmi: hdmi@ff3c0000 {
                compatible = "rockchip,rk322xh-hdmi";
                reg = <0x0 0xff3c0000 0x0 0x20000>,
                      <0x0 0xff430000 0x0 0x10000>;
                interrupts = <GIC_SPI xx IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI xx IRQ_TYPE_LEVEL_HIGH>,
                             <GIC_SPI xx IRQ_TYPE_LEVEL_HIGH>;
		......
                rockchip,hdcp_enable = <0>; 
                rockchip,cec_enable = <1>; 
                status = "disabled";
        };
  •  

HDMI DDC 驱动

RK下DDC的初始化、使用在操作接口里面,没有单独进行
其实现位于’rockchip_hdmiv2_hw.c’中
Linux驱动学习--HDMI开发(二)HDMI驱动源码分析(RK平台)
具体的实现就不说了,本质就是使用给定的I2C接口获取EDID等相关信息

HDMI HDCP驱动

RK平台下的HDMI HDCP驱动位于’rockchip_hdmiv2_hdcp.c’中
其中定义了关于 HDCP 的相关操作接口,下面介绍一下初始化函数

//HDCP的初始化函数,会在HDMI设备初始化中调用
void rockchip_hdmiv2_hdcp_init(struct hdmi *hdmi)
{
	pr_info("%s", __func__);
	if (!hdcp) {
		hdcp_init(hdmi);
	} else {
		if (hdcp->keys)
			hdcp_load_key(hdmi, hdcp->keys);
		else
			pr_info("hdcpkeys is no load\n");
	}
}

static int hdcp_init(struct hdmi *hdmi)
{
......
//构造hdcp的相关操作接口,之后会调用到
	hdmi->ops->hdcp_cb = rockchip_hdmiv2_hdcp_start;
	hdmi->ops->hdcp_auth2nd = rockchip_hdmiv2_hdcp_2nd_auth;
	hdmi->ops->hdcp_power_off_cb = rockchip_hdmiv2_hdcp_stop;
}
  •  

下面是初始化流程中,HDCP部分

rockchip_hdmiv2_probe() 
--> rockchip_hdmiv2_dev_initial()
-----> rockchip_hdmiv2_hdcp_init(hdmi);
  •  

HDMI CEC驱动

RK平台下的HDMI HDCP驱动位于’rockchip_hdmiv2_cec.c’中
其中定义了关于 CEC 的相关操作接口,下面介绍一下初始化函数

void rockchip_hdmiv2_cec_init(struct hdmi *hdmi)
{
	struct hdmi_dev *hdmi_dev = hdmi->property->priv;

	if (init) {
		rockchip_hdmi_cec_init(hdmi,
				       rockchip_hdmiv2_cec_sendframe,
				       rockchip_hdmiv2_cec_readframe,
				       rockchip_hdmiv2_cec_setcecla);
		init = 0;
		/* init_waitqueue_head(&wait); */
	}
	/*
	 * Enable sending all message even if sink refuse
	 * message broadcasted by us.
	 */
	if (hdmi_dev->soctype == HDMI_SOC_RK3288 &&
	    hdmi_readl(hdmi_dev, REVISION_ID) == 0x1a)
		writel_relaxed((1 << 4) | (1 << 20),
			       RK_GRF_VIRT + RK3288_GRF_SOC_CON16);

	hdmi_writel(hdmi_dev, IH_MUTE_CEC_STAT0, m_ERR_INITIATOR |
			m_ARB_LOST | m_NACK | m_DONE);
	HDMIDBG(1, "%s", __func__);
}

  •  

下面是初始化流程中,HDCP部分

rockchip_hdmiv2_probe() 
--> rockchip_hdmiv2_dev_initial()
-----> rockchip_hdmiv2_cec_init(hdmi);
  •  

HDMI 主设备驱动

之前说了,在dts中定义了HDMI的相关接口后,就会调用HDMI的初始化函数
下面是我整理出来的初始化流程图

compatible = "rockchip,rk322xh-hdmi-hdcp2";
			compatible = "rockchip,rk322xh-hdmi";
				rockchip_hdmiv2_probe()         	drivers\video\rockchip\hdmi\rockchip-hdmiv2\rockchip_hdmiv2.c
					rockchip_hdmiv2_clk_enable()
					rockchip_hdmiv2_dev_init_ops()	根据rockchip_hdmiv2_hw.c中提供的hdmi操作接口填充ops结构体   rockchip_hdmiv2_hw.c
					rockchip_hdmi_register()							drivers\video\rockchip\hdmi\rockchip-hdmi-core.c
						hdmi->vic = uboot_vic		如果uboot中初始过hdmi,会将vic传入内核,使用其来初始化
							rk_hdmi_register()			android\u-boot\drivers\video\rk_hdmi.c
						rk_get_lcdc_drv()		vop0 lcdc的获取				lcdcdrivers\video\rockchip\rk_fb.c
						hdmi_register_display_sysfs()						drivers\video\rockchip\hdmi\rockchip-hdmi-sysfs.c
						hdmi_init_modelist(hdmi)	设置分辨率等配置 			drivers\video\rockchip\hdmi\rockchip-hdmi-core.c
						switch_dev_register()		注册switch设备,检测hdmi设备的获取	drivers\switch\switch_class.c
					rockchip_hdmiv2_dev_initial()		根据hdmi的类型,设置基本的寄存器		drivers\video\rockchip\hdmi\rockchip-hdmiv2\rockchip_hdmiv2_hw.c
						hdmi_dev_detect_hotplug()	根据寄存器判断是否支持热拔插
						rockchip_hdmiv2_write_phy()	填写设备地址(i2c地址) 开启中断
						rockchip_hdmiv2_cec_init(hdmi); CEC初始化
						rockchip_hdmiv2_hdcp_init(hdmi);   hdcp初始化
					
					rk_display_device_enable()		使能显示设备
  •  

其中,我们来详细看一下’’

struct hdmi *rockchip_hdmi_register(struct hdmi_property *property,
				    struct hdmi_ops *ops)
{
......
	hdmi->property = property;
	hdmi->ops = ops;
	hdmi->enable = false;
	hdmi->mute = HDMI_AV_UNMUTE;
	hdmi->hotplug = HDMI_HPD_REMOVED;
	hdmi->autoset = HDMI_AUTO_CONFIG;
	if (uboot_vic > 0) {
		hdmi->vic = uboot_vic & HDMI_UBOOT_VIC_MASK;
		if (uboot_vic & HDMI_UBOOT_NOT_INIT)
			hdmi->uboot = 0;
		else
			hdmi->uboot = 1;
		hdmi->autoset = 0;
	} else if (hdmi->autoset) {
		hdmi->vic = 0;
	} else {
		hdmi->vic = hdmi->property->defaultmode;
	}
	hdmi->colormode = HDMI_VIDEO_DEFAULT_COLORMODE;
	hdmi->colordepth = hdmi->property->defaultdepth;
	hdmi->colorimetry = HDMI_COLORIMETRY_NO_DATA;
	hdmi->mode_3d = HDMI_3D_NONE;
	hdmi->audio.type = HDMI_AUDIO_DEFAULT_TYPE;
	hdmi->audio.channel = HDMI_AUDIO_DEFAULT_CHANNEL;
	hdmi->audio.rate = HDMI_AUDIO_DEFAULT_RATE;
	hdmi->audio.word_length = HDMI_AUDIO_DEFAULT_WORDLENGTH;
	hdmi->xscale = 100;
	hdmi->yscale = 100;

	if (hdmi->property->videosrc == DISPLAY_SOURCE_LCDC0)
		hdmi->lcdc = rk_get_lcdc_drv("lcdc0");
	else
		hdmi->lcdc = rk_get_lcdc_drv("lcdc1");
	if (!hdmi->lcdc)
		goto err_create_wq;
	if (hdmi->lcdc->prop == EXTEND)
		hdmi->property->display = DISPLAY_AUX;
	else
		hdmi->property->display = DISPLAY_MAIN;
	memset(name, 0, 32);
	sprintf(name, "hdmi-%s", hdmi->property->name);
	hdmi->workqueue = create_singlethread_workqueue(name);
	if (hdmi->workqueue == NULL) {
		pr_err("HDMI,: create workqueue failed.\n");
		goto err_create_wq;
	}
	hdmi->ddev = hdmi_register_display_sysfs(hdmi, NULL);
	if (hdmi->ddev == NULL) {
		pr_err("HDMI : register display sysfs failed.\n");
		goto err_register_display;
	}
	hdmi->id = i;
	hdmi_init_modelist(hdmi);
	......
	}
  •  

可以看到,在这里设置hdmi的各种配置,lcdc等,
最后还会注册一个 sysfs的接口,并定义了一系列操作来接口

static struct rk_display_ops hdmi_display_ops = {
	.setenable = hdmi_set_enable,
	.getenable = hdmi_get_enable,
	.getstatus = hdmi_get_status,
	.getmodelist = hdmi_get_modelist,
	.setmode = hdmi_set_mode,
	.getmode = hdmi_get_mode,
	.set3dmode = hdmi_set_3dmode,
	.get3dmode = hdmi_get_3dmode,
	.getedidaudioinfo = hdmi_get_edidaudioinfo,
	.setcolor = hdmi_set_color,
	.getcolor = hdmi_get_color,
	.getmonspecs = hdmi_get_monspecs,
	.setscale = hdmi_set_scale,
	.getscale = hdmi_get_scale,
	.getdebug = hdmi_get_debug,
};
  • 对应我们设备的相关节点
    Linux驱动学习--HDMI开发(二)HDMI驱动源码分析(RK平台)
上一篇:Ubuntu18.04外接显示屏配置问题


下一篇:HDMI接口与协议