feat(rpi5): add PCI SMCCC support

BCM2712 changes:
  - support all 3 PCIe RCs / segments.
  - don't check for link up: the RC can now be configured to fabricate
all-ones AXI OKAY responses, so no more Arm SErrors when the link is
down (or other conditions).

Also, limit bus 0 to devfn 0 as accesses beyond that may result in
lock-ups.

Change-Id: Ic64785cd68b22571c6638fc3f771703113bc76f6
Signed-off-by: Mario Bălănică <mariobalanica02@gmail.com>
diff --git a/plat/rpi/rpi4/rpi4_pci_svc.c b/plat/rpi/common/rpi_pci_svc.c
similarity index 76%
rename from plat/rpi/rpi4/rpi4_pci_svc.c
rename to plat/rpi/common/rpi_pci_svc.c
index e4ef5c1..c22f6d8 100644
--- a/plat/rpi/rpi4/rpi4_pci_svc.c
+++ b/plat/rpi/common/rpi_pci_svc.c
@@ -1,9 +1,10 @@
 /*
- * Copyright (c) 2021, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2021-2024, Arm Limited and Contributors. All rights reserved.
+ * Copyright (c) 2024, Mario Bălănică <mariobalanica02@gmail.com>
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
- * The RPi4 has a single nonstandard PCI config region. It is broken into two
+ * The RPi has a single nonstandard PCI config region. It is broken into two
  * pieces, the root port config registers and a window to a single device's
  * config space which can move between devices. There isn't (yet) an
  * authoritative public document on this since the available BCM2711 reference
@@ -29,62 +30,63 @@
 
 #include <lib/mmio.h>
 
-static spinlock_t pci_lock;
-
-#define PCIE_REG_BASE		U(RPI_IO_BASE + 0x01500000)
 #define PCIE_MISC_PCIE_STATUS	0x4068
 #define PCIE_EXT_CFG_INDEX	0x9000
-/* A small window pointing at the ECAM of the device selected by CFG_INDEX */
 #define PCIE_EXT_CFG_DATA	0x8000
+#define	PCIE_EXT_CFG_BDF_SHIFT	12
+
 #define INVALID_PCI_ADDR	0xFFFFFFFF
 
-#define	PCIE_EXT_BUS_SHIFT	20
-#define	PCIE_EXT_DEV_SHIFT	15
-#define	PCIE_EXT_FUN_SHIFT	12
+static spinlock_t pci_lock;
 
+static uint64_t pcie_rc_bases[] = { RPI_PCIE_RC_BASES };
 
 static uint64_t pci_segment_lib_get_base(uint32_t address, uint32_t offset)
 {
-	uint64_t	base;
-	uint32_t	bus, dev, fun;
-	uint32_t	status;
+	uint64_t base;
+	uint32_t seg, bus, dev, fun;
 
-	base = PCIE_REG_BASE;
+	seg = PCI_ADDR_SEG(address);
 
-	offset &= PCI_OFFSET_MASK;  /* Pick off the 4k register offset */
+	if (seg >= ARRAY_SIZE(pcie_rc_bases)) {
+		return INVALID_PCI_ADDR;
+	}
 
 	/* The root port is at the base of the PCIe register space */
-	if (address != 0U) {
-		/*
-		 * The current device must be at CFG_DATA, a 4K window mapped,
-		 * via CFG_INDEX, to the device we are accessing. At the same
-		 * time we must avoid accesses to certain areas of the cfg
-		 * space via CFG_DATA. Detect those accesses and report that
-		 * the address is invalid.
-		 */
-		base += PCIE_EXT_CFG_DATA;
-		bus = PCI_ADDR_BUS(address);
-		dev = PCI_ADDR_DEV(address);
-		fun = PCI_ADDR_FUN(address);
-		address = (bus << PCIE_EXT_BUS_SHIFT) |
-			  (dev << PCIE_EXT_DEV_SHIFT) |
-			  (fun << PCIE_EXT_FUN_SHIFT);
+	base = pcie_rc_bases[seg];
 
-		/* Allow only dev = 0 on root port and bus 1 */
-		if ((bus < 2U) && (dev > 0U)) {
-			return INVALID_PCI_ADDR;
-		}
+	bus = PCI_ADDR_BUS(address);
+	dev = PCI_ADDR_DEV(address);
+	fun = PCI_ADDR_FUN(address);
 
-		/* Assure link up before reading bus 1 */
-		status = mmio_read_32(PCIE_REG_BASE + PCIE_MISC_PCIE_STATUS);
-		if ((status & 0x30) != 0x30) {
+	/* There can only be the root port on bus 0 */
+	if ((bus == 0U) && ((dev > 0U) || (fun > 0U))) {
+		return INVALID_PCI_ADDR;
+	}
+
+	/* There can only be one device on bus 1 */
+	if ((bus == 1U) && (dev > 0U)) {
+		return INVALID_PCI_ADDR;
+	}
+
+	if (bus > 0) {
+#if RPI_PCIE_ECAM_SERROR_QUIRK
+		uint32_t status = mmio_read_32(base + PCIE_MISC_PCIE_STATUS);
+
+		/* Assure link up before accessing downstream of root port */
+		if ((status & 0x30) == 0U) {
 			return INVALID_PCI_ADDR;
 		}
-
-		/* Adjust which device the CFG_DATA window is pointing at */
-		mmio_write_32(PCIE_REG_BASE + PCIE_EXT_CFG_INDEX, address);
+#endif
+		/*
+		 * Device function is mapped at CFG_DATA, a 4 KB window
+		 * movable by writing its B/D/F location to CFG_INDEX.
+		 */
+		mmio_write_32(base + PCIE_EXT_CFG_INDEX, address << PCIE_EXT_CFG_BDF_SHIFT);
+		base += PCIE_EXT_CFG_DATA;
 	}
-	return base + offset;
+
+	return base + (offset & PCI_OFFSET_MASK);
 }
 
 /**
@@ -130,7 +132,7 @@
 			*val = mmio_read_32(base);
 			break;
 		default: /* should be unreachable */
-			*val = 0;
+			*val = 0U;
 			ret = SMC_PCI_CALL_INVAL_PARAM;
 		}
 	}
@@ -204,9 +206,12 @@
 uint32_t pci_get_bus_for_seg(uint32_t seg, uint32_t *bus_range, uint32_t *nseg)
 {
 	uint32_t ret = SMC_PCI_CALL_SUCCESS;
-	*nseg = 0U; /* only a single segment */
-	if (seg == 0U) {
-		*bus_range = 0xFF00; /* start 0, end 255 */
+	uint32_t rc_count = ARRAY_SIZE(pcie_rc_bases);
+
+	*nseg = (seg < rc_count - 1U) ? seg + 1U : 0U;
+
+	if (seg < rc_count) {
+		*bus_range = 0U + (0xFF << 8); /* start 0, end 255 */
 	} else {
 		*bus_range = 0U;
 		ret = SMC_PCI_CALL_NOT_IMPL;
diff --git a/plat/rpi/rpi4/include/rpi_hw.h b/plat/rpi/rpi4/include/rpi_hw.h
index 8162492..53ce0f8 100644
--- a/plat/rpi/rpi4/include/rpi_hw.h
+++ b/plat/rpi/rpi4/include/rpi_hw.h
@@ -69,4 +69,11 @@
 #define	RPI4_LOCAL_CONTROL_BASE_ADDRESS		ULL(0xff800000)
 #define	RPI4_LOCAL_CONTROL_PRESCALER		ULL(0xff800008)
 
+/*
+ * PCI Express
+ */
+#define RPI_PCIE_RC_BASES		(RPI_IO_BASE + ULL(0x01500000))
+
+#define RPI_PCIE_ECAM_SERROR_QUIRK	1
+
 #endif /* RPI_HW_H */
diff --git a/plat/rpi/rpi4/platform.mk b/plat/rpi/rpi4/platform.mk
index f17911f..cbfa6f2 100644
--- a/plat/rpi/rpi4/platform.mk
+++ b/plat/rpi/rpi4/platform.mk
@@ -113,5 +113,5 @@
 endif
 
 ifeq ($(SMC_PCI_SUPPORT), 1)
-BL31_SOURCES            +=      plat/rpi/rpi4/rpi4_pci_svc.c
+BL31_SOURCES            +=      plat/rpi/common/rpi_pci_svc.c
 endif
diff --git a/plat/rpi/rpi5/include/rpi_hw.h b/plat/rpi/rpi5/include/rpi_hw.h
index 384542e..a737676 100644
--- a/plat/rpi/rpi5/include/rpi_hw.h
+++ b/plat/rpi/rpi5/include/rpi_hw.h
@@ -48,4 +48,11 @@
 #define	RPI4_LOCAL_CONTROL_BASE_ADDRESS		(RPI_IO_BASE + ULL(0x7c280000))
 #define	RPI4_LOCAL_CONTROL_PRESCALER		(RPI_IO_BASE + ULL(0x7c280008))
 
+/*
+ * PCI Express
+ */
+#define RPI_PCIE_RC_BASES		RPI_IO_BASE + ULL(0x00100000), \
+					RPI_IO_BASE + ULL(0x00110000), \
+					RPI_IO_BASE + ULL(0x00120000)
+
 #endif /* RPI_HW_H */
diff --git a/plat/rpi/rpi5/platform.mk b/plat/rpi/rpi5/platform.mk
index 81b7ded..70c5add 100644
--- a/plat/rpi/rpi5/platform.mk
+++ b/plat/rpi/rpi5/platform.mk
@@ -86,6 +86,9 @@
 # Use normal memory mapping for ROM, FIP, SRAM and DRAM
 RPI3_USE_UEFI_MAP		:= 0
 
+# SMCCC PCI support (should be enabled for ACPI builds)
+SMC_PCI_SUPPORT			:= 0
+
 # Process platform flags
 # ----------------------
 
@@ -96,6 +99,7 @@
 endif
 $(eval $(call add_define,RPI3_RUNTIME_UART))
 $(eval $(call add_define,RPI3_USE_UEFI_MAP))
+$(eval $(call add_define,SMC_PCI_SUPPORT))
 
 ifeq (${ARCH},aarch32)
   $(error Error: AArch32 not supported on rpi5)
@@ -105,3 +109,7 @@
 PLAT_BL_COMMON_SOURCES	+=	drivers/rpi3/rng/rpi3_rng.c		\
 				plat/rpi/common/rpi3_stack_protector.c
 endif
+
+ifeq ($(SMC_PCI_SUPPORT), 1)
+BL31_SOURCES		+=	plat/rpi/common/rpi_pci_svc.c
+endif