sb/intel/lynxpoint: Add native USB init

Implement native USB initialisation for Lynx Point. This is only needed
when MRC.bin is not used.

TO DO: Figure out how to deal with the FIXME's and TODO's lying around.

Change-Id: Ie0fbeeca7b1ca1557173772d733fd2fa27703373
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/64179
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.c b/src/northbridge/intel/haswell/native_raminit/raminit_native.c
index 6a00254..ef61d4e 100644
--- a/src/northbridge/intel/haswell/native_raminit/raminit_native.c
+++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.c
@@ -5,6 +5,7 @@
 #include <northbridge/intel/haswell/haswell.h>
 #include <northbridge/intel/haswell/raminit.h>
 #include <southbridge/intel/lynxpoint/me.h>
+#include <southbridge/intel/lynxpoint/pch.h>
 #include <types.h>
 
 static bool early_init_native(int s3resume)
@@ -15,6 +16,8 @@
 	/** TODO: CPU replacement check must be skipped in warm boots and S3 resumes **/
 	const bool cpu_replaced = !s3resume && intel_early_me_cpu_replacement_check();
 
+	early_usb_init();
+
 	if (!CONFIG(INTEL_LYNXPOINT_LP))
 		dmi_early_init();
 
diff --git a/src/southbridge/intel/lynxpoint/Makefile.inc b/src/southbridge/intel/lynxpoint/Makefile.inc
index 49f03f1..14bba2e1 100644
--- a/src/southbridge/intel/lynxpoint/Makefile.inc
+++ b/src/southbridge/intel/lynxpoint/Makefile.inc
@@ -35,7 +35,7 @@
 romstage-y += early_usb.c early_me.c me_status.c early_pch.c
 romstage-y += pmutil.c
 
-romstage-$(CONFIG_USE_NATIVE_RAMINIT) += early_pch_native.c
+romstage-$(CONFIG_USE_NATIVE_RAMINIT) += early_pch_native.c early_usb_native.c iobp.c
 
 ifeq ($(CONFIG_INTEL_LYNXPOINT_LP),y)
 romstage-y += lp_gpio.c
diff --git a/src/southbridge/intel/lynxpoint/early_usb.c b/src/southbridge/intel/lynxpoint/early_usb.c
index a753681..52e8ac1 100644
--- a/src/southbridge/intel/lynxpoint/early_usb.c
+++ b/src/southbridge/intel/lynxpoint/early_usb.c
@@ -4,17 +4,6 @@
 #include <device/pci_def.h>
 #include "pch.h"
 
-/* HCD_INDEX == 2 selects 0:1a.0 (PCH_EHCI2), any other index
- * selects 0:1d.0 (PCH_EHCI1) for usbdebug use.
- */
-#if CONFIG_USBDEBUG_HCD_INDEX != 2
-#define PCH_EHCI1_TEMP_BAR0 CONFIG_EHCI_BAR
-#define PCH_EHCI2_TEMP_BAR0 (PCH_EHCI1_TEMP_BAR0 + 0x400)
-#else
-#define PCH_EHCI2_TEMP_BAR0 CONFIG_EHCI_BAR
-#define PCH_EHCI1_TEMP_BAR0 (PCH_EHCI2_TEMP_BAR0 + 0x400)
-#endif
-
 /*
  * Setup USB controller MMIO BAR to prevent the
  * reference code from resetting the controller.
diff --git a/src/southbridge/intel/lynxpoint/early_usb_native.c b/src/southbridge/intel/lynxpoint/early_usb_native.c
new file mode 100644
index 0000000..cb6f6ee
--- /dev/null
+++ b/src/southbridge/intel/lynxpoint/early_usb_native.c
@@ -0,0 +1,584 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <console/console.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <device/pci_def.h>
+#include <device/pci_ops.h>
+#include <northbridge/intel/haswell/haswell.h>
+#include <northbridge/intel/haswell/raminit.h>
+#include <southbridge/intel/lynxpoint/iobp.h>
+#include <southbridge/intel/lynxpoint/pch.h>
+#include <timer.h>
+#include <types.h>
+
+static unsigned int is_usbr_enabled(void)
+{
+	return !!(pci_read_config32(PCH_XHCI_DEV, XHCI_USB3FUS) & BIT(5));
+}
+
+static char *const xhci_bar = (char *)PCH_XHCI_TEMP_BAR0;
+
+static void ehci_hcs_init(const pci_devfn_t dev, const uintptr_t ehci_bar)
+{
+	pci_write_config32(dev, PCI_BASE_ADDRESS_0, ehci_bar);
+
+	/** FIXME: Determine whether Bus Master is required (or clean it up afterwards) **/
+	pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY);
+
+	char *const mem_bar = (char *)ehci_bar;
+
+	/**
+	 * Shared EHCI/XHCI ports w/a.
+	 * This step is required when some of the ports are routed to EHCI
+	 * and other ports are routed XHCI at the same time.
+	 *
+	 * FIXME: Under which conditions should this be done?
+	 */
+	pci_and_config16(dev, 0x78, ~0x03);
+
+	/* Skip reset if usbdebug is enabled */
+	if (!CONFIG(USBDEBUG_IN_PRE_RAM))
+		setbits32(mem_bar + EHCI_USB_CMD, EHCI_USB_CMD_HCRESET);
+
+	/* 2: Configure number of controllers and ports */
+	pci_or_config16(dev, EHCI_ACCESS_CNTL, ACCESS_CNTL_ENABLE);
+	clrsetbits32(mem_bar + EHCI_HCS_PARAMS, 0xf << 12, 0);
+	clrsetbits32(mem_bar + EHCI_HCS_PARAMS, 0xf <<  0, 2 + is_usbr_enabled());
+	pci_and_config16(dev, EHCI_ACCESS_CNTL, ~ACCESS_CNTL_ENABLE);
+
+	pci_or_config16(dev, 0x78, BIT(2));
+	pci_or_config16(dev, 0x7c, BIT(14) | BIT(7));
+	pci_update_config32(dev, 0x8c, ~(0xf << 8), (4 << 8));
+	pci_update_config32(dev, 0x8c, ~BIT(26), BIT(17));
+}
+
+static inline unsigned int physical_port_count(void)
+{
+	return MAX_USB2_PORTS;
+}
+
+static unsigned int hs_port_count(void)
+{
+	/** TODO: Apparently, WPT-LP has 10 USB2 ports **/
+	if (CONFIG(INTEL_LYNXPOINT_LP))
+		return 8;
+
+	switch ((pci_read_config32(PCH_XHCI_DEV, XHCI_USB3FUS) >> 1) & 3) {
+	case 3:
+		return 8;
+	case 2:
+		return 10;
+	case 1:
+		return 12;
+	case 0:
+	default:
+		return 14;
+	}
+}
+
+static unsigned int ss_port_count(void)
+{
+	if (CONFIG(INTEL_LYNXPOINT_LP))
+		return 4;
+
+	switch ((pci_read_config32(PCH_XHCI_DEV, XHCI_USB3FUS) >> 3) & 3) {
+	case 3:
+		return 0;
+	case 2:
+		return 2;
+	case 1:
+		return 4;
+	case 0:
+	default:
+		return 6;
+	}
+}
+
+static void common_ehci_hcs_init(void)
+{
+	const bool is_lp = CONFIG(INTEL_LYNXPOINT_LP);
+
+	ehci_hcs_init(PCH_EHCI1_DEV, PCH_EHCI1_TEMP_BAR0);
+	if (!is_lp)
+		ehci_hcs_init(PCH_EHCI2_DEV, PCH_EHCI2_TEMP_BAR0);
+
+	pch_iobp_update(0xe5007f04, 0, 0x00004481);
+
+	for (unsigned int port = 0; port < physical_port_count(); port++)
+		pch_iobp_update(0xe500400f + port * 0x100, ~(1 << 0), 0 << 0);
+
+	pch_iobp_update(0xe5007f14, ~(3 << 19), (3 << 19));
+
+	if (is_lp)
+		pch_iobp_update(0xe5007f02, ~(3 << 22), (0 << 22));
+}
+
+static void xhci_open_memory_space(void)
+{
+	/** FIXME: Determine whether Bus Master is required (or clean it up afterwards) **/
+	pci_write_config32(PCH_XHCI_DEV, PCI_BASE_ADDRESS_0, (uintptr_t)xhci_bar);
+	pci_or_config16(PCH_XHCI_DEV, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY);
+}
+
+static void xhci_close_memory_space(void)
+{
+	pci_and_config16(PCH_XHCI_DEV, PCI_COMMAND, ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY));
+	pci_write_config32(PCH_XHCI_DEV, PCI_BASE_ADDRESS_0, 0);
+}
+
+static void common_xhci_hc_init(void)
+{
+	const bool is_lp = CONFIG(INTEL_LYNXPOINT_LP);
+
+	if (!is_lp) {
+		const unsigned int max_ports = 15 + ss_port_count();
+		clrsetbits32(xhci_bar + XHCI_HCS_PARAMS_1, 0xf << 28, max_ports << 28);
+	}
+
+	clrsetbits32(xhci_bar + XHCI_HCS_PARAMS_3, 0xffff << 16 | 0xff, 0x200 << 16 | 0x0a);
+	clrsetbits32(xhci_bar + XHCI_HCC_PARAMS, BIT(5), BIT(10) | BIT(9));
+
+	if (!is_lp)
+		clrsetbits32(xhci_bar + 0x8008, BIT(19), 0);
+
+	if (is_lp)
+		clrsetbits32(xhci_bar + 0x8058, BIT(8), BIT(16));
+	else
+		clrsetbits32(xhci_bar + 0x8058, BIT(8), BIT(16) | BIT(20));
+
+	clrsetbits32(xhci_bar + 0x8060, 0, BIT(25) | BIT(18));
+	clrsetbits32(xhci_bar + 0x8090, 0, BIT(14) | BIT(8));
+	clrsetbits32(xhci_bar + 0x8094, 0, BIT(23) | BIT(21) | BIT(14));
+	clrsetbits32(xhci_bar + 0x80e0, BIT(16), BIT(6));
+	clrsetbits32(xhci_bar + 0x80ec, (7 << 12) | (7 << 9), (0 << 12) | (6 << 9));
+	clrsetbits32(xhci_bar + 0x80f0, BIT(20), 0);
+
+	if (is_lp)
+		clrsetbits32(xhci_bar + 0x80fc, 0, BIT(25));
+
+	if (is_lp)
+		clrsetbits32(xhci_bar + 0x8110, BIT(8) | BIT(2), BIT(20) | BIT(11));
+	else
+		clrsetbits32(xhci_bar + 0x8110, BIT(2), BIT(20) | BIT(11));
+
+	if (is_lp)
+		write32(xhci_bar + 0x8140, 0xff00f03c);
+	else
+		write32(xhci_bar + 0x8140, 0xff03c132);
+
+	if (is_lp)
+		clrsetbits32(xhci_bar + 0x8154, BIT(21), BIT(13));
+	else
+		clrsetbits32(xhci_bar + 0x8154, BIT(21) | BIT(13), 0);
+
+	clrsetbits32(xhci_bar + 0x8154, BIT(3), 0);
+
+	if (is_lp) {
+		clrsetbits32(xhci_bar + 0x8164, 0, BIT(1) | BIT(0));
+		write32(xhci_bar + 0x8174, 0x01400c0a);
+		write32(xhci_bar + 0x817c, 0x033200a3);
+		write32(xhci_bar + 0x8180, 0x00cb0028);
+		write32(xhci_bar + 0x8184, 0x0064001e);
+	}
+
+	/*
+	 * Note: Register at offset 0x44 is 32-bit, but bit 31 is write-once.
+	 * We use these weird partial accesses here to avoid locking bit 31.
+	 */
+	pci_or_config16(PCH_XHCI_DEV, 0x44, BIT(15) | BIT(14) | BIT(10) | BIT(0));
+	pci_or_config8(PCH_XHCI_DEV, 0x44 + 2, 0x0f);
+
+	/* LPT-LP >= B0 */
+	if (is_lp)
+		clrsetbits32(xhci_bar + 0x8188, 0, BIT(26) | BIT(24));
+
+	/* LPT-H >= C0 */
+	if (!is_lp)
+		clrsetbits32(xhci_bar + 0x8188, 0, BIT(24));
+}
+
+static inline bool is_mem_sr(void)
+{
+	return pci_read_config16(PCH_LPC_DEV, GEN_PMCON_2) & GEN_PMCON_2_MEM_SR;
+}
+
+static bool should_restore_xhci_smart_auto(void)
+{
+	if (!is_mem_sr())
+		return false;
+
+	return pci_read_config32(PCH_LPC_DEV, PMIR) & PMIR_XHCI_SMART_AUTO;
+}
+
+enum usb_port_route {
+	ROUTE_TO_EHCI,
+	ROUTE_TO_XHCI,
+};
+
+/* Returns whether port reset was successful */
+static bool reset_usb2_ports(const unsigned int ehci_ports)
+{
+	for (unsigned int port = 0; port < ehci_ports; port++) {
+		/* Initiate port reset for all USB2 ports */
+		clrsetbits32(
+			xhci_bar + XHCI_USB2_PORTSC(port),
+			XHCI_USB2_PORTSC_PED,
+			XHCI_USB2_PORTSC_PR);
+	}
+	/* Poll for port reset bit to be cleared or time out at 100ms */
+	struct stopwatch timer;
+	stopwatch_init_msecs_expire(&timer, 100);
+	uint32_t reg32;
+	do {
+		reg32 = 0;
+		for (unsigned int port = 0; port < ehci_ports; port++)
+			reg32 |= read32(xhci_bar + XHCI_USB2_PORTSC(port));
+
+		reg32 &= XHCI_USB2_PORTSC_PR;
+		if (!reg32) {
+			const long elapsed_time = stopwatch_duration_usecs(&timer);
+			printk(BIOS_DEBUG, "%s: took %lu usecs\n", __func__, elapsed_time);
+			return true;
+		}
+		/* Reference code has a 10 ms delay here, but a smaller delay works too */
+		udelay(100);
+	} while (!stopwatch_expired(&timer));
+	printk(BIOS_ERR, "%s: timed out\n", __func__);
+	return !reg32;
+}
+
+/* Returns whether warm reset was successful */
+static bool warm_reset_usb3_ports(const unsigned int xhci_ports)
+{
+	for (unsigned int port = 0; port < xhci_ports; port++) {
+		/* Initiate warm reset for all USB3 ports */
+		clrsetbits32(
+			xhci_bar + XHCI_USB3_PORTSC(port),
+			XHCI_USB3_PORTSC_PED,
+			XHCI_USB3_PORTSC_WPR);
+	}
+	/* Poll for port reset bit to be cleared or time out at 100ms */
+	struct stopwatch timer;
+	stopwatch_init_msecs_expire(&timer, 100);
+	uint32_t reg32;
+	do {
+		reg32 = 0;
+		for (unsigned int port = 0; port < xhci_ports; port++)
+			reg32 |= read32(xhci_bar + XHCI_USB3_PORTSC(port));
+
+		reg32 &= XHCI_USB3_PORTSC_PR;
+		if (!reg32) {
+			const long elapsed_time = stopwatch_duration_usecs(&timer);
+			printk(BIOS_DEBUG, "%s: took %lu usecs\n", __func__, elapsed_time);
+			return true;
+		}
+		/* Reference code has a 10 ms delay here, but a smaller delay works too */
+		udelay(100);
+	} while (!stopwatch_expired(&timer));
+	printk(BIOS_ERR, "%s: timed out\n", __func__);
+	return !reg32;
+}
+
+static void perform_xhci_ehci_switching_flow(const enum usb_port_route usb_route)
+{
+	const pci_devfn_t dev = PCH_XHCI_DEV;
+
+	const unsigned int ehci_ports = hs_port_count() + is_usbr_enabled();
+	const unsigned int xhci_ports = ss_port_count();
+
+	const uint32_t ehci_mask = BIT(ehci_ports) - 1;
+	const uint32_t xhci_mask = BIT(xhci_ports) - 1;
+
+	/** TODO: Handle USBr port? How, though? **/
+	pci_update_config32(dev, XHCI_USB2PRM, ~XHCI_USB2PR_HCSEL, ehci_mask);
+	pci_update_config32(dev, XHCI_USB3PRM, ~XHCI_USB3PR_SSEN,  xhci_mask);
+
+	/*
+	 * Workaround for USB2PR / USB3PR value not surviving warm reset.
+	 * Restore USB Port Routing registers if OS HC Switch driver has been executed.
+	 */
+	if (should_restore_xhci_smart_auto()) {
+		/** FIXME: Derive values from mainboard code instead? **/
+		pci_update_config32(dev, XHCI_USB2PR, ~XHCI_USB2PR_HCSEL, ehci_mask);
+		pci_update_config32(dev, XHCI_USB3PR, ~XHCI_USB3PR_SSEN,  xhci_mask);
+	}
+
+	/* Later stages shouldn't need the value of this bit */
+	pci_and_config32(PCH_LPC_DEV, PMIR, ~PMIR_XHCI_SMART_AUTO);
+
+	/**
+	 * FIXME: Things here depend on the chosen routing mode.
+	 *        For now, implement both functions.
+	 */
+
+	/* Route to EHCI if xHCI disabled or auto mode */
+	if (usb_route == ROUTE_TO_EHCI) {
+		if (!reset_usb2_ports(ehci_ports))
+			printk(BIOS_ERR, "USB2 port reset timed out\n");
+
+		pci_and_config32(dev, XHCI_USB2PR, ~XHCI_USB2PR_HCSEL);
+
+		for (unsigned int port = 0; port < ehci_ports; port++) {
+			clrsetbits32(
+				xhci_bar + XHCI_USB2_PORTSC(port),
+				XHCI_USB2_PORTSC_PED,
+				XHCI_USB2_PORTSC_CHST);
+		}
+
+		if (!warm_reset_usb3_ports(xhci_ports))
+			printk(BIOS_ERR, "USB3 warm reset timed out\n");
+
+		/* FIXME: BWG says this should be inside the warm reset function */
+		pci_and_config32(dev, XHCI_USB3PR, ~XHCI_USB3PR_SSEN);
+
+		for (unsigned int port = 0; port < ehci_ports; port++) {
+			clrsetbits32(
+				xhci_bar + XHCI_USB3_PORTSC(port),
+				XHCI_USB3_PORTSC_PED,
+				XHCI_USB3_PORTSC_CHST);
+		}
+
+		setbits32(xhci_bar + XHCI_USBCMD, BIT(0));
+		clrbits32(xhci_bar + XHCI_USBCMD, BIT(0));
+	}
+
+	/* Route to xHCI if xHCI enabled */
+	if (usb_route == ROUTE_TO_XHCI) {
+		if (is_mem_sr()) {
+			if (!warm_reset_usb3_ports(xhci_ports))
+				printk(BIOS_ERR, "USB3 warm reset timed out\n");
+		}
+
+		const uint32_t xhci_port_mask = pci_read_config32(dev, XHCI_USB3PRM) & 0x3f;
+		pci_update_config32(dev, XHCI_USB3PR, ~XHCI_USB3PR_SSEN, xhci_port_mask);
+
+		const uint32_t ehci_port_mask = pci_read_config32(dev, XHCI_USB2PRM) & 0x7fff;
+		pci_update_config32(dev, XHCI_USB2PR, ~XHCI_USB2PR_HCSEL, ehci_port_mask);
+	}
+}
+
+/* Do not shift in this macro, as it can cause undefined behaviour for bad port/oc values */
+#define PORT_TO_OC_SHIFT(port, oc)	((oc) * 8 + (port))
+
+/* Avoid shifting into undefined behaviour */
+static inline bool shift_ok(const int shift)
+{
+	return shift >= 0 && shift < 32;
+}
+
+static void usb_overcurrent_mapping(void)
+{
+	const bool is_lp = CONFIG(INTEL_LYNXPOINT_LP);
+
+	uint32_t ehci_1_ocmap = 0;
+	uint32_t ehci_2_ocmap = 0;
+	uint32_t xhci_1_ocmap = 0;
+	uint32_t xhci_2_ocmap = 0;
+
+	/*
+	 * EHCI
+	 */
+	for (unsigned int idx = 0; idx < physical_port_count(); idx++) {
+		const struct usb2_port_config *const port = &mainboard_usb2_ports[idx];
+		printk(BIOS_DEBUG, "USB2 port %u => ", idx);
+		if (!port->enable) {
+			printk(BIOS_DEBUG, "disabled\n");
+			continue;
+		}
+		const unsigned short oc_pin = port->oc_pin;
+		if (oc_pin == USB_OC_PIN_SKIP) {
+			printk(BIOS_DEBUG, "not mapped to OC pin\n");
+			continue;
+		}
+		/* Ports 0 .. 7 => OC 0 .. 3 */
+		if (idx < 8 && oc_pin <= 3) {
+			const int shift = PORT_TO_OC_SHIFT(idx, oc_pin);
+			if (shift_ok(shift)) {
+				printk(BIOS_DEBUG, "mapped to OC pin %u\n", oc_pin);
+				ehci_1_ocmap |= 1 << shift;
+				continue;
+			}
+		}
+		/* Ports 8 .. 13 => OC 4 .. 7 (LPT-H only) */
+		if (!is_lp && idx >= 8 && oc_pin >= 4) {
+			const int shift = PORT_TO_OC_SHIFT(idx, oc_pin - 4);
+			if (shift_ok(shift)) {
+				printk(BIOS_DEBUG, "mapped to OC pin %u\n", oc_pin);
+				ehci_2_ocmap |= 1 << shift;
+				continue;
+			}
+		}
+		printk(BIOS_ERR, "Invalid OC pin %u for USB2 port %u\n", oc_pin, idx);
+	}
+	printk(BIOS_DEBUG, "\n");
+	pci_write_config32(PCH_EHCI1_DEV, EHCI_OCMAP, ehci_1_ocmap);
+	if (!is_lp)
+		pci_write_config32(PCH_EHCI2_DEV, EHCI_OCMAP, ehci_2_ocmap);
+
+	/*
+	 * xHCI
+	 */
+	for (unsigned int idx = 0; idx < ss_port_count(); idx++) {
+		const struct usb3_port_config *const port = &mainboard_usb3_ports[idx];
+		printk(BIOS_DEBUG, "USB3 port %u => ", idx);
+		if (!port->enable) {
+			printk(BIOS_DEBUG, "disabled\n");
+			continue;
+		}
+		const unsigned short oc_pin = port->oc_pin;
+		if (oc_pin == USB_OC_PIN_SKIP) {
+			printk(BIOS_DEBUG, "not mapped to OC pin\n");
+			continue;
+		}
+		/* Ports 0 .. 5 => OC 0 .. 3 */
+		if (oc_pin <= 3) {
+			const int shift = PORT_TO_OC_SHIFT(idx, oc_pin);
+			if (shift_ok(shift)) {
+				printk(BIOS_DEBUG, "mapped to OC pin %u\n", oc_pin);
+				xhci_1_ocmap |= 1 << shift;
+				continue;
+			}
+		}
+		/* Ports 0 .. 5 => OC 4 .. 7 (LPT-H only) */
+		if (!is_lp && oc_pin >= 4) {
+			const int shift = PORT_TO_OC_SHIFT(idx, oc_pin - 4);
+			if (shift_ok(shift)) {
+				printk(BIOS_DEBUG, "mapped to OC pin %u\n", oc_pin);
+				xhci_2_ocmap |= 1 << shift;
+				continue;
+			}
+		}
+		printk(BIOS_ERR, "Invalid OC pin %u for USB3 port %u\n", oc_pin, idx);
+	}
+	printk(BIOS_DEBUG, "\n");
+	pci_write_config32(PCH_XHCI_DEV, XHCI_U2OCM1, ehci_1_ocmap);
+	pci_write_config32(PCH_XHCI_DEV, XHCI_U3OCM1, xhci_1_ocmap);
+	if (!is_lp) {
+		pci_write_config32(PCH_XHCI_DEV, XHCI_U2OCM2, ehci_2_ocmap);
+		pci_write_config32(PCH_XHCI_DEV, XHCI_U3OCM2, xhci_2_ocmap);
+	}
+}
+
+static uint8_t get_ehci_tune_param_1(const struct usb2_port_config *const port)
+{
+	const bool is_lp = CONFIG(INTEL_LYNXPOINT_LP);
+
+	const enum pch_platform_type plat_type = get_pch_platform_type();
+	const enum usb2_port_location location = port->location;
+	const uint16_t length = port->length;
+	if (!is_lp) {
+		if (plat_type == PCH_TYPE_DESKTOP) {
+			if (location == USB_PORT_BACK_PANEL)
+				return 4; /* Back Panel */
+			else
+				return 3; /* Front Panel */
+
+		} else if (plat_type == PCH_TYPE_MOBILE) {
+			if (location == USB_PORT_INTERNAL)
+				return 5; /* Internal Topology */
+			else if (location == USB_PORT_DOCK)
+				return 4; /* Dock */
+			else if (length < 0x70)
+				return 5; /* Back Panel, less than 7" */
+			else
+				return 6; /* Back Panel, 7" or more */
+		}
+	} else {
+		if (location == USB_PORT_BACK_PANEL || location == USB_PORT_MINI_PCIE) {
+			if (length < 0x70)
+				return 5; /* Back Panel, less than 7" */
+			else
+				return 6; /* Back Panel, 7" or more */
+		} else if (location == USB_PORT_DOCK) {
+			return 4; /* Dock */
+		} else {
+			return 5; /* Internal Topology */
+		}
+	}
+	printk(BIOS_ERR, "%s: Unhandled case\n", __func__);
+	return 0;
+}
+
+static uint8_t get_ehci_tune_param_2(const struct usb2_port_config *const port)
+{
+	const bool is_lp = CONFIG(INTEL_LYNXPOINT_LP);
+
+	const enum pch_platform_type plat_type = get_pch_platform_type();
+	const enum usb2_port_location location = port->location;
+	const uint16_t length = port->length;
+	if (!is_lp) {
+		if (plat_type == PCH_TYPE_DESKTOP) {
+			if (location == USB_PORT_BACK_PANEL) {
+				if (length < 0x80)
+					return 2; /* Back Panel, less than 8" */
+				else if (length < 0x130)
+					return 3; /* Back Panel, 8"-13" */
+				else
+					return 4; /* Back Panel, 13" or more */
+			} else {
+				return 2; /* Front Panel */
+			}
+
+		} else if (plat_type == PCH_TYPE_MOBILE) {
+			if (location == USB_PORT_INTERNAL) {
+				return 2; /* Internal Topology */
+			} else if (location == USB_PORT_DOCK) {
+				if (length < 0x50)
+					return 1; /* Dock, less than 5" */
+				else
+					return 2; /* Dock, 5" or more */
+			} else {
+				if (length < 0x100)
+					return 2; /* Back Panel, less than 10" */
+				else
+					return 3; /* Back Panel, 10" or more */
+			}
+		}
+	} else {
+		if (location == USB_PORT_BACK_PANEL || location == USB_PORT_MINI_PCIE) {
+			if (length < 0x100)
+				return 2; /* Back Panel, less than 10" */
+			else
+				return 3; /* Back Panel, 10" or more */
+		} else if (location == USB_PORT_DOCK) {
+			if (length < 0x50)
+				return 1; /* Dock, less than 5" */
+			else
+				return 2; /* Dock, 5" or more */
+		} else {
+			return 2; /* Internal Topology */
+		}
+	}
+	printk(BIOS_ERR, "%s: Unhandled case\n", __func__);
+	return 0;
+}
+
+static void program_ehci_port_length(void)
+{
+	for (unsigned int port = 0; port < physical_port_count(); port++) {
+		if (!mainboard_usb2_ports[port].enable)
+			continue;
+		const uint32_t addr = 0xe5004000 + (port + 1) * 0x100;
+		const uint8_t param_1 = get_ehci_tune_param_1(&mainboard_usb2_ports[port]);
+		const uint8_t param_2 = get_ehci_tune_param_2(&mainboard_usb2_ports[port]);
+		pch_iobp_update(addr, ~0x7f00, param_2 << 11 | param_1 << 8);
+	}
+}
+
+void early_usb_init(void)
+{
+	/** TODO: Make this configurable? How do the modes affect usbdebug? **/
+	const enum usb_port_route usb_route = ROUTE_TO_XHCI;
+	///(pd->boot_mode == 2 && pd->usb_xhci_on_resume) ? ROUTE_TO_XHCI : ROUTE_TO_EHCI;
+
+	common_ehci_hcs_init();
+	xhci_open_memory_space();
+	common_xhci_hc_init();
+	perform_xhci_ehci_switching_flow(usb_route);
+	usb_overcurrent_mapping();
+	program_ehci_port_length();
+	/** FIXME: USB per port control is missing, is it needed? **/
+	xhci_close_memory_space();
+	/** TODO: Close EHCI memory space? **/
+}
diff --git a/src/southbridge/intel/lynxpoint/pch.h b/src/southbridge/intel/lynxpoint/pch.h
index f833706..d8fcf52 100644
--- a/src/southbridge/intel/lynxpoint/pch.h
+++ b/src/southbridge/intel/lynxpoint/pch.h
@@ -114,6 +114,7 @@
 
 void pch_dmi_setup_physical_layer(void);
 void pch_dmi_tc_vc_mapping(u32 vc0, u32 vc1, u32 vcp, u32 vcm);
+void early_usb_init(void);
 
 void usb_ehci_sleep_prepare(pci_devfn_t dev, u8 slp_typ);
 void usb_ehci_disable(pci_devfn_t dev);
@@ -201,6 +202,8 @@
 #define GEN_PMCON_1		0xa0
 #define  SMI_LOCK		(1 << 4)
 #define GEN_PMCON_2		0xa2
+#define  GEN_PMCON_2_DISB	(1 << 7)
+#define  GEN_PMCON_2_MEM_SR	(1 << 5)
 #define  SYSTEM_RESET_STS	(1 << 4)
 #define  THERMTRIP_STS		(1 << 3)
 #define  SYSPWR_FLR		(1 << 1)
@@ -214,6 +217,7 @@
 #define PMIR			0xac
 #define  PMIR_CF9LOCK		(1 << 31)
 #define  PMIR_CF9GR		(1 << 20)
+#define  PMIR_XHCI_SMART_AUTO	(1 << 16) /* c.f. LPT BWG or WPT-LP BIOS spec */
 
 /* GEN_PMCON_3 bits */
 #define RTC_BATTERY_DEAD	(1 << 2)
@@ -281,6 +285,20 @@
 #define SATA_DTLE_DATA_SHIFT	24
 #define SATA_DTLE_EDGE_SHIFT	16
 
+/*
+ * HCD_INDEX == 2 selects 0:1a.0 (PCH_EHCI2), any other index
+ * selects 0:1d.0 (PCH_EHCI1) for usbdebug use.
+ */
+#if CONFIG_USBDEBUG_HCD_INDEX != 2
+#define PCH_EHCI1_TEMP_BAR0 CONFIG_EHCI_BAR
+#define PCH_EHCI2_TEMP_BAR0 (PCH_EHCI1_TEMP_BAR0 + 0x400)
+#else
+#define PCH_EHCI2_TEMP_BAR0 CONFIG_EHCI_BAR
+#define PCH_EHCI1_TEMP_BAR0 (PCH_EHCI2_TEMP_BAR0 + 0x400)
+#endif
+
+#define PCH_XHCI_TEMP_BAR0	0xe8100000
+
 /* EHCI PCI Registers */
 #define EHCI_PWR_CTL_STS	0x54
 #define  PWR_CTL_SET_MASK	0x3
@@ -288,10 +306,15 @@
 #define  PWR_CTL_SET_D3		0x3
 #define  PWR_CTL_ENABLE_PME	(1 << 8)
 #define  PWR_CTL_STATUS_PME	(1 << 15)
+#define EHCI_OCMAP		0x74
+#define EHCI_ACCESS_CNTL	0x80
+#define  ACCESS_CNTL_ENABLE	(1 << 0)
 
 /* EHCI Memory Registers */
+#define EHCI_HCS_PARAMS		0x04
 #define EHCI_USB_CMD		0x20
 #define  EHCI_USB_CMD_RUN	(1 << 0)
+#define  EHCI_USB_CMD_HCRESET	(1 << 1)
 #define  EHCI_USB_CMD_PSE	(1 << 4)
 #define  EHCI_USB_CMD_ASE	(1 << 5)
 #define EHCI_PORTSC(port)	(0x64 + (port) * 4)
@@ -300,6 +323,10 @@
 
 /* XHCI PCI Registers */
 #define XHCI_PWR_CTL_STS	0x74
+#define XHCI_U2OCM1		0xc0
+#define XHCI_U2OCM2		0xc4
+#define XHCI_U3OCM1		0xc8
+#define XHCI_U3OCM2		0xcc
 #define XHCI_USB2PR		0xd0
 #define XHCI_USB2PRM		0xd4
 #define  XHCI_USB2PR_HCSEL	0x7fff
@@ -312,6 +339,27 @@
 #define XHCI_USB3PDO		0xe8
 
 /* XHCI Memory Registers */
+#define XHCI_HCS_PARAMS_1	0x04
+#define XHCI_HCS_PARAMS_2	0x08
+#define XHCI_HCS_PARAMS_3	0x0c
+#define XHCI_HCC_PARAMS		0x10
+#define XHCI_USBCMD		0x80
+#define XHCI_USB2_PORTSC(port)	(0x480 + ((port) * 0x10))
+#define  XHCI_USB2_PORTSC_WPR	(1 << 31)	/* Warm Port Reset */
+#define  XHCI_USB2_PORTSC_CEC	(1 << 23)	/* Port Config Error Change */
+#define  XHCI_USB2_PORTSC_PLC	(1 << 22)	/* Port Link State Change */
+#define  XHCI_USB2_PORTSC_PRC	(1 << 21)	/* Port Reset Change */
+#define  XHCI_USB2_PORTSC_OCC	(1 << 20)	/* Over-current Change */
+#define  XHCI_USB2_PORTSC_WRC	(1 << 19)	/* Warm Port Reset Change */
+#define  XHCI_USB2_PORTSC_PEC	(1 << 18)	/* Port Enabled Disabled Change */
+#define  XHCI_USB2_PORTSC_CSC	(1 << 17)	/* Connect Status Change */
+#define  XHCI_USB2_PORTSC_CHST	(0x7f << 17)
+#define  XHCI_USB2_PORTSC_LWS	(1 << 16)	/* Port Link State Write Strobe */
+#define  XHCI_USB2_PORTSC_PP	(1 <<  9)
+#define  XHCI_USB2_PORTSC_PR	(1 <<  4)	/* Port Reset */
+#define  XHCI_USB2_PORTSC_PED	(1 <<  1)	/* Port Enable/Disabled */
+#define  XHCI_USB2_PORTSC_CCS	(1 <<  0)	/* Current Connect Status */
+
 #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 */
@@ -319,6 +367,7 @@
 #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_PR	(1 << 4)	/* Port Reset */
 #define  XHCI_USB3_PORTSC_PED	(1 << 1)	/* Port Enabled/Disabled */
 #define  XHCI_USB3_PORTSC_WPR	(1 << 31)	/* Warm Port Reset */
 #define  XHCI_USB3_PORTSC_PLS	(0xf << 5)	/* Port Link State */