From 083522d76662cda71328df1f3d75e5a9057c7c9f Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Fri, 15 Dec 2006 06:54:08 +1100 Subject: USB: Implement support for EHCI with big endian MMIO This patch implements supports for EHCI controllers whose MMIO registers are big endian and enables that functionality for the Toshiba SCC chip. It does _not_ add support for big endian in-memory data structures as this is not needed for that chip and I hope it will never be. The guts of the patch are to convert readl(...) to ehci_readl(ehci, ...) and similarly for register writes. Signed-off-by: Kou Ishizaki Signed-off-by: Benjamin Herrenschmidt Acked-by: Geoff Levand Acked-by: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 118 +++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 55 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 bfe5f307cba..df00fcbadfb 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -47,7 +47,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci_quiesce (ehci); hcd->state = HC_STATE_QUIESCING; } - ehci->command = readl (&ehci->regs->command); + ehci->command = ehci_readl(ehci, &ehci->regs->command); if (ehci->reclaim) ehci->reclaim_ready = 1; ehci_work(ehci); @@ -60,7 +60,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) ehci->bus_suspended = 0; while (port--) { u32 __iomem *reg = &ehci->regs->port_status [port]; - u32 t1 = readl (reg) & ~PORT_RWC_BITS; + u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; u32 t2 = t1; /* keep track of which ports we suspend */ @@ -79,7 +79,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (t1 != t2) { ehci_vdbg (ehci, "port %d, %08x -> %08x\n", port + 1, t1, t2); - writel (t2, reg); + ehci_writel(ehci, t2, reg); } } @@ -92,8 +92,8 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) mask = INTR_MASK; if (!device_may_wakeup(&hcd->self.root_hub->dev)) mask &= ~STS_PCD; - writel(mask, &ehci->regs->intr_enable); - readl(&ehci->regs->intr_enable); + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + ehci_readl(ehci, &ehci->regs->intr_enable); ehci->next_statechange = jiffies + msecs_to_jiffies(10); spin_unlock_irq (&ehci->lock); @@ -118,26 +118,26 @@ 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 = readl(&ehci->regs->intr_enable); + temp = ehci_readl(ehci, &ehci->regs->intr_enable); ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss"); /* at least some APM implementations will try to deliver * IRQs right away, so delay them until we're ready. */ - writel(0, &ehci->regs->intr_enable); + ehci_writel(ehci, 0, &ehci->regs->intr_enable); /* re-init operational registers */ - writel(0, &ehci->regs->segment); - writel(ehci->periodic_dma, &ehci->regs->frame_list); - writel((u32) ehci->async->qh_dma, &ehci->regs->async_next); + ehci_writel(ehci, 0, &ehci->regs->segment); + ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); + ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); /* restore CMD_RUN, framelist size, and irq threshold */ - writel (ehci->command, &ehci->regs->command); + ehci_writel(ehci, ehci->command, &ehci->regs->command); /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); while (i--) { - temp = readl (&ehci->regs->port_status [i]); + temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); if (test_bit(i, &ehci->bus_suspended) && @@ -145,20 +145,20 @@ static int ehci_bus_resume (struct usb_hcd *hcd) ehci->reset_done [i] = jiffies + msecs_to_jiffies (20); temp |= PORT_RESUME; } - writel (temp, &ehci->regs->port_status [i]); + ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } i = HCS_N_PORTS (ehci->hcs_params); mdelay (20); while (i--) { - temp = readl (&ehci->regs->port_status [i]); + temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (test_bit(i, &ehci->bus_suspended) && (temp & PORT_SUSPEND)) { temp &= ~(PORT_RWC_BITS | PORT_RESUME); - writel (temp, &ehci->regs->port_status [i]); + ehci_writel(ehci, temp, &ehci->regs->port_status [i]); ehci_vdbg (ehci, "resumed port %d\n", i + 1); } } - (void) readl (&ehci->regs->command); + (void) ehci_readl(ehci, &ehci->regs->command); /* maybe re-activate the schedule(s) */ temp = 0; @@ -168,14 +168,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd) temp |= CMD_PSE; if (temp) { ehci->command |= temp; - writel (ehci->command, &ehci->regs->command); + ehci_writel(ehci, ehci->command, &ehci->regs->command); } ehci->next_statechange = jiffies + msecs_to_jiffies(5); hcd->state = HC_STATE_RUNNING; /* Now we can safely re-enable irqs */ - writel(INTR_MASK, &ehci->regs->intr_enable); + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); spin_unlock_irq (&ehci->lock); return 0; @@ -217,7 +217,8 @@ static int check_reset_complete ( // what happens if HCS_N_CC(params) == 0 ? port_status |= PORT_OWNER; port_status &= ~PORT_RWC_BITS; - writel (port_status, &ehci->regs->port_status [index]); + ehci_writel(ehci, port_status, + &ehci->regs->port_status [index]); } else ehci_dbg (ehci, "port %d high speed\n", index + 1); @@ -268,13 +269,14 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) /* port N changes (bit N)? */ spin_lock_irqsave (&ehci->lock, flags); for (i = 0; i < ports; i++) { - temp = readl (&ehci->regs->port_status [i]); + temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (temp & PORT_OWNER) { /* don't report this in GetPortStatus */ if (temp & PORT_CSC) { temp &= ~PORT_RWC_BITS; temp |= PORT_CSC; - writel (temp, &ehci->regs->port_status [i]); + ehci_writel(ehci, temp, + &ehci->regs->port_status [i]); } continue; } @@ -373,18 +375,18 @@ static int ehci_hub_control ( if (!wIndex || wIndex > ports) goto error; wIndex--; - temp = readl (&ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); if (temp & PORT_OWNER) break; switch (wValue) { case USB_PORT_FEAT_ENABLE: - writel (temp & ~PORT_PE, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp & ~PORT_PE, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_C_ENABLE: - writel((temp & ~PORT_RWC_BITS) | PORT_PEC, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_PEC, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_SUSPEND: if (temp & PORT_RESET) @@ -396,8 +398,8 @@ static int ehci_hub_control ( goto error; /* resume signaling for 20 msec */ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); - writel (temp | PORT_RESUME, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp | PORT_RESUME, + &ehci->regs->port_status [wIndex]); ehci->reset_done [wIndex] = jiffies + msecs_to_jiffies (20); } @@ -407,16 +409,17 @@ static int ehci_hub_control ( break; case USB_PORT_FEAT_POWER: if (HCS_PPC (ehci->hcs_params)) - writel (temp & ~(PORT_RWC_BITS | PORT_POWER), - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, + temp & ~(PORT_RWC_BITS | PORT_POWER), + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_C_CONNECTION: - writel((temp & ~PORT_RWC_BITS) | PORT_CSC, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_C_OVER_CURRENT: - writel((temp & ~PORT_RWC_BITS) | PORT_OCC, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_OCC, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_C_RESET: /* GetPortStatus clears reset */ @@ -424,7 +427,7 @@ static int ehci_hub_control ( default: goto error; } - readl (&ehci->regs->command); /* unblock posted write */ + ehci_readl(ehci, &ehci->regs->command); /* unblock posted write */ break; case GetHubDescriptor: ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) @@ -440,7 +443,7 @@ static int ehci_hub_control ( goto error; wIndex--; status = 0; - temp = readl (&ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); // wPortChange bits if (temp & PORT_CSC) @@ -458,12 +461,14 @@ static int ehci_hub_control ( ehci->reset_done [wIndex] = 0; /* stop resume signaling */ - temp = readl (&ehci->regs->port_status [wIndex]); - writel (temp & ~(PORT_RWC_BITS | PORT_RESUME), - &ehci->regs->port_status [wIndex]); - retval = handshake ( - &ehci->regs->port_status [wIndex], - PORT_RESUME, 0, 2000 /* 2msec */); + temp = ehci_readl(ehci, + &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, + temp & ~(PORT_RWC_BITS | PORT_RESUME), + &ehci->regs->port_status [wIndex]); + retval = handshake(ehci, + &ehci->regs->port_status [wIndex], + PORT_RESUME, 0, 2000 /* 2msec */); if (retval != 0) { ehci_err (ehci, "port %d resume error %d\n", wIndex + 1, retval); @@ -480,13 +485,13 @@ static int ehci_hub_control ( ehci->reset_done [wIndex] = 0; /* force reset to complete */ - writel (temp & ~(PORT_RWC_BITS | PORT_RESET), - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET), + &ehci->regs->port_status [wIndex]); /* REVISIT: some hardware needs 550+ usec to clear * this bit; seems too long to spin routinely... */ - retval = handshake ( - &ehci->regs->port_status [wIndex], + retval = handshake(ehci, + &ehci->regs->port_status [wIndex], PORT_RESET, 0, 750); if (retval != 0) { ehci_err (ehci, "port %d reset error %d\n", @@ -496,7 +501,8 @@ static int ehci_hub_control ( /* see what we found out */ temp = check_reset_complete (ehci, wIndex, - readl (&ehci->regs->port_status [wIndex])); + ehci_readl(ehci, + &ehci->regs->port_status [wIndex])); } // don't show wPortStatus if it's owned by a companion hc @@ -541,7 +547,7 @@ static int ehci_hub_control ( if (!wIndex || wIndex > ports) goto error; wIndex--; - temp = readl (&ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); if (temp & PORT_OWNER) break; @@ -555,13 +561,13 @@ static int ehci_hub_control ( goto error; if (device_may_wakeup(&hcd->self.root_hub->dev)) temp |= PORT_WAKE_BITS; - writel (temp | PORT_SUSPEND, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp | PORT_SUSPEND, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_POWER: if (HCS_PPC (ehci->hcs_params)) - writel (temp | PORT_POWER, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp | PORT_POWER, + &ehci->regs->port_status [wIndex]); break; case USB_PORT_FEAT_RESET: if (temp & PORT_RESUME) @@ -589,7 +595,8 @@ static int ehci_hub_control ( ehci->reset_done [wIndex] = jiffies + msecs_to_jiffies (50); } - writel (temp, &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp, + &ehci->regs->port_status [wIndex]); break; /* For downstream facing ports (these): one hub port is put @@ -604,13 +611,14 @@ static int ehci_hub_control ( ehci_quiesce(ehci); ehci_halt(ehci); temp |= selector << 16; - writel (temp, &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp, + &ehci->regs->port_status [wIndex]); break; default: goto error; } - readl (&ehci->regs->command); /* unblock posted writes */ + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ break; default: -- cgit v1.2.3 From e6316565e568b3b5733be10cfca3c27259bef499 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 Jan 2007 11:58:00 -0500 Subject: EHCI: local variable for port status register This patch (as708) introduces a local variable to hold the port status-register address in ehci-hub.c. There's not much improvement in the object code, but it sure is a lot easier to read. Signed-off-by: Alan Stern Cc: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 55 ++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 31 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 df00fcbadfb..12f881ff4b2 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -193,6 +193,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) static int check_reset_complete ( struct ehci_hcd *ehci, int index, + u32 __iomem *status_reg, int port_status ) { if (!(port_status & PORT_CONNECT)) { @@ -217,8 +218,7 @@ static int check_reset_complete ( // what happens if HCS_N_CC(params) == 0 ? port_status |= PORT_OWNER; port_status &= ~PORT_RWC_BITS; - ehci_writel(ehci, port_status, - &ehci->regs->port_status [index]); + ehci_writel(ehci, port_status, status_reg); } else ehci_dbg (ehci, "port %d high speed\n", index + 1); @@ -347,6 +347,7 @@ 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 temp, status; unsigned long flags; int retval = 0; @@ -375,18 +376,17 @@ static int ehci_hub_control ( if (!wIndex || wIndex > ports) goto error; wIndex--; - temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, status_reg); if (temp & PORT_OWNER) break; switch (wValue) { case USB_PORT_FEAT_ENABLE: - ehci_writel(ehci, temp & ~PORT_PE, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp & ~PORT_PE, status_reg); break; case USB_PORT_FEAT_C_ENABLE: ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_PEC, - &ehci->regs->port_status [wIndex]); + status_reg); break; case USB_PORT_FEAT_SUSPEND: if (temp & PORT_RESET) @@ -399,7 +399,7 @@ static int ehci_hub_control ( /* resume signaling for 20 msec */ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); ehci_writel(ehci, temp | PORT_RESUME, - &ehci->regs->port_status [wIndex]); + status_reg); ehci->reset_done [wIndex] = jiffies + msecs_to_jiffies (20); } @@ -411,15 +411,15 @@ static int ehci_hub_control ( if (HCS_PPC (ehci->hcs_params)) ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_POWER), - &ehci->regs->port_status [wIndex]); + status_reg); break; case USB_PORT_FEAT_C_CONNECTION: ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC, - &ehci->regs->port_status [wIndex]); + status_reg); break; case USB_PORT_FEAT_C_OVER_CURRENT: ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_OCC, - &ehci->regs->port_status [wIndex]); + status_reg); break; case USB_PORT_FEAT_C_RESET: /* GetPortStatus clears reset */ @@ -443,7 +443,7 @@ static int ehci_hub_control ( goto error; wIndex--; status = 0; - temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, status_reg); // wPortChange bits if (temp & PORT_CSC) @@ -461,13 +461,11 @@ static int ehci_hub_control ( ehci->reset_done [wIndex] = 0; /* stop resume signaling */ - temp = ehci_readl(ehci, - &ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, status_reg); ehci_writel(ehci, - temp & ~(PORT_RWC_BITS | PORT_RESUME), - &ehci->regs->port_status [wIndex]); - retval = handshake(ehci, - &ehci->regs->port_status [wIndex], + temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + retval = handshake(ehci, status_reg, PORT_RESUME, 0, 2000 /* 2msec */); if (retval != 0) { ehci_err (ehci, "port %d resume error %d\n", @@ -486,12 +484,11 @@ static int ehci_hub_control ( /* force reset to complete */ ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET), - &ehci->regs->port_status [wIndex]); + status_reg); /* REVISIT: some hardware needs 550+ usec to clear * this bit; seems too long to spin routinely... */ - retval = handshake(ehci, - &ehci->regs->port_status [wIndex], + retval = handshake(ehci, status_reg, PORT_RESET, 0, 750); if (retval != 0) { ehci_err (ehci, "port %d reset error %d\n", @@ -500,9 +497,8 @@ static int ehci_hub_control ( } /* see what we found out */ - temp = check_reset_complete (ehci, wIndex, - ehci_readl(ehci, - &ehci->regs->port_status [wIndex])); + temp = check_reset_complete (ehci, wIndex, status_reg, + ehci_readl(ehci, status_reg)); } // don't show wPortStatus if it's owned by a companion hc @@ -547,7 +543,7 @@ static int ehci_hub_control ( if (!wIndex || wIndex > ports) goto error; wIndex--; - temp = ehci_readl(ehci, &ehci->regs->port_status [wIndex]); + temp = ehci_readl(ehci, status_reg); if (temp & PORT_OWNER) break; @@ -561,13 +557,12 @@ static int ehci_hub_control ( goto error; if (device_may_wakeup(&hcd->self.root_hub->dev)) temp |= PORT_WAKE_BITS; - ehci_writel(ehci, temp | PORT_SUSPEND, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); break; case USB_PORT_FEAT_POWER: if (HCS_PPC (ehci->hcs_params)) ehci_writel(ehci, temp | PORT_POWER, - &ehci->regs->port_status [wIndex]); + status_reg); break; case USB_PORT_FEAT_RESET: if (temp & PORT_RESUME) @@ -595,8 +590,7 @@ static int ehci_hub_control ( ehci->reset_done [wIndex] = jiffies + msecs_to_jiffies (50); } - ehci_writel(ehci, temp, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp, status_reg); break; /* For downstream facing ports (these): one hub port is put @@ -611,8 +605,7 @@ static int ehci_hub_control ( ehci_quiesce(ehci); ehci_halt(ehci); temp |= selector << 16; - ehci_writel(ehci, temp, - &ehci->regs->port_status [wIndex]); + ehci_writel(ehci, temp, status_reg); break; default: -- cgit v1.2.3 From 625b5c9a0069ef1b61feb3ce599b39f1b04b5666 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 Jan 2007 11:58:47 -0500 Subject: EHCI: don't hide ports owned by the companion This patch (as709) changes the way ehci-hcd presents port status values for ports owned by the companion controller. It no longer hides the information; in particular, it allows the core to see the disconnect event that occurs when a full- or low-speed device is switched over to the companion. This is required for the next patch in this series. Signed-off-by: Alan Stern Cc: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 64 +++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 29 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 12f881ff4b2..076474d95db 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -270,16 +270,14 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) spin_lock_irqsave (&ehci->lock, flags); for (i = 0; i < ports; i++) { temp = ehci_readl(ehci, &ehci->regs->port_status [i]); - if (temp & PORT_OWNER) { - /* don't report this in GetPortStatus */ - if (temp & PORT_CSC) { - temp &= ~PORT_RWC_BITS; - temp |= PORT_CSC; - ehci_writel(ehci, temp, - &ehci->regs->port_status [i]); - } - continue; - } + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + if (!(temp & PORT_CONNECT)) ehci->reset_done [i] = 0; if ((temp & mask) != 0 @@ -377,8 +375,13 @@ static int ehci_hub_control ( goto error; wIndex--; temp = ehci_readl(ehci, status_reg); - if (temp & PORT_OWNER) - break; + + /* + * 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). + */ switch (wValue) { case USB_PORT_FEAT_ENABLE: @@ -501,24 +504,27 @@ static int ehci_hub_control ( ehci_readl(ehci, status_reg)); } - // don't show wPortStatus if it's owned by a companion hc - if (!(temp & PORT_OWNER)) { - if (temp & PORT_CONNECT) { - status |= 1 << USB_PORT_FEAT_CONNECTION; - // status may be from integrated TT - status |= ehci_port_speed(ehci, temp); - } - if (temp & PORT_PE) - status |= 1 << USB_PORT_FEAT_ENABLE; - if (temp & (PORT_SUSPEND|PORT_RESUME)) - status |= 1 << USB_PORT_FEAT_SUSPEND; - if (temp & PORT_OC) - status |= 1 << USB_PORT_FEAT_OVER_CURRENT; - if (temp & PORT_RESET) - status |= 1 << USB_PORT_FEAT_RESET; - if (temp & PORT_POWER) - status |= 1 << USB_PORT_FEAT_POWER; + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + // status may be from integrated TT + status |= ehci_port_speed(ehci, temp); } + if (temp & PORT_PE) + status |= 1 << USB_PORT_FEAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= 1 << USB_PORT_FEAT_SUSPEND; + if (temp & PORT_OC) + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + if (temp & PORT_RESET) + status |= 1 << USB_PORT_FEAT_RESET; + if (temp & PORT_POWER) + status |= 1 << USB_PORT_FEAT_POWER; #ifndef EHCI_VERBOSE_DEBUG if (status & ~0xffff) /* only if wPortChange is interesting */ -- cgit v1.2.3 From 57e06c11372eccf5acebdd4664eb025fee76c561 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 Jan 2007 11:59:45 -0500 Subject: EHCI: force high-speed devices to run at full speed This patch (as710) adds a sysfs class-device attribute file named "companion" for EHCI controllers. The file contains a list of port numbers that are dedicated to the companion controller; by writing a port number to the file the user can force a high-speed device attached directly to the computer to run at full speed. (As far as I know it is not possible to do this for a device attached to an external hub.) A port is removed from the file by writing the negative of its port number. Several users have asked for this facility and it seems like a useful thing to have. Every now and then one runs across a device which behaves much better at full speed than at high speed. Signed-off-by: Alan Stern Cc: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 107 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 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 076474d95db..3cfba69e076 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -188,6 +188,103 @@ static int ehci_bus_resume (struct usb_hcd *hcd) #endif /* CONFIG_PM */ +/*-------------------------------------------------------------------------*/ + +/* Display the ports dedicated to the companion controller */ +static ssize_t show_companion(struct class_device *class_dev, char *buf) +{ + struct ehci_hcd *ehci; + int nports, index, n; + int count = PAGE_SIZE; + char *ptr = buf; + + ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); + nports = HCS_N_PORTS(ehci->hcs_params); + + for (index = 0; index < nports; ++index) { + if (test_bit(index, &ehci->companion_ports)) { + n = scnprintf(ptr, count, "%d\n", index + 1); + ptr += n; + count -= n; + } + } + return ptr - buf; +} + +/* + * Dedicate or undedicate a port to the companion controller. + * Syntax is "[-]portnum", where a leading '-' sign means + * return control of the port to the EHCI controller. + */ +static ssize_t store_companion(struct class_device *class_dev, + const char *buf, size_t count) +{ + struct ehci_hcd *ehci; + int portnum, new_owner, try; + u32 __iomem *status_reg; + u32 port_status; + + ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev))); + new_owner = PORT_OWNER; /* Owned by companion */ + if (sscanf(buf, "%d", &portnum) != 1) + return -EINVAL; + if (portnum < 0) { + portnum = - portnum; + new_owner = 0; /* Owned by EHCI */ + } + if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params)) + return -ENOENT; + status_reg = &ehci->regs->port_status[--portnum]; + if (new_owner) + set_bit(portnum, &ehci->companion_ports); + else + clear_bit(portnum, &ehci->companion_ports); + + /* + * The controller won't set the OWNER bit if the port is + * enabled, so this loop will sometimes require at least two + * iterations: one to disable the port and one to set OWNER. + */ + + for (try = 4; try > 0; --try) { + spin_lock_irq(&ehci->lock); + port_status = ehci_readl(ehci, status_reg); + if ((port_status & PORT_OWNER) == new_owner + || (port_status & (PORT_OWNER | PORT_CONNECT)) + == 0) + try = 0; + else { + port_status ^= PORT_OWNER; + port_status &= ~(PORT_PE | PORT_RWC_BITS); + ehci_writel(ehci, port_status, status_reg); + } + spin_unlock_irq(&ehci->lock); + if (try > 1) + msleep(5); + } + return count; +} +static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion); + +static inline void create_companion_file(struct ehci_hcd *ehci) +{ + int i; + + /* with integrated TT there is no companion! */ + if (!ehci_is_TDI(ehci)) + i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev, + &class_device_attr_companion); +} + +static inline void remove_companion_file(struct ehci_hcd *ehci) +{ + /* with integrated TT there is no companion! */ + if (!ehci_is_TDI(ehci)) + class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev, + &class_device_attr_companion); +} + + /*-------------------------------------------------------------------------*/ static int check_reset_complete ( @@ -504,6 +601,16 @@ static int ehci_hub_control ( ehci_readl(ehci, status_reg)); } + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &ehci->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + ehci_writel(ehci, temp, status_reg); + ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1); + temp = ehci_readl(ehci, status_reg); + } + /* * Even if OWNER is set, there's no harm letting khubd * see the wPortStatus values (they should all be 0 except -- cgit v1.2.3 From 629e4427aa817d5c9f11885420abf54b8f5967dc Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 22 Jan 2007 16:08:53 -0500 Subject: EHCI: fix interrupt-driven remote wakeup Now that port status change notifications are interrupt-driven, ehci-hcd needs to tell usbcore when a remote-wakeup resume operation is finished -- we can no longer rely on the core to poll and find out. This patch (as843) uses the root-hub status timer to force a poll after the resume is complete. The patch also changes the test for detecting when the TDRSMDN resume period has expired. It's necessary to use time_after_eq() instead of time_after(), since the polling is triggered precisely by a timer. The same change is made for TDRSTR reset expiration, for consistency. Signed-off-by: Alan Stern Cc: David Brownell Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-hub.c | 50 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 18 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 3cfba69e076..0d83c6df1a3 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -379,8 +379,8 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) ehci->reset_done [i] = 0; if ((temp & mask) != 0 || ((temp & PORT_RESUME) != 0 - && time_after (jiffies, - ehci->reset_done [i]))) { + && time_after_eq(jiffies, + ehci->reset_done[i]))) { if (i < 7) buf [0] |= 1 << (i + 1); else @@ -554,31 +554,45 @@ static int ehci_hub_control ( status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; /* whoever resumes must GetPortStatus to complete it!! */ - if ((temp & PORT_RESUME) - && time_after (jiffies, - ehci->reset_done [wIndex])) { - status |= 1 << USB_PORT_FEAT_C_SUSPEND; - ehci->reset_done [wIndex] = 0; + if (temp & PORT_RESUME) { - /* stop resume signaling */ - temp = ehci_readl(ehci, status_reg); - ehci_writel(ehci, + /* Remote Wakeup received? */ + if (!ehci->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + ehci->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* 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])) { + status |= 1 << USB_PORT_FEAT_C_SUSPEND; + ehci->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = ehci_readl(ehci, status_reg); + ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESUME), status_reg); - retval = handshake(ehci, status_reg, + retval = 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; + if (retval != 0) { + ehci_err(ehci, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); } - temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); } /* whoever resets must GetPortStatus to complete it!! */ if ((temp & PORT_RESET) - && time_after (jiffies, - ehci->reset_done [wIndex])) { + && time_after_eq(jiffies, + ehci->reset_done[wIndex])) { status |= 1 << USB_PORT_FEAT_C_RESET; ehci->reset_done [wIndex] = 0; -- cgit v1.2.3