diff --git a/src/southbridge/intel/lynxpoint/Makefile.inc b/src/southbridge/intel/lynxpoint/Makefile.inc index 1689132510..7be2d03bb4 100644 --- a/src/southbridge/intel/lynxpoint/Makefile.inc +++ b/src/southbridge/intel/lynxpoint/Makefile.inc @@ -49,7 +49,7 @@ smm-$(CONFIG_SPI_FLASH_SMM) += spi.c ramstage-$(CONFIG_HAVE_SMI_HANDLER) += smi.c pmutil.c smm-$(CONFIG_HAVE_SMI_HANDLER) += smihandler.c me_9.x.c finalize.c pch.c -smm-$(CONFIG_HAVE_SMI_HANDLER) += pmutil.c +smm-$(CONFIG_HAVE_SMI_HANDLER) += pmutil.c usb_ehci.c usb_xhci.c romstage-y += early_usb.c early_smbus.c early_me.c me_status.c early_pch.c romstage-y += reset.c early_spi.c rcba.c diff --git a/src/southbridge/intel/lynxpoint/pch.h b/src/southbridge/intel/lynxpoint/pch.h index 32c7d9bd01..c16c009038 100644 --- a/src/southbridge/intel/lynxpoint/pch.h +++ b/src/southbridge/intel/lynxpoint/pch.h @@ -89,6 +89,9 @@ #if defined (__SMM__) && !defined(__ASSEMBLER__) void intel_pch_finalize_smm(void); +void usb_ehci_sleep_prepare(device_t dev, u8 slp_typ); +void usb_ehci_disable(device_t dev); +void usb_xhci_sleep_prepare(device_t dev, u8 slp_typ); #endif @@ -356,11 +359,49 @@ int early_pch_init(const void *gpio_map, #define SATA_DTLE_DATA_SHIFT 24 #define SATA_DTLE_EDGE_SHIFT 16 -/* USB Registers */ -#define EHCI_PWR_CNTL_STS 0x54 -#define EHCI_PWR_STS_MASK 0x3 -#define EHCI_PWR_STS_SET_D0 0x0 -#define EHCI_PWR_STS_SET_D3 0x3 +/* EHCI PCI Registers */ +#define EHCI_PWR_CTL_STS 0x54 +#define PWR_CTL_SET_MASK 0x3 +#define PWR_CTL_SET_D0 0x0 +#define PWR_CTL_SET_D3 0x3 +#define PWR_CTL_ENABLE_PME (1 << 8) + +/* EHCI Memory Registers */ +#define EHCI_USB_CMD 0x20 +#define EHCI_USB_CMD_RUN (1 << 0) +#define EHCI_USB_CMD_PSE (1 << 4) +#define EHCI_USB_CMD_ASE (1 << 5) +#define EHCI_PORTSC(port) (0x64 + (port * 4)) +#define EHCI_PORTSC_ENABLED (1 << 2) +#define EHCI_PORTSC_SUSPEND (1 << 7) + +/* XHCI PCI Registers */ +#define XHCI_PWR_CTL_STS 0x74 +#define XHCI_USB2PR 0xd0 +#define XHCI_USB2PRM 0xd4 +#define XHCI_USB2PR_HCSEL 0x7fff +#define XHCI_USB3PR 0xd8 +#define XHCI_USB3PR_SSEN 0x3f +#define XHCI_USB3PRM 0xdc +#define XHCI_USB3FUS 0xe0 +#define XHCI_USB3FUS_SS_MASK 3 +#define XHCI_USB3FUS_SS_SHIFT 3 +#define XHCI_USB3PDO 0xe8 + +/* XHCI Memory Registers */ +#define XHCI_USB3_PORTSC(port) ((pch_is_lp() ? 0x510 : 0x570) + (port * 0x10)) +#define XHCI_USB3_PORTSC_CHST (0x7f << 17) +#define XHCI_USB3_PORTSC_WCE (1 << 25) /* Wake on Connect */ +#define XHCI_USB3_PORTSC_WDE (1 << 26) /* Wake on Disconnect */ +#define XHCI_USB3_PORTSC_WOE (1 << 27) /* Wake on Overcurrent */ +#define XHCI_USB3_PORTSC_WRC (1 << 19) /* Warm Reset Complete */ +#define XHCI_USB3_PORTSC_LWS (1 << 16) /* Link Write Strobe */ +#define XHCI_USB3_PORTSC_WPR (1 << 31) /* Warm Port Reset */ +#define XHCI_USB3_PORTSC_PLS (0xf << 5) /* Port Link State */ +#define XHCI_PLSR_DISABLED (4 << 5) /* Port is disabled */ +#define XHCI_PLSR_RXDETECT (5 << 5) /* Port is disconnected */ +#define XHCI_PLSR_POLLING (7 << 5) /* Port is polling */ +#define XHCI_PLSW_ENABLE (5 << 5) /* Transition from disabled */ /* Serial IO IOBP Registers */ #define SIO_IOBP_PORTCTRL0 0xcb000000 /* SDIO D23:F0 */ diff --git a/src/southbridge/intel/lynxpoint/smihandler.c b/src/southbridge/intel/lynxpoint/smihandler.c index add53ed5a9..e920cfe3e2 100644 --- a/src/southbridge/intel/lynxpoint/smihandler.c +++ b/src/southbridge/intel/lynxpoint/smihandler.c @@ -106,97 +106,6 @@ static void busmaster_disable_on_bus(int bus) } } -/* Handler for EHCI controller on entry to S3/S4/S5 */ -static void ehci_sleep_prepare(device_t dev, u8 slp_typ) -{ - u32 reg32; - u32 bar0_base; - u16 pwr_state; - u16 pci_cmd; - - /* Check if the controller is disabled or not present */ - bar0_base = pci_read_config32(dev, PCI_BASE_ADDRESS_0); - if (bar0_base == 0 || bar0_base == 0xffffffff) - return; - pci_cmd = pci_read_config32(dev, PCI_COMMAND); - - switch (slp_typ) { - case SLP_TYP_S4: - case SLP_TYP_S5: - /* Check if controller is in D3 power state */ - pwr_state = pci_read_config16(dev, EHCI_PWR_CNTL_STS); - if ((pwr_state & EHCI_PWR_STS_MASK) == EHCI_PWR_STS_SET_D3) { - /* Put in D0 */ - pwr_state &= ~EHCI_PWR_STS_MASK; - pwr_state |= EHCI_PWR_STS_SET_D0; - pci_write_config16(dev, EHCI_PWR_CNTL_STS, pwr_state); - - /* Make sure memory bar is set */ - pci_write_config32(dev, PCI_BASE_ADDRESS_0, bar0_base); - - /* Make sure memory space is enabled */ - pci_write_config16(dev, PCI_COMMAND, pci_cmd | - PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); - } - - /* - * If Run/Stop (bit0) is clear in USB2.0_CMD: - * - Clear Async Schedule Enable (bit5) and - * - Clear Periodic Schedule Enable (bit4) and - * - Set Run/Stop (bit0) - */ - reg32 = read32(bar0_base + 0x20); - if (reg32 & (1 << 0)) { - reg32 &= ~((1 << 5) | (1 << 4)); - reg32 |= (1 << 0); - write32(bar0_base + 0x20, reg32); - } - - /* Check for Port Enabled in PORTSC */ - reg32 = read32(bar0_base + 0x64); - if (reg32 & (1 << 2)) { - /* Set suspend bit in PORTSC if not already set */ - if (!(reg32 & (1 << 7))) { - reg32 |= (1 << 7); - write32(bar0_base + 0x64, reg32); - } - - /* Delay 25ms !! */ - udelay(25 * 1000); - - /* Clear Run/Stop bit */ - reg32 = read32(bar0_base + 0x20); - reg32 &= (1 << 0); - write32(bar0_base + 0x20, reg32); - } - - pwr_state = pci_read_config16(dev, EHCI_PWR_CNTL_STS); - if ((pwr_state & EHCI_PWR_STS_MASK) == EHCI_PWR_STS_SET_D3) { - /* Restore pci command reg */ - pci_write_config16(dev, PCI_COMMAND, pci_cmd); - - /* Enable D3 */ - pwr_state |= EHCI_PWR_STS_SET_D3; - pci_write_config16(dev, EHCI_PWR_CNTL_STS, pwr_state); - } - } -} - -/* Handler for XHCI controller on entry to S3/S4/S5 */ -static void xhci_sleep_prepare(device_t dev, u8 slp_typ) -{ - u16 reg16; - - switch (slp_typ) { - case SLP_TYP_S3: - case SLP_TYP_S4: - case SLP_TYP_S5: - /* Set D3Hot state and PME enable bit */ - reg16 = pci_read_config16(dev, 0x74); - reg16 |= (1 << 8) | (1 << 1) | (1 << 0); - pci_write_config16(dev, 0x74, reg16); - } -} static void southbridge_smi_sleep(void) { @@ -226,9 +135,9 @@ static void southbridge_smi_sleep(void) mainboard_smi_sleep(slp_typ-2); /* USB sleep preparations */ - ehci_sleep_prepare(PCH_EHCI1_DEV, slp_typ); - ehci_sleep_prepare(PCH_EHCI2_DEV, slp_typ); - xhci_sleep_prepare(PCH_XHCI_DEV, slp_typ); + usb_ehci_sleep_prepare(PCH_EHCI1_DEV, slp_typ); + usb_ehci_sleep_prepare(PCH_EHCI2_DEV, slp_typ); + usb_xhci_sleep_prepare(PCH_XHCI_DEV, slp_typ); #if CONFIG_ELOG_GSMI /* Log S3, S4, and S5 entry */ diff --git a/src/southbridge/intel/lynxpoint/usb_ehci.c b/src/southbridge/intel/lynxpoint/usb_ehci.c index f06151b138..4382611cf7 100644 --- a/src/southbridge/intel/lynxpoint/usb_ehci.c +++ b/src/southbridge/intel/lynxpoint/usb_ehci.c @@ -19,12 +19,123 @@ */ #include +#include #include #include #include -#include "pch.h" #include #include +#include "pch.h" + +#ifdef __SMM__ + +void usb_ehci_disable(device_t dev) +{ + u16 reg16; + u32 reg32; + + /* Set 0xDC[0]=1 */ + pci_or_config32(dev, 0xdc, (1 << 0)); + + /* Set D3Hot state and disable PME */ + reg16 = pci_read_config16(dev, EHCI_PWR_CTL_STS); + reg16 &= ~(PWR_CTL_ENABLE_PME | PWR_CTL_SET_MASK); + reg16 |= PWR_CTL_SET_D3; + pci_write_config16(dev, EHCI_PWR_CTL_STS, reg16); + + /* Clear memory and bus master */ + pci_write_config32(dev, PCI_BASE_ADDRESS_0, 0); + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 &= ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); + pci_write_config32(dev, PCI_COMMAND, reg32); + + /* Disable device */ + switch (dev) { + case PCH_EHCI1_DEV: + RCBA32_OR(FD, PCH_DISABLE_EHCI1); + break; + case PCH_EHCI2_DEV: + RCBA32_OR(FD, PCH_DISABLE_EHCI2); + break; + } +} + +/* Handler for EHCI controller on entry to S3/S4/S5 */ +void usb_ehci_sleep_prepare(device_t dev, u8 slp_typ) +{ + u32 reg32; + u32 bar0_base; + u16 pwr_state; + u16 pci_cmd; + + /* Check if the controller is disabled or not present */ + bar0_base = pci_read_config32(dev, PCI_BASE_ADDRESS_0); + if (bar0_base == 0 || bar0_base == 0xffffffff) + return; + pci_cmd = pci_read_config32(dev, PCI_COMMAND); + + switch (slp_typ) { + case SLP_TYP_S4: + case SLP_TYP_S5: + /* Check if controller is in D3 power state */ + pwr_state = pci_read_config16(dev, EHCI_PWR_CTL_STS); + if ((pwr_state & PWR_CTL_SET_MASK) == PWR_CTL_SET_D3) { + /* Put in D0 */ + u32 new_state = pwr_state & ~PWR_CTL_SET_MASK; + new_state |= PWR_CTL_SET_D0; + pci_write_config16(dev, EHCI_PWR_CTL_STS, new_state); + + /* Make sure memory bar is set */ + pci_write_config32(dev, PCI_BASE_ADDRESS_0, bar0_base); + + /* Make sure memory space is enabled */ + pci_write_config16(dev, PCI_COMMAND, pci_cmd | + PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + } + + /* + * If Run/Stop (bit0) is clear in USB2.0_CMD: + * - Clear Async Schedule Enable (bit5) and + * - Clear Periodic Schedule Enable (bit4) and + * - Set Run/Stop (bit0) + */ + reg32 = read32(bar0_base + EHCI_USB_CMD); + if (reg32 & EHCI_USB_CMD_RUN) { + reg32 &= ~(EHCI_USB_CMD_PSE | EHCI_USB_CMD_ASE); + reg32 |= EHCI_USB_CMD_RUN; + write32(bar0_base + EHCI_USB_CMD, reg32); + } + + /* Check for Port Enabled in PORTSC(0) (RMH) */ + reg32 = read32(bar0_base + EHCI_PORTSC(0)); + if (reg32 & EHCI_PORTSC_ENABLED) { + /* Set suspend bit in PORTSC if not already set */ + if (!(reg32 & EHCI_PORTSC_SUSPEND)) { + reg32 |= EHCI_PORTSC_SUSPEND; + write32(bar0_base + EHCI_PORTSC(0), reg32); + } + + /* Delay 25ms !! */ + udelay(25 * 1000); + + /* Clear Run/Stop bit */ + reg32 = read32(bar0_base + EHCI_USB_CMD); + reg32 &= EHCI_USB_CMD_RUN; + write32(bar0_base + EHCI_USB_CMD, reg32); + } + + /* Restore state to D3 if that is what it was at the start */ + if ((pwr_state & PWR_CTL_SET_MASK) == PWR_CTL_SET_D3) { + /* Restore pci command reg */ + pci_write_config16(dev, PCI_COMMAND, pci_cmd); + + /* Enable D3 */ + pci_write_config16(dev, EHCI_PWR_CTL_STS, pwr_state); + } + } +} + +#else /* !__SMM__ */ static void usb_ehci_clock_gating(struct device *dev) { @@ -98,3 +209,5 @@ static const struct pci_driver pch_usb_ehci __pci_driver = { .vendor = PCI_VENDOR_ID_INTEL, .devices = pci_device_ids, }; + +#endif /* !__SMM__ */ diff --git a/src/southbridge/intel/lynxpoint/usb_xhci.c b/src/southbridge/intel/lynxpoint/usb_xhci.c index 33f33f4d70..df264e31f4 100644 --- a/src/southbridge/intel/lynxpoint/usb_xhci.c +++ b/src/southbridge/intel/lynxpoint/usb_xhci.c @@ -18,12 +18,187 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include +#include #include #include #include #include #include "pch.h" +#ifdef __SMM__ + +static u32 usb_xhci_mem_base(device_t dev) +{ + u32 mem_base = pci_read_config32(dev, PCI_BASE_ADDRESS_0); + + /* Check if the controller is disabled or not present */ + if (mem_base == 0 || mem_base == 0xffffffff) + return 0; + + return mem_base & ~0xf; +} + +#endif + +#if 0 + +static int usb_xhci_port_count_usb3(device_t dev) +{ + if (pch_is_lp()) { + /* LynxPoint-LP has 4 SS ports */ + return 4; + } else { + /* LynxPoint-H can have 0, 2, 4, or 6 SS ports */ + u32 mem_base = usb_xhci_mem_base(dev); + u32 fus = read32(mem_base + XHCI_USB3FUS); + fus >>= XHCI_USB3FUS_SS_SHIFT; + fus &= XHCI_USB3FUS_SS_MASK; + switch (fus) { + case 3: return 0; + case 2: return 2; + case 1: return 4; + case 0: default: return 6; + } + } + return 0; +} + +static void usb_xhci_reset_status_usb3(u32 mem_base, int port) +{ + u32 portsc = mem_base + XHCI_USB3_PORTSC(port); + write32(portsc, read32(portsc) | XHCI_USB3_PORTSC_CHST); +} + +static void usb_xhci_reset_port_usb3(u32 mem_base, int port) +{ + u32 portsc = mem_base + XHCI_USB3_PORTSC(port); + write32(portsc, read32(portsc) | XHCI_USB3_PORTSC_WPR); +} + +#define XHCI_RESET_DELAY_US 1000 /* 1ms */ +#define XHCI_RESET_TIMEOUT 100 /* 100ms */ + +/* + * 1) Wait until port is done polling + * 2) If port is disconnected + * a) Issue warm port reset + * b) Poll for warm reset complete + * c) Write 1 to port change status bits + */ +static void usb_xhci_reset_usb3(device_t dev, int all) +{ + u32 status, port_disabled; + int timeout, port; + int port_count = usb_xhci_port_count_usb3(dev); + u32 mem_base = usb_xhci_mem_base(dev); + + if (!mem_base || !port_count) + return; + + /* Get mask of disabled ports */ + port_disabled = pci_read_config32(dev, XHCI_USB3PDO); + + /* Wait until all enabled ports are done polling */ + for (timeout = XHCI_RESET_TIMEOUT; timeout; timeout--) { + int complete = 1; + for (port = 0; port < port_count; port++) { + /* Skip disabled ports */ + if (port_disabled & (1 << port)) + continue; + /* Read port link status field */ + status = read32(mem_base + XHCI_USB3_PORTSC(port)); + status &= XHCI_USB3_PORTSC_PLS; + if (status == XHCI_PLSR_POLLING) + complete = 0; + } + /* Exit if all ports not polling */ + if (complete) + break; + udelay(XHCI_RESET_DELAY_US); + } + + /* Reset all requested ports */ + for (port = 0; port < port_count; port++) { + u32 portsc = mem_base + XHCI_USB3_PORTSC(port); + /* Skip disabled ports */ + if (port_disabled & (1 << port)) + continue; + status = read32(portsc) & XHCI_USB3_PORTSC_PLS; + /* Reset all or only disconnected ports */ + if (all || status == XHCI_PLSR_RXDETECT) + usb_xhci_reset_port_usb3(mem_base, port); + else + port_disabled |= 1 << port; /* No reset */ + } + + /* Wait for warm reset complete on all reset ports */ + for (timeout = XHCI_RESET_TIMEOUT; timeout; timeout--) { + int complete = 1; + for (port = 0; port < port_count; port++) { + /* Only check ports that were reset */ + if (port_disabled & (1 << port)) + continue; + /* Check if warm reset is complete */ + status = read32(mem_base + XHCI_USB3_PORTSC(port)); + if (!(status & XHCI_USB3_PORTSC_WRC)) + complete = 0; + } + /* Check for warm reset complete in any port */ + if (complete) + break; + udelay(XHCI_RESET_DELAY_US); + } + + /* Clear port change status bits */ + for (port = 0; port < port_count; port++) + usb_xhci_reset_status_usb3(mem_base, port); +} + +#endif + +#ifdef __SMM__ + +/* Handler for XHCI controller on entry to S3/S4/S5 */ +void usb_xhci_sleep_prepare(device_t dev, u8 slp_typ) +{ + u16 reg16; + u32 reg32; + u32 mem_base = usb_xhci_mem_base(dev); + + if (!mem_base || slp_typ < 3) + return; + + if (pch_is_lp()) { + /* Set D0 state */ + reg16 = pci_read_config16(dev, XHCI_PWR_CTL_STS); + reg16 &= ~PWR_CTL_SET_MASK; + reg16 |= PWR_CTL_SET_D0; + pci_write_config16(dev, XHCI_PWR_CTL_STS, reg16); + + /* Clear PCI 0xB0[14:13] */ + reg32 = pci_read_config32(dev, 0xb0); + reg32 &= ~((1 << 14) | (1 << 13)); + pci_write_config32(dev, 0xb0, reg32); + + /* Clear MMIO 0x816c[14,2] */ + reg32 = read32(mem_base + 0x816c); + reg32 &= ~((1 << 14) | (1 << 2)); + write32(mem_base + 0x816c, reg32); + + /* Set MMIO 0x80e0[15] */ + reg32 = read32(mem_base + 0x80e0); + reg32 |= (1 << 15); + write32(mem_base + 0x80e0, reg32); + } + + /* Set D3Hot state and enable PME */ + pci_or_config16(dev, XHCI_PWR_CTL_STS, PWR_CTL_SET_D3); + pci_or_config16(dev, XHCI_PWR_CTL_STS, PWR_CTL_ENABLE_PME); +} + +#else /* !__SMM__ */ + static void usb_xhci_clock_gating(device_t dev) { u32 reg32; @@ -173,3 +348,4 @@ static const struct pci_driver pch_usb_xhci __pci_driver = { .vendor = PCI_VENDOR_ID_INTEL, .devices = pci_device_ids, }; +#endif /* !__SMM__ */