瀏覽代碼

disp: msm: dp: improve interop experience for fast hotplug scenarios

Current implementation waits for 10ms prior to sending the connection
notification to user mode. This delay is to check for any potential
IRQ HPD event from the sink which may require a link maintenance.
However, this delay may not be sufficient for certain use cases.
Increase this delay to 150ms and modify the implementation to exit
the wait whenever an IRQ HPD is received. This ensures that we can
process the IRQ HPD in a timely manner as per the specification. To
further improve debug ability, add the support to configure this delay
though debugfs:

   echo [delay_ms] > /sys/kernel/debug/drm_dp/connect_notification_delay_ms

Certain cables are unable to handle back-to-back HPD notifications and
may end up skipping some events. To improve interoperability, delay the
handling of disconnect notification. Sinks would typically issue an HPD
high following an HPD low only after they sense that the mainlink has
been torn down. Delaying the handling of HPD low would in turn delay the
issuing of the subsequent HPD high from the sink. Here again, make this
delay configurable through debugfs to improve debug ability of these
interop issues:

   echo [delay_ms] > /sys/kernel/debug/drm_dp/disconnect_delay_ms

Change-Id: Ie29198af4dcda6d392798a3a93ebb3ddaa6746c8
Signed-off-by: Aravind Venkateswaran <[email protected]>
Aravind Venkateswaran 4 年之前
父節點
當前提交
7454e06259
共有 3 個文件被更改,包括 82 次插入3 次删除
  1. 35 0
      msm/dp/dp_debug.c
  2. 12 0
      msm/dp/dp_debug.h
  3. 35 3
      msm/dp/dp_display.c

+ 35 - 0
msm/dp/dp_debug.c

@@ -2275,6 +2275,37 @@ static int dp_debug_init_feature_toggle(struct dp_debug_private *debug,
 	return rc;
 }
 
+static int dp_debug_init_configs(struct dp_debug_private *debug,
+		struct dentry *dir)
+{
+	int rc = 0;
+	struct dentry *file;
+
+	file = debugfs_create_ulong("connect_notification_delay_ms", 0644, dir,
+		&debug->dp_debug.connect_notification_delay_ms);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs connect_notification_delay_ms failed, rc=%d\n",
+		       DEBUG_NAME, rc);
+		return rc;
+	}
+	debug->dp_debug.connect_notification_delay_ms =
+		DEFAULT_CONNECT_NOTIFICATION_DELAY_MS;
+
+	file = debugfs_create_u32("disconnect_delay_ms", 0644, dir,
+		&debug->dp_debug.disconnect_delay_ms);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs disconnect_delay_ms failed, rc=%d\n",
+		       DEBUG_NAME, rc);
+		return rc;
+	}
+	debug->dp_debug.disconnect_delay_ms = DEFAULT_DISCONNECT_DELAY_MS;
+
+	return rc;
+
+}
+
 static int dp_debug_init(struct dp_debug *dp_debug)
 {
 	int rc = 0;
@@ -2341,6 +2372,10 @@ static int dp_debug_init(struct dp_debug *dp_debug)
 	if (rc)
 		goto error_remove_dir;
 
+	rc = dp_debug_init_configs(debug, dir);
+	if (rc)
+		goto error_remove_dir;
+
 	return 0;
 
 error_remove_dir:

+ 12 - 0
msm/dp/dp_debug.h

@@ -41,6 +41,11 @@
 	pr_err("[drm:%s][msm-dp-err][%-4d]"fmt, __func__,   \
 		       current->pid, ##__VA_ARGS__)
 
+#define DEFAULT_DISCONNECT_DELAY_MS 0
+#define MAX_DISCONNECT_DELAY_MS 10000
+#define DEFAULT_CONNECT_NOTIFICATION_DELAY_MS 150
+#define MAX_CONNECT_NOTIFICATION_DELAY_MS 5000
+
 /**
  * struct dp_debug
  * @debug_en: specifies whether debug mode enabled
@@ -63,6 +68,10 @@
  * @mst_sim_remove_con: specifies whether sim connector is to be removed
  * @mst_sim_remove_con_id: specifies id of sim connector to be removed
  * @mst_port_cnt: number of mst ports to be added during hpd
+ * @connect_notification_delay_ms: time (in ms) to wait for any attention
+ *              messages before sending the connect notification uevent
+ * @disconnect_delay_ms: time (in ms) to wait before turning off the mainlink
+ *              in response to HPD low of cable disconnect event
  */
 struct dp_debug {
 	bool debug_en;
@@ -85,6 +94,9 @@ struct dp_debug {
 	bool mst_sim_remove_con;
 	int mst_sim_remove_con_id;
 	u32 mst_port_cnt;
+	unsigned long connect_notification_delay_ms;
+	u32 disconnect_delay_ms;
+
 	struct dp_mst_connector mst_connector_cache;
 	u8 *(*get_edid)(struct dp_debug *dp_debug);
 	void (*abort)(struct dp_debug *dp_debug);

+ 35 - 3
msm/dp/dp_display.c

@@ -11,6 +11,7 @@
 #include <linux/of_irq.h>
 #include <linux/soc/qcom/fsa4480-i2c.h>
 #include <linux/usb/phy.h>
+#include <linux/jiffies.h>
 
 #include "sde_connector.h"
 
@@ -158,6 +159,7 @@ struct dp_display_private {
 	struct device_node *aux_switch_node;
 	struct dentry *root;
 	struct completion notification_comp;
+	struct completion attention_comp;
 
 	struct dp_hpd     *hpd;
 	struct dp_parser  *parser;
@@ -1026,6 +1028,8 @@ static void dp_display_host_deinit(struct dp_display_private *dp)
 static int dp_display_process_hpd_high(struct dp_display_private *dp)
 {
 	int rc = -EINVAL;
+	unsigned long wait_timeout_ms;
+	unsigned long t;
 
 	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state);
 	mutex_lock(&dp->session_lock);
@@ -1101,7 +1105,14 @@ end:
 	 * Delay the HPD connect notification to see if sink generates any
 	 * IRQ HPDs immediately after the HPD high.
 	 */
-	usleep_range(10000, 10100);
+	reinit_completion(&dp->attention_comp);
+	wait_timeout_ms = min_t(unsigned long,
+			dp->debug->connect_notification_delay_ms,
+			(unsigned long) MAX_CONNECT_NOTIFICATION_DELAY_MS);
+	t = wait_for_completion_timeout(&dp->attention_comp,
+		msecs_to_jiffies(wait_timeout_ms));
+	DP_DEBUG("wait_timeout=%lu ms, time_waited=%u ms\n", wait_timeout_ms,
+		jiffies_to_msecs(t));
 
 	/*
 	 * If an IRQ HPD is pending, then do not send a connect notification.
@@ -1118,7 +1129,7 @@ end:
 	 */
 	if (!dp->mst.mst_active &&
 		(work_busy(&dp->attention_work) == WORK_BUSY_PENDING)) {
-		SDE_EVT32_EXTERNAL(dp->state, 99);
+		SDE_EVT32_EXTERNAL(dp->state, 99, jiffies_to_msecs(t));
 		DP_DEBUG("Attention pending, skip HPD notification\n");
 		goto skip_notify;
 	}
@@ -1127,7 +1138,8 @@ end:
 		dp_display_send_hpd_notification(dp);
 
 skip_notify:
-	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state, rc);
+	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state,
+		wait_timeout_ms, rc);
 	return rc;
 }
 
@@ -1327,6 +1339,9 @@ static int dp_display_handle_disconnect(struct dp_display_private *dp)
 
 static void dp_display_disconnect_sync(struct dp_display_private *dp)
 {
+	int disconnect_delay_ms;
+
+	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state);
 	/* cancel any pending request */
 	dp_display_state_add(DP_STATE_ABORTED);
 
@@ -1338,7 +1353,22 @@ static void dp_display_disconnect_sync(struct dp_display_private *dp)
 	cancel_work_sync(&dp->attention_work);
 	flush_workqueue(dp->wq);
 
+	/*
+	 * Delay the teardown of the mainlink for better interop experience.
+	 * It is possible that certain sinks can issue an HPD high immediately
+	 * following an HPD low as soon as they detect the mainlink being
+	 * turned off. This can sometimes result in the HPD low pulse getting
+	 * lost with certain cable. This issue is commonly seen when running
+	 * DP LL CTS test 4.2.1.3.
+	 */
+	disconnect_delay_ms = min_t(u32, dp->debug->disconnect_delay_ms,
+			(u32) MAX_DISCONNECT_DELAY_MS);
+	DP_DEBUG("disconnect delay = %d ms\n", disconnect_delay_ms);
+	msleep(disconnect_delay_ms);
+
 	dp_display_handle_disconnect(dp);
+	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state,
+		disconnect_delay_ms);
 }
 
 static int dp_display_usbpd_disconnect_cb(struct device *dev)
@@ -1559,6 +1589,7 @@ static int dp_display_usbpd_attention_cb(struct device *dev)
 	} else if ((dp->hpd->hpd_irq && dp_display_state_is(DP_STATE_READY)) ||
 			dp->debug->mst_hpd_sim) {
 		queue_work(dp->wq, &dp->attention_work);
+		complete_all(&dp->attention_comp);
 	} else if (dp->process_hpd_connect ||
 			 !dp_display_state_is(DP_STATE_CONNECTED)) {
 		dp_display_state_remove(DP_STATE_ABORTED);
@@ -3371,6 +3402,7 @@ static int dp_display_probe(struct platform_device *pdev)
 	}
 
 	init_completion(&dp->notification_comp);
+	init_completion(&dp->attention_comp);
 
 	dp->pdev = pdev;
 	dp->name = "drm_dp";