/* KallistiOS ##version##

   hardware/scif.c
   Copyright (C)2000,2001,2004 Dan Potter
*/

#include <stdio.h>
#include <errno.h>
#include <kos/dbgio.h>
#include <arch/arch.h>
#include <arch/spinlock.h>
#include <arch/irq.h>
#include <dc/fs_dcload.h>

/*

This module handles very basic serial I/O using the SH4's SCIF port. FIFO
mode is used by default; you can turn this off to avoid forcing a wait
when there is no serial device attached.

Unlike in KOS 1.x, this is not designed to be used as the normal I/O, but
simply as an early debugging device in case something goes wrong in the
kernel or for debugging it.

*/

/* SCIF registers */
#define SCIFREG08(x) *((volatile uint8 *)(x))
#define SCIFREG16(x) *((volatile uint16 *)(x))
#define SCSMR2	SCIFREG16(0xffeb0000)
#define SCBRR2	SCIFREG08(0xffe80004)
#define SCSCR2	SCIFREG16(0xffe80008)
#define SCFTDR2	SCIFREG08(0xffe8000C)
#define SCFSR2	SCIFREG16(0xffe80010)
#define SCFRDR2	SCIFREG08(0xffe80014)
#define SCFCR2	SCIFREG16(0xffe80018)
#define SCFDR2	SCIFREG16(0xffe8001C)
#define SCSPTR2	SCIFREG16(0xffe80020)
#define SCLSR2	SCIFREG16(0xffe80024)

/* Default serial parameters */
static int serial_baud = DEFAULT_SERIAL_BAUD,
	serial_fifo = DEFAULT_SERIAL_FIFO;

// This will get set to zero if we fail to send.
static int serial_enabled = 1;

/* Set serial parameters; this is not platform independent like I want
   it to be, but it should be generic enough to be useful. */
void scif_set_parameters(int baud, int fifo) {
	serial_baud = baud;
	serial_fifo = fifo;
}

// Receive ring buffer
#define BUFSIZE 1024
static uint8 recvbuf[BUFSIZE];
static int rb_head = 0, rb_tail = 0, rb_cnt = 0;
static int rb_paused = 0;

static void rb_reset() {
	rb_head = rb_tail = rb_cnt = rb_paused = 0;
}

static void rb_push_char(int c) {
	recvbuf[rb_head] = c;
	rb_head = (rb_head + 1) % BUFSIZE;
	rb_cnt++;

	// If we're within 32 bytes of being out of space, pause for
	// the moment.
	if (!rb_paused && (BUFSIZE - rb_cnt) < 32) {
		rb_paused = 1;
		SCSPTR2 = 0x20;		// Set CTS=0
	}
}

static int rb_pop_char() {
	int c;
	c = recvbuf[rb_tail];
	rb_tail = (rb_tail + 1) % BUFSIZE;
	rb_cnt--;

	// If we're paused and clear again, re-enabled receiving.
	if (rb_paused && (BUFSIZE - rb_cnt) >= 64) {
		rb_paused = 0;
		SCSPTR2 = 0x00;
	}

	return c;
}

/* static int rb_space_free() {
	return BUFSIZE - rb_cnt;
} */

static int rb_space_used() {
	return rb_cnt;
}


/* Serial receive and receive error interrupts. When this is triggered we
   must look for available data and error conditions, and clear them all
   out if possible. If our internal ring buffer comes close to overflowing,
   the best we can do is twiddle RTS/CTS for a while. */
static void scif_err_irq(irq_t src, irq_context_t * cxt) {
	// Clear status bits
	SCSCR2 &= ~0x08;
	SCSCR2 |= 0x08;

	printf("scif_err_irq called\n");

	// Did we get an error condition?
	if (SCFSR2 & 0x9c) {	// Check ER, BRK, FER, PER
		printf("SCFSR2 status was %04x\n", SCFSR2);
		/* Try to clear it */
		SCFCR2 = 0x06;
		SCFCR2 = 0x88;
		SCFSR2 &= ~0x9c;
	}
	if (SCLSR2 & 0x01) {	/* ORER */
		printf("SCLSR2 status was %04x\n", SCLSR2);
		/* Try to clear it */
		SCFCR2 = 0x06;
		SCFCR2 = 0x88;
		SCLSR2 = 0x00;
	}
}

static void scif_data_irq(irq_t src, irq_context_t * cxt) {
	// Clear status bits
	SCSCR2 &= ~0x40;
	SCSCR2 |= 0x40;

	//printf("scif_data_irq called\n");

	// Check for received data available.
	if (SCFSR2 & 3) {
		while (SCFDR2 & 0x1f) {
			int c = SCFRDR2;
			//printf("received char '%d'\n", c);
			rb_push_char(c);
		}
		SCFSR2 &= ~3;
	}
}

// Are we using IRQs?
static int scif_irq_usage = 0;
int scif_set_irq_usage(int on) {
	scif_irq_usage = on;

	// Clear out the buffer in any case
	rb_reset();

	if (scif_irq_usage) {
		/* Hook the SCIF interrupt */
		irq_set_handler(EXC_SCIF_ERI, scif_err_irq);
		irq_set_handler(EXC_SCIF_BRI, scif_err_irq);
		irq_set_handler(EXC_SCIF_RXI, scif_data_irq);
		*((vuint16*)0xffd0000c) |= 0x000e << 4;

		/* Enable transmit/receive, recv/recv error ints */
		SCSCR2 |= 0x48;
	} else {
		/* Disable transmit/receive, recv/recv error ints */
		SCSCR2 &= ~0x48;

		/* Unhook the SCIF interrupt */
		*((vuint16*)0xffd0000c) &= ~(0x000e << 4);
		irq_set_handler(EXC_SCIF_ERI, NULL);
		irq_set_handler(EXC_SCIF_BRI, NULL);
		irq_set_handler(EXC_SCIF_RXI, NULL);
	}

	return 0;
}

// We are always detected, though we might end up realizing there's no
// cable connected later...
int scif_detected() {
	return 1;
}

// We use this for the dbgio interface because we always init SCIF.
int scif_init_fake() {
	return 0;
}

/* Initialize the SCIF port; baud_rate must be at least 9600 and
   no more than 57600. 115200 does NOT work for most PCs. */
// recv trigger to 1 byte
int scif_init() {
	int i;
	/* int fifo = 1; */

	// If dcload-serial is active, then do nothing here, or we'll
	// screw that up.
	if (dcload_type == DCLOAD_TYPE_SER)
		return 0;

	/* Disable interrupts, transmit/receive, and use internal clock */
	SCSCR2 = 0;

	/* Enter reset mode */
	SCFCR2 = 0x06;
	
	/* 8N1, use P0 clock */
	SCSMR2 = 0;
	
	/* If baudrate unset, set baudrate, N = P0/(32*B)-1 */
	if (SCBRR2 == 0xff)
		SCBRR2 = (50000000 / (32 * serial_baud)) - 1;

	/* Wait a bit for it to stabilize */
	for (i=0; i<10000; i++)
		asm("nop");

	/* Unreset, enable hardware flow control, triggers on 8 bytes */
	SCFCR2 = 0x48;
	
	/* Disable manual pin control */
	SCSPTR2 = 0;
	
	/* Clear status */
	(void)SCFSR2;
	SCFSR2 = 0x60;
	(void)SCLSR2;
	SCLSR2 = 0;
	
	/* Enable transmit/receive */
	SCSCR2 = 0x30;

	/* Wait a bit for it to stabilize */
	for (i=0; i<10000; i++)
		asm("nop");

	return 0;
}

int scif_shutdown() {
	scif_set_irq_usage(DBGIO_MODE_POLLED);
	return 0;
}

/* Read one char from the serial port (-1 if nothing to read) */
int scif_read() {
	if (!serial_enabled) {
		errno = EIO;
		return -1;
	}

	if (scif_irq_usage) {
		// Do we have anything ready?
		if (rb_space_used() <= 0) {
			errno = EAGAIN;
			return -1;
		} else
			return rb_pop_char();
	} else {
		int c;

		if (!(SCFDR2 & 0x1f)) {
			errno = EAGAIN;
			return -1;
		}

		// Get the input char
		c = SCFRDR2;

		// Ack
		SCFSR2 &= ~0x92;

		return c;
	}
}

/* Write one char to the serial port (call serial_flush()!) */
int scif_write(int c) {
	int timeout = 100000;

	if (!serial_enabled) {
		errno = EIO;
		return -1;
	}
	
	/* Wait until the transmit buffer has space. Too long of a failure
	   is indicative of no serial cable. */
	while (!(SCFSR2 & 0x20) && timeout > 0)
		timeout--;
	if (timeout <= 0) {
		serial_enabled = 0;
		errno = EIO;
		return -1;
	}
	
	/* Send the char */
	SCFTDR2 = c;
	
	/* Clear status */
	SCFSR2 &= 0xff9f;

	return 1;
}

/* Flush all FIFO'd bytes out of the serial port buffer */
int scif_flush() {
	int timeout = 100000;

	if (!serial_enabled) {
		errno = EIO;
		return -1;
	}

	SCFSR2 &= 0xbf;

	while (!(SCFSR2 & 0x40) && timeout > 0)
		timeout--;
	if (timeout <= 0) {
		serial_enabled = 0;
		errno = EIO;
		return -1;
	}

	SCFSR2 &= 0xbf;

	return 0;
}

/* Send an entire buffer */
int scif_write_buffer(const uint8 *data, int len, int xlat) {
	int rv, i = 0, c;
	while (len-- > 0) {
		c = *data++;
		if (xlat) {
			if (c == '\n') {
				if (scif_write('\r') < 0)
					return -1;
				i++;
			}
		}
		rv = scif_write(c);
		if (rv < 0)
			return -1;
		i += rv;
	}
	if (scif_flush() < 0)
		return -1;

	return i;
}

/* Read an entire buffer (block) */
int scif_read_buffer(uint8 *data, int len) {
	int c, i = 0;
	while (len-- > 0) {
		while ( (c = scif_read()) == -1) {
			if (errno != EAGAIN)
				return -1;
		}
		*data++ = c;
		i++;
	}

	return i;
}

// Tie all of that together into a dbgio package.
dbgio_handler_t dbgio_scif = {
	"scif",
	scif_detected,
	scif_init_fake,
	scif_shutdown,
	scif_set_irq_usage,
	scif_read,
	scif_write,
	scif_flush,
	scif_write_buffer,
	scif_read_buffer
};
