--- /dev/null
+/**
+ * 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;
+}