aboutsummaryrefslogtreecommitdiff
path: root/drivers/firewire/ohci.c
diff options
context:
space:
mode:
authorClemens Ladisch <clemens@ladisch.de>2010-01-20 09:58:02 +0100
committerStefan Richter <stefanr@s5r6.in-berlin.de>2010-02-19 20:51:10 +0100
commitb677532b971276f48e82578b4d829fb4382e7b41 (patch)
tree5f773a4d65614872c619109595c09b8f9c93bda1 /drivers/firewire/ohci.c
parenta67483d2be12dfc5563c09e6169bec9a88f434b0 (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.c52
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);