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