1 /* $OpenBSD: bcm2835_dmac.c,v 1.5 2024/10/17 05:10:53 jsg 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 const 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
bcmdmac_channel_type(struct bcmdmac_channel * ch)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
bcmdmac_channel_used(struct bcmdmac_channel * ch)109 bcmdmac_channel_used(struct bcmdmac_channel *ch)
110 {
111 return ch->ch_callback != NULL;
112 }
113
114 void
bcmdmac_write(struct bcmdmac_softc * sc,bus_size_t offset,uint32_t value)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
bcmdmac_read(struct bcmdmac_softc * sc,bus_size_t offset)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
bcmdmac_match(struct device * parent,void * match,void * aux)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
bcmdmac_attach(struct device * parent,struct device * self,void * aux)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 = 32 - __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
bcmdmac_intr(void * arg)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 *
bcmdmac_alloc(enum bcmdmac_type type,int ipl,void (* cb)(uint32_t,uint32_t,void *),void * cbarg)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
bcmdmac_free(struct bcmdmac_channel * ch)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
bcmdmac_set_conblk_addr(struct bcmdmac_channel * ch,bus_addr_t addr)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
bcmdmac_transfer(struct bcmdmac_channel * ch)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
bcmdmac_halt(struct bcmdmac_channel * ch)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