1/*     $OpenBSD: bcm2835_mbox.c,v 1.5 2024/05/28 09:19:04 claudio Exp $ */
2
3/*
4 * Copyright (c) 2020 Tobias Heider <tobhe@openbsd.org>
5 * Copyright (c) 2019 Neil Ashford <ashfordneil0@gmail.com>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/*-
21 * Copyright (c) 2012 The NetBSD Foundation, Inc.
22 * All rights reserved.
23 *
24 * This code is derived from software contributed to The NetBSD Foundation
25 * by Nick Hudson
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 *    notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 *    notice, this list of conditions and the following disclaimer in the
34 *    documentation and/or other materials provided with the distribution.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
37 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
38 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
39 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
40 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 * POSSIBILITY OF SUCH DAMAGE.
47 */
48
49#include <sys/types.h>
50#include <sys/systm.h>
51#include <sys/mutex.h>
52
53#include <machine/bus.h>
54#include <machine/fdt.h>
55#include <machine/intr.h>
56
57#include <dev/ofw/fdt.h>
58#include <dev/ofw/openfirm.h>
59
60#include <dev/ic/bcm2835_mbox.h>
61#include <dev/ic/bcm2835_vcprop.h>
62
63#define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
64
65struct cfdriver bcmmbox_cd = { NULL, "bcmmbox", DV_DULL };
66
67struct bcmmbox_softc {
68	struct device sc_dev;
69	bus_space_tag_t sc_iot;
70	bus_space_handle_t sc_ioh;
71
72	bus_dma_tag_t	sc_dmat;
73	bus_dmamap_t	sc_dmamap;
74
75	void *sc_ih;
76
77	struct mutex sc_intr_lock;
78	int sc_chan[BCMMBOX_NUM_CHANNELS];
79	uint32_t sc_mbox[BCMMBOX_NUM_CHANNELS];
80};
81
82static struct bcmmbox_softc *bcmmbox_sc;
83
84int bcmmbox_match(struct device *, void *, void *);
85void bcmmbox_attach(struct device *, struct device *, void *);
86
87const struct cfattach bcmmbox_ca = {
88	sizeof(struct bcmmbox_softc),
89	bcmmbox_match,
90	bcmmbox_attach,
91};
92
93uint32_t bcmmbox_reg_read(struct bcmmbox_softc *, int);
94void bcmmbox_reg_write(struct bcmmbox_softc *, int, uint32_t);
95void bcmmbox_reg_flush(struct bcmmbox_softc *, int);
96int bcmmbox_intr(void *);
97int bcmmbox_intr_helper(struct bcmmbox_softc *, int);
98
99int
100bcmmbox_match(struct device *parent, void *match, void *aux)
101{
102	struct fdt_attach_args *faa = aux;
103
104	return OF_is_compatible(faa->fa_node, "brcm,bcm2835-mbox");
105}
106
107void
108bcmmbox_attach(struct device *parent, struct device *self, void *aux)
109{
110	struct bcmmbox_softc *sc = (struct bcmmbox_softc *)self;
111	struct fdt_attach_args *faa = aux;
112	bus_addr_t addr;
113	bus_size_t size;
114
115	if (bcmmbox_sc) {
116		printf(": a similar device as already attached\n");
117		return;
118	}
119	bcmmbox_sc = sc;
120
121	mtx_init(&sc->sc_intr_lock, IPL_VM);
122
123	if (faa->fa_nreg < 1) {
124		printf(": no registers\n");
125		return;
126	}
127
128	addr = faa->fa_reg[0].addr;
129	size = faa->fa_reg[0].size;
130
131	sc->sc_dmat = faa->fa_dmat;
132	sc->sc_iot = faa->fa_iot;
133	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh)) {
134		printf(": can't map registers\n");
135		return;
136	}
137
138	if (bus_dmamap_create(sc->sc_dmat, ~0UL, 1, ~0UL, 0,
139	    BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &sc->sc_dmamap) != 0) {
140		printf(": unable to create dma map\n");
141		goto clean_bus_space_map;
142	}
143
144	sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_VM, bcmmbox_intr, sc,
145	    DEVNAME(sc));
146	if (sc->sc_ih == NULL) {
147		printf(": failed to establish interrupt\n");
148		goto clean_dmamap;
149	}
150
151	/* enable interrupt in hardware */
152	bcmmbox_reg_write(sc, BCMMBOX_CFG, BCMMBOX_CFG_DATA_IRQ_EN);
153
154	printf("\n");
155
156	return;
157
158 clean_dmamap:
159	bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap);
160
161 clean_bus_space_map:
162	bus_space_unmap(sc->sc_iot, sc->sc_ioh, size);
163}
164
165uint32_t
166bcmmbox_reg_read(struct bcmmbox_softc *sc, int addr)
167{
168	return bus_space_read_4(sc->sc_iot, sc->sc_ioh, addr);
169}
170
171void
172bcmmbox_reg_write(struct bcmmbox_softc *sc, int addr, uint32_t val)
173{
174	bus_space_write_4(sc->sc_iot, sc->sc_ioh, addr, val);
175}
176
177void
178bcmmbox_reg_flush(struct bcmmbox_softc *sc, int flags)
179{
180	bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, BCMMBOX_SIZE, flags);
181}
182
183int
184bcmmbox_intr(void *cookie)
185{
186	struct bcmmbox_softc *sc = cookie;
187	int ret;
188
189	mtx_enter(&sc->sc_intr_lock);
190	ret = bcmmbox_intr_helper(sc, 1);
191	mtx_leave(&sc->sc_intr_lock);
192
193	return ret;
194}
195
196int
197bcmmbox_intr_helper(struct bcmmbox_softc *sc, int broadcast)
198{
199	uint32_t mbox, chan, data;
200	int ret = 0;
201
202	bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_READ);
203
204	while (!ISSET(bcmmbox_reg_read(sc, BCMMBOX_STATUS), BCMMBOX_STATUS_EMPTY)) {
205		mbox = bcmmbox_reg_read(sc, BCMMBOX0_READ);
206
207		chan = mbox & BCMMBOX_CHANNEL_MASK;
208		data = mbox & ~BCMMBOX_CHANNEL_MASK;
209		ret = 1;
210
211		if ((sc->sc_mbox[chan] & BCMMBOX_CHANNEL_MASK) != 0) {
212			printf("%s: chan %d overflow\n", DEVNAME(sc), chan);
213			continue;
214		}
215
216		sc->sc_mbox[chan] = data | BCMMBOX_CHANNEL_MASK;
217
218		if (broadcast)
219			wakeup(&sc->sc_chan[chan]);
220	}
221
222	return ret;
223}
224
225void
226bcmmbox_read(uint8_t chan, uint32_t *data)
227{
228	struct bcmmbox_softc *sc = bcmmbox_sc;
229	uint32_t mbox, rchan, rdata, status;
230
231	KASSERT(sc != NULL);
232	KASSERT(chan == (chan & BCMMBOX_CHANNEL_MASK));
233
234	while (1) {
235		bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_READ);
236		status = bcmmbox_reg_read(sc, BCMMBOX0_STATUS);
237		if (ISSET(status, BCMMBOX_STATUS_EMPTY))
238			continue;
239
240		mbox = bcmmbox_reg_read(sc, BCMMBOX0_READ);
241
242		rchan = mbox & BCMMBOX_CHANNEL_MASK;
243		rdata = mbox & ~BCMMBOX_CHANNEL_MASK;
244
245		if (rchan == chan) {
246			*data = rdata;
247			return;
248		}
249	}
250}
251
252void
253bcmmbox_write(uint8_t chan, uint32_t data)
254{
255	struct bcmmbox_softc *sc = bcmmbox_sc;
256	uint32_t rdata;
257
258	KASSERT(sc != NULL);
259	KASSERT(chan == (chan & BCMMBOX_CHANNEL_MASK));
260	KASSERT(data == (data & ~BCMMBOX_CHANNEL_MASK));
261
262	while (1) {
263		bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_READ);
264		rdata = bcmmbox_reg_read(sc, BCMMBOX0_STATUS);
265		if (!ISSET(rdata, BCMMBOX_STATUS_FULL))
266			break;
267	}
268
269	bcmmbox_reg_write(sc, BCMMBOX1_WRITE, chan | data);
270	bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_WRITE);
271}
272
273int
274bcmmbox_post(uint8_t chan, void *buf, size_t len, uint32_t *res)
275{
276	struct bcmmbox_softc *sc = bcmmbox_sc;
277	bus_dmamap_t map;
278	int error;
279
280	KASSERT(sc != NULL);
281	if (sc == NULL)
282		return (ENXIO);
283
284	map = sc->sc_dmamap;
285
286	error = bus_dmamap_load(sc->sc_dmat, map, buf, len, NULL,
287	    BUS_DMA_NOWAIT | BUS_DMA_READ | BUS_DMA_WRITE);
288	if (error != 0)
289		return (error);
290
291	bus_dmamap_sync(sc->sc_dmat, map, 0, len,
292	    BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
293
294	bcmmbox_write(chan, map->dm_segs[0].ds_addr);
295	bcmmbox_read(chan, res);
296
297	bus_dmamap_sync(sc->sc_dmat, map, 0, len,
298	    BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
299
300	bus_dmamap_unload(sc->sc_dmat, map);
301
302	return (0);
303}
304