/*
* 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 = 32, /* control block byte alignment */
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;
};
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)
{
return soc.busdram | (PTR2UINT(va) & ~KSEGM);
}
static uintptr
dmaioaddr(void *va)
{
return soc.busio | (PTR2UINT(va) & ~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, 0, "dma");
}
cb = ctlr->cb;
ti = 0;
switch(dir){
case DmaD2M:
cachedinvse(dst, len);
ti = Srcdreq | Destinc;
cb->sourcead = dmaioaddr(src);
cb->destad = dmaaddr(dst);
break;
case DmaM2D:
cachedwbse(src, len);
ti = Destdreq | Srcinc;
cb->sourcead = dmaaddr(src);
cb->destad = dmaioaddr(dst);
break;
case DmaM2M:
cachedwbse(src, len);
cachedinvse(dst, 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;
cachedwbse(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;
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;
}
|