interrupt_controller: gicv3: add support for LPIs

The LPI (Locality-specific Peripheral Interrupts) are edge-triggered
message-based interrupts that can use an Interrupt Translation
Service (ITS) to route an interrupt to a specific Redistributor and
connected PE.

This implement the necessary LPI support when an ITS is enabled.

The LPI states are stored in memory-backed tables.

Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
diff --git a/drivers/interrupt_controller/intc_gicv3.c b/drivers/interrupt_controller/intc_gicv3.c
index 18de217..042d6c0 100644
--- a/drivers/interrupt_controller/intc_gicv3.c
+++ b/drivers/interrupt_controller/intc_gicv3.c
@@ -11,6 +11,8 @@
 #include "intc_gic_common_priv.h"
 #include "intc_gicv3_priv.h"
 
+#include <string.h>
+
 /* Redistributor base addresses for each core */
 mem_addr_t gic_rdists[CONFIG_MP_NUM_CPUS];
 
@@ -20,6 +22,20 @@
 #define IGROUPR_VAL	0x0U
 #endif
 
+/*
+ * We allocate memory for PROPBASE to cover 2 ^ lpi_id_bits LPIs to
+ * deal with (one configuration byte per interrupt). PENDBASE has to
+ * be 64kB aligned (one bit per LPI, plus 8192 bits for SPI/PPI/SGI).
+ */
+#define ITS_MAX_LPI_NRBITS	16 /* 64K LPIs */
+
+#define LPI_PROPBASE_SZ(nrbits)	ROUND_UP(BIT(nrbits), KB(64))
+#define LPI_PENDBASE_SZ(nrbits)	ROUND_UP(BIT(nrbits) / 8, KB(64))
+
+#ifdef CONFIG_GIC_V3_ITS
+static uintptr_t lpi_prop_table;
+#endif
+
 static inline mem_addr_t gic_get_rdist(void)
 {
 	return gic_rdists[arch_curr_cpu()->id];
@@ -48,9 +64,47 @@
 	return 0;
 }
 
+#ifdef CONFIG_GIC_V3_ITS
+static void arm_gic_lpi_setup(unsigned int intid, bool enable)
+{
+	uint8_t *cfg = &((uint8_t *)lpi_prop_table)[intid - 8192];
+
+	if (enable) {
+		*cfg |= BIT(0);
+	} else {
+		*cfg &= ~BIT(0);
+	}
+
+	dsb();
+}
+
+static void arm_gic_lpi_set_priority(unsigned int intid, unsigned int prio)
+{
+	uint8_t *cfg = &((uint8_t *)lpi_prop_table)[intid - 8192];
+
+	*cfg &= 0xfc;
+	*cfg |= prio & 0xfc;
+
+	dsb();
+}
+
+static bool arm_gic_lpi_is_enabled(unsigned int intid)
+{
+	uint8_t *cfg = &((uint8_t *)lpi_prop_table)[intid - 8192];
+
+	return (*cfg & BIT(0));
+}
+#endif
+
 void arm_gic_irq_set_priority(unsigned int intid,
 			      unsigned int prio, uint32_t flags)
 {
+#ifdef CONFIG_GIC_V3_ITS
+	if (intid >= 8192) {
+		arm_gic_lpi_set_priority(intid, prio);
+		return;
+	}
+#endif
 	uint32_t mask = BIT(intid & (GIC_NUM_INTR_PER_REG - 1));
 	uint32_t idx = intid / GIC_NUM_INTR_PER_REG;
 	uint32_t shift;
@@ -80,6 +134,12 @@
 
 void arm_gic_irq_enable(unsigned int intid)
 {
+#ifdef CONFIG_GIC_V3_ITS
+	if (intid >= 8192) {
+		arm_gic_lpi_setup(intid, true);
+		return;
+	}
+#endif
 	uint32_t mask = BIT(intid & (GIC_NUM_INTR_PER_REG - 1));
 	uint32_t idx = intid / GIC_NUM_INTR_PER_REG;
 
@@ -99,6 +159,12 @@
 
 void arm_gic_irq_disable(unsigned int intid)
 {
+#ifdef CONFIG_GIC_V3_ITS
+	if (intid >= 8192) {
+		arm_gic_lpi_setup(intid, false);
+		return;
+	}
+#endif
 	uint32_t mask = BIT(intid & (GIC_NUM_INTR_PER_REG - 1));
 	uint32_t idx = intid / GIC_NUM_INTR_PER_REG;
 
@@ -109,6 +175,11 @@
 
 bool arm_gic_irq_is_enabled(unsigned int intid)
 {
+#ifdef CONFIG_GIC_V3_ITS
+	if (intid >= 8192) {
+		return arm_gic_lpi_is_enabled(intid);
+	}
+#endif
 	uint32_t mask = BIT(intid & (GIC_NUM_INTR_PER_REG - 1));
 	uint32_t idx = intid / GIC_NUM_INTR_PER_REG;
 	uint32_t val;
@@ -184,6 +255,58 @@
 		;
 }
 
+#ifdef CONFIG_GIC_V3_ITS
+/*
+ * Setup LPIs Configuration & Pending tables for redistributors
+ * LPI configuration is global, each redistributor has a pending table
+ */
+static void gicv3_rdist_setup_lpis(mem_addr_t rdist)
+{
+	unsigned int lpi_id_bits = MIN(GICD_TYPER_IDBITS(sys_read32(GICD_TYPER)),
+				       ITS_MAX_LPI_NRBITS);
+	uintptr_t lpi_pend_table;
+	uint64_t reg;
+	uint32_t ctlr;
+
+	/* If not, alloc a common prop table for all redistributors */
+	if (!lpi_prop_table) {
+		lpi_prop_table = (uintptr_t)k_aligned_alloc(4 * 1024, LPI_PROPBASE_SZ(lpi_id_bits));
+		memset((void *)lpi_prop_table, 0, LPI_PROPBASE_SZ(lpi_id_bits));
+	}
+
+	lpi_pend_table = (uintptr_t)k_aligned_alloc(64 * 1024, LPI_PENDBASE_SZ(lpi_id_bits));
+	memset((void *)lpi_pend_table, 0, LPI_PENDBASE_SZ(lpi_id_bits));
+
+	ctlr = sys_read32(rdist + GICR_CTLR);
+	ctlr &= ~GICR_CTLR_ENABLE_LPIS;
+	sys_write32(ctlr, rdist + GICR_CTLR);
+
+	/* PROPBASE */
+	reg = (GIC_BASER_SHARE_INNER << GITR_PROPBASER_SHAREABILITY_SHIFT) |
+	      (GIC_BASER_CACHE_RAWAWB << GITR_PROPBASER_INNER_CACHE_SHIFT) |
+	      (lpi_prop_table & (GITR_PROPBASER_ADDR_MASK << GITR_PROPBASER_ADDR_SHIFT)) |
+	      (GIC_BASER_CACHE_INNERLIKE << GITR_PROPBASER_OUTER_CACHE_SHIFT) |
+	      ((lpi_id_bits - 1) & GITR_PROPBASER_ID_BITS_MASK);
+	sys_write64(reg, rdist + GICR_PROPBASER);
+	/* TOFIX: check SHAREABILITY validity */
+
+	/* PENDBASE */
+	reg = (GIC_BASER_SHARE_INNER << GITR_PENDBASER_SHAREABILITY_SHIFT) |
+	      (GIC_BASER_CACHE_RAWAWB << GITR_PENDBASER_INNER_CACHE_SHIFT) |
+	      (lpi_pend_table & (GITR_PENDBASER_ADDR_MASK << GITR_PENDBASER_ADDR_SHIFT)) |
+	      (GIC_BASER_CACHE_INNERLIKE << GITR_PENDBASER_OUTER_CACHE_SHIFT) |
+	      GITR_PENDBASER_PTZ;
+	sys_write64(reg, rdist + GICR_PENDBASER);
+	/* TOFIX: check SHAREABILITY validity */
+
+	ctlr = sys_read32(rdist + GICR_CTLR);
+	ctlr |= GICR_CTLR_ENABLE_LPIS;
+	sys_write32(ctlr, rdist + GICR_CTLR);
+
+	dsb();
+}
+#endif
+
 /*
  * Initialize the cpu interface. This should be called by each core.
  */
@@ -334,6 +457,11 @@
 	cpu = arch_curr_cpu()->id;
 	gic_rdists[cpu] = GIC_RDIST_BASE + MPIDR_TO_CORE(GET_MPIDR()) * 0x20000;
 
+#ifdef CONFIG_GIC_V3_ITS
+	/* Enable LPIs in Redistributor */
+	gicv3_rdist_setup_lpis(gic_get_rdist());
+#endif
+
 	gicv3_rdist_enable(gic_get_rdist());
 
 	gicv3_cpuif_init();
diff --git a/drivers/interrupt_controller/intc_gicv3_priv.h b/drivers/interrupt_controller/intc_gicv3_priv.h
index 31f08e7..c5ed758 100644
--- a/drivers/interrupt_controller/intc_gicv3_priv.h
+++ b/drivers/interrupt_controller/intc_gicv3_priv.h
@@ -10,6 +10,20 @@
 #include <zephyr/types.h>
 #include <device.h>
 
+/* Cache and Share ability for ITS & Redistributor LPI state tables */
+#define GIC_BASER_CACHE_NGNRNE		0x0UL /* Device-nGnRnE */
+#define GIC_BASER_CACHE_INNERLIKE	0x0UL /* Same as Inner Cacheability. */
+#define GIC_BASER_CACHE_NCACHEABLE	0x1UL /* Normal Outer Non-cacheable */
+#define GIC_BASER_CACHE_RAWT		0x2UL /* Normal Outer Cacheable Read-allocate, Write-through */
+#define GIC_BASER_CACHE_RAWB		0x3UL /* Normal Outer Cacheable Read-allocate, Write-back */
+#define GIC_BASER_CACHE_WAWT		0x4UL /* Normal Outer Cacheable Write-allocate, Write-through */
+#define GIC_BASER_CACHE_WAWB		0x5UL /* Normal Outer Cacheable Write-allocate, Write-back */
+#define GIC_BASER_CACHE_RAWAWT		0x6UL /* Normal Outer Cacheable Read-allocate, Write-allocate, Write-through */
+#define GIC_BASER_CACHE_RAWAWB		0x7UL /* Normal Outer Cacheable Read-allocate, Write-allocate, Write-back */
+#define GIC_BASER_SHARE_NO		0x0UL /* Non-shareable */
+#define GIC_BASER_SHARE_INNER		0x1UL /* Inner Shareable */
+#define GIC_BASER_SHARE_OUTER		0x2UL /* Outer Shareable */
+
 /*
  * GIC Register Interface Base Addresses
  */
@@ -25,6 +39,8 @@
 #define GICR_TYPER			0x0008
 #define GICR_STATUSR			0x0010
 #define GICR_WAKER			0x0014
+#define GICR_PROPBASER			0x0070
+#define GICR_PENDBASER			0x0078
 
 /* Register bit definations */
 
@@ -41,12 +57,35 @@
 #define GICD_CTLR_RWP			31
 
 /* GICR_CTLR */
+#define GICR_CTLR_ENABLE_LPIS		BIT(0)
 #define GICR_CTLR_RWP			3
 
 /* GICR_WAKER */
 #define GICR_WAKER_PS			1
 #define GICR_WAKER_CA			2
 
+/* GICR_PROPBASER */
+#define GITR_PROPBASER_ID_BITS_MASK		0x1fUL
+#define GITR_PROPBASER_INNER_CACHE_SHIFT	7
+#define GITR_PROPBASER_INNER_CACHE_MASK		0x7UL
+#define GITR_PROPBASER_SHAREABILITY_SHIFT	10
+#define GITR_PROPBASER_SHAREABILITY_MASK	0x3UL
+#define GITR_PROPBASER_ADDR_SHIFT		12
+#define GITR_PROPBASER_ADDR_MASK		0xFFFFFFFFFFUL
+#define GITR_PROPBASER_OUTER_CACHE_SHIFT	56
+#define GITR_PROPBASER_OUTER_CACHE_MASK		0x7UL
+
+/* GICR_PENDBASER */
+#define GITR_PENDBASER_INNER_CACHE_SHIFT	7
+#define GITR_PENDBASER_INNER_CACHE_MASK		0x7UL
+#define GITR_PENDBASER_SHAREABILITY_SHIFT	10
+#define GITR_PENDBASER_SHAREABILITY_MASK	0x3UL
+#define GITR_PENDBASER_ADDR_SHIFT		16
+#define GITR_PENDBASER_ADDR_MASK		0xFFFFFFFFFUL
+#define GITR_PENDBASER_OUTER_CACHE_SHIFT	56
+#define GITR_PENDBASER_OUTER_CACHE_MASK		0x7UL
+#define GITR_PENDBASER_PTZ			BIT64(62)
+
 /* GITCD_IROUTER */
 #define GIC_DIST_IROUTER		0x6000
 #define IROUTER(base, n)		(base + GIC_DIST_IROUTER + (n) * 8)
diff --git a/include/drivers/interrupt_controller/gic.h b/include/drivers/interrupt_controller/gic.h
index 5fb605f..a46bc6c 100644
--- a/include/drivers/interrupt_controller/gic.h
+++ b/include/drivers/interrupt_controller/gic.h
@@ -216,6 +216,9 @@
 /* GICD_TYPER.ITLinesNumber 0:4 */
 #define GICD_TYPER_ITLINESNUM_MASK	0x1f
 
+/* GICD_TYPER.IDbits */
+#define GICD_TYPER_IDBITS(typer)	((((typer) >> 19) & 0x1f) + 1)
+
 /*
  * Common Helper Constants
  */