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  */
scmi_get_channel(scmi_channel_t * ch)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  */
scmi_send_sync_command(scmi_channel_t * ch)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  */
scmi_put_channel(scmi_channel_t * ch)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  */
scmi_proto_version(void * p,uint32_t proto_id,uint32_t * version)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  */
scmi_proto_msg_attr(void * p,uint32_t proto_id,uint32_t command_id,uint32_t * attr)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  */
scmi_init(scmi_channel_t * ch)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