| From 68aa95d5d4de31c9348c1628ffa85c805305ebc5 Mon Sep 17 00:00:00 2001 |
| From: Alan Stern <stern@rowland.harvard.edu> |
| Date: Wed, 12 Oct 2011 10:39:14 -0400 |
| Subject: EHCI: workaround for MosChip controller bug |
| |
| From: Alan Stern <stern@rowland.harvard.edu> |
| |
| commit 68aa95d5d4de31c9348c1628ffa85c805305ebc5 upstream. |
| |
| This patch (as1489) works around a hardware bug in MosChip EHCI |
| controllers. Evidently when one of these controllers increments the |
| frame-index register, it changes the three low-order bits (the |
| microframe counter) before changing the higher order bits (the frame |
| counter). If the register is read at just the wrong time, the value |
| obtained is too low by 8. |
| |
| When the appropriate quirk flag is set, we work around this problem by |
| reading the frame-index register a second time if the first value's |
| three low-order bits are all 0. This gives the hardware a chance to |
| finish updating the register, yielding the correct value. |
| |
| Signed-off-by: Alan Stern <stern@rowland.harvard.edu> |
| Tested-by: Jason N Pitt <jpitt@fhcrc.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| drivers/usb/host/ehci-dbg.c | 2 +- |
| drivers/usb/host/ehci-hcd.c | 3 +-- |
| drivers/usb/host/ehci-pci.c | 5 +++++ |
| drivers/usb/host/ehci-sched.c | 30 +++++++++++++++++++++++++----- |
| drivers/usb/host/ehci.h | 17 +++++++++++++++++ |
| 5 files changed, 49 insertions(+), 8 deletions(-) |
| |
| --- a/drivers/usb/host/ehci-dbg.c |
| +++ b/drivers/usb/host/ehci-dbg.c |
| @@ -808,7 +808,7 @@ static ssize_t fill_registers_buffer(str |
| next += temp; |
| |
| temp = scnprintf (next, size, "uframe %04x\n", |
| - ehci_readl(ehci, &ehci->regs->frame_index)); |
| + ehci_read_frame_index(ehci)); |
| size -= temp; |
| next += temp; |
| |
| --- a/drivers/usb/host/ehci-hcd.c |
| +++ b/drivers/usb/host/ehci-hcd.c |
| @@ -1195,8 +1195,7 @@ ehci_endpoint_reset(struct usb_hcd *hcd, |
| static int ehci_get_frame (struct usb_hcd *hcd) |
| { |
| struct ehci_hcd *ehci = hcd_to_ehci (hcd); |
| - return (ehci_readl(ehci, &ehci->regs->frame_index) >> 3) % |
| - ehci->periodic_size; |
| + return (ehci_read_frame_index(ehci) >> 3) % ehci->periodic_size; |
| } |
| |
| /*-------------------------------------------------------------------------*/ |
| --- a/drivers/usb/host/ehci-pci.c |
| +++ b/drivers/usb/host/ehci-pci.c |
| @@ -224,6 +224,11 @@ static int ehci_pci_setup(struct usb_hcd |
| pci_dev_put(p_smbus); |
| } |
| break; |
| + case PCI_VENDOR_ID_NETMOS: |
| + /* MosChip frame-index-register bug */ |
| + ehci_info(ehci, "applying MosChip frame-index workaround\n"); |
| + ehci->frame_index_bug = 1; |
| + break; |
| } |
| |
| /* optional debug port, normally in the first BAR */ |
| --- a/drivers/usb/host/ehci-sched.c |
| +++ b/drivers/usb/host/ehci-sched.c |
| @@ -36,6 +36,27 @@ |
| |
| static int ehci_get_frame (struct usb_hcd *hcd); |
| |
| +#ifdef CONFIG_PCI |
| + |
| +static unsigned ehci_read_frame_index(struct ehci_hcd *ehci) |
| +{ |
| + unsigned uf; |
| + |
| + /* |
| + * The MosChip MCS9990 controller updates its microframe counter |
| + * a little before the frame counter, and occasionally we will read |
| + * the invalid intermediate value. Avoid problems by checking the |
| + * microframe number (the low-order 3 bits); if they are 0 then |
| + * re-read the register to get the correct value. |
| + */ |
| + uf = ehci_readl(ehci, &ehci->regs->frame_index); |
| + if (unlikely(ehci->frame_index_bug && ((uf & 7) == 0))) |
| + uf = ehci_readl(ehci, &ehci->regs->frame_index); |
| + return uf; |
| +} |
| + |
| +#endif |
| + |
| /*-------------------------------------------------------------------------*/ |
| |
| /* |
| @@ -482,7 +503,7 @@ static int enable_periodic (struct ehci_ |
| ehci_to_hcd(ehci)->state = HC_STATE_RUNNING; |
| |
| /* make sure ehci_work scans these */ |
| - ehci->next_uframe = ehci_readl(ehci, &ehci->regs->frame_index) |
| + ehci->next_uframe = ehci_read_frame_index(ehci) |
| % (ehci->periodic_size << 3); |
| if (unlikely(ehci->broken_periodic)) |
| ehci->last_periodic_enable = ktime_get_real(); |
| @@ -1409,7 +1430,7 @@ iso_stream_schedule ( |
| goto fail; |
| } |
| |
| - now = ehci_readl(ehci, &ehci->regs->frame_index) & (mod - 1); |
| + now = ehci_read_frame_index(ehci) & (mod - 1); |
| |
| /* Typical case: reuse current schedule, stream is still active. |
| * Hopefully there are no gaps from the host falling behind |
| @@ -2276,7 +2297,7 @@ scan_periodic (struct ehci_hcd *ehci) |
| */ |
| now_uframe = ehci->next_uframe; |
| if (HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) { |
| - clock = ehci_readl(ehci, &ehci->regs->frame_index); |
| + clock = ehci_read_frame_index(ehci); |
| clock_frame = (clock >> 3) & (ehci->periodic_size - 1); |
| } else { |
| clock = now_uframe + mod - 1; |
| @@ -2455,8 +2476,7 @@ restart: |
| || ehci->periodic_sched == 0) |
| break; |
| ehci->next_uframe = now_uframe; |
| - now = ehci_readl(ehci, &ehci->regs->frame_index) & |
| - (mod - 1); |
| + now = ehci_read_frame_index(ehci) & (mod - 1); |
| if (now_uframe == now) |
| break; |
| |
| --- a/drivers/usb/host/ehci.h |
| +++ b/drivers/usb/host/ehci.h |
| @@ -139,6 +139,7 @@ struct ehci_hcd { /* one per controlle |
| unsigned fs_i_thresh:1; /* Intel iso scheduling */ |
| unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ |
| unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ |
| + unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */ |
| |
| /* required for usb32 quirk */ |
| #define OHCI_CTRL_HCFS (3 << 6) |
| @@ -737,6 +738,22 @@ static inline u32 hc32_to_cpup (const st |
| } |
| |
| #endif |
| + |
| +/*-------------------------------------------------------------------------*/ |
| + |
| +#ifdef CONFIG_PCI |
| + |
| +/* For working around the MosChip frame-index-register bug */ |
| +static unsigned ehci_read_frame_index(struct ehci_hcd *ehci); |
| + |
| +#else |
| + |
| +static inline unsigned ehci_read_frame_index(struct ehci_hcd *ehci) |
| +{ |
| + return ehci_readl(ehci, &ehci->regs->frame_index); |
| +} |
| + |
| +#endif |
| |
| /*-------------------------------------------------------------------------*/ |
| |