xref: /freebsd/sys/dev/firmware/arm/scmi.c (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Ruslan Bukin <br@bsdpad.com>
5  *
6  * This work was supported by Innovate UK project 105694, "Digital Security
7  * by Design (DSbD) Technology Platform Prototype".
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/bus.h>
34 #include <sys/cpu.h>
35 #include <sys/kernel.h>
36 #include <sys/lock.h>
37 #include <sys/module.h>
38 #include <sys/mutex.h>
39 
40 #include <dev/clk/clk.h>
41 #include <dev/fdt/simplebus.h>
42 #include <dev/fdt/fdt_common.h>
43 #include <dev/ofw/ofw_bus_subr.h>
44 
45 #include "dev/mailbox/arm/arm_doorbell.h"
46 
47 #include "scmi.h"
48 #include "scmi_protocols.h"
49 
50 struct scmi_softc {
51 	struct simplebus_softc	simplebus_sc;
52 	device_t		dev;
53 	device_t		tx_shmem;
54 	struct arm_doorbell	*db;
55 	struct mtx		mtx;
56 	int			req_done;
57 };
58 
59 static device_t
60 scmi_get_shmem(struct scmi_softc *sc, int index)
61 {
62 	phandle_t *shmems;
63 	phandle_t node;
64 	device_t dev;
65 	size_t len;
66 
67 	node = ofw_bus_get_node(sc->dev);
68 	if (node <= 0)
69 		return (NULL);
70 
71 	len = OF_getencprop_alloc_multi(node, "shmem", sizeof(*shmems),
72 	    (void **)&shmems);
73 	if (len <= 0) {
74 		device_printf(sc->dev, "%s: Can't get shmem node.\n", __func__);
75 		return (NULL);
76 	}
77 
78 	if (index >= len) {
79 		OF_prop_free(shmems);
80 		return (NULL);
81 	}
82 
83 	dev = OF_device_from_xref(shmems[index]);
84 	if (dev == NULL)
85 		device_printf(sc->dev, "%s: Can't get shmem device.\n",
86 		    __func__);
87 
88 	OF_prop_free(shmems);
89 
90 	return (dev);
91 }
92 
93 static void
94 scmi_callback(void *arg)
95 {
96 	struct scmi_softc *sc;
97 
98 	sc = arg;
99 
100 	dprintf("%s sc %p\n", __func__, sc);
101 
102 	SCMI_LOCK(sc);
103 	sc->req_done = 1;
104 	wakeup(sc);
105 	SCMI_UNLOCK(sc);
106 }
107 
108 static int
109 scmi_request_locked(struct scmi_softc *sc, struct scmi_req *req)
110 {
111 	struct scmi_smt_header hdr;
112 	int timeout;
113 
114 	bzero(&hdr, sizeof(struct scmi_smt_header));
115 
116 	SCMI_ASSERT_LOCKED(sc);
117 
118 	/* Read header */
119 	scmi_shmem_read(sc->tx_shmem, 0, &hdr, SMT_HEADER_SIZE);
120 
121 	if ((hdr.channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE) == 0)
122 		return (1);
123 
124 	/* Update header */
125 	hdr.channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE;
126 	hdr.msg_header = req->protocol_id << SMT_HEADER_PROTOCOL_ID_S;
127 	hdr.msg_header |= req->message_id << SMT_HEADER_MESSAGE_ID_S;
128 	hdr.length = sizeof(hdr.msg_header) + req->in_size;
129 	hdr.flags |= SCMI_SHMEM_FLAG_INTR_ENABLED;
130 
131 	/* Write header */
132 	scmi_shmem_write(sc->tx_shmem, 0, &hdr, SMT_HEADER_SIZE);
133 
134 	/* Write request */
135 	scmi_shmem_write(sc->tx_shmem, SMT_HEADER_SIZE, req->in_buf,
136 	    req->in_size);
137 
138 	sc->req_done = 0;
139 
140 	/* Interrupt SCP firmware. */
141 	arm_doorbell_set(sc->db);
142 
143 	timeout = 200;
144 
145 	dprintf("%s: request\n", __func__);
146 
147 	do {
148 		if (cold) {
149 			if (arm_doorbell_get(sc->db))
150 				break;
151 			DELAY(10000);
152 		} else {
153 			msleep(sc, &sc->mtx, 0, "scmi", hz / 10);
154 			if (sc->req_done)
155 				break;
156 		}
157 	} while (timeout--);
158 
159 	if (timeout <= 0)
160 		return (-1);
161 
162 	dprintf("%s: got reply, timeout %d\n", __func__, timeout);
163 
164 	/* Read header. */
165 	scmi_shmem_read(sc->tx_shmem, 0, &hdr, SMT_HEADER_SIZE);
166 
167 	/* Read response */
168 	scmi_shmem_read(sc->tx_shmem, SMT_HEADER_SIZE, req->out_buf,
169 	    req->out_size);
170 
171 	return (0);
172 }
173 
174 int
175 scmi_request(device_t dev, struct scmi_req *req)
176 {
177 	struct scmi_softc *sc;
178 	int error;
179 
180 	sc = device_get_softc(dev);
181 
182 	SCMI_LOCK(sc);
183 	error = scmi_request_locked(sc, req);
184 	SCMI_UNLOCK(sc);
185 
186 	return (error);
187 }
188 
189 static int
190 scmi_probe(device_t dev)
191 {
192 
193 	if (!ofw_bus_is_compatible(dev, "arm,scmi"))
194 		return (ENXIO);
195 
196 	if (!ofw_bus_status_okay(dev))
197 		return (ENXIO);
198 
199 	device_set_desc(dev, "ARM SCMI interface driver");
200 
201 	return (BUS_PROBE_DEFAULT);
202 }
203 
204 static int
205 scmi_attach(device_t dev)
206 {
207 	struct scmi_softc *sc;
208 	phandle_t node;
209 	int error;
210 
211 	sc = device_get_softc(dev);
212 	sc->dev = dev;
213 
214 	node = ofw_bus_get_node(dev);
215 	if (node == -1)
216 		return (ENXIO);
217 
218 	sc->tx_shmem = scmi_get_shmem(sc, 0);
219 	if (sc->tx_shmem == NULL) {
220 		device_printf(dev, "TX shmem dev not found.\n");
221 		return (ENXIO);
222 	}
223 
224 	sc->db = arm_doorbell_ofw_get(sc->dev, "tx");
225 	if (sc->db == NULL) {
226 		device_printf(dev, "Doorbell device not found.\n");
227 		return (ENXIO);
228 	}
229 
230 	mtx_init(&sc->mtx, device_get_nameunit(dev), "SCMI", MTX_DEF);
231 
232 	arm_doorbell_set_handler(sc->db, scmi_callback, sc);
233 
234 	simplebus_init(dev, node);
235 
236 	/*
237 	 * Allow devices to identify.
238 	 */
239 	bus_generic_probe(dev);
240 
241 	/*
242 	 * Now walk the OFW tree and attach top-level devices.
243 	 */
244 	for (node = OF_child(node); node > 0; node = OF_peer(node))
245 		simplebus_add_device(dev, node, 0, NULL, -1, NULL);
246 
247 	error = bus_generic_attach(dev);
248 
249 	return (error);
250 }
251 
252 static int
253 scmi_detach(device_t dev)
254 {
255 
256 	return (0);
257 }
258 
259 static device_method_t scmi_methods[] = {
260 	DEVMETHOD(device_probe,		scmi_probe),
261 	DEVMETHOD(device_attach,	scmi_attach),
262 	DEVMETHOD(device_detach,	scmi_detach),
263 	DEVMETHOD_END
264 };
265 
266 DEFINE_CLASS_1(scmi, scmi_driver, scmi_methods, sizeof(struct scmi_softc),
267     simplebus_driver);
268 
269 DRIVER_MODULE(scmi, simplebus, scmi_driver, 0, 0);
270 MODULE_VERSION(scmi, 1);
271