Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;

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:
                cachedwbinvse(dst, len);
                ti = Srcdreq | Destinc;
                cb->sourcead = DMAIO(src);
                cb->destad = DMAADDR(dst);
                break;
        case DmaM2D:
                cachedwbse(src, len);
                ti = Destdreq | Srcinc;
                cb->sourcead = DMAADDR(src);
                cb->destad = DMAIO(dst);
                break;
        case DmaM2M:
                cachedwbse(src, len);
                cachedwbinvse(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;
}