If registering a USB typeC connector, the connector node may not be a child
of the DWC3 QCOM device.  Utilize devcon graph search to lookup if any
remote endpoints contain the connector.  If a connector is present, the
DWC3 QCOM will register a USB role switch to receive role change events, as
well as attain a reference to the DWC3 core role switch to pass the event
down.

Signed-off-by: Wesley Cheng <wch...@codeaurora.org>
---
 drivers/usb/dwc3/dwc3-qcom.c | 120 ++++++++++++++++++++++++++++++++++-
 1 file changed, 118 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index e1e78e9824b1..340760ef0e01 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -19,6 +19,8 @@
 #include <linux/usb/of.h>
 #include <linux/reset.h>
 #include <linux/iopoll.h>
+#include <linux/fwnode.h>
+#include <linux/usb/role.h>
 
 #include "core.h"
 
@@ -71,6 +73,9 @@ struct dwc3_qcom {
        struct notifier_block   vbus_nb;
        struct notifier_block   host_nb;
 
+       struct usb_role_switch *role_sw;
+       struct usb_role_switch *dwc3_drd_sw;
+
        const struct dwc3_acpi_pdata *acpi_pdata;
 
        enum usb_dr_mode        mode;
@@ -190,6 +195,73 @@ static int dwc3_qcom_register_extcon(struct dwc3_qcom 
*qcom)
        return 0;
 }
 
+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);
+       struct fwnode_handle *child;
+       bool enable = false;
+
+       if (!qcom->dwc3_drd_sw) {
+               child = device_get_next_child_node(qcom->dev, NULL);
+               if (child) {
+                       qcom->dwc3_drd_sw = 
usb_role_switch_find_by_fwnode(child);
+                       fwnode_handle_put(child);
+                       if (IS_ERR(qcom->dwc3_drd_sw)) {
+                               qcom->dwc3_drd_sw = NULL;
+                               return 0;
+                       }
+               }
+       }
+
+       usb_role_switch_set_role(qcom->dwc3_drd_sw, role);
+
+       if (role == USB_ROLE_DEVICE)
+               enable = true;
+       else
+               enable = false;
+
+       qcom->mode = (role == USB_ROLE_HOST) ? USB_DR_MODE_HOST :
+                                              USB_DR_MODE_PERIPHERAL;
+       dwc3_qcom_vbus_overrride_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_HOST:
+               role = USB_ROLE_HOST;
+               break;
+       case USB_DR_MODE_PERIPHERAL:
+               role = USB_ROLE_DEVICE;
+               break;
+       default:
+               role = USB_ROLE_DEVICE;
+               break;
+       }
+
+       return role;
+}
+
+static int dwc3_qcom_setup_role_switch(struct dwc3_qcom *qcom)
+{
+       struct usb_role_switch_desc dwc3_role_switch = {NULL};
+
+       dwc3_role_switch.fwnode = dev_fwnode(qcom->dev);
+       dwc3_role_switch.set = dwc3_qcom_usb_role_switch_set;
+       dwc3_role_switch.get = dwc3_qcom_usb_role_switch_get;
+       dwc3_role_switch.driver_data = qcom;
+       qcom->role_sw = usb_role_switch_register(qcom->dev, &dwc3_role_switch);
+       if (IS_ERR(qcom->role_sw))
+               return PTR_ERR(qcom->role_sw);
+
+       return 0;
+}
+
 static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
 {
        if (qcom->hs_phy_irq) {
@@ -540,6 +612,42 @@ static int dwc3_qcom_of_register_core(struct 
platform_device *pdev)
        return 0;
 }
 
+static int dwc3_qcom_connector_check(struct fwnode_handle *fwnode)
+{
+       if (fwnode && (!fwnode_property_match_string(fwnode, "compatible",
+                                                    "gpio-usb-b-connector") ||
+           !fwnode_property_match_string(fwnode, "compatible",
+                                         "usb-c-connector")))
+               return 1;
+
+       return 0;
+}
+
+static void *dwc3_qcom_find_usb_connector_match(struct device_connection *con,
+                                               int ep, void *data)
+{
+       struct fwnode_handle *fwnode;
+
+       /* Check if the "connector" node is the parent of the remote endpoint */
+       if (dwc3_qcom_connector_check(con->fwnode))
+               return fwnode;
+
+       /* else, check if it is a child node */
+       fwnode = fwnode_get_named_child_node(con->fwnode, "connector");
+       if (dwc3_qcom_connector_check(fwnode))
+               return fwnode;
+
+       return 0;
+}
+
+static bool dwc3_qcom_find_usb_connector(struct platform_device *pdev)
+{
+       struct fwnode_handle *fwnode = pdev->dev.fwnode;
+
+       return fwnode_connection_find_match(fwnode, "connector", NULL,
+                                           dwc3_qcom_find_usb_connector_match);
+}
+
 static int dwc3_qcom_probe(struct platform_device *pdev)
 {
        struct device_node      *np = pdev->dev.of_node;
@@ -644,8 +752,13 @@ static int dwc3_qcom_probe(struct platform_device *pdev)
        if (qcom->mode == USB_DR_MODE_PERIPHERAL)
                dwc3_qcom_vbus_overrride_enable(qcom, true);
 
-       /* register extcon to override sw_vbus on Vbus change later */
-       ret = dwc3_qcom_register_extcon(qcom);
+       if (dwc3_qcom_find_usb_connector(pdev)) {
+               ret = dwc3_qcom_setup_role_switch(qcom);
+       } else {
+               /* register extcon to override sw_vbus on Vbus change later */
+               ret = dwc3_qcom_register_extcon(qcom);
+       }
+
        if (ret)
                goto depopulate;
 
@@ -679,6 +792,9 @@ static int dwc3_qcom_remove(struct platform_device *pdev)
        struct device *dev = &pdev->dev;
        int i;
 
+       usb_role_switch_unregister(qcom->role_sw);
+       usb_role_switch_put(qcom->dwc3_drd_sw);
+
        of_platform_depopulate(dev);
 
        for (i = qcom->num_clocks - 1; i >= 0; i--) {
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

Reply via email to