/*
 * File      : serial.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://openlab.rt-thread.com/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2009-06-11     Bernard      first version
 */

#include <rthw.h>
#include <rtthread.h>

#include <inc/hw_types.h>
#include <inc/hw_memmap.h>
#include <inc/hw_uart.h>
#include <inc/hw_ints.h>
#include <driverlib/gpio.h>
#include <driverlib/sysctl.h>
#include <driverlib/interrupt.h>

#include <uart.h>
#include "board.h"

extern void rt_hw_interrupt_thread_switch(void);

#define RT_UART_RX_BUFFER_SIZE 64

/* LM3S serial device */
struct rt_lm3s_serial
{
	/* inherit from device */
	struct rt_device parent;

	rt_uint32_t hw_base;
	rt_uint32_t baudrate;

	/* reception field */
	rt_uint16_t save_index, read_index;
	rt_uint8_t  rx_buffer[RT_UART_RX_BUFFER_SIZE];
};

#ifdef RT_USING_UART1
struct rt_lm3s_serial serial1;
#endif
#ifdef RT_USING_UART2
struct rt_lm3s_serial serial2;
#endif

void rt_hw_serial_init(void);

void rt_hw_uart_isr(struct rt_lm3s_serial* serial)
{
    rt_device_t device;
    rt_uint32_t status;

    device = (struct rt_device*)serial;
    UARTIntStatus(serial->hw_base, true);

    /* clear interrupt status */
    UARTIntClear(serial->hw_base, status);

    if (device->flag & RT_DEVICE_FLAG_INT_RX)
    {
        char ch;
		rt_base_t level;

        while (UARTCharsAvail(serial->hw_base))
        {
            ch = UARTCharGetNonBlocking(serial->hw_base);

            /* disable interrupt */
			level = rt_hw_interrupt_disable();

			/* read character */
			serial->rx_buffer[serial->save_index] = ch;
			serial->save_index ++;
			if (serial->save_index >= RT_UART_RX_BUFFER_SIZE)
				serial->save_index = 0;

			/* if the next position is read index, discard this 'read char' */
			if (serial->save_index == serial->read_index)
			{
				serial->read_index ++;
				if (serial->read_index >= RT_UART_RX_BUFFER_SIZE)
					serial->read_index = 0;
			}

			/* enable interrupt */
			rt_hw_interrupt_enable(level);
        }

		/* invoke callback */
		if(device->rx_indicate != RT_NULL)
		{
		    rt_int32_t length;

		    length = serial->save_index - serial->read_index;
		    if (length < 0) length += RT_UART_RX_BUFFER_SIZE;
            device->rx_indicate(device, length);
		}
    }
}

#ifdef RT_USING_UART1
void rt_hw_uart_isr_1(int irqno)
{
	/* enter interrupt */
	rt_interrupt_enter();

	/* get serial device */
	rt_hw_uart_isr(&serial1);

	/* leave interrupt */
	rt_interrupt_leave();
	rt_hw_interrupt_thread_switch();
}
#endif

#ifdef RT_USING_UART2
void rt_hw_uart_isr_2(int irqno)
{
	/* enter interrupt */
	rt_interrupt_enter();

	/* get serial device */
	rt_hw_uart_isr(&serial2);

	/* leave interrupt */
	rt_interrupt_leave();
	rt_hw_interrupt_thread_switch();
}
#endif

/**
 * @addtogroup LM3S
 */
/*@{*/

static rt_err_t rt_serial_init (rt_device_t dev)
{
	return RT_EOK;
}

static rt_err_t rt_serial_open(rt_device_t dev, rt_uint16_t oflag)
{
	struct rt_lm3s_serial* serial;
	serial = (struct rt_lm3s_serial*) dev;

	RT_ASSERT(serial != RT_NULL);

    if (dev->flag & RT_DEVICE_FLAG_INT_RX)
    {
        /* enable interrupt */
        if (serial->hw_base == UART0_BASE)
            IntEnable(INT_UART0);
        else if (serial->hw_base == UART1_BASE)
            IntEnable(INT_UART1);

        UARTIntEnable(serial->hw_base, UART_INT_RX | UART_INT_RT);
    }

	return RT_EOK;
}

static rt_err_t rt_serial_close(rt_device_t dev)
{
	struct rt_lm3s_serial* serial;
	serial = (struct rt_lm3s_serial*) dev;

	RT_ASSERT(serial != RT_NULL);

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
	{
		/* disable UART rx interrupt */
		UARTIntDisable(serial->hw_base, UART_INT_RX | UART_INT_RT);
	}

	return RT_EOK;
}

static rt_err_t rt_serial_control(rt_device_t dev, rt_uint8_t cmd, void *args)
{
	return RT_EOK;
}

static rt_size_t rt_serial_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)
{
	rt_uint8_t* ptr;
	struct rt_lm3s_serial *serial = (struct rt_lm3s_serial*)dev;
	RT_ASSERT(serial != RT_NULL);

	/* point to buffer */
	ptr = (rt_uint8_t*) buffer;

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
	{
		while (size)
		{
			/* interrupt receive */
			rt_base_t level;

			/* disable interrupt */
			level = rt_hw_interrupt_disable();
			if (serial->read_index != serial->save_index)
			{
				*ptr = serial->rx_buffer[serial->read_index];

				serial->read_index ++;
				if (serial->read_index >= RT_UART_RX_BUFFER_SIZE)
					serial->read_index = 0;
			}
			else
			{
				/* no data in rx buffer */

				/* enable interrupt */
				rt_hw_interrupt_enable(level);
				break;
			}

			/* enable interrupt */
			rt_hw_interrupt_enable(level);

			ptr ++; size --;
		}

		return (rt_uint32_t)ptr - (rt_uint32_t)buffer;
	}
	else if (dev->flag & RT_DEVICE_FLAG_DMA_RX)
	{
		/* not support right now */
		RT_ASSERT(0);
	}

	/* polling mode */
	while (size)
	{
	    *ptr = UARTCharGetNonBlocking(serial->hw_base);

	    ptr ++; size --;
	}

	return (rt_size_t)ptr - (rt_size_t)buffer;
}

static rt_size_t rt_serial_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
{
	struct rt_lm3s_serial* serial;
	char *ptr;

	serial = (struct rt_lm3s_serial*) dev;
	if (dev->flag & RT_DEVICE_FLAG_INT_TX)
	{
		/* not support */
		RT_ASSERT(0);
	}
	else if (dev->flag & RT_DEVICE_FLAG_DMA_TX)
	{
		/* not support */
		RT_ASSERT(0);
	}

	/* polling write */
	ptr = (char *)buffer;

	if (dev->flag & RT_DEVICE_FLAG_STREAM)
	{
		/* stream mode */
		while (size)
		{
			if (*ptr == '\n')
				while (UARTCharPutNonBlocking(serial->hw_base, '\r') == false);

			while (UARTCharPutNonBlocking(serial->hw_base, *ptr) == false);

			ptr ++;
			size --;
		}
	}
	else
	{
		while (size)
		{
			while (UARTCharPutNonBlocking(serial->hw_base, *ptr) == false);

			ptr ++;
			size --;
		}
	}

	return (rt_size_t) ptr - (rt_size_t) buffer;
}

void rt_hw_serial_init(void)
{
	struct rt_lm3s_serial* serial;

#ifdef RT_USING_UART1
	serial = &serial1;

	serial->parent.type = RT_Device_Class_Char;

	serial->hw_base = UART0_BASE;
	serial->baudrate = 115200;

	rt_memset(serial->rx_buffer, 0, sizeof(serial->rx_buffer));
	serial->read_index = serial->save_index = 0;

	/* enable UART0 clock */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

	/* set UART0 pinmux */
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

	/* Configure the UART for 115,200, 8-N-1 operation. */
	UARTConfigSetExpClk(UART0_BASE, SysCtlClockGet(), serial->baudrate,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
                         UART_CONFIG_PAR_NONE));

	serial->parent.init 	= rt_serial_init;
	serial->parent.open 	= rt_serial_open;
	serial->parent.close    = rt_serial_close;
	serial->parent.read 	= rt_serial_read;
	serial->parent.write    = rt_serial_write;
	serial->parent.control  = rt_serial_control;
	serial->parent.private  = RT_NULL;

	rt_device_register(&serial->parent,
		"uart1", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
#endif

#ifdef RT_USING_UART2
	serial = &serial2;

	serial->parent.type = RT_Device_Class_Char;

	serial->hw_base = 0xE0010000;
	serial->baudrate = 115200;

	rt_memset(serial->rx_buffer, 0, sizeof(serial->rx_buffer));
	serial->read_index = serial->save_index = 0;

	serial->parent.init 	= rt_serial_init;
	serial->parent.open 	= rt_serial_open;
	serial->parent.close    = rt_serial_close;
	serial->parent.read 	= rt_serial_read;
	serial->parent.write    = rt_serial_write;
	serial->parent.control  = rt_serial_control;
	serial->parent.private  = RT_NULL;

	rt_device_register(&serial->parent,
		"uart2", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
#endif
}

/*@}*/
