/*
    SCN2681_DUART.c, Copyright  (c) by George Fankhauser,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public Licence.
 
    A full copy of the GNU GPL is provided in the file COPYING found in the 
    development root of Topsy.
*/
/*
	File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/IO/mips/Drivers/SCN2681_DUART.c,v $
 	Author(s):             George Fankhauser
 	Affiliation:           ETH Zuerich, TIK
 	Version:               $Revision: 2936 $
 	Creation Date:         
 	Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $      by: $Author: baderma $
	
*/

#include "Drivers/SCN2681_DUART.h"
#include "Messages.h"
#include "IODevice.h"
#include "IOHal.h"
#include "Lock.h"
#include "Configuration.h"
#include "TMIPC.h"

IODeviceDesc scn2681_uart_b = {
                                NULL, NULL,              /* pointers for doubly linked list */
                                "ttyb",                  /* device name */
                                (Address)UART_B_BASE,    /* device base address */
                                NULL,                    /* buffer must be allocated by driver */
                                -1,                      /* ttyb interrupt is handled by ttya handler */
                                NULL,                    /* device interrupt handler */
                                0,                       /* thread id filled in after start */
                                devSCN2681_read,         /* device read function */
                                devSCN2681_write,        /* device write function */
                                devSCN2681_close,        /* device close function */
                                devSCN2681_init,         /* device init function */
                                //-CJ I don't know but on the IDT board the ttyb should not be initaliesed twice
                                //-CJ I'm sorry but this creates some other bugs :(
                                //	NULL,                    /* device init function */
                                NULL,                    /* private msg handler */
                                FALSE,                   /* initialisation state, normally FALSE */
                                NULL                     /* data extension */
                              };

IODeviceDesc scn2681_uart_a = {
                                NULL, NULL,              /* pointers for doubly linked list */
                                "ttya",                  /* device name */
                                (Address)UART_A_BASE,    /* device base address */
                                NULL,                    /* buffer must be allocated by driver */
                                DUARTINT,                /* interrupt */
                                (InterruptHandler)devSCN2681_interruptHandler,
                                /* ttya & b interrupt handler */
                                0,                       /* thread id filled in after start */
                                devSCN2681_read,         /* device read function */
                                devSCN2681_write,        /* device write function */
                                devSCN2681_close,        /* device close function */
                                devSCN2681_init,         /* device init function */
                                NULL,                    /* private msg handler */
                                FALSE,                   /* initialisation state, normally FALSE */
                                &scn2681_uart_b           /* data extension */
                              };

static int checkWaiting(IODevice dev, char* rx)
{
  devSCN2681 uart;
  Message msg;
  ThreadId to;

  uart = (devSCN2681)(dev->extension);
  if ( uart->wait == 0 )
    return 0;

  uart->waitbuf[0] = *rx;

  msg.from = dev->threadId;
  msg.id = IO_READREPLY;
  msg.enveloppe = uart->waitbuf;
  msg.msg.ioReadReply.errorCode = IO_READOK;
  msg.msg.ioReadReply.bytesRead = 1;
  to = uart->wait;

  //-CJ	printf("@@@ wake up Thread % li from % li read %02x '%c'\n",
  //-CJ			to, dev->threadId, uart->waitbuf[0], uart->waitbuf[0]);

  uart->wait = 0;
  uart->waitbuf = NULL;

  if ( kSend(to, &msg) != TM_OK )
    return 0;
  else
    return 1;
}

void devSCN2681_interruptHandler(IODevice this)
{
  devSCN2681 uartA, uartB;
  IODevice devA, devB;
  char* rxA;
  char* rxB;
  char* statusA;
  char* statusB;

  devA = this;
  uartA = (devSCN2681)(this->extension);
  devB = uartA->otherChannel;
  uartB = (devSCN2681)(devB->extension);
  rxA = (char*)((unsigned long)(devA->base) + RX_REGISTER);
  rxB = (char*)((unsigned long)(devB->base) + RX_REGISTER);
  statusA = (char*)((unsigned long)(devA->base) + STATUS_REGISTER);
  statusB = (char*)((unsigned long)(devB->base) + STATUS_REGISTER);

  /* Since the interrupt on this device is shared, we need to check which
   * channel received a character
   */
  if ((*statusA & RECV_MASK) == RECV_MASK)
  {
    if ( checkWaiting(devA, rxA) == 1 )
      return;

    if ((uartA->in + 1) % SCN2681_BUFSIZE == uartA->out)
    {
      WARNING("UARTA overflow\n");
      return;
    }
    devA->buffer[uartA->in] = *rxA;
    uartA->in = ((uartA->in + 1) % SCN2681_BUFSIZE);
  }
  if ((*statusB & RECV_MASK) == RECV_MASK)
  {
    if ( checkWaiting(devB, rxB) == 1 )
      return;

    if ((uartB->in + 1) % SCN2681_BUFSIZE == uartB->out)
    {
      WARNING("UARTB overflow\n");
      return;
    }
    devB->buffer[uartB->in] = *rxB;
    uartB->in = ((uartB->in + 1) % SCN2681_BUFSIZE);
  }
}



Error devSCN2681_init(IODevice this)
{
  char* command = (char*)((unsigned long)(this->base) + COMMAND_REGISTER);
  char* mode = (char*)((unsigned long)(this->base) + MODE_REGISTER);
  char* status = (char*)((unsigned long)(this->base) + STATUS_REGISTER);
  void* tmp;
  char* interrupt = (char*)((unsigned long)(this->base) + INTERRUPT_REGISTER);
  devSCN2681 uart;

  /* Both UART register sets are identical, except for the interrupt
      * mask register
      */
  if (this->interruptHandler == NULL)
  {
    interrupt -= UART_REGISTER_SET_SIZE;
  }

  /* device init */
  *command = 0xa;	/* disable rx/tx */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  *mode = 0x13;	/* no parity - 8 bits/char */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  *mode = 0x7;	/* 1 stop bit */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  *status = 0xbb;	/* 9600 baud */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  *interrupt = 0x22;	/* both receiver interrupts enabled */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  *command = 0x5;	/* enable receive/transmit */
  ioDelayAtLeastCycles( NBCYCLESFORDELAY);

  /* buffers init - be careful to check for existing buffers */
  if (!this->isInitialised)
  {
    tmp = this->extension;
    hmAlloc(&(this->extension), sizeof(devSCN2681Desc));
    hmAlloc((Address*)&(this->buffer), SCN2681_BUFSIZE);
    uart = (devSCN2681)this->extension;
    uart->otherChannel = (IODevice)tmp;
    this->isInitialised = TRUE;
  }
  else
  {
    uart = (devSCN2681)this->extension;
  }
  uart->in = 0;
  uart->out = 0;

  return IO_INITOK;
}


Error devSCN2681_read(IODevice this, ThreadId threadId, char* buffer,
                      long int* size)
{
  long int i = 0;
  devSCN2681 uart;

  uart = (devSCN2681)this->extension;
  while (TRUE)
  {
    /* no more characters available, buffer empty
     */
    if (uart->in == uart->out)
    {
      break;
    }
    /* user is satisfied
     */
    if (i >= *size)
    {
      break;
    }

    buffer[i++] = this->buffer[uart->out];
    uart->out = ((uart->out + 1) % SCN2681_BUFSIZE);
  }
#if 1
  if ( i == 0 && uart->wait == 0)
  {
    /* wait until we have some data to read */
    uart->wait = threadId;
    uart->waitbuf = buffer;
    //-CJ		printf("@@@ Thread % li put into the wait queue\n", threadId);
    return IO_LATER;
  }
#endif
  *size = i;
  return IO_READOK;
}


Error devSCN2681_write(IODevice this, ThreadId threadId, char* buffer, long int* size)
{
  char* transmit = (char*)((unsigned long)(this->base) + TX_REGISTER);
  char* status = (char*)((unsigned long)(this->base) + STATUS_REGISTER);
  long int i = 0;

  for (i = 0; i < *size; i++)
  {
    while ((*status & READY_TO_SEND) != READY_TO_SEND)
    {
      ioDelayAtLeastCycles( NBCYCLESFORDELAY);
    }				/* wait for uart to become ready */
    *transmit = buffer[i];		/* send it finally */

    /* Will be moved into a user stringlib *
     * Translate newlines into carriage returns plus newlines for
     * correct terminal output
     *
     *	if (buffer[i] == '\n') {
     *	while ((*status & READY_TO_SEND) != READY_TO_SEND) {
     *	ioDelayAtLeastCycles( NBCYCLESFORDELAY);
     *	}							*//* wait for uart to become ready *//*
     *	    *transmit = '\r';		*//* send it finally *//*
     *	} 
     */
    ioDelayAtLeastCycles( NBCYCLESFORDELAY);
  }
  return IO_WRITEOK;
}


Error devSCN2681_close(IODevice this)
{
  /* this message is forwarded from ioThread */
  /* release buffers if any */

  /* this does not work at the moment (you get starnge errors) */
  /*  if (this->isInitialised) {
   
      hmFree((Address)this->buffer);     *//* Deallocate buffer space *//*
      hmFree((Address)this->extension);  *//* Deallocate descriptor space *//*
   
      this->isInitialised = FALSE;       *//* Set Initialized flag *//*
    }
  */
  return IO_CLOSEOK;
}
