From patchwork Mon Apr 11 11:35:02 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 65502 Delivered-To: patch@linaro.org Received: by 10.112.43.237 with SMTP id z13csp1414405lbl; Mon, 11 Apr 2016 04:37:43 -0700 (PDT) X-Received: by 10.66.66.10 with SMTP id b10mr32286458pat.12.1460374663054; Mon, 11 Apr 2016 04:37:43 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id hs10si3213081pad.75.2016.04.11.04.37.42; Mon, 11 Apr 2016 04:37:43 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754352AbcDKLft (ORCPT + 29 others); Mon, 11 Apr 2016 07:35:49 -0400 Received: from comal.ext.ti.com ([198.47.26.152]:40576 "EHLO comal.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754308AbcDKLfq (ORCPT ); Mon, 11 Apr 2016 07:35:46 -0400 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id u3BBZVXe006930; Mon, 11 Apr 2016 06:35:31 -0500 Received: from DLEE71.ent.ti.com (dlee71.ent.ti.com [157.170.170.114]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id u3BBZVo5001885; Mon, 11 Apr 2016 06:35:31 -0500 Received: from dlep33.itg.ti.com (157.170.170.75) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.224.2; Mon, 11 Apr 2016 06:35:31 -0500 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep33.itg.ti.com (8.14.3/8.13.8) with ESMTP id u3BBZ7FY014612; Mon, 11 Apr 2016 06:35:28 -0500 From: Roger Quadros To: CC: , , , , , , , , , , , Roger Quadros Subject: [PATCH v6 06/10] usb: dwc3: add dual-role support Date: Mon, 11 Apr 2016 14:35:02 +0300 Message-ID: <1460374506-9779-7-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1460374506-9779-1-git-send-email-rogerq@ti.com> References: <1460374506-9779-1-git-send-email-rogerq@ti.com> MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Register with the USB OTG core. Since we don't support OTG yet we just work as a dual-role device even if device tree says "otg". Get ID and VBUS information from the OTG controller and kick the OTG state machine. Make sure dual-role functionality works across system suspend/resume. Signed-off-by: Roger Quadros --- drivers/usb/dwc3/core.c | 544 ++++++++++++++++++++++++++++++++++++++++++++-- drivers/usb/dwc3/core.h | 20 ++ drivers/usb/dwc3/gadget.c | 6 +- drivers/usb/dwc3/host.c | 2 + 4 files changed, 550 insertions(+), 22 deletions(-) -- 2.5.0 diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 1c754749..f24c091 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -57,6 +57,7 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) reg = dwc3_readl(dwc->regs, DWC3_GCTL); reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); reg |= DWC3_GCTL_PRTCAPDIR(mode); + dwc->current_mode = mode; dwc3_writel(dwc->regs, DWC3_GCTL, reg); } @@ -742,13 +743,444 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) return 0; } -static int dwc3_core_init_mode(struct dwc3 *dwc) +/* Get OTG events and sync it to OTG fsm */ +static void dwc3_otg_fsm_sync(struct dwc3 *dwc) +{ + u32 reg; + int id, vbus; + + /* + * calling usb_otg_sync_inputs() during resume breaks host + * if adapter was removed during suspend as xhci driver + * is not prepared to see hcd removal before xhci_resume. + */ + if (dwc->otg_prevent_sync) + return; + + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + dwc3_trace(trace_dwc3_core, "otgstatus 0x%x\n", reg); + + id = !!(reg & DWC3_OSTS_CONIDSTS); + vbus = !!(reg & DWC3_OSTS_BSESVLD); + + dwc3_trace(trace_dwc3_core, "id %d vbus %d\n", id, vbus); + dwc->otg->fsm.id = id; + dwc->otg->fsm.b_sess_vld = vbus; + usb_otg_sync_inputs(dwc->otg); +} + +static void dwc3_otg_mask_irq(struct dwc3 *dwc) +{ + dwc->oevten = dwc3_readl(dwc->regs, DWC3_OEVTEN); + dwc3_writel(dwc->regs, DWC3_OEVTEN, 0); +} + +static void dwc3_otg_unmask_irq(struct dwc3 *dwc) +{ + dwc3_writel(dwc->regs, DWC3_OEVTEN, dwc->oevten); +} + +static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) +{ + dwc->oevten &= ~(disable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, dwc->oevten); +} + +static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask) +{ + dwc->oevten |= (enable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, dwc->oevten); +} + +#define DWC3_OTG_ALL_EVENTS (DWC3_OEVTEN_XHCIRUNSTPSETEN | \ + DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \ + DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \ + DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \ + DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \ + DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \ + DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVHOSTENDEN | \ + DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \ + DWC3_OEVTEN_BDEVVBUSCHNGE) + +static int dwc3_drd_start_host(struct usb_otg *otg, int on); +static int dwc3_drd_start_gadget(struct usb_otg *otg, int on); +static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + /* + * this bit is needed for otg-host to work after system suspend/resume + */ + if ((dwc->otg->state == OTG_STATE_A_HOST) && + !(dwc->oevt & DWC3_OEVT_DEVICEMODE)) { + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_drd_start_host(dwc->otg, true); + spin_lock_irqsave(&dwc->lock, flags); + } + + dwc3_otg_fsm_sync(dwc); + dwc3_otg_unmask_irq(dwc); + + dwc->oevt = 0; + spin_unlock_irqrestore(&dwc->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t dwc3_otg_irq(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + irqreturn_t ret = IRQ_NONE; + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_OEVT); + if (reg) { + dwc->oevt = reg; + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + dwc3_otg_mask_irq(dwc); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +/* --------------------- Dual-Role management ------------------------------- */ +static void dwc3_otgregs_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Prevent host/device reset from resetting OTG core. + * If we don't do this then xhci_reset (USBCMD.HCRST) will reset + * the signal outputs sent to the PHY, the OTG FSM logic of the + * core and also the resets to the VBUS filters inside the core. + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + /* Disable hibernation for simplicity */ + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_GBLHIBERNATIONEN; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + /* + * Initialize OTG registers as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + /* OEVT = FFFF */ + dwc3_writel(dwc->regs, DWC3_OEVT, ~0); + /* OEVTEN = 0 */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */ + dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* + * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0, + * OCTL.HNPReq = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN | + DWC3_OCTL_HNPREQ); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); +} + +static int dwc3_drd_start_host(struct usb_otg *otg, int on) +{ + struct dwc3 *dwc = dev_get_drvdata(otg->dev); + u32 reg; + unsigned long flags; + + dwc3_trace(trace_dwc3_core, "%s: %d\n", __func__, on); + + /* switch OTG core */ + if (on) { + /* As per Figure 11-10 A-Device Flow Diagram */ + + spin_lock_irqsave(&dwc->lock, flags); + /* OCFG.HNPCap = 0, OCFG.SRPCap = 0 */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + /* + * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0, + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + + /* + * OCFG.DisPrtPwrCutoff = 0/1 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~DWC3_OCFG_DISPWRCUTTOFF; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + + /* start the xHCI host driver */ + spin_unlock_irqrestore(&dwc->lock, flags); + usb_otg_start_host(otg, true); + spin_lock_irqsave(&dwc->lock, flags); + + /* + * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP + * We don't want SRP/HNP for simple dual-role so leave + * these disabled. + */ + + /* + * OEVTEN.OTGADevHostEvntEn = 1 + * OEVTEN.OTGADevSessEndDetEvntEn = 1 + * We don't want HNP/role-swap so leave these disabled. + */ + + /* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + + /* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PRTPWRCTL; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + spin_unlock_irqrestore(&dwc->lock, flags); + } else { + /* + * Exit from A-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* stop the HCD */ + usb_otg_start_host(otg, false); + + spin_lock_irqsave(&dwc->lock, flags); + /* + * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0 + * OEVTEN.OTGADevSessEndDetEvntEn=0, + * OEVTEN.OTGADevHostEvntEn = 0 + * But we don't disable any OTG events + */ + + /* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + + /* Initialize OTG registers */ + dwc3_otgregs_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + return 0; +} + +static int dwc3_drd_start_gadget(struct usb_otg *otg, int on) +{ + struct dwc3 *dwc = dev_get_drvdata(otg->dev); + u32 reg; + unsigned long flags; + + dwc3_trace(trace_dwc3_core, "%s: %d\n", __func__, on); + if (on) + dwc3_event_buffers_setup(dwc); + + if (on) { + /* As per Figure 11-20 B-Device Flow Diagram */ + + spin_lock_irqsave(&dwc->lock, flags); + /* + * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1 + * but we set them to 0 for simple dual-role operation. + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP); + /* OCFG.OTGSftRstMsk = 0/1 */ + reg |= DWC3_OCFG_SFTRSTMASK; + dwc3_writel(dwc->regs, DWC3_OCFG, reg); + /* + * OCTL.PeriMode = 1 + * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0 + * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg |= DWC3_OCTL_PERIMODE; + reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ | + DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN); + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + /* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */ + dwc3_otg_enable_events(dwc, DWC3_OEVT_BDEVSESSVLDDET); + /* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */ + if (!dwc->dis_u2_susphy_quirk) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + /* GCTL.GblHibernationEn = 0 */ + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_GBLHIBERNATIONEN; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + spin_unlock_irqrestore(&dwc->lock, flags); + + /* start the Peripheral driver */ + usb_otg_start_gadget(otg, true); + } else { + /* + * Exit from B-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* stop the Peripheral driver */ + usb_otg_start_gadget(otg, false); + + spin_lock_irqsave(&dwc->lock, flags); + + /* + * OEVTEN.OTGBDevHNPChngEvntEn = 0 + * OEVTEN.OTGBDevVBusChngEvntEn = 0 + * OEVTEN.OTGBDevBHostEndEvntEn = 0 + */ + reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); + reg &= ~(DWC3_OEVT_BDEVHNPCHNG | DWC3_OEVT_BDEVVBUSCHNG | + DWC3_OEVT_BDEVBHOSTEND); + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); + + /* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */ + reg = dwc3_readl(dwc->regs, DWC3_OCTL); + reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ); + reg |= DWC3_OCTL_PERIMODE; + dwc3_writel(dwc->regs, DWC3_OCTL, reg); + + /* Initialize OTG registers */ + dwc3_otgregs_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + return 0; +} + +static struct otg_fsm_ops dwc3_drd_ops = { + .start_host = dwc3_drd_start_host, + .start_gadget = dwc3_drd_start_gadget, +}; + +static int dwc3_drd_register(struct dwc3 *dwc) +{ + int ret; + + /* register parent as DRD device with OTG core */ + dwc->otg = usb_otg_register(dwc->dev, &dwc->otg_config); + if (IS_ERR(dwc->otg)) { + ret = PTR_ERR(dwc->otg); + if (ret == -ENOTSUPP) + dev_err(dwc->dev, "CONFIG_USB_OTG needed for dual-role\n"); + else + dev_err(dwc->dev, "Failed to register with OTG core\n"); + + return ret; + } + + return 0; +} + +static int dwc3_drd_init(struct dwc3 *dwc) { - struct device *dev = dwc->dev; int ret; + struct usb_otg_caps *otgcaps = &dwc->otg_caps; + u32 reg; + unsigned long flags; struct resource *res; struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + dwc->otg_irq = platform_get_irq_byname(dwc3_pdev, "otg"); + if (dwc->otg_irq <= 0) { + dwc->otg_irq = platform_get_irq_byname(dwc3_pdev, + "dwc_usb3"); + if (dwc->otg_irq <= 0) { + res = platform_get_resource(dwc3_pdev, + IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dwc->dev, "missing otg IRQ\n"); + return -ENODEV; + } + dwc->otg_irq = res->start; + } + } + + otgcaps->otg_rev = 0; + otgcaps->hnp_support = false; + otgcaps->srp_support = false; + otgcaps->adp_support = false; + dwc->otg_config.fsm_ops = &dwc3_drd_ops; + dwc->otg_config.otg_caps = otgcaps; + + ret = dwc3_drd_register(dwc); + if (ret) + return ret; + + /* disable all otg irqs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_writel(dwc->regs, DWC3_OEVT, ~0); + + ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq, + dwc3_otg_thread_irq, + IRQF_SHARED, "dwc3-otg", dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + dwc->otg_irq, ret); + ret = -ENODEV; + goto error; + } + + spin_lock_irqsave(&dwc->lock, flags); + + /* + * As per Figure 11-4 OTG Driver Overall Programming Flow, + * block "Initialize GCTL for OTG operation". + */ + /* GCTL.PrtCapDir=2'b11 */ + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + /* GUSB2PHYCFG0.SusPHY=0 */ + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + /* Initialize OTG registers */ + dwc3_otgregs_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + dwc3_otg_fsm_sync(dwc); + + return 0; + +error: + usb_otg_unregister(dwc->dev); + + return ret; +} + +static void dwc3_drd_exit(struct dwc3 *dwc) +{ + usb_otg_unregister(dwc->dev); +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_core_init_mode(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + int ret; + switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); @@ -767,30 +1199,32 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: - dwc->otg_irq = platform_get_irq_byname(dwc3_pdev, "otg"); - if (dwc->otg_irq <= 0) { - dwc->otg_irq = platform_get_irq_byname(dwc3_pdev, - "dwc_usb3"); - if (dwc->otg_irq <= 0) { - res = platform_get_resource(dwc3_pdev, - IORESOURCE_IRQ, 0); - if (!res) { - dev_err(dwc->dev, "missing otg IRQ\n"); - return -ENODEV; - } - dwc->otg_irq = res->start; + ret = dwc3_drd_init(dwc); + if (ret) { + dev_err(dev, + "limiting to peripheral only as dual-role init failed: %d", + ret); + dwc->dr_mode = USB_DR_MODE_PERIPHERAL; + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + return ret; } } - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + ret = dwc3_host_init(dwc); if (ret) { dev_err(dev, "failed to initialize host\n"); + dwc3_drd_exit(dwc); return ret; } ret = dwc3_gadget_init(dwc); if (ret) { dev_err(dev, "failed to initialize gadget\n"); + dwc3_host_exit(dwc); + dwc3_drd_exit(dwc); return ret; } break; @@ -814,6 +1248,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) case USB_DR_MODE_OTG: dwc3_host_exit(dwc); dwc3_gadget_exit(dwc); + dwc3_drd_exit(dwc); break; default: /* do nothing */ @@ -1142,6 +1577,30 @@ static int dwc3_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static int dwc3_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->otg_prevent_sync = true; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static void dwc3_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->otg_prevent_sync = false; + spin_unlock_irqrestore(&dwc->lock, flags); + if (dwc->dr_mode == USB_DR_MODE_OTG) + dwc3_otg_fsm_sync(dwc); +} + static int dwc3_suspend(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); @@ -1149,18 +1608,40 @@ static int dwc3_suspend(struct device *dev) spin_lock_irqsave(&dwc->lock, flags); + /* Save OTG state only if we're really using it */ + if (dwc->current_mode == DWC3_GCTL_PRTCAP_OTG) { + dwc->ocfg = dwc3_readl(dwc->regs, DWC3_OCFG); + dwc->octl = dwc3_readl(dwc->regs, DWC3_OCTL); + dwc3_otg_mask_irq(dwc); + } + + dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL); + switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: dwc3_gadget_suspend(dwc); - /* FALLTHROUGH */ + break; + case USB_DR_MODE_OTG: + dwc->otg_protocol = dwc->otg->fsm.protocol; + + switch (dwc->otg->fsm.protocol) { + case PROTO_GADGET: + dwc3_gadget_suspend(dwc); + break; + case PROTO_HOST: + case PROTO_UNDEF: + default: + /* nothing */ + break; + } case USB_DR_MODE_HOST: + case USB_DR_MODE_UNKNOWN: default: - dwc3_event_buffers_cleanup(dwc); + /* nothing */ break; } - dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL); + dwc3_event_buffers_cleanup(dwc); spin_unlock_irqrestore(&dwc->lock, flags); usb_phy_shutdown(dwc->usb3_phy); @@ -1198,15 +1679,34 @@ static int dwc3_resume(struct device *dev) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: dwc3_gadget_resume(dwc); - /* FALLTHROUGH */ + break; + case USB_DR_MODE_OTG: + switch (dwc->otg_protocol) { + case PROTO_GADGET: + dwc3_gadget_resume(dwc); + break; + case PROTO_HOST: + break; + case PROTO_UNDEF: + default: + /* nothing */ + break; + } case USB_DR_MODE_HOST: + case USB_DR_MODE_UNKNOWN: default: /* do nothing */ break; } + /* Restore OTG state only if we're really using it */ + if (dwc->current_mode == DWC3_GCTL_PRTCAP_OTG) { + dwc3_writel(dwc->regs, DWC3_OCFG, dwc->ocfg); + dwc3_writel(dwc->regs, DWC3_OCTL, dwc->octl); + dwc3_otg_unmask_irq(dwc); + } + spin_unlock_irqrestore(&dwc->lock, flags); pm_runtime_disable(dev); @@ -1223,6 +1723,8 @@ err_usb2phy_init: static const struct dev_pm_ops dwc3_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) + .prepare = dwc3_prepare, + .complete = dwc3_complete, }; #define DWC3_PM_OPS &(dwc3_dev_pm_ops) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 79422dd..c9f83c5 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -754,6 +755,12 @@ struct dwc3_scratchpad_array { * @maximum_speed: maximum speed requested (mainly for testing purposes) * @revision: revision register contents * @dr_mode: requested mode of operation + * @otg: usb otg data structure + * @otg_config: otg controller configuration + * @otg_prevent_sync: flag to block events to otg fsm + * @otg_protocol: saved copy of otg state during suspend + * @current_mode: current mode of operation written to PRTCAPDIR + * @oevt: cached OEVT register during OTG irq * @gadget_irq: IRQ number for Peripheral IRQs * @otg_irq: IRQ number for OTG IRQs * @usb2_phy: pointer to USB2 PHY @@ -763,6 +770,9 @@ struct dwc3_scratchpad_array { * @ulpi: pointer to ulpi interface * @dcfg: saved contents of DCFG register * @gctl: saved contents of GCTL register + * @ocfg: saved contents of OCFG register + * @octl: saved contents of OCTL register + * @oevten: saved contents of OEVTEN register * @isoch_delay: wValue from Set Isochronous Delay request; * @u2sel: parameter from Set SEL request. * @u2pel: parameter from Set SEL request. @@ -858,6 +868,13 @@ struct dwc3 { size_t regs_size; enum usb_dr_mode dr_mode; + struct usb_otg *otg; + struct usb_otg_caps otg_caps; + struct usb_otg_config otg_config; + bool otg_prevent_sync; + int otg_protocol; + u32 current_mode; + u32 oevt; int gadget_irq; int otg_irq; @@ -865,6 +882,9 @@ struct dwc3 { /* used for suspend/resume */ u32 dcfg; u32 gctl; + u32 ocfg; + u32 octl; + u32 oevten; u32 nr_scratch; u32 num_event_buffers; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 90f514c..83d5c57 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2894,7 +2894,11 @@ int dwc3_gadget_init(struct dwc3 *dwc) if (ret) goto err5; - ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); + if (dwc->dr_mode == USB_DR_MODE_OTG) + ret = usb_otg_add_gadget_udc(dwc->dev, &dwc->gadget, dwc->dev); + else + ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); + if (ret) { dev_err(dwc->dev, "failed to register udc\n"); goto err5; diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index f2b60a4..89339d1 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -69,6 +69,8 @@ int dwc3_host_init(struct dwc3 *dwc) memset(&pdata, 0, sizeof(pdata)); pdata.usb3_lpm_capable = dwc->usb3_lpm_capable; + if (dwc->dr_mode == USB_DR_MODE_OTG) + pdata.otg_dev = dwc->dev; ret = platform_device_add_data(xhci, &pdata, sizeof(pdata)); if (ret) {