mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
usb: xhci: tegra: Unlink power domain devices
This commit unlinks xhci-tegra platform device with SS/host power domain devices. Reasons for this change is - at ELPG entry, PHY sleepwalk and wake configuration need to be done before powering down SS/host partitions, and PHY need be powered off after powering down SS/host partitions. Sequence looks like roughly below: tegra_xusb_enter_elpg() -> xhci_suspend() -> enable PHY sleepwalk and wake if needed -> power down SS/host partitions -> power down PHY If SS/host power domains are linked to xhci-tegra platform device, we are not able to perform the sequence like above. This commit introduces: 1. tegra_xusb_unpowergate_partitions() to power up SS and host partitions together. If SS/host power domain devices are available, it invokes pm_runtime_get_sync() to request power driver to power up partitions; If power domain devices are not available, tegra_powergate_sequence_power_up() will be used to power up partitions. 2. tegra_xusb_powergate_partitions() to power down SS and host partitions together. If SS/host power domain devices are available, it invokes pm_runtime_put_sync() to request power driver to power down partitions; If power domain devices are not available, tegra_powergate_power_off() will be used to power down partitions. Signed-off-by: JC Kuo <jckuo@nvidia.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
23eca83155
commit
41a7426d25
1 changed files with 112 additions and 94 deletions
|
@ -2,7 +2,7 @@
|
||||||
/*
|
/*
|
||||||
* NVIDIA Tegra xHCI host controller driver
|
* NVIDIA Tegra xHCI host controller driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2014 NVIDIA Corporation
|
* Copyright (c) 2014-2020, NVIDIA CORPORATION. All rights reserved.
|
||||||
* Copyright (C) 2014 Google, Inc.
|
* Copyright (C) 2014 Google, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -249,8 +249,7 @@ struct tegra_xusb {
|
||||||
|
|
||||||
struct device *genpd_dev_host;
|
struct device *genpd_dev_host;
|
||||||
struct device *genpd_dev_ss;
|
struct device *genpd_dev_ss;
|
||||||
struct device_link *genpd_dl_host;
|
bool use_genpd;
|
||||||
struct device_link *genpd_dl_ss;
|
|
||||||
|
|
||||||
struct phy **phys;
|
struct phy **phys;
|
||||||
unsigned int num_phys;
|
unsigned int num_phys;
|
||||||
|
@ -821,36 +820,12 @@ static void tegra_xusb_phy_disable(struct tegra_xusb *tegra)
|
||||||
|
|
||||||
static int tegra_xusb_runtime_suspend(struct device *dev)
|
static int tegra_xusb_runtime_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
struct tegra_xusb *tegra = dev_get_drvdata(dev);
|
|
||||||
|
|
||||||
regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
|
|
||||||
tegra_xusb_clk_disable(tegra);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tegra_xusb_runtime_resume(struct device *dev)
|
static int tegra_xusb_runtime_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct tegra_xusb *tegra = dev_get_drvdata(dev);
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = tegra_xusb_clk_enable(tegra);
|
|
||||||
if (err) {
|
|
||||||
dev_err(dev, "failed to enable clocks: %d\n", err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 0;
|
return 0;
|
||||||
|
|
||||||
disable_clk:
|
|
||||||
tegra_xusb_clk_disable(tegra);
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
@ -1026,10 +1001,9 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra)
|
||||||
static void tegra_xusb_powerdomain_remove(struct device *dev,
|
static void tegra_xusb_powerdomain_remove(struct device *dev,
|
||||||
struct tegra_xusb *tegra)
|
struct tegra_xusb *tegra)
|
||||||
{
|
{
|
||||||
if (tegra->genpd_dl_ss)
|
if (!tegra->use_genpd)
|
||||||
device_link_del(tegra->genpd_dl_ss);
|
return;
|
||||||
if (tegra->genpd_dl_host)
|
|
||||||
device_link_del(tegra->genpd_dl_host);
|
|
||||||
if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss))
|
if (!IS_ERR_OR_NULL(tegra->genpd_dev_ss))
|
||||||
dev_pm_domain_detach(tegra->genpd_dev_ss, true);
|
dev_pm_domain_detach(tegra->genpd_dev_ss, true);
|
||||||
if (!IS_ERR_OR_NULL(tegra->genpd_dev_host))
|
if (!IS_ERR_OR_NULL(tegra->genpd_dev_host))
|
||||||
|
@ -1055,20 +1029,84 @@ static int tegra_xusb_powerdomain_init(struct device *dev,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host,
|
tegra->use_genpd = true;
|
||||||
DL_FLAG_PM_RUNTIME |
|
|
||||||
DL_FLAG_STATELESS);
|
return 0;
|
||||||
if (!tegra->genpd_dl_host) {
|
}
|
||||||
dev_err(dev, "adding host device link failed!\n");
|
|
||||||
return -ENODEV;
|
static int tegra_xusb_unpowergate_partitions(struct tegra_xusb *tegra)
|
||||||
|
{
|
||||||
|
struct device *dev = tegra->dev;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (tegra->use_genpd) {
|
||||||
|
rc = pm_runtime_get_sync(tegra->genpd_dev_ss);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to enable XUSB SS partition\n");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pm_runtime_get_sync(tegra->genpd_dev_host);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to enable XUSB Host partition\n");
|
||||||
|
pm_runtime_put_sync(tegra->genpd_dev_ss);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
} 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\n");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
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\n");
|
||||||
|
tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss,
|
return 0;
|
||||||
DL_FLAG_PM_RUNTIME |
|
}
|
||||||
DL_FLAG_STATELESS);
|
|
||||||
if (!tegra->genpd_dl_ss) {
|
static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra)
|
||||||
dev_err(dev, "adding superspeed device link failed!\n");
|
{
|
||||||
return -ENODEV;
|
struct device *dev = tegra->dev;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (tegra->use_genpd) {
|
||||||
|
rc = pm_runtime_put_sync(tegra->genpd_dev_host);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to disable XUSB Host partition\n");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pm_runtime_put_sync(tegra->genpd_dev_ss);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to disable XUSB SS partition\n");
|
||||||
|
pm_runtime_get_sync(tegra->genpd_dev_host);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to disable XUSB Host partition\n");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
|
||||||
|
if (rc < 0) {
|
||||||
|
dev_err(dev, "failed to disable XUSB SS partition\n");
|
||||||
|
tegra_powergate_sequence_power_up(TEGRA_POWERGATE_XUSBC,
|
||||||
|
tegra->host_clk,
|
||||||
|
tegra->host_rst);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1432,25 +1470,6 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||||
err);
|
err);
|
||||||
goto put_padctl;
|
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 {
|
} else {
|
||||||
err = tegra_xusb_powerdomain_init(&pdev->dev, tegra);
|
err = tegra_xusb_powerdomain_init(&pdev->dev, tegra);
|
||||||
if (err)
|
if (err)
|
||||||
|
@ -1525,10 +1544,22 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||||
*/
|
*/
|
||||||
platform_set_drvdata(pdev, tegra);
|
platform_set_drvdata(pdev, tegra);
|
||||||
|
|
||||||
|
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);
|
err = tegra_xusb_phy_enable(tegra);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err);
|
dev_err(&pdev->dev, "failed to enable PHYs: %d\n", err);
|
||||||
goto put_hcd;
|
goto disable_regulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1547,30 +1578,22 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||||
goto disable_phy;
|
goto disable_phy;
|
||||||
}
|
}
|
||||||
|
|
||||||
pm_runtime_enable(&pdev->dev);
|
err = tegra_xusb_unpowergate_partitions(tegra);
|
||||||
|
if (err)
|
||||||
if (!pm_runtime_enabled(&pdev->dev))
|
|
||||||
err = tegra_xusb_runtime_resume(&pdev->dev);
|
|
||||||
else
|
|
||||||
err = pm_runtime_get_sync(&pdev->dev);
|
|
||||||
|
|
||||||
if (err < 0) {
|
|
||||||
dev_err(&pdev->dev, "failed to enable device: %d\n", err);
|
|
||||||
goto free_firmware;
|
goto free_firmware;
|
||||||
}
|
|
||||||
|
|
||||||
tegra_xusb_config(tegra);
|
tegra_xusb_config(tegra);
|
||||||
|
|
||||||
err = tegra_xusb_load_firmware(tegra);
|
err = tegra_xusb_load_firmware(tegra);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&pdev->dev, "failed to load firmware: %d\n", err);
|
dev_err(&pdev->dev, "failed to load firmware: %d\n", err);
|
||||||
goto put_rpm;
|
goto powergate;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED);
|
err = usb_add_hcd(tegra->hcd, tegra->xhci_irq, IRQF_SHARED);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err);
|
dev_err(&pdev->dev, "failed to add USB HCD: %d\n", err);
|
||||||
goto put_rpm;
|
goto powergate;
|
||||||
}
|
}
|
||||||
|
|
||||||
device_wakeup_enable(tegra->hcd->self.controller);
|
device_wakeup_enable(tegra->hcd->self.controller);
|
||||||
|
@ -1622,24 +1645,21 @@ static int tegra_xusb_probe(struct platform_device *pdev)
|
||||||
usb_put_hcd(xhci->shared_hcd);
|
usb_put_hcd(xhci->shared_hcd);
|
||||||
remove_usb2:
|
remove_usb2:
|
||||||
usb_remove_hcd(tegra->hcd);
|
usb_remove_hcd(tegra->hcd);
|
||||||
put_rpm:
|
powergate:
|
||||||
if (!pm_runtime_status_suspended(&pdev->dev))
|
tegra_xusb_powergate_partitions(tegra);
|
||||||
tegra_xusb_runtime_suspend(&pdev->dev);
|
|
||||||
put_hcd:
|
|
||||||
usb_put_hcd(tegra->hcd);
|
|
||||||
free_firmware:
|
free_firmware:
|
||||||
dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
|
dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
|
||||||
tegra->fw.phys);
|
tegra->fw.phys);
|
||||||
disable_phy:
|
disable_phy:
|
||||||
tegra_xusb_phy_disable(tegra);
|
tegra_xusb_phy_disable(tegra);
|
||||||
pm_runtime_disable(&pdev->dev);
|
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:
|
put_powerdomains:
|
||||||
if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
|
tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
|
||||||
tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
|
|
||||||
tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
|
|
||||||
} else {
|
|
||||||
tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
|
|
||||||
}
|
|
||||||
put_padctl:
|
put_padctl:
|
||||||
tegra_xusb_padctl_put(tegra->padctl);
|
tegra_xusb_padctl_put(tegra->padctl);
|
||||||
return err;
|
return err;
|
||||||
|
@ -1664,15 +1684,13 @@ static int tegra_xusb_remove(struct platform_device *pdev)
|
||||||
pm_runtime_put_sync(&pdev->dev);
|
pm_runtime_put_sync(&pdev->dev);
|
||||||
pm_runtime_disable(&pdev->dev);
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
|
||||||
if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) {
|
tegra_xusb_powergate_partitions(tegra);
|
||||||
tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC);
|
|
||||||
tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA);
|
tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
|
||||||
} else {
|
|
||||||
tegra_xusb_powerdomain_remove(&pdev->dev, tegra);
|
|
||||||
}
|
|
||||||
|
|
||||||
tegra_xusb_phy_disable(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);
|
tegra_xusb_padctl_put(tegra->padctl);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in a new issue