/****************************************************************************/
/* Copyright 2002 Compaq Computer Corporation.                              */
/* LAB wrapper copyright 2003 Joshua Wise.                                  */
/*                                           .                              */
/* Copying or modifying this code for any purpose is permitted,             */
/* provided that this copyright notice is preserved in its entirety         */
/* in all copies or modifications.  NEITHER COMPAQ COMPUTER CORPORATION     */
/* NOR ANY CONTRIBUTORS TO THE SOURCE OF THIS APPLICATION                   */
/* MAKE ANY WARRANTIES, EXPRESSED OR IMPLIED, AS TO THE USEFULNESS          */
/* OR CORRECTNESS OF THIS CODE OR ITS FITNESS FOR ANY PARTICULAR            */
/* PURPOSE.                                                                 */
/****************************************************************************/
/*
 * Wrapper for LAB serial.
 *
 */

#include <linux/termios.h>
#include <linux/poll.h>
#include <asm/unistd.h>
#include <asm/hardware.h>

#include <linux/bootldr/bootldr.h>
#include "serial.h"
#include "buttons.h"
#include "cpu.h"

extern long sys_poll(struct pollfd *ufds, unsigned int nfds, int timeout);
extern long sys_read(int, const char *, int);
extern long sys_write(int, const char *, int);

int packetize_output = 0;
int getc_verbose_errors = 1;
int getc_errno = 0;

extern int labserfd;

extern void setbaud(int baud);
//extern int get_pushed_char(void);
void button_check(void) {};
void serial_putnstr(struct serial_device *device,
                    const char	*str,
                    size_t	n);


int lab_char_ready(struct serial_device *sd) {
  struct pollfd pfd;
  pfd.fd=0;
  pfd.events=POLLIN|POLLPRI;
  return sys_poll(&pfd,1,0);
};

unsigned char lab_read_char(struct serial_device *sd) {
	unsigned char c;
	
	c=0;
	
	if (lab_char_ready(sd))
		sys_read(labserfd, &c, 1);
	
	return c;
};


unsigned int lab_read_status(struct serial_device *sd) {
  return 0;
};

int lab_set_baudrate(struct serial_device *sd, int speed) {
  setbaud(B115200);
  return 115200;
};

void* lab_serbase(struct serial_device *sd) {
  return 0;
};

void lab_write_char(struct serial_device *sd, unsigned char c) {
  if (!sd) return;
  if (!sd->enabled) return;
  sys_write(labserfd, &c, 1); 
};

void lab_drain_uart(struct serial_device *sd) {
  /* XXX impossible right now... */
};


static struct serial_device lab_serial = { 
  char_ready: lab_char_ready, 
  read_char: lab_read_char, 
  read_status: lab_read_status,
  write_char: lab_write_char, 
  set_baudrate: lab_set_baudrate, 
  serbase: lab_serbase,
  drain_uart: lab_drain_uart,
  enabled: 1,
  _serbase:  0
};

struct serial_device *serial = &lab_serial;

/* This function has been inspected and is not causing the
 * crash.
 * --joshua, 20030511
 */
void serial_putc(struct serial_device *device, char c) 
{

  if (device == 0x00000006)
  	panic("SIX!!!");
  if (device < 0xc0000000)
  	panic("device < 0xc0000000");
  
  if (device->enabled == 0) return;
  device->write_char(device, c);
}

byte serial_do_getc(struct serial_device *device,
    vfuncp	    idler,
    unsigned long   timeout,
    int*	    statp)
{
   byte c, rxstat;
   int	do_timeout = timeout != 0;
   int	ch;

   getc_errno = 0; /* reset errno */

   while (!device->char_ready(device)) {
       if ((ch = get_pushed_char()) != -1)
	   return (ch);
   
       if (do_timeout) {
	   if (!timeout)
	       break;
	   timeout--;
       }
       
       if (idler)
	   idler();
   }

   /* give priority to pushed chars */
   if ((ch = get_pushed_char()) != -1)
       return (ch);
   
   if (do_timeout && timeout == 0) {
       c = 0;
       rxstat = -1;
   }
   else {
       c = device->read_char(device);
       rxstat = device->read_status(device);
   }
   
#if 0
   if (rxstat) {
      getc_errno = rxstat;
      if (getc_verbose_errors && !statp) {
         putLabeledWord("RXSTAT error: ", rxstat);
      }
      if (statp)
	  *statp = rxstat;
   }
#endif
   return(c);
}

byte
serial_getc(struct serial_device *device)
{
#ifdef CONFIG_MACH_SPOT
    return (serial_do_getc(device, spot_idle, 0, NULL));
#else
    return (serial_do_getc(device, button_check, 0, NULL));
#endif
}

/*
 * Reads and returns a character from the serial port
 *  - Times out after delay iterations checking for presence of character
 *  - Sets *error_p to UART error bits or -1 on timeout
 *  - On timeout, sets *error_p to -1 and returns 0
 */
byte
serial_awaitkey(struct serial_device *device, 
    unsigned long   delay,
    int*	    error_p)
{
    return (serial_do_getc(device, button_check, delay, error_p));
}

byte serial_do_getc_seconds(struct serial_device *device,
                     vfuncp	    idler,
                     unsigned long   timeout_seconds,
                     int*	    statp)
{
  byte c, rxstat;
  int	do_timeout = timeout_seconds != 0;
  int  timed_out = 0;
  unsigned long start_time = __REG(RCNR);
  int	ch;

  getc_errno = 0; /* reset errno */

  while (!device->char_ready(device)) {
    if ((ch = get_pushed_char()) != -1)
      return (ch);
   
    if (do_timeout) {
      unsigned long time = __REG(RCNR);
      if ((time - start_time) > timeout_seconds) {
        timed_out = 1;
        break;
      }
    }
       
    if (idler)
      idler();
  }

  /* give priority to pushed chars */
  if ((ch = get_pushed_char()) != -1)
    return (ch);
   
  if (do_timeout && timed_out) {
    c = 0;
    rxstat = -1;
    if (statp)
    	*statp=-1;
  }
  else {
    c = device->read_char(device);
    rxstat = device->read_status(device);
  }
   
  return(c);
}

/*
 * Reads and returns a character from the serial port
 *  - Times out after delay seconds checking for presence of character
 *  - Sets *error_p to UART error bits or -1 on timeout
 *  - On timeout, sets *error_p to -1 and returns 0
 */
byte
serial_awaitkey_seconds(struct serial_device *device,
    unsigned long   delay_seconds,
    int*	    error_p)
{
    return (serial_do_getc_seconds(device, button_check, delay_seconds, error_p));
}

void serial_do_putnstr(struct serial_device *device,
    const char* str,
    size_t	n)
{
   if(device < 0xC0000000)
   {
   	panic("serial_do_putnstr: device < 0xc0000000");
   };
   
   if (str < 0xc0000000)
   {
   	panic("serial_do_putnstr: str < 0xc00000000");
   };
   
   while (n && *str != '\0') {
      serial_putc(device, *str) ;
      str++ ;
      n--;
   }
}

void
serial_putnstr(struct serial_device *device,
    const char	*str,
    size_t	n)
{
   if (str == NULL)
      return;
   if (str < 0xc0000000)
   {
     panic("string fault");
   };
   
   if (n > 0x10000)
   {
     panic("size fault");
   };
   
   if (device < 0xc0000000)
     panic("device fault");
   
#if defined(CONFIG_PACKETIZE)
   if (packetize_output) {
       int  len;
       char len_str[16];

       len = strlen(str);
       if (n < len)
	   len = n;

       /* add the msg header */
       dwordtodecimal(len_str, len);
       serial_do_putnstr(device, "MSG/", 4);
       len = strlen(len_str);
       len_str[len] = '/';
       ++len;
       len_str[len] = '\0';
       serial_do_putnstr(device, len_str, len);
   }
#endif   
/* BUGBUG */
#warning This is a quick hack to fix a random NULL pointer.
#warning This should probably be inspected more carefully.
#warning 	--joshua, 20030511
#if 0
   serial_do_putnstr(device, str, n);
#else
   sys_write(labserfd, str, n);
#endif
}

void serial_putstr(struct serial_device *device, const char *str)
{
    serial_putnstr(device, str, strlen(str));
}

void
serial_putstr_sync(struct serial_device *device, 
    const char*   s)
{
    serial_putstr(device, s);
    serial_drain_uart(device);
}

void serial_drain_uart(struct serial_device *device)
{
  if (device->drain_uart != NULL)
    device->drain_uart(device); 
}

int serial_set_baudrate(struct serial_device *device, int speed)
{
  if (device->set_baudrate != NULL)
    return device->set_baudrate(device, speed); 
  return -ENODEV;
}

void drain_uart(void)
{
  serial_drain_uart(serial); 
}

// fscking namespace takeovers...
unsigned char getc() { return serial_getc(serial); }
//void putc(char c) { serial_putc(serial, c); }
void putc(char c) {
    if (packetize_output) {
	char	buf[2];
	buf[0] = c;
	buf[1] = '\0';
	serial_putnstr(serial, buf, 1);
    }
    else
	serial_putc(serial, c);
}
void putstr(const char *s) { serial_putstr(serial, s); }
void putnstr(const char *s, int n) { serial_putnstr(serial, s, n); }
byte awaitkey_seconds(unsigned long delay_seconds, int* error_p) {
  return serial_awaitkey_seconds(serial, delay_seconds, error_p);
}
void putstr_sync(const char* s) { serial_putstr_sync(serial, s); }
void do_putnstr(const char* s, int n) { serial_do_putnstr(serial, s, n); }
void putLabeledWord(const char *msg, unsigned long value)
{
   char buf[9];
   binarytohex(buf,value,4);
   putstr(msg);
   putstr(buf);
   putstr("\r\n");
}

void putLabeledAddrWord(const char *msg, unsigned long *value)
{
   char buf[9];

   putstr(msg);
   binarytohex(buf, (unsigned long) value, 4);
   putstr(" *0x" );
   putstr(buf);
   putstr( " == " );
   binarytohex(buf,*value,4);
   putstr(buf);
   putstr("\r\n");
}


void putHexInt32(unsigned long value)
{
  char buf[9];
  binarytohex(buf,value,4);
  putstr(buf);
}

void putHexInt16(word value)
{
  char buf[9];
  binarytohex(buf,value,2);
  putstr(buf);
}

void putHexInt8(byte value)
{
  char buf[3];
  binarytohex(buf,value,1);
  putstr(buf);
}

void putDecInt(unsigned long value)
{
  char buf[16];
  dwordtodecimal(buf, value);
  putstr(buf);
}

void putDecIntWidth(unsigned long value, unsigned int width)
{
  char buf[16];
  unsigned int length;

  dwordtodecimal(buf, value);

  for(length = strlen(buf); length < width; ++length)
    putc(' ');

  putstr(buf);
}

