[PATCH 1/2] usb: cdns3: Fix hw based role switch

From: Roger Quadros
Date: Thu Aug 15 2019 - 04:11:02 EST


Signed-off-by: Roger Quadros <rogerq@xxxxxx>
---
drivers/usb/cdns3/core.c | 76 ++++++++++++++++++++++++++--------------
drivers/usb/cdns3/core.h | 2 ++
drivers/usb/cdns3/drd.c | 2 +-
3 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c
index b132acca60f1..07f8d8f23930 100644
--- a/drivers/usb/cdns3/core.c
+++ b/drivers/usb/cdns3/core.c
@@ -97,7 +97,7 @@ static void cdns3_exit_roles(struct cdns3 *cdns)
cdns3_drd_exit(cdns);
}

-enum usb_role cdsn3_real_role_switch_get(struct device *dev);
+static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns);

/**
* cdns3_core_init_role - initialize role of operation
@@ -176,7 +176,7 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
if (ret)
goto err;

- cdns->role = cdsn3_real_role_switch_get(cdns->dev);
+ cdns->role = cdsn3_hw_role_state_machine(cdns);

ret = cdns3_role_start(cdns, cdns->role);
if (ret) {
@@ -192,15 +192,14 @@ static int cdns3_core_init_role(struct cdns3 *cdns)
}

/**
- * cdsn3_real_role_switch_get - get real role of controller based on hardware
- * settings.
- * @dev: Pointer to device structure
+ * cdsn3_hw_role_state_machine - role switch state machine based on hw events
*
- * Returns role
+ * @cdns: Pointer to controller structure
+ *
+ * Returns next role to be entered based on hw events.
*/
-enum usb_role cdsn3_real_role_switch_get(struct device *dev)
+static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns)
{
- struct cdns3 *cdns = dev_get_drvdata(dev);
enum usb_role role;
int id, vbus;

@@ -253,46 +252,40 @@ enum usb_role cdsn3_real_role_switch_get(struct device *dev)
}

/**
- * cdns3_role_switch_set - work queue handler for role switch
+ * cdns3_hw_role_switch - switch roles based on HW state
*
- * @dev: pointer to device object
- * @role - the previous role
- * Handles below events:
- * - Role switch for dual-role devices
- * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices
+ * @cdns: controller
*/
-static int cdns3_role_switch_set(struct device *dev, enum usb_role role)
+int cdns3_hw_role_switch(struct cdns3 *cdns)
{
- struct cdns3 *cdns = dev_get_drvdata(dev);
- enum usb_role real_role = USB_ROLE_NONE;
- enum usb_role current_role;
+ enum usb_role real_role, current_role;
int ret = 0;

/* Check if dr_mode was changed.*/
+ /* FIXME: why do we need this call? */
ret = cdns3_drd_update_mode(cdns);
if (ret)
return ret;

+ /* FIXME: don't do anything if userspace role switch has override */
pm_runtime_get_sync(cdns->dev);

- real_role = cdsn3_real_role_switch_get(cdns->dev);
+ current_role = cdns->role;
+ real_role = cdsn3_hw_role_state_machine(cdns);

/* Do nothing if nothing changed */
- if (cdns->role == real_role)
+ if (current_role == real_role)
goto exit;

cdns3_role_stop(cdns);

- real_role = cdsn3_real_role_switch_get(cdns->dev);
-
- current_role = role;
- dev_dbg(cdns->dev, "Switching role");
+ dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role);

ret = cdns3_role_start(cdns, real_role);
if (ret) {
/* Back to current role */
dev_err(cdns->dev, "set %d has failed, back to %d\n",
- role, current_role);
+ real_role, current_role);
ret = cdns3_role_start(cdns, current_role);
if (ret)
dev_err(cdns->dev, "back to %d failed too\n",
@@ -303,9 +296,38 @@ static int cdns3_role_switch_set(struct device *dev, enum usb_role role)
return ret;
}

+/**
+ * cdsn3_role_get - get current role of controller.
+ *
+ * @dev: Pointer to device structure
+ *
+ * Returns role
+ */
+static enum usb_role cdns3_role_get(struct device *dev)
+{
+ struct cdns3 *cdns = dev_get_drvdata(dev);
+
+ return cdns->role;
+}
+
+/**
+ * cdns3_role_set - set current role of controller.
+ *
+ * @dev: pointer to device object
+ * @role - the previous role
+ * Handles below events:
+ * - Role switch for dual-role devices
+ * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices
+ */
+static int cdns3_role_set(struct device *dev, enum usb_role role)
+{
+ /* FIXME: doing nothing for now */
+ return -EPERM;
+}
+
static const struct usb_role_switch_desc cdns3_switch_desc = {
- .set = cdns3_role_switch_set,
- .get = cdsn3_real_role_switch_get,
+ .set = cdns3_role_set,
+ .get = cdns3_role_get,
.allow_userspace_control = true,
};

diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h
index 2a35f025b0a5..e0d23f2934bb 100644
--- a/drivers/usb/cdns3/core.h
+++ b/drivers/usb/cdns3/core.h
@@ -103,4 +103,6 @@ struct cdns3 {
struct usb_role_switch *role_sw;
};

+int cdns3_hw_role_switch(struct cdns3 *cdns);
+
#endif /* __LINUX_CDNS3_CORE_H */
diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c
index 77f8a1516140..780fc7d59932 100644
--- a/drivers/usb/cdns3/drd.c
+++ b/drivers/usb/cdns3/drd.c
@@ -265,7 +265,7 @@ static irqreturn_t cdns3_drd_thread_irq(int irq, void *data)
{
struct cdns3 *cdns = data;

- usb_role_switch_set_role(cdns->role_sw, cdns->role);
+ cdns3_hw_role_switch(cdns);

return IRQ_HANDLED;
}