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:
JC Kuo 2021-01-20 15:34:13 +08:00 коммит произвёл Thierry Reding
Родитель 23eca83155
Коммит 41a7426d25
1 изменённых файлов: 112 добавлений и 94 удалений

Просмотреть файл

@ -2,7 +2,7 @@
/*
* 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.
*/
@ -249,8 +249,7 @@ 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;
bool use_genpd;
struct phy **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)
{
struct tegra_xusb *tegra = dev_get_drvdata(dev);
regulator_bulk_disable(tegra->soc->num_supplies, tegra->supplies);
tegra_xusb_clk_disable(tegra);
return 0;
}
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;
disable_clk:
tegra_xusb_clk_disable(tegra);
return err;
}
#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,
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 (!tegra->use_genpd)
return;
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))
@ -1055,20 +1029,84 @@ 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;
tegra->use_genpd = true;
return 0;
}
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,
DL_FLAG_PM_RUNTIME |
DL_FLAG_STATELESS);
if (!tegra->genpd_dl_ss) {
dev_err(dev, "adding superspeed device link failed!\n");
return -ENODEV;
return 0;
}
static int tegra_xusb_powergate_partitions(struct tegra_xusb *tegra)
{
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;
@ -1432,25 +1470,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)
@ -1525,10 +1544,22 @@ static int tegra_xusb_probe(struct platform_device *pdev)
*/
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);
if (err < 0) {
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;
}
pm_runtime_enable(&pdev->dev);
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);
err = tegra_xusb_unpowergate_partitions(tegra);
if (err)
goto free_firmware;
}
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;
}
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);
@ -1622,24 +1645,21 @@ put_usb3:
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);
put_hcd:
usb_put_hcd(tegra->hcd);
powergate:
tegra_xusb_powergate_partitions(tegra);
free_firmware:
dma_free_coherent(&pdev->dev, tegra->fw.size, tegra->fw.virt,
tegra->fw.phys);
disable_phy:
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:
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;
@ -1664,15 +1684,13 @@ static int tegra_xusb_remove(struct platform_device *pdev)
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&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_powerdomain_remove(&pdev->dev, tegra);
}
tegra_xusb_powergate_partitions(tegra);
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;