//-----------------------------------------------------------------------
// SPC(CZ-6BS1/内蔵)を直接アクセスする関数群
//	Mach2(p)には対応しないので注意
//-----------------------------------------------------------------------

#include <iocslib.h>
#include <time.h>
#include <string.h>
#include "doslibm.h"
#include "defines.h"
#include "acdata.h"
#include "ascsi.h"
#include "dma.h"

#define	DEBUG	0
#if DEBUG
#include "astdio.h"
#endif

//-----------------------------------------------------------------------
// グローバル変数

volatile DMAREG *dma;	// DMAレジスタのアドレス
volatile SPCREG *spc;	// SPCレジスタのアドレス
static	time_t	tmout;	// タイムアウト時間（1/CLK_TCK秒）:0=タイムアウトなし

//-----------------------------------------------------------------------

int SPC_init(time_t tm)
// 初期化
// tm=待ちがあるところのタイムアウト時間（秒）
{
int type;
int sp,ret,ret2;
unchar *sram;
unchar buff[6];
	sp=SUPER(0); // スーパーバイザーへ
	
	sram=(unchar *)0xed006f;
	if (*sram!='V') {
		#if DEBUG
		printf2("SCSIはない/使わないになっている\n");
		#endif
		ret=(1);
		goto SPC_init_END;
	}
	// SCSIを使うになっている
	sram=(unchar *)0xed0070;
	type=*sram;
	ret=BUS_ERR((unchar*)0xfc0000,buff,sizeof(short)); // 内蔵領域チェック
//	printf2("buserr ret=%d\n",ret);
	if (ret) {
		#if DEBUG
		printf2("バスエラーが起こった＝内蔵SCSIはない:%d\n",ret);
		#endif
	} else 	{
		ret=memcmp((unchar*)0xfc0024,"SCSIIN",6); // 内蔵ボードがあるか
//		printf2("内蔵ret=%d\n",ret);
	}
	ret2=BUS_ERR((unchar*)0xea0000,buff,sizeof(short)); // 外付けボード領域チェック
	// 0xea0044ではバスエラーが起こらないので注意
//	printf2("buserr ret2=%d\n",ret2);
	if (ret2) {
		#if DEBUG
		printf2("バスエラーが起こった＝外付けSCSIはない:%d\n",ret2);
		#endif
	} else 	{
		ret2=memcmp((unchar*)0xea0044,"SCSIEX",6);
//		printf2("外付けret2=%d\n",ret2);
	}
	
	if (ret2) { // 外付けなし
		if (ret) { // 内蔵もない
			#if DEBUG
			printf2("内蔵SCSIも外付けSCSIもない\n");
			#endif
			ret=(1); // SCSIはない
			goto SPC_init_END;
		} else { // 内蔵はある
			type&=(~BIT3); // 内蔵を使うの印
		}
	} else { // 外付けがある
		if (ret) { // 内蔵はない
			type|=BIT3; // 外付けを使うの印
		} // 内蔵もある;どちらを使うかはSRAMの情報に従う
	}
	#if DEBUG
	{
	unshort *sramw;
	sramw=(unshort *)0xed0098;
	if (*sramw==0x4368) { // Mach2(p)である
		printf2("SCSI Interface : Mach2pは使えません\n");
	}
	}
	#endif
	
	// ポート設定
	dma=DMAadrs(1); // DMA CH1
	#if DEBUG			//	   CZ-6BS1 : 内蔵
	printf2("SCSI Interface : %s\n",type&BIT3? "外付け":"内蔵");
	#endif
	spc=SPC_adrs(type);
	spc->bdid=type&7; // 自分のID
	tmout=tm*CLK_TCK; // タイムアウト時間をclock()の単位で記録
	ret=0;
    SPC_init_END:;
	if (sp>=0) SUPER(sp);
	return(ret);
}

//-----------------------------------------------------------------------

static int WaitPhase(unchar phase)
// 指定フェーズになるのを待つ
{
unchar psns;
time_t st;
	#if DEBUG
	printf2("-PHASE WAIT-");
	#endif
	st=clock(); // 開始時間
	while (psns=spc->psns,!(psns&PSNS_REQ)) { // REQ=1待ち
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitPhase)!!");
				#endif
				S_RESET(); // バスをリセットする
				return(-2);
			}
		}
		if (spc->ints&INTS_DISCONNECT) { // Dis-Connectされた
			#if DEBUG
			printf2("-DisConnect-");
			#endif
			S_RESET(); // バスをリセットする
			return(-1);
		}
	}
	psns&=0b111; // MSG|CD|IOだけ取りだし
	if (psns!=phase) { // 目的の状態ではない
		#if DEBUG
		printf2("-目的の状態ではない-");
		#endif
		return((spc->ints<<8)|spc->psns);
	}
	spc->pctl=psns;	// フェーズの決定
	#if DEBUG
	printf2("-PHASE END-");
	#endif
	return(0);
}

static int WaitINTS(unchar *ints)
// INTSの変化を待つ
{
time_t st;
	st=clock(); // 開始時間
	while (!(*ints=spc->ints)) {
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitINTS)!!");
				#endif
				S_RESET(); // バスをリセットする
				return(-2);
			}
		}
	}
	spc->ints=*ints; // 割り込み要因リセット
	return(*ints!=INTS_COMPLETE);
}

static int WaitDREGEmpty()
// DREGの空きを待つ
{
time_t st;
	st=clock(); // 開始時間
	while (!(spc->ssts&SSTS_DREG_EMPTY)) { // 何等かのデータがdregにある
		if (spc->ints&INTS_CANTTRNS) { // TRANSFERコマンドを実行出来なくなった
			#if DEBUG
			printf2("-TRANSFERコマンドが実行出来ない-");
			#endif
			return((spc->ints<<8)|spc->psns);
		}
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitDREGEmpty)!!");
				#endif
				S_RESET(); // バスをリセットする
				return(-2);
			}
		}
	}
	return(0);
}

static int WaitDREGIn()
// データがまだ来てない時は待つ
{
time_t st;
	st=clock(); // 開始時間
	while (spc->ssts&SSTS_DREG_EMPTY) {
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitDREGIn)!!");
				#endif
				S_RESET(); // バスをリセットする
				return(-2);
			}
		}
	}
	return(0);
}

//-----------------------------------------------------------------------

int SPC_select(unlong id,int atn)
// セレクションフェーズ
// atn :ATN=atnで実行する(0/1のみ)
{
unchar ints;
int ret=0;
int sp;
	#if DEBUG
	printf2("SELECT");
	#endif
	
	sp=SUPER(0); // スーパーバイザーへ
	spc->pctl=0;			// bit0=0:セレクションフェーズ
	if (atn) {
		spc->scmd=SCMD_SET_ATN;	// 次にメッセージアウトが続く
	}
	{
	time_t st;
	st=clock(); // 開始時間
	while (spc->ssts & 0b11111000) { // バスが空くのを待つ
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT!!");
				#endif
				S_RESET(); // バスをリセットする
				ret=(-2);
				goto SPC_select_END;
			}
		}
	}
	}
	id&=0x7;			// LUNは取り除く(LUNはMESSAGE_OUTで処理するから)
	spc->sctl=0b00010000;		// アービトレーションフェーズを実行する(0x10)
	spc->temp=(BIT0<<id)|(spc->bdid);
	{
	time_t t;
	if (tmout==0) t=0;
	else t=(tmout*1000/CLK_TCK)/(200*2)/256;
	spc->tch=(unchar)(t>>8);
	spc->tcm=(unchar) t; 		// タイムアウト時間１
	}
	spc->tcl=3;			// タイムアウト時間２(X68での推奨値)
	//
	spc->ints=spc->ints;		// 割り込み要因リセット
	spc->scmd=SCMD_SELECT;
	ret=WaitINTS(&ints);		// INTS変化待ち
	if (ret>0) {
		if (ints&INTS_TIMEOUT) { // タイムアウトが起こった
			#if DEBUG
			printf2("-TimeOut-");
			#endif
			spc->temp=0;	// バス解放(insideX68 p479)
			ret=(-1);
			goto SPC_select_END;
		}
		// タイムアウト以外のエラーが起こった
		#if DEBUG
		printf2("-タイムアウト以外のエラーが起こった-");
		#endif
		ret=((ints<<8)|spc->psns);
	} // ret=0/<0
    SPC_select_END:;
	#if DEBUG
	printf2(":%08b\n",spc->psns);
	#endif
	if (sp>=0) SUPER(sp);
	return(ret);
}

//-----------------------------------------------------------------------

int SPC_command(int len,unchar *param)
// コマンドアウトフェーズ
{
int ret=0;
unchar ints;
int sp;
	#if DEBUG
	printf2("COMMAND");
	#endif
	
	sp=SUPER(0); // スーパーバイザーへ
	ret=WaitPhase(PSNS_COMMAND); // コマンドアウトフェーズ待ち
	if (ret) goto SPC_command_END; // エラー時
	#if DEBUG
	printf2("-len=%d-",len);
	#endif
	spc->tch=0;
	spc->tcm=0;
	spc->tcl=len;	// コマンド長さは<256なのでここだけ入れる
	
	spc->ints=spc->ints;		// 割り込み要因リセット
	spc->scmd=SCMD_TRANSFER;
	ints=spc->ints;			// 割り込み要因読みだし
	spc->ints=ints;			// 割り込み要因リセット
	if (ints) {			// 何等かの割り込みがある
		#if DEBUG
		printf2("-Unknown Interrupt-");
		#endif
		ret=((ints<<8)|spc->psns);
		goto SPC_command_END;
	}
	
	// コマンド転送(CPU)
	#if DEBUG
	printf2("-CPU-");
	#endif
	ret=WaitDREGEmpty(); // DREGの空き待ち
	if (ret) goto SPC_command_END;
	while (len-->0) {
		spc->dreg=*param++;
		ret=WaitDREGEmpty(); // コマンドがまだ受け取られていない時は待つ
		if (ret) goto SPC_command_END;
	}
	#if DEBUG
	printf2("-CPU END-");
	#endif
	ret=WaitINTS(&ints); // INTS変化待ち
	if (ret>0) {
		#if DEBUG
		printf2("-コマンドが完了しない-");
		#endif
		ret=((ints<<8)|spc->psns);
		goto SPC_command_END;
	} // ret=0/<0
    SPC_command_END:;
	#if DEBUG
	printf2(":%08b\n",spc->psns);
	//printf2("STS=%08b\n",spc->ssts);
	#endif
	//
	if (sp>=0) SUPER(sp);
	return(ret);
}

//-----------------------------------------------------------------------

int SPC_data_inout(int len,unchar *buff,int direc)
// データイン・アウトフェーズ
// direc : in=DMA_IN/out=DMA_OUT | BIT15=CPU
{
int ret,fcpu;
unchar ints;
int sp;
	fcpu=direc&BIT15;
	direc&=~BIT15;
	#if DEBUG
	printf2("DATA IN/OUT(%s)",fcpu? "CPU":"DMA");
	#endif
	sp=SUPER(0); // スーパーバイザーへ
	
	// データイン・アウトフェーズ待ち
	ret=WaitPhase(direc==DMA_IN? PSNS_DATA_IN:PSNS_DATA_OUT);
	if (ret) goto SPC_datainout_END; // エラー時
	// DATAINの時は、もうこの時点でデータが転送されてきている
	
	spc->tch=(unchar)(len>>16); // 長さH
	spc->tcm=(unchar)(len>>8 ); // 長さM
	spc->tcl=(unchar) len     ; // 長さL
	
	spc->ints=spc->ints;	// 割り込み要因リセット
	spc->scmd=SCMD_TRANSFER;
	
	ints=spc->ints;		// 割り込み要因読みだし
	spc->ints=ints;		// 割り込み要因リセット
	if (ints) {		// 何等かの割り込みがある
		#if DEBUG
		printf2("-Unknown Interrupt-");
		#endif
		ret=((ints<<8)|spc->psns);
		goto SPC_datainout_END;
	}
	
	if (fcpu) { // データ転送(CPU)
	    #if DEBUG
	    printf2("-CPU(len=%d)-",len);
	    #endif
	    if (direc==DMA_IN) { // DATAIN
		#if DEBUG
		printf2("(DATAIN)");
		#endif
		while (len-->0) {
			ret=WaitDREGIn(); // データがまだ来てない時は待つ
			if (ret) goto SPC_datainout_END;
			*buff++=spc->dreg;
		}
	    } else { // DATAOUT
		#if DEBUG
		printf2("(DATAOUT)");
		#endif
		while (len-->0) {
			spc->dreg=*buff++;
			ret=WaitDREGEmpty(); // データがまだ受理されていない時は待つ
			if (ret) goto SPC_datainout_END;
		}
	    }
	    #if DEBUG
	    printf2("-CPU END-");
	    #endif
	} else { // データ転送(DMA)
	    dma->ccr=0x00;		// DMA停止
	    dma->dcr=0b10000000;	// 外部要求＋ホールドなしサイクルスチールモード
	    dma->ocr=direc|0b00110001;	// パックなし ポートサイズ 8bit バイト(8bit)転送
	    				// chain動作なし、オートリクエスト最大速度
	    dma->scr=0b00000100;	// mar側+1,dar側不変
	    dma->cpr=0b00000011;	// CH優先順位:もっとも低い
	    dma->mfc=0b00000101;	// mar側:スーパーバイザーデータ領域
	    dma->dfc=0b00000101;	// dar側:スーパーバイザーデータ領域
	    dma->mar=buff;
	    dma->dar=&(spc->dreg);
	    #if DEBUG
	    printf2("-DMA(len=%d)-",len);
	    #endif
	    #define	DMA_MAX_LEN	0xff00	// DMAの１回転送で送れるカウント数
	    // DMAは１回の転送でDMA_MAX_LENまでしか送れないので、それ以上の時は分割する
	    while (len>0) {
		time_t st;
		dma->mtc=(len>DMA_MAX_LEN? DMA_MAX_LEN:len);
		dma->csr=0xff;	// フラグクリア
		dma->ccr|=BIT7; // DMA動作開始
		st=clock(); // 開始時間
		while (!(dma->csr&DMA_COMPLETE)) { // DMA complete待ち
			ints=spc->ints;
			if (ints&INTS_CANTTRNS) { // TRANSFERコマンドを実行出来なくなった
				#if DEBUG
				printf2("-Can't execute transfer-");
				#endif
				goto DMA_STOP;
			}
			if (ints&INTS_COMPLETE) { // Transfer Complete（先に転送が終わった）
				// SCSI BIOSでは再度転送を試みるが、ここではしない
				#if DEBUG
				printf2("-先に転送が終わった-");
				#endif
			    DMA_STOP:;
				dma->ccr=BIT4;	// DMA強制停止
				ret=((ints<<8)|spc->psns);
				goto SPC_datainout_END;
			}
			if (tmout>0) {
				if (clock()-st>tmout) {
					#if DEBUG
					printf2("!!TIMEOUT!!");
					#endif
					S_RESET(); // バスをリセットする
					ret=(-2);
					goto SPC_datainout_END;
				}
			}
		}
		if (dma->cer&0b00011111) { // DMAエラー
			#if DEBUG
			printf2("-DMAエラー:%08b-",dma->cer);
			#endif
			goto DMA_ERR;
		}
		if (dma->mtc!=0) { // データが転送しきれていない
			#if DEBUG
			printf2("-データが転送しきれていない:%d-",dma->mtc);
			#endif
		    DMA_ERR:;
			ret=((spc->ints<<8)|spc->psns);
			goto SPC_datainout_END;
		}
		if (len<=DMA_MAX_LEN) break;
		len-=DMA_MAX_LEN;
	    }
	    #if DEBUG
	    printf2("-DMA END-");
	    #endif
	}
	ret=WaitINTS(&ints); // INTS変化待ち
	if (ret>0) {
		#if DEBUG
		printf2("-データ転送が完了しない-");
		#endif
		ret=((ints<<8)|spc->psns);
	} // ret=0/<0
    SPC_datainout_END:;
	#if DEBUG
	printf2(":%08b\n",spc->psns);
	//printf2("STS=%08b\n",spc->ssts);
	#endif
	if (sp>=0) SUPER(sp);
	return(ret);
}

//-----------------------------------------------------------------------

static int WaitPSNS(unchar psns)
{
int ret=0;
time_t st;
	st=clock(); // 開始時間
	while (spc->psns&psns) {
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitPSNS)!!");
				#endif
				S_RESET(); // バスをリセットする
				ret=(-2);
				break;
			}
		}
	}
	return(ret);
}

static int WaitPSNSn(unchar psns)
{
int ret=0;
time_t st;
	st=clock(); // 開始時間
	while (!(spc->psns&psns)) {
		if (tmout>0) {
			if (clock()-st>tmout) {
				#if DEBUG
				printf2("!!TIMEOUT(WaitPSNSn)!!");
				#endif
				S_RESET(); // バスをリセットする
				ret=(-2);
				break;
			}
		}
	}
	return(ret);
}

int SPC_status_message(unchar *dat,unchar phase)
// ステータスイン／メッセージイン・アウトフェーズ
// phase : PSNSのフェーズ状態そのもの(PSNS_*)
{
int ret;
int sp;
	#if DEBUG
	{
	static unchar *mes;
	switch (phase) {
		case PSNS_STATUS     :mes="STATUS IN";   break;
		case PSNS_MESSAGE_IN :mes="MESSAGE IN";  break;
		case PSNS_MESSAGE_OUT:mes="MESSAGE OUT"; break;
	}
	printf2(mes);
	}
	#endif
	
	sp=SUPER(0); // スーパーバイザーへ
	ret=WaitPhase(phase); // フェーズ待ち
	if (!ret) {
		// バス上から１バイトのデータを得る
		ret=WaitPSNSn(PSNS_REQ); // REQ=1待ち
		if (!ret) {
			if (phase==PSNS_MESSAGE_OUT) {
				spc->temp=*dat;
				// ここを共通にするため、message outも引数をアドレス渡しする
			} else {
				*dat=spc->temp;
			}
			spc->scmd=SCMD_SET_ACKB;
			ret=WaitPSNS(PSNS_REQ);
			if (!ret) {
				spc->scmd=SCMD_RESET_ACKB;
				ret=WaitPSNS(PSNS_ACK);
			}
		}
	}
	#if DEBUG
	printf2(":%08b\n",spc->psns);
	#endif
	if (sp>=0) SUPER(sp);
	return(ret);
}

//-----------------------------------------------------------------------
// テスト
//-----------------------------------------------------------------------

#if DEBUG
#include <stdlib.h>

#define	READ	1

#if READ
static unchar ReadExt[]={
	 0x28
	,0x00			// bit7,6,5=LUN
	,0x00,0x00,0x00,0x00	// LBA H.H,H.L,L.H,L.L (68形式)
	,0x00
	,0x00,0x01		// 転送ブロック数 H,L ; このテストでは常に１
	,0x00
};
#else
static unchar WriteExt[]={
	 0x2a
	,0x00			// bit7,6,5=LUN
	,0x00,0x00,0x00,0x00	// LBA H.H,H.L,L.H,L.L (68形式)
	,0x00
	,0x00,0x01		// 転送ブロック数 H,L ; このテストでは常に１
	,0x00
};
#endif

#define	SECSZ	2048
unchar buff[SECSZ];

void main()
{
unchar stat,mes;
int i,j;
int id=5;
int sp;
	sp=B_SUPER(0);
	if (SPC_init(0)) {
		printf2("SCSIが準備出来ない\n");
		exit(2);
	}
//	printf2("psns=%p\n",&spc->psns);
//	printf2("pctl=%p\n",&spc->pctl);
//	printf2("tcl=%p\n",&spc->tcl);
	
	if (S_CheckReady(id)) {
		printf2("ドライブが準備出来ない:ID%d($%x)\n",id,id);
		exit(3);
	}
	
	memset(buff,0xff,sizeof(buff));
	
	if (i=SPC_select(id,0)) {
		printf2("SELECT ERROR:%016b\n",i);
		exit(1);
	}
	#if READ
	ltoc(0,&ReadExt[2]);	// LBA
	if (i=SPC_command(sizeof(ReadExt),ReadExt)) {
		printf2("COMMAND ERROR:%016b\n",i);
		exit(1);
	} else {
		if (i=SPC_data_inout(SECSZ,buff,DMA_IN|BIT15)) {
			printf2("DATA IN/OUT ERROR:%016b\n",i);
		}
	}
	#else
	ltoc(0,&WriteExt[2]);	// LBA
	if (i=SPC_command(sizeof(WriteExt),WriteExt)) {
		printf2("COMMAND ERROR:%016b\n",i);
		exit(1);
	} else {
		if (i=SPC_data_inout(SECSZ,buff,DMA_OUT)) {
			printf2("DATA IN/OUT ERROR:%016b\n",i);
		}
	}
	#endif
	if (i=SPC_status_in(&stat)) {
		printf2("STATUS ERROR:%016b\n",i);
		exit(1);
	}
	printf2("Status=$%02x\n",stat);
	if (i=SPC_message_in(&mes)) {
		printf2("MESSAGE ERROR:%016b\n",i);
		exit(1);
	}
	printf2("Message=$%02x\n",mes);
	// データ表示
	B_SUPER(sp);
	for (i=0;i<SECSZ;i+=16) {
		printf2("%04X ",i);
		for (j=i;j<i+16;j++) {
			printf2("%02X ",buff[j]);
		}
		printf2("\n");
	}
	exit(0);
}
#endif

//-----------------------------------------------------------------------
