//=============================================================================
//
//      cycduart.c - Cyclone UART Diagnostics
//
//=============================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
//
// eCos 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 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//=============================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   Scott Coulter, Jeff Frazier, Eric Breeden
// Contributors:
// Date:        2001-01-25
// Purpose:     
// Description: 
//
//####DESCRIPTIONEND####
//
//===========================================================================*/
#include <redboot.h>
#include <cyg/hal/hal_iop310.h>        // Hardware definitions
#include "cycduart.h"
#include "iq80310.h"
#include "test_menu.h"

#define DFLTLOOPERMS 500

//extern int printf(char*,...);
extern long hexIn(void);

int break_flag = 0;
unsigned long baud_rate = 0;

static int duart_already_init = FALSE;
static unsigned int uart_unit = DFLTPORT;
static int looperms;

static int calc_looperms(void);
void serial_init(void);
int inreg(int);
void outreg(int, unsigned char);
void serial_set(unsigned long);
void serial_loopback(int);
int serial_getc(void);
void serial_putc(int);
int serial_write(int, const unsigned char *, int);
int serial_read(int, unsigned char *, int, int);

extern int enable_external_interrupt (int int_id);
extern int disable_external_interrupt (int int_id);

extern int isr_connect(int int_num, void (*handler)(int), int arg);
extern int isr_disconnect(int int_num);


void duart_initialize(void)
{
    if (duart_already_init == FALSE) {
	/* Calculate the time constant for timeouts on serial_read. */
	if ((looperms = calc_looperms()) <= 0)
	    looperms = DFLTLOOPERMS;
    }

    /* Initialize the serial port and set the baud rate.
     * The baud rate is set here for sanity only; the autobaud
     * mechanism will change it as required when the host connects.
     */

    serial_init();
    serial_set(baud_rate?baud_rate:9600L);  

    duart_already_init = TRUE;
}						


/* Establish the loop/time constant to be used in the timing loop in
 * serial_read.  This is done by putting the UART into loopback mode.
 * After transmitting a character at 300 baud, we wait for the character
 * to be received.  Then divide the number of loops waited by the number
 * of milliseconds it takes to transmit 10 bits at 300 baud.
 * If your transmitter doesn't have a loopback mode, this value can be
 * calculated using a timer or some other facility, or an approximate
 * constant can be used.
 */

#define TESTBAUD 300L
#define NBYTES	 10
#define BITS_PER_BYTE 10	/* 1 start bit, 8 data bits, 1 stop bit */
#define TOTAL_MS (NBYTES*BITS_PER_BYTE*1000/TESTBAUD)

static int
calc_looperms(void)
{
    int	i, count, c;
    int	totalloops = 0;

    serial_init();
    serial_set(TESTBAUD);		/* set 300 baud */
    serial_loopback(1);		/* enable loop back mode */

    for (i=0; i < NBYTES; i++) {
	count = 1;
	serial_putc(0xaa);	/* xmit character */

	/*
	 * The timing loop is the same as the loops in serial_read.
	 * Any changes to the loops in serial_read should be reflected
	 * here.
	 */
	do {
	    c = serial_getc();
	} while (c < 0 && count++ > 0);

	totalloops += count;
    }

    serial_loopback(0);

    return(totalloops/TOTAL_MS);
}

/*
 * Initialize the device driver.
 */
void serial_init(void)
{
    /* If the serial port has been init'd before, there may be data in it  */
    /* Wait for the transmit FIFO to empty out before resetting anything   */  
    if (duart_already_init == TRUE)	{
	while (!(inreg(LSR) & LSR_TSRE));
    }

    /*
     * Configure active port, (uart_unit already set.)
     *
     * Set 8 bits, 1 stop bit, no parity.
     *
     * LCR<7>       0       divisor latch access bit
     * LCR<6>       0       break control (1=send break)
     * LCR<5>       0       stick parity (0=space, 1=mark)
     * LCR<4>       0       parity even (0=odd, 1=even)
     * LCR<3>       0       parity enable (1=enabled)
     * LCR<2>       0       # stop bits (0=1, 1=1.5)
     * LCR<1:0>     11      bits per character(00=5, 01=6, 10=7, 11=8)
     */

    outreg(LCR, 0x3);
   
    /* Assert DTR and RTS to prevent hardware handshake problems with
       serial terminals, etc. which can be connected to the serial port */
    outreg(MCR, MCR_DTR | MCR_RTS);

    outreg(FCR, FIFO_ENABLE);   /* Enable the FIFO                 */
    outreg(IER, INT_ENABLE);    /* Enable appropriate interrupts   */

}

/* Read a received character if one is available.  Return -1 otherwise. */
int serial_getc(void)
{
    if (inreg(LSR) & LSR_DR)
         return inreg(DataIn);

    return -1;
}

/* Transmit a character. */
void serial_putc(int c)
{
    while ((inreg(LSR) & LSR_THRE) == 0)
        ;
    outreg(DataOut, c);
}

/*
 * Set the baud rate.
 */
void serial_set(unsigned long baud)
{
    unsigned char sav_lcr;

    if(baud == 0)
	baud = 9600L;

    /*
     * Enable access to the divisor latches by setting DLAB in LCR.
     *
     */
    sav_lcr = inreg(LCR);
    outreg(LCR, LCR_DLAB | sav_lcr);

    /*
     * Set divisor latches.
     */
    outreg(BaudLsb, XTAL/(16*baud));
    outreg(BaudMsb, (XTAL/(16*baud)) >> 8);

    /*
     * Restore line control register
     */
    outreg(LCR, sav_lcr);
}

/*
 * This routine is used by calc_looperms to put the UART in loopback mode.
 */

void serial_loopback(int flag)
{
    if (flag)
	outreg(MCR, inreg(MCR) | MCR_LOOP);		/* enable loop back mode */
    else
	outreg(MCR, inreg(MCR) & ~MCR_LOOP);	/* disable loop back mode */
}

/*
 * These routines are used to read and write to the registers of the
 * 16552.  The delay routine guarantees the required recovery time between
 * cycles to the 16552.
 * DUART is the base address of the 16552.
 * DUART_DELTA gives the spacing between adjacent registers of the 16552.
 * For example, if A0,A1,A2 of the 16552 are connected to A2,A3,A4 of
 * the processor, DUART_DELTA must be 4.
 */

int inreg(int reg)
{
    int val;
    val = *((volatile unsigned char *)TERMINAL + (uart_unit * SCALE + reg));

    return val;
}

void outreg(int reg, unsigned char val)
{
    *((volatile unsigned char *)TERMINAL + (uart_unit * SCALE + reg)) = val;
}



/****************************************************************/
/* The following functions are all part of the Breeze UART test */
/****************************************************************/
  

static volatile int uart_int;


/************************************************/
/* BUS_TEST										*/
/* This routine performs a walking ones test	*/
/* on the given uart chip to test it's bus		*/
/* interface.  It writes to the scratchpad reg.	*/
/* then reads it back.  During					*/
/* this test all 8 data lines from the chip		*/
/* get written with both 1 and 0.				*/
/************************************************/
static int bus_test (void)
{
    unsigned char	out, in;
    int			bitpos;
    volatile int 	junk;

    junk = (int) &junk;	/* Don't let compiler optimize or "registerize" */

    outreg(SCR,0);		/* Clear scratchpad register */

    for (bitpos = 0; bitpos < 8; bitpos++) {
	out = 1 << bitpos;

	outreg(SCR,out);	/* Write data to scratchpad reg. */

	junk = ~0;			/* Force data lines high */

	in = inreg(SCR);	/* Read data */

	printf ("%02X ", in);

	/* make sure it's what we wrote */
	if (in != out)
	    return (0);
    }
    outreg(SCR,0);	/* Clear scratchpad register */
    printf ("\n");

    return (1);
}

/************************************************/
/* DISABLE_UART_INTS							*/
/* This routine disables uart interrupts		*/
/************************************************/
static void disable_uart_ints (void)
{
    outreg(IER,0);		/* Make the uart shut up */
}

/************************************************/
/* UART_ISR										*/
/* This routine responds to uart interrupts		*/
/* must return 1 to indicate that an interrupt  */
/* was serviced.								*/
/************************************************/
static void uart_isr (int unused)
{
    unsigned char iir;

    disable_uart_ints ();
    uart_int = 1;

    /* read the IIR to clear the interrupt */
    iir = inreg(IIR);

    return ;
}

/************************************************/
/* INIT_UART									*/
/* This routine initializes the 16550 interrupt */
/* and uart registers and initializes the uart  */
/* count.										*/
/************************************************/
static void init_uart (void)
{
    outreg(IER,0x02);		/* Enable Tx Empty interrupt -
				   should generate an interrupt since Tx is
				   empty to begin with */
}


/****************************************/
/* UART DIAGNOSTIC TEST					*/
/****************************************/
void uart_test (MENU_ARG arg)
{
    volatile int loop;
    int	looplim;
    int int_id;
    int i, baud;

    /*11/01/00 */
    char info[] = {"Move Console Cable back to Connector J9 and hit <CR> to exit test"};
    int index;

    looplim = 400000;

    /* perform tests on both UARTs */
    for (uart_unit = 0; uart_unit < 2; uart_unit++)	{

	if (uart_unit == 0)
	    int_id = UART1_INT_ID;
	else
	    int_id = UART2_INT_ID;
				
	if (!bus_test ())
	    printf ("\nERROR:  bus_test for UART Unit %d failed\n", uart_unit);
	else {
	    printf ("\nbus_test for UART Unit %d passed\n", uart_unit);
		
	    uart_int = 0;   

	    isr_connect (int_id, uart_isr, 0);

	    if (enable_external_interrupt(int_id) != OK)
		printf("ERROR enabling UART UINT %d interrupt!\n", uart_unit);

	    init_uart ();
		
	    loop = 0;

	    while (!uart_int && (loop < looplim))
		loop++;
	    if (!uart_int)
		printf ("UART Unit %d INTERRUPT test failed %X\n", uart_unit, loop) ;
	    else
		printf ("UART Unit %d INTERRUPT test passed\n", uart_unit);

	    serial_putc(' ');
	}

	/* disable UART interrupt */
	if (disable_external_interrupt(int_id)!= OK)
	    printf("ERROR disabling UART UNIT %d interrupt!\n", uart_unit);

	/* disconnect test handler */
	isr_disconnect (int_id);

    }

    /* 11/01/00 */
    /* #if 0 */	 /* writing to port 2 doesnt work yet... */
#if 1 /* writing to port 2 doesnt work yet... */

/*
	printf ("\nMove the Console Cable to the 2nd Serial Port,\n");
	printf ("Connector J10,\n");
    printf ("and Hit <CR> when the cable is connected.\n\n");
	printf ("After alphabet prints, move Console Cable back to 1st Serial Port,\n");
	printf ("Connector J9,\n");
	printf ("and hit <CR> to exit test\n");
*/  

/* 10/30/00 */
    uart_unit = DFLTPORT;	/* test J10, the PCI-700 GDB port */

    printf ("\nMove the Console Cable to the 2nd Serial Port, Connector J10,\n");
    printf ("and Hit <CR> when the cable is connected.\n");
    printf ("The alphabet should print on the screen.\n\n");

/* 11/01/00 */
/*
	printf ("After alphabet prints, move Console Cable back to 1st Serial Port,\n");
	printf ("Connector J9,\n");
	printf ("and hit <CR> to exit test\n");
*/
    baud = 115200;
    serial_init();
    serial_set(baud?baud:115200L);

/*	while (serial_getc() == -1); */
    while (serial_getc() != 0x0d);	/* wait for a carriage return character to start test */

/*
	while (1)
	{
		for ( i = 65; i <= 90; i++ )
			serial_putc(i);
	}
*/
    for ( i = 65; i <= 90; i++ )	/* transmit the alphabet */
	serial_putc(i);

    serial_putc(10);	/* transmit a New Line */
    serial_putc(13);	/* transmit a Carriage Return */
    serial_putc(10);	/* transmit a New Line */

    for (index=0; info[index] != '\0'; index++)	/* transmit some instructions to the user */
	serial_putc(info[index]);

    /* point at default port before returning */
    /*	uart_unit = DFLTPORT; */

    (void)hexIn();
  
#endif
	
    printf ("\n\nUART tests done.\n");
    printf ("Press return to continue.\n");
    (void) hexIn();
}




