Browse Source

Merge pull request #963 from soby-mathew/sm/scmi_dev

Add SCMI power domain and system power protocol support
pull/965/merge
danh-arm 8 years ago
committed by GitHub
parent
commit
b32e6b2b35
  1. 4
      docs/user-guide.md
  2. 3
      include/plat/arm/common/plat_arm.h
  3. 18
      include/plat/arm/css/common/css_def.h
  4. 9
      plat/arm/board/common/board_css_common.c
  5. 9
      plat/arm/board/juno/include/platform_def.h
  6. 7
      plat/arm/board/juno/juno_topology.c
  7. 22
      plat/arm/css/common/css_common.mk
  8. 132
      plat/arm/css/drivers/scmi/scmi.h
  9. 196
      plat/arm/css/drivers/scmi/scmi_common.c
  10. 148
      plat/arm/css/drivers/scmi/scmi_private.h
  11. 84
      plat/arm/css/drivers/scmi/scmi_pwr_dmn_proto.c
  12. 74
      plat/arm/css/drivers/scmi/scmi_sys_pwr_proto.c
  13. 385
      plat/arm/css/drivers/scp/css_pm_scmi.c

4
docs/user-guide.md

@ -622,6 +622,10 @@ map is explained in the [Firmware Design].
SCP_BL2U to the FIP and FWU_FIP respectively, and enables them to be loaded
during boot. Default is 1.
* `CSS_USE_SCMI_DRIVER`: Boolean flag which selects SCMI driver instead of
SCPI driver for communicating with the SCP during power management operations.
If this option is set to 1, then SCMI driver will be used. Default is 0.
#### ARM FVP platform specific build options
* `FVP_CLUSTER_COUNT` : Configures the cluster count to be used to

3
include/plat/arm/common/plat_arm.h

@ -46,7 +46,7 @@ void arm_setup_page_tables(uintptr_t total_base,
* arm_lock_xxx() macros
*/
#define ARM_INSTANTIATE_LOCK DEFINE_BAKERY_LOCK(arm_lock);
#define ARM_LOCK_GET_INSTANCE (&arm_lock)
/*
* These are wrapper macros to the Coherent Memory Bakery Lock API.
*/
@ -60,6 +60,7 @@ void arm_setup_page_tables(uintptr_t total_base,
* Empty macros for all other BL stages other than BL31 and BL32
*/
#define ARM_INSTANTIATE_LOCK
#define ARM_LOCK_GET_INSTANCE 0
#define arm_lock_init()
#define arm_lock_get()
#define arm_lock_release()

18
include/plat/arm/css/common/css_def.h

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2016, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@ -47,6 +47,17 @@
CSS_IRQ_TZ_WDOG, \
CSS_IRQ_SEC_SYS_TIMER
/*
* The lower Non-secure MHU channel is being used for SCMI for ARM Trusted
* Firmware.
* TODO: Move SCMI to Secure channel once the migration to SCMI in SCP is
* complete.
*/
#define MHU_CPU_INTR_L_SET_OFFSET 0x108
#define MHU_CPU_INTR_H_SET_OFFSET 0x128
#define CSS_SCMI_PAYLOAD_BASE (NSRAM_BASE + 0x500)
#define CSS_SCMI_MHU_DB_REG_OFF MHU_CPU_INTR_L_SET_OFFSET
/*
* SCP <=> AP boot configuration
*
@ -63,6 +74,11 @@
CSS_DEVICE_SIZE, \
MT_DEVICE | MT_RW | MT_SECURE)
#define CSS_MAP_NSRAM MAP_REGION_FLAT( \
NSRAM_BASE, \
NSRAM_SIZE, \
MT_DEVICE | MT_RW | MT_SECURE)
/* Platform ID address */
#define SSC_VERSION_OFFSET 0x040

9
plat/arm/board/common/board_css_common.c

@ -49,6 +49,15 @@ const mmap_region_t plat_arm_mmap[] = {
ARM_MAP_SHARED_RAM,
V2M_MAP_IOFPGA,
CSS_MAP_DEVICE,
#if CSS_USE_SCMI_DRIVER
/*
* The SCMI payload area is currently in the Non Secure SRAM. This is
* a potential security risk but this will be resolved once SCP
* completely replaces SCPI with SCMI as the only communication
* protocol.
*/
CSS_MAP_NSRAM,
#endif
SOC_CSS_MAP_DEVICE,
{0}
};

9
plat/arm/board/juno/include/platform_def.h

@ -74,8 +74,13 @@
#endif
#ifdef IMAGE_BL31
# define PLAT_ARM_MMAP_ENTRIES 5
# define MAX_XLAT_TABLES 2
# if CSS_USE_SCMI_DRIVER
# define PLAT_ARM_MMAP_ENTRIES 6
# define MAX_XLAT_TABLES 3
# else
# define PLAT_ARM_MMAP_ENTRIES 5
# define MAX_XLAT_TABLES 2
# endif
#endif
#ifdef IMAGE_BL32

7
plat/arm/board/juno/juno_topology.c

@ -51,3 +51,10 @@ unsigned int plat_arm_get_cluster_core_count(u_register_t mpidr)
return (((mpidr) & 0x100) ? JUNO_CLUSTER1_CORE_COUNT :\
JUNO_CLUSTER0_CORE_COUNT);
}
/*
* The array mapping platform core position (implemented by plat_my_core_pos())
* to the SCMI power domain ID implemented by SCP.
*/
const uint32_t plat_css_core_pos_to_scmi_dmn_id_map[PLATFORM_CORE_COUNT] = {
2, 3, 4, 5, 0, 1 };

22
plat/arm/css/common/css_common.mk

@ -1,5 +1,5 @@
#
# Copyright (c) 2015-2016, ARM Limited and Contributors. All rights reserved.
# Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
@ -8,6 +8,9 @@
# By default, SCP images are needed by CSS platforms.
CSS_LOAD_SCP_IMAGES ?= 1
# By default, SCMI driver is disabled for CSS platforms
CSS_USE_SCMI_DRIVER ?= 0
PLAT_INCLUDES += -Iinclude/plat/arm/css/common \
-Iinclude/plat/arm/css/common/aarch64
@ -25,10 +28,18 @@ BL2U_SOURCES += plat/arm/css/common/css_bl2u_setup.c \
plat/arm/css/drivers/scpi/css_scpi.c
BL31_SOURCES += plat/arm/css/common/css_pm.c \
plat/arm/css/common/css_topology.c \
plat/arm/css/drivers/scp/css_pm_scpi.c \
plat/arm/css/common/css_topology.c
ifeq (${CSS_USE_SCMI_DRIVER},0)
BL31_SOURCES += plat/arm/css/drivers/scp/css_pm_scpi.c \
plat/arm/css/drivers/scpi/css_mhu.c \
plat/arm/css/drivers/scpi/css_scpi.c
else
BL31_SOURCES += plat/arm/css/drivers/scp/css_pm_scmi.c \
plat/arm/css/drivers/scmi/scmi_common.c \
plat/arm/css/drivers/scmi/scmi_pwr_dmn_proto.c \
plat/arm/css/drivers/scmi/scmi_sys_pwr_proto.c
endif
ifneq (${RESET_TO_BL31},0)
$(error "Using BL31 as the reset vector is not supported on CSS platforms. \
@ -56,3 +67,8 @@ CSS_DETECT_PRE_1_7_0_SCP := 1
# Process CSS_DETECT_PRE_1_7_0_SCP flag
$(eval $(call assert_boolean,CSS_DETECT_PRE_1_7_0_SCP))
$(eval $(call add_define,CSS_DETECT_PRE_1_7_0_SCP))
# Process CSS_USE_SCMI_DRIVER flag
$(eval $(call assert_boolean,CSS_USE_SCMI_DRIVER))
$(eval $(call add_define,CSS_USE_SCMI_DRIVER))

132
plat/arm/css/drivers/scmi/scmi.h

@ -0,0 +1,132 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __CSS_SCMI_H__
#define __CSS_SCMI_H__
#include <bakery_lock.h>
#include <stddef.h>
#include <stdint.h>
/* Supported SCMI Protocol Versions */
#define SCMI_PWR_DMN_PROTO_VER MAKE_SCMI_VERSION(1, 0)
#define SCMI_SYS_PWR_PROTO_VER MAKE_SCMI_VERSION(1, 0)
#define GET_SCMI_MAJOR_VER(ver) (((ver) >> 16) & 0xffff)
#define GET_SCMI_MINOR_VER(ver) ((ver) & 0xffff)
#define MAKE_SCMI_VERSION(maj, min) \
((((maj) & 0xffff) << 16) | ((min) & 0xffff))
/* Macro to check if the driver is compatible with the SCMI version reported */
#define is_scmi_version_compatible(drv, scmi) \
((GET_SCMI_MAJOR_VER(drv) == GET_SCMI_MAJOR_VER(scmi)) && \
(GET_SCMI_MINOR_VER(drv) <= GET_SCMI_MINOR_VER(scmi)))
/* SCMI Protocol identifiers */
#define SCMI_PWR_DMN_PROTO_ID 0x11
#define SCMI_SYS_PWR_PROTO_ID 0x12
/* Mandatory messages IDs for all SCMI protocols */
#define SCMI_PROTO_VERSION_MSG 0x0
#define SCMI_PROTO_ATTR_MSG 0x1
#define SCMI_PROTO_MSG_ATTR_MSG 0x2
/* SCMI power domain management protocol message IDs */
#define SCMI_PWR_STATE_SET_MSG 0x4
#define SCMI_PWR_STATE_GET_MSG 0x5
/* SCMI system power management protocol message IDs */
#define SCMI_SYS_PWR_STATE_SET_MSG 0x3
#define SCMI_SYS_PWR_STATE_GET_MSG 0x4
/* Helper macros for system power management protocol commands */
/*
* Macros to describe the bit-fields of the `attribute` of system power domain
* protocol PROTOCOL_MSG_ATTRIBUTE message.
*/
#define SYS_PWR_ATTR_WARM_RESET_SHIFT 31
#define SCMI_SYS_PWR_WARM_RESET_SUPPORTED (1U << SYS_PWR_ATTR_WARM_RESET_SHIFT)
#define SYS_PWR_ATTR_SUSPEND_SHIFT 30
#define SCMI_SYS_PWR_SUSPEND_SUPPORTED (1 << SYS_PWR_ATTR_SUSPEND_SHIFT)
/*
* Macros to describe the bit-fields of the `flags` parameter of system power
* domain protocol SYSTEM_POWER_STATE_SET message.
*/
#define SYS_PWR_SET_GRACEFUL_REQ_SHIFT 0
#define SCMI_SYS_PWR_GRACEFUL_REQ (1 << SYS_PWR_SET_GRACEFUL_REQ_SHIFT)
#define SCMI_SYS_PWR_FORCEFUL_REQ (0 << SYS_PWR_SET_GRACEFUL_REQ_SHIFT)
/*
* Macros to describe the `system_state` parameter of system power
* domain protocol SYSTEM_POWER_STATE_SET message.
*/
#define SCMI_SYS_PWR_SHUTDOWN 0x0
#define SCMI_SYS_PWR_COLD_RESET 0x1
#define SCMI_SYS_PWR_WARM_RESET 0x2
#define SCMI_SYS_PWR_POWER_UP 0x3
#define SCMI_SYS_PWR_SUSPEND 0x4
/* SCMI Error code definitions */
#define SCMI_E_QUEUED 1
#define SCMI_E_SUCCESS 0
#define SCMI_E_NOT_SUPPORTED -1
#define SCMI_E_INVALID_PARAM -2
#define SCMI_E_DENIED -3
#define SCMI_E_NOT_FOUND -4
#define SCMI_E_OUT_OF_RANGE -5
#define SCMI_E_BUSY -6
/*
* SCMI driver platform information. The details of the doorbell mechanism
* can be found in the SCMI specification.
*/
typedef struct scmi_channel_plat_info {
/* SCMI mailbox memory */
uintptr_t scmi_mbx_mem;
/* The door bell register address */
uintptr_t db_reg_addr;
/* The bit mask that need to be preserved when ringing doorbell */
uint32_t db_preserve_mask;
/* The bit mask that need to be set to ring doorbell */
uint32_t db_modify_mask;
} scmi_channel_plat_info_t;
/*
* Structure to represent an SCMI channel.
*/
typedef struct scmi_channel {
scmi_channel_plat_info_t *info;
/* The lock for channel access */
bakery_lock_t *lock;
/* Indicate whether the channel is initialized */
int is_initialized;
} scmi_channel_t;
/* External Common API */
void *scmi_init(scmi_channel_t *ch);
int scmi_proto_msg_attr(void *p, uint32_t proto_id, uint32_t command_id,
uint32_t *attr);
int scmi_proto_version(void *p, uint32_t proto_id, uint32_t *version);
/*
* Power domain protocol commands. Refer to the SCMI specification for more
* details on these commands.
*/
int scmi_pwr_state_set(void *p, uint32_t domain_id, uint32_t scmi_pwr_state);
int scmi_pwr_state_get(void *p, uint32_t domain_id, uint32_t *scmi_pwr_state);
/*
* System power management protocol commands. Refer SCMI specification for more
* details on these commands.
*/
int scmi_sys_pwr_state_set(void *p, uint32_t flags, uint32_t system_state);
int scmi_sys_pwr_state_get(void *p, uint32_t *system_state);
#endif /* __CSS_SCMI_H__ */

196
plat/arm/css/drivers/scmi/scmi_common.c

@ -0,0 +1,196 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include "scmi.h"
#include "scmi_private.h"
/*
* Private helper function to get exclusive access to SCMI channel.
*/
void scmi_get_channel(scmi_channel_t *ch)
{
assert(ch->lock);
bakery_lock_get(ch->lock);
/* Make sure any previous command has finished */
assert(SCMI_IS_CHANNEL_FREE(
((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status));
}
/*
* Private helper function to transfer ownership of channel from AP to SCP.
*/
void scmi_send_sync_command(scmi_channel_t *ch)
{
mailbox_mem_t *mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
SCMI_MARK_CHANNEL_BUSY(mbx_mem->status);
/*
* Ensure that any write to the SCMI payload area is seen by SCP before
* we write to the doorbell register. If these 2 writes were reordered
* by the CPU then SCP would read stale payload data
*/
dmbst();
SCMI_RING_DOORBELL(ch->info->db_reg_addr, ch->info->db_modify_mask,
ch->info->db_preserve_mask);
/*
* Ensure that the write to the doorbell register is ordered prior to
* checking whether the channel is free.
*/
dmbsy();
/* Wait for channel to be free */
while (!SCMI_IS_CHANNEL_FREE(mbx_mem->status))
;
/*
* Ensure that any read to the SCMI payload area is done after reading
* mailbox status. If these 2 reads were reordered then the CPU would
* read invalid payload data
*/
dmbld();
}
/*
* Private helper function to release exclusive access to SCMI channel.
*/
void scmi_put_channel(scmi_channel_t *ch)
{
/* Make sure any previous command has finished */
assert(SCMI_IS_CHANNEL_FREE(
((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status));
assert(ch->lock);
bakery_lock_release(ch->lock);
}
/*
* API to query the SCMI protocol version.
*/
int scmi_proto_version(void *p, uint32_t proto_id, uint32_t *version)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id, SCMI_PROTO_VERSION_MSG,
token);
mbx_mem->len = SCMI_PROTO_VERSION_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *version);
assert(mbx_mem->len == SCMI_PROTO_VERSION_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to query the protocol message attributes for a SCMI protocol.
*/
int scmi_proto_msg_attr(void *p, uint32_t proto_id,
uint32_t command_id, uint32_t *attr)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id,
SCMI_PROTO_MSG_ATTR_MSG, token);
mbx_mem->len = SCMI_PROTO_MSG_ATTR_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG1(mbx_mem->payload, command_id);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *attr);
assert(mbx_mem->len == SCMI_PROTO_MSG_ATTR_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* SCMI Driver initialization API. Returns initialized channel on success
* or NULL on error. The return type is an opaque void pointer.
*/
void *scmi_init(scmi_channel_t *ch)
{
uint32_t version;
int ret;
assert(ch && ch->info);
assert(ch->info->db_reg_addr);
assert(ch->info->db_modify_mask);
assert(ch->info->db_preserve_mask);
assert(ch->lock);
bakery_lock_init(ch->lock);
ch->is_initialized = 1;
ret = scmi_proto_version(ch, SCMI_PWR_DMN_PROTO_ID, &version);
if (ret != SCMI_E_SUCCESS) {
WARN("SCMI power domain protocol version message failed");
goto error;
}
if (!is_scmi_version_compatible(SCMI_PWR_DMN_PROTO_VER, version)) {
WARN("SCMI power domain protocol version 0x%x incompatible with driver version 0x%x",
version, SCMI_PWR_DMN_PROTO_VER);
goto error;
}
VERBOSE("SCMI power domain protocol version 0x%x detected\n", version);
ret = scmi_proto_version(ch, SCMI_SYS_PWR_PROTO_ID, &version);
if ((ret != SCMI_E_SUCCESS)) {
WARN("SCMI system power protocol version message failed");
goto error;
}
if (!is_scmi_version_compatible(SCMI_SYS_PWR_PROTO_VER, version)) {
WARN("SCMI system power management protocol version 0x%x incompatible with driver version 0x%x",
version, SCMI_SYS_PWR_PROTO_VER);
goto error;
}
VERBOSE("SCMI system power management protocol version 0x%x detected\n",
version);
INFO("SCMI driver initialized\n");
return (void *)ch;
error:
ch->is_initialized = 0;
return NULL;
}

148
plat/arm/css/drivers/scmi/scmi_private.h

@ -0,0 +1,148 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef __CSS_SCMI_PRIVATE_H__
#define __CSS_SCMI_PRIVATE_H__
/*
* SCMI power domain management protocol message and response lengths. It is
* calculated as sum of length in bytes of the message header (4) and payload
* area (the number of bytes of parameters or return values in the payload).
*/
#define SCMI_PROTO_VERSION_MSG_LEN 4
#define SCMI_PROTO_VERSION_RESP_LEN 12
#define SCMI_PROTO_MSG_ATTR_MSG_LEN 8
#define SCMI_PROTO_MSG_ATTR_RESP_LEN 12
#define SCMI_PWR_STATE_SET_MSG_LEN 16
#define SCMI_PWR_STATE_SET_RESP_LEN 8
#define SCMI_PWR_STATE_GET_MSG_LEN 8
#define SCMI_PWR_STATE_GET_RESP_LEN 12
#define SCMI_SYS_PWR_STATE_SET_MSG_LEN 12
#define SCMI_SYS_PWR_STATE_SET_RESP_LEN 8
#define SCMI_SYS_PWR_STATE_GET_MSG_LEN 4
#define SCMI_SYS_PWR_STATE_GET_RESP_LEN 12
/* SCMI message header format bit field */
#define SCMI_MSG_ID_SHIFT 0
#define SCMI_MSG_ID_WIDTH 8
#define SCMI_MSG_ID_MASK ((1 << SCMI_MSG_ID_WIDTH) - 1)
#define SCMI_MSG_TYPE_SHIFT 8
#define SCMI_MSG_TYPE_WIDTH 2
#define SCMI_MSG_TYPE_MASK ((1 << SCMI_MSG_TYPE_WIDTH) - 1)
#define SCMI_MSG_PROTO_ID_SHIFT 10
#define SCMI_MSG_PROTO_ID_WIDTH 8
#define SCMI_MSG_PROTO_ID_MASK ((1 << SCMI_MSG_PROTO_ID_WIDTH) - 1)
#define SCMI_MSG_TOKEN_SHIFT 18
#define SCMI_MSG_TOKEN_WIDTH 10
#define SCMI_MSG_TOKEN_MASK ((1 << SCMI_MSG_TOKEN_WIDTH) - 1)
/* SCMI mailbox flags */
#define SCMI_FLAG_RESP_POLL 0
#define SCMI_FLAG_RESP_INT 1
/* SCMI power domain protocol `POWER_STATE_SET` message flags */
#define SCMI_PWR_STATE_SET_FLAG_SYNC 0
#define SCMI_PWR_STATE_SET_FLAG_ASYNC 1
/*
* Helper macro to create an SCMI message header given protocol, message id
* and token.
*/
#define SCMI_MSG_CREATE(protocol, msg_id, token) \
((((protocol) & SCMI_MSG_PROTO_ID_MASK) << SCMI_MSG_PROTO_ID_SHIFT) | \
(((msg_id) & SCMI_MSG_ID_MASK) << SCMI_MSG_ID_SHIFT) | \
(((token) & SCMI_MSG_TOKEN_MASK) << SCMI_MSG_TOKEN_SHIFT))
/* Helper macro to get the token from a SCMI message header */
#define SCMI_MSG_GET_TOKEN(msg) \
(((msg) >> SCMI_MSG_TOKEN_SHIFT) & SCMI_MSG_TOKEN_MASK)
/* SCMI Channel Status bit fields */
#define SCMI_CH_STATUS_RES0_MASK 0xFFFFFFFE
#define SCMI_CH_STATUS_FREE_SHIFT 0
#define SCMI_CH_STATUS_FREE_WIDTH 1
#define SCMI_CH_STATUS_FREE_MASK ((1 << SCMI_CH_STATUS_FREE_WIDTH) - 1)
/* Helper macros to check and write the channel status */
#define SCMI_IS_CHANNEL_FREE(status) \
(!!(((status) >> SCMI_CH_STATUS_FREE_SHIFT) & SCMI_CH_STATUS_FREE_MASK))
#define SCMI_MARK_CHANNEL_BUSY(status) do { \
assert(SCMI_IS_CHANNEL_FREE(status)); \
(status) &= ~(SCMI_CH_STATUS_FREE_MASK << \
SCMI_CH_STATUS_FREE_SHIFT); \
} while (0)
/* Helper macros to copy arguments to the mailbox payload */
#define SCMI_PAYLOAD_ARG1(payld_arr, arg1) \
mmio_write_32((uintptr_t)&payld_arr[0], arg1)
#define SCMI_PAYLOAD_ARG2(payld_arr, arg1, arg2) do { \
SCMI_PAYLOAD_ARG1(payld_arr, arg1); \
mmio_write_32((uintptr_t)&payld_arr[1], arg2); \
} while (0)
#define SCMI_PAYLOAD_ARG3(payld_arr, arg1, arg2, arg3) do { \
SCMI_PAYLOAD_ARG2(payld_arr, arg1, arg2); \
mmio_write_32((uintptr_t)&payld_arr[2], arg3); \
} while (0)
/* Helper macros to read return values from the mailbox payload */
#define SCMI_PAYLOAD_RET_VAL1(payld_arr, val1) \
(val1) = mmio_read_32((uintptr_t)&payld_arr[0])
#define SCMI_PAYLOAD_RET_VAL2(payld_arr, val1, val2) do { \
SCMI_PAYLOAD_RET_VAL1(payld_arr, val1); \
(val2) = mmio_read_32((uintptr_t)&payld_arr[1]); \
} while (0)
#define SCMI_PAYLOAD_RET_VAL3(payld_arr, val1, val2, val3) do { \
SCMI_PAYLOAD_RET_VAL2(payld_arr, val1, val2); \
(val3) = mmio_read_32((uintptr_t)&payld_arr[2]); \
} while (0)
/* Helper macro to ring doorbell */
#define SCMI_RING_DOORBELL(addr, modify_mask, preserve_mask) do { \
uint32_t db = mmio_read_32(addr) & (preserve_mask); \
mmio_write_32(addr, db | (modify_mask)); \
} while (0)
/*
* Private data structure for representing the mailbox memory layout. Refer
* the SCMI specification for more details.
*/
typedef struct mailbox_mem {
uint32_t res_a; /* Reserved */
volatile uint32_t status;
uint64_t res_b; /* Reserved */
uint32_t flags;
volatile uint32_t len;
uint32_t msg_header;
uint32_t payload[];
} mailbox_mem_t;
/* Private APIs for use within SCMI driver */
void scmi_get_channel(scmi_channel_t *ch);
void scmi_send_sync_command(scmi_channel_t *ch);
void scmi_put_channel(scmi_channel_t *ch);
static inline void validate_scmi_channel(scmi_channel_t *ch)
{
assert(ch && ch->is_initialized);
assert(ch->info && ch->info->scmi_mbx_mem);
}
#endif /* __CSS_SCMI_PRIVATE_H__ */

84
plat/arm/css/drivers/scmi/scmi_pwr_dmn_proto.c

@ -0,0 +1,84 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include "scmi.h"
#include "scmi_private.h"
/*
* API to set the SCMI power domain power state.
*/
int scmi_pwr_state_set(void *p, uint32_t domain_id,
uint32_t scmi_pwr_state)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
/*
* Only asynchronous mode of `set power state` command is allowed on
* application processors.
*/
uint32_t pwr_state_set_msg_flag = SCMI_PWR_STATE_SET_FLAG_ASYNC;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_SET_MSG, token);
mbx_mem->len = SCMI_PWR_STATE_SET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG3(mbx_mem->payload, pwr_state_set_msg_flag,
domain_id, scmi_pwr_state);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
assert(mbx_mem->len == SCMI_PWR_STATE_SET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to get the SCMI power domain power state.
*/
int scmi_pwr_state_get(void *p, uint32_t domain_id,
uint32_t *scmi_pwr_state)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_GET_MSG, token);
mbx_mem->len = SCMI_PWR_STATE_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG1(mbx_mem->payload, domain_id);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *scmi_pwr_state);
assert(mbx_mem->len == SCMI_PWR_STATE_GET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}

74
plat/arm/css/drivers/scmi/scmi_sys_pwr_proto.c

@ -0,0 +1,74 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include "scmi.h"
#include "scmi_private.h"
/*
* API to set the SCMI system power state
*/
int scmi_sys_pwr_state_set(void *p, uint32_t flags, uint32_t system_state)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_SET_MSG, token);
mbx_mem->len = SCMI_SYS_PWR_STATE_SET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
SCMI_PAYLOAD_ARG2(mbx_mem->payload, flags, system_state);
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL1(mbx_mem->payload, ret);
assert(mbx_mem->len == SCMI_SYS_PWR_STATE_SET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}
/*
* API to get the SCMI system power state
*/
int scmi_sys_pwr_state_get(void *p, uint32_t *system_state)
{
mailbox_mem_t *mbx_mem;
int token = 0, ret;
scmi_channel_t *ch = (scmi_channel_t *)p;
validate_scmi_channel(ch);
scmi_get_channel(ch);
mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem);
mbx_mem->msg_header = SCMI_MSG_CREATE(SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_GET_MSG, token);
mbx_mem->len = SCMI_SYS_PWR_STATE_GET_MSG_LEN;
mbx_mem->flags = SCMI_FLAG_RESP_POLL;
scmi_send_sync_command(ch);
/* Get the return values */
SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *system_state);
assert(mbx_mem->len == SCMI_SYS_PWR_STATE_GET_RESP_LEN);
assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header));
scmi_put_channel(ch);
return ret;
}

385
plat/arm/css/drivers/scp/css_pm_scmi.c

@ -0,0 +1,385 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <css_def.h>
#include <css_pm.h>
#include <debug.h>
#include <plat_arm.h>
#include <platform.h>
#include <string.h>
#include "../scmi/scmi.h"
#include "css_scp.h"
/*
* This file implements the SCP helper functions using SCMI protocol.
*/
/*
* SCMI power state parameter bit field encoding for ARM CSS platforms.
*
* 31 20 19 16 15 12 11 8 7 4 3 0
* +-------------------------------------------------------------+
* | SBZ | Max level | Level 3 | Level 2 | Level 1 | Level 0 |
* | | | state | state | state | state |
* +-------------------------------------------------------------+
*
* `Max level` encodes the highest level that has a valid power state
* encoded in the power state.
*/
#define SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT 16
#define SCMI_PWR_STATE_MAX_PWR_LVL_WIDTH 4
#define SCMI_PWR_STATE_MAX_PWR_LVL_MASK \
((1 << SCMI_PWR_STATE_MAX_PWR_LVL_WIDTH) - 1)
#define SCMI_SET_PWR_STATE_MAX_PWR_LVL(pwr_state, max_lvl) \
(pwr_state) |= ((max_lvl) & SCMI_PWR_STATE_MAX_PWR_LVL_MASK) \
<< SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT
#define SCMI_GET_PWR_STATE_MAX_PWR_LVL(pwr_state) \
(((pwr_state) >> SCMI_PWR_STATE_MAX_PWR_LVL_SHIFT) \
& SCMI_PWR_STATE_MAX_PWR_LVL_MASK)
#define SCMI_PWR_STATE_LVL_WIDTH 4
#define SCMI_PWR_STATE_LVL_MASK \
((1 << SCMI_PWR_STATE_LVL_WIDTH) - 1)
#define SCMI_SET_PWR_STATE_LVL(pwr_state, lvl, lvl_state) \
(pwr_state) |= ((lvl_state) & SCMI_PWR_STATE_LVL_MASK) \
<< (SCMI_PWR_STATE_LVL_WIDTH * (lvl))
#define SCMI_GET_PWR_STATE_LVL(pwr_state, lvl) \
(((pwr_state) >> (SCMI_PWR_STATE_LVL_WIDTH * (lvl))) & \
SCMI_PWR_STATE_LVL_MASK)
/*
* The SCMI power state enumeration for a power domain level
*/
typedef enum {
scmi_power_state_off = 0,
scmi_power_state_on = 1,
scmi_power_state_sleep = 2,
} scmi_power_state_t;
/*
* This mapping array has to be exported by the platform. Each element at
* a given index maps that core to an SCMI power domain.
*/
extern uint32_t plat_css_core_pos_to_scmi_dmn_id_map[];
/*
* The global handle for invoking the SCMI driver APIs after the driver
* has been initialized.
*/
void *scmi_handle;
/* The SCMI channel global object */
static scmi_channel_t scmi_channel;
ARM_INSTANTIATE_LOCK
/*
* Helper function to suspend a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_suspend(const psci_power_state_t *target_state)
{
int lvl, ret;
uint32_t scmi_pwr_state = 0;
/* At least power domain level 0 should be specified to be suspended */
assert(target_state->pwr_domain_state[ARM_PWR_LVL0] ==
ARM_LOCAL_STATE_OFF);
/* Check if power down at system power domain level is requested */
if (CSS_SYSTEM_PWR_STATE(target_state) == ARM_LOCAL_STATE_OFF) {
/* Issue SCMI command for SYSTEM_SUSPEND */
ret = scmi_sys_pwr_state_set(scmi_handle,
SCMI_SYS_PWR_FORCEFUL_REQ,
SCMI_SYS_PWR_SUSPEND);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI system power domain suspend return 0x%x unexpected\n",
ret);
panic();
}
return;
}
/*
* If we reach here, then assert that power down at system power domain
* level is running.
*/
assert(target_state->pwr_domain_state[CSS_SYSTEM_PWR_DMN_LVL] ==
ARM_LOCAL_STATE_RUN);
/* For level 0, specify `scmi_power_state_sleep` as the power state */
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, ARM_PWR_LVL0,
scmi_power_state_sleep);
for (lvl = ARM_PWR_LVL1; lvl <= PLAT_MAX_PWR_LVL; lvl++) {
if (target_state->pwr_domain_state[lvl] == ARM_LOCAL_STATE_RUN)
break;
assert(target_state->pwr_domain_state[lvl] ==
ARM_LOCAL_STATE_OFF);
/*
* Specify `scmi_power_state_off` as power state for higher
* levels.
*/
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_off);
}
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
ret = scmi_pwr_state_set(scmi_handle,
plat_css_core_pos_to_scmi_dmn_id_map[plat_my_core_pos()],
scmi_pwr_state);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
}
/*
* Helper function to turn off a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_off(const psci_power_state_t *target_state)
{
int lvl = 0, ret;
uint32_t scmi_pwr_state = 0;
/* At-least the CPU level should be specified to be OFF */
assert(target_state->pwr_domain_state[ARM_PWR_LVL0] ==
ARM_LOCAL_STATE_OFF);
/* PSCI CPU OFF cannot be used to turn OFF system power domain */
assert(target_state->pwr_domain_state[CSS_SYSTEM_PWR_DMN_LVL] ==
ARM_LOCAL_STATE_RUN);
for (; lvl <= PLAT_MAX_PWR_LVL; lvl++) {
if (target_state->pwr_domain_state[lvl] == ARM_LOCAL_STATE_RUN)
break;
assert(target_state->pwr_domain_state[lvl] ==
ARM_LOCAL_STATE_OFF);
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_off);
}
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
ret = scmi_pwr_state_set(scmi_handle,
plat_css_core_pos_to_scmi_dmn_id_map[plat_my_core_pos()],
scmi_pwr_state);
if (ret != SCMI_E_QUEUED && ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
}
/*
* Helper function to turn ON a CPU power domain and its parent power domains
* if applicable.
*/
void css_scp_on(u_register_t mpidr)
{
int lvl = 0, ret;
uint32_t scmi_pwr_state = 0;
for (; lvl <= PLAT_MAX_PWR_LVL; lvl++)
SCMI_SET_PWR_STATE_LVL(scmi_pwr_state, lvl,
scmi_power_state_on);
SCMI_SET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state, lvl - 1);
ret = scmi_pwr_state_set(scmi_handle,
plat_css_core_pos_to_scmi_dmn_id_map[plat_core_pos_by_mpidr(mpidr)],
scmi_pwr_state);
if (ret != SCMI_E_QUEUED && ret != SCMI_E_SUCCESS) {
ERROR("SCMI set power state command return 0x%x unexpected\n",
ret);
panic();
}
}
/*
* Helper function to get the power state of a power domain node as reported
* by the SCP.
*/
int css_scp_get_power_state(u_register_t mpidr, unsigned int power_level)
{
int ret, cpu_idx;
uint32_t scmi_pwr_state = 0, lvl_state;
/* We don't support get power state at the system power domain level */
if ((power_level > PLAT_MAX_PWR_LVL) ||
(power_level == CSS_SYSTEM_PWR_DMN_LVL)) {
WARN("Invalid power level %u specified for SCMI get power state\n",
power_level);
return PSCI_E_INVALID_PARAMS;
}
cpu_idx = plat_core_pos_by_mpidr(mpidr);
assert(cpu_idx > -1);
ret = scmi_pwr_state_get(scmi_handle,
plat_css_core_pos_to_scmi_dmn_id_map[cpu_idx],
&scmi_pwr_state);
if (ret != SCMI_E_SUCCESS) {
WARN("SCMI get power state command return 0x%x unexpected\n",
ret);
return PSCI_E_INVALID_PARAMS;
}
/*
* Find the maximum power level described in the get power state
* command. If it is less than the requested power level, then assume
* the requested power level is ON.
*/
if (SCMI_GET_PWR_STATE_MAX_PWR_LVL(scmi_pwr_state) < power_level)
return HW_ON;
lvl_state = SCMI_GET_PWR_STATE_LVL(scmi_pwr_state, power_level);
if (lvl_state == scmi_power_state_on)
return HW_ON;
assert((lvl_state == scmi_power_state_off) ||
(lvl_state == scmi_power_state_sleep));
return HW_OFF;
}
/*
* Helper function to shutdown the system via SCMI.
*/
void __dead2 css_scp_sys_shutdown(void)
{
int ret;
/*
* Disable GIC CPU interface to prevent pending interrupt from waking
* up the AP from WFI.
*/
plat_arm_gic_cpuif_disable();
/*
* Issue SCMI command for SYSTEM_SHUTDOWN. First issue a graceful
* request and if that fails force the request.
*/
ret = scmi_sys_pwr_state_set(scmi_handle,
SCMI_SYS_PWR_FORCEFUL_REQ,
SCMI_SYS_PWR_SHUTDOWN);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI system power domain shutdown return 0x%x unexpected\n",
ret);
panic();
}
wfi();
ERROR("CSS System Shutdown: operation not handled.\n");
panic();
}
/*
* Helper function to reset the system via SCMI.
*/
void __dead2 css_scp_sys_reboot(void)
{
int ret;
/*
* Disable GIC CPU interface to prevent pending interrupt from waking
* up the AP from WFI.
*/
plat_arm_gic_cpuif_disable();
/*
* Issue SCMI command for SYSTEM_REBOOT. First issue a graceful
* request and if that fails force the request.
*/
ret = scmi_sys_pwr_state_set(scmi_handle,
SCMI_SYS_PWR_FORCEFUL_REQ,
SCMI_SYS_PWR_COLD_RESET);
if (ret != SCMI_E_SUCCESS) {
ERROR("SCMI system power domain reset return 0x%x unexpected\n",
ret);
panic();
}
wfi();
ERROR("CSS System Reset: operation not handled.\n");
panic();
}
scmi_channel_plat_info_t plat_css_scmi_plat_info = {
.scmi_mbx_mem = CSS_SCMI_PAYLOAD_BASE,
.db_reg_addr = PLAT_CSS_MHU_BASE + CSS_SCMI_MHU_DB_REG_OFF,
.db_preserve_mask = 0xfffffffd,
.db_modify_mask = 0x2,
};
void plat_arm_pwrc_setup(void)
{
scmi_channel.info = &plat_css_scmi_plat_info;
scmi_channel.lock = ARM_LOCK_GET_INSTANCE;
scmi_handle = scmi_init(&scmi_channel);
if (scmi_handle == NULL) {
ERROR("SCMI Initialization failed\n");
panic();
}
}
/******************************************************************************
* This function overrides the default definition for ARM platforms. Initialize
* the SCMI driver, query capability via SCMI and modify the PSCI capability
* based on that.
*****************************************************************************/
const plat_psci_ops_t *plat_arm_psci_override_pm_ops(plat_psci_ops_t *ops)
{
uint32_t msg_attr;
int ret;
assert(scmi_handle);
/* Check that power domain POWER_STATE_SET message is supported */
ret = scmi_proto_msg_attr(scmi_handle, SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_SET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS) {
ERROR("Set power state command is not supported by SCMI\n");
panic();
}
/*
* Don't support PSCI NODE_HW_STATE call if SCMI doesn't support
* POWER_STATE_GET message.
*/
ret = scmi_proto_msg_attr(scmi_handle, SCMI_PWR_DMN_PROTO_ID,
SCMI_PWR_STATE_GET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS)
ops->get_node_hw_state = NULL;
/* Check if the SCMI SYSTEM_POWER_STATE_SET message is supported */
ret = scmi_proto_msg_attr(scmi_handle, SCMI_SYS_PWR_PROTO_ID,
SCMI_SYS_PWR_STATE_SET_MSG, &msg_attr);
if (ret != SCMI_E_SUCCESS) {
/* System power management operations are not supported */
ops->system_off = NULL;
ops->system_reset = NULL;
ops->get_sys_suspend_power_state = NULL;
} else if (!(msg_attr & SCMI_SYS_PWR_SUSPEND_SUPPORTED)) {
/*
* System power management protocol is available, but it does
* not support SYSTEM SUSPEND.
*/
ops->get_sys_suspend_power_state = NULL;
}
return ops;
}
Loading…
Cancel
Save