/*
* 8250-like UART
*/

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

enum {                                  /* registers */
       Rbr             = 0,            /* Receiver Buffer (RO) */
       Thr             = 0,            /* Transmitter Holding (WO) */
       Ier             = 1,            /* Interrupt Enable */
       Iir             = 2,            /* Interrupt Identification (RO) */
       Fcr             = 2,            /* FIFO Control (WO) */
       Lcr             = 3,            /* Line Control */
       Mcr             = 4,            /* Modem Control */
       Lsr             = 5,            /* Line Status */
       Msr             = 6,            /* Modem Status */
       Scr             = 7,            /* Scratch Pad */
       Mdr             = 8,            /* Mode Def'n (omap rw) */
//      Usr             = 31,           /* Uart Status Register; missing in omap? */
       Dll             = 0,            /* Divisor Latch LSB */
       Dlm             = 1,            /* Divisor Latch MSB */
};

enum {                                  /* Usr */
       Busy            = 0x01,
};

enum {                                  /* Ier */
       Erda            = 0x01,         /* Enable Received Data Available */
       Ethre           = 0x02,         /* Enable Thr Empty */
       Erls            = 0x04,         /* Enable Receiver Line Status */
       Ems             = 0x08,         /* Enable Modem Status */
};

enum {                                  /* Iir */
       Ims             = 0x00,         /* Ms interrupt */
       Ip              = 0x01,         /* Interrupt Pending (not) */
       Ithre           = 0x02,         /* Thr Empty */
       Irda            = 0x04,         /* Received Data Available */
       Irls            = 0x06,         /* Receiver Line Status */
       Ictoi           = 0x0C,         /* Character Time-out Indication */
       IirMASK         = 0x3F,
       Ifena           = 0xC0,         /* FIFOs enabled */
};

enum {                                  /* Fcr */
       FIFOena         = 0x01,         /* FIFO enable */
       FIFOrclr        = 0x02,         /* clear Rx FIFO */
       FIFOtclr        = 0x04,         /* clear Tx FIFO */
//      FIFOdma         = 0x08,
       FIFO1           = 0x00,         /* Rx FIFO trigger level 1 byte */
       FIFO4           = 0x40,         /*      4 bytes */
       FIFO8           = 0x80,         /*      8 bytes */
       FIFO14          = 0xC0,         /*      14 bytes */
};

enum {                                  /* Lcr */
       Wls5            = 0x00,         /* Word Length Select 5 bits/byte */
       Wls6            = 0x01,         /*      6 bits/byte */
       Wls7            = 0x02,         /*      7 bits/byte */
       Wls8            = 0x03,         /*      8 bits/byte */
       WlsMASK         = 0x03,
       Stb             = 0x04,         /* 2 stop bits */
       Pen             = 0x08,         /* Parity Enable */
       Eps             = 0x10,         /* Even Parity Select */
       Stp             = 0x20,         /* Stick Parity */
       Brk             = 0x40,         /* Break */
       Dlab            = 0x80,         /* Divisor Latch Access Bit */
};

enum {                                  /* Mcr */
       Dtr             = 0x01,         /* Data Terminal Ready */
       Rts             = 0x02,         /* Ready To Send */
       Out1            = 0x04,         /* no longer in use */
//      Ie              = 0x08,         /* IRQ Enable (cd_sts_ch on omap) */
       Dm              = 0x10,         /* Diagnostic Mode loopback */
};

enum {                                  /* Lsr */
       Dr              = 0x01,         /* Data Ready */
       Oe              = 0x02,         /* Overrun Error */
       Pe              = 0x04,         /* Parity Error */
       Fe              = 0x08,         /* Framing Error */
       Bi              = 0x10,         /* Break Interrupt */
       Thre            = 0x20,         /* Thr Empty */
       Temt            = 0x40,         /* Transmitter Empty */
       FIFOerr         = 0x80,         /* error in receiver FIFO */
};

enum {                                  /* Msr */
       Dcts            = 0x01,         /* Delta Cts */
       Ddsr            = 0x02,         /* Delta Dsr */
       Teri            = 0x04,         /* Trailing Edge of Ri */
       Ddcd            = 0x08,         /* Delta Dcd */
       Cts             = 0x10,         /* Clear To Send */
       Dsr             = 0x20,         /* Data Set Ready */
       Ri              = 0x40,         /* Ring Indicator */
       Dcd             = 0x80,         /* Carrier Detect */
};

enum {                                  /* Mdr */
       Modemask        = 7,
       Modeuart        = 0,
};


typedef struct Ctlr {
       u32int* io;
       int     irq;
       int     tbdf;
       int     iena;
       int     poll;

       uchar   sticky[Scr+1];

       Lock;
       int     hasfifo;
       int     checkfifo;
       int     fena;
} Ctlr;

extern PhysUart i8250physuart;

static Ctlr i8250ctlr[] = {
{       .io     = (u32int*)PHYSCONS,
       .irq    = Uartirq,
       .tbdf   = -1,
       .poll   = 0, },
};

static Uart i8250uart[] = {
{       .regs   = &i8250ctlr[0], /* not [2] */
       .name   = "COM3",
       .freq   = 3686000,      /* Not used, we use the global i8250freq */
       .phys   = &i8250physuart,
       .console= 1,
       .next   = nil, },
};

#define csr8r(c, r)     ((c)->io[r])
#define csr8w(c, r, v)  ((c)->io[r] = (c)->sticky[r] | (v), coherence())
#define csr8o(c, r, v)  ((c)->io[r] = (v), coherence())

static long
i8250status(Uart* uart, void* buf, long n, long offset)
{
       char *p;
       Ctlr *ctlr;
       uchar ier, lcr, mcr, msr;

       ctlr = uart->regs;
       p = malloc(READSTR);
       mcr = ctlr->sticky[Mcr];
       msr = csr8r(ctlr, Msr);
       ier = ctlr->sticky[Ier];
       lcr = ctlr->sticky[Lcr];
       snprint(p, READSTR,
               "b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n"
               "dev(%d) type(%d) framing(%d) overruns(%d) "
               "berr(%d) serr(%d)%s%s%s%s\n",

               uart->baud,
               uart->hup_dcd,
               (msr & Dsr) != 0,
               uart->hup_dsr,
               (lcr & WlsMASK) + 5,
               (ier & Ems) != 0,
               (lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n',
               (mcr & Rts) != 0,
               (lcr & Stb) ? 2: 1,
               ctlr->fena,

               uart->dev,
               uart->type,
               uart->ferr,
               uart->oerr,
               uart->berr,
               uart->serr,
               (msr & Cts) ? " cts": "",
               (msr & Dsr) ? " dsr": "",
               (msr & Dcd) ? " dcd": "",
               (msr & Ri) ? " ring": ""
       );
       n = readstr(offset, buf, n, p);
       free(p);

       return n;
}

static void
i8250fifo(Uart* uart, int level)
{
       Ctlr *ctlr;

       ctlr = uart->regs;
       if(ctlr->hasfifo == 0)
               return;

       /*
        * Changing the FIFOena bit in Fcr flushes data
        * from both receive and transmit FIFOs; there's
        * no easy way to guarantee not losing data on
        * the receive side, but it's possible to wait until
        * the transmitter is really empty.
        */
       ilock(ctlr);
       while(!(csr8r(ctlr, Lsr) & Temt))
               ;

       /*
        * Set the trigger level, default is the max.
        * value.
        * Some UARTs require FIFOena to be set before
        * other bits can take effect, so set it twice.
        */
       ctlr->fena = level;
       switch(level){
       case 0:
               break;
       case 1:
               level = FIFO1|FIFOena;
               break;
       case 4:
               level = FIFO4|FIFOena;
               break;
       case 8:
               level = FIFO8|FIFOena;
               break;
       default:
               level = FIFO14|FIFOena;
               break;
       }
       csr8w(ctlr, Fcr, level);
       csr8w(ctlr, Fcr, level);
       iunlock(ctlr);
}

static void
i8250dtr(Uart* uart, int on)
{
       Ctlr *ctlr;

       /*
        * Toggle DTR.
        */
       ctlr = uart->regs;
       if(on)
               ctlr->sticky[Mcr] |= Dtr;
       else
               ctlr->sticky[Mcr] &= ~Dtr;
       csr8w(ctlr, Mcr, 0);
}

static void
i8250rts(Uart* uart, int on)
{
       Ctlr *ctlr;

       /*
        * Toggle RTS.
        */
       ctlr = uart->regs;
       if(on)
               ctlr->sticky[Mcr] |= Rts;
       else
               ctlr->sticky[Mcr] &= ~Rts;
       csr8w(ctlr, Mcr, 0);
}

static void
i8250modemctl(Uart* uart, int on)
{
       Ctlr *ctlr;

       ctlr = uart->regs;
       ilock(&uart->tlock);
       if(on){
               ctlr->sticky[Ier] |= Ems;
               csr8w(ctlr, Ier, 0);
               uart->modem = 1;
               uart->cts = csr8r(ctlr, Msr) & Cts;
       }
       else{
               ctlr->sticky[Ier] &= ~Ems;
               csr8w(ctlr, Ier, 0);
               uart->modem = 0;
               uart->cts = 1;
       }
       iunlock(&uart->tlock);

       /* modem needs fifo */
       (*uart->phys->fifo)(uart, on);
}

static int
i8250parity(Uart* uart, int parity)
{
       int lcr;
       Ctlr *ctlr;

       ctlr = uart->regs;
       lcr = ctlr->sticky[Lcr] & ~(Eps|Pen);

       switch(parity){
       case 'e':
               lcr |= Eps|Pen;
               break;
       case 'o':
               lcr |= Pen;
               break;
       case 'n':
               break;
       default:
               return -1;
       }
       ctlr->sticky[Lcr] = lcr;
       csr8w(ctlr, Lcr, 0);

       uart->parity = parity;

       return 0;
}

static int
i8250stop(Uart* uart, int stop)
{
       int lcr;
       Ctlr *ctlr;

       ctlr = uart->regs;
       lcr = ctlr->sticky[Lcr] & ~Stb;

       switch(stop){
       case 1:
               break;
       case 2:
               lcr |= Stb;
               break;
       default:
               return -1;
       }
       ctlr->sticky[Lcr] = lcr;
       csr8w(ctlr, Lcr, 0);

       uart->stop = stop;

       return 0;
}

static int
i8250bits(Uart* uart, int bits)
{
       int lcr;
       Ctlr *ctlr;

       ctlr = uart->regs;
       lcr = ctlr->sticky[Lcr] & ~WlsMASK;

       switch(bits){
       case 5:
               lcr |= Wls5;
               break;
       case 6:
               lcr |= Wls6;
               break;
       case 7:
               lcr |= Wls7;
               break;
       case 8:
               lcr |= Wls8;
               break;
       default:
               return -1;
       }
       ctlr->sticky[Lcr] = lcr;
       csr8w(ctlr, Lcr, 0);

       uart->bits = bits;

       return 0;
}

static int
i8250baud(Uart* uart, int baud)
{
#ifdef notdef                           /* don't change the speed */
       ulong bgc;
       Ctlr *ctlr;
       extern int i8250freq;   /* In the config file */

       /*
        * Set the Baud rate by calculating and setting the Baud rate
        * Generator Constant. This will work with fairly non-standard
        * Baud rates.
        */
       if(i8250freq == 0 || baud <= 0)
               return -1;
       bgc = (i8250freq+8*baud-1)/(16*baud);

       ctlr = uart->regs;
       while(csr8r(ctlr, Usr) & Busy)
               delay(1);
       csr8w(ctlr, Lcr, Dlab);         /* begin kludge */
       csr8o(ctlr, Dlm, bgc>>8);
       csr8o(ctlr, Dll, bgc);
       csr8w(ctlr, Lcr, 0);
#endif
       uart->baud = baud;
       return 0;
}

static void
i8250break(Uart* uart, int ms)
{
       Ctlr *ctlr;

       if (up == nil)
               panic("i8250break: nil up");
       /*
        * Send a break.
        */
       if(ms <= 0)
               ms = 200;

       ctlr = uart->regs;
       csr8w(ctlr, Lcr, Brk);
       tsleep(&up->sleep, return0, 0, ms);
       csr8w(ctlr, Lcr, 0);
}

static void
emptyoutstage(Uart *uart, int n)
{
       _uartputs((char *)uart->op, n);
       uart->op = uart->oe = uart->ostage;
}

static void
i8250kick(Uart* uart)
{
       int i;
       Ctlr *ctlr;

       if(/* uart->cts == 0 || */ uart->blocked)
               return;

       if(!normalprint) {                      /* early */
               if (uart->op < uart->oe)
                       emptyoutstage(uart, uart->oe - uart->op);
               while ((i = uartstageoutput(uart)) > 0)
                       emptyoutstage(uart, i);
               return;
       }

       /* nothing more to send? then disable xmit intr */
       ctlr = uart->regs;
       if (uart->op >= uart->oe && qlen(uart->oq) == 0 &&
           csr8r(ctlr, Lsr) & Temt) {
               ctlr->sticky[Ier] &= ~Ethre;
               csr8w(ctlr, Ier, 0);
               return;
       }

       /*
        *  128 here is an arbitrary limit to make sure
        *  we don't stay in this loop too long.  If the
        *  chip's output queue is longer than 128, too
        *  bad -- presotto
        */
       for(i = 0; i < 128; i++){
               if(!(csr8r(ctlr, Lsr) & Thre))
                       break;
               if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
                       break;
               csr8o(ctlr, Thr, *uart->op++);          /* start tx */
               ctlr->sticky[Ier] |= Ethre;
               csr8w(ctlr, Ier, 0);                    /* intr when done */
       }
}

void
serialkick(void)
{
       uartkick(&i8250uart[CONSOLE]);
}

static void
i8250interrupt(Ureg*, void* arg)
{
       Ctlr *ctlr;
       Uart *uart;
       int iir, lsr, old, r;

       uart = arg;
       ctlr = uart->regs;
       for(iir = csr8r(ctlr, Iir); !(iir & Ip); iir = csr8r(ctlr, Iir)){
               switch(iir & IirMASK){
               case Ims:               /* Ms interrupt */
                       r = csr8r(ctlr, Msr);
                       if(r & Dcts){
                               ilock(&uart->tlock);
                               old = uart->cts;
                               uart->cts = r & Cts;
                               if(old == 0 && uart->cts)
                                       uart->ctsbackoff = 2;
                               iunlock(&uart->tlock);
                       }
                       if(r & Ddsr){
                               old = r & Dsr;
                               if(uart->hup_dsr && uart->dsr && !old)
                                       uart->dohup = 1;
                               uart->dsr = old;
                       }
                       if(r & Ddcd){
                               old = r & Dcd;
                               if(uart->hup_dcd && uart->dcd && !old)
                                       uart->dohup = 1;
                               uart->dcd = old;
                       }
                       break;
               case Ithre:             /* Thr Empty */
                       uartkick(uart);
                       break;
               case Irda:              /* Received Data Available */
               case Irls:              /* Receiver Line Status */
               case Ictoi:             /* Character Time-out Indication */
                       /*
                        * Consume any received data.
                        * If the received byte came in with a break,
                        * parity or framing error, throw it away;
                        * overrun is an indication that something has
                        * already been tossed.
                        */
                       while((lsr = csr8r(ctlr, Lsr)) & Dr){
                               if(lsr & (FIFOerr|Oe))
                                       uart->oerr++;
                               if(lsr & Pe)
                                       uart->perr++;
                               if(lsr & Fe)
                                       uart->ferr++;
                               r = csr8r(ctlr, Rbr);
                               if(!(lsr & (Bi|Fe|Pe)))
                                       uartrecv(uart, r);
                       }
                       break;

               default:
                       iprint("weird uart interrupt type %#2.2uX\n", iir);
                       break;
               }
       }
}

static void
i8250disable(Uart* uart)
{
       Ctlr *ctlr;

       /*
        * Turn off DTR and RTS, disable interrupts and fifos.
        */
       (*uart->phys->dtr)(uart, 0);
       (*uart->phys->rts)(uart, 0);
       (*uart->phys->fifo)(uart, 0);

       ctlr = uart->regs;
       ctlr->sticky[Ier] = 0;
       csr8w(ctlr, Ier, 0);

       if(ctlr->iena != 0){
               if(irqdisable(ctlr->irq, i8250interrupt, uart, uart->name) == 0)
                       ctlr->iena = 0;
       }
}

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

       if (up == nil)
               return;                         /* too soon */

       ctlr = uart->regs;

       /* omap only: set uart/irda/cir mode to uart */
       mode = csr8r(ctlr, Mdr);
       csr8o(ctlr, Mdr, (mode & ~Modemask) | Modeuart);

       ctlr->sticky[Lcr] = Wls8;               /* no parity */
       csr8w(ctlr, Lcr, 0);

       /*
        * Check if there is a FIFO.
        * Changing the FIFOena bit in Fcr flushes data
        * from both receive and transmit FIFOs; there's
        * no easy way to guarantee not losing data on
        * the receive side, but it's possible to wait until
        * the transmitter is really empty.
        * Also, reading the Iir outwith i8250interrupt()
        * can be dangerous, but this should only happen
        * once, before interrupts are enabled.
        */
       ilock(ctlr);
       if(!ctlr->checkfifo){
               /*
                * Wait until the transmitter is really empty.
                */
               while(!(csr8r(ctlr, Lsr) & Temt))
                       ;
               csr8w(ctlr, Fcr, FIFOena);
               if(csr8r(ctlr, Iir) & Ifena)
                       ctlr->hasfifo = 1;
               csr8w(ctlr, Fcr, 0);
               ctlr->checkfifo = 1;
       }
       iunlock(ctlr);

       /*
        * Enable interrupts and turn on DTR and RTS.
        * Be careful if this is called to set up a polled serial line
        * early on not to try to enable interrupts as interrupt-
        * -enabling mechanisms might not be set up yet.
        */
       if(ie){
               if(ctlr->iena == 0 && !ctlr->poll){
                       irqenable(ctlr->irq, i8250interrupt, uart, uart->name);
                       ctlr->iena = 1;
               }
               ctlr->sticky[Ier] = Erda;
//              ctlr->sticky[Mcr] |= Ie;                /* not on omap */
               ctlr->sticky[Mcr] = 0;
       }
       else{
               ctlr->sticky[Ier] = 0;
               ctlr->sticky[Mcr] = 0;
       }
       csr8w(ctlr, Ier, 0);
       csr8w(ctlr, Mcr, 0);

       (*uart->phys->dtr)(uart, 1);
       (*uart->phys->rts)(uart, 1);

       /*
        * During startup, the i8259 interrupt controller is reset.
        * This may result in a lost interrupt from the i8250 uart.
        * The i8250 thinks the interrupt is still outstanding and does not
        * generate any further interrupts. The workaround is to call the
        * interrupt handler to clear any pending interrupt events.
        * Note: this must be done after setting Ier.
        */
       if(ie)
               i8250interrupt(nil, uart);
}

static Uart*
i8250pnp(void)
{
       return i8250uart;
}

static int
i8250getc(Uart* uart)
{
       Ctlr *ctlr;

       ctlr = uart->regs;
       while(!(csr8r(ctlr, Lsr) & Dr))
               delay(1);
       return csr8r(ctlr, Rbr);
}

static void
i8250putc(Uart* uart, int c)
{
       int i;
       Ctlr *ctlr;

       if (!normalprint) {             /* too early; use brute force */
               int s = splhi();

               while (!(((ulong *)PHYSCONS)[Lsr] & Thre))
                       ;
               ((ulong *)PHYSCONS)[Thr] = c;
               coherence();
               splx(s);
               return;
       }

       ctlr = uart->regs;
       for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 128; i++)
               delay(1);
       csr8o(ctlr, Thr, (uchar)c);
       for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 128; i++)
               delay(1);
}

void
serialputc(int c)
{
       i8250putc(&i8250uart[CONSOLE], c);
}

void
serialputs(char* s, int n)
{
       _uartputs(s, n);
}

#ifdef notdef
static void
i8250poll(Uart* uart)
{
       Ctlr *ctlr;

       /*
        * If PhysUart has a non-nil .poll member, this
        * routine will be called from the uartclock timer.
        * If the Ctlr .poll member is non-zero, when the
        * Uart is enabled interrupts will not be enabled
        * and the result is polled input and output.
        * Not very useful here, but ports to new hardware
        * or simulators can use this to get serial I/O
        * without setting up the interrupt mechanism.
        */
       ctlr = uart->regs;
       if(ctlr->iena || !ctlr->poll)
               return;
       i8250interrupt(nil, uart);
}
#endif

PhysUart i8250physuart = {
       .name           = "i8250",
       .pnp            = i8250pnp,
       .enable         = i8250enable,
       .disable        = i8250disable,
       .kick           = i8250kick,
       .dobreak        = i8250break,
       .baud           = i8250baud,
       .bits           = i8250bits,
       .stop           = i8250stop,
       .parity         = i8250parity,
       .modemctl       = i8250modemctl,
       .rts            = i8250rts,
       .dtr            = i8250dtr,
       .status         = i8250status,
       .fifo           = i8250fifo,
       .getc           = i8250getc,
       .putc           = i8250putc,
//      .poll           = i8250poll,            /* only in 9k, not 9 */
};

static void
i8250dumpregs(Ctlr* ctlr)
{
       int dlm, dll;
       int _uartprint(char*, ...);

       csr8w(ctlr, Lcr, Dlab);
       dlm = csr8r(ctlr, Dlm);
       dll = csr8r(ctlr, Dll);
       csr8w(ctlr, Lcr, 0);

       _uartprint("dlm %#ux dll %#ux\n", dlm, dll);
}

Uart*   uartenable(Uart *p);

/* must call this from a process's context */
int
i8250console(void)
{
       Uart *uart = &i8250uart[CONSOLE];

       if (up == nil)
               return -1;                      /* too early */

       if(uartenable(uart) != nil /* && uart->console */){
               // iprint("i8250console: enabling console uart\n");
               serialoq = uart->oq;
               uart->opens++;
               consuart = uart;
       }
       uartctl(uart, "b115200 l8 pn r1 s1 i1");
       return 0;
}

void
_uartputs(char* s, int n)
{
       char *e;

       for(e = s+n; s < e; s++){
               if(*s == '\n')
                       i8250putc(&i8250uart[CONSOLE], '\r');
               i8250putc(&i8250uart[CONSOLE], *s);
       }
}

int
_uartprint(char* fmt, ...)
{
       int n;
       va_list arg;
       char buf[PRINTSIZE];

       va_start(arg, fmt);
       n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
       va_end(arg);
       _uartputs(buf, n);

       return n;
}