diff mbox series

[3/3] usb: dwc3: dwc3-qcom: Make dwc3-qcom a role-switch signal recipient

Message ID 20210704013314.200951-4-bryan.odonoghue@linaro.org
State New
Headers show
Series Implement role-switch notifications from dwc3-drd to dwc3-qcom | expand

Commit Message

Bryan O'Donoghue July 4, 2021, 1:33 a.m. UTC
When switching role from host to peripheral or peripheral to host we need
to set SoC UTMI signal logic in software since this is not done
automatically by the PHY or DWC3 core.

We have existing extcon code in dwc3-qcom which already implements the
right logic for extcon based systems, however, as we move to USB
role-switching we need to similarly facilitate the same UTMI switch
notification.

Setting the dwc3-qcom wrapper up as a USB role switch signal recipient
allows us to replicate the extcon logic with the role-switch API by
receiving the set_role() from dwc3-core and calling the existing VBUS
extcon code.

Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>

---
 drivers/usb/dwc3/dwc3-qcom.c | 69 ++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

-- 
2.30.1
diff mbox series

Patch

diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 2e61302e3e91..1aec387a8537 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -20,6 +20,7 @@ 
 #include <linux/usb/of.h>
 #include <linux/reset.h>
 #include <linux/iopoll.h>
+#include <linux/usb/role.h>
 
 #include "core.h"
 
@@ -81,6 +82,7 @@  struct dwc3_qcom {
 	struct extcon_dev	*host_edev;
 	struct notifier_block	vbus_nb;
 	struct notifier_block	host_nb;
+	struct usb_role_switch	*role_sw;
 
 	const struct dwc3_acpi_pdata *acpi_pdata;
 
@@ -154,6 +156,66 @@  static int dwc3_qcom_host_notifier(struct notifier_block *nb,
 	return NOTIFY_DONE;
 }
 
+#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)
+static int dwc3_qcom_usb_role_switch_set(struct usb_role_switch *sw,
+					 enum usb_role role)
+{
+	struct dwc3_qcom *qcom = usb_role_switch_get_drvdata(sw);
+	bool enable;
+
+	switch (role) {
+	case USB_ROLE_DEVICE:
+		qcom->mode = USB_DR_MODE_PERIPHERAL;
+		enable = true;
+		break;
+	case USB_ROLE_HOST:
+	default:
+		qcom->mode = USB_DR_MODE_HOST;
+		enable = false;
+		break;
+	}
+
+	dwc3_qcom_vbus_override_enable(qcom, enable);
+	return 0;
+}
+
+static enum usb_role dwc3_qcom_usb_role_switch_get(struct usb_role_switch *sw)
+{
+	struct dwc3_qcom *qcom = usb_role_switch_get_drvdata(sw);
+	enum usb_role role;
+
+	switch (qcom->mode) {
+	case USB_DR_MODE_PERIPHERAL:
+		role = USB_ROLE_DEVICE;
+		break;
+	case  USB_DR_MODE_HOST:
+	default:
+		role = USB_ROLE_HOST;
+		break;
+	}
+
+	return role;
+}
+
+static int dwc3_qcom_setup_role_switch(struct dwc3_qcom *qcom)
+{
+	struct usb_role_switch_desc dwc3_qcom_role_switch = {NULL};
+
+	dwc3_qcom_role_switch.fwnode = dev_fwnode(qcom->dev);
+	dwc3_qcom_role_switch.set = dwc3_qcom_usb_role_switch_set;
+	dwc3_qcom_role_switch.get = dwc3_qcom_usb_role_switch_get;
+	dwc3_qcom_role_switch.driver_data = qcom;
+	qcom->role_sw = usb_role_switch_register(qcom->dev,
+						 &dwc3_qcom_role_switch);
+	if (IS_ERR(qcom->role_sw))
+		return PTR_ERR(qcom->role_sw);
+
+	return 0;
+}
+#else
+#define dwc3_qcom_setup_role_switch(x) 0
+#endif
+
 static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom)
 {
 	struct device		*dev = qcom->dev;
@@ -818,6 +880,10 @@  static int dwc3_qcom_probe(struct platform_device *pdev)
 	if (ret)
 		goto interconnect_exit;
 
+	ret = dwc3_qcom_setup_role_switch(qcom);
+	if (ret)
+		goto interconnect_exit;
+
 	device_init_wakeup(&pdev->dev, 1);
 	qcom->is_suspended = false;
 	pm_runtime_set_active(dev);
@@ -850,6 +916,9 @@  static int dwc3_qcom_remove(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	int i;
 
+	if (qcom->role_sw)
+		usb_role_switch_unregister(qcom->role_sw);
+
 	device_remove_software_node(&qcom->dwc3->dev);
 	of_platform_depopulate(dev);