diff options
Diffstat (limited to 'drivers/usb/host/ehci-hub.c')
| -rw-r--r-- | drivers/usb/host/ehci-hub.c | 296 |
1 files changed, 182 insertions, 114 deletions
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 2b702772d04d..1aee392e8492 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2001-2004 by David Brownell - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* this file is part of ehci-hcd.c */ @@ -27,20 +14,12 @@ */ /*-------------------------------------------------------------------------*/ -#include <linux/usb/otg.h> #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) #ifdef CONFIG_PM -static int ehci_hub_control( - struct usb_hcd *hcd, - u16 typeReq, - u16 wValue, - u16 wIndex, - char *buf, - u16 wLength -); +static void unlink_empty_async_suspended(struct ehci_hcd *ehci); static int persist_enabled_on_companion(struct usb_device *udev, void *unused) { @@ -78,10 +57,8 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) if (test_bit(port, &ehci->owned_ports)) { reg = &ehci->regs->port_status[port]; status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; - if (!(status & PORT_POWER)) { - status |= PORT_POWER; - ehci_writel(ehci, status, reg); - } + if (!(status & PORT_POWER)) + ehci_port_power(ehci, port, true); } } @@ -166,7 +143,7 @@ static int ehci_port_change(struct ehci_hcd *ehci) return 0; } -static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, +void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, bool suspending, bool do_wakeup) { int port; @@ -183,7 +160,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, spin_lock_irq(&ehci->lock); /* clear phy low-power mode before changing wakeup flags */ - if (ehci->has_hostpc) { + if (ehci->has_tdi_phy_lpm) { port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; @@ -211,13 +188,11 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, 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) { + if (ehci->has_tdi_phy_lpm) { port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; @@ -233,6 +208,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci, spin_unlock_irq(&ehci->lock); } +EXPORT_SYMBOL_GPL(ehci_adjust_port_wakeup_flags); static int ehci_bus_suspend (struct usb_hcd *hcd) { @@ -240,6 +216,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) int port; int mask; int changed; + bool fs_idle_delay; ehci_dbg(ehci, "suspend root hub\n"); @@ -274,6 +251,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci->bus_suspended = 0; ehci->owned_ports = 0; changed = 0; + fs_idle_delay = false; port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *reg = &ehci->regs->port_status [port]; @@ -302,18 +280,40 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) } if (t1 != t2) { - ehci_vdbg (ehci, "port %d, %08x -> %08x\n", - port + 1, t1, t2); + /* + * On some controllers, Wake-On-Disconnect will + * generate false wakeup signals until the bus + * switches over to full-speed idle. For their + * sake, add a delay if we need one. + */ + if ((t2 & PORT_WKDISC_E) && + ehci_port_speed(ehci, t2) == + USB_PORT_STAT_HIGH_SPEED) + fs_idle_delay = true; ehci_writel(ehci, t2, reg); changed = 1; } } + spin_unlock_irq(&ehci->lock); - 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); + if (changed && ehci_has_fsl_susp_errata(ehci)) + /* + * Wait for at least 10 millisecondes to ensure the controller + * enter the suspend status before initiating a port resume + * using the Force Port Resume bit (Not-EHCI compatible). + */ + usleep_range(10000, 20000); + if ((changed && ehci->has_tdi_phy_lpm) || fs_idle_delay) { + /* + * Wait for HCD to enter low-power mode or for the bus + * to switch to full-speed idle. + */ + usleep_range(5000, 5500); + } + + if (changed && ehci->has_tdi_phy_lpm) { + spin_lock_irq(&ehci->lock); port = HCS_N_PORTS(ehci->hcs_params); while (port--) { u32 __iomem *hostpc_reg = &ehci->regs->hostpc[port]; @@ -326,8 +326,8 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) port, (t3 & HOSTPC_PHCD) ? "succeeded" : "failed"); } + spin_unlock_irq(&ehci->lock); } - spin_unlock_irq(&ehci->lock); /* Apparently some devices need a >= 1-uframe delay here */ if (ehci->bus_suspended) @@ -343,8 +343,14 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) goto done; ehci->rh_state = EHCI_RH_SUSPENDED; - end_unlink_async(ehci); unlink_empty_async_suspended(ehci); + + /* Some Synopsys controllers mistakenly leave IAA turned on */ + ehci_writel(ehci, STS_IAA, &ehci->regs->status); + + /* Any IAA cycle that started before the suspend is now invalid */ + end_iaa_cycle(ehci); + ehci_handle_start_intr_unlinks(ehci); ehci_handle_intr_unlinks(ehci); end_free_itds(ehci); @@ -435,7 +441,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) goto shutdown; /* clear phy low-power mode before resume */ - if (ehci->bus_suspended && ehci->has_hostpc) { + if (ehci->bus_suspended && ehci->has_tdi_phy_lpm) { i = HCS_N_PORTS(ehci->hcs_params); while (i--) { if (test_bit(i, &ehci->bus_suspended)) { @@ -467,10 +473,13 @@ static int ehci_bus_resume (struct usb_hcd *hcd) ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } - /* msleep for 20ms only if code is trying to resume port */ + /* + * msleep for USB_RESUME_TIMEOUT ms only if code is trying to resume + * port + */ if (resume_needed) { spin_unlock_irq(&ehci->lock); - msleep(20); + msleep(USB_RESUME_TIMEOUT); spin_lock_irq(&ehci->lock); if (ehci->shutdown) goto shutdown; @@ -482,7 +491,6 @@ static int ehci_bus_resume (struct usb_hcd *hcd) if (test_bit(i, &resume_needed)) { temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME); ehci_writel(ehci, temp, &ehci->regs->port_status [i]); - ehci_vdbg (ehci, "resumed port %d\n", i + 1); } } @@ -506,10 +514,18 @@ static int ehci_bus_resume (struct usb_hcd *hcd) return -ESHUTDOWN; } +static unsigned long ehci_get_resuming_ports(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + return ehci->resuming_ports; +} + #else #define ehci_bus_suspend NULL #define ehci_bus_resume NULL +#define ehci_get_resuming_ports NULL #endif /* CONFIG_PM */ @@ -627,7 +643,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) * always set, seem to clear PORT_OCC and PORT_CSC when writing to * PORT_POWER; that's surprising, but maybe within-spec. */ - if (!ignore_oc) + if (!ignore_oc && !ehci->spurious_oc) mask = PORT_CSC | PORT_PEC | PORT_OCC; else mask = PORT_CSC | PORT_PEC; @@ -651,14 +667,15 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) /* * Return status information even for ports with OWNER set. - * Otherwise khubd wouldn't see the disconnect event when a + * Otherwise hub_wq wouldn't see the disconnect event when a * high-speed device is switched over to the companion * controller by the user. */ if ((temp & mask) != 0 || test_bit(i, &ehci->port_c_suspend) || (ehci->reset_done[i] && time_after_eq( - jiffies, ehci->reset_done[i]))) { + jiffies, ehci->reset_done[i])) + || ehci_has_ci_pec_bug(ehci, temp)) { if (i < 7) buf [0] |= 1 << (i + 1); else @@ -685,7 +702,7 @@ ehci_hub_descriptor ( int ports = HCS_N_PORTS (ehci->hcs_params); u16 temp; - desc->bDescriptorType = 0x29; + desc->bDescriptorType = USB_DT_HUB; desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */ desc->bHubContrCurrent = 0; @@ -697,22 +714,22 @@ ehci_hub_descriptor ( memset(&desc->u.hs.DeviceRemovable[0], 0, temp); memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - temp = 0x0008; /* per-port overcurrent reporting */ + temp = HUB_CHAR_INDV_PORT_OCPM; /* per-port overcurrent reporting */ if (HCS_PPC (ehci->hcs_params)) - temp |= 0x0001; /* per-port power control */ + temp |= HUB_CHAR_INDV_PORT_LPSM; /* per-port power control */ else - temp |= 0x0002; /* no power switching */ + temp |= HUB_CHAR_NO_LPSM; /* no power switching */ #if 0 // re-enable when we support USB_PORT_FEAT_INDICATOR below. if (HCS_INDICATOR (ehci->hcs_params)) - temp |= 0x0080; /* per-port indicators (LEDs) */ + temp |= HUB_CHAR_PORTIND; /* per-port indicators (LEDs) */ #endif desc->wHubCharacteristics = cpu_to_le16(temp); } /*-------------------------------------------------------------------------*/ -static int ehci_hub_control ( +int ehci_hub_control( struct usb_hcd *hcd, u16 typeReq, u16 wValue, @@ -722,15 +739,24 @@ static int ehci_hub_control ( ) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); int ports = HCS_N_PORTS (ehci->hcs_params); - u32 __iomem *status_reg = &ehci->regs->port_status[ - (wIndex & 0xff) - 1]; - u32 __iomem *hostpc_reg = &ehci->regs->hostpc[(wIndex & 0xff) - 1]; + u32 __iomem *status_reg, *hostpc_reg; u32 temp, temp1, status; unsigned long flags; int retval = 0; unsigned selector; /* + * Avoid out-of-bounds values while calculating the port index + * from wIndex. The compiler doesn't like pointers to invalid + * addresses, even if they are never used. + */ + temp = (wIndex - 1) & 0xff; + if (temp >= HCS_N_PORTS_MAX) + temp = 0; + status_reg = &ehci->regs->port_status[temp]; + hostpc_reg = &ehci->regs->hostpc[temp]; + + /* * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. * HCS_INDICATOR may say we can change LEDs to off/amber/green. * (track current state ourselves) ... blink for diagnostics, @@ -758,7 +784,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 + * companion controller, hub_wq needs to be able to clear * the port-change status bits (especially * USB_PORT_STAT_C_CONNECTION). */ @@ -778,7 +804,7 @@ static int ehci_hub_control ( #ifdef CONFIG_USB_OTG if ((hcd->self.otg_port == (wIndex + 1)) && hcd->self.b_hnp_enable) { - otg_start_hnp(hcd->phy->otg); + otg_start_hnp(hcd->usb_phy->otg); break; } #endif @@ -788,7 +814,7 @@ static int ehci_hub_control ( goto error; /* clear phy low-power mode before resume */ - if (ehci->has_hostpc) { + if (ehci->has_tdi_phy_lpm) { temp1 = ehci_readl(ehci, hostpc_reg); ehci_writel(ehci, temp1 & ~HOSTPC_PHCD, hostpc_reg); @@ -800,15 +826,19 @@ static int ehci_hub_control ( temp &= ~PORT_WAKE_BITS; ehci_writel(ehci, temp | PORT_RESUME, status_reg); ehci->reset_done[wIndex] = jiffies - + msecs_to_jiffies(20); + + msecs_to_jiffies(USB_RESUME_TIMEOUT); + set_bit(wIndex, &ehci->resuming_ports); + usb_hcd_start_port_resume(&hcd->self, wIndex); break; case USB_PORT_FEAT_C_SUSPEND: clear_bit(wIndex, &ehci->port_c_suspend); break; case USB_PORT_FEAT_POWER: - if (HCS_PPC (ehci->hcs_params)) - ehci_writel(ehci, temp & ~PORT_POWER, - status_reg); + if (HCS_PPC(ehci->hcs_params)) { + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, false); + spin_lock_irqsave(&ehci->lock, flags); + } break; case USB_PORT_FEAT_C_CONNECTION: ehci_writel(ehci, temp | PORT_CSC, status_reg); @@ -846,7 +876,14 @@ static int ehci_hub_control ( if (temp & PORT_PEC) status |= USB_PORT_STAT_C_ENABLE << 16; - if ((temp & PORT_OCC) && !ignore_oc){ + if (ehci_has_ci_pec_bug(ehci, temp)) { + status |= USB_PORT_STAT_C_ENABLE << 16; + ehci_info(ehci, + "PE is cleared by HW port:%d PORTSC:%08x\n", + wIndex + 1, temp); + } + + if ((temp & PORT_OCC) && (!ignore_oc && !ehci->spurious_oc)){ status |= USB_PORT_STAT_C_OVERCURRENT << 16; /* @@ -854,63 +891,60 @@ static int ehci_hub_control ( * However, not all EHCI implementations do this * automatically, even if they _do_ support per-port * power switching; they're allowed to just limit the - * current. khubd will turn the power back on. + * current. hub_wq will turn the power back on. */ if (((temp & PORT_OC) || (ehci->need_oc_pp_cycle)) && HCS_PPC(ehci->hcs_params)) { - ehci_writel(ehci, - temp & ~(PORT_RWC_BITS | PORT_POWER), - status_reg); + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, false); + spin_lock_irqsave(&ehci->lock, flags); temp = ehci_readl(ehci, status_reg); } } - /* whoever resumes must GetPortStatus to complete it!! */ - if (temp & PORT_RESUME) { + /* no reset or resume pending */ + if (!ehci->reset_done[wIndex]) { /* Remote Wakeup received? */ - if (!ehci->reset_done[wIndex]) { + if (temp & PORT_RESUME) { /* resume signaling for 20 msec */ ehci->reset_done[wIndex] = jiffies + msecs_to_jiffies(20); usb_hcd_start_port_resume(&hcd->self, wIndex); + set_bit(wIndex, &ehci->resuming_ports); /* check the port again */ mod_timer(&ehci_to_hcd(ehci)->rh_timer, ehci->reset_done[wIndex]); } - /* resume completed? */ - else if (time_after_eq(jiffies, - ehci->reset_done[wIndex])) { - clear_bit(wIndex, &ehci->suspended_ports); - set_bit(wIndex, &ehci->port_c_suspend); - ehci->reset_done[wIndex] = 0; - usb_hcd_end_port_resume(&hcd->self, wIndex); - - /* stop resume signaling */ - temp &= ~(PORT_RWC_BITS | - PORT_SUSPEND | PORT_RESUME); - ehci_writel(ehci, temp, status_reg); - clear_bit(wIndex, &ehci->resuming_ports); - retval = ehci_handshake(ehci, status_reg, - PORT_RESUME, 0, 2000 /* 2msec */); - if (retval != 0) { - ehci_err(ehci, - "port %d resume error %d\n", + /* reset or resume not yet complete */ + } else if (!time_after_eq(jiffies, ehci->reset_done[wIndex])) { + ; /* wait until it is complete */ + + /* resume completed */ + } else if (test_bit(wIndex, &ehci->resuming_ports)) { + clear_bit(wIndex, &ehci->suspended_ports); + set_bit(wIndex, &ehci->port_c_suspend); + ehci->reset_done[wIndex] = 0; + usb_hcd_end_port_resume(&hcd->self, wIndex); + + /* stop resume signaling */ + temp &= ~(PORT_RWC_BITS | PORT_SUSPEND | PORT_RESUME); + ehci_writel(ehci, temp, status_reg); + clear_bit(wIndex, &ehci->resuming_ports); + retval = ehci_handshake(ehci, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + ehci_err(ehci, "port %d resume error %d\n", wIndex + 1, retval); - goto error; - } - temp = ehci_readl(ehci, status_reg); + goto error; } - } + temp = ehci_readl(ehci, status_reg); /* whoever resets must GetPortStatus to complete it!! */ - if ((temp & PORT_RESET) - && time_after_eq(jiffies, - ehci->reset_done[wIndex])) { + } else { status |= USB_PORT_STAT_C_RESET << 16; ehci->reset_done [wIndex] = 0; - clear_bit(wIndex, &ehci->resuming_ports); /* force reset to complete */ ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET), @@ -931,11 +965,6 @@ static int ehci_hub_control ( ehci_readl(ehci, status_reg)); } - if (!(temp & (PORT_RESUME|PORT_RESET))) { - ehci->reset_done[wIndex] = 0; - clear_bit(wIndex, &ehci->resuming_ports); - } - /* transfer dedicated ports to the companion hc */ if ((temp & PORT_CONNECT) && test_bit(wIndex, &ehci->companion_ports)) { @@ -947,7 +976,7 @@ static int ehci_hub_control ( } /* - * Even if OWNER is set, there's no harm letting khubd + * Even if OWNER is set, there's no harm letting hub_wq * see the wPortStatus values (they should all be 0 except * for PORT_POWER anyway). */ @@ -985,10 +1014,8 @@ static int ehci_hub_control ( if (test_bit(wIndex, &ehci->port_c_suspend)) status |= USB_PORT_STAT_C_SUSPEND << 16; -#ifndef VERBOSE_DEBUG - if (status & ~0xffff) /* only if wPortChange is interesting */ -#endif - dbg_port (ehci, "GetStatus", wIndex + 1, temp); + if (status & ~0xffff) /* only if wPortChange is interesting */ + dbg_port(ehci, "GetStatus", wIndex + 1, temp); put_unaligned_le32(status, buf); break; case SetHubFeature: @@ -1031,12 +1058,12 @@ static int ehci_hub_control ( /* After above check the port must be connected. * Set appropriate bit thus could put phy into low power - * mode if we have hostpc feature + * mode if we have tdi_phy_lpm feature */ temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); - if (ehci->has_hostpc) { + if (ehci->has_tdi_phy_lpm) { spin_unlock_irqrestore(&ehci->lock, flags); msleep(5);/* 5ms for HCD enter low pwr mode */ spin_lock_irqsave(&ehci->lock, flags); @@ -1048,15 +1075,23 @@ static int ehci_hub_control ( wIndex, (temp1 & HOSTPC_PHCD) ? "succeeded" : "failed"); } + if (ehci_has_fsl_susp_errata(ehci)) { + /* 10ms for HCD enter suspend */ + spin_unlock_irqrestore(&ehci->lock, flags); + usleep_range(10000, 20000); + spin_lock_irqsave(&ehci->lock, flags); + } set_bit(wIndex, &ehci->suspended_ports); break; case USB_PORT_FEAT_POWER: - if (HCS_PPC (ehci->hcs_params)) - ehci_writel(ehci, temp | PORT_POWER, - status_reg); + if (HCS_PPC(ehci->hcs_params)) { + spin_unlock_irqrestore(&ehci->lock, flags); + ehci_port_power(ehci, wIndex, true); + spin_lock_irqsave(&ehci->lock, flags); + } break; case USB_PORT_FEAT_RESET: - if (temp & PORT_RESUME) + if (temp & (PORT_SUSPEND|PORT_RESUME)) goto error; /* line status bits may report this as low speed, * which can be fine if this root hub has a @@ -1070,7 +1105,6 @@ static int ehci_hub_control ( wIndex + 1); temp |= PORT_OWNER; } else { - ehci_vdbg (ehci, "port %d reset\n", wIndex + 1); temp |= PORT_RESET; temp &= ~PORT_PE; @@ -1080,6 +1114,13 @@ static int ehci_hub_control ( */ ehci->reset_done [wIndex] = jiffies + msecs_to_jiffies (50); + + /* + * Force full-speed connect for FSL high-speed + * erratum; disable HS Chirp by setting PFSC bit + */ + if (ehci_has_fsl_hs_errata(ehci)) + temp |= (1 << PORTSC_FSL_PFSC); } ehci_writel(ehci, temp, status_reg); break; @@ -1091,6 +1132,15 @@ static int ehci_hub_control ( * about the EHCI-specific stuff. */ case USB_PORT_FEAT_TEST: +#ifdef CONFIG_USB_HCD_TEST_MODE + if (selector == EHSET_TEST_SINGLE_STEP_SET_FEATURE) { + spin_unlock_irqrestore(&ehci->lock, flags); + retval = ehset_single_step_set_feature(hcd, + wIndex + 1); + spin_lock_irqsave(&ehci->lock, flags); + break; + } +#endif if (!selector || selector > 5) goto error; spin_unlock_irqrestore(&ehci->lock, flags); @@ -1132,6 +1182,7 @@ error_exit: spin_unlock_irqrestore (&ehci->lock, flags); return retval; } +EXPORT_SYMBOL_GPL(ehci_hub_control); static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum) { @@ -1152,3 +1203,20 @@ static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum) reg = &ehci->regs->port_status[portnum - 1]; return ehci_readl(ehci, reg) & PORT_OWNER; } + +static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable) +{ + struct usb_hcd *hcd = ehci_to_hcd(ehci); + u32 __iomem *status_reg = &ehci->regs->port_status[portnum]; + u32 temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS; + + if (enable) + ehci_writel(ehci, temp | PORT_POWER, status_reg); + else + ehci_writel(ehci, temp & ~PORT_POWER, status_reg); + + if (hcd->driver->port_power) + hcd->driver->port_power(hcd, portnum, enable); + + return 0; +} |
