/*
 * serial.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * Serial interface driver
 */


#include"types.h"
#include"config.h"
#include"errno.h"
#include"lib.h"
#include"interrupt.h"
#include"proc.h"
#include"mp.h"
#include"sp.h"
#include"serial.h"
#include"device.h"


enum{
	/* IO port */
	COM1_BASE=0x3F8,
	COM2_BASE=0x2F8,

	COM1_IRQ=4,		/* COM1 IRQ number */
	COM2_IRQ=3,		/* COM2 IRQ number */

	/* Interrupt reason */
	MODEM_STATUS=0x0,
	TRANSMIT_EMPTY=0x2,
	RECEIVE_DATA=0x4,
	LINE_STATUS=0x6,
	RECEIVE_TIMEOUT=0xc,

	/* LSR bit */
	LSR_EMPTY_TR=0x20,
};


typedef struct{
	int thr;	/* COM_THR=0 */
	int rbr;	/* COM_RBR=0 */
	int dll;	/* COM_DLL=0 */
	int ier;	/* COM_IER=1 */
	int dlm;	/* COM_DLM=1 */
	int iir;	/* COM_IIR=2 */
	int fcr;	/* COM_FCR=2 */
	int lcr;	/* COM_LCR=3 */
	int mcr;	/* COM_MCR=4 */
	int lsr;	/* COM_LSR=5 */
	int msr;	/* COM_MSR=6 */
	int scr;	/* COM_SCR=7 */
}COM_REG;

/* COM configuration struct */
typedef struct{
	uchar lcr;		/* LCR value */
	uchar ier;		/* IER value */
	int boud;		/* boud late */
	void *inbuf;	/* input buffer */
}COM_CONF;


static COM_REG reg[2]={
	{COM1_BASE+0,COM1_BASE+0,COM1_BASE+0,COM1_BASE+1,COM1_BASE+1,COM1_BASE+2,
	COM1_BASE+2,COM1_BASE+3,COM1_BASE+4,COM1_BASE+5,COM1_BASE+6,COM1_BASE+7},
	{COM2_BASE+0,COM2_BASE+0,COM2_BASE+0,COM2_BASE+1,COM2_BASE+1,COM2_BASE+2,
	COM2_BASE+2,COM2_BASE+3,COM2_BASE+4,COM2_BASE+5,COM2_BASE+6,COM2_BASE+7},
};
static char *uart_name[5]={
	NULL,
	"UART8250 compatible",
	"UART16450 compatible",
	"UART16550A compatible",
	"UART16750 compatible"
};
/*
 * UARTの種類
 * 0 no uart,1 : 8250, 2 : 16450, 3 : 16550A, 4 : 16750
 */
static int uart[2]={0,0};					/* UARTの種類 */
static WAIT_INTR wait_intr_queue[2]={		/* 割り込み待ち用 */
	{NULL,0},{NULL,0}
};
/*static WAIT_QUEUE wait_queue[2]={
	{(PROC*)&intr_wait_queue[0],NULL,0,0},
	{(PROC*)&intr_wait_queue[1],NULL,0,0}
};*/
static int fifo_buf_size[2]={1,1};			/* FIFO buffer size */


static int com1_intr_handler();
static int com2_intr_handler();
static void set_boudrate(int,ushort);
static int write_ttys(int,const char*,size_t);
static int write_com(int,const char*,size_t);
static int detect_uart(int);
static int write_ttys0(void*,size_t,size_t);
static int write_ttys1(void*,size_t,size_t);
static int ioctl_ttys0(int,void*);
static int ioctl_ttys1(int,void*);


static DEV_INFO com_info[2]={
	{"ttys0",0,0,0,NULL,NULL,write_ttys0,ioctl_ttys0},
	{"ttys1",0,0,0,NULL,NULL,write_ttys1,ioctl_ttys1}
};


/*
 * COM1 interrupt handler
 * return : Task switch on
 */
int com1_intr_handler()
{
/***************************************/
	printk("Interrupt IRQ4\n");
/***************************************/
	switch(inb(reg[0].iir)&0xf)
	{
		case TRANSMIT_EMPTY:
			wake_intr(&wait_intr_queue[0]);
			break;
		case RECEIVE_DATA:
		case RECEIVE_TIMEOUT:
		case LINE_STATUS:
		case MODEM_STATUS:
		;
	}

	return 1;
}


/*
 * COM2 interrupt handler
 * return : Task switch on
 */
int com2_intr_handler()
{
/***************************************/
	printk("Interrupt IRQ3\n");
/***************************************/
	switch(inb(reg[1].iir)&0xf)
	{
		case TRANSMIT_EMPTY:
			wake_intr(&wait_intr_queue[1]);
			break;
		case RECEIVE_DATA:
		case RECEIVE_TIMEOUT:
		case LINE_STATUS:
		case MODEM_STATUS:
		;
	}

	return 1;
}


/*
 * Set up boudrate
 * parameters : Com number,Boudrate divisor
 */
void set_boudrate(int com,ushort div)
{
	uchar old;


	old=inb(reg[com].lcr);
	outb(reg[com].lcr,0x80);	/* set DLAB */
	outw(reg[com].dll,div);		/* input divisor */
	outb(reg[com].lcr,div);		/* set LCR */
	outb(reg[com].mcr,div>>8);	/* set MCR */
	outb(reg[com].lcr,old);
}


/*
 * Configuraton COM
 * parameters : host,config struct
 * return : 0,or error number
 */
int conf_com(int com,COM_CONF *conf)
{
	outb(reg[com].ier,0);				/* Disable interrupt */

	set_boudrate(com,conf->boud);		/* Set baudrate */
	outb(reg[com].lcr,conf->lcr);		/* Set line control */
	switch(uart[com])					/* Set fifo buffer */
	{
		case 4:	/* UART16750 */
			fifo_buf_size[com]=64;		/* FIFO buffer size */
			outb(reg[com].fcr,0xe7);	/* 64byte FIFO enable */
			break;
		case 3:	/* UART16550A */
			fifo_buf_size[com]=16;		/* FIFO buffer size */
			outb(reg[com].fcr,0xc7);	/* enable FIFO,trigger 14byte,clear Transmit FIFO,clear Receive FIFO */
	}
	inb(reg[com].lsr);					/* Reset LSR */
	inb(reg[com].msr);					/* Reset MSR */
	if(conf->ier!=0)					/* Set interrupt */
	{
		outb(reg[com].mcr,0x8);			/* Turn on IRQ */
		outb(reg[com].ier,conf->ier);
		if((conf->ier&0x2)==0x2)
		{
			wait_intr(&wait_intr_queue[com],2000);				/* Transmit buffer empty intnerrupt will occuer */
			if(wait_intr_queue[com].flag==-1)return -ETIMEOUT;
		}
	}

	return 0;
}


/*
 * Write COM
 * parameters : com number,buffer,Write bytes
 * return : Write bytes
 */
int write_ttys(int com,const char *buf,size_t count)
{
	int i,n;


	if(count==0)return 0;

	/* SMPなら割り込みが同じcpuに発生するようにする */
	if(activ_cpu>1)
	{
		if(com==0)
		{
			set_physical_intr(COM1_IRQ);
			set_intr_cpu(COM1_IRQ,get_current_cpu());
		}
		else
		{
			set_physical_intr(COM2_IRQ);
			set_intr_cpu(COM2_IRQ,get_current_cpu());
		}
	}

	n=count;
	for(i=count%fifo_buf_size[com],n-=i;i>0;--i)outb(reg[com].thr,*buf++);
	wait_intr(&wait_intr_queue[com],2000);
	if(wait_intr_queue[com].flag==-1)return -ETIMEOUT;

	for(;n>0;n-=fifo_buf_size[com])
	{
		for(i=0;i<fifo_buf_size[com];++i)outb(reg[com].thr,*buf++);
		wait_intr(&wait_intr_queue[com],2000);
		if(wait_intr_queue[com].flag==-1)return -ETIMEOUT;
	}

	/* SMPなら割り込み設定を戻しておく */
	if(activ_cpu>1)
	{
		if(com==0)
		{
			set_logical_intr(COM1_IRQ);
			set_intr_anony(COM1_IRQ);
		}
		else
		{
			set_logical_intr(COM2_IRQ);
			set_intr_anony(COM2_IRQ);
		}
	}

	return count;
}


/*
 * Print Kernel message(No interrupt mode)
 * parameters : output buffer,byte count
 * return : write bytes
 */
int write_com1(const char *buf,size_t count)
{
	return write_com(0,buf,count);
}

int write_com2(const char *buf,size_t count)
{
	return write_com(1,buf,count);
}

/*
 * parameters : com number,output buffer,byte count
 * return : write byte
 */
int write_com(int com,const char *buf,size_t count)
{
	int i,n;


	if(count==0)return 0;

	n=count;
	for(i=count%fifo_buf_size[com],n-=i;i>0;--i)outb(reg[com].thr,*buf++);
	while((inb(reg[com].lsr)&LSR_EMPTY_TR)!=LSR_EMPTY_TR);		/* Wait until transmitter bffer enmpty */

	for(;n>0;n-=fifo_buf_size[com])
	{
		for(i=0;i<fifo_buf_size[com];++i)outb(reg[com].thr,*buf++);
		while((inb(reg[com].lsr)&LSR_EMPTY_TR)!=LSR_EMPTY_TR);	/* Wait until transmitter bffer enmpty */
	}

	return count;
}


/************************************************************************************************
 *
 * Init COM
 *
 ************************************************************************************************/


void init_com()
{
	int i;
	COM_CONF conf;


	conf.lcr=0x3;		/* 8bit,1stop bit,no parity(8N1) */
	conf.boud=B38400;
	conf.inbuf=NULL;
	conf.ier=0;

	/* デバイスの確認 */
	for(i=0;i<2;++i)
	{
		if((uart[i]=detect_uart(i))==0)continue;
		if(conf_com(i,&conf)!=0)continue;	/* Init com */
		regist_device(&com_info[0]);		/* Regster to dev filesystem */
	}
}


/*
 * Detect UART
 * parameters : Com number
 * retrun : 0 no uart,1 : 8250, 2 : 16450, 3 : 16550A, 4 : 16750
 */
int detect_uart(int com)
{
	uchar value;


	/* Check if a UART is present anyway */
	outb(reg[com].mcr,0x10);
	if((inb(reg[com].msr)&0xf0)!=0)return 0;
	outb(reg[com].mcr,0x1f);
	if((inb(reg[com].msr)&0xf0)!=0xf0)return 0;

	/* Look for the scratch register */
	outb(reg[com].scr,0x55);
	if(inb(reg[com].scr)!=0x55)return 1;
	outb(reg[com].scr,0xaa);
	if(inb(reg[com].scr)!=0xaa)return 1;

	/* Check if there's a FIFO */
	outb(reg[com].fcr,0x21);
	value=inb(reg[com].iir);
	if ((value&0xc0)!=0xc0) return 2;
	if ((value&0x20)!=0x20) return 3;
	return 4;
}


/*
 * Interrupt start
 */
void set_com_intr()
{
	int i;


	/* 割り込みの設定 */
	irq_entry[IRQ4]=com1_intr_handler;
	irq_entry[IRQ3]=com2_intr_handler;
	release_irq_mask(COM1_IRQ);
	release_irq_mask(COM2_IRQ);

	/* Print com infomation */
	for(i=0;i<2;++i)
		if(uart[i]>0)printk("ttys%d : %s\n",i,uart_name[uart[i]]);
}


/************************************************************************************************
 *
 * System call interface
 *
 ************************************************************************************************/


/*
 * Write call
 */
int write_ttys0(void *buf,size_t count,size_t x)
{
	return write_ttys(0,(const char*)buf,count);
}

int write_ttys1(void *buf,size_t count,size_t x)
{
	return write_ttys(1,(const char*)buf,count);
}

int ioctl_ttys0(int command,void *param)
{
	return 0;
}

int ioctl_ttys1(int command,void *param)
{
	return 0;
}
/***************************************************************************************/
void test_com()
{
	COM_CONF conf;

	conf.lcr=0x3;		/* 8bit,1stop bit,no parity */
	conf.boud=B38400;
	conf.inbuf=NULL;
	conf.ier=0xd;
	conf_com(0,&conf);

	printk("ok\n");
}
/****************************************************************************************/
