1/*	$OpenBSD: qcaoss.c,v 1.1 2023/05/23 14:10:27 patrick Exp $	*/
2/*
3 * Copyright (c) 2023 Patrick Wildt <patrick@blueri.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <sys/param.h>
19#include <sys/systm.h>
20#include <sys/device.h>
21#include <sys/malloc.h>
22#include <sys/atomic.h>
23
24#include <machine/bus.h>
25#include <machine/fdt.h>
26
27#include <dev/ofw/openfirm.h>
28#include <dev/ofw/ofw_misc.h>
29#include <dev/ofw/fdt.h>
30
31#define AOSS_DESC_MAGIC			0x0
32#define AOSS_DESC_VERSION		0x4
33#define AOSS_DESC_FEATURES		0x8
34#define AOSS_DESC_UCORE_LINK_STATE	0xc
35#define AOSS_DESC_UCORE_LINK_STATE_ACK	0x10
36#define AOSS_DESC_UCORE_CH_STATE	0x14
37#define AOSS_DESC_UCORE_CH_STATE_ACK	0x18
38#define AOSS_DESC_UCORE_MBOX_SIZE	0x1c
39#define AOSS_DESC_UCORE_MBOX_OFFSET	0x20
40#define AOSS_DESC_MCORE_LINK_STATE	0x24
41#define AOSS_DESC_MCORE_LINK_STATE_ACK	0x28
42#define AOSS_DESC_MCORE_CH_STATE	0x2c
43#define AOSS_DESC_MCORE_CH_STATE_ACK	0x30
44#define AOSS_DESC_MCORE_MBOX_SIZE	0x34
45#define AOSS_DESC_MCORE_MBOX_OFFSET	0x38
46
47#define AOSS_MAGIC			0x4d41494c
48#define AOSS_VERSION			1
49
50#define AOSS_STATE_UP			(0xffffU << 0)
51#define AOSS_STATE_DOWN			(0xffffU << 16)
52
53#define HREAD4(sc, reg)							\
54	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
55#define HWRITE4(sc, reg, val)						\
56	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
57
58struct qcaoss_softc {
59	struct device		sc_dev;
60	bus_space_tag_t		sc_iot;
61	bus_space_handle_t	sc_ioh;
62
63	size_t			sc_offset;
64	size_t			sc_size;
65
66	struct mbox_channel	*sc_mc;
67};
68
69struct qcaoss_softc *qcaoss_sc;
70
71int	qcaoss_match(struct device *, void *, void *);
72void	qcaoss_attach(struct device *, struct device *, void *);
73
74const struct cfattach qcaoss_ca = {
75	sizeof (struct qcaoss_softc), qcaoss_match, qcaoss_attach
76};
77
78struct cfdriver qcaoss_cd = {
79	NULL, "qcaoss", DV_DULL
80};
81
82int
83qcaoss_match(struct device *parent, void *match, void *aux)
84{
85	struct fdt_attach_args *faa = aux;
86
87	return OF_is_compatible(faa->fa_node, "qcom,aoss-qmp");
88}
89
90void
91qcaoss_attach(struct device *parent, struct device *self, void *aux)
92{
93	struct qcaoss_softc *sc = (struct qcaoss_softc *)self;
94	struct fdt_attach_args *faa = aux;
95	int i;
96
97	if (faa->fa_nreg < 1) {
98		printf(": no registers\n");
99		return;
100	}
101
102	sc->sc_iot = faa->fa_iot;
103	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
104	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
105		printf(": can't map registers\n");
106		return;
107	}
108
109	sc->sc_mc = mbox_channel_idx(faa->fa_node, 0, NULL);
110	if (sc->sc_mc == NULL) {
111		bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size);
112		printf(": can't find mbox\n");
113		return;
114	}
115
116	if (HREAD4(sc, AOSS_DESC_MAGIC) != AOSS_MAGIC ||
117	    HREAD4(sc, AOSS_DESC_VERSION) != AOSS_VERSION) {
118		printf(": invalid QMP info\n");
119		return;
120	}
121
122	sc->sc_offset = HREAD4(sc, AOSS_DESC_MCORE_MBOX_OFFSET);
123	sc->sc_size = HREAD4(sc, AOSS_DESC_MCORE_MBOX_SIZE);
124	if (sc->sc_size == 0) {
125		printf(": invalid mailbox size\n");
126		return;
127	}
128
129	HWRITE4(sc, AOSS_DESC_UCORE_LINK_STATE_ACK,
130	    HREAD4(sc, AOSS_DESC_UCORE_LINK_STATE));
131
132	HWRITE4(sc, AOSS_DESC_MCORE_LINK_STATE, AOSS_STATE_UP);
133	mbox_send(sc->sc_mc, NULL, 0);
134
135	for (i = 1000; i > 0; i--) {
136		if (HREAD4(sc, AOSS_DESC_MCORE_LINK_STATE_ACK) == AOSS_STATE_UP)
137			break;
138		delay(1000);
139	}
140	if (i == 0) {
141		printf(": didn't get link state ack\n");
142		return;
143	}
144
145	HWRITE4(sc, AOSS_DESC_MCORE_CH_STATE, AOSS_STATE_UP);
146	mbox_send(sc->sc_mc, NULL, 0);
147
148	for (i = 1000; i > 0; i--) {
149		if (HREAD4(sc, AOSS_DESC_UCORE_CH_STATE) == AOSS_STATE_UP)
150			break;
151		delay(1000);
152	}
153	if (i == 0) {
154		printf(": didn't get open channel\n");
155		return;
156	}
157
158	HWRITE4(sc, AOSS_DESC_UCORE_CH_STATE_ACK, AOSS_STATE_UP);
159	mbox_send(sc->sc_mc, NULL, 0);
160
161	for (i = 1000; i > 0; i--) {
162		if (HREAD4(sc, AOSS_DESC_MCORE_CH_STATE_ACK) == AOSS_STATE_UP)
163			break;
164		delay(1000);
165	}
166	if (i == 0) {
167		printf(": didn't get channel ack\n");
168		return;
169	}
170
171	printf("\n");
172
173	qcaoss_sc = sc;
174}
175
176int
177qcaoss_send(char *data, size_t len)
178{
179	struct qcaoss_softc *sc = qcaoss_sc;
180	uint32_t reg;
181	int i;
182
183	if (sc == NULL)
184		return ENXIO;
185
186	if (data == NULL || sizeof(uint32_t) + len > sc->sc_size ||
187	    (len % sizeof(uint32_t)) != 0)
188		return EINVAL;
189
190	/* Write data first, needs to be 32-bit access. */
191	for (i = 0; i < len; i += 4) {
192		memcpy(&reg, data + i, sizeof(reg));
193		HWRITE4(sc, sc->sc_offset + sizeof(uint32_t) + i, reg);
194	}
195
196	/* Commit transaction by writing length. */
197	HWRITE4(sc, sc->sc_offset, len);
198
199	/* Assert it's stored and inform peer. */
200	KASSERT(HREAD4(sc, sc->sc_offset) == len);
201	mbox_send(sc->sc_mc, NULL, 0);
202
203	for (i = 1000; i > 0; i--) {
204		if (HREAD4(sc, sc->sc_offset) == 0)
205			break;
206		delay(1000);
207	}
208	if (i == 0) {
209		printf("%s: timeout sending message\n", sc->sc_dev.dv_xname);
210		HWRITE4(sc, sc->sc_offset, 0);
211		return ETIMEDOUT;
212	}
213
214	return 0;
215}
216