/*
 * (C) Copyright 2001
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * (C) Copyright 2001
 * James Dougherty (jfd@cs.stanford.edu)
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */
/*
 * COM1 NS16550 support for the MOUSSE Processor card.
 */
#include <config.h>
#include "ns16550.h"
#include "mousse.h"

#define NS16550_FIFO

#define UART ((void *) 0xffe08080) /* COM1 */
#define NS16550_CLOCK_SPEED (115200 * 16 * 10)

/*
 * Baud rate conversation table for 16550
 */
static const
ns16550_baud_rate_table speed_table[] = {
    { B50, (unsigned short) (NS16550_CLOCK_SPEED/(16*B50)) },
    { B75, (unsigned short) (NS16550_CLOCK_SPEED/(16*B75)) },
    { B110, (unsigned short) (NS16550_CLOCK_SPEED/(16*B110)) },
    { B150, (unsigned short) (NS16550_CLOCK_SPEED/(16*B150)) },
    { B300, (unsigned short) (NS16550_CLOCK_SPEED/(16*B300)) },
    { B600, (unsigned short) (NS16550_CLOCK_SPEED/(16*B600)) },
    { B1200, (unsigned short) (NS16550_CLOCK_SPEED/(16*B1200)) },
    { B1800, (unsigned short) (NS16550_CLOCK_SPEED/(16*B1800)) },
    { B2000, (unsigned short) (NS16550_CLOCK_SPEED/(16*B2000)) },
    { B2400, (unsigned short) (NS16550_CLOCK_SPEED/(16*B2400)) },
    { B3600, (unsigned short) (NS16550_CLOCK_SPEED/(16*B3600)) },
    { B4800, (unsigned short) (NS16550_CLOCK_SPEED/(16*B4800)) },
    { B7200, (unsigned short) (NS16550_CLOCK_SPEED/(16*B7200)) },
    { B9600, (unsigned short) (NS16550_CLOCK_SPEED/(16*B9600)) },
    { B14400, (unsigned short) (NS16550_CLOCK_SPEED/(16*B14400)) },
    { B19200, (unsigned short) (NS16550_CLOCK_SPEED/(16*B19200)) },
    { B28800, (unsigned short) (NS16550_CLOCK_SPEED/(16*B28800)) },
    { B38400, (unsigned short) (NS16550_CLOCK_SPEED/(16*B38400)) },
    { B56000, (unsigned short) (NS16550_CLOCK_SPEED/(16*B56000)) },
    { B57600, (unsigned short) (NS16550_CLOCK_SPEED/(16*B57600)) },
    { B115200, (unsigned short) (NS16550_CLOCK_SPEED/(16*B115200)) },
    { B128000, (unsigned short) (NS16550_CLOCK_SPEED/(16*B128000)) },
    { 0 ,0 },
};

static void set_speed (unsigned int code)
{
    unsigned char scratch_pad;
    volatile ns16550_uart *uart = UART;
 
    scratch_pad = uart->LCR;
    uart->LCR = LCR_DLAB | scratch_pad;
    uart->DLL  = (unsigned char) (code & 0xff);
    uart->DLM = (code >> 8);
    uart->LCR = scratch_pad;
 
    return;
}

static unsigned short get_speed (void)
{
    volatile ns16550_uart *uart = UART;
    unsigned int code; 
    code = uart->DLL;
    return (uart->DLL | (uart->DLM << 8));
}

/*
 * Given the baud rate, convert the baud rate to a value, that has
 * to be loaded into the DLL and DLM register. The caller, can set the
 * baud rate once the DLAB bit has been set high, by writing the value
 * returned into the DLL and DLM latches.
 *
 * Returns:
 * - a 0, if the baudrate is not one of the known baudrates.
 * - a positive number, which can be loaded into the Divisor latch register
 *   for setting the baudrate.
 */

static unsigned short
ns16550_baud2code (unsigned int baudrate)
{
    int count = 0;

    for (count = 0; speed_table[count].baudrate > 0; count++) {
        if (speed_table[count].baudrate == baudrate) {
            return (speed_table[count].value);
        }
    }
    return (0);  /* baudrate not found in table */
}

static unsigned short
ns16550_code2baud (unsigned int code)
{
    int count = 0;

    for (count = 0; speed_table[count].baudrate > 0; count++) {
        if (speed_table[count].value == code) {
            return (speed_table[count].baudrate);
        }
    }
    return (0);  /* baudrate not found in table */
}

/*
 *
 * This routine will initialize the UART to a known state of speed,stopbits
 * etc.,
 * 
 * Things to do in this routine :
 * -----------------------------
 *    
 *  2. Reset the TX and RX FIFO's.
 *  3. Set default speed.
 *  4. Set the data 8 bits / character.
 *  5. Set the parity to nothing.
 *  6. Set the number of stop bits to 2.
 *  7. Set the receive trigger level.
 *  8. Enable the Rx and Tx FIFO's.
 *  9. Raise the DTR.
 * 10. Set the interrupt for Rx, LSR and MSR. Transmit holding register empty
 *    is generated as and when necessary.
 * 
 * NOTE : The interrupt hadnler must be set in the calling routine for the 
 *        UART ports.
 *
 */
void
ns16550_init (int baud)
{
    volatile ns16550_uart *uart = UART;
    unsigned char scratch_pad;

    /* Disable all interrupts before setting the interrupt handler. */
    scratch_pad = 0;
    uart->IER = scratch_pad;

    /* Clear the FIFO's, set speed to 9600 baud */
    uart->FCR = (FCR_RX_CLR | FCR_TX_CLR);
    ns16550_setspeed(baud);    
    /*
     * Set the data bits, stop bits and parity. By Default 8 data bits/char,
     * 2 stop bits and no parity.
     */
    uart->LCR = ((LCR_8BITS | LCR_2_SB) & ~LCR_PARITY_ENABLE);

#ifdef NS16550_FIFO
    /*
     * For now set the trigger level to 14. This we might have to change
     * after some initial testing.
     */
    uart->FCR = (TRIGGER_ON_14 | FCR_ENABLE | FCR_DMA_MODE);
#endif

    /*
     * Raise the DTR & DTR in case of AUX/Console port.
     */
    uart->MCR = (MCR_DTR | MCR_RTS);
    scratch_pad = uart->IIR;
    scratch_pad = uart->MSR;
    
    scratch_pad = IER_ENABLE_RX_DATA |
                  IER_ENABLE_RX_LSR |
                  IER_ENABLE_MODEM_STAT;
    uart->IER = scratch_pad;
}

/*
 * Set the speed on the UART.  Return the new speed, or zero if one
 * cannot be set.  If zero is passed in, do not change the speed,
 * just return the current speed.
 */
int
ns16550_setspeed (int speed)
{
    int code;

    if (!speed) {
        return (ns16550_code2baud (get_speed ()));
    }

    /*
     * Translate speeds to codes.  Change baud rates if codes are good
     * and are different from what we were already running with:
     */
    code = ns16550_baud2code(speed);
    if (!code) { /* Unknown speed being set */
        return(0);
    }

    set_speed (code);

    return (speed);
}

/*
 * Read a byte from the console.
 */
int
ns16550_getc (void)
{
    unsigned char ch;
    char lsr_stat;
    volatile ns16550_uart *uart = UART;
 
    lsr_stat = uart->LSR;
    while ((lsr_stat & LSR_DATAREADY) == 0) {
        lsr_stat = uart->LSR;       /* Loop for a character */
    }
 
    ch = uart->RxBuf;   /* Get the character */
 
    return(ch);
}


int
ns16550_tstc(void)
{
  volatile ns16550_uart *uart = UART;
  return ((uart->LSR & LSR_DATAREADY) != 0);
}

/*
 *
 * Write a byte to the console.
 *
 */
unsigned char
ns16550_putc (unsigned char ch)
{
    volatile ns16550_uart *uart = UART;
    unsigned char lsr_stat;
 
    lsr_stat = uart->LSR;
    while (((lsr_stat & LSR_THRE) == 0) || ((lsr_stat & LSR_TEMT) == 0)) {
        lsr_stat = uart->LSR; /* Wait for transmitter ready */
    }
 
    uart->TxBuf = ch;   /* Store the character */
    return (ch);
}

/*
 * Return TRUE if a break has been recieved,  else return FALSE.
 *
 */
int
ns16550_check_char (void)
{
    volatile ns16550_uart *uart = UART;

    if (uart->LSR & LSR_DATAREADY) {
        return 1;
    }

    return 0;
}

/*
 * Return TRUE if a break has been recieved,  else return FALSE.
 *
 */
int
ns16550_check_break (void)
{
    volatile ns16550_uart *uart = UART;
    static int break_recvd = 0;
    volatile int i;

    if (uart->LSR & LSR_BREAK_INTR) {
        while (uart->LSR & LSR_DATAREADY) {
            i = uart->RxBuf;
        }
        break_recvd = 1;
        return 1;
    }

    if (break_recvd) {
        while (uart->LSR & LSR_DATAREADY) {
            i = uart->RxBuf;
        }
        break_recvd = 0;
    }

    return 0;
}
