[PATCH v4 1/2] USB host: Move AMD PLL quirk to pci-quirks.c

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch moves the AMD PLL quirk code in OHCI/EHCI driver to pci-quirks.c,
and exports the functions to be used by xHCI driver later.

AMD PLL quirk disable the optional PM feature inside specific
SB700/SB800/Hudson-2/3 platforms under the following conditions:

1. If an isochronous device is connected to OHCI/EHCI/xHCI port and is active;
2. Optional PM feature that powers down the internal Bus PLL when the link is
   in low power state is enabled.

Without AMD PLL quirk, USB isochronous stream may stutter or have breaks
occasionally, which greatly impair the performance of audio/video streams.

Currently AMD PLL quirk is implemented in OHCI and EHCI driver, and will be
added to xHCI driver too. They are doing similar things actually, so move
the quirk code to pci-quirks.c, which has several advantages:

1. Remove duplicate defines and functions in OHCI/EHCI (and xHCI) driver and
   make them cleaner;
2. AMD chipset information will be probed only once and then stored.
   Currently they're probed during every OHCI/EHCI initialization, move
   the detect code to pci-quirks.c saves the repeat detect cost;
3. Build up synchronization among OHCI/EHCI/xHCI driver. In current
   code, every host controller enable/disable PLL only according to
   its own status, and may enable PLL while there is still isoc transfer on
   other HCs. Move the quirk to pci-quirks.c prevents this issue.

Signed-off-by: Andiry Xu <andiry.xu@xxxxxxx>
Cc: David Brownell <dbrownell@xxxxxxxxxxxxxxxxxxxxx>
Cc: Alex He <alex.he@xxxxxxx>
---
 drivers/usb/host/ehci-hcd.c   |   12 +-
 drivers/usb/host/ehci-pci.c   |   36 +-----
 drivers/usb/host/ehci-sched.c |   73 ++----------
 drivers/usb/host/ehci.h       |    1 -
 drivers/usb/host/ohci-hcd.c   |   19 +--
 drivers/usb/host/ohci-pci.c   |  109 +---------------
 drivers/usb/host/ohci-q.c     |    8 +-
 drivers/usb/host/ohci.h       |    9 --
 drivers/usb/host/pci-quirks.c |  271 +++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/pci-quirks.h |    5 +
 10 files changed, 311 insertions(+), 232 deletions(-)

diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 6fee3cd..5655832 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -114,12 +114,10 @@ MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n");
 
 #define	INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT)
 
-/* for ASPM quirk of ISOC on AMD SB800 */
-static struct pci_dev *amd_nb_dev;
-
 /*-------------------------------------------------------------------------*/
 
 #include "ehci.h"
+#include "pci-quirks.h"
 #include "ehci-dbg.c"
 
 /*-------------------------------------------------------------------------*/
@@ -507,6 +505,7 @@ static void ehci_work (struct ehci_hcd *ehci)
 static void ehci_stop (struct usb_hcd *hcd)
 {
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
+	struct pci_dev		*pdev = to_pci_dev(hcd->self.controller);
 
 	ehci_dbg (ehci, "stop\n");
 
@@ -532,10 +531,9 @@ static void ehci_stop (struct usb_hcd *hcd)
 	spin_unlock_irq (&ehci->lock);
 	ehci_mem_cleanup (ehci);
 
-	if (amd_nb_dev) {
-		pci_dev_put(amd_nb_dev);
-		amd_nb_dev = NULL;
-	}
+	if (pdev->vendor == PCI_VENDOR_ID_ATI ||
+	    pdev->vendor == PCI_VENDOR_ID_AMD)
+		usb_amd_dev_put();
 
 #ifdef	EHCI_STATS
 	ehci_dbg (ehci, "irq normal %ld err %ld reclaim %ld (lost %ld)\n",
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c
index 76179c3..21e4438 100644
--- a/drivers/usb/host/ehci-pci.c
+++ b/drivers/usb/host/ehci-pci.c
@@ -44,35 +44,6 @@ static int ehci_pci_reinit(struct ehci_hcd *ehci, struct pci_dev *pdev)
 	return 0;
 }
 
-static int ehci_quirk_amd_SB800(struct ehci_hcd *ehci)
-{
-	struct pci_dev *amd_smbus_dev;
-	u8 rev = 0;
-
-	amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, 0x4385, NULL);
-	if (!amd_smbus_dev)
-		return 0;
-
-	pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID, &rev);
-	if (rev < 0x40) {
-		pci_dev_put(amd_smbus_dev);
-		amd_smbus_dev = NULL;
-		return 0;
-	}
-
-	if (!amd_nb_dev)
-		amd_nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x1510, NULL);
-	if (!amd_nb_dev)
-		ehci_err(ehci, "QUIRK: unable to get AMD NB device\n");
-
-	ehci_info(ehci, "QUIRK: Enable AMD SB800 L1 fix\n");
-
-	pci_dev_put(amd_smbus_dev);
-	amd_smbus_dev = NULL;
-
-	return 1;
-}
-
 /* called during probe() after chip reset completes */
 static int ehci_pci_setup(struct usb_hcd *hcd)
 {
@@ -131,9 +102,6 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
 	/* cache this readonly data; minimize chip reads */
 	ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
 
-	if (ehci_quirk_amd_SB800(ehci))
-		ehci->amd_l1_fix = 1;
-
 	retval = ehci_halt(ehci);
 	if (retval)
 		return retval;
@@ -184,6 +152,8 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
 		}
 		break;
 	case PCI_VENDOR_ID_AMD:
+		/* AMD PLL quirk */
+		usb_amd_find_chipset_info();
 		/* AMD8111 EHCI doesn't work, according to AMD errata */
 		if (pdev->device == 0x7463) {
 			ehci_info(ehci, "ignoring AMD8111 (errata)\n");
@@ -229,6 +199,8 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
 		}
 		break;
 	case PCI_VENDOR_ID_ATI:
+		/* AMD PLL quirk */
+		usb_amd_find_chipset_info();
 		/* SB600 and old version of SB700 have a bug in EHCI controller,
 		 * which causes usb devices lose response in some cases.
 		 */
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index aa46f57..501e134 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -1590,63 +1590,6 @@ itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd)
 	*hw_p = cpu_to_hc32(ehci, itd->itd_dma | Q_TYPE_ITD);
 }
 
-#define AB_REG_BAR_LOW 0xe0
-#define AB_REG_BAR_HIGH 0xe1
-#define AB_INDX(addr) ((addr) + 0x00)
-#define AB_DATA(addr) ((addr) + 0x04)
-#define NB_PCIE_INDX_ADDR 0xe0
-#define NB_PCIE_INDX_DATA 0xe4
-#define NB_PIF0_PWRDOWN_0 0x01100012
-#define NB_PIF0_PWRDOWN_1 0x01100013
-
-static void ehci_quirk_amd_L1(struct ehci_hcd *ehci, int disable)
-{
-	u32 addr, addr_low, addr_high, val;
-
-	outb_p(AB_REG_BAR_LOW, 0xcd6);
-	addr_low = inb_p(0xcd7);
-	outb_p(AB_REG_BAR_HIGH, 0xcd6);
-	addr_high = inb_p(0xcd7);
-	addr = addr_high << 8 | addr_low;
-	outl_p(0x30, AB_INDX(addr));
-	outl_p(0x40, AB_DATA(addr));
-	outl_p(0x34, AB_INDX(addr));
-	val = inl_p(AB_DATA(addr));
-
-	if (disable) {
-		val &= ~0x8;
-		val |= (1 << 4) | (1 << 9);
-	} else {
-		val |= 0x8;
-		val &= ~((1 << 4) | (1 << 9));
-	}
-	outl_p(val, AB_DATA(addr));
-
-	if (amd_nb_dev) {
-		addr = NB_PIF0_PWRDOWN_0;
-		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR, addr);
-		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, &val);
-		if (disable)
-			val &= ~(0x3f << 7);
-		else
-			val |= 0x3f << 7;
-
-		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, val);
-
-		addr = NB_PIF0_PWRDOWN_1;
-		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR, addr);
-		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, &val);
-		if (disable)
-			val &= ~(0x3f << 7);
-		else
-			val |= 0x3f << 7;
-
-		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, val);
-	}
-
-	return;
-}
-
 /* fit urb's itds into the selected schedule slot; activate as needed */
 static int
 itd_link_urb (
@@ -1675,8 +1618,8 @@ itd_link_urb (
 	}
 
 	if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
-		if (ehci->amd_l1_fix == 1)
-			ehci_quirk_amd_L1(ehci, 1);
+		if (usb_amd_need_pll_quirk())
+			usb_amd_quirk_pll_disable();
 	}
 
 	ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
@@ -1804,8 +1747,8 @@ itd_complete (
 	ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
 
 	if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
-		if (ehci->amd_l1_fix == 1)
-			ehci_quirk_amd_L1(ehci, 0);
+		if (usb_amd_need_pll_quirk())
+			usb_amd_quirk_pll_enable();
 	}
 
 	if (unlikely(list_is_singular(&stream->td_list))) {
@@ -2095,8 +2038,8 @@ sitd_link_urb (
 	}
 
 	if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
-		if (ehci->amd_l1_fix == 1)
-			ehci_quirk_amd_L1(ehci, 1);
+		if (usb_amd_need_pll_quirk())
+			usb_amd_quirk_pll_disable();
 	}
 
 	ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
@@ -2200,8 +2143,8 @@ sitd_complete (
 	ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
 
 	if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
-		if (ehci->amd_l1_fix == 1)
-			ehci_quirk_amd_L1(ehci, 0);
+		if (usb_amd_need_pll_quirk())
+			usb_amd_quirk_pll_enable();
 	}
 
 	if (list_is_singular(&stream->td_list)) {
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 799ac16..ba8eab3 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -131,7 +131,6 @@ struct ehci_hcd {			/* one per controller */
 	unsigned		has_amcc_usb23:1;
 	unsigned		need_io_watchdog:1;
 	unsigned		broken_periodic:1;
-	unsigned		amd_l1_fix:1;
 	unsigned		fs_i_thresh:1;	/* Intel iso scheduling */
 	unsigned		use_dummy_qh:1;	/* AMD Frame List table quirk*/
 
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 759a12f..b8c4f29 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -75,6 +75,7 @@ static const char	hcd_name [] = "ohci_hcd";
 #define	STATECHANGE_DELAY	msecs_to_jiffies(300)
 
 #include "ohci.h"
+#include "pci-quirks.h"
 
 static void ohci_dump (struct ohci_hcd *ohci, int verbose);
 static int ohci_init (struct ohci_hcd *ohci);
@@ -85,18 +86,8 @@ static int ohci_restart (struct ohci_hcd *ohci);
 #endif
 
 #ifdef CONFIG_PCI
-static void quirk_amd_pll(int state);
-static void amd_iso_dev_put(void);
 static void sb800_prefetch(struct ohci_hcd *ohci, int on);
 #else
-static inline void quirk_amd_pll(int state)
-{
-	return;
-}
-static inline void amd_iso_dev_put(void)
-{
-	return;
-}
 static inline void sb800_prefetch(struct ohci_hcd *ohci, int on)
 {
 	return;
@@ -898,6 +889,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
 static void ohci_stop (struct usb_hcd *hcd)
 {
 	struct ohci_hcd		*ohci = hcd_to_ohci (hcd);
+	struct pci_dev		*pdev = to_pci_dev(hcd->self.controller);
 
 	ohci_dump (ohci, 1);
 
@@ -911,8 +903,11 @@ static void ohci_stop (struct usb_hcd *hcd)
 
 	if (quirk_zfmicro(ohci))
 		del_timer(&ohci->unlink_watchdog);
-	if (quirk_amdiso(ohci))
-		amd_iso_dev_put();
+	if (pdev->vendor == PCI_VENDOR_ID_ATI &&
+		(pdev->device == 0x4397 ||
+		 pdev->device == 0x4398 ||
+		 pdev->device == 0x4399))
+		usb_amd_dev_put();
 
 	remove_debug_files (ohci);
 	ohci_mem_cleanup (ohci);
diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c
index 36ee9a6..aa19e6b 100644
--- a/drivers/usb/host/ohci-pci.c
+++ b/drivers/usb/host/ohci-pci.c
@@ -22,24 +22,6 @@
 #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 broken_suspend(struct usb_hcd *hcd)
@@ -168,11 +150,13 @@ static int ohci_quirk_nec(struct usb_hcd *hcd)
 static int ohci_quirk_amd700(struct usb_hcd *hcd)
 {
 	struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+	struct pci_dev *amd_smbus_dev;
 	u8 rev = 0;
 
-	if (!amd_smbus_dev)
-		amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
-				PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
+	usb_amd_find_chipset_info();
+
+	amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI,
+			PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL);
 	if (!amd_smbus_dev)
 		return 0;
 
@@ -184,19 +168,8 @@ static int ohci_quirk_amd700(struct usb_hcd *hcd)
 		ohci_dbg(ohci, "enabled AMD prefetch quirk\n");
 	}
 
-	if ((rev > 0x3b) || (rev < 0x30)) {
-		pci_dev_put(amd_smbus_dev);
-		amd_smbus_dev = NULL;
-		return 0;
-	}
-
-	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");
+	pci_dev_put(amd_smbus_dev);
+	amd_smbus_dev = NULL;
 
 	return 0;
 }
@@ -215,74 +188,6 @@ static int ohci_quirk_nvidia_shutdown(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, that stream may stutter
- * or have breaks occasionally.  For transfers going to speakers, this
- * makes a very audible mess...
- *
- * That 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 that 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) << 4) | ((!bit) << 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 << 12);
-		val |= ((!bit) << 4) | ((!bit) << 9);
-		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;
-		}
-	}
-
-}
-
 static void sb800_prefetch(struct ohci_hcd *ohci, int on)
 {
 	struct pci_dev *pdev;
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index 83094d0..b03c9a8 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -51,8 +51,8 @@ __acquires(ohci->lock)
 	case PIPE_ISOCHRONOUS:
 		ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs--;
 		if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0) {
-			if (quirk_amdiso(ohci))
-				quirk_amd_pll(1);
+			if (usb_amd_need_pll_quirk())
+				usb_amd_quirk_pll_enable();
 			if (quirk_amdprefetch(ohci))
 				sb800_prefetch(ohci, 0);
 		}
@@ -685,8 +685,8 @@ static void td_submit_urb (
 				urb->iso_frame_desc [cnt].length, urb, cnt);
 		}
 		if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0) {
-			if (quirk_amdiso(ohci))
-				quirk_amd_pll(0);
+			if (usb_amd_need_pll_quirk())
+				usb_amd_quirk_pll_disable();
 			if (quirk_amdprefetch(ohci))
 				sb800_prefetch(ohci, 1);
 		}
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 51facb9..7a43380 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -401,7 +401,6 @@ struct ohci_hcd {
 #define	OHCI_QUIRK_NEC		0x40			/* lost interrupts */
 #define	OHCI_QUIRK_FRAME_NO	0x80			/* no big endian frame_no shift */
 #define	OHCI_QUIRK_HUB_POWER	0x100			/* distrust firmware power/oc setup */
-#define	OHCI_QUIRK_AMD_ISO	0x200			/* ISO transfers*/
 #define	OHCI_QUIRK_AMD_PREFETCH	0x400			/* pre-fetch for ISO transfer */
 #define	OHCI_QUIRK_SHUTDOWN	0x800			/* nVidia power bug */
 	// there are also chip quirks/bugs in init logic
@@ -431,10 +430,6 @@ static inline int quirk_zfmicro(struct ohci_hcd *ohci)
 {
 	return ohci->flags & OHCI_QUIRK_ZFMICRO;
 }
-static inline int quirk_amdiso(struct ohci_hcd *ohci)
-{
-	return ohci->flags & OHCI_QUIRK_AMD_ISO;
-}
 static inline int quirk_amdprefetch(struct ohci_hcd *ohci)
 {
 	return ohci->flags & OHCI_QUIRK_AMD_PREFETCH;
@@ -448,10 +443,6 @@ static inline int quirk_zfmicro(struct ohci_hcd *ohci)
 {
 	return 0;
 }
-static inline int quirk_amdiso(struct ohci_hcd *ohci)
-{
-	return 0;
-}
 static inline int quirk_amdprefetch(struct ohci_hcd *ohci)
 {
 	return 0;
diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index 4c502c8..99dc93a 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -52,6 +52,277 @@
 #define EHCI_USBLEGCTLSTS	4		/* legacy control/status */
 #define EHCI_USBLEGCTLSTS_SOOE	(1 << 13)	/* SMI on ownership change */
 
+/* AMD quirk use */
+#define	AB_REG_BAR_LOW		0xe0
+#define	AB_REG_BAR_HIGH		0xe1
+#define	AB_REG_BAR_SB700	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
+#define	NB_PIF0_PWRDOWN_0	0x01100012
+#define	NB_PIF0_PWRDOWN_1	0x01100013
+
+static struct amd_chipset_info {
+	struct pci_dev	*nb_dev;
+	struct pci_dev	*smbus_dev;
+	int nb_type;
+	int sb_type;
+	int isoc_reqs;
+	int probe_count;
+	int probe_result;
+} amd_chipset;
+
+static DEFINE_SPINLOCK(amd_lock);
+
+void usb_amd_find_chipset_info(void)
+{
+	u8 rev = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd_lock, flags);
+
+	amd_chipset.probe_count++;
+	/* probe only once */
+	if (amd_chipset.probe_count > 1) {
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	amd_chipset.smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, 0x4385, NULL);
+	if (amd_chipset.smbus_dev) {
+		pci_read_config_byte(amd_chipset.smbus_dev,
+					PCI_REVISION_ID, &rev);
+		if (rev >= 0x40)
+			amd_chipset.sb_type = 1;
+		else if (rev >= 0x30 && rev <= 0x3b)
+			amd_chipset.sb_type = 3;
+	} else {
+		amd_chipset.smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD,
+							0x780b, NULL);
+		if (!amd_chipset.smbus_dev) {
+			spin_unlock_irqrestore(&amd_lock, flags);
+			return;
+		}
+		pci_read_config_byte(amd_chipset.smbus_dev,
+					PCI_REVISION_ID, &rev);
+		if (rev >= 0x11 && rev <= 0x18)
+			amd_chipset.sb_type = 2;
+	}
+
+	if (amd_chipset.sb_type == 0) {
+		if (amd_chipset.smbus_dev) {
+			pci_dev_put(amd_chipset.smbus_dev);
+			amd_chipset.smbus_dev = NULL;
+		}
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	amd_chipset.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x9601, NULL);
+	if (amd_chipset.nb_dev) {
+		amd_chipset.nb_type = 1;
+	} else {
+		amd_chipset.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD,
+							0x1510, NULL);
+		if (amd_chipset.nb_dev) {
+			amd_chipset.nb_type = 2;
+		} else  {
+			amd_chipset.nb_dev = pci_get_device(PCI_VENDOR_ID_AMD,
+								0x9600, NULL);
+			if (amd_chipset.nb_dev)
+				amd_chipset.nb_type = 3;
+		}
+	}
+
+	amd_chipset.probe_result = 1;
+	printk(KERN_DEBUG "QUIRK: Enable AMD PLL fix\n");
+
+	spin_unlock_irqrestore(&amd_lock, flags);
+	return;
+}
+EXPORT_SYMBOL_GPL(usb_amd_find_chipset_info);
+
+inline int usb_amd_need_pll_quirk(void)
+{
+	return amd_chipset.probe_result;
+}
+EXPORT_SYMBOL_GPL(usb_amd_need_pll_quirk);
+
+/*
+ * The hardware normally enables the A-link power management feature, which
+ * lets the system lower the power consumption in idle states.
+ *
+ * This USB quirk prevents the link going into that lower power state
+ * during isochronous transfers.
+ *
+ * Without this quirk, isochronous stream on OHCI/EHCI/xHCI controllers of
+ * some AMD platforms may stutter or have breaks occasionally.
+ */
+static void usb_amd_quirk_pll(int disable)
+{
+	u32 addr, addr_low, addr_high, val;
+	u32 bit = disable ? 0 : 1;
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd_lock, flags);
+
+	if (!amd_chipset.probe_result) {
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	if (disable) {
+		amd_chipset.isoc_reqs++;
+		if (amd_chipset.isoc_reqs > 1) {
+			spin_unlock_irqrestore(&amd_lock, flags);
+			return;
+		}
+	} else {
+		amd_chipset.isoc_reqs--;
+		if (amd_chipset.isoc_reqs > 0) {
+			spin_unlock_irqrestore(&amd_lock, flags);
+			return;
+		}
+	}
+
+	if (amd_chipset.sb_type == 1 || amd_chipset.sb_type == 2) {
+		outb_p(AB_REG_BAR_LOW, 0xcd6);
+		addr_low = inb_p(0xcd7);
+		outb_p(AB_REG_BAR_HIGH, 0xcd6);
+		addr_high = inb_p(0xcd7);
+		addr = addr_high << 8 | addr_low;
+
+		outl_p(0x30, AB_INDX(addr));
+		outl_p(0x40, AB_DATA(addr));
+		outl_p(0x34, AB_INDX(addr));
+		val = inl_p(AB_DATA(addr));
+	} else if (amd_chipset.sb_type == 3) {
+		pci_read_config_dword(amd_chipset.smbus_dev,
+					AB_REG_BAR_SB700, &addr);
+		outl(AX_INDXC, AB_INDX(addr));
+		outl(0x40, AB_DATA(addr));
+		outl(AX_DATAC, AB_INDX(addr));
+		val = inl(AB_DATA(addr));
+	} else {
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	if (disable) {
+		val &= ~0x08;
+		val |= (1 << 4) | (1 << 9);
+	} else {
+		val |= 0x08;
+		val &= ~((1 << 4) | (1 << 9));
+	}
+	outl_p(val, AB_DATA(addr));
+
+	if (!amd_chipset.nb_dev) {
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	if (amd_chipset.nb_type == 1 || amd_chipset.nb_type == 3) {
+		addr = PCIE_P_CNTL;
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_ADDR, addr);
+		pci_read_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, &val);
+
+		val &= ~(1 | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 12));
+		val |= bit | (bit << 3) | (bit << 12);
+		val |= ((!bit) << 4) | ((!bit) << 9);
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, val);
+
+		addr = BIF_NB;
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_ADDR, addr);
+		pci_read_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, &val);
+		val &= ~(1 << 8);
+		val |= bit << 8;
+
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, val);
+	} else if (amd_chipset.nb_type == 2) {
+		addr = NB_PIF0_PWRDOWN_0;
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_ADDR, addr);
+		pci_read_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, &val);
+		if (disable)
+			val &= ~(0x3f << 7);
+		else
+			val |= 0x3f << 7;
+
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, val);
+
+		addr = NB_PIF0_PWRDOWN_1;
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_ADDR, addr);
+		pci_read_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, &val);
+		if (disable)
+			val &= ~(0x3f << 7);
+		else
+			val |= 0x3f << 7;
+
+		pci_write_config_dword(amd_chipset.nb_dev,
+					NB_PCIE_INDX_DATA, val);
+	}
+
+	spin_unlock_irqrestore(&amd_lock, flags);
+	return;
+}
+
+void usb_amd_quirk_pll_disable(void)
+{
+	return usb_amd_quirk_pll(1);
+}
+EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable);
+
+void usb_amd_quirk_pll_enable(void)
+{
+	return usb_amd_quirk_pll(0);
+}
+EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_enable);
+
+void usb_amd_dev_put(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&amd_lock, flags);
+
+	amd_chipset.probe_count--;
+	if (amd_chipset.probe_count > 0) {
+		spin_unlock_irqrestore(&amd_lock, flags);
+		return;
+	}
+
+	if (amd_chipset.nb_dev) {
+		pci_dev_put(amd_chipset.nb_dev);
+		amd_chipset.nb_dev = NULL;
+	}
+	if (amd_chipset.smbus_dev) {
+		pci_dev_put(amd_chipset.smbus_dev);
+		amd_chipset.smbus_dev = NULL;
+	}
+	amd_chipset.nb_type = 0;
+	amd_chipset.sb_type = 0;
+	amd_chipset.isoc_reqs = 0;
+	amd_chipset.probe_result = 0;
+
+	spin_unlock_irqrestore(&amd_lock, flags);
+}
+EXPORT_SYMBOL_GPL(usb_amd_dev_put);
 
 /*
  * Make sure the controller is completely inactive, unable to
diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h
index 1564edf..272963b 100644
--- a/drivers/usb/host/pci-quirks.h
+++ b/drivers/usb/host/pci-quirks.h
@@ -3,5 +3,10 @@
 
 void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
 int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+void usb_amd_find_chipset_info(void);
+inline int usb_amd_need_pll_quirk(void);
+void usb_amd_quirk_pll_disable(void);
+void usb_amd_quirk_pll_enable(void);
+void usb_amd_dev_put(void);
 
 #endif  /*  __LINUX_USB_PCI_QUIRKS_H  */
-- 
1.7.1



--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux