#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"

enum {
       CTRL = 0,
       MODE,
       IRQEN,
       IRQDIS,
       MASK,
       INTSTAT,
       RXFIFOLVL = 0x20/4,
       CHANSTAT = 0x2C/4,
       FIFO = 0x30/4,
};

enum {
       TXFULL = 1<<4,
       TXEMPTY = 1<<3,
       RXEMPTY = 1<<1,
       RXTRIG = 1<<0,
};

typedef struct Ctlr {
       Lock;
       ulong *r;
       int irq, iena;
} Ctlr;

Uart* uartenable(Uart *);

extern PhysUart zynqphysuart;

static Ctlr zctlr[1] = {
       {
               .r = (void *) VMAP,
               .irq = UART1IRQ,
       }
};

static Uart zuart[1] = {
       {
               .regs = &zctlr[0],
               .name = "UART1",
               .freq = 25000000,
               .phys = &zynqphysuart,
               .console = 1,
               .baud = 115200,
       }
};

void
uartinit(void)
{
       consuart = zuart;
}

static Uart *
zuartpnp(void)
{
       return zuart;
}

static void
zuartkick(Uart *uart)
{
       Ctlr *ct;
       int i;

       if(uart->blocked)
               return;
       ct = uart->regs;
       for(i = 0; i < 128; i++){
               if((ct->r[CHANSTAT] & TXFULL) != 0)
                       break;
               if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
                       break;
               ct->r[FIFO] = *uart->op++;
       }
}

static void
zuartintr(Ureg *, void *arg)
{
       Uart *uart;
       Ctlr *ct;
       int c;
       ulong fl;

       uart = arg;
       ct = uart->regs;
       fl = ct->r[INTSTAT] & ct->r[MASK];
       ct->r[INTSTAT] = fl;
       if((fl & RXTRIG) != 0)
               while((ct->r[CHANSTAT] & RXEMPTY) == 0){
                       c = ct->r[FIFO];
                       uartrecv(uart, c);
               }
       if((fl & TXEMPTY) != 0)
               zuartkick(uart);
}

static void
zuartenable(Uart *uart, int ie)
{
       Ctlr *ctlr;

       ctlr = uart->regs;
       ilock(ctlr);
       while((ctlr->r[CHANSTAT] & TXEMPTY) == 0)
               ;
       ctlr->r[IRQDIS] = -1;
       ctlr->r[RXFIFOLVL] = 1;
       if(ie){
               if(!ctlr->iena){
                       intrenable(ctlr->irq, zuartintr, uart, LEVEL, uart->name);
                       ctlr->iena = 1;
               }
               ctlr->r[IRQEN] = RXTRIG | TXEMPTY;
       }
       iunlock(ctlr);
}

static int
zuartgetc(Uart *uart)
{
       Ctlr *c;

       c = uart->regs;
       while((c->r[CHANSTAT] & RXEMPTY) != 0)
               ;
       return c->r[FIFO];
}

static void
zuartputc(Uart *uart, int c)
{
       Ctlr *ct;

       ct = uart->regs;
       while((ct->r[CHANSTAT] & TXFULL) != 0)
               ;
       ct->r[FIFO] = c;
       return;
}

int
uartconsole(void)
{
       Uart *uart = zuart;

       if(up == nil)
               return -1;

       if(uartenable(uart) != nil){
               serialoq = uart->oq;
               uart->opens++;
               consuart = uart;
       }
       return 0;
}

int
zuartbits(Uart *uart, int n)
{
       Ctlr *ct;

       ct = uart->regs;
       ct->r[MODE] &= ~6;
       switch(n){
       case 8:
               return 0;
       case 7:
               ct->r[MODE] |= 4;
               return 0;
       case 6:
               ct->r[MODE] |= 6;
               return 0;
       default:
               return -1;
       }
}

int
zuartbaud(Uart *, int n)
{
       print("uart baud %d\n", n);
       return 0;
}

int
zuartparity(Uart *uart, int p)
{
       Ctlr *ct;

       ct = uart->regs;
       switch(p){
       case 'o':
               ct->r[MODE] = ct->r[MODE] & ~0x38 | 0x08;
               return 0;
       case 'e':
               ct->r[MODE] = ct->r[MODE] & ~0x38;
               return 0;
       case 'n':
               ct->r[MODE] = ct->r[MODE] & 0x38 | 0x20;
               return 0;
       default:
               return -1;
       }
}

void
zuartnop(Uart *, int)
{
}

int
zuartnope(Uart *, int)
{
       return -1;
}


PhysUart zynqphysuart = {
       .pnp = zuartpnp,
       .enable = zuartenable,
       .kick = zuartkick,
       .getc = zuartgetc,
       .putc = zuartputc,
       .bits = zuartbits,
       .baud = zuartbaud,
       .parity = zuartparity,

       .stop = zuartnope,
       .rts = zuartnop,
       .dtr = zuartnop,
       .dobreak = zuartnop,
       .fifo = zuartnop,
       .power = zuartnop,
       .modemctl = zuartnop,
};