From: JC Kuo <jckuo@nvidia.com>
To: <gregkh@linuxfoundation.org>, <thierry.reding@gmail.com>,
<jonathanh@nvidia.com>, <pdeschrijver@nvidia.com>,
<afrid@nvidia.com>
Cc: <linux-tegra@vger.kernel.org>, <linux-usb@vger.kernel.org>,
<devicetree@vger.kernel.org>, <nkristam@nvidia.com>,
<skomatineni@nvidia.com>, JC Kuo <jckuo@nvidia.com>
Subject: [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM
Date: Fri, 14 Jun 2019 15:48:24 +0800 [thread overview]
Message-ID: <20190614074824.22023-4-jckuo@nvidia.com> (raw)
In-Reply-To: <20190614074824.22023-1-jckuo@nvidia.com>
This commit enables XUSB host controller ELPG for runtime and system
power management.
NEED CLEANUP.
Signed-off-by: JC Kuo <jckuo@nvidia.com>
---
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 <linux/phy/phy.h>
#include <linux/phy/tegra/xusb.h>
#include <linux/platform_device.h>
+#include <linux/usb/ch9.h>
#include <linux/pm.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
@@ -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
--
2.17.1
next prev parent reply other threads:[~2019-06-14 7:48 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-06-14 7:48 [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk JC Kuo
2019-06-14 7:48 ` [PATCH 6/8] phy: tegra: xusb: t210: support wake and sleepwalk JC Kuo
2019-07-04 13:53 ` Jon Hunter
2019-06-14 7:48 ` [PATCH 7/8] arm64: tegra: add Tegra210 XUSB PADCTL irq JC Kuo
2019-06-14 7:48 ` JC Kuo [this message]
2019-06-18 6:33 ` [PATCH 8/8] xhci: tegra: enable ELPG for runtime/system PM Greg KH
2019-06-18 8:01 ` JC Kuo
2019-07-04 14:05 ` Jon Hunter
2019-07-04 13:42 ` [PATCH 5/8] soc/tegra: pmc: support T210 USB 2.0 Sleepwalk Jon Hunter
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190614074824.22023-4-jckuo@nvidia.com \
--to=jckuo@nvidia.com \
--cc=afrid@nvidia.com \
--cc=devicetree@vger.kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=jonathanh@nvidia.com \
--cc=linux-tegra@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=nkristam@nvidia.com \
--cc=pdeschrijver@nvidia.com \
--cc=skomatineni@nvidia.com \
--cc=thierry.reding@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).