ref: cc4ac997a0da6ea60e23ffb5f8c9361a3eebef32
dir: /sys/src/9/bcm/dma.c/
/* * bcm2835 dma controller * * simplest to use only channels 0-6 * channels 7-14 have reduced functionality * channel 15 is at a weird address * channels 0 and 15 have an "external 128 bit 8 word read FIFO" * for memory to memory transfers * * Experiments show that only channels 2-5,11-12 work with mmc */ #include "u.h" #include "../port/lib.h" #include "../port/error.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #define DMAREGS (VIRTIO+0x7000) #define DBG if(Dbg) enum { Nchan = 7, /* number of dma channels */ Regsize = 0x100, /* size of regs for each chan */ Cbalign = 64, /* control block byte alignment (allow for 64-byte cache on bcm2836) */ Dbg = 0, /* registers for each dma controller */ Cs = 0x00>>2, Conblkad = 0x04>>2, Ti = 0x08>>2, Sourcead = 0x0c>>2, Destad = 0x10>>2, Txfrlen = 0x14>>2, Stride = 0x18>>2, Nextconbk = 0x1c>>2, Debug = 0x20>>2, /* collective registers */ Intstatus = 0xfe0>>2, Enable = 0xff0>>2, /* Cs */ Reset = 1<<31, Abort = 1<<30, Error = 1<<8, Waitwrite = 1<<6, Waitdreq = 1<<5, Paused = 1<<4, Dreq = 1<<3, Int = 1<<2, End = 1<<1, Active = 1<<0, /* Ti */ Permapshift= 16, Srcignore = 1<<11, Srcdreq = 1<<10, Srcwidth128 = 1<<9, Srcinc = 1<<8, Destignore = 1<<7, Destdreq = 1<<6, Destwidth128 = 1<<5, Destinc = 1<<4, Waitresp = 1<<3, Tdmode = 1<<1, Inten = 1<<0, /* Debug */ Lite = 1<<28, Clrerrors = 7<<0, }; typedef struct Ctlr Ctlr; typedef struct Cb Cb; struct Ctlr { u32int *regs; Cb *cb; Rendez r; int dmadone; void *flush; int len; }; struct Cb { u32int ti; u32int sourcead; u32int destad; u32int txfrlen; u32int stride; u32int nextconbk; u32int reserved[2]; }; static Ctlr dma[Nchan]; static u32int *dmaregs = (u32int*)DMAREGS; uintptr dmaaddr(void *va) { if(va == nil) return soc.busdram; return soc.busdram | (PADDR(va) - PHYSDRAM); } static uintptr dmaioaddr(void *va) { return soc.busio | ((uintptr)va - soc.virtio); } static void dump(char *msg, uchar *p, int n) { print("%s", msg); while(n-- > 0) print(" %2.2x", *p++); print("\n"); } static void dumpdregs(char *msg, u32int *r) { int i; print("%s: %#p =", msg, r); for(i = 0; i < 9; i++) print(" %8.8uX", r[i]); print("\n"); } static int dmadone(void *a) { return ((Ctlr*)a)->dmadone; } static void dmainterrupt(Ureg*, void *a) { Ctlr *ctlr; ctlr = a; ctlr->regs[Cs] = Int; ctlr->dmadone = 1; wakeup(&ctlr->r); } void dmastart(int chan, int dev, int dir, void *src, void *dst, int len) { Ctlr *ctlr; Cb *cb; int ti; ctlr = &dma[chan]; if(ctlr->regs == nil){ ctlr->regs = (u32int*)(DMAREGS + chan*Regsize); ctlr->cb = xspanalloc(sizeof(Cb), Cbalign, 0); assert(ctlr->cb != nil); dmaregs[Enable] |= 1<<chan; ctlr->regs[Cs] = Reset; while(ctlr->regs[Cs] & Reset) ; intrenable(IRQDMA(chan), dmainterrupt, ctlr, BUSUNKNOWN, "dma"); } ctlr->len = len; cb = ctlr->cb; ti = 0; switch(dir){ case DmaD2M: ctlr->flush = dst; dmaflush(1, dst, len); ti = Srcdreq | Destinc; cb->sourcead = dmaioaddr(src); cb->destad = dmaaddr(dst); break; case DmaM2D: ctlr->flush = nil; dmaflush(1, src, len); ti = Destdreq | Srcinc; cb->sourcead = dmaaddr(src); cb->destad = dmaioaddr(dst); break; case DmaM2M: ctlr->flush = dst; dmaflush(1, dst, len); dmaflush(1, src, len); ti = Srcinc | Destinc; cb->sourcead = dmaaddr(src); cb->destad = dmaaddr(dst); break; } cb->ti = ti | dev<<Permapshift | Inten; cb->txfrlen = len; cb->stride = 0; cb->nextconbk = 0; dmaflush(1, cb, sizeof(Cb)); ctlr->regs[Cs] = 0; microdelay(1); ctlr->regs[Conblkad] = dmaaddr(cb); DBG print("dma start: %ux %ux %ux %ux %ux %ux\n", cb->ti, cb->sourcead, cb->destad, cb->txfrlen, cb->stride, cb->nextconbk); DBG print("intstatus %ux\n", dmaregs[Intstatus]); dmaregs[Intstatus] = 0; ctlr->regs[Cs] = Int; microdelay(1); coherence(); DBG dumpdregs("before Active", ctlr->regs); ctlr->regs[Cs] = Active; DBG dumpdregs("after Active", ctlr->regs); } int dmawait(int chan) { Ctlr *ctlr; u32int *r; int s; ctlr = &dma[chan]; tsleep(&ctlr->r, dmadone, ctlr, 3000); ctlr->dmadone = 0; if(ctlr->flush != nil){ dmaflush(0, ctlr->flush, ctlr->len); ctlr->flush = nil; } r = ctlr->regs; DBG dumpdregs("after sleep", r); s = r[Cs]; if((s & (Active|End|Error)) != End){ print("dma chan %d %s Cs %ux Debug %ux\n", chan, (s&End)? "error" : "timeout", s, r[Debug]); r[Cs] = Reset; r[Debug] = Clrerrors; return -1; } r[Cs] = Int|End; return 0; } void dmaflush(int clean, void *p, ulong len) { uintptr s = (uintptr)p; uintptr e = (uintptr)p + len; if(clean){ s &= ~(BLOCKALIGN-1); e += BLOCKALIGN-1; e &= ~(BLOCKALIGN-1); cachedwbse((void*)s, e - s); return; } if(s & BLOCKALIGN-1){ s &= ~(BLOCKALIGN-1); cachedwbinvse((void*)s, BLOCKALIGN); s += BLOCKALIGN; } if(e & BLOCKALIGN-1){ e &= ~(BLOCKALIGN-1); if(e < s) return; cachedwbinvse((void*)e, BLOCKALIGN); } if(s < e) cachedinvse((void*)s, e - s); }