[RFC 11/19] dwc3: add OTG handling code
From: Robert Baldyga
Date: Wed Mar 18 2015 - 10:08:03 EST
This patch introduces OTG support in DWC3 driver. OTG feature is responsible
for dynamic USB role switching (between host and peripheral modes) based
on detected cable type.
Each role switch causes complete core reinitialization. In peripheral
mode UDC device is activated using VBUS session control mechanism. In
host mode the new XHCI device is registered to make bus visible in system.
This code is inspired by DWC3 driver from Hardkernel Linux sources [1].
[1] https://github.com/hardkernel/linux.
Signed-off-by: Robert Baldyga <r.baldyga@xxxxxxxxxxx>
---
drivers/usb/dwc3/Kconfig | 1 +
drivers/usb/dwc3/Makefile | 4 +
drivers/usb/dwc3/core.h | 4 +
drivers/usb/dwc3/otg.c | 497 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc3/otg.h | 56 ++++++
5 files changed, 562 insertions(+)
create mode 100644 drivers/usb/dwc3/otg.c
create mode 100644 drivers/usb/dwc3/otg.h
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 827c4f8..d3ae67f 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -34,6 +34,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))
+ select USB_OTG_FSM
help
This is the default mode of working of DWC3 controller where
both host and gadget features are enabled.
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 46172f4..7a67ee7 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -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
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index be29cb2..5f8df7c 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -745,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;
@@ -844,6 +846,8 @@ struct dwc3 {
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
unsigned vbus_session:1;
+
+ unsigned needs_reinit:1;
};
/* -------------------------------------------------------------------------- */
diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c
new file mode 100644
index 0000000..708ab22
--- /dev/null
+++ b/drivers/usb/dwc3/otg.c
@@ -0,0 +1,497 @@
+/**
+ * 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@xxxxxxxxxxxxxx>
+ * Anton Tikhomirov <av.tikhomirov@xxxxxxxxxxx>
+ * Robert Baldyga <r.baldyga@xxxxxxxxxxx>
+ *
+ * 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 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, "failed to reinitialize core\n");
+ 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 dwc3 *dwc = dotg->dwc;
+ 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);
+ if (dwc->needs_reinit) {
+ ret = dwc3_core_init(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to reinitialize core\n");
+ return ret;
+ }
+ dwc->needs_reinit = 0;
+ }
+ 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);
+ dwc3_core_exit(dwc);
+ dwc->needs_reinit = 1;
+ 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;
+
+ do {
+ state_changed = otg_statemachine(fsm);
+ } while (state_changed > 0);
+}
+
+/**
+ * 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)
+{
+ otg->gadget = gadget;
+ if (!gadget)
+ otg->state = OTG_STATE_UNDEFINED;
+
+ 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 */
+
+/*
+ * 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;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &b_sess_vld);
+ if (ret < 0)
+ return ret;
+
+ 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;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &id);
+ if (ret < 0)
+ return ret;
+
+ 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,
+ 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)
+ return -ENODEV;
+
+ 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)
+ return;
+
+ 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;
+ 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");
+ return 0;
+ }
+
+ /* Allocate and init otg instance */
+ dotg = devm_kzalloc(dwc->dev, sizeof(struct dwc3_otg), GFP_KERNEL);
+ if (!dotg)
+ return -ENOMEM;
+
+ /* This reference is used by dwc3 modules for checking otg existence */
+ dwc->dotg = dotg;
+ dotg->dwc = dwc;
+
+ dotg->otg.set_peripheral = dwc3_otg_set_peripheral;
+ dotg->otg.set_host = NULL;
+
+ dotg->otg.usb_phy = dwc->usb2_phy;
+
+ 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");
+
+ 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;
+
+ if (!dotg)
+ return;
+
+ sysfs_remove_group(&dwc->dev->kobj, &dwc3_otg_attr_group);
+ kfree(dotg);
+ dwc->dotg = NULL;
+}
diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h
new file mode 100644
index 0000000..ffb8f0e
--- /dev/null
+++ b/drivers/usb/dwc3/otg.h
@@ -0,0 +1,56 @@
+/**
+ * otg.h - 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@xxxxxxxxxxxxxx>
+ * Anton Tikhomirov <av.tikhomirov@xxxxxxxxxxx>
+ * Robert Baldyga <r.baldyga@xxxxxxxxxxx>
+ *
+ * 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_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.
+ */
+struct dwc3_otg {
+ struct usb_otg otg;
+ struct otg_fsm fsm;
+ struct dwc3 *dwc;
+ int irq;
+ void __iomem *regs;
+
+ struct regulator *vbus_reg;
+};
+
+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);
+
+
+void dwc3_otg_run_sm(struct otg_fsm *fsm);
+
+#endif /* __LINUX_USB_DWC3_OTG_H */
--
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/