/*	
 *	clipcom.c
 *
 *	調歩同期シリアル通信ドライバ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2004 Naoyuki Sawa
 *
 *	* Mon May 17 22:46:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 *	* Tue May 18 12:30:00 JST 2004 Naoyuki Sawa
 *	- com_send()を送信バッファが空きができるまでブロックするように変更。
 *	* Sun May 30 13:51:00 JST 2004 Naoyuki Sawa
 *	- com_rts()、com_cts()を追加しました。
 *	* Mon May 31 13:51:00 JST 2004 Naoyuki Sawa
 *	- 送受信コールバック機構\を追加しました。
 *	* Tue Jun 1 20:05:00 JST 2004 Naoyuki Sawa
 *	- #RTS/#CTSの論理が逆だったのを修正しました。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

/*
 *	シリアル通信ドライバ構\造体
 */
typedef struct _COMDRIVER {
	int open;			/* 開かれていたら1 */
	void (*recv_callback)();	/* 受信通知コールバック */
	void (*send_callback)();	/* 送信完了コールバック */
	int rxbuf_overflow;		/* 受信バッファがオーバーフローしたら1 */
	//
	int rxbuf_size;			/* 受信バッファサイズ */
	int rxbuf_pos;			/* 受信バッファ内の有効データ先頭位置 */
	int rxbuf_len;			/* 受信バッファ内の有効データバイト数 */
	unsigned char* rxbuf;		/* 受信バッファ */
	//
	int txbuf_size;			/* 送信バッファサイズ */
	int txbuf_pos;			/* 送信バッファ内の有効データ先頭位置 */
	int txbuf_len;			/* 送信バッファ内の有効データバイト数 */
	unsigned char* txbuf;		/* 送信バッファ */
} COMDRIVER;
static COMDRIVER com;

void com_isr();
static void _com_isr() __attribute__((unused));

/****************************************************************************
 *	
 ****************************************************************************/

void
com_open(int baudrate, int parity, int bytesize, int stopbits, int rxbuf_size, int txbuf_size, void (*recv_callback)(), void (*send_callback)())
{
	int RLD;	/* 8ビットプログラマブルタイマのリロードレジスタ設定値 */
	int fPCIN;	/* プリスケーラ入力クロック周波数(Hz) */
	int bps;	/* 転送速度(ビット/秒) */
	int pdr;	/* プリスケーラの分周比 */
	int sdr;	/* シリアルインターフェイス内部分周比(16または8) */
	//
	int i_pdr;
	int i_sdr;
	//
	int ok_i_pdr;
	int ok_i_sdr;
	int ok_RLD;
	int ok_bps;

	/* 既に開かれていた場合に備えて、まず確実に閉じます。 */
	com_close();

	/* ドライバ構\造体を初期化します。 */
	memset(&com, 0, sizeof com);
	com.open = 1;
	com.recv_callback = recv_callback;
	com.send_callback = send_callback;
	com.rxbuf_size = rxbuf_size ? rxbuf_size : 128;
	com.rxbuf = (unsigned char*)calloc(1, com.rxbuf_size);
	com.txbuf_size = txbuf_size ? txbuf_size : 128;
	com.txbuf = (unsigned char*)calloc(1, com.txbuf_size);

	/* プリスケーラ入力クロック周波数を取得します。 */
	if(bP0_P0D_P07D) {
		fPCIN = 24000000;
	} else {
		fPCIN = 48000000;
	}

	/* 最適な設定を検索します。 */
	ok_i_pdr = 0;	/* 警告抑制 */
	ok_i_sdr = 0;	/* 警告抑制 */
	ok_RLD   = 0;	/* 警告抑制 */
	ok_bps   = 0;	/* 初回採用のため */

	/* 8ビットプログラマブルタイマ3のプリスケーラ分周比は、1/{1,2,4,8,16,32,64,128,256}。
	 *                                                        高   ←   精度   →   低
	 */
	for(i_pdr = -1; i_pdr <= 7; i_pdr++) {
		pdr = 1 << (i_pdr + 1);

		/* シリアルインターフェイスの内部分周比は、1/{16,8}。
		 *                                        低←精度→高
		 */
		for(i_sdr = 0; i_sdr <= 1; i_sdr++) {
			sdr = 16 >> i_sdr;

			/* 8ビットプログラマブルタイマのリロードレジスタ設定値を算出します。
			 *
			 *	      fPCIN ÷ pdr ÷ sdr               fPCIN             
			 *	RLD = ------------------- - 1 = ---------------------- - 1
			 *	          2 × bps	        2 × bps × pdr × sdr    
			 */
			RLD = fPCIN / (2 * baudrate * pdr * sdr) - 1;
			if(RLD < 0 || RLD > 255) continue; /* 設定不可 */

			/* 本当の転送速度を算出します。
			 *	                 fPCIN            
			 *	bps = ----------------------------
			 *	      2 × (RLD + 1) × pdr × sdr
			 */
			bps = fPCIN / (2 * (RLD + 1) * pdr * sdr);

			/* 以前に見つかった設定よりも誤差が小さければ、採用します。
			 * 初回はok_bps=0となっているので、必ず採用されるはずです。
			 */
			if(abs(baudrate - bps) < abs(baudrate - ok_bps)) {
				ok_i_pdr = i_pdr;
				ok_i_sdr = i_sdr;
				ok_RLD   = RLD;
				ok_bps   = bps;
			}
		}
	}
	if(!ok_bps) DIE(); /* 設定不可 */

	/* シリアルインタフェース動作停止。 */
	bSIF1_CTL_TXEN1 = 0;	/* Serial I/F Ch.1 transmit enable => 禁止 */
	bSIF1_CTL_RXEN1 = 0;	/* Serial I/F Ch.1 receive enable  => 禁止 */

	/* 8ビットプログラマブルタイマ3停止。 */
	pT8_CTL3 = 0;			/* 8-bit timer 3 clock control register => タイマ停止       */
	bCLKCTL_T8_23_P8TON3 = 0;	/* 8-bit timer 3 clock control          => プリスケーラ停止 */

	/*{{S1C33209/221/222テクニカルマニュアル『調歩同期式インターフェースの設定』(s1c33209_221_222j.pdf B-III-8-13〜)参照*/

	/* ■入出力端子の設定 */
	bPF_CFEX_CFEX4 = 0;	/* P04 port extended function #DMAACK2/P04,etc      => P04,etc   */
	bP0_CFP0_CFP04 = 1;	/* P04 function selection SIN1/P04                  => SIN1      */
	bPF_CFEX_CFEX5 = 0;	/* P05 port extended function #DMAEND2/P05,etc      => P05,etc   */
	bP0_CFP0_CFP05 = 1;	/* P05 function selection SOUT1/P05                 => SOUT1     */
	/*{{2004/05/17現在、ハードウェアフロー制御未対応です。端子設定のみ*/
	bPF_CFEX_CFEX0 = 0;	/* P12,P14 port extended function DST2,DCLK/P12,P14 => P12,P14   */
	bP1_CFP1_CFP12 = 0;	/* P12 function selection EXCL2,T8UF2/P12           => P12(#RTS) */
	bP1_IOC1_IOC12 = 1;	/* P12 I/O control                                  => 出力      */
	//bP1_P1D_P12D = ?;	/* P12 port                                         => 後で設定  */
	bP1_CFP1_CFP14 = 0;	/* P14 function selection FOSC1/P14                 => P14(#CTS) */
	bP1_IOC1_IOC14 = 0;	/* P14 I/O control                                  => 入力      */
	/*}}2004/05/17現在、ハードウェアフロー制御未対応です。端子設定のみ*/

	/* ■インタフェースモード設定 */
	bSIF1_IRDA_IRMD1 = 0;	/* Serial I/F Ch.1 interface mode selection => 通常のインタフェース(非IrDA) */

	/* ■転送モード設定 */
	switch(bytesize) {
	case 7: bSIF1_CTL_SMD1 = 2; break;	/* Serial I/F Ch.1 transmit mode selection => 調歩同期式7bit */
	case 8: bSIF1_CTL_SMD1 = 3; break;	/* Serial I/F Ch.1 transmit mode selection => 調歩同期式8bit */
	default: DIE();
	}

	/* ■データフォーマットの設定 */
	switch(stopbits) {
	case 1: bSIF1_CTL_STPB1 = 0; break;	/* Serial I/F Ch.1 stop bit selection => 1bit */
	case 2: bSIF1_CTL_STPB1 = 1; break;	/* Serial I/F Ch.1 stop bit selection => 2bit */
	default: DIE();
	}
	switch(parity) {
	case 0: bSIF1_CTL_EPR1 = 0; break;	/* Serial I/F Ch.1 parity enable         => パリティなし */
	case 1: bSIF1_CTL_EPR1 = 1;		/* Serial I/F Ch.1 parity enable         => パリティ付   */
		bSIF1_CTL_PMD1 = 1; break;	/* Serial I/F Ch.1 parity mode selection => 奇数         */
	case 2: bSIF1_CTL_EPR1 = 1;		/* Serial I/F Ch.1 parity enable         => パリティ付   */
		bSIF1_CTL_PMD1 = 0; break;	/* Serial I/F Ch.1 parity mode selection => 偶数         */
	default: DIE();
	}

	/* ■入力クロックの設定 */
	bSIF1_CTL_SSCK1 = 0;			/* Serial I/F Ch.1 input clock selection => 内部クロック */
	if(ok_i_pdr < 0) {
		bCLKSEL_T8_P8TPCK3 = 1;		/* 8-bit timer 3 clock selection => 1/θ */
	} else {
		bCLKSEL_T8_P8TPCK3 = 0;		/* 8-bit timer 3 clock selection => 分周クロック */
		bCLKCTL_T8_23_P8TS3 = ok_i_pdr;	/* 8-bit timer 3 clock division ratio selection  */
	}
	pT8_RLD3 = ok_RLD;			/* 8-bit timer 3 reload data register */
	bSIF1_IRDA_DIVMD1 = ok_i_sdr;		/* Serial I/F Ch.1 async. clock division ratio */

	/*}}S1C33209/221/222テクニカルマニュアル『調歩同期式インターフェースの設定』(s1c33209_221_222j.pdf B-III-8-13〜)参照*/

	/* 割り込みサービスルーチンをインストールします。 */
	pceVectorSetTrap(TRAP_SERR1, com_isr);	/* Serial I/F Ch.1 receive error */
	pceVectorSetTrap(TRAP_SRX1, com_isr);	/* Serial I/F Ch.1 receive buffer full */
	pceVectorSetTrap(TRAP_STX1, com_isr);	/* Serial I/F Ch.1 transmit buffer empty */

	/* 割り込みプライオリティを設定します。
	 * * サウンド割り込み中から呼ばれるコールバック処理は、音楽ドライバによっては時間がかかる場合があります。
	 *   サウンド割り込み中は、IL=5で多重割り込みを受け付けています。(PIECE KERNEL v1.20 現在)
	 *   シリアル受信バッファオーバーランエラーを防ぐために、サウンド割り込みよりも高い優先度を設定してください。
	 *   2004/05/17現在の実装では、確実のために、最高プライオリティの7を与えることにしました。
	 */
	bINT_PSIO1_PAD_PSIO1 = 7;	/* Serial I/F Ch.1 interrupt priority */

	/* com_open()直後の初期状態で、RTS信号を1(データ受信可能\)とします。 */
	com_rts(1);

	/* いきなり受信エラーになってしまわないよう、開始前に確実にエラー要因をリセットしておきます。 */
	com_reset();

	/* 割り込み要因のリセットは必要ありません。
	 * 割り込みルーチンの中でステータスを調べてから処理を行うので、不要な割り込みがかかっても問題ありません。
	 */

ENTER_CS;
	/* 割り込みを許可します。 */
	bINT_ESIF_ESERR1 = 1;	/* Serial I/F Ch.1 receive error         => 許可 */
	bINT_ESIF_ESRX1 = 1;	/* Serial I/F Ch.1 receive buffer full   => 許可 */
	bINT_ESIF_ESTX1 = 1;	/* Serial I/F Ch.1 transmit buffer empty => 許可 */

	/* 8ビットプログラマブルタイマ3開始。 */
	bCLKCTL_T8_23_P8TON3 = 1;	/* 8-bit timer 3 clock control          => プリスケーラ開始 */
	pT8_CTL3 = 7;			/* 8-bit timer 3 clock control register => タイマ開始、プリセット、クロック出力(シリアルI/Fのために必要!!) */

	/* シリアルインタフェース動作開始。 */
	bSIF1_CTL_TXEN1 = 1;	/* Serial I/F Ch.1 transmit enable => 許可 */
	bSIF1_CTL_RXEN1 = 1;	/* Serial I/F Ch.1 receive enable  => 許可 */
LEAVE_CS;
}

void
com_close()
{
	/* 開かれていなければ、何もしません。 */
	if(!com.open) return;

ENTER_CS;
	/* シリアルインタフェース動作停止。 */
	bSIF1_CTL_TXEN1 = 0;	/* Serial I/F Ch.1 transmit enable => 禁止 */
	bSIF1_CTL_RXEN1 = 0;	/* Serial I/F Ch.1 receive enable  => 禁止 */

	/* 8ビットプログラマブルタイマ3停止。 */
	pT8_CTL3 = 0;			/* 8-bit timer 3 clock control register => タイマ停止       */
	bCLKCTL_T8_23_P8TON3 = 0;	/* 8-bit timer 3 clock control          => プリスケーラ停止 */

	/* 割り込みを禁止します。 */
	bINT_ESIF_ESERR1 = 0;	/* Serial I/F Ch.1 receive error         => 禁止 */
	bINT_ESIF_ESRX1 = 0;	/* Serial I/F Ch.1 receive buffer full   => 禁止 */
	bINT_ESIF_ESTX1 = 0;	/* Serial I/F Ch.1 transmit buffer empty => 禁止 */
LEAVE_CS;

	/* ドライバ構\造体をクリアします。 */
	free(com.rxbuf);
	free(com.txbuf);
	memset(&com, 0, sizeof com);

	/* 端子設定はとりあえずそのままでも大丈夫かな? */
}

/* void com_isr() */
asm("
	.code
	.align 1
com_isr:
	pushn %r15
	ld.w %r0, %alr
	ld.w %r1, %ahr
	pushn %r1
	call _com_isr
	popn %r1
	ld.w %ahr, %r1
	ld.w %alr, %r0
	popn %r15
	reti
");

static void
_com_isr()
{
	int v;
	int i;

	/* 割り込み要因をクリアします。
	 * シリアルCh.1の受信エラー、受信バッファフル、送信バッファエンプティ割り込み要因を全てクリアします。
	 * 同じレジスタに存在するシリアルCh.0の割り込み要因も同時にクリアされてしまいますが、止むを得ません。
	 * P/ECE KernelはRD/WR方式の割り込みクリア方式を選択しているので、特定の要因だけをクリアできません。
	 * 「pINT_FSIF = pINT_FSIF & ~0x28／＊Ch.1の要因以外を維持＊／」としても無駄です。
	 * Read〜Modify〜Writeの間で変化した割り込み要因を(意図せずに)クリアしてしまうことが避けられません。
	 * 幸い、P/ECEのハードウェアはシリアルCh.0を利用していないので、要因をクリアしても問題ないはずです。
	 */
	pINT_FSIF = 0; /* bINT_FSIF_FSERR1 = bINT_FSIF_FSRX1 = bINT_FSIF_FSTX1 = 0 */

	/* 受信バッファフル? */
	if(bSIF1_STATUS_RDBF1) {
		/* 受信キャラクタを読み出します。 */
		v = pSIF1_RXD;
		/* ドライバ構\造体の受信バッファに空きあり? */
		if(com.rxbuf_len < com.rxbuf_size) {
			/* ドライバ構\造体の受信バッファに格納します。 */
			i = com.rxbuf_pos + com.rxbuf_len;
			if(i >= com.rxbuf_size) i -= com.rxbuf_size;
			com.rxbuf[i] = v;
			com.rxbuf_len++;
		} else {
			/* ドライバ構\造体の受信バッファオーバーフロー。 */
			com.rxbuf_overflow = 1;
		}
		/* 1バイト受信または受信エラーが発生したら、受信通知コールバックを呼び出します。 */
		if(com.recv_callback) com.recv_callback();
	}
	/* この後、次のキャラクタを受信した場合は、再び割り込み要因がセットされて、割り込みルーチン終了直後にまた割り込みがかかります。 */

	/* 送信バッファエンプティ? */
	if(bSIF1_STATUS_TDBE1) {
		/* ドライバ構\造体の送信バッファにデータあり? */
		if(com.txbuf_len) {
			/* 1キャラクタ送信します。 */
			v = com.txbuf[com.txbuf_pos];
			pSIF1_TXD = v;
			com.txbuf_pos++;
			if(com.txbuf_pos == com.txbuf_size) com.txbuf_pos = 0;
			com.txbuf_len--;
		} else {
			/* シリアルI/Fの送信バッファとドライバの送信バッファがどちらも空になったら、送信完了コールバックを呼び出します。
			 * ※2004/05/31現在の実装では、必ずしも送信完了タイミングでのみコールバックするわけではありません。
			 *   シリアルI/Fとドライバの送信バッファがどちらも空である任意のタイミングで、コールバックする可能\性があります。
			 *   あまり使いみちはないかも知れません・・・
			 */
			if(com.send_callback) com.send_callback();
		}
	}
	/* この後、送信バッファが空になった場合は、再び割り込み要因がセットされて、割り込みルーチン終了直後にまた割り込みがかかります。 */
}

int
com_recv(void* _buf, int maxlen)
{
	char* buf = (char*)_buf;
	int len = -1;
	int i;

	/* COMポートが開かれていなければエラー。 */
	if(!com.open) DIE();

ENTER_CS;
	if(!com.rxbuf_overflow && /* ドライバ構\造体の受信バッファオーバーフロー */
	   !bSIF1_STATUS_OER1  && /* Serial I/F Ch.1 overrun error flag */
	   !bSIF1_STATUS_PER1  && /* Serial I/F Ch.1 parity error flag */
	   !bSIF1_STATUS_FER1) {  /* Serial I/F Ch.1 flaming error flag */

		/* 受信データサイズを、指定されたバッファサイズ以下に切り詰めます。 */
		len = com.rxbuf_len;
		if(len > maxlen) len = maxlen;

		/* ドライバ構\造体の受信バッファから、指定されたバッファにコピーします。 */
		for(i = 0; i < len; i++) {
			*buf++ = com.rxbuf[com.rxbuf_pos];
			com.rxbuf_pos++;
			if(com.rxbuf_pos == com.rxbuf_size) com.rxbuf_pos = 0;
		}
		com.rxbuf_len -= len;
	}
LEAVE_CS;

	return len;
}

#if 0
//送信バッファがいっぱいなら、ブロックせずに要求送信サイズ未満で終了するバージョンです。
//アプリケーション側で送信バッファ空き待ちの戦略が立てられるので、柔軟ですが、面倒です。
//送信バッファが空くまでブロックするバージョンに置き換えたので、こちらはもう使いません。
//int
//com_send(const void* _buf, int len)
//{
//	const char* buf = (const char*)_buf;
//	int maxlen;
//	int i;
//	int j;
//
//	/* COMポートが開かれていなければエラー。 */
//	if(!com.open) DIE();
//
//	/* 送信データサイズに負値が指定されていたら、AsciiZ文字列と見なします。 */
//	if(len < 0) len = strlen(buf);
//
//ENTER_CS;
//	/* 送信データサイズを、ドライバ構\造体の送信バッファの空きサイズ以下に切り詰めます。 */
//	maxlen = com.txbuf_size - com.txbuf_len;
//	if(len > maxlen) len = maxlen;
//
//	/* 最初の書き込みインデクスを求めます。 */
//	i = com.txbuf_pos + com.txbuf_len;
//	if(i >= com.txbuf_size) i -= com.txbuf_size;
//
//	/* 送信データを、ドライバ構\造体の送信バッファに追加します。 */
//	for(j = 0; j < len; j++) {
//		com.txbuf[i] = *buf++;
//		i++;
//		if(i == com.txbuf_size) i = 0;
//	}
//	com.txbuf_len += len;
//
//	/* 送信動作が停止していた場合、最初のキャラクタの送信を開始するために、直接、割り込みサービスルーチンを呼び出します。
//	 * 既に送信動作中で、送信バッファエンプティでなければ、割り込みサービスルーチンは何もせずに処理を返すので、安全です。
//	 */
//	_com_isr();
//LEAVE_CS;
//
//	return len;
//}
#else
int
com_send(const void* _buf, int len)
{
	const char* buf = (const char*)_buf;
	int i;
	int n;

	/* COMポートが開かれていなければエラー。 */
	if(!com.open) DIE();

	/* 送信データサイズに負値が指定されていたら、AsciiZ文字列と見なします。 */
	if(len < 0) len = strlen(buf);

	/* 全ての送信データをドライバ構\造体の送信バッファに格納できるまで... */
	n = len;
	while(n) {
		/* このタイミングで、送信バッファエンプティ割り込みや、その他のデバイスの割り込みを受付可能\です。 */
ENTER_CS;
		/* ドライバ構\造体の送信バッファに空きがあれば、格納します。 */
		if(com.txbuf_len < com.txbuf_size) {
			i = com.txbuf_pos + com.txbuf_len;
			if(i >= com.txbuf_size) i -= com.txbuf_size;
			com.txbuf[i] = *buf++;
			com.txbuf_len++;
			n--;
		}

		/* 以下の二つの理由により、明示的に割り込みサービスルーチンを呼び出します。
		 *
		 * １．	送信動作が停止していた場合、最初のキャラクタの送信を開始するために、直接、割り込みサービスルーチンを呼び出します。
		 *	既に送信動作中で、送信バッファエンプティでなければ、割り込みサービスルーチンは何もせずに処理を返すので、安全です。
		 *
		 *	１．の理由だけならば、上の「if(com.txbuf_len < com.txbuf_size) {...}」ブロックが実行された場合にだけ_com_isr()を
		 *	呼ぶほうが効率がいいのですが、次に述べる２．の理由により、毎回_com_isr()を呼ぶことにしました。
		 *
		 * ２．	ドライバ構\造体の送信バッファに空きが無く、かつcom_send()が割り込み禁止で呼ばれた場合、割り込みが発生しなくても
		 *	ここで明示的に割り込みサービスルーチンを呼び出すことにより、バッファエンプティが処理されてバッファに空きができます。
		 *
		 *	毎回_com_isr()を呼んでいるので、送信バッファ空き待ちブロック中にシリアル通信割り込みは発生しなくても構\いません。
		 *	しかし、ブロック中にシリアル通信割り込み以外の割り込みが長期間禁止されてしまうことを避けるために、
		 *	毎回ENTER_CSとLEAVE_CSを行って、シリアル通信割り込み以外の割り込みを受け付ける機会を設けることにしました。
		 */
		_com_isr();
LEAVE_CS;
		/* このタイミングで、送信バッファエンプティ割り込みや、その他のデバイスの割り込みを受付可能\です。 */
	}

	return len;
}
#endif

void
com_reset()
{
	/* COMポートが開かれていなければエラー。 */
	if(!com.open) DIE();

ENTER_CS;
	/* ドライバ構\造体をリセットします。 */
	com.rxbuf_overflow = 0;
	com.rxbuf_pos = 0;
	com.rxbuf_len = 0;
	com.txbuf_pos = 0;
	com.txbuf_len = 0;

	/* エラー要因をリセットします。 */
	bSIF1_STATUS_OER1 = 0;	/* Serial I/F Ch.1 overrun error flag */
	bSIF1_STATUS_PER1 = 0;	/* Serial I/F Ch.1 parity error flag */
	bSIF1_STATUS_FER1 = 0;	/* Serial I/F Ch.1 flaming error flag */
LEAVE_CS;
}

void
com_rts(int value)
{
	/* COMポートが開かれていなければエラー。 */
	if(!com.open) DIE();

	/* RS-232C信号線上では、RTS={0:-12V,1:+12V}です。(TxDやRxDとは逆です) */
	value = !value;

	/* #RTS端子の出力を設定します。
	 * Read/Modify/Writeの動作になるので、割り込み禁止が必要です!!
	 */
ENTER_CS;
	bP1_P1D_P12D = value;
LEAVE_CS;
}

int
com_cts()
{
	int value;

	/* COMポートが開かれていなければエラー。 */
	if(!com.open) DIE();

	/* #CTS端子の入力を取得します。
	 * 読み込みだけなので、割り込み禁止は不要です。
	 */
	value = bP1_P1D_P14D;

	/* RS-232C信号線上では、CTS={0:-12V,1:+12V}です。(TxDやRxDとは逆です) */
	value = !value;

	return value;
}
