Эх сурвалжийг харах

disp: msm: dp: add debug node to capture source and sink crc

This change adds a debug node named 'crc' to drm_dp to read
the frame CRC values for DP controller and DP Sink. In order
to facilitate the immediate read of the CRC values when
accessed, it enables the CRC calculation on the controller
and sink automatically when the stream is enabled. In addition
to the frame CRC values it also reads the MISR values from
controller and PHY to validate the data flow from controller
to PHY.

Change-Id: I1acee2dba931e4635caf4a400e336a72c86e88bf
Signed-off-by: Rajkumar Subbiah <[email protected]>
Rajkumar Subbiah 3 жил өмнө
parent
commit
7eef92843d

+ 107 - 0
msm/dp/dp_catalog.c

@@ -1118,6 +1118,7 @@ static void dp_catalog_panel_config_ctrl(struct dp_catalog_panel *panel,
 	struct dp_catalog_private *catalog;
 	struct dp_io_data *io_data;
 	u32 strm_reg_off = 0, mainlink_ctrl;
+	u32 reg;
 
 	if (!panel) {
 		DP_ERR("invalid input\n");
@@ -1150,6 +1151,10 @@ static void dp_catalog_panel_config_ctrl(struct dp_catalog_panel *panel,
 		dp_write(MMSS_DP_ASYNC_FIFO_CONFIG, 0x01);
 	else
 		dp_write(MMSS_DP_ASYNC_FIFO_CONFIG, 0x00);
+
+	reg = dp_read(MMSS_DP_TIMING_ENGINE_EN);
+	reg |= BIT(8);
+	dp_write(MMSS_DP_TIMING_ENGINE_EN, reg);
 }
 
 static void dp_catalog_panel_config_dto(struct dp_catalog_panel *panel,
@@ -1419,6 +1424,77 @@ static void dp_catalog_ctrl_usb_reset(struct dp_catalog_ctrl *ctrl, bool flip)
 	wmb();
 }
 
+static int dp_catalog_ctrl_setup_misr(struct dp_catalog_ctrl *ctrl)
+{
+	struct dp_catalog_private *catalog;
+	struct dp_io_data *io_data;
+	u32 val;
+
+	if (!ctrl) {
+		DP_ERR("invalid input\n");
+		return -EINVAL;
+	}
+
+	catalog = dp_catalog_get_priv(ctrl);
+
+	io_data = catalog->io.dp_phy;
+	dp_write(DP_PHY_MISR_CTRL, 0x3);
+	/* make sure misr hw is reset */
+	wmb();
+	dp_write(DP_PHY_MISR_CTRL, 0x1);
+	/* make sure misr is brought out of reset */
+	wmb();
+
+	io_data = catalog->io.dp_link;
+	val = 1;	// frame count
+	val |= BIT(10); // clear status
+	val |= BIT(8);  // enable
+	dp_write(DP_MISR40_CTRL, val);
+	/* make sure misr control is applied */
+	wmb();
+
+	return 0;
+}
+
+static int dp_catalog_ctrl_read_misr(struct dp_catalog_ctrl *ctrl, struct dp_misr40_data *data)
+{
+	struct dp_catalog_private *catalog;
+	struct dp_io_data *io_data;
+	u32 val;
+	int i, j;
+	u32 addr;
+
+	if (!ctrl) {
+		DP_ERR("invalid input\n");
+		return -EINVAL;
+	}
+
+	catalog = dp_catalog_get_priv(ctrl);
+
+	io_data = catalog->io.dp_phy;
+	val = dp_read(DP_PHY_MISR_STATUS);
+	if (!val) {
+		DP_WARN("phy misr not ready!");
+		return -EAGAIN;
+	}
+
+	addr = DP_PHY_MISR_TX0;
+	for (i = 0; i < 8; i++) {
+		data->phy_misr[i] = 0;
+		for (j = 0; j < 4; j++) {
+			val = dp_read(addr) & 0xff;
+			data->phy_misr[i] |= val << (j * 8);
+			addr += 4;
+		}
+	}
+
+	io_data = catalog->io.dp_link;
+	for (i = 0; i < 8; i++)
+		data->ctrl_misr[i] = dp_read(DP_MISR40_TX0 + (i * 4));
+
+	return 0;
+}
+
 static void dp_catalog_panel_tpg_cfg(struct dp_catalog_panel *panel, u32 pattern)
 {
 	struct dp_catalog_private *catalog;
@@ -1627,6 +1703,34 @@ static bool dp_catalog_panel_dhdr_busy(struct dp_catalog_panel *panel)
 	return dp_flush & BIT(DP_DHDR_FLUSH) ? true : false;
 }
 
+static int dp_catalog_panel_get_src_crc(struct dp_catalog_panel *panel, u16 *crc)
+{
+	struct dp_catalog_private *catalog;
+	struct dp_io_data *io_data;
+	u32 offset;
+	u32 reg;
+
+	if (panel->stream_id >= DP_STREAM_MAX) {
+		DP_ERR("invalid stream_id:%d\n", panel->stream_id);
+		return -EINVAL;
+	}
+
+	catalog = dp_catalog_get_priv(panel);
+	io_data = catalog->io.dp_link;
+
+	if (panel->stream_id == DP_STREAM_0)
+		offset = MMSS_DP_PSR_CRC_RG;
+	else
+		offset = MMSS_DP1_CRC_RG;
+
+	reg = dp_read(offset); //GR
+	crc[0] = reg & 0xffff;
+	crc[1] = reg >> 16;
+	crc[2] = dp_read(offset + 4); //B
+
+	return 0;
+}
+
 static void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl)
 {
 	u32 sw_reset;
@@ -2896,6 +3000,8 @@ struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_parser *parser)
 		.fec_config = dp_catalog_ctrl_fec_config,
 		.mainlink_levels = dp_catalog_ctrl_mainlink_levels,
 		.late_phy_init = dp_catalog_ctrl_late_phy_init,
+		.setup_misr = dp_catalog_ctrl_setup_misr,
+		.read_misr = dp_catalog_ctrl_read_misr,
 	};
 	struct dp_catalog_hpd hpd = {
 		.config_hpd	= dp_catalog_hpd_config_hpd,
@@ -2925,6 +3031,7 @@ struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_parser *parser)
 		.pps_flush = dp_catalog_panel_pps_flush,
 		.dhdr_flush = dp_catalog_panel_dhdr_flush,
 		.dhdr_busy = dp_catalog_panel_dhdr_busy,
+		.get_src_crc = dp_catalog_panel_get_src_crc,
 	};
 
 	if (!dev || !parser) {

+ 8 - 0
msm/dp/dp_catalog.h

@@ -46,6 +46,11 @@ struct dp_catalog_vsc_sdp_colorimetry {
 	u8 data[32];
 };
 
+struct dp_misr40_data {
+	u32 ctrl_misr[8];
+	u32 phy_misr[8];
+};
+
 struct dp_catalog_aux {
 	u32 data;
 	u32 isr;
@@ -103,6 +108,8 @@ struct dp_catalog_ctrl {
 
 	int (*late_phy_init)(struct dp_catalog_ctrl *ctrl,
 					u8 lane_cnt, bool flipped);
+	int (*setup_misr)(struct dp_catalog_ctrl *ctrl);
+	int (*read_misr)(struct dp_catalog_ctrl *ctrl, struct dp_misr40_data *data);
 };
 
 struct dp_catalog_hpd {
@@ -221,6 +228,7 @@ struct dp_catalog_panel {
 	void (*pps_flush)(struct dp_catalog_panel *panel);
 	void (*dhdr_flush)(struct dp_catalog_panel *panel);
 	bool (*dhdr_busy)(struct dp_catalog_panel *panel);
+	int (*get_src_crc)(struct dp_catalog_panel *panel, u16 *crc);
 };
 
 struct dp_catalog;

+ 27 - 0
msm/dp/dp_ctrl.c

@@ -1308,6 +1308,7 @@ static int dp_ctrl_stream_on(struct dp_ctrl *dp_ctrl, struct dp_panel *panel)
 	/* wait for link training completion before fec config as per spec */
 	dp_ctrl_fec_setup(ctrl);
 	dp_ctrl_dsc_setup(ctrl, panel);
+	panel->sink_crc_enable(panel, true);
 
 	return rc;
 }
@@ -1515,6 +1516,30 @@ void dp_ctrl_set_sim_mode(struct dp_ctrl *dp_ctrl, bool en)
 	DP_INFO("sim_mode=%d\n", ctrl->sim_mode);
 }
 
+int dp_ctrl_setup_misr(struct dp_ctrl *dp_ctrl)
+{
+	struct dp_ctrl_private *ctrl;
+
+	if (!dp_ctrl)
+		return -EINVAL;
+
+	ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+	return ctrl->catalog->setup_misr(ctrl->catalog);
+}
+
+int dp_ctrl_read_misr(struct dp_ctrl *dp_ctrl, struct dp_misr40_data *data)
+{
+	struct dp_ctrl_private *ctrl;
+
+	if (!dp_ctrl)
+		return -EINVAL;
+
+	ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+	return ctrl->catalog->read_misr(ctrl->catalog, data);
+}
+
 struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in)
 {
 	int rc = 0;
@@ -1565,6 +1590,8 @@ struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in)
 	dp_ctrl->stream_pre_off = dp_ctrl_stream_pre_off;
 	dp_ctrl->set_mst_channel_info = dp_ctrl_set_mst_channel_info;
 	dp_ctrl->set_sim_mode = dp_ctrl_set_sim_mode;
+	dp_ctrl->setup_misr = dp_ctrl_setup_misr;
+	dp_ctrl->read_misr = dp_ctrl_read_misr;
 
 	return dp_ctrl;
 error:

+ 3 - 0
msm/dp/dp_ctrl.h

@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -32,6 +33,8 @@ struct dp_ctrl {
 			enum dp_stream_id strm,
 			u32 ch_start_slot, u32 ch_tot_slots);
 	void (*set_sim_mode)(struct dp_ctrl *dp_ctrl, bool en);
+	int (*setup_misr)(struct dp_ctrl *dp_ctrl);
+	int (*read_misr)(struct dp_ctrl *dp_ctrl, struct dp_misr40_data *data);
 };
 
 struct dp_ctrl_in {

+ 97 - 1
msm/dp/dp_debug.c

@@ -333,7 +333,7 @@ static ssize_t dp_debug_read_dpcd(struct file *file,
 		}
 	}
 
-	len += scnprintf(buf + len , buf_size - len, "%04x: ", debug->dpcd_offset);
+	len += scnprintf(buf + len, buf_size - len, "%04x: ", debug->dpcd_offset);
 
 	while (offset < debug->dpcd_size)
 		len += scnprintf(buf + len, buf_size - len, "%02x ", dpcd[offset++]);
@@ -351,6 +351,90 @@ bail:
 	return len;
 }
 
+static ssize_t dp_debug_read_crc(struct file *file, char __user *user_buff, size_t count,
+		loff_t *ppos)
+{
+	struct dp_debug_private *debug = file->private_data;
+	char *buf;
+	int const buf_size = SZ_4K;
+	u32 len = 0;
+	u16 src_crc[3] = {0};
+	u16 sink_crc[3] = {0};
+	struct dp_misr40_data misr40 = {0};
+	u32 retries = 2;
+	struct drm_connector *drm_conn;
+	struct sde_connector *sde_conn;
+	struct dp_panel *panel;
+	int i;
+	int rc;
+
+	if (!debug || !debug->aux)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	buf = kzalloc(buf_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&debug->lock);
+
+	if (!debug->panel || !debug->ctrl)
+		goto bail;
+
+	if (debug->panel->mst_state) {
+		drm_conn = drm_connector_lookup((*debug->connector)->dev, NULL, debug->mst_con_id);
+		if (!drm_conn) {
+			DP_ERR("connector %u not in mst list\n", debug->mst_con_id);
+			goto bail;
+		}
+
+		sde_conn = to_sde_connector(drm_conn);
+		panel = sde_conn->drv_panel;
+	} else {
+		panel = debug->panel;
+	}
+
+	panel->get_src_crc(panel, src_crc);
+	panel->get_sink_crc(panel, sink_crc);
+
+	len += scnprintf(buf + len, buf_size - len, "FRAME_CRC:\nSource vs Sink\n");
+
+	len += scnprintf(buf + len, buf_size - len, "CRC_R: %04X %04X\n", src_crc[0], sink_crc[0]);
+	len += scnprintf(buf + len, buf_size - len, "CRC_G: %04X %04X\n", src_crc[1], sink_crc[1]);
+	len += scnprintf(buf + len, buf_size - len, "CRC_B: %04X %04X\n", src_crc[2], sink_crc[2]);
+
+	debug->ctrl->setup_misr(debug->ctrl);
+
+	while (retries--) {
+		mutex_unlock(&debug->lock);
+		msleep(30);
+		mutex_lock(&debug->lock);
+
+		rc = debug->ctrl->read_misr(debug->ctrl, &misr40);
+		if (rc != -EAGAIN)
+			break;
+	}
+
+	len += scnprintf(buf + len, buf_size - len, "\nMISR40:\nCTLR vs PHY\n");
+	for (i = 0; i < 4; i++) {
+		len += scnprintf(buf + len, buf_size - len, "Lane%d %08X%08X %08X%08X\n", i,
+				misr40.ctrl_misr[2 * i], misr40.ctrl_misr[(2 * i) + 1],
+				misr40.phy_misr[2 * i], misr40.phy_misr[(2 * i) + 1]);
+	}
+
+	len = min_t(size_t, count, len);
+	if (!copy_to_user(user_buff, buf, len))
+		*ppos += len;
+
+bail:
+	mutex_unlock(&debug->lock);
+	kfree(buf);
+
+	return len;
+}
+
 static ssize_t dp_debug_write_hpd(struct file *file,
 		const char __user *user_buff, size_t count, loff_t *ppos)
 {
@@ -1871,6 +1955,11 @@ static const struct file_operations dpcd_fops = {
 	.read = dp_debug_read_dpcd,
 };
 
+static const struct file_operations crc_fops = {
+	.open = simple_open,
+	.read = dp_debug_read_crc,
+};
+
 static const struct file_operations connected_fops = {
 	.open = simple_open,
 	.read = dp_debug_read_connected,
@@ -2104,6 +2193,13 @@ static int dp_debug_init_sink_caps(struct dp_debug_private *debug,
 		return rc;
 	}
 
+	file = debugfs_create_file("crc", 0644, dir, debug, &crc_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs crc failed, rc=%d\n", DEBUG_NAME, rc);
+		return rc;
+	}
+
 	return rc;
 }
 

+ 2 - 0
msm/dp/dp_link.h

@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  *
  * Copyright (c) 2008 Keith Packard
@@ -38,6 +39,7 @@
 struct drm_dp_aux;
 
 #define DP_LINK_CAP_ENHANCED_FRAMING (1 << 0)
+#define DP_LINK_CAP_CRC (1 << 1)
 
 struct drm_dp_link {
 	unsigned char revision;

+ 71 - 0
msm/dp/dp_panel.c

@@ -1682,6 +1682,10 @@ static int dp_panel_read_dpcd(struct dp_panel *dp_panel, bool multi_func)
 	if (drm_dp_enhanced_frame_cap(dpcd))
 		link_info->capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
 
+	rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_TEST_SINK_MISC, &temp, 1);
+	if ((rlen == 1) && (temp & DP_TEST_CRC_SUPPORTED))
+		link_info->capabilities |= DP_LINK_CAP_CRC;
+
 	dfp_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] &
 						DP_DOWN_STREAM_PORT_COUNT;
 
@@ -3050,6 +3054,70 @@ static void dp_panel_update_pps(struct dp_panel *dp_panel, char *pps_cmd)
 	catalog->pps_flush(catalog);
 }
 
+int dp_panel_get_src_crc(struct dp_panel *dp_panel, u16 *crc)
+{
+	struct dp_catalog_panel *catalog;
+	struct dp_panel_private *panel;
+
+	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+	catalog = panel->catalog;
+	return catalog->get_src_crc(catalog, crc);
+}
+
+int dp_panel_get_sink_crc(struct dp_panel *dp_panel, u16 *crc)
+{
+	int rc = 0;
+	struct dp_panel_private *panel;
+	struct drm_dp_aux *drm_aux;
+	u8 crc_bytes[6];
+
+	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+	drm_aux = panel->aux->drm_aux;
+
+	/*
+	 * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes
+	 * per component (RGB or CrYCb).
+	 */
+	rc = drm_dp_dpcd_read(drm_aux, DP_TEST_CRC_R_CR, crc_bytes, 6);
+	if (rc < 0)
+		return rc;
+
+	rc = 0;
+	crc[0] = crc_bytes[0] | crc_bytes[1] << 8;
+	crc[1] = crc_bytes[2] | crc_bytes[3] << 8;
+	crc[2] = crc_bytes[4] | crc_bytes[5] << 8;
+
+	return rc;
+}
+
+int dp_panel_sink_crc_enable(struct dp_panel *dp_panel, bool enable)
+{
+	int rc = 0;
+	struct dp_panel_private *panel;
+	struct drm_dp_aux *drm_aux;
+	ssize_t ret;
+	u8 buf;
+
+	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+	drm_aux = panel->aux->drm_aux;
+
+	if (dp_panel->link_info.capabilities & DP_LINK_CAP_CRC) {
+		ret = drm_dp_dpcd_readb(drm_aux, DP_TEST_SINK, &buf);
+		if (ret < 0)
+			return ret;
+
+		ret = drm_dp_dpcd_writeb(drm_aux, DP_TEST_SINK, buf | DP_TEST_SINK_START);
+		if (ret < 0)
+			return ret;
+
+		drm_dp_dpcd_readb(drm_aux, DP_TEST_SINK, &buf);
+		DP_DEBUG("Enabled CRC: %x\n", buf);
+	}
+
+	return rc;
+}
+
 struct dp_panel *dp_panel_get(struct dp_panel_in *in)
 {
 	int rc = 0;
@@ -3122,6 +3190,9 @@ struct dp_panel *dp_panel_get(struct dp_panel_in *in)
 	dp_panel->read_mst_cap = dp_panel_read_mst_cap;
 	dp_panel->convert_to_dp_mode = dp_panel_convert_to_dp_mode;
 	dp_panel->update_pps = dp_panel_update_pps;
+	dp_panel->get_src_crc = dp_panel_get_src_crc;
+	dp_panel->get_sink_crc = dp_panel_get_sink_crc;
+	dp_panel->sink_crc_enable = dp_panel_sink_crc_enable;
 
 	sde_conn = to_sde_connector(dp_panel->connector);
 	sde_conn->drv_panel = dp_panel;

+ 3 - 0
msm/dp/dp_panel.h

@@ -193,6 +193,9 @@ struct dp_panel {
 		const struct drm_display_mode *drm_mode,
 		struct dp_display_mode *dp_mode);
 	void (*update_pps)(struct dp_panel *dp_panel, char *pps_cmd);
+	int (*sink_crc_enable)(struct dp_panel *dp_panel, bool enable);
+	int (*get_src_crc)(struct dp_panel *dp_panel, u16 *crc);
+	int (*get_sink_crc)(struct dp_panel *dp_panel, u16 *crc);
 };
 
 struct dp_tu_calc_input {

+ 14 - 0
msm/dp/dp_reg.h

@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
+ * Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.
  * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
  */
 
@@ -86,9 +87,16 @@
 #define MMSS_DP_AUDIO_TIMING_RBR_48		(0x00000094)
 #define MMSS_DP_AUDIO_TIMING_HBR_48		(0x00000098)
 
+#define DP_MISR40_CTRL				(0x000000D0)
+#define DP_MISR40_TX0				(0x000000D4)
+#define DP_MISR40_TX1				(0x000000DC)
+#define DP_MISR40_TX2				(0x000000E4)
+#define DP_MISR40_TX3				(0x000000EC)
 #define MMSS_DP_PSR_CRC_RG			(0x00000154)
 #define MMSS_DP_PSR_CRC_B			(0x00000158)
 
+#define MMSS_DP1_CRC_RG				(0x00000164)
+#define MMSS_DP1_CRC_B				(0x00000168)
 #define DP_COMPRESSION_MODE_CTRL		(0x00000180)
 #define DP_PPS_HB_0_3				(0x00000184)
 #define DP_PPS_PB_0_3				(0x00000188)
@@ -384,6 +392,12 @@
 #define DP_PHY_AUX_INTERRUPT_STATUS_V420	(0x00D8)
 #define DP_PHY_AUX_INTERRUPT_STATUS_V600        (0x00E0)
 #define DP_PHY_SPARE0_V420			(0x00C8)
+#define DP_PHY_MISR_CTRL			(0x00C0)
+#define DP_PHY_MISR_STATUS			(0x010C)
+#define DP_PHY_MISR_TX0				(0x0110)
+#define DP_PHY_MISR_TX1				(0x0130)
+#define DP_PHY_MISR_TX2				(0x0150)
+#define DP_PHY_MISR_TX3				(0x0170)
 #define TXn_TX_DRV_LVL_V420			(0x0014)
 #define TXn_TRANSCEIVER_BIAS_EN_V420		(0x0054)
 #define TXn_HIGHZ_DRVR_EN_V420			(0x0058)