diff options
Diffstat (limited to '')
-rw-r--r-- | plat/nuvoton/npcm845x/npcm845x_psci.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/plat/nuvoton/npcm845x/npcm845x_psci.c b/plat/nuvoton/npcm845x/npcm845x_psci.c new file mode 100644 index 0000000..a954265 --- /dev/null +++ b/plat/nuvoton/npcm845x/npcm845x_psci.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2015-2023, ARM Limited and Contributors. All rights reserved. + * + * Copyright (C) 2017-2023 Nuvoton Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> + +#include <arch.h> +#include <arch_helpers.h> +#include <common/debug.h> +#include <drivers/arm/gicv2.h> +#include <lib/mmio.h> +#include <lib/psci/psci.h> +#include <lib/semihosting.h> +#include <npcm845x_clock.h> +#include <plat/arm/common/plat_arm.h> +#include <plat/common/platform.h> +#include <plat_npcm845x.h> + +#define ADP_STOPPED_APPLICATION_EXIT 0x20026 + +/* Make composite power state parameter till power level 0 */ +#if PSCI_EXTENDED_STATE_ID +/* Not Extended */ +#define npcm845x_make_pwrstate_lvl0(lvl0_state, pwr_lvl, type) \ + (((lvl0_state) << PSTATE_ID_SHIFT) | \ + ((type) << PSTATE_TYPE_SHIFT)) +#else +#define npcm845x_make_pwrstate_lvl0(lvl0_state, pwr_lvl, type) \ + (((lvl0_state) << PSTATE_ID_SHIFT) | \ + ((pwr_lvl) << PSTATE_PWR_LVL_SHIFT) | \ + ((type) << PSTATE_TYPE_SHIFT)) +#endif /* PSCI_EXTENDED_STATE_ID */ + +#define npcm845x_make_pwrstate_lvl1(lvl1_state, lvl0_state, pwr_lvl, type) \ + (((lvl1_state) << PLAT_LOCAL_PSTATE_WIDTH) | \ + npcm845x_make_pwrstate_lvl0(lvl0_state, pwr_lvl, type)) + +/* + * The table storing the valid idle power states. Ensure that the + * array entries are populated in ascending order of state-id to + * enable us to use binary search during power state validation. + * The table must be terminated by a NULL entry. + */ +static const unsigned int npcm845x_pm_idle_states[] = { +/* + * Cluster = 0 (RUN) CPU=1 (RET, higest in idle) - + * Retention. The Power state is Stand-by + */ + +/* State-id - 0x01 */ + npcm845x_make_pwrstate_lvl1(PLAT_LOCAL_STATE_RUN, PLAT_LOCAL_STATE_RET, + MPIDR_AFFLVL0, PSTATE_TYPE_STANDBY), + +/* + * For testing purposes. + * Only CPU suspend to standby is supported by NPCM845x + */ + /* State-id - 0x02 */ + npcm845x_make_pwrstate_lvl1(PLAT_LOCAL_STATE_RUN, PLAT_LOCAL_STATE_OFF, + MPIDR_AFFLVL0, PSTATE_TYPE_POWERDOWN), + 0, +}; + +/******************************************************************************* + * Platform handler called to check the validity of the non secure + * entrypoint. + ******************************************************************************/ +int npcm845x_validate_ns_entrypoint(uintptr_t entrypoint) +{ + /* + * Check if the non secure entrypoint lies within the non + * secure DRAM. + */ + NOTICE("%s() nuvoton_psci\n", __func__); +#ifdef PLAT_ARM_TRUSTED_DRAM_BASE + if ((entrypoint >= PLAT_ARM_TRUSTED_DRAM_BASE) && + (entrypoint < (PLAT_ARM_TRUSTED_DRAM_BASE + + PLAT_ARM_TRUSTED_DRAM_SIZE))) { + return PSCI_E_INVALID_ADDRESS; + } +#endif /* PLAT_ARM_TRUSTED_DRAM_BASE */ + /* For TFTS purposes, '0' is also illegal */ + #ifdef SPD_tspd + if (entrypoint == 0) { + return PSCI_E_INVALID_ADDRESS; + } + #endif /* SPD_tspd */ + return PSCI_E_SUCCESS; +} + +/******************************************************************************* + * Platform handler called when a CPU is about to enter standby. + ******************************************************************************/ +void npcm845x_cpu_standby(plat_local_state_t cpu_state) +{ + NOTICE("%s() nuvoton_psci\n", __func__); + + uint64_t scr; + + scr = read_scr_el3(); + write_scr_el3(scr | SCR_IRQ_BIT | SCR_FIQ_BIT); + + /* + * Enter standby state + * dsb is good practice before using wfi to enter low power states + */ + isb(); + dsb(); + wfi(); + + /* Once awake */ + write_scr_el3(scr); +} + +/******************************************************************************* + * Platform handler called when a power domain is about to be turned on. The + * mpidr determines the CPU to be turned on. + ******************************************************************************/ +int npcm845x_pwr_domain_on(u_register_t mpidr) +{ + int rc = PSCI_E_SUCCESS; + int cpu_id = plat_core_pos_by_mpidr(mpidr); + + if ((unsigned int)cpu_id >= PLATFORM_CORE_COUNT) { + ERROR("%s() CPU 0x%X\n", __func__, cpu_id); + return PSCI_E_INVALID_PARAMS; + } + + if (cpu_id == -1) { + /* domain on was not called by a CPU */ + ERROR("%s() was not per CPU 0x%X\n", __func__, cpu_id); + return PSCI_E_INVALID_PARAMS; + } + + unsigned int pos = (unsigned int)plat_core_pos_by_mpidr(mpidr); + uintptr_t hold_base = PLAT_NPCM_TM_HOLD_BASE; + + assert(pos < PLATFORM_CORE_COUNT); + + hold_base += pos * PLAT_NPCM_TM_HOLD_ENTRY_SIZE; + + mmio_write_64(hold_base, PLAT_NPCM_TM_HOLD_STATE_GO); + /* No cache maintenance here, hold_base is mapped as device memory. */ + + /* Make sure that the write has completed */ + dsb(); + isb(); + + sev(); + + return rc; +} + + +/******************************************************************************* + * Platform handler called when a power domain is about to be suspended. The + * target_state encodes the power state that each level should transition to. + ******************************************************************************/ +void npcm845x_pwr_domain_suspend(const psci_power_state_t *target_state) +{ + NOTICE("%s() nuvoton_psci\n", __func__); + + for (size_t i = 0; (uint64_t)i <= PLAT_MAX_PWR_LVL; i++) { + INFO("%s: target_state->pwr_domain_state[%lu]=%x\n", + __func__, i, target_state->pwr_domain_state[i]); + } + + gicv2_cpuif_disable(); + + NOTICE("%s() Out of suspend\n", __func__); +} + + +/******************************************************************************* + * Platform handler called when a power domain has just been powered on after + * being turned off earlier. The target_state encodes the low power state that + * each level has woken up from. + ******************************************************************************/ +void npcm845x_pwr_domain_on_finish(const psci_power_state_t *target_state) +{ + NOTICE("%s() nuvoton_psci\n", __func__); + + for (size_t i = 0; (uint64_t)i <= PLAT_MAX_PWR_LVL; i++) { + INFO("%s: target_state->pwr_domain_state[%lu]=%x\n", + __func__, i, target_state->pwr_domain_state[i]); + } + + assert(target_state->pwr_domain_state[MPIDR_AFFLVL0] == + PLAT_LOCAL_STATE_OFF); + + gicv2_pcpu_distif_init(); + gicv2_cpuif_enable(); +} + + +/******************************************************************************* + * Platform handler called when a power domain has just been powered on after + * having been suspended earlier. The target_state encodes the low power state + * that each level has woken up from. + ******************************************************************************/ +void npcm845x_pwr_domain_suspend_finish(const psci_power_state_t *target_state) +{ + NOTICE("%s() nuvoton_psci\n", __func__); + + for (size_t i = 0; (uint64_t)i <= PLAT_MAX_PWR_LVL; i++) { + INFO("%s: target_state->pwr_domain_state[%lu]=%x\n", + __func__, i, target_state->pwr_domain_state[i]); + } + + assert(target_state->pwr_domain_state[MPIDR_AFFLVL0] == + PLAT_LOCAL_STATE_OFF); + + gicv2_pcpu_distif_init(); + gicv2_cpuif_enable(); +} + + +void __dead2 npcm845x_system_reset(void) +{ + uintptr_t RESET_BASE_ADDR; + uint32_t val; + + NOTICE("%s() nuvoton_psci\n", __func__); + console_flush(); + + dsbsy(); + isb(); + + /* + * In future - support all reset types. For now, SW1 reset + * Enable software reset 1 to reboot the BMC + */ + RESET_BASE_ADDR = (uintptr_t)0xF0801000; + + /* Read SW1 control register */ + val = mmio_read_32(RESET_BASE_ADDR + 0x44); + /* Keep SPI BMC & MC persist*/ + val &= 0xFBFFFFDF; + /* Setting SW1 control register */ + mmio_write_32(RESET_BASE_ADDR + 0x44, val); + /* Set SW1 reset */ + mmio_write_32(RESET_BASE_ADDR + 0x14, 0x8); + dsb(); + + while (1) { + ; + } +} + +int npcm845x_validate_power_state(unsigned int power_state, + psci_power_state_t *req_state) +{ + unsigned int state_id; + int i; + + NOTICE("%s() nuvoton_psci\n", __func__); + assert(req_state); + + /* + * Currently we are using a linear search for finding the matching + * entry in the idle power state array. This can be made a binary + * search if the number of entries justify the additional complexity. + */ + for (i = 0; !!npcm845x_pm_idle_states[i]; i++) { + if (power_state == npcm845x_pm_idle_states[i]) { + break; + } + } + + /* Return error if entry not found in the idle state array */ + if (!npcm845x_pm_idle_states[i]) { + return PSCI_E_INVALID_PARAMS; + } + + i = 0; + state_id = psci_get_pstate_id(power_state); + + /* Parse the State ID and populate the state info parameter */ + while (state_id) { + req_state->pwr_domain_state[i++] = (uint8_t)state_id & + PLAT_LOCAL_PSTATE_MASK; + state_id >>= PLAT_LOCAL_PSTATE_WIDTH; + } + + return PSCI_E_SUCCESS; +} + +/* + * The NPCM845 doesn't truly support power management at SYSTEM power domain. + * The SYSTEM_SUSPEND will be down-graded to the cluster level within + * the platform layer. The `fake` SYSTEM_SUSPEND allows us to validate + * some of the driver save and restore sequences on FVP. + */ +#if !ARM_BL31_IN_DRAM +void npcm845x_get_sys_suspend_power_state(psci_power_state_t *req_state) +{ + unsigned int i; + + NOTICE("%s() nuvoton_psci\n", __func__); + + for (i = ARM_PWR_LVL0; (uint64_t)i <= PLAT_MAX_PWR_LVL; i++) { + req_state->pwr_domain_state[i] = (uint8_t)PLAT_LOCAL_STATE_OFF; + } +} +#endif /* !ARM_BL31_IN_DRAM */ + +/* + * The rest of the PSCI implementation are for testing purposes only. + * Not supported in Arbel + */ +void __dead2 npcm845x_system_off(void) +{ + console_flush(); + + dsbsy(); + isb(); + + /* NPCM845 doesn't allow real system off, Do reaset instead */ + /* Do reset here TBD which, in the meanwhile SW1 reset */ + for (;;) { + wfi(); + } +} + +void __dead2 plat_secondary_cold_boot_setup(void); + +void __dead2 npcm845x_pwr_down_wfi( + const psci_power_state_t *target_state) +{ + uintptr_t hold_base = PLAT_NPCM_TM_HOLD_BASE; + unsigned int pos = plat_my_core_pos(); + + if (pos == 0) { + /* + * The secondaries will always be in a wait + * for warm boot on reset, but the BSP needs + * to be able to distinguish between waiting + * for warm boot (e.g. after psci_off, waiting + * for psci_on) and a cold boot. + */ + mmio_write_64(hold_base, PLAT_NPCM_TM_HOLD_STATE_BSP_OFF); + /* No cache maintenance here, we run with caches off already. */ + dsb(); + isb(); + } + + wfe(); + + while (1) { + ; + } +} + +/******************************************************************************* + * Platform handler called when a power domain is about to be turned off. The + * target_state encodes the power state that each level should transition to. + ******************************************************************************/ +void npcm845x_pwr_domain_off(const psci_power_state_t *target_state) +{ + NOTICE("%s() nuvoton_psci\n", __func__); + + for (size_t i = 0; (uint64_t)i <= PLAT_MAX_PWR_LVL; i++) { + INFO("%s: target_state->pwr_domain_state[%lu]=%x\n", + __func__, i, target_state->pwr_domain_state[i]); + } + + plat_secondary_cold_boot_setup(); +} + +static const plat_psci_ops_t npcm845x_plat_psci_ops = { + .cpu_standby = npcm845x_cpu_standby, + .pwr_domain_on = npcm845x_pwr_domain_on, + .pwr_domain_suspend = npcm845x_pwr_domain_suspend, + .pwr_domain_on_finish = npcm845x_pwr_domain_on_finish, + .pwr_domain_suspend_finish = npcm845x_pwr_domain_suspend_finish, + .system_reset = npcm845x_system_reset, + .validate_power_state = npcm845x_validate_power_state, + .validate_ns_entrypoint = npcm845x_validate_ns_entrypoint, + + /* For testing purposes only This PSCI states are not supported */ + .pwr_domain_off = npcm845x_pwr_domain_off, + .pwr_domain_pwr_down_wfi = npcm845x_pwr_down_wfi, +}; + +/* For reference only + * typedef struct plat_psci_ops { + * void (*cpu_standby)(plat_local_state_t cpu_state); + * int (*pwr_domain_on)(u_register_t mpidr); + * void (*pwr_domain_off)(const psci_power_state_t *target_state); + * void (*pwr_domain_suspend_pwrdown_early)( + * const psci_power_state_t *target_state); + * void (*pwr_domain_suspend)(const psci_power_state_t *target_state); + * void (*pwr_domain_on_finish)(const psci_power_state_t *target_state); + * void (*pwr_domain_on_finish_late)( + * const psci_power_state_t *target_state); + * void (*pwr_domain_suspend_finish)( + * const psci_power_state_t *target_state); + * void __dead2 (*pwr_domain_pwr_down_wfi)( + * const psci_power_state_t *target_state); + * void __dead2 (*system_off)(void); + * void __dead2 (*system_reset)(void); + * int (*validate_power_state)(unsigned int power_state, + * psci_power_state_t *req_state); + * int (*validate_ns_entrypoint)(uintptr_t ns_entrypoint); + * void (*get_sys_suspend_power_state)( + * psci_power_state_t *req_state); + * int (*get_pwr_lvl_state_idx)(plat_local_state_t pwr_domain_state, + * int pwrlvl); + * int (*translate_power_state_by_mpidr)(u_register_t mpidr, + * unsigned int power_state, + * psci_power_state_t *output_state); + * int (*get_node_hw_state)(u_register_t mpidr, unsigned int power_level); + * int (*mem_protect_chk)(uintptr_t base, u_register_t length); + * int (*read_mem_protect)(int *val); + * int (*write_mem_protect)(int val); + * int (*system_reset2)(int is_vendor, + * int reset_type, u_register_t cookie); + * } plat_psci_ops_t; + */ + +int plat_setup_psci_ops(uintptr_t sec_entrypoint, + const plat_psci_ops_t **psci_ops) +{ + uintptr_t *entrypoint = (void *)PLAT_NPCM_TM_ENTRYPOINT; + + *entrypoint = sec_entrypoint; + + *psci_ops = &npcm845x_plat_psci_ops; + + return 0; +} |