From 7b284e782b70291aab65ec657bf685bc3b3e2688 Mon Sep 17 00:00:00 2001 From: Benoit Goby Date: Fri, 10 Sep 2010 01:16:15 -0700 Subject: [PATCH] usb: host: Add power_off_on_bus_suspend option 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 --- drivers/usb/host/ehci-tegra.c | 286 ++++++++++++++++++++-------------- include/linux/tegra_usb.h | 35 +++++ 2 files changed, 207 insertions(+), 114 deletions(-) create mode 100644 include/linux/tegra_usb.h diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 7f45f66fc82e..aea114f1dc3f 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -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 index 000000000000..2947ed26879a --- /dev/null +++ b/include/linux/tegra_usb.h @@ -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_ */ -- 2.34.1