USB host: Move AMD PLL quirk to pci-quirks.c
authorAndiry Xu <andiry.xu@amd.com>
Tue, 1 Mar 2011 06:57:05 +0000 (14:57 +0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 1 Mar 2011 21:01:45 +0000 (16:01 -0500)
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@amd.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: Alex He <alex.he@amd.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-pci.c
drivers/usb/host/ehci-sched.c
drivers/usb/host/ehci.h
drivers/usb/host/ohci-hcd.c
drivers/usb/host/ohci-pci.c
drivers/usb/host/ohci-q.c
drivers/usb/host/ohci.h
drivers/usb/host/pci-quirks.c
drivers/usb/host/pci-quirks.h

index f69305d3a9b81ae320ca7a15cf8f2f6e440d0ff1..e6277536f39289c950436851dc2fb1980bf94042 100644 (file)
@@ -114,13 +114,11 @@ 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 "ehci-dbg.c"
+#include "pci-quirks.h"
 
 /*-------------------------------------------------------------------------*/
 
@@ -532,10 +530,8 @@ 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 (ehci->amd_pll_fix == 1)
+               usb_amd_dev_put();
 
 #ifdef EHCI_STATS
        ehci_dbg (ehci, "irq normal %ld err %ld reclaim %ld (lost %ld)\n",
index 07bb982e59f654fdda2ca3ab51bf4560ec714e46..d5eaea7caf89ebe8987fb00bff32cce78ede29cc 100644 (file)
@@ -44,42 +44,6 @@ static int ehci_pci_reinit(struct ehci_hcd *ehci, struct pci_dev *pdev)
        return 0;
 }
 
-static int ehci_quirk_amd_hudson(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) {
-               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;
-               }
-       } else {
-               amd_smbus_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x780b, NULL);
-               if (!amd_smbus_dev)
-                       return 0;
-               pci_read_config_byte(amd_smbus_dev, PCI_REVISION_ID, &rev);
-               if (rev < 0x11 || rev > 0x18) {
-                       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);
-
-       ehci_info(ehci, "QUIRK: Enable exception for AMD Hudson ASPM\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)
 {
@@ -138,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_hudson(ehci))
-               ehci->amd_l1_fix = 1;
-
        retval = ehci_halt(ehci);
        if (retval)
                return retval;
@@ -191,6 +152,9 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
                }
                break;
        case PCI_VENDOR_ID_AMD:
+               /* AMD PLL quirk */
+               if (usb_amd_find_chipset_info())
+                       ehci->amd_pll_fix = 1;
                /* AMD8111 EHCI doesn't work, according to AMD errata */
                if (pdev->device == 0x7463) {
                        ehci_info(ehci, "ignoring AMD8111 (errata)\n");
@@ -236,6 +200,9 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
                }
                break;
        case PCI_VENDOR_ID_ATI:
+               /* AMD PLL quirk */
+               if (usb_amd_find_chipset_info())
+                       ehci->amd_pll_fix = 1;
                /* SB600 and old version of SB700 have a bug in EHCI controller,
                 * which causes usb devices lose response in some cases.
                 */
index 30fbdbe1cf1e5adfefe0b19c6c84aba3b856c01f..1543c838b3d108e8b1611f8c2b354e94a770c452 100644 (file)
@@ -1587,63 +1587,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 (
@@ -1672,8 +1615,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 (ehci->amd_pll_fix == 1)
+                       usb_amd_quirk_pll_disable();
        }
 
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
@@ -1801,8 +1744,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 (ehci->amd_pll_fix == 1)
+                       usb_amd_quirk_pll_enable();
        }
 
        if (unlikely(list_is_singular(&stream->td_list))) {
@@ -2092,8 +2035,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 (ehci->amd_pll_fix == 1)
+                       usb_amd_quirk_pll_disable();
        }
 
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
@@ -2197,8 +2140,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 (ehci->amd_pll_fix == 1)
+                       usb_amd_quirk_pll_enable();
        }
 
        if (list_is_singular(&stream->td_list)) {
index 799ac16a54b4cae3aff98d0f03b641cb8ddc2494..f86d3fa20214a1bcd5587f80f39e290e856852bd 100644 (file)
@@ -131,7 +131,7 @@ 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                amd_pll_fix:1;
        unsigned                fs_i_thresh:1;  /* Intel iso scheduling */
        unsigned                use_dummy_qh:1; /* AMD Frame List table quirk*/
 
index 759a12ff8048b64dec471ee18a1f2e4a937ae209..7b791bf1e7b48668c0b4dafef873ce694391e40a 100644 (file)
@@ -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;
@@ -912,7 +903,7 @@ 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();
+               usb_amd_dev_put();
 
        remove_debug_files (ohci);
        ohci_mem_cleanup (ohci);
index 36ee9a666e937af93cb27af0321b5b409cf49929..9816a2870d000afa91d2ad19b58d611f9732427c 100644 (file)
 #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,14 @@ 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);
+       if (usb_amd_find_chipset_info())
+               ohci->flags |= OHCI_QUIRK_AMD_PLL;
+
+       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 +169,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 +189,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;
index 83094d067e0f40bdf18370210e90cbd4d7b24ef8..dd24fc115e4892cc9649da9adae214a4fca7a6d9 100644 (file)
@@ -52,7 +52,7 @@ __acquires(ohci->lock)
                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);
+                               usb_amd_quirk_pll_enable();
                        if (quirk_amdprefetch(ohci))
                                sb800_prefetch(ohci, 0);
                }
@@ -686,7 +686,7 @@ static void td_submit_urb (
                }
                if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0) {
                        if (quirk_amdiso(ohci))
-                               quirk_amd_pll(0);
+                               usb_amd_quirk_pll_disable();
                        if (quirk_amdprefetch(ohci))
                                sb800_prefetch(ohci, 1);
                }
index 51facb985c847801ca3af267aac4887293e18ad3..bad11a72c2022acdd7294ed7f84081849d5ee01e 100644 (file)
@@ -401,7 +401,7 @@ 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_PLL      0x200                   /* AMD PLL quirk*/
 #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
@@ -433,7 +433,7 @@ static inline int quirk_zfmicro(struct ohci_hcd *ohci)
 }
 static inline int quirk_amdiso(struct ohci_hcd *ohci)
 {
-       return ohci->flags & OHCI_QUIRK_AMD_ISO;
+       return ohci->flags & OHCI_QUIRK_AMD_PLL;
 }
 static inline int quirk_amdprefetch(struct ohci_hcd *ohci)
 {
index 4c502c890ebd2a881d666a517949026e55b3591d..1d586d4f7b56d5e3a0c38cf793dfec8dbf2554af 100644 (file)
 #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);
+
+int 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.probe_result;
+       }
+
+       amd_chipset.smbus_dev = pci_get_device(PCI_VENDOR_ID_ATI, 0x4385, NULL);
+       if (amd_chipset.smbus_dev) {
+               rev = amd_chipset.smbus_dev->revision;
+               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 0;
+               }
+               rev = amd_chipset.smbus_dev->revision;
+               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 0;
+       }
+
+       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 amd_chipset.probe_result;
+}
+EXPORT_SYMBOL_GPL(usb_amd_find_chipset_info);
+
+/*
+ * 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 (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)
+{
+       usb_amd_quirk_pll(1);
+}
+EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable);
+
+void usb_amd_quirk_pll_enable(void)
+{
+       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
index 1564edfff6fe324174cd61dfccf75264056e2ce3..6ae9f78e993876c384f6540263f6935b8bfcdd4e 100644 (file)
@@ -1,7 +1,17 @@
 #ifndef __LINUX_USB_PCI_QUIRKS_H
 #define __LINUX_USB_PCI_QUIRKS_H
 
+#ifdef CONFIG_PCI
 void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
 int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
+int usb_amd_find_chipset_info(void);
+void usb_amd_dev_put(void);
+void usb_amd_quirk_pll_disable(void);
+void usb_amd_quirk_pll_enable(void);
+#else
+static inline void usb_amd_quirk_pll_disable(void) {}
+static inline void usb_amd_quirk_pll_enable(void) {}
+static inline void usb_amd_dev_put(void) {}
+#endif  /* CONFIG_PCI */
 
 #endif  /*  __LINUX_USB_PCI_QUIRKS_H  */