Browse Source

drm/bridge: cdns-mhdp: Check link status if HPD interrupt is received

Add code to interrupt handler to check if the DisplayPort link is
still up if it is supposed to be. First check that the HPD state is
asserted (the connector is plugged). Then read DPCD status and check
if the channel eq and clock recovery is still ok. Mark the connector's
link status property as bad if DPCD read fails or link status is not
ok. The link status check and the property setting is done while
holding mode_config.mutex to avoid a race. The fine grained locking
implementation in drm_connector_set_link_status_property() is broken
so we must use the legacy giant-lock.

Signed-off-by: Jyri Sarha <jsarha@ti.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Jyri Sarha 6 years ago
parent
commit
2ef56ca11b
1 changed files with 57 additions and 2 deletions
  1. 57 2
      drivers/gpu/drm/bridge/cdns-mhdp.c

+ 57 - 2
drivers/gpu/drm/bridge/cdns-mhdp.c

@@ -85,6 +85,11 @@
 #define FW_STANDBY				0
 #define FW_ACTIVE				1
 
+#define DPTX_READ_EVENT_HPD_TO_HIGH            BIT(0)
+#define DPTX_READ_EVENT_HPD_TO_LOW             BIT(1)
+#define DPTX_READ_EVENT_HPD_PULSE              BIT(2)
+#define DPTX_READ_EVENT_HPD_STATE              BIT(3)
+
 static inline u32 get_unaligned_be24(const void *p)
 {
 	const u8 *_p = p;
@@ -492,7 +497,7 @@ int cdns_mhdp_get_edid_block(void *data, u8 *edid,
 	return ret;
 }
 
-static __maybe_unused
+static
 int cdns_mhdp_read_event(struct cdns_mhdp_device *mhdp)
 {
 	u8 event = 0;
@@ -778,6 +783,53 @@ static int load_firmware(struct cdns_mhdp_device *mhdp)
 	return 0;
 }
 
+static void mhdp_check_link(struct cdns_mhdp_device *mhdp)
+{
+	struct drm_connector *conn = &mhdp->connector;
+	u8 status[DP_LINK_STATUS_SIZE];
+	bool hpd_state;
+	int hpd_event;
+	int ret;
+
+	/* Nothing to check if there is no link */
+	if (!mhdp->link_up)
+		return;
+
+	hpd_event = cdns_mhdp_read_event(mhdp);
+
+	/* Geting event bits failed, bail out */
+	if (hpd_event < 0) {
+		dev_warn(mhdp->dev, "%s: read event failed: %d\n",
+			 __func__, hpd_event);
+		return;
+	}
+
+	hpd_state = !!(hpd_event & DPTX_READ_EVENT_HPD_STATE);
+
+	/* No point the check the link if HPD is down (cable is unplugged) */
+	if (!hpd_state)
+		return;
+
+	/*
+	 * Prevent display reconfiguration between link check and link
+	 * status property setting. We must use the legacy giant-lock
+	 * since drm_connector_set_link_status_property()'s fine
+	 * grained DRM locking implementation is broken.
+	 */
+	mutex_lock(&conn->dev->mode_config.mutex);
+
+	/* Check if the link is still up */
+	ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status);
+
+	if (ret < 0 || /* If dpcd read fails, assume the link is down too */
+	    !drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) ||
+	    !drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes))
+		/* Link is broken, indicate it with the link status property */
+		drm_connector_set_link_status_property(conn, DRM_MODE_LINK_STATUS_BAD);
+
+	mutex_unlock(&conn->dev->mode_config.mutex);
+}
+
 static irqreturn_t mhdp_irq_handler(int irq, void *data)
 {
 	struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data;
@@ -803,8 +855,11 @@ static irqreturn_t mhdp_irq_handler(int irq, void *data)
 	bridge_attached = mhdp->bridge_attached;
 	spin_unlock(&mhdp->start_lock);
 
-	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD))
+	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) {
+		mhdp_check_link(mhdp);
+
 		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
+	}
 
 	return IRQ_HANDLED;
 }