This commit enables XUSB host controller ELPG for runtime and system power management. NEED CLEANUP. Signed-off-by: JC Kuo <jckuo@xxxxxxxxxx> --- 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