[PATCH 3/3] usb: dwc3: dwc3-qcom: Find USB connector and register role switch

From: Wesley Cheng
Date: Fri Jul 31 2020 - 00:57:41 EST


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 <wcheng@xxxxxxxxxxxxxx>
---
drivers/usb/dwc3/dwc3-qcom.c | 103 ++++++++++++++++++++++++++++++++++-
1 file changed, 101 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index e1e78e9824b1..fe1e8a4ab7d6 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,25 @@ static int dwc3_qcom_of_register_core(struct platform_device *pdev)
return 0;
}

+static void *dwc3_qcom_find_usb_connector_match(struct device_connection *con,
+ int ep, void *data)
+{
+ if (!fwnode_property_match_string(con->fwnode, "compatible",
+ "gpio-usb-b-connector") ||
+ !fwnode_property_match_string(con->fwnode, "compatible",
+ "usb-c-connector"))
+ return con->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 +735,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 +775,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