You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
10 KiB
313 lines
10 KiB
/*
|
|
* Copyright (c) 2015, ARM Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <arch_helpers.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <platform.h>
|
|
#include <psci.h>
|
|
|
|
/*
|
|
* The platform hooks exported by the platform using the earlier version of
|
|
* platform interface
|
|
*/
|
|
const plat_pm_ops_t *pm_ops;
|
|
|
|
/*
|
|
* The hooks exported by the compatibility layer
|
|
*/
|
|
static plat_psci_ops_t compat_psci_ops;
|
|
|
|
/*
|
|
* The secure entry point to be used on warm reset.
|
|
*/
|
|
static unsigned long secure_entrypoint;
|
|
|
|
/*
|
|
* This array stores the 'power_state' requests of each CPU during
|
|
* CPU_SUSPEND and SYSTEM_SUSPEND to support querying of state-ID
|
|
* by the platform.
|
|
*/
|
|
unsigned int psci_power_state_compat[PLATFORM_CORE_COUNT];
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper to parse the power state and populate the
|
|
* 'pwr_domain_state' for each power level. It is assumed that, when in
|
|
* compatibility mode, the PSCI generic layer need to know only whether the
|
|
* affinity level will be OFF or in RETENTION and if the platform supports
|
|
* multiple power down and retention states, it will be taken care within
|
|
* the platform layer.
|
|
******************************************************************************/
|
|
static int parse_power_state(unsigned int power_state,
|
|
psci_power_state_t *req_state)
|
|
{
|
|
int i;
|
|
int pstate = psci_get_pstate_type(power_state);
|
|
int aff_lvl = psci_get_pstate_pwrlvl(power_state);
|
|
|
|
if (aff_lvl > PLATFORM_MAX_AFFLVL)
|
|
return PSCI_E_INVALID_PARAMS;
|
|
|
|
/* Sanity check the requested state */
|
|
if (pstate == PSTATE_TYPE_STANDBY) {
|
|
/*
|
|
* Set the CPU local state as retention and ignore the higher
|
|
* levels. This allows the generic PSCI layer to invoke
|
|
* plat_psci_ops 'cpu_standby' hook and the compatibility
|
|
* layer invokes the 'affinst_standby' handler with the
|
|
* correct power_state parameter thus preserving the correct
|
|
* behavior.
|
|
*/
|
|
req_state->pwr_domain_state[0] =
|
|
PLAT_MAX_RET_STATE;
|
|
} else {
|
|
for (i = 0; i <= aff_lvl; i++)
|
|
req_state->pwr_domain_state[i] =
|
|
PLAT_MAX_OFF_STATE;
|
|
}
|
|
|
|
return PSCI_E_SUCCESS;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper to set the 'power_state' in
|
|
* psci_power_state_compat[] at index corresponding to the current core.
|
|
******************************************************************************/
|
|
static void set_psci_power_state_compat(unsigned int power_state)
|
|
{
|
|
unsigned int my_core_pos = plat_my_core_pos();
|
|
|
|
psci_power_state_compat[my_core_pos] = power_state;
|
|
flush_dcache_range((uintptr_t) &psci_power_state_compat[my_core_pos],
|
|
sizeof(psci_power_state_compat[my_core_pos]));
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'validate_power_state'
|
|
* hook.
|
|
******************************************************************************/
|
|
static int validate_power_state_compat(unsigned int power_state,
|
|
psci_power_state_t *req_state)
|
|
{
|
|
int rc;
|
|
assert(req_state);
|
|
|
|
if (pm_ops->validate_power_state) {
|
|
rc = pm_ops->validate_power_state(power_state);
|
|
if (rc != PSCI_E_SUCCESS)
|
|
return rc;
|
|
}
|
|
|
|
/* Store the 'power_state' parameter for the current CPU. */
|
|
set_psci_power_state_compat(power_state);
|
|
|
|
return parse_power_state(power_state, req_state);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t
|
|
* 'get_sys_suspend_power_state' hook.
|
|
******************************************************************************/
|
|
void get_sys_suspend_power_state_compat(psci_power_state_t *req_state)
|
|
{
|
|
unsigned int power_state;
|
|
assert(req_state);
|
|
|
|
power_state = pm_ops->get_sys_suspend_power_state();
|
|
|
|
/* Store the 'power_state' parameter for the current CPU. */
|
|
set_psci_power_state_compat(power_state);
|
|
|
|
if (parse_power_state(power_state, req_state) != PSCI_E_SUCCESS)
|
|
assert(0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'validate_ns_entrypoint'
|
|
* hook.
|
|
******************************************************************************/
|
|
static int validate_ns_entrypoint_compat(uintptr_t ns_entrypoint)
|
|
{
|
|
return pm_ops->validate_ns_entrypoint(ns_entrypoint);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'affinst_standby' hook.
|
|
******************************************************************************/
|
|
static void cpu_standby_compat(plat_local_state_t cpu_state)
|
|
{
|
|
unsigned int powerstate = psci_get_suspend_powerstate();
|
|
|
|
assert(powerstate != PSCI_INVALID_DATA);
|
|
|
|
pm_ops->affinst_standby(powerstate);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'affinst_on' hook.
|
|
******************************************************************************/
|
|
static int pwr_domain_on_compat(u_register_t mpidr)
|
|
{
|
|
int level, rc;
|
|
|
|
/*
|
|
* The new PSCI framework does not hold the locks for higher level
|
|
* power domain nodes when this hook is invoked. Hence figuring out the
|
|
* target state of the parent power domains does not make much sense.
|
|
* Hence we hard-code the state as PSCI_STATE_OFF for all the levels.
|
|
* We expect the platform to perform the necessary CPU_ON operations
|
|
* when the 'affinst_on' is invoked only for level 0.
|
|
*/
|
|
for (level = PLATFORM_MAX_AFFLVL; level >= 0; level--) {
|
|
rc = pm_ops->affinst_on((unsigned long)mpidr, secure_entrypoint,
|
|
level, PSCI_STATE_OFF);
|
|
if (rc != PSCI_E_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'affinst_off' hook.
|
|
******************************************************************************/
|
|
static void pwr_domain_off_compat(const psci_power_state_t *target_state)
|
|
{
|
|
int level;
|
|
unsigned int plat_state;
|
|
|
|
for (level = 0; level <= PLATFORM_MAX_AFFLVL; level++) {
|
|
plat_state = (is_local_state_run(
|
|
target_state->pwr_domain_state[level]) ?
|
|
PSCI_STATE_ON : PSCI_STATE_OFF);
|
|
pm_ops->affinst_off(level, plat_state);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'affinst_suspend' hook.
|
|
******************************************************************************/
|
|
static void pwr_domain_suspend_compat(const psci_power_state_t *target_state)
|
|
{
|
|
int level;
|
|
unsigned int plat_state;
|
|
|
|
for (level = 0; level <= psci_get_suspend_afflvl(); level++) {
|
|
plat_state = (is_local_state_run(
|
|
target_state->pwr_domain_state[level]) ?
|
|
PSCI_STATE_ON : PSCI_STATE_OFF);
|
|
pm_ops->affinst_suspend(secure_entrypoint, level, plat_state);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'affinst_on_finish'
|
|
* hook.
|
|
******************************************************************************/
|
|
static void pwr_domain_on_finish_compat(const psci_power_state_t *target_state)
|
|
{
|
|
int level;
|
|
unsigned int plat_state;
|
|
|
|
for (level = PLATFORM_MAX_AFFLVL; level >= 0; level--) {
|
|
plat_state = (is_local_state_run(
|
|
target_state->pwr_domain_state[level]) ?
|
|
PSCI_STATE_ON : PSCI_STATE_OFF);
|
|
pm_ops->affinst_on_finish(level, plat_state);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t
|
|
* 'affinst_suspend_finish' hook.
|
|
******************************************************************************/
|
|
static void pwr_domain_suspend_finish_compat(
|
|
const psci_power_state_t *target_state)
|
|
{
|
|
int level;
|
|
unsigned int plat_state;
|
|
|
|
for (level = psci_get_suspend_afflvl(); level >= 0; level--) {
|
|
plat_state = (is_local_state_run(
|
|
target_state->pwr_domain_state[level]) ?
|
|
PSCI_STATE_ON : PSCI_STATE_OFF);
|
|
pm_ops->affinst_suspend_finish(level, plat_state);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'system_off' hook.
|
|
******************************************************************************/
|
|
static void __dead2 system_off_compat(void)
|
|
{
|
|
pm_ops->system_off();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* The PSCI compatibility helper for plat_pm_ops_t 'system_reset' hook.
|
|
******************************************************************************/
|
|
static void __dead2 system_reset_compat(void)
|
|
{
|
|
pm_ops->system_reset();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Export the compatibility compat_psci_ops. The assumption made is that the
|
|
* power domains correspond to affinity instances on the platform.
|
|
******************************************************************************/
|
|
int plat_setup_psci_ops(uintptr_t sec_entrypoint,
|
|
const plat_psci_ops_t **psci_ops)
|
|
{
|
|
platform_setup_pm(&pm_ops);
|
|
|
|
secure_entrypoint = (unsigned long) sec_entrypoint;
|
|
|
|
/*
|
|
* It is compulsory for the platform ports using the new porting
|
|
* interface to export a hook to validate the power state parameter
|
|
*/
|
|
compat_psci_ops.validate_power_state = validate_power_state_compat;
|
|
|
|
/*
|
|
* Populate the compatibility plat_psci_ops_t hooks if available
|
|
*/
|
|
if (pm_ops->validate_ns_entrypoint)
|
|
compat_psci_ops.validate_ns_entrypoint =
|
|
validate_ns_entrypoint_compat;
|
|
|
|
if (pm_ops->affinst_standby)
|
|
compat_psci_ops.cpu_standby = cpu_standby_compat;
|
|
|
|
if (pm_ops->affinst_on)
|
|
compat_psci_ops.pwr_domain_on = pwr_domain_on_compat;
|
|
|
|
if (pm_ops->affinst_off)
|
|
compat_psci_ops.pwr_domain_off = pwr_domain_off_compat;
|
|
|
|
if (pm_ops->affinst_suspend)
|
|
compat_psci_ops.pwr_domain_suspend = pwr_domain_suspend_compat;
|
|
|
|
if (pm_ops->affinst_on_finish)
|
|
compat_psci_ops.pwr_domain_on_finish =
|
|
pwr_domain_on_finish_compat;
|
|
|
|
if (pm_ops->affinst_suspend_finish)
|
|
compat_psci_ops.pwr_domain_suspend_finish =
|
|
pwr_domain_suspend_finish_compat;
|
|
|
|
if (pm_ops->system_off)
|
|
compat_psci_ops.system_off = system_off_compat;
|
|
|
|
if (pm_ops->system_reset)
|
|
compat_psci_ops.system_reset = system_reset_compat;
|
|
|
|
if (pm_ops->get_sys_suspend_power_state)
|
|
compat_psci_ops.get_sys_suspend_power_state =
|
|
get_sys_suspend_power_state_compat;
|
|
|
|
*psci_ops = &compat_psci_ops;
|
|
return 0;
|
|
}
|
|
|