RK3568 HDMI EDID处理过程

一.简介

EDID是什么?

EDID的全称是Extended Display Identification Data(扩展显示标识数据),VGA、DVI的EDID由主块128字节组成,HDMI的EDID增加扩展块(128字节),扩展块的内容主要是和音频属性相关的,DVI和VGA没有音频,HDMI自带音频,扩展块数据规范按照CEA-861x标准定义,未来可能增加到512或256的整数倍。其中包含有关显示器及其性能的参数,包括供应商信息、最大图像大小、颜色设置、厂商预设置、频率范围的限制以及显示器名和序列号的字符串等等。形象地说,EDID就是显示器的身份证、户口本、技能证书等证件的集合,目的就是告诉别人我是谁,我从哪来,我能干什么。

为什么要使用EDID?

为了能让PC或其他的图像输出设备更好的识别显示器属性

EDID并非古而有之,在古老的CRT时期是没有EDID这个概念的,那为什么后来会有呢?因为随着显示设备的发展,显示器的种类越来越多,模拟的、数字的、普屏的、宽屏的、17寸、19寸、22寸……这让PC傻了眼,分辨率和时序的种类太多了,而每种显示器又不可能支持所有的分辨率,那怎么知道该给显示器一个什么样的分辨率啊?显示出的效果是最佳效果吗?不仅如此,随便输出一个分辨率还有损坏显示器硬件的可能,这可太危险了。于是,EDID临危受命,担当起显示器和PC之前的传话筒。“PC你好,我是A显示器,我能显示N种分辨率,最佳分辨率是XXX”。“显示器你好,收到你的信息,现在就按最佳分辨率给你输出”。这下大家明白了吧,EDID就是为了能让PC或其他的图像输出设备更好的识别显示器属性而出现的。

二.EDID的调用流程

对应文件:
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
./drivers/gpu/drm/drm_edid.c

.get_modes = dw_hdmi_connector_get_modes
									->edid = drm_get_edid(connector, hdmi->ddc); //获取edid
										->drm_do_get_edid
											->drm_do_probe_ddc_edid //尝试通过i2获取edid信息,成功时为0,失败时为-1。
												->drm_get_displayid //如果edid存在,则获取显示id

如果获取到edid,则调用:

				 hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); //检测显示器是否是HDMI,是就返回true,否则return false;
					->drm_find_cea_extension //
						->drm_find_edid_extension //在edid中搜索CEA扩展块
							->if (cea_db_is_hdmi_vsdb(&edid_ext[i]))
									return true; //因为HDMI标识符在特定于供应商的块中,所以从CEA扩展的所有数据块中搜索它。
				 
                 hdmi->sink_has_audio = drm_detect_monitor_audio(edid); //检测显示器audio音频功能,如果显示器支持音频,就返回true,否则return false;
				 
                 drm_mode_connector_update_edid_property(connector, edid); //更新连接器的edid属性
				 
                 cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); 
				 
                 ret = drm_add_edid_modes(connector, edid); //从连接的显示器中读取到的edid数据添加分辨率[mode](如果mode可用),
*edid spec说 mode(分辨率)应该按以下顺序优先选择:

	*-首选 详细 mode(分辨率)

	*-来自基础块的其他详细 mode(分辨率)

	*-扩展块的详细 mode(分辨率)

	*-CVT 3字节代码 mode(分辨率)

	*-标准定时代码

	*-已建立的时间代码

	*-根据GTF或CVT范围信息推断的 mode(分辨率)


     /* Store the ELD */
    drm_edid_to_eld(connector, edid); //从edid构建ELD, 填充ELD(类似于edid的数据)缓冲区以传递给音频驱动程序。这个HDCP和端口字段留给图形驱动程序填写。

    drm_mode_connector_update_hdr_property(connector, metedata); //原子替换现有blob属性

如果获取不到edid,则调用:

				 hdmi->sink_is_hdmi = true;
				 hdmi->sink_has_audio = true;			 
				 for (i = 0; i < sizeof(def_modes); i++) { //去遍历def_mode数组的值,他们对应在edid_cea_modes里面的分辨率[mode]-->取自CEA-861规范。
					mode = drm_display_mode_from_vic_index(connector, def_modes, 31, i); //调用该接口来设置def_modes数组中第一个分辨率值
						->调用vic = svd_to_vic(video_db[video_index]); // vic值对应 edid_cea_modes 中的分辨率参数
							->if (!drm_valid_cea_vic(vic))
								->vic > 0 && vic < ARRAY_SIZE(edid_cea_modes); 则设置 edid_cea_modes 里面相应的分辨率
									->newmode = drm_mode_duplicate(dev, &edid_cea_modes[vic]);
										->drm_mode_copy(nmode, mode);分配和复制现有模式, 返回:成功时指向复制模式的指针,错误时为空。

三.尝试修改HDMI的屏幕分辨率

尝试修改kernel/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c下的函数dw_hdmi_connector_get_modes()。

static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
	struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
					     connector);
	struct hdr_static_metadata *metedata =
			&connector->hdr_sink_metadata.hdmi_type1;
	struct edid *edid;
	struct drm_display_mode *mode;
	struct drm_display_info *info = &connector->display_info;
	int i, ret = 0;

	memset(metedata, 0, sizeof(*metedata));
	if (!hdmi->ddc)
		return 0;

-	//edid = drm_get_edid(connector, hdmi->ddc);
+	edid = NULL;
	if (edid) {
		dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
			edid->width_cm, edid->height_cm);

		hdmi->support_hdmi = drm_detect_hdmi_monitor(edid);
		hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
		hdmi->rgb_quant_range_selectable = drm_rgb_quant_range_selectable(edid);
		drm_connector_update_edid_property(connector, edid);
		cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid);
		ret = drm_add_edid_modes(connector, edid);
		dw_hdmi_update_hdr_property(connector);
		kfree(edid);
	} else {
		hdmi->support_hdmi = true;
		hdmi->sink_has_audio = true;
		hdmi->rgb_quant_range_selectable = false;

		for (i = 0; i < ARRAY_SIZE(dw_hdmi_default_modes); i++) {
			struct drm_display_mode *ptr =
				&dw_hdmi_default_modes[i];

				struct student hdmi1;

+				ptr->hdisplay = hdmi1.hdisplay_m;
+	            ptr->vdisplay = hdmi1.vdisplay_m;
+	            ptr->hsync_start = hdmi1.hsync_start_m;
+	            ptr->hsync_end = hdmi1.hsync_end_m;
+	            ptr->htotal = hdmi1.htotal_m;
+	            ptr->vsync_start = hdmi1.vsync_start_m;
+	            ptr->vsync_end = hdmi1.vsync_end_m;
+	            ptr->vtotal = hdmi1.vtotal_m;
+	            ptr->clock = hdmi1.clock_m;

+				printk("drm_mode_duplicate ptr->hdisplay:%d\n",ptr->hdisplay);
+				printk("drm_mode_duplicate ptr->vdisplay:%d\n",ptr->vdisplay);
+				printk("drm_mode_duplicate ptr->hsync_start:%d\n",ptr->hsync_start);
+				printk("drm_mode_duplicate ptr->hsync_end:%d\n",ptr->hsync_end);
+				printk("drm_mode_duplicate ptr->htotal:%d\n",ptr->htotal);
+				printk("drm_mode_duplicate ptr->vsync_start:%d\n",ptr->vsync_start);
+				printk("drm_mode_duplicate ptr->vsync_end:%d\n",ptr->vsync_end);
+				printk("drm_mode_duplicate ptr->vtotal:%d\n",ptr->vtotal);
+				printk("drm_mode_duplicate ptr->clock:%d\n",ptr->clock);

			mode = drm_mode_duplicate(connector->dev, ptr);
			if (mode) {
				if (!i) {
					mode->type = DRM_MODE_TYPE_PREFERRED;
					mode->picture_aspect_ratio =
						HDMI_PICTURE_ASPECT_NONE;
				}
				drm_mode_probed_add(connector, mode);
				ret++;
			}
		}
		info->edid_hdmi_dc_modes = 0;
		info->hdmi.y420_dc_modes = 0;
		info->color_formats = 0;

		dev_info(hdmi->dev, "failed to get edid\n");
	}
	dw_hdmi_check_output_type_changed(hdmi);

	return ret;
}

你可能感兴趣的:(RK3568,计算机外设)