#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"
#include "../port/etherif.h"

#include "msaturn.h"

enum{
       Etcr = Saturn + 0x0c00,
       Etsr = Saturn + 0x0c02,
       Ercr = Saturn + 0x0c04,
       Ersr = Saturn + 0x0c06,
       Eisr = Saturn + 0x0d04,
       Eimr = Saturn + 0x0d06,
       Emacaddr0 = Saturn + 0x0e02,
       Miicr = Saturn + 0x0f02,
       Miiwdr = Saturn + 0x0f04,
       Miirdr = Saturn + 0x0f06,

       Ethermem = 0xf2c00000,
       Etherfsize = 0x2000,
       Nrx = 14,
       Ntx = 2,                // Nrx + Ntx must be 16

       Ersr_rxfpmask = 0xf,
       Ersr_rxevent = RBIT(0, ushort),
       Etcr_txfpmask = 0xf,
       Ercr_rxenab = RBIT(0, ushort),
       Ercr_auienab = RBIT(2, ushort),
       Etcr_txstart = RBIT(1, ushort),
       Etcr_retries = 0xf<<8,
       Ei_txecall = RBIT(0, ushort),
       Ei_txretry = RBIT(2, ushort),
       Ei_txdefer = RBIT(3, ushort),
       Ei_txcrs = RBIT(4, ushort),
       Ei_txdone = RBIT(5, ushort),
       Ei_rxcrcerr = RBIT(8, ushort),
       Ei_rxdrib = RBIT(9, ushort),
       Ei_rxdone = RBIT(10, ushort),
       Ei_rxshort = RBIT(11, ushort),
       Ei_rxlong = RBIT(12, ushort),

       Miicr_regshift = 6,
       Miicr_read = RBIT(10, ushort),
       Miicr_preambledis = RBIT(12, ushort),
       Miicr_accack = RBIT(14, ushort),
       Miicr_accbsy = RBIT(15, ushort),
};

typedef struct {
       Lock;
       int             txbusy;
       int             txempty;
       int             txfull;
       int             ntx;                    /* number of entries in transmit ring */
       int             rxlast;

       int             active;
       ulong   interrupts;     /* statistics */
       ulong   overflows;
} Ctlr;

static ushort*etcr=(ushort*)Etcr;
static ushort*etsr=(ushort*)Etsr;
static ushort*ercr=(ushort*)Ercr;
static ushort*ersr=(ushort*)Ersr;
static ushort*eimr=(ushort*)Eimr;
static ushort*eisr=(ushort*)Eisr;
static ushort*miicr=(ushort*)Miicr;
static ushort*miirdr=(ushort*)Miirdr;

static void
txfill(Ether*ether, Ctlr*ctlr)
{
       int len;
       Block *b;
       ushort*dst;

       while(ctlr->ntx<Ntx){
               if((b=qget(ether->oq)) == nil)
                       break;

               len = BLEN(b);
               dst = (ushort*)(Ethermem+(ctlr->txempty+Nrx)*Etherfsize);
               *dst = len;
               memmove(&dst[1], b->rp, len);
               ctlr->ntx++;
               ctlr->txempty++;
               if(ctlr->txempty==Ntx)
                       ctlr->txempty = 0;
               freeb(b);
       }
}

static void
txrestart(Ctlr*ctlr)
{
       if(ctlr->ntx==0 || ctlr->txbusy)
               return;
       ctlr->txbusy = 1;
       *etcr = Etcr_txstart|Etcr_retries|(ctlr->txfull+Nrx);
}

static void interrupt(Ureg*, void*);

static void
transmit(Ether*ether)
{
       Ctlr *ctlr;

       ctlr = ether->ctlr;
       ilock(ctlr);
       txfill(ether, ctlr);
       txrestart(ctlr);
       iunlock(ctlr);

}

static void
interrupt(Ureg*, void*arg)
{
       Ctlr*ctlr;
       Ether*ether = arg;
       Etherpkt*pkt;
       ushort ie;
       int rx, len;
       Block *b;

       ctlr = ether->ctlr;
       if(!ctlr->active)
               return; /* not ours */
       ctlr->interrupts++;

       ilock(ctlr);
       ie = *eisr;
       *eisr = ie;
       intack();

       if(ie==0)
               iprint("interrupt: no interrupt source?\n");

       if(ie&Ei_txdone){
               if((*etcr&Etcr_txstart)==0){
                       if(ctlr->txbusy){
                               ctlr->txbusy = 0;
                               ctlr->ntx--;
                               ctlr->txfull++;
                               if(ctlr->txfull==Ntx)
                                       ctlr->txfull = 0;
                       }
                       txrestart(ctlr);
                       txfill(ether, ctlr);
                       txrestart(ctlr);
               }
               else
                       iprint("interrupt: bogus tx interrupt\n");
               ie &= ~Ei_txdone;
       }

       if(ie&Ei_rxdone){
               rx=*ersr&Ersr_rxfpmask;
               while(ctlr->rxlast!=rx){

                       ctlr->rxlast++;
                       if(ctlr->rxlast >= Nrx)
                               ctlr->rxlast = 0;

                       pkt = (Etherpkt*)(Ethermem+ctlr->rxlast*Etherfsize);
                       len = *(ushort*)pkt;
                       if((b = iallocb(len+sizeof(ushort))) != nil){
                               memmove(b->wp, pkt, len+sizeof(ushort));
                               b->rp += sizeof(ushort);
                               b->wp = b->rp + len;
                               etheriq(ether, b);
                       }else
                               ether->soverflows++;
                       rx=*ersr&Ersr_rxfpmask;
               }
               ie &= ~Ei_rxdone;
       }

       if(ie&Ei_txretry){
               iprint("ethersaturn: txretry!\n");
               ie &= ~Ei_txretry;
               ctlr->txbusy = 0;
               txrestart(ctlr);
       }

       ie &= ~Ei_txcrs;
       if(ie)
               iprint("interrupt: unhandled interrupts %.4uX\n", ie);
       iunlock(ctlr);
}

static int
reset(Ether* ether)
{
       Ctlr*ctlr;

       *ercr = 0;
       ctlr = malloc(sizeof(*ctlr));
       memset(ctlr, 0, sizeof(*ctlr));
       ctlr->active = 1;

       ether->ctlr = ctlr;
       ether->transmit = transmit;
       ether->irq = Vecether;
       ether->arg = ether;
       memmove(ether->ea, (ushort*)Emacaddr0, Eaddrlen);

       *ercr = Ercr_rxenab|Ercr_auienab|(Nrx-1);
       *eimr = Ei_rxdone|Ei_txretry|Ei_txdone;

       iprint("reset: ercr %.4uX\n", *ercr);

       intrenable(ether->irq, interrupt, ether, ether->name);

       return 0;
}

void
ethersaturnlink(void)
{
       addethercard("saturn", reset);
}