//==========================================================================
//
//      devs/serial/cortexm/lm3s/lm3s_serial.c
//
//      LM3S Serial I/O Interface Module
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 2011 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):     stuartw
// Date:          2011-06-29
// Purpose:       LM3S Serial I/O module for interrupt driven IO.
// Description:
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/hal.h>
#include <pkgconf/infra.h>
#include <pkgconf/system.h>
#include <pkgconf/io_serial.h>
#include <pkgconf/io.h>
#include <pkgconf/kernel.h>

#include <cyg/io/io.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/io/devtab.h>
#include <cyg/io/serial.h>
#include <cyg/infra/diag.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>


#ifdef CYGPKG_IO_SERIAL_CORTEXM_LM3S

// Uncomment this to dump serial port settings after initialisation
// #define LM3S_DEBUG_SERIAL_INIT

#include "lm3s_serial.h"

//==========================================================================

// Size of the buffer for received characters.
#define LM3S_RXBUFSIZE 16

typedef struct lm3s_serial_info
{
    CYG_WORD            uart;
    CYG_ADDRWORD        base;
    CYG_WORD            int_num;
    cyg_int32           rx_pin;
    cyg_int32           tx_pin;

    cyg_bool            tx_active;

    volatile cyg_uint8  buf[LM3S_RXBUFSIZE];
    volatile int        buf_head;
    volatile int        buf_tail;

    cyg_interrupt       serial_interrupt;
    cyg_handle_t        serial_interrupt_handle;
} lm3s_serial_info;

//==========================================================================

static bool lm3s_serial_init( struct cyg_devtab_entry *tab );
static bool lm3s_serial_putc_interrupt( serial_channel *chan, unsigned char c );
static Cyg_ErrNo lm3s_serial_lookup( struct cyg_devtab_entry **tab,
                                     struct cyg_devtab_entry *sub_tab,
                                     const char *name );
static unsigned char lm3s_serial_getc_interrupt( serial_channel *chan );
static Cyg_ErrNo lm3s_serial_set_config( serial_channel *chan,
                                         cyg_uint32 key,
                                         const void *xbuf,
                                         cyg_uint32 *len );
static void lm3s_serial_start_xmit( serial_channel *chan );
static void lm3s_serial_stop_xmit( serial_channel *chan );

static cyg_uint32 lm3s_serial_ISR( cyg_vector_t vector, cyg_addrword_t data );
static void lm3s_serial_DSR( cyg_vector_t vector,
                             cyg_ucount32 count,
                             cyg_addrword_t data );

//==========================================================================

#if ((CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL0_BUFSIZE > 0) || \
     (CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL1_BUFSIZE > 0))
static SERIAL_FUNS( lm3s_serial_funs_interrupt,
                    lm3s_serial_putc_interrupt,
                    lm3s_serial_getc_interrupt,
                    lm3s_serial_set_config,
                    lm3s_serial_start_xmit,
                    lm3s_serial_stop_xmit );
#endif

//==========================================================================

#ifdef CYGPKG_IO_SERIAL_CORTEXM_LM3S_SERIAL0
static lm3s_serial_info lm3s_serial_info0 = {
    uart            : 0,
    base            : (CYG_ADDRWORD) CYGHWR_HAL_LM3S_UART0,
    int_num         : CYGNUM_HAL_INTERRUPT_UART0,
    rx_pin          : CYGHWR_HAL_LM3S_UART0_RX,
    tx_pin          : CYGHWR_HAL_LM3S_UART0_TX
};

#if CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL0_BUFSIZE > 0
static unsigned char lm3s_serial_out_buf0[
                            CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL0_BUFSIZE ];
static unsigned char lm3s_serial_in_buf0[
                            CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL0_BUFSIZE ];

static SERIAL_CHANNEL_USING_INTERRUPTS( lm3s_serial_channel0,
                                        lm3s_serial_funs_interrupt,
                                        lm3s_serial_info0,
                                        CYG_SERIAL_BAUD_RATE(CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL0_BAUD),
                                        CYG_SERIAL_STOP_DEFAULT,
                                        CYG_SERIAL_PARITY_DEFAULT,
                                        CYG_SERIAL_WORD_LENGTH_DEFAULT,
                                        CYG_SERIAL_FLAGS_DEFAULT,
                                        &lm3s_serial_out_buf0[ 0 ],
                                        sizeof( lm3s_serial_out_buf0 ),
                                        &lm3s_serial_in_buf0[ 0 ],
                                        sizeof( lm3s_serial_in_buf0 )
    );
#endif

DEVTAB_ENTRY( lm3s_serial_io0,
              CYGDAT_IO_SERIAL_CORTEXM_LM3S_SERIAL0_NAME,
              0,               // Does not depend on a lower level interface
              &cyg_io_serial_devio,
              lm3s_serial_init,
              lm3s_serial_lookup,     // Serial driver may need initializing
              &lm3s_serial_channel0 );
#endif //  CYGPKG_IO_SERIAL_CORTEXM_LM3S_SERIAL0

#ifdef CYGPKG_IO_SERIAL_CORTEXM_LM3S_SERIAL1
static lm3s_serial_info lm3s_serial_info1 = {
    uart            : 1,
    base            : (CYG_ADDRWORD) CYGHWR_HAL_LM3S_UART1,
    int_num         : CYGNUM_HAL_INTERRUPT_UART1,
    rx_pin          : CYGHWR_HAL_LM3S_UART1_RX,
    tx_pin          : CYGHWR_HAL_LM3S_UART1_TX
};

#if CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL1_BUFSIZE > 0
static unsigned char lm3s_serial_out_buf1[
                            CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL1_BUFSIZE ];
static unsigned char lm3s_serial_in_buf1[
                            CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL1_BUFSIZE ];

static SERIAL_CHANNEL_USING_INTERRUPTS( lm3s_serial_channel1,
                                        lm3s_serial_funs_interrupt,
                                        lm3s_serial_info1,
                                        CYG_SERIAL_BAUD_RATE(CYGNUM_IO_SERIAL_CORTEXM_LM3S_SERIAL1_BAUD),
                                        CYG_SERIAL_STOP_DEFAULT,
                                        CYG_SERIAL_PARITY_DEFAULT,
                                        CYG_SERIAL_WORD_LENGTH_DEFAULT,
                                        CYG_SERIAL_FLAGS_DEFAULT,
                                        &lm3s_serial_out_buf1[ 0 ],
                                        sizeof( lm3s_serial_out_buf1 ),
                                        &lm3s_serial_in_buf1[ 0 ],
                                        sizeof( lm3s_serial_in_buf1 )
    );
#endif

DEVTAB_ENTRY( lm3s_serial_io1,
              CYGDAT_IO_SERIAL_CORTEXM_LM3S_SERIAL1_NAME,
              0,               // Does not depend on a lower level interface
              &cyg_io_serial_devio,
              lm3s_serial_init,
              lm3s_serial_lookup,     // Serial driver may need initializing
              &lm3s_serial_channel1 );
#endif //  CYGPKG_IO_SERIAL_CORTEXM_LM3S_SERIAL1

#ifdef LM3S_DEBUG_SERIAL_INIT
//==========================================================================
// Internal function to dump some interesting configuration settings for a
// serial port

static void
lm3s_dump_serial_config_port( serial_channel *chan)
{
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;
    CYG_ADDRWORD base = lm3s_chan->base;
    cyg_uint32      reg;

    diag_printf("lm3s_dump_serial_config_port:\n");

    // Dump the serial port config
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_SR, reg );
    diag_printf("lm3s_serial_config_port: SR 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_FR, reg );
    diag_printf("lm3s_serial_config_port: FR 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_IBRD, reg );
    diag_printf("lm3s_serial_config_port: IBRD 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_FBRD, reg );
    diag_printf("lm3s_serial_config_port: FBRD 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_LCRH, reg );
    diag_printf("lm3s_serial_config_port: LCRH 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_CTL, reg );
    diag_printf("lm3s_serial_config_port: CTL 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_IFLS, reg );
    diag_printf("lm3s_serial_config_port: IFLS 0x%X\n", reg);
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_IM, reg );
    diag_printf("lm3s_serial_config_port: IM 0x%X\n", reg);

    // Dump the SYSCTL registers
    HAL_READ_UINT32(CYGHWR_HAL_LM3S_SC + CYGHWR_HAL_LM3S_SC_RCGC0, reg);
    diag_printf("lm3s_serial_config_port: RCGC0 0x%X\n", reg);
    HAL_READ_UINT32(CYGHWR_HAL_LM3S_SC + CYGHWR_HAL_LM3S_SC_RCGC1, reg);
    diag_printf("lm3s_serial_config_port: RCGC1 0x%X\n", reg);
    HAL_READ_UINT32(CYGHWR_HAL_LM3S_SC + CYGHWR_HAL_LM3S_SC_RCGC2, reg);
    diag_printf("lm3s_serial_config_port: RCGC2 0x%X\n", reg);

    // Dump the GPIO registers
    if( base == CYGHWR_HAL_LM3S_UART0 ) {
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOA + CYGHWR_HAL_LM3S_GPIO_AFSEL, reg);
        diag_printf("lm3s_serial_config_port: GPIOA AFSEL 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOA + CYGHWR_HAL_LM3S_GPIO_DR2R, reg);
        diag_printf("lm3s_serial_config_port: GPIOA DR2R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOA + CYGHWR_HAL_LM3S_GPIO_DR4R, reg);
        diag_printf("lm3s_serial_config_port: GPIOA DR4R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOA + CYGHWR_HAL_LM3S_GPIO_DR8R, reg);
        diag_printf("lm3s_serial_config_port: GPIOA DR8R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOA + CYGHWR_HAL_LM3S_GPIO_PCTL, reg);
        diag_printf("lm3s_serial_config_port: GPIOA PCTL 0x%X\n", reg);
    }
    if( base == CYGHWR_HAL_LM3S_UART1 ) {
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOD + CYGHWR_HAL_LM3S_GPIO_AFSEL, reg);
        diag_printf("lm3s_serial_config_port: GPIOD AFSEL 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOD + CYGHWR_HAL_LM3S_GPIO_DR2R, reg);
        diag_printf("lm3s_serial_config_port: GPIOD DR2R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOD + CYGHWR_HAL_LM3S_GPIO_DR4R, reg);
        diag_printf("lm3s_serial_config_port: GPIOD DR4R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOD + CYGHWR_HAL_LM3S_GPIO_DR8R, reg);
        diag_printf("lm3s_serial_config_port: GPIOD DR8R 0x%X\n", reg);
        HAL_READ_UINT32(CYGHWR_HAL_LM3S_GPIOD + CYGHWR_HAL_LM3S_GPIO_PCTL, reg);
        diag_printf("lm3s_serial_config_port: GPIOD PCTL 0x%X\n", reg);
    }
}
#endif

//==========================================================================
// Internal function to actually configure the hardware to desired baud
// rate, etc.

static bool
lm3s_serial_config_port( serial_channel     *chan,
                         cyg_serial_info_t  *new_config,
                         bool               init)
{
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;
    CYG_ADDRWORD base = lm3s_chan->base;
    cyg_uint32      lcrh;
    cyg_uint32      ctl;

    // Set up FIFO buffer
    lm3s_chan->buf_head = lm3s_chan->buf_tail = 0;

#ifdef CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL
    // Don't reconfigure if we're already using the channel for debug.
    if( lm3s_chan->uart != CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL )
#endif /* CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL */
    {
        // Disable UART
        HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_CTL, ctl );
        ctl &= ~( CYGHWR_HAL_LM3S_UART_CTL_UARTEN |
                  CYGHWR_HAL_LM3S_UART_CTL_TXE | CYGHWR_HAL_LM3S_UART_CTL_RXE );
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_CTL, ctl );

        // Set up GPIO pins
        CYGHWR_HAL_LM3S_GPIO_SET( lm3s_chan->rx_pin );
        CYGHWR_HAL_LM3S_GPIO_SET( lm3s_chan->tx_pin );

        // Set up baud rate
        hal_lm3s_uart_setbaud( base, select_baud[ new_config->baud ] );

        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_FR, 0 );

        // 8 bits - 1 stop - Enable FIFO
        lcrh = ( CYGHWR_HAL_LM3S_UART_LCRH_WLEN( 3 ) |
                 CYGHWR_HAL_LM3S_UART_LCRH_FEN );
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_LCRH, lcrh );

        // Enable the UART
        ctl = ( CYGHWR_HAL_LM3S_UART_CTL_UARTEN |
                CYGHWR_HAL_LM3S_UART_CTL_TXE | CYGHWR_HAL_LM3S_UART_CTL_RXE );
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_CTL, ctl );
    }

    // Enable interrupts for RX FIFO with enough data or timed out with any.
    HAL_WRITE_UINT32(( base + CYGHWR_HAL_LM3S_UART_IM ),
                     ( CYGHWR_HAL_LM3S_UART_IM_RXIM |
                       CYGHWR_HAL_LM3S_UART_IM_RTIM ));

    // We're not transmitting until told so.
    lm3s_chan->tx_active = false;

    // Store the config.
    chan->config = *new_config;

#ifdef LM3S_DEBUG_SERIAL_INIT
    // Dump the serial port config
    lm3s_dump_serial_config_port( chan);
#endif

    return true;
}

//==========================================================================
// Function to initialize the device.  Called at bootstrap time.

static bool
lm3s_serial_init( struct cyg_devtab_entry *tab )
{
    serial_channel *chan = (serial_channel *)tab->priv;
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;
    int res;

    // Only required for interrupt driven devices
    (chan->callbacks->serial_init)( chan );

    if( chan->out_cbuf.len != 0 )
    {
        // Only add an ISR for UART1 (temporary only, remove later)
        if( lm3s_chan->uart == CYGNUM_HAL_VIRTUAL_VECTOR_DEBUG_CHANNEL )
        {
            cyg_drv_interrupt_create( lm3s_chan->int_num,
                                      0x80,
                                      (cyg_addrword_t)chan,
                                      lm3s_serial_ISR,
                                      lm3s_serial_DSR,
                                      &lm3s_chan->serial_interrupt_handle,
                                      &lm3s_chan->serial_interrupt );
            cyg_drv_interrupt_attach( lm3s_chan->serial_interrupt_handle );
            cyg_drv_interrupt_unmask( lm3s_chan->int_num );
        }
    }

    res = lm3s_serial_config_port( chan, &chan->config, true );

    return res;
}

//==========================================================================
// This routine is called when the device is "looked" up (i.e. attached)

static Cyg_ErrNo
lm3s_serial_lookup( struct cyg_devtab_entry **tab,
                    struct cyg_devtab_entry *sub_tab,
                    const char              *name)
{
    serial_channel *chan = (serial_channel *) (*tab)->priv;

    // Only required for interrupt driven devices
    (chan->callbacks->serial_init)( chan );

    return ENOERR;
}

//==========================================================================
// Send a character to the device output buffer.
// Return 'true' if character is sent to device

static bool
lm3s_serial_putc_interrupt( serial_channel *chan, unsigned char c )
{
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *) chan->dev_priv;
    CYG_ADDRWORD base = lm3s_chan->base;
    cyg_uint32 status;

    bool ret_val;

    // Read the register which has the TX FIFO status.
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_FR, status );

    // If the TX FIFO is not full then we can send the character, otherwise
    // we fail.
    if(( status & CYGHWR_HAL_LM3S_UART_FR_TXFF ) == 0 )
    {
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_DR, c );
        ret_val = true;
    }
    else
    {
        ret_val = false;
    }

    return ret_val;
}

//==========================================================================
// Fetch a character from the device input buffer

static unsigned char
lm3s_serial_getc_interrupt( serial_channel *chan )
{
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;
    CYG_ADDRWORD base = lm3s_chan->base;
    CYG_WORD32 c;

    // Read data
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_DR, c);
    return( (unsigned char)( c & 0xFF ));
}

//==========================================================================
// Set up the device characteristics; baud rate, etc.

static Cyg_ErrNo
lm3s_serial_set_config( serial_channel  *chan,
                        cyg_uint32      key,
                        const void      *xbuf,
                        cyg_uint32      *len )
{
    Cyg_ErrNo ret_val = ENOERR;

    switch( key )
    {
        case CYG_IO_SET_CONFIG_SERIAL_INFO:
        {
            cyg_serial_info_t *config = (cyg_serial_info_t *)xbuf;

            if( *len < sizeof( cyg_serial_info_t ))
            {
                ret_val = -EINVAL;
            }
            else
            {
                *len = sizeof( cyg_serial_info_t );

                if ( true != lm3s_serial_config_port( chan, config, false ))
                {
                    ret_val = -EINVAL;
                }
            }

            break;
        }


        default:
        {
            ret_val = -EINVAL;
        }
    }

    return ret_val;
}

//==========================================================================
// Enable the transmitter on the device

static void
lm3s_serial_start_xmit( serial_channel *chan )
{
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;

    if( !lm3s_chan->tx_active )
    {
        CYG_ADDRWORD base = lm3s_chan->base;
        cyg_uint32 mask;

        lm3s_chan->tx_active = true;

        // Enable the TX interrupt.
        HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_IM, mask );
        mask |= CYGHWR_HAL_LM3S_UART_IM_TXIM;
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_IM, mask );

        (chan->callbacks->xmt_char)(chan);
    }
}

//==========================================================================
// Disable the transmitter on the device

static void
lm3s_serial_stop_xmit(serial_channel *chan)
{
    lm3s_serial_info * const lm3s_chan = (lm3s_serial_info *) chan->dev_priv;


    if( lm3s_chan->tx_active )
    {
        const CYG_ADDRWORD base = lm3s_chan->base;
        cyg_uint32 mask;

        lm3s_chan->tx_active = false;

        // Disable the TX interrupt.
        HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_IM, mask );
        mask &= ~CYGHWR_HAL_LM3S_UART_IM_TXIM;
        HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_IM, mask );
    }
}

//==========================================================================
// Serial I/O - low level interrupt handler (ISR)
//
// This ISR does rather more than other serial driver ISRs. Normally,
// the ISR just masks the interrupt vector and schedules the DSR,
// which then handles all IO. However, to give us a better chance of
// keeping up with receive data this ISR receives any incoming data into
// a circular buffer, providing an additional FIFO.
// hardware. Transmission is still completely offloaded to the DSR.

static cyg_uint32
lm3s_serial_ISR( cyg_vector_t vector, cyg_addrword_t data )
{
    serial_channel *chan = (serial_channel *)data;
    lm3s_serial_info *lm3s_chan = (lm3s_serial_info *)chan->dev_priv;
    CYG_ADDRWORD base = lm3s_chan->base;
    cyg_uint32 stat;
    cyg_uint32 ret = CYG_ISR_HANDLED;

    cyg_drv_interrupt_acknowledge(vector);

    cyg_uint32 mask;

    // Assume we won't need the DSR
    bool callDsr = false;

    // Get the interrupts we need to process.
    HAL_READ_UINT32( base + CYGHWR_HAL_LM3S_UART_MIS, mask );

    // Clear the outstanding interrupts.
    HAL_WRITE_UINT32( base + CYGHWR_HAL_LM3S_UART_ICR, mask );

    // Read the TX and RX FIFO states.
    HAL_READ_UINT32(base + CYGHWR_HAL_LM3S_UART_FR, stat);

    // If the RX FIFO is not full we read any available bytes.
    if(( stat & CYGHWR_HAL_LM3S_UART_FR_RXFE ) == 0 )
    {
        cyg_uint32 c;

        while(( stat & CYGHWR_HAL_LM3S_UART_FR_RXFE ) == 0 )
        {
            int next = lm3s_chan->buf_head+1;

            if( next == LM3S_RXBUFSIZE )
            {
                next = 0;
            }

            HAL_READ_UINT32(( base + CYGHWR_HAL_LM3S_UART_DR ), c );

            if( next != lm3s_chan->buf_tail )
            {
                lm3s_chan->buf[lm3s_chan->buf_head] = ( c & 0xFF );
                lm3s_chan->buf_head = next;
                callDsr = true;
            }
            else
            {
                // Buffer overflow - packets dropped
            }

            HAL_READ_UINT32(( base + CYGHWR_HAL_LM3S_UART_FR ), stat );
        }
    }

    if(( !callDsr ) && lm3s_chan->tx_active )
    {
        // So far we don't need to schedule the DSR. If we are transmitting
        // and the TX FIFO is not full we should so do.
        if(( stat & CYGHWR_HAL_LM3S_UART_FR_TXFF ) == 0 )
        {
            callDsr = true;
        }
    }

    // If we need to schedule the DSR then disable the interrupt.
    if( callDsr )
    {
        ret |= CYG_ISR_CALL_DSR;

        cyg_drv_interrupt_mask( vector );
    }

    return ret;
}

//==========================================================================
// Serial I/O - high level interrupt handler (DSR)

static void
lm3s_serial_DSR( cyg_vector_t   vector,
                 cyg_ucount32   count,
                 cyg_addrword_t data )
{
    serial_channel *chan = ( serial_channel * )data;
    lm3s_serial_info *lm3s_chan = ( lm3s_serial_info *)chan->dev_priv;
    const CYG_ADDRWORD base = lm3s_chan->base;
    CYG_WORD32 stat;

    while( lm3s_chan->buf_head != lm3s_chan->buf_tail )
    {
        int next = lm3s_chan->buf_tail + 1;
        cyg_uint8 c;

        if( next == LM3S_RXBUFSIZE )
        {
            next = 0;
        }

        c = lm3s_chan->buf[ lm3s_chan->buf_tail ];
        lm3s_chan->buf_tail = next;

        (chan->callbacks->rcv_char)( chan, c );
    }

    HAL_READ_UINT32(base + CYGHWR_HAL_LM3S_UART_FR, stat);

    if( lm3s_chan->tx_active )
    {
        if(( stat & CYGHWR_HAL_LM3S_UART_FR_TXFF ) == 0 )
        {
            (chan->callbacks->xmt_char)( chan );
        }
    }

    cyg_drv_interrupt_unmask( vector );
}

//==========================================================================
#endif // CYGPKG_IO_SERIAL_CORTEXM_LM3S
// end of lm3s_serial.c
