From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megi@xff.cz>
Date: Fri, 1 Sep 2023 00:57:04 +0200
Subject: drm: bridge: dw-hdmi: Allow to accept HPD status from other drivers

This change allows other drivers to provide HPD status.

Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 63 +++++++++-
 1 file changed, 59 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 6c1d79474505..7053b98e55dc 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -9,6 +9,7 @@
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/extcon.h>
 #include <linux/hdmi.h>
 #include <linux/i2c.h>
 #include <linux/irq.h>
@@ -197,6 +198,8 @@ struct dw_hdmi {
 	hdmi_codec_plugged_cb plugged_cb;
 	struct device *codec_dev;
 	enum drm_connector_status last_connector_result;
+	struct extcon_dev *extcon;
+	struct notifier_block extcon_nb;
 };
 
 #define HDMI_IH_PHY_STAT0_RX_SENSE \
@@ -1689,6 +1692,12 @@ static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
 enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
 					       void *data)
 {
+	if (hdmi->extcon) {
+		return extcon_get_state(hdmi->extcon, EXTCON_DISP_HDMI) > 0
+			? connector_status_connected
+			: connector_status_disconnected;
+	}
+
 	return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ?
 		connector_status_connected : connector_status_disconnected;
 }
@@ -1699,7 +1708,7 @@ void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
 {
 	u8 old_mask = hdmi->phy_mask;
 
-	if (force || disabled || !rxsense)
+	if (force || disabled || !rxsense || hdmi->extcon)
 		hdmi->phy_mask |= HDMI_PHY_RX_SENSE;
 	else
 		hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE;
@@ -3124,7 +3133,7 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 			status = connector_status_disconnected;
 	}
 
-	if (status != connector_status_unknown) {
+	if (status != connector_status_unknown && !hdmi->extcon) {
 		dev_dbg(hdmi->dev, "EVENT=%s\n",
 			status == connector_status_connected ?
 			"plugin" : "plugout");
@@ -3333,6 +3342,25 @@ bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi)
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_bus_fmt_is_420);
 
+static int dw_hdmi_extcon_notifier(struct notifier_block *nb,
+				   unsigned long event, void *ptr)
+{
+	struct dw_hdmi *hdmi = container_of(nb, struct dw_hdmi, extcon_nb);
+
+	enum drm_connector_status status = dw_hdmi_phy_read_hpd(hdmi, NULL);
+
+	dev_info(hdmi->dev, "EVENT=%s\n",
+		 status == connector_status_connected ? "plugin" : "plugout");
+
+	if (hdmi->bridge.dev) {
+		//XXX: ???
+		drm_helper_hpd_irq_event(hdmi->bridge.dev);
+		drm_bridge_hpd_notify(&hdmi->bridge, status);
+	}
+
+	return NOTIFY_DONE;
+}
+
 struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 			      const struct dw_hdmi_plat_data *plat_data)
 {
@@ -3374,15 +3402,38 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 	if (ret < 0)
 		return ERR_PTR(ret);
 
+	hdmi->extcon = extcon_get_edev_by_phandle(dev, 0);
+	if (IS_ERR(hdmi->extcon)) {
+		if (PTR_ERR(hdmi->extcon) == -ENODEV) {
+			hdmi->extcon = NULL;
+		} else {
+			if (PTR_ERR(hdmi->extcon) != -EPROBE_DEFER)
+				dev_err(dev, "Invalid or missing extcon\n");
+			return ERR_CAST(hdmi->extcon);
+		}
+	}
+
+	if (hdmi->extcon) {
+		/* don't register IRQ for native HPD */
+		hdmi->phy_mask = (u8)~(HDMI_PHY_RX_SENSE);
+
+		hdmi->extcon_nb.notifier_call = dw_hdmi_extcon_notifier;
+		ret = extcon_register_notifier_all(hdmi->extcon, &hdmi->extcon_nb);
+		if (ret < 0) {
+			dev_err(dev, "failed to register extcon notifier\n");
+			return ERR_PTR(ret);
+		}
+	}
+
 	ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
 	if (ddc_node) {
 		hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node);
 		of_node_put(ddc_node);
 		if (!hdmi->ddc) {
 			dev_dbg(hdmi->dev, "failed to read ddc node\n");
-			return ERR_PTR(-EPROBE_DEFER);
+			ret = -EPROBE_DEFER;
+			goto err_extcon;
 		}
-
 	} else {
 		dev_dbg(hdmi->dev, "no ddc property found\n");
 	}
@@ -3627,6 +3678,8 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
 	clk_disable_unprepare(hdmi->isfr_clk);
 err_res:
 	i2c_put_adapter(hdmi->ddc);
+err_extcon:
+	extcon_unregister_notifier_all(hdmi->extcon, &hdmi->extcon_nb);
 
 	return ERR_PTR(ret);
 }
@@ -3634,6 +3687,8 @@ EXPORT_SYMBOL_GPL(dw_hdmi_probe);
 
 void dw_hdmi_remove(struct dw_hdmi *hdmi)
 {
+	extcon_unregister_notifier_all(hdmi->extcon, &hdmi->extcon_nb);
+
 	drm_bridge_remove(&hdmi->bridge);
 
 	if (hdmi->audio && !IS_ERR(hdmi->audio))
-- 
Armbian

