summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJoshua Cha <joshuac@nvidia.com>2011-07-27 11:39:54 +0900
committerNiket Sirsi <nsirsi@nvidia.com>2011-07-27 15:05:50 -0700
commita5dc52ce49d00bba963544251a1fe858e774780b (patch)
treee9510f6d33d89c72840647fa8ecedcaf2795c044 /drivers
parenteb7639ad28dae74d982b29f6caa7c98ec0baa501 (diff)
usb: host: ehci-tegra: Power saving on USB suspend
In current implementation, the HSIC port cannot have PHY power off without re-enumeration when it is suspended. This patch will do following upon USB port suspend: * disable the USB PHY clock * disable the shared EMC clock * keep the USB core but running it at 0.95v VDD With this patch, VDD can be reduced to when the HSIC port is suspended. There is no re-enumeration when HSIC port is resumed, also it will not affect the normal LP0 suspend/resume. BUG 817725 BUG 796594 (cherry picked from http://git-master/r/30224) Change-Id: Ie0553335ae50bca1a91e94d46d14bb9127874ae4 Reviewed-on: http://git-master/r/37226 Reviewed-by: Joshua Cha <joshuac@nvidia.com> Tested-by: Joshua Cha <joshuac@nvidia.com> Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/host/ehci-tegra.c85
1 files changed, 65 insertions, 20 deletions
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 116b0806af03..ea7b4cafcbf5 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -47,13 +47,15 @@ struct tegra_ehci_hcd {
struct ehci_hcd *ehci;
struct tegra_usb_phy *phy;
struct clk *clk;
+ struct clk *clk_min;
struct clk *emc_clk;
struct clk *sclk_clk;
struct otg_transceiver *transceiver;
+ int hub_suspend_req;
int host_resumed;
- int bus_suspended;
+ int bus_is_power_down;
int port_resuming;
- int power_down_on_bus_suspend;
+ int require_power_down_on_bus_suspend;
struct delayed_work work;
enum tegra_usb_phy_port_speed port_speed;
};
@@ -69,6 +71,7 @@ static void tegra_ehci_power_up(struct usb_hcd *hcd, bool is_dpd)
#endif
tegra_usb_phy_power_on(tegra->phy, is_dpd);
tegra->host_resumed = 1;
+ tegra->bus_is_power_down = 0;
}
static void tegra_ehci_power_down(struct usb_hcd *hcd, bool is_dpd)
@@ -77,11 +80,16 @@ static void tegra_ehci_power_down(struct usb_hcd *hcd, bool is_dpd)
tegra->host_resumed = 0;
tegra_usb_phy_power_off(tegra->phy, is_dpd);
+ if (tegra->bus_is_power_down) {
+ clk_disable(tegra->clk_min);
+ return;
+ }
#ifndef CONFIG_USB_HOTPLUG
clk_disable(tegra->clk);
#endif
- clk_disable(tegra->emc_clk);
clk_disable(tegra->sclk_clk);
+ clk_disable(tegra->emc_clk);
+ tegra->hub_suspend_req = 0;
}
static irqreturn_t tegra_ehci_irq (struct usb_hcd *hcd)
@@ -184,6 +192,9 @@ static int tegra_ehci_hub_control(
pr_err("%s: timeout waiting for SUSPEND\n", __func__);
set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports);
+
+ tegra->hub_suspend_req = 1;
+
goto done;
}
@@ -529,24 +540,51 @@ static int tegra_ehci_setup(struct usb_hcd *hcd)
#ifdef CONFIG_PM
static int tegra_ehci_bus_suspend(struct usb_hcd *hcd)
{
+ int ret = 0;
struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
- int error_status = 0;
- error_status = ehci_bus_suspend(hcd);
- if (!error_status && tegra->power_down_on_bus_suspend) {
- tegra_usb_suspend(hcd, false);
- tegra->bus_suspended = 1;
+ if (0 != (ret = ehci_bus_suspend(hcd)))
+ return ret;
+
+ if (tegra->require_power_down_on_bus_suspend &&
+ tegra->hub_suspend_req) {
+ tegra_usb_set_phy_clock(tegra->phy, 0);
+
+ if (0 != (ret = clk_enable(tegra->clk_min))) {
+ pr_err("HSIC USB core clk enable failed\n");
+ return ret;
+ }
+
+ clk_disable(tegra->clk);
+
+ clk_disable(tegra->emc_clk);
+
+ tegra->hub_suspend_req = 0;
+ tegra->bus_is_power_down = 1;
}
- return error_status;
+ return ret;
}
static int tegra_ehci_bus_resume(struct usb_hcd *hcd)
{
struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+ int ret;
+
+ if (tegra->require_power_down_on_bus_suspend &&
+ tegra->bus_is_power_down) {
+ if (0 != (ret = clk_enable(tegra->clk)))
+ pr_err("HSIC usb PHY clk enable failed\n");
+
+ if (0 != (ret = clk_enable(tegra->emc_clk)))
+ pr_err("HSIC USB EMC clk enable failed\n");
+
+ clk_disable(tegra->clk_min);
- if (tegra->bus_suspended && tegra->power_down_on_bus_suspend) {
- tegra_usb_resume(hcd, false);
- tegra->bus_suspended = 0;
+ tegra_usb_set_phy_clock(tegra->phy, 1);
+
+ msleep(50);
+
+ tegra->bus_is_power_down = 0;
}
return ehci_bus_resume(hcd);
@@ -852,7 +890,7 @@ static int tegra_ehci_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, tegra);
- tegra->clk = clk_get(&pdev->dev, NULL);
+ tegra->clk = clk_get(&pdev->dev, "usb2");
if (IS_ERR(tegra->clk)) {
dev_err(&pdev->dev, "Can't get ehci clock\n");
err = PTR_ERR(tegra->clk);
@@ -863,6 +901,15 @@ static int tegra_ehci_probe(struct platform_device *pdev)
if (err)
goto fail_clken;
+ tegra->clk_min = clk_get(&pdev->dev, "usb2min");
+ if (IS_ERR(tegra->clk_min)) {
+ dev_err(&pdev->dev, "Can't get ehci clock fast\n");
+ err = PTR_ERR(tegra->clk_min);
+ goto fail_clk_min;
+ }
+ /* only need enable usb2min at the USB suspend time to ensure the
+ * VDD core at least is 1.1v
+ */
tegra->sclk_clk = clk_get(&pdev->dev, "sclk");
if (IS_ERR(tegra->sclk_clk)) {
@@ -916,7 +963,9 @@ static int tegra_ehci_probe(struct platform_device *pdev)
}
tegra->host_resumed = 1;
- tegra->power_down_on_bus_suspend = pdata->power_down_on_bus_suspend;
+
+ tegra->require_power_down_on_bus_suspend =
+ pdata->power_down_on_bus_suspend;
tegra->ehci = hcd_to_ehci(hcd);
irq = platform_get_irq(pdev, 0);
@@ -973,6 +1022,8 @@ fail_emc_clk:
fail_sclk_clk:
clk_disable(tegra->clk);
fail_clken:
+ clk_put(tegra->clk_min);
+fail_clk_min:
clk_put(tegra->clk);
fail_clk:
usb_put_hcd(hcd);
@@ -987,9 +1038,6 @@ static int tegra_ehci_resume(struct platform_device *pdev)
struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev);
struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci);
- if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend))
- return 0;
-
return tegra_usb_resume(hcd, true);
}
@@ -998,9 +1046,6 @@ static int tegra_ehci_suspend(struct platform_device *pdev, pm_message_t state)
struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev);
struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci);
- if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend))
- return 0;
-
if (time_before(jiffies, tegra->ehci->next_statechange))
msleep(10);