xref: /openbsd/sys/dev/fdt/bcm2835_dmac.c (revision 4cfece93)
1 /*     $OpenBSD: bcm2835_dmac.c,v 1.1 2020/04/21 18:56:54 tobhe 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) 2014 Jared D. McNeill <jmcneill@invisible.ca>
22  * All rights reserved.
23  *
24  * Redistribution and use in source and binary forms, with or without
25  * modification, are permitted provided that the following conditions
26  * are met:
27  * 1. Redistributions of source code must retain the above copyright
28  *    notice, this list of conditions and the following disclaimer.
29  * 2. Redistributions in binary form must reproduce the above copyright
30  *    notice, this list of conditions and the following disclaimer in the
31  *    documentation and/or other materials provided with the distribution.
32  *
33  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
34  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
36  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
37  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
38  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
40  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
41  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
42  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43  * SUCH DAMAGE.
44  */
45 
46 #include <sys/types.h>
47 #include <sys/atomic.h>
48 #include <sys/malloc.h>
49 #include <sys/mutex.h>
50 #include <sys/systm.h>
51 
52 #include <machine/bus.h>
53 #include <machine/fdt.h>
54 #include <machine/intr.h>
55 
56 #include <dev/ofw/fdt.h>
57 #include <dev/ofw/openfirm.h>
58 
59 #include "dev/ic/bcm2835_dmac.h"
60 
61 #define BCMDMAC_CHANNELMASK ((1 << 12) - 1)
62 #define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
63 
64 struct bcmdmac_softc {
65 	struct device sc_dev;
66 	bus_space_tag_t sc_iot;
67 	bus_space_handle_t sc_ioh;
68 	int sc_fa_node;
69 
70 	struct mutex sc_lock;
71 	struct bcmdmac_channel *sc_channels;
72 	int sc_nchannels;
73 	uint32_t sc_channelmask;
74 };
75 
76 static struct bcmdmac_softc *bcmdmac_sc = NULL;
77 
78 struct bcmdmac_channel {
79 	struct bcmdmac_softc *ch_sc;
80 	void *ch_ih;
81 	uint8_t ch_index;
82 	void (*ch_callback)(uint32_t, uint32_t, void *);
83 	void *ch_callbackarg;
84 	uint32_t ch_debug;
85 };
86 
87 int bcmdmac_match(struct device *, void *, void *);
88 void bcmdmac_attach(struct device *, struct device *, void *);
89 
90 struct cfattach bcmdmac_ca = {
91 	sizeof(struct bcmdmac_softc),
92 	bcmdmac_match,
93 	bcmdmac_attach,
94 };
95 
96 struct cfdriver bcmdmac_cd = { NULL, "bcmdmac", DV_DULL };
97 
98 /* utilities */
99 enum bcmdmac_type
100 bcmdmac_channel_type(struct bcmdmac_channel ch)
101 {
102 	if (ISSET(ch.ch_debug, DMAC_DEBUG_LITE))
103 		return BCMDMAC_TYPE_LITE;
104 	else
105 		return BCMDMAC_TYPE_NORMAL;
106 }
107 
108 int
109 bcmdmac_channel_used(struct bcmdmac_channel ch)
110 {
111 	return ch.ch_callback != NULL;
112 }
113 
114 void
115 bcmdmac_write(struct bcmdmac_softc *sc, bus_size_t offset, uint32_t value)
116 {
117 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, offset, value);
118 }
119 
120 uint32_t
121 bcmdmac_read(struct bcmdmac_softc *sc, bus_size_t offset)
122 {
123 	return bus_space_read_4(sc->sc_iot, sc->sc_ioh, offset);
124 }
125 
126 /* driver handles */
127 int
128 bcmdmac_match(struct device *parent, void *match, void *aux)
129 {
130 	struct fdt_attach_args *faa = aux;
131 
132 	return OF_is_compatible(faa->fa_node, "brcm,bcm2835-dma");
133 }
134 
135 void
136 bcmdmac_attach(struct device *parent, struct device *self, void *aux)
137 {
138 	struct bcmdmac_softc *sc = (struct bcmdmac_softc *)self;
139 	struct fdt_attach_args *faa = aux;
140 	struct bcmdmac_channel *ch;
141 	uint32_t val;
142 	int index;
143 	bus_addr_t addr;
144 	bus_size_t size;
145 
146 	if (bcmdmac_sc) {
147 		printf(": already attached\n");
148 		return;
149 	}
150 	bcmdmac_sc = sc;
151 
152 	sc->sc_iot = faa->fa_iot;
153 	sc->sc_fa_node = faa->fa_node;
154 
155 	if (faa->fa_nreg < 1) {
156 		printf(": no registers\n");
157 		return;
158 	}
159 
160 	addr = faa->fa_reg[0].addr;
161 	size = faa->fa_reg[0].size;
162 	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh)) {
163 		printf(": can't map registers\n");
164 		return;
165 	}
166 
167 	sc->sc_channelmask =
168 	    OF_getpropint(faa->fa_node, "brcm,dma-channel-mask", -1);
169 	sc->sc_channelmask &= BCMDMAC_CHANNELMASK;
170 
171 	mtx_init(&sc->sc_lock, IPL_SCHED);
172 
173 	sc->sc_nchannels = 31 - __builtin_clz(sc->sc_channelmask);
174 	sc->sc_channels = malloc(sizeof(*sc->sc_channels) * sc->sc_nchannels,
175 	    M_DEVBUF, M_WAITOK);
176 
177 	printf(":");
178 	for (index = 0; index < sc->sc_nchannels; index++) {
179 		ch = &sc->sc_channels[index];
180 		ch->ch_sc = sc;
181 		ch->ch_index = index;
182 		ch->ch_callback = NULL;
183 		ch->ch_callbackarg = NULL;
184 		ch->ch_ih = NULL;
185 		if (!ISSET(sc->sc_channelmask, (1 << index)))
186 			continue;
187 
188 		printf(" DMA%d", index);
189 
190 		ch->ch_debug = bcmdmac_read(sc, DMAC_DEBUG(index));
191 
192 		val = bcmdmac_read(sc, DMAC_CS(index));
193 		val |= DMAC_CS_RESET;
194 		bcmdmac_write(sc, DMAC_CS(index), val);
195 	}
196 	printf("\n");
197 }
198 
199 int
200 bcmdmac_intr(void *arg)
201 {
202 	struct bcmdmac_channel *ch = arg;
203 	struct bcmdmac_softc *sc = ch->ch_sc;
204 	uint32_t cs, ce;
205 
206 	cs = bcmdmac_read(sc, DMAC_CS(ch->ch_index));
207 	bcmdmac_write(sc, DMAC_CS(ch->ch_index), cs);
208 	cs &= DMAC_CS_INT | DMAC_CS_END | DMAC_CS_ERROR;
209 
210 	ce = bcmdmac_read(sc, DMAC_DEBUG(ch->ch_index));
211 	ce &= DMAC_DEBUG_READ_ERROR | DMAC_DEBUG_FIFO_ERROR
212 	    | DMAC_DEBUG_READ_LAST_NOT_SET_ERROR;
213 	bcmdmac_write(sc, DMAC_DEBUG(ch->ch_index), ce);
214 
215 	if (ch->ch_callback)
216 		ch->ch_callback(cs, ce, ch->ch_callbackarg);
217 
218 	return 1;
219 }
220 
221 struct bcmdmac_channel *
222 bcmdmac_alloc(enum bcmdmac_type type, int ipl,
223 	    void (*cb)(uint32_t, uint32_t, void *), void *cbarg)
224 {
225 	struct bcmdmac_softc *sc = bcmdmac_sc;
226 	struct bcmdmac_channel *ch = NULL;
227 	int index;
228 
229 	if (sc == NULL)
230 		return NULL;
231 
232 	mtx_enter(&sc->sc_lock);
233 	for (index = 0; index < sc->sc_nchannels; index++) {
234 		if (!ISSET(sc->sc_channelmask, (1 << index)))
235 			continue;
236 		if (bcmdmac_channel_type(sc->sc_channels[index]) != type)
237 			continue;
238 		if (bcmdmac_channel_used(sc->sc_channels[index]))
239 			continue;
240 
241 		ch = &sc->sc_channels[index];
242 		ch->ch_callback = cb;
243 		ch->ch_callbackarg = cbarg;
244 		break;
245 	}
246 	mtx_leave(&sc->sc_lock);
247 
248 	if (ch == NULL)
249 		return NULL;
250 
251 	KASSERT(ch->ch_ih == NULL);
252 
253 	ch->ch_ih = fdt_intr_establish_idx(sc->sc_fa_node, ch->ch_index, ipl,
254 					   bcmdmac_intr, ch, sc->sc_dev.dv_xname);
255 
256 	if (ch->ch_ih == NULL) {
257 		printf("%s: failed to establish interrupt for DMA%d\n",
258 		       DEVNAME(sc), ch->ch_index);
259 		ch->ch_callback = NULL;
260 		ch->ch_callbackarg = NULL;
261 		ch = NULL;
262 	}
263 
264 	return ch;
265 }
266 
267 void
268 bcmdmac_free(struct bcmdmac_channel *ch)
269 {
270 	struct bcmdmac_softc *sc = ch->ch_sc;
271 	uint32_t val;
272 
273 	bcmdmac_halt(ch);
274 
275 	/* reset chip */
276 	val = bcmdmac_read(sc, DMAC_CS(ch->ch_index));
277 	val |= DMAC_CS_RESET;
278 	val &= ~DMAC_CS_ACTIVE;
279 	bcmdmac_write(sc, DMAC_CS(ch->ch_index), val);
280 
281 	mtx_enter(&sc->sc_lock);
282 	fdt_intr_disestablish(ch->ch_ih);
283 	ch->ch_ih = NULL;
284 	ch->ch_callback = NULL;
285 	ch->ch_callbackarg = NULL;
286 	mtx_leave(&sc->sc_lock);
287 }
288 
289 void
290 bcmdmac_set_conblk_addr(struct bcmdmac_channel *ch, bus_addr_t addr)
291 {
292 	struct bcmdmac_softc *sc = ch->ch_sc;
293 
294 	bcmdmac_write(sc, DMAC_CONBLK_AD(ch->ch_index), addr);
295 }
296 
297 int
298 bcmdmac_transfer(struct bcmdmac_channel *ch)
299 {
300 	struct bcmdmac_softc *sc = ch->ch_sc;
301 	uint32_t val;
302 
303 	val = bcmdmac_read(sc, DMAC_CS(ch->ch_index));
304 	if (ISSET(val, DMAC_CS_ACTIVE))
305 		return EBUSY;
306 
307 	val |= DMAC_CS_ACTIVE;
308 	bcmdmac_write(sc, DMAC_CS(ch->ch_index), val);
309 
310 	return 0;
311 }
312 
313 void
314 bcmdmac_halt(struct bcmdmac_channel *ch)
315 {
316 	struct bcmdmac_softc *sc = ch->ch_sc;
317 	uint32_t val;
318 
319 	/* pause DMA */
320 	val = bcmdmac_read(sc, DMAC_CS(ch->ch_index));
321 	val &= ~DMAC_CS_ACTIVE;
322 	bcmdmac_write(sc, DMAC_CS(ch->ch_index), val);
323 
324 	/* XXX wait for paused state */
325 
326 	/* end descriptor chain */
327 	bcmdmac_write(sc, DMAC_NEXTCONBK(ch->ch_index), 0);
328 
329 	/* resume DMA, which then stops */
330 	val |= DMAC_CS_ACTIVE | DMAC_CS_ABORT;
331 	bcmdmac_write(sc, DMAC_CS(ch->ch_index), val);
332 }
333