/*
 * (INCA) ASC UART support
 */

#include <config.h>

#if defined(CONFIG_PURPLE) || defined(CONFIG_INCA_IP)

#ifdef CONFIG_PURPLE
#define	serial_init	asc_serial_init
#define	serial_putc	asc_serial_putc
#define	serial_puts	asc_serial_puts
#define	serial_getc	asc_serial_getc
#define	serial_tstc	asc_serial_tstc
#define	serial_setbrg	asc_serial_setbrg
#endif

#include <common.h>
#include <asm/inca-ip.h>
#include "asc_serial.h"

#ifdef CONFIG_PURPLE

#undef ASC_FIFO_PRESENT
#define TOUT_LOOP	100000

/* Set base address for second FPI interrupt control register bank */
#define SFPI_INTCON_BASEADDR	0xBF0F0000

/* Register offset from base address */
#define FBS_ISR		0x00000000	/* Interrupt status register */
#define FBS_IMR		0x00000008	/* Interrupt mask register */
#define FBS_IDIS	0x00000010	/* Interrupt disable register */

/* Interrupt status register bits */
#define FBS_ISR_AT	0x00000040	/* ASC transmit interrupt */
#define FBS_ISR_AR 	0x00000020	/* ASC receive interrupt */
#define FBS_ISR_AE	0x00000010	/* ASC error interrupt */
#define FBS_ISR_AB	0x00000008	/* ASC transmit buffer interrupt */
#define FBS_ISR_AS      0x00000004 	/* ASC start of autobaud detection interrupt */
#define FBS_ISR_AF	0x00000002	/* ASC end of autobaud detection interrupt */

#else

#define ASC_FIFO_PRESENT

#endif


#define SET_BIT(reg, mask)                  reg |= (mask)
#define CLEAR_BIT(reg, mask)                reg &= (~mask)
#define CLEAR_BITS(reg, mask)               CLEAR_BIT(reg, mask)
#define SET_BITS(reg, mask)                 SET_BIT(reg, mask)
#define SET_BITFIELD(reg, mask, off, val)   {reg &= (~mask); reg |= (val << off);}

extern uint incaip_get_fpiclk(void);

static int serial_setopt (void);

/* pointer to ASC register base address */
static volatile incaAsc_t *pAsc = (incaAsc_t *)INCA_IP_ASC;

/******************************************************************************
*
* serial_init - initialize a INCAASC channel
*
* This routine initializes the number of data bits, parity
* and set the selected baud rate. Interrupts are disabled.
* Set the modem control signals if the option is selected.
*
* RETURNS: N/A
*/

int serial_init (void)
{
#ifdef CONFIG_INCA_IP
    /* we have to set PMU.EN13 bit to enable an ASC device*/
    INCAASC_PMU_ENABLE(13);
#endif

    /* and we have to set CLC register*/
    CLEAR_BIT(pAsc->asc_clc, ASCCLC_DISS);
    SET_BITFIELD(pAsc->asc_clc, ASCCLC_RMCMASK, ASCCLC_RMCOFFSET, 0x0001);

    /* initialy we are in async mode */
    pAsc->asc_con = ASCCON_M_8ASYNC;

    /* select input port */
    pAsc->asc_pisel = (CONSOLE_TTY & 0x1);

#ifdef ASC_FIFO_PRESENT
    /* TXFIFO's filling level */
    SET_BITFIELD(pAsc->asc_txfcon, ASCTXFCON_TXFITLMASK,
		    ASCTXFCON_TXFITLOFF, INCAASC_TXFIFO_FL);
    /* enable TXFIFO */
    SET_BIT(pAsc->asc_txfcon, ASCTXFCON_TXFEN);

    /* RXFIFO's filling level */
    SET_BITFIELD(pAsc->asc_txfcon, ASCRXFCON_RXFITLMASK,
		    ASCRXFCON_RXFITLOFF, INCAASC_RXFIFO_FL);
    /* enable RXFIFO */
    SET_BIT(pAsc->asc_rxfcon, ASCRXFCON_RXFEN);
#endif

    /* enable error signals */
    SET_BIT(pAsc->asc_con, ASCCON_FEN);
    SET_BIT(pAsc->asc_con, ASCCON_OEN);

#ifdef CONFIG_INCA_IP
    /* acknowledge ASC interrupts */
    ASC_INTERRUPTS_CLEAR(INCAASC_IRQ_LINE_ALL);

    /* disable ASC interrupts */
    ASC_INTERRUPTS_DISABLE(INCAASC_IRQ_LINE_ALL);
#endif

#ifdef ASC_FIFO_PRESENT
    /* set FIFOs into the transparent mode */
    SET_BIT(pAsc->asc_txfcon, ASCTXFCON_TXTMEN);
    SET_BIT(pAsc->asc_rxfcon, ASCRXFCON_RXTMEN);
#endif

    /* set baud rate */
    serial_setbrg();

    /* set the options */
    serial_setopt();

    return 0;
}

void serial_setbrg (void)
{
    ulong      uiReloadValue, fdv;
    ulong      f_ASC;

#ifdef CONFIG_INCA_IP
    f_ASC = incaip_get_fpiclk();
#else
    f_ASC = ASC_CLOCK_RATE;
#endif

#ifndef INCAASC_USE_FDV
    fdv = 2;
    uiReloadValue = (f_ASC / (fdv * 16 * CONFIG_BAUDRATE)) - 1;
#else
    fdv = INCAASC_FDV_HIGH_BAUDRATE;
    uiReloadValue = (f_ASC / (8192 * CONFIG_BAUDRATE / fdv)) - 1;
#endif /* INCAASC_USE_FDV */

    if ( (uiReloadValue < 0) || (uiReloadValue > 8191) )
    {
#ifndef INCAASC_USE_FDV
	fdv = 3;
	uiReloadValue = (f_ASC / (fdv * 16 * CONFIG_BAUDRATE)) - 1;
#else
	fdv = INCAASC_FDV_LOW_BAUDRATE;
	uiReloadValue = (f_ASC / (8192 * CONFIG_BAUDRATE / fdv)) - 1;
#endif /* INCAASC_USE_FDV */

	if ( (uiReloadValue < 0) || (uiReloadValue > 8191) )
	{
	    return;    /* can't impossibly generate that baud rate */
	}
    }

    /* Disable Baud Rate Generator; BG should only be written when R=0 */
    CLEAR_BIT(pAsc->asc_con, ASCCON_R);

#ifndef INCAASC_USE_FDV
    /*
     * Disable Fractional Divider (FDE)
     * Divide clock by reload-value + constant (BRS)
     */
    /* FDE = 0 */
    CLEAR_BIT(pAsc->asc_con, ASCCON_FDE);

    if ( fdv == 2 )
	CLEAR_BIT(pAsc->asc_con, ASCCON_BRS);   /* BRS = 0 */
    else
	SET_BIT(pAsc->asc_con, ASCCON_BRS); /* BRS = 1 */

#else /* INCAASC_USE_FDV */

    /* Enable Fractional Divider */
    SET_BIT(pAsc->asc_con, ASCCON_FDE); /* FDE = 1 */

    /* Set fractional divider value */
    pAsc->asc_fdv = fdv & ASCFDV_VALUE_MASK;

#endif /* INCAASC_USE_FDV */

    /* Set reload value in BG */
    pAsc->asc_bg = uiReloadValue;

    /* Enable Baud Rate Generator */
    SET_BIT(pAsc->asc_con, ASCCON_R);           /* R = 1 */
}

/*******************************************************************************
*
* serial_setopt - set the serial options
*
* Set the channel operating mode to that specified. Following options
* are supported: CREAD, CSIZE, PARENB, and PARODD.
*
* Note, this routine disables the transmitter.  The calling routine
* may have to re-enable it.
*
* RETURNS:
* Returns 0 to indicate success, otherwise -1 is returned
*/

static int serial_setopt (void)
{
    ulong  con;

    switch ( ASC_OPTIONS & ASCOPT_CSIZE )
    {
    /* 7-bit-data */
    case ASCOPT_CS7:
	con = ASCCON_M_7ASYNCPAR;   /* 7-bit-data and parity bit */
	break;

    /* 8-bit-data */
    case ASCOPT_CS8:
	if ( ASC_OPTIONS & ASCOPT_PARENB )
	    con = ASCCON_M_8ASYNCPAR;   /* 8-bit-data and parity bit */
	else
	    con = ASCCON_M_8ASYNC;      /* 8-bit-data no parity */
	break;

    /*
     *  only 7 and 8-bit frames are supported
     *  if we don't use IOCTL extensions
     */
    default:
	return -1;
    }

    if ( ASC_OPTIONS & ASCOPT_STOPB )
	SET_BIT(con, ASCCON_STP);       /* 2 stop bits */
    else
	CLEAR_BIT(con, ASCCON_STP);     /* 1 stop bit */

    if ( ASC_OPTIONS & ASCOPT_PARENB )
	SET_BIT(con, ASCCON_PEN);           /* enable parity checking */
    else
	CLEAR_BIT(con, ASCCON_PEN);         /* disable parity checking */

    if ( ASC_OPTIONS & ASCOPT_PARODD )
	SET_BIT(con, ASCCON_ODD);       /* odd parity */
    else
	CLEAR_BIT(con, ASCCON_ODD);     /* even parity */

    if ( ASC_OPTIONS & ASCOPT_CREAD )
	SET_BIT(pAsc->asc_whbcon, ASCWHBCON_SETREN); /* Receiver enable */

    pAsc->asc_con |= con;

    return 0;
}

void serial_putc (const char c)
{
#ifdef ASC_FIFO_PRESENT
    uint txFl = 0;
#else
    uint timeout = 0;
#endif

    if (c == '\n') serial_putc ('\r');

#ifdef ASC_FIFO_PRESENT
    /* check do we have a free space in the TX FIFO */
    /* get current filling level */
    do
    {
	txFl = ( pAsc->asc_fstat & ASCFSTAT_TXFFLMASK ) >> ASCFSTAT_TXFFLOFF;
    }
    while ( txFl == INCAASC_TXFIFO_FULL );
#else

    while(!(*(volatile unsigned long*)(SFPI_INTCON_BASEADDR + FBS_ISR) &
			   FBS_ISR_AB))
    {
	    if (timeout++ > TOUT_LOOP)
	    {
		    break;
	    }
    }
#endif

    pAsc->asc_tbuf = c; /* write char to Transmit Buffer Register */

#ifndef ASC_FIFO_PRESENT
    *(volatile unsigned long*)(SFPI_INTCON_BASEADDR + FBS_ISR) = FBS_ISR_AB |
								 FBS_ISR_AT;
#endif

    /* check for errors */
    if ( pAsc->asc_con & ASCCON_OE )
    {
	SET_BIT(pAsc->asc_whbcon, ASCWHBCON_CLROE);
	return;
    }
}

void serial_puts (const char *s)
{
    while (*s)
    {
	serial_putc (*s++);
    }
}

int serial_getc (void)
{
    ulong symbol_mask;
    char c;

    while (!serial_tstc());

    symbol_mask =
	((ASC_OPTIONS & ASCOPT_CSIZE) == ASCOPT_CS7) ? (0x7f) : (0xff);

    c = (char)(pAsc->asc_rbuf & symbol_mask);

#ifndef ASC_FIFO_PRESENT
    *(volatile unsigned long*)(SFPI_INTCON_BASEADDR + FBS_ISR) = FBS_ISR_AR;
#endif

    return c;
}

int serial_tstc (void)
{
    int res = 1;

#ifdef ASC_FIFO_PRESENT
    if ( (pAsc->asc_fstat & ASCFSTAT_RXFFLMASK) == 0 )
    {
	res = 0;
    }
#else
    if (!(*(volatile unsigned long*)(SFPI_INTCON_BASEADDR + FBS_ISR) &
								FBS_ISR_AR))

    {
	res = 0;
    }
#endif
    else if ( pAsc->asc_con & ASCCON_FE )
    {
	SET_BIT(pAsc->asc_whbcon, ASCWHBCON_CLRFE);
	res = 0;
    }
    else if ( pAsc->asc_con & ASCCON_PE )
    {
	SET_BIT(pAsc->asc_whbcon, ASCWHBCON_CLRPE);
	res = 0;
    }
    else if ( pAsc->asc_con & ASCCON_OE )
    {
	SET_BIT(pAsc->asc_whbcon, ASCWHBCON_CLROE);
	res = 0;
    }

    return res;
}
#endif /* CONFIG_PURPLE || CONFIG_INCA_IP */