From: Joe Lee <joe2_lee@xxxxxxxxxxxxxx> For AMD Promontory xHCI host,although you can disable USB ports in BIOSsettings,those ports will be enabled anyway after you remove a device onthat 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. Signed-off-by: Joe Lee <joe2_lee@xxxxxxxxxxxxxx> --- v9: - Fix multi-line comment style v8: - usb_amd_pt_check_port() function return a bool v7: - add a empty function for the case when CONFIG_USB_PCI is not defined in pci-quirks.h v6: - Fix coding format. v5: - Add check disable port setting before set PORT_WAKE_BITS v4: - Remove the patch code in case USB_PORT_FEAT_REMOTE_WAKE_MASK v3: - Fix some checkpatch.pl --- drivers/usb/host/pci-quirks.c | 128 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/pci-quirks.h | 5 ++ drivers/usb/host/xhci-hub.c | 7 +++ drivers/usb/host/xhci-pci.c | 11 ++++ drivers/usb/host/xhci.h | 2 +- 5 files changed, 152 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 6dda362..bf8354e 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -65,6 +65,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 @@ -511,6 +528,117 @@ void usb_amd_dev_put(void) } EXPORT_SYMBOL_GPL(usb_amd_dev_put); +bool usb_amd_pt_check_port(struct device *device, int port) +{ + unsigned char value; + struct pci_dev *pdev; + + 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; + + if ((pdev->device == 0x43b9) || (pdev->device == 0x43ba)) { + /* device is AMD_PROMONTORYA_4(0x43b9) or + * PROMONTORYA_3(0x43ba) + * disable port setting + * PT_4_P1_REG[7..1] is USB2.0 port6 to 0 + * PT4_P2_REG[6..0] is USB 13 to port 7 + * 0 : disable ;1 : enable + */ + if (port > 6) { + pci_write_config_word(pdev, PT_ADDR_INDX, + PT4_P2_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port - 7))) + return false; + else + return true; + } else { + pci_write_config_word(pdev, PT_ADDR_INDX, + PT4_P1_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port + 1))) + return false; + else + return true; + } + } else if (pdev->device == 0x43bb) { + /* device is AMD_PROMONTORYA_2(0x43bb) + * disable port setting + * PT2_P1_REG[7..5] is USB2.0 port2 to 0 + * PT2_P2_REG[5..0] is USB 9 to port3 + * 0 : disable ;1 : enable + */ + if (port > 2) { + pci_write_config_word(pdev, PT_ADDR_INDX, PT2_P2_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port - 3))) + return false; + else + return true; + } else { + pci_write_config_word(pdev, PT_ADDR_INDX, PT2_P1_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port + 5))) + return false; + else + return true; + } + } else { + /* device is AMD_PROMONTORYA_1(0x43bc) + * disable port setting + * PT1_P1_REG[7..4] is USB2.0 port3 to 0 + * PT1_P2_REG[5..0] is USB 9 to port4 + * 0 : disable ;1 : enable + */ + if (port > 3) { + pci_write_config_word(pdev, PT_ADDR_INDX, PT1_P2_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port - 4))) + return false; + else + return true; + + } else { + pci_write_config_word(pdev, PT_ADDR_INDX, PT1_P1_REG); + + pci_read_config_byte(pdev, PT_READ_INDX, &value); + if (value & (1<<(port + 4))) + return false; + else + return true; + + } + } +} +EXPORT_SYMBOL_GPL(usb_amd_pt_check_port); + /* * Make sure the controller is completely inactive, unable to * generate interrupts or do DMA. diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index b68dcb5..4ca0d9b 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -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 */ diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index a2336de..0df0497 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1528,6 +1528,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; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 76f3929..f056754 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -54,6 +54,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"; @@ -137,6 +141,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; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 2b48aa4..7c87189 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1828,7 +1828,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) unsigned int num_active_eps; -- 2.7.4 -- 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