 /*
 |  ---- Floppy disk driver and associated bits ----
*/

#include <sys/hardware.h>
#include <sys/types.h>
#include <sys/useful.h>
#include <sys/cia.h>
#include <sys/custom.h>
#include <sys/unix.h>
#include <sys/extern.h>

#define fdd_SyncWord (0x4489)
#define fdd_AdkCon   (0x9500)

 /*
 |  Some function declarations are necessary
 |  Note that K&R doesn't allow parameter typing here
*/
void fdd_dmastart();
void fdd_dmadone();
void fdd_cachetoraw();
u_long *fdd_mfmblkencode();
u_long *fdd_mfmblkdecode();
void fdd_setpos();
void fdd_done();
void fdd_performio();
int fdd_motoroff();


 /*
 |  Some information about the drive
*/
#define FDSETTLEDL	(18000)		/* usec : change dir -> seek */
#define FDSTEPDL	(3500)		/* usec : after stepping */
#define FDPRESIDEDL	(1000)		/* usec : before writing */
#define FDWRITEDL	(1300)		/* usec : after writing */

#define FDSTEPOUT	(1)		/* Direction for outwards */
#define FDSTEPIN	(0)		/* Direction for inwards */

#define FDCUNITMASK	(0x78)		/* mask for all units (bits 6-3) */

#define FDB_CHANGED	2
#define FDB_PROTECT	3
#define FDB_CYLZERO	4
#define FDB_READY	5

#define DISKLEN_READ	(0)		/* fake mask for reading */
#define DISKLEN_WRITE	(1 << 14)	/* bit for writing */
#define DISKLEN_DMAEN	(1 << 15)	/* dma go */
#define DMABUFSZ	((DISKLEN_WRITE-1)*2)	/* largest dma possible */



 /*
 |  Describes a floppy device
*/
struct fdtype
{
	u_int	driveid;	/* Drive identification */
	u_int	ncylinders;	/* Number of cylinders */
	u_int	nsectors;	/* Number of sectors */
	u_int	nreadw;		/* Number of shorts read / track */
	u_int	nwritew;	/* Number of shorts written / track */
	u_int	gap;		/* Track gap size */
	u_int	precomp[2];	/* Precompensation values */
	char	*desc;		/* Description of drive type */
};

 /*
 |  Working drive information
*/
struct fdd_softc
{
	struct fdtype	*type;	/* See above */
	void	*cachep;	/* Chached track data */
	short	cachetrk;	/* Which track is chached */
	u_short	*dmatrk;	/* Buffer for dma reading/writing */
	char	hwunit;		/* Unit number */
	short	unitmask;	/* Makes things more efficient */
	char	pstepdir;	/* Previous step direction */
	short	curcyl;		/* Current cylinder under the head */
	int	flags;		/* Boolean status' */
	int	bflags;		/* Status of current block transfer */
	int	nsectors;	/* Sectors / track */
	int	retries;	/* Number of retries */
	int	retried;	/* Retries done so far */
	int	b_blkno;	/* Block in progess */
	char	*b_data;	/* Pointer to src/dst of data */
};

 /*
 |  Flags for fdd_softc->flags
*/
#define FDF_MOTORON	(0x01)	/* Motor is running */
#define FDF_MOTOROFF	(0x02)	/* Motor is waiting to be offed */
#define FDF_WMOTOROFF	(0x04)	/* Wakeup needed after off */
#define FDF_DIRTY	(0x08)	/* Track chache needs writing */
#define FDF_WRITEWAIT	(0x10)	/* Need to head select dalay in setpos */
#define FDF_HAVELABEL	(0x20)	/* Disc label is valid */
#define FDF_JUSTFLUSH	(0x40)	/* Do not chache track */
#define FDF_NOTRACK0	(0x80)	/* Could not calibrate drive */
#define FDF_OPEN	(0x100)	/* Device has been open()ed */
#define FDF_ERROR	(0x200)	/* Device has generated some error */

 /*
 |  Flags for fdd_softc->bflags
*/
#define FDBF_ERROR	(0x01)	/* An error has occurer */
#define FDBF_READ	(0x02)	/* The operation is a read */
#define FDBF_DONE	(0x04)	/* The transfer has been completed */

 /*
 |  This describes the standard internal floppy
*/
struct fdtype fdtype[] =
{
	{ 0x00000000, 80, 11, 7358, 6815, 414, { 80, 161 }, "3.5dd" }
};
int nfdtype = sizeof(fdtype) / sizeof(*fdtype);

 /*
 |  The Amiga has four possible floppies
*/
struct fdd_softc DRIVES[4];
int DATABUF[1][1408];
u_short DMABUF[1][7358];

int fdc_side;
struct fdd_softc *fdc_indma;


 /*
 |  Hardware controling macros
*/

/* Unit number to bit */
#define FDUNITMASK(unit)	(1<<(3+(unit)))

/* Select a particular unit */
#define FDSELECT(un)		do { *CIAB_PRB &= ~(un); } while(0)

/* Deselect a particular unit */
#define FDDESELECT(un)		do { *CIAB_PRB |= (un); delay(1); } while(0)

/* Test a bit in the hardware */
#define FDTESTC(bit)		((*CIAA_PRA & (1<<(bit))) == 0)

/* Turn the motor either on or off */
#define FDSETMOTOR(on)		do{ \
					if(on) *CIAB_PRB &= ~CIAB_PRB_MTR; \
					else *CIAB_PRB |= CIAB_PRB_MTR; \
				}while(0)

/* Set the head to be used */
#define FDSETHEAD(head)		do{ \
					if(head) *CIAB_PRB &= ~CIAB_PRB_SIDE; \
					else *CIAB_PRB |= CIAB_PRB_SIDE; \
					delay(1); \
				}while(0)

/* Set head direction (true=inwards) */
#define FDSETDIR(in)		do{ \
					if(in) *CIAB_PRB &= ~CIAB_PRB_DIR; \
					else *CIAB_PRB |= CIAB_PRB_DIR; \
					delay(1); \
				}while(0)

/* Step the head in current direction */
#define FDSTEP			do{ \
					*CIAB_PRB &= ~CIAB_PRB_STEP; \
					*CIAB_PRB |= CIAB_PRB_STEP; \
				}while(0)
/* Start a DMA transfer */
#define FDDMASTART( len, towr )	do{ \
					int dmasz = (len) | \
					            ((towr)?DISKLEN_WRITE:0) | \
						    DISKLEN_DMAEN; \
					*DSKLEN = dmasz; \
					*DSKLEN = dmasz; \
				}while(0);

/* Stop a DMA transfer */
#define FDDMASTOP		do{ *DSKLEN = 0; }while(0)



 /*
 |  Initialise the disk hardware.  Automatically called
*/
void fdd_init( minor )
int16	minor;
{
	*DSKSYNC  = fdd_SyncWord;	/* Word to sync to */
	*ADKCON   = fdd_AdkCon;		/* Controller bits */
	*INTENA   = 0x8002;		/* Enable diskblock and dsync */
	*DMACON   = 0x8210;		/* Master DMA and disk DMA on */

	DRIVES[minor].curcyl = DRIVES[minor].cachetrk = -1;
	DRIVES[minor].type = &fdtype[0];
	DRIVES[minor].hwunit = minor;
	DRIVES[minor].unitmask = 1<<(3+minor);
	DRIVES[minor].retries = 2;
	DRIVES[minor].cachep = (void*)DATABUF[minor];
	DRIVES[minor].dmatrk = DMABUF[minor];
	DRIVES[minor].nsectors = DRIVES[minor].type->nsectors;
	DRIVES[minor].flags = DRIVES[minor].bflags = 0;

	/* Move the head about (calibrate) */
	fdd_setpos( &DRIVES[minor], 0, 0 );
	fdd_setpos( &DRIVES[minor], DRIVES[minor].type->ncylinders, 0 );
	fdd_setpos( &DRIVES[minor], 0, 0 );

	return;
}


 /*
 |  Open a device.  Called once only
*/
int fdd_open( minor )
int16	minor;
{
	if( (minor>3) || (minor<0) )
	{
		panic( "Tried to open floppy %i\n", minor );
	}

	DRIVES[minor].flags |= FDF_OPEN;

	return( 0 );
}


 /*
 |  Close a device.  Called once only
*/
int fdd_close( minor )
int16	minor;
{
	if( (minor>3) || (minor<0) )
	{
		panic( "Tried to close floppy %i\n", minor );
	}

	if( DRIVES[minor].flags & FDF_DIRTY )
	{
		DRIVES[minor].flags |= FDF_JUSTFLUSH;
		untimeout( fdd_motoroff, &DRIVES[minor] ); 
		fdd_performio( &DRIVES[minor] );
	}

	DRIVES[minor].flags &= ~FDF_OPEN;

	return( 0 );
}


 /*
 |  Write a block to disk
*/
int fdd_write( minor, rawflag )
int16	minor;
int16	rawflag;
{
	DRIVES[minor].bflags = 0;
	DRIVES[minor].b_data = udata.u_buf->bf_data;
	DRIVES[minor].b_blkno = udata.u_buf->bf_blk;

	fdd_performio( &DRIVES[minor] );

	while( (DRIVES[minor].bflags & FDBF_DONE) == 0 );
	DRIVES[minor].bflags &= ~FDBF_DONE;

	return(512);
}


 /*
 |  Read a block from disk
*/
int fdd_read( minor, rawflag )
int16	minor;
int16	rawflag;
{
	DRIVES[minor].bflags = FDBF_READ;
	DRIVES[minor].b_data = udata.u_buf->bf_data;
	DRIVES[minor].b_blkno  = udata.u_buf->bf_blk;

	fdd_performio( &DRIVES[minor] );

	while( (DRIVES[minor].bflags & FDBF_DONE) == 0 );
	DRIVES[minor].bflags &= ~FDBF_DONE;

	return(512);
}


 /*
 |  Initiates an io operation on the disc.
 |  Usually fdd_performio()->fdd_dmastart()->fdd_blockdone()->
 |          fdd_dmadone()->fdd_done()
*/
void fdd_performio( sc )
struct fdd_softc *sc;
{
	int	trk;

	/* Keep the motor on */
	untimeout( fdd_motoroff, sc );
	sc->flags &= ~FDF_MOTOROFF;

	/* figure trk given blkno */
	trk = sc->b_blkno / sc->nsectors;

	/* If we're flush then we *must* do a physical transfer */
	if( sc->flags & FDF_JUSTFLUSH )
		goto forcedma;
	
	/* check to see if same as currently cached track if so we need to 
	   do no dma read */
	if( trk == sc->cachetrk )
	{
		timeout( fdd_motoroff, sc, 3*hz );
		fdd_done( sc );
		return;
	}

forcedma:
	/* start dma read of `trk' */
	fdd_dmastart( sc, trk );
	return;
}


 /*
 |  select drive seek to track exit with motor on.
 |  fdd_setpos(x, 0, 0) does calibrates the drive.
*/
void fdd_setpos( sc, trk, towrite )
struct fdd_softc *sc;
int	trk, towrite;
{
	int	nstep, sdir, ondly, ncyl, nside;

	FDDESELECT( FDCUNITMASK );
	FDSETMOTOR( 1 );
	delay( 1 );
	FDSELECT( sc->unitmask );
	delay( 1 );
	
	if( (sc->flags & FDF_MOTORON) == 0 )
	{
		ondly = 0;
		while( FDTESTC(FDB_READY) == 0 )
		{
			delay( 1000 );
			if( ++ondly >= 1000 )
				break;
		}
	}
	sc->flags |= FDF_MOTORON;

	ncyl = trk / 2;
	nside = trk % 2;

	if( sc->curcyl==ncyl && fdc_side==nside )
		return;

	if( towrite )
		sc->flags |= FDF_WRITEWAIT;
	
	nstep = ncyl - sc->curcyl;
	if( nstep )
	{
		/* figure direction */
		if( nstep>0 && ncyl!=0 )
		{
			sdir = FDSTEPIN;
			FDSETDIR( 1 );
		}
		else
		{
			nstep = -nstep;
			sdir = FDSTEPOUT;
			FDSETDIR( 0 );
		}
		if( ncyl == 0 )
		{
			/* either just want cylinder 0 or doing a calibrate */
			nstep = 256;
			while( FDTESTC(FDB_CYLZERO) == 0 && nstep-- )
			{
				FDSTEP;
				delay( FDSTEPDL );
			}
			if( nstep < 0 )
				sc->flags |= FDF_NOTRACK0;
		}
		else
		{
			/* step the needed amount amount */
			while( nstep-- )
			{
				FDSTEP;
				delay( FDSTEPDL );
			}
		}
		/* if switched directions allow drive to settle */
		if( sc->pstepdir != sdir )
			delay( FDSETTLEDL );
		sc->pstepdir = sdir;
		sc->curcyl = ncyl;
	}
	if( nside == fdc_side )
		return;
	/* select side */
	fdc_side = nside;
	FDSETHEAD( nside );
	delay( FDPRESIDEDL );
}


 /*
 |  Initiate a dma operation
*/
void fdd_dmastart( sc, trk )
struct fdd_softc *sc;
int	trk;
{
	int	adkmask, ndmaw, write, dmatrk;

	/* flush the cached track if dirty else read requested track */
	if( sc->flags & FDF_DIRTY )
	{
		fdd_cachetoraw( sc );
		ndmaw = sc->type->nwritew;
		dmatrk = sc->cachetrk;
		write = 1;
	}
	else
	{
		ndmaw = sc->type->nreadw;
		dmatrk = trk;
		write = 0;
	}
	sc->cachetrk = trk;
	fdc_indma = sc;
	fdd_setpos( sc, dmatrk, write );

	/* setup dma stuff */
	if( write == 0 )
	{
		*ADKCON = ADKF_MSBSYNC;
		*ADKCON = ADKF_SETCLR | ADKF_WORDSYNC | ADKF_FAST;
		*DSKSYNC = fdd_SyncWord;
	}
	else
	{
		*ADKCON = ADKF_PRECOMP1 | ADKF_PRECOMP0 | ADKF_WORDSYNC |
		    ADKF_MSBSYNC;
		adkmask = ADKF_SETCLR | ADKF_FAST | ADKF_MFMPREC;
		if( dmatrk >= sc->type->precomp[0] )
			adkmask |= ADKF_PRECOMP0;
		if( dmatrk >= sc->type->precomp[1] )
			adkmask |= ADKF_PRECOMP1;
		*ADKCON = adkmask;
	}
	*DSKPT = sc->dmatrk;
	FDDMASTART( ndmaw, write );
}


 /*
 |  Called when a dma operation has been completed
*/
void fdd_dmadone( sc, timeo )
struct fdd_softc *sc;
int	timeo;
{
	fdc_indma = NULL;
	FDDMASTOP;

	/* guarantee the drive has been at current head and cyl for at least
	   FDWRITEDL after a write. */
	if( sc->flags & FDF_WRITEWAIT )
	{
		delay( FDWRITEDL );
		sc->flags &= ~FDF_WRITEWAIT;
	}

	if( sc->flags & FDF_DIRTY )
	{
		/* if buffer dirty, the last dma cleaned it */
		sc->flags &= ~FDF_DIRTY;
		if( timeo )
			warning("devfdd: write of track cache timed out");
		if( sc->flags & FDF_JUSTFLUSH )
		{
			sc->flags &= ~FDF_JUSTFLUSH;
			if( (sc->flags & FDF_MOTOROFF) == 0 )
				timeout( fdd_motoroff, sc, 3*hz ); 
			/* we are done dma'ing */
			fdd_done( sc );
			return;
		}
		/* load the cache */
		fdd_dmastart( sc, sc->cachetrk );
		return;
	}

	/* cache loaded decode it into cache buffer */
	if( timeo == 0 && fdrawtocache(sc)==0 )
		sc->retried = 0;
	else
	{
		warning("devfdd: error");
		if( sc->retried >= sc->retries )
		{
			sc->retried = 0;
			sc->cachetrk = -1;
		}
		else
		{
			sc->retried++;
			untimeout( fdd_motoroff, sc );
			fdd_dmastart( sc, sc->cachetrk );
			/*fdcalibrate( sc );*/
			return;
		}
	}

	/* motor runs for 3 seconds after last dma */
	if( (sc->flags & FDF_MOTOROFF) == 0 )
		timeout( fdd_motoroff, sc, 3*hz ); 

	fdd_done( sc );
}


 /*
 |  This is called when an block transfer has been done
*/
void fdd_done( sc )
struct fdd_softc *sc;
{
	char		*data;
	int		sz, blk;

	/* check to see if unit is just flushing the cache, that is we
	   have no io queued */
	if( sc->flags & FDF_MOTOROFF )
	{
		fdd_motoroff( sc );
		return;
	}

	/* check for an error that may have occured while getting the track */
	if( sc->cachetrk == -1 )
	{
		sc->retried = 0;
		sc->bflags |= FDBF_ERROR;
	}
	else
		if( (sc->bflags & FDBF_ERROR) == 0 )
		{
			data = sc->cachep;
			/* get offset of data in track cache and limit the copy
			   size to not exceed the cache's end */
			data += (sc->b_blkno % sc->nsectors) * 512;
			if( sc->bflags & FDBF_READ )
				qcopy( data, sc->b_data, 128 );
			else
			{
				qcopy( sc->b_data, data, 128 );
				sc->flags |= FDF_DIRTY;
			}
		}
	sc->bflags |= FDBF_DONE;
}


 /*
 |  encode the track cache into raw MFM ready for dma
 |  when we go to multiple disk formats, this will call type dependent
 |  functions
*/
void
fdd_cachetoraw(sc)
	struct fdd_softc *sc;
{
	static u_long mfmnull[4];
	u_long *rp, *crp, *dp, hcksum, dcksum, info, zero;
	int sec, i;

	rp = (u_long*)sc->dmatrk;

	/*
	 * not yet one sector (- 1 long) gap.
	 * for now use previous drivers values
	 */
	for (i = 0; i < sc->type->gap; i++)
		*rp++ = 0xaaaaaaaa;
	/*
	 * process sectors
	 */
	dp = sc->cachep;
	zero = 0;
	info = 0xff000000 | (sc->cachetrk << 16) | sc->nsectors;
	for (sec = 0; sec < sc->nsectors; sec++, info += (1 << 8) - 1) {
		hcksum = dcksum = 0;
		/*
		 * sector format
		 *	offset		description
		 *-----------------------------------
		 *  0			null 
		 *  1			sync 
		 * oddbits	evenbits
		 *----------------------
		 *  2		3	[0xff]b [trk]b [sec]b [togap]b
		 *  4-7		8-11	null
		 * 12		13	header cksum [2-11]
		 * 14		15	data cksum [16-271]
		 * 16-143	144-271	data
		 */
		*rp = 0xaaaaaaaa;
		if (*(rp - 1) & 0x1)
			*rp &= 0x7fffffff;	/* clock bit correction */
		rp++;
		*rp++ = (fdd_SyncWord << 16) | fdd_SyncWord;
		rp = fdd_mfmblkencode(&info, rp, &hcksum, 1);
		rp = fdd_mfmblkencode(mfmnull, rp, &hcksum, 4);
		rp = fdd_mfmblkencode(&hcksum, rp, NULL, 1);

		crp = rp;
		rp = fdd_mfmblkencode(dp, rp + 2, &dcksum, 128);
		dp += 128;
		crp = fdd_mfmblkencode(&dcksum, crp, NULL, 1);
		if (*(crp - 1) & 0x1)
			*crp &= 0x7fffffff;	/* clock bit correction */
		else if ((*crp & 0x40000000) == 0)
			*crp |= 0x80000000;
        }
	*rp = 0xaaa80000;
	if (*(rp - 1) & 0x1)
		*rp &= 0x7fffffff;
}


 /*
 |  Locates the sync mark in a bufferful of mfm data
*/
u_long *fdfindsync(rp, ep)
	u_long *rp, *ep;
{
	u_short *sp;

	sp = (u_short *)rp;
	while ((u_long *)sp < ep && *sp != fdd_SyncWord)
		sp++;
	while ((u_long *)sp < ep && *sp == fdd_SyncWord)
		sp++;
	if ((u_long *)sp < ep)
		return((u_long *)sp);
	return(NULL);
}

 /*
 |  decode raw MFM from dma into units track cache.
 |  when we go to multiple disk formats, this will call type dependent
 |  functions
*/
int fdrawtocache(sc)
	struct fdd_softc *sc;
{
	u_long mfmnull[4];
	u_long *dp, *rp, *erp, *crp, *srp, hcksum, dcksum, info, cktmp;
	int cnt, doagain;

	doagain = 1;
	srp = rp = (u_long*)sc->dmatrk;
	erp = (u_long*)((u_short *)rp + sc->type->nreadw);
	cnt = 0;
again:
	if (doagain == 0 || (rp = srp = fdfindsync(srp, erp)) == NULL) {
		return(-1);
	}
	
	/*
	 * process sectors
	 */
	for (; cnt < sc->nsectors; cnt++) {
		hcksum = dcksum = 0;
		rp = fdd_mfmblkdecode(rp, &info, &hcksum, 1);
		rp = fdd_mfmblkdecode(rp, mfmnull, &hcksum, 4);
		rp = fdd_mfmblkdecode(rp, &cktmp, NULL, 1);
		if (cktmp != hcksum) {
			goto again;
		}
		if (((info >> 16) & 0xff) != sc->cachetrk) {
			goto again;
		}

		rp = fdd_mfmblkdecode(rp, &cktmp, NULL, 1);
		dp = sc->cachep;
		dp += 128 * ((info >> 8) & 0xff);
		crp = fdd_mfmblkdecode(rp, dp, &dcksum, 128);
		if (cktmp != dcksum) {
			goto again;
		}

		/*
		 * if we are at gap then we can no longer be sure
		 * of correct sync marks
		 */
		if ((info && 0xff) == 1)
			doagain = 1;
		else
			doagain = 0;
		srp = rp = fdfindsync(crp, erp);
	}
	return(0);
}

 /*
 |  encode len longwords of `dp' data in amiga mfm block format (`rp')
 |  this format specified that the odd bits are at current pos and even
 |  bits at len + current pos
*/
u_long *fdd_mfmblkencode(dp, rp, cp, len)
	u_long *dp, *rp, *cp;
	int len;
{
	u_long *sdp, *edp, d, dtmp, correct;
	int i;
	
	sdp = dp;
	edp = dp + len;

	if (*(rp - 1) & 0x1)
		correct = 1;
	else
		correct = 0;
	/*
	 * do odd bits
	 */
	while (dp < edp) {
		d = (*dp >> 1) & 0x55555555;	/* remove clock bits */
		dtmp = d ^ 0x55555555;
		d |= ((dtmp >> 1) | 0x80000000) & (dtmp << 1);
		/*
		 * correct upper clock bit if needed
		 */
		if (correct)
			d &= 0x7fffffff;
		if (d & 0x1)
			correct = 1;
		else
			correct = 0;
		/*
		 * do checksums and store in raw buffer
		 */
		if (cp)
			*cp ^= d;
		*rp++ = d;
		dp++;
	}
	/*
	 * do even bits
	 */
	dp = sdp;
	while (dp < edp) {
		d = *dp & 0x55555555;	/* remove clock bits */
		dtmp = d ^ 0x55555555;
		d |= ((dtmp >> 1) | 0x80000000) & (dtmp << 1);
		/*
		 * correct upper clock bit if needed
		 */
		if (correct)
			d &= 0x7fffffff;
		if (d & 0x1)
			correct = 1;
		else
			correct = 0;
		/*
		 * do checksums and store in raw buffer
		 */
		if (cp)
			*cp ^= d;
		*rp++ = d;
		dp++;
	}
	if (cp)
		*cp &= 0x55555555;
	return(rp);
}

 /*
 |  decode len longwords of `dp' data in amiga mfm block format (`rp')
 |  this format specified that the odd bits are at current pos and even
 |  bits at len + current pos
*/
u_long *fdd_mfmblkdecode(rp, dp, cp, len)
	u_long *rp, *dp, *cp;
	int len;
{
	u_long o, e;
	int cnt;

	cnt = len;
	while (cnt--) {
		o = *rp;
		e = *(rp + len);
		if (cp) {
			*cp ^= o;
			*cp ^= e;
		}
		o &= 0x55555555;
		e &= 0x55555555;
		*dp++ = (o << 1) | e;
		rp++;
	}
	if (cp)
		*cp &= 0x55555555;
	return(rp + len);
}


 /*
 |  Diskblock interrupts come here
*/
int fdd_blockdone()
{
	if( fdc_indma )
		fdd_dmadone( fdc_indma, 0 );
}


 /*
 |  turn motor off if possible otherwise mark as needed and will be done
 |  later.
*/
int fdd_motoroff( sc )
struct fdd_softc	*sc;
{
	if( (sc->flags & FDF_MOTORON) == 0 ) 
		return;

	/* if we have a timeout on a dma op let fdd_dmadone() deal with it */
	if( fdc_indma == sc )
	{
		fdd_dmadone( sc, 1 );
		return;
	}

	/* flush cache if needed */
	if( sc->flags & FDF_DIRTY )
	{
		sc->flags |= FDF_JUSTFLUSH | FDF_MOTOROFF;
		/* if dma'ing done for now, fdd_done() will call us again */
		if( fdc_indma )
			return;
		fdd_dmastart( sc, sc->cachetrk );
		return;
	}

	/* if controller is busy just schedule us to be called back */
	if( fdc_indma )
	{
		/* controller is busy; set flag and let fdd_done() recall us */
		sc->flags |= FDF_MOTOROFF;
		return;
	}

	sc->flags &= ~(FDF_MOTORON | FDF_MOTOROFF);
	FDDESELECT( FDCUNITMASK );
	FDSETMOTOR( 0 );
	delay( 1 );
	FDSELECT( sc->unitmask );
	delay( 4 );
	FDDESELECT( sc->unitmask );
	delay( 1 );
}

