lynxpoint: Move USB SMI sleep code to separate USB files
Move this to the existing USB source files so they can share some
helper functions and keep the main smihandler code cleaner.
The XHCI sleep prepare code now implements the actual sleep
preparation steps from the ref code instead of the docs.
Change-Id: Ic90adbdaba947a6b53824e548c785b4fb3990ab5
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/63800
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/4406
Tested-by: build bot (Jenkins)
Reviewed-by: Patrick Georgi <patrick@georgi-clan.de>
diff --git a/src/southbridge/intel/lynxpoint/Makefile.inc b/src/southbridge/intel/lynxpoint/Makefile.inc
index 1689132..7be2d03 100644
--- a/src/southbridge/intel/lynxpoint/Makefile.inc
+++ b/src/southbridge/intel/lynxpoint/Makefile.inc
@@ -49,7 +49,7 @@
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 32c7d9bd..c16c009 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 @@
#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 add53ed..e920cfe 100644
--- a/src/southbridge/intel/lynxpoint/smihandler.c
+++ b/src/southbridge/intel/lynxpoint/smihandler.c
@@ -106,97 +106,6 @@
}
}
-/* 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 @@
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 f06151b..4382611 100644
--- a/src/southbridge/intel/lynxpoint/usb_ehci.c
+++ b/src/southbridge/intel/lynxpoint/usb_ehci.c
@@ -19,12 +19,123 @@
*/
#include <console/console.h>
+#include <delay.h>
#include <device/device.h>
#include <device/pci.h>
#include <device/pci_ids.h>
-#include "pch.h"
#include <usbdebug.h>
#include <arch/io.h>
+#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 @@
.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 33f33f4..df264e3 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 <console/console.h>
+#include <delay.h>
#include <device/device.h>
#include <device/pci.h>
#include <device/pci_ids.h>
#include <arch/io.h>
#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 @@
.vendor = PCI_VENDOR_ID_INTEL,
.devices = pci_device_ids,
};
+#endif /* !__SMM__ */