/// \file main.c Simple UART device driver. No speed setting yet.
// \author Panu-Kristian Poiksalo, VLSI Solution Oy

/* For free support for VSIDE, please visit www.vsdsp-forum.com */

// If you add the libary name to S:CONFIG.TXT, and put the library 
// to S:SYS, it will be loaded and run during boot-up. Use 8.3 file names.

// A short example on how to use the UART file interface:
//	void *libUart=LoadLibrary("devUart"); // (assuming this project is called 'devUart')
//	FILE *uart = (FILE *)RunProgram("devUart",NULL);
//	fprintf(uart,"Hello, world.\n");
//  DropLibrary(libUart);

#include <vo_stdio.h>
#include <volink.h>     // Linker directives like DLLENTRY
#include <apploader.h>  // RunLibraryFunction etc
#include <string.h>
#include <timers.h>
#include <vs1005h.h>
#include <imem.h>
#include <ringbuf.h>
#include <kernel.h>
#include <consolestate.h>
#include <uimessages.h>
#include <ctype.h>


/* If one below a power of two, memory usage efficiency is best */
#define UART_RX_BUFFER_SIZE (256-1)
/* If one below a power of two, memory usage efficiency is best */
#define UART_TX_BUFFER_SIZE (256-1)
__mem_y char __align uartRxBuffer[UART_RX_BUFFER_SIZE];
__mem_y char __align uartTxBuffer[UART_TX_BUFFER_SIZE];

struct Uart {
  FILE *origIn, *origOut;
  u_int16 origIntEna0Mask;
  u_int32 origRxVec, origTxVec;
  __mem_y char *rxRd, *rxWr, *txRd, *txWr;
} uart = {
  0, 0, 0, 0, 0, uartRxBuffer, uartRxBuffer, uartTxBuffer, uartTxBuffer
};


#if 0
void WatchdogReset(void) {
  //PERIP(UART_DATA) = 'R';
  PERIP(WDOG_CF) = 1;
  PERIP(WDOG_KEY) = 0x4ea9;
  while (1)
    ;
}
#endif
	
void IntUartTxC(void) {
  while (!(PERIP(UART_STATUS) & UART_ST_TXFULL) && uart.txWr != uart.txRd) {
    PERIP(UART_DATA) = *uart.txRd;
    uart.txRd = ringmodY(uart.txRd, MAKEMODF(UART_TX_BUFFER_SIZE));
  }
}

void IntUartRxC(void) {
  static u_int16 inProtocol = 0;	
  static u_int32 value;
  static u_int16 address;
  char c = PERIP(UART_DATA);

  if (!(appFlags & APP_FLAG_RAWTTY)) {
    if (c==0x03) { //Ctrl-C
      static u_int16 count = 0;
      if (appFlags & APP_FLAG_QUIT) {
	count++;
	//PERIP(UART_DATA) = '0'+count;
	if (count==2) { // 3rd Ctrl-C -> force watchdog reset
	  PERIP(WDOG_CF) = 1;
	  PERIP(WDOG_KEY) = 0x4ea9;
	  while (1) {
	    //Wait for watchdog reset				
	  }
	}
      } else {
	count=0;
	appFlags |= APP_FLAG_QUIT;
      }
    } else if (c==0x02) { //Ctrl-B
      inProtocol = 1;
      value = 0;
      return;
    }
    if (inProtocol) {
      if (isxdigit(c)) {
	c = tolower(c);
	value = (value << 4) | (c-(isdigit(c)?'0':'a'));
      } else if (c == 'm') {
	address = value;
	value = 0;
      } else if (c == 's') {
	SystemUiMessage(0, address, value);
	inProtocol = 0;
      }
      return;		
    }
  }

  *uart.rxWr = c;
  uart.rxWr = ringmodY(uart.rxWr, MAKEMODF(UART_RX_BUFFER_SIZE));
}



void UartPutChar(char c) {
  __mem_y char *nextTxWr = ringmodY(uart.txWr, MAKEMODF(UART_TX_BUFFER_SIZE));

  while (nextTxWr == uart.txRd) {
    Delay(1);
  }

  Disable();
  if (!(PERIP(UART_STATUS) & UART_ST_TXFULL) && uart.txWr == uart.txRd) {
    PERIP(UART_DATA) = c;
  } else {
    *uart.txWr = c;
    uart.txWr = nextTxWr;
  }
  Enable();
}

char UartGetChar(void) {
  char ch;
  while (uart.rxRd == uart.rxWr) {
    Delay(1);
  }
  ch = *uart.rxRd;
  uart.rxRd = ringmodY(uart.rxRd, MAKEMODF(UART_RX_BUFFER_SIZE));
  return ch;
}

extern SIMPLE_FILE uartInFile;

IOCTL_RESULT UartIoctl(register __i0 VO_FILE *self, s_int16 request, IOCTL_ARGUMENT arg) {
  if (request == IOCTL_TEST) {
    return (self == &uartInFile) ?
      (s_int16)(uart.rxWr-uart.rxRd)%(UART_RX_BUFFER_SIZE) :
      (s_int16)(uart.txWr-uart.txRd)%(UART_TX_BUFFER_SIZE);
  }
  return S_ERROR;
};

u_int16 UartWrite(register __i0 VO_FILE *self, void *buf, u_int16 sourceIndex, u_int16 bytes) {
  UartPutChar(*((u_int16 *)buf)); //Send character to raw UART port
  return 1;
};

u_int16 UartRead(register __i0 VO_FILE *self, void *buf, u_int16 destinationIndex, u_int16 bytes) {
  char ch = UartGetChar();
  MemCopyPackedBigEndian(buf,destinationIndex,&ch,1,1);
  return 1;
};

u_int16 StdInWrite(register __i0 VO_FILE *self, void *buf, u_int16 sourceIndex, u_int16 bytes) {
  *uart.rxWr = ((char*)buf)[0]>>8;
  uart.rxWr = ringmodY(uart.rxWr, MAKEMODF(UART_RX_BUFFER_SIZE));
  return 1;
};



const FILEOPS ConsoleInFileOperations = { NULL, NULL, UartIoctl, UartRead, StdInWrite };
//DLLENTRY(uartInFile)
SIMPLE_FILE uartInFile = {__MASK_PRESENT | __MASK_OPEN | __MASK_READABLE | __MASK_WRITABLE | __MASK_FILE, NULL, &ConsoleInFileOperations};

const FILEOPS ConsoleOutFileOperations = { NULL, NULL, UartIoctl, UartRead, UartWrite };
//DLLENTRY(uartOutFile)
SIMPLE_FILE uartOutFile = {__MASK_PRESENT | __MASK_OPEN | __MASK_READABLE | __MASK_WRITABLE | __MASK_FILE, NULL, &ConsoleOutFileOperations};

extern u_int16 IntUartRxVector;
extern u_int16 IntUartTxVector;

void init(char *parameters) {
  uart.origIntEna0Mask = PERIP(INT_ENABLE0_HP) | ~(INTF_UART_TX|INTF_UART_RX);
  uart.origRxVec = ReadIMem((0x20+INTV_UART_RX));
  uart.origTxVec = ReadIMem((0x20+INTV_UART_TX));
  uart.origIn = vo_stdin;
  uart.origOut = vo_stdout;
}

// Library finalization code. This is called when DropLibrary is called
void fini(void) {
  PERIP(INT_ENABLE0_HP) &= uart.origIntEna0Mask;
  WriteIMem((0x20+INTV_UART_RX), uart.origRxVec);
  WriteIMem((0x20+INTV_UART_TX), uart.origTxVec);
  vo_stdin = uart.origIn;
  vo_stdout = uart.origOut;
}

// The main function returns a pointer to the UART file.
ioresult main(char *parameters) {
  int in=0, out=0;

  while (*parameters) {
    char c = tolower(*parameters);
    if (c == 'i') {
      in=1;
    } else if (c == 'o') {
      out=1;
    }
    parameters++;
  }

  if (!(in|out)) {
    in = out = 1;
  }

  if (in) {
    WriteIMem((0x20+INTV_UART_RX), ReadIMem((u_int16)(&IntUartRxVector)));
    PERIP(INT_ENABLE0_HP) |= INTF_UART_RX;
    vo_stdin = &uartInFile;
  }

  if (out) {
    WriteIMem((0x20+INTV_UART_TX), ReadIMem((u_int16)(&IntUartTxVector)));
    PERIP(INT_ENABLE0_HP) |= INTF_UART_TX;
    vo_stdout = &uartOutFile;
  }

  return S_OK;
}
