/*
 * 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
 * 2006-08-23     Bernard      first version
 * 2009-05-14     Bernard      add RT-THread device interface
 */

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

#include "AT91SAM7S.h"
#include "serial.h"

/**
 * @addtogroup AT91SAM7
 */
/*@{*/
typedef volatile rt_uint32_t REG32;
struct rt_at91serial_hw
{
	REG32	 US_CR; 	// Control Register
	REG32	 US_MR; 	// Mode Register
	REG32	 US_IER; 	// Interrupt Enable Register
	REG32	 US_IDR; 	// Interrupt Disable Register
	REG32	 US_IMR; 	// Interrupt Mask Register
	REG32	 US_CSR; 	// Channel Status Register
	REG32	 US_RHR; 	// Receiver Holding Register
	REG32	 US_THR; 	// Transmitter Holding Register
	REG32	 US_BRGR; 	// Baud Rate Generator Register
	REG32	 US_RTOR; 	// Receiver Time-out Register
	REG32	 US_TTGR; 	// Transmitter Time-guard Register
	REG32	 Reserved0[5]; 	//
	REG32	 US_FIDI; 	// FI_DI_Ratio Register
	REG32	 US_NER; 	// Nb Errors Register
	REG32	 Reserved1[1]; 	//
	REG32	 US_IF; 	// IRDA_FILTER Register
	REG32	 Reserved2[44]; 	//
	REG32	 US_RPR; 	// Receive Pointer Register
	REG32	 US_RCR; 	// Receive Counter Register
	REG32	 US_TPR; 	// Transmit Pointer Register
	REG32	 US_TCR; 	// Transmit Counter Register
	REG32	 US_RNPR; 	// Receive Next Pointer Register
	REG32	 US_RNCR; 	// Receive Next Counter Register
	REG32	 US_TNPR; 	// Transmit Next Pointer Register
	REG32	 US_TNCR; 	// Transmit Next Counter Register
	REG32	 US_PTCR; 	// PDC Transfer Control Register
	REG32	 US_PTSR; 	// PDC Transfer Status Register
};

struct rt_at91serial
{
	struct rt_device parent;

	struct rt_at91serial_hw* hw_base;
	rt_uint16_t peripheral_id;
	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_at91serial serial1;
#endif
#ifdef RT_USING_UART2
struct rt_at91serial serial2;
#endif

static void rt_hw_serial_isr(int irqno)
{
	rt_base_t level;
	struct rt_device* device;
	struct rt_at91serial* serial = RT_NULL;

	if (irqno == AT91C_ID_US0)
	{
#ifdef RT_USING_UART1
		/* serial 1 */
		serial = &serial1;
#endif
	}
	else if (irqno == AT91C_ID_US1)
	{
#ifdef RT_USING_UART2
		/* serial 2 */
		serial = &serial2;
#endif
	}
	RT_ASSERT(serial != RT_NULL);

	/* get generic device object */
	device = (rt_device_t)serial;

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

	/* get received character */
	serial->rx_buffer[serial->save_index] = serial->hw_base->US_RHR;

	/* move to next position */
	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);

	/* indicate to upper layer application */
	if (device->rx_indicate != RT_NULL)
		device->rx_indicate(device, 1);

	/* ack interrupt */
	AT91C_AIC_EOICR = 1;
}

static rt_err_t rt_serial_init (rt_device_t dev)
{
	rt_uint32_t bd;
	struct rt_at91serial* serial = (struct rt_at91serial*) dev;

	RT_ASSERT(serial != RT_NULL);
	/* must be US0 or US1 */
	RT_ASSERT(((serial->peripheral_id == AT91C_ID_US0) ||
		(serial->peripheral_id == AT91C_ID_US1)));

	/* Enable Clock for USART */
	AT91C_PMC_PCER = 1 << serial->peripheral_id;

	/* Enable RxD0 and TxDO Pin */
	if (serial->peripheral_id == AT91C_ID_US0)
	{
		/* set pinmux */
		AT91C_PIO_PDR = (1 << 5) | (1 << 6);
	}
	else if (serial->peripheral_id == AT91C_ID_US1)
	{
		/* set pinmux */
		AT91C_PIO_PDR = (1 << 21) | (1 << 22);
	}

	serial->hw_base->US_CR = AT91C_US_RSTRX	| 	/* Reset Receiver      */
					AT91C_US_RSTTX		|		/* Reset Transmitter   */
					AT91C_US_RXDIS		|		/* Receiver Disable    */
					AT91C_US_TXDIS;				/* Transmitter Disable */

	serial->hw_base->US_MR = AT91C_US_USMODE_NORMAL |	/* Normal Mode */
					AT91C_US_CLKS_CLOCK		|		/* Clock = MCK */
					AT91C_US_CHRL_8_BITS	|		/* 8-bit Data  */
					AT91C_US_PAR_NONE		|		/* No Parity   */
					AT91C_US_NBSTOP_1_BIT;			/* 1 Stop Bit  */

	/* set baud rate divisor */
	bd =  ((MCK*10)/(serial->baudrate * 16));
	if ((bd % 10) >= 5) bd = (bd / 10) + 1;
	else bd /= 10;

	serial->hw_base->US_BRGR = bd;
	serial->hw_base->US_CR = AT91C_US_RXEN |		/* Receiver Enable     */
					AT91C_US_TXEN;					/* Transmitter Enable  */

	/* reset rx index */
	serial->save_index = 0;
	serial->read_index = 0;

	/* reset rx buffer */
	rt_memset(serial->rx_buffer, 0, RT_UART_RX_BUFFER_SIZE);

	return RT_EOK;
}

static rt_err_t rt_serial_open(rt_device_t dev, rt_uint16_t oflag)
{
	struct rt_at91serial *serial = (struct rt_at91serial*)dev;
	RT_ASSERT(serial != RT_NULL);

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
	{
		/* enable UART rx interrupt */
		serial->hw_base->US_IER = 1 << 0; 		/* RxReady interrupt */
		serial->hw_base->US_IMR |= 1 << 0; 		/* umask RxReady interrupt */

		/* install UART handler */
		rt_hw_interrupt_install(serial->peripheral_id, rt_hw_serial_isr, RT_NULL);
		AT91C_AIC_SMR(serial->peripheral_id) = 5 | (0x01 << 5);
		rt_hw_interrupt_umask(serial->peripheral_id);
	}

	return RT_EOK;
}

static rt_err_t rt_serial_close(rt_device_t dev)
{
	struct rt_at91serial *serial = (struct rt_at91serial*)dev;
	RT_ASSERT(serial != RT_NULL);

	if (dev->flag & RT_DEVICE_FLAG_INT_RX)
	{
		/* disable interrupt */
		serial->hw_base->US_IDR = 1 << 0; 		/* RxReady interrupt */
		serial->hw_base->US_IMR &= ~(1 << 0); 	/* mask RxReady interrupt */
	}

	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_at91serial *serial = (struct rt_at91serial*)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);
	}
	else
	{
		/* poll mode */
		while (size)
		{
			/* Wait for Full Rx Buffer */
			while (!(serial->hw_base->US_CSR & AT91C_US_RXRDY));

			/* Read Character */
			*ptr = serial->hw_base->US_RHR;
			ptr ++;
			size --;
		}

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

	return 0;
}

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

	ptr = (rt_uint8_t*) buffer;
	if (dev->open_flag & RT_DEVICE_OFLAG_WRONLY)
	{
		if (dev->flag & RT_DEVICE_FLAG_STREAM)
		{
			/* it's a stream mode device */
			while (size)
			{
				/* stream mode */
				if (*ptr == '\n')
				{
					while (!(serial->hw_base->US_CSR & AT91C_US_TXRDY));
					serial->hw_base->US_THR = '\r';
				}

				/* Wait for Empty Tx Buffer */
				while (!(serial->hw_base->US_CSR & AT91C_US_TXRDY));

				/* Transmit Character */
				serial->hw_base->US_THR = *ptr;
				ptr ++; size --;
			}
		}
		else
		{
			while (size)
			{
				/* Wait for Empty Tx Buffer */
				while (!(serial->hw_base->US_CSR & AT91C_US_TXRDY));

				/* Transmit Character */
				serial->hw_base->US_THR = *ptr;
				ptr ++; size --;
			}
		}
	}

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

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

rt_err_t rt_hw_serial_init()
{
	rt_device_t device;

#ifdef RT_USING_UART1
	device = (rt_device_t) &serial1;

	/* init serial device private data */
	serial1.hw_base 		= (struct rt_at91serial_hw*)AT91C_BASE_US0;
	serial1.peripheral_id 	= AT91C_ID_US0;
	serial1.baudrate		= 115200;

	/* set device virtual interface */
	device->init 	= rt_serial_init;
	device->open 	= rt_serial_open;
	device->close 	= rt_serial_close;
	device->read 	= rt_serial_read;
	device->write 	= rt_serial_write;
	device->control = rt_serial_control;

	/* register uart1 on device subsystem */
	rt_device_register(device, "uart1", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
#endif

#ifdef RT_USING_UART2
	device = (rt_device_t) &serial2;

	serial2.hw_base 		= (struct rt_at91serial_hw*)AT91C_BASE_US1;
	serial2.peripheral_id 	= AT91C_ID_US1;
	serial2.baudrate		= 115200;

	/* set device virtual interface */
	device->init 	= rt_serial_init;
	device->open 	= rt_serial_open;
	device->close 	= rt_serial_close;
	device->read 	= rt_serial_read;
	device->write 	= rt_serial_write;
	device->control = rt_serial_control;

	/* register uart2 on device subsystem */
	rt_device_register(device, "uart2", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
#endif

	return RT_EOK;
}

/*@}*/