This is first release of otg driver for the dwc3 Synopsys USB3 core. The otg driver implements the otg final state machine and control the activation of the device controller or host controller. In this first implementation, only simple DRD mode is implemented, determine if A or B device according to the ID pin as reflected in the OSTS.ConIDSts field. Signed-off-by: Ido Shayevitz <idos@xxxxxxxxxxxxxx> --- drivers/usb/dwc3/Kconfig | 6 +- drivers/usb/dwc3/Makefile | 2 + drivers/usb/dwc3/core.c | 15 +- drivers/usb/dwc3/core.h | 51 ++++- drivers/usb/dwc3/dwc3_otg.c | 512 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/dwc3_otg.h | 38 +++ drivers/usb/dwc3/gadget.c | 63 +++++ drivers/usb/host/xhci-plat.c | 21 ++ drivers/usb/host/xhci.c | 13 +- 9 files changed, 708 insertions(+), 13 deletions(-) create mode 100644 drivers/usb/dwc3/dwc3_otg.c create mode 100644 drivers/usb/dwc3/dwc3_otg.h diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index d13c60f..0cc108d 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -1,9 +1,9 @@ config USB_DWC3 tristate "DesignWare USB3 DRD Core Support" - depends on (USB && USB_GADGET) + depends on (USB || USB_GADGET) select USB_OTG_UTILS - select USB_GADGET_DUALSPEED - select USB_GADGET_SUPERSPEED + select USB_GADGET_DUALSPEED if USB_GADGET + select USB_GADGET_SUPERSPEED if USB_GADGET select USB_XHCI_PLATFORM if USB_SUPPORT && USB_XHCI_HCD help Say Y or M here if your system has a Dual Role SuperSpeed diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index d441fe4..ffb3f55 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -1,11 +1,13 @@ ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG +ccflags-y += -Idrivers/usb/host obj-$(CONFIG_USB_DWC3) += dwc3.o dwc3-y := core.o dwc3-y += host.o dwc3-y += gadget.o ep0.o +dwc3-y += dwc3_otg.o ifneq ($(CONFIG_DEBUG_FS),) dwc3-y += debugfs.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index c34452a..5343e39 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -517,15 +517,24 @@ static int __devinit dwc3_probe(struct platform_device *pdev) break; case DWC3_MODE_DRD: dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + ret = dwc3_otg_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize otg\n"); + goto err1; + } + ret = dwc3_host_init(dwc); if (ret) { dev_err(dev, "failed to initialize host\n"); + dwc3_otg_exit(dwc); goto err1; } ret = dwc3_gadget_init(dwc); if (ret) { dev_err(dev, "failed to initialize gadget\n"); + dwc3_host_exit(dwc); + dwc3_otg_exit(dwc); goto err1; } break; @@ -554,8 +563,9 @@ err2: dwc3_host_exit(dwc); break; case DWC3_MODE_DRD: - dwc3_host_exit(dwc); dwc3_gadget_exit(dwc); + dwc3_host_exit(dwc); + dwc3_otg_exit(dwc); break; default: /* do nothing */ @@ -588,8 +598,9 @@ static int __devexit dwc3_remove(struct platform_device *pdev) dwc3_host_exit(dwc); break; case DWC3_MODE_DRD: - dwc3_host_exit(dwc); dwc3_gadget_exit(dwc); + dwc3_host_exit(dwc); + dwc3_otg_exit(dwc); break; default: /* do nothing */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 151eca8..793758b 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -50,6 +50,8 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include "dwc3_otg.h" + /* Global constants */ #define DWC3_EP0_BOUNCE_SIZE 512 #define DWC3_ENDPOINTS_NUM 32 @@ -152,8 +154,9 @@ /* OTG Registers */ #define DWC3_OCFG 0xcc00 #define DWC3_OCTL 0xcc04 -#define DWC3_OEVTEN 0xcc08 -#define DWC3_OSTS 0xcc0C +#define DWC3_OEVT 0xcc08 +#define DWC3_OEVTEN 0xcc0c +#define DWC3_OSTS 0xcc10 /* Bit fields */ @@ -203,6 +206,9 @@ #define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) #define DWC3_MAX_HIBER_SCRATCHBUFS 15 +/* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_SRP_SUPPORT (1 << 10) + /* Device Configuration Register */ #define DWC3_DCFG_LPM_CAP (1 << 22) #define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) @@ -358,6 +364,37 @@ #define DWC3_DEPCMD_TYPE_BULK 2 #define DWC3_DEPCMD_TYPE_INTR 3 +/* OTG Events Register */ +#define DWC3_OEVT_DEVICEMODE (1 << 31) +#define DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT (1 << 24) +#define DWC3_OEVTEN_OTGADEVBHOSTENDEVNT (1 << 20) +#define DWC3_OEVTEN_OTGADEVHOSTEVNT (1 << 19) +#define DWC3_OEVTEN_OTGADEVHNPCHNGEVNT (1 << 18) +#define DWC3_OEVTEN_OTGADEVSRPDETEVNT (1 << 17) +#define DWC3_OEVTEN_OTGADEVSESSENDDETEVNT (1 << 16) +#define DWC3_OEVTEN_OTGBDEVBHOSTENDEVNT (1 << 11) +#define DWC3_OEVTEN_OTGBDEVHNPCHNGEVNT (1 << 10) +#define DWC3_OEVTEN_OTGBDEVSESSVLDDETEVNT (1 << 9) +#define DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT (1 << 8) + +/* OTG OSTS register */ +#define DWC3_OTG_OSTS_OTGSTATE_SHIFT (8) +#define DWC3_OTG_OSTS_OTGSTATE (0xF << DWC3_OTG_OSTS_OTGSTATE_SHIFT) +#define DWC3_OTG_OSTS_PERIPHERALSTATE (1 << 4) +#define DWC3_OTG_OSTS_XHCIPRTPOWER (1 << 3) +#define DWC3_OTG_OSTS_BSESVALID (1 << 2) +#define DWC3_OTG_OSTS_VBUSVALID (1 << 1) +#define DWC3_OTG_OSTS_CONIDSTS (1 << 0) + +/* OTG OSTS register */ +#define DWC3_OTG_OCTL_PERIMODE (1 << 6) +#define DWC3_OTG_OCTL_PRTPWRCTL (1 << 5) +#define DWC3_OTG_OCTL_HNPREQ (1 << 4) +#define DWC3_OTG_OCTL_SESREQ (1 << 3) +#define DWC3_OTG_OCTL_TERMSELDLPULSE (1 << 2) +#define DWC3_OTG_OCTL_DEVSETHNPEN (1 << 1) +#define DWC3_OTG_OCTL_HSTSETHNPEN (1 << 0) + /* Structures */ struct dwc3_trb; @@ -658,6 +695,7 @@ struct dwc3 { spinlock_t lock; struct device *dev; + struct dwc3_otg *dotg; struct platform_device *xhci; struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM]; @@ -720,6 +758,12 @@ struct dwc3 { u8 test_mode; u8 test_mode_nr; + + /* Indicate if the gadget was powered by the otg driver */ + bool vbus_active; + + /* Indicate if software connect was issued by the usb_gadget_driver */ + bool softconnect; }; /* -------------------------------------------------------------------------- */ @@ -859,6 +903,9 @@ union dwc3_event { void dwc3_set_mode(struct dwc3 *dwc, u32 mode); int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc); +int dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); + int dwc3_host_init(struct dwc3 *dwc); void dwc3_host_exit(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/dwc3_otg.c b/drivers/usb/dwc3/dwc3_otg.c new file mode 100644 index 0000000..4449187 --- /dev/null +++ b/drivers/usb/dwc3/dwc3_otg.c @@ -0,0 +1,512 @@ +/** + * dwc3_otg.c - DesignWare USB3 DRD Controller OTG + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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/usb.h> +#include <linux/usb/hcd.h> +#include <linux/platform_device.h> + +#include "core.h" +#include "dwc3_otg.h" +#include "io.h" +#include "xhci.h" + + +/** + * dwc3_otg_set_host_regs - reset dwc3 otg registers to host operation. + * + * This function sets the OTG registers to work in A-Device host mode. + * This function should be called just before entering to A-Device mode. + * + * @w: Pointer to the dwc3 otg workqueue. + */ +static void dwc3_otg_set_host_regs(struct dwc3_otg *dotg) +{ + u32 octl; + + /* Set OCTL[6](PeriMode) to 0 (host) */ + octl = dwc3_readl(dotg->regs, DWC3_OCTL); + octl &= ~DWC3_OTG_OCTL_PERIMODE; + dwc3_writel(dotg->regs, DWC3_OCTL, octl); + + /* + * TODO: add more OTG registers writes for HOST mode here, + * see figure 12-10 A-device flow in dwc3 Synopsis spec + */ +} + +/** + * dwc3_otg_set_peripheral_regs - reset dwc3 otg registers to peripheral operation. + * + * This function sets the OTG registers to work in B-Device peripheral mode. + * This function should be called just before entering to B-Device mode. + * + * @w: Pointer to the dwc3 otg workqueue. + */ +static void dwc3_otg_set_peripheral_regs(struct dwc3_otg *dotg) +{ + u32 octl; + + /* Set OCTL[6](PeriMode) to 1 (peripheral) */ + octl = dwc3_readl(dotg->regs, DWC3_OCTL); + octl |= DWC3_OTG_OCTL_PERIMODE; + dwc3_writel(dotg->regs, DWC3_OCTL, octl); + + /* + * TODO: add more OTG registers writes for PERIPHERAL mode here, + * see figure 12-19 B-device flow in dwc3 Synopsis spec + */ +} + +/** + * dwc3_otg_start_host - helper function for starting/stoping the host controller driver. + * + * @otg: Pointer to the otg_transceiver structure. + * @on: start / stop the host controller driver. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_host(struct usb_otg *otg, int on) +{ + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int ret = 0; + + if (!otg->host) + return -EINVAL; + + hcd = bus_to_hcd(otg->host); + xhci = hcd_to_xhci(hcd); + if (on) { + dev_dbg(otg->phy->dev, "%s: turn on host %s\n", + __func__, otg->host->bus_name); + dwc3_otg_set_host_regs(dotg); + + /* + * This should be revisited for more testing post-silicon. + * In worst case we may need to disconnect the root hub + * before stopping the controller so that it does not + * interfere with runtime pm/system pm. + * We can also consider registering and unregistering xhci + * platform device. It is almost similar to add_hcd and + * remove_hcd, But we may not use standard set_host method + * anymore. + */ + ret = hcd->driver->start(hcd); + if (ret) { + dev_err(otg->phy->dev, + "%s: failed to start primary hcd, ret=%d\n", + __func__, ret); + return ret; + } + + ret = xhci->shared_hcd->driver->start(xhci->shared_hcd); + if (ret) { + dev_err(otg->phy->dev, + "%s: failed to start secondary hcd, ret=%d\n", + __func__, ret); + return ret; + } + } else { + dev_dbg(otg->phy->dev, "%s: turn off host %s\n", + __func__, otg->host->bus_name); + hcd->driver->stop(hcd); + } + + return 0; +} + +/** + * dwc3_otg_set_host - bind/unbind the host controller driver. + * + * @otg: Pointer to the otg_transceiver structure. + * @host: Pointer to the usb_bus structure. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + + if (host) { + dev_dbg(otg->phy->dev, "%s: set host %s\n", + __func__, host->bus_name); + otg->host = host; + + /* + * Only after both peripheral and host are set then check + * OTG sm. This prevents unnecessary activation of the sm + * in case the ID is high. + */ + if (otg->gadget) + schedule_work(&dotg->sm_work); + } else { + if (otg->phy->state == OTG_STATE_A_HOST) { + dwc3_otg_start_host(otg, 0); + otg->host = NULL; + otg->phy->state = OTG_STATE_UNDEFINED; + schedule_work(&dotg->sm_work); + } else { + otg->host = NULL; + } + } + + return 0; +} + +/** + * dwc3_otg_start_peripheral - bind/unbind the peripheral controller. + * + * @otg: Pointer to the otg_transceiver structure. + * @gadget: pointer to the usb_gadget structure. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_peripheral(struct usb_otg *otg, int on) +{ + struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg); + + if (!otg->gadget) + return -EINVAL; + + if (on) { + dev_dbg(otg->phy->dev, "%s: turn on gadget %s\n", + __func__, otg->gadget->name); + dwc3_otg_set_peripheral_regs(dotg); + usb_gadget_vbus_connect(otg->gadget); + } else { + dev_dbg(otg->phy->dev, "%s: turn off gadget %s\n", + __func__, otg->gadget->name); + usb_gadget_vbus_disconnect(otg->gadget); + } + + return 0; +} + +/** + * dwc3_otg_set_peripheral - bind/unbind the peripheral controller driver. + * + * @otg: Pointer to the otg_transceiver structure. + * @gadget: pointer to the usb_gadget structure. + * + * 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); + + if (gadget) { + dev_dbg(otg->phy->dev, "%s: set gadget %s\n", + __func__, gadget->name); + otg->gadget = gadget; + + /* + * Only after both peripheral and host are set then check + * OTG sm. This prevents unnecessary activation of the sm + * in case the ID is grounded. + */ + if (otg->host) + schedule_work(&dotg->sm_work); + } else { + if (otg->phy->state == OTG_STATE_B_PERIPHERAL) { + dwc3_otg_start_peripheral(otg, 0); + otg->gadget = NULL; + otg->phy->state = OTG_STATE_UNDEFINED; + schedule_work(&dotg->sm_work); + } else { + otg->gadget = NULL; + } + } + + return 0; +} + +/** + * dwc3_otg_interrupt - interrupt handler for dwc3 otg events. + * @_dotg: Pointer to out controller context structure + * + * Returns IRQ_HANDLED on success otherwise IRQ_NONE. + */ +static irqreturn_t dwc3_otg_interrupt(int irq, void *_dotg) +{ + struct dwc3_otg *dotg = (struct dwc3_otg *)_dotg; + u32 oevt_reg; + int ret = IRQ_NONE; + int handled_irqs = 0; + + oevt_reg = dwc3_readl(dotg->regs, DWC3_OEVT); + + if (oevt_reg & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) { + /* + * ID sts has changed, read it and later, in the workqueue + * function, switch from A to B or from B to A. + */ + dotg->osts = dwc3_readl(dotg->regs, DWC3_OSTS); + if ((dotg->otg.phy->state == OTG_STATE_B_IDLE) || + (dotg->otg.phy->state == OTG_STATE_A_IDLE)) { + + /* + * OTG state is ABOUT to change to A or B device, but + * since ID sts was chnaged, then we return the state + * machine to the start point. + */ + dotg->otg.phy->state = OTG_STATE_UNDEFINED; + } + schedule_work(&dotg->sm_work); + + handled_irqs |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT; + ret = IRQ_HANDLED; + } + + /* + * Clear the interrupts we handled. + */ + if (ret == IRQ_HANDLED) + dwc3_writel(dotg->regs, DWC3_OEVT, handled_irqs); + + return ret; +} + +/** + * dwc3_otg_sm_work - workqueue function. + * + * @w: Pointer to the dwc3 otg workqueue + * + * NOTE: After any change in phy->state, + * we must reschdule the state machine. + */ +static void dwc3_otg_sm_work(struct work_struct *w) +{ + struct dwc3_otg *dotg = container_of(w, struct dwc3_otg, sm_work); + struct usb_phy *phy = dotg->otg.phy; + + dev_dbg(phy->dev, "%s state\n", otg_state_string(phy->state)); + + /* Check OTG state */ + switch (phy->state) { + case OTG_STATE_UNDEFINED: + /* Switch to A or B-Device according to IDSTS */ + if (dotg->osts & DWC3_OTG_OSTS_CONIDSTS) + phy->state = OTG_STATE_B_IDLE; + else + phy->state = OTG_STATE_A_IDLE; + + schedule_work(&dotg->sm_work); + break; + case OTG_STATE_B_IDLE: + if (dwc3_otg_start_peripheral(&dotg->otg, 1)) { + /* + * Probably set_peripheral was not called yet. + * We will re-try as soon as it will be called + */ + dev_err(phy->dev, + "unable to start B-device\n"); + phy->state = OTG_STATE_UNDEFINED; + } else + phy->state = OTG_STATE_B_PERIPHERAL; + + schedule_work(&dotg->sm_work); + break; + case OTG_STATE_B_PERIPHERAL: + if (!(dotg->osts & DWC3_OTG_OSTS_CONIDSTS)) { + dwc3_otg_start_peripheral(&dotg->otg, 0); + phy->state = OTG_STATE_A_IDLE; + schedule_work(&dotg->sm_work); + } + break; + case OTG_STATE_A_IDLE: + /* Switch to A-Device*/ + if (dwc3_otg_start_host(&dotg->otg, 1)) { + /* + * Probably set_host was not called yet. + * We will re-try as soon as it will be called + */ + dev_err(phy->dev, + "unable to start A-device\n"); + phy->state = OTG_STATE_UNDEFINED; + } else + phy->state = OTG_STATE_A_HOST; + + schedule_work(&dotg->sm_work); + break; + case OTG_STATE_A_HOST: + if (dotg->osts & DWC3_OTG_OSTS_CONIDSTS) { + dwc3_otg_start_host(&dotg->otg, 0); + phy->state = OTG_STATE_B_IDLE; + schedule_work(&dotg->sm_work); + } + break; + default: + dev_err(phy->dev, "%s: invalid otg-state\n", __func__); + + } +} + + +/** + * dwc3_otg_reset - reset dwc3 otg registers. + * + * @w: Pointer to the dwc3 otg workqueue + */ +static void dwc3_otg_reset(struct dwc3_otg *dotg) +{ + /* + * OCFG[2] - OTG-Version = 1 + * OCFG[1] - HNPCap = 0 + * OCFG[0] - SRPCap = 0 + */ + dwc3_writel(dotg->regs, DWC3_OCFG, 0x4); + + /* + * 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, 0x40); + + /* Clear all otg events (interrupts) indications */ + dwc3_writel(dotg->regs, DWC3_OEVT, 0xFFFF); + + /* Enable only the ConIDStsChngEn event*/ + dwc3_writel(dotg->regs, DWC3_OEVTEN, + DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT); + + /* Read OSTS */ + dotg->osts = dwc3_readl(dotg->regs, DWC3_OSTS); + +} + +/** + * dwc3_otg_init - Initializes otg related registers + * @dwc: Pointer to out controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_otg_init(struct dwc3 *dwc) +{ + u32 reg; + int ret = 0; + struct dwc3_otg *dotg; + + dev_dbg(dwc->dev, "dwc3_otg_init\n"); + + /* + * 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)) { + /* + * No OTG support in the HW core. + * We return 0 to indicate no error, since this is acceptable + * situation, just continue probe the dwc3 driver without otg. + */ + dev_dbg(dwc->dev, "dwc3_otg address space is not supported\n"); + return 0; + } + + /* Allocate and init otg instance */ + dotg = kzalloc(sizeof(struct dwc3_otg), GFP_KERNEL); + if (!dotg) { + dev_err(dwc->dev, "unable to allocate dwc3_otg\n"); + return -ENOMEM; + } + + dotg->irq = platform_get_irq(to_platform_device(dwc->dev), 0); + if (dotg->irq < 0) { + dev_err(dwc->dev, "%s: missing IRQ\n", __func__); + ret = -ENODEV; + goto err1; + } + + dotg->regs = dwc->regs; + + dotg->otg.set_peripheral = dwc3_otg_set_peripheral; + dotg->otg.set_host = dwc3_otg_set_host; + + /* This reference is used by dwc3 modules for checking otg existance */ + dwc->dotg = dotg; + + dotg->otg.phy = kzalloc(sizeof(struct usb_phy), GFP_KERNEL); + if (!dotg->otg.phy) { + dev_err(dwc->dev, "unable to allocate dwc3_otg.phy\n"); + ret = -ENOMEM; + goto err1; + } + + dotg->otg.phy->otg = &dotg->otg; + dotg->otg.phy->dev = dwc->dev; + + ret = usb_set_transceiver(dotg->otg.phy); + if (ret) { + dev_err(dotg->otg.phy->dev, + "%s: failed to set transceiver, already exists\n", + __func__); + goto err2; + } + + dwc3_otg_reset(dotg); + + dotg->otg.phy->state = OTG_STATE_UNDEFINED; + + INIT_WORK(&dotg->sm_work, dwc3_otg_sm_work); + + ret = request_irq(dotg->irq, dwc3_otg_interrupt, IRQF_SHARED, + "dwc3_otg", dotg); + if (ret) { + dev_err(dotg->otg.phy->dev, "failed to request irq #%d --> %d\n", + dotg->irq, ret); + goto err3; + } + + return 0; + +err3: + cancel_work_sync(&dotg->sm_work); + usb_set_transceiver(NULL); +err2: + kfree(dotg->otg.phy); +err1: + dwc->dotg = NULL; + kfree(dotg); + + return ret; +} + +/** + * dwc3_otg_exit + * @dwc: Pointer to out controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +void dwc3_otg_exit(struct dwc3 *dwc) +{ + struct dwc3_otg *dotg = dwc->dotg; + + /* dotg is null when GHWPARAMS6[10]=SRPSupport=0, see dwc3_otg_init */ + if (dotg) { + cancel_work_sync(&dotg->sm_work); + usb_set_transceiver(NULL); + free_irq(dotg->irq, dotg); + kfree(dotg->otg.phy); + kfree(dotg); + dwc->dotg = NULL; + } +} diff --git a/drivers/usb/dwc3/dwc3_otg.h b/drivers/usb/dwc3/dwc3_otg.h new file mode 100644 index 0000000..a048306 --- /dev/null +++ b/drivers/usb/dwc3/dwc3_otg.h @@ -0,0 +1,38 @@ +/** + * dwc3_otg.h - DesignWare USB3 DRD Controller OTG + * + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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/workqueue.h> + +#include <linux/usb/otg.h> + +/** + * struct dwc3_otg: OTG driver data. Shared by HCD and DCD. + * @otg: USB OTG Transceiver structure. + * @irq: IRQ number assigned for HSUSB controller. + * @regs: ioremapped register base address. + * @sm_work: OTG state machine work. + * @osts: last value of the OSTS register, as read from HW. + */ +struct dwc3_otg { + struct usb_otg otg; + int irq; + void __iomem *regs; + struct work_struct sm_work; + u32 osts; +}; +#endif /* __LINUX_USB_DWC3_OTG_H */ diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 58fdfad..7d51411 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -49,6 +49,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> +#include <linux/usb/otg.h> #include "core.h" #include "gadget.h" @@ -1462,12 +1463,64 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) is_on = !!is_on; spin_lock_irqsave(&dwc->lock, flags); + + dwc->softconnect = is_on; + + if ((dwc->dotg && !dwc->vbus_active) || + !dwc->gadget_driver) { + + spin_unlock_irqrestore(&dwc->lock, flags); + + /* + * Need to wait for vbus_session(on) from otg driver or to + * the udc_start. + */ + return 0; + } + ret = dwc3_gadget_run_stop(dwc, is_on); + spin_unlock_irqrestore(&dwc->lock, flags); return ret; } +static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct dwc3 *dwc = gadget_to_dwc(_gadget); + unsigned long flags; + + if (!dwc->dotg) + return -EPERM; + + is_active = !!is_active; + + spin_lock_irqsave(&dwc->lock, flags); + + /* Mark that the vbus was powered */ + dwc->vbus_active = is_active; + + /* + * Check if upper level usb_gadget_driver was already registerd with + * this udc controller driver (if dwc3_gadget_start was called) + */ + if (dwc->gadget_driver && dwc->softconnect) { + if (dwc->vbus_active) { + /* + * Both vbus was activated by otg and pullup was + * signaled by the gadget driver. + */ + dwc3_gadget_run_stop(dwc, 1); + } else { + dwc3_gadget_run_stop(dwc, 0); + } + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + static int dwc3_gadget_start(struct usb_gadget *g, struct usb_gadget_driver *driver) { @@ -1571,6 +1624,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .get_frame = dwc3_gadget_get_frame, .wakeup = dwc3_gadget_wakeup, .set_selfpowered = dwc3_gadget_set_selfpowered, + .vbus_session = dwc3_gadget_vbus_session, .pullup = dwc3_gadget_pullup, .udc_start = dwc3_gadget_start, .udc_stop = dwc3_gadget_stop, @@ -2471,6 +2525,15 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc) goto err7; } + if (dwc->dotg) { + /* dwc3 otg driver is active (DRD mode + SRPSupport=1) */ + ret = otg_set_peripheral(&dwc->dotg->otg, &dwc->gadget); + if (ret) { + dev_err(dwc->dev, "failed to set peripheral to otg\n"); + goto err7; + } + } + return 0; err7: diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 689bc18..e03b0bb 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -14,9 +14,12 @@ #include <linux/platform_device.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/usb/otg.h> #include "xhci.h" +static struct usb_phy *phy; + static void xhci_plat_quirks(struct device *dev, struct xhci_hcd *xhci) { /* @@ -149,6 +152,19 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto put_usb3_hcd; + phy = usb_get_transceiver(); + if (phy && phy->otg) { + dev_dbg(&pdev->dev, "%s otg support available\n", __func__); + hcd->driver->stop(hcd); + ret = otg_set_host(phy->otg, &hcd->self); + if (ret) { + dev_err(&pdev->dev, "%s otg_set_host failed\n", + __func__); + usb_put_transceiver(phy); + goto put_usb3_hcd; + } + } + return 0; put_usb3_hcd: @@ -182,6 +198,11 @@ static int xhci_plat_remove(struct platform_device *dev) usb_put_hcd(hcd); kfree(xhci); + if (phy && phy->otg) { + otg_set_host(phy->otg, NULL); + usb_put_transceiver(phy); + } + return 0; } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index a979cd0..287cecd 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -521,6 +521,13 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg(xhci, "xhci_run\n"); + xhci_dbg(xhci, "Calling HCD init\n"); + /* Initialize HCD and host controller data structures. */ + ret = xhci_init(hcd); + if (ret) + return ret; + xhci_dbg(xhci, "Called HCD init\n"); + ret = xhci_try_enable_msi(hcd); if (ret) return ret; @@ -4519,12 +4526,6 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); } - xhci_dbg(xhci, "Calling HCD init\n"); - /* Initialize HCD and host controller data structures. */ - retval = xhci_init(hcd); - if (retval) - goto error; - xhci_dbg(xhci, "Called HCD init\n"); return 0; error: kfree(xhci); -- 1.7.6 Consultant for Qualcomm Innovation Center, Inc. Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html