From patchwork Tue Feb 27 11:30:19 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 129776 Delivered-To: patch@linaro.org Received: by 10.46.66.2 with SMTP id p2csp1279859lja; Tue, 27 Feb 2018 03:30:33 -0800 (PST) X-Google-Smtp-Source: AH8x224aL1UKAHEbYFBr+mPFSxVMKHYpM2J/oZH8tpi/k8D7AR7X/olE5Q+n1C6b/0ogUBXM81LI X-Received: by 2002:a17:902:a512:: with SMTP id s18-v6mr13850405plq.296.1519731032867; Tue, 27 Feb 2018 03:30:32 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1519731032; cv=none; d=google.com; s=arc-20160816; b=vLvdQuznqFp99LjOy0PnBSrQvouEYT6Tl8FoZ6xEp0DWcmMCy5Wl3djY7QEfDwMkkp urshEJnZ201NWkWXBlecnwyCln2TkDlnZNJGph+dO2MsmXQnE5Nkrme49J3j60BvjVKa ez+5Z0NDlizuTTb1IfgqMU2nDsK5AfAnfRxcuJ7SrcGYX2Nd2P3JJw4ILcnQNPT79LT8 kRcLzOp6bHeOvYx1M1kIsaEGMzazd7R10ov9lRHQBo9bla17DgmG0ZscGPSRNgYWSSH+ 36Yvt14fnnRj8roffRnZmn5z1JU5+enqsDI+odG26CisZzvVQeL3D2/DyfhDuGwmeLqf HLUw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature :arc-authentication-results; bh=KlxEHQy0kd8sV1657likp53MFnSkGSJ0EqRsFnWfZ5Q=; b=0YsxPY6M6CZ5tdkRd+kkP2VeCj2gS7uMljpIXlbiQlMmYddPr1/2dkEy2dmFJ/tObA M9mahS/kgXTFnhjQf82SJR1aTBEOiGoxc04vP0W5qpEDfNfRHs2hW16XArswyt9+OiZm dpp46ZYYIDMcQz1yLfF3X0Zc1mgrmK6Kcgb6wml0Yy4ZAIh7cP0K8fS0eXD7Y+Aw2Ll+ v4Ra7M2jHfLEbcTmpDvEcdCRQP+fXyQs/TkTft0kWLt8Xc5KtK5z+cWAMXzGOiuLYHuA BXukKOhOH/pbPNO0AZ+Las1rESRhCaaOZI8HOMP3rtAGw0ZqDPf16FJVw2Q8mE6pVPah XBRA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=i4JptBnz; 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=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id y64si8540042pfj.47.2018.02.27.03.30.32; Tue, 27 Feb 2018 03:30:32 -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; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=i4JptBnz; 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=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753067AbeB0La2 (ORCPT + 28 others); Tue, 27 Feb 2018 06:30:28 -0500 Received: from fllnx210.ext.ti.com ([198.47.19.17]:13419 "EHLO fllnx210.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752762AbeB0La0 (ORCPT ); Tue, 27 Feb 2018 06:30:26 -0500 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by fllnx210.ext.ti.com (8.15.1/8.15.1) with ESMTP id w1RBUOu9014380; Tue, 27 Feb 2018 05:30:24 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ti.com; s=ti-com-17Q1; t=1519731024; bh=rk/A1pLIRz5ldUz8bZovpQe6ZsUuP1L3YU7Hji3ka4I=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=i4JptBnzYI4GPFsM9VADZYDIkydJm2GNE+ukY+IfXHSFaiz/Irf9J1KyCLIb/BZ7+ P7u9FkhBVTJZ+APrCIEtyOJenbSnxklUpm7ln3n08mToa8V/KEH7z3jdcR0GRVFuff sFz9X62mB6Kz8BgTubqqY4ckvZNeM16aA9iSu/xk= Received: from DLEE115.ent.ti.com (dlee115.ent.ti.com [157.170.170.26]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id w1RBUO3d025507; Tue, 27 Feb 2018 05:30:24 -0600 Received: from DLEE109.ent.ti.com (157.170.170.41) by DLEE115.ent.ti.com (157.170.170.26) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1261.35; Tue, 27 Feb 2018 05:30:24 -0600 Received: from dflp32.itg.ti.com (10.64.6.15) by DLEE109.ent.ti.com (157.170.170.41) with Microsoft SMTP Server (version=TLS1_0, cipher=TLS_RSA_WITH_AES_256_CBC_SHA) id 15.1.1261.35 via Frontend Transport; Tue, 27 Feb 2018 05:30:24 -0600 Received: from dlelxv97.itg.ti.com (dlelxv97.itg.ti.com [172.17.2.193]) by dflp32.itg.ti.com (8.14.3/8.13.8) with ESMTP id w1RBUOPO028971; Tue, 27 Feb 2018 05:30:24 -0600 Received: from lta0400828d.ti.com (vboxa0400828d.emea.dhcp.ti.com [172.22.233.227]) by dlelxv97.itg.ti.com (8.14.3/8.13.8) with ESMTP id w1RBUMRr022612; Tue, 27 Feb 2018 05:30:22 -0600 From: Roger Quadros To: CC: , , Roger Quadros Subject: [PATCH v2 2/2] usb: dwc3: add dual role support using OTG block Date: Tue, 27 Feb 2018 13:30:19 +0200 Message-ID: <1519731019-2220-1-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1519730952-2102-1-git-send-email-rogerq@ti.com> References: <1519730952-2102-1-git-send-email-rogerq@ti.com> MIME-Version: 1.0 X-EXCLAIMER-MD-CONFIG: e1e8a2fd-e40a-4ac6-ac9b-f7e9cc9ee180 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This is useful on platforms (e.g. TI AM437x) that don't have ID available on a GPIO but do have the OTG block. We can obtain the ID state via the OTG block and use it for dual-role switching. Signed-off-by: Roger Quadros --- drivers/usb/dwc3/core.c | 67 ++++++- drivers/usb/dwc3/core.h | 29 +++ drivers/usb/dwc3/drd.c | 489 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 557 insertions(+), 28 deletions(-) -- cheers, -roger Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index df4569d..397a4d4 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -89,10 +89,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) return 0; } -static void dwc3_event_buffers_cleanup(struct dwc3 *dwc); -static int dwc3_event_buffers_setup(struct dwc3 *dwc); - -static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) { u32 reg; @@ -110,16 +107,19 @@ static void __dwc3_set_mode(struct work_struct *work) unsigned long flags; int ret; - if (!dwc->desired_dr_role) + if (dwc->dr_mode != USB_DR_MODE_OTG) return; - if (dwc->desired_dr_role == dwc->current_dr_role) + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG) + dwc3_otg_update(dwc, 0); + + if (!dwc->desired_dr_role) return; - if (dwc->dr_mode != USB_DR_MODE_OTG) + if (dwc->desired_dr_role == dwc->current_dr_role) return; - if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG) + if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev) return; switch (dwc->current_dr_role) { @@ -130,6 +130,13 @@ static void __dwc3_set_mode(struct work_struct *work) dwc3_gadget_exit(dwc); dwc3_event_buffers_cleanup(dwc); break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; default: break; } @@ -165,9 +172,14 @@ static void __dwc3_set_mode(struct work_struct *work) if (ret) dev_err(dwc->dev, "failed to initialize peripheral\n"); break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_init(dwc); + dwc3_otg_update(dwc, 0); + break; default: break; } + } void dwc3_set_mode(struct dwc3 *dwc, u32 mode) @@ -351,7 +363,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) * * Returns 0 on success otherwise negative errno. */ -static int dwc3_event_buffers_setup(struct dwc3 *dwc) +int dwc3_event_buffers_setup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; @@ -368,7 +380,7 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) return 0; } -static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +void dwc3_event_buffers_cleanup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; @@ -1329,6 +1341,20 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg) if (!PMSG_IS_AUTO(msg)) dwc3_core_exit(dwc); break; + case DWC3_GCTL_PRTCAP_OTG: + /* do nothing during runtime_suspend */ + if (PMSG_IS_AUTO(msg)) + break; + + if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_suspend(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + dwc3_otg_exit(dwc); + dwc3_core_exit(dwc); + break; default: /* do nothing */ break; @@ -1360,6 +1386,27 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg) return ret; } break; + case DWC3_GCTL_PRTCAP_OTG: + /* nothing to do on runtime_resume */ + if (PMSG_IS_AUTO(msg)) + break; + + ret = dwc3_core_init(dwc); + if (ret) + return ret; + + dwc3_set_prtcap(dwc, dwc->current_dr_role); + + dwc3_otg_init(dwc); + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) { + dwc3_otg_host_init(dwc); + } else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_resume(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + } + + break; default: /* do nothing */ break; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 0d4c698..09243a6 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -58,6 +58,11 @@ #define DWC3_DEVICE_EVENT_CMD_CMPL 10 #define DWC3_DEVICE_EVENT_OVERFLOW 11 +/* Controller's role while using the OTG block */ +#define DWC3_OTG_ROLE_IDLE 0 +#define DWC3_OTG_ROLE_HOST 1 +#define DWC3_OTG_ROLE_DEVICE 2 + #define DWC3_GEVNTCOUNT_MASK 0xfffc #define DWC3_GEVNTCOUNT_EHB BIT(31) #define DWC3_GSNPSID_MASK 0xffff0000 @@ -863,6 +868,10 @@ struct dwc3_scratchpad_array { * @regs_size: address space size * @fladj: frame length adjustment * @irq_gadget: peripheral controller's IRQ number + * @otg_irq: IRQ number for OTG IRQs + * @current_otg_role: current role of operation while using the OTG block + * @desired_otg_role: desired role of operation while using the OTG block + * @otg_restart_host: flag that OTG controller needs to restart host * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) @@ -996,6 +1005,10 @@ struct dwc3 { u32 fladj; u32 irq_gadget; + u32 otg_irq; + u32 current_otg_role; + u32 desired_otg_role; + bool otg_restart_host; u32 nr_scratch; u32 u1u2; u32 maximum_speed; @@ -1257,6 +1270,7 @@ struct dwc3_gadget_ep_cmd_params { #define DWC3_HAS_OTG BIT(3) /* prototypes */ +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode); void dwc3_set_mode(struct dwc3 *dwc, u32 mode); u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type); @@ -1274,6 +1288,9 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc) bool dwc3_has_imod(struct dwc3 *dwc); +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_event_buffers_cleanup(struct dwc3 *dwc); + #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); void dwc3_host_exit(struct dwc3 *dwc); @@ -1317,11 +1334,23 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_drd_init(struct dwc3 *dwc); void dwc3_drd_exit(struct dwc3 *dwc); +void dwc3_otg_init(struct dwc3 *dwc); +void dwc3_otg_exit(struct dwc3 *dwc); +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus); +void dwc3_otg_host_init(struct dwc3 *dwc); #else static inline int dwc3_drd_init(struct dwc3 *dwc) { return 0; } static inline void dwc3_drd_exit(struct dwc3 *dwc) { } +static inline void dwc3_otg_init(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ } +static inline void dwc3_otg_host_init(struct dwc3 *dwc) +{ } #endif /* power management interface */ diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index cc8ab9a..1d8c557 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -8,22 +8,423 @@ */ #include +#include #include "debug.h" #include "core.h" #include "gadget.h" -static void dwc3_drd_update(struct dwc3 *dwc) +static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); + + reg &= ~(disable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN); + + reg |= (enable_mask); + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +static void dwc3_otg_clear_events(struct dwc3 *dwc) +{ + u32 reg = dwc3_readl(dwc->regs, DWC3_OEVT); + + dwc3_writel(dwc->regs, DWC3_OEVTEN, reg); +} + +#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_BDEVBHOSTENDEN | \ + DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \ + DWC3_OEVTEN_BDEVVBUSCHNGEN) + +static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc) { + struct dwc3 *dwc = _dwc; + + spin_lock(&dwc->lock); + if (dwc->otg_restart_host) { + dwc3_otg_host_init(dwc); + dwc->otg_restart_host = 0; + } + + spin_unlock(&dwc->lock); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + + 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) { + /* ignore non OTG events, we can't disable them in OEVTEN */ + if (!(reg & DWC3_OTG_ALL_EVENTS)) { + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + return IRQ_NONE; + } + + if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST && + !(reg & DWC3_OEVT_DEVICEMODE)) + dwc->otg_restart_host = 1; + dwc3_writel(dwc->regs, DWC3_OEVT, reg); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +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_otg_clear_events(dwc); + /* 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_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; +} + +void dwc3_otg_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_prtcap(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); +} + +void dwc3_otg_exit(struct dwc3 *dwc) +{ + /* disable all OTG IRQs */ + dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS); + /* clear all events */ + dwc3_otg_clear_events(dwc); +} + +/* should be called before Host controller driver is started */ +void dwc3_otg_host_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-10 A-Device Flow Diagram */ + /* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */ + + /* + * 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); + + /* + * 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); +} + +/* should be called after Host controller driver is stopped */ +static void dwc3_otg_host_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from A-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * 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); +} + +/* should be called before the gadget controller driver is started */ +static void dwc3_otg_device_init(struct dwc3 *dwc) +{ + u32 reg; + + /* As per Figure 11-20 B-Device Flow Diagram */ + + /* + * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1 + * but we keep them 0 for simple dual-role operation. + */ + reg = dwc3_readl(dwc->regs, DWC3_OCFG); + /* 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_OEVTEN_BDEVSESSVLDDETEN); + /* 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. Already 0. */ +} + +/* should be called after the gadget controller driver is stopped */ +static void dwc3_otg_device_exit(struct dwc3 *dwc) +{ + u32 reg; + + /* + * Exit from B-device flow as per + * Figure 11-4 OTG Driver Overall Programming Flow + */ + + /* + * OEVTEN.OTGBDevHNPChngEvntEn = 0 + * OEVTEN.OTGBDevVBusChngEvntEn = 0 + * OEVTEN.OTGBDevBHostEndEvntEn = 0 + */ + dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN | + DWC3_OEVTEN_BDEVVBUSCHNGEN | + DWC3_OEVTEN_BDEVBHOSTENDEN); + + /* 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); +} + +void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus) +{ + int ret; + u32 reg; int id; + unsigned long flags; - id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); - if (id < 0) - id = 0; + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; - dwc3_set_mode(dwc, id ? - DWC3_GCTL_PRTCAP_HOST : - DWC3_GCTL_PRTCAP_DEVICE); + /* don't do anything if debug user changed role to not OTG */ + if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG) + return; + + if (!ignore_idstatus) { + reg = dwc3_readl(dwc->regs, DWC3_OSTS); + id = !!(reg & DWC3_OSTS_CONIDSTS); + + dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE : + DWC3_OTG_ROLE_HOST; + } + + if (dwc->desired_otg_role == dwc->current_otg_role) + return; + + switch (dwc->current_otg_role) { + case DWC3_OTG_ROLE_HOST: + dwc3_host_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otg_host_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case DWC3_OTG_ROLE_DEVICE: + dwc3_gadget_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc3_event_buffers_cleanup(dwc); + dwc3_otg_device_exit(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + default: + break; + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc->current_otg_role = dwc->desired_otg_role; + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (dwc->desired_otg_role) { + case DWC3_OTG_ROLE_HOST: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_host_init(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, + PHY_MODE_USB_HOST); + } + break; + case DWC3_OTG_ROLE_DEVICE: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_otgregs_init(dwc); + dwc3_otg_device_init(dwc); + dwc3_event_buffers_setup(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, + PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + default: + break; + } +} + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + if (dwc->edev) { + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + } } static int dwc3_drd_notifier(struct notifier_block *nb, @@ -40,11 +441,11 @@ static int dwc3_drd_notifier(struct notifier_block *nb, int dwc3_drd_init(struct dwc3 *dwc) { - int ret; + int ret, irq; - if (dwc->dev->of_node) { - if (of_property_read_bool(dwc->dev->of_node, "extcon")) - dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); + if (dwc->dev->of_node && + of_property_read_bool(dwc->dev->of_node, "extcon")) { + dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); if (IS_ERR(dwc->edev)) return PTR_ERR(dwc->edev); @@ -56,19 +457,71 @@ int dwc3_drd_init(struct dwc3 *dwc) dev_err(dwc->dev, "couldn't register cable notifier\n"); return ret; } - } - dwc3_drd_update(dwc); + dwc3_drd_update(dwc); + } else { + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG); + dwc->current_dr_role = DWC3_GCTL_PRTCAP_OTG; + + /* use OTG block to get ID event */ + 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_otg_clear_events(dwc); + + 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; + } + + dwc3_otg_init(dwc); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + } return 0; } void dwc3_drd_exit(struct dwc3 *dwc) { - extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, - &dwc->edev_nb); + unsigned long flags; + + if (dwc->edev) + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + cancel_work_sync(&dwc->drd_work); + + /* debug user might have changed role, clean based on current role */ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + case DWC3_GCTL_PRTCAP_OTG: + dwc3_otg_exit(dwc); + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE; + spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_otg_update(dwc, 1); + break; + default: + break; + } - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - flush_work(&dwc->drd_work); - dwc3_gadget_exit(dwc); + if (!dwc->edev) + free_irq(dwc->otg_irq, dwc); }