diff --git a/plat/allwinner/common/allwinner-common.mk b/plat/allwinner/common/allwinner-common.mk index 901d88895..bf48a70a3 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 30014d2b6..79474e502 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 + 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 000000000..148f50e2a --- /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 +#include +#include +#include +#include +#include + +#include +#include + +#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 4e1a02882..eb1b7e7b8 100644 --- a/plat/allwinner/common/sunxi_pm.c +++ b/plat/allwinner/common/sunxi_pm.c @@ -8,200 +8,14 @@ #include -#include #include -#include -#include -#include #include #include -#include #include -#include -#include #include -#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; + 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); } - 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; - } - - *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 000000000..74763ef7e --- /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 + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * 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(); +}