/*
* omap3530 system dma controller
*
* terminology: a block consist of frame(s), a frame consist of elements
* (uchar, ushort, or ulong sized).
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "../port/netif.h"

enum {
       Nirq    = 4,
       Baseirq = 12,

       Nchan   = 32,
};

/*
* has a sw reset bit
* dma req lines 1, 2, 6, 63 are available for `system expansion'
*/

typedef struct Regs Regs;
typedef struct Dchan Dchan;
struct Regs {
       uchar   _pad0[8];
       /* bitfield of intrs pending, by Dchan; write 1s to clear */
       ulong   irqsts[Nirq];
       ulong   irqen[Nirq];    /* bitfield of intrs enabled, by Dchan */
       ulong   syssts;         /* 1<<0 is Resetdone */
       ulong   syscfg;         /* 1<<1 is Softreset */
       uchar   _pad1[0x64 - 0x30];

       ulong   caps[5];        /* caps[1] not defined */
       ulong   gcr;            /* knobs */
       ulong   _pad2;

       struct Dchan {
               ulong   ccr;    /* chan ctrl: incr, etc. */
               ulong   clnkctrl; /* link ctrl */
               ulong   cicr;   /* intr ctrl */
               ulong   csr;    /* status */
               ulong   csdp;   /* src & dest params */
               ulong   cen;    /* element # */
               ulong   cfn;    /* frame # */
               ulong   cssa;   /* src start addr */
               ulong   cdsa;   /* dest start addr */
               ulong   csei;   /* src element index */
               ulong   csfi;   /* src frame index | pkt size */
               ulong   cdei;   /* dest element index */
               ulong   cdfi;   /* dest frame index | pkt size */
               ulong   csac;   /* src addr value (read-only?) */
               ulong   cdac;   /* dest addr value */
               ulong   ccen;   /* curr transferred element # (in frame) */
               ulong   ccfn;   /* curr transferred frame # (in xfer) */
               ulong   color;
               uchar   _pad3[24];
       } chan[Nchan];
};

enum {
       /* cicr/csr bits */
       Blocki  = 1 << 5,

       /* ccr bits */
       Enable  = 1 << 7,
};

typedef struct Xfer Xfer;
static struct Xfer {
       Rendez  *rend;
       int     *done;          /* flag to set on intr */
} xfer[Nirq];

int
isdmadone(int irq)
{
       Dchan *cp;
       Regs *regs = (Regs *)PHYSSDMA;

       cp = regs->chan + irq;
       return cp->csr & Blocki;
}

static void
dmaintr(Ureg *, void *a)
{
       int i = (int)a;                 /* dma request & chan # */
       Dchan *cp;
       Regs *regs = (Regs *)PHYSSDMA;

       assert(i >= 0 && i < Nirq);

       *xfer[i].done = 1;
       assert(xfer[i].rend != nil);
       wakeup(xfer[i].rend);

       cp = regs->chan + i;
       if(!(cp->csr & Blocki))
               iprint("dmaintr: req %d: Blocki not set; csr %#lux\n",
                       i, cp->csr);
       cp->csr |= cp->csr;                     /* extinguish intr source */
       coherence();
       regs->irqsts[i] = regs->irqsts[i];      /* extinguish intr source */
       coherence();
       regs->irqen[i] &= ~(1 << i);
       coherence();

       xfer[i].rend = nil;
       coherence();
}

void
zerowds(ulong *wdp, int cnt)
{
       while (cnt-- > 0)
               *wdp++ = 0;
}

static int
istestdmadone(void *arg)
{
       return *(int *)arg;
}

void
dmainit(void)
{
       int n;
       char name[16];
       Dchan *cp;
       Regs *regs = (Regs *)PHYSSDMA;

       if (probeaddr((uintptr)&regs->syssts) < 0)
               panic("dmainit: no syssts reg");
       regs->syssts = 0;
       coherence();
       regs->syscfg |= 1<<1;           /* Softreset */
       coherence();
       while(!(regs->syssts & (1<<0))) /* Resetdone? */
               ;

       for (n = 0; n < Nchan; n++) {
               cp = regs->chan + n;
               cp->ccr = 0;
               cp->clnkctrl = 0;
               cp->cicr = 0;
               cp->csr = 0;
               cp->csdp = 0;
               cp->cen = cp->cfn = 0;
               cp->cssa = cp->cdsa = 0;
               cp->csei = cp->csfi = 0;
               cp->cdei = cp->cdfi = 0;
//              cp->csac = cp->cdac = 0;                // ro
               cp->ccen = cp->ccfn = 0;
               cp->color = 0;
       }
       zerowds((void *)regs->irqsts, sizeof regs->irqsts / sizeof(ulong));
       zerowds((void *)regs->irqen,  sizeof regs->irqen / sizeof(ulong));
       coherence();

       regs->gcr = 65;                 /* burst size + 1 */
       coherence();

       for (n = 0; n < Nirq; n++) {
               snprint(name, sizeof name, "dma%d", n);
               intrenable(Baseirq + n, dmaintr, (void *)n, nil, name);
       }
}

enum {
       Testbyte        = 0252,
       Testsize        = 256,
       Scratch         = MB,
};

/*
* try to confirm sane operation
*/
void
dmatest(void)
{
       int n, done;
       uchar *bp;
       static ulong pat = 0x87654321;
       static Rendez trendez;

       if (up == nil)
               panic("dmatest: up not set yet");
       bp = (uchar *)KADDR(PHYSDRAM + 128*MB);
       memset(bp, Testbyte, Scratch);
       done = 0;
       dmastart((void *)PADDR(bp), Postincr, (void *)PADDR(&pat), Const,
               Testsize, &trendez, &done);
       sleep(&trendez, istestdmadone, &done);
       cachedinvse(bp, Scratch);

       if (((ulong *)bp)[0] != pat)
               panic("dmainit: copied incorrect data %#lux != %#lux",
                       ((ulong *)bp)[0], pat);
       for (n = Testsize; n < Scratch && bp[n] != Testbyte; n++)
               ;
       if (n >= Scratch)
               panic("dmainit: ran wild over memory, clobbered ≥%,d bytes", n);
       if (bp[n] == Testbyte && n != Testsize)
               iprint("dma: %d-byte dma stopped after %d bytes!\n",
                       Testsize, n);
}

/* addresses are physical */
int
dmastart(void *to, int tmode, void *from, int fmode, uint len, Rendez *rend,
       int *done)
{
       int irq, chan;
       uint ruplen;
       Dchan *cp;
       Regs *regs = (Regs *)PHYSSDMA;
       static Lock alloclck;

       /* allocate free irq (and chan) */
       ilock(&alloclck);
       for (irq = 0; irq < Nirq && xfer[irq].rend != nil; irq++)
               ;
       if (irq >= Nirq)
               panic("dmastart: no available irqs; too many concurrent dmas");
       chan = irq;
       xfer[irq].rend = rend;                  /* for wakeup at intr time */
       xfer[irq].done = done;
       *done = 0;
       iunlock(&alloclck);

       ruplen = ROUND(len, sizeof(ulong));
       assert(to != from);

       cp = regs->chan + chan;
       cp->ccr &= ~Enable;                     /* paranoia */
       cp->cicr = 0;
       regs->irqen[irq] &= ~(1 << chan);
       coherence();

       cp->csdp = 2;                           /* 2 = log2(sizeof(ulong)) */
       cp->cssa = (uintptr)from;
       cp->cdsa = (uintptr)to;
       cp->ccr = tmode << 14 | fmode << 12;
       cp->csei = cp->csfi = cp->cdei = cp->cdfi = 1;
       cp->cen = ruplen / sizeof(ulong);       /* ulongs / frame */
       cp->cfn = 1;                            /* 1 frame / xfer */
       cp->cicr = Blocki;                      /* intr at end of block */

       regs->irqen[irq] |= 1 << chan;
       coherence();

       cp->ccr |= Enable;                      /* fire! */
       coherence();

       return irq;
}