diff --git a/docs/user-guide.md b/docs/user-guide.md index 3cf1f0f..0065ac0 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -622,6 +622,10 @@ 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 diff --git a/include/plat/arm/common/plat_arm.h b/include/plat/arm/common/plat_arm.h index 7967173..62c0ce7 100644 --- a/include/plat/arm/common/plat_arm.h +++ b/include/plat/arm/common/plat_arm.h @@ -46,7 +46,7 @@ * 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 @@ * 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() diff --git a/include/plat/arm/css/common/css_def.h b/include/plat/arm/css/common/css_def.h index 6f9d640..0b74ced 100644 --- a/include/plat/arm/css/common/css_def.h +++ b/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 */ @@ -48,6 +48,17 @@ 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 * * The SCP/AP boot configuration is a 32-bit word located at a known offset from @@ -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 diff --git a/plat/arm/board/common/board_css_common.c b/plat/arm/board/common/board_css_common.c index 1758a23..42f754e 100644 --- a/plat/arm/board/common/board_css_common.c +++ b/plat/arm/board/common/board_css_common.c @@ -49,6 +49,15 @@ 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} }; diff --git a/plat/arm/board/juno/include/platform_def.h b/plat/arm/board/juno/include/platform_def.h index 8f03826..68c38ee 100644 --- a/plat/arm/board/juno/include/platform_def.h +++ b/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 diff --git a/plat/arm/board/juno/juno_topology.c b/plat/arm/board/juno/juno_topology.c index d2e0c77..b9412b1 100644 --- a/plat/arm/board/juno/juno_topology.c +++ b/plat/arm/board/juno/juno_topology.c @@ -51,3 +51,10 @@ 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 }; diff --git a/plat/arm/css/common/css_common.mk b/plat/arm/css/common/css_common.mk index a3d4513..c2ae921 100644 --- a/plat/arm/css/common/css_common.mk +++ b/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 @@ 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 @@ # 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)) + diff --git a/plat/arm/css/drivers/scmi/scmi.h b/plat/arm/css/drivers/scmi/scmi.h new file mode 100644 index 0000000..850402a --- /dev/null +++ b/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 +#include +#include + +/* 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__ */ diff --git a/plat/arm/css/drivers/scmi/scmi_common.c b/plat/arm/css/drivers/scmi/scmi_common.c new file mode 100644 index 0000000..d0051c7 --- /dev/null +++ b/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 +#include +#include +#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; +} diff --git a/plat/arm/css/drivers/scmi/scmi_private.h b/plat/arm/css/drivers/scmi/scmi_private.h new file mode 100644 index 0000000..20e1e9b --- /dev/null +++ b/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__ */ diff --git a/plat/arm/css/drivers/scmi/scmi_pwr_dmn_proto.c b/plat/arm/css/drivers/scmi/scmi_pwr_dmn_proto.c new file mode 100644 index 0000000..90c5d6b --- /dev/null +++ b/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 +#include +#include +#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; +} diff --git a/plat/arm/css/drivers/scmi/scmi_sys_pwr_proto.c b/plat/arm/css/drivers/scmi/scmi_sys_pwr_proto.c new file mode 100644 index 0000000..f6da394 --- /dev/null +++ b/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 +#include +#include +#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; +} diff --git a/plat/arm/css/drivers/scp/css_pm_scmi.c b/plat/arm/css/drivers/scp/css_pm_scmi.c new file mode 100644 index 0000000..b4c0a7d --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#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; +}