/***********************************************************************************************************************
PicoMite MMBasic

Serial.c

<COPYRIGHT HOLDERS>  Geoff Graham, Peter Mather
Copyright (c) 2021, <COPYRIGHT HOLDERS> All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 
1.	Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2.	Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
    in the documentation and/or other materials provided with the distribution.
3.	The name MMBasic be used when referring to the interpreter in any documentation and promotional material and the original copyright message be displayed 
    on the console at startup (additional copyright messages may be added).
4.	All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed 
    by the <copyright holder>.
5.	Neither the name of the <copyright holder> nor the names of its contributors may be used to endorse or promote products derived from this software 
    without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDERS> AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDERS> BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

************************************************************************************************************************/


#include "MMBasic_Includes.h"
#include "Hardware_Includes.h"
#include "hardware/uart.h"
#include "hardware/irq.h"

// variables for com1
int com1 = 0;														// true if COM1 is enabled
int com1_buf_size;													// size of the buffer used to receive chars
int com1_baud = 0;													// determines the baud rate
char *com1_interrupt;												// pointer to the interrupt routine
int com1_ilevel;													// number nbr of chars in the buffer for an interrupt
int com1_TX_complete = false;
unsigned char *com1Rx_buf;											// pointer to the buffer for received characters
volatile int com1Rx_head, com1Rx_tail;								// head and tail of the ring buffer for com1
unsigned char *com1Tx_buf;											// pointer to the buffer for transmitted characters
volatile int com1Tx_head, com1Tx_tail;								// head and tail of the ring buffer for com1
volatile int com1complete=1;
uint16_t Rx1Buffer;
char com1_mode;                                                     // keeps track of the settings for com4
unsigned char com1_bit9 = 0;                                        // used to track the 9th bit
extern uint32_t ticks_per_microsecond;

// variables for com2
int com2 = 0;														// true if COM2 is enabled
int com2_buf_size;													// size of the buffer used to receive chars
int com2_baud = 0;													// determines the baud rate
char *com2_interrupt;												// pointer to the interrupt routine
int com2_ilevel;													// number nbr of chars in the buffer for an interrupt
int com2_TX_complete = false;
unsigned char *com2Rx_buf;											// pointer to the buffer for received characters
volatile int com2Rx_head, com2Rx_tail;								// head and tail of the ring buffer for com2 Rx
unsigned char *com2Tx_buf;											// pointer to the buffer for transmitted characters
volatile int com2Tx_head, com2Tx_tail;								// head and tail of the ring buffer for com2 Tx
volatile int com2complete=1;
char com2_mode;                                                     // keeps track of the settings for com4
unsigned char com2_bit9 = 0;                                        // used to track the 9th bit
// uart interrupt handler
void on_uart_irq0() {
    if(uart_is_readable(uart0)) {
		char cc = uart_getc(uart0); 
		if(!(Option.SerialConsole & 1)){
			if(GPSchannel==1){
				*gpsbuf=cc;
				gpsbuf++;
				gpscount++;
				if((char)cc==10 || gpscount==128){
					if(gpscurrent){
						*gpsbuf=0;
						gpscurrent=0;
						gpscount=0;
						gpsbuf=gpsbuf1;
						gpsready=gpsbuf2;
					} else {
						*gpsbuf=0;
						gpscurrent=1;
						gpscount=0;
						gpsbuf=gpsbuf2;
						gpsready=gpsbuf1;
					}
				}
			} else {
				com1Rx_buf[com1Rx_head]  =cc;   // store the byte in the ring buffer
				com1Rx_head = (com1Rx_head + 1) % com1_buf_size;     // advance the head of the queue
				if(com1Rx_head == com1Rx_tail) {                           // if the buffer has overflowed
					com1Rx_tail = (com1Rx_tail + 1) % com1_buf_size; // throw away the oldest char
				}
			}
		} else {
	  		ConsoleRxBuf[ConsoleRxBufHead]  = cc;   // store the byte in the ring buffer
	  		if(BreakKey && ConsoleRxBuf[ConsoleRxBufHead] == BreakKey) {// if the user wants to stop the progran
	  			MMAbort = true;                                        // set the flag for the interpreter to see
	  			ConsoleRxBufHead = ConsoleRxBufTail;                    // empty the buffer
	  		} else {
	  			ConsoleRxBufHead = (ConsoleRxBufHead + 1) % CONSOLE_RX_BUF_SIZE;     // advance the head of the queue
	  			if(ConsoleRxBufHead == ConsoleRxBufTail) {                           // if the buffer has overflowed
	  				ConsoleRxBufTail = (ConsoleRxBufTail + 1) % CONSOLE_RX_BUF_SIZE; // throw away the oldest char
	  			}
	  		}
		}
    }
    if(uart_is_writable(uart0)){
		if(!(Option.SerialConsole & 1)){
			if(com1Tx_head != com1Tx_tail) {
				uart_putc_raw(uart0,com1Tx_buf[com1Tx_tail]);
				com1Tx_tail = (com1Tx_tail + 1) % TX_BUFFER_SIZE;       // advance the tail of the queue
			} else {
				uart_set_irq_enables(uart0, true, false);
				com1_TX_complete=true;
			}
		} else {
	  		if(ConsoleTxBufTail != ConsoleTxBufHead) {
	  			uart_putc_raw(uart0,ConsoleTxBuf[ConsoleTxBufTail]);
	  			ConsoleTxBufTail = (ConsoleTxBufTail + 1) % CONSOLE_TX_BUF_SIZE; // advance the tail of the queue
	  		} else {
				uart_set_irq_enables(uart0, true, false);
	  		}
		}
    }
}
void on_uart_irq1() {
	if (uart_is_readable(uart1)) {
		char cc = uart_getc(uart1); 
		if(!(Option.SerialConsole & 2)){
			if(GPSchannel==2){
				*gpsbuf=cc;
				gpsbuf++;
				gpscount++;
				if((char)cc==10 || gpscount==128){
					if(gpscurrent){
						*gpsbuf=0;
						gpscurrent=0;
						gpscount=0;
						gpsbuf=gpsbuf1;
						gpsready=gpsbuf2;
					} else {
						*gpsbuf=0;
						gpscurrent=1;
						gpscount=0;
						gpsbuf=gpsbuf2;
						gpsready=gpsbuf1;
					}
				}
			} else {
				com2Rx_buf[com2Rx_head]  = cc;   // store the byte in the ring buffer
				com2Rx_head = (com2Rx_head + 1) % com2_buf_size;     // advance the head of the queue
				if(com2Rx_head == com2Rx_tail) {                           // if the buffer has overflowed
					com2Rx_tail = (com2Rx_tail + 1) % com2_buf_size; // throw away the oldest char
				}
			}
		} else {
	  		ConsoleRxBuf[ConsoleRxBufHead]  = cc;   // store the byte in the ring buffer
	  		if(BreakKey && ConsoleRxBuf[ConsoleRxBufHead] == BreakKey) {// if the user wants to stop the progran
	  			MMAbort = true;                                        // set the flag for the interpreter to see
	  			ConsoleRxBufHead = ConsoleRxBufTail;                    // empty the buffer
	  		} else {
	  			ConsoleRxBufHead = (ConsoleRxBufHead + 1) % CONSOLE_RX_BUF_SIZE;     // advance the head of the queue
	  			if(ConsoleRxBufHead == ConsoleRxBufTail) {                           // if the buffer has overflowed
	  				ConsoleRxBufTail = (ConsoleRxBufTail + 1) % CONSOLE_RX_BUF_SIZE; // throw away the oldest char
	  			}
	  		}
		}
    }
    if(uart_is_writable(uart1)){
		if(!(Option.SerialConsole & 2)){
			if(com2Tx_head != com2Tx_tail) {
				uart_putc_raw(uart1,com2Tx_buf[com2Tx_tail]);
				com2Tx_tail = (com2Tx_tail + 1) % TX_BUFFER_SIZE;       // advance the tail of the queue
			} else {
				uart_set_irq_enables(uart1, true, false);
				com2_TX_complete=true;
			}
		} else {
	  		if(ConsoleTxBufTail != ConsoleTxBufHead) {
	  			uart_putc_raw(uart1,ConsoleTxBuf[ConsoleTxBufTail]);
	  			ConsoleTxBufTail = (ConsoleTxBufTail + 1) % CONSOLE_TX_BUF_SIZE; // advance the tail of the queue
	  		} else {
				uart_set_irq_enables(uart1, true, false);
	  		}
		}
    }
}

/***************************************************************************************************
Initialise the serial function including the timer and interrupts.
****************************************************************************************************/

#define UART_ID  (uart ? uart1: uart0)
void invert_serial(int uart){
	int txpin, rxpin;
	if(uart==0){
		txpin=PinDef[UART0TXpin].GPno;
		rxpin=PinDef[UART0RXpin].GPno;
	} else {
		txpin=PinDef[UART1TXpin].GPno;
		rxpin=PinDef[UART1RXpin].GPno;
	}
	gpio_set_outover(txpin, GPIO_OVERRIDE_INVERT);
	gpio_set_inover(rxpin, GPIO_OVERRIDE_INVERT);
}
void MIPS16 setupuart(int uart, int s2,int parity, int b7, int baud, int inv){
	uart_init(UART_ID,baud);
    uart_set_hw_flow(UART_ID, false, false);
    uart_set_format(UART_ID, b7, s2, parity);
    uart_set_fifo_enabled(UART_ID, false);
	if(inv)invert_serial(uart);
    int UART_IRQ = (UART_ID == uart0 ? UART0_IRQ : UART1_IRQ);
    if(uart){
		irq_set_exclusive_handler(UART_IRQ, on_uart_irq1);
    	irq_set_enabled(UART_IRQ, true);
	} else {
		irq_set_exclusive_handler(UART_IRQ, on_uart_irq0);
    	irq_set_enabled(UART_IRQ, true);
	}
    uart_set_irq_enables(UART_ID, true, false);
	uart_set_irq_enables(UART_ID, true, false); 
}
/***************************************************************************************************
Initialise the serial function including the timer and interrupts.
****************************************************************************************************/
void MIPS16 SerialOpen(unsigned char *spec) {
	int baud, i, s2, parity, b7, bufsize, inv=0, ilevel=1;
	char *interrupt;

	getargs(&spec, 21, (unsigned char *)":,");										// this is a macro and must be the first executable stmt
	if(argc != 2 && (argc & 0x01) == 0) error("COM specification");

    b7 = 8;
	parity = UART_PARITY_NONE;
	s2 = 1;
    for(i = 0; i < 5; i++) {
    	if(str_equal(argv[argc - 1], (unsigned char *)"EVEN")) {
    		if(parity)error("Syntax");
    		else {parity = UART_PARITY_EVEN; argc -= 2; }	// set even parity
    	}
    	if(str_equal(argv[argc - 1], (unsigned char *)"ODD")) {
    		if(parity)error("Syntax");
    		else {parity = UART_PARITY_ODD; argc -= 2; }	// set even parity
    	}
    	if(str_equal(argv[argc - 1], (unsigned char *)"INV")) { inv = 1; argc -= 2; };	// invert the serial port
    	if(str_equal(argv[argc - 1], (unsigned char *)"DE")) error("DE not Supported");	// get the two stop bit option
	   	if(str_equal(argv[argc - 1], (unsigned char *)"OC")) error("OC not Supported");	// get the two stop bit option
    	if(str_equal(argv[argc - 1], (unsigned char *)"9BIT")) error("9BIT not Supported");	// get the two stop bit option
     	if(str_equal(argv[argc - 1], (unsigned char *)"S2")) { s2 = 2; argc -= 2; }	// get the two stop bit option
    	if(str_equal(argv[argc - 1], (unsigned char *)"7BIT")) { b7 = 7; argc -= 2; }	// set the 7 bit byte option
    }
	if(argc < 1 || argc > 9) error("COM specification");

	if(argc >= 3 && *argv[2]) {
		baud = getint(argv[2],Option.CPU_Speed*1000/16/65535,921600);									// get the baud rate as a number
	} else
		baud = COM_DEFAULT_BAUD_RATE;

	if(argc >= 5 && *argv[4])
		bufsize = getinteger(argv[4]);								// get the buffer size as a number
	else
		bufsize = COM_DEFAULT_BUF_SIZE;

	if(argc >= 7) {
    	InterruptUsed = true;
    	argv[6]=(unsigned char *)strupr((char *)argv[6]);
		interrupt = (char *)GetIntAddress(argv[6]);							// get the interrupt location
	} else
		interrupt = NULL;

	if(argc == 9) {
		ilevel = getinteger(argv[8]);								// get the buffer level for interrupt as a number
		if(ilevel < 1 || ilevel > bufsize) error("COM specification");
	} else
		ilevel = 1;

/*	if(argc >= 11) {
    	InterruptUsed = true;
    	argv[6]=strupr(argv[10]);
		TXinterrupt = GetIntAddress(argv[10]);							// get the interrupt location
	} else
		TXinterrupt = NULL;
*/

	if(spec[3] == '1') {
	///////////////////////////////// this is COM1 ////////////////////////////////////

		if(com1) error("Already open");
		if(UART0TXpin==99 || UART0RXpin==99)error("Pins not set for COM1");

 		com1_buf_size = bufsize;									// extracted from the comspec above
		com1_interrupt = interrupt;
		com1_ilevel	= ilevel;

		// setup for receive
		com1Rx_buf = GetMemory(com1_buf_size);						// setup the buffer
		com1Rx_head = com1Rx_tail = 0;
		ExtCfg(UART0RXpin, EXT_COM_RESERVED, 0);                   // reserve the pin for com use


		// setup for transmit
		com1Tx_buf = GetMemory(TX_BUFFER_SIZE);						// setup the buffer
		com1Tx_head = com1Tx_tail = 0;
		ExtCfg(UART0TXpin, EXT_COM_RESERVED, 0);

        setupuart(0, s2, parity, b7, baud, inv);
        com1 = true;
		uSec(1000);
		com1Rx_head = com1Rx_tail = 0;
		com1Tx_head = com1Tx_tail = 0;

	}
	else if (spec[3] == '2') {
	///////////////////////////////// this is COM2 ////////////////////////////////////

		if(com2) error("Already open");
		if(UART1TXpin==99 || UART1RXpin==99)error("Pins not set for COM2");

 		com2_buf_size = bufsize;									// extracted from the comspec above
		com2_interrupt = interrupt;
		com2_ilevel	= ilevel;
//		com2_TX_interrupt = TXinterrupt;
//		com2_TX_complete = false;

		// setup for receive
		com2Rx_buf = GetMemory(com2_buf_size);						// setup the buffer
		com2Rx_head = com2Rx_tail = 0;
		ExtCfg(UART1RXpin, EXT_COM_RESERVED, 0);                   // reserve the pin for com use


		// setup for transmit
		com2Tx_buf = GetMemory(TX_BUFFER_SIZE);						// setup the buffer
		com2Tx_head = com2Tx_tail = 0;
		ExtCfg(UART1TXpin, EXT_COM_RESERVED, 0);                   // reserve the pin for com use
        setupuart(1, s2, parity, b7, baud, inv);
        com2 = true;
		uSec(1000);
		com2Rx_head = com2Rx_tail = 0;
		com2Tx_head = com2Tx_tail = 0;
	}
}

/***************************************************************************************************
Close a serial port.
****************************************************************************************************/
void MIPS16 SerialClose(int comnbr) {

	if(comnbr == 1 && com1) {
		uart_deinit(uart0);
		com1 = false;
		com1_interrupt = NULL;
		if(UART0RXpin!=99)ExtCfg(UART0RXpin, EXT_NOT_CONFIG, 0);
		if(UART0TXpin!=99)ExtCfg(UART0TXpin, EXT_NOT_CONFIG, 0);
		if(com1Rx_buf!=NULL){FreeMemory(com1Rx_buf); com1Rx_buf=NULL;}
		if(com1Tx_buf!=NULL){FreeMemory(com1Tx_buf); com1Tx_buf=NULL;}
	}

	else if(comnbr == 2 && com2) {
		uart_deinit(uart1);
		com2 = false;
		com2_interrupt = NULL;
		if(UART1RXpin!=99)ExtCfg(UART1RXpin, EXT_NOT_CONFIG, 0);
		if(UART1TXpin!=99)ExtCfg(UART1TXpin, EXT_NOT_CONFIG, 0);
		if(com2Rx_buf!=NULL){FreeMemory(com2Rx_buf); com2Rx_buf=NULL;}
		if(com2Tx_buf!=NULL){FreeMemory(com2Tx_buf); com2Tx_buf=NULL;}
	}
}



/***************************************************************************************************
Add a character to the serial output buffer.
****************************************************************************************************/
unsigned char SerialPutchar(int comnbr, unsigned char c) {
	if(comnbr == 1) {
      	while(com1Tx_tail == ((com1Tx_head + 1) % TX_BUFFER_SIZE))    // wait if the buffer is full
          if(MMAbort) {                                             // allow the user to abort a hung serial port
              com1Tx_tail = com1Tx_head = 0;                        // clear the buffer
              longjmp(mark, 1);                                     // and abort
        }
        int empty=uart_is_writable(uart0);
		com1Tx_buf[com1Tx_head] = c;							// add the char
		com1Tx_head = (com1Tx_head + 1) % TX_BUFFER_SIZE;		   // advance the head of the queue
		if(empty){
	        uart_set_irq_enables(uart0, true, true);
			irq_set_pending(UART0_IRQ);
		}
	}
	else if(comnbr == 2) {
      	while(com2Tx_tail == ((com2Tx_head + 1) % TX_BUFFER_SIZE))    // wait if the buffer is full
          if(MMAbort) {                                             // allow the user to abort a hung serial port
              com2Tx_tail = com2Tx_head = 0;                        // clear the buffer
              longjmp(mark, 1);                                     // and abort
        }
        int empty=uart_is_writable(uart1);
		com2Tx_buf[com2Tx_head] = c;							// add the char
		com2Tx_head = (com2Tx_head + 1) % TX_BUFFER_SIZE;		   // advance the head of the queue
		if(empty){
	        uart_set_irq_enables(uart1, true, true);
			irq_set_pending(UART1_IRQ);
		}
	}
	return c;
}



/***************************************************************************************************
Get the status the serial receive buffer.
Returns the number of characters waiting in the buffer
****************************************************************************************************/
int SerialRxStatus(int comnbr) {
	int i = 0;
	if(comnbr == 1) {
	    uart_set_irq_enables(uart0, false, true);
		i = com1Rx_head - com1Rx_tail;
		uart_set_irq_enables(uart0, true, true);
		if(i < 0) i += com1_buf_size;
	}
	else if(comnbr == 2) {
	    uart_set_irq_enables(uart1, false, true);
		i = com2Rx_head - com2Rx_tail;
	    uart_set_irq_enables(uart1, true, true);
		if(i < 0) i += com2_buf_size;
	}
	return i;
}


/***************************************************************************************************
Get the status the serial transmit buffer.
Returns the number of characters waiting in the buffer
****************************************************************************************************/
int SerialTxStatus(int comnbr) {
	int i = 0;
	if(comnbr == 1) {
		i = com1Tx_head - com1Tx_tail;
		if(i < 0) i += TX_BUFFER_SIZE;
	}
	else if(comnbr == 2) {
		i = com2Tx_head - com2Tx_tail;
		if(i < 0) i += TX_BUFFER_SIZE;
	}
	return i;
}



/***************************************************************************************************
Get a character from the serial receive buffer.
Note that this is returned as an integer and -1 means that there are no characters available
****************************************************************************************************/
int SerialGetchar(int comnbr) {
	int c;
    c = -1;                                                         // -1 is no data
	if(comnbr == 1) {
	    uart_set_irq_enables(uart0, false, true);
		if(com1Rx_head != com1Rx_tail) {                            // if the queue has something in it
			c = com1Rx_buf[com1Rx_tail];                            // get the char
 			com1Rx_tail = (com1Rx_tail + 1) % com1_buf_size;        // and remove from the buffer
		}
		uart_set_irq_enables(uart0, true, true);
	}
	else if(comnbr == 2) {

	    uart_set_irq_enables(uart1, false, true);
		if(com2Rx_head != com2Rx_tail) {                            // if the queue has something in it
			c = com2Rx_buf[com2Rx_tail];                            // get the char
 			com2Rx_tail = (com2Rx_tail + 1) % com2_buf_size;        // and remove from the buffer
		}
	    uart_set_irq_enables(uart1, true, true);
	}
	return c;
}

