From: Robert Baldyga Date: Mon, 23 Feb 2015 11:01:54 +0000 (+0100) Subject: dwc3: add otg handling code X-Git-Tag: submit/tizen/20150416.081342~39 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2732bfea8e49307cc36ccc3e73882a4109b06c87;p=platform%2Fkernel%2Flinux-exynos.git dwc3: add otg handling code This code is based on DWC3 driver from https://github.com/hardkernel/linux. Signed-off-by: Robert Baldyga --- diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 46172f47f02d..7a67ee79496c 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.c b/drivers/usb/dwc3/core.c index 81b092ba5b85..badbcc87fa07 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -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); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index fdab715a0631..30abb5fc4258 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -223,6 +223,7 @@ /* 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) @@ -381,6 +382,20 @@ #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 index 000000000000..56bc6b667ff0 --- /dev/null +++ b/drivers/usb/dwc3/otg.c @@ -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 + * Anton Tikhomirov + * + * 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 +#include +#include +#include +#include + +#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 index 000000000000..25bc02734b3d --- /dev/null +++ b/drivers/usb/dwc3/otg.h @@ -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 + * Anton Tikhomirov + * + * 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 + +#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 */