usb: host: Add power_off_on_bus_suspend option
authorBenoit Goby <benoit@android.com>
Fri, 10 Sep 2010 08:16:15 +0000 (01:16 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:28:37 +0000 (16:28 -0700)
If the device connected to a port has out-of-band wakeup
signaling, the phy and controller may be powered off on bus suspend.

Change-Id: Ia206f05d01160411b97aefa83045cd759d35b66d
Signed-off-by: Benoit Goby <benoit@android.com>
drivers/usb/host/ehci-tegra.c
include/linux/tegra_usb.h [new file with mode: 0644]

index 7f45f66fc82e2027de8c4364fa34051985b91a7b..aea114f1dc3f3480fe806b54dc5f7a52175533b7 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <linux/clk.h>
 #include <linux/platform_device.h>
+#include <linux/tegra_usb.h>
 #include <linux/irq.h>
 #include <linux/usb/otg.h>
 #include <mach/usb_phy.h>
@@ -48,7 +49,9 @@ struct tegra_ehci_hcd {
        struct clk *clk;
        struct otg_transceiver *transceiver;
        int host_resumed;
+       int bus_suspended;
        struct tegra_ehci_context context;
+       int power_down_on_bus_suspend;
 };
 
 static void tegra_ehci_power_up(struct usb_hcd *hcd)
@@ -137,6 +140,130 @@ static void tegra_ehci_restart(struct usb_hcd *hcd)
        ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
 }
 
+static int tegra_usb_suspend(struct usb_hcd *hcd)
+{
+       struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+       struct ehci_regs __iomem *hw = tegra->ehci->regs;
+       struct tegra_ehci_context *context = &tegra->context;
+       unsigned long flags;
+
+       spin_lock_irqsave(&tegra->ehci->lock, flags);
+
+       context->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3;
+
+       if (context->port_speed > TEGRA_USB_PHY_PORT_HIGH) {
+               /* If no device connection or invalid speeds,
+                * don't save the context */
+               context->valid = false;
+       } else {
+               context->command        = readl(&hw->command);
+               context->intr_enable    = readl(&hw->intr_enable);
+               context->frame_list     = readl(&hw->frame_list);
+               context->async_next     = readl(&hw->async_next);
+               context->txfilltunning  = readl(&hw->reserved[2]);
+               context->otgsc          = readl(&hw->reserved[18]);
+               context->valid = true;
+       }
+
+       ehci_halt(tegra->ehci);
+       clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+       spin_unlock_irqrestore(&tegra->ehci->lock, flags);
+
+       tegra_ehci_power_down(ehci_to_hcd(tegra->ehci));
+       return 0;
+}
+
+static int tegra_usb_resume(struct usb_hcd *hcd)
+{
+       struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+       struct tegra_ehci_context *context = &tegra->context;
+       struct ehci_regs __iomem *hw = tegra->ehci->regs;
+       unsigned long val;
+
+       set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+       tegra_ehci_power_up(ehci_to_hcd(tegra->ehci));
+
+       if (!context->valid)
+               goto restart;
+
+       /* Restore register context */
+       writel(TEGRA_USB_USBMODE_HOST, &hw->reserved[19]);
+       writel(context->otgsc,         &hw->reserved[18]);
+       writel(context->txfilltunning, &hw->reserved[2]);
+       writel(context->async_next,    &hw->async_next);
+       writel(context->frame_list,    &hw->frame_list);
+       writel(context->command,       &hw->command);
+
+       /* Enable Port Power */
+       val = readl(&hw->port_status[0]);
+       val |= PORT_POWER;
+       writel(val, &hw->port_status[0]);
+       udelay(10);
+
+       /* Program the field PTC in PORTSC based on the saved speed mode */
+       val = readl(&hw->port_status[0]);
+       val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
+       if (context->port_speed == TEGRA_USB_PHY_PORT_HIGH)
+               val |= TEGRA_USB_PORTSC1_PTC(5);
+       else if (context->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL)
+               val |= TEGRA_USB_PORTSC1_PTC(6);
+       else if (context->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW)
+               val |= TEGRA_USB_PORTSC1_PTC(7);
+       writel(val, &hw->port_status[0]);
+       udelay(10);
+
+       /* Disable test mode by setting PTC field to NORMAL_OP */
+       val = readl(&hw->port_status[0]);
+       val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
+       writel(val, &hw->port_status[0]);
+       udelay(10);
+
+       /* Poll until CCS is enabled */
+       if (handshake(tegra->ehci, &hw->port_status[0], PORT_CONNECT,
+                                                       PORT_CONNECT, 2000)) {
+               pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__);
+               goto restart;
+       }
+
+       /* Poll until PE is enabled */
+       if (handshake(tegra->ehci, &hw->port_status[0], PORT_PE,
+                                                       PORT_PE, 2000)) {
+               pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__);
+               goto restart;
+       }
+
+       /* Clear the PCI status, to avoid an interrupt taken upon resume */
+       val = readl(&hw->status);
+       val |= STS_PCD;
+       writel(val, &hw->status);
+
+       /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */
+       val = readl(&hw->port_status[0]);
+       if ((val & PORT_POWER) && (val & PORT_PE)) {
+               val |= PORT_SUSPEND;
+               writel(val, &hw->port_status[0]);
+
+               /* Wait until port suspend completes */
+               if (handshake(tegra->ehci, &hw->port_status[0], PORT_SUSPEND,
+                                                       PORT_SUSPEND, 1000)) {
+                       pr_err("%s: timeout waiting for PORT_SUSPEND\n",
+                                                               __func__);
+                       goto restart;
+               }
+       }
+
+       /* Restore interrupt register */
+       writel(context->intr_enable, &hw->intr_enable);
+       udelay(10);
+
+       return 0;
+
+restart:
+       tegra_ehci_restart(hcd);
+       return 0;
+}
+
 static int tegra_ehci_reset(struct usb_hcd *hcd)
 {
        unsigned long temp;
@@ -222,6 +349,32 @@ static int tegra_ehci_setup(struct usb_hcd *hcd)
        return retval;
 }
 
+static int tegra_ehci_bus_suspend(struct usb_hcd *hcd)
+{
+       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);
+               tegra->bus_suspended = 1;
+       }
+
+       return error_status;
+}
+
+static int tegra_ehci_bus_resume(struct usb_hcd *hcd)
+{
+       struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
+
+       if (tegra->bus_suspended && tegra->power_down_on_bus_suspend) {
+               tegra_usb_resume(hcd);
+               tegra->bus_suspended = 0;
+       }
+
+       return ehci_bus_resume(hcd);
+}
+
 static const struct hc_driver tegra_ehci_hc_driver = {
        .description            = hcd_name,
        .product_desc           = "Tegra EHCI Host Controller",
@@ -244,8 +397,8 @@ static const struct hc_driver tegra_ehci_hc_driver = {
        .hub_control            = tegra_ehci_hub_control,
        .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
 #ifdef CONFIG_PM
-       .bus_suspend            = ehci_bus_suspend,
-       .bus_resume             = ehci_bus_resume,
+       .bus_suspend            = tegra_ehci_bus_suspend,
+       .bus_resume             = tegra_ehci_bus_resume,
 #endif
        .relinquish_port        = ehci_relinquish_port,
        .port_handed_over       = ehci_port_handed_over,
@@ -257,11 +410,18 @@ static int tegra_ehci_probe(struct platform_device *pdev)
        struct usb_hcd *hcd;
        struct ehci_hcd *ehci;
        struct tegra_ehci_hcd *tegra;
+       struct tegra_ehci_platform_data *pdata;
        struct tegra_utmip_config *config;
        int err = 0;
        int irq;
        int instance = pdev->id;
 
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(&pdev->dev, "Platform data missing\n");
+               return -EINVAL;
+       }
+
        tegra = kzalloc(sizeof(struct tegra_ehci_hcd), GFP_KERNEL);
        if (!tegra)
                return -ENOMEM;
@@ -302,7 +462,7 @@ static int tegra_ehci_probe(struct platform_device *pdev)
                goto fail_io;
        }
 
-       config = pdev->dev.platform_data;
+       config = pdata->phy_config;
 
        tegra->phy = tegra_usb_phy_open(instance, hcd->regs, config,
                                                TEGRA_USB_PHY_MODE_HOST);
@@ -320,6 +480,7 @@ static int tegra_ehci_probe(struct platform_device *pdev)
 
        tegra_usb_phy_power_on(tegra->phy);
        tegra->host_resumed = 1;
+       tegra->power_down_on_bus_suspend = pdata->power_down_on_bus_suspend;
 
        irq = platform_get_irq(pdev, 0);
        if (!irq) {
@@ -334,7 +495,7 @@ static int tegra_ehci_probe(struct platform_device *pdev)
        tegra->ehci = ehci;
 
 #ifdef CONFIG_USB_OTG_UTILS
-       if (instance == 0) {
+       if (pdata->operating_mode == TEGRA_USB_OTG) {
                tegra->transceiver = otg_get_transceiver();
                if (tegra->transceiver)
                        otg_set_host(tegra->transceiver, &hcd->self);
@@ -375,128 +536,25 @@ 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);
-       struct tegra_ehci_context *context = &tegra->context;
-       struct ehci_regs __iomem *hw = tegra->ehci->regs;
-       unsigned long val;
-
-       set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-       tegra_ehci_power_up(ehci_to_hcd(tegra->ehci));
 
-       if (!context->valid)
-               goto restart;
-
-       /* Restore register context */
-       writel(TEGRA_USB_USBMODE_HOST, &hw->reserved[19]);
-       writel(context->otgsc,         &hw->reserved[18]);
-       writel(context->txfilltunning, &hw->reserved[2]);
-       writel(context->async_next,    &hw->async_next);
-       writel(context->frame_list,    &hw->frame_list);
-       writel(context->command,       &hw->command);
+       if (tegra->bus_suspended)
+               return 0;
 
-       /* Enable Port Power */
-       val = readl(&hw->port_status[0]);
-       val |= PORT_POWER;
-       writel(val, &hw->port_status[0]);
-       udelay(10);
-
-       /* Program the field PTC in PORTSC based on the saved speed mode */
-       val = readl(&hw->port_status[0]);
-       val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
-       if (context->port_speed == TEGRA_USB_PHY_PORT_HIGH)
-               val |= TEGRA_USB_PORTSC1_PTC(5);
-       else if (context->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL)
-               val |= TEGRA_USB_PORTSC1_PTC(6);
-       else if (context->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW)
-               val |= TEGRA_USB_PORTSC1_PTC(7);
-       writel(val, &hw->port_status[0]);
-       udelay(10);
-
-       /* Disable test mode by setting PTC field to NORMAL_OP */
-       val = readl(&hw->port_status[0]);
-       val &= ~(TEGRA_USB_PORTSC1_PTC(~0));
-       writel(val, &hw->port_status[0]);
-       udelay(10);
-
-       /* Poll until CCS is enabled */
-       if (handshake(tegra->ehci, &hw->port_status[0], PORT_CONNECT,
-                                                       PORT_CONNECT, 2000)) {
-               pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__);
-               goto restart;
-       }
-
-       /* Poll until PE is enabled */
-       if (handshake(tegra->ehci, &hw->port_status[0], PORT_PE,
-                                                       PORT_PE, 2000)) {
-               pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__);
-               goto restart;
-       }
-
-       /* Clear the PCI status, to avoid an interrupt taken upon resume */
-       val = readl(&hw->status);
-       val |= STS_PCD;
-       writel(val, &hw->status);
-
-       /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */
-       val = readl(&hw->port_status[0]);
-       if ((val & PORT_POWER) && (val & PORT_PE)) {
-               val |= PORT_SUSPEND;
-               writel(val, &hw->port_status[0]);
-
-               /* Wait until port suspend completes */
-               if (handshake(tegra->ehci, &hw->port_status[0], PORT_SUSPEND,
-                                                       PORT_SUSPEND, 1000)) {
-                       pr_err("%s: timeout waiting for PORT_SUSPEND\n",
-                                                               __func__);
-                       goto restart;
-               }
-       }
-
-       /* Restore interrupt register */
-       writel(context->intr_enable, &hw->intr_enable);
-       udelay(10);
-       return 0;
-
-restart:
-       tegra_ehci_restart(hcd);
-       return 0;
+       return tegra_usb_resume(hcd);
 }
 
 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);
-       struct ehci_regs __iomem *hw = tegra->ehci->regs;
-       struct tegra_ehci_context *context = &tegra->context;
-       unsigned long flags;
+
+       if (tegra->bus_suspended)
+               return 0;
 
        if (time_before(jiffies, tegra->ehci->next_statechange))
                msleep(10);
 
-       spin_lock_irqsave(&tegra->ehci->lock, flags);
-
-       context->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3;
-
-       if (context->port_speed > TEGRA_USB_PHY_PORT_HIGH) {
-               /* If no device connection or invalid speeds,
-                * don't save the context */
-               context->valid = false;
-       } else {
-               context->command        = readl(&hw->command);
-               context->intr_enable    = readl(&hw->intr_enable);
-               context->frame_list     = readl(&hw->frame_list);
-               context->async_next     = readl(&hw->async_next);
-               context->txfilltunning  = readl(&hw->reserved[2]);
-               context->otgsc          = readl(&hw->reserved[18]);
-               context->valid = true;
-       }
-
-       ehci_halt(tegra->ehci);
-       clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-
-       spin_unlock_irqrestore(&tegra->ehci->lock, flags);
-
-       tegra_ehci_power_down(ehci_to_hcd(tegra->ehci));
-       return 0;
+       return tegra_usb_suspend(hcd);
 }
 #endif
 
diff --git a/include/linux/tegra_usb.h b/include/linux/tegra_usb.h
new file mode 100644 (file)
index 0000000..2947ed2
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef _TEGRA_USB_H_
+#define _TEGRA_USB_H_
+
+enum tegra_usb_operating_modes {
+       TEGRA_USB_DEVICE,
+       TEGRA_USB_HOST,
+       TEGRA_USB_OTG,
+};
+
+struct tegra_ehci_platform_data {
+       enum tegra_usb_operating_modes operating_mode;
+       /* power down the phy on bus suspend */
+       int power_down_on_bus_suspend;
+       void *phy_config;
+};
+
+#endif /* _TEGRA_USB_H_ */