plat: a8k: enable PMU overflow interrupt handler

This patch enables handling PMU overflow IRQ by GIC SPI's
directly in EL3. Also implement additional SMC routine,
which can disable the solution on demand in runtime.

Since it is possible to configure PMU interrupt trigger type
in the MADT ACPI table, it is enough to set it only once in EL3
during initialization.

Change-Id: Ie76aa62ccc4fd7cabfec9e3d5ed9970ada1c1b2a
Signed-off-by: Marcin Wojtas <mw@semihalf.com>
Reviewed-by: Kostya Porotchkin <kostap@marvell.com>
diff --git a/include/plat/marvell/a8k/common/marvell_def.h b/include/plat/marvell/a8k/common/marvell_def.h
index 7dacf82..9429753 100644
--- a/include/plat/marvell/a8k/common/marvell_def.h
+++ b/include/plat/marvell/a8k/common/marvell_def.h
@@ -63,6 +63,7 @@
 #define MARVELL_DRAM_END		(MARVELL_DRAM_BASE + \
 					 MARVELL_DRAM_SIZE - 1)
 
+#define MARVELL_IRQ_PIC0		28
 #define MARVELL_IRQ_SEC_PHY_TIMER	29
 
 #define MARVELL_IRQ_SEC_SGI_0		8
diff --git a/include/plat/marvell/a8k/common/plat_marvell.h b/include/plat/marvell/a8k/common/plat_marvell.h
index 81cbf38..9ca68d3 100644
--- a/include/plat/marvell/a8k/common/plat_marvell.h
+++ b/include/plat/marvell/a8k/common/plat_marvell.h
@@ -94,6 +94,14 @@
 void plat_marvell_system_reset(void);
 
 /*
+ * Miscellaneous platform SMC routines
+ */
+#ifdef MVEBU_PMU_IRQ_WA
+void mvebu_pmu_interrupt_enable(void);
+void mvebu_pmu_interrupt_disable(void);
+#endif
+
+/*
  * Optional functions required in Marvell standard platforms
  */
 void plat_marvell_io_setup(void);
diff --git a/plat/marvell/a8k/common/include/platform_def.h b/plat/marvell/a8k/common/include/platform_def.h
index f7bd23f..06d4fa9 100644
--- a/plat/marvell/a8k/common/include/platform_def.h
+++ b/plat/marvell/a8k/common/include/platform_def.h
@@ -134,6 +134,8 @@
 	INTR_PROP_DESC(MARVELL_IRQ_SEC_SGI_0, GIC_HIGHEST_SEC_PRIORITY, grp, \
 			GIC_INTR_CFG_LEVEL), \
 	INTR_PROP_DESC(MARVELL_IRQ_SEC_SGI_6, GIC_HIGHEST_SEC_PRIORITY, grp, \
+			GIC_INTR_CFG_LEVEL), \
+	INTR_PROP_DESC(MARVELL_IRQ_PIC0, GIC_HIGHEST_SEC_PRIORITY, grp, \
 			GIC_INTR_CFG_LEVEL)
 
 #define PLAT_MARVELL_G1S_IRQ_PROPS(grp) \
@@ -199,4 +201,6 @@
 
 #define BL32_BASE				TRUSTED_DRAM_BASE
 
+#define MVEBU_PMU_IRQ_WA
+
 #endif /* __PLATFORM_DEF_H__ */
diff --git a/plat/marvell/common/marvell_gicv2.c b/plat/marvell/common/marvell_gicv2.c
index ba8e409..19e1ec0 100644
--- a/plat/marvell/common/marvell_gicv2.c
+++ b/plat/marvell/common/marvell_gicv2.c
@@ -5,7 +5,11 @@
  * https://spdx.org/licenses
  */
 
+#include <bakery_lock.h>
+#include <debug.h>
 #include <gicv2.h>
+#include <interrupt_mgmt.h>
+#include <mmio.h>
 #include <plat_marvell.h>
 #include <platform.h>
 #include <platform_def.h>
@@ -17,6 +21,21 @@
 #pragma weak plat_marvell_gic_driver_init
 #pragma weak plat_marvell_gic_init
 
+#define A7K8K_PIC_CAUSE_REG		0xf03f0100
+#define A7K8K_PIC0_MASK_REG		0xf03f0108
+
+#define A7K8K_PIC_PMUOF_IRQ_MASK	(1 << 17)
+
+#define A7K8K_PIC_MAX_IRQS		32
+#define A7K8K_PIC_MAX_IRQ_MASK		((1UL << A7K8K_PIC_MAX_IRQS) - 1)
+
+#define A7K8K_ODMIN_SET_REG		0xf0300040
+#define A7K8K_ODMI_PMU_IRQ(idx)		((2 + idx) << 12)
+
+#define A7K8K_ODMI_PMU_GIC_IRQ(idx)	(130 + idx)
+
+static DEFINE_BAKERY_LOCK(a7k8k_irq_lock);
+
 /*
  * On a GICv2 system, the Group 1 secure interrupts are treated as Group 0
  * interrupts.
@@ -50,6 +69,74 @@
 	gicv2_driver_init(&marvell_gic_data);
 }
 
+static uint64_t a7k8k_pmu_interrupt_handler(uint32_t id,
+					  uint32_t flags,
+					  void *handle,
+					  void *cookie)
+{
+	unsigned int idx = plat_my_core_pos();
+	uint32_t irq;
+
+	bakery_lock_get(&a7k8k_irq_lock);
+
+	/* Acknowledge IRQ */
+	irq = plat_ic_acknowledge_interrupt();
+
+	plat_ic_end_of_interrupt(irq);
+
+	if (irq != MARVELL_IRQ_PIC0) {
+		bakery_lock_release(&a7k8k_irq_lock);
+		return 0;
+	}
+
+	/* Acknowledge PMU overflow IRQ in PIC0 */
+	mmio_setbits_32(A7K8K_PIC_CAUSE_REG, A7K8K_PIC_PMUOF_IRQ_MASK);
+
+	/* Trigger ODMI Frame IRQ */
+	mmio_write_32(A7K8K_ODMIN_SET_REG, A7K8K_ODMI_PMU_IRQ(idx));
+
+	bakery_lock_release(&a7k8k_irq_lock);
+
+	return 0;
+}
+
+void mvebu_pmu_interrupt_enable(void)
+{
+	unsigned int idx;
+	uint32_t flags;
+	int32_t rc;
+
+	/* Reset PIC */
+	mmio_write_32(A7K8K_PIC_CAUSE_REG, A7K8K_PIC_MAX_IRQ_MASK);
+	/* Unmask PMU overflow IRQ in PIC0 */
+	mmio_clrbits_32(A7K8K_PIC0_MASK_REG, A7K8K_PIC_PMUOF_IRQ_MASK);
+
+	/* Configure ODMI Frame IRQs as edge triggered */
+	for (idx = 0; idx < PLATFORM_CORE_COUNT; idx++)
+		gicv2_interrupt_set_cfg(A7K8K_ODMI_PMU_GIC_IRQ(idx),
+					GIC_INTR_CFG_EDGE);
+
+	/*
+	 * Register IRQ handler as INTR_TYPE_S_EL1 as its the only valid type
+	 * for GICv2 in ARM-TF.
+	 */
+	flags = 0U;
+	set_interrupt_rm_flag((flags), (NON_SECURE));
+	rc = register_interrupt_type_handler(INTR_TYPE_S_EL1,
+					     a7k8k_pmu_interrupt_handler,
+					     flags);
+	if (rc != 0)
+		panic();
+}
+
+void mvebu_pmu_interrupt_disable(void)
+{
+	/* Reset PIC */
+	mmio_write_32(A7K8K_PIC_CAUSE_REG, A7K8K_PIC_MAX_IRQ_MASK);
+	/* Mask PMU overflow IRQ in PIC0 */
+	mmio_setbits_32(A7K8K_PIC0_MASK_REG, A7K8K_PIC_PMUOF_IRQ_MASK);
+}
+
 void plat_marvell_gic_init(void)
 {
 	gicv2_distif_init();
diff --git a/plat/marvell/common/mrvl_sip_svc.c b/plat/marvell/common/mrvl_sip_svc.c
index ec293af..a0ca50d 100644
--- a/plat/marvell/common/mrvl_sip_svc.c
+++ b/plat/marvell/common/mrvl_sip_svc.c
@@ -9,6 +9,7 @@
 #include <cache_llc.h>
 #include <debug.h>
 #include <marvell_plat_priv.h>
+#include <plat_marvell.h>
 #include <runtime_svc.h>
 #include <smcc.h>
 #include "comphy/phy-comphy-cp110.h"
@@ -30,6 +31,8 @@
 /* Miscellaneous FID's' */
 #define MV_SIP_DRAM_SIZE	0x82000010
 #define MV_SIP_LLC_ENABLE	0x82000011
+#define MV_SIP_PMU_IRQ_ENABLE	0x82000012
+#define MV_SIP_PMU_IRQ_DISABLE	0x82000013
 
 #define MAX_LANE_NR		6
 #define MVEBU_COMPHY_OFFSET	0x441000
@@ -109,6 +112,14 @@
 			llc_runtime_enable(i);
 
 		SMC_RET1(handle, 0);
+#ifdef MVEBU_PMU_IRQ_WA
+	case MV_SIP_PMU_IRQ_ENABLE:
+		mvebu_pmu_interrupt_enable();
+		SMC_RET1(handle, 0);
+	case MV_SIP_PMU_IRQ_DISABLE:
+		mvebu_pmu_interrupt_disable();
+		SMC_RET1(handle, 0);
+#endif
 
 	default:
 		ERROR("%s: unhandled SMC (0x%x)\n", __func__, smc_fid);