From patchwork Thu Feb 16 13:06:14 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 94069 Delivered-To: patch@linaro.org Received: by 10.140.20.99 with SMTP id 90csp2495904qgi; Thu, 16 Feb 2017 05:07:16 -0800 (PST) X-Received: by 10.98.144.218 with SMTP id q87mr2527082pfk.51.1487250436148; Thu, 16 Feb 2017 05:07:16 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h1si6918548pli.197.2017.02.16.05.07.15; Thu, 16 Feb 2017 05:07:16 -0800 (PST) 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; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932188AbdBPNHJ (ORCPT + 25 others); Thu, 16 Feb 2017 08:07:09 -0500 Received: from fllnx209.ext.ti.com ([198.47.19.16]:13404 "EHLO fllnx209.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754750AbdBPNHG (ORCPT ); Thu, 16 Feb 2017 08:07:06 -0500 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by fllnx209.ext.ti.com (8.15.1/8.15.1) with ESMTP id v1GD6Sbp025213; Thu, 16 Feb 2017 07:06:28 -0600 Received: from DFLE72.ent.ti.com (dfle72.ent.ti.com [128.247.5.109]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6Nn2019607; Thu, 16 Feb 2017 07:06:23 -0600 Received: from dlep32.itg.ti.com (157.170.170.100) by DFLE72.ent.ti.com (128.247.5.109) with Microsoft SMTP Server id 14.3.294.0; Thu, 16 Feb 2017 07:06:23 -0600 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6JN6006214; Thu, 16 Feb 2017 07:06:21 -0600 From: Roger Quadros To: CC: , , , Roger Quadros Subject: [PATCH v2 1/4] usb: dwc3: core.h: add some register definitions Date: Thu, 16 Feb 2017 15:06:14 +0200 Message-ID: <1487250377-13653-2-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1487250377-13653-1-git-send-email-rogerq@ti.com> References: <1487250377-13653-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 Add OTG and GHWPARAMS6 register definitions Signed-off-by: Roger Quadros --- drivers/usb/dwc3/core.h | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) -- 2.7.4 diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index d514dca..fc82d2e 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -204,6 +204,15 @@ /* Global User Control 1 Register */ #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +/* Global Status Register */ +#define DWC3_GSTS_OTG_IP BIT(10) +#define DWC3_GSTS_BC_IP BIT(9) +#define DWC3_GSTS_ADP_IP BIT(8) +#define DWC3_GSTS_HOST_IP BIT(7) +#define DWC3_GSTS_DEVICE_IP BIT(6) +#define DWC3_GSTS_CSR_TIMEOUT BIT(5) +#define DWC3_GSTS_BUS_ERR_ADDR_VLD BIT(4) + /* Global USB2 PHY Configuration Register */ #define DWC3_GUSB2PHYCFG_PHYSOFTRST BIT(31) #define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS BIT(30) @@ -288,6 +297,11 @@ #define DWC3_MAX_HIBER_SCRATCHBUFS 15 /* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_BCSUPPORT BIT(14) +#define DWC3_GHWPARAMS6_OTG3SUPPORT BIT(13) +#define DWC3_GHWPARAMS6_ADPSUPPORT BIT(12) +#define DWC3_GHWPARAMS6_HNPSUPPORT BIT(11) +#define DWC3_GHWPARAMS6_SRPSUPPORT BIT(10) #define DWC3_GHWPARAMS6_EN_FPGA BIT(7) /* Global HWPARAMS7 Register */ @@ -469,6 +483,74 @@ #define DWC3_DEV_IMOD_INTERVAL_SHIFT 0 #define DWC3_DEV_IMOD_INTERVAL_MASK (0xffff << 0) +/* OTG Configuration Register */ +#define DWC3_OCFG_DISPWRCUTTOFF BIT(5) +#define DWC3_OCFG_HIBDISMASK BIT(4) +#define DWC3_OCFG_SFTRSTMASK BIT(3) +#define DWC3_OCFG_OTGVERSION BIT(2) +#define DWC3_OCFG_HNPCAP BIT(1) +#define DWC3_OCFG_SRPCAP BIT(0) + +/* OTG CTL Register */ +#define DWC3_OCTL_OTG3GOERR BIT(7) +#define DWC3_OCTL_PERIMODE BIT(6) +#define DWC3_OCTL_PRTPWRCTL BIT(5) +#define DWC3_OCTL_HNPREQ BIT(4) +#define DWC3_OCTL_SESREQ BIT(3) +#define DWC3_OCTL_TERMSELIDPULSE BIT(2) +#define DWC3_OCTL_DEVSETHNPEN BIT(1) +#define DWC3_OCTL_HSTSETHNPEN BIT(0) + +/* OTG Event Register */ +#define DWC3_OEVT_DEVICEMODE BIT(31) +#define DWC3_OEVT_XHCIRUNSTPSET BIT(27) +#define DWC3_OEVT_DEVRUNSTPSET BIT(26) +#define DWC3_OEVT_HIBENTRY BIT(25) +#define DWC3_OEVT_CONIDSTSCHNG BIT(24) +#define DWC3_OEVT_HRRCONFNOTIF BIT(23) +#define DWC3_OEVT_HRRINITNOTIF BIT(22) +#define DWC3_OEVT_ADEVIDLE BIT(21) +#define DWC3_OEVT_ADEVBHOSTEND BIT(20) +#define DWC3_OEVT_ADEVHOST BIT(19) +#define DWC3_OEVT_ADEVHNPCHNG BIT(18) +#define DWC3_OEVT_ADEVSRPDET BIT(17) +#define DWC3_OEVT_ADEVSESSENDDET BIT(16) +#define DWC3_OEVT_BDEVBHOSTEND BIT(11) +#define DWC3_OEVT_BDEVHNPCHNG BIT(10) +#define DWC3_OEVT_BDEVSESSVLDDET BIT(9) +#define DWC3_OEVT_BDEVVBUSCHNG BIT(8) +#define DWC3_OEVT_BSESSVLD BIT(3) +#define DWC3_OEVT_HSTNEGSTS BIT(2) +#define DWC3_OEVT_SESREQSTS BIT(1) +#define DWC3_OEVT_ERROR BIT(0) + +/* OTG Event Enable Register */ +#define DWC3_OEVTEN_XHCIRUNSTPSETEN BIT(27) +#define DWC3_OEVTEN_DEVRUNSTPSETEN BIT(26) +#define DWC3_OEVTEN_HIBENTRYEN BIT(25) +#define DWC3_OEVTEN_CONIDSTSCHNGEN BIT(24) +#define DWC3_OEVTEN_HRRCONFNOTIFEN BIT(23) +#define DWC3_OEVTEN_HRRINITNOTIFEN BIT(22) +#define DWC3_OEVTEN_ADEVIDLEEN BIT(21) +#define DWC3_OEVTEN_ADEVBHOSTENDEN BIT(20) +#define DWC3_OEVTEN_ADEVHOSTEN BIT(19) +#define DWC3_OEVTEN_ADEVHNPCHNGEN BIT(18) +#define DWC3_OEVTEN_ADEVSRPDETEN BIT(17) +#define DWC3_OEVTEN_ADEVSESSENDDETEN BIT(16) +#define DWC3_OEVTEN_BDEVHOSTENDEN BIT(11) +#define DWC3_OEVTEN_BDEVHNPCHNGEN BIT(10) +#define DWC3_OEVTEN_BDEVSESSVLDDETEN BIT(9) +#define DWC3_OEVTEN_BDEVVBUSCHNGE BIT(8) + +/* OTG Status Register */ +#define DWC3_OSTS_DEVRUNSTP BIT(13) +#define DWC3_OSTS_XHCIRUNSTP BIT(12) +#define DWC3_OSTS_PERIPHERALSTATE BIT(4) +#define DWC3_OSTS_XHCIPRTPOWER BIT(3) +#define DWC3_OSTS_BSESVLD BIT(2) +#define DWC3_OSTS_VBUSVLD BIT(1) +#define DWC3_OSTS_CONIDSTS BIT(0) + /* Structures */ struct dwc3_trb; From patchwork Thu Feb 16 13:06:15 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 94067 Delivered-To: patch@linaro.org Received: by 10.140.20.99 with SMTP id 90csp2495718qgi; Thu, 16 Feb 2017 05:06:50 -0800 (PST) X-Received: by 10.98.62.153 with SMTP id y25mr2552171pfj.162.1487250410906; Thu, 16 Feb 2017 05:06:50 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y71si6938081plh.62.2017.02.16.05.06.50; Thu, 16 Feb 2017 05:06:50 -0800 (PST) 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; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932162AbdBPNGk (ORCPT + 25 others); Thu, 16 Feb 2017 08:06:40 -0500 Received: from lelnx193.ext.ti.com ([198.47.27.77]:29400 "EHLO lelnx193.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932096AbdBPNGg (ORCPT ); Thu, 16 Feb 2017 08:06:36 -0500 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by lelnx193.ext.ti.com (8.15.1/8.15.1) with ESMTP id v1GD6PR6013517; Thu, 16 Feb 2017 07:06:25 -0600 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6PdZ030257; Thu, 16 Feb 2017 07:06:25 -0600 Received: from dlep32.itg.ti.com (157.170.170.100) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.3.294.0; Thu, 16 Feb 2017 07:06:24 -0600 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6JN7006214; Thu, 16 Feb 2017 07:06:23 -0600 From: Roger Quadros To: CC: , , , Roger Quadros Subject: [PATCH v2 2/4] usb: dwc3: omap: don't miss events during suspend/resume Date: Thu, 16 Feb 2017 15:06:15 +0200 Message-ID: <1487250377-13653-3-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1487250377-13653-1-git-send-email-rogerq@ti.com> References: <1487250377-13653-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 The USB cable state can change during suspend/resume so be sure to check and update the extcon state. Signed-off-by: Roger Quadros --- drivers/usb/dwc3/dwc3-omap.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) -- 2.7.4 diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index fe4efb8..a7de60e 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -598,9 +598,25 @@ static int dwc3_omap_resume(struct device *dev) return 0; } +static void dwc3_omap_complete(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + if (extcon_get_state(omap->edev, EXTCON_USB)) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF); + + if (extcon_get_state(omap->edev, EXTCON_USB_HOST)) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT); +} + static const struct dev_pm_ops dwc3_omap_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_omap_suspend, dwc3_omap_resume) + .complete = dwc3_omap_complete, }; #define DEV_PM_OPS (&dwc3_omap_dev_pm_ops) From patchwork Thu Feb 16 13:06:16 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 94066 Delivered-To: patch@linaro.org Received: by 10.140.20.99 with SMTP id 90csp2495606qgi; Thu, 16 Feb 2017 05:06:38 -0800 (PST) X-Received: by 10.99.144.201 with SMTP id a192mr2724157pge.187.1487250398579; Thu, 16 Feb 2017 05:06:38 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 62si6930167ply.77.2017.02.16.05.06.38; Thu, 16 Feb 2017 05:06:38 -0800 (PST) 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; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932100AbdBPNGg (ORCPT + 25 others); Thu, 16 Feb 2017 08:06:36 -0500 Received: from lelnx193.ext.ti.com ([198.47.27.77]:29403 "EHLO lelnx193.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754475AbdBPNGd (ORCPT ); Thu, 16 Feb 2017 08:06:33 -0500 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by lelnx193.ext.ti.com (8.15.1/8.15.1) with ESMTP id v1GD6RiW013528; Thu, 16 Feb 2017 07:06:27 -0600 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6RmI030276; Thu, 16 Feb 2017 07:06:27 -0600 Received: from dlep32.itg.ti.com (157.170.170.100) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.3.294.0; Thu, 16 Feb 2017 07:06:27 -0600 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6JN8006214; Thu, 16 Feb 2017 07:06:25 -0600 From: Roger Quadros To: CC: , , , Roger Quadros Subject: [PATCH v2 3/4] usb: dwc3: add dual-role support Date: Thu, 16 Feb 2017 15:06:16 +0200 Message-ID: <1487250377-13653-4-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1487250377-13653-1-git-send-email-rogerq@ti.com> References: <1487250377-13653-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 If dr_mode is "otg" then support dual role mode of operation. Get ID and VBUS information from the OTG controller and put the controller in the appropriate state. This is our dual-role state table. ID VBUS dual-role state -- ---- --------------- 0 x A_HOST - Host controller active 1 0 B_IDLE - Both Host and Gadget controllers inactive 1 1 B_PERIPHERAL - Gadget controller active Calling dwc3_otg_fsm_sync() directly from dwc3_complete() can lock up the system at xHCI add/remove so we use a work queue for it. Signed-off-by: Roger Quadros --- drivers/usb/dwc3/core.c | 595 ++++++++++++++++++++++++++++++++++++++++++++-- drivers/usb/dwc3/core.h | 44 +++- drivers/usb/dwc3/gadget.c | 18 +- 3 files changed, 633 insertions(+), 24 deletions(-) -- 2.7.4 diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 369bab1..619fa7c 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,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); } @@ -839,6 +841,505 @@ static int dwc3_core_get_phy(struct dwc3 *dwc) return 0; } +static int dwc3_drd_start_host(struct dwc3 *dwc, int on, bool skip); +static int dwc3_drd_start_gadget(struct dwc3 *dwc, int on); + +/* dwc->lock must be held */ +static void dwc3_drd_statemachine(struct dwc3 *dwc, int id, int vbus) +{ + enum usb_otg_state new_state; + int protocol; + + if (id == dwc->otg_fsm.id && vbus == dwc->otg_fsm.b_sess_vld) + return; + + dwc->otg_fsm.id = id; + dwc->otg_fsm.b_sess_vld = vbus; + + if (!id) { + new_state = OTG_STATE_A_HOST; + } else{ + if (vbus) + new_state = OTG_STATE_B_PERIPHERAL; + else + new_state = OTG_STATE_B_IDLE; + } + + if (dwc->otg.state == new_state) + return; + + protocol = dwc->otg_fsm.protocol; + switch (new_state) { + case OTG_STATE_B_IDLE: + if (protocol == PROTO_GADGET) + dwc3_drd_start_gadget(dwc, 0); + else if (protocol == PROTO_HOST) + dwc3_drd_start_host(dwc, 0, 0); + dwc->otg_fsm.protocol = PROTO_UNDEF; + break; + case OTG_STATE_B_PERIPHERAL: + if (protocol == PROTO_HOST) + dwc3_drd_start_host(dwc, 0, 0); + + if (protocol != PROTO_GADGET) { + dwc->otg_fsm.protocol = PROTO_GADGET; + dwc3_drd_start_gadget(dwc, 1); + } + break; + case OTG_STATE_A_HOST: + if (protocol == PROTO_GADGET) + dwc3_drd_start_gadget(dwc, 0); + + if (protocol != PROTO_HOST) { + dwc->otg_fsm.protocol = PROTO_HOST; + dwc3_drd_start_host(dwc, 1, 0); + } + break; + default: + dev_err(dwc->dev, "drd: invalid usb-drd state: %s\n", + usb_otg_state_string(new_state)); + return; + } + + dwc->otg.state = new_state; +} + +/* dwc->lock must be held */ +static void dwc3_otg_fsm_sync(struct dwc3 *dwc) +{ + u32 reg; + int id, vbus; + + /* + * calling dwc3_otg_fsm_sync() 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); + id = !!(reg & DWC3_OSTS_CONIDSTS); + vbus = !!(reg & DWC3_OSTS_BSESVLD); + dwc3_drd_statemachine(dwc, id, vbus); +} + +static void dwc3_drd_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, + otg_work); + + spin_lock(&dwc->lock); + dwc3_otg_fsm_sync(dwc); + spin_unlock(&dwc->lock); +} + +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 irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + + spin_lock(&dwc->lock); + if (dwc->otg_needs_host_start) { + dwc3_drd_start_host(dwc, true, 1); + dwc->otg_needs_host_start = 0; + } + + dwc3_otg_fsm_sync(dwc); + spin_unlock(&dwc->lock); + + return IRQ_HANDLED; +} + +static irqreturn_t dwc3_otg_irq(int irq, void *_dwc) +{ + u32 reg; + struct dwc3 *dwc = _dwc; + irqreturn_t ret = IRQ_NONE; + + reg = dwc3_readl(dwc->regs, DWC3_OEVT); + if (reg) { + if ((dwc->otg_fsm.protocol == PROTO_HOST) && + !(reg & DWC3_OEVT_DEVICEMODE)) + dwc->otg_needs_host_start = 1; + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + 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); +} + +/* dwc->lock must be held */ +static int dwc3_drd_start_host(struct dwc3 *dwc, int on, bool skip) +{ + u32 reg; + + /* switch OTG core */ + if (on) { + /* As per Figure 11-10 A-Device Flow Diagram */ + /* 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 */ + if (!skip) { + spin_unlock(&dwc->lock); + dwc3_host_init(dwc); + spin_lock(&dwc->lock); + } + + /* + * 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); + } else { + /* + * Exit from A-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* stop the HCD */ + if (!skip) { + spin_unlock(&dwc->lock); + dwc3_host_exit(dwc); + spin_lock(&dwc->lock); + } + + /* + * 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); + } + + return 0; +} + +/* dwc->lock must be held */ +static int dwc3_drd_start_gadget(struct dwc3 *dwc, int on) +{ + u32 reg; + + if (on) + dwc3_event_buffers_setup(dwc); + + if (on) { + /* As per Figure 11-20 B-Device Flow Diagram */ + + /* + * 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); + + /* start the Peripheral driver */ + if (dwc->gadget_driver) { + __dwc3_gadget_start(dwc); + if (dwc->gadget_pullup) + dwc3_gadget_run_stop(dwc, true, false); + } + } else { + /* + * Exit from B-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + /* stop the Peripheral driver */ + if (dwc->gadget_driver) { + if (dwc->gadget_pullup) + dwc3_gadget_run_stop(dwc, false, false); + spin_unlock(&dwc->lock); + if (dwc->gadget_driver->disconnect) + dwc->gadget_driver->disconnect(&dwc->gadget); + spin_lock(&dwc->lock); + __dwc3_gadget_stop(dwc); + } + + /* + * 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); + } + + return 0; +} + +static int dwc3_otg_get_irq(struct dwc3 *dwc) +{ + struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); + int irq; + + irq = platform_get_irq_byname(dwc3_pdev, "otg"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq_byname(dwc3_pdev, "dwc_usb3"); + if (irq > 0) + goto out; + + if (irq == -EPROBE_DEFER) + goto out; + + irq = platform_get_irq(dwc3_pdev, 0); + if (irq > 0) + goto out; + + if (irq != -EPROBE_DEFER) + dev_err(dwc->dev, "missing otg IRQ\n"); + + if (!irq) + irq = -EINVAL; + +out: + return irq; +} + +/* dwc->lock must be held */ +static void dwc3_otg_core_init(struct dwc3 *dwc) +{ + u32 reg; + + /* + * 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); + + /* force drd state machine update the first time */ + dwc->otg_fsm.b_sess_vld = -1; + dwc->otg_fsm.id = -1; +} + +/* dwc->lock must be held */ +static void dwc3_otg_core_exit(struct dwc3 *dwc) +{ + /* disable all otg irqs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_writel(dwc->regs, DWC3_OEVT, ~0); +} + +static int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret, irq; + unsigned long flags; + + INIT_WORK(&dwc->otg_work, dwc3_drd_work); + + irq = dwc3_otg_get_irq(dwc); + if (irq < 0) + return irq; + + dwc->otg_irq = irq; + + /* disable all otg irqs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_writel(dwc->regs, DWC3_OEVT, ~0); + + irq_set_status_flags(dwc->otg_irq, IRQ_NOAUTOEN); + 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; + return ret; + } + + ret = dwc3_gadget_init(dwc); + if (ret) { + free_irq(dwc->otg_irq, dwc); + return ret; + } + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_core_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + queue_work(system_power_efficient_wq, &dwc->otg_work); + + return 0; +} + +static void dwc3_drd_exit(struct dwc3 *dwc) +{ + unsigned long flags; + + cancel_work_sync(&dwc->otg_work); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_core_exit(dwc); + if (dwc->otg_fsm.protocol == PROTO_HOST) + dwc3_drd_start_host(dwc, 0, 0); + dwc->otg_fsm.protocol = PROTO_UNDEF; + free_irq(dwc->otg_irq, dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + dwc3_gadget_exit(dwc); +} + +/* -------------------------------------------------------------------------- */ + static int dwc3_core_init_mode(struct dwc3 *dwc) { struct device *dev = dwc->dev; @@ -862,17 +1363,10 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: - ret = dwc3_host_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize host\n"); - return ret; - } - - ret = dwc3_gadget_init(dwc); + ret = dwc3_drd_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize gadget\n"); + dev_err(dev, "failed to initialize dual-role\n"); return ret; } break; @@ -894,8 +1388,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_host_exit(dwc); break; case USB_DR_MODE_OTG: - dwc3_host_exit(dwc); - dwc3_gadget_exit(dwc); + dwc3_drd_exit(dwc); break; default: /* do nothing */ @@ -1207,19 +1700,35 @@ static int dwc3_suspend_common(struct dwc3 *dwc) { unsigned long flags; + spin_lock_irqsave(&dwc->lock, flags); + switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: - spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_suspend(dwc); - spin_unlock_irqrestore(&dwc->lock, flags); + break; + case USB_DR_MODE_OTG: + switch (dwc->otg_fsm.protocol) { + case PROTO_GADGET: + dwc3_gadget_suspend(dwc); + break; + case PROTO_HOST: + case PROTO_UNDEF: + default: + /* nothing */ + break; + } break; case USB_DR_MODE_HOST: + case USB_DR_MODE_UNKNOWN: default: /* do nothing */ break; } + if (dwc->dr_mode == USB_DR_MODE_OTG) + dwc3_otg_core_exit(dwc); + + spin_unlock_irqrestore(&dwc->lock, flags); dwc3_core_exit(dwc); return 0; @@ -1234,19 +1743,40 @@ static int dwc3_resume_common(struct dwc3 *dwc) if (ret) return ret; + spin_lock_irqsave(&dwc->lock, flags); + switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: - spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_resume(dwc); - spin_unlock_irqrestore(&dwc->lock, flags); - /* FALLTHROUGH */ + break; + case USB_DR_MODE_OTG: + switch (dwc->otg_fsm.protocol) { + case PROTO_GADGET: + dwc3_gadget_resume(dwc); + break; + case PROTO_HOST: + break; + case PROTO_UNDEF: + default: + /* nothing */ + break; + } + break; case USB_DR_MODE_HOST: + case USB_DR_MODE_UNKNOWN: default: /* do nothing */ break; } + if (dwc->dr_mode == USB_DR_MODE_OTG) { + dwc3_otg_core_init(dwc); + if (dwc->otg_fsm.protocol == PROTO_HOST) + dwc3_drd_start_host(dwc, true, 1); + } + + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; } @@ -1301,6 +1831,7 @@ static int dwc3_runtime_resume(struct device *dev) dwc3_gadget_process_pending_events(dwc); break; case USB_DR_MODE_HOST: + case USB_DR_MODE_UNKNOWN: default: /* do nothing */ break; @@ -1336,6 +1867,30 @@ static int dwc3_runtime_idle(struct device *dev) #endif /* CONFIG_PM */ #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) + queue_work(system_power_efficient_wq, &dwc->otg_work); +} + static int dwc3_suspend(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); @@ -1373,6 +1928,10 @@ static const struct dev_pm_ops dwc3_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_suspend, dwc3_resume) SET_RUNTIME_PM_OPS(dwc3_runtime_suspend, dwc3_runtime_resume, dwc3_runtime_idle) +#ifdef CONFIG_PM_SLEEP + .prepare = dwc3_prepare, + .complete = dwc3_complete, +#endif }; #ifdef CONFIG_OF diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index fc82d2e..bf8951d 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -35,6 +35,11 @@ #include +#include +#include + +#include + #define DWC3_MSG_MAX 500 /* Global constants */ @@ -854,15 +859,23 @@ struct dwc3_scratchpad_array { * @event_buffer_list: a list of event buffers * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver + * @gadget_pullup: gadget driver's pullup request flag * @regs: base address for our registers * @regs_size: address space size + * @dr_mode: requested mode of operation + * @otg: usb otg data structure + * @otg-fsm: usb otg fsm data structure + * @otg_prevent_sync: flag to block events to otg fsm + * @current_mode: current mode of operation written to PRTCAPDIR + * @otg_work: work struct for otg/dual-role + * @otg_needs_host_start: flag that OTG controller needs to start host * @fladj: frame length adjustment * @irq_gadget: peripheral controller's IRQ number + * @otg_irq: IRQ number for OTG IRQs * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) * @revision: revision register contents - * @dr_mode: requested mode of operation * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW @@ -873,6 +886,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. @@ -964,6 +980,7 @@ struct dwc3 { struct usb_gadget gadget; struct usb_gadget_driver *gadget_driver; + bool gadget_pullup; struct usb_phy *usb2_phy; struct usb_phy *usb3_phy; @@ -973,14 +990,30 @@ struct dwc3 { struct ulpi *ulpi; + /* used for suspend/resume */ + u32 dcfg; + u32 gctl; + u32 ocfg; + u32 octl; + u32 oevten; + void __iomem *regs; size_t regs_size; enum usb_dr_mode dr_mode; + struct usb_otg otg; + struct otg_fsm otg_fsm; + bool otg_prevent_sync; + u32 current_mode; + struct work_struct otg_work; + bool otg_needs_host_start; + enum usb_phy_interface hsphy_mode; u32 fladj; u32 irq_gadget; + int otg_irq; + u32 nr_scratch; u32 u1u2; u32 maximum_speed; @@ -1277,6 +1310,9 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend); +int __dwc3_gadget_start(struct dwc3 *dwc); +void __dwc3_gadget_stop(struct dwc3 *dwc); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } @@ -1296,6 +1332,12 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) { return 0; } +static inline int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) +{ return 0; } +static inline int __dwc3_gadget_start(struct dwc3 *dwc) +{ return 0; } +static inline void __dwc3_gadget_stop(struct dwc3 *dwc) +{ } #endif /* power management interface */ diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 1341ed4..68a0e99 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1534,7 +1534,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, return 0; } -static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) +int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) { u32 reg; u32 timeout = 500; @@ -1542,6 +1542,10 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) if (pm_runtime_suspended(dwc->dev)) return 0; + if (dwc->dr_mode == USB_DR_MODE_OTG && + dwc->otg_fsm.protocol != PROTO_GADGET) + return 0; + reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (is_on) { if (dwc->revision <= DWC3_REVISION_187A) { @@ -1603,6 +1607,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) } spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_pullup = is_on; ret = dwc3_gadget_run_stop(dwc, is_on, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -1679,7 +1684,7 @@ static void dwc3_gadget_setup_nump(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DCFG, reg); } -static int __dwc3_gadget_start(struct dwc3 *dwc) +int __dwc3_gadget_start(struct dwc3 *dwc) { struct dwc3_ep *dep; int ret = 0; @@ -1812,8 +1817,11 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc->gadget_driver = driver; - if (pm_runtime_active(dwc->dev)) - __dwc3_gadget_start(dwc); + if (pm_runtime_active(dwc->dev)) { + if (!(dwc->dr_mode == USB_DR_MODE_OTG && + dwc->otg_fsm.protocol != PROTO_GADGET)) + __dwc3_gadget_start(dwc); + } spin_unlock_irqrestore(&dwc->lock, flags); @@ -1827,7 +1835,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, return ret; } -static void __dwc3_gadget_stop(struct dwc3 *dwc) +void __dwc3_gadget_stop(struct dwc3 *dwc) { dwc3_gadget_disable_irq(dwc); __dwc3_gadget_ep_disable(dwc->eps[0]); From patchwork Thu Feb 16 13:06:17 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 94068 Delivered-To: patch@linaro.org Received: by 10.140.20.99 with SMTP id 90csp2495842qgi; Thu, 16 Feb 2017 05:07:07 -0800 (PST) X-Received: by 10.98.18.217 with SMTP id 86mr2548563pfs.90.1487250427418; Thu, 16 Feb 2017 05:07:07 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y71si6938081plh.62.2017.02.16.05.07.07; Thu, 16 Feb 2017 05:07:07 -0800 (PST) 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; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754879AbdBPNHF (ORCPT + 25 others); Thu, 16 Feb 2017 08:07:05 -0500 Received: from fllnx210.ext.ti.com ([198.47.19.17]:17363 "EHLO fllnx210.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754750AbdBPNGf (ORCPT ); Thu, 16 Feb 2017 08:06:35 -0500 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by fllnx210.ext.ti.com (8.15.1/8.15.1) with ESMTP id v1GD6Tq7020237; Thu, 16 Feb 2017 07:06:29 -0600 Received: from DFLE73.ent.ti.com (dfle73.ent.ti.com [128.247.5.110]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6Tvq019660; Thu, 16 Feb 2017 07:06:29 -0600 Received: from dlep32.itg.ti.com (157.170.170.100) by DFLE73.ent.ti.com (128.247.5.110) with Microsoft SMTP Server id 14.3.294.0; Thu, 16 Feb 2017 07:06:28 -0600 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id v1GD6JN9006214; Thu, 16 Feb 2017 07:06:27 -0600 From: Roger Quadros To: CC: , , , Roger Quadros Subject: [PATCH v2 4/4] usb: dwc3: Workaround for super-speed host on dra7 in dual-role mode Date: Thu, 16 Feb 2017 15:06:17 +0200 Message-ID: <1487250377-13653-5-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1487250377-13653-1-git-send-email-rogerq@ti.com> References: <1487250377-13653-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 dra7 OTG core limits the host controller to USB2.0 (high-speed) mode when we're operating in dual-role. We work around that by bypassing the OTG core and reading the extcon framework directly for ID/VBUS events. Signed-off-by: Roger Quadros --- Documentation/devicetree/bindings/usb/dwc3.txt | 2 + drivers/usb/dwc3/core.c | 169 ++++++++++++++++++++++++- drivers/usb/dwc3/core.h | 5 + 3 files changed, 170 insertions(+), 6 deletions(-) -- 2.7.4 Signed-off-by: Roger Quadros diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index e3e6983..9955c0d 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -53,6 +53,8 @@ Optional properties: - snps,quirk-frame-length-adjustment: Value for GFLADJ_30MHZ field of GFLADJ register for post-silicon frame length adjustment when the fladj_30mhz_sdbnd signal is invalid or incorrect. + - extcon: phandle to the USB connector extcon device. If present, extcon + device will be used to get USB cable events instead of OTG controller. - tx-fifo-resize: determines if the FIFO *has* to be reallocated. diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 619fa7c..b02d911 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -918,6 +918,19 @@ static void dwc3_otg_fsm_sync(struct dwc3 *dwc) if (dwc->otg_prevent_sync) return; + if (dwc->edev) { + /* get ID */ + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + /* Host means ID == 0 */ + id = !id; + + /* get VBUS */ + vbus = extcon_get_state(dwc->edev, EXTCON_USB); + dwc3_drd_statemachine(dwc, id, vbus); + + return; + } + reg = dwc3_readl(dwc->regs, DWC3_OSTS); id = !!(reg & DWC3_OSTS_CONIDSTS); vbus = !!(reg & DWC3_OSTS_BSESVLD); @@ -934,6 +947,17 @@ static void dwc3_drd_work(struct work_struct *work) spin_unlock(&dwc->lock); } +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + + if (!dwc->otg_prevent_sync) + queue_work(system_power_efficient_wq, &dwc->otg_work); + + return NOTIFY_DONE; +} + static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) { dwc->oevten &= ~(disable_mask); @@ -1040,6 +1064,27 @@ static int dwc3_drd_start_host(struct dwc3 *dwc, int on, bool skip) { u32 reg; + if (!dwc->edev) + goto otg; + + if (on) + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + + if (!skip) { + spin_unlock(&dwc->lock); + + /* start or stop the HCD */ + if (on) + dwc3_host_init(dwc); + else + dwc3_host_exit(dwc); + + spin_lock(&dwc->lock); + } + + return 0; + +otg: /* switch OTG core */ if (on) { /* As per Figure 11-10 A-Device Flow Diagram */ @@ -1133,6 +1178,33 @@ static int dwc3_drd_start_gadget(struct dwc3 *dwc, int on) if (on) dwc3_event_buffers_setup(dwc); + if (!dwc->edev) + goto otg; + + if (on) { + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + /* start the Peripheral driver */ + if (dwc->gadget_driver) { + __dwc3_gadget_start(dwc); + if (dwc->gadget_pullup) + dwc3_gadget_run_stop(dwc, true, false); + } + } else { + /* stop the Peripheral driver */ + if (dwc->gadget_driver) { + if (dwc->gadget_pullup) + dwc3_gadget_run_stop(dwc, false, false); + spin_unlock(&dwc->lock); + if (dwc->gadget_driver->disconnect) + dwc->gadget_driver->disconnect(&dwc->gadget); + spin_lock(&dwc->lock); + __dwc3_gadget_stop(dwc); + } + } + + return 0; + +otg: if (on) { /* As per Figure 11-20 B-Device Flow Diagram */ @@ -1251,6 +1323,13 @@ static void dwc3_otg_core_init(struct dwc3 *dwc) { u32 reg; + /* force drd state machine update the first time */ + dwc->otg_fsm.b_sess_vld = -1; + dwc->otg_fsm.id = -1; + + if (dwc->edev) + return; + /* * As per Figure 11-4 OTG Driver Overall Programming Flow, * block "Initialize GCTL for OTG operation". @@ -1264,15 +1343,14 @@ static void dwc3_otg_core_init(struct dwc3 *dwc) /* Initialize OTG registers */ dwc3_otgregs_init(dwc); - - /* force drd state machine update the first time */ - dwc->otg_fsm.b_sess_vld = -1; - dwc->otg_fsm.id = -1; } /* dwc->lock must be held */ static void dwc3_otg_core_exit(struct dwc3 *dwc) { + if (dwc->edev) + return; + /* disable all otg irqs */ dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); /* clear all events */ @@ -1286,6 +1364,57 @@ static int dwc3_drd_init(struct dwc3 *dwc) INIT_WORK(&dwc->otg_work, dwc3_drd_work); + /* If extcon device is present we don't rely on OTG core for ID event */ + if (dwc->edev) { + int id, vbus; + + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "Couldn't register USB cable notifier\n"); + return -ENODEV; + } + + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "Couldn't register USB-HOST cable notifier\n"); + ret = -ENODEV; + goto extcon_fail; + } + + /* sanity check id & vbus states */ + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + vbus = extcon_get_state(dwc->edev, EXTCON_USB); + if (id < 0 || vbus < 0) { + dev_err(dwc->dev, "Invalid USB cable state. id %d, vbus %d\n", + id, vbus); + ret = -ENODEV; + goto fail; + } + + ret = dwc3_gadget_init(dwc); + if (ret) + goto fail; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_core_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + queue_work(system_power_efficient_wq, &dwc->otg_work); + + return 0; + +fail: + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); +extcon_fail: + extcon_unregister_notifier(dwc->edev, EXTCON_USB, + &dwc->edev_nb); + + return ret; + } + irq = dwc3_otg_get_irq(dwc); if (irq < 0) return irq; @@ -1326,13 +1455,24 @@ static void dwc3_drd_exit(struct dwc3 *dwc) { unsigned long flags; + spin_lock(&dwc->lock); + dwc->otg_prevent_sync = true; + spin_unlock(&dwc->lock); cancel_work_sync(&dwc->otg_work); + spin_lock_irqsave(&dwc->lock, flags); dwc3_otg_core_exit(dwc); if (dwc->otg_fsm.protocol == PROTO_HOST) dwc3_drd_start_host(dwc, 0, 0); dwc->otg_fsm.protocol = PROTO_UNDEF; - free_irq(dwc->otg_irq, dwc); + if (dwc->edev) { + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + extcon_unregister_notifier(dwc->edev, EXTCON_USB, + &dwc->edev_nb); + } else { + free_irq(dwc->otg_irq, dwc); + } spin_unlock_irqrestore(&dwc->lock, flags); dwc3_gadget_exit(dwc); @@ -1587,6 +1727,14 @@ static int dwc3_probe(struct platform_device *pdev) dwc3_get_properties(dwc); + if (dev->of_node) { + if (of_property_read_bool(dev->of_node, "extcon")) + dwc->edev = extcon_get_edev_by_phandle(dev, 0); + + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + } + platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); @@ -1743,6 +1891,13 @@ static int dwc3_resume_common(struct dwc3 *dwc) if (ret) return ret; + if (dwc->dr_mode == USB_DR_MODE_OTG && + dwc->edev) { + if (dwc->otg_fsm.protocol == PROTO_HOST) + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + else if (dwc->otg_fsm.protocol == PROTO_GADGET) + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + } spin_lock_irqsave(&dwc->lock, flags); switch (dwc->dr_mode) { @@ -1771,8 +1926,10 @@ static int dwc3_resume_common(struct dwc3 *dwc) if (dwc->dr_mode == USB_DR_MODE_OTG) { dwc3_otg_core_init(dwc); - if (dwc->otg_fsm.protocol == PROTO_HOST) + if ((dwc->otg_fsm.protocol == PROTO_HOST) && + !dwc->edev) { dwc3_drd_start_host(dwc, true, 1); + } } spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index bf8951d..fc060ae 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -38,6 +38,7 @@ #include #include +#include #include #define DWC3_MSG_MAX 500 @@ -869,6 +870,8 @@ struct dwc3_scratchpad_array { * @current_mode: current mode of operation written to PRTCAPDIR * @otg_work: work struct for otg/dual-role * @otg_needs_host_start: flag that OTG controller needs to start host + * @edev: extcon handle + * @edev_nb: extcon notifier * @fladj: frame length adjustment * @irq_gadget: peripheral controller's IRQ number * @otg_irq: IRQ number for OTG IRQs @@ -1007,6 +1010,8 @@ struct dwc3 { u32 current_mode; struct work_struct otg_work; bool otg_needs_host_start; + struct extcon_dev *edev; + struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode;