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