]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
USB: EHCI: ASPM quirk of ISOC on AMD SB800
authorAlex He <alex.he@amd.com>
Tue, 7 Dec 2010 02:10:08 +0000 (10:10 +0800)
committerAK <andi@firstfloor.org>
Thu, 31 Mar 2011 18:57:37 +0000 (11:57 -0700)
commit 05570297ecbe834b1756b522412b68eaffb9ab11 upstream.

When ASPM PM Feature is enabled on UMI link, devices that use ISOC stream of
data transfer may be exposed to longer latency causing less than optimal per-
formance of the device. The longer latencies are normal and are due to link
wake time coming out of low power state which happens frequently to save
power when the link is not active.
The following code will make exception for certain features of ASPM to be by
passed and keep the logic normal state only when the ISOC device is connected
and active. This change will allow the device to run at optimal performance
yet minimize the impact on overall power savings.

Signed-off-by: Alex He <alex.he@amd.com>
Acked-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Andi Kleen <ak@linux.intel.com>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-pci.c
drivers/usb/host/ehci-sched.c
drivers/usb/host/ehci.h

index b14be9cd6f790779a46f5daed22b6bd7333a34b9..626731269293e308a43325ee0aaa346c8f923c42 100644 (file)
@@ -102,6 +102,9 @@ MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications");
 
 #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"
@@ -501,6 +504,11 @@ 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;
+       }
+
 #ifdef EHCI_STATS
        ehci_dbg (ehci, "irq normal %ld err %ld reclaim %ld (lost %ld)\n",
                ehci->stats.normal, ehci->stats.error, ehci->stats.reclaim,
index 19f5070d5c486c19b346816a7fa1b3d3930b97b0..1851c09d2e54a621b47ac5f159692aff9ceb30c4 100644 (file)
@@ -41,6 +41,35 @@ 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)
 {
@@ -99,6 +128,9 @@ 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;
index 93f58e52503bb8e69f0d3167a91c02c81c2b5857..a530856ee1ba943bfce2c43c3810f758956900cf 100644 (file)
@@ -1586,6 +1586,63 @@ 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 (
@@ -1613,6 +1670,12 @@ itd_link_urb (
                        next_uframe >> 3, next_uframe & 0x7);
                stream->start = jiffies;
        }
+
+       if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
+               if (ehci->amd_l1_fix == 1)
+                       ehci_quirk_amd_L1(ehci, 1);
+       }
+
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
 
        /* fill iTDs uframe by uframe */
@@ -1739,6 +1802,11 @@ itd_complete (
        (void) disable_periodic(ehci);
        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 (unlikely(list_is_singular(&stream->td_list))) {
                ehci_to_hcd(ehci)->self.bandwidth_allocated
                                -= stream->bandwidth;
@@ -2026,6 +2094,12 @@ sitd_link_urb (
                        stream->interval, hc32_to_cpu(ehci, stream->splits));
                stream->start = jiffies;
        }
+
+       if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
+               if (ehci->amd_l1_fix == 1)
+                       ehci_quirk_amd_L1(ehci, 1);
+       }
+
        ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
 
        /* fill sITDs frame by frame */
@@ -2128,6 +2202,11 @@ sitd_complete (
        (void) disable_periodic(ehci);
        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 (list_is_singular(&stream->td_list)) {
                ehci_to_hcd(ehci)->self.bandwidth_allocated
                                -= stream->bandwidth;
index 8b3d9c8f33ef6205509dcc1a21c498ba4e81edb2..c702e4b9f2eece4b6ea8124eed0f7ab3b14b1904 100644 (file)
@@ -131,6 +131,7 @@ struct ehci_hcd {                   /* one per controller */
        unsigned                need_io_watchdog:1;
        unsigned                broken_periodic:1;
        unsigned                fs_i_thresh:1;  /* Intel iso scheduling */
+       unsigned                amd_l1_fix:1;
 
        /* required for usb32 quirk */
        #define OHCI_CTRL_HCFS          (3 << 6)