Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > fc11cd6e1c513a17304da94a5390f3cd > files > 3809

kernel-2.6.18-194.11.1.el5.src.rpm

From: Pete Zaitcev <zaitcev@redhat.com>
Date: Tue, 26 Aug 2008 21:18:43 -0600
Subject: [usb] work around ISO transfers in SB700
Message-id: 20080826211843.54f486aa.zaitcev@redhat.com
O-Subject: [RHEL 5.3 bz457723] Work around ISO transfers in SB700
Bugzilla: 457723
RH-Acked-by: Brian Maly <bmaly@redhat.com>
RH-Acked-by: John Feeney <jfeeney@redhat.com>

The SB700 is a gift that never stops giving. This time, hardware fails
to notice running ISO transfers from OHCI (e.g. for any kind of USB sound)
and powers down the bus. Fortunately, it resumes almost immediately, but
that produces a distortion.

AMD provided the original workaround, and backported it to RHEL 5.
I verified that it's safe (but reviewers would do well to verify it again).
The relationship between the instance of OHCI and the rest of SB700
looks terribly implicit, but I think it's correct (e.g. we won't try
to apply this workaround to hardware which does not support it, or
when OHCI is on an unrelated add-on card).

AMD tested a Brew-built kernel I gave them.

Please ack.

-- Pete

diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 94d8cf4..72a6902 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -135,6 +135,20 @@ static int ohci_init (struct ohci_hcd *ohci);
 static void ohci_stop (struct usb_hcd *hcd);
 static int ohci_reboot (struct notifier_block *, unsigned long , void *);
 
+#ifdef CONFIG_PCI
+static void quirk_amd_pll(int state);
+static void amd_iso_dev_put(void);
+#else
+static inline void quirk_amd_pll(int state)
+{
+	return;
+}
+static inline void amd_iso_dev_put(void)
+{
+	return;
+}
+#endif
+
 #include "ohci-hub.c"
 #include "ohci-dbg.c"
 #include "ohci-mem.c"
@@ -775,7 +789,10 @@ static void ohci_stop (struct usb_hcd *hcd)
 
 	ohci_usb_reset (ohci);
 	ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
-	
+
+	if (ohci->flags & OHCI_QUIRK_AMD_ISO)
+		amd_iso_dev_put();
+
 	remove_debug_files (ohci);
 	unregister_reboot_notifier (&ohci->reboot_notifier);
 	ohci_mem_cleanup (ohci);
diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c
index b268537..1ebc2ce 100644
--- a/drivers/usb/host/ohci-pci.c
+++ b/drivers/usb/host/ohci-pci.c
@@ -18,6 +18,27 @@
 #error "This file is PCI bus glue.  CONFIG_PCI must be defined."
 #endif
 
+#include <linux/pci.h>
+#include <linux/io.h>
+
+/* constants used to work around PM-related transfer
+ * glitches in some AMD 700 series southbridges
+ */
+#define        AB_REG_BAR      0xf0
+#define        AB_INDX(addr)   ((addr) + 0x00)
+#define        AB_DATA(addr)   ((addr) + 0x04)
+#define        AX_INDXC        0X30
+#define        AX_DATAC        0x34
+
+#define        NB_PCIE_INDX_ADDR       0xe0
+#define        NB_PCIE_INDX_DATA       0xe4
+#define        PCIE_P_CNTL             0x10040
+#define        BIF_NB                  0x10002
+
+static struct pci_dev *amd_smbus_dev;
+static struct pci_dev *amd_hb_dev;
+static int amd_ohci_iso_count;
+
 /*-------------------------------------------------------------------------*/
 
 static int
@@ -29,6 +50,34 @@ ohci_pci_reset (struct usb_hcd *hcd)
 	return ohci_init (ohci);
 }
 
+static void __devinit
+ohci_quirk_amd700(struct ohci_hcd *ohci)
+{
+	u8 rev = 0;
+
+	if (!amd_smbus_dev)
+		amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
+				PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
+
+	if (!amd_smbus_dev)
+		return;
+
+	pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID, &rev);
+	if ((rev > 0x3b) || (rev < 0x30)) {
+		pci_dev_put(amd_smbus_dev);
+		amd_smbus_dev = NULL;
+		return;
+	}
+
+	amd_ohci_iso_count++;
+
+	if (!amd_hb_dev)
+		amd_hb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x9600, NULL);
+
+	ohci->flags |= OHCI_QUIRK_AMD_ISO;
+	ohci_dbg(ohci, "enabled AMD ISO transfers quirk\n");
+}
+
 static int __devinit
 ohci_pci_start (struct usb_hcd *hcd)
 {
@@ -93,6 +142,13 @@ ohci_pci_start (struct usb_hcd *hcd)
 				"enabled Compaq ZFMicro chipset quirk\n");
 		}
 
+		else if (pdev->vendor == PCI_VENDOR_ID_ATI
+				&& (pdev->device == 0x4397
+				|| pdev->device == 0x4398
+				|| pdev->device == 0x4399)) {
+			ohci_quirk_amd700(ohci);
+		}
+
 		/* RWC may not be set for add-in PCI cards, since boot
 		 * firmware probably ignored them.  This transfers PCI
 		 * PM wakeup capabilities (once the PCI layer is fixed).
@@ -112,6 +168,73 @@ ohci_pci_start (struct usb_hcd *hcd)
 	return 0;
 }
 
+/*
+ * The hardware normally enables the A-link power management feature
+ * which lets the system lower the power consumption in idle states.
+ *
+ * Assume the system is configured to have USB 1.1 ISO transfers going
+ * to or from a USB device. Without this quirk, the stream may stutter
+ * or have breaks occasionally. For transfers going to speakers, this
+ * makes a very audible mess.
+ *
+ * The audio playback corruption is due to the audio stream getting
+ * interrupted occasionally when the link goes in lower power state.
+ * This USB quirk prevents the link going into lower power state
+ * during audio playback or other ISO operations.
+ */
+static void quirk_amd_pll(int on)
+{
+	u32 addr;
+	u32 val;
+	u32 bit = on > 0?1:0;
+
+	pci_read_config_dword(amd_smbus_dev, AB_REG_BAR, &addr);
+
+	/* BIT names/meanings are NDA-protected, sorry... */
+
+	outl(AX_INDXC, AB_INDX(addr));
+	outl(0x40, AB_DATA(addr));
+	outl(AX_DATAC, AB_INDX(addr));
+	val = inl(AB_DATA(addr));
+	val &= ~((1<<3)|(1<<4)|(1<<9));
+	val |= (bit<<3)|((bit?0:1)<<4)|((bit?0:1)<<9);
+	outl(val, AB_DATA(addr));
+
+	if (amd_hb_dev) {
+		addr = PCIE_P_CNTL;
+		pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_ADDR, addr);
+
+		pci_read_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, &val);
+		val &= ~(1|(1<<3)|(1<<4)|(1<<9)|(1<<12));
+		val |= bit|(bit<<3)|((bit?0:1)<<4)|((bit?0:1)<<9);
+		val |= bit<<12;
+		pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, val);
+
+		addr = BIF_NB;
+		pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_ADDR, addr);
+
+		pci_read_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, &val);
+		val &= ~(1<<8);
+		val |= bit<<8;
+		pci_write_config_dword(amd_hb_dev, NB_PCIE_INDX_DATA, val);
+	}
+}
+
+static void amd_iso_dev_put(void)
+{
+	amd_ohci_iso_count--;
+	if (amd_ohci_iso_count == 0) {
+		if (amd_smbus_dev) {
+			pci_dev_put(amd_smbus_dev);
+			amd_smbus_dev = NULL;
+		}
+		if (amd_hb_dev) {
+			pci_dev_put(amd_hb_dev);
+			amd_hb_dev = NULL;
+		}
+	}
+}
+
 #ifdef	CONFIG_PM
 
 static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message)
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index e372306..2797ede 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -61,6 +61,9 @@ __acquires(ohci->lock)
 	switch (usb_pipetype (urb->pipe)) {
 	case PIPE_ISOCHRONOUS:
 		ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs--;
+		if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0)
+			if (ohci->flags & OHCI_QUIRK_AMD_ISO)
+				quirk_amd_pll(1);
 		break;
 	case PIPE_INTERRUPT:
 		ohci_to_hcd(ohci)->self.bandwidth_int_reqs--;
@@ -689,6 +692,9 @@ static void td_submit_urb (
 				data + urb->iso_frame_desc [cnt].offset,
 				urb->iso_frame_desc [cnt].length, urb, cnt);
 		}
+		if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0)
+			if (ohci->flags & OHCI_QUIRK_AMD_ISO)
+				quirk_amd_pll(0);
 		periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0
 			&& ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0;
 		break;
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index caacf14..2c39785 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -397,6 +397,7 @@ struct ohci_hcd {
 #define	OHCI_QUIRK_INITRESET	0x04			/* SiS, OPTi, ... */
 #define	OHCI_BIG_ENDIAN		0x08			/* big endian HC */
 #define	OHCI_QUIRK_ZFMICRO	0x10			/* Compaq ZFMicro chipset*/
+#define	OHCI_QUIRK_AMD_ISO	0x200			/* ISO transfers */
 	// there are also chip quirks/bugs in init logic
 
 };