From patchwork Fri Jun 14 07:46:49 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115797 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="oLtUon4L"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCPP2hczz9sND for ; Fri, 14 Jun 2019 17:47:21 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726381AbfFNHrT (ORCPT ); Fri, 14 Jun 2019 03:47:19 -0400 Received: from hqemgate14.nvidia.com ([216.228.121.143]:16049 "EHLO hqemgate14.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726349AbfFNHrT (ORCPT ); Fri, 14 Jun 2019 03:47:19 -0400 Received: from hqpgpgate101.nvidia.com (Not Verified[216.228.121.13]) by hqemgate14.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:47:19 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate101.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:47:18 -0700 X-PGP-Universal: processed; by hqpgpgate101.nvidia.com on Fri, 14 Jun 2019 00:47:18 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:47:16 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 1/8] clk: tegra: Add PLLE HW power sequencer control Date: Fri, 14 Jun 2019 15:46:49 +0800 Message-ID: <20190614074652.21960-2-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074652.21960-1-jckuo@nvidia.com> References: <20190614074652.21960-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498439; bh=EJI7wfVIK8X8lBoGLuzdrYmOj+GleZkOEyqz3XrNkn8=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=oLtUon4LOyfGQdBd0X0gjlVqjGe/8EKCwKgec/qpPxOCDkGYF+hIfmJqTMLXhkVCC U77YWe/xRKiM/Rxkj36IZPBZs9c67PBwF5ThFKChaG2o+Deot/EigUlBIXQnX9crkg 2Rt+/JTOKrROg+vcijA0sUqb+D5cSQZkikgtWTo4lJ18eFX7aN+laBIzfIMiqpr9Mr NSS5NmDyHbO8VD4yhk38usmqFDVCV1OllkeIux2dDPi//8NpSDUzYJlFHTCq/8WRVB 5fzXiWUNbndhxwUg4LF+zz5V3BrlAapXIfjgkqnM/Fu0Cn1qgh8wSM0InLDvOgf4nb 3xrsc32MIhUWA== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org PLLE hardware power sequencer has to be enabled after PEX/SATA UPHY PLL's sequencers are enabled. tegra210_plle_hw_sequence_start() for XUSB PADCTL driver to enable PLLE hardware sequencer at proper time. tegra210_plle_hw_sequence_is_enabled() for XUSB PADCTL driver to check whether PLLE hardware sequencer has been enabled or not. Signed-off-by: JC Kuo --- drivers/clk/tegra/clk-tegra210.c | 45 ++++++++++++++++++++++++++++++++ include/linux/clk/tegra.h | 2 ++ 2 files changed, 47 insertions(+) diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c index e1ba62d2b1a0..14d330669f36 100644 --- a/drivers/clk/tegra/clk-tegra210.c +++ b/drivers/clk/tegra/clk-tegra210.c @@ -398,6 +398,14 @@ static const char *mux_pllmcp_clkm[] = { #define PLLRE_BASE_DEFAULT_MASK 0x1c000000 #define PLLRE_MISC0_WRITE_MASK 0x67ffffff +/* PLLE */ +#define PLLE_MISC_IDDQ_SW_CTRL (1 << 14) +#define PLLE_AUX_USE_LOCKDET (1 << 3) +#define PLLE_AUX_SS_SEQ_INCLUDE (1 << 31) +#define PLLE_AUX_ENABLE_SWCTL (1 << 4) +#define PLLE_AUX_SS_SWCTL (1 << 6) +#define PLLE_AUX_SEQ_ENABLE (1 << 24) + /* PLLX */ #define PLLX_USE_DYN_RAMP 1 #define PLLX_BASE_LOCK (1 << 27) @@ -484,6 +492,43 @@ static const char *mux_pllmcp_clkm[] = { #define PLLU_MISC0_WRITE_MASK 0xbfffffff #define PLLU_MISC1_WRITE_MASK 0x00000007 +bool tegra210_plle_hw_sequence_is_enabled(void) +{ + u32 val; + + val = readl_relaxed(clk_base + PLLE_AUX); + if (val & PLLE_AUX_SEQ_ENABLE) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(tegra210_plle_hw_sequence_is_enabled); + +void tegra210_plle_hw_sequence_start(void) +{ + u32 val; + + if (tegra210_plle_hw_sequence_is_enabled()) + return; + + val = readl_relaxed(clk_base + PLLE_MISC0); + val &= ~PLLE_MISC_IDDQ_SW_CTRL; + writel_relaxed(val, clk_base + PLLE_MISC0); + + val = readl_relaxed(clk_base + PLLE_AUX); + val |= (PLLE_AUX_USE_LOCKDET | PLLE_AUX_SS_SEQ_INCLUDE); + val &= ~(PLLE_AUX_ENABLE_SWCTL | PLLE_AUX_SS_SWCTL); + writel_relaxed(val, clk_base + PLLE_AUX); + + fence_udelay(1, clk_base); + + val |= PLLE_AUX_SEQ_ENABLE; + writel_relaxed(val, clk_base + PLLE_AUX); + + fence_udelay(1, clk_base); +} +EXPORT_SYMBOL_GPL(tegra210_plle_hw_sequence_start); + void tegra210_xusb_pll_hw_control_enable(void) { u32 val; diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h index b8aef62cc3f5..07b6d6145c95 100644 --- a/include/linux/clk/tegra.h +++ b/include/linux/clk/tegra.h @@ -110,6 +110,8 @@ static inline void tegra_cpu_clock_resume(void) } #endif +extern void tegra210_plle_hw_sequence_start(void); +extern bool tegra210_plle_hw_sequence_is_enabled(void); extern void tegra210_xusb_pll_hw_control_enable(void); extern void tegra210_xusb_pll_hw_sequence_start(void); extern void tegra210_sata_pll_hw_control_enable(void); From patchwork Fri Jun 14 07:46:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115798 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="fIhclZ5t"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCPS2KL8z9sNT for ; Fri, 14 Jun 2019 17:47:24 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725942AbfFNHrW (ORCPT ); Fri, 14 Jun 2019 03:47:22 -0400 Received: from hqemgate14.nvidia.com ([216.228.121.143]:16061 "EHLO hqemgate14.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726349AbfFNHrV (ORCPT ); Fri, 14 Jun 2019 03:47:21 -0400 Received: from hqpgpgate102.nvidia.com (Not Verified[216.228.121.13]) by hqemgate14.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:47:21 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate102.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:47:20 -0700 X-PGP-Universal: processed; by hqpgpgate102.nvidia.com on Fri, 14 Jun 2019 00:47:20 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:47:18 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 2/8] clk: tegra: don't enable PLLE HW sequencer at init Date: Fri, 14 Jun 2019 15:46:50 +0800 Message-ID: <20190614074652.21960-3-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074652.21960-1-jckuo@nvidia.com> References: <20190614074652.21960-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498441; bh=0y6D2RBvajc0M/fTjBfcOd4qCrAhXSgcSFikZbfauPw=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=fIhclZ5tSXvUJqp1EiVtMXF2lNKkg+wjSnuEjmdwzUsPfj4JhA8jxvKJWOjBEumin oFaj3M042oehVgpMT8sjk5OVmK7bh4GyqjJRtmvtqQmqTDbbDro3yGecxsU09U6XNc kN8pK/CyOiyxi/6amCO9oOpOYLBLh+qhiL1btZOJVs1ncfGPkBkwO8Gqzj+I9CgK7g 4EiMASF1ijYykxFiK/ZifteEck1eYzDFD/YFyYy8wtPxEumj8oswEZ6QLCTQmc1o7m PYO+rnsC/7LeyYW/evSe6G7132rIJmsKXyVShnzEpLjtgFiBfkM1RCA/xVXICIxpnc JBchUjOpIN0Gg== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org PLLE hardware power sequencer references PEX/SATA UPHY PLL hardware power sequencers' output to enable/disable PLLE. PLLE hardware power sequencer has to be enabled only after PEX/SATA UPHY PLL's sequencers are enabled. Signed-off-by: JC Kuo --- drivers/clk/tegra/clk-pll.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/drivers/clk/tegra/clk-pll.c b/drivers/clk/tegra/clk-pll.c index 1583f5fc992f..e6de65987fd2 100644 --- a/drivers/clk/tegra/clk-pll.c +++ b/drivers/clk/tegra/clk-pll.c @@ -2469,18 +2469,6 @@ static int clk_plle_tegra210_enable(struct clk_hw *hw) pll_writel(val, PLLE_SS_CTRL, pll); udelay(1); - val = pll_readl_misc(pll); - val &= ~PLLE_MISC_IDDQ_SW_CTRL; - pll_writel_misc(val, pll); - - val = pll_readl(pll->params->aux_reg, pll); - val |= (PLLE_AUX_USE_LOCKDET | PLLE_AUX_SS_SEQ_INCLUDE); - val &= ~(PLLE_AUX_ENABLE_SWCTL | PLLE_AUX_SS_SWCTL); - pll_writel(val, pll->params->aux_reg, pll); - udelay(1); - val |= PLLE_AUX_SEQ_ENABLE; - pll_writel(val, pll->params->aux_reg, pll); - out: if (pll->lock) spin_unlock_irqrestore(pll->lock, flags); From patchwork Fri Jun 14 07:46:51 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115799 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="HWrE3IRv"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCPV48H1z9sDB for ; Fri, 14 Jun 2019 17:47:26 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726402AbfFNHrZ (ORCPT ); Fri, 14 Jun 2019 03:47:25 -0400 Received: from hqemgate16.nvidia.com ([216.228.121.65]:19572 "EHLO hqemgate16.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726349AbfFNHrY (ORCPT ); Fri, 14 Jun 2019 03:47:24 -0400 Received: from hqpgpgate101.nvidia.com (Not Verified[216.228.121.13]) by hqemgate16.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:47:23 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate101.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:47:23 -0700 X-PGP-Universal: processed; by hqpgpgate101.nvidia.com on Fri, 14 Jun 2019 00:47:23 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:47:20 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 3/8] phy: tegra: xusb: t210: rearrange UPHY init Date: Fri, 14 Jun 2019 15:46:51 +0800 Message-ID: <20190614074652.21960-4-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074652.21960-1-jckuo@nvidia.com> References: <20190614074652.21960-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498443; bh=3ZSOKOoyb5zQgP82Cq7isdFr7HrO85773O5LFupfquw=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=HWrE3IRvuzYH0e5xBrKkv02CAu27AwGc8RKuHM/3IZ8viLp3dZM31TiYD4KS5s8R4 /A8bBeqrMu7AotGpuzzyaEdTWCSByBfDYprUw7gyF50Pr1cZdnbnNOX6noMn5fN63s 41dyyL9M+O11fVED9n+Bs0/Sl/YEkXOpPHjaPCC2tplVfG3D29YM/PSmSB2Wq8lvkt lKgk5PWoL351Uxc9XMeO2vq6jV8b7iwYSOVPGQY10Ct2EG34mUEFxuGNtM/K2VuTwt bEVX8BUUw7U0QwCYv8ItZtmqTS9IzrdF8LSK20J5kko9wCSHM/sjHk/2Q2WXdpQxyU xHTaqd4CbNbxw== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org This commit is a preparation for enabling XUSB LP0 support. It rearranges T210 XUSB PADCTL UPHY initialization sequence, for the following reasons: 1. PLLE hardware power sequencer has to be enabled only after both PEX UPHY PLL and SATA UPHY PLL are initialized. 2. Once UPHY PLL hardware power sequncer is enabled, do not assert reset to PEX/SATA PLLs. 3. At LP0 exit, XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN, XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY, and XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN bits have to be cleared after XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE and XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE bits get set. 4. Move XUSB_PADCTL_SS_PORT_MAP and XUSB_PADCTL_UPHY_USB3_PADX_ECTL* registers programming from tegra210_usb3_port_enable() to tegra210_pcie_phy_power_on()/tegra210_sata_phy_power_on() so that XUSB USB3 ports will be programmed at LP0 exit. Signed-off-by: JC Kuo --- drivers/phy/tegra/xusb-tegra210.c | 443 ++++++++++++++++++------------ drivers/phy/tegra/xusb.c | 2 +- drivers/phy/tegra/xusb.h | 2 + 3 files changed, 264 insertions(+), 183 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 18cea8311d22..007bf352b45e 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -240,6 +240,8 @@ to_tegra210_xusb_padctl(struct tegra_xusb_padctl *padctl) return container_of(padctl, struct tegra210_xusb_padctl, base); } +static int tegra210_usb3_lane_map(struct tegra_xusb_lane *lane); + /* must be called under padctl->lock */ static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl) { @@ -453,35 +455,44 @@ static int tegra210_pex_uphy_enable(struct tegra_xusb_padctl *padctl) static void tegra210_pex_uphy_disable(struct tegra_xusb_padctl *padctl) { struct tegra_xusb_pcie_pad *pcie = to_pcie_pad(padctl->pcie); - - mutex_lock(&padctl->lock); + u32 value; + int i; if (WARN_ON(pcie->enable == 0)) - goto unlock; + return; if (--pcie->enable > 0) - goto unlock; + return; - reset_control_assert(pcie->rst); + for (i = 0; i < padctl->pcie->soc->num_lanes; i++) { + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(i); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + } clk_disable_unprepare(pcie->pll); - -unlock: - mutex_unlock(&padctl->lock); } /* must be called under padctl->lock */ -static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb) +static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl) { struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); + struct tegra_xusb_lane *lane = tegra_xusb_find_lane(padctl, "sata", 0); unsigned long timeout; u32 value; int err; + bool usb = false; if (sata->enable > 0) { sata->enable++; return 0; } + if (!lane) + return 0; + + if (tegra_xusb_lane_check(lane, "usb3-ss")) + usb = true; + err = clk_prepare_enable(sata->pll); if (err < 0) return err; @@ -695,30 +706,36 @@ static int tegra210_sata_uphy_enable(struct tegra_xusb_padctl *padctl, bool usb) static void tegra210_sata_uphy_disable(struct tegra_xusb_padctl *padctl) { struct tegra_xusb_sata_pad *sata = to_sata_pad(padctl->sata); - - mutex_lock(&padctl->lock); + u32 value; + int i; if (WARN_ON(sata->enable == 0)) - goto unlock; + return; if (--sata->enable > 0) - goto unlock; + return; - reset_control_assert(sata->rst); + for (i = 0; i < padctl->sata->soc->num_lanes; i++) { + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(i); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + } clk_disable_unprepare(sata->pll); - -unlock: - mutex_unlock(&padctl->lock); } static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) { - u32 value; + return 0; +} - mutex_lock(&padctl->lock); +static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +{ + return 0; +} - if (padctl->enable++ > 0) - goto out; +static void tegra210_aux_mux_lp0_clamp_disable(struct tegra_xusb_padctl *padctl) +{ + u32 value; value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; @@ -735,24 +752,12 @@ static int tegra210_xusb_padctl_enable(struct tegra_xusb_padctl *padctl) value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); value &= ~XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - -out: - mutex_unlock(&padctl->lock); - return 0; } -static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) +static void tegra210_aux_mux_lp0_clamp_enable(struct tegra_xusb_padctl *padctl) { u32 value; - mutex_lock(&padctl->lock); - - if (WARN_ON(padctl->enable == 0)) - goto out; - - if (--padctl->enable > 0) - goto out; - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN; padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); @@ -768,12 +773,76 @@ static int tegra210_xusb_padctl_disable(struct tegra_xusb_padctl *padctl) value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); value |= XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN; padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); +} + +static int tegra210_uphy_init(struct tegra_xusb_padctl *padctl) +{ + struct tegra_xusb_pcie_pad *pcie; + struct tegra_xusb_sata_pad *sata; + u32 value; + int err; + int i; + + if (tegra210_plle_hw_sequence_is_enabled()) { + dev_dbg(padctl->dev, "PLLE is already in HW control\n"); + /* skip pll initialization, update plle refcount only */ + if (padctl->pcie) { + pcie = to_pcie_pad(padctl->pcie); + if (pcie->enable == 0) { + err = clk_prepare_enable(pcie->pll); + if (err < 0) + return err; + pcie->enable++; + } + } + if (padctl->sata) { + sata = to_sata_pad(padctl->sata); + if (sata->enable == 0) { + err = clk_prepare_enable(sata->pll); + if (err < 0) + return err; + sata->enable++; + } + } + goto skip_pll_init; + } + + if (padctl->pcie) + tegra210_pex_uphy_enable(padctl); + if (padctl->sata) + tegra210_sata_uphy_enable(padctl); + + tegra210_plle_hw_sequence_start(); + +skip_pll_init: + for (i = 0; i < padctl->pcie->soc->num_lanes; i++) { + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(i); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + } + + for (i = 0; i < padctl->sata->soc->num_lanes; i++) { + value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); + value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(i); + padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + } + + tegra210_aux_mux_lp0_clamp_disable(padctl); -out: - mutex_unlock(&padctl->lock); return 0; } +static void __maybe_unused +tegra210_uphy_deinit(struct tegra_xusb_padctl *padctl) +{ + tegra210_aux_mux_lp0_clamp_enable(padctl); + + if (padctl->pcie) + tegra210_pex_uphy_disable(padctl); + if (padctl->sata) + tegra210_sata_uphy_disable(padctl); +} + static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl, unsigned int index, bool idle) { @@ -1420,6 +1489,113 @@ static const struct tegra_xusb_lane_soc tegra210_pcie_lanes[] = { TEGRA210_LANE("pcie-6", 0x028, 24, 0x3, pcie), }; +static int tegra210_usb3_phy_power_on(struct phy *phy) +{ + struct device *dev = &phy->dev; + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb3_port *usb3 = tegra_xusb_find_usb3_port(padctl, + tegra210_usb3_lane_map(lane)); + int index; + u32 value; + + if (!usb3) { + dev_err(dev, "no USB3 port found for lane %u\n", lane->index); + return -ENODEV; + } + index = usb3->base.index; + + value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + + if (!usb3->internal) + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + else + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); + + value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); + value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); + padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT); + value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL << + XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT; + padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); + + padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL, + XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + return 0; +} + +static int tegra210_usb3_phy_power_off(struct phy *phy) +{ + struct device *dev = &phy->dev; + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + struct tegra_xusb_usb3_port *usb3 = tegra_xusb_find_usb3_port(padctl, + tegra210_usb3_lane_map(lane)); + int index; + u32 value; + + if (!usb3) { + dev_err(dev, "no USB3 port found for lane %u\n", lane->index); + return -ENODEV; + } + index = usb3->base.index; + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(250, 350); + + value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); + padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); + + return 0; +} static struct tegra_xusb_lane * tegra210_pcie_lane_probe(struct tegra_xusb_pad *pad, struct device_node *np, unsigned int index) @@ -1461,6 +1637,13 @@ static const struct tegra_xusb_lane_ops tegra210_pcie_lane_ops = { static int tegra210_pcie_phy_init(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + + mutex_lock(&padctl->lock); + + tegra210_uphy_init(padctl); + + mutex_unlock(&padctl->lock); return tegra210_xusb_padctl_enable(lane->pad->padctl); } @@ -1476,20 +1659,13 @@ static int tegra210_pcie_phy_power_on(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_padctl *padctl = lane->pad->padctl; - u32 value; - int err; + int err = 0; mutex_lock(&padctl->lock); - err = tegra210_pex_uphy_enable(padctl); - if (err < 0) - goto unlock; + if (tegra_xusb_lane_check(lane, "usb3-ss")) + err = tegra210_usb3_phy_power_on(phy); - value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); - value |= XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); - padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); - -unlock: mutex_unlock(&padctl->lock); return err; } @@ -1498,15 +1674,15 @@ static int tegra210_pcie_phy_power_off(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_padctl *padctl = lane->pad->padctl; - u32 value; + int err = 0; - value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); - value &= ~XUSB_PADCTL_USB3_PAD_MUX_PCIE_IDDQ_DISABLE(lane->index); - padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + mutex_lock(&padctl->lock); - tegra210_pex_uphy_disable(padctl); + if (tegra_xusb_lane_check(lane, "usb3-ss")) + err = tegra210_usb3_phy_power_off(phy); - return 0; + mutex_unlock(&padctl->lock); + return err; } static const struct phy_ops tegra210_pcie_phy_ops = { @@ -1632,7 +1808,13 @@ static const struct tegra_xusb_lane_ops tegra210_sata_lane_ops = { static int tegra210_sata_phy_init(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + + mutex_lock(&padctl->lock); + + tegra210_uphy_init(padctl); + mutex_unlock(&padctl->lock); return tegra210_xusb_padctl_enable(lane->pad->padctl); } @@ -1647,20 +1829,13 @@ static int tegra210_sata_phy_power_on(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_padctl *padctl = lane->pad->padctl; - u32 value; - int err; + int err = 0; mutex_lock(&padctl->lock); - err = tegra210_sata_uphy_enable(padctl, false); - if (err < 0) - goto unlock; - - value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); - value |= XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); - padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + if (tegra_xusb_lane_check(lane, "usb3-ss")) + err = tegra210_usb3_phy_power_on(phy); -unlock: mutex_unlock(&padctl->lock); return err; } @@ -1669,15 +1844,15 @@ static int tegra210_sata_phy_power_off(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_padctl *padctl = lane->pad->padctl; - u32 value; + int err = 0; - value = padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); - value &= ~XUSB_PADCTL_USB3_PAD_MUX_SATA_IDDQ_DISABLE(lane->index); - padctl_writel(padctl, value, XUSB_PADCTL_USB3_PAD_MUX); + mutex_lock(&padctl->lock); - tegra210_sata_uphy_disable(lane->pad->padctl); + if (tegra_xusb_lane_check(lane, "usb3-ss")) + err = tegra210_usb3_phy_power_off(phy); - return 0; + mutex_unlock(&padctl->lock); + return err; } static const struct phy_ops tegra210_sata_phy_ops = { @@ -1802,125 +1977,11 @@ static const struct tegra_xusb_port_ops tegra210_hsic_port_ops = { static int tegra210_usb3_port_enable(struct tegra_xusb_port *port) { - struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); - struct tegra_xusb_padctl *padctl = port->padctl; - struct tegra_xusb_lane *lane = usb3->base.lane; - unsigned int index = port->index; - u32 value; - int err; - - value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); - - if (!usb3->internal) - value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); - else - value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_INTERNAL(index); - - value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); - value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, usb3->port); - padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); - - /* - * TODO: move this code into the PCIe/SATA PHY ->power_on() callbacks - * and conditionalize based on mux function? This seems to work, but - * might not be the exact proper sequence. - */ - err = regulator_enable(usb3->supply); - if (err < 0) - return err; - - value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); - value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_MASK << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT); - value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_VAL << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL1_TX_TERM_CTRL_SHIFT; - padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL1(index)); - - value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); - value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_MASK << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT); - value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_VAL << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL2_RX_CTLE_SHIFT; - padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL2(index)); - - padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL3_RX_DFE_VAL, - XUSB_PADCTL_UPHY_USB3_PADX_ECTL3(index)); - - value = padctl_readl(padctl, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); - value &= ~(XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_MASK << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT); - value |= XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_VAL << - XUSB_PADCTL_UPHY_USB3_PAD_ECTL4_RX_CDR_CTRL_SHIFT; - padctl_writel(padctl, value, XUSB_PADCTL_UPHY_USB3_PADX_ECTL4(index)); - - padctl_writel(padctl, XUSB_PADCTL_UPHY_USB3_PAD_ECTL6_RX_EQ_CTRL_H_VAL, - XUSB_PADCTL_UPHY_USB3_PADX_ECTL6(index)); - - if (lane->pad == padctl->sata) - err = tegra210_sata_uphy_enable(padctl, true); - else - err = tegra210_pex_uphy_enable(padctl); - - if (err) { - dev_err(&port->dev, "%s: failed to enable UPHY: %d\n", - __func__, err); - return err; - } - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - - usleep_range(100, 200); - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - - usleep_range(100, 200); - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - return 0; } static void tegra210_usb3_port_disable(struct tegra_xusb_port *port) { - struct tegra_xusb_usb3_port *usb3 = to_usb3_port(port); - struct tegra_xusb_padctl *padctl = port->padctl; - struct tegra_xusb_lane *lane = port->lane; - unsigned int index = port->index; - u32 value; - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - - usleep_range(100, 200); - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - - usleep_range(250, 350); - - value = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); - value |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_VCORE_DOWN(index); - padctl_writel(padctl, value, XUSB_PADCTL_ELPG_PROGRAM1); - - if (lane->pad == padctl->sata) - tegra210_sata_uphy_disable(padctl); - else - tegra210_pex_uphy_disable(padctl); - - regulator_disable(usb3->supply); - - value = padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); - value &= ~XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(index); - value |= XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(index, 0x7); - padctl_writel(padctl, value, XUSB_PADCTL_SS_PORT_MAP); } static const struct tegra_xusb_lane_map tegra210_usb3_map[] = { @@ -1933,6 +1994,24 @@ static const struct tegra_xusb_lane_map tegra210_usb3_map[] = { { 0, NULL, 0 } }; +static int tegra210_usb3_lane_map(struct tegra_xusb_lane *lane) +{ + const struct tegra_xusb_lane_map *map; + + for (map = tegra210_usb3_map; map->type; map++) { + if (map->index == lane->index && + strcmp(map->type, lane->pad->soc->name) == 0) { + dev_dbg(lane->pad->padctl->dev, + "lane = %s map to port = usb3-%d\n", + lane->pad->soc->lanes[lane->index].name, + map->port); + return map->port; + } + } + + return -1; +} + static struct tegra_xusb_lane * tegra210_usb3_port_map(struct tegra_xusb_port *port) { diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 2ea8497af82a..7fbba53f6097 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -370,7 +370,7 @@ static int tegra_xusb_setup_pads(struct tegra_xusb_padctl *padctl) return 0; } -static bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane, +bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane, const char *function) { const char *func = lane->soc->funcs[lane->function]; diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 093076ca27fd..1bfe14b2a274 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -127,6 +127,8 @@ struct tegra_xusb_lane_ops { void (*remove)(struct tegra_xusb_lane *lane); }; +bool tegra_xusb_lane_check(struct tegra_xusb_lane *lane, const char *function); + /* * pads */ From patchwork Fri Jun 14 07:46:52 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115800 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="DeMM9Zp+"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCPX2YdCz9sDB for ; Fri, 14 Jun 2019 17:47:28 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726432AbfFNHr2 (ORCPT ); Fri, 14 Jun 2019 03:47:28 -0400 Received: from hqemgate16.nvidia.com ([216.228.121.65]:19580 "EHLO hqemgate16.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726349AbfFNHr1 (ORCPT ); Fri, 14 Jun 2019 03:47:27 -0400 Received: from hqpgpgate102.nvidia.com (Not Verified[216.228.121.13]) by hqemgate16.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:47:25 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate102.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:47:25 -0700 X-PGP-Universal: processed; by hqpgpgate102.nvidia.com on Fri, 14 Jun 2019 00:47:25 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:47:23 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 4/8] phy: tegra: xusb: add sleepwalk and suspend/resume Date: Fri, 14 Jun 2019 15:46:52 +0800 Message-ID: <20190614074652.21960-5-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074652.21960-1-jckuo@nvidia.com> References: <20190614074652.21960-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498445; bh=ae8GRZ+AExarJlvCDBzEx/4oOa+upNZbT8vttOwLs2Q=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=DeMM9Zp+5vnjMfYsQiuoqsci+DCs8W0PAI1x6+KKLdyebM/Am43r8kjRmMO31wj60 QRqzG24OH53eGZIXQ6wUEIfodJRmsZeGuu3t5UKbe+XpzFx0iyxeETbFkkAd4YjLbM w2+qfp1aosdTMLyjtEHJ2uTOa9W9Qqi8ksjtdJtv/8MqjsA15rSneB7k6bd+mQDk6R egpM11Hj5FbbzTzqUQE6dK0odcTdkzDg9kZdQi8p4T7Z2VV+4IbnHiclFlMFMsr8gT L4uUOysC4cj8jrQcE2Pl+BTPjs4rouwlLGfWArfJZECI8GFOvpsU9RXvZ5qtZg6blr D9XVJ/xgOAngw== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org This commit adds sleepwalk/wake and suspend/resume interfaces to Tegra XUSB PHY driver. Signed-off-by: JC Kuo --- drivers/phy/tegra/xusb.c | 78 ++++++++++++++++++++++++++++++++++ drivers/phy/tegra/xusb.h | 8 ++++ include/linux/phy/tegra/xusb.h | 12 ++++++ 3 files changed, 98 insertions(+) diff --git a/drivers/phy/tegra/xusb.c b/drivers/phy/tegra/xusb.c index 7fbba53f6097..14461d59b947 100644 --- a/drivers/phy/tegra/xusb.c +++ b/drivers/phy/tegra/xusb.c @@ -975,10 +975,36 @@ static int tegra_xusb_padctl_remove(struct platform_device *pdev) return err; } +static int tegra_xusb_padctl_suspend_noirq(struct device *dev) +{ + struct tegra_xusb_padctl *padctl = dev_get_drvdata(dev); + + if (padctl->soc->ops->suspend_noirq) + return padctl->soc->ops->suspend_noirq(padctl); + + return 0; +} + +static int tegra_xusb_padctl_resume_noirq(struct device *dev) +{ + struct tegra_xusb_padctl *padctl = dev_get_drvdata(dev); + + if (padctl->soc->ops->resume_noirq) + return padctl->soc->ops->resume_noirq(padctl); + + return 0; +} + +static const struct dev_pm_ops tegra_xusb_padctl_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_xusb_padctl_suspend_noirq, + tegra_xusb_padctl_resume_noirq) +}; + static struct platform_driver tegra_xusb_padctl_driver = { .driver = { .name = "tegra-xusb-padctl", .of_match_table = tegra_xusb_padctl_of_match, + .pm = &tegra_xusb_padctl_pm_ops, }, .probe = tegra_xusb_padctl_probe, .remove = tegra_xusb_padctl_remove, @@ -1045,6 +1071,58 @@ int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl, } EXPORT_SYMBOL_GPL(tegra_xusb_padctl_hsic_set_idle); +int tegra_xusb_padctl_enable_phy_sleepwalk(struct tegra_xusb_padctl *padctl, + struct phy *phy, + enum usb_device_speed speed) +{ + if (padctl->soc->ops->phy_sleepwalk) + return padctl->soc->ops->phy_sleepwalk(padctl, phy, true, + speed); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_enable_phy_sleepwalk); + +int tegra_xusb_padctl_disable_phy_sleepwalk(struct tegra_xusb_padctl *padctl, + struct phy *phy) +{ + if (padctl->soc->ops->phy_sleepwalk) + return padctl->soc->ops->phy_sleepwalk(padctl, phy, false, 0); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_disable_phy_sleepwalk); + +int tegra_xusb_padctl_enable_phy_wake(struct tegra_xusb_padctl *padctl, + struct phy *phy) +{ + if (padctl->soc->ops->phy_wake) + return padctl->soc->ops->phy_wake(padctl, phy, true); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_enable_phy_wake); + +int tegra_xusb_padctl_disable_phy_wake(struct tegra_xusb_padctl *padctl, + struct phy *phy) +{ + if (padctl->soc->ops->phy_wake) + return padctl->soc->ops->phy_wake(padctl, phy, false); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_disable_phy_wake); + +int tegra_xusb_padctl_remote_wake_detected(struct tegra_xusb_padctl *padctl, + struct phy *phy) +{ + if (padctl->soc->ops->remote_wake_detected) + return padctl->soc->ops->remote_wake_detected(phy); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(tegra_xusb_padctl_remote_wake_detected); + int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, unsigned int port, bool enable) { diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h index 1bfe14b2a274..9482914536ac 100644 --- a/drivers/phy/tegra/xusb.h +++ b/drivers/phy/tegra/xusb.h @@ -11,6 +11,7 @@ #include #include +#include #include /* legacy entry points for backwards-compatibility */ @@ -368,12 +369,19 @@ struct tegra_xusb_padctl_ops { const struct tegra_xusb_padctl_soc *soc); void (*remove)(struct tegra_xusb_padctl *padctl); + int (*suspend_noirq)(struct tegra_xusb_padctl *padctl); + int (*resume_noirq)(struct tegra_xusb_padctl *padctl); int (*usb3_save_context)(struct tegra_xusb_padctl *padctl, unsigned int index); int (*hsic_set_idle)(struct tegra_xusb_padctl *padctl, unsigned int index, bool idle); int (*usb3_set_lfps_detect)(struct tegra_xusb_padctl *padctl, unsigned int index, bool enable); + int (*phy_sleepwalk)(struct tegra_xusb_padctl *padctl, struct phy *phy, + bool enable, enum usb_device_speed speed); + int (*phy_wake)(struct tegra_xusb_padctl *padctl, struct phy *phy, + bool enable); + int (*remote_wake_detected)(struct phy *phy); }; struct tegra_xusb_padctl_soc { diff --git a/include/linux/phy/tegra/xusb.h b/include/linux/phy/tegra/xusb.h index ee59562c8354..2fb12e3baaee 100644 --- a/include/linux/phy/tegra/xusb.h +++ b/include/linux/phy/tegra/xusb.h @@ -8,6 +8,7 @@ struct tegra_xusb_padctl; struct device; +enum usb_device_speed; struct tegra_xusb_padctl *tegra_xusb_padctl_get(struct device *dev); void tegra_xusb_padctl_put(struct tegra_xusb_padctl *padctl); @@ -18,5 +19,16 @@ int tegra_xusb_padctl_hsic_set_idle(struct tegra_xusb_padctl *padctl, unsigned int port, bool idle); int tegra_xusb_padctl_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, unsigned int port, bool enable); +int tegra_xusb_padctl_enable_phy_sleepwalk(struct tegra_xusb_padctl *padctl, + struct phy *phy, + enum usb_device_speed speed); +int tegra_xusb_padctl_disable_phy_sleepwalk(struct tegra_xusb_padctl *padctl, + struct phy *phy); +int tegra_xusb_padctl_enable_phy_wake(struct tegra_xusb_padctl *padctl, + struct phy *phy); +int tegra_xusb_padctl_disable_phy_wake(struct tegra_xusb_padctl *padctl, + struct phy *phy); +int tegra_xusb_padctl_remote_wake_detected(struct tegra_xusb_padctl *padctl, + struct phy *phy); #endif /* PHY_TEGRA_XUSB_H */ From patchwork Fri Jun 14 07:48:21 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115801 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="n6aQNDnV"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCQz3FZ0z9sND for ; Fri, 14 Jun 2019 17:48:43 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725886AbfFNHsm (ORCPT ); Fri, 14 Jun 2019 03:48:42 -0400 Received: from hqemgate16.nvidia.com ([216.228.121.65]:19679 "EHLO hqemgate16.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725812AbfFNHsl (ORCPT ); Fri, 14 Jun 2019 03:48:41 -0400 Received: from hqpgpgate102.nvidia.com (Not Verified[216.228.121.13]) by hqemgate16.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:48:38 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate102.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:48:37 -0700 X-PGP-Universal: processed; by hqpgpgate102.nvidia.com on Fri, 14 Jun 2019 00:48:37 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:48:35 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk Date: Fri, 14 Jun 2019 15:48:21 +0800 Message-ID: <20190614074824.22023-1-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498518; bh=wD5jeDhd4NbflTdBkrDxAUuRq7an+cRA/OcNQto7+KY=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: MIME-Version:X-Originating-IP:X-ClientProxiedBy:Content-Type; b=n6aQNDnVDs8iNHYAohUrxXPER0SAJPS1duN1DvT5RabmRFkn8zTE06Hj5cv0ye3eD FmNKvPm7J/vHD+RmZNSkG14maovaopSkW48h/+vKwHHbSwiBsWtZGn1xwEpfUkWkrT 6rH52gatIPE9hbkUWmMR9bBRh+i8mkp3d7a5gqR+uu2aLqv+ucbCmk+GWP0t1eASpW 97x7UCrDenE4W4xgyAdfIqOZEQeppeW0BC6vMxEZ0eQaW7el+nZzvu03aNihJM2XiQ iLFa9q4rarab6XbM36OSwMz3cjyJQl5oQdHRxl+vywa/v43mPKm9+NgxT1lKb4uJxn KnXoXbAe1lKyA== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org This commit implements Tegra210 PMC USB 2.0 (UTMI and HSIC) Sleepwalk programming sequence. With Sleepwalk enabled, XUSB host controller can be put into ELPG (Engine Level PowerGate) state when controller is idle to save power. The Sleepwalk logic is in charge of wake event detection and maintain resume signal accordingly till XUSB host controller is bring out of ELPG. Signed-off-by: JC Kuo --- drivers/soc/tegra/pmc.c | 462 ++++++++++++++++++++++++++++++++++++++++ include/soc/tegra/pmc.h | 13 ++ 2 files changed, 475 insertions(+) diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 8878720dd779..8a143cfc83b3 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -142,6 +142,142 @@ #define TEGRA_SMC_PMC_READ 0xaa #define TEGRA_SMC_PMC_WRITE 0xbb +/* USB2 SLEEPWALK registers */ +#define UTMIP(_port, _offset1, _offset2) \ + (((_port) <= 2) ? (_offset1) : (_offset2)) + +#define PMC_UTMIP_UHSIC_SLEEP_CFG(x) UTMIP(x, 0x1fc, 0x4d0) +#define UTMIP_MASTER_ENABLE(x) UTMIP(x, BIT(8 * (x)), BIT(0)) +#define UTMIP_FSLS_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 1), \ + BIT(1)) +#define UTMIP_PCTRL_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 2), \ + BIT(2)) +#define UTMIP_TCTRL_USE_PMC(x) UTMIP(x, BIT(8 * (x) + 3), \ + BIT(3)) +#define UTMIP_WAKE_VAL(_port, _value) (((_value) & 0xf) << \ + (UTMIP(_port, 8 * (_port) + 4, 4))) +#define UTMIP_WAKE_VAL_NONE(_port) UTMIP_WAKE_VAL(_port, 12) +#define UTMIP_WAKE_VAL_ANY(_port) UTMIP_WAKE_VAL(_port, 15) + +#define PMC_UTMIP_UHSIC_SLEEP_CFG1 (0x4d0) +#define UTMIP_RPU_SWITC_LOW_USE_PMC_PX(x) BIT((x) + 8) +#define UTMIP_RPD_CTRL_USE_PMC_PX(x) BIT((x) + 16) + +#define PMC_UTMIP_MASTER_CONFIG (0x274) +#define UTMIP_PWR(x) UTMIP(x, BIT(x), BIT(4)) +#define UHSIC_PWR(x) BIT(3) + +#define PMC_USB_DEBOUNCE_DEL (0xec) +#define DEBOUNCE_VAL(x) (((x) & 0xffff) << 0) +#define UTMIP_LINE_DEB_CNT(x) (((x) & 0xf) << 16) +#define UHSIC_LINE_DEB_CNT(x) (((x) & 0xf) << 20) + +#define PMC_UTMIP_UHSIC_FAKE(x) UTMIP(x, 0x218, 0x294) +#define UTMIP_FAKE_USBOP_VAL(x) UTMIP(x, BIT(4 * (x)), BIT(8)) +#define UTMIP_FAKE_USBON_VAL(x) UTMIP(x, BIT(4 * (x) + 1), \ + BIT(9)) +#define UTMIP_FAKE_USBOP_EN(x) UTMIP(x, BIT(4 * (x) + 2), \ + BIT(10)) +#define UTMIP_FAKE_USBON_EN(x) UTMIP(x, BIT(4 * (x) + 3), \ + BIT(11)) + +#define PMC_UTMIP_UHSIC_SLEEPWALK_CFG(x) UTMIP(x, 0x200, 0x288) +#define UTMIP_LINEVAL_WALK_EN(x) UTMIP(x, BIT(8 * (x) + 7), \ + BIT(15)) + +#define PMC_USB_AO (0xf0) +#define USBOP_VAL_PD(x) UTMIP(x, BIT(4 * (x)), BIT(20)) +#define USBON_VAL_PD(x) UTMIP(x, BIT(4 * (x) + 1), \ + BIT(21)) +#define STROBE_VAL_PD(x) BIT(12) +#define DATA0_VAL_PD(x) BIT(13) +#define DATA1_VAL_PD BIT(24) + +#define PMC_UTMIP_UHSIC_SAVED_STATE(x) UTMIP(x, 0x1f0, 0x280) +#define SPEED(_port, _value) (((_value) & 0x3) << \ + (UTMIP(_port, 8 * (_port), 8))) +#define UTMI_HS(_port) SPEED(_port, 0) +#define UTMI_FS(_port) SPEED(_port, 1) +#define UTMI_LS(_port) SPEED(_port, 2) +#define UTMI_RST(_port) SPEED(_port, 3) + +#define PMC_UTMIP_UHSIC_TRIGGERS (0x1ec) +#define UTMIP_CLR_WALK_PTR(x) UTMIP(x, BIT(x), BIT(16)) +#define UTMIP_CAP_CFG(x) UTMIP(x, BIT((x) + 4), BIT(17)) +#define UTMIP_CLR_WAKE_ALARM(x) UTMIP(x, BIT((x) + 12), \ + BIT(19)) +#define UHSIC_CLR_WALK_PTR BIT(3) +#define UHSIC_CLR_WAKE_ALARM BIT(15) + +#define PMC_UTMIP_SLEEPWALK_PX(x) UTMIP(x, 0x204 + (4 * (x)), \ + 0x4e0) +/* phase A */ +#define UTMIP_USBOP_RPD_A BIT(0) +#define UTMIP_USBON_RPD_A BIT(1) +#define UTMIP_AP_A BIT(4) +#define UTMIP_AN_A BIT(5) +#define UTMIP_HIGHZ_A BIT(6) +/* phase B */ +#define UTMIP_USBOP_RPD_B BIT(8) +#define UTMIP_USBON_RPD_B BIT(9) +#define UTMIP_AP_B BIT(12) +#define UTMIP_AN_B BIT(13) +#define UTMIP_HIGHZ_B BIT(14) +/* phase C */ +#define UTMIP_USBOP_RPD_C BIT(16) +#define UTMIP_USBON_RPD_C BIT(17) +#define UTMIP_AP_C BIT(20) +#define UTMIP_AN_C BIT(21) +#define UTMIP_HIGHZ_C BIT(22) +/* phase D */ +#define UTMIP_USBOP_RPD_D BIT(24) +#define UTMIP_USBON_RPD_D BIT(25) +#define UTMIP_AP_D BIT(28) +#define UTMIP_AN_D BIT(29) +#define UTMIP_HIGHZ_D BIT(30) + +#define PMC_UTMIP_UHSIC_LINE_WAKEUP (0x26c) +#define UTMIP_LINE_WAKEUP_EN(x) UTMIP(x, BIT(x), BIT(4)) +#define UHSIC_LINE_WAKEUP_EN BIT(3) + +#define PMC_UTMIP_TERM_PAD_CFG (0x1f8) +#define PCTRL_VAL(x) (((x) & 0x3f) << 1) +#define TCTRL_VAL(x) (((x) & 0x3f) << 7) + +#define PMC_UTMIP_PAD_CFGX(x) (0x4c0 + (4 * (x))) +#define RPD_CTRL_PX(x) (((x) & 0x1f) << 22) + +#define PMC_UHSIC_SLEEP_CFG PMC_UTMIP_UHSIC_SLEEP_CFG(0) +#define UHSIC_MASTER_ENABLE BIT(24) +#define UHSIC_WAKE_VAL(_value) (((_value) & 0xf) << 28) +#define UHSIC_WAKE_VAL_SD10 UHSIC_WAKE_VAL(2) +#define UHSIC_WAKE_VAL_NONE UHSIC_WAKE_VAL(12) + +#define PMC_UHSIC_FAKE PMC_UTMIP_UHSIC_FAKE(0) +#define UHSIC_FAKE_STROBE_VAL BIT(12) +#define UHSIC_FAKE_DATA_VAL BIT(13) +#define UHSIC_FAKE_STROBE_EN BIT(14) +#define UHSIC_FAKE_DATA_EN BIT(15) + +#define PMC_UHSIC_SAVED_STATE PMC_UTMIP_UHSIC_SAVED_STATE(0) +#define UHSIC_MODE(_value) (((_value) & 0x1) << 24) +#define UHSIC_HS UHSIC_MODE(0) +#define UHSIC_RST UHSIC_MODE(1) + +#define PMC_UHSIC_SLEEPWALK_CFG PMC_UTMIP_UHSIC_SLEEPWALK_CFG(0) +#define UHSIC_WAKE_WALK_EN BIT(30) +#define UHSIC_LINEVAL_WALK_EN BIT(31) + +#define PMC_UHSIC_SLEEPWALK_P0 (0x210) +#define UHSIC_DATA0_RPD_A BIT(1) +#define UHSIC_DATA0_RPU_B BIT(11) +#define UHSIC_DATA0_RPU_C BIT(19) +#define UHSIC_DATA0_RPU_D BIT(27) +#define UHSIC_STROBE_RPU_A BIT(2) +#define UHSIC_STROBE_RPD_B BIT(8) +#define UHSIC_STROBE_RPD_C BIT(16) +#define UHSIC_STROBE_RPD_D BIT(24) + struct tegra_powergate { struct generic_pm_domain genpd; struct tegra_pmc *pmc; @@ -689,6 +825,332 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain) return err; } +/* T210 USB2 SLEEPWALK APIs */ +int tegra_pmc_utmi_phy_enable_sleepwalk(int port, enum usb_device_speed speed, + struct tegra_utmi_pad_config *config) +{ + u32 reg; + + pr_debug("PMC %s : port %d, speed %d\n", __func__, port, speed); + + /* ensure sleepwalk logic is disabled */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~UTMIP_MASTER_ENABLE(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + /* ensure sleepwalk logics are in low power mode */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_MASTER_CONFIG); + reg |= UTMIP_PWR(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_MASTER_CONFIG); + + /* set debounce time */ + reg = tegra_pmc_readl(pmc, PMC_USB_DEBOUNCE_DEL); + reg &= ~UTMIP_LINE_DEB_CNT(~0); + reg |= UTMIP_LINE_DEB_CNT(0x1); + tegra_pmc_writel(pmc, reg, PMC_USB_DEBOUNCE_DEL); + + /* ensure fake events of sleepwalk logic are desiabled */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_FAKE(port)); + reg &= ~(UTMIP_FAKE_USBOP_VAL(port) | UTMIP_FAKE_USBON_VAL(port) | + UTMIP_FAKE_USBOP_EN(port) | UTMIP_FAKE_USBON_EN(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_FAKE(port)); + + /* ensure wake events of sleepwalk logic are not latched */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg &= ~UTMIP_LINE_WAKEUP_EN(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + /* disable wake event triggers of sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~UTMIP_WAKE_VAL(port, ~0); + reg |= UTMIP_WAKE_VAL_NONE(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + /* power down the line state detectors of the pad */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port)); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + /* save state per speed */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SAVED_STATE(port)); + reg &= ~SPEED(port, ~0); + if (speed == USB_SPEED_HIGH) + reg |= UTMI_HS(port); + else if (speed == USB_SPEED_FULL) + reg |= UTMI_FS(port); + else if (speed == USB_SPEED_LOW) + reg |= UTMI_LS(port); + else + reg |= UTMI_RST(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SAVED_STATE(port)); + + /* enable the trigger of the sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port)); + reg |= UTMIP_LINEVAL_WALK_EN(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEPWALK_CFG(port)); + + /* reset the walk pointer and clear the alarm of the sleepwalk logic, + * as well as capture the configuration of the USB2.0 pad + */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS); + reg |= (UTMIP_CLR_WALK_PTR(port) | UTMIP_CLR_WAKE_ALARM(port) | + UTMIP_CAP_CFG(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS); + + /* program electrical parameters read from XUSB PADCTL */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_TERM_PAD_CFG); + reg &= ~(TCTRL_VAL(~0) | PCTRL_VAL(~0)); + reg |= (TCTRL_VAL(config->tctrl) | PCTRL_VAL(config->pctrl)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_TERM_PAD_CFG); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_PAD_CFGX(port)); + reg &= ~RPD_CTRL_PX(~0); + reg |= RPD_CTRL_PX(config->rpd_ctrl); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_PAD_CFGX(port)); + + /* setup the pull-ups and pull-downs of the signals during the four + * stages of sleepwalk. + * if device is connected, program sleepwalk logic to maintain a J and + * keep driving K upon seeing remote wake. + */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_SLEEPWALK_PX(port)); + reg = (UTMIP_USBOP_RPD_A | UTMIP_USBOP_RPD_B | UTMIP_USBOP_RPD_C | + UTMIP_USBOP_RPD_D); + reg |= (UTMIP_USBON_RPD_A | UTMIP_USBON_RPD_B | UTMIP_USBON_RPD_C | + UTMIP_USBON_RPD_D); + if (speed == USB_SPEED_UNKNOWN) { + reg |= (UTMIP_HIGHZ_A | UTMIP_HIGHZ_B | UTMIP_HIGHZ_C | + UTMIP_HIGHZ_D); + } else if ((speed == USB_SPEED_HIGH) || (speed == USB_SPEED_FULL)) { + /* J state: D+/D- = high/low, K state: D+/D- = low/high */ + reg |= UTMIP_HIGHZ_A; + reg |= UTMIP_AP_A; + reg |= (UTMIP_AN_B | UTMIP_AN_C | UTMIP_AN_D); + } else if (speed == USB_SPEED_LOW) { + /* J state: D+/D- = low/high, K state: D+/D- = high/low */ + reg |= UTMIP_HIGHZ_A; + reg |= UTMIP_AN_A; + reg |= (UTMIP_AP_B | UTMIP_AP_C | UTMIP_AP_D); + } + tegra_pmc_writel(pmc, reg, PMC_UTMIP_SLEEPWALK_PX(port)); + + /* power up the line state detectors of the pad */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg &= ~(USBOP_VAL_PD(port) | USBON_VAL_PD(port)); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + usleep_range(50, 100); + + /* switch the electric control of the USB2.0 pad to PMC */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg |= (UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) | + UTMIP_TCTRL_USE_PMC(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG1); + reg |= (UTMIP_RPD_CTRL_USE_PMC_PX(port) | + UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG1); + + /* set the wake signaling trigger events */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~UTMIP_WAKE_VAL(port, ~0); + reg |= UTMIP_WAKE_VAL_ANY(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + /* enable the wake detection */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg |= UTMIP_MASTER_ENABLE(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg |= UTMIP_LINE_WAKEUP_EN(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + return 0; +} +EXPORT_SYMBOL(tegra_pmc_utmi_phy_enable_sleepwalk); + +int tegra_pmc_utmi_phy_disable_sleepwalk(int port) +{ + u32 reg; + + pr_debug("PMC %s : port %d\n", __func__, port); + + /* disable the wake detection */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~UTMIP_MASTER_ENABLE(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg &= ~UTMIP_LINE_WAKEUP_EN(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + /* switch the electric control of the USB2.0 pad to XUSB or USB2 */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~(UTMIP_FSLS_USE_PMC(port) | UTMIP_PCTRL_USE_PMC(port) | + UTMIP_TCTRL_USE_PMC(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG1); + reg &= ~(UTMIP_RPD_CTRL_USE_PMC_PX(port) | + UTMIP_RPU_SWITC_LOW_USE_PMC_PX(port)); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG1); + + /* disable wake event triggers of sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + reg &= ~UTMIP_WAKE_VAL(port, ~0); + reg |= UTMIP_WAKE_VAL_NONE(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_SLEEP_CFG(port)); + + /* power down the line state detectors of the port */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg |= (USBOP_VAL_PD(port) | USBON_VAL_PD(port)); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + /* clear alarm of the sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS); + reg |= UTMIP_CLR_WAKE_ALARM(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS); + + return 0; +} +EXPORT_SYMBOL(tegra_pmc_utmi_phy_disable_sleepwalk); + +int tegra_pmc_hsic_phy_enable_sleepwalk(int port) +{ + u32 reg; + + pr_debug("PMC %s : port %dn", __func__, port); + + /* ensure sleepwalk logic is disabled */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg &= ~UHSIC_MASTER_ENABLE; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + /* ensure sleepwalk logics are in low power mode */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_MASTER_CONFIG); + reg |= UHSIC_PWR(port); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_MASTER_CONFIG); + + /* set debounce time */ + reg = tegra_pmc_readl(pmc, PMC_USB_DEBOUNCE_DEL); + reg &= ~UHSIC_LINE_DEB_CNT(~0); + reg |= UHSIC_LINE_DEB_CNT(0x1); + tegra_pmc_writel(pmc, reg, PMC_USB_DEBOUNCE_DEL); + + /* ensure fake events of sleepwalk logic are desiabled */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_FAKE); + reg &= ~(UHSIC_FAKE_STROBE_VAL | UHSIC_FAKE_DATA_VAL | + UHSIC_FAKE_STROBE_EN | UHSIC_FAKE_DATA_EN); + tegra_pmc_writel(pmc, reg, PMC_UHSIC_FAKE); + + /* ensure wake events of sleepwalk logic are not latched */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg &= ~UHSIC_LINE_WAKEUP_EN; + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + /* disable wake event triggers of sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg &= ~UHSIC_WAKE_VAL(~0); + reg |= UHSIC_WAKE_VAL_NONE; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + /* power down the line state detectors of the port */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + /* save state, HSIC always comes up as HS */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SAVED_STATE); + reg &= ~UHSIC_MODE(~0); + reg |= UHSIC_HS; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SAVED_STATE); + + /* enable the trigger of the sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEPWALK_CFG); + reg |= (UHSIC_WAKE_WALK_EN | UHSIC_LINEVAL_WALK_EN); + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEPWALK_CFG); + + /* reset the walk pointer and clear the alarm of the sleepwalk logic, + * as well as capture the configuration of the USB2.0 port + */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS); + reg |= (UHSIC_CLR_WALK_PTR | UHSIC_CLR_WAKE_ALARM); + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS); + + /* setup the pull-ups and pull-downs of the signals during the four + * stages of sleepwalk. + * maintain a HSIC IDLE and keep driving HSIC RESUME upon remote wake + */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEPWALK_P0); + reg = (UHSIC_DATA0_RPD_A | UHSIC_DATA0_RPU_B | UHSIC_DATA0_RPU_C | + UHSIC_DATA0_RPU_D); + reg |= (UHSIC_STROBE_RPU_A | UHSIC_STROBE_RPD_B | UHSIC_STROBE_RPD_C | + UHSIC_STROBE_RPD_D); + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEPWALK_P0); + + /* power up the line state detectors of the port */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg &= ~(STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + usleep_range(50, 100); + + /* set the wake signaling trigger events */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg &= ~UHSIC_WAKE_VAL(~0); + reg |= UHSIC_WAKE_VAL_SD10; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + /* enable the wake detection */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg |= UHSIC_MASTER_ENABLE; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg |= UHSIC_LINE_WAKEUP_EN; + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + return 0; +} +EXPORT_SYMBOL(tegra_pmc_hsic_phy_enable_sleepwalk); + +int tegra_pmc_hsic_phy_disable_sleepwalk(int port) +{ + u32 reg; + + pr_debug("PMC %s : port %dn", __func__, port); + + /* disable the wake detection */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg &= ~UHSIC_MASTER_ENABLE; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_LINE_WAKEUP); + reg &= ~UHSIC_LINE_WAKEUP_EN; + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_LINE_WAKEUP); + + /* disable wake event triggers of sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UHSIC_SLEEP_CFG); + reg &= ~UHSIC_WAKE_VAL(~0); + reg |= UHSIC_WAKE_VAL_NONE; + tegra_pmc_writel(pmc, reg, PMC_UHSIC_SLEEP_CFG); + + /* power down the line state detectors of the port */ + reg = tegra_pmc_readl(pmc, PMC_USB_AO); + reg |= (STROBE_VAL_PD(port) | DATA0_VAL_PD(port) | DATA1_VAL_PD); + tegra_pmc_writel(pmc, reg, PMC_USB_AO); + + /* clear alarm of the sleepwalk logic */ + reg = tegra_pmc_readl(pmc, PMC_UTMIP_UHSIC_TRIGGERS); + reg |= UHSIC_CLR_WAKE_ALARM; + tegra_pmc_writel(pmc, reg, PMC_UTMIP_UHSIC_TRIGGERS); + + return 0; +} +EXPORT_SYMBOL(tegra_pmc_hsic_phy_disable_sleepwalk); + /** * tegra_powergate_power_on() - power on partition * @id: partition ID diff --git a/include/soc/tegra/pmc.h b/include/soc/tegra/pmc.h index 57e58faf660b..987109e79da9 100644 --- a/include/soc/tegra/pmc.h +++ b/include/soc/tegra/pmc.h @@ -11,6 +11,7 @@ #define __SOC_TEGRA_PMC_H__ #include +#include #include @@ -171,6 +172,18 @@ enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void); void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode); void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode); +/* T210 USB2 SLEEPWALK APIs */ +struct tegra_utmi_pad_config { + u32 tctrl; + u32 pctrl; + u32 rpd_ctrl; +}; +int tegra_pmc_utmi_phy_enable_sleepwalk(int port, enum usb_device_speed speed, + struct tegra_utmi_pad_config *config); +int tegra_pmc_utmi_phy_disable_sleepwalk(int port); +int tegra_pmc_hsic_phy_enable_sleepwalk(int port); +int tegra_pmc_hsic_phy_disable_sleepwalk(int port); + #else static inline int tegra_powergate_power_on(unsigned int id) { From patchwork Fri Jun 14 07:48:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115802 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="aJCboY/M"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCR02f4Jz9sNk for ; Fri, 14 Jun 2019 17:48:44 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725985AbfFNHsn (ORCPT ); Fri, 14 Jun 2019 03:48:43 -0400 Received: from hqemgate15.nvidia.com ([216.228.121.64]:12204 "EHLO hqemgate15.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725836AbfFNHsm (ORCPT ); Fri, 14 Jun 2019 03:48:42 -0400 Received: from hqpgpgate102.nvidia.com (Not Verified[216.228.121.13]) by hqemgate15.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:48:40 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate102.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:48:40 -0700 X-PGP-Universal: processed; by hqpgpgate102.nvidia.com on Fri, 14 Jun 2019 00:48:40 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:48:38 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk Date: Fri, 14 Jun 2019 15:48:22 +0800 Message-ID: <20190614074824.22023-2-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074824.22023-1-jckuo@nvidia.com> References: <20190614074824.22023-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498520; bh=OW+2qAuZRaJiekkHaO4wJGZdUR5W1NVzsiLy+GmsDOc=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=aJCboY/M3U0C/dw1CJ0vcyPtU9qak+v4hPDqHA1GnClu19oTEOSBPxYVXd0X7HSkO SpnWfyAlAQgZmBR8N5Ab2+fBJTCNCJUdlhPZWDYaPeKM8DhKdAPp/YjyimIRib9MLx E1I53OFCvYFnN+tbJwySIjiTQTuzFvtsAp42B7JOFVL+3yZarnLQ65QO8vjkUXOuTk 5zm82er6C+5u5tnPFkc8resjua1Ey/eyVMAWK046gMsCbtddO6IiBx+PCUe2jQPYow UV6QeyoGoArfnOIFuJ9W5kocK1CIOSBVqN65pl43GRUCtd153Y281+kWTs1XMYPPNn MWLyMoJOhCmWg== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org This commit implements Tegra210 XUSB PADCTL wake and sleepwalk routines. Signed-off-by: JC Kuo --- drivers/phy/tegra/xusb-tegra210.c | 574 ++++++++++++++++++++++++++++-- 1 file changed, 548 insertions(+), 26 deletions(-) diff --git a/drivers/phy/tegra/xusb-tegra210.c b/drivers/phy/tegra/xusb-tegra210.c index 007bf352b45e..afa4bfa23be8 100644 --- a/drivers/phy/tegra/xusb-tegra210.c +++ b/drivers/phy/tegra/xusb-tegra210.c @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -48,6 +49,20 @@ #define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP_MASK(x) (0x7 << ((x) * 5)) #define XUSB_PADCTL_SS_PORT_MAP_PORTX_MAP(x, v) (((v) & 0x7) << ((x) * 5)) +#define XUSB_PADCTL_ELPG_PROGRAM_0 0x20 +#define USB2_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x)) +#define USB2_PORT_WAKEUP_EVENT(x) BIT((x) + 7) +#define SS_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 14) +#define SS_PORT_WAKEUP_EVENT(x) BIT((x) + 21) +#define USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(x) BIT((x) + 28) +#define USB2_HSIC_PORT_WAKEUP_EVENT(x) BIT((x) + 30) +#define ALL_WAKE_EVENTS ( \ + USB2_PORT_WAKEUP_EVENT(0) | USB2_PORT_WAKEUP_EVENT(1) | \ + USB2_PORT_WAKEUP_EVENT(2) | USB2_PORT_WAKEUP_EVENT(3) | \ + SS_PORT_WAKEUP_EVENT(0) | SS_PORT_WAKEUP_EVENT(1) | \ + SS_PORT_WAKEUP_EVENT(2) | SS_PORT_WAKEUP_EVENT(3) | \ + USB2_HSIC_PORT_WAKEUP_EVENT(0)) + #define XUSB_PADCTL_ELPG_PROGRAM1 0x024 #define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_VCORE_DOWN (1 << 31) #define XUSB_PADCTL_ELPG_PROGRAM1_AUX_MUX_LP0_CLAMP_EN_EARLY (1 << 30) @@ -81,6 +96,8 @@ #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR (1 << 2) #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DISC_OVRD (1 << 1) #define XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_CHRP_OVRD (1 << 0) +#define RPD_CTRL(x) (((x) & 0x1f) << 26) +#define RPD_CTRL_VALUE(x) (((x) >> 26) & 0x1f) #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0 0x284 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD (1 << 11) @@ -99,6 +116,8 @@ #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_SHIFT 12 #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK 0x7f #define XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_VAL 0x1e +#define TCTRL_VALUE(x) (((x) & 0x3f) >> 0) +#define PCTRL_VALUE(x) (((x) >> 6) & 0x3f) #define XUSB_PADCTL_HSIC_PADX_CTL0(x) (0x300 + (x) * 0x20) #define XUSB_PADCTL_HSIC_PAD_CTL0_RPU_STROBE (1 << 18) @@ -228,10 +247,18 @@ struct tegra210_xusb_fuse_calibration { u32 rpd_ctrl; }; +struct tegra210_xusb_padctl_context { + u32 usb2_pad_mux; + u32 usb2_port_cap; + u32 ss_port_map; + u32 usb3_pad_mux; +}; + struct tegra210_xusb_padctl { struct tegra_xusb_padctl base; - + struct tegra_utmi_pad_config utmi_pad_cfg; struct tegra210_xusb_fuse_calibration fuse; + struct tegra210_xusb_padctl_context context; }; static inline struct tegra210_xusb_padctl * @@ -868,6 +895,317 @@ static int tegra210_hsic_set_idle(struct tegra_xusb_padctl *padctl, return 0; } +static int tegra210_usb3_phy_enable_sleepwalk(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + int index = tegra210_usb3_lane_map(lane); + struct device *dev = padctl->dev; + u32 reg; + + if (index < 0) { + dev_err(dev, "invalid usb3 port number %d\n", index); + return -EINVAL; + } + + dev_dbg(dev, "phy enable sleepwalk on usb3-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + reg |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + reg |= XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(250, 350); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static void tegra210_utmi_phy_get_pad_config( + struct tegra_xusb_padctl *padctl, + int port, struct tegra_utmi_pad_config *config) +{ + u32 reg; + + reg = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); + config->tctrl = TCTRL_VALUE(reg); + config->pctrl = PCTRL_VALUE(reg); + + reg = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(port)); + config->rpd_ctrl = RPD_CTRL_VALUE(reg); +} + +static int tegra210_usb3_phy_disable_sleepwalk(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + int index = tegra210_usb3_lane_map(lane); + struct device *dev = padctl->dev; + u32 reg; + + if (index < 0) { + dev_err(dev, "invalid usb3 port number %d\n", index); + return -EINVAL; + } + + dev_dbg(dev, "phy disable sleepwalk on usb3-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + reg &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN_EARLY(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1); + + usleep_range(100, 200); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM1); + reg &= ~XUSB_PADCTL_ELPG_PROGRAM1_SSPX_ELPG_CLAMP_EN(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM1); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_usb3_phy_enable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + int index = tegra210_usb3_lane_map(lane); + struct device *dev = padctl->dev; + u32 reg; + + if (index < 0) { + dev_err(dev, "invalid usb3 port number %d\n", index); + return -EINVAL; + } + + dev_dbg(dev, "phy enable wake on usb3-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= SS_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= SS_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_usb3_phy_disable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + int index = tegra210_usb3_lane_map(lane); + struct device *dev = padctl->dev; + u32 reg; + + if (index < 0) { + dev_err(dev, "invalid usb3 port number %d\n", index); + return -EINVAL; + } + + dev_dbg(dev, "phy disable wake on usb3-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg &= ~SS_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= SS_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_utmi_phy_enable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 reg; + + dev_dbg(dev, "phy enable wake on usb2-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_utmi_phy_disable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 reg; + + dev_dbg(dev, "phy disable wake on usb2-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg &= ~USB2_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_hsic_phy_enable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 reg; + + dev_dbg(dev, "phy enable wake on hsic-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_HSIC_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_hsic_phy_disable_wake(struct phy *phy) +{ + struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct device *dev = padctl->dev; + u32 reg; + + dev_dbg(dev, "phy disable wake on hsic-%d\n", index); + + mutex_lock(&padctl->lock); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg &= ~USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + usleep_range(10, 20); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + reg &= ~ALL_WAKE_EVENTS; + reg |= USB2_HSIC_PORT_WAKEUP_EVENT(index); + padctl_writel(padctl, reg, XUSB_PADCTL_ELPG_PROGRAM_0); + + mutex_unlock(&padctl->lock); + + return 0; +} + +static int tegra210_usb3_phy_remote_wake_detected( + struct tegra_xusb_padctl *padctl, int port) +{ + u32 reg; + + if (port < 0) { + dev_err(padctl->dev, "invalid usb3 port number %d\n", + port); + return false; + } + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + if ((reg & SS_PORT_WAKE_INTERRUPT_ENABLE(port)) && + (reg & SS_PORT_WAKEUP_EVENT(port))) + return true; + else + return false; +} + +static int tegra210_utmi_phy_remote_wake_detected( + struct tegra_xusb_padctl *padctl, int port) +{ + u32 reg; + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + if ((reg & USB2_PORT_WAKE_INTERRUPT_ENABLE(port)) && + (reg & USB2_PORT_WAKEUP_EVENT(port))) + return true; + else + return false; +} + +static int tegra210_hsic_phy_remote_wake_detected( + struct tegra_xusb_padctl *padctl, int port) +{ + u32 reg; + + dev_dbg(padctl->dev, "hsic-%d remote wake detected\n", port); + + reg = padctl_readl(padctl, XUSB_PADCTL_ELPG_PROGRAM_0); + if ((reg & USB2_HSIC_PORT_WAKE_INTERRUPT_ENABLE(port)) && + (reg & USB2_HSIC_PORT_WAKEUP_EVENT(port))) + return true; + else + return false; +} + static int tegra210_usb3_set_lfps_detect(struct tegra_xusb_padctl *padctl, unsigned int index, bool enable) { @@ -970,8 +1308,23 @@ static int tegra210_usb2_phy_init(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct tegra_xusb_usb2_port *port; + int err; u32 value; + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + err = regulator_enable(port->supply); + if (err) + return err; + + mutex_lock(&padctl->lock); + value = padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); value &= ~(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK << XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT); @@ -979,12 +1332,28 @@ static int tegra210_usb2_phy_init(struct phy *phy) XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_SHIFT; padctl_writel(padctl, value, XUSB_PADCTL_USB2_PAD_MUX); + mutex_unlock(&padctl->lock); + return tegra210_xusb_padctl_enable(padctl); } static int tegra210_usb2_phy_exit(struct phy *phy) { struct tegra_xusb_lane *lane = phy_get_drvdata(phy); + struct tegra_xusb_padctl *padctl = lane->pad->padctl; + unsigned int index = lane->index; + struct tegra_xusb_usb2_port *port; + int err; + + port = tegra_xusb_find_usb2_port(padctl, index); + if (!port) { + dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); + return -ENODEV; + } + + err = regulator_disable(port->supply); + if (err) + return err; return tegra210_xusb_padctl_disable(lane->pad->padctl); } @@ -996,19 +1365,14 @@ static int tegra210_usb2_phy_power_on(struct phy *phy) struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); struct tegra_xusb_padctl *padctl = lane->pad->padctl; struct tegra210_xusb_padctl *priv; - struct tegra_xusb_usb2_port *port; unsigned int index = lane->index; u32 value; int err; - port = tegra_xusb_find_usb2_port(padctl, index); - if (!port) { - dev_err(&phy->dev, "no port found for USB2 lane %u\n", index); - return -ENODEV; - } - priv = to_tegra210_xusb_padctl(padctl); + mutex_lock(&padctl->lock); + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_MASK << XUSB_PADCTL_USB2_BIAS_PAD_CTL0_HS_SQUELCH_LEVEL_SHIFT) | @@ -1062,12 +1426,6 @@ static int tegra210_usb2_phy_power_on(struct phy *phy) padctl_writel(padctl, value, XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADX_CTL1(index)); - err = regulator_enable(port->supply); - if (err) - return err; - - mutex_lock(&padctl->lock); - if (pad->enable > 0) { pad->enable++; mutex_unlock(&padctl->lock); @@ -1076,7 +1434,7 @@ static int tegra210_usb2_phy_power_on(struct phy *phy) err = clk_prepare_enable(pad->clk); if (err) - goto disable_regulator; + goto out; value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL1); value &= ~((XUSB_PADCTL_USB2_BIAS_PAD_CTL1_TRK_START_TIMER_MASK << @@ -1108,8 +1466,7 @@ static int tegra210_usb2_phy_power_on(struct phy *phy) return 0; -disable_regulator: - regulator_disable(port->supply); +out: mutex_unlock(&padctl->lock); return err; } @@ -1119,16 +1476,9 @@ static int tegra210_usb2_phy_power_off(struct phy *phy) struct tegra_xusb_lane *lane = phy_get_drvdata(phy); struct tegra_xusb_usb2_pad *pad = to_usb2_pad(lane->pad); struct tegra_xusb_padctl *padctl = lane->pad->padctl; - struct tegra_xusb_usb2_port *port; + unsigned int index = lane->index; u32 value; - port = tegra_xusb_find_usb2_port(padctl, lane->index); - if (!port) { - dev_err(&phy->dev, "no port found for USB2 lane %u\n", - lane->index); - return -ENODEV; - } - mutex_lock(&padctl->lock); if (WARN_ON(pad->enable == 0)) @@ -1137,12 +1487,19 @@ static int tegra210_usb2_phy_power_off(struct phy *phy) if (--pad->enable > 0) goto out; + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL0_PD; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL0(index)); + + value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value |= XUSB_PADCTL_USB2_OTG_PAD_CTL1_PD_DR; + padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index)); + value = padctl_readl(padctl, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); value |= XUSB_PADCTL_USB2_BIAS_PAD_CTL0_PD; padctl_writel(padctl, value, XUSB_PADCTL_USB2_BIAS_PAD_CTL0); out: - regulator_disable(port->supply); mutex_unlock(&padctl->lock); return 0; } @@ -1863,6 +2220,114 @@ static const struct phy_ops tegra210_sata_phy_ops = { .owner = THIS_MODULE, }; +static inline bool is_usb3_phy(struct phy *phy) +{ + return (phy->ops == &tegra210_pcie_phy_ops || + phy->ops == &tegra210_sata_phy_ops); +} + +static inline bool is_hsic_phy(struct phy *phy) +{ + return phy->ops == &tegra210_hsic_phy_ops; +} + +static inline bool is_utmi_phy(struct phy *phy) +{ + return phy->ops == &tegra210_usb2_phy_ops; +} + +static int tegra210_xusb_padctl_phy_wake(struct tegra_xusb_padctl *padctl, + struct phy *phy, bool enable) +{ + if (!phy) + return 0; + + if (is_usb3_phy(phy)) { + if (enable) + return tegra210_usb3_phy_enable_wake(phy); + else + return tegra210_usb3_phy_disable_wake(phy); + } else if (is_utmi_phy(phy)) { + if (enable) + return tegra210_utmi_phy_enable_wake(phy); + else + return tegra210_utmi_phy_disable_wake(phy); + } else if (is_hsic_phy(phy)) { + if (enable) + return tegra210_hsic_phy_enable_wake(phy); + else + return tegra210_hsic_phy_disable_wake(phy); + } else + return -EINVAL; + + return 0; +} + +int tegra210_xusb_padctl_remote_wake_detected(struct phy *phy) +{ + struct tegra_xusb_lane *lane; + struct tegra_xusb_padctl *padctl; + + if (!phy) + return 0; + + lane = phy_get_drvdata(phy); + padctl = lane->pad->padctl; + + if (is_utmi_phy(phy)) + return tegra210_utmi_phy_remote_wake_detected(padctl, + lane->index); + else if (is_hsic_phy(phy)) + return tegra210_hsic_phy_remote_wake_detected(padctl, + lane->index); + else if (is_usb3_phy(phy)) + return tegra210_usb3_phy_remote_wake_detected(padctl, + tegra210_usb3_lane_map(lane)); + + return -EINVAL; +} + +static int tegra210_xusb_padctl_phy_sleepwalk(struct tegra_xusb_padctl *padctl, + struct phy *phy, bool enable, + enum usb_device_speed speed) +{ + struct tegra210_xusb_padctl *priv; + struct tegra_xusb_lane *lane; + + if (!phy) + return 0; + + priv = to_tegra210_xusb_padctl(padctl); + lane = phy_get_drvdata(phy); + + if (is_usb3_phy(phy)) { + if (enable) + return tegra210_usb3_phy_enable_sleepwalk(phy); + else + return tegra210_usb3_phy_disable_sleepwalk(phy); + } else if (is_utmi_phy(phy)) { + tegra210_utmi_phy_get_pad_config(padctl, lane->index, + &priv->utmi_pad_cfg); + if (enable) + return tegra_pmc_utmi_phy_enable_sleepwalk( + lane->index, speed, + &priv->utmi_pad_cfg); + else + return tegra_pmc_utmi_phy_disable_sleepwalk( + lane->index); + } else if (is_hsic_phy(phy)) { + if (enable) + return tegra_pmc_hsic_phy_enable_sleepwalk( + lane->index); + else + return tegra_pmc_hsic_phy_disable_sleepwalk( + lane->index); + } else + return -EINVAL; + + return 0; +} + static struct tegra_xusb_pad * tegra210_sata_pad_probe(struct tegra_xusb_padctl *padctl, const struct tegra_xusb_pad_soc *soc, @@ -2081,11 +2546,68 @@ static void tegra210_xusb_padctl_remove(struct tegra_xusb_padctl *padctl) { } +static void tegra210_xusb_padctl_save(struct tegra_xusb_padctl *padctl) +{ + struct tegra210_xusb_padctl *priv = to_tegra210_xusb_padctl(padctl); + + priv->context.usb2_pad_mux = + padctl_readl(padctl, XUSB_PADCTL_USB2_PAD_MUX); + priv->context.usb2_port_cap = + padctl_readl(padctl, XUSB_PADCTL_USB2_PORT_CAP); + priv->context.ss_port_map = + padctl_readl(padctl, XUSB_PADCTL_SS_PORT_MAP); + priv->context.usb3_pad_mux = + padctl_readl(padctl, XUSB_PADCTL_USB3_PAD_MUX); +} + +static void tegra210_xusb_padctl_restore(struct tegra_xusb_padctl *padctl) +{ + struct tegra210_xusb_padctl *priv = to_tegra210_xusb_padctl(padctl); + + padctl_writel(padctl, priv->context.usb2_pad_mux, + XUSB_PADCTL_USB2_PAD_MUX); + padctl_writel(padctl, priv->context.usb2_port_cap, + XUSB_PADCTL_USB2_PORT_CAP); + padctl_writel(padctl, priv->context.ss_port_map, + XUSB_PADCTL_SS_PORT_MAP); + padctl_writel(padctl, priv->context.usb3_pad_mux, + XUSB_PADCTL_USB3_PAD_MUX); +} + +static int tegra210_xusb_padctl_suspend_noirq(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + + tegra210_uphy_deinit(padctl); + + tegra210_xusb_padctl_save(padctl); + + mutex_unlock(&padctl->lock); + return 0; +} + +static int tegra210_xusb_padctl_resume_noirq(struct tegra_xusb_padctl *padctl) +{ + mutex_lock(&padctl->lock); + + tegra210_xusb_padctl_restore(padctl); + + tegra210_uphy_init(padctl); + + mutex_unlock(&padctl->lock); + return 0; +} + static const struct tegra_xusb_padctl_ops tegra210_xusb_padctl_ops = { .probe = tegra210_xusb_padctl_probe, .remove = tegra210_xusb_padctl_remove, + .suspend_noirq = tegra210_xusb_padctl_suspend_noirq, + .resume_noirq = tegra210_xusb_padctl_resume_noirq, .usb3_set_lfps_detect = tegra210_usb3_set_lfps_detect, .hsic_set_idle = tegra210_hsic_set_idle, + .phy_sleepwalk = tegra210_xusb_padctl_phy_sleepwalk, + .phy_wake = tegra210_xusb_padctl_phy_wake, + .remote_wake_detected = tegra210_xusb_padctl_remote_wake_detected, }; const struct tegra_xusb_padctl_soc tegra210_xusb_padctl_soc = { From patchwork Fri Jun 14 07:48:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115803 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="BO4LxvBn"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCR10Z1yz9sDB for ; Fri, 14 Jun 2019 17:48:45 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726129AbfFNHsn (ORCPT ); Fri, 14 Jun 2019 03:48:43 -0400 Received: from hqemgate14.nvidia.com ([216.228.121.143]:16153 "EHLO hqemgate14.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725812AbfFNHsn (ORCPT ); Fri, 14 Jun 2019 03:48:43 -0400 Received: from hqpgpgate101.nvidia.com (Not Verified[216.228.121.13]) by hqemgate14.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:48:42 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate101.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:48:42 -0700 X-PGP-Universal: processed; by hqpgpgate101.nvidia.com on Fri, 14 Jun 2019 00:48:42 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:48:40 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq Date: Fri, 14 Jun 2019 15:48:23 +0800 Message-ID: <20190614074824.22023-3-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074824.22023-1-jckuo@nvidia.com> References: <20190614074824.22023-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498523; bh=6qKqRFd6GAuK0yLFJRTxGWM3uu9b0JXXYVOPAnyT3Lc=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=BO4LxvBnZGBAWkkWcX2clkcCe7sK5EmVM7i/MGmnNtzL8OywLFrG8mSwwe9h2HHct iyipoemhOlHmA/g9rwrFImPnbpJDFdb8jhsQsxaQGpcp0UXgTdDkijw9KQDkdtq/f0 9McLlNOCpcNC0WD5zjN+M9azs/i26bOzV9gMH3dlhcR/EZHB3W2ViYLtD7//ubC2SL C7MxX4p8WfHLf/+/ThcSpFvmpKGQhZderrCXKOs4g8BW+jo/B25rPIvv4M8t7Vq5Ly yH00mmJz5+n5J88LmqVoibF6t5qbxrGQbCk6OmbcB69YgT/d4waveKYrCxclP42aBg bwyOt/Euzat3Q== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org XUSB PADCTL interrupt will be raised when USB wake event happens. This is required for supporting XUSB host controller ELPG. Signed-off-by: JC Kuo --- arch/arm64/boot/dts/nvidia/tegra210.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi index a550c0a4d572..7496302a1a8a 100644 --- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi +++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi @@ -909,7 +909,8 @@ reg-names = "hcd", "fpci", "ipfs"; interrupts = , - ; + , + ; clocks = <&tegra_car TEGRA210_CLK_XUSB_HOST>, <&tegra_car TEGRA210_CLK_XUSB_HOST_SRC>, From patchwork Fri Jun 14 07:48:24 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: JC Kuo X-Patchwork-Id: 1115804 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=linux-tegra-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=nvidia.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=nvidia.com header.i=@nvidia.com header.b="VRCffOmj"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 45QCR52Qxpz9sDB for ; Fri, 14 Jun 2019 17:48:49 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726259AbfFNHsr (ORCPT ); Fri, 14 Jun 2019 03:48:47 -0400 Received: from hqemgate16.nvidia.com ([216.228.121.65]:19687 "EHLO hqemgate16.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726187AbfFNHsr (ORCPT ); Fri, 14 Jun 2019 03:48:47 -0400 Received: from hqpgpgate101.nvidia.com (Not Verified[216.228.121.13]) by hqemgate16.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Fri, 14 Jun 2019 00:48:44 -0700 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate101.nvidia.com (PGP Universal service); Fri, 14 Jun 2019 00:48:44 -0700 X-PGP-Universal: processed; by hqpgpgate101.nvidia.com on Fri, 14 Jun 2019 00:48:44 -0700 Received: from jckuo-lt.nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13) with Microsoft SMTP Server (TLS) id 15.0.1473.3; Fri, 14 Jun 2019 07:48:42 +0000 From: JC Kuo To: , , , , CC: , , , , , JC Kuo Subject: [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM Date: Fri, 14 Jun 2019 15:48:24 +0800 Message-ID: <20190614074824.22023-4-jckuo@nvidia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190614074824.22023-1-jckuo@nvidia.com> References: <20190614074824.22023-1-jckuo@nvidia.com> MIME-Version: 1.0 X-Originating-IP: [10.124.1.5] X-ClientProxiedBy: HQMAIL101.nvidia.com (172.20.187.10) To HQMAIL107.nvidia.com (172.20.187.13) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1560498524; bh=sr2VW0zHTf7Ju6J83UlMbBLhtTBEMR53WnGQYE47ZMc=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=VRCffOmj0Wy9IyK3TOeSe7JziBuUQOmnFCsYZJoQNrEVaJzUU7zvLpZDi3lkmBNKX 6nR+9FbTwtVa0t89lHpZ/wvfOBzBG6m9uh51DLW5RlO2Lm18XDcF/d/7LI0nDj3jeG 4i6YcDQtHMsfQej9mO3PAzVCbr8a0wre1gtd+dw4nhqDafuXnPlZkVmT5e7csNAvNV ff+SAvceIcHiygQFzrGDpFQ15tqt5fAYkDedk/Vwi3n19MWpdKLSwZRL4kktjhxh52 sA/7OOMu8QCdWOEkJWg7b747ugPztgemsoWuBkTYONoYZqc9rTUUAwS+weDmjChbjy u2RwWJXyOCC8A== Sender: linux-tegra-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-tegra@vger.kernel.org This commit enables XUSB host controller ELPG for runtime and system power management. NEED CLEANUP. Signed-off-by: JC Kuo --- drivers/usb/host/xhci-tegra.c | 802 ++++++++++++++++++++++++++++------ 1 file changed, 671 insertions(+), 131 deletions(-) diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 294158113d62..ade56e63212b 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -38,7 +39,17 @@ #define XUSB_CFG_4 0x010 #define XUSB_BASE_ADDR_SHIFT 15 #define XUSB_BASE_ADDR_MASK 0x1ffff +#define XUSB_CFG_16 0x040 +#define XUSB_CFG_24 0x060 +#define XUSB_CFG_AXI_CFG 0x0f8 +#define XUSB_CFG_ARU_C11PAGESEL 0x404 +#define XUSB_HSP0 BIT(12) #define XUSB_CFG_ARU_C11_CSBRANGE 0x41c +#define XUSB_CFG_ARU_CONTEXT 0x43c +#define XUSB_CFG_ARU_CONTEXT_HS_PLS 0x478 +#define XUSB_CFG_ARU_CONTEXT_FS_PLS 0x47c +#define XUSB_CFG_ARU_CONTEXT_HSFS_SPEED 0x480 +#define XUSB_CFG_ARU_CONTEXT_HSFS_PP 0x484 #define XUSB_CFG_CSB_BASE_ADDR 0x800 /* FPCI mailbox registers */ @@ -63,11 +74,20 @@ #define MBOX_SMI_INTR_EN BIT(3) /* IPFS registers */ +#define XUSB_HOST_MSI_BAR_SZ_0 0x0c0 +#define XUSB_HOST_MSI_AXI_BAR_ST_0 0x0c4 +#define XUSB_HOST_MSI_FPCI_BAR_ST_0 0x0c8 +#define XUSB_HOST_MSI_VEC0_0 0x100 +#define XUSB_HOST_MSI_EN_VEC0_0 0x140 #define IPFS_XUSB_HOST_CONFIGURATION_0 0x180 #define IPFS_EN_FPCI BIT(0) +#define XUSB_HOST_FPCI_ERROR_MASKS_0 0x184 #define IPFS_XUSB_HOST_INTR_MASK_0 0x188 #define IPFS_IP_INT_MASK BIT(16) +#define XUSB_HOST_IPFS_INTR_ENABLE_0 0x198 +#define XUSB_HOST_UFPCI_CONFIG_0 0x19c #define IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0 0x1bc +#define XUSB_HOST_MCCIF_FIFOCTRL_0 0x1dc #define CSB_PAGE_SELECT_MASK 0x7fffff #define CSB_PAGE_SELECT_SHIFT 9 @@ -164,6 +184,31 @@ struct tegra_xusb_soc { bool has_ipfs; }; +struct tegra_xhci_ipfs_context { + u32 msi_bar_sz; + u32 msi_axi_barst; + u32 msi_fpci_barst; + u32 msi_vec0; + u32 msi_en_vec0; + u32 fpci_error_masks; + u32 intr_mask; + u32 ipfs_intr_enable; + u32 ufpci_config; + u32 clkgate_hysteresis; + u32 xusb_host_mccif_fifo_cntrl; +}; + +struct tegra_xhci_fpci_context { + u32 hs_pls; + u32 fs_pls; + u32 hsfs_speed; + u32 hsfs_pp; + u32 cfg_aru; + u32 cfg_order; + u32 cfg_fladj; + u32 cfg_sid; +}; + struct tegra_xusb { struct device *dev; void __iomem *regs; @@ -173,6 +218,7 @@ struct tegra_xusb { int xhci_irq; int mbox_irq; + int padctl_irq; void __iomem *ipfs_base; void __iomem *fpci_base; @@ -198,8 +244,6 @@ struct tegra_xusb { struct device *genpd_dev_host; struct device *genpd_dev_ss; - struct device_link *genpd_dl_host; - struct device_link *genpd_dl_ss; struct phy **phys; unsigned int num_phys; @@ -210,9 +254,15 @@ struct tegra_xusb { void *virt; dma_addr_t phys; } fw; + + bool suspended; + struct tegra_xhci_fpci_context fpci_ctx; + struct tegra_xhci_ipfs_context ipfs_ctx; }; static struct hc_driver __read_mostly tegra_xhci_hc_driver; +static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime); +static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime); static inline u32 fpci_readl(struct tegra_xusb *tegra, unsigned int offset) { @@ -585,6 +635,14 @@ static void tegra_xusb_mbox_handle(struct tegra_xusb *tegra, enable); if (err < 0) break; + + if (!enable) { + /* + * Add this delay to increase stability of + * directing U3. + */ + usleep_range(500, 1000); + } } if (err < 0) { @@ -621,6 +679,9 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) mutex_lock(&tegra->lock); + if (pm_runtime_suspended(tegra->dev) || tegra->suspended) + goto out; + value = fpci_readl(tegra, XUSB_CFG_ARU_MBOX_DATA_OUT); tegra_xusb_mbox_unpack(&msg, value); @@ -634,13 +695,14 @@ static irqreturn_t tegra_xusb_mbox_thread(int irq, void *data) tegra_xusb_mbox_handle(tegra, &msg); +out: mutex_unlock(&tegra->lock); return IRQ_HANDLED; } -static void tegra_xusb_config(struct tegra_xusb *tegra, - struct resource *regs) +static void tegra_xusb_config(struct tegra_xusb *tegra) { + resource_size_t base_addr = tegra->hcd->rsrc_start; u32 value; if (tegra->soc->has_ipfs) { @@ -654,7 +716,7 @@ static void tegra_xusb_config(struct tegra_xusb *tegra, /* Program BAR0 space */ value = fpci_readl(tegra, XUSB_CFG_4); value &= ~(XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); - value |= regs->start & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); + value |= base_addr & (XUSB_BASE_ADDR_MASK << XUSB_BASE_ADDR_SHIFT); fpci_writel(tegra, value, XUSB_CFG_4); usleep_range(100, 200); @@ -777,44 +839,57 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra) static int tegra_xusb_runtime_suspend(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); + int ret; - tegra_xusb_phy_disable(tegra); - regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); - tegra_xusb_clk_disable(tegra); + synchronize_irq(tegra->mbox_irq); - return 0; + mutex_lock(&tegra->lock); + ret = tegra_xhci_enter_elpg(tegra, true); + mutex_unlock(&tegra->lock); + + return ret; } static int tegra_xusb_runtime_resume(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - int err; + int ret; - err = tegra_xusb_clk_enable(tegra); - if (err) { - dev_err(dev, "failed to enable clocks: %d\n", err); - return err; - } + mutex_lock(&tegra->lock); + ret = tegra_xhci_exit_elpg(tegra, true); + mutex_unlock(&tegra->lock); - err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); - if (err) { - dev_err(dev, "failed to enable regulators: %d\n", err); - goto disable_clk; - } + return ret; +} - err = tegra_xusb_phy_enable(tegra); - if (err < 0) { - dev_err(dev, "failed to enable PHYs: %d\n", err); - goto disable_regulator; +static int tegra_xusb_request_firmware(struct tegra_xusb *tegra) +{ + struct tegra_xusb_fw_header *header; + struct device *dev = tegra->dev; + const struct firmware *fw; + int rc; + + if (!tegra->fw.virt) { + rc = request_firmware(&fw, tegra->soc->firmware, tegra->dev); + if (rc < 0) { + dev_err(dev, "failed to request firmware: %d\n", rc); + return rc; + } + + header = (struct tegra_xusb_fw_header *)fw->data; + tegra->fw.size = le32_to_cpu(header->fwimg_len); + tegra->fw.virt = dma_alloc_coherent(dev, tegra->fw.size, + &tegra->fw.phys, GFP_KERNEL); + if (!tegra->fw.virt) { + release_firmware(fw); + return -ENOMEM; + } + + memcpy(tegra->fw.virt, fw->data, tegra->fw.size); + release_firmware(fw); } return 0; - -disable_regulator: - regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); -disable_clk: - tegra_xusb_clk_disable(tegra); - return err; } static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) @@ -822,7 +897,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) unsigned int code_tag_blocks, code_size_blocks, code_blocks; struct tegra_xusb_fw_header *header; struct device *dev = tegra->dev; - const struct firmware *fw; unsigned long timeout; time64_t timestamp; struct tm time; @@ -830,27 +904,9 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) u32 value; int err; - err = request_firmware(&fw, tegra->soc->firmware, tegra->dev); - if (err < 0) { - dev_err(tegra->dev, "failed to request firmware: %d\n", err); + err = tegra_xusb_request_firmware(tegra); + if (err) return err; - } - - /* Load Falcon controller with its firmware. */ - header = (struct tegra_xusb_fw_header *)fw->data; - tegra->fw.size = le32_to_cpu(header->fwimg_len); - - tegra->fw.virt = dma_alloc_coherent(tegra->dev, tegra->fw.size, - &tegra->fw.phys, GFP_KERNEL); - if (!tegra->fw.virt) { - dev_err(tegra->dev, "failed to allocate memory for firmware\n"); - release_firmware(fw); - return -ENOMEM; - } - - header = (struct tegra_xusb_fw_header *)tegra->fw.virt; - memcpy(tegra->fw.virt, fw->data, tegra->fw.size); - release_firmware(fw); if (csb_readl(tegra, XUSB_CSB_MP_ILOAD_BASE_LO) != 0) { dev_info(dev, "Firmware already loaded, Falcon state %#x\n", @@ -865,6 +921,7 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) * Boot code of the firmware reads the ILOAD_BASE registers * to get to the start of the DFI in system memory. */ + header = (struct tegra_xusb_fw_header *)tegra->fw.virt; address = tegra->fw.phys + sizeof(*header); csb_writel(tegra, address >> 32, XUSB_CSB_MP_ILOAD_BASE_HI); csb_writel(tegra, address, XUSB_CSB_MP_ILOAD_BASE_LO); @@ -942,10 +999,6 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) static void tegra_xusb_powerdomain_remove(struct device *dev, struct tegra_xusb *tegra) { - if (tegra->genpd_dl_ss) - device_link_del(tegra->genpd_dl_ss); - if (tegra->genpd_dl_host) - device_link_del(tegra->genpd_dl_host); if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss)) dev_pm_domain_detach(tegra->genpd_dev_ss, true); if (!IS_ERR_OR_NULL(tegra->genpd_dev_host)) @@ -971,25 +1024,102 @@ static int tegra_xusb_powerdomain_init(struct device *dev, return err; } - tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); - if (!tegra->genpd_dl_host) { - dev_err(dev, "adding host device link failed!\n"); - return -ENODEV; + return 0; +} + +static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + bool use_genpd; + int rc; + + use_genpd = of_property_read_bool(dev->of_node, "power-domains"); + + if (use_genpd) + rc = pm_runtime_get_sync(tegra->genpd_dev_ss); + else { + rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, + tegra->ss_clk, + tegra->ss_rst); + } + if (rc < 0) { + dev_err(dev, "failed to enable XUSB SS partition: %d\n", rc); + return rc; + } + + if (use_genpd) + rc = pm_runtime_get_sync(tegra->genpd_dev_host); + else { + rc = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + } + if (rc < 0) { + dev_err(dev, "failed to enable XUSB Host partition: %d\n", rc); + if (use_genpd) + pm_runtime_put_sync(tegra->genpd_dev_ss); + else + tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + return rc; + } + + return 0; +} + +static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra) +{ + struct device *dev = tegra->dev; + bool use_genpd; + int rc; + + use_genpd = of_property_read_bool(dev->of_node, "power-domains"); + + if (use_genpd) + rc = pm_runtime_put_sync(tegra->genpd_dev_host); + else + rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); + + if (rc < 0) { + dev_err(dev, "failed to disable XUSB Host partition: %d\n", rc); + return rc; } - tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss, - DL_FLAG_PM_RUNTIME | - DL_FLAG_STATELESS); - if (!tegra->genpd_dl_ss) { - dev_err(dev, "adding superspeed device link failed!\n"); - return -ENODEV; + if (use_genpd) + rc = pm_runtime_put_sync(tegra->genpd_dev_ss); + else + rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + + if (rc < 0) { + dev_err(dev, "failed to disable XUSB SS partition: %d\n", rc); + if (use_genpd) + pm_runtime_get_sync(tegra->genpd_dev_host); + else { + tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, + tegra->host_clk, + tegra->host_rst); + } + return rc; } return 0; } +static irqreturn_t tegra_xusb_padctl_irq(int irq, void *data) +{ + struct tegra_xusb *tegra = data; + + mutex_lock(&tegra->lock); + if (tegra->suspended) { + mutex_unlock(&tegra->lock); + return IRQ_HANDLED; + } + mutex_unlock(&tegra->lock); + + pm_runtime_resume(tegra->dev); + + return IRQ_HANDLED; +} + static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb_mbox_msg msg; @@ -1035,6 +1165,10 @@ static int tegra_xusb_probe(struct platform_device *pdev) if (tegra->mbox_irq < 0) return tegra->mbox_irq; + tegra->padctl_irq = platform_get_irq(pdev, 2); + if (tegra->padctl_irq < 0) + return tegra->padctl_irq; + tegra->padctl = tegra_xusb_padctl_get(&pdev->dev); if (IS_ERR(tegra->padctl)) return PTR_ERR(tegra->padctl); @@ -1119,25 +1253,6 @@ static int tegra_xusb_probe(struct platform_device *pdev) err); goto put_padctl; } - - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBA, - tegra->ss_clk, - tegra->ss_rst); - if (err) { - dev_err(&pdev->dev, - "failed to enable XUSBA domain: %d\n", err); - goto put_padctl; - } - - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC, - tegra->host_clk, - tegra->host_rst); - if (err) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - dev_err(&pdev->dev, - "failed to enable XUSBC domain: %d\n", err); - goto put_padctl; - } } else { err = tegra_xusb_powerdomain_init(&pdev->dev, tegra); if (err) @@ -1197,6 +1312,10 @@ static int tegra_xusb_probe(struct platform_device *pdev) err = -ENOMEM; goto put_powerdomains; } + tegra->hcd->skip_phy_initialization = 1; + tegra->hcd->regs = tegra->regs; + tegra->hcd->rsrc_start = regs->start; + tegra->hcd->rsrc_len = resource_size(regs); /* * This must happen after usb_create_hcd(), because usb_create_hcd() @@ -1204,33 +1323,40 @@ static int tegra_xusb_probe(struct platform_device *pdev) */ platform_set_drvdata(pdev, tegra); - pm_runtime_enable(&pdev->dev); - if (pm_runtime_enabled(&pdev->dev)) - err = pm_runtime_get_sync(&pdev->dev); - else - err = tegra_xusb_runtime_resume(&pdev->dev); + err = tegra_xusb_clk_enable(tegra); + if (err) { + dev_err(tegra->dev, "failed to enable clocks: %d\n", err); + goto put_hcd; + } + + err = regulator_bulk_enable(tegra->soc->num_supplies, tegra->supplies); + if (err) { + dev_err(tegra->dev, "failed to enable regulators: %d\n", err); + goto disable_clk; + } + err = tegra_xusb_phy_enable(tegra); if (err < 0) { - dev_err(&pdev->dev, "failed to enable device: %d\n", err); - goto disable_rpm; + dev_err(tegra->dev, "failed to enable PHYs: %d\n", err); + goto disable_regulator; } - tegra_xusb_config(tegra, regs); + err = tegra_xusb_unpowergate_partitions(tegra); + if (err) + goto disable_phy; + + tegra_xusb_config(tegra); err = tegra_xusb_load_firmware(tegra); if (err < 0) { dev_err(&pdev->dev, "failed to load firmware: %d\n", err); - goto put_rpm; + goto powergate; } - tegra->hcd->regs = tegra->regs; - tegra->hcd->rsrc_start = regs->start; - tegra->hcd->rsrc_len = resource_size(regs); - err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED); if (err < 0) { dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err); - goto put_rpm; + goto powergate; } device_wakeup_enable(tegra->hcd->self.controller); @@ -1253,6 +1379,26 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_usb3; } + err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, + tegra_xusb_mbox_irq, + tegra_xusb_mbox_thread, 0, + dev_name(&pdev->dev), tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request mbox IRQ: %d\n", err); + goto remove_usb3; + } + + err = devm_request_threaded_irq(&pdev->dev, tegra->padctl_irq, + NULL, + tegra_xusb_padctl_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_HIGH, + dev_name(&pdev->dev), tegra); + if (err < 0) { + dev_err(&pdev->dev, "failed to request padctl IRQ: %d\n", err); + goto remove_usb3; + } + mutex_lock(&tegra->lock); /* Enable firmware messages from controller. */ @@ -1268,14 +1414,16 @@ static int tegra_xusb_probe(struct platform_device *pdev) mutex_unlock(&tegra->lock); - err = devm_request_threaded_irq(&pdev->dev, tegra->mbox_irq, - tegra_xusb_mbox_irq, - tegra_xusb_mbox_thread, 0, - dev_name(&pdev->dev), tegra); - if (err < 0) { - dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); - goto remove_usb3; - } + /* Enable wake for both USB 2.0 and USB 3.0 roothubs */ + device_init_wakeup(&tegra->hcd->self.root_hub->dev, true); + device_init_wakeup(&xhci->shared_hcd->self.root_hub->dev, true); + device_init_wakeup(tegra->dev, true); + + pm_runtime_use_autosuspend(tegra->dev); + pm_runtime_set_autosuspend_delay(tegra->dev, 2000); + pm_runtime_mark_last_busy(tegra->dev); + pm_runtime_set_active(tegra->dev); + pm_runtime_enable(tegra->dev); return 0; @@ -1285,19 +1433,18 @@ static int tegra_xusb_probe(struct platform_device *pdev) usb_put_hcd(xhci->shared_hcd); remove_usb2: usb_remove_hcd(tegra->hcd); -put_rpm: - if (!pm_runtime_status_suspended(&pdev->dev)) - tegra_xusb_runtime_suspend(&pdev->dev); -disable_rpm: - pm_runtime_disable(&pdev->dev); +powergate: + tegra_xusb_powergate_partitions(tegra); +disable_phy: + tegra_xusb_phy_disable(tegra); +disable_regulator: + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); +disable_clk: + tegra_xusb_clk_disable(tegra); +put_hcd: usb_put_hcd(tegra->hcd); put_powerdomains: - if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - } else { - tegra_xusb_powerdomain_remove(&pdev->dev, tegra); - } + tegra_xusb_powerdomain_remove(&pdev->dev, tegra); put_padctl: tegra_xusb_padctl_put(tegra->padctl); return err; @@ -1308,6 +1455,8 @@ static int tegra_xusb_remove(struct platform_device *pdev) struct tegra_xusb *tegra = platform_get_drvdata(pdev); struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + pm_runtime_get_sync(&pdev->dev); + usb_remove_hcd(xhci->shared_hcd); usb_put_hcd(xhci->shared_hcd); xhci->shared_hcd = NULL; @@ -1317,38 +1466,429 @@ static int tegra_xusb_remove(struct platform_device *pdev) dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt, tegra->fw.phys); - pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + pm_runtime_put(&pdev->dev); - if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); - tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); - } else { + tegra_xusb_powergate_partitions(tegra); + + if (of_property_read_bool(pdev->dev.of_node, "power-domains")) tegra_xusb_powerdomain_remove(&pdev->dev, tegra); - } + tegra_xusb_phy_disable(tegra); + tegra_xusb_clk_disable(tegra); + regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies); tegra_xusb_padctl_put(tegra->padctl); return 0; } +static void tegra_xhci_save_context(struct tegra_xusb *tegra) +{ + if (tegra->soc->has_ipfs) { + /* Save IPFS registers */ + tegra->ipfs_ctx.msi_bar_sz = + ipfs_readl(tegra, XUSB_HOST_MSI_BAR_SZ_0); + tegra->ipfs_ctx.msi_axi_barst = + ipfs_readl(tegra, XUSB_HOST_MSI_AXI_BAR_ST_0); + tegra->ipfs_ctx.msi_fpci_barst = + ipfs_readl(tegra, XUSB_HOST_MSI_FPCI_BAR_ST_0); + tegra->ipfs_ctx.msi_vec0 = + ipfs_readl(tegra, XUSB_HOST_MSI_VEC0_0); + tegra->ipfs_ctx.msi_en_vec0 = + ipfs_readl(tegra, XUSB_HOST_MSI_EN_VEC0_0); + tegra->ipfs_ctx.fpci_error_masks = + ipfs_readl(tegra, XUSB_HOST_FPCI_ERROR_MASKS_0); + tegra->ipfs_ctx.intr_mask = + ipfs_readl(tegra, IPFS_XUSB_HOST_INTR_MASK_0); + tegra->ipfs_ctx.ipfs_intr_enable = + ipfs_readl(tegra, XUSB_HOST_IPFS_INTR_ENABLE_0); + tegra->ipfs_ctx.ufpci_config = + ipfs_readl(tegra, XUSB_HOST_UFPCI_CONFIG_0); + tegra->ipfs_ctx.clkgate_hysteresis = + ipfs_readl(tegra, + IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); + tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl = + ipfs_readl(tegra, XUSB_HOST_MCCIF_FIFOCTRL_0); + } + + /* Save FPCI registers */ + tegra->fpci_ctx.hs_pls = + fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HS_PLS); + tegra->fpci_ctx.fs_pls = + fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_FS_PLS); + tegra->fpci_ctx.hsfs_speed = + fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_SPEED); + tegra->fpci_ctx.hsfs_pp = + fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT_HSFS_PP); + tegra->fpci_ctx.cfg_aru = fpci_readl(tegra, XUSB_CFG_ARU_CONTEXT); + tegra->fpci_ctx.cfg_order = fpci_readl(tegra, XUSB_CFG_AXI_CFG); + tegra->fpci_ctx.cfg_fladj = fpci_readl(tegra, XUSB_CFG_24); + tegra->fpci_ctx.cfg_sid = fpci_readl(tegra, XUSB_CFG_16); +} + +static void tegra_xhci_restore_context(struct tegra_xusb *tegra) +{ + /* Restore FPCI registers */ + fpci_writel(tegra, tegra->fpci_ctx.hs_pls, XUSB_CFG_ARU_CONTEXT_HS_PLS); + fpci_writel(tegra, tegra->fpci_ctx.fs_pls, XUSB_CFG_ARU_CONTEXT_FS_PLS); + fpci_writel(tegra, tegra->fpci_ctx.hsfs_speed, + XUSB_CFG_ARU_CONTEXT_HSFS_SPEED); + fpci_writel(tegra, tegra->fpci_ctx.hsfs_pp, + XUSB_CFG_ARU_CONTEXT_HSFS_PP); + fpci_writel(tegra, tegra->fpci_ctx.cfg_aru, XUSB_CFG_ARU_CONTEXT); + fpci_writel(tegra, tegra->fpci_ctx.cfg_order, XUSB_CFG_AXI_CFG); + fpci_writel(tegra, tegra->fpci_ctx.cfg_fladj, XUSB_CFG_24); + fpci_writel(tegra, tegra->fpci_ctx.cfg_sid, XUSB_CFG_16); + + if (tegra->soc->has_ipfs) { + /* Restore IPFS registers */ + ipfs_writel(tegra, tegra->ipfs_ctx.msi_bar_sz, + XUSB_HOST_MSI_BAR_SZ_0); + ipfs_writel(tegra, tegra->ipfs_ctx.msi_axi_barst, + XUSB_HOST_MSI_AXI_BAR_ST_0); + ipfs_writel(tegra, tegra->ipfs_ctx.msi_fpci_barst, + XUSB_HOST_MSI_FPCI_BAR_ST_0); + ipfs_writel(tegra, tegra->ipfs_ctx.msi_vec0, + XUSB_HOST_MSI_VEC0_0); + ipfs_writel(tegra, tegra->ipfs_ctx.msi_en_vec0, + XUSB_HOST_MSI_EN_VEC0_0); + ipfs_writel(tegra, tegra->ipfs_ctx.fpci_error_masks, + XUSB_HOST_FPCI_ERROR_MASKS_0); + ipfs_writel(tegra, tegra->ipfs_ctx.intr_mask, + IPFS_XUSB_HOST_INTR_MASK_0); + ipfs_writel(tegra, tegra->ipfs_ctx.ipfs_intr_enable, + XUSB_HOST_IPFS_INTR_ENABLE_0); + ipfs_writel(tegra, tegra->ipfs_ctx.ufpci_config, + XUSB_HOST_UFPCI_CONFIG_0); + ipfs_writel(tegra, tegra->ipfs_ctx.clkgate_hysteresis, + IPFS_XUSB_HOST_CLKGATE_HYSTERESIS_0); + ipfs_writel(tegra, tegra->ipfs_ctx.xusb_host_mccif_fifo_cntrl, + XUSB_HOST_MCCIF_FIFOCTRL_0); + } +} + +static enum usb_device_speed +tegra_xhci_portsc_to_speed(struct tegra_xusb *tegra, u32 portsc) +{ + if (DEV_LOWSPEED(portsc)) + return USB_SPEED_LOW; + else if (DEV_HIGHSPEED(portsc)) + return USB_SPEED_HIGH; + else if (DEV_FULLSPEED(portsc)) + return USB_SPEED_FULL; + else if (DEV_SUPERSPEED_ANY(portsc)) + return USB_SPEED_SUPER; + else + return USB_SPEED_UNKNOWN; +} + +static void tegra_xhci_enable_phy_sleepwalk_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + enum usb_device_speed speed; + struct phy *phy; + int index, offset; + int i, j, k; + struct xhci_hub *rhub; + u32 portsc; + + for (i = 0, k = 0; i < tegra->soc->num_types; i++) { + if (strcmp(tegra->soc->phy_types[i].name, "usb3") == 0) + rhub = &xhci->usb3_rhub; + else + rhub = &xhci->usb2_rhub; + + if (strcmp(tegra->soc->phy_types[i].name, "hsic") == 0) + offset = tegra->soc->ports.usb2.count; + else + offset = 0; + + for (j = 0; j < tegra->soc->phy_types[i].num; j++) { + phy = tegra->phys[k++]; + + if (!phy) + continue; + + index = j + offset; + + if (index >= rhub->num_ports) + continue; + + portsc = readl(rhub->ports[index]->addr); + speed = tegra_xhci_portsc_to_speed(tegra, portsc); + tegra_xusb_padctl_enable_phy_sleepwalk(padctl, phy, + speed); + tegra_xusb_padctl_enable_phy_wake(padctl, phy); + } + } +} + +static void tegra_xhci_disable_phy_wake(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_wake(padctl, tegra->phys[i]); + } +} + +static void tegra_xhci_disable_phy_sleepwalk(struct tegra_xusb *tegra) +{ + struct tegra_xusb_padctl *padctl = tegra->padctl; + int i; + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + tegra_xusb_padctl_disable_phy_sleepwalk(padctl, tegra->phys[i]); + } +} + +static int tegra_xhci_check_ports_for_u3(struct tegra_xusb *tegra) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + struct xhci_hub *rhubs[] = {&xhci->usb2_rhub, &xhci->usb3_rhub, NULL}; + struct xhci_hub **rhub; + unsigned long flags; + u32 usbcmd, reg; + int i, ret = 0; + + spin_lock_irqsave(&xhci->lock, flags); + + usbcmd = readl(&xhci->op_regs->command); + usbcmd &= ~CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + for (rhub = rhubs; (*rhub) != NULL; rhub++) { + for (i = 0; i < (*rhub)->num_ports; i++) { + reg = readl((*rhub)->ports[i]->addr); + if (!(reg & PORT_PE)) + continue; + + if ((reg & PORT_PLS_MASK) != XDEV_U3) { + dev_info(dev, "%d-%d isn't suspended: 0x%08x\n", + (*rhub)->hcd->self.busnum, i + 1, reg); + ret = -EBUSY; + } + } + } + + spin_unlock_irqrestore(&xhci->lock, flags); + + return ret; +} + +/* caller must hold tegra->lock */ +static int tegra_xhci_enter_elpg(struct tegra_xusb *tegra, bool runtime) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + bool do_wakeup = runtime ? true : device_may_wakeup(dev); + unsigned int i; + int rc; + + dev_info(dev, "entering ELPG\n"); + + rc = tegra_xhci_check_ports_for_u3(tegra); + if (rc < 0) + goto out; + + rc = xhci_suspend(xhci, do_wakeup); + + if (rc) { + dev_warn(dev, "xhci_suspend() failed %d\n", rc); + goto out; + } + + tegra_xhci_save_context(tegra); + + if (do_wakeup) + tegra_xhci_enable_phy_sleepwalk_wake(tegra); + + tegra_xusb_powergate_partitions(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!do_wakeup) + phy_exit(tegra->phys[i]); + } + + tegra_xusb_clk_disable(tegra); +out: + if (!rc) + dev_info(tegra->dev, "entering ELPG done\n"); + else { + u32 usbcmd; + + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + dev_info(tegra->dev, "entering ELPG failed\n"); + pm_runtime_mark_last_busy(tegra->dev); + } + + return rc; +} + +/* caller must hold tegra->lock */ +static int tegra_xhci_exit_elpg(struct tegra_xusb *tegra, bool runtime) +{ + struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + struct device *dev = tegra->dev; + struct tegra_xusb_mbox_msg msg; + bool do_wakeup = runtime ? true : device_may_wakeup(dev); + unsigned int i; + int rc; + u32 usbcmd; + + dev_info(dev, "exiting ELPG\n"); + pm_runtime_mark_last_busy(tegra->dev); + + rc = tegra_xusb_clk_enable(tegra); + if (rc) { + dev_warn(dev, "failed to enable xhci clocks %d\n", rc); + goto out; + } + + rc = tegra_xusb_unpowergate_partitions(tegra); + if (rc) + goto disable_clks; + + if (do_wakeup) + tegra_xhci_disable_phy_wake(tegra); + + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + if (!do_wakeup) + phy_init(tegra->phys[i]); + + phy_power_on(tegra->phys[i]); + } + + tegra_xusb_config(tegra); + tegra_xhci_restore_context(tegra); + + rc = tegra_xusb_load_firmware(tegra); + if (rc < 0) + goto disable_phy; + + msg.cmd = MBOX_CMD_MSG_ENABLED; + msg.data = 0; + + rc = tegra_xusb_mbox_send(tegra, &msg); + if (rc < 0) { + dev_err(dev, "failed to enable messages: %d\n", rc); + goto disable_phy; + } + + if (do_wakeup) + tegra_xhci_disable_phy_sleepwalk(tegra); + + rc = xhci_resume(xhci, 0); + + usbcmd = readl(&xhci->op_regs->command); + usbcmd |= CMD_EIE; + writel(usbcmd, &xhci->op_regs->command); + + goto out; + +disable_phy: + for (i = 0; i < tegra->num_phys; i++) { + if (!tegra->phys[i]) + continue; + + phy_power_off(tegra->phys[i]); + if (!do_wakeup) + phy_exit(tegra->phys[i]); + } + tegra_xusb_powergate_partitions(tegra); +disable_clks: + tegra_xusb_clk_disable(tegra); +out: + if (!rc) + dev_info(dev, "exiting ELPG done\n"); + else + dev_info(dev, "exiting ELPG failed\n"); + + return rc; +} + #ifdef CONFIG_PM_SLEEP static int tegra_xusb_suspend(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); - bool wakeup = device_may_wakeup(dev); + int ret; + + synchronize_irq(tegra->mbox_irq); + + mutex_lock(&tegra->lock); + + if (pm_runtime_suspended(dev)) { + ret = tegra_xhci_exit_elpg(tegra, true); + if (ret < 0) + goto out; + } + + ret = tegra_xhci_enter_elpg(tegra, false); + if (ret < 0) { + if (pm_runtime_suspended(dev)) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } - /* TODO: Powergate controller across suspend/resume. */ - return xhci_suspend(xhci, wakeup); + goto out; + } + +out: + if (!ret) { + tegra->suspended = true; + pm_runtime_disable(dev); + } + + mutex_unlock(&tegra->lock); + + return ret; } static int tegra_xusb_resume(struct device *dev) { struct tegra_xusb *tegra = dev_get_drvdata(dev); - struct xhci_hcd *xhci = hcd_to_xhci(tegra->hcd); + int ret = 0; + + mutex_lock(&tegra->lock); + + if (!tegra->suspended) { + mutex_unlock(&tegra->lock); + return 0; + } + + ret = tegra_xhci_exit_elpg(tegra, true); + if (ret < 0) { + mutex_unlock(&tegra->lock); + return ret; + } + + tegra->suspended = false; + mutex_unlock(&tegra->lock); - return xhci_resume(xhci, 0); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; } #endif