Merge pull request #693 from dp-arm/pmf-asm

Move pmf headers to include/lib/pmf and add assembler helper
diff --git a/plat/rockchip/common/include/plat_private.h b/plat/rockchip/common/include/plat_private.h
index 7199899..b9b634e 100644
--- a/plat/rockchip/common/include/plat_private.h
+++ b/plat/rockchip/common/include/plat_private.h
@@ -56,6 +56,7 @@
 	int (*sys_pwr_dm_resume)(void);
 	void (*sys_gbl_soft_reset)(void) __dead2;
 	void (*system_off)(void) __dead2;
+	void (*sys_pwr_down_wfi)(const psci_power_state_t *state_info) __dead2;
 };
 
 /******************************************************************************
diff --git a/plat/rockchip/common/plat_pm.c b/plat/rockchip/common/plat_pm.c
index b6291bb..7372fcf 100644
--- a/plat/rockchip/common/plat_pm.c
+++ b/plat/rockchip/common/plat_pm.c
@@ -311,6 +311,18 @@
 	rockchip_ops->system_off();
 }
 
+static void
+__dead2 rockchip_pwr_domain_pwr_down_wfi(const psci_power_state_t *target_state)
+{
+	if ((RK_CORE_PWR_STATE(target_state) == PLAT_MAX_OFF_STATE) &&
+		(rockchip_ops)) {
+		if (RK_SYSTEM_PWR_STATE(target_state) == PLAT_MAX_OFF_STATE &&
+		    rockchip_ops->sys_pwr_down_wfi)
+			rockchip_ops->sys_pwr_down_wfi(target_state);
+	}
+	psci_power_down_wfi();
+}
+
 /*******************************************************************************
  * Export the platform handlers via plat_rockchip_psci_pm_ops. The rockchip
  * standard
@@ -323,6 +335,7 @@
 	.pwr_domain_suspend = rockchip_pwr_domain_suspend,
 	.pwr_domain_on_finish = rockchip_pwr_domain_on_finish,
 	.pwr_domain_suspend_finish = rockchip_pwr_domain_suspend_finish,
+	.pwr_domain_pwr_down_wfi = rockchip_pwr_domain_pwr_down_wfi,
 	.system_reset = rockchip_system_reset,
 	.system_off = rockchip_system_poweroff,
 	.validate_power_state = rockchip_validate_power_state,
diff --git a/plat/rockchip/rk3399/drivers/pmu/pmu.c b/plat/rockchip/rk3399/drivers/pmu/pmu.c
index 01f84e9..d2d1acd 100644
--- a/plat/rockchip/rk3399/drivers/pmu/pmu.c
+++ b/plat/rockchip/rk3399/drivers/pmu/pmu.c
@@ -47,6 +47,7 @@
 #include <pmu_com.h>
 #include <pwm.h>
 #include <soc.h>
+#include <bl31.h>
 
 DEFINE_BAKERY_LOCK(rockchip_pd_lock);
 
@@ -734,6 +735,90 @@
 	return 0;
 }
 
+/**
+ * init_pmu_counts - Init timing counts in the PMU register area
+ *
+ * At various points when we power up or down parts of the system we need
+ * a delay to wait for power / clocks to become stable.  The PMU has counters
+ * to help software do the delay properly.  Basically, it works like this:
+ * - Software sets up counter values
+ * - When software turns on something in the PMU, the counter kicks off
+ * - The hardware sets a bit automatically when the counter has finished and
+ *   software knows that the initialization is done.
+ *
+ * It's software's job to setup these counters.  The hardware power on default
+ * for these settings is conservative, setting everything to 0x5dc0
+ * (750 ms in 32 kHz counts or 1 ms in 24 MHz counts).
+ *
+ * Note that some of these counters are only really used at suspend/resume
+ * time (for instance, that's the only time we turn off/on the oscillator) and
+ * others are used during normal runtime (like turning on/off a CPU or GPU) but
+ * it doesn't hurt to init everything at boot.
+ *
+ * Also note that these counters can run off the 32 kHz clock or the 24 MHz
+ * clock.  While the 24 MHz clock can give us more precision, it's not always
+ * available (like when we turn the oscillator off at sleep time). The
+ * pmu_use_lf (lf: low freq) is available in power mode.  Current understanding
+ * is that counts work like this:
+ *    IF (pmu_use_lf == 0) || (power_mode_en == 0)
+ *      use the 24M OSC for counts
+ *    ELSE
+ *      use the 32K OSC for counts
+ *
+ * Notes:
+ * - There is a separate bit for the PMU called PMU_24M_EN_CFG.  At the moment
+ *   we always keep that 0.  This apparently choose between using the PLL as
+ *   the source for the PMU vs. the 24M clock.  If we ever set it to 1 we
+ *   should consider how it affects these counts (if at all).
+ * - The power_mode_en is documented to auto-clear automatically when we leave
+ *   "power mode".  That's why most clocks are on 24M.  Only timings used when
+ *   in "power mode" are 32k.
+ * - In some cases the kernel may override these counts.
+ *
+ * The PMU_STABLE_CNT / PMU_OSC_CNT / PMU_PLLLOCK_CNT are important CNTs
+ * in power mode, we need to ensure that they are available.
+ */
+static void init_pmu_counts(void)
+{
+	/* COUNTS FOR INSIDE POWER MODE */
+
+	/*
+	 * From limited testing, need PMU stable >= 2ms, but go overkill
+	 * and choose 30 ms to match testing on past SoCs.  Also let
+	 * OSC have 30 ms for stabilization.
+	 */
+	mmio_write_32(PMU_BASE + PMU_STABLE_CNT, CYCL_32K_CNT_MS(30));
+	mmio_write_32(PMU_BASE + PMU_OSC_CNT, CYCL_32K_CNT_MS(30));
+
+	/* Unclear what these should be; try 3 ms */
+	mmio_write_32(PMU_BASE + PMU_WAKEUP_RST_CLR_CNT, CYCL_32K_CNT_MS(3));
+
+	/* Unclear what this should be, but set the default explicitly */
+	mmio_write_32(PMU_BASE + PMU_TIMEOUT_CNT, 0x5dc0);
+
+	/* COUNTS FOR OUTSIDE POWER MODE */
+
+	/* Put something sorta conservative here until we know better */
+	mmio_write_32(PMU_BASE + PMU_PLLLOCK_CNT, CYCL_24M_CNT_MS(3));
+	mmio_write_32(PMU_BASE + PMU_DDRIO_PWRON_CNT, CYCL_24M_CNT_MS(1));
+	mmio_write_32(PMU_BASE + PMU_CENTER_PWRDN_CNT, CYCL_24M_CNT_MS(1));
+	mmio_write_32(PMU_BASE + PMU_CENTER_PWRUP_CNT, CYCL_24M_CNT_MS(1));
+
+	/*
+	 * Set CPU/GPU to 1 us.
+	 *
+	 * NOTE: Even though ATF doesn't configure the GPU we'll still setup
+	 * counts here.  After all ATF controls all these other bits and also
+	 * chooses which clock these counters use.
+	 */
+	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRDN_CNT, CYCL_24M_CNT_US(1));
+	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRUP_CNT, CYCL_24M_CNT_US(1));
+	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRDN_CNT, CYCL_24M_CNT_US(1));
+	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRUP_CNT, CYCL_24M_CNT_US(1));
+	mmio_write_32(PMU_BASE + PMU_GPU_PWRDN_CNT, CYCL_24M_CNT_US(1));
+	mmio_write_32(PMU_BASE + PMU_GPU_PWRUP_CNT, CYCL_24M_CNT_US(1));
+}
+
 static void sys_slp_config(void)
 {
 	uint32_t slp_mode_cfg = 0;
@@ -772,57 +857,15 @@
 		       BIT(PMU_OSC_DIS) |
 		       BIT(PMU_PMU_USE_LF);
 
-	mmio_setbits_32(PMU_BASE + PMU_WKUP_CFG4, BIT(PMU_CLUSTER_L_WKUP_EN));
-	mmio_setbits_32(PMU_BASE + PMU_WKUP_CFG4, BIT(PMU_CLUSTER_B_WKUP_EN));
 	mmio_setbits_32(PMU_BASE + PMU_WKUP_CFG4, BIT(PMU_GPIO_WKUP_EN));
 	mmio_write_32(PMU_BASE + PMU_PWRMODE_CON, slp_mode_cfg);
 
-	/*
-	 * About to switch PMU counters to 32K; switch all timings to 32K
-	 * for simplicity even if we don't plan on using them.
-	 */
-	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRDN_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRUP_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRDN_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRUP_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_CENTER_PWRDN_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_CENTER_PWRUP_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_WAKEUP_RST_CLR_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_OSC_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_DDRIO_PWRON_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_PLLLOCK_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_PLLRST_CNT, CYCL_32K_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_STABLE_CNT, CYCL_32K_CNT_MS(3));
-
-	mmio_clrbits_32(PMU_BASE + PMU_SFT_CON, BIT(PMU_24M_EN_CFG));
 
 	mmio_write_32(PMU_BASE + PMU_PLL_CON, PLL_PD_HW);
 	mmio_write_32(PMUGRF_BASE + PMUGRF_SOC_CON0, EXTERNAL_32K);
 	mmio_write_32(PMUGRF_BASE, IOMUX_CLK_32K); /* 32k iomux */
 }
 
-static void sys_slp_unconfig(void)
-{
-	/*
-	 * About to switch PMU counters to 24M; switch all timings to 24M
-	 * for simplicity even if we don't plan on using them.
-	 */
-	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRDN_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_L_PWRUP_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRDN_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_SCU_B_PWRUP_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_CENTER_PWRDN_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_CENTER_PWRUP_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_WAKEUP_RST_CLR_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_OSC_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_DDRIO_PWRON_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_PLLLOCK_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_PLLRST_CNT, CYCL_24M_CNT_MS(3));
-	mmio_write_32(PMU_BASE + PMU_STABLE_CNT, CYCL_24M_CNT_MS(3));
-
-	mmio_setbits_32(PMU_BASE + PMU_SFT_CON, BIT(PMU_24M_EN_CFG));
-}
-
 static void set_hw_idle(uint32_t hw_idle)
 {
 	mmio_setbits_32(PMU_BASE + PMU_BUS_CLR, hw_idle);
@@ -879,6 +922,10 @@
 	}
 	mmio_setbits_32(PMU_BASE + PMU_PWRDN_CON, BIT(PMU_SCU_B_PWRDWN_EN));
 
+	/*
+	 * Disabling PLLs/PWM/DVFS is approaching WFI which is
+	 * the last steps in suspend.
+	 */
 	plls_suspend_prepare();
 	disable_dvfs_plls();
 	disable_pwms();
@@ -899,7 +946,16 @@
 	enable_dvfs_plls();
 	plls_resume_finish();
 
-	sys_slp_unconfig();
+	/*
+	 * The wakeup status is not cleared by itself, we need to clear it
+	 * manually. Otherwise we will alway query some interrupt next time.
+	 *
+	 * NOTE: If the kernel needs to query this, we might want to stash it
+	 * somewhere.
+	 */
+	mmio_write_32(PMU_BASE + PMU_WAKEUP_STATUS, 0xffffffff);
+
+	mmio_write_32(PMU_BASE + PMU_WKUP_CFG4, 0x00);
 
 	mmio_write_32(SGRF_BASE + SGRF_SOC_CON0_1(1),
 		      (cpu_warm_boot_addr >> CPU_BOOT_ADDR_ALIGN) |
@@ -993,6 +1049,42 @@
 	while (1)
 		;
 }
+static void __dead2 sys_pwr_down_wfi(const psci_power_state_t *target_state)
+{
+	uint32_t wakeup_status;
+
+	/*
+	 * Check wakeup status and abort suspend early if we see a wakeup
+	 * event.
+	 *
+	 * NOTE: technically I we're supposed to just execute a wfi here and
+	 * we'll either execute a normal suspend/resume or the wfi will be
+	 * treated as a no-op if a wake event was present and caused an abort
+	 * of the suspend/resume.  For some reason that's not happening and if
+	 * we execute the wfi while a wake event is pending then the whole
+	 * system wedges.
+	 *
+	 * Until the above is solved this extra check prevents system wedges in
+	 * most cases but there is still a small race condition between checking
+	 * PMU_WAKEUP_STATUS and executing wfi.  If a wake event happens in
+	 * there then we will die.
+	 */
+	wakeup_status = mmio_read_32(PMU_BASE + PMU_WAKEUP_STATUS);
+	if (wakeup_status) {
+		WARN("early wake, will not enter power mode.\n");
+
+		mmio_write_32(PMU_BASE + PMU_PWRMODE_CON, 0);
+
+		disable_mmu_icache_el3();
+		bl31_warm_entrypoint();
+
+		while (1)
+			;
+	} else {
+		/* Enter WFI */
+		psci_power_down_wfi();
+	}
+}
 
 static struct rockchip_pm_ops_cb pm_ops = {
 	.cores_pwr_dm_on = cores_pwr_domain_on,
@@ -1008,6 +1100,7 @@
 	.sys_pwr_dm_resume = sys_pwr_domain_resume,
 	.sys_gbl_soft_reset = soc_soft_reset,
 	.system_off = soc_system_off,
+	.sys_pwr_down_wfi = sys_pwr_down_wfi,
 };
 
 void plat_rockchip_pmu_init(void)
@@ -1037,6 +1130,14 @@
 		      CPU_BOOT_ADDR_WMASK);
 	mmio_write_32(PMU_BASE + PMU_NOC_AUTO_ENA, NOC_AUTO_ENABLE);
 
+	/*
+	 * Enable Schmitt trigger for better 32 kHz input signal, which is
+	 * important for suspend/resume reliability among other things.
+	 */
+	mmio_write_32(PMUGRF_BASE + PMUGRF_GPIO0A_SMT, GPIO0A0_SMT_ENABLE);
+
+	init_pmu_counts();
+
 	nonboot_cpus_off();
 
 	INFO("%s(%d): pd status %x\n", __func__, __LINE__,
diff --git a/plat/rockchip/rk3399/drivers/pmu/pmu.h b/plat/rockchip/rk3399/drivers/pmu/pmu.h
index c821efc..65fe7db 100644
--- a/plat/rockchip/rk3399/drivers/pmu/pmu.h
+++ b/plat/rockchip/rk3399/drivers/pmu/pmu.h
@@ -819,6 +819,7 @@
 
 #define AP_PWROFF		0x0a
 
+#define GPIO0A0_SMT_ENABLE	BITS_WITH_WMASK(1, 3, 0)
 #define GPIO1A6_IOMUX		BITS_WITH_WMASK(0, 3, 12)
 
 #define TSADC_INT_PIN		38
@@ -876,6 +877,7 @@
 #define	GRF_SOC_CON4		0x0e210
 #define GRF_GPIO4C_IOMUX	0x0e028
 
+#define PMUGRF_GPIO0A_SMT	0x0120
 #define PMUGRF_SOC_CON0		0x0180
 
 #define CCI_FORCE_WAKEUP	WMSK_BIT(8)
diff --git a/tools/fiptool/fiptool.c b/tools/fiptool/fiptool.c
index 4a3e61a..68ddcf5 100644
--- a/tools/fiptool/fiptool.c
+++ b/tools/fiptool/fiptool.c
@@ -587,7 +587,7 @@
 {
 	toc_entry_t *toc_entry = toc_entries;
 
-	printf("fiptfool create [--plat-toc-flags <value>] [opts] FIP_FILENAME\n");
+	printf("fiptool create [--plat-toc-flags <value>] [opts] FIP_FILENAME\n");
 	printf("  --plat-toc-flags <value>\t16-bit platform specific flag field "
 	    "occupying bits 32-47 in 64-bit ToC header.\n");
 	fputc('\n', stderr);
@@ -672,7 +672,7 @@
 {
 	toc_entry_t *toc_entry = toc_entries;
 
-	printf("fiptfool update [--out FIP_FILENAME] "
+	printf("fiptool update [--out FIP_FILENAME] "
 	    "[--plat-toc-flags <value>] [opts] FIP_FILENAME\n");
 	printf("  --out FIP_FILENAME\t\tSet an alternative output FIP file.\n");
 	printf("  --plat-toc-flags <value>\t16-bit platform specific flag field "