X-Git-Url: http://pilppa.org/gitweb/?a=blobdiff_plain;f=drivers%2Fusb%2Fhost%2Fehci-hub.c;h=71aeca019e880075368393c7c7f0d75dd3118cae;hb=756aa6b3d536afe85e151138cb03a293998887b3;hp=9af529d22b3e609ee8af02b5fa9fc5c03021f13a;hpb=460223d21d960e420775ca4d11ddaa8389af4d25;p=linux-2.6-omap-h63xx.git diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 9af529d22b3..71aeca019e8 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -28,6 +28,89 @@ /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_PERSIST + +static int ehci_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +); + +/* After a power loss, ports that were owned by the companion must be + * reset so that the companion can still own them. + */ +static void ehci_handover_companion_ports(struct ehci_hcd *ehci) +{ + u32 __iomem *reg; + u32 status; + int port; + __le32 buf; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + + if (!ehci->owned_ports) + return; + + /* Give the connections some time to appear */ + msleep(20); + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + + /* Port already owned by companion? */ + if (status & PORT_OWNER) + clear_bit(port, &ehci->owned_ports); + else if (test_bit(port, &ehci->companion_ports)) + ehci_writel(ehci, status & ~PORT_PE, reg); + else + ehci_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_RESET, port + 1, + NULL, 0); + } + } + + if (!ehci->owned_ports) + return; + msleep(90); /* Wait for resets to complete */ + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + ehci_hub_control(hcd, GetPortStatus, + 0, port + 1, + (char *) &buf, sizeof(buf)); + + /* The companion should now own the port, + * but if something went wrong the port must not + * remain enabled. + */ + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + if (status & PORT_OWNER) + ehci_writel(ehci, status | PORT_CSC, reg); + else { + ehci_dbg(ehci, "failed handover port %d: %x\n", + port + 1, status); + ehci_writel(ehci, status & ~PORT_PE, reg); + } + } + } + + ehci->owned_ports = 0; +} + +#else /* CONFIG_USB_PERSIST */ + +static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci) +{ } + +#endif + #ifdef CONFIG_PM static int ehci_bus_suspend (struct usb_hcd *hcd) @@ -60,14 +143,16 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) * then manually resume them in the bus_resume() routine. */ ehci->bus_suspended = 0; + ehci->owned_ports = 0; while (port--) { u32 __iomem *reg = &ehci->regs->port_status [port]; u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; u32 t2 = t1; /* keep track of which ports we suspend */ - if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && - !(t1 & PORT_SUSPEND)) { + if (t1 & PORT_OWNER) + set_bit(port, &ehci->owned_ports); + else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { t2 |= PORT_SUSPEND; set_bit(port, &ehci->bus_suspended); } @@ -108,6 +193,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); u32 temp; + u32 power_okay; int i; if (time_before (jiffies, ehci->next_statechange)) @@ -120,8 +206,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) * the last user of the controller, not reset/pm hardware keeping * state we gave to it. */ - temp = ehci_readl(ehci, &ehci->regs->intr_enable); - ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss"); + power_okay = ehci_readl(ehci, &ehci->regs->intr_enable); + ehci_dbg(ehci, "resume root hub%s\n", + power_okay ? "" : " after power loss"); /* at least some APM implementations will try to deliver * IRQs right away, so delay them until we're ready. @@ -136,6 +223,10 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* restore CMD_RUN, framelist size, and irq threshold */ ehci_writel(ehci, ehci->command, &ehci->regs->command); + /* Some controller/firmware combinations need a delay during which + * they set up the port statuses. See Bugzilla #8190. */ + mdelay(8); + /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); while (i--) { @@ -180,6 +271,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); spin_unlock_irq (&ehci->lock); + + if (!power_okay) + ehci_handover_companion_ports(ehci); return 0; } @@ -444,7 +538,8 @@ 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 - 1]; + u32 __iomem *status_reg = &ehci->regs->port_status[ + (wIndex & 0xff) - 1]; u32 temp, status; unsigned long flags; int retval = 0; @@ -552,9 +647,24 @@ static int ehci_hub_control ( status |= 1 << USB_PORT_FEAT_C_CONNECTION; if (temp & PORT_PEC) status |= 1 << USB_PORT_FEAT_C_ENABLE; - if ((temp & PORT_OCC) && !ignore_oc) + + if ((temp & PORT_OCC) && !ignore_oc){ status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + /* + * Hubs should disable port power on over-current. + * 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. + */ + if (HCS_PPC (ehci->hcs_params)){ + ehci_writel(ehci, + temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + } + } + /* whoever resumes must GetPortStatus to complete it!! */ if (temp & PORT_RESUME) { @@ -653,8 +763,7 @@ static int ehci_hub_control ( if (status & ~0xffff) /* only if wPortChange is interesting */ #endif dbg_port (ehci, "GetStatus", wIndex + 1, temp); - // we "know" this alignment is good, caller used kmalloc()... - *((__le32 *) buf) = cpu_to_le32 (status); + put_unaligned(cpu_to_le32 (status), (__le32 *) buf); break; case SetHubFeature: switch (wValue) {