1 /* $NetBSD: bcm2835_dmac.c,v 1.9 2014/09/14 14:29:57 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 Jared D. McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include "opt_ddb.h" 30 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: bcm2835_dmac.c,v 1.9 2014/09/14 14:29:57 jmcneill Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/bus.h> 36 #include <sys/device.h> 37 #include <sys/intr.h> 38 #include <sys/systm.h> 39 #include <sys/kmem.h> 40 #include <sys/mutex.h> 41 42 #include <arm/broadcom/bcm_amba.h> 43 #include <arm/broadcom/bcm2835reg.h> 44 #include <arm/broadcom/bcm2835_intr.h> 45 46 #include <arm/broadcom/bcm2835_dmac.h> 47 48 #define BCM_DMAC_CHANNELMASK 0x00000fff 49 50 struct bcm_dmac_softc; 51 52 struct bcm_dmac_channel { 53 struct bcm_dmac_softc *ch_sc; 54 void *ch_ih; 55 uint8_t ch_index; 56 void (*ch_callback)(void *); 57 void *ch_callbackarg; 58 uint32_t ch_debug; 59 }; 60 61 #define DMAC_CHANNEL_TYPE(ch) \ 62 (((ch)->ch_debug & DMAC_DEBUG_LITE) ? \ 63 BCM_DMAC_TYPE_LITE : BCM_DMAC_TYPE_NORMAL) 64 #define DMAC_CHANNEL_USED(ch) \ 65 ((ch)->ch_callback != NULL) 66 67 struct bcm_dmac_softc { 68 device_t sc_dev; 69 bus_space_tag_t sc_iot; 70 bus_space_handle_t sc_ioh; 71 kmutex_t sc_lock; 72 struct bcm_dmac_channel *sc_channels; 73 int sc_nchannels; 74 uint32_t sc_channelmask; 75 }; 76 77 #define DMAC_READ(sc, reg) \ 78 bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) 79 #define DMAC_WRITE(sc, reg, val) \ 80 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 81 82 static int bcm_dmac_match(device_t, cfdata_t, void *); 83 static void bcm_dmac_attach(device_t, device_t, void *); 84 85 static int bcm_dmac_intr(void *); 86 87 #if defined(DDB) 88 void bcm_dmac_dump_regs(void); 89 #endif 90 91 CFATTACH_DECL_NEW(bcmdmac_amba, sizeof(struct bcm_dmac_softc), 92 bcm_dmac_match, bcm_dmac_attach, NULL, NULL); 93 94 static int 95 bcm_dmac_match(device_t parent, cfdata_t cf, void *aux) 96 { 97 struct amba_attach_args *aaa = aux; 98 99 if (strcmp(aaa->aaa_name, "bcmdmac") != 0) 100 return 0; 101 102 if (aaa->aaa_addr != BCM2835_DMA0_BASE) 103 return 0; 104 105 return 1; 106 } 107 108 static void 109 bcm_dmac_attach(device_t parent, device_t self, void *aux) 110 { 111 struct bcm_dmac_softc *sc = device_private(self); 112 const prop_dictionary_t cfg = device_properties(self); 113 struct bcm_dmac_channel *ch; 114 struct amba_attach_args *aaa = aux; 115 uint32_t val; 116 int index; 117 118 sc->sc_dev = self; 119 sc->sc_iot = aaa->aaa_iot; 120 121 if (bus_space_map(aaa->aaa_iot, aaa->aaa_addr, aaa->aaa_size, 0, 122 &sc->sc_ioh)) { 123 aprint_error(": unable to map device\n"); 124 return; 125 } 126 127 prop_dictionary_get_uint32(cfg, "chanmask", &sc->sc_channelmask); 128 sc->sc_channelmask &= BCM_DMAC_CHANNELMASK; 129 130 mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SCHED); 131 132 sc->sc_nchannels = 31 - __builtin_clz(sc->sc_channelmask); 133 sc->sc_channels = kmem_alloc( 134 sizeof(*sc->sc_channels) * sc->sc_nchannels, KM_SLEEP); 135 if (sc->sc_channels == NULL) { 136 aprint_error(": couldn't allocate channels\n"); 137 return; 138 } 139 140 aprint_normal(":"); 141 for (index = 0; index < sc->sc_nchannels; index++) { 142 ch = &sc->sc_channels[index]; 143 ch->ch_sc = sc; 144 ch->ch_index = index; 145 ch->ch_callback = NULL; 146 ch->ch_callbackarg = NULL; 147 ch->ch_ih = NULL; 148 if ((__BIT(index) & sc->sc_channelmask) == 0) 149 continue; 150 151 aprint_normal(" DMA%d", index); 152 153 ch->ch_debug = DMAC_READ(sc, DMAC_DEBUG(index)); 154 155 val = DMAC_READ(sc, DMAC_CS(index)); 156 val |= DMAC_CS_RESET; 157 DMAC_WRITE(sc, DMAC_CS(index), val); 158 } 159 aprint_normal("\n"); 160 aprint_naive("\n"); 161 } 162 163 static int 164 bcm_dmac_intr(void *priv) 165 { 166 struct bcm_dmac_channel *ch = priv; 167 struct bcm_dmac_softc *sc = ch->ch_sc; 168 uint32_t cs; 169 170 cs = DMAC_READ(sc, DMAC_CS(ch->ch_index)); 171 if (!(cs & DMAC_CS_INTMASK)) 172 return 0; 173 174 DMAC_WRITE(sc, DMAC_CS(ch->ch_index), cs); 175 176 if (ch->ch_callback) 177 ch->ch_callback(ch->ch_callbackarg); 178 179 return 1; 180 } 181 182 struct bcm_dmac_channel * 183 bcm_dmac_alloc(enum bcm_dmac_type type, int ipl, void (*cb)(void *), 184 void *cbarg) 185 { 186 struct bcm_dmac_softc *sc; 187 struct bcm_dmac_channel *ch = NULL; 188 device_t dev; 189 int index; 190 191 dev = device_find_by_driver_unit("bcmdmac", 0); 192 if (dev == NULL) 193 return NULL; 194 sc = device_private(dev); 195 196 mutex_enter(&sc->sc_lock); 197 for (index = 0; index < sc->sc_nchannels; index++) { 198 if ((sc->sc_channelmask & __BIT(index)) == 0) 199 continue; 200 if (DMAC_CHANNEL_TYPE(&sc->sc_channels[index]) != type) 201 continue; 202 if (DMAC_CHANNEL_USED(&sc->sc_channels[index])) 203 continue; 204 205 ch = &sc->sc_channels[index]; 206 ch->ch_callback = cb; 207 ch->ch_callbackarg = cbarg; 208 break; 209 } 210 mutex_exit(&sc->sc_lock); 211 212 if (ch == NULL) 213 return NULL; 214 215 KASSERT(ch->ch_ih == NULL); 216 ch->ch_ih = bcm2835_intr_establish(BCM2835_INT_DMA0 + ch->ch_index, 217 ipl, bcm_dmac_intr, ch); 218 if (ch->ch_ih == NULL) { 219 aprint_error_dev(sc->sc_dev, 220 "failed to establish interrupt for DMA%d\n", ch->ch_index); 221 ch->ch_callback = NULL; 222 ch->ch_callbackarg = NULL; 223 ch = NULL; 224 } 225 226 return ch; 227 } 228 229 void 230 bcm_dmac_free(struct bcm_dmac_channel *ch) 231 { 232 struct bcm_dmac_softc *sc = ch->ch_sc; 233 uint32_t val; 234 235 bcm_dmac_halt(ch); 236 237 val = DMAC_READ(sc, DMAC_CS(ch->ch_index)); 238 val |= DMAC_CS_RESET; 239 val |= DMAC_CS_ABORT; 240 val &= ~DMAC_CS_ACTIVE; 241 DMAC_WRITE(sc, DMAC_CS(ch->ch_index), val); 242 243 mutex_enter(&sc->sc_lock); 244 intr_disestablish(ch->ch_ih); 245 ch->ch_ih = NULL; 246 ch->ch_callback = NULL; 247 ch->ch_callbackarg = NULL; 248 mutex_exit(&sc->sc_lock); 249 } 250 251 void 252 bcm_dmac_set_conblk_addr(struct bcm_dmac_channel *ch, bus_addr_t addr) 253 { 254 struct bcm_dmac_softc *sc = ch->ch_sc; 255 256 DMAC_WRITE(sc, DMAC_CONBLK_AD(ch->ch_index), addr); 257 } 258 259 int 260 bcm_dmac_transfer(struct bcm_dmac_channel *ch) 261 { 262 struct bcm_dmac_softc *sc = ch->ch_sc; 263 uint32_t val; 264 265 val = DMAC_READ(sc, DMAC_CS(ch->ch_index)); 266 if (val & DMAC_CS_ACTIVE) 267 return EBUSY; 268 269 val |= DMAC_CS_ACTIVE; 270 DMAC_WRITE(sc, DMAC_CS(ch->ch_index), val); 271 272 return 0; 273 } 274 275 void 276 bcm_dmac_halt(struct bcm_dmac_channel *ch) 277 { 278 bcm_dmac_set_conblk_addr(ch, 0); 279 } 280 281 #if defined(DDB) 282 void 283 bcm_dmac_dump_regs(void) 284 { 285 struct bcm_dmac_softc *sc; 286 device_t dev; 287 int index; 288 289 dev = device_find_by_driver_unit("bcmdmac", 0); 290 if (dev == NULL) 291 return; 292 sc = device_private(dev); 293 294 for (index = 0; index < sc->sc_nchannels; index++) { 295 if ((sc->sc_channelmask & __BIT(index)) == 0) 296 continue; 297 printf("%d_CS: %08X\n", index, 298 DMAC_READ(sc, DMAC_CS(index))); 299 printf("%d_CONBLK_AD: %08X\n", index, 300 DMAC_READ(sc, DMAC_CONBLK_AD(index))); 301 printf("%d_DEBUG: %08X\n", index, 302 DMAC_READ(sc, DMAC_DEBUG(index))); 303 } 304 } 305 #endif 306