diff options
Diffstat (limited to 'plat/arm/css/drivers/scmi/scmi_common.c')
-rw-r--r-- | plat/arm/css/drivers/scmi/scmi_common.c | 196 |
1 files changed, 196 insertions, 0 deletions
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 00000000..d0051c7a --- /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 <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; +} |