diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2010-01-20 09:58:02 +0100 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2010-02-19 20:51:10 +0100 |
commit | b677532b971276f48e82578b4d829fb4382e7b41 (patch) | |
tree | 5f773a4d65614872c619109595c09b8f9c93bda1 /drivers/firewire/ohci.c | |
parent | a67483d2be12dfc5563c09e6169bec9a88f434b0 (diff) |
firewire: ohci: work around cycle timer bugs on VIA controllers
VIA controllers sometimes return an inconsistent value when reading the
isochronous cycle timer register. To work around this, read the
register multiple times and add consistency checks.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Reported-by: Pieter Palmers <pieterp@joow.be>
Reported-by: HÃ¥kan Johansson <f96hajo@chalmers.se>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Diffstat (limited to 'drivers/firewire/ohci.c')
-rw-r--r-- | drivers/firewire/ohci.c | 52 |
1 files changed, 49 insertions, 3 deletions
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index 6610d2d3880..d6ba897b219 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -192,6 +192,7 @@ struct fw_ohci { bool use_dualbuffer; bool old_uninorth; bool bus_reset_packet_quirk; + bool iso_cycle_timer_quirk; /* * Spinlock for accessing fw_ohci data. Never call out of @@ -1794,14 +1795,57 @@ static int ohci_enable_phys_dma(struct fw_card *card, #endif /* CONFIG_FIREWIRE_OHCI_REMOTE_DMA */ } +static inline u32 cycle_timer_ticks(u32 cycle_timer) +{ + u32 ticks; + + ticks = cycle_timer & 0xfff; + ticks += 3072 * ((cycle_timer >> 12) & 0x1fff); + ticks += (3072 * 8000) * (cycle_timer >> 25); + return ticks; +} + static u64 ohci_get_bus_time(struct fw_card *card) { struct fw_ohci *ohci = fw_ohci(card); - u32 cycle_time; + u32 c0, c1, c2; + u32 t0, t1, t2; + s32 diff01, diff12; u64 bus_time; - cycle_time = reg_read(ohci, OHCI1394_IsochronousCycleTimer); - bus_time = ((u64)atomic_read(&ohci->bus_seconds) << 32) | cycle_time; + if (!ohci->iso_cycle_timer_quirk) { + c2 = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + } else { + /* + * VIA controllers have two bugs when updating the iso cycle + * timer register: + * 1) When the lowest six bits are wrapping around to zero, + * a read that happens at the same time will return garbage + * in the lowest ten bits. + * 2) When the cycleOffset field wraps around to zero, the + * cycleCount field is not incremented for about 60 ns. + * + * To catch these, we read the register three times and ensure + * that the difference between each two consecutive reads is + * approximately the same, i.e., less than twice the other. + * Furthermore, any negative difference indicates an error. + * (A PCI read should take at least 20 ticks of the 24.576 MHz + * timer to execute, so we have enough precision to compute the + * ratio of the differences.) + */ + do { + c0 = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + c1 = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + c2 = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + t0 = cycle_timer_ticks(c0); + t1 = cycle_timer_ticks(c1); + t2 = cycle_timer_ticks(c2); + diff01 = t1 - t0; + diff12 = t2 - t1; + } while (diff01 <= 0 || diff12 <= 0 || + diff01 / diff12 >= 2 || diff12 / diff01 >= 2); + } + bus_time = ((u64)atomic_read(&ohci->bus_seconds) << 32) | c2; return bus_time; } @@ -2498,6 +2542,8 @@ static int __devinit pci_probe(struct pci_dev *dev, #endif ohci->bus_reset_packet_quirk = dev->vendor == PCI_VENDOR_ID_TI; + ohci->iso_cycle_timer_quirk = dev->vendor == PCI_VENDOR_ID_VIA; + ar_context_init(&ohci->ar_request_ctx, ohci, OHCI1394_AsReqRcvContextControlSet); |