/*****************************************************************************
serial mouse driver
*****************************************************************************/
#include <kernel/kernel.h>
#include <kernel/driver.h>
#include <kernel/sys.h>
#include <os/mouse.h>
#include <errno.h>

#define DEBUG
#include <kernel/debug.h>

/*!
 *  \ingroup	drivers
 *  \defgroup	sermouse    Serial mouse
 *  @{
 */

//#define peek40(OFF) *((word*) (0x400 + (OFF))) // xxx - FIX!
#define	BUF_SIZE	64

typedef struct	/* circular queue */
{
	unsigned char *data;
	unsigned size, in_ptr, out_ptr;
} queue_t;

typedef struct
{
	device_t dev;
	unsigned char _irq, _buffer[BUF_SIZE];
	unsigned short _io_adr;
	queue_t _queue;
	/* which COM port to use (1=COM1, 2=COM2, etc.) */
	unsigned char _com;
} SerMouse;

/*****************************************************************************
*****************************************************************************/
void enable_irq_at_8259(unsigned short irq)
{
	unsigned char mask;

	if(irq < 8)
	{
		mask = 1 << irq;
		out(0x21, in(0x21) & ~mask);
	}
	else if(irq < 16)
	{
		irq -= 8;
		mask = 1 << irq;
		out(0xA1, in(0xA1) & ~mask);
		out(0x21, in(0x21) & ~0x04);
	}
}
/*****************************************************************************
*****************************************************************************/
void disable_irq_at_8259(unsigned short irq)
{
	unsigned char mask;

	if(irq < 8)
	{
		mask = 1 << irq;
		out(0x21, in(0x21) | mask);
	}
	else if(irq < 16)
	{
		irq -= 8;
		mask = 1 << irq;
		out(0xA1, in(0xA1) | mask);
	}
}

static __inline__ unsigned crit_begin(void)
{
	unsigned ret_val;

	__asm__ __volatile__("pushfl\n"
		"popl %0\n"
		"cli"
		: "=a"(ret_val)
		:);
	return ret_val;
}

static __inline__ void crit_end(unsigned flags)
{
	__asm__ __volatile__("pushl %0\n"
		"popfl"
		:
		: "m"(flags)
		);
}

int inq(queue_t *q, unsigned char data)
{
	unsigned flags, temp;

	flags = crit_begin();
	temp = q->in_ptr + 1;
	if(temp >= q->size)
		temp = 0;
/* if in_ptr reaches out_ptr, the queue is full */
	if(temp == q->out_ptr)
	{
		crit_end(flags);
		return -1;
	}
	q->data[q->in_ptr] = data;
	q->in_ptr = temp;
	crit_end(flags);
	return 0;
}
/*****************************************************************************
*****************************************************************************/
int deq(queue_t *q, unsigned char *data)
{
	unsigned flags;

	flags = crit_begin();
/* if in_ptr == out_ptr, the queue is empty */
	if(q->in_ptr == q->out_ptr)
	{
		crit_end(flags);
		return -1;
	}
	*data = q->data[q->out_ptr];
	q->out_ptr++;
	if(q->out_ptr >= q->size)
		q->out_ptr = 0;
	crit_end(flags);
	return 0;
}

bool mseRequest(device_t* dev, request_t* req)
{
	unsigned char temp;
	SerMouse *ctx = (SerMouse*) dev;

	TRACE2("{%c%c}", req->code / 256, req->code % 256);
	switch (req->code)
	{
	case DEV_READ:
		if (req->params.read.length < sizeof(mouse_packet_t))
		{
			TRACE1("%d: too small\n", req->params.read.length);
			req->result = EBUFFER;
			return false;
		}

		devStartRequest(dev, req);
		return true;
		
	case DEV_OPEN:
	case DEV_CLOSE:
		hndSignal(req->event, true);
		return true;

	case DEV_REMOVE:
		/* disable interrupts at serial chip */
		out(ctx->_io_adr + 1, 0);
		/* disable interrupts at 8259s */
		disable_irq_at_8259(ctx->_irq);
		hndFree(ctx);
		hndSignal(req->event, true);
		return true;

	case DEV_ISR:
		temp = in(ctx->_io_adr  + 5);
		if (temp & 0x01)
		{
			temp = in(ctx->_io_adr + 0);
			inq(&ctx->_queue, temp);
		}

		in(ctx->_io_adr + 2);

		// req->event ignored here
		return true;
	}

	req->result = ENOTIMPL;
	return false;
}

/*****************************************************************************
*****************************************************************************/
#define	MAX_ID	16

bool STDCALL INIT_CODE drvInit(driver_t* drv)
{
	static const unsigned char com_to_irq[] =
	{
		4, 3, 4, 3
	};

	static const word io_port[] =
	{
		0x3f8, 0x2f8, 0x3f8, 0x2f8
	};

/**/
	unsigned char b, id[MAX_ID], *ptr;
	unsigned temp;
	SerMouse *ctx;

	ctx = hndAlloc(sizeof(SerMouse), NULL);
	
	ctx->dev.driver = drv;
	ctx->dev.request = mseRequest;
	ctx->dev.req_first = ctx->dev.req_last = NULL;
	ctx->_queue.data = ctx->_buffer;
	ctx->_queue.size = BUF_SIZE;
	ctx->_queue.in_ptr = ctx->_queue.out_ptr = 0;
	ctx->_com = 1;

	if (ctx->_com < 1 || ctx->_com > 4)
	{
		TRACE1("Invalid COM port %u (must be 1-4)\n", ctx->_com);
		return false;
	}
/* get COM port I/O address from BIOS data segment */
	//ctx->_io_adr = peek40(0 + (ctx->_com - 1) * 2);
	ctx->_io_adr = io_port[ctx->_com - 1];
	if(ctx->_io_adr == 0)
	{
		TRACE1("Sorry, no COM%u serial port on this PC\n", ctx->_com);
		return false;
	}
	TRACE1("activating mouse on COM%u...\n", ctx->_com);
	ctx->_irq = com_to_irq[ctx->_com - 1];
	/* install handler */
	devRegisterIrq((device_t*) ctx, ctx->_irq, true);

	
/* set up serial chip
turn off handshaking lines to power-down mouse, then delay */
	out(ctx->_io_adr + 4, 0);
	msleep(500);
	out(ctx->_io_adr + 3, 0x80);	/* set DLAB to access baud divisor */
	out(ctx->_io_adr + 1, 0);	/* 1200 baud */
	out(ctx->_io_adr + 0, 96);
	out(ctx->_io_adr + 3, 0x02);	/* clear DLAB bit, set 7N1 format */
	out(ctx->_io_adr + 2, 0);	/* turn off FIFO, if any */
/* activate Out2, RTS, and DTR to power the mouse */
	out(ctx->_io_adr + 4, 0x0B);
/* enable receiver interrupts */
	out(ctx->_io_adr + 1, 0x01);
/* enable interrupts at 8259s */
	enable_irq_at_8259(ctx->_irq);
/* wait a moment to get ID bytes from mouse. Microsoft mice just return
a single 'M' byte, some 3-button mice return "M3", my Logitech mouse
returns a string of bytes. I could find no documentation on these,
but I figured out some of it on my own. */
	ptr = id;
	for(temp = 750; temp != 0; temp--)
	{
		while(deq(&ctx->_queue, &b) == 0)
		{
			TRACE1("%02X ", b);
			*ptr = b;
			ptr++;
			if(ptr >= id + MAX_ID)
				goto FOO;
		}
		msleep(1);
	}
	TRACE0("\n");

	if (id == ptr)
		TRACE0("No serial mouse found\n");

FOO:
	if(ptr >= id + 11)
	{
/* find the 'M' */
		for(ptr = id; ptr < id + MAX_ID - 7; ptr++)
		{
			if(*ptr == 'M')
				break;
		}
		if(ptr < id + MAX_ID)
		{
/* four bytes that each encode 4 bits of the numeric portion
of the PnP ID. Each byte has b4 set (i.e. ORed with 0x10) */
			temp = (ptr[8] & 0x0F);
			temp <<= 4;
			temp |= (ptr[9] & 0x0F);
			temp <<= 4;
			temp |= (ptr[10] & 0x0F);
			temp <<= 4;
			temp |= (ptr[11] & 0x0F);
/* three bytes that each encode one character of the character portion
of the PnP ID. Each byte has b5 set (i.e. ORed with 0x20) */
			TRACE4("mouse PnP ID: %c%c%c%04X\n",
				'@' + (ptr[5] & 0x1F),
				'@' + (ptr[6] & 0x1F),
				'@' + (ptr[7] & 0x1F), temp);
		}
/* example: Logitech serial mouse LGI8001
('L' - '@') | 0x20 == 0x2C
('G' - '@') | 0x20 == 0x27
('I' - '@') | 0x20 == 0x29
('8' - '0') | 0x10 == 0x18
('0' - '0') | 0x10 == 0x10
('0' - '0') | 0x10 == 0x10
('1' - '0') | 0x10 == 0x11 */
	}
	

	return true;
}

//@}