fix(plat/marvell/a3k): reset GIC before resetting via CM3 secure coprocessor

Add code that acknowledges all SoC interrupts and resets the Generic
Interrupt Controller before resetting the SoC via the Cortex-M3 secure
coprocessor.

Recall that Turris MOX has a HW bug wherein a SoC reset initiated by
writing the magic value to the North Bridge Warm Reset register may
randomly freeze the board.

Back in 2021 we introduced the CM3_SYSTEM_RESET build option for the
Armada 3700 platform, which, when enabled, adds code to the PSCI reset
handler so that the SoC reset is done by requesting the firmware in the
Cortex-M3 secure coprocessor to do it, instead of writing the Warm Reset
register.

The secure coprocessor firmware tried various things to put the board
into a state where the SoC reset circuit would work correctly. This
managed to fix the issue for some boards, but not for all of them.

Another considered method to overcome this issue was to reset all the
SoC peripheral controllers one by one by writing to specific registers,
instead of triggering the SoC reset circuit via the Warm Reset register.
This method was not used because until now, there was one peripheral
that I could not find a way how to reset properly: the Generic Interrupt
Controller (GIC).

After 3 years I have finally found a way how to reset the GIC, and it
needs to be done by the main processor, before the secure coprocessor
resets the main processor.

Change-Id: Icc23251ef97738b6b48af514d5118440ec21cdd7
Signed-off-by: Marek BehĂșn <marek.behun@nic.cz>
diff --git a/plat/marvell/armada/a3k/common/cm3_system_reset.c b/plat/marvell/armada/a3k/common/cm3_system_reset.c
index f105d59..030a614 100644
--- a/plat/marvell/armada/a3k/common/cm3_system_reset.c
+++ b/plat/marvell/armada/a3k/common/cm3_system_reset.c
@@ -8,11 +8,22 @@
 #include <stdbool.h>
 
 #include <common/debug.h>
+#include <drivers/arm/gic_common.h>
+#include <drivers/arm/gicv3.h>
 #include <drivers/delay_timer.h>
 #include <lib/mmio.h>
+#include <lib/utils_def.h>
 
+#include <a3700_pm.h>
+#include <platform_def.h>
 #include <mvebu_def.h>
 
+/* IO Decoder Error Interrupt Status Registers */
+#define MVEBU_DEC_WIN_REGS_BASE(p)		(MVEBU_REGS_BASE + 0xC000 + \
+						 (p) * 0x100)
+#define MVEBU_DEC_WIN_ERR_INT_STS_REG(p)	(MVEBU_DEC_WIN_REGS_BASE(p) + \
+						 0xF8)
+
 /* Cortex-M3 Secure Processor Mailbox Registers */
 #define MVEBU_RWTM_PARAM0_REG			(MVEBU_RWTM_REG_BASE)
 #define MVEBU_RWTM_CMD_REG			(MVEBU_RWTM_REG_BASE + 0x40)
@@ -23,6 +34,122 @@
 #define MVEBU_RWTM_REBOOT_CMD		0x0009
 #define MVEBU_RWTM_REBOOT_MAGIC		0xDEADBEEF
 
+static inline uint32_t a3700_gicd_read(uint32_t reg)
+{
+	return mmio_read_32(PLAT_MARVELL_GICD_BASE + reg);
+}
+
+static inline void a3700_gicd_write(uint32_t reg, uint32_t value)
+{
+	mmio_write_32(PLAT_MARVELL_GICD_BASE + reg, value);
+}
+
+static void a3700_gicd_ctlr_clear_bits(uint32_t bits)
+{
+	uint32_t val;
+
+	val = a3700_gicd_read(GICD_CTLR);
+	if ((val & bits) != 0U) {
+		a3700_gicd_write(GICD_CTLR, val & ~bits);
+		mdelay(1);
+
+		if ((a3700_gicd_read(GICD_CTLR) & GICD_CTLR_RWP_BIT) != 0U) {
+			ERROR("could not clear bits 0x%x in GIC distributor control\n",
+			      bits);
+		}
+	}
+}
+
+static void a3700_gic_dist_disable_irqs(void)
+{
+	int i;
+
+	for (i = 32; i < 224; i += 32) {
+		a3700_gicd_write(GICD_ICENABLER + (i >> 3), GENMASK_32(31, 0));
+	}
+}
+
+static inline uintptr_t a3700_rdist_base(unsigned int proc)
+{
+	return PLAT_MARVELL_GICR_BASE + (proc << GICR_V3_PCPUBASE_SHIFT);
+}
+
+static inline uint32_t a3700_gicr_read(unsigned int proc, uint32_t reg)
+{
+	return mmio_read_32(a3700_rdist_base(proc) + reg);
+}
+
+static inline void a3700_gicr_write(unsigned int proc, uint32_t reg,
+				    uint32_t value)
+{
+	mmio_write_32(a3700_rdist_base(proc) + reg, value);
+}
+
+static void a3700_gic_redist_disable_irqs(unsigned int proc)
+{
+	a3700_gicr_write(proc, GICR_ICENABLER0, GENMASK_32(31, 0));
+	mdelay(1);
+
+	if ((a3700_gicr_read(proc, GICR_CTLR) & GICR_CTLR_RWP_BIT) != 0U) {
+		ERROR("could not disable core %u PPIs & SGIs\n", proc);
+	}
+}
+
+static void a3700_gic_redist_mark_asleep(unsigned int proc)
+{
+	a3700_gicr_write(proc, GICR_WAKER,
+			 a3700_gicr_read(proc, GICR_WAKER) | WAKER_PS_BIT);
+	mdelay(1);
+
+	if ((a3700_gicr_read(proc, GICR_WAKER) & WAKER_CA_BIT) == 0U) {
+		ERROR("could not mark core %u redistributor asleep\n", proc);
+	}
+}
+
+static void a3700_io_addr_dec_ack_err_irq(void)
+{
+	unsigned int periph;
+
+	for (periph = 0; periph < 16; ++periph) {
+		/* periph 6 does not exist */
+		if (periph == 6)
+			continue;
+
+		mmio_write_32(MVEBU_DEC_WIN_ERR_INT_STS_REG(periph),
+			      GENMASK_32(1, 0));
+	}
+}
+
+static void a3700_gic_reset(void)
+{
+	a3700_gic_redist_disable_irqs(0);
+	a3700_gic_redist_disable_irqs(1);
+
+	a3700_gic_redist_mark_asleep(0);
+	a3700_gic_redist_mark_asleep(1);
+
+	a3700_io_addr_dec_ack_err_irq();
+
+	a3700_pm_ack_irq();
+
+	a3700_gic_dist_disable_irqs();
+
+	a3700_gicd_ctlr_clear_bits(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1NS_BIT |
+				   CTLR_ENABLE_G1S_BIT);
+
+	/* Clearing ARE_S and ARE_NS bits is undefined in the specification, but
+	 * works if the previous operations are successful. We need to do it in
+	 * order to put GIC into the same state it was in just after reset. If
+	 * this is successful, the rWTM firmware in the secure coprocessor will
+	 * reset all other peripherals one by one, load new firmware and boot
+	 * it, all without triggering the true warm reset via the WARM_RESET
+	 * register (which may hang the board).
+	 */
+
+	a3700_gicd_ctlr_clear_bits(CTLR_ARE_S_BIT);
+	a3700_gicd_ctlr_clear_bits(CTLR_ARE_NS_BIT);
+}
+
 static inline bool rwtm_completed(void)
 {
 	return (mmio_read_32(MVEBU_RWTM_HOST_INT_RESET_REG) &
@@ -43,6 +170,11 @@
 {
 	int tries = 5;
 
+	/* Put GIC into the same state it was just after reset. This is needed
+	 * for the reset issue workaround to work.
+	 */
+	a3700_gic_reset();
+
 	for (; tries > 0; --tries) {
 		mmio_clrbits_32(MVEBU_RWTM_HOST_INT_RESET_REG,
 				MVEBU_RWTM_HOST_INT_SP_COMPLETE);
diff --git a/plat/marvell/armada/a3k/common/include/a3700_pm.h b/plat/marvell/armada/a3k/common/include/a3700_pm.h
index 44dbb9f..1be82b2 100644
--- a/plat/marvell/armada/a3k/common/include/a3700_pm.h
+++ b/plat/marvell/armada/a3k/common/include/a3700_pm.h
@@ -48,6 +48,8 @@
 
 struct pm_wake_up_src_config *mv_wake_up_src_config_get(void);
 
+void a3700_pm_ack_irq(void);
+
 void cm3_system_reset(void);
 
 #endif /* A3700_PM_H */
diff --git a/plat/marvell/armada/a3k/common/plat_pm.c b/plat/marvell/armada/a3k/common/plat_pm.c
index e2d15ab..d573b79 100644
--- a/plat/marvell/armada/a3k/common/plat_pm.c
+++ b/plat/marvell/armada/a3k/common/plat_pm.c
@@ -197,7 +197,7 @@
 {
 }
 
-static void a3700_pm_ack_irq(void)
+void a3700_pm_ack_irq(void)
 {
 	uint32_t reg;