From 7db077f2e3116cc47d9a146f39555559faef42d7 Mon Sep 17 00:00:00 2001
From: Varun Wadekar <vwadekar@nvidia.com>
Date: Tue, 13 Feb 2018 20:31:12 -0800
Subject: [PATCH] Tegra210: support for cluster idle from the CPU

This patch adds support to enter/exit to/from cluster idle power
state on Tegra210 platforms that do not load BPMP firmware.

The CPU initates the cluster idle sequence on the last standing
CPU, by following these steps:

Entry
-----
* stop other CPUs from waking up
* program the PWM pinmux to tristate for OVR PMIC
* program the flow controller to enter CC6 state
* skip L1 $ flush during cluster power down, as L2 $ is inclusive
  of L1 $ on Cortex-A57 CPUs

Exit
----
* program the PWM pinmux to un-tristate for OVR PMIC
* allow other CPUs to wake up

This patch also makes sure that cluster idle state entry is not
enabled until CL-DVFS is ready.

Change-Id: I54cf31bf72b4a09d9bf9d2baaed6ee5a963c7808
Signed-off-by: Varun Wadekar <vwadekar@nvidia.com>
---
 plat/nvidia/tegra/include/t210/tegra_def.h    | 13 +++
 .../tegra/soc/t210/plat_psci_handlers.c       | 87 +++++++++++++++++--
 plat/nvidia/tegra/soc/t210/platform_t210.mk   |  3 +
 3 files changed, 94 insertions(+), 9 deletions(-)

diff --git a/plat/nvidia/tegra/include/t210/tegra_def.h b/plat/nvidia/tegra/include/t210/tegra_def.h
index 02a49b8f5..7eb8a878d 100644
--- a/plat/nvidia/tegra/include/t210/tegra_def.h
+++ b/plat/nvidia/tegra/include/t210/tegra_def.h
@@ -137,6 +137,8 @@
  ******************************************************************************/
 #define TEGRA_MISC_BASE			U(0x70000000)
 #define  HARDWARE_REVISION_OFFSET	U(0x804)
+#define  PINMUX_AUX_DVFS_PWM		U(0x3184)
+#define  PINMUX_PWM_TRISTATE		(U(1) << 4)
 
 /*******************************************************************************
  * Tegra UART controller base addresses
@@ -193,6 +195,17 @@
 #define MC_SMMU_PPCS_ASID_0		0x270U
 #define  PPCS_SMMU_ENABLE		(0x1U << 31)
 
+/*******************************************************************************
+ * Tegra CLDVFS constants
+ ******************************************************************************/
+#define TEGRA_CL_DVFS_BASE		U(0x70110000)
+#define DVFS_DFLL_CTRL			U(0x00)
+#define  ENABLE_OPEN_LOOP		U(1)
+#define  ENABLE_CLOSED_LOOP		U(2)
+#define DVFS_DFLL_OUTPUT_CFG		U(0x20)
+#define  DFLL_OUTPUT_CFG_I2C_EN_BIT	(U(1) << 30)
+#define  DFLL_OUTPUT_CFG_CLK_EN_BIT	(U(1) << 6)
+
 /*******************************************************************************
  * Tegra SE constants
  ******************************************************************************/
diff --git a/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c b/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
index f52d975d8..0aa36b4ef 100644
--- a/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
+++ b/plat/nvidia/tegra/soc/t210/plat_psci_handlers.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -35,6 +35,7 @@
 #define SCLK_BURST_POLICY_DEFAULT	0x10000000
 
 static int cpu_powergate_mask[PLATFORM_MAX_CPUS_PER_CLUSTER];
+static bool tegra_bpmp_available = true;
 
 int32_t tegra_soc_validate_power_state(unsigned int power_state,
 					psci_power_state_t *req_state)
@@ -53,11 +54,12 @@ int32_t tegra_soc_validate_power_state(unsigned int power_state,
 
 	case PSTATE_ID_CLUSTER_IDLE:
 	case PSTATE_ID_CLUSTER_POWERDN:
+
 		/*
-		 * Cluster powerdown/idle request only for afflvl 1
+		 * Cluster idle request for afflvl 0
 		 */
-		req_state->pwr_domain_state[MPIDR_AFFLVL1] = state_id;
 		req_state->pwr_domain_state[MPIDR_AFFLVL0] = PSTATE_ID_CORE_POWERDN;
+		req_state->pwr_domain_state[MPIDR_AFFLVL1] = state_id;
 
 		break;
 
@@ -83,7 +85,7 @@ int32_t tegra_soc_validate_power_state(unsigned int power_state,
 
 /*******************************************************************************
  * Platform handler to calculate the proper target power level at the
- * specified affinity level
+ * specified affinity level.
  ******************************************************************************/
 plat_local_state_t tegra_soc_get_target_pwr_state(unsigned int lvl,
 					     const plat_local_state_t *states,
@@ -92,7 +94,7 @@ plat_local_state_t tegra_soc_get_target_pwr_state(unsigned int lvl,
 	plat_local_state_t target = PSCI_LOCAL_STATE_RUN;
 	int cpu = plat_my_core_pos();
 	int core_pos = read_mpidr() & MPIDR_CPU_MASK;
-	uint32_t bpmp_reply, data[3];
+	uint32_t bpmp_reply, data[3], val;
 	int ret;
 
 	/* get the power state at this level */
@@ -109,9 +111,40 @@ plat_local_state_t tegra_soc_get_target_pwr_state(unsigned int lvl,
 
 			/* Cluster idle not allowed */
 			target = PSCI_LOCAL_STATE_RUN;
+
+			/*******************************************
+			 * BPMP is not present, so handle CC6 entry
+			 * from the CPU
+			 ******************************************/
+
+			/* check if cluster idle state has been enabled */
+			val = mmio_read_32(TEGRA_CL_DVFS_BASE + DVFS_DFLL_CTRL);
+			if (val == ENABLE_CLOSED_LOOP) {
+				/*
+				 * flag to indicate that BPMP firmware is not
+				 * available and the CPU has to handle entry/exit
+				 * for all power states
+				 */
+				tegra_bpmp_available = false;
+
+				/*
+				 * Acquire the cluster idle lock to stop
+				 * other CPUs from powering up.
+				 */
+				tegra_fc_ccplex_pgexit_lock();
+
+				/* Cluster idle only from the last standing CPU */
+				if (tegra_pmc_is_last_on_cpu() && tegra_fc_is_ccx_allowed()) {
+					/* Cluster idle allowed */
+					target = PSTATE_ID_CLUSTER_IDLE;
+				} else {
+					/* release cluster idle lock */
+					tegra_fc_ccplex_pgexit_unlock();
+				}
+			}
 		} else {
 
-			/* Cluster idle */
+			/* Cluster power-down */
 			data[0] = (uint32_t)cpu;
 			data[1] = TEGRA_PM_CC6;
 			data[2] = TEGRA_PM_SC1;
@@ -120,10 +153,10 @@ plat_local_state_t tegra_soc_get_target_pwr_state(unsigned int lvl,
 					(void *)&bpmp_reply,
 					(int)sizeof(bpmp_reply));
 
-			/* check if cluster idle entry is allowed */
+			/* check if cluster power down is allowed */
 			if ((ret != 0L) || (bpmp_reply != BPMP_CCx_ALLOWED)) {
 
-				/* Cluster idle not allowed */
+				/* Cluster power down not allowed */
 				target = PSCI_LOCAL_STATE_RUN;
 			}
 		}
@@ -176,7 +209,9 @@ int tegra_soc_pwr_domain_suspend(const psci_power_state_t *target_state)
 	unsigned int stateid_afflvl2 = pwr_domain_state[MPIDR_AFFLVL2];
 	unsigned int stateid_afflvl1 = pwr_domain_state[MPIDR_AFFLVL1];
 	unsigned int stateid_afflvl0 = pwr_domain_state[MPIDR_AFFLVL0];
+	uint32_t cfg;
 	int ret = PSCI_E_SUCCESS;
+	uint32_t val;
 
 	if (stateid_afflvl2 == PSTATE_ID_SOC_POWERDN) {
 
@@ -197,6 +232,17 @@ int tegra_soc_pwr_domain_suspend(const psci_power_state_t *target_state)
 
 		assert(stateid_afflvl0 == PSTATE_ID_CORE_POWERDN);
 
+		if (!tegra_bpmp_available) {
+
+			/* PWM tristate */
+			cfg = mmio_read_32(TEGRA_CL_DVFS_BASE + DVFS_DFLL_OUTPUT_CFG);
+			if (cfg & DFLL_OUTPUT_CFG_CLK_EN_BIT) {
+				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);
+			}
+		}
+
 		/* Prepare for cluster idle */
 		tegra_fc_cluster_idle(mpidr);
 
@@ -245,6 +291,7 @@ int tegra_soc_pwr_domain_power_down_wfi(const psci_power_state_t *target_state)
 int tegra_soc_pwr_domain_on_finish(const psci_power_state_t *target_state)
 {
 	const plat_params_from_bl2_t *plat_params = bl31_get_plat_params();
+	uint32_t cfg;
 	uint32_t val;
 
 	/* platform parameter passed by the previous bootloader */
@@ -286,7 +333,29 @@ int tegra_soc_pwr_domain_on_finish(const psci_power_state_t *target_state)
 		 * Restore Boot and Power Management Processor (BPMP) reset
 		 * address and reset it.
 		 */
-		tegra_fc_reset_bpmp();
+		if (tegra_bpmp_available)
+			tegra_fc_reset_bpmp();
+	}
+
+	/*
+	 * Check if we are exiting cluster idle state
+	 */
+	if (target_state->pwr_domain_state[MPIDR_AFFLVL1] ==
+			PSTATE_ID_CLUSTER_IDLE) {
+
+		if (!tegra_bpmp_available) {
+
+			/* PWM un-tristate */
+			cfg = mmio_read_32(TEGRA_CL_DVFS_BASE + DVFS_DFLL_OUTPUT_CFG);
+			if (cfg & DFLL_OUTPUT_CFG_CLK_EN_BIT) {
+				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);
+			}
+
+			/* release cluster idle lock */
+			tegra_fc_ccplex_pgexit_unlock();
+		}
 	}
 
 	/*
diff --git a/plat/nvidia/tegra/soc/t210/platform_t210.mk b/plat/nvidia/tegra/soc/t210/platform_t210.mk
index 59077eb81..723534a1d 100644
--- a/plat/nvidia/tegra/soc/t210/platform_t210.mk
+++ b/plat/nvidia/tegra/soc/t210/platform_t210.mk
@@ -51,3 +51,6 @@ A53_DISABLE_NON_TEMPORAL_HINT	:=	1
 ERRATA_A53_826319		:=	1
 ERRATA_A53_836870		:=	1
 ERRATA_A53_855873		:=	1
+
+# Skip L1 $ flush when powering down Cortex-A57 CPUs
+SKIP_A57_L1_FLUSH_PWR_DWN	:=	1
-- 
GitLab