aboutsummaryrefslogtreecommitdiff
path: root/plat/allwinner/common
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2021-01-16 00:56:48 -0600
committerSamuel Holland <samuel@sholland.org>2021-01-24 17:21:31 -0600
commitfe753c97408a92ba245239abd0c056050ae42630 (patch)
treed483c7d1d585ef20f07ca9eac569722db30a4d5c /plat/allwinner/common
parentdae98b3a986c69517e59e3a1eb08e2b22225cb2d (diff)
downloadtrusted-firmware-a-fe753c97408a92ba245239abd0c056050ae42630.tar.gz
allwinner: Split native and SCPI-based PSCI implementations
In order to keep SCP firmware as optional, the original, limited native PSCI implementation was kept around as a fallback. This turned out to be a good decision, as some newer SoCs omit the ARISC, and thus cannot run SCP firmware. However, keeping the two implementations in one file makes things unnecessarily messy. First, it is difficult to compile out the SCPI-based implementation where it is not applicable. Second the check is done in each callback, while scpi_available is only updated at boot. This makes the individual callbacks unnecessarily complicated. It is cleaner to provide two entirely separate implementations in two separate files. The native implementation does not support any kind of CPU suspend, so its callbacks are greatly simplified. One function, sunxi_validate_ns_entrypoint, is shared between the two implementations. Finally, the logic for choosing between implementations is kept in a third file, to provide for platforms where only one implementation is applicable and the other is compiled out. Change-Id: I4914f07d8e693dbce218e0e2394bef15c42945f8 Signed-off-by: Samuel Holland <samuel@sholland.org>
Diffstat (limited to 'plat/allwinner/common')
-rw-r--r--plat/allwinner/common/allwinner-common.mk2
-rw-r--r--plat/allwinner/common/include/sunxi_private.h6
-rw-r--r--plat/allwinner/common/sunxi_native_pm.c81
-rw-r--r--plat/allwinner/common/sunxi_pm.c238
-rw-r--r--plat/allwinner/common/sunxi_scpi_pm.c223
5 files changed, 318 insertions, 232 deletions
diff --git a/plat/allwinner/common/allwinner-common.mk b/plat/allwinner/common/allwinner-common.mk
index 901d888954..bf48a70a3a 100644
--- a/plat/allwinner/common/allwinner-common.mk
+++ b/plat/allwinner/common/allwinner-common.mk
@@ -30,7 +30,9 @@ BL31_SOURCES += drivers/allwinner/axp/common.c \
plat/common/plat_psci_common.c \
${AW_PLAT}/common/sunxi_bl31_setup.c \
${AW_PLAT}/common/sunxi_cpu_ops.c \
+ ${AW_PLAT}/common/sunxi_native_pm.c \
${AW_PLAT}/common/sunxi_pm.c \
+ ${AW_PLAT}/common/sunxi_scpi_pm.c \
${AW_PLAT}/${PLAT}/sunxi_power.c \
${AW_PLAT}/common/sunxi_security.c \
${AW_PLAT}/common/sunxi_topology.c
diff --git a/plat/allwinner/common/include/sunxi_private.h b/plat/allwinner/common/include/sunxi_private.h
index 30014d2b6d..79474e5023 100644
--- a/plat/allwinner/common/include/sunxi_private.h
+++ b/plat/allwinner/common/include/sunxi_private.h
@@ -7,6 +7,8 @@
#ifndef SUNXI_PRIVATE_H
#define SUNXI_PRIVATE_H
+#include <lib/psci/psci.h>
+
void sunxi_configure_mmu_el3(int flags);
void sunxi_cpu_on(u_register_t mpidr);
@@ -14,6 +16,10 @@ void sunxi_cpu_power_off_others(void);
void sunxi_cpu_power_off_self(void);
void sunxi_power_down(void);
+void sunxi_set_native_psci_ops(const plat_psci_ops_t **psci_ops);
+int sunxi_set_scpi_psci_ops(const plat_psci_ops_t **psci_ops);
+int sunxi_validate_ns_entrypoint(uintptr_t ns_entrypoint);
+
int sunxi_pmic_setup(uint16_t socid, const void *fdt);
void sunxi_security_setup(void);
diff --git a/plat/allwinner/common/sunxi_native_pm.c b/plat/allwinner/common/sunxi_native_pm.c
new file mode 100644
index 0000000000..148f50e2af
--- /dev/null
+++ b/plat/allwinner/common/sunxi_native_pm.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017-2021, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <common/debug.h>
+#include <drivers/arm/gicv2.h>
+#include <drivers/delay_timer.h>
+#include <lib/mmio.h>
+#include <lib/psci/psci.h>
+
+#include <sunxi_mmap.h>
+#include <sunxi_private.h>
+
+#define SUNXI_WDOG0_CTRL_REG (SUNXI_R_WDOG_BASE + 0x0010)
+#define SUNXI_WDOG0_CFG_REG (SUNXI_R_WDOG_BASE + 0x0014)
+#define SUNXI_WDOG0_MODE_REG (SUNXI_R_WDOG_BASE + 0x0018)
+
+static int sunxi_pwr_domain_on(u_register_t mpidr)
+{
+ sunxi_cpu_on(mpidr);
+
+ return PSCI_E_SUCCESS;
+}
+
+static void sunxi_pwr_domain_off(const psci_power_state_t *target_state)
+{
+ gicv2_cpuif_disable();
+
+ sunxi_cpu_power_off_self();
+}
+
+static void sunxi_pwr_domain_on_finish(const psci_power_state_t *target_state)
+{
+ gicv2_pcpu_distif_init();
+ gicv2_cpuif_enable();
+}
+
+static void __dead2 sunxi_system_off(void)
+{
+ gicv2_cpuif_disable();
+
+ /* Attempt to power down the board (may not return) */
+ sunxi_power_down();
+
+ /* Turn off all CPUs */
+ sunxi_cpu_power_off_others();
+ sunxi_cpu_power_off_self();
+ psci_power_down_wfi();
+}
+
+static void __dead2 sunxi_system_reset(void)
+{
+ gicv2_cpuif_disable();
+
+ /* Reset the whole system when the watchdog times out */
+ mmio_write_32(SUNXI_WDOG0_CFG_REG, 1);
+ /* Enable the watchdog with the shortest timeout (0.5 seconds) */
+ mmio_write_32(SUNXI_WDOG0_MODE_REG, (0 << 4) | 1);
+ /* Wait for twice the watchdog timeout before panicking */
+ mdelay(1000);
+
+ ERROR("PSCI: System reset failed\n");
+ panic();
+}
+
+static const plat_psci_ops_t sunxi_native_psci_ops = {
+ .pwr_domain_on = sunxi_pwr_domain_on,
+ .pwr_domain_off = sunxi_pwr_domain_off,
+ .pwr_domain_on_finish = sunxi_pwr_domain_on_finish,
+ .system_off = sunxi_system_off,
+ .system_reset = sunxi_system_reset,
+ .validate_ns_entrypoint = sunxi_validate_ns_entrypoint,
+};
+
+void sunxi_set_native_psci_ops(const plat_psci_ops_t **psci_ops)
+{
+ *psci_ops = &sunxi_native_psci_ops;
+}
diff --git a/plat/allwinner/common/sunxi_pm.c b/plat/allwinner/common/sunxi_pm.c
index 4e1a028820..eb1b7e7b85 100644
--- a/plat/allwinner/common/sunxi_pm.c
+++ b/plat/allwinner/common/sunxi_pm.c
@@ -8,200 +8,14 @@
#include <platform_def.h>
-#include <arch_helpers.h>
#include <common/debug.h>
-#include <drivers/arm/css/css_scpi.h>
-#include <drivers/arm/gicv2.h>
-#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <lib/psci/psci.h>
-#include <plat/common/platform.h>
#include <sunxi_cpucfg.h>
-#include <sunxi_def.h>
-#include <sunxi_mmap.h>
#include <sunxi_private.h>
-#define SUNXI_WDOG0_CTRL_REG (SUNXI_R_WDOG_BASE + 0x0010)
-#define SUNXI_WDOG0_CFG_REG (SUNXI_R_WDOG_BASE + 0x0014)
-#define SUNXI_WDOG0_MODE_REG (SUNXI_R_WDOG_BASE + 0x0018)
-
-#define CPU_PWR_LVL MPIDR_AFFLVL0
-#define CLUSTER_PWR_LVL MPIDR_AFFLVL1
-#define SYSTEM_PWR_LVL MPIDR_AFFLVL2
-
-#define CPU_PWR_STATE(state) \
- ((state)->pwr_domain_state[CPU_PWR_LVL])
-#define CLUSTER_PWR_STATE(state) \
- ((state)->pwr_domain_state[CLUSTER_PWR_LVL])
-#define SYSTEM_PWR_STATE(state) \
- ((state)->pwr_domain_state[SYSTEM_PWR_LVL])
-
-/*
- * The addresses for the SCP exception vectors are defined in the or1k
- * architecture specification.
- */
-#define OR1K_VEC_FIRST 0x01
-#define OR1K_VEC_LAST 0x0e
-#define OR1K_VEC_ADDR(n) (0x100 * (n))
-
-/*
- * This magic value is the little-endian representation of the or1k
- * instruction "l.mfspr r2, r0, 0x12", which is guaranteed to be the
- * first instruction in the SCP firmware.
- */
-#define SCP_FIRMWARE_MAGIC 0xb4400012
-
-static bool scpi_available;
-
-static inline scpi_power_state_t scpi_map_state(plat_local_state_t psci_state)
-{
- if (is_local_state_run(psci_state))
- return scpi_power_on;
- if (is_local_state_retn(psci_state))
- return scpi_power_retention;
- return scpi_power_off;
-}
-
-static void sunxi_cpu_standby(plat_local_state_t cpu_state)
-{
- u_register_t scr = read_scr_el3();
-
- assert(is_local_state_retn(cpu_state));
-
- write_scr_el3(scr | SCR_IRQ_BIT);
- wfi();
- write_scr_el3(scr);
-}
-
-static int sunxi_pwr_domain_on(u_register_t mpidr)
-{
- if (scpi_available) {
- scpi_set_css_power_state(mpidr,
- scpi_power_on,
- scpi_power_on,
- scpi_power_on);
- } else {
- sunxi_cpu_on(mpidr);
- }
-
- return PSCI_E_SUCCESS;
-}
-
-static void sunxi_pwr_domain_off(const psci_power_state_t *target_state)
-{
- plat_local_state_t cpu_pwr_state = CPU_PWR_STATE(target_state);
- plat_local_state_t cluster_pwr_state = CLUSTER_PWR_STATE(target_state);
- plat_local_state_t system_pwr_state = SYSTEM_PWR_STATE(target_state);
-
- if (is_local_state_off(cpu_pwr_state))
- gicv2_cpuif_disable();
-
- if (scpi_available) {
- scpi_set_css_power_state(read_mpidr(),
- scpi_map_state(cpu_pwr_state),
- scpi_map_state(cluster_pwr_state),
- scpi_map_state(system_pwr_state));
- } else {
- sunxi_cpu_power_off_self();
- }
-}
-
-static void sunxi_pwr_domain_on_finish(const psci_power_state_t *target_state)
-{
- if (is_local_state_off(SYSTEM_PWR_STATE(target_state)))
- gicv2_distif_init();
- if (is_local_state_off(CPU_PWR_STATE(target_state))) {
- gicv2_pcpu_distif_init();
- gicv2_cpuif_enable();
- }
-}
-
-static void __dead2 sunxi_system_off(void)
-{
- gicv2_cpuif_disable();
-
- if (scpi_available) {
- /* Send the power down request to the SCP */
- uint32_t ret = scpi_sys_power_state(scpi_system_shutdown);
-
- if (ret == SCP_OK) {
- wfi();
- }
-
- ERROR("PSCI: SCPI %s failed: %d\n", "shutdown", ret);
- }
-
- /* Attempt to power down the board (may not return) */
- sunxi_power_down();
-
- /* Turn off all CPUs */
- sunxi_cpu_power_off_others();
- sunxi_cpu_power_off_self();
- psci_power_down_wfi();
-}
-
-static void __dead2 sunxi_system_reset(void)
-{
- gicv2_cpuif_disable();
-
- if (scpi_available) {
- /* Send the system reset request to the SCP */
- uint32_t ret = scpi_sys_power_state(scpi_system_reboot);
-
- if (ret == SCP_OK) {
- wfi();
- }
-
- ERROR("PSCI: SCPI %s failed: %d\n", "reboot", ret);
- }
-
- /* Reset the whole system when the watchdog times out */
- mmio_write_32(SUNXI_WDOG0_CFG_REG, 1);
- /* Enable the watchdog with the shortest timeout (0.5 seconds) */
- mmio_write_32(SUNXI_WDOG0_MODE_REG, (0 << 4) | 1);
- /* Wait for twice the watchdog timeout before panicking */
- mdelay(1000);
-
- ERROR("PSCI: System reset failed\n");
- panic();
-}
-
-static int sunxi_validate_power_state(unsigned int power_state,
- psci_power_state_t *req_state)
-{
- unsigned int power_level = psci_get_pstate_pwrlvl(power_state);
- unsigned int type = psci_get_pstate_type(power_state);
-
- assert(req_state != NULL);
-
- if (power_level > PLAT_MAX_PWR_LVL)
- return PSCI_E_INVALID_PARAMS;
-
- if (type == PSTATE_TYPE_STANDBY) {
- /* Only one retention power state is supported. */
- if (psci_get_pstate_id(power_state) > 0)
- return PSCI_E_INVALID_PARAMS;
- /* The SoC cannot be suspended without losing state */
- if (power_level == SYSTEM_PWR_LVL)
- return PSCI_E_INVALID_PARAMS;
- for (unsigned int i = 0; i <= power_level; ++i)
- req_state->pwr_domain_state[i] = PLAT_MAX_RET_STATE;
- } else {
- /* Only one off power state is supported. */
- if (psci_get_pstate_id(power_state) > 0)
- return PSCI_E_INVALID_PARAMS;
- for (unsigned int i = 0; i <= power_level; ++i)
- req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
- }
- /* Higher power domain levels should all remain running */
- for (unsigned int i = power_level + 1; i <= PLAT_MAX_PWR_LVL; ++i)
- req_state->pwr_domain_state[i] = PSCI_LOCAL_STATE_RUN;
-
- return PSCI_E_SUCCESS;
-}
-
-static int sunxi_validate_ns_entrypoint(uintptr_t ns_entrypoint)
+int sunxi_validate_ns_entrypoint(uintptr_t ns_entrypoint)
{
/* The non-secure entry point must be in DRAM */
if (ns_entrypoint < SUNXI_DRAM_BASE) {
@@ -211,25 +25,6 @@ static int sunxi_validate_ns_entrypoint(uintptr_t ns_entrypoint)
return PSCI_E_SUCCESS;
}
-static void sunxi_get_sys_suspend_power_state(psci_power_state_t *req_state)
-{
- assert(req_state);
-
- for (unsigned int i = 0; i <= PLAT_MAX_PWR_LVL; ++i)
- req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
-}
-
-static plat_psci_ops_t sunxi_psci_ops = {
- .cpu_standby = sunxi_cpu_standby,
- .pwr_domain_on = sunxi_pwr_domain_on,
- .pwr_domain_off = sunxi_pwr_domain_off,
- .pwr_domain_on_finish = sunxi_pwr_domain_on_finish,
- .system_off = sunxi_system_off,
- .system_reset = sunxi_system_reset,
- .validate_power_state = sunxi_validate_power_state,
- .validate_ns_entrypoint = sunxi_validate_ns_entrypoint,
-};
-
int plat_setup_psci_ops(uintptr_t sec_entrypoint,
const plat_psci_ops_t **psci_ops)
{
@@ -243,33 +38,12 @@ int plat_setup_psci_ops(uintptr_t sec_entrypoint,
sec_entrypoint >> 32);
}
- /* Check for a valid SCP firmware, and boot the SCP if found. */
- if (mmio_read_32(SUNXI_SCP_BASE) == SCP_FIRMWARE_MAGIC) {
- /* Program SCP exception vectors to the firmware entrypoint. */
- for (unsigned int i = OR1K_VEC_FIRST; i <= OR1K_VEC_LAST; ++i) {
- uint32_t vector = SUNXI_SRAM_A2_BASE + OR1K_VEC_ADDR(i);
- uint32_t offset = SUNXI_SCP_BASE - vector;
-
- mmio_write_32(vector, offset >> 2);
- clean_dcache_range(vector, sizeof(uint32_t));
- }
- /* Take the SCP out of reset. */
- mmio_setbits_32(SUNXI_R_CPUCFG_BASE, BIT(0));
- /* Wait for the SCP firmware to boot. */
- if (scpi_wait_ready() == 0)
- scpi_available = true;
- }
-
- NOTICE("PSCI: System suspend is %s\n",
- scpi_available ? "available via SCPI" : "unavailable");
- if (scpi_available) {
- /* Suspend is only available via SCPI. */
- sunxi_psci_ops.pwr_domain_suspend = sunxi_pwr_domain_off;
- sunxi_psci_ops.pwr_domain_suspend_finish = sunxi_pwr_domain_on_finish;
- sunxi_psci_ops.get_sys_suspend_power_state = sunxi_get_sys_suspend_power_state;
+ if (sunxi_set_scpi_psci_ops(psci_ops) == 0) {
+ INFO("PSCI: Suspend is available via SCPI\n");
+ } else {
+ INFO("PSCI: Suspend is unavailable\n");
+ sunxi_set_native_psci_ops(psci_ops);
}
- *psci_ops = &sunxi_psci_ops;
-
return 0;
}
diff --git a/plat/allwinner/common/sunxi_scpi_pm.c b/plat/allwinner/common/sunxi_scpi_pm.c
new file mode 100644
index 0000000000..74763ef7ed
--- /dev/null
+++ b/plat/allwinner/common/sunxi_scpi_pm.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2017-2021, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+
+#include <platform_def.h>
+
+#include <arch_helpers.h>
+#include <common/debug.h>
+#include <drivers/arm/css/css_scpi.h>
+#include <drivers/arm/gicv2.h>
+#include <lib/mmio.h>
+#include <lib/psci/psci.h>
+
+#include <sunxi_mmap.h>
+#include <sunxi_private.h>
+
+/*
+ * The addresses for the SCP exception vectors are defined in the or1k
+ * architecture specification.
+ */
+#define OR1K_VEC_FIRST 0x01
+#define OR1K_VEC_LAST 0x0e
+#define OR1K_VEC_ADDR(n) (0x100 * (n))
+
+/*
+ * This magic value is the little-endian representation of the or1k
+ * instruction "l.mfspr r2, r0, 0x12", which is guaranteed to be the
+ * first instruction in the SCP firmware.
+ */
+#define SCP_FIRMWARE_MAGIC 0xb4400012
+
+#define CPU_PWR_LVL MPIDR_AFFLVL0
+#define CLUSTER_PWR_LVL MPIDR_AFFLVL1
+#define SYSTEM_PWR_LVL MPIDR_AFFLVL2
+
+#define CPU_PWR_STATE(state) \
+ ((state)->pwr_domain_state[CPU_PWR_LVL])
+#define CLUSTER_PWR_STATE(state) \
+ ((state)->pwr_domain_state[CLUSTER_PWR_LVL])
+#define SYSTEM_PWR_STATE(state) \
+ ((state)->pwr_domain_state[SYSTEM_PWR_LVL])
+
+static inline scpi_power_state_t scpi_map_state(plat_local_state_t psci_state)
+{
+ if (is_local_state_run(psci_state)) {
+ return scpi_power_on;
+ }
+ if (is_local_state_retn(psci_state)) {
+ return scpi_power_retention;
+ }
+ return scpi_power_off;
+}
+
+static void sunxi_cpu_standby(plat_local_state_t cpu_state)
+{
+ u_register_t scr = read_scr_el3();
+
+ assert(is_local_state_retn(cpu_state));
+
+ write_scr_el3(scr | SCR_IRQ_BIT);
+ wfi();
+ write_scr_el3(scr);
+}
+
+static int sunxi_pwr_domain_on(u_register_t mpidr)
+{
+ scpi_set_css_power_state(mpidr,
+ scpi_power_on,
+ scpi_power_on,
+ scpi_power_on);
+
+ return PSCI_E_SUCCESS;
+}
+
+static void sunxi_pwr_domain_off(const psci_power_state_t *target_state)
+{
+ plat_local_state_t cpu_pwr_state = CPU_PWR_STATE(target_state);
+ plat_local_state_t cluster_pwr_state = CLUSTER_PWR_STATE(target_state);
+ plat_local_state_t system_pwr_state = SYSTEM_PWR_STATE(target_state);
+
+ if (is_local_state_off(cpu_pwr_state)) {
+ gicv2_cpuif_disable();
+ }
+
+ scpi_set_css_power_state(read_mpidr(),
+ scpi_map_state(cpu_pwr_state),
+ scpi_map_state(cluster_pwr_state),
+ scpi_map_state(system_pwr_state));
+}
+
+static void sunxi_pwr_domain_on_finish(const psci_power_state_t *target_state)
+{
+ if (is_local_state_off(SYSTEM_PWR_STATE(target_state))) {
+ gicv2_distif_init();
+ }
+ if (is_local_state_off(CPU_PWR_STATE(target_state))) {
+ gicv2_pcpu_distif_init();
+ gicv2_cpuif_enable();
+ }
+}
+
+static void __dead2 sunxi_system_off(void)
+{
+ uint32_t ret;
+
+ gicv2_cpuif_disable();
+
+ /* Send the power down request to the SCP. */
+ ret = scpi_sys_power_state(scpi_system_shutdown);
+ if (ret != SCP_OK) {
+ ERROR("PSCI: SCPI %s failed: %d\n", "shutdown", ret);
+ }
+
+ psci_power_down_wfi();
+}
+
+static void __dead2 sunxi_system_reset(void)
+{
+ uint32_t ret;
+
+ gicv2_cpuif_disable();
+
+ /* Send the system reset request to the SCP. */
+ ret = scpi_sys_power_state(scpi_system_reboot);
+ if (ret != SCP_OK) {
+ ERROR("PSCI: SCPI %s failed: %d\n", "reboot", ret);
+ }
+
+ psci_power_down_wfi();
+}
+
+static int sunxi_validate_power_state(unsigned int power_state,
+ psci_power_state_t *req_state)
+{
+ unsigned int power_level = psci_get_pstate_pwrlvl(power_state);
+ unsigned int type = psci_get_pstate_type(power_state);
+
+ assert(req_state != NULL);
+
+ if (power_level > PLAT_MAX_PWR_LVL) {
+ return PSCI_E_INVALID_PARAMS;
+ }
+
+ if (type == PSTATE_TYPE_STANDBY) {
+ /* Only one retention power state is supported. */
+ if (psci_get_pstate_id(power_state) > 0) {
+ return PSCI_E_INVALID_PARAMS;
+ }
+ /* The SoC cannot be suspended without losing state */
+ if (power_level == SYSTEM_PWR_LVL) {
+ return PSCI_E_INVALID_PARAMS;
+ }
+ for (unsigned int i = 0; i <= power_level; ++i) {
+ req_state->pwr_domain_state[i] = PLAT_MAX_RET_STATE;
+ }
+ } else {
+ /* Only one off power state is supported. */
+ if (psci_get_pstate_id(power_state) > 0) {
+ return PSCI_E_INVALID_PARAMS;
+ }
+ for (unsigned int i = 0; i <= power_level; ++i) {
+ req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
+ }
+ }
+ /* Higher power domain levels should all remain running */
+ for (unsigned int i = power_level + 1; i <= PLAT_MAX_PWR_LVL; ++i) {
+ req_state->pwr_domain_state[i] = PSCI_LOCAL_STATE_RUN;
+ }
+
+ return PSCI_E_SUCCESS;
+}
+
+static void sunxi_get_sys_suspend_power_state(psci_power_state_t *req_state)
+{
+ assert(req_state != NULL);
+
+ for (unsigned int i = 0; i <= PLAT_MAX_PWR_LVL; ++i) {
+ req_state->pwr_domain_state[i] = PLAT_MAX_OFF_STATE;
+ }
+}
+
+static const plat_psci_ops_t sunxi_scpi_psci_ops = {
+ .cpu_standby = sunxi_cpu_standby,
+ .pwr_domain_on = sunxi_pwr_domain_on,
+ .pwr_domain_off = sunxi_pwr_domain_off,
+ .pwr_domain_suspend = sunxi_pwr_domain_off,
+ .pwr_domain_on_finish = sunxi_pwr_domain_on_finish,
+ .pwr_domain_suspend_finish = sunxi_pwr_domain_on_finish,
+ .system_off = sunxi_system_off,
+ .system_reset = sunxi_system_reset,
+ .validate_power_state = sunxi_validate_power_state,
+ .validate_ns_entrypoint = sunxi_validate_ns_entrypoint,
+ .get_sys_suspend_power_state = sunxi_get_sys_suspend_power_state,
+};
+
+int sunxi_set_scpi_psci_ops(const plat_psci_ops_t **psci_ops)
+{
+ *psci_ops = &sunxi_scpi_psci_ops;
+
+ /* Check for a valid SCP firmware. */
+ if (mmio_read_32(SUNXI_SCP_BASE) != SCP_FIRMWARE_MAGIC) {
+ return -1;
+ }
+
+ /* Program SCP exception vectors to the firmware entrypoint. */
+ for (unsigned int i = OR1K_VEC_FIRST; i <= OR1K_VEC_LAST; ++i) {
+ uint32_t vector = SUNXI_SRAM_A2_BASE + OR1K_VEC_ADDR(i);
+ uint32_t offset = SUNXI_SCP_BASE - vector;
+
+ mmio_write_32(vector, offset >> 2);
+ clean_dcache_range(vector, sizeof(uint32_t));
+ }
+
+ /* Take the SCP out of reset. */
+ mmio_setbits_32(SUNXI_R_CPUCFG_BASE, BIT(0));
+
+ /* Wait for the SCP firmware to boot. */
+ return scpi_wait_ready();
+}