233 lines
4.2 KiB
C
233 lines
4.2 KiB
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;
|
|
};
|
|
|
|
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;
|
|
}
|