From 749da5f82fe33ff68dd4aa1a5e35cd9aa6246dab Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 4 Mar 2010 17:05:08 -0500 Subject: USB: straighten out port feature vs. port status usage This patch (as1349b) clears up the confusion in many USB host controller drivers between port features and port statuses. In mosty cases it's true that the status bit is in the position given by the corresponding feature value, but that's not always true and it's not guaranteed in the USB spec. There's no functional change, just replacing expressions of the form (1 << USB_PORT_FEAT_x) with USB_PORT_STAT_x, which has the same value. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'drivers/usb/host/ehci-hub.c') diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index c7178bcde67..c44018109a1 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -659,7 +659,7 @@ static int ehci_hub_control ( * Even if OWNER is set, so the port is owned by the * companion controller, khubd needs to be able to clear * the port-change status bits (especially - * USB_PORT_FEAT_C_CONNECTION). + * USB_PORT_STAT_C_CONNECTION). */ switch (wValue) { @@ -729,12 +729,12 @@ static int ehci_hub_control ( // wPortChange bits if (temp & PORT_CSC) - status |= 1 << USB_PORT_FEAT_C_CONNECTION; + status |= USB_PORT_STAT_C_CONNECTION << 16; if (temp & PORT_PEC) - status |= 1 << USB_PORT_FEAT_C_ENABLE; + status |= USB_PORT_STAT_C_ENABLE << 16; if ((temp & PORT_OCC) && !ignore_oc){ - status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + status |= USB_PORT_STAT_C_OVERCURRENT << 16; /* * Hubs should disable port power on over-current. @@ -791,7 +791,7 @@ static int ehci_hub_control ( if ((temp & PORT_RESET) && time_after_eq(jiffies, ehci->reset_done[wIndex])) { - status |= 1 << USB_PORT_FEAT_C_RESET; + status |= USB_PORT_STAT_C_RESET << 16; ehci->reset_done [wIndex] = 0; /* force reset to complete */ @@ -833,7 +833,7 @@ static int ehci_hub_control ( */ if (temp & PORT_CONNECT) { - status |= 1 << USB_PORT_FEAT_CONNECTION; + status |= USB_PORT_STAT_CONNECTION; // status may be from integrated TT if (ehci->has_hostpc) { temp1 = ehci_readl(ehci, hostpc_reg); @@ -842,11 +842,11 @@ static int ehci_hub_control ( status |= ehci_port_speed(ehci, temp); } if (temp & PORT_PE) - status |= 1 << USB_PORT_FEAT_ENABLE; + status |= USB_PORT_STAT_ENABLE; /* maybe the port was unsuspended without our knowledge */ if (temp & (PORT_SUSPEND|PORT_RESUME)) { - status |= 1 << USB_PORT_FEAT_SUSPEND; + status |= USB_PORT_STAT_SUSPEND; } else if (test_bit(wIndex, &ehci->suspended_ports)) { clear_bit(wIndex, &ehci->suspended_ports); ehci->reset_done[wIndex] = 0; @@ -855,13 +855,13 @@ static int ehci_hub_control ( } if (temp & PORT_OC) - status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + status |= USB_PORT_STAT_OVERCURRENT; if (temp & PORT_RESET) - status |= 1 << USB_PORT_FEAT_RESET; + status |= USB_PORT_STAT_RESET; if (temp & PORT_POWER) - status |= 1 << USB_PORT_FEAT_POWER; + status |= USB_PORT_STAT_POWER; if (test_bit(wIndex, &ehci->port_c_suspend)) - status |= 1 << USB_PORT_FEAT_C_SUSPEND; + status |= USB_PORT_STAT_C_SUSPEND << 16; #ifndef VERBOSE_DEBUG if (status & ~0xffff) /* only if wPortChange is interesting */ -- cgit v1.2.3-70-g09d2 From eab80de01cb398419ef3305f35abcb367c647c8b Mon Sep 17 00:00:00 2001 From: Alek Du Date: Mon, 10 May 2010 11:17:49 +0800 Subject: USB: EHCI: clear PHCD before resuming This is a bug fix for PHCD (phy clock disable) low power feature: After PHCD is set, any write to PORTSC register is illegal, so when resume ports, clear PHCD bit first. Signed-off-by: Alek Du Cc: David Brownell Cc: Alan Stern Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers/usb/host/ehci-hub.c') diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index c44018109a1..ef956220f85 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -294,6 +294,16 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); while (i--) { + /* clear phy low power mode before resume */ + if (ehci->has_hostpc) { + u32 __iomem *hostpc_reg = + (u32 __iomem *)((u8 *)ehci->regs + + HOSTPC0 + 4 * (i & 0xff)); + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp & ~HOSTPC_PHCD, + hostpc_reg); + mdelay(5); + } temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && @@ -678,6 +688,13 @@ static int ehci_hub_control ( if (temp & PORT_SUSPEND) { if ((temp & PORT_PE) == 0) goto error; + /* clear phy low power mode before resume */ + if (hostpc_reg) { + temp1 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, + hostpc_reg); + mdelay(5); + } /* resume signaling for 20 msec */ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); ehci_writel(ehci, temp | PORT_RESUME, -- cgit v1.2.3-70-g09d2 From 16032c4f5b291af541e9114a09ea20ff5a0dc474 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 12 May 2010 18:21:35 -0400 Subject: USB: EHCI: fix controller wakeup flag settings during suspend This patch (as1380) fixes a bug in the wakeup settings for EHCI host controllers. When the controller is suspended, if it isn't enabled for remote wakeup then we have to turn off all the port wakeup flags. Disabling PCI PME# isn't good enough, because some systems (Intel) evidently use alternate wakeup signalling paths. In addition, the patch improves the handling of the Intel Moorestown hardware by performing various power-up and power-down delays just once instead of once for each port (i.e., the delays are moved outside of the port loops). This requires extra code, but the total delay time is reduced. There are also a few additional minor cleanups. Signed-off-by: Alan Stern Reported-by: Ondrej Zary CC: Alek Du CC: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-au1xxx.c | 16 +--- drivers/usb/host/ehci-fsl.c | 2 + drivers/usb/host/ehci-hub.c | 173 +++++++++++++++++++++++++++++------------ drivers/usb/host/ehci-pci.c | 15 +--- drivers/usb/host/ehci.h | 10 +++ 5 files changed, 145 insertions(+), 71 deletions(-) (limited to 'drivers/usb/host/ehci-hub.c') diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c index 7a27b7c4ee8..faa61748db7 100644 --- a/drivers/usb/host/ehci-au1xxx.c +++ b/drivers/usb/host/ehci-au1xxx.c @@ -224,26 +224,17 @@ static int ehci_hcd_au1xxx_drv_suspend(struct device *dev) msleep(10); /* Root hub was already suspended. Disable irq emission and - * mark HW unaccessible, bail out if RH has been resumed. Use - * the spinlock to properly synchronize with possible pending - * RH suspend or resume activity. - * - * This is still racy as hcd->state is manipulated outside of - * any locks =P But that will be a different fix. + * mark HW unaccessible. The PM and USB cores make sure that + * the root hub is either suspended or stopped. */ spin_lock_irqsave(&ehci->lock, flags); - if (hcd->state != HC_STATE_SUSPENDED) { - rc = -EINVAL; - goto bail; - } + ehci_prepare_ports_for_controller_suspend(ehci); ehci_writel(ehci, 0, &ehci->regs->intr_enable); (void)ehci_readl(ehci, &ehci->regs->intr_enable); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); au1xxx_stop_ehc(); - -bail: spin_unlock_irqrestore(&ehci->lock, flags); // could save FLADJ in case of Vaux power loss @@ -273,6 +264,7 @@ static int ehci_hcd_au1xxx_drv_resume(struct device *dev) if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) { int mask = INTR_MASK; + ehci_prepare_ports_for_controller_resume(ehci); if (!hcd->self.root_hub->do_remote_wakeup) mask &= ~STS_PCD; ehci_writel(ehci, mask, &ehci->regs->intr_enable); diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 0e26aa13f15..5cd967d2893 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -313,6 +313,7 @@ static int ehci_fsl_drv_suspend(struct device *dev) struct ehci_fsl *ehci_fsl = hcd_to_ehci_fsl(hcd); void __iomem *non_ehci = hcd->regs; + ehci_prepare_ports_for_controller_suspend(hcd_to_ehci(hcd)); if (!fsl_deep_sleep()) return 0; @@ -327,6 +328,7 @@ static int ehci_fsl_drv_resume(struct device *dev) struct ehci_hcd *ehci = hcd_to_ehci(hcd); void __iomem *non_ehci = hcd->regs; + ehci_prepare_ports_for_controller_resume(ehci); if (!fsl_deep_sleep()) return 0; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index ef956220f85..e7d3d8def28 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -106,12 +106,75 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) ehci->owned_ports = 0; } +static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, + bool suspending) +{ + int port; + u32 temp; + + /* If remote wakeup is enabled for the root hub but disabled + * for the controller, we must adjust all the port wakeup flags + * when the controller is suspended or resumed. In all other + * cases they don't need to be changed. + */ + if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup || + device_may_wakeup(ehci_to_hcd(ehci)->self.controller)) + return; + + /* clear phy low-power mode before changing wakeup flags */ + if (ehci->has_hostpc) { + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg; + + hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs + + HOSTPC0 + 4 * port); + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg); + } + msleep(5); + } + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + u32 t2 = t1 & ~PORT_WAKE_BITS; + + /* If we are suspending the controller, clear the flags. + * If we are resuming the controller, set the wakeup flags. + */ + if (!suspending) { + if (t1 & PORT_CONNECT) + t2 |= PORT_WKOC_E | PORT_WKDISC_E; + else + t2 |= PORT_WKOC_E | PORT_WKCONN_E; + } + ehci_vdbg(ehci, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + ehci_writel(ehci, t2, reg); + } + + /* enter phy low-power mode again */ + if (ehci->has_hostpc) { + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg; + + hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs + + HOSTPC0 + 4 * port); + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp | HOSTPC_PHCD, hostpc_reg); + } + } +} + static int ehci_bus_suspend (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); int port; int mask; - u32 __iomem *hostpc_reg = NULL; + int changed; ehci_dbg(ehci, "suspend root hub\n"); @@ -155,15 +218,13 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) */ ehci->bus_suspended = 0; ehci->owned_ports = 0; + changed = 0; port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *reg = &ehci->regs->port_status [port]; u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; - u32 t2 = t1; + u32 t2 = t1 & ~PORT_WAKE_BITS; - if (ehci->has_hostpc) - hostpc_reg = (u32 __iomem *)((u8 *)ehci->regs - + HOSTPC0 + 4 * (port & 0xff)); /* keep track of which ports we suspend */ if (t1 & PORT_OWNER) set_bit(port, &ehci->owned_ports); @@ -172,40 +233,45 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) set_bit(port, &ehci->bus_suspended); } - /* enable remote wakeup on all ports */ + /* enable remote wakeup on all ports, if told to do so */ if (hcd->self.root_hub->do_remote_wakeup) { /* only enable appropriate wake bits, otherwise the * hardware can not go phy low power mode. If a race * condition happens here(connection change during bits * set), the port change detection will finally fix it. */ - if (t1 & PORT_CONNECT) { + if (t1 & PORT_CONNECT) t2 |= PORT_WKOC_E | PORT_WKDISC_E; - t2 &= ~PORT_WKCONN_E; - } else { + else t2 |= PORT_WKOC_E | PORT_WKCONN_E; - t2 &= ~PORT_WKDISC_E; - } - } else - t2 &= ~PORT_WAKE_BITS; + } if (t1 != t2) { ehci_vdbg (ehci, "port %d, %08x -> %08x\n", port + 1, t1, t2); ehci_writel(ehci, t2, reg); - if (hostpc_reg) { - u32 t3; + changed = 1; + } + } - spin_unlock_irq(&ehci->lock); - msleep(5);/* 5ms for HCD enter low pwr mode */ - spin_lock_irq(&ehci->lock); - t3 = ehci_readl(ehci, hostpc_reg); - ehci_writel(ehci, t3 | HOSTPC_PHCD, hostpc_reg); - t3 = ehci_readl(ehci, hostpc_reg); - ehci_dbg(ehci, "Port%d phy low pwr mode %s\n", + if (changed && ehci->has_hostpc) { + spin_unlock_irq(&ehci->lock); + msleep(5); /* 5 ms for HCD to enter low-power mode */ + spin_lock_irq(&ehci->lock); + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *hostpc_reg; + u32 t3; + + hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs + + HOSTPC0 + 4 * port); + t3 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, t3 | HOSTPC_PHCD, hostpc_reg); + t3 = ehci_readl(ehci, hostpc_reg); + ehci_dbg(ehci, "Port %d phy low-power mode %s\n", port, (t3 & HOSTPC_PHCD) ? "succeeded" : "failed"); - } } } @@ -291,19 +357,28 @@ static int ehci_bus_resume (struct usb_hcd *hcd) msleep(8); spin_lock_irq(&ehci->lock); + /* clear phy low-power mode before resume */ + if (ehci->bus_suspended && ehci->has_hostpc) { + i = HCS_N_PORTS(ehci->hcs_params); + while (i--) { + if (test_bit(i, &ehci->bus_suspended)) { + u32 __iomem *hostpc_reg; + + hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs + + HOSTPC0 + 4 * i); + temp = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp & ~HOSTPC_PHCD, + hostpc_reg); + } + } + spin_unlock_irq(&ehci->lock); + msleep(5); + spin_lock_irq(&ehci->lock); + } + /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); while (i--) { - /* clear phy low power mode before resume */ - if (ehci->has_hostpc) { - u32 __iomem *hostpc_reg = - (u32 __iomem *)((u8 *)ehci->regs - + HOSTPC0 + 4 * (i & 0xff)); - temp = ehci_readl(ehci, hostpc_reg); - ehci_writel(ehci, temp & ~HOSTPC_PHCD, - hostpc_reg); - mdelay(5); - } temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && @@ -685,23 +760,25 @@ static int ehci_hub_control ( goto error; if (ehci->no_selective_suspend) break; - if (temp & PORT_SUSPEND) { - if ((temp & PORT_PE) == 0) - goto error; - /* clear phy low power mode before resume */ - if (hostpc_reg) { - temp1 = ehci_readl(ehci, hostpc_reg); - ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, + if (!(temp & PORT_SUSPEND)) + break; + if ((temp & PORT_PE) == 0) + goto error; + + /* clear phy low-power mode before resume */ + if (hostpc_reg) { + temp1 = ehci_readl(ehci, hostpc_reg); + ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, hostpc_reg); - mdelay(5); - } - /* resume signaling for 20 msec */ - temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); - ehci_writel(ehci, temp | PORT_RESUME, - status_reg); - ehci->reset_done [wIndex] = jiffies - + msecs_to_jiffies (20); + spin_unlock_irqrestore(&ehci->lock, flags); + msleep(5);/* wait to leave low-power mode */ + spin_lock_irqsave(&ehci->lock, flags); } + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + ehci_writel(ehci, temp | PORT_RESUME, status_reg); + ehci->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); break; case USB_PORT_FEAT_C_SUSPEND: clear_bit(wIndex, &ehci->port_c_suspend); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index d120059bbbf..d43d176161a 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -287,23 +287,15 @@ static int ehci_pci_suspend(struct usb_hcd *hcd) msleep(10); /* Root hub was already suspended. Disable irq emission and - * mark HW unaccessible, bail out if RH has been resumed. Use - * the spinlock to properly synchronize with possible pending - * RH suspend or resume activity. - * - * This is still racy as hcd->state is manipulated outside of - * any locks =P But that will be a different fix. + * mark HW unaccessible. The PM and USB cores make sure that + * the root hub is either suspended or stopped. */ spin_lock_irqsave (&ehci->lock, flags); - if (hcd->state != HC_STATE_SUSPENDED) { - rc = -EINVAL; - goto bail; - } + ehci_prepare_ports_for_controller_suspend(ehci); ehci_writel(ehci, 0, &ehci->regs->intr_enable); (void)ehci_readl(ehci, &ehci->regs->intr_enable); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - bail: spin_unlock_irqrestore (&ehci->lock, flags); // could save FLADJ in case of Vaux power loss @@ -333,6 +325,7 @@ static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) !hibernated) { int mask = INTR_MASK; + ehci_prepare_ports_for_controller_resume(ehci); if (!hcd->self.root_hub->do_remote_wakeup) mask &= ~STS_PCD; ehci_writel(ehci, mask, &ehci->regs->intr_enable); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 4ebe9ad209e..650a687f285 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -536,6 +536,16 @@ struct ehci_fstn { /*-------------------------------------------------------------------------*/ +/* Prepare the PORTSC wakeup flags during controller suspend/resume */ + +#define ehci_prepare_ports_for_controller_suspend(ehci) \ + ehci_adjust_port_wakeup_flags(ehci, true); + +#define ehci_prepare_ports_for_controller_resume(ehci) \ + ehci_adjust_port_wakeup_flags(ehci, false); + +/*-------------------------------------------------------------------------*/ + #ifdef CONFIG_USB_EHCI_ROOT_HUB_TT /* -- cgit v1.2.3-70-g09d2