/* uart_intr.c - HCS12 UART (SCI) interrupt driven serial I/O driver.
 * Copyright 2008 Colin D. Bennett <colin@gibibit.com>.
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *    This program 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 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * This program uses interrupt driven asynchronous serial I/O on the HCS12 CPU. 
 * The HCS12 CPU has two SCI (UART) ports, SCI0 and SCI1.
 * We use SCI1 because SCI0 is reserved by DBug12. Connect to the connector 
 * P2 (the DB9 connector nearest to the power connector on the Dragon12-Plus
 * board) to use SCI1.
 *
 * The port baud rate and CPU frequency must be set (via UART_BAUD & F_CPU),
 * which is taken care of by the Makefile, via gcc '-D' options. We must
 * know the CPU clock frequency because the UART transmitter and receiver
 * run off of the same clock, divided by a certain value, which depends on
 * both the CPU clock rate and the baud rate. The actual calculation is done
 * in the hc12/setbaud.h utility header.
 *
 * Note: Be sure to disable hardware flow control (RTS/CTS) on your computer
 * when trying to communicate with the HCS12 CPU, since it is unsupported.
 */

#include "hc12drivers/uart_intr.h"
#include "hc12/hcs12.h"
#include "hc12/interrupt.h"
#include "hc12/vectors12.h"


#define BUFSIZE        64     /* Must be a power of 2. */
#define BUFSIZE_MASK   (BUFSIZE - 1)
#if (BUFSIZE & BUFSIZE_MASK)
# error BUFSIZE is not a power of 2
#endif

struct fifo
{
	char buf[BUFSIZE];  // Ring buffer
	uint8_t head;       // Index of oldest byte
	uint8_t tail;       // Index of newest byte
	uint8_t size;       // Number of bytes in FIFO
};
typedef volatile struct fifo fifo_t;

static void fifo_init(fifo_t *fifo)
{
	fifo->head = 0;
	fifo->tail = 0;
	fifo->size = 0;
}

static fifo_t rxfifo;
static fifo_t txfifo;

DECLARE_ISR(uart_interrupt);

void uart_init(void)
{
	/****** Configure SCI module. ******/
	disable_interrupts();  /* TODO Save and restore global interrupt flag? */

	/* Select baud rate. */
	/* The setbaud.h utility header takes in: F_CPU, UART_BAUD.
	 * It defines SCI1BDH and SCI1BDL for us based on calculations. */
#include "hc12/setbaud.h"    
	SCI1BDH = SCIBDH_VALUE;
	SCI1BDL = SCIBDL_VALUE;

	/* Configure word length, parity, and other configuration bits */
	SCI1CR1 = 0; /* no loopback mode, normal 8 data bits, no parity */

	fifo_init(&rxfifo);
	fifo_init(&txfifo);

	SET_INTERRUPT_VECTOR(UserSCI1, uart_interrupt);

	/* Enable the transmitter and receiver
	 * and the receive data register full interrupt. */
	SCI1CR2 = TE | RE | RIE;

	enable_interrupts();
}

/* Interrupt driven UART write. Enqueues the character c in the 
 * transmit FIFO buffer. This function may block until there is
 * room in the FIFO for the character. */
void uart_putchar(char c)
{
	disable_interrupts();

	while (txfifo.size == BUFSIZE)
	{
		// Wait for space to become available
		enable_interrupts();   // Note that the WAI instruction
		wait();                // ... will be executed before any interrupts
		disable_interrupts();  // ... can be processed.
	}

	uint8_t newtail = (txfifo.tail + 1) & BUFSIZE_MASK;
	txfifo.buf[newtail] = c;
	txfifo.tail = newtail;
	txfifo.size++;

	SCI1CR2 |= TIEN;
	enable_interrupts();
}

/* Interrupt driven UART read. Returns the next character in the 
 * receive buffer. This function blocks until a character is available. */
char uart_getchar(void)
{
	disable_interrupts();
	while (rxfifo.size == 0)
	{
		// Enable interrupts and enter WAIT mode.
		// Note that the WAI instruction is actually executed after the
		// CLI instruction before any interrupts are allowed.
		enable_interrupts();
		wait();   
		disable_interrupts();
	}

	uint8_t newhead = (rxfifo.head + 1) & BUFSIZE_MASK;
	rxfifo.head = newhead;
	rxfifo.size--;
	char c = rxfifo.buf[newhead];

	enable_interrupts();
	return c;
}

/* Handler for RDRF (receive data register full) event.
 * This means that a byte has been shifted into SCIDRL and we should
 * read it and store it into the receive FIFO buffer. 
 * (This function is called by the uart_interrupt interrupt handler.)*/
static void handle_rx_data_register_full(void)
{
	char c = SCI1DRL;    // Read data byte from SCI.

	if (rxfifo.size == BUFSIZE)
		return;

	uint8_t newtail = (rxfifo.tail + 1) & BUFSIZE_MASK;
	rxfifo.buf[newtail] = c;
	rxfifo.tail = newtail;
	rxfifo.size++;
}

/* Handler for TDRE (transmit data register empty) event.
 * The SCIDRL register is a single byte buffer between this user code and
 * the SCI module itself. The TDRE event means that the byte previously
 * written to SCIDRL for transmission has been retrieved by the SCI module
 * and we can write the next byte to be sent to SCIDRL. If there are no
 * more bytes to transmit, then we disable the TDRE interrupt until
 * more bytes are put into the transmit buffer with uart_putchar().
 * (This function is called by the uart_interrupt interrupt handler.)*/
static void handle_transmit_data_register_empty(void)
{
	if (txfifo.size != 0)
	{
		uint8_t newhead = (txfifo.head + 1) & BUFSIZE_MASK;
		char c = txfifo.buf[newhead];
		SCI1DRL = c;          /* Write to the SCI data register. */
		txfifo.head = newhead;
		txfifo.size--;
	}
	else
	{
		// Disable further Tx interrupts until more bytes put into Tx buffer.
		SCI1CR2 &= ~TIEN;     
	}
}

/* The interrupt service routine for the SCI1 module. It handles the 
 * SCI1 interrupt which is triggered by both receive and transmit events. */
ISR(uart_interrupt)
{
	if (SCI1SR1 & RDRF)
		handle_rx_data_register_full();
	if (SCI1SR1 & TDRE)
		handle_transmit_data_register_empty();
}

