Commit bde0716d authored by Joe Lee's avatar Joe Lee Committed by Greg Kroah-Hartman
Browse files

xhci: workaround for AMD Promontory disabled ports wakeup



For AMD Promontory xHCI host, although you can disable USB ports in
BIOS settings, those ports will be enabled anyway after you remove a
device on that port and re-plug it in again. It's a known limitation of
the chip. As a workaround we can clear the PORT_WAKE_BITS.

[commit and code comment rephrasing -Mathias]
Signed-off-by: default avatarJoe Lee <asmt.swfae@gmail.com>
Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 7928b2cb
Loading
Loading
Loading
Loading
+109 −0
Original line number Diff line number Diff line
@@ -66,6 +66,23 @@
#define	AX_INDXC		0x30
#define	AX_DATAC		0x34

#define PT_ADDR_INDX		0xE8
#define PT_READ_INDX		0xE4
#define PT_SIG_1_ADDR		0xA520
#define PT_SIG_2_ADDR		0xA521
#define PT_SIG_3_ADDR		0xA522
#define PT_SIG_4_ADDR		0xA523
#define PT_SIG_1_DATA		0x78
#define PT_SIG_2_DATA		0x56
#define PT_SIG_3_DATA		0x34
#define PT_SIG_4_DATA		0x12
#define PT4_P1_REG		0xB521
#define PT4_P2_REG		0xB522
#define PT2_P1_REG		0xD520
#define PT2_P2_REG		0xD521
#define PT1_P1_REG		0xD522
#define PT1_P2_REG		0xD523

#define	NB_PCIE_INDX_ADDR	0xe0
#define	NB_PCIE_INDX_DATA	0xe4
#define	PCIE_P_CNTL		0x10040
@@ -512,6 +529,98 @@ void usb_amd_dev_put(void)
}
EXPORT_SYMBOL_GPL(usb_amd_dev_put);

/*
 * Check if port is disabled in BIOS on AMD Promontory host.
 * BIOS Disabled ports may wake on connect/disconnect and need
 * driver workaround to keep them disabled.
 * Returns true if port is marked disabled.
 */
bool usb_amd_pt_check_port(struct device *device, int port)
{
	unsigned char value, port_shift;
	struct pci_dev *pdev;
	u16 reg;

	pdev = to_pci_dev(device);
	pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_1_ADDR);

	pci_read_config_byte(pdev, PT_READ_INDX, &value);
	if (value != PT_SIG_1_DATA)
		return false;

	pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_2_ADDR);

	pci_read_config_byte(pdev, PT_READ_INDX, &value);
	if (value != PT_SIG_2_DATA)
		return false;

	pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_3_ADDR);

	pci_read_config_byte(pdev, PT_READ_INDX, &value);
	if (value != PT_SIG_3_DATA)
		return false;

	pci_write_config_word(pdev, PT_ADDR_INDX, PT_SIG_4_ADDR);

	pci_read_config_byte(pdev, PT_READ_INDX, &value);
	if (value != PT_SIG_4_DATA)
		return false;

	/* Check disabled port setting, if bit is set port is enabled */
	switch (pdev->device) {
	case 0x43b9:
	case 0x43ba:
	/*
	 * device is AMD_PROMONTORYA_4(0x43b9) or PROMONTORYA_3(0x43ba)
	 * PT4_P1_REG bits[7..1] represents USB2.0 ports 6 to 0
	 * PT4_P2_REG bits[6..0] represents ports 13 to 7
	 */
		if (port > 6) {
			reg = PT4_P2_REG;
			port_shift = port - 7;
		} else {
			reg = PT4_P1_REG;
			port_shift = port + 1;
		}
		break;
	case 0x43bb:
	/*
	 * device is AMD_PROMONTORYA_2(0x43bb)
	 * PT2_P1_REG bits[7..5] represents USB2.0 ports 2 to 0
	 * PT2_P2_REG bits[5..0] represents ports 9 to 3
	 */
		if (port > 2) {
			reg = PT2_P2_REG;
			port_shift = port - 3;
		} else {
			reg = PT2_P1_REG;
			port_shift = port + 5;
		}
		break;
	case 0x43bc:
	/*
	 * device is AMD_PROMONTORYA_1(0x43bc)
	 * PT1_P1_REG[7..4] represents USB2.0 ports 3 to 0
	 * PT1_P2_REG[5..0] represents ports 9 to 4
	 */
		if (port > 3) {
			reg = PT1_P2_REG;
			port_shift = port - 4;
		} else {
			reg = PT1_P1_REG;
			port_shift = port + 4;
		}
		break;
	default:
		return false;
	}
	pci_write_config_word(pdev, PT_ADDR_INDX, reg);
	pci_read_config_byte(pdev, PT_READ_INDX, &value);

	return !(value & BIT(port_shift));
}
EXPORT_SYMBOL_GPL(usb_amd_pt_check_port);

/*
 * Make sure the controller is completely inactive, unable to
 * generate interrupts or do DMA.
+5 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
void sb800_prefetch(struct device *dev, int on);
bool usb_xhci_needs_pci_reset(struct pci_dev *pdev);
bool usb_amd_pt_check_port(struct device *device, int port);
#else
struct pci_dev;
static inline void usb_amd_quirk_pll_disable(void) {}
@@ -25,6 +26,10 @@ static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
static inline void usb_amd_dev_put(void) {}
static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
static inline void sb800_prefetch(struct device *dev, int on) {}
static inline bool usb_amd_pt_check_port(struct device *device, int port)
{
	return false;
}
#endif  /* CONFIG_USB_PCI */

#endif  /*  __LINUX_USB_PCI_QUIRKS_H  */
+7 −0
Original line number Diff line number Diff line
@@ -1522,6 +1522,13 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
				t2 |= PORT_WKOC_E | PORT_WKCONN_E;
				t2 &= ~PORT_WKDISC_E;
			}

			if ((xhci->quirks & XHCI_U2_DISABLE_WAKE) &&
			    (hcd->speed < HCD_USB3)) {
				if (usb_amd_pt_check_port(hcd->self.controller,
							  port_index))
					t2 &= ~PORT_WAKE_BITS;
			}
		} else
			t2 &= ~PORT_WAKE_BITS;

+11 −0
Original line number Diff line number Diff line
@@ -42,6 +42,10 @@
#define PCI_DEVICE_ID_INTEL_APL_XHCI			0x5aa8
#define PCI_DEVICE_ID_INTEL_DNV_XHCI			0x19d0

#define PCI_DEVICE_ID_AMD_PROMONTORYA_4			0x43b9
#define PCI_DEVICE_ID_AMD_PROMONTORYA_3			0x43ba
#define PCI_DEVICE_ID_AMD_PROMONTORYA_2			0x43bb
#define PCI_DEVICE_ID_AMD_PROMONTORYA_1			0x43bc
#define PCI_DEVICE_ID_ASMEDIA_1042A_XHCI		0x1142

static const char hcd_name[] = "xhci_hcd";
@@ -125,6 +129,13 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
	if (pdev->vendor == PCI_VENDOR_ID_AMD)
		xhci->quirks |= XHCI_TRUST_TX_LENGTH;

	if ((pdev->vendor == PCI_VENDOR_ID_AMD) &&
		((pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_4) ||
		(pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_3) ||
		(pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_2) ||
		(pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_1)))
		xhci->quirks |= XHCI_U2_DISABLE_WAKE;

	if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
		xhci->quirks |= XHCI_LPM_SUPPORT;
		xhci->quirks |= XHCI_INTEL_HOST;
+1 −1
Original line number Diff line number Diff line
@@ -1822,7 +1822,7 @@ struct xhci_hcd {
/* For controller with a broken Port Disable implementation */
#define XHCI_BROKEN_PORT_PED	(1 << 25)
#define XHCI_LIMIT_ENDPOINT_INTERVAL_7	(1 << 26)
/* Reserved. It was XHCI_U2_DISABLE_WAKE */
#define XHCI_U2_DISABLE_WAKE	(1 << 27)
#define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
#define XHCI_HW_LPM_DISABLE	(1 << 29)