[RFC] drm/msm/dp: Add typec_mux implementation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Implement a typec_mux in order to allow a Type-C controller to signal
the connection and attention of DisplayPort to the related USB-C port.

The remains of support for something along this lines was left in
the dp_display as the driver was upstreamed, so these are reused with
minimal modifications necessary.

When operating in this mode, HPD interrupts has still been observed in
the ISR so, in line with the downstream kernel, these are ignored.

Signed-off-by: Bjorn Andersson <bjorn.andersson@xxxxxxxxxx>
---

This applies on top of https://lore.kernel.org/linux-arm-msm/20211001180058.1021913-1-bjorn.andersson@xxxxxxxxxx/

 drivers/gpu/drm/msm/Kconfig         |  1 +
 drivers/gpu/drm/msm/dp/dp_display.c | 52 ++++++++++++++++-----------
 drivers/gpu/drm/msm/dp/dp_hpd.c     | 54 +++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
index 5879f67bc88c..4e4b98c448cb 100644
--- a/drivers/gpu/drm/msm/Kconfig
+++ b/drivers/gpu/drm/msm/Kconfig
@@ -9,6 +9,7 @@ config DRM_MSM
 	depends on QCOM_OCMEM || QCOM_OCMEM=n
 	depends on QCOM_LLCC || QCOM_LLCC=n
 	depends on QCOM_COMMAND_DB || QCOM_COMMAND_DB=n
+	depends on TYPEC || TYPEC=n
 	select IOMMU_IO_PGTABLE
 	select QCOM_MDT_LOADER if ARCH_QCOM
 	select REGULATOR
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 56a79aeffed4..e863f537047a 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -85,6 +85,8 @@ struct dp_display_private {
 	bool hpd_irq_on;
 	bool audio_supported;
 
+	bool use_hw_hpd;
+
 	struct platform_device *pdev;
 	struct dentry *root;
 
@@ -466,11 +468,10 @@ static int dp_display_handle_irq_hpd(struct dp_display_private *dp)
 	return 0;
 }
 
-static int dp_display_usbpd_attention_cb(struct device *dev)
+static int dp_display_usbpd_attention(struct dp_display_private *dp)
 {
 	int rc = 0;
 	u32 sink_request;
-	struct dp_display_private *dp = dev_get_dp_display_private(dev);
 
 	/* check for any test request issued by sink */
 	rc = dp_link_process_request(dp->link);
@@ -690,7 +691,7 @@ static int dp_irq_hpd_handle(struct dp_display_private *dp, u32 data)
 		return 0;
 	}
 
-	ret = dp_display_usbpd_attention_cb(&dp->pdev->dev);
+	ret = dp_display_usbpd_attention(dp);
 	if (ret == -ECONNRESET) { /* cable unplugged */
 		dp->core_initialized = false;
 	}
@@ -709,6 +710,13 @@ static void dp_display_deinit_sub_modules(struct dp_display_private *dp)
 	dp_audio_put(dp->audio);
 }
 
+static int dp_display_usbpd_attention_cb(struct device *dev)
+{
+	struct dp_display_private *dp = dev_get_dp_display_private(dev);
+
+	return dp_irq_hpd_handle(dp, 0);
+}
+
 static int dp_init_sub_modules(struct dp_display_private *dp)
 {
 	int rc = 0;
@@ -731,6 +739,8 @@ static int dp_init_sub_modules(struct dp_display_private *dp)
 		goto error;
 	}
 
+	dp->use_hw_hpd = !of_property_read_bool(dev->of_node, "mode-switch");
+
 	dp->parser = dp_parser_get(dp->pdev);
 	if (IS_ERR(dp->parser)) {
 		rc = PTR_ERR(dp->parser);
@@ -1135,27 +1145,29 @@ static irqreturn_t dp_display_irq_handler(int irq, void *dev_id)
 		return IRQ_NONE;
 	}
 
-	hpd_isr_status = dp_catalog_hpd_get_intr_status(dp->catalog);
+	if (dp->use_hw_hpd) {
+		hpd_isr_status = dp_catalog_hpd_get_intr_status(dp->catalog);
 
-	DRM_DEBUG_DP("hpd isr status=%#x\n", hpd_isr_status);
-	if (hpd_isr_status & 0x0F) {
-		/* hpd related interrupts */
-		if (hpd_isr_status & DP_DP_HPD_PLUG_INT_MASK)
-			dp_add_event(dp, EV_HPD_PLUG_INT, 0, 0);
+		DRM_DEBUG_DP("hpd isr status=%#x\n", hpd_isr_status);
+		if (hpd_isr_status & 0x0F) {
+			/* hpd related interrupts */
+			if (hpd_isr_status & DP_DP_HPD_PLUG_INT_MASK)
+				dp_add_event(dp, EV_HPD_PLUG_INT, 0, 0);
 
-		if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
-			/* stop sentinel connect pending checking */
-			dp_del_event(dp, EV_CONNECT_PENDING_TIMEOUT);
-			dp_add_event(dp, EV_IRQ_HPD_INT, 0, 0);
-		}
+			if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
+				/* stop sentinel connect pending checking */
+				dp_del_event(dp, EV_CONNECT_PENDING_TIMEOUT);
+				dp_add_event(dp, EV_IRQ_HPD_INT, 0, 0);
+			}
 
-		if (hpd_isr_status & DP_DP_HPD_REPLUG_INT_MASK) {
-			dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0);
-			dp_add_event(dp, EV_HPD_PLUG_INT, 0, 3);
-		}
+			if (hpd_isr_status & DP_DP_HPD_REPLUG_INT_MASK) {
+				dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0);
+				dp_add_event(dp, EV_HPD_PLUG_INT, 0, 3);
+			}
 
-		if (hpd_isr_status & DP_DP_HPD_UNPLUG_INT_MASK)
-			dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0);
+			if (hpd_isr_status & DP_DP_HPD_UNPLUG_INT_MASK)
+				dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0);
+		}
 	}
 
 	/* DP controller isr */
diff --git a/drivers/gpu/drm/msm/dp/dp_hpd.c b/drivers/gpu/drm/msm/dp/dp_hpd.c
index e1c90fa47411..2a7ed9b8354e 100644
--- a/drivers/gpu/drm/msm/dp/dp_hpd.c
+++ b/drivers/gpu/drm/msm/dp/dp_hpd.c
@@ -7,6 +7,9 @@
 
 #include <linux/slab.h>
 #include <linux/device.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
 
 #include "dp_hpd.h"
 
@@ -22,6 +25,8 @@ struct dp_hpd_private {
 	struct device *dev;
 	struct dp_usbpd_cb *dp_cb;
 	struct dp_usbpd dp_usbpd;
+	struct typec_mux *mux;
+	bool connected;
 };
 
 int dp_hpd_connect(struct dp_usbpd *dp_usbpd, bool hpd)
@@ -47,9 +52,45 @@ int dp_hpd_connect(struct dp_usbpd *dp_usbpd, bool hpd)
 	return rc;
 }
 
+static int dp_hpd_mux_set(struct typec_mux *mux, struct typec_mux_state *state)
+{
+	struct dp_hpd_private *dp_hpd = typec_mux_get_drvdata(mux);
+	struct dp_usbpd *usbpd = &dp_hpd->dp_usbpd;
+	struct typec_displayport_data *dp_data = state->data;
+	int pin_assign = 0;
+
+	if (dp_data) {
+		pin_assign = DP_CONF_GET_PIN_ASSIGN(dp_data->conf);
+		usbpd->hpd_high = !!(dp_data->status & DP_STATUS_HPD_STATE);
+		usbpd->hpd_irq = !!(dp_data->status & DP_STATUS_IRQ_HPD);
+		usbpd->multi_func = pin_assign == DP_PIN_ASSIGN_C || DP_PIN_ASSIGN_E;
+	}
+
+	if (!pin_assign) {
+		if (dp_hpd->connected) {
+			dp_hpd->connected = false;
+			dp_hpd->dp_cb->disconnect(dp_hpd->dev);
+		}
+	} else if (!dp_hpd->connected) {
+		dp_hpd->connected = true;
+		dp_hpd->dp_cb->configure(dp_hpd->dev);
+	} else {
+		dp_hpd->dp_cb->attention(dp_hpd->dev);
+	}
+
+	return 0;
+}
+
+static void dp_hpd_unregister_typec_mux(void *data)
+{
+	typec_mux_unregister(data);
+}
+
 struct dp_usbpd *dp_hpd_get(struct device *dev, struct dp_usbpd_cb *cb)
 {
+	struct typec_mux_desc mux_desc = {};
 	struct dp_hpd_private *dp_hpd;
+	int rc;
 
 	if (!cb) {
 		pr_err("invalid cb data\n");
@@ -65,5 +106,18 @@ struct dp_usbpd *dp_hpd_get(struct device *dev, struct dp_usbpd_cb *cb)
 
 	dp_hpd->dp_usbpd.connect = dp_hpd_connect;
 
+	mux_desc.fwnode = dev->fwnode;
+	mux_desc.set = dp_hpd_mux_set;
+	mux_desc.drvdata = dp_hpd;
+	dp_hpd->mux = typec_mux_register(dev, &mux_desc);
+	if (IS_ERR(dp_hpd->mux)) {
+		dev_err(dev, "unable to register typec mux\n");
+		return ERR_CAST(dp_hpd->mux);
+	}
+
+	rc = devm_add_action_or_reset(dev, dp_hpd_unregister_typec_mux, dp_hpd->mux);
+	if (rc)
+		return ERR_PTR(rc);
+
 	return &dp_hpd->dp_usbpd;
 }
-- 
2.29.2




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux