dra7 OTG core limits the host controller to USB2.0 (high-speed) mode
when we're operating in dual-role.

We work around that by bypassing the OTG core and reading the
extcon framework directly for ID/VBUS events.

Signed-off-by: Roger Quadros <rog...@ti.com>
---
 Documentation/devicetree/bindings/usb/dwc3.txt |   2 +
 drivers/usb/dwc3/core.c                        | 169 ++++++++++++++++++++++++-
 drivers/usb/dwc3/core.h                        |   5 +
 3 files changed, 170 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt 
b/Documentation/devicetree/bindings/usb/dwc3.txt
index e3e6983..9955c0d 100644
--- a/Documentation/devicetree/bindings/usb/dwc3.txt
+++ b/Documentation/devicetree/bindings/usb/dwc3.txt
@@ -53,6 +53,8 @@ Optional properties:
  - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ
        register for post-silicon frame length adjustment when the
        fladj_30mhz_sdbnd signal is invalid or incorrect.
+ - extcon: phandle to the USB connector extcon device. If present, extcon
+       device will be used to get USB cable events instead of OTG controller.
 
  - <DEPRECATED> tx-fifo-resize: determines if the FIFO *has* to be reallocated.
 
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 619fa7c..b02d911 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -918,6 +918,19 @@ static void dwc3_otg_fsm_sync(struct dwc3 *dwc)
        if (dwc->otg_prevent_sync)
                return;
 
+       if (dwc->edev) {
+               /* get ID */
+               id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+               /* Host means ID == 0 */
+               id = !id;
+
+               /* get VBUS */
+               vbus = extcon_get_state(dwc->edev, EXTCON_USB);
+               dwc3_drd_statemachine(dwc, id, vbus);
+
+               return;
+       }
+
        reg = dwc3_readl(dwc->regs, DWC3_OSTS);
        id = !!(reg & DWC3_OSTS_CONIDSTS);
        vbus = !!(reg & DWC3_OSTS_BSESVLD);
@@ -934,6 +947,17 @@ static void dwc3_drd_work(struct work_struct *work)
        spin_unlock(&dwc->lock);
 }
 
+static int dwc3_drd_notifier(struct notifier_block *nb,
+                            unsigned long event, void *ptr)
+{
+       struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
+
+       if (!dwc->otg_prevent_sync)
+               queue_work(system_power_efficient_wq, &dwc->otg_work);
+
+       return NOTIFY_DONE;
+}
+
 static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask)
 {
        dwc->oevten &= ~(disable_mask);
@@ -1040,6 +1064,27 @@ static int dwc3_drd_start_host(struct dwc3 *dwc, int on, 
bool skip)
 {
        u32 reg;
 
+       if (!dwc->edev)
+               goto otg;
+
+       if (on)
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+
+       if (!skip) {
+               spin_unlock(&dwc->lock);
+
+               /* start or stop the HCD */
+               if (on)
+                       dwc3_host_init(dwc);
+               else
+                       dwc3_host_exit(dwc);
+
+               spin_lock(&dwc->lock);
+       }
+
+       return 0;
+
+otg:
        /* switch OTG core */
        if (on) {
                /* As per Figure 11-10 A-Device Flow Diagram */
@@ -1133,6 +1178,33 @@ static int dwc3_drd_start_gadget(struct dwc3 *dwc, int 
on)
        if (on)
                dwc3_event_buffers_setup(dwc);
 
+       if (!dwc->edev)
+               goto otg;
+
+       if (on) {
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+               /* start the Peripheral driver  */
+               if (dwc->gadget_driver) {
+                       __dwc3_gadget_start(dwc);
+                       if (dwc->gadget_pullup)
+                               dwc3_gadget_run_stop(dwc, true, false);
+               }
+       } else {
+               /* stop the Peripheral driver */
+               if (dwc->gadget_driver) {
+                       if (dwc->gadget_pullup)
+                               dwc3_gadget_run_stop(dwc, false, false);
+                       spin_unlock(&dwc->lock);
+                       if (dwc->gadget_driver->disconnect)
+                               dwc->gadget_driver->disconnect(&dwc->gadget);
+                       spin_lock(&dwc->lock);
+                       __dwc3_gadget_stop(dwc);
+               }
+       }
+
+       return 0;
+
+otg:
        if (on) {
                /* As per Figure 11-20 B-Device Flow Diagram */
 
@@ -1251,6 +1323,13 @@ static void dwc3_otg_core_init(struct dwc3 *dwc)
 {
        u32 reg;
 
+       /* force drd state machine update the first time */
+       dwc->otg_fsm.b_sess_vld = -1;
+       dwc->otg_fsm.id = -1;
+
+       if (dwc->edev)
+               return;
+
        /*
         * As per Figure 11-4 OTG Driver Overall Programming Flow,
         * block "Initialize GCTL for OTG operation".
@@ -1264,15 +1343,14 @@ static void dwc3_otg_core_init(struct dwc3 *dwc)
 
        /* Initialize OTG registers */
        dwc3_otgregs_init(dwc);
-
-       /* force drd state machine update the first time */
-       dwc->otg_fsm.b_sess_vld = -1;
-       dwc->otg_fsm.id = -1;
 }
 
 /* dwc->lock must be held */
 static void dwc3_otg_core_exit(struct dwc3 *dwc)
 {
+       if (dwc->edev)
+               return;
+
        /* disable all otg irqs */
        dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
        /* clear all events */
@@ -1286,6 +1364,57 @@ static int dwc3_drd_init(struct dwc3 *dwc)
 
        INIT_WORK(&dwc->otg_work, dwc3_drd_work);
 
+       /* If extcon device is present we don't rely on OTG core for ID event */
+       if (dwc->edev) {
+               int id, vbus;
+
+               dwc->edev_nb.notifier_call = dwc3_drd_notifier;
+               ret = extcon_register_notifier(dwc->edev, EXTCON_USB,
+                                              &dwc->edev_nb);
+               if (ret < 0) {
+                       dev_err(dwc->dev, "Couldn't register USB cable 
notifier\n");
+                       return -ENODEV;
+               }
+
+               ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
+                                              &dwc->edev_nb);
+               if (ret < 0) {
+                       dev_err(dwc->dev, "Couldn't register USB-HOST cable 
notifier\n");
+                       ret = -ENODEV;
+                       goto extcon_fail;
+               }
+
+               /* sanity check id & vbus states */
+               id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+               vbus = extcon_get_state(dwc->edev, EXTCON_USB);
+               if (id < 0 || vbus < 0) {
+                       dev_err(dwc->dev, "Invalid USB cable state. id %d, vbus 
%d\n",
+                               id, vbus);
+                       ret = -ENODEV;
+                       goto fail;
+               }
+
+               ret = dwc3_gadget_init(dwc);
+               if (ret)
+                       goto fail;
+
+               spin_lock_irqsave(&dwc->lock, flags);
+               dwc3_otg_core_init(dwc);
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               queue_work(system_power_efficient_wq, &dwc->otg_work);
+
+               return 0;
+
+fail:
+               extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+                                          &dwc->edev_nb);
+extcon_fail:
+               extcon_unregister_notifier(dwc->edev, EXTCON_USB,
+                                          &dwc->edev_nb);
+
+               return ret;
+       }
+
        irq = dwc3_otg_get_irq(dwc);
        if (irq < 0)
                return irq;
@@ -1326,13 +1455,24 @@ static void dwc3_drd_exit(struct dwc3 *dwc)
 {
        unsigned long flags;
 
+       spin_lock(&dwc->lock);
+       dwc->otg_prevent_sync = true;
+       spin_unlock(&dwc->lock);
        cancel_work_sync(&dwc->otg_work);
+
        spin_lock_irqsave(&dwc->lock, flags);
        dwc3_otg_core_exit(dwc);
        if (dwc->otg_fsm.protocol == PROTO_HOST)
                dwc3_drd_start_host(dwc, 0, 0);
        dwc->otg_fsm.protocol = PROTO_UNDEF;
-       free_irq(dwc->otg_irq, dwc);
+       if (dwc->edev) {
+               extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+                                          &dwc->edev_nb);
+               extcon_unregister_notifier(dwc->edev, EXTCON_USB,
+                                          &dwc->edev_nb);
+       } else {
+               free_irq(dwc->otg_irq, dwc);
+       }
        spin_unlock_irqrestore(&dwc->lock, flags);
 
        dwc3_gadget_exit(dwc);
@@ -1587,6 +1727,14 @@ static int dwc3_probe(struct platform_device *pdev)
 
        dwc3_get_properties(dwc);
 
+       if (dev->of_node) {
+               if (of_property_read_bool(dev->of_node, "extcon"))
+                       dwc->edev = extcon_get_edev_by_phandle(dev, 0);
+
+               if (IS_ERR(dwc->edev))
+                       return PTR_ERR(dwc->edev);
+       }
+
        platform_set_drvdata(pdev, dwc);
        dwc3_cache_hwparams(dwc);
 
@@ -1743,6 +1891,13 @@ static int dwc3_resume_common(struct dwc3 *dwc)
        if (ret)
                return ret;
 
+       if (dwc->dr_mode == USB_DR_MODE_OTG &&
+           dwc->edev) {
+               if (dwc->otg_fsm.protocol == PROTO_HOST)
+                       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+               else if (dwc->otg_fsm.protocol == PROTO_GADGET)
+                       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+       }
        spin_lock_irqsave(&dwc->lock, flags);
 
        switch (dwc->dr_mode) {
@@ -1771,8 +1926,10 @@ static int dwc3_resume_common(struct dwc3 *dwc)
 
        if (dwc->dr_mode == USB_DR_MODE_OTG) {
                dwc3_otg_core_init(dwc);
-               if (dwc->otg_fsm.protocol == PROTO_HOST)
+               if ((dwc->otg_fsm.protocol == PROTO_HOST) &&
+                   !dwc->edev) {
                        dwc3_drd_start_host(dwc, true, 1);
+               }
        }
 
        spin_unlock_irqrestore(&dwc->lock, flags);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index bf8951d..fc060ae 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -38,6 +38,7 @@
 #include <linux/usb/otg.h>
 #include <linux/usb/otg-fsm.h>
 
+#include <linux/extcon.h>
 #include <linux/workqueue.h>
 
 #define DWC3_MSG_MAX   500
@@ -869,6 +870,8 @@ struct dwc3_scratchpad_array {
  * @current_mode: current mode of operation written to PRTCAPDIR
  * @otg_work: work struct for otg/dual-role
  * @otg_needs_host_start: flag that OTG controller needs to start host
+ * @edev: extcon handle
+ * @edev_nb: extcon notifier
  * @fladj: frame length adjustment
  * @irq_gadget: peripheral controller's IRQ number
  * @otg_irq: IRQ number for OTG IRQs
@@ -1007,6 +1010,8 @@ struct dwc3 {
        u32                     current_mode;
        struct work_struct      otg_work;
        bool                    otg_needs_host_start;
+       struct extcon_dev       *edev;
+       struct notifier_block   edev_nb;
 
        enum usb_phy_interface  hsphy_mode;
 
-- 
2.7.4

Reply via email to