]> err.no Git - linux-2.6/blobdiff - drivers/usb/host/ohci-pci.c
USB: quirk PLL power down mode
[linux-2.6] / drivers / usb / host / ohci-pci.c
index 4696cc912e16203540a5185568a27e02bb7c15e2..083e8df0a817f83b925ac397f5e90cb43b8af1d2 100644 (file)
 #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 broken_suspend(struct usb_hcd *hcd)
@@ -143,6 +165,103 @@ static int ohci_quirk_nec(struct usb_hcd *hcd)
        return 0;
 }
 
+static int ohci_quirk_amd700(struct usb_hcd *hcd)
+{
+       struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+       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 0;
+
+       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 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");
+
+       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;
+               }
+       }
+
+}
+
 /* List of quirks for OHCI */
 static const struct pci_device_id ohci_pci_quirks[] = {
        {
@@ -181,6 +300,19 @@ static const struct pci_device_id ohci_pci_quirks[] = {
                PCI_DEVICE(PCI_VENDOR_ID_ITE, 0x8152),
                .driver_data = (unsigned long) broken_suspend,
        },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4397),
+               .driver_data = (unsigned long)ohci_quirk_amd700,
+       },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4398),
+               .driver_data = (unsigned long)ohci_quirk_amd700,
+       },
+       {
+               PCI_DEVICE(PCI_VENDOR_ID_ATI, 0x4399),
+               .driver_data = (unsigned long)ohci_quirk_amd700,
+       },
+
        /* FIXME for some of the early AMD 760 southbridges, OHCI
         * won't work at all.  blacklist them.
         */