summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plat/rockchip/common/include/plat_private.h1
-rw-r--r--plat/rockchip/common/plat_pm.c13
-rw-r--r--plat/rockchip/rk3399/drivers/pmu/pmu.c58
3 files changed, 70 insertions, 2 deletions
diff --git a/plat/rockchip/common/include/plat_private.h b/plat/rockchip/common/include/plat_private.h
index 71998998..b9b634e7 100644
--- a/plat/rockchip/common/include/plat_private.h
+++ b/plat/rockchip/common/include/plat_private.h
@@ -56,6 +56,7 @@ struct rockchip_pm_ops_cb {
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 b6291bbf..7372fcff 100644
--- a/plat/rockchip/common/plat_pm.c
+++ b/plat/rockchip/common/plat_pm.c
@@ -311,6 +311,18 @@ static void __dead2 rockchip_system_poweroff(void)
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 @@ const plat_psci_ops_t plat_rockchip_psci_pm_ops = {
.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 827f4830..d2d1acde 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);
@@ -756,8 +757,9 @@ static int hlvl_pwr_domain_resume(uint32_t lvl, plat_local_state_t lvl_state)
*
* 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). Current
- * understanding is that counts work like this:
+ * 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
@@ -920,6 +922,10 @@ static int sys_pwr_domain_suspend(void)
}
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();
@@ -940,6 +946,17 @@ static int sys_pwr_domain_resume(void)
enable_dvfs_plls();
plls_resume_finish();
+ /*
+ * 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) |
CPU_BOOT_ADDR_WMASK);
@@ -1032,6 +1049,42 @@ void __dead2 soc_system_off(void)
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,
@@ -1047,6 +1100,7 @@ static struct rockchip_pm_ops_cb pm_ops = {
.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)