Commit 48a7ed7b authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] ehci-hcd more polite on cardbus

This patch makes the EHCI driver behave reasonably well in the
cardbus configurations I can test ... basically, it now sees
when a card is gone, and cleans up accordingly.  There are also
some related cleanups:  hardware handshakes will time out (not
that I've ever seen them fail), and some state management puts
a bit more effort into being strictly to-spec.
parent 53aef8ec
...@@ -65,6 +65,8 @@ ...@@ -65,6 +65,8 @@
* *
* HISTORY: * HISTORY:
* *
* 2002-07-25 Sanity check PCI reads, mostly for better cardbus support,
* clean up HC run state handshaking.
* 2002-05-24 Preliminary FS/LS interrupts, using scheduling shortcuts * 2002-05-24 Preliminary FS/LS interrupts, using scheduling shortcuts
* 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other * 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other
* missing pieces: enabling 64bit dma, handoff from BIOS/SMM. * missing pieces: enabling 64bit dma, handoff from BIOS/SMM.
...@@ -83,7 +85,7 @@ ...@@ -83,7 +85,7 @@
* 2001-June Works with usb-storage and NEC EHCI on 2.4 * 2001-June Works with usb-storage and NEC EHCI on 2.4
*/ */
#define DRIVER_VERSION "2002-May-24" #define DRIVER_VERSION "2002-Jul-25"
#define DRIVER_AUTHOR "David Brownell" #define DRIVER_AUTHOR "David Brownell"
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" #define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
...@@ -112,6 +114,40 @@ MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); ...@@ -112,6 +114,40 @@ MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes");
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/*
* handshake - spin reading hc until handshake completes or fails
* @ptr: address of hc register to be read
* @mask: bits to look at in result of read
* @done: value of those bits when handshake succeeds
* @usec: timeout in microseconds
*
* Returns negative errno, or zero on success
*
* Success happens when the "mask" bits have the specified value (hardware
* handshake done). There are two failure modes: "usec" have passed (major
* hardware flakeout), or the register reads as all-ones (hardware removed).
*
* That last failure should_only happen in cases like physical cardbus eject
* before driver shutdown. But it also seems to be caused by bugs in cardbus
* bridge shutdown: shutting down the bridge before the devices using it.
*/
static int handshake (u32 *ptr, u32 mask, u32 done, int usec)
{
u32 result;
do {
result = readl (ptr);
if (result == ~(u32)0) /* card removed */
return -ENODEV;
result &= mask;
if (result == done)
return 0;
udelay (1);
usec--;
} while (usec > 0);
return -ETIMEDOUT;
}
/* /*
* hc states include: unknown, halted, ready, running * hc states include: unknown, halted, ready, running
* transitional states are messy just now * transitional states are messy just now
...@@ -119,36 +155,65 @@ MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); ...@@ -119,36 +155,65 @@ MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes");
* a "ready" hc can be finishing prefetched work * a "ready" hc can be finishing prefetched work
*/ */
/* halt a non-running controller */ /* force HC to halt state from unknown (EHCI spec section 2.3) */
static void ehci_reset (struct ehci_hcd *ehci) static int ehci_halt (struct ehci_hcd *ehci)
{
u32 temp = readl (&ehci->regs->status);
if ((temp & STS_HALT) != 0)
return 0;
temp = readl (&ehci->regs->command);
temp &= ~CMD_RUN;
writel (temp, &ehci->regs->command);
return handshake (&ehci->regs->status, STS_HALT, STS_HALT, 16 * 125);
}
/* reset a non-running (STS_HALT == 1) controller */
static int ehci_reset (struct ehci_hcd *ehci)
{ {
u32 command = readl (&ehci->regs->command); u32 command = readl (&ehci->regs->command);
command |= CMD_RESET; command |= CMD_RESET;
dbg_cmd (ehci, "reset", command); dbg_cmd (ehci, "reset", command);
writel (command, &ehci->regs->command); writel (command, &ehci->regs->command);
while (readl (&ehci->regs->command) & CMD_RESET)
continue;
ehci->hcd.state = USB_STATE_HALT; ehci->hcd.state = USB_STATE_HALT;
return handshake (&ehci->regs->command, CMD_RESET, 0, 050);
} }
/* idle the controller (from running) */ /* idle the controller (from running) */
static void ehci_ready (struct ehci_hcd *ehci) static void ehci_ready (struct ehci_hcd *ehci)
{ {
u32 command; u32 temp;
#ifdef DEBUG #ifdef DEBUG
if (!HCD_IS_RUNNING (ehci->hcd.state)) if (!HCD_IS_RUNNING (ehci->hcd.state))
BUG (); BUG ();
#endif #endif
while (!(readl (&ehci->regs->status) & (STS_ASS | STS_PSS))) /* wait for any schedule enables/disables to take effect */
udelay (100); temp = 0;
command = readl (&ehci->regs->command); if (ehci->async)
command &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); temp = STS_ASS;
writel (command, &ehci->regs->command); if (ehci->next_uframe != -1)
temp |= STS_PSS;
if (handshake (&ehci->regs->status, STS_ASS | STS_PSS,
temp, 16 * 125) != 0) {
ehci->hcd.state = USB_STATE_HALT;
return;
}
// hardware can take 16 microframes to turn off ... /* then disable anything that's still active */
temp = readl (&ehci->regs->command);
temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE);
writel (temp, &ehci->regs->command);
/* hardware can take 16 microframes to turn off ... */
if (handshake (&ehci->regs->status, STS_ASS | STS_PSS,
0, 16 * 125) != 0) {
ehci->hcd.state = USB_STATE_HALT;
return;
}
ehci->hcd.state = USB_STATE_READY; ehci->hcd.state = USB_STATE_READY;
} }
...@@ -236,6 +301,10 @@ static int ehci_start (struct usb_hcd *hcd) ...@@ -236,6 +301,10 @@ static int ehci_start (struct usb_hcd *hcd)
/* cache this readonly data; minimize PCI reads */ /* cache this readonly data; minimize PCI reads */
ehci->hcs_params = readl (&ehci->caps->hcs_params); ehci->hcs_params = readl (&ehci->caps->hcs_params);
/* force HC to halt state */
if ((retval = ehci_halt (ehci)) != 0)
return retval;
/* /*
* hw default: 1K periodic list heads, one per frame. * hw default: 1K periodic list heads, one per frame.
* periodic_size can shrink by USBCMD update if hcc_params allows. * periodic_size can shrink by USBCMD update if hcc_params allows.
...@@ -257,8 +326,10 @@ static int ehci_start (struct usb_hcd *hcd) ...@@ -257,8 +326,10 @@ static int ehci_start (struct usb_hcd *hcd)
/* controller state: unknown --> reset */ /* controller state: unknown --> reset */
/* EHCI spec section 4.1 */ /* EHCI spec section 4.1 */
// FIXME require STS_HALT before reset... if ((retval = ehci_reset (ehci)) != 0) {
ehci_reset (ehci); ehci_mem_cleanup (ehci);
return retval;
}
writel (INTR_MASK, &ehci->regs->intr_enable); writel (INTR_MASK, &ehci->regs->intr_enable);
writel (ehci->periodic_dma, &ehci->regs->frame_list); writel (ehci->periodic_dma, &ehci->regs->frame_list);
...@@ -335,8 +406,6 @@ static int ehci_start (struct usb_hcd *hcd) ...@@ -335,8 +406,6 @@ static int ehci_start (struct usb_hcd *hcd)
if (usb_register_root_hub (udev, &ehci->hcd.pdev->dev) != 0) { if (usb_register_root_hub (udev, &ehci->hcd.pdev->dev) != 0) {
if (hcd->state == USB_STATE_RUNNING) if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci); ehci_ready (ehci);
while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
udelay (100);
ehci_reset (ehci); ehci_reset (ehci);
hcd->self.root_hub = 0; hcd->self.root_hub = 0;
usb_free_dev (udev); usb_free_dev (udev);
...@@ -355,16 +424,14 @@ static void ehci_stop (struct usb_hcd *hcd) ...@@ -355,16 +424,14 @@ static void ehci_stop (struct usb_hcd *hcd)
dbg ("%s: stop", hcd->self.bus_name); dbg ("%s: stop", hcd->self.bus_name);
/* no more interrupts ... */
if (hcd->state == USB_STATE_RUNNING) if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci); ehci_ready (ehci);
while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
udelay (100);
ehci_reset (ehci); ehci_reset (ehci);
// root hub is shut down separately (first, when possible) /* root hub is shut down separately (first, when possible) */
scan_async (ehci); tasklet_disable (&ehci->tasklet);
if (ehci->next_uframe != -1) ehci_tasklet ((unsigned long) ehci);
scan_periodic (ehci);
ehci_mem_cleanup (ehci); ehci_mem_cleanup (ehci);
dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status)); dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status));
...@@ -412,8 +479,6 @@ dbg ("%s: suspend port %d", hcd->self.bus_name, i); ...@@ -412,8 +479,6 @@ dbg ("%s: suspend port %d", hcd->self.bus_name, i);
if (hcd->state == USB_STATE_RUNNING) if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci); ehci_ready (ehci);
while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
udelay (100);
writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command); writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command);
// save pci FLADJ value // save pci FLADJ value
...@@ -489,6 +554,12 @@ static void ehci_irq (struct usb_hcd *hcd) ...@@ -489,6 +554,12 @@ static void ehci_irq (struct usb_hcd *hcd)
u32 status = readl (&ehci->regs->status); u32 status = readl (&ehci->regs->status);
int bh; int bh;
/* e.g. cardbus physical eject */
if (status == ~(u32) 0) {
dbg ("%s: device removed!", hcd->self.bus_name);
goto dead;
}
status &= INTR_MASK; status &= INTR_MASK;
if (!status) /* irq sharing? */ if (!status) /* irq sharing? */
return; return;
...@@ -517,10 +588,13 @@ static void ehci_irq (struct usb_hcd *hcd) ...@@ -517,10 +588,13 @@ static void ehci_irq (struct usb_hcd *hcd)
/* PCI errors [4.15.2.4] */ /* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) { if (unlikely ((status & STS_FATAL) != 0)) {
err ("%s: fatal error, state %x", hcd->self.bus_name, hcd->state); err ("%s: fatal error, state %x",
hcd->self.bus_name, hcd->state);
dead:
ehci_reset (ehci); ehci_reset (ehci);
// generic layer kills/unlinks all urbs /* generic layer kills/unlinks all urbs, then
// then tasklet cleans up the rest * uses ehci_stop to clean up the rest
*/
bh = 1; bh = 1;
} }
......
...@@ -718,8 +718,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -718,8 +718,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
u32 cmd = readl (&ehci->regs->command); u32 cmd = readl (&ehci->regs->command);
/* in case a clear of CMD_ASE didn't take yet */ /* in case a clear of CMD_ASE didn't take yet */
while (readl (&ehci->regs->status) & STS_ASS) (void) handshake (&ehci->regs->status, STS_ASS, 0, 150);
udelay (100);
qh->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); /* [4.8] */ qh->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); /* [4.8] */
qh->qh_next.qh = qh; qh->qh_next.qh = qh;
...@@ -917,11 +916,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -917,11 +916,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
if (ehci->hcd.state != USB_STATE_HALT) { if (ehci->hcd.state != USB_STATE_HALT) {
if (cmd & CMD_PSE) if (cmd & CMD_PSE)
writel (cmd & ~CMD_ASE, &ehci->regs->command); writel (cmd & ~CMD_ASE, &ehci->regs->command);
else { else
ehci_ready (ehci); ehci_ready (ehci);
while (readl (&ehci->regs->status) & STS_ASS)
udelay (100);
}
} }
qh->qh_next.qh = ehci->async = 0; qh->qh_next.qh = ehci->async = 0;
......
...@@ -171,15 +171,19 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) ...@@ -171,15 +171,19 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void enable_periodic (struct ehci_hcd *ehci) static int enable_periodic (struct ehci_hcd *ehci)
{ {
u32 cmd; u32 cmd;
int status;
/* did clearing PSE did take effect yet? /* did clearing PSE did take effect yet?
* takes effect only at frame boundaries... * takes effect only at frame boundaries...
*/ */
while (readl (&ehci->regs->status) & STS_PSS) status = handshake (&ehci->regs->status, STS_PSS, 0, 9 * 125);
udelay (20); if (status != 0) {
ehci->hcd.state = USB_STATE_HALT;
return status;
}
cmd = readl (&ehci->regs->command) | CMD_PSE; cmd = readl (&ehci->regs->command) | CMD_PSE;
writel (cmd, &ehci->regs->command); writel (cmd, &ehci->regs->command);
...@@ -189,23 +193,29 @@ static void enable_periodic (struct ehci_hcd *ehci) ...@@ -189,23 +193,29 @@ static void enable_periodic (struct ehci_hcd *ehci)
/* make sure tasklet scans these */ /* make sure tasklet scans these */
ehci->next_uframe = readl (&ehci->regs->frame_index) ehci->next_uframe = readl (&ehci->regs->frame_index)
% (ehci->periodic_size << 3); % (ehci->periodic_size << 3);
return 0;
} }
static void disable_periodic (struct ehci_hcd *ehci) static int disable_periodic (struct ehci_hcd *ehci)
{ {
u32 cmd; u32 cmd;
int status;
/* did setting PSE not take effect yet? /* did setting PSE not take effect yet?
* takes effect only at frame boundaries... * takes effect only at frame boundaries...
*/ */
while (!(readl (&ehci->regs->status) & STS_PSS)) status = handshake (&ehci->regs->status, STS_PSS, STS_PSS, 9 * 125);
udelay (20); if (status != 0) {
ehci->hcd.state = USB_STATE_HALT;
return status;
}
cmd = readl (&ehci->regs->command) & ~CMD_PSE; cmd = readl (&ehci->regs->command) & ~CMD_PSE;
writel (cmd, &ehci->regs->command); writel (cmd, &ehci->regs->command);
/* posted write ... */ /* posted write ... */
ehci->next_uframe = -1; ehci->next_uframe = -1;
return 0;
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -217,6 +227,7 @@ static void intr_deschedule ( ...@@ -217,6 +227,7 @@ static void intr_deschedule (
unsigned period unsigned period
) { ) {
unsigned long flags; unsigned long flags;
int status;
period >>= 3; // FIXME microframe periods not handled yet period >>= 3; // FIXME microframe periods not handled yet
...@@ -234,9 +245,11 @@ static void intr_deschedule ( ...@@ -234,9 +245,11 @@ static void intr_deschedule (
/* maybe turn off periodic schedule */ /* maybe turn off periodic schedule */
if (!ehci->periodic_urbs) if (!ehci->periodic_urbs)
disable_periodic (ehci); status = disable_periodic (ehci);
else else {
status = 0;
vdbg ("periodic schedule still enabled"); vdbg ("periodic schedule still enabled");
}
spin_unlock_irqrestore (&ehci->lock, flags); spin_unlock_irqrestore (&ehci->lock, flags);
...@@ -245,7 +258,7 @@ static void intr_deschedule ( ...@@ -245,7 +258,7 @@ static void intr_deschedule (
* (yeech!) to be sure it's done. * (yeech!) to be sure it's done.
* No other threads may be mucking with this qh. * No other threads may be mucking with this qh.
*/ */
if (((ehci_get_frame (&ehci->hcd) - frame) % period) == 0) if (!status && ((ehci_get_frame (&ehci->hcd) - frame) % period) == 0)
udelay (125); udelay (125);
qh->qh_state = QH_STATE_IDLE; qh->qh_state = QH_STATE_IDLE;
...@@ -501,7 +514,7 @@ static int intr_submit ( ...@@ -501,7 +514,7 @@ static int intr_submit (
/* maybe enable periodic schedule processing */ /* maybe enable periodic schedule processing */
if (!ehci->periodic_urbs++) if (!ehci->periodic_urbs++)
enable_periodic (ehci); status = enable_periodic (ehci);
break; break;
} while (frame); } while (frame);
...@@ -913,8 +926,12 @@ itd_schedule (struct ehci_hcd *ehci, struct urb *urb) ...@@ -913,8 +926,12 @@ itd_schedule (struct ehci_hcd *ehci, struct urb *urb)
usb_claim_bandwidth (urb->dev, urb, usecs, 1); usb_claim_bandwidth (urb->dev, urb, usecs, 1);
/* maybe enable periodic schedule processing */ /* maybe enable periodic schedule processing */
if (!ehci->periodic_urbs++) if (!ehci->periodic_urbs++) {
enable_periodic (ehci); if ((status = enable_periodic (ehci)) != 0) {
// FIXME deschedule right away
err ("itd_schedule, enable = %d", status);
}
}
return 0; return 0;
...@@ -994,7 +1011,7 @@ itd_complete ( ...@@ -994,7 +1011,7 @@ itd_complete (
/* defer stopping schedule; completion can submit */ /* defer stopping schedule; completion can submit */
ehci->periodic_urbs--; ehci->periodic_urbs--;
if (!ehci->periodic_urbs) if (!ehci->periodic_urbs)
disable_periodic (ehci); (void) disable_periodic (ehci);
return flags; return flags;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment