feat(gicv5): add a GICv5 driver

Change-Id: I10e125c3866e50ed5adde2e4944245f47e50f2e6
Signed-off-by: Boyan Karatotev <boyan.karatotev@arm.com>
diff --git a/drivers/arm/gic/aarch64/gic_v5.c b/drivers/arm/gic/aarch64/gic_v5.c
new file mode 100644
index 0000000..0f8f5fa
--- /dev/null
+++ b/drivers/arm/gic/aarch64/gic_v5.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+
+#include <arch.h>
+#include <arch_features.h>
+#include <arch_helpers.h>
+#include <drivers/arm/gic_v5.h>
+#include <platform.h>
+
+#include <platform_def.h>
+
+/*
+ * Data structure to store the GIC per CPU context before entering
+ * a suspend to powerdown (with loss of context).
+ */
+struct gicv5_pcpu_ctx {
+	/* CPU IF registers. Only those currently in use */
+	u_register_t icc_cr0_el1;
+	/* PPI registers. We don't touch handling mode. */
+	u_register_t icc_ppi_enabler[2];
+	u_register_t icc_ppi_xpendr[2];
+	u_register_t icc_ppi_priorityr[16];
+};
+
+static uintptr_t irs_base;
+static struct gicv5_pcpu_ctx cpu_ctx[PLATFORM_CORE_COUNT];
+
+/* CPU MPIDR != GICv5 IAFFID. This holds the mapping, initialised on CPU_ON. */
+static uint16_t iaffids[PLATFORM_CORE_COUNT];
+
+/* the IST is a power of 2 since that's what goes in IRS_IST_CFGR.LPI_ID_BITS */
+struct l2_iste ist[next_power_of_2(PLATFORM_CORE_COUNT) * IRQ_NUM_SGIS];
+
+static inline uint8_t log2(uint32_t num)
+{
+	return (31 - __builtin_clz(num));
+}
+
+static inline bool is_interrupt(unsigned int interrupt_id)
+{
+	unsigned int_type = EXTRACT(INT_TYPE, interrupt_id);
+
+	return int_type == INT_PPI || int_type == INT_LPI || int_type == INT_SPI;
+}
+
+static inline u_register_t read_icc_ppi_priorityrn(unsigned n)
+{
+	switch (n) {
+	case 0:
+		return read_icc_ppi_priorityr0();
+	case 1:
+		return read_icc_ppi_priorityr1();
+	case 2:
+		return read_icc_ppi_priorityr2();
+	case 3:
+		return read_icc_ppi_priorityr3();
+	case 4:
+		return read_icc_ppi_priorityr4();
+	case 5:
+		return read_icc_ppi_priorityr5();
+	case 6:
+		return read_icc_ppi_priorityr6();
+	case 7:
+		return read_icc_ppi_priorityr7();
+	case 8:
+		return read_icc_ppi_priorityr8();
+	case 9:
+		return read_icc_ppi_priorityr9();
+	case 10:
+		return read_icc_ppi_priorityr10();
+	case 11:
+		return read_icc_ppi_priorityr11();
+	case 12:
+		return read_icc_ppi_priorityr12();
+	case 13:
+		return read_icc_ppi_priorityr13();
+	case 14:
+		return read_icc_ppi_priorityr14();
+	case 15:
+		return read_icc_ppi_priorityr15();
+	default:
+		panic();
+	}
+}
+
+static inline void write_icc_ppi_priorityrn(unsigned n, u_register_t val)
+{
+	switch (n) {
+	case 0:
+		return write_icc_ppi_priorityr0(val);
+	case 1:
+		return write_icc_ppi_priorityr1(val);
+	case 2:
+		return write_icc_ppi_priorityr2(val);
+	case 3:
+		return write_icc_ppi_priorityr3(val);
+	case 4:
+		return write_icc_ppi_priorityr4(val);
+	case 5:
+		return write_icc_ppi_priorityr5(val);
+	case 6:
+		return write_icc_ppi_priorityr6(val);
+	case 7:
+		return write_icc_ppi_priorityr7(val);
+	case 8:
+		return write_icc_ppi_priorityr8(val);
+	case 9:
+		return write_icc_ppi_priorityr9(val);
+	case 10:
+		return write_icc_ppi_priorityr10(val);
+	case 11:
+		return write_icc_ppi_priorityr11(val);
+	case 12:
+		return write_icc_ppi_priorityr12(val);
+	case 13:
+		return write_icc_ppi_priorityr13(val);
+	case 14:
+		return write_icc_ppi_priorityr14(val);
+	case 15:
+		return write_icc_ppi_priorityr15(val);
+	default:
+		panic();
+	}
+}
+
+bool is_gicv5_mode(void)
+{
+	return is_feat_gcie_supported();
+}
+
+bool gicv5_is_irq_spi(unsigned int irq_num)
+{
+	return EXTRACT(INT_TYPE, irq_num) == INT_SPI;
+}
+
+void gicv5_enable_cpuif(void)
+{
+	write_icc_cr0_el1(read_icc_cr0_el1() | ICC_CR0_EL1_EN_BIT);
+	/* make sure effects are visible */
+	isb();
+}
+
+void gicv5_setup_cpuif(void)
+{
+	iaffids[platform_get_core_pos(read_mpidr_el1())] = read_icc_iaffidr_el1();
+
+	write_icc_pcr_el1(GICV5_IDLE_PRIORITY);
+
+	gicv5_enable_cpuif();
+}
+
+void gicv5_disable_cpuif(void)
+{
+	write_icc_cr0_el1(read_icc_cr0_el1() & ~ICC_CR0_EL1_EN_BIT);
+	/* make sure effects are visible */
+	isb();
+}
+
+void gicv5_save_cpuif_context(void)
+{
+	unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+	cpu_ctx[core_pos].icc_cr0_el1 = read_icc_cr0_el1();
+	cpu_ctx[core_pos].icc_ppi_enabler[0] = read_icc_ppi_enabler0();
+	cpu_ctx[core_pos].icc_ppi_enabler[1] = read_icc_ppi_enabler1();
+
+	cpu_ctx[core_pos].icc_ppi_xpendr[0] = read_icc_ppi_spendr0();
+	cpu_ctx[core_pos].icc_ppi_xpendr[1] = read_icc_ppi_spendr1();
+
+	for (int i = 0; i < 15; i++) {
+		cpu_ctx[core_pos].icc_ppi_priorityr[i] = read_icc_ppi_priorityrn(i);
+	}
+
+	/* Make sure all PPIs are inactive, i.e. not suspending mid interrupt */
+	assert(read_icc_ppi_sactiver0() == 0UL && read_icc_ppi_sactiver1() == 0UL);
+}
+
+void gicv5_restore_cpuif_context(void)
+{
+	unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+	write_icc_ppi_enabler0(cpu_ctx[core_pos].icc_ppi_enabler[0]);
+	write_icc_ppi_enabler1(cpu_ctx[core_pos].icc_ppi_enabler[1]);
+
+	write_icc_ppi_spendr0(cpu_ctx[core_pos].icc_ppi_xpendr[0]);
+	write_icc_ppi_spendr1(cpu_ctx[core_pos].icc_ppi_xpendr[1]);
+	/* clear interrupts that shouldn't be pending */
+	write_icc_ppi_cpendr0(~cpu_ctx[core_pos].icc_ppi_xpendr[0]);
+	write_icc_ppi_cpendr1(~cpu_ctx[core_pos].icc_ppi_xpendr[1]);
+
+	for (int i = 0; i < 15; i++) {
+		write_icc_ppi_priorityrn(i, cpu_ctx[core_pos].icc_ppi_priorityr[i]);
+	}
+
+	/* don't bother saving it, just put the same value in */
+	write_icc_pcr_el1(GICV5_IDLE_PRIORITY);
+
+	write_icc_cr0_el1(cpu_ctx[core_pos].icc_cr0_el1);
+	/* make sure effects are visible */
+	isb();
+}
+
+void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority)
+{
+	unsigned irq_idx, irq_reg;
+	u_register_t priorityr;
+
+	assert(priority < (1UL << GICCDPRI_PRIORITY_WIDTH));
+	assert(is_interrupt(interrupt_id));
+
+	if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+		giccdpri(interrupt_id | INPLACE(GICCDPRI_PRIORITY, priority));
+		return;
+	}
+
+	/* it's a PPI, get rid of the INTR TYPE field */
+	interrupt_id = EXTRACT(INT_ID, interrupt_id);
+	irq_reg = interrupt_id / ICC_PPI_PRIORITYR_FIELD_NUM;
+	irq_idx = interrupt_id % ICC_PPI_PRIORITYR_FIELD_NUM;
+
+	priorityr = read_icc_ppi_priorityrn(irq_reg) &
+		    ICC_PPI_PRIORITYR_FIELD_MASK <<
+		    (irq_idx * ICC_PPI_PRIORITYR_FIELD_NUM);
+	write_icc_ppi_priorityrn(irq_reg, priorityr |
+			(priority << (irq_idx * ICC_PPI_PRIORITYR_FIELD_NUM)));
+}
+
+void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos)
+{
+	giccdpend(gicv5_get_sgi_num(sgi_id, core_pos) | GICCDPEND_PENDING_BIT);
+}
+
+void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos)
+{
+	assert(is_interrupt(interrupt_id));
+
+	/* PPIs are local to the CPU, can't be rerouted */
+	if (EXTRACT(INT_TYPE, interrupt_id) == INT_PPI) {
+		return;
+	}
+
+	/*
+	 * The expecation is that a core will be up (CPU_ON) before it gets
+	 * targetted by interrupts. Otherwise the IAFFID isn't available yet
+	 * and the interrupt will be misrouted.
+	 */
+	assert(iaffids[core_pos] != 0 || core_pos == 0);
+	giccdaff(INPLACE(GICCDAFF_IAFFID, iaffids[core_pos]) | interrupt_id);
+
+	/* wait for the target to take effect so retargetting an already
+	 * enabled interrupt ends up in the correct destination */
+	gsbsys();
+}
+
+void gicv5_intr_enable(unsigned int interrupt_id)
+{
+	unsigned int irq_idx;
+
+	assert(is_interrupt(interrupt_id));
+
+	if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+		giccden(interrupt_id);
+		return;
+	}
+
+	/* it's a PPI, get rid of the INTR TYPE field */
+	interrupt_id = EXTRACT(INT_ID, interrupt_id);
+	irq_idx = interrupt_id % ICC_PPI_ENABLER_FIELD_NUM;
+
+	if (interrupt_id / ICC_PPI_ENABLER_FIELD_NUM == 0) {
+		write_icc_ppi_enabler0(read_icc_ppi_enabler0() | (1UL << irq_idx));
+	} else {
+		write_icc_ppi_enabler1(read_icc_ppi_enabler1() | (1UL << irq_idx));
+	}
+}
+
+void gicv5_intr_disable(unsigned int interrupt_id)
+{
+	unsigned int irq_idx;
+
+	assert(is_interrupt(interrupt_id));
+
+	if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+		giccddis(interrupt_id);
+		/* wait for the interrupt to become disabled */
+		gsbsys();
+		return;
+	}
+
+	/* it's a PPI, get rid of the INTR TYPE field */
+	interrupt_id = EXTRACT(INT_ID, interrupt_id);
+	irq_idx = interrupt_id % ICC_PPI_ENABLER_FIELD_NUM;
+
+	if (interrupt_id / ICC_PPI_ENABLER_FIELD_NUM == 0) {
+		write_icc_ppi_enabler0(read_icc_ppi_enabler0() & ~(1UL << irq_idx));
+	} else {
+		write_icc_ppi_enabler1(read_icc_ppi_enabler1() & ~(1UL << irq_idx));
+	}
+}
+
+unsigned int gicv5_acknowledge_interrupt(void)
+{
+	u_register_t iar = gicrcdia();
+	assert((iar & GICRCDIA_VALID_BIT) != 0);
+
+	/* wait for the intr ack to complete (i.e. make it Active) and refetch
+	 * instructions so they don't operate on anything stale */
+	gsback();
+	isb();
+
+	return iar & ~GICRCDIA_VALID_BIT;
+}
+
+unsigned int gicv5_is_intr_pending(unsigned int interrupt_id)
+{
+	u_register_t icsr;
+	u_register_t ppi_spendr;
+
+	assert(is_interrupt(interrupt_id));
+
+	if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+		/* request interrupt information */
+		giccdrcfg(interrupt_id);
+		/* wait for the register to update */
+		isb();
+		icsr = read_icc_icsr_el1();
+
+		/* interrupt is unreachable, something has gone wrong */
+		assert((icsr & ICC_ICSR_EL1_F_BIT) == 0);
+		return !!(icsr & ICC_ICSR_EL1_PENDING_BIT);
+	}
+
+	/* it's a PPI, get rid of the INTR TYPE field */
+	interrupt_id = EXTRACT(INT_ID, interrupt_id);
+
+	if (interrupt_id / ICC_PPI_XPENDR_FIELD_NUM == 0) {
+		ppi_spendr = read_icc_ppi_spendr0();
+	} else {
+		ppi_spendr = read_icc_ppi_spendr1();
+	}
+
+	return !!(ppi_spendr & BIT(interrupt_id % ICC_PPI_XPENDR_FIELD_NUM));
+}
+
+void gicv5_intr_clear(unsigned int interrupt_id)
+{
+	unsigned int irq_idx;
+
+	assert(is_interrupt(interrupt_id));
+
+	if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+		giccdpend(interrupt_id);
+		return;
+	}
+
+	/* it's a PPI, get rid of the INTR TYPE field */
+	interrupt_id = EXTRACT(INT_ID, interrupt_id);
+	irq_idx = interrupt_id % ICC_PPI_XPENDR_FIELD_NUM;
+
+	if (interrupt_id / ICC_PPI_XPENDR_FIELD_NUM == 0) {
+		write_icc_ppi_cpendr0(read_icc_ppi_cpendr0() | (1UL << irq_idx));
+	} else {
+		write_icc_ppi_cpendr1(read_icc_ppi_cpendr1() | (1UL << irq_idx));
+	}
+}
+
+void gicv5_end_of_interrupt(unsigned int raw_iar)
+{
+	giccddi(raw_iar);
+	giccdeoi();
+	/* no isb as we won't interact with the gic before the eret */
+}
+
+/* currently a single IRS is expected and an ITS/IWB are not used */
+void gicv5_setup(void)
+{
+#if ENABLE_ASSERTIONS
+	uint32_t irs_idr2 = read_IRS_IDR2(irs_base);
+#endif
+	uint8_t id_bits = log2(ARRAY_SIZE(ist));
+	/* min_id_bits <= log2(length(ist)) <= id_bits */
+	assert(EXTRACT(IRS_IDR2_MIN_LPI_ID_BITS, irs_idr2) <= id_bits);
+	assert(EXTRACT(IRS_IDR2_ID_BITS, irs_idr2) >= id_bits);
+
+	/* make sure all ISTEs aren't enabled */
+	memset(ist, 0x0, sizeof(ist));
+
+	/* write zeroes throughout except LPI_ID_Bits which is the lowest 5 bits */
+	write_IRS_IST_CFGR(irs_base, id_bits);
+	/* make the IST valid */
+	write_IRS_IST_BASER(irs_base,
+			    (((uintptr_t) &ist) & MASK(IRS_IST_BASER_ADDR)) |
+			    IRS_IST_BASER_VALID_BIT);
+	WAIT_FOR_IDLE(irs_base, IRS_IST_STATUSR);
+
+	/* enable the IRS */
+	write_IRS_CR0(irs_base, IRS_CR0_IRSEN_BIT);
+	WAIT_FOR_IDLE(irs_base, IRS_CR0);
+}
+
+uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos)
+{
+	assert(index <= IRQ_NUM_SGIS);
+
+	return (core_pos * IRQ_NUM_SGIS + index) | INPLACE(INT_TYPE, INT_LPI);
+}
+
+void gicv5_init(uintptr_t irs_base_addr)
+{
+	irs_base = irs_base_addr;
+}
diff --git a/drivers/arm/gic/arm_gic.c b/drivers/arm/gic/arm_gic.c
index d3b2536..5fe8316 100644
--- a/drivers/arm/gic/arm_gic.c
+++ b/drivers/arm/gic/arm_gic.c
@@ -4,6 +4,8 @@
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
+#include <stdbool.h>
+
 #include <arch.h>
 #include <arch_helpers.h>
 #include <assert.h>
@@ -11,15 +13,32 @@
 #include <drivers/arm/gic_v2v3_common.h>
 #include <drivers/arm/gic_v2.h>
 #include <drivers/arm/gic_v3.h>
-#include <stdbool.h>
+#include <drivers/arm/gic_v5.h>
 
 /* Record whether a GICv3 was detected on the system */
 static unsigned int gicv3_detected;
+static unsigned int gicv5_detected;
+static bool gic_done_init;
+
+int arm_gic_get_version(void)
+{
+	assert(gic_done_init);
+
+	if (gicv3_detected) {
+		return 3;
+	} else if (gicv5_detected) {
+		return 5;
+	} else {
+		return 2;
+	}
+}
 
 void arm_gic_enable_interrupts_local(void)
 {
 	if (gicv3_detected)
 		gicv3_enable_cpuif();
+	else if (gicv5_detected)
+		gicv5_enable_cpuif();
 	else
 		gicv2_enable_cpuif();
 }
@@ -29,6 +48,8 @@
 	if (gicv3_detected) {
 		gicv3_probe_redistif_addr();
 		gicv3_setup_cpuif();
+	} else if (gicv5_detected) {
+		gicv5_setup_cpuif();
 	} else {
 		gicv2_probe_gic_cpu_id();
 		gicv2_setup_cpuif();
@@ -39,6 +60,8 @@
 {
 	if (gicv3_detected)
 		gicv3_disable_cpuif();
+	else if (gicv5_detected)
+		gicv5_disable_cpuif();
 	else
 		gicv2_disable_cpuif();
 }
@@ -47,6 +70,8 @@
 {
 	if (gicv3_detected)
 		gicv3_save_cpuif_context();
+	else if (gicv5_detected)
+		gicv5_save_cpuif_context();
 	else
 		gicv2_save_cpuif_context();
 }
@@ -55,16 +80,21 @@
 {
 	if (gicv3_detected)
 		gicv3_restore_cpuif_context();
+	else if (gicv5_detected)
+		gicv5_restore_cpuif_context();
 	else
 		gicv2_restore_cpuif_context();
 }
 
 void arm_gic_save_context_global(void)
 {
-	if (gicv3_detected)
+	if (gicv3_detected) {
 		gicv3_save_sgi_ppi_context();
-	else
+	} else if (gicv5_detected) {
+		/* NOP, done by EL3 */
+	} else {
 		gicv2_save_sgi_ppi_context();
+	}
 }
 
 void arm_gic_restore_context_global(void)
@@ -72,6 +102,8 @@
 	if (gicv3_detected) {
 		gicv3_setup_distif();
 		gicv3_restore_sgi_ppi_context();
+	} else if (gicv5_detected) {
+		/* NOP, done by EL3 */
 	} else {
 		gicv2_setup_distif();
 		gicv2_restore_sgi_ppi_context();
@@ -82,6 +114,8 @@
 {
 	if (gicv3_detected)
 		gicv3_setup_distif();
+	else if (gicv5_detected)
+		gicv5_setup();
 	else
 		gicv2_setup_distif();
 }
@@ -90,7 +124,11 @@
 {
 	if (gicv3_detected)
 		return gicv3_get_ipriorityr(num);
-	else
+	/* TODO only used for SDEI, currently not supported/ported */
+	else if (gicv5_detected) {
+		assert(0);
+		return 0;
+	} else
 		return gicv2_gicd_get_ipriorityr(num);
 }
 
@@ -99,6 +137,8 @@
 {
 	if (gicv3_detected)
 		gicv3_set_ipriorityr(num, priority);
+	else if (gicv5_detected)
+		gicv5_set_priority(num, priority);
 	else
 		gicv2_gicd_set_ipriorityr(num, priority);
 }
@@ -107,6 +147,8 @@
 {
 	if (gicv3_detected)
 		gicv3_send_sgi(sgi_id, core_pos);
+	else if (gicv5_detected)
+		gicv5_send_sgi(sgi_id, core_pos);
 	else
 		gicv2_send_sgi(sgi_id, core_pos);
 }
@@ -115,6 +157,8 @@
 {
 	if (gicv3_detected)
 		gicv3_set_intr_route(num, core_pos);
+	else if (gicv5_detected)
+		gicv5_set_intr_route(num, core_pos);
 	else
 		gicv2_set_itargetsr(num, core_pos);
 }
@@ -123,7 +167,11 @@
 {
 	if (gicv3_detected)
 		return gicv3_get_isenabler(num) != 0;
-	else
+	/* TODO only used for SDEI, currently not supported/ported */
+	else if (gicv5_detected) {
+		assert(0);
+		return 0;
+	} else
 		return gicv2_gicd_get_isenabler(num) != 0;
 }
 
@@ -131,6 +179,8 @@
 {
 	if (gicv3_detected)
 		gicv3_set_isenabler(num);
+	else if (gicv5_detected)
+		gicv5_intr_enable(num);
 	else
 		gicv2_gicd_set_isenabler(num);
 }
@@ -139,6 +189,8 @@
 {
 	if (gicv3_detected)
 		gicv3_set_icenabler(num);
+	else if (gicv5_detected)
+		gicv5_intr_disable(num);
 	else
 		gicv2_gicd_set_icenabler(num);
 }
@@ -150,6 +202,9 @@
 	if (gicv3_detected) {
 		*raw_iar = gicv3_acknowledge_interrupt();
 		return *raw_iar;
+	} else if (gicv5_detected) {
+		*raw_iar = gicv5_acknowledge_interrupt();
+		return *raw_iar;
 	} else {
 		*raw_iar = gicv2_gicc_read_iar();
 		return get_gicc_iar_intid(*raw_iar);
@@ -160,6 +215,8 @@
 {
 	if (gicv3_detected)
 		return gicv3_get_ispendr(num);
+	else if (gicv5_detected)
+		return gicv5_is_intr_pending(num);
 	else
 		return gicv2_gicd_get_ispendr(num);
 }
@@ -168,6 +225,8 @@
 {
 	if (gicv3_detected)
 		gicv3_set_icpendr(num);
+	else if (gicv5_detected)
+		gicv5_intr_clear(num);
 	else
 		gicv2_gicd_set_icpendr(num);
 }
@@ -176,28 +235,44 @@
 {
 	if (gicv3_detected)
 		gicv3_end_of_interrupt(raw_iar);
+	else if (gicv5_detected)
+		gicv5_end_of_interrupt(raw_iar);
 	else
 		gicv2_gicc_write_eoir(raw_iar);
 }
 
+void arm_gic_probe(void)
+{
+	if (is_gicv3_mode()) {
+		gicv3_detected = 1;
+		INFO("GICv3 mode detected\n");
+	} else if (is_gicv5_mode()) {
+		gicv5_detected = 1;
+		INFO("GICv5 mode detected\n");
+	} else {
+		INFO("GICv2 mode detected\n");
+	}
+
+	gic_done_init = true;
+}
+
+/* to not change the API, pretend the IRS is a distributor */
 void arm_gic_init(uintptr_t gicc_base,
 		uintptr_t gicd_base,
 		uintptr_t gicr_base)
 {
-
-	if (is_gicv3_mode()) {
-		gicv3_detected = 1;
+	if (gicv3_detected) {
 		gicv3_init(gicr_base, gicd_base);
+	} else if (gicv5_detected) {
+		gicv5_init(gicd_base);
 	} else {
 		gicv2_init(gicc_base, gicd_base);
 	}
-
-	INFO("%s mode detected\n", (gicv3_detected) ?
-			"GICv3" : "GICv2");
 }
 
 bool arm_gic_is_espi_supported(void)
 {
+	/* TODO only used for FFA, currently not supported/ported on GICv5 */
 	if (!gicv3_detected) {
 		return false;
 	}
diff --git a/include/drivers/arm/arm_gic.h b/include/drivers/arm/arm_gic.h
index 528ec6e..472f190 100644
--- a/include/drivers/arm/arm_gic.h
+++ b/include/drivers/arm/arm_gic.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -37,6 +37,9 @@
 #define GIC_LOWEST_NS_PRIORITY	254 /* 255 would disable an interrupt */
 #define GIC_SPURIOUS_INTERRUPT	1023
 
+/* return the GIC version detected */
+int arm_gic_get_version(void);
+
 /******************************************************************************
  * Setup the global GIC interface. In case of GICv2, it would be the GIC
  * Distributor and in case of GICv3 it would be GIC Distributor and
@@ -121,6 +124,11 @@
 void arm_gic_intr_clear(unsigned int num);
 
 /******************************************************************************
+ * Discover the GIC version in use.
+ *****************************************************************************/
+void arm_gic_probe(void);
+
+/******************************************************************************
  * Initialize the GIC Driver. This function will detect the GIC Architecture
  * present on the system and initialize the appropriate driver. The
  * `gicr_base` argument will be ignored on GICv2 systems.
diff --git a/include/drivers/arm/gic_v5.h b/include/drivers/arm/gic_v5.h
new file mode 100644
index 0000000..9c0b503
--- /dev/null
+++ b/include/drivers/arm/gic_v5.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __GIC_V5_H__
+#define __GIC_V5_H__
+
+#include <drivers/arm/arm_gic.h>
+#include <lib/mmio.h>
+
+#define INT_PPI					1
+#define INT_LPI					2
+#define INT_SPI					3
+
+#define GICV5_MAX_PPI_ID			128
+
+#define GICV5_IDLE_PRIORITY			0xff
+
+#define INT_TYPE_SHIFT				U(29)
+#define INT_TYPE_WIDTH				U(3)
+#define INT_ID_SHIFT				U(0)
+#define INT_ID_WIDTH				U(24)
+
+#define IRM_TARGETTED				0
+#define IRM_1OFN				1
+
+#define HM_EDGE					0
+#define HM_LEVEL				1
+
+struct l2_iste {
+	uint16_t iaffid;
+	uint8_t priority	: 5;
+	uint8_t hwu		: 2;
+	uint8_t res0		: 4;
+	uint8_t irm		: 1;
+	uint8_t enable		: 1;
+	uint8_t hm		: 1;
+	uint8_t active		: 1;
+	uint8_t pending		: 1;
+};
+
+#define DEFINE_GICV5_MMIO_WRITE_FUNC(_name, _offset)				\
+static inline void write_##_name(uintptr_t base, uint32_t val)			\
+{										\
+	mmio_write_32(base + _offset, val);					\
+}
+
+#define DEFINE_GICV5_MMIO_READ_FUNC(_name, _offset)				\
+static inline uint32_t read_##_name(uintptr_t base)				\
+{										\
+	return mmio_read_32(base + _offset);					\
+}
+
+#define DEFINE_GICV5_MMIO_RW_FUNCS(_name, _offset)				\
+	DEFINE_GICV5_MMIO_READ_FUNC(_name, _offset)				\
+	DEFINE_GICV5_MMIO_WRITE_FUNC(_name, _offset)
+
+#define IRS_IDR2_MIN_LPI_ID_BITS_WIDTH		UL(4)
+#define IRS_IDR2_MIN_LPI_ID_BITS_SHIFT		U(5)
+#define IRS_IDR2_ID_BITS_WIDTH			UL(5)
+#define IRS_IDR2_ID_BITS_SHIFT			U(0)
+#define IRS_CR0_IRSEN_BIT			BIT(0)
+#define IRS_CR0_IDLE_BIT			BIT(1)
+#define IRS_IST_STATUSR_IDLE_BIT		BIT(0)
+#define IRS_IST_BASER_VALID_BIT			BIT(0)
+#define IRS_IST_BASER_ADDR_SHIFT		6UL
+#define IRS_IST_BASER_ADDR_WIDTH		50UL
+
+DEFINE_GICV5_MMIO_READ_FUNC(IRS_IDR2,				0x0008)
+DEFINE_GICV5_MMIO_RW_FUNCS(  IRS_CR0,				0x0080)
+DEFINE_GICV5_MMIO_RW_FUNCS(  IRS_IST_BASER,			0x0180)
+DEFINE_GICV5_MMIO_RW_FUNCS(  IRS_IST_CFGR,			0x0190)
+DEFINE_GICV5_MMIO_RW_FUNCS(  IRS_IST_STATUSR,			0x0194)
+
+#define WAIT_FOR_IDLE(base, reg)						\
+	do {									\
+		while ((read_##reg(base) & reg##_IDLE_BIT) == 0) {}		\
+	} while (0)
+
+#ifdef __aarch64__
+bool is_gicv5_mode(void);
+bool gicv5_is_irq_spi(unsigned int interrupt_id);
+void gicv5_enable_cpuif(void);
+void gicv5_setup_cpuif(void);
+void gicv5_disable_cpuif(void);
+void gicv5_save_cpuif_context(void);
+void gicv5_restore_cpuif_context(void);
+void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority);
+void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos);
+void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos);
+void gicv5_intr_enable(unsigned int interrupt_id);
+void gicv5_intr_disable(unsigned int interrupt_id);
+unsigned int gicv5_acknowledge_interrupt(void);
+unsigned int gicv5_is_intr_pending(unsigned int interrupt_id);
+void gicv5_intr_clear(unsigned int interrupt_id);
+void gicv5_end_of_interrupt(unsigned int raw_iar);
+void gicv5_setup(void);
+uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos);
+void gicv5_init(uintptr_t irs_base_addr);
+#else
+static inline bool is_gicv5_mode(void) { return false; }
+static inline bool gicv5_is_irq_spi(unsigned int interrupt_id) { return false; }
+static inline void gicv5_enable_cpuif(void) {}
+static inline void gicv5_setup_cpuif(void) {}
+static inline void gicv5_disable_cpuif(void) {}
+static inline void gicv5_save_cpuif_context(void) {}
+static inline void gicv5_restore_cpuif_context(void) {}
+static inline void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority) {}
+static inline void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos) {}
+static inline void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos) {}
+static inline void gicv5_intr_enable(unsigned int interrupt_id) {}
+static inline void gicv5_intr_disable(unsigned int interrupt_id) {}
+static inline unsigned int gicv5_acknowledge_interrupt(void) { return -1; }
+static inline unsigned int gicv5_is_intr_pending(unsigned int interrupt_id) { return -1; }
+static inline void gicv5_intr_clear(unsigned int interrupt_id) {}
+static inline void gicv5_end_of_interrupt(unsigned int raw_iar) {}
+static inline void gicv5_setup(void) {}
+static inline uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos) { return 0; }
+static inline void gicv5_init(uintptr_t irs_base_addr) {}
+#endif
+
+#endif /* __GIC_V5_H__ */
diff --git a/include/lib/aarch32/arch_features.h b/include/lib/aarch32/arch_features.h
index 44a6845..7c5aed7 100644
--- a/include/lib/aarch32/arch_features.h
+++ b/include/lib/aarch32/arch_features.h
@@ -71,4 +71,10 @@
 	return EXTRACT(ID_PFR1_GIC, read_id_pfr1()) >= 1;
 }
 
+static inline bool is_feat_gcie_supported(void)
+{
+	/* v9 only, we can't have AArch32 in EL2 */
+	return 0;
+}
+
 #endif /* ARCH_FEATURES_H */
diff --git a/include/lib/aarch64/arch.h b/include/lib/aarch64/arch.h
index 2258c7d..fbef12f 100644
--- a/include/lib/aarch64/arch.h
+++ b/include/lib/aarch64/arch.h
@@ -148,6 +148,20 @@
 #define ICC_PPI_PRIORITYR14	S3_0_C12_C15_6
 #define ICC_PPI_PRIORITYR15	S3_0_C12_C15_7
 
+#define ICC_CR0_EL1_EN_BIT			BIT(0)
+#define ICC_ICSR_EL1_F_BIT			BIT(0)
+#define ICC_ICSR_EL1_PENDING_BIT		BIT(2)
+#define ICC_PPI_PRIORITYR_FIELD_NUM		8UL
+#define ICC_PPI_PRIORITYR_FIELD_MASK		GENMASK(7, 0)
+#define ICC_PPI_ENABLER_FIELD_NUM		64UL
+#define ICC_PPI_XPENDR_FIELD_NUM		64UL
+
+#define GICCDAFF_IAFFID_SHIFT			32UL
+#define GICCDPRI_PRIORITY_SHIFT			35UL
+#define GICCDPRI_PRIORITY_WIDTH			5UL
+#define GICCDPEND_PENDING_BIT			BIT(32)
+#define GICRCDIA_VALID_BIT			BIT(32)
+
 /*******************************************************************************
  * Definitions for EL2 system registers.
  ******************************************************************************/
@@ -606,6 +620,11 @@
 #define ID_AA64PFR2_EL1_FPMR_WIDTH		U(4)
 #define ID_AA64PFR2_EL1_FPMR_SUPPORTED		ULL(0x1)
 
+#define ID_AA64PFR2_EL1_GCIE_SHIFT		U(12)
+#define ID_AA64PFR2_EL1_GCIE_MASK		ULL(0xf)
+#define ID_AA64PFR2_EL1_GCIE_WIDTH		U(4)
+#define ID_AA64PFR2_EL1_GCIE_SUPPORTED		ULL(0x1)
+
 /* ID_PFR1_EL1 definitions */
 #define ID_PFR1_VIRTEXT_SHIFT	U(12)
 #define ID_PFR1_VIRTEXT_MASK	U(0xf)
diff --git a/include/lib/aarch64/arch_features.h b/include/lib/aarch64/arch_features.h
index eb846cd..a873501 100644
--- a/include/lib/aarch64/arch_features.h
+++ b/include/lib/aarch64/arch_features.h
@@ -619,4 +619,10 @@
 	return EXTRACT(ID_AA64PFR0_GIC, read_id_aa64pfr0_el1())
 			>= ID_AA64PFR0_GICV3_GICV4_SUPPORTED;
 }
+
+static inline bool is_feat_gcie_supported(void)
+{
+	return EXTRACT(ID_AA64PFR2_EL1_GCIE, read_id_aa64pfr2_el1())
+			>= ID_AA64PFR2_EL1_GCIE_SUPPORTED;
+}
 #endif /* ARCH_FEATURES_H */
diff --git a/include/lib/irq.h b/include/lib/irq.h
index 17993a8..d4d0a3c 100644
--- a/include/lib/irq.h
+++ b/include/lib/irq.h
@@ -16,6 +16,7 @@
  * timer fires off
  */
 #define IRQ_WAKE_SGI		IRQ_NS_SGI_7
+#define IRQ_NUM_SGIS		(IRQ_NS_SGI_7 + 1)
 
 #ifndef __ASSEMBLY__
 
diff --git a/include/lib/utils_def.h b/include/lib/utils_def.h
index c9bd527..ae85a81 100644
--- a/include/lib/utils_def.h
+++ b/include/lib/utils_def.h
@@ -233,4 +233,14 @@
 							      0;  \
 	}))
 
+/*
+ * Get the next power of 2 for x. Eg. next_power_of_2(12) is 16
+ */
+#define b2(x)   ((x)    | ((x)    >> 1))
+#define b4(x)   (b2(x)  | (b2(x)  >> 2))
+#define b8(x)   (b4(x)  | (b4(x)  >> 4))
+#define b16(x)  (b8(x)  | (b8(x)  >> 8))
+#define b32(x)  (b16(x) | (b16(x) >> 16))
+#define next_power_of_2(x)      (b32(x - 1) + 1)
+
 #endif /* UTILS_DEF_H */
diff --git a/plat/arm/fvp/fvp_def.h b/plat/arm/fvp/fvp_def.h
index 222fadd..1d4e507 100644
--- a/plat/arm/fvp/fvp_def.h
+++ b/plat/arm/fvp/fvp_def.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2024, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -71,6 +71,11 @@
 #define GICC_BASE		0x2c000000
 
 /*******************************************************************************
+ * GICv5 related constants
+ ******************************************************************************/
+#define IRS_BASE		0x2f1a0000
+
+/*******************************************************************************
  * PL011 related constants
  ******************************************************************************/
 #define PL011_UART0_BASE	0x1c090000
diff --git a/plat/arm/fvp/plat_setup.c b/plat/arm/fvp/plat_setup.c
index f09bf59..c897289 100644
--- a/plat/arm/fvp/plat_setup.c
+++ b/plat/arm/fvp/plat_setup.c
@@ -52,5 +52,9 @@
 
 void plat_arm_gic_init(void)
 {
-	arm_gic_init(GICC_BASE, GICD_BASE, GICR_BASE);
+	if (arm_gic_get_version() != 5) {
+		arm_gic_init(GICC_BASE, GICD_BASE, GICR_BASE);
+	} else {
+		arm_gic_init(0, IRS_BASE, 0);
+	}
 }
diff --git a/tftf/framework/framework.mk b/tftf/framework/framework.mk
index edcae60..f920cbf 100644
--- a/tftf/framework/framework.mk
+++ b/tftf/framework/framework.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2018-2024, Arm Limited. All rights reserved.
+# Copyright (c) 2018-2025, Arm Limited. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -90,6 +90,7 @@
 ifeq (${ARCH},aarch64)
 # ARMv8.3 Pointer Authentication support files
 FRAMEWORK_SOURCES	+=						\
+	drivers/arm/gic/aarch64/gic_v5.c				\
 	lib/exceptions/aarch64/sync.c					\
 	lib/exceptions/aarch64/serror.c					\
 	lib/extensions/pauth/aarch64/pauth.c				\
diff --git a/tftf/framework/main.c b/tftf/framework/main.c
index 0ae03e5..51e49b8 100644
--- a/tftf/framework/main.c
+++ b/tftf/framework/main.c
@@ -564,6 +564,8 @@
 	pauth_init_enable();
 #endif /* ENABLE_PAUTH */
 
+	arm_gic_probe();
+
 	tftf_platform_setup();
 	tftf_init_topology();