]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
usb: dwc3: Add dual-role support
authorRoger Quadros <rogerq@ti.com>
Wed, 5 Apr 2017 10:39:31 +0000 (13:39 +0300)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Tue, 11 Apr 2017 07:58:31 +0000 (10:58 +0300)
If dr_mode is "otg" then support dual role mode of operation.
Currently this mode is only supported when an extcon handle is
present in the dwc3 device tree node. This is needed to
get the ID status events of the port.

We're using a workqueue to manage the dual-role state transitions
as the extcon notifier (dwc3_drd_notifier) is called in an atomic
context by extcon_sync() and this doesn't go well with
usb_del_gadget_udc() causing a lockdep and softirq warning.

Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/dwc3/Kconfig
drivers/usb/dwc3/Makefile
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/drd.c [new file with mode: 0644]

index c5aa235863e8c7e88b30544f8e1aba98c9a18d03..aee55114d269a30f5799e0171e37b5d34ab6a3c1 100644 (file)
@@ -41,6 +41,7 @@ config USB_DWC3_GADGET
 config USB_DWC3_DUAL_ROLE
        bool "Dual Role mode"
        depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3))
+       depends on (EXTCON=y || EXTCON=USB_DWC3)
        help
          This is the default mode of working of DWC3 controller where
          both host and gadget features are enabled.
index ffca34029b21ff6c3bdb3e44ae5531c74d78856a..f15fabbd1e59e9d23e07936be6cfa05a3489975f 100644 (file)
@@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
        dwc3-y                          += gadget.o ep0.o
 endif
 
+ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
+       dwc3-y                          += drd.o
+endif
+
 ifneq ($(CONFIG_USB_DWC3_ULPI),)
        dwc3-y                          += ulpi.o
 endif
index 458e7c6cc0028911b153d4a7b3f94d0a2bd19ce1..455d89a1cd6dbbb3e3f707bc42bada4a9569e5d7 100644 (file)
@@ -921,7 +921,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
                break;
        case USB_DR_MODE_OTG:
                INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
-               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+               ret = dwc3_drd_init(dwc);
+               if (ret) {
+                       if (ret != -EPROBE_DEFER)
+                               dev_err(dev, "failed to initialize dual-role\n");
+                       return ret;
+               }
                break;
        default:
                dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -941,9 +946,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
                dwc3_host_exit(dwc);
                break;
        case USB_DR_MODE_OTG:
-               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
-               dwc3_gadget_exit(dwc);
-               flush_work(&dwc->drd_work);
+               dwc3_drd_exit(dwc);
                break;
        default:
                /* do nothing */
index 1fe23e36485f53c807ca11fe77d187028b790fc8..981c77f5628e105731b41756b798c988b6c6692d 100644 (file)
@@ -785,6 +785,8 @@ struct dwc3_scratchpad_array {
  * @dr_mode: requested mode of operation
  * @current_dr_role: current role of operation when in dual-role mode
  * @desired_dr_role: desired role of operation when in dual-role mode
+ * @edev: extcon handle
+ * @edev_nb: extcon notifier
  * @hsphy_mode: UTMI phy mode, one of following:
  *             - USBPHY_INTERFACE_MODE_UTMI
  *             - USBPHY_INTERFACE_MODE_UTMIW
@@ -898,6 +900,8 @@ struct dwc3 {
        enum usb_dr_mode        dr_mode;
        u32                     current_dr_role;
        u32                     desired_dr_role;
+       struct extcon_dev       *edev;
+       struct notifier_block   edev_nb;
        enum usb_phy_interface  hsphy_mode;
 
        u32                     fladj;
@@ -1218,6 +1222,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
 { return 0; }
 #endif
 
+#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+int dwc3_drd_init(struct dwc3 *dwc);
+void dwc3_drd_exit(struct dwc3 *dwc);
+#else
+static inline int dwc3_drd_init(struct dwc3 *dwc)
+{ return 0; }
+static inline void dwc3_drd_exit(struct dwc3 *dwc)
+{ }
+#endif
+
 /* power management interface */
 #if !IS_ENABLED(CONFIG_USB_DWC3_HOST)
 int dwc3_gadget_suspend(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
new file mode 100644 (file)
index 0000000..2765c51
--- /dev/null
@@ -0,0 +1,85 @@
+/**
+ * drd.c - DesignWare USB3 DRD Controller Dual-role support
+ *
+ * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Roger Quadros <rogerq@ti.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/extcon.h>
+
+#include "debug.h"
+#include "core.h"
+#include "gadget.h"
+
+static void dwc3_drd_update(struct dwc3 *dwc)
+{
+       int id;
+
+       id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+       if (id < 0)
+               id = 0;
+
+       dwc3_set_mode(dwc, id ?
+                     DWC3_GCTL_PRTCAP_HOST :
+                     DWC3_GCTL_PRTCAP_DEVICE);
+}
+
+static int dwc3_drd_notifier(struct notifier_block *nb,
+                            unsigned long event, void *ptr)
+{
+       struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
+
+       dwc3_set_mode(dwc, event ?
+                     DWC3_GCTL_PRTCAP_HOST :
+                     DWC3_GCTL_PRTCAP_DEVICE);
+
+       return NOTIFY_DONE;
+}
+
+int dwc3_drd_init(struct dwc3 *dwc)
+{
+       int ret;
+
+       if (dwc->dev->of_node) {
+               if (of_property_read_bool(dwc->dev->of_node, "extcon"))
+                       dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
+
+               if (IS_ERR(dwc->edev))
+                       return PTR_ERR(dwc->edev);
+
+               dwc->edev_nb.notifier_call = dwc3_drd_notifier;
+               ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
+                                              &dwc->edev_nb);
+               if (ret < 0) {
+                       dev_err(dwc->dev, "couldn't register cable notifier\n");
+                       return ret;
+               }
+       }
+
+       dwc3_drd_update(dwc);
+
+       return 0;
+}
+
+void dwc3_drd_exit(struct dwc3 *dwc)
+{
+       extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+                                  &dwc->edev_nb);
+
+       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+       flush_work(&dwc->drd_work);
+       dwc3_gadget_exit(dwc);
+}