1/*	$OpenBSD: scmi.c,v 1.1 2023/02/13 19:26:15 kettenis Exp $	*/
2
3/*
4 * Copyright (c) 2023 Mark Kettenis <kettenis@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/param.h>
20#include <sys/device.h>
21#include <sys/systm.h>
22
23#include <machine/bus.h>
24#include <machine/fdt.h>
25
26#include <dev/ofw/openfirm.h>
27#include <dev/ofw/ofw_clock.h>
28#include <dev/ofw/fdt.h>
29
30#include <dev/fdt/pscivar.h>
31
32struct scmi_shmem {
33	uint32_t reserved1;
34	uint32_t channel_status;
35#define SCMI_CHANNEL_ERROR		(1 << 1)
36#define SCMI_CHANNEL_FREE		(1 << 0)
37	uint32_t reserved2;
38	uint32_t reserved3;
39	uint32_t channel_flags;
40	uint32_t length;
41	uint32_t message_header;
42	uint32_t message_payload[];
43};
44
45#define SCMI_SUCCESS		0
46#define SCMI_NOT_SUPPORTED	-1
47#define SCMI_BUSY		-6
48#define SCMI_COMMS_ERROR	-7
49
50/* Protocols */
51#define SCMI_BASE		0x10
52#define SCMI_CLOCK		0x14
53
54/* Common messages */
55#define SCMI_PROTOCOL_VERSION			0x0
56#define SCMI_PROTOCOL_ATTRIBUTES		0x1
57#define SCMI_PROTOCOL_MESSAGE_ATTRIBUTES	0x2
58
59/* Clock management messages */
60#define SCMI_CLOCK_ATTRIBUTES			0x3
61#define SCMI_CLOCK_DESCRIBE_RATES		0x4
62#define SCMI_CLOCK_RATE_SET			0x5
63#define SCMI_CLOCK_RATE_GET			0x6
64#define SCMI_CLOCK_CONFIG_SET			0x7
65#define  SCMI_CLOCK_CONFIG_SET_ENABLE		(1 << 0)
66
67static inline void
68scmi_message_header(volatile struct scmi_shmem *shmem,
69    uint32_t protocol_id, uint32_t message_id)
70{
71	shmem->message_header = (protocol_id << 10) | (message_id << 0);
72}
73
74
75struct scmi_softc {
76	struct device			sc_dev;
77	bus_space_tag_t			sc_iot;
78	bus_space_handle_t		sc_ioh;
79	volatile struct scmi_shmem	*sc_shmem;
80
81	uint32_t			sc_smc_id;
82
83	struct clock_device		sc_cd;
84};
85
86int	scmi_match(struct device *, void *, void *);
87void	scmi_attach(struct device *, struct device *, void *);
88
89const struct cfattach scmi_ca = {
90	sizeof(struct scmi_softc), scmi_match, scmi_attach
91};
92
93struct cfdriver scmi_cd = {
94	NULL, "scmi", DV_DULL
95};
96
97void	scmi_attach_proto(struct scmi_softc *, int);
98void	scmi_attach_clock(struct scmi_softc *, int);
99int32_t	scmi_command(struct scmi_softc *);
100
101int
102scmi_match(struct device *parent, void *match, void *aux)
103{
104	struct fdt_attach_args *faa = aux;
105
106	return OF_is_compatible(faa->fa_node, "arm,scmi-smc");
107}
108
109void
110scmi_attach(struct device *parent, struct device *self, void *aux)
111{
112	struct scmi_softc *sc = (struct scmi_softc *)self;
113	volatile struct scmi_shmem *shmem;
114	struct fdt_attach_args *faa = aux;
115	struct fdt_reg reg;
116	int32_t status;
117	uint32_t version;
118	uint32_t phandle;
119	void *node;
120	int proto;
121
122	phandle = OF_getpropint(faa->fa_node, "shmem", 0);
123	node = fdt_find_phandle(phandle);
124	if (node == NULL || !fdt_is_compatible(node, "arm,scmi-shmem") ||
125	    fdt_get_reg(node, 0, &reg)) {
126		printf(": no shared memory\n");
127		return;
128	}
129
130	sc->sc_smc_id = OF_getpropint(faa->fa_node, "arm,smc-id", 0);
131	if (sc->sc_smc_id == 0) {
132		printf(": no SMC id\n");
133		return;
134	}
135
136	sc->sc_iot = faa->fa_iot;
137	if (bus_space_map(sc->sc_iot, reg.addr,
138	    reg.size, 0, &sc->sc_ioh)) {
139		printf(": can't map shared memory\n");
140		return;
141	}
142	sc->sc_shmem = bus_space_vaddr(sc->sc_iot, sc->sc_ioh);
143	shmem = sc->sc_shmem;
144
145	if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0) {
146		printf(": channel busy\n");
147		return;
148	}
149
150	scmi_message_header(shmem, SCMI_BASE, SCMI_PROTOCOL_VERSION);
151	shmem->length = sizeof(uint32_t);
152	status = scmi_command(sc);
153	if (status != SCMI_SUCCESS) {
154		printf(": protocol version command failed\n");
155		return;
156	}
157
158	version = shmem->message_payload[1];
159	printf(": SCMI %d.%d\n", version >> 16, version & 0xffff);
160
161	for (proto = OF_child(faa->fa_node); proto; proto = OF_peer(proto))
162		scmi_attach_proto(sc, proto);
163}
164
165int32_t
166scmi_command(struct scmi_softc *sc)
167{
168	volatile struct scmi_shmem *shmem = sc->sc_shmem;
169	int32_t status;
170
171	shmem->channel_status = 0;
172	status = smccc(sc->sc_smc_id, 0, 0, 0);
173	if (status != PSCI_SUCCESS)
174		return SCMI_NOT_SUPPORTED;
175	if ((shmem->channel_status & SCMI_CHANNEL_ERROR))
176		return SCMI_COMMS_ERROR;
177	if ((shmem->channel_status & SCMI_CHANNEL_FREE) == 0)
178		return SCMI_BUSY;
179	return shmem->message_payload[0];
180}
181
182void
183scmi_attach_proto(struct scmi_softc *sc, int node)
184{
185	switch (OF_getpropint(node, "reg", -1)) {
186	case SCMI_CLOCK:
187		scmi_attach_clock(sc, node);
188		break;
189	default:
190		break;
191	}
192}
193
194/* Clock management. */
195
196void	scmi_clock_enable(void *, uint32_t *, int);
197uint32_t scmi_clock_get_frequency(void *, uint32_t *);
198int	scmi_clock_set_frequency(void *, uint32_t *, uint32_t);
199
200void
201scmi_attach_clock(struct scmi_softc *sc, int node)
202{
203	volatile struct scmi_shmem *shmem = sc->sc_shmem;
204	int32_t status;
205	int nclocks;
206
207	scmi_message_header(shmem, SCMI_CLOCK, SCMI_PROTOCOL_ATTRIBUTES);
208	shmem->length = sizeof(uint32_t);
209	status = scmi_command(sc);
210	if (status != SCMI_SUCCESS)
211		return;
212
213	nclocks = shmem->message_payload[1] & 0xffff;
214	if (nclocks == 0)
215		return;
216
217	sc->sc_cd.cd_node = node;
218	sc->sc_cd.cd_cookie = sc;
219	sc->sc_cd.cd_enable = scmi_clock_enable;
220	sc->sc_cd.cd_get_frequency = scmi_clock_get_frequency;
221	sc->sc_cd.cd_set_frequency = scmi_clock_set_frequency;
222	clock_register(&sc->sc_cd);
223}
224
225void
226scmi_clock_enable(void *cookie, uint32_t *cells, int on)
227{
228	struct scmi_softc *sc = cookie;
229	volatile struct scmi_shmem *shmem = sc->sc_shmem;
230	uint32_t idx = cells[0];
231
232	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_CONFIG_SET);
233	shmem->length = 3 * sizeof(uint32_t);
234	shmem->message_payload[0] = idx;
235	shmem->message_payload[1] = on ? SCMI_CLOCK_CONFIG_SET_ENABLE : 0;
236	scmi_command(sc);
237}
238
239uint32_t
240scmi_clock_get_frequency(void *cookie, uint32_t *cells)
241{
242	struct scmi_softc *sc = cookie;
243	volatile struct scmi_shmem *shmem = sc->sc_shmem;
244	uint32_t idx = cells[0];
245	int32_t status;
246
247	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_GET);
248	shmem->length = 2 * sizeof(uint32_t);
249	shmem->message_payload[0] = idx;
250	status = scmi_command(sc);
251	if (status != SCMI_SUCCESS)
252		return 0;
253	if (shmem->message_payload[2] != 0)
254		return 0;
255
256	return shmem->message_payload[1];
257}
258
259int
260scmi_clock_set_frequency(void *cookie, uint32_t *cells, uint32_t freq)
261{
262	struct scmi_softc *sc = cookie;
263	volatile struct scmi_shmem *shmem = sc->sc_shmem;
264	uint32_t idx = cells[0];
265	int32_t status;
266
267	scmi_message_header(shmem, SCMI_CLOCK, SCMI_CLOCK_RATE_SET);
268	shmem->length = 5 * sizeof(uint32_t);
269	shmem->message_payload[0] = 0;
270	shmem->message_payload[1] = idx;
271	shmem->message_payload[2] = freq;
272	shmem->message_payload[3] = 0;
273	status = scmi_command(sc);
274	if (status != SCMI_SUCCESS)
275		return -1;
276
277	return 0;
278}
279