drm/msm/dp: Enable YUV422/YUV420 format

Select YUV422/YUV420 format as desired output for
current resolution if supported.
Chooses best resolution as per below priority order
	1. RGB + DC
	2. YUV422 + DC
	3. YUV420 + DC
	4. RGB
	5. YUV420

Change-Id: I50dfd4767eecacddcb208c3d2e63638845cc9b2a
Signed-off-by: Narender Ankam <nankam@codeaurora.org>
Signed-off-by: Chirag Khurana <ckhurana@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c
index 5396a35..94392d3 100644
--- a/drivers/gpu/drm/msm/dp/dp_catalog.c
+++ b/drivers/gpu/drm/msm/dp/dp_catalog.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -19,6 +19,7 @@
 
 #include "dp_catalog.h"
 #include "dp_reg.h"
+#include "msm_kms.h"
 
 #define DP_GET_MSB(x)	(x >> 8)
 #define DP_GET_LSB(x)	(x & 0xff)
@@ -394,7 +395,8 @@
 	return dp_read(catalog, io_data, DP_HDCP_STATUS);
 }
 
-static void dp_catalog_panel_setup_infoframe_sdp(struct dp_catalog_panel *panel)
+static void dp_catalog_panel_setup_hdr_infoframe_sdp(
+		struct dp_catalog_panel *panel)
 {
 	struct dp_catalog_private *catalog;
 	struct drm_msm_ext_hdr_metadata *hdr;
@@ -501,7 +503,7 @@
 			DUMP_PREFIX_NONE, 16, 4, buf, off, false);
 }
 
-static void dp_catalog_panel_setup_vsc_sdp(struct dp_catalog_panel *panel)
+static void dp_catalog_panel_setup_hdr_vsc_sdp(struct dp_catalog_panel *panel)
 {
 	struct dp_catalog_private *catalog;
 	struct dp_io_data *io_data;
@@ -627,8 +629,8 @@
 		cfg2 |= BIT(15) | BIT(16);
 		dp_write(catalog, io_data, MMSS_DP_SDP_CFG2, cfg2);
 
-		dp_catalog_panel_setup_vsc_sdp(panel);
-		dp_catalog_panel_setup_infoframe_sdp(panel);
+		dp_catalog_panel_setup_hdr_vsc_sdp(panel);
+		dp_catalog_panel_setup_hdr_infoframe_sdp(panel);
 
 		/* indicates presence of VSC (BIT(6) of MISC1) */
 		misc |= BIT(14);
@@ -772,6 +774,7 @@
 	catalog = dp_catalog_get_priv(ctrl);
 	io_data = catalog->io.dp_link;
 
+	misc_val = cc;
 	misc_val |= (tb << 5);
 	misc_val |= BIT(0); /* Configure clock to synchronous mode */
 
@@ -780,7 +783,7 @@
 }
 
 static void dp_catalog_ctrl_config_msa(struct dp_catalog_ctrl *ctrl,
-					u32 rate, u32 stream_rate_khz)
+			u32 rate, u32 stream_rate_khz, u32 out_format)
 {
 	u32 pixel_m, pixel_n;
 	u32 mvid, nvid;
@@ -815,6 +818,9 @@
 
 	pr_debug("rate = %d\n", rate);
 
+	if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		mvid *= 2;
+
 	if (link_rate_hbr2 == rate)
 		nvid *= 2;
 
@@ -1319,6 +1325,158 @@
 	return ret;
 }
 
+static void dp_catalog_panel_setup_vsc_sdp(struct dp_catalog_panel *panel)
+{
+	struct dp_catalog_private *catalog;
+	struct dp_io_data *io_data;
+	u32 header, parity, data;
+	u8 bpc, off = 0;
+	u8 buf[SZ_128];
+
+	if (!panel) {
+		pr_err("invalid input\n");
+		return;
+	}
+
+	catalog = dp_catalog_get_priv(panel);
+	io_data = catalog->io.dp_link;
+
+	/* HEADER BYTE 1 */
+	header = panel->vsc_sdp_data.vsc_header_byte1;
+	parity = dp_header_get_parity(header);
+	data   = ((header << HEADER_BYTE_1_BIT)
+			| (parity << PARITY_BYTE_1_BIT));
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_0, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	/* HEADER BYTE 2 */
+	header = panel->vsc_sdp_data.vsc_header_byte2;
+	parity = dp_header_get_parity(header);
+	data   = ((header << HEADER_BYTE_2_BIT)
+			| (parity << PARITY_BYTE_2_BIT));
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_1, data);
+
+	/* HEADER BYTE 3 */
+	header = panel->vsc_sdp_data.vsc_header_byte3;
+	parity = dp_header_get_parity(header);
+	data   = ((header << HEADER_BYTE_3_BIT)
+			| (parity << PARITY_BYTE_3_BIT));
+	data |= dp_read(catalog, io_data, MMSS_DP_GENERIC0_1);
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_1, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	data = 0;
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_2, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_3, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_4, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_5, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	switch (panel->vsc_sdp_data.bpc) {
+	case 10:
+		bpc = BIT(1);
+		break;
+	case 8:
+	default:
+		bpc = BIT(0);
+		break;
+	}
+
+	data = (panel->vsc_sdp_data.colorimetry & 0xF) |
+		((panel->vsc_sdp_data.pixel_encoding & 0xF) << 4) |
+		(bpc << 8) |
+		((panel->vsc_sdp_data.dynamic_range & 0x1) << 15) |
+		((panel->vsc_sdp_data.content_type & 0x7) << 16);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_6, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	data = 0;
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_7, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_8, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	dp_write(catalog, io_data, MMSS_DP_GENERIC0_9, data);
+	memcpy(buf + off, &data, sizeof(data));
+	off += sizeof(data);
+
+	print_hex_dump(KERN_DEBUG, "[drm-dp] VSC: ",
+			DUMP_PREFIX_NONE, 16, 4, buf, off, false);
+}
+
+static void dp_catalog_panel_config_vsc_sdp(struct dp_catalog_panel *panel,
+		bool en)
+{
+	struct dp_catalog_private *catalog;
+	struct dp_io_data *io_data;
+	u32 cfg, cfg2, misc;
+
+	if (!panel) {
+		pr_err("invalid input\n");
+		return;
+	}
+
+	catalog = dp_catalog_get_priv(panel);
+	io_data = catalog->io.dp_link;
+
+	cfg = dp_read(catalog, io_data, MMSS_DP_SDP_CFG);
+	cfg2 = dp_read(catalog, io_data, MMSS_DP_SDP_CFG2);
+	misc = dp_read(catalog, io_data, DP_MISC1_MISC0);
+
+	if (en) {
+		/* GEN0_SDP_EN */
+		cfg |= BIT(17);
+		dp_write(catalog, io_data, MMSS_DP_SDP_CFG, cfg);
+
+		/* GENERIC0_SDPSIZE */
+		cfg2 |= BIT(16);
+		dp_write(catalog, io_data, MMSS_DP_SDP_CFG2, cfg2);
+
+		dp_catalog_panel_setup_vsc_sdp(panel);
+
+		/* indicates presence of VSC (BIT(6) of MISC1) */
+		misc |= BIT(14);
+
+		pr_debug("Enabled\n");
+	} else {
+		/* GEN0_SDP_EN */
+		cfg &= ~BIT(17);
+		dp_write(catalog, io_data, MMSS_DP_SDP_CFG, cfg);
+
+		/* GENERIC0_SDPSIZE */
+		cfg2 &= ~BIT(16);
+		dp_write(catalog, io_data, MMSS_DP_SDP_CFG2, cfg2);
+
+		/* switch back to MSA */
+		misc &= ~BIT(14);
+
+		pr_debug("Disabled\n");
+	}
+
+	pr_debug("misc settings = 0x%x\n", misc);
+	dp_write(catalog, io_data, DP_MISC1_MISC0, misc);
+
+	dp_write(catalog, io_data, MMSS_DP_SDP_CFG3, 0x01);
+	dp_write(catalog, io_data, MMSS_DP_SDP_CFG3, 0x00);
+}
+
 /* panel related catalog functions */
 static int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel)
 {
@@ -1762,6 +1920,7 @@
 		.config_hdr = dp_catalog_panel_config_hdr,
 		.tpg_config = dp_catalog_panel_tpg_cfg,
 		.config_spd = dp_catalog_panel_config_spd,
+		.config_vsc_sdp = dp_catalog_panel_config_vsc_sdp,
 	};
 
 	if (!dev || !parser) {
diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.h b/drivers/gpu/drm/msm/dp/dp_catalog.h
index dbcbd8b..12dce42 100644
--- a/drivers/gpu/drm/msm/dp/dp_catalog.h
+++ b/drivers/gpu/drm/msm/dp/dp_catalog.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -64,6 +64,22 @@
 	struct drm_msm_ext_hdr_metadata hdr_meta;
 };
 
+struct dp_catalog_vsc_sdp_data {
+	u32 vsc_header_byte0;
+	u32 vsc_header_byte1;
+	u32 vsc_header_byte2;
+	u32 vsc_header_byte3;
+
+	u32 bpc;
+
+	u32 version;
+	u32 length;
+	u32 pixel_encoding;
+	u32 colorimetry;
+	u32 dynamic_range;
+	u32 content_type;
+};
+
 struct dp_catalog_aux {
 	u32 data;
 	u32 isr;
@@ -94,7 +110,7 @@
 	void (*mainlink_ctrl)(struct dp_catalog_ctrl *ctrl, bool enable);
 	void (*config_misc)(struct dp_catalog_ctrl *ctrl, u32 cc, u32 tb);
 	void (*config_msa)(struct dp_catalog_ctrl *ctrl, u32 rate,
-				u32 stream_rate_khz);
+			u32 stream_rate_khz, u32 out_format);
 	void (*set_pattern)(struct dp_catalog_ctrl *ctrl, u32 pattern);
 	void (*reset)(struct dp_catalog_ctrl *ctrl);
 	void (*usb_reset)(struct dp_catalog_ctrl *ctrl, bool flip);
@@ -159,6 +175,7 @@
 	u8 *spd_vendor_name;
 	u8 *spd_product_description;
 
+	struct dp_catalog_vsc_sdp_data vsc_sdp_data;
 	struct dp_catalog_hdr_data hdr_data;
 
 	/* TPG */
@@ -174,6 +191,7 @@
 	void (*config_hdr)(struct dp_catalog_panel *panel, bool en);
 	void (*tpg_config)(struct dp_catalog_panel *panel, bool enable);
 	void (*config_spd)(struct dp_catalog_panel *panel);
+	void (*config_vsc_sdp)(struct dp_catalog_panel *panel, bool en);
 };
 
 struct dp_catalog {
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
index befbc5c..43548a0 100644
--- a/drivers/gpu/drm/msm/dp/dp_ctrl.c
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -18,6 +18,7 @@
 #include <linux/completion.h>
 #include <linux/delay.h>
 
+#include "msm_kms.h"
 #include "dp_ctrl.h"
 
 #define DP_KHZ_TO_HZ 1000
@@ -149,9 +150,16 @@
 {
 	u32 config = 0, tbd;
 	u8 *dpcd = ctrl->panel->dpcd;
+	u32 out_format = ctrl->panel->pinfo.out_format;
 
 	config |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK  */
-	config |= (0 << 11); /* RGB */
+
+	if (out_format & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		config |= (1 << 11); /* YUV420 */
+	else if (out_format & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422)
+		config |= (2 << 11); /* YUV422 */
+	else
+		config |= (0 << 11); /* RGB */
 
 	/* Scrambler reset enable */
 	if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP)
@@ -178,6 +186,20 @@
 	ctrl->catalog->config_ctrl(ctrl->catalog, config);
 }
 
+static void dp_ctrl_misc_ctrl(struct dp_ctrl_private *ctrl)
+{
+	u32 out_format = ctrl->panel->pinfo.out_format;
+	u32 cc, tb;
+
+	tb = ctrl->link->get_test_bits_depth(ctrl->link,
+		ctrl->panel->pinfo.bpp);
+	cc = ctrl->link->get_colorimetry_config(ctrl->link);
+	if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422)
+		cc |= (0x01 << 1); /* Set YUV422 */
+
+	ctrl->catalog->config_misc(ctrl->catalog, cc, tb);
+}
+
 /**
  * dp_ctrl_configure_source_params() - configures DP transmitter source params
  * @ctrl: Display Port Driver data
@@ -187,17 +209,13 @@
  */
 static void dp_ctrl_configure_source_params(struct dp_ctrl_private *ctrl)
 {
-	u32 cc, tb;
 
 	ctrl->catalog->lane_mapping(ctrl->catalog);
 	ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
 
 	dp_ctrl_config_ctrl(ctrl);
+	dp_ctrl_misc_ctrl(ctrl);
 
-	tb = ctrl->link->get_test_bits_depth(ctrl->link,
-		ctrl->panel->pinfo.bpp);
-	cc = ctrl->link->get_colorimetry_config(ctrl->link);
-	ctrl->catalog->config_misc(ctrl->catalog, cc, tb);
 	ctrl->panel->timing_cfg(ctrl->panel);
 }
 
@@ -251,7 +269,8 @@
 {
 	u32 const multiplier = 1000000;
 	u64 pclk, lclk;
-	u8 bpp, ln_cnt;
+	u32 bpp;
+	u8 ln_cnt;
 	int run_idx = 0;
 	u32 lwidth, h_blank;
 	u32 fifo_empty = 0;
@@ -309,6 +328,8 @@
 	int min_hblank_tmp = 0;
 	bool extra_req_bytes_is_neg = false;
 	struct dp_panel_info *pinfo = &ctrl->panel->pinfo;
+	int div = 0;
+	u32 out_format = pinfo->out_format;
 
 	u8 dp_brute_force = 1;
 	u64 brute_force_threshold = 10;
@@ -317,10 +338,26 @@
 	ln_cnt =  ctrl->link->link_params.lane_count;
 
 	bpp = pinfo->bpp;
-	lwidth = pinfo->h_active;
-	h_blank = pinfo->h_back_porch + pinfo->h_front_porch +
-				pinfo->h_sync_width;
-	pclk = pinfo->pixel_clk_khz * 1000;
+	if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422) {
+		switch (pinfo->bpp) {
+		case 24:
+			bpp = 16;
+			break;
+		case 30:
+			bpp = 20;
+			break;
+		default:
+			bpp = 16;
+			break;
+		};
+	} else if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420) {
+		div = 1;
+	}
+
+	lwidth = pinfo->h_active >> div;
+	h_blank = (pinfo->h_back_porch + pinfo->h_front_porch +
+			pinfo->h_sync_width) >> div;
+	pclk = (pinfo->pixel_clk_khz * 1000) >> div;
 
 	boundary_moderation_en = 0;
 	upper_bdry_cnt = 0;
@@ -1201,7 +1238,7 @@
 	ctrl->dp_ctrl.push_idle(&ctrl->dp_ctrl);
 	ctrl->dp_ctrl.reset(&ctrl->dp_ctrl);
 
-	ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz;
+	ctrl->pixel_rate = ctrl->panel->get_pixel_clk(ctrl->panel);
 
 	do {
 		if (ret == -EAGAIN) {
@@ -1229,7 +1266,9 @@
 
 		ctrl->catalog->config_msa(ctrl->catalog,
 			drm_dp_bw_code_to_link_rate(
-			ctrl->link->link_params.bw_code), ctrl->pixel_rate);
+			ctrl->link->link_params.bw_code),
+			ctrl->pixel_rate,
+			ctrl->panel->pinfo.out_format);
 
 		reinit_completion(&ctrl->idle_comp);
 
@@ -1374,7 +1413,7 @@
 			drm_dp_link_rate_to_bw_code(rate);
 		ctrl->link->link_params.lane_count =
 			ctrl->panel->link_info.num_lanes;
-		ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz;
+		ctrl->pixel_rate = ctrl->panel->get_pixel_clk(ctrl->panel);
 	}
 
 	pr_debug("bw_code=%d, lane_count=%d, pixel_rate=%d\n",
@@ -1395,7 +1434,8 @@
 	while (--link_train_max_retries && !atomic_read(&ctrl->aborted)) {
 		ctrl->catalog->config_msa(ctrl->catalog,
 			drm_dp_bw_code_to_link_rate(
-			ctrl->link->link_params.bw_code), ctrl->pixel_rate);
+			ctrl->link->link_params.bw_code),
+			ctrl->pixel_rate, ctrl->panel->pinfo.out_format);
 
 		rc = dp_ctrl_setup_main_link(ctrl, true);
 		if (!rc)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 9ae0482..eb40715 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -1023,6 +1023,8 @@
 {
 	const u32 num_components = 3, default_bpp = 24;
 	struct dp_display_private *dp;
+	u32 pixel_clk_khz = 0;
+	u32 rate_ratio = RGB_24BPP_TMDS_CHAR_RATE_RATIO;
 
 	if (!dp_display) {
 		pr_err("invalid input\n");
@@ -1031,13 +1033,43 @@
 	dp = container_of(dp_display, struct dp_display_private, dp_display);
 
 	mutex_lock(&dp->session_lock);
-	mode->timing.bpp =
-		dp_display->connector->display_info.bpc * num_components;
+	if (mode->timing.out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420) {
+		mode->timing.bpp =
+			dp_display->connector->display_info.y420_bpc *
+			num_components;
+		rate_ratio = YUV420_24BPP_TMDS_CHAR_RATE_RATIO;
+	} else {
+		mode->timing.bpp =
+			dp_display->connector->display_info.bpc *
+			num_components;
+	}
+
+	pixel_clk_khz = mode->timing.pixel_clk_khz / rate_ratio;
+
 	if (!mode->timing.bpp)
 		mode->timing.bpp = default_bpp;
 
 	mode->timing.bpp = dp->panel->get_mode_bpp(dp->panel,
-			mode->timing.bpp, mode->timing.pixel_clk_khz);
+			mode->timing.bpp, pixel_clk_khz);
+
+	/* Refactor bits per pixel for YUV422 format */
+	if (mode->timing.out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422) {
+		switch (mode->timing.bpp) {
+		case 18:
+			mode->timing.bpp = 24;
+			break;
+		case 24:
+			mode->timing.bpp = 30;
+			break;
+		case 30:
+			mode->timing.bpp = 36;
+			break;
+		default:
+			mode->timing.bpp = 30;
+			break;
+		};
+		pr_debug("YCC422 bpp = %d\n", mode->timing.bpp);
+	}
 
 	dp->panel->pinfo = mode->timing;
 	dp->panel->init(dp->panel);
@@ -1287,12 +1319,14 @@
 	return 0;
 }
 
-static int dp_display_validate_mode(struct dp_display *dp, u32 mode_pclk_khz)
+static int dp_display_get_dc_support(struct dp_display *dp,
+		u32 mode_pclk_khz, u32 out_format, bool dc_enable)
 {
-	const u32 num_components = 3, default_bpp = 24;
 	struct dp_display_private *dp_display;
 	struct drm_dp_link *link_info;
-	u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
+	u32 mode_rate_khz = 0, supported_rate_khz = 0;
+	u32 default_bpp = 24, max_supported_bpp = 30;
+	u32 rate_ratio = RGB_24BPP_TMDS_CHAR_RATE_RATIO;
 
 	if (!dp || !mode_pclk_khz || !dp->connector) {
 		pr_err("invalid params\n");
@@ -1302,7 +1336,51 @@
 	dp_display = container_of(dp, struct dp_display_private, dp_display);
 	link_info = &dp_display->panel->link_info;
 
-	mode_bpp = dp->connector->display_info.bpc * num_components;
+	if (out_format & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		rate_ratio = YUV420_24BPP_TMDS_CHAR_RATE_RATIO;
+
+	mode_pclk_khz /= rate_ratio;
+
+	mode_rate_khz = mode_pclk_khz * default_bpp;
+	if (dc_enable)
+		mode_rate_khz = mode_pclk_khz * max_supported_bpp;
+
+	supported_rate_khz = link_info->num_lanes * link_info->rate * 8;
+
+	if (mode_rate_khz > supported_rate_khz)
+		return false;
+
+	return true;
+}
+
+static int dp_display_validate_mode(struct dp_display *dp,
+		u32 mode_pclk_khz, u32 flags)
+{
+	const u32 num_components = 3, default_bpp = 24;
+	struct dp_display_private *dp_display;
+	struct drm_dp_link *link_info;
+	u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
+
+	if (!dp || !dp->connector) {
+		pr_err("invalid params\n");
+		return -EINVAL;
+	}
+
+	if (!mode_pclk_khz) {
+		pr_err("invalid mode_pclk_khz\n");
+		return -EINVAL;
+	}
+
+	dp_display = container_of(dp, struct dp_display_private, dp_display);
+	link_info = &dp_display->panel->link_info;
+
+	if ((flags & SDE_DRM_MODE_FLAG_FMT_MASK) ==
+			DRM_MODE_FLAG_SUPPORTS_YUV420)
+		mode_bpp =
+			dp->connector->display_info.y420_bpc * num_components;
+	else
+		mode_bpp = dp->connector->display_info.bpc * num_components;
+
 	if (!mode_bpp)
 		mode_bpp = default_bpp;
 
@@ -1412,6 +1490,7 @@
 	g_dp_display->set_mode      = dp_display_set_mode;
 	g_dp_display->validate_mode = dp_display_validate_mode;
 	g_dp_display->get_modes     = dp_display_get_modes;
+	g_dp_display->get_dc_support = dp_display_get_dc_support;
 	g_dp_display->prepare       = dp_display_prepare;
 	g_dp_display->unprepare     = dp_display_unprepare;
 	g_dp_display->request_irq   = dp_request_irq;
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
index 6b83368..533bc6c 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.h
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -35,9 +35,12 @@
 
 	int (*set_mode)(struct dp_display *dp_display,
 			struct dp_display_mode *mode);
-	int (*validate_mode)(struct dp_display *dp_display, u32 mode_pclk_khz);
+	int (*validate_mode)(struct dp_display *dp_display,
+			u32 mode_pclk_khz, u32 flags);
 	int (*get_modes)(struct dp_display *dp_display,
 		struct dp_display_mode *dp_mode);
+	int (*get_dc_support)(struct dp_display *dp_display,
+		u32 mode_pclk_khz, u32 out_format, bool dc_enable);
 	int (*prepare)(struct dp_display *dp_display);
 	int (*unprepare)(struct dp_display *dp_display);
 	int (*request_irq)(struct dp_display *dp_display);
diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c
index fdd40a7..5f06009 100644
--- a/drivers/gpu/drm/msm/dp/dp_drm.c
+++ b/drivers/gpu/drm/msm/dp/dp_drm.c
@@ -31,11 +31,82 @@
 	HDR_ENABLE
 };
 
+static int get_sink_dc_support(struct dp_display *dp,
+		struct drm_display_mode *mode)
+{
+	int dc_format = 0;
+	struct drm_connector *connector = dp->connector;
+
+	if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV420) &&
+			(connector->display_info.edid_hdmi_dc_modes
+			& DRM_EDID_YCBCR420_DC_30))
+		if (dp->get_dc_support(dp, mode->clock,
+				MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420, true))
+			dc_format |= MSM_MODE_FLAG_YUV420_DC_ENABLE;
+
+	if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_RGB) &&
+			(connector->display_info.edid_hdmi_dc_modes
+			 & DRM_EDID_HDMI_DC_30))
+		if (dp->get_dc_support(dp, mode->clock,
+				MSM_MODE_FLAG_COLOR_FORMAT_RGB444, true))
+			dc_format |= MSM_MODE_FLAG_RGB444_DC_ENABLE;
+
+	if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV422) &&
+			(connector->display_info.edid_hdmi_dc_modes
+			 & DRM_EDID_HDMI_DC_30))
+		if (dp->get_dc_support(dp, mode->clock,
+				MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422, false))
+			dc_format |= MSM_MODE_FLAG_YUV422_DC_ENABLE;
+
+	return dc_format;
+}
+
+static u32 choose_best_format(struct dp_display *dp,
+		struct drm_display_mode *mode)
+{
+	/*
+	 * choose priority:
+	 * 1. DC + RGB
+	 * 2. DC + YUV422
+	 * 3. DC + YUV420
+	 * 4. RGB
+	 * 5. YUV420
+	 */
+	int dc_format;
+
+	dc_format = get_sink_dc_support(dp, mode);
+	if (dc_format & MSM_MODE_FLAG_RGB444_DC_ENABLE)
+		return (MSM_MODE_FLAG_COLOR_FORMAT_RGB444
+				| MSM_MODE_FLAG_RGB444_DC_ENABLE);
+	else if (dc_format & MSM_MODE_FLAG_YUV422_DC_ENABLE)
+		return (MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422
+				| MSM_MODE_FLAG_YUV422_DC_ENABLE);
+	else if (dc_format & MSM_MODE_FLAG_YUV420_DC_ENABLE)
+		return (MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420
+				| MSM_MODE_FLAG_YUV420_DC_ENABLE);
+	else if (mode->flags & DRM_MODE_FLAG_SUPPORTS_RGB)
+		return MSM_MODE_FLAG_COLOR_FORMAT_RGB444;
+	else if (mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV420)
+		return MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420;
+
+	pr_err("Can't get available best display format\n");
+
+	return MSM_MODE_FLAG_COLOR_FORMAT_RGB444;
+}
+
 static void convert_to_dp_mode(const struct drm_display_mode *drm_mode,
 			struct dp_display_mode *dp_mode, struct dp_display *dp)
 {
 	memset(dp_mode, 0, sizeof(*dp_mode));
 
+	dp_mode->timing.out_format = MSM_MODE_FLAG_COLOR_FORMAT_RGB444;
+	if (drm_mode->private_flags & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422)
+		dp_mode->timing.out_format =
+			MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422;
+	else if (drm_mode->private_flags & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		dp_mode->timing.out_format =
+			MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420;
+
 	dp_mode->timing.h_active = drm_mode->hdisplay;
 	dp_mode->timing.h_back_porch = drm_mode->htotal - drm_mode->hsync_end;
 	dp_mode->timing.h_sync_width = drm_mode->htotal -
@@ -61,6 +132,8 @@
 
 	dp_mode->timing.h_active_low =
 		!!(drm_mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+	dp_mode->flags = drm_mode->flags;
 }
 
 static void convert_to_drm_mode(const struct dp_display_mode *dp_mode,
@@ -99,6 +172,7 @@
 		flags |= DRM_MODE_FLAG_PVSYNC;
 
 	drm_mode->flags = flags;
+	drm_mode->flags |= (dp_mode->flags & SDE_DRM_MODE_FLAG_FMT_MASK);
 
 	drm_mode->type = 0x48;
 	drm_mode_set_name(drm_mode);
@@ -153,6 +227,7 @@
 		       bridge->id, rc);
 		dp->unprepare(dp);
 	}
+
 }
 
 static void dp_bridge_enable(struct drm_bridge *drm_bridge)
@@ -272,6 +347,15 @@
 
 	convert_to_dp_mode(mode, &dp_mode, dp);
 	convert_to_drm_mode(&dp_mode, adjusted_mode);
+
+	/* Clear the private flags before assigning new one */
+	adjusted_mode->private_flags = 0;
+
+	adjusted_mode->private_flags |=
+		choose_best_format(dp, adjusted_mode);
+	SDE_DEBUG("Adjusted mode private flags: 0x%x\n",
+			adjusted_mode->private_flags);
+
 end:
 	return ret;
 }
@@ -574,6 +658,8 @@
 {
 	struct dp_display *dp_disp;
 	struct dp_debug *debug;
+	u32 pclk = 0;
+	u32 rate_ratio = RGB_24BPP_TMDS_CHAR_RATE_RATIO;
 
 	if (!mode || !display) {
 		pr_err("invalid params\n");
@@ -585,7 +671,23 @@
 
 	mode->vrefresh = drm_mode_vrefresh(mode);
 
-	if (mode->clock > dp_disp->max_pclk_khz)
+	if ((mode->flags & SDE_DRM_MODE_FLAG_FMT_MASK) ==
+			DRM_MODE_FLAG_SUPPORTS_YUV420) {
+		rate_ratio = YUV420_24BPP_TMDS_CHAR_RATE_RATIO;
+	} else if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_RGB) &&
+			(mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV420)) {
+		if (mode->clock > dp_disp->max_pclk_khz) {
+			/* Clear RGB and YUV422 support flags */
+			mode->flags &= ~DRM_MODE_FLAG_SUPPORTS_RGB;
+			mode->flags &= ~DRM_MODE_FLAG_SUPPORTS_YUV422;
+			/* Only YUV420 format is now supported */
+			rate_ratio = YUV420_24BPP_TMDS_CHAR_RATE_RATIO;
+		}
+	}
+
+	pclk = mode->clock / rate_ratio;
+
+	if (pclk > dp_disp->max_pclk_khz)
 		return MODE_BAD;
 
 	if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
@@ -594,5 +696,5 @@
 			mode->picture_aspect_ratio != debug->aspect_ratio))
 		return MODE_BAD;
 
-	return dp_disp->validate_mode(dp_disp, mode->clock);
+	return dp_disp->validate_mode(dp_disp, pclk, mode->flags);
 }
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
index 7132699..756298f 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.c
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -14,6 +14,7 @@
 
 #define pr_fmt(fmt)	"[drm-dp] %s: " fmt, __func__
 
+#include "msm_kms.h"
 #include "dp_panel.h"
 
 #define DP_PANEL_DEFAULT_BPP 24
@@ -43,6 +44,17 @@
 	ITU_R_BT_2020_RGB,
 };
 
+enum dp_panel_hdr_yuv_colorimetry {
+	ITU_R_BT_601,
+	ITU_R_BT_709,
+	xvYCC_601,
+	xvYCC_709,
+	sYCC_601,
+	ADOBE_YCC_601,
+	ITU_R_BT_2020_YcCBcCRc,
+	ITU_R_BT_2020_YCBCR,
+};
+
 enum dp_panel_hdr_dynamic_range {
 	VESA,
 	CEA,
@@ -537,6 +549,50 @@
 	panel->catalog->tpg_config(catalog, true);
 }
 
+static int dp_panel_setup_vsc_sdp(struct dp_panel *dp_panel)
+{
+	struct dp_catalog_panel *catalog;
+	struct dp_panel_private *panel;
+	struct dp_panel_info *pinfo;
+	int rc = 0;
+
+	if (!dp_panel) {
+		pr_err("invalid input\n");
+		rc = -EINVAL;
+		goto end;
+	}
+
+	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+	catalog = panel->catalog;
+	pinfo = &panel->dp_panel.pinfo;
+
+	memset(&catalog->vsc_sdp_data, 0, sizeof(catalog->vsc_sdp_data));
+
+	if (pinfo->out_format & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420) {
+		catalog->vsc_sdp_data.vsc_header_byte0 = 0x00;
+		catalog->vsc_sdp_data.vsc_header_byte1 = 0x07;
+		catalog->vsc_sdp_data.vsc_header_byte2 = 0x05;
+		catalog->vsc_sdp_data.vsc_header_byte3 = 0x13;
+
+		/* VSC SDP Payload for DB16 */
+		catalog->vsc_sdp_data.pixel_encoding = YCbCr420;
+		catalog->vsc_sdp_data.colorimetry = ITU_R_BT_709;
+
+		/* VSC SDP Payload for DB17 */
+		catalog->vsc_sdp_data.dynamic_range = CEA;
+
+		/* VSC SDP Payload for DB18 */
+		catalog->vsc_sdp_data.content_type = GRAPHICS;
+
+		catalog->vsc_sdp_data.bpc = pinfo->bpp / 3;
+
+		if (panel->catalog->config_vsc_sdp)
+			panel->catalog->config_vsc_sdp(catalog, true);
+	}
+end:
+	return rc;
+}
+
 static int dp_panel_timing_cfg(struct dp_panel *dp_panel)
 {
 	int rc = 0;
@@ -596,11 +652,37 @@
 	catalog->dp_active = data;
 
 	panel->catalog->timing_cfg(catalog);
+	dp_panel_setup_vsc_sdp(dp_panel);
 	panel->panel_on = true;
 end:
 	return rc;
 }
 
+static u32 dp_panel_get_pixel_clk(struct dp_panel *dp_panel)
+{
+	struct dp_panel_private *panel;
+	struct dp_panel_info *pinfo;
+	u32 pixel_clk_khz = 0;
+	u32 rate_ratio = RGB_24BPP_TMDS_CHAR_RATE_RATIO;
+
+	if (!dp_panel) {
+		pr_err("invalid input\n");
+		return 0;
+	}
+
+	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+	pinfo = &panel->dp_panel.pinfo;
+
+	pixel_clk_khz = pinfo->pixel_clk_khz;
+
+	if (pinfo->out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		rate_ratio = YUV420_24BPP_TMDS_CHAR_RATE_RATIO;
+
+	pixel_clk_khz /= rate_ratio;
+
+	return pixel_clk_khz;
+}
+
 static int dp_panel_edid_register(struct dp_panel_private *panel)
 {
 	int rc = 0;
@@ -619,6 +701,16 @@
 	sde_edid_deinit((void **)&panel->dp_panel.edid_ctrl);
 }
 
+static inline char *get_out_format(u32 out_format)
+{
+	if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+		return "Y420";
+	else if (out_format == MSM_MODE_FLAG_COLOR_FORMAT_YCBCR422)
+		return "Y422";
+	else
+		return "RGB";
+}
+
 static int dp_panel_init_panel_info(struct dp_panel *dp_panel)
 {
 	int rc = 0;
@@ -648,6 +740,7 @@
 			pinfo->v_front_porch,
 			pinfo->v_sync_width);
 	pr_info("pixel clock (KHz)=(%d)\n", pinfo->pixel_clk_khz);
+	pr_info("%s\n", get_out_format(pinfo->out_format));
 	pr_info("bpp = %d\n", pinfo->bpp);
 	pr_info("active low (h|v)=(%d|%d)\n", pinfo->h_active_low,
 		pinfo->v_active_low);
@@ -856,6 +949,7 @@
 	dp_panel->spd_config = dp_panel_spd_config;
 	dp_panel->setup_hdr = dp_panel_setup_hdr;
 	dp_panel->hdr_supported = dp_panel_hdr_supported;
+	dp_panel->get_pixel_clk = dp_panel_get_pixel_clk;
 
 	dp_panel_edid_register(panel);
 
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
index 6c2e186..564ace3 100644
--- a/drivers/gpu/drm/msm/dp/dp_panel.h
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -28,6 +28,7 @@
 	DP_LANE_COUNT_4	= 4,
 };
 
+
 #define DP_MAX_DOWNSTREAM_PORTS 0x10
 
 struct dp_panel_info {
@@ -45,11 +46,13 @@
 	u32 refresh_rate;
 	u32 pixel_clk_khz;
 	u32 bpp;
+	u32 out_format;
 };
 
 struct dp_display_mode {
 	struct dp_panel_info timing;
 	u32 capabilities;
+	u32 flags;
 };
 
 struct dp_panel_in {
@@ -94,6 +97,7 @@
 	void (*tpg_config)(struct dp_panel *dp_panel, bool enable);
 	int (*spd_config)(struct dp_panel *dp_panel);
 	bool (*hdr_supported)(struct dp_panel *dp_panel);
+	u32 (*get_pixel_clk)(struct dp_panel *dp_panel);
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/msm_kms.h b/drivers/gpu/drm/msm/msm_kms.h
index 25523b0..828e0f6 100644
--- a/drivers/gpu/drm/msm/msm_kms.h
+++ b/drivers/gpu/drm/msm/msm_kms.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved.
  * Copyright (C) 2013 Red Hat
  * Author: Rob Clark <robdclark@gmail.com>
  *
@@ -26,6 +26,9 @@
 
 #define MAX_PLANE	4
 
+#define RGB_24BPP_TMDS_CHAR_RATE_RATIO		1
+#define YUV420_24BPP_TMDS_CHAR_RATE_RATIO	2
+
 /**
  * Device Private DRM Mode Flags
  * drm_mode->private_flags