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: Joe Lee <asmt.swfae@gmail.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
7928b2cbe5
commit
bde0716d1f
@ -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.
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user