diff mbox

[4/4] drm: bridge/dw-hdmi: use rx sense for plug detection if hpd is unreliable

Message ID 1452243727-7242-4-git-send-email-p.zabel@pengutronix.de (mailing list archive)
State New, archived
Headers show

Commit Message

Philipp Zabel Jan. 8, 2016, 9:02 a.m. UTC
Due to the voltage divider on the HPD line, the HDMI connector on
imx6q-sabrelite doesn't reliably detect connected DVI monitors.
This patch allows to use the RX_SENSE signals as a workaround when
enabled by a boolean device tree property 'hpd-unreliable'.

Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 drivers/gpu/drm/bridge/dw-hdmi.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

Comments

Russell King - ARM Linux Jan. 8, 2016, 11:34 a.m. UTC | #1
On Fri, Jan 08, 2016 at 10:02:07AM +0100, Philipp Zabel wrote:
> Due to the voltage divider on the HPD line, the HDMI connector on
> imx6q-sabrelite doesn't reliably detect connected DVI monitors.
> This patch allows to use the RX_SENSE signals as a workaround when
> enabled by a boolean device tree property 'hpd-unreliable'.

There's a got-cha here.  On iMX6S, the RXSENSE interrupts bounce
around madly if the HDMI interface is not fully configured.  I've
seen this many times at boot time (I've been carrying a patch which
reports the HPD/RXSENSE state to the kernel log for a long time now,
it can be rather noisy.)

It's also out-of-spec for reading the EDID: the EDID is only valid
when HPD is asserted.  When HPD is deasserted, the EDID may not be
accessible.  Using RXSENSE opens a window where the EDID may be
unavailable, or may be mid-way through being updated depending on
how the sink hardware works.

With a Yamaha RX-V677 AV receiver and a Panasonic TV, I've observed
this:

initial(-rxsense,-hpd), AV standby, TV standby.
Connected to AV: +rxsense, +hpd
        EDID reads from AV
TV standby->on: -hpd, 2s, +hpd
        EDID reads from TV
AV standby->on: -hpd, 1.2s, +hpd
	EDID reads combined TV/AV
AV on->standby: -hpd, 1.2s, +hpd
        EDID reads from TV

Note that RXSENSE did not drop, but the EDID changed, and that change
was properly signalled via HPD according to the HDMI standard.
diff mbox

Patch

diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c
index 2388a55..7ffaa44 100644
--- a/drivers/gpu/drm/bridge/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/dw-hdmi.c
@@ -111,6 +111,7 @@  struct dw_hdmi {
 	struct device *dev;
 	struct clk *isfr_clk;
 	struct clk *iahb_clk;
+	bool hpd_unreliable;
 
 	struct hdmi_data_info hdmi_data;
 	const struct dw_hdmi_plat_data *plat_data;
@@ -1413,6 +1414,8 @@  dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
 {
 	struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
 					     connector);
+	/* If HPD is not reliable, use RX_SENSE as fallback */
+	u8 stat_mask = hdmi->hpd_unreliable ? HDMI_PHY_RX_SENSE : HDMI_PHY_HPD;
 
 	mutex_lock(&hdmi->mutex);
 	hdmi->force = DRM_FORCE_UNSPECIFIED;
@@ -1420,7 +1423,7 @@  dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
 	dw_hdmi_update_phy_mask(hdmi);
 	mutex_unlock(&hdmi->mutex);
 
-	if (hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD)
+	if ((hdmi_readb(hdmi, HDMI_PHY_STAT0) & stat_mask) == stat_mask)
 		return connector_status_connected;
 
 	/* free previous EDID block */
@@ -1556,7 +1559,7 @@  static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
 static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 {
 	struct dw_hdmi *hdmi = dev_id;
-	u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat;
+	u8 intr_stat, intr_mask, phy_int_pol, phy_pol_mask, phy_stat;
 
 	intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
 	phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0);
@@ -1583,9 +1586,12 @@  static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 	 * other end of the link.  Use this to decide whether we should
 	 * power on the phy as HPD may be toggled by the sink to merely
 	 * ask the source to re-read the EDID.
+	 * If HPD is known to be unreliable, ignore it completely.
 	 */
-	if (intr_stat &
-	    (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) {
+	intr_mask = hdmi->hpd_unreliable ?
+		    HDMI_IH_PHY_STAT0_RX_SENSE :
+		    HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD;
+	if (intr_stat & intr_mask) {
 		mutex_lock(&hdmi->mutex);
 		if (!hdmi->disabled && !hdmi->force) {
 			/*
@@ -1594,14 +1600,14 @@  static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 			 */
 			if (!(phy_stat & HDMI_PHY_RX_SENSE))
 				hdmi->rxsense = false;
-
 			/*
 			 * Only set the software rxsense status when both
 			 * rxsense and hpd indicates we're connected.
 			 * This avoids what seems to be bad behaviour in
 			 * at least iMX6S versions of the phy.
 			 */
-			if (phy_stat & HDMI_PHY_HPD)
+			else if ((phy_stat & HDMI_PHY_HPD) ||
+				 hdmi->hpd_unreliable)
 				hdmi->rxsense = true;
 
 			dw_hdmi_update_power(hdmi);
@@ -1617,8 +1623,7 @@  static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 	}
 
 	hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
-	hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
-		    HDMI_IH_MUTE_PHY_STAT0);
+	hdmi_writeb(hdmi, ~intr_mask, HDMI_IH_MUTE_PHY_STAT0);
 
 	return IRQ_HANDLED;
 }
@@ -1679,6 +1684,7 @@  int dw_hdmi_bind(struct device *dev, struct device *master,
 	struct device_node *ddc_node;
 	struct dw_hdmi_audio_data audio;
 	struct dw_hdmi *hdmi;
+	u8 intr_mask;
 	int ret;
 	u32 val = 1;
 
@@ -1770,6 +1776,8 @@  int dw_hdmi_bind(struct device *dev, struct device *master,
 
 	initialize_hdmi_ih_mutes(hdmi);
 
+	hdmi->hpd_unreliable = of_property_read_bool(np, "hpd-unreliable");
+
 	ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
 					dw_hdmi_irq, IRQF_SHARED,
 					dev_name(dev), hdmi);
@@ -1801,8 +1809,10 @@  int dw_hdmi_bind(struct device *dev, struct device *master,
 		goto err_iahb;
 
 	/* Unmute interrupts */
-	hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
-		    HDMI_IH_MUTE_PHY_STAT0);
+	intr_mask = hdmi->hpd_unreliable ?
+		    HDMI_IH_PHY_STAT0_RX_SENSE :
+		    HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD;
+	hdmi_writeb(hdmi, ~intr_mask, HDMI_IH_MUTE_PHY_STAT0);
 
 	memset(&pdevinfo, 0, sizeof(pdevinfo));
 	pdevinfo.parent = dev;