[PATCH 2/7] xHCI: AMD Hudson PLL quirk

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

 



>From c9b3d354d4e2b9ea8fc77cc7c2386e348e865144 Mon Sep 17 00:00:00 2001
From: Andiry Xu <andiry.xu@xxxxxxx>
Date: Mon, 20 Dec 2010 17:52:00 +0800
Subject: [PATCH 2/7] xHCI: AMD Hudson PLL quirk

This patch disables the optional PM feature inside the Hudson3 platform under
the following conditions:

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

The PM feature needs to be disabled to eliminate PLL startup delays when the
link comes out of low power state. The performance of DMA data transfer could
be impacted if system delay were encountered and in addition to the PLL start
up delays. Disabling the PM would leave room for unpredictable system delays
in order to guarantee uninterrupted data transfer to isochronous audio or
video streaming devices that require time sensitive information. If data in
an audio/video stream was interrupted then erratic audio or video performance
may be encountered.

Signed-off-by: Andiry Xu <andiry.xu@xxxxxxx>
---
 drivers/usb/host/xhci-pci.c  |  111 ++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci-ring.c |   29 +++++++++++-
 drivers/usb/host/xhci.c      |    3 +
 drivers/usb/host/xhci.h      |    3 +
 4 files changed, 145 insertions(+), 1 deletions(-)

diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index bb668a8..3f0b17d 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -29,6 +29,7 @@
 #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK	0x1000
 
 static const char hcd_name[] = "xhci_hcd";
+static struct pci_dev	*amd_nb_dev;
 
 /* called after powerup, by probe or system-pm "wakeup" */
 static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev)
@@ -47,6 +48,114 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev)
 	return 0;
 }
 
+static int xhci_quirk_amd_hudson(struct xhci_hcd *xhci)
+{
+	struct pci_dev *amd_smbus_dev;
+	u8 rev = 0;
+
+	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, 0x9601, NULL);
+	if (!amd_nb_dev)
+		xhci_dbg(xhci, "QUIRK: Unable to get AMD NB device\n");
+
+	xhci_dbg(xhci, "QUIRK: Enable AMD Hudson-3 PLL fix\n");
+
+	pci_dev_put(amd_smbus_dev);
+	amd_smbus_dev = NULL;
+	return 1;
+}
+
+
+#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	PCIE_P_CNTL		0x10040
+#define	BIF_NB			0x10002
+
+void xhci_quirk_amd_pll(struct xhci_hcd *xhci, int disable)
+{
+	u32 addr, addr_low, addr_high, val;
+	u32 bit = disable ? 0 : 1;
+
+	if (!request_region(0xcd6, 2, "PM_reg")) {
+		xhci_err(xhci, "PM_reg region already in use!\n");
+		return;
+	}
+
+	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;
+	release_region(0xcd6, 2);
+
+	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) {
+		xhci_dbg(xhci, "Disable SB PLL Shutdown\n");
+		val &= ~0x08;
+		val |= (1 << 4) | (1 << 9);
+	} else {
+		xhci_dbg(xhci, "Enable SB PLL Shutdown\n");
+		val |= 0x08;
+		val &= ~((1 << 4) | (1 << 9));
+	}
+
+	outl_p(val, AB_DATA(addr));
+
+	if (amd_nb_dev) {
+		if (disable)
+			xhci_dbg(xhci, "Disable NB PLL Shutdown\n");
+		else
+			xhci_dbg(xhci, "Enable NB PLL Shutdown\n");
+
+		addr = PCIE_P_CNTL;
+		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR, addr);
+		pci_read_config_dword(amd_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_nb_dev, NB_PCIE_INDX_DATA, val);
+
+		addr = BIF_NB;
+		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_ADDR, addr);
+
+		pci_read_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, &val);
+		val &= ~(1 << 8);
+		val |= bit << 8;
+		pci_write_config_dword(amd_nb_dev, NB_PCIE_INDX_DATA, val);
+	}
+
+	return;
+}
+
+void amd_nb_dev_put(void)
+{
+	if (amd_nb_dev) {
+		pci_dev_put(amd_nb_dev);
+		amd_nb_dev = NULL;
+	}
+}
+
 /* called during probe() after chip reset completes */
 static int xhci_pci_setup(struct usb_hcd *hcd)
 {
@@ -81,6 +190,8 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
 	}
 	if (pdev->vendor == PCI_VENDOR_ID_NEC)
 		xhci->quirks |= XHCI_NEC_HOST;
+	if (xhci_quirk_amd_hudson(xhci))
+		xhci->quirks |= XHCI_AMD_PLL_FIX;
 
 	/* Make sure the HC is halted. */
 	retval = xhci_halt(xhci);
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index df558f6..d560f1d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -609,6 +609,15 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci,
 
 	/* Only giveback urb when this is the last td in urb */
 	if (urb_priv->td_cnt == urb_priv->length) {
+		switch (usb_pipetype(urb->pipe)) {
+		case PIPE_ISOCHRONOUS:
+			xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs--;
+			if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs	== 0) {
+				if (xhci->quirks & XHCI_AMD_PLL_FIX)
+					xhci_quirk_amd_pll(xhci, 0);
+			}
+			break;
+		}
 		usb_hcd_unlink_urb_from_ep(hcd, urb);
 		xhci_dbg(xhci, "Giveback %s URB %p\n", adjective, urb);
 
@@ -1453,8 +1462,20 @@ td_cleanup:
 
 		urb_priv->td_cnt++;
 		/* Giveback the urb when all the tds are completed */
-		if (urb_priv->td_cnt == urb_priv->length)
+		if (urb_priv->td_cnt == urb_priv->length) {
 			ret = 1;
+
+			switch (usb_pipetype(urb->pipe)) {
+			case PIPE_ISOCHRONOUS:
+				xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs--;
+				if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs
+					== 0) {
+					if (xhci->quirks & XHCI_AMD_PLL_FIX)
+						xhci_quirk_amd_pll(xhci, 0);
+				}
+				break;
+			}
+		}
 	}
 
 	return ret;
@@ -3003,6 +3024,12 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 		}
 	}
 
+	if (xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs	== 0) {
+		if (xhci->quirks & XHCI_AMD_PLL_FIX)
+			xhci_quirk_amd_pll(xhci, 1);
+	}
+	xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs++;
+
 	wmb();
 	start_trb->field[3] |= start_cycle;
 
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 2947d94..22cbeeb 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -519,6 +519,9 @@ void xhci_stop(struct usb_hcd *hcd)
 	del_timer_sync(&xhci->event_ring_timer);
 #endif
 
+	if (xhci->quirks & XHCI_AMD_PLL_FIX)
+		amd_nb_dev_put();
+
 	xhci_dbg(xhci, "// Disabling event ring interrupts\n");
 	temp = xhci_readl(xhci, &xhci->op_regs->status);
 	xhci_writel(xhci, temp & ~STS_EINT, &xhci->op_regs->status);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 170c367..ea13c7a 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1254,6 +1254,7 @@ struct xhci_hcd {
 #define	XHCI_LINK_TRB_QUIRK	(1 << 0)
 #define XHCI_RESET_EP_QUIRK	(1 << 1)
 #define XHCI_NEC_HOST		(1 << 2)
+#define XHCI_AMD_PLL_FIX		(1 << 3)
 	u32			port_c_suspend[8];	/* port suspend change*/
 	u32			suspended_ports[8];	/* which ports are
 							   suspended */
@@ -1467,6 +1468,8 @@ void xhci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep);
 int xhci_discover_or_reset_device(struct usb_hcd *hcd, struct usb_device *udev);
 int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
 void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev);
+void xhci_quirk_amd_pll(struct xhci_hcd *xhci, int disable);
+void amd_nb_dev_put(void);
 
 /* xHCI ring, segment, TRB, and TD functions */
 dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb);
-- 
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