1 /* 2 * Copyright (c) 2017-2024, Arm Limited and Contributors. All rights reserved. 3 * 4 * SPDX-License-Identifier: BSD-3-Clause 5 */ 6 7 #include <assert.h> 8 9 #include <arch_helpers.h> 10 #include <common/debug.h> 11 #include <drivers/arm/css/scmi.h> 12 #include <drivers/delay_timer.h> 13 14 #include "scmi_private.h" 15 16 #if HW_ASSISTED_COHERENCY 17 #define scmi_lock_init(lock) 18 #define scmi_lock_get(lock) spin_lock(lock) 19 #define scmi_lock_release(lock) spin_unlock(lock) 20 #else 21 #define scmi_lock_init(lock) bakery_lock_init(lock) 22 #define scmi_lock_get(lock) bakery_lock_get(lock) 23 #define scmi_lock_release(lock) bakery_lock_release(lock) 24 #endif 25 26 27 /* 28 * Private helper function to get exclusive access to SCMI channel. 29 */ 30 void scmi_get_channel(scmi_channel_t *ch) 31 { 32 assert(ch->lock); 33 scmi_lock_get(ch->lock); 34 35 /* Make sure any previous command has finished */ 36 assert(SCMI_IS_CHANNEL_FREE( 37 ((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status)); 38 } 39 40 /* 41 * Private helper function to transfer ownership of channel from AP to SCP. 42 */ 43 void scmi_send_sync_command(scmi_channel_t *ch) 44 { 45 mailbox_mem_t *mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem); 46 47 SCMI_MARK_CHANNEL_BUSY(mbx_mem->status); 48 49 /* 50 * Ensure that any write to the SCMI payload area is seen by SCP before 51 * we write to the doorbell register. If these 2 writes were reordered 52 * by the CPU then SCP would read stale payload data 53 */ 54 dmbst(); 55 56 ch->info->ring_doorbell(ch->info); 57 /* 58 * Ensure that the write to the doorbell register is ordered prior to 59 * checking whether the channel is free. 60 */ 61 dmbsy(); 62 63 /* Wait for channel to be free */ 64 while (!SCMI_IS_CHANNEL_FREE(mbx_mem->status)) { 65 if (ch->info->delay != 0) 66 udelay(ch->info->delay); 67 } 68 69 /* 70 * Ensure that any read to the SCMI payload area is done after reading 71 * mailbox status. If these 2 reads were reordered then the CPU would 72 * read invalid payload data 73 */ 74 dmbld(); 75 } 76 77 /* 78 * Private helper function to release exclusive access to SCMI channel. 79 */ 80 void scmi_put_channel(scmi_channel_t *ch) 81 { 82 /* Make sure any previous command has finished */ 83 assert(SCMI_IS_CHANNEL_FREE( 84 ((mailbox_mem_t *)(ch->info->scmi_mbx_mem))->status)); 85 86 assert(ch->lock); 87 scmi_lock_release(ch->lock); 88 } 89 90 /* 91 * API to query the SCMI protocol version. 92 */ 93 int scmi_proto_version(void *p, uint32_t proto_id, uint32_t *version) 94 { 95 mailbox_mem_t *mbx_mem; 96 unsigned int token = 0; 97 int ret; 98 scmi_channel_t *ch = (scmi_channel_t *)p; 99 100 validate_scmi_channel(ch); 101 102 scmi_get_channel(ch); 103 104 mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem); 105 mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id, SCMI_PROTO_VERSION_MSG, 106 token); 107 mbx_mem->len = SCMI_PROTO_VERSION_MSG_LEN; 108 mbx_mem->flags = SCMI_FLAG_RESP_POLL; 109 110 scmi_send_sync_command(ch); 111 112 /* Get the return values */ 113 SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *version); 114 assert(mbx_mem->len == SCMI_PROTO_VERSION_RESP_LEN); 115 assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header)); 116 117 scmi_put_channel(ch); 118 119 return ret; 120 } 121 122 /* 123 * API to query the protocol message attributes for a SCMI protocol. 124 */ 125 int scmi_proto_msg_attr(void *p, uint32_t proto_id, 126 uint32_t command_id, uint32_t *attr) 127 { 128 mailbox_mem_t *mbx_mem; 129 unsigned int token = 0; 130 int ret; 131 scmi_channel_t *ch = (scmi_channel_t *)p; 132 133 validate_scmi_channel(ch); 134 135 scmi_get_channel(ch); 136 137 mbx_mem = (mailbox_mem_t *)(ch->info->scmi_mbx_mem); 138 mbx_mem->msg_header = SCMI_MSG_CREATE(proto_id, 139 SCMI_PROTO_MSG_ATTR_MSG, token); 140 mbx_mem->len = SCMI_PROTO_MSG_ATTR_MSG_LEN; 141 mbx_mem->flags = SCMI_FLAG_RESP_POLL; 142 SCMI_PAYLOAD_ARG1(mbx_mem->payload, command_id); 143 144 scmi_send_sync_command(ch); 145 146 /* Get the return values */ 147 SCMI_PAYLOAD_RET_VAL2(mbx_mem->payload, ret, *attr); 148 assert(mbx_mem->len == SCMI_PROTO_MSG_ATTR_RESP_LEN); 149 assert(token == SCMI_MSG_GET_TOKEN(mbx_mem->msg_header)); 150 151 scmi_put_channel(ch); 152 153 return ret; 154 } 155 156 /* 157 * SCMI Driver initialization API. Returns initialized channel on success 158 * or NULL on error. The return type is an opaque void pointer. 159 */ 160 void *scmi_init(scmi_channel_t *ch) 161 { 162 uint32_t version; 163 int ret; 164 165 assert(ch && ch->info); 166 assert(ch->info->db_reg_addr); 167 assert(ch->info->db_modify_mask); 168 assert(ch->info->db_preserve_mask); 169 assert(ch->info->ring_doorbell != NULL); 170 171 assert(ch->lock); 172 173 scmi_lock_init(ch->lock); 174 175 ch->is_initialized = 1; 176 177 ret = scmi_proto_version(ch, SCMI_PWR_DMN_PROTO_ID, &version); 178 if (ret != SCMI_E_SUCCESS) { 179 WARN("SCMI power domain protocol version message failed\n"); 180 goto error; 181 } 182 183 if (!is_scmi_version_compatible(SCMI_PWR_DMN_PROTO_VER, version)) { 184 WARN("SCMI power domain protocol version 0x%x incompatible with driver version 0x%x\n", 185 version, SCMI_PWR_DMN_PROTO_VER); 186 goto error; 187 } 188 189 VERBOSE("SCMI power domain protocol version 0x%x detected\n", version); 190 191 ret = scmi_proto_version(ch, SCMI_SYS_PWR_PROTO_ID, &version); 192 if ((ret != SCMI_E_SUCCESS)) { 193 WARN("SCMI system power protocol version message failed\n"); 194 goto error; 195 } 196 197 if (!is_scmi_version_compatible(SCMI_SYS_PWR_PROTO_VER, version)) { 198 WARN("SCMI system power management protocol version 0x%x incompatible with driver version 0x%x\n", 199 version, SCMI_SYS_PWR_PROTO_VER); 200 goto error; 201 } 202 203 VERBOSE("SCMI system power management protocol version 0x%x detected\n", 204 version); 205 206 INFO("SCMI driver initialized\n"); 207 208 return (void *)ch; 209 210 error: 211 ch->is_initialized = 0; 212 return NULL; 213 } 214