summaryrefslogtreecommitdiffstats
path: root/drivers/firewire/ohci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firewire/ohci.c')
-rw-r--r--drivers/firewire/ohci.c185
1 files changed, 183 insertions, 2 deletions
diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c
index c026f46fc157..b983581cfe35 100644
--- a/drivers/firewire/ohci.c
+++ b/drivers/firewire/ohci.c
@@ -264,6 +264,8 @@ static char ohci_driver_name[] = KBUILD_MODNAME;
#define PCI_DEVICE_ID_AGERE_FW643 0x5901
#define PCI_DEVICE_ID_JMICRON_JMB38X_FW 0x2380
#define PCI_DEVICE_ID_TI_TSB12LV22 0x8009
+#define PCI_DEVICE_ID_TI_TSB12LV26 0x8020
+#define PCI_DEVICE_ID_TI_TSB82AA2 0x8025
#define PCI_VENDOR_ID_PINNACLE_SYSTEMS 0x11bd
#define QUIRK_CYCLE_TIMER 1
@@ -271,6 +273,7 @@ static char ohci_driver_name[] = KBUILD_MODNAME;
#define QUIRK_BE_HEADERS 4
#define QUIRK_NO_1394A 8
#define QUIRK_NO_MSI 16
+#define QUIRK_TI_SLLZ059 32
/* In case of multiple matches in ohci_quirks[], only the first one is used. */
static const struct {
@@ -300,6 +303,12 @@ static const struct {
{PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB12LV22, PCI_ANY_ID,
QUIRK_CYCLE_TIMER | QUIRK_RESET_PACKET | QUIRK_NO_1394A},
+ {PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB12LV26, PCI_ANY_ID,
+ QUIRK_RESET_PACKET | QUIRK_TI_SLLZ059},
+
+ {PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_TSB82AA2, PCI_ANY_ID,
+ QUIRK_RESET_PACKET | QUIRK_TI_SLLZ059},
+
{PCI_VENDOR_ID_TI, PCI_ANY_ID, PCI_ANY_ID,
QUIRK_RESET_PACKET},
@@ -316,6 +325,7 @@ MODULE_PARM_DESC(quirks, "Chip quirks (default = 0"
", AR/selfID endianess = " __stringify(QUIRK_BE_HEADERS)
", no 1394a enhancements = " __stringify(QUIRK_NO_1394A)
", disable MSI = " __stringify(QUIRK_NO_MSI)
+ ", workaround for TI SLLZ059 errata = " __stringify(QUIRK_TI_SLLZ059)
")");
#define OHCI_PARAM_DEBUG_AT_AR 1
@@ -1714,6 +1724,114 @@ static u32 update_bus_time(struct fw_ohci *ohci)
return ohci->bus_time | cycle_time_seconds;
}
+static int get_status_for_port(struct fw_ohci *ohci, int port_index)
+{
+ int reg;
+
+ mutex_lock(&ohci->phy_reg_mutex);
+ reg = write_phy_reg(ohci, 7, port_index);
+ mutex_unlock(&ohci->phy_reg_mutex);
+ if (reg < 0)
+ return reg;
+
+ mutex_lock(&ohci->phy_reg_mutex);
+ reg = read_phy_reg(ohci, 8);
+ mutex_unlock(&ohci->phy_reg_mutex);
+ if (reg < 0)
+ return reg;
+
+ switch (reg & 0x0f) {
+ case 0x06:
+ return 2; /* is child node (connected to parent node) */
+ case 0x0e:
+ return 3; /* is parent node (connected to child node) */
+ }
+ return 1; /* not connected */
+}
+
+static int get_self_id_pos(struct fw_ohci *ohci, u32 self_id,
+ int self_id_count)
+{
+ int i;
+ u32 entry;
+ for (i = 0; i < self_id_count; i++) {
+ entry = ohci->self_id_buffer[i];
+ if ((self_id & 0xff000000) == (entry & 0xff000000))
+ return -1;
+ if ((self_id & 0xff000000) < (entry & 0xff000000))
+ return i;
+ }
+ return i;
+}
+
+/*
+ * This function implements a work around for the Texas Instruments PHY
+ * TSB41BA3D. This phy has a bug at least in combination with the TI
+ * LLCs TSB82AA2B and TSB12LV26. The selfid coming from the locally
+ * connected phy is not propagated into the selfid buffer of the OHCI
+ * (see http://www.ti.com/litv/pdf/sllz059 for details).
+ * The main idea is to construct the selfid ourselves.
+ */
+
+static int find_and_insert_self_id(struct fw_ohci *ohci, int self_id_count)
+{
+ int reg;
+ int i;
+ int pos;
+ int status;
+ u32 self_id;
+
+/*
+ * preset bits in self_id
+ *
+ * link active: 0b1
+ * speed: 0b11
+ * bridge: 0b00
+ * contender: 0b1
+ * initiated reset: 0b0
+ * more packets: 0b0
+ */
+ self_id = 0x8040C800;
+
+ reg = reg_read(ohci, OHCI1394_NodeID);
+ if (!(reg & OHCI1394_NodeID_idValid)) {
+ fw_notify("node ID not valid, new bus reset in progress\n");
+ return -EBUSY;
+ }
+ self_id |= ((reg & 0x3f) << 24); /* phy ID */
+
+ mutex_lock(&ohci->phy_reg_mutex);
+ reg = read_phy_reg(ohci, 4);
+ mutex_unlock(&ohci->phy_reg_mutex);
+ if (reg < 0)
+ return reg;
+ self_id |= ((reg & 0x07) << 8); /* power class */
+
+ mutex_lock(&ohci->phy_reg_mutex);
+ reg = read_phy_reg(ohci, 1);
+ mutex_unlock(&ohci->phy_reg_mutex);
+ if (reg < 0)
+ return reg;
+ self_id |= ((reg & 0x3f) << 16); /* gap count */
+
+ for (i = 0; i < 3; i++) {
+ status = get_status_for_port(ohci, i);
+ if (status < 0)
+ return status;
+ self_id |= ((status & 0x3) << (6 - (i * 2)));
+ }
+
+ pos = get_self_id_pos(ohci, self_id, self_id_count);
+ if (pos >= 0) {
+ memmove(&(ohci->self_id_buffer[pos+1]),
+ &(ohci->self_id_buffer[pos]),
+ (self_id_count - pos) * sizeof(*ohci->self_id_buffer));
+ ohci->self_id_buffer[pos] = self_id;
+ self_id_count++;
+ }
+ return self_id_count;
+}
+
static void bus_reset_work(struct work_struct *work)
{
struct fw_ohci *ohci =
@@ -1755,10 +1873,12 @@ static void bus_reset_work(struct work_struct *work)
* bit extra to get the actual number of self IDs.
*/
self_id_count = (reg >> 3) & 0xff;
- if (self_id_count == 0 || self_id_count > 252) {
+
+ if (self_id_count > 252) {
fw_notify("inconsistent self IDs\n");
return;
}
+
generation = (cond_le32_to_cpu(ohci->self_id_cpu[0]) >> 16) & 0xff;
rmb();
@@ -1770,6 +1890,19 @@ static void bus_reset_work(struct work_struct *work)
ohci->self_id_buffer[j] =
cond_le32_to_cpu(ohci->self_id_cpu[i]);
}
+
+ if (ohci->quirks & QUIRK_TI_SLLZ059) {
+ self_id_count = find_and_insert_self_id(ohci, self_id_count);
+ if (self_id_count < 0) {
+ fw_notify("could not construct local self IDs\n");
+ return;
+ }
+ }
+
+ if (self_id_count == 0) {
+ fw_notify("inconsistent self IDs\n");
+ return;
+ }
rmb();
/*
@@ -2050,13 +2183,50 @@ static int configure_1394a_enhancements(struct fw_ohci *ohci)
return 0;
}
+#define TSB41BA3D_VID 0x00080028
+#define TSB41BA3D_PID 0x00833005
+
+static int probe_tsb41ba3d(struct fw_ohci *ohci)
+{
+ int reg;
+ int i;
+ int vendor_id;
+ int product_id;
+
+ reg = read_phy_reg(ohci, 2);
+ if (reg < 0)
+ return reg;
+
+ if ((reg & PHY_EXTENDED_REGISTERS) == PHY_EXTENDED_REGISTERS) {
+ vendor_id = 0;
+ for (i = 10; i < 13; i++) {
+ reg = read_paged_phy_reg(ohci, 1, i);
+ if (reg < 0)
+ return reg;
+ vendor_id = (vendor_id << 8) | reg;
+ }
+ product_id = 0;
+ for (i = 13; i < 16; i++) {
+ reg = read_paged_phy_reg(ohci, 1, i);
+ if (reg < 0)
+ return reg;
+ product_id = (product_id << 8) | reg;
+ }
+
+ if ((vendor_id == TSB41BA3D_VID) &&
+ (product_id == TSB41BA3D_PID))
+ return 1;
+ }
+ return 0;
+}
+
static int ohci_enable(struct fw_card *card,
const __be32 *config_rom, size_t length)
{
struct fw_ohci *ohci = fw_ohci(card);
struct pci_dev *dev = to_pci_dev(card->device);
u32 lps, seconds, version, irqs;
- int i, ret;
+ int i, ret, tsb41ba3d_found;
if (software_reset(ohci)) {
fw_error("Failed to reset ohci card.\n");
@@ -2087,6 +2257,17 @@ static int ohci_enable(struct fw_card *card,
return -EIO;
}
+ if (ohci->quirks & QUIRK_TI_SLLZ059) {
+ tsb41ba3d_found = probe_tsb41ba3d(ohci);
+ if (tsb41ba3d_found < 0)
+ return tsb41ba3d_found;
+ if (!tsb41ba3d_found) {
+ fw_notify("No TSB41BA3D found, "
+ "resetting QUIRK_TI_SLLZ059\n");
+ ohci->quirks &= ~QUIRK_TI_SLLZ059;
+ }
+ }
+
reg_write(ohci, OHCI1394_HCControlClear,
OHCI1394_HCControl_noByteSwapData);
OpenPOWER on IntegriCloud