//
// FDC μPD765A
//

#include	"compiler.h"
#include	"cpucore.h"
#include	"pccore.h"
#include	"iocore.h"
#include	"diskimage/fddfile.h"
#if defined(SUPPORT_SWSEEKSND)
#include	"sound/pcmmix.h"
#include	"fdd_mtr.res"
#endif
#include	"soundmng.h"

#if 1
#undef	TRACEOUT
#define	TRACEOUT(s)	(void)(s)
#endif	/* 0 */


enum {
	FDC_DMACH2HD	= 2,
	FDC_DMACH2DD	= 3
};

static const UINT8 FDCCMD_TABLE[32] = {
						0, 0, 8, 2, 1, 8, 8, 1, 0, 8, 1, 0, 8, 5, 0, 2,
						0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0};


#define FDC_FORCEREADY (1)
#define	FDC_DELAYERROR7

#define FDC_INT_DELAY		5			/*!< Delay 100ms */

void fdc_intwait(NEVENTITEM item) {
	
	if (item->flag & NEVENT_SETEVENT) {
		fdc.intreq = TRUE;
		if (fdc.chgreg & 1) {
			pic_setirq(0x0b);
		}
		else {
			pic_setirq(0x0a);
		}
	}
}

void fdc_interrupt(void) {
	
	nevent_set(NEVENT_FDCINT, 512, fdc_intwait, NEVENT_ABSOLUTE);
}

static void fdc_interruptreset(void) {

	fdc.intreq = FALSE;
}

static BOOL fdc_isfdcinterrupt(void) {

	return(fdc.intreq);
}

REG8 DMACCALL fdc_dmafunc(REG8 func) {

//	TRACEOUT(("fdc_dmafunc = %d", func));
	switch(func) {
		case DMAEXT_START:
			return(1);

		case DMAEXT_END:				// TC
			fdc.tc = 1;
			break;
	}
	return(0);
}

static void fdc_dmaready(REG8 enable) {

	if (fdc.chgreg & 1) {
		dmac.dmach[FDC_DMACH2HD].ready = enable;
	}
	else {
		dmac.dmach[FDC_DMACH2DD].ready = enable;
	}
}


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

void fdcsend_error7(void) {

	fdc.tc = 0;
	fdc.event = FDCEVENT_BUFSEND;
	fdc.bufp = 0;
	fdc.bufcnt = 7;
	fdc.buf[0] = (UINT8)(fdc.stat[fdc.us] >>  0);
	fdc.buf[1] = (UINT8)(fdc.stat[fdc.us] >>  8);
	fdc.buf[2] = (UINT8)(fdc.stat[fdc.us] >> 16);
	fdc.buf[3] = fdc.C;
	fdc.buf[4] = fdc.H;
	fdc.buf[5] = fdc.R;
	fdc.buf[6] = fdc.N;
	fdc.status &= 0x0f;
	fdc.status |= (1 << fdc.us);
	fdc.status |= FDCSTAT_RQM | FDCSTAT_CB | FDCSTAT_DIO;
	fdc.stat[fdc.us] = 0;										// ver0.29
	fdc_dmaready(0);
	dmac_check();
	fdc_interrupt();
}

void fdcsend_success7(void) {

	fdc.tc = 0;
	fdc.event = FDCEVENT_BUFSEND;
	fdc.bufp = 0;
	fdc.bufcnt = 7;
#ifdef SUPPORT_KAI_IMAGES
	if (fdd_fdcresult() == FALSE) {
		fdc.buf[0] = (fdc.hd << 2) | fdc.us;
		fdc.buf[1] = 0;
		fdc.buf[2] = 0;
	}
	else {
		//	FDイメージファイルがFDCリザルトコードを持っている場合は
		//	(fdc.statに設定しているはずなので)そちらを設定
		fdc.buf[0] = (fdc.hd << 2) | fdc.us | (UINT8)(fdc.stat[fdc.us] >>  0);
		fdc.buf[1] = (UINT8)(fdc.stat[fdc.us] >>  8);
		fdc.buf[2] = (UINT8)(fdc.stat[fdc.us] >> 16);
	}
#else
	fdc.buf[0] = (fdc.hd << 2) | fdc.us;
	fdc.buf[1] = 0;
	fdc.buf[2] = 0;
#endif
	fdc.buf[3] = fdc.C;
	fdc.buf[4] = fdc.H;
	fdc.buf[5] = fdc.R;
	fdc.buf[6] = fdc.N;
	fdc.status &= 0x0f;
	fdc.status |= (1 << fdc.us);
	fdc.status |= FDCSTAT_RQM | FDCSTAT_CB | FDCSTAT_DIO;
	fdc.stat[fdc.us] = 0;										// ver0.29
	fdc_dmaready(0);
	dmac_check();
	fdc_interrupt();
}

#if 0
// FDCのタイムアウト			まぁ本当はこんなんじゃダメだけど…	ver0.29
void fdctimeoutproc(NEVENTITEM item) {

	if (item->flag & NEVENT_SETEVENT) {
		fdc.stat[fdc.us] = FDCRLT_IC0 | FDCRLT_EN | (fdc.hd << 2) | fdc.us;
		fdcsend_error7();
	}
}

static void fdc_timeoutset(void) {

	nevent_setbyms(NEVENT_FDCTIMEOUT, 166, fdctimeoutproc, NEVENT_ABSOLUTE);
}
#endif



static BOOL FDC_DriveCheck(BOOL protectcheck) {

//	if (!fddfile[fdc.us].fname[0]) {
	if (!fdd_diskready(fdc.us)) {
		fdc.stat[fdc.us] = FDCRLT_IC0 | FDCRLT_NR | (fdc.hd << 2) | fdc.us;
		fdcsend_error7();
		return(FALSE);
	}
//	else if ((protectcheck) && (fddfile[fdc.us].protect)) {
	else if ((protectcheck) && fdd_diskprotect(fdc.us)) {
		fdc.stat[fdc.us] = FDCRLT_IC0 | FDCRLT_NW | (fdc.hd << 2) | fdc.us;
		fdcsend_error7();
		return(FALSE);
	}
	return(TRUE);
}

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

static void get_mtmfsk(void) {

	fdc.mt = (fdc.cmd >> 7) & 1;
	fdc.mf = fdc.cmd & 0x40;							// ver0.29
	fdc.sk = (fdc.cmd >> 5) & 1;
}

static void get_hdus(void) {

	fdc.hd = (fdc.cmds[0] >> 2) & 1;
	fdc.us = fdc.cmds[0] & 3;
}

static void get_chrn(void) {

	fdc.C = fdc.cmds[1];
	fdc.H = fdc.cmds[2];
	fdc.R = fdc.cmds[3];
	fdc.N = fdc.cmds[4];
}

static void get_eotgsldtl(void) {

	fdc.eot = fdc.cmds[5];
	fdc.gpl = fdc.cmds[6];
	fdc.dtl = fdc.cmds[7];
}

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

static void FDC_Invalid(void) {							// cmd: xx

	fdc.event = FDCEVENT_BUFSEND;
	fdc.bufcnt = 1;
	fdc.bufp = 0;
	fdc.buf[0] = 0x80;
	fdc.status &= 0x0f;
	fdc.status |= FDCSTAT_RQM | FDCSTAT_CB | FDCSTAT_DIO;
}

#ifdef SUPPORT_KAI_IMAGES
static void FDC_ReadDiagnostic(void) {					// cmd: 02

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
			get_chrn();
			get_eotgsldtl();
			fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;

			if (FDC_DriveCheck(FALSE)) {
				fdc.event = FDCEVENT_BUFSEND;
//				fdc.bufcnt = makedianosedata();
				fdc.bufp = 0;
			}
			break;

		default:
			fdc.event = FDCEVENT_NEUTRAL;
			break;
	}
}
#endif

static void FDC_Specify(void) {							// cmd: 03

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			fdc.srt = fdc.cmds[0] >> 4;
			fdc.hut = fdc.cmds[0] & 0x0f;
			fdc.hlt = fdc.cmds[1] >> 1;
			fdc.nd = fdc.cmds[1] & 1;
			fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
			break;
	}
	fdc.event = FDCEVENT_NEUTRAL;
	fdc.status &= 0x0f;
	fdc.status |= FDCSTAT_RQM;
}

static void FDC_SenseDeviceStatus(void) {				// cmd: 04

	switch (fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
			fdc.buf[0] = (fdc.hd << 2) | fdc.us;
			fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
			if (fdc.equip & (1 << fdc.us)) {
				fdc.buf[0] |= 0x08;
				if (!fdc.treg[fdc.us]) {
					fdc.buf[0] |= 0x10;
				}
				if (fdd_diskready(fdc.us) || (fdc.ctrlreg & 0x40)) {/* 170107 念のため */
					fdc.buf[0] |= 0x20;
				}
				if (fddfile[fdc.us].protect) {
					fdc.buf[0] |= 0x40;
				}
			}
			else {
				fdc.buf[0] |= 0x80;
			}
//			TRACEOUT(("FDC_SenseDeviceStatus %.2x", fdc.buf[0]));
			fdc.event = FDCEVENT_BUFSEND;
			fdc.bufcnt = 1;
			fdc.bufp = 0;
			fdc.status &= 0x0f;
			fdc.status |= (1 << fdc.us);
			fdc.status |= FDCSTAT_RQM | FDCSTAT_CB | FDCSTAT_DIO;
			break;

		default:
			fdc.event = FDCEVENT_NEUTRAL;
			fdc.status &= 0x0f;
			fdc.status |= FDCSTAT_RQM;
			break;
	}
}

static BRESULT writesector(void) {

	fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
	if (!FDC_DriveCheck(TRUE)) {
		return(FAILURE);
	}
	if (fdd_write()) {
		fdc.stat[fdc.us] = fdc.us | (fdc.hd << 2) | FDCRLT_IC0 | FDCRLT_ND;
		fdcsend_error7();
		return(FAILURE);
	}
	fdc.event = FDCEVENT_BUFRECV;
	fdc.bufcnt = 128 << fdc.N;
	fdc.bufp = 0;
//	fdc.status = FDCSTAT_RQM | FDCSTAT_NDM | FDCSTAT_CB;
#if defined(SUPPORT_SWSEEKSND)
	if(np2cfg.MOTOR) fddmtrsnd_play(1, TRUE);
#else
	if(np2cfg.MOTOR) soundmng_pcmplay(SOUND_PCMSEEK, FALSE);
#endif
	fdc_dmaready(1);
	dmac_check();
	return(SUCCESS);
}

static void FDC_WriteData(void) {						// cmd: 05
														// cmd: 09
	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
			get_chrn();
			get_eotgsldtl();
			fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
			fdc.treg[fdc.us] = fdc.C;	/* 170101 ST modified to work on Windows 9x/2000 */
			if (FDC_DriveCheck(TRUE)) {
				fdc.event = FDCEVENT_BUFRECV;
				fdc.bufcnt = 128 << fdc.N;
				fdc.bufp = 0;
#if 1															// ver0.27 ??
				fdc.status &= 0x0f;
				fdc.status |= (1 << fdc.us);
				fdc.status |= FDCSTAT_RQM | FDCSTAT_CB;
				if (fdc.nd) {
					fdc.status |= FDCSTAT_NDM;
				}
#else
				fdc.status = FDCSTAT_RQM | FDCSTAT_NDM | FDCSTAT_CB;
#endif
				fdc_dmaready(1);
				dmac_check();
			}
			break;

		case FDCEVENT_BUFRECV:
			if (writesector()) {
				return;
			}
			/* 170101 ST modified to work on Windows 9x/2000 form ... */
			//if (fdc.tc) {
			//	fdcsend_success7();
			//	return;
			//}
			if ((fdc.tc) || (fdc.R++ == fdc.eot)) {
				fdc.R = 1;
				if (fdc.mt) {
					fdc.hd ^= 1;
					fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
					fdc.H ^= 1;
					if (fdc.tc || !fdc.H) {
						fdc.C++;
						fdcsend_success7();
						break;
					}
				}
				else {
					fdc.C++;
					fdcsend_success7();
				}
				//fdc.stat[fdc.us] = fdc.us | (fdc.hd << 2) |
				//									FDCRLT_IC0 | FDCRLT_EN;
				//fdcsend_error7();
				break;
			}
			/* 170101 ST modified to work on Windows 9x/2000 ... to */
			break;

		default:
			fdc.event = FDCEVENT_NEUTRAL;
			fdc.status &= 0x0f;
			fdc.status |= FDCSTAT_RQM;
			break;
	}
}

static void readsector(void) {

	fdc.stat[fdc.us] = (fdc.hd << 2) | fdc.us;
	if (!FDC_DriveCheck(FALSE)) {
		return;
	}
	if (fdd_read()) {
		fdc.stat[fdc.us] = fdc.us | (fdc.hd << 2) | FDCRLT_IC0 | FDCRLT_ND;
		fdcsend_error7();
		return;
	}
#if defined(SUPPORT_SWSEEKSND)
	if(np2cfg.MOTOR) fddmtrsnd_play(1, TRUE);
#else
	if(np2cfg.MOTOR) soundmng_pcmplay(SOUND_PCMSEEK, FALSE);
#endif

	fdc.event = FDCEVENT_BUFSEND2;
	fdc.bufp = 0;
#if 0															// ver0.27 ??
	fdc.status = FDCSTAT_NDM | FDCSTAT_CB;
	if (!(fdc.ctrlreg & 0x10)) {
		fdc.status |= FDCSTAT_RQM | FDCSTAT_DIO;
	}
#else
	fdc.status &= 0x0f;
	fdc.status |= (1 << fdc.us);
	fdc.status |= FDCSTAT_RQM | FDCSTAT_DIO | FDCSTAT_CB;
	if (fdc.nd) {
		fdc.status |= FDCSTAT_NDM;
	}
#endif
	fdc_dmaready(1);
	dmac_check();
}

static void FDC_ReadData(void) {						// cmd: 06
														// cmd: 0c
	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
			get_chrn();
			get_eotgsldtl();
			fdc.treg[fdc.us] = fdc.C;	/* 170101 ST modified to work on Windows 9x/2000 */
			readsector();
			break;

		case FDCEVENT_NEXTDATA:
			fdc.bufcnt = 0;
			if (fdc.R++ == fdc.eot) {
				/* 170101 ST modified to work on Windows 9x/2000 form ... */
				fdc.R = 1;
				if (fdc.mt) {
					fdc.hd ^= 1;
					fdc.H ^= 1;
					if (fdc.H) {
						readsector();
						break;
					}
				}
				fdc.C++;
				fdcsend_success7();
				//fdc.stat[fdc.us] = fdc.us | (fdc.hd << 2) |
				//									FDCRLT_IC0 | FDCRLT_EN;
				//fdcsend_error7();
				break;
				/* 170101 ST modified to work on Windows 9x/2000 ... to */
			}
			readsector();
			break;

#ifdef FDC_DELAYERROR7
		case FDCEVENT_BUSY:
			break;
#endif

		default:
			fdc.event = FDCEVENT_NEUTRAL;
			fdc.status &= 0x0f;
			fdc.status |= FDCSTAT_RQM;
			break;
	}
}

static void FDC_Recalibrate(void) {						// cmd: 07

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
//			fdc.ncn = 0;
//			fdc.R = 1;						/* 170107 for Windows95 */
//			fdc.crcn = fdc.R;				/* 170107 for Windows95 */
			fdc.int_stat[fdc.us] = /* (fdc.hd << 2) | */ fdc.us;
			fdc.int_stat[fdc.us] |= FDCRLT_SE;
			if (!(fdc.equip & (1 << fdc.us))) {
				fdc.int_stat[fdc.us] |= FDCRLT_IC0;
				if (!(fdc.ctrlreg & 0x40)) {
					// 強制ReadyでなければNR
					fdc.int_stat[fdc.us] |= FDCRLT_NR;
				}
				else {
					// 強制ReadyならばNRは立たないがECが立つ(sector0が見つからないので)
					fdc.int_stat[fdc.us] |= FDCRLT_EC;
				}
			}
			else if (!fdd_diskready(fdc.us)) {
				// Win95, 98用
				fdc.ncn = 0;
				fdc.R = 1;						/* 170107 for Windows95 */
				fdc.crcn = fdc.R;				/* 170107 for Windows95 */
				if (fdc.ctrlreg & 0x40) {
					fdc.treg[fdc.us] = fdc.ncn;
				}else{
					fdc.int_stat[fdc.us] |= FDCRLT_NR;
				}
			}
			else {
				fdc.ncn = 0;
				fdc.treg[fdc.us] = fdc.ncn;
				fdc.R = 1;						/* 170107 for Windows95 */
				fdc.crcn = fdc.R;				/* 170107 for Windows95 */
				/* 170107 for Windows95 form ... */
//				if (fdd_seek()) {
//					fdc.stat[fdc.us] |= FDCRLT_IC0;
				if (fdd_diskready(fdc.us)) {
#if defined(SUPPORT_SWSEEKSND)
					if(np2cfg.MOTOR) fddmtrsnd_play(1, TRUE);
#else
					if(np2cfg.MOTOR) soundmng_pcmplay(SOUND_PCMSEEK1, FALSE);
#endif
				}
//				}
				/* 170107 for Windows95 ... to */
			}
			fdc.int_timer[fdc.us] = FDC_INT_DELAY;
			fdc.status &= 0x0f;
			fdc.status |= (1 << fdc.us);

//			fdc_interrupt();
			break;
//		default:
//		fdc_interrupt();
//		break;
	}
	fdc.event = FDCEVENT_NEUTRAL;
	fdc.status |= FDCSTAT_RQM;
	fdc.status &= ~FDCSTAT_CB;
}

static void FDC_SenceintStatus(void) {					// cmd: 08

	int		i;

	fdc.event = FDCEVENT_BUFSEND;
	fdc.bufp = 0;
	fdc.bufcnt = 0;
	fdc.status &= 0x0f;
	fdc.status |= FDCSTAT_RQM | FDCSTAT_CB | FDCSTAT_DIO;


//	if (fdc_isfdcinterrupt()) {
//		i = 0;
		if (fdc.stat[fdc.us]) {
			fdc.buf[0] = (UINT8)fdc.stat[fdc.us];
			fdc.buf[1] = fdc.treg[fdc.us];
			fdc.bufcnt = 2;
			fdc.stat[fdc.us] = 0;
//			TRACEOUT(("fdc stat - %d [%.2x]", fdc.us, fdc.buf[0]));
		}
		else {
			for (i=0; i<4; i++) {
				if (fdc.stat[i]) {
					fdc.buf[0] = (UINT8)fdc.stat[i];
					fdc.buf[1] = fdc.treg[i];
					fdc.bufcnt = 2;
					fdc.stat[i] = 0;
//					TRACEOUT(("fdc stat - %d [%.2x]", i, fdc.buf[0]));
					break;
				}
			}
		}
//		for (; i<4; i++) {
//			if (fdc.stat[i]) {
//				break;
//			}
//		}
//		if (i >= 4) {
//			fdc_interruptreset();
//		}
//
//	}
	if (!fdc.bufcnt) {
		fdc.buf[0] = FDCRLT_IC1;
		fdc.bufcnt = 1;
	}
	/* 170101 ST modified to work on Windows 9x/2000 form ... */
	else if ((fdc.ctrlreg & 0x08) == 0) {
		//fdc.buf[0] |= 0x08;
		//fdc.bufcnt = 1;
		fdc.ctrlreg |= 0x08; // アクセスされたらモータ回す･･･ np21w ver0.86 rev39
	}
	/* 170101 ST modified to work on Windows 9x/2000 ... to */
}

static void FDC_ReadID(void) {							// cmd: 0a

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			fdc.mf = fdc.cmd & 0x40;
			get_hdus();
			if (fdd_readid() == SUCCESS) {
				fdcsend_success7();
				//fdc.status = 0x80; // TEST
#if defined(SUPPORT_SWSEEKSND)
				if(np2cfg.MOTOR) fddmtrsnd_play(1, TRUE);
#else
				if(np2cfg.MOTOR) soundmng_pcmplay(SOUND_PCMSEEK, FALSE);
#endif
			}
			else {
				fdc.stat[fdc.us] = fdc.us | (fdc.hd << 2) |
//													FDCRLT_IC0 | FDCRLT_MA;
													FDCRLT_IC0 | FDCRLT_ND;

				fdcsend_error7();
			}
			break;
	}
}

static void FDC_WriteID(void) {							// cmd: 0d

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
//			TRACE_("FDC_WriteID FDCEVENT_CMDRECV", 0);
			get_hdus();
			fdc.N = fdc.cmds[1];
			fdc.sc = fdc.cmds[2];
			fdc.gpl = fdc.cmds[3];
			fdc.d = fdc.cmds[4];
			if (FDC_DriveCheck(TRUE)) {
//				TRACE_("FDC_WriteID FDC_DriveCheck", 0);
				if (fdd_formatinit()) {
//					TRACE_("FDC_WriteID fdd_formatinit", 0);
					fdcsend_error7();
					break;
				}
				/* 170107 modified to work on Windows 9x/2000 form ... */
				if (!fdd_isformating()) {
					fdc.crcn = 1;
					fdcsend_success7();
					break;
				}
				/* 170107 modified to work on Windows 9x/2000 ... to */
//				TRACE_("FDC_WriteID FDCEVENT_BUFRECV", 0);
				fdc.event = FDCEVENT_BUFRECV;
				fdc.bufcnt = 4;
				fdc.bufp = 0;
#if 1															// ver0.27 ??
				fdc.status &= 0x0f;
				fdc.status |= (1 << fdc.us);
				fdc.status |= FDCSTAT_RQM | FDCSTAT_CB;
				if (fdc.nd) {
					fdc.status |= FDCSTAT_NDM;
				}
#else
				fdc.status = FDCSTAT_RQM | FDCSTAT_NDM | FDCSTAT_CB;
#endif
				fdc_dmaready(1);
				dmac_check();
			}
			break;

		case FDCEVENT_BUFRECV:
			if (fdd_formating(fdc.buf)) {
				fdcsend_error7();
				break;
			}
			if ((fdc.tc) || (!fdd_isformating())) {
				fdcsend_success7();
				return;
			}
			fdc.event = FDCEVENT_BUFRECV;
			fdc.bufcnt = 4;
			fdc.bufp = 0;
#if 1															// ver0.27 ??
			fdc.status &= 0x0f;
			fdc.status |= (1 << fdc.us);
			fdc.status |= FDCSTAT_RQM | FDCSTAT_CB;
			if (fdc.nd) {
				fdc.status |= FDCSTAT_NDM;
			}
#else
			fdc.status = FDCSTAT_RQM | FDCSTAT_NDM | FDCSTAT_CB;
#endif
			break;

		default:
			fdc.event = FDCEVENT_NEUTRAL;
			fdc.status = FDCSTAT_RQM;
			break;
	}
}

static void FDC_Seek(void) {							// cmd: 0f

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
//			fdc.ncn = fdc.cmds[1];
//			fdc.treg[fdc.us] = fdc.ncn;
//			fdc.R = 1;
//			fdc.crcn = fdc.R;				/* 170107 for Windows95 */
			/* 170101 ST modified to work on Windows 9x/2000 */
			fdc.int_stat[fdc.us] = /*(fdc.hd << 2) |*/ fdc.us;
			fdc.int_stat[fdc.us] |= FDCRLT_SE;
			if (!fdd_diskready(fdc.us)) {
				fdc.int_stat[fdc.us] |= FDCRLT_IC0 | FDCRLT_NR;
			}
			else {
				fdc.ncn = fdc.cmds[1];
				fdc.treg[fdc.us] = fdc.ncn;
				fdc.R = 1;
				fdc.crcn = fdc.R;				/* 170107 for Windows95 */
				/* 170107 for Windows95 form ... */
				//if (fdd_seek()) {
				//	fdc.stat[fdc.us] |= FDCRLT_IC0;
#if defined(SUPPORT_SWSEEKSND)
					if(np2cfg.MOTOR) fddmtrsnd_play(1, TRUE);
#else
					if(np2cfg.MOTOR) soundmng_pcmplay(SOUND_PCMSEEK1, FALSE);
#endif
				//}
				/* 170107 for Windows95 ... to */
			}
			fdc.int_timer[fdc.us] = FDC_INT_DELAY;
			fdc.status &= 0x0f;
			fdc.status |= (1 << fdc.us);
//			fdc_interrupt();
			break;
	}
	fdc.event = FDCEVENT_NEUTRAL;
	fdc.status |= FDCSTAT_RQM;
	fdc.status &= ~FDCSTAT_CB;
}

#ifdef SUPPORT_KAI_IMAGES
static void FDC_ScanEqual(void) {						// cmd: 11, 19, 1d

	switch(fdc.event) {
		case FDCEVENT_CMDRECV:
			get_hdus();
			get_chrn();
			fdc.eot = fdc.cmds[5];
			fdc.gpl = fdc.cmds[6];
			fdc.stp = fdc.cmds[7];
			break;
	}
}
#endif

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

typedef void (*FDCOPE)(void);

static const FDCOPE FDC_Ope[0x20] = {
				FDC_Invalid,
				FDC_Invalid,
				FDC_ReadData,			// FDC_ReadDiagnostic,
				FDC_Specify,
				FDC_SenseDeviceStatus,
				FDC_WriteData,
				FDC_ReadData,
				FDC_Recalibrate,
				FDC_SenceintStatus,
				FDC_WriteData,
				FDC_ReadID,
				FDC_Invalid,
				FDC_ReadData,
				FDC_WriteID,
				FDC_Invalid,
				FDC_Seek,
				FDC_Invalid,					// 10
#ifdef SUPPORT_KAI_IMAGES
				FDC_ScanEqual,
#else
				FDC_Invalid,			// FDC_ScanEqual,
#endif
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
#ifdef SUPPORT_KAI_IMAGES
				FDC_ScanEqual,
#else
				FDC_Invalid,			// FDC_ScanEqual,
#endif
				FDC_Invalid,
				FDC_Invalid,
				FDC_Invalid,
#ifdef SUPPORT_KAI_IMAGES
				FDC_ScanEqual,
#else
				FDC_Invalid,			// FDC_ScanEqual,
#endif
				FDC_Invalid,
				FDC_Invalid};


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

static void fdcstatusreset(void) {

	fdc.event = FDCEVENT_NEUTRAL;
	fdc.status = FDCSTAT_RQM;
}

void DMACCALL fdc_datawrite(REG8 data) {

//	if ((fdc.status & (FDCSTAT_RQM | FDCSTAT_DIO)) == FDCSTAT_RQM) {
		switch(fdc.event) {
			case FDCEVENT_BUFRECV:
//				TRACE_("write", fdc.bufp);
				fdc.buf[fdc.bufp++] = data;
				if ((!(--fdc.bufcnt)) || (fdc.tc)) {
					fdc.status &= ~FDCSTAT_RQM;
					FDC_Ope[fdc.cmd & 0x1f]();
				}
				break;

			case FDCEVENT_CMDRECV:
				fdc.cmds[fdc.cmdp++] = data;
				if (!(--fdc.cmdcnt)) {
					fdc.status &= ~FDCSTAT_RQM;
					FDC_Ope[fdc.cmd & 0x1f]();
				}
				break;

			default:
				fdc.cmd = data;
				get_mtmfsk();
				fdc.status |= FDCSTAT_CB;	// コマンド受けたらCBをセット
				if (FDCCMD_TABLE[data & 0x1f]) {
					fdc.event = FDCEVENT_CMDRECV;
					fdc.cmdp = 0;
					fdc.cmdcnt = FDCCMD_TABLE[data & 0x1f];
					fdc.status |= FDCSTAT_RQM;
				}
				else {
					fdc.status &= ~FDCSTAT_RQM;
					FDC_Ope[fdc.cmd & 0x1f]();
				}
				break;
		}
//	}
}

REG8 DMACCALL fdc_dataread(void) {

	int		i;

//	if ((fdc.status & (FDCSTAT_RQM | FDCSTAT_DIO))
//									== (FDCSTAT_RQM | FDCSTAT_DIO)) {
		switch(fdc.event) {
			case FDCEVENT_BUFSEND:
				fdc.lastdata = fdc.buf[fdc.bufp++];
				if (!(--fdc.bufcnt)) {
					fdc.event = FDCEVENT_NEUTRAL;
					fdc.status &= ~(1 << fdc.us);
					fdc.status &= ~(FDCSTAT_DIO | FDCSTAT_CB);
					fdc.status |= FDCSTAT_RQM;
				}
				// 割り込みクリア処理
				for (i = 0; i < 4; i++) {
					if (fdc.stat[i]) {
						break;
					}
				}
				if (i >= 4) {
					fdc_interruptreset();
				}
				break;

			case FDCEVENT_BUFSEND2:
				if (fdc.bufcnt) {
					fdc.lastdata = fdc.buf[fdc.bufp++];
					fdc.bufcnt--;
				}
				if (fdc.tc) {
					if (!fdc.bufcnt) {						// ver0.26
						fdc.R++;
						if ((fdc.cmd & 0x80) && fdd_seeksector()) {
							fdc.C += fdc.hd;
							fdc.H = fdc.hd ^ 1;
							fdc.R = 1;
						}
					}
					/* 170101 ST modified to work on Windows 9x/2000 */
					fdc.R = 1;
					fdcsend_success7();
				}
				if (!fdc.bufcnt) {
					fdc.event = FDCEVENT_NEXTDATA;
					fdc.status &= ~(FDCSTAT_RQM | FDCSTAT_NDM);
					FDC_Ope[fdc.cmd & 0x1f]();
				}
				break;
		}
//	}
	return(fdc.lastdata);
}

/* SEEKとRECALIBRATEの割り込み通知処理 */
void fdc_intdelay(void) {

	int		i;

	for (i = 0; i < 4; i++) {
		if (fdc.int_timer[i]) {
			fdc.int_timer[i]--;
			if (!fdc.int_timer[i]) {
				fdc.stat[i] = fdc.int_stat[i];
				fdc_interrupt();
			}
		}
	}
}

// ---- I/O

static void IOOUTCALL fdc_o92(UINT port, REG8 dat) {

	TRACEOUT(("fdc out %.2x %.2x [%.4x:%.4x]", port, dat, CPU_CS, CPU_IP));

	if (((port >> 4) ^ fdc.chgreg) & 1) {
		return;
	}
//	/* 170101 ST modified to work on Windows 9x/2000 form ... */
//	if (fdc.status & FDCSTAT_DIO) {
//		fdc.status &= ~FDCSTAT_DIO;
//		fdc.status |= FDCSTAT_RQM;
//	}
//	/* 170101 ST modified to work on Windows 9x/2000 ... to */

	if ((fdc.status & (FDCSTAT_RQM | FDCSTAT_DIO)) == FDCSTAT_RQM) {
		fdc_datawrite(dat);
	}
}

static void IOOUTCALL fdc_o94(UINT port, REG8 dat) {

	UINT8	i;

	TRACEOUT(("fdc out %.2x %.2x [%.4x:%.4x]", port, dat, CPU_CS, CPU_IP));

	if (((port >> 4) ^ fdc.chgreg) & 1) {
		return;
	}
	if ((fdc.ctrlreg ^ dat) & 0x10) {
		fdcstatusreset();
		fdc_dmaready(0);
		dmac_check();
	}
	/* 170101 ST modified to work on Windows 9x/2000 form ... */
	if ((fdc.ctrlreg ^ dat) & 0x80) {
		if (dat & 0x80) {
			fdcstatusreset();

			if(dat & 0x08){ // XXX: Workaround for OSASK
				// FDCリセット後 1,024ms以内にINTがアクティブになる
				for (i = 0; i < 4; i++) {
					fdc.int_stat[i] = fdc.stat[i];
					fdc.int_timer[i] = FDC_INT_DELAY;
				}
			}
		}
	}
	if (fdc.chgreg & 4) {
		if ((fdc.ctrlreg ^ dat) & 0x08) {
			if (dat & 0x08) {
				for (i = 0; i < 4; i++) {
					if (fdd_diskready(i)) {
						fdc.int_stat[i] = FDCRLT_AI | i;
						fdc.int_timer[i] = FDC_INT_DELAY;
						fdc.status |= (1 << i);
					}
				}
			}
		}
	}
	/* 170101 ST modified to work on Windows 9x/2000 ... to */
	fdc.ctrlreg = dat;
}

static REG8 IOINPCALL fdc_i90(UINT port) {

	TRACEOUT(("fdc in %.2x %.2x [%.4x:%.4x]", port, fdc.status,
															CPU_CS, CPU_IP));

	if (((port >> 4) ^ fdc.chgreg) & 1) {
		return(0xff);
	}
	return(fdc.status);
}

static REG8 IOINPCALL fdc_i92(UINT port) {

	REG8	ret;

	if (((port >> 4) ^ fdc.chgreg) & 1) {
		return(0xff);
	}
	if ((fdc.status & (FDCSTAT_RQM | FDCSTAT_DIO))
										== (FDCSTAT_RQM | FDCSTAT_DIO)) {
		ret = fdc_dataread();
	}
	else {
		ret = fdc.lastdata;
	}
	TRACEOUT(("fdc in %.2x %.2x [%.4x:%.4x]", port, ret, CPU_CS, CPU_IP));
	return(ret);
}

static REG8 IOINPCALL fdc_i94(UINT port) {

	REG8 ret;

	if (((port >> 4) ^ fdc.chgreg) & 1) {
		return(0xff);
	}

	ret = 0x40;
	if (!(port & 0x10))		/* CC */
	{
		ret |= 0x20;		/* DMA */
		ret |= 0x10;		/* readyを立てるるる */
	}

	if (pccore.dipsw[0] & 8)
	{
		ret |= 0x04;		/* 内蔵優先 */
	}
	else
	{
		ret |= 0x08;		/* 外付け優先 */
	}
	TRACEOUT(("fdc in %.2x %.2x [%.4x:%.4x]", port, ret,
														CPU_CS, CPU_IP));

	return ret;
}


static void IOOUTCALL fdc_obe(UINT port, REG8 dat) {
	
	TRACEOUT(("fdc out %.2x %.2x [%.4x:%.4x]", port, dat, CPU_CS, CPU_IP));

	fdc.chgreg = dat;
	if (fdc.chgreg & 2) {
		CTRL_FDMEDIA = DISKTYPE_2HD;
	}
	else {
		CTRL_FDMEDIA = DISKTYPE_2DD;
	}
	(void)port;
}

static REG8 IOINPCALL fdc_ibe(UINT port) {

	(void)port;
	TRACEOUT(("fdc in %.2x %.2x [%.4x:%.4x]", port, ((fdc.chgreg & 3) | 8),
														CPU_CS, CPU_IP));

	return((fdc.chgreg & 3) | 8 | 0xf0);
}

static void IOOUTCALL fdc_o4be(UINT port, REG8 dat) {
	
	TRACEOUT(("fdc out %.2x %.2x [%.4x:%.4x]", port, dat, CPU_CS, CPU_IP));

	fdc.reg144 = dat;
	if (dat & 0x10) {
		fdc.rpm[(dat >> 5) & 3] = dat & 1;
	}
	(void)port;
}

static REG8 IOINPCALL fdc_i4be(UINT port) {

	(void)port;
	TRACEOUT(("fdc in %.2x %.2x [%.4x:%.4x]", port, (fdc.rpm[(fdc.reg144 >> 5) & 3] | 0xf0),
													CPU_CS, CPU_IP));
	return(fdc.rpm[(fdc.reg144 >> 5) & 3] | 0xfe);
}


// ---- I/F

static const IOOUT fdco90[4] = {
					NULL,		fdc_o92,	fdc_o94,	NULL};
static const IOINP fdci90[4] = {
					fdc_i90,	fdc_i92,	fdc_i94,	NULL};
static const IOOUT fdcobe[1] = {fdc_obe};
static const IOINP fdcibe[1] = {fdc_ibe};

void fdc_reset(const NP2CFG *pConfig) {

	ZeroMemory(&fdc, sizeof(fdc));
	fdc.equip = pConfig->fddequip;
#if defined(SUPPORT_PC9821)
	fdc.support144 = 1;
#else
	fdc.support144 = pConfig->usefd144;
#endif
	fdcstatusreset();
	dmac_attach(DMADEV_2HD, FDC_DMACH2HD);
	dmac_attach(DMADEV_2DD, FDC_DMACH2DD);
	CTRL_FDMEDIA = DISKTYPE_2HD;
	fdc.chgreg = 3;
}

void fdc_bind(void) {

	iocore_attachcmnoutex(0x0090, 0x00f9, fdco90, 4);
	iocore_attachcmninpex(0x0090, 0x00f9, fdci90, 4);
	iocore_attachcmnoutex(0x00c8, 0x00f9, fdco90, 4);
	iocore_attachcmninpex(0x00c8, 0x00f9, fdci90, 4);

	if (fdc.support144) {
		iocore_attachout(0x04be, fdc_o4be);
		iocore_attachinp(0x04be, fdc_i4be);
	}
	iocore_attachsysoutex(0x00be, 0x0cff, fdcobe, 1);
	iocore_attachsysinpex(0x00be, 0x0cff, fdcibe, 1);
}

