soc/amd/common/xhci: Add support for logging XHCI wake events

AMD SoCs currently only log the GPE# when an XHCI controller wakes the
system. Add code to log XHCI wake events to the elog.

BRANCH=guybrush
BUG=b:186792595
TEST=builds

Change-Id: Ic0489e1df55c4e63cb8a306099e3f31c82eebd58
Signed-off-by: Robert Zieba <robertzieba@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/67936
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Karthik Ramasubramanian <kramasub@google.com>
diff --git a/src/soc/amd/common/block/acpi/elog.c b/src/soc/amd/common/block/acpi/elog.c
index b9268aa..85e4f93 100644
--- a/src/soc/amd/common/block/acpi/elog.c
+++ b/src/soc/amd/common/block/acpi/elog.c
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 
 #include <amdblocks/acpi.h>
+#include <amdblocks/xhci.h>
 #include <elog.h>
 #include <soc/southbridge.h>
 
@@ -26,9 +27,16 @@
 	int i;
 	uint32_t valid_gpe = state->gpe0_sts & state->gpe0_en;
 
+	if (!ENV_SMM)
+		return;
+
 	for (i = 0; i <= 31; i++) {
-		if (valid_gpe & (1U << i))
+		if (valid_gpe & (1U << i)) {
 			elog_add_event_wake(ELOG_WAKE_SOURCE_GPE, i);
+
+			if (CONFIG(SOC_AMD_COMMON_BLOCK_XHCI_ELOG) && i == XHCI_GEVENT)
+				soc_xhci_log_wake_events();
+		}
 	}
 }
 
diff --git a/src/soc/amd/common/block/include/amdblocks/xhci.h b/src/soc/amd/common/block/include/amdblocks/xhci.h
new file mode 100644
index 0000000..6d1bc26
--- /dev/null
+++ b/src/soc/amd/common/block/include/amdblocks/xhci.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef AMD_BLOCK_XHCI_H
+#define AMD_BLOCK_XHCI_H
+
+#include <cpu/x86/smm.h>
+#include <device/pci_type.h>
+#include <device/pci_def.h>
+#include <device/xhci.h>
+
+#include <types.h>
+
+#define XHCI_GEVENT GEVENT_31
+
+#define SOC_XHCI_DEVICES {\
+	SOC_XHCI_0,\
+	SOC_XHCI_1,\
+	SOC_XHCI_2,\
+	SOC_XHCI_3,\
+	SOC_XHCI_4,\
+	SOC_XHCI_5,\
+	SOC_XHCI_6,\
+	SOC_XHCI_7,\
+}
+
+void soc_xhci_store_resources(struct smm_pci_resource_info *slots, size_t count);
+void soc_xhci_log_wake_events(void);
+
+#endif /* AMD_BLOCK_XHCI_H */
diff --git a/src/soc/amd/common/block/xhci/Kconfig b/src/soc/amd/common/block/xhci/Kconfig
new file mode 100644
index 0000000..ba9a8e8
--- /dev/null
+++ b/src/soc/amd/common/block/xhci/Kconfig
@@ -0,0 +1,16 @@
+config SOC_AMD_COMMON_BLOCK_XHCI
+	bool
+	help
+	  Select this option to use AMD common XHCI support.
+
+if SOC_AMD_COMMON_BLOCK_XHCI
+
+config SOC_AMD_COMMON_BLOCK_XHCI_ELOG
+	bool
+	default y
+	depends on ELOG
+	select SMM_PCI_RESOURCE_STORE
+	help
+	  Enables logging of XHCI events in the elog
+
+endif
diff --git a/src/soc/amd/common/block/xhci/Makefile.inc b/src/soc/amd/common/block/xhci/Makefile.inc
new file mode 100644
index 0000000..30f79b7
--- /dev/null
+++ b/src/soc/amd/common/block/xhci/Makefile.inc
@@ -0,0 +1,2 @@
+ramstage-$(CONFIG_SOC_AMD_COMMON_BLOCK_XHCI)    += xhci.c
+smm-$(CONFIG_SOC_AMD_COMMON_BLOCK_XHCI_ELOG)    += elog.c
diff --git a/src/soc/amd/common/block/xhci/elog.c b/src/soc/amd/common/block/xhci/elog.c
new file mode 100644
index 0000000..64a2c48
--- /dev/null
+++ b/src/soc/amd/common/block/xhci/elog.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <amdblocks/xhci.h>
+#include <console/console.h>
+#include <cpu/x86/smm.h>
+#include <device/pci_def.h>
+#include <device/pci_ids.h>
+#include <device/pci_ops.h>
+#include <device/pci_type.h>
+#include <device/xhci.h>
+#include <elog.h>
+
+#include <inttypes.h>
+
+#define PORTSC_OFFSET 0x400
+#define PORTSC_STRIDE 0x10
+#define XHCI_PROG_ID 0x30
+
+static void xhci_port_wake_check(uintptr_t base, uint8_t controller, uint8_t num, uint8_t event)
+{
+	for (uint8_t i = 0; i < num; i++) {
+		uint32_t portsc = read32p(base + i * PORTSC_STRIDE);
+
+		/* Encode the controller number and port number. */
+		uint32_t payload = controller << 8 | i;
+
+		/* Ensure that we've read a valid value. */
+		if (portsc == 0xffffffff)
+			continue;
+
+		/* Check for connect/disconnect wake. */
+		if (xhci_portsc_csc(portsc) && xhci_portsc_wake_capable(portsc)) {
+			elog_add_event_wake(event, payload);
+			continue;
+		}
+
+		if (xhci_portsc_plc(portsc) && xhci_portsc_resume(portsc))
+			elog_add_event_wake(event, payload);
+	}
+}
+
+struct xhci_context {
+	uintptr_t bar;
+	uint8_t controller;
+};
+
+static void xhci_cap_callback(void *data, const struct xhci_supported_protocol *protocol)
+{
+	const struct xhci_context *context = (const struct xhci_context *)data;
+	uint8_t count = protocol->port_count;
+	const struct xhci_capability_regs *cap_regs =
+		(const struct xhci_capability_regs *)context->bar;
+	uint8_t controller = context->controller;
+	/* PORTSC registers start at operational base + 0x400 + 0x10 * (n - 1). */
+	uintptr_t op_base = context->bar + cap_regs->caplength;
+	uintptr_t addr = op_base + PORTSC_OFFSET + PORTSC_STRIDE * (protocol->port_offset - 1);
+
+	switch (protocol->major_rev) {
+	case 2:
+		xhci_port_wake_check(addr, controller, count, ELOG_WAKE_SOURCE_PME_XHCI_USB_2);
+		break;
+
+	case 3:
+		xhci_port_wake_check(addr, controller, count, ELOG_WAKE_SOURCE_PME_XHCI_USB_3);
+		break;
+
+	default:
+		printk(BIOS_WARNING, "Skipping logging XHCI events for controller %u, unsupported protocol",
+		       controller);
+		break;
+	}
+}
+
+void soc_xhci_log_wake_events(void)
+{
+	const volatile struct smm_pci_resource_info *res_store;
+	size_t res_count;
+	uint8_t i_xhci = 0;
+
+	smm_pci_get_stored_resources(&res_store, &res_count);
+	for (size_t i_slot = 0; i_slot < res_count; i_slot++) {
+		/* Skip any non-XHCI controller devices. */
+		if (res_store[i_slot].class_device != PCI_CLASS_SERIAL_USB ||
+		    res_store[i_slot].class_prog != XHCI_PROG_ID) {
+			continue;
+		}
+
+		/* Validate our BAR. */
+		uintptr_t stored_bar = res_store[i_slot].resources[0].base;
+		uintptr_t bar = pci_s_read_config32(res_store[i_slot].pci_addr,
+						    PCI_BASE_ADDRESS_0);
+		bar &= ~PCI_BASE_ADDRESS_MEM_ATTR_MASK;
+
+		if (!stored_bar || !bar || bar != stored_bar) {
+			printk(BIOS_WARNING, "Skipping logging XHCI events for controller %u, resource error, stored %" PRIxPTR ", found %" PRIxPTR "\n",
+			       i_xhci, stored_bar, bar);
+			i_xhci++;
+			continue;
+		}
+
+		struct xhci_context context = {
+			.bar = bar,
+			.controller = i_xhci,
+		};
+
+		const struct resource *res = (const struct resource *) &res_store[i_slot].resources[0];
+		enum cb_err err
+			= xhci_resource_for_each_supported_usb_cap(res, &context,
+								   &xhci_cap_callback);
+		if (err)
+			printk(BIOS_ERR, "Failed to iterate over capabilities for XHCI controller %u (%d)\n",
+			       i_xhci, err);
+
+		i_xhci++;
+	}
+}
diff --git a/src/soc/amd/common/block/xhci/xhci.c b/src/soc/amd/common/block/xhci/xhci.c
new file mode 100644
index 0000000..e5c431a
--- /dev/null
+++ b/src/soc/amd/common/block/xhci/xhci.c
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <amdblocks/xhci.h>
+#include <cpu/x86/smm.h>
+#include <device/device.h>
+#include <soc/xhci.h>
+
+void soc_xhci_store_resources(struct smm_pci_resource_info *slots, size_t count)
+{
+	const struct device *devices[] = SOC_XHCI_DEVICES;
+	size_t devices_count;
+
+	for (devices_count = 0; devices_count < ARRAY_SIZE(devices); devices_count++) {
+		if (!devices[devices_count])
+			break;
+	}
+
+	smm_pci_resource_store_fill_resources(slots, count, &devices[0], devices_count);
+}