Tegra: support for System Suspend using sc7entry-fw binary

This patch adds support to enter System Suspend on Tegra210 platforms
without the traditional BPMP firmware. The BPMP firmware will no longer
be supported on Tegra210 platforms and its functionality will be
divided across the CPU and sc7entry-fw.

The sc7entry-fw takes care of performing the hardware sequence required
to enter System Suspend (SC7 power state) from the COP. The CPU is required
to load this firmware to the internal RAM of the COP and start the sequence.
The CPU also make sure that the COP is off after cold boot and is only
powered on when we want to start the actual System Suspend sequence.

The previous bootloader loads the firmware to TZDRAM and passes its base and
size as part of the boot parameters. The EL3 layer is supposed to sanitize
the parameters before touching the firmware blob.

To assist the warmboot code with the PMIC discovery, EL3 is also supposed to
program PMC's scratch register #210, with appropriate values. Without these
settings the warmboot code wont be able to get the device out of System
Suspend.

Change-Id: I5a7b868512dbfd6cfefd55acf3978a1fd7ebf1e2
Signed-off-by: Varun Wadekar <vwadekar@nvidia.com>
diff --git a/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c b/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
index bb3b8fe..4c49386 100644
--- a/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
+++ b/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
@@ -41,6 +41,7 @@
 					psci_power_state_t *req_state)
 {
 	int state_id = psci_get_pstate_id(power_state);
+	const plat_params_from_bl2_t *plat_params = bl31_get_plat_params();
 
 	/* Sanity check the requested state id */
 	switch (state_id) {
@@ -62,6 +63,15 @@
 		break;
 
 	case PSTATE_ID_SOC_POWERDN:
+
+		/*
+		 * sc7entry-fw must be present in the system when the bpmp
+		 * firmware is not present, for a successful System Suspend
+		 * entry.
+		 */
+		if (!tegra_bpmp_init() && !plat_params->sc7entry_fw_base)
+			return PSCI_E_NOT_SUPPORTED;
+
 		/*
 		 * System powerdown request only for afflvl 2
 		 */
@@ -205,12 +215,26 @@
 
 		if (!tegra_bpmp_available) {
 
-			/* PWM tristate */
+			/* Find if the platform uses OVR2/MAX77621 PMIC */
 			cfg = mmio_read_32(TEGRA_CL_DVFS_BASE + DVFS_DFLL_OUTPUT_CFG);
 			if (cfg & DFLL_OUTPUT_CFG_CLK_EN_BIT) {
+				/* OVR2 */
+
+				/* PWM tristate */
 				val = mmio_read_32(TEGRA_MISC_BASE + PINMUX_AUX_DVFS_PWM);
 				val |= PINMUX_PWM_TRISTATE;
 				mmio_write_32(TEGRA_MISC_BASE + PINMUX_AUX_DVFS_PWM, val);
+
+				/*
+				 * SCRATCH201[1] is being used to identify CPU
+				 * PMIC in warmboot code.
+				 * 0 : OVR2
+				 * 1 : MAX77621
+				 */
+				tegra_pmc_write_32(PMC_SCRATCH201, 0x0);
+			} else {
+				/* MAX77621 */
+				tegra_pmc_write_32(PMC_SCRATCH201, 0x2);
 			}
 		}
 
@@ -237,6 +261,8 @@
 	const plat_local_state_t *pwr_domain_state =
 		target_state->pwr_domain_state;
 	unsigned int stateid_afflvl2 = pwr_domain_state[PLAT_MAX_PWR_LVL];
+	const plat_params_from_bl2_t *plat_params = bl31_get_plat_params();
+	uint32_t val;
 
 	if (stateid_afflvl2 == PSTATE_ID_SOC_POWERDN) {
 
@@ -245,6 +271,36 @@
 			tegra_se_save_tzram();
 		}
 
+		/*
+		 * The CPU needs to load the System suspend entry firmware
+		 * if nothing is running on the BPMP.
+		 */
+		if (!tegra_bpmp_available) {
+
+			/*
+			 * BPMP firmware is not running on the co-processor, so
+			 * we need to explicitly load the firmware to enable
+			 * entry/exit to/from System Suspend and set the BPMP
+			 * on its way.
+			 */
+
+			/* Power off BPMP before we proceed */
+			tegra_fc_bpmp_off();
+
+			/* Copy the firmware to BPMP's internal RAM */
+			(void)memcpy((void *)(uintptr_t)TEGRA_IRAM_BASE,
+				(const void *)plat_params->sc7entry_fw_base,
+				plat_params->sc7entry_fw_size);
+
+			/* Power on the BPMP and execute from IRAM base */
+			tegra_fc_bpmp_on(TEGRA_IRAM_BASE);
+
+			/* Wait until BPMP powers up */
+			do {
+				val = mmio_read_32(TEGRA_RES_SEMA_BASE + STA_OFFSET);
+			} while (val != SIGN_OF_LIFE);
+		}
+
 		/* enter system suspend */
 		tegra_fc_soc_powerdn(mpidr);
 	}
@@ -256,7 +312,7 @@
 {
 	const plat_params_from_bl2_t *plat_params = bl31_get_plat_params();
 	uint32_t cfg;
-	uint32_t val;
+	uint32_t val, entrypoint = 0;
 
 	/* platform parameter passed by the previous bootloader */
 	if (plat_params->l2_ecc_parity_prot_dis != 1) {
@@ -295,10 +351,14 @@
 
 		/*
 		 * Restore Boot and Power Management Processor (BPMP) reset
-		 * address and reset it.
+		 * address and reset it, if it is supported by the platform.
 		 */
-		if (tegra_bpmp_available)
-			tegra_fc_reset_bpmp();
+		if (!tegra_bpmp_available) {
+			tegra_fc_bpmp_off();
+		} else {
+			entrypoint = tegra_pmc_read_32(PMC_SCRATCH39);
+			tegra_fc_bpmp_on(entrypoint);
+		}
 	}
 
 	/*
diff --git a/plat/nvidia/tegra/soc/t210/plat_setup.c b/plat/nvidia/tegra/soc/t210/plat_setup.c
index 25105ba..4fdd5a8 100644
--- a/plat/nvidia/tegra/soc/t210/plat_setup.c
+++ b/plat/nvidia/tegra/soc/t210/plat_setup.c
@@ -5,6 +5,7 @@
  */
 
 #include <arch_helpers.h>
+#include <assert.h>
 #include <cortex_a57.h>
 #include <common/bl_common.h>
 #include <common/debug.h>
@@ -150,6 +151,42 @@
 			GICV2_INTR_GROUP0, GIC_INTR_CFG_EDGE),
 };
 
+void plat_late_platform_setup(void)
+{
+	const plat_params_from_bl2_t *plat_params = bl31_get_plat_params();
+	uint64_t tzdram_start, tzdram_end, sc7entry_end;
+	int ret;
+
+	/* memmap TZDRAM area containing the SC7 Entry Firmware */
+	if (plat_params->sc7entry_fw_base && plat_params->sc7entry_fw_size) {
+
+		assert(plat_params->sc7entry_fw_size <= TEGRA_IRAM_SIZE);
+
+		/*
+		 * Verify that the SC7 entry firmware resides inside the TZDRAM
+		 * aperture, _after_ the BL31 code.
+		 */
+		tzdram_start = plat_params->tzdram_base;
+		tzdram_end = plat_params->tzdram_base + plat_params->tzdram_size;
+		sc7entry_end = plat_params->sc7entry_fw_base +
+			       plat_params->sc7entry_fw_size;
+		if ((plat_params->sc7entry_fw_base < (tzdram_start + BL31_SIZE)) ||
+		    (sc7entry_end > tzdram_end)) {
+			panic();
+		}
+
+		/* power off BPMP processor until SC7 entry */
+		tegra_fc_bpmp_off();
+
+		/* memmap SC7 entry firmware code */
+		ret = mmap_add_dynamic_region(plat_params->sc7entry_fw_base,
+				plat_params->sc7entry_fw_base,
+				plat_params->sc7entry_fw_size,
+				MT_NS | MT_RO | MT_EXECUTE_NEVER);
+		assert(ret == 0);
+	}
+}
+
 /*******************************************************************************
  * Initialize the GIC and SGIs
  ******************************************************************************/