dwc3: add otg handling code
authorRobert Baldyga <r.baldyga@samsung.com>
Mon, 23 Feb 2015 11:01:54 +0000 (12:01 +0100)
committerMarek Szyprowski <m.szyprowski@samsung.com>
Mon, 13 Apr 2015 10:44:40 +0000 (12:44 +0200)
This code is based on DWC3 driver from https://github.com/hardkernel/linux.

Signed-off-by: Robert Baldyga <r.baldyga@samsung.com>
drivers/usb/dwc3/Makefile
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/otg.c [new file with mode: 0644]
drivers/usb/dwc3/otg.h [new file with mode: 0644]

index 46172f47f02d8f5c73ce376d0d6a69f2a8246d1b..7a67ee79496c35bbfac84a085e7dd183e9e9645b 100644 (file)
@@ -15,6 +15,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
        dwc3-y                          += gadget.o ep0.o
 endif
 
+ifneq ($(filter y,$(CONFIG_USB_DWC3_DUAL_ROLE)),)
+       dwc3-y                          += otg.o
+endif
+
 ifneq ($(CONFIG_DEBUG_FS),)
        dwc3-y                          += debugfs.o
 endif
index 81b092ba5b85922c9431b5a9fc830093c11950fd..badbcc87fa07312047f69e1de8767aada86884e4 100644 (file)
@@ -211,7 +211,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
 {
        struct dwc3_event_buffer        *evt;
        int                             n;
@@ -436,7 +436,7 @@ static void dwc3_phy_setup(struct dwc3 *dwc)
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int dwc3_core_init(struct dwc3 *dwc)
+int dwc3_core_init(struct dwc3 *dwc)
 {
        unsigned long           timeout;
        u32                     hwparams4 = dwc->hwparams.hwparams4;
@@ -580,7 +580,7 @@ err0:
        return ret;
 }
 
-static void dwc3_core_exit(struct dwc3 *dwc)
+void dwc3_core_exit(struct dwc3 *dwc)
 {
        dwc3_free_scratch_buffers(dwc);
        usb_phy_shutdown(dwc->usb2_phy);
index fdab715a063119d6e696a8f66ea26d4a1613e983..30abb5fc4258c63ff5d5fe0ac6d52487872f4390 100644 (file)
 
 /* Global HWPARAMS6 Register */
 #define DWC3_GHWPARAMS6_EN_FPGA                        (1 << 7)
+#define DWC3_GHWPARAMS6_SRP_SUPPORT            (1 << 10)
 
 /* Device Configuration Register */
 #define DWC3_DCFG_DEVADDR(addr)        ((addr) << 3)
 #define DWC3_DEPCMD_TYPE_BULK          2
 #define DWC3_DEPCMD_TYPE_INTR          3
 
+/* OTG Control Register */
+#define DWC3_OTG_OCTL_PERIMODE         (1 << 6)
+#define DWC3_OTG_OCTL_PORTPWR          (1 << 5)
+
+/* OTG Events Register */
+#define DWC3_OEVT_DEVICEMODE                   (1 << 31)
+#define DWC3_OEVT_CLEAR_ALL                    (~DWC3_OEVT_DEVICEMODE)
+#define DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT                (1 << 24)
+#define DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT                (1 << 8)
+
+/* OTG Status Register */
+#define DWC3_OTG_OSTS_BSESVALID                (1 << 2)
+#define DWC3_OTG_OSTS_CONIDSTS         (1 << 0)
+
 /* Structures */
 
 struct dwc3_trb;
@@ -730,6 +745,8 @@ struct dwc3 {
        struct dwc3_event_buffer **ev_buffs;
        struct dwc3_ep          *eps[DWC3_ENDPOINTS_NUM];
 
+       struct dwc3_otg         *dotg;
+
        struct usb_gadget       gadget;
        struct usb_gadget_driver *gadget_driver;
 
@@ -828,6 +845,8 @@ struct dwc3 {
 
        unsigned                tx_de_emphasis_quirk:1;
        unsigned                tx_de_emphasis:2;
+
+       unsigned                needs_reinit:1;
 };
 
 /* -------------------------------------------------------------------------- */
@@ -978,6 +997,13 @@ struct dwc3_gadget_ep_cmd_params {
 /* prototypes */
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
 int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc);
+int dwc3_otg_start(struct dwc3 *dwc);
+void dwc3_otg_stop(struct dwc3 *dwc);
+int dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+int dwc3_core_init(struct dwc3 *dwc);
+void dwc3_core_exit(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
 
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_host_init(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c
new file mode 100644 (file)
index 0000000..56bc6b6
--- /dev/null
@@ -0,0 +1,648 @@
+/**
+ * otg.c - DesignWare USB3 DRD Controller OTG
+ *
+ * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com
+ *
+ * Authors: Ido Shayevitz <idos@codeaurora.org>
+ *         Anton Tikhomirov <av.tikhomirov@samsung.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.
+ */
+
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/delay.h>
+
+#include "core.h"
+#include "otg.h"
+#include "io.h"
+
+static struct dwc3_ext_otg_ops *dwc3_otg_rsw_probe(struct dwc3 *dwc)
+{
+       return NULL;
+}
+
+static int dwc3_otg_statemachine(struct otg_fsm *fsm, bool reset)
+{
+       struct dwc3_otg *dotg   = container_of(fsm, struct dwc3_otg, fsm);
+       enum usb_otg_state prev_state = dotg->state;
+       int ret                 = 0;
+
+       if (reset) {
+               if (dotg->state == OTG_STATE_A_HOST) {
+                       otg_drv_vbus(fsm, 0);
+                       otg_start_host(fsm, 0);
+               } else if (dotg->state == OTG_STATE_B_PERIPHERAL) {
+                       otg_start_gadget(fsm, 0);
+               }
+
+               dotg->state = OTG_STATE_UNDEFINED;
+               goto exit;
+       }
+
+       switch (dotg->state) {
+       case OTG_STATE_UNDEFINED:
+               if (fsm->id)
+                       dotg->state = OTG_STATE_B_IDLE;
+               else
+                       dotg->state = OTG_STATE_A_IDLE;
+               break;
+       case OTG_STATE_B_IDLE:
+               if (!fsm->id) {
+                       dotg->state = OTG_STATE_A_IDLE;
+               } else if (fsm->b_sess_vld) {
+                       ret = otg_start_gadget(fsm, 1);
+                       if (!ret)
+                               dotg->state = OTG_STATE_B_PERIPHERAL;
+                       else
+                               pr_err("OTG SM: cannot start gadget\n");
+               }
+               break;
+       case OTG_STATE_B_PERIPHERAL:
+               if (!fsm->id || !fsm->b_sess_vld) {
+                       ret = otg_start_gadget(fsm, 0);
+                       if (!ret)
+                               dotg->state = OTG_STATE_B_IDLE;
+                       else
+                               pr_err("OTG SM: cannot stop gadget\n");
+               }
+               break;
+       case OTG_STATE_A_IDLE:
+               if (fsm->id) {
+                       dotg->state = OTG_STATE_B_IDLE;
+               } else {
+                       ret = otg_start_host(fsm, 1);
+                       if (!ret) {
+                               otg_drv_vbus(fsm, 1);
+                               dotg->state = OTG_STATE_A_HOST;
+                       } else {
+                               pr_err("OTG SM: cannot start host\n");
+                       }
+               }
+               break;
+       case OTG_STATE_A_HOST:
+               if (fsm->id) {
+                       otg_drv_vbus(fsm, 0);
+                       ret = otg_start_host(fsm, 0);
+                       if (!ret)
+                               dotg->state = OTG_STATE_A_IDLE;
+                       else
+                               pr_err("OTG SM: cannot stop host\n");
+               }
+               break;
+       default:
+               pr_err("OTG SM: invalid state\n");
+       }
+
+exit:
+       if (!ret)
+               ret = (dotg->state != prev_state);
+
+       pr_debug("OTG SM: %s => %s\n", usb_otg_state_string(prev_state),
+               (ret > 0) ? usb_otg_state_string(dotg->state) : "(no change)");
+
+       return ret;
+}
+
+static void dwc3_otg_set_host_mode(struct dwc3_otg *dotg)
+{
+       struct dwc3     *dwc = dotg->dwc;
+       u32             reg;
+
+       dwc->needs_reinit = 1;
+
+       if (dotg->regs) {
+               reg = dwc3_readl(dotg->regs, DWC3_OCTL);
+               reg &= ~DWC3_OTG_OCTL_PERIMODE;
+               dwc3_writel(dotg->regs, DWC3_OCTL, reg);
+       } else {
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+       }
+}
+
+static void dwc3_otg_set_peripheral_mode(struct dwc3_otg *dotg)
+{
+       struct dwc3     *dwc = dotg->dwc;
+       u32             reg;
+
+       dwc->needs_reinit = 1;
+
+       if (dotg->regs) {
+               reg = dwc3_readl(dotg->regs, DWC3_OCTL);
+               reg |= DWC3_OTG_OCTL_PERIMODE;
+               dwc3_writel(dotg->regs, DWC3_OCTL, reg);
+       } else {
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+       }
+}
+
+static void dwc3_otg_drv_vbus(struct otg_fsm *fsm, int on)
+{
+       struct dwc3_otg *dotg = container_of(fsm, struct dwc3_otg, fsm);
+       int             ret;
+
+       /* Regulator is not available */
+       if (IS_ERR(dotg->vbus_reg) || !dotg->vbus_reg)
+               return;
+
+       if (on)
+               ret = regulator_enable(dotg->vbus_reg);
+       else
+               ret = regulator_disable(dotg->vbus_reg);
+
+       if (ret)
+               dev_err(dotg->dwc->dev, "Failed to turn Vbus %s\n",
+                       on ? "on" : "off");
+}
+
+static int dwc3_otg_start_host(struct otg_fsm *fsm, int on)
+{
+       struct usb_otg  *otg = fsm->otg;
+       struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
+       struct dwc3     *dwc = dotg->dwc;
+       struct device   *dev = dotg->dwc->dev;
+       int             ret;
+
+       if (!dotg->dwc->xhci)
+               return -EINVAL;
+
+       dev_info(dev, "Turn %s host\n", on ? "on" : "off");
+
+       if (on) {
+               pm_runtime_get_sync(dev);
+               dwc3_otg_set_host_mode(dotg);
+               if (dwc->needs_reinit) {
+                       ret = dwc3_core_init(dwc);
+                       if (ret) {
+                               dev_err(dwc->dev, "%s: failed to reinitialize core\n",
+                                               __func__);
+                               return ret;
+                       }
+                       dwc->needs_reinit = 0;
+               }
+               ret = platform_device_add(dwc->xhci);
+               if (ret) {
+                       dev_err(dev, "%s: cannot add xhci\n", __func__);
+                       return ret;
+               }
+       } else {
+               platform_device_del(dwc->xhci);
+               dwc3_core_exit(dwc);
+               dwc->needs_reinit = 1;
+               pm_runtime_put_sync(dev);
+       }
+
+       return 0;
+}
+
+static int dwc3_otg_start_gadget(struct otg_fsm *fsm, int on)
+{
+       struct usb_otg  *otg = fsm->otg;
+       struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
+       struct device   *dev = dotg->dwc->dev;
+       int             ret;
+
+       if (!otg->gadget)
+               return -EINVAL;
+
+       dev_info(dev, "Turn %s gadget %s\n",
+               on ? "on" : "off", otg->gadget->name);
+
+       if (on) {
+               pm_runtime_get_sync(dev);
+               dwc3_otg_set_peripheral_mode(dotg);
+               ret = usb_gadget_vbus_connect(otg->gadget);
+       } else {
+               /*
+                * Delay VBus OFF signal delivery to not miss Disconnect
+                * interrupt (80ms is minimum; ascertained by experiment)
+                */
+               msleep(200);
+
+               ret = usb_gadget_vbus_disconnect(otg->gadget);
+               pm_runtime_put_sync(dev);
+       }
+
+       return ret;
+}
+
+static struct otg_fsm_ops dwc3_otg_fsm_ops = {
+       .drv_vbus       = dwc3_otg_drv_vbus,
+       .start_host     = dwc3_otg_start_host,
+       .start_gadget   = dwc3_otg_start_gadget,
+};
+
+void dwc3_otg_run_sm(struct otg_fsm *fsm)
+{
+       int     state_changed;
+
+       mutex_lock(&fsm->lock);
+       do {
+               state_changed = dwc3_otg_statemachine(fsm, false);
+       } while (state_changed > 0);
+       mutex_unlock(&fsm->lock);
+}
+
+/**
+ * dwc3_otg_set_peripheral -  bind/unbind the peripheral controller driver.
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int dwc3_otg_set_peripheral(struct usb_otg *otg,
+                               struct usb_gadget *gadget)
+{
+       struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
+       struct otg_fsm  *fsm = &dotg->fsm;
+       struct device   *dev = dotg->dwc->dev;
+
+       if (gadget) {
+               dev_err(dev, "Binding gadget %s\n", gadget->name);
+
+               otg->gadget = gadget;
+       } else {
+               dev_err(dev, "Unbinding gadget\n");
+
+               mutex_lock(&fsm->lock);
+               if (dotg->state == OTG_STATE_B_PERIPHERAL) {
+                       /* Reset OTG SM */
+                       dwc3_otg_statemachine(fsm, true);
+               }
+               otg->gadget = NULL;
+               mutex_unlock(&fsm->lock);
+
+               dwc3_otg_run_sm(fsm);
+       }
+
+       return 0;
+}
+
+static irqreturn_t dwc3_otg_thread_interrupt(int irq, void *_dotg)
+{
+       struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg;
+
+       dwc3_otg_run_sm(&dotg->fsm);
+
+       return IRQ_HANDLED;
+}
+
+static int dwc3_otg_get_id_state(struct dwc3_otg *dotg)
+{
+       u32     reg = dwc3_readl(dotg->regs, DWC3_OSTS);
+
+       return !!(reg & DWC3_OTG_OSTS_CONIDSTS);
+}
+
+static int dwc3_otg_get_b_sess_state(struct dwc3_otg *dotg)
+{
+       u32     reg = dwc3_readl(dotg->regs, DWC3_OSTS);
+
+       return !!(reg & DWC3_OTG_OSTS_BSESVALID);
+}
+
+/**
+ * dwc3_otg_interrupt - interrupt handler for dwc3 otg events.
+ *
+ * @irq: irq number.
+ * @_dotg: Pointer to dwc3 otg context structure.
+ */
+static irqreturn_t dwc3_otg_interrupt(int irq, void *_dotg)
+{
+       struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg;
+       struct otg_fsm  *fsm = &dotg->fsm;
+       u32             oevt, handled_events = 0;
+       irqreturn_t     ret = IRQ_NONE;
+
+       oevt = dwc3_readl(dotg->regs, DWC3_OEVT);
+
+       /* ID */
+       if (oevt & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) {
+               fsm->id = dwc3_otg_get_id_state(dotg);
+               handled_events |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT;
+       }
+
+       /* VBus */
+       if (oevt & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT) {
+               fsm->b_sess_vld = dwc3_otg_get_b_sess_state(dotg);
+               handled_events |= DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT;
+       }
+
+       if (handled_events) {
+               dwc3_writel(dotg->regs, DWC3_OEVT, handled_events);
+               ret = IRQ_WAKE_THREAD;
+       }
+
+       return ret;
+}
+
+static void dwc3_otg_enable_irq(struct dwc3_otg *dotg)
+{
+       /* Enable only connector ID status & VBUS change events */
+       dwc3_writel(dotg->regs, DWC3_OEVTEN,
+                       DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT |
+                       DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT);
+}
+
+static void dwc3_otg_disable_irq(struct dwc3_otg *dotg)
+{
+       dwc3_writel(dotg->regs, DWC3_OEVTEN, 0x0);
+}
+
+/**
+ * dwc3_otg_reset - reset dwc3 otg registers.
+ *
+ * @dotg: Pointer to dwc3 otg context structure.
+ */
+static void dwc3_otg_reset(struct dwc3_otg *dotg)
+{
+       /*
+        * OCFG[2] - OTG-Version = 0
+        * OCFG[1] - HNPCap = 0
+        * OCFG[0] - SRPCap = 0
+        */
+       dwc3_writel(dotg->regs, DWC3_OCFG, 0x0);
+
+       /*
+        * OCTL[6] - PeriMode = 1
+        * OCTL[5] - PrtPwrCtl = 0
+        * OCTL[4] - HNPReq = 0
+        * OCTL[3] - SesReq = 0
+        * OCTL[2] - TermSelDLPulse = 0
+        * OCTL[1] - DevSetHNPEn = 0
+        * OCTL[0] - HstSetHNPEn = 0
+        */
+       dwc3_writel(dotg->regs, DWC3_OCTL, DWC3_OTG_OCTL_PERIMODE);
+
+       /* Clear all otg events (interrupts) indications  */
+       dwc3_writel(dotg->regs, DWC3_OEVT, DWC3_OEVT_CLEAR_ALL);
+
+}
+
+/* SysFS interface */
+
+static ssize_t
+dwc3_otg_show_state(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+
+       return snprintf(buf, PAGE_SIZE, "%s\n",
+                       usb_otg_state_string(dwc->dotg->state));
+}
+
+static DEVICE_ATTR(state, S_IRUSR | S_IRGRP,
+       dwc3_otg_show_state, NULL);
+
+/*
+ * id and b_sess attributes allow to change DRD mode and B-Session state.
+ * Can be used for debug purpose.
+ */
+
+static ssize_t
+dwc3_otg_show_b_sess(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       struct otg_fsm  *fsm = &dwc->dotg->fsm;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", fsm->b_sess_vld);
+}
+
+static ssize_t
+dwc3_otg_store_b_sess(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t n)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       struct otg_fsm  *fsm = &dwc->dotg->fsm;
+       int             b_sess_vld;
+
+       if (sscanf(buf, "%d", &b_sess_vld) != 1)
+               return -EINVAL;
+
+       fsm->b_sess_vld = !!b_sess_vld;
+
+       dwc3_otg_run_sm(fsm);
+
+       return n;
+}
+
+static DEVICE_ATTR(b_sess, S_IWUSR | S_IRUSR | S_IRGRP,
+       dwc3_otg_show_b_sess, dwc3_otg_store_b_sess);
+
+static ssize_t
+dwc3_otg_show_id(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       struct otg_fsm  *fsm = &dwc->dotg->fsm;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", fsm->id);
+}
+
+static ssize_t
+dwc3_otg_store_id(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t n)
+{
+       struct dwc3     *dwc = dev_get_drvdata(dev);
+       struct otg_fsm  *fsm = &dwc->dotg->fsm;
+       int id;
+
+       if (sscanf(buf, "%d", &id) != 1)
+               return -EINVAL;
+
+       fsm->id = !!id;
+
+       dwc3_otg_run_sm(fsm);
+
+       return n;
+}
+
+static DEVICE_ATTR(id, S_IWUSR | S_IRUSR | S_IRGRP,
+       dwc3_otg_show_id, dwc3_otg_store_id);
+
+static struct attribute *dwc3_otg_attributes[] = {
+       &dev_attr_id.attr,
+       &dev_attr_b_sess.attr,
+       &dev_attr_state.attr,
+       NULL
+};
+
+static const struct attribute_group dwc3_otg_attr_group = {
+       .attrs = dwc3_otg_attributes,
+};
+
+/**
+ * dwc3_otg_start
+ * @dwc: pointer to our controller context structure
+ */
+int dwc3_otg_start(struct dwc3 *dwc)
+{
+       struct dwc3_otg         *dotg = dwc->dotg;
+       struct otg_fsm          *fsm = &dotg->fsm;
+       int                     ret;
+
+       if (dotg->ext_otg_ops) {
+               ret = dwc3_ext_otg_start(dotg);
+               if (ret) {
+                       dev_err(dwc->dev, "failed to start external OTG\n");
+                       return ret;
+               }
+       } else {
+               dotg->regs = dwc->regs;
+
+               dwc3_otg_reset(dotg);
+
+               dotg->fsm.id = dwc3_otg_get_id_state(dotg);
+               dotg->fsm.b_sess_vld = dwc3_otg_get_b_sess_state(dotg);
+
+               dotg->irq = platform_get_irq(to_platform_device(dwc->dev), 0);
+               ret = devm_request_threaded_irq(dwc->dev, dotg->irq,
+                               dwc3_otg_interrupt,
+                               dwc3_otg_thread_interrupt,
+                               IRQF_SHARED, "dwc3-otg", dotg);
+               if (ret) {
+                       dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+                                       dotg->irq, ret);
+                       return ret;
+               }
+
+               dwc3_otg_enable_irq(dotg);
+       }
+
+       dwc3_otg_run_sm(fsm);
+
+       return 0;
+}
+
+/**
+ * dwc3_otg_stop
+ * @dwc: pointer to our controller context structure
+ */
+void dwc3_otg_stop(struct dwc3 *dwc)
+{
+       struct dwc3_otg         *dotg = dwc->dotg;
+
+       if (dotg->ext_otg_ops) {
+               dwc3_ext_otg_stop(dotg);
+       } else {
+               dwc3_otg_disable_irq(dotg);
+               free_irq(dotg->irq, dotg);
+       }
+}
+
+/**
+ * dwc3_otg_init - Initializes otg related registers
+ * @dwc: pointer to our controller context structure
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+int dwc3_otg_init(struct dwc3 *dwc)
+{
+       struct dwc3_otg         *dotg;
+       struct dwc3_ext_otg_ops *ops = NULL;
+       u32                     reg;
+       int                     ret = 0;
+
+       /*
+        * GHWPARAMS6[10] bit is SRPSupport.
+        * This bit also reflects DWC_USB3_EN_OTG
+        */
+       reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
+       if (!(reg & DWC3_GHWPARAMS6_SRP_SUPPORT)) {
+               dev_err(dwc->dev, "dwc3_otg address space is not supported\n");
+
+               /*
+                * Some SoCs (e.g. Exynos5) don't have HW OTG, however, some
+                * boards use simplified role switch (rsw) function based on
+                * ID/BSes gpio interrupts. As a fall-back try to bind to rsw.
+                */
+               ops = dwc3_otg_rsw_probe(dwc);
+               if (ops)
+                       goto has_ext_otg;
+
+               /*
+                * No HW OTG support in the core.
+                * We return 0 to indicate no error, since this is acceptable
+                * situation, just continue probe the dwc3 driver without otg.
+                */
+               return 0;
+       }
+
+has_ext_otg:
+       /* Allocate and init otg instance */
+       dotg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL);
+       if (!dotg) {
+               dev_err(dwc->dev, "unable to allocate dwc3_otg\n");
+               return -ENOMEM;
+       }
+
+       /* This reference is used by dwc3 modules for checking otg existance */
+       dwc->dotg = dotg;
+       dotg->dwc = dwc;
+
+       dotg->ext_otg_ops = ops;
+
+       dotg->otg.set_peripheral = dwc3_otg_set_peripheral;
+       dotg->otg.set_host = NULL;
+
+       dotg->otg.usb_phy = dwc->usb2_phy;
+       dotg->state = OTG_STATE_UNDEFINED;
+
+       mutex_init(&dotg->fsm.lock);
+       dotg->fsm.ops = &dwc3_otg_fsm_ops;
+       dotg->fsm.otg = &dotg->otg;
+
+       dotg->vbus_reg = devm_regulator_get(dwc->dev->parent, "dwc3-vbus");
+       if (IS_ERR(dotg->vbus_reg))
+               dev_info(dwc->dev, "vbus regulator is not available\n");
+
+       if (dotg->ext_otg_ops) {
+               ret = dwc3_ext_otg_setup(dotg);
+               if (ret) {
+                       dev_err(dwc->dev, "failed to setup external OTG\n");
+                       return ret;
+               }
+       }
+
+       ret = sysfs_create_group(&dwc->dev->kobj, &dwc3_otg_attr_group);
+       if (ret)
+               dev_err(dwc->dev, "failed to create dwc3 otg attributes\n");
+
+       return 0;
+}
+
+/**
+ * dwc3_otg_exit
+ * @dwc: pointer to our controller context structure
+ */
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+       struct dwc3_otg         *dotg = dwc->dotg;
+       u32                     reg;
+
+       reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
+       if (!(reg & DWC3_GHWPARAMS6_SRP_SUPPORT)) {
+               if (dotg->ext_otg_ops) {
+                       dwc3_ext_otg_exit(dotg);
+                       goto has_ext_otg;
+               }
+
+               return;
+       }
+
+has_ext_otg:
+       sysfs_remove_group(&dwc->dev->kobj, &dwc3_otg_attr_group);
+       dotg->state = OTG_STATE_UNDEFINED;
+       kfree(dotg);
+       dwc->dotg = NULL;
+}
diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h
new file mode 100644 (file)
index 0000000..25bc027
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * otg.c - DesignWare USB3 DRD Controller OTG
+ *
+ * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com
+ *
+ * Authors: Ido Shayevitz <idos@codeaurora.org>
+ *         Anton Tikhomirov <av.tikhomirov@samsung.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.
+ */
+
+#ifndef __LINUX_USB_DWC3_OTG_H
+#define __LINUX_USB_DWC3_OTG_H
+
+#include <linux/usb/otg-fsm.h>
+
+#include "core.h"
+
+struct dwc3_ext_otg_ops {
+       int     (*setup)(struct device *dev, struct otg_fsm *fsm);
+       void    (*exit)(struct device *dev);
+       int     (*start) (struct device *dev);
+       void    (*stop)(struct device *dev);
+};
+
+/**
+ * struct dwc3_otg: OTG driver data. Shared by HCD and DCD.
+ * @otg: USB OTG Transceiver structure.
+ * @fsm: OTG Final State Machine.
+ * @dwc: pointer to our controller context structure.
+ * @irq: IRQ number assigned for HSUSB controller.
+ * @regs: ioremapped register base address.
+ * @vbus_reg: Vbus regulator.
+ * @ext_otg_ops: external OTG engine ops.
+ */
+struct dwc3_otg {
+       struct usb_otg          otg;
+       struct otg_fsm          fsm;
+       struct dwc3             *dwc;
+       int                     irq;
+       void __iomem            *regs;
+
+       struct regulator        *vbus_reg;
+
+       struct dwc3_ext_otg_ops *ext_otg_ops;
+
+       enum usb_otg_state      state;
+};
+
+static inline int dwc3_ext_otg_setup(struct dwc3_otg *dotg)
+{
+       struct device *dev = dotg->dwc->dev->parent;
+
+       if (!dotg->ext_otg_ops->setup)
+               return -EOPNOTSUPP;
+       return dotg->ext_otg_ops->setup(dev, &dotg->fsm);
+}
+
+static inline int dwc3_ext_otg_exit(struct dwc3_otg *dotg)
+{
+       struct device *dev = dotg->dwc->dev->parent;
+
+       if (!dotg->ext_otg_ops->exit)
+               return -EOPNOTSUPP;
+       dotg->ext_otg_ops->exit(dev);
+       return 0;
+}
+
+static inline int dwc3_ext_otg_start(struct dwc3_otg *dotg)
+{
+       struct device *dev = dotg->dwc->dev->parent;
+
+       if (!dotg->ext_otg_ops->start)
+               return -EOPNOTSUPP;
+       return dotg->ext_otg_ops->start(dev);
+}
+
+static inline int dwc3_ext_otg_stop(struct dwc3_otg *dotg)
+{
+       struct device *dev = dotg->dwc->dev->parent;
+
+       if (!dotg->ext_otg_ops->stop)
+               return -EOPNOTSUPP;
+       dotg->ext_otg_ops->stop(dev);
+       return 0;
+}
+
+void dwc3_otg_run_sm(struct otg_fsm *fsm);
+
+#endif /* __LINUX_USB_DWC3_OTG_H */