/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1986,1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:fd.c 12.0$ */
/* $ACIS:fd.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/standca/RCS/fd.c,v $ */

#ifndef lint
static char *rcsid = "$Header:fd.c 12.0$";
#endif

/*
 *  fd.c - standalone floppy disk device driver
 *
 */

#include        "param.h"
#include        "inode.h"
#include        "fs.h"
#include        "time.h"
#include        "proc.h"
#include 	"../machineio/hdreg.h"

#include 	"saio.h"
#include	"sa.h"
#include	"dma.h"

/* Disk block definitions */

#define	BSIZE	DEV_BSIZE
#define	BSHIFT	DEV_BSHIFT

#define	DsktDma	0

static struct dma_req temp;
static struct dma_req *r = &temp;      /* request dma structure */



struct fddevice {		       /*      must cast data when used         */
	unsigned char digout;	       /* 0:   digital output register */
	unsigned char empty;	       /* 1:   no register at 0xf00003f3 */
	unsigned char status;	       /* 2:   status register */
	unsigned char data;	       /* 3:   data register */
	unsigned char fixd;	       /* 4:   fixed disk register */
	unsigned char digin;	       /* 5:   digital input register */
};

char fdstatus, fdcylinder;	       /* fd state info */
static char DsktSr0, DsktSr1, DsktSr2;
static char DsktCyl, DsktHd;
static char junk;
char fdresult();

#define FDADDR0 0xf00003f2
int fdaddrs[] = {FDADDR0, 0xf0000372};
struct fddevice *fdadapter = (struct fddevice *)FDADDR0;

/* Main status register */
#define DsktDAB	0x01		       /* device 0 seeking */
#define DsktDBB	0x02		       /* device 1 seeking */
#define DsktDCB	0x03		       /* device 2 seeking */
#define DsktDDB	0x04		       /* device 3 seeking */
#define DsktCB	0x10		       /* Controller busy */
#define DsktNDM	0x20		       /* PIO execution phase */
#define DsktDIO	0x40		       /* Direction */
#define DsktRQM	0x80		       /* Data Register Ready */

/* Partition table */
/*#ifndef SQUEEEEZE */
struct partab {
	daddr_t len;		       /* # of blocks in partition */
	int start;		       /* starting cylinder of partition */
} fd_sizes[] = {
	2400,0, 		       /* 1200K diskettes cyl 0 - 80 */
	720, 0,			       /* 360 K diskettes cyl 0 - 40 */
};


/*
 * Drive description table.
 * Returned from SAIODEVDATA call.
 */
struct fdst {
	short nsect;		       /* # sectors/track */
	short ntrak;		       /* # surfaces or heads */
	short nspc;		       /* # sectors/cylinder */
	short ncyl;		       /* # cylinders */
	short steprate;		       /* Minimum stepping rate */
	short precompcyl;	       /* Cylinder at which to start precomp */
	struct partab *off;	       /* partition offset table (cylinders) */
	char eot;		       /* End of Track (last sector or sector fdcount) */
	char gpl;		       /* Gap length */
	char step;		       /* Stepping rate and head unload time */
	char xfer;		       /* Data transfer rate */
} fdst[] = { 
	/* 360K diskette */
	{ 9, 2, 2 * 9, 40, 0, 0, &fd_sizes[1], 0x09, 0x23, 0xe0, 0x01 },
	/* 1.2M diskette */
	{ 15, 2, 2 * 15, 80, 0, 0, &fd_sizes[0], 0x0f, 0x1b, 0x10, 0x00 },
	/* 360K diskette in 360K drive */
	{ 9, 2, 2 * 9, 40, 0, 0, &fd_sizes[1], 0x09, 0x2a, 0xe0, 0x02 },
};

int fd_type[] = {0,0,0,0};	       /* default to 360k in 1.2M */
#define fdtype	fd_type[unit]
int fd_last_type = -1;		       /* no one opened yet */
static int fd_auto_density = 0;	       /* flag to tell fdwait to shut up about
					  timeout errors */
#define	FD_MAX_DRIVE	3
#define FD_MAX_TYPES	(sizeof(fdst)/sizeof(fdst[0]))

/* fdcount of characters to copy from sector received */
int fdcount;
char *fdma;

int fddebug;

/* number of retries to complete operation */
static int retry_count = 0, cmd_error = 0;
int fdint = 0;			       /* fd interrupt flag */
int fd_on = 0;			       /* which floppys are on */

#define FDSECSIZE	512
char  *dma_bufptr;

/*
 *	Strategy Routine:
 *	Arguments:
 *	  Pointer to  I/O buffer structure
 *	  R/W function flag
 *	Function:
 *	  Start up the device
 */
fdstrategy(io, func)
	register struct iob *io;
{
#ifdef IBMRTPC
	int	unit = io->i_unit;
	register struct fdst *st = &fdst[fdtype];
	register u_short cylinder;
	register u_long sector;
	register u_long maxbytes;
	char dma_buf[2048 + (30 * FDSECSIZE)];	 /* allocate on stack */

	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	/* get a buffer pointer that is on a 2k boundary */
	dma_bufptr = (char *)(((int)(dma_buf + 2048 - 1)) & ~((int)(2048 - 1)));


	fdma = io->i_ma;

#define FDHEADS		2
	cylinder = (io->i_bn * (unsigned)(BSIZE / FDSECSIZE)) /
	    (unsigned)(st->nspc);
	sector = io->i_bn * (unsigned)(BSIZE / FDSECSIZE);
	sector %= (st->nspc);	       /* should be between 0-17 */

	/* make sure we're using the proper transfer rate */
	if (fdtype != fd_last_type) {
		fdmotoron(unit);
	} else {
		fdselect(unit);
	}
	/* send seek commands to adapter */
	fdseek(unit,(cylinder & 0xff), ((sector / st->nsect) & 1));
	retry_count = 0;


	/* loop while there is still more data to write */
	while (fdma < (io->i_ma + io->i_cc)) {
		maxbytes = (st->nspc - sector) * FDSECSIZE;
		/* make sure the cylinder and sector really exist */
		if ((cylinder >= st->ncyl) || (sector > st->nspc))
			return (-1);   /* return nasty error message */

		/* used for partial sector transfers */
		fdcount = ((maxbytes) > ((io->i_ma + io->i_cc) - fdma)) 
			? ((io->i_ma + io->i_cc) - fdma + 1) : (maxbytes);
#ifdef DEBUG
		if (fddebug > 0)
			printf("\rfd: cyl %x  sec %x  fdcount %x  ", 
						cylinder, sector, fdcount);
#endif DEBUG
		/* determine if READ or WRITE */
		if (io->i_flgs & F_RDDATA) {
			/* READ */
/* changed according to DOS listing in AT tech ref p. 5-92 */
#define FDREAD	0xe6
			fdcmd(FDREAD);
		} else {
			/* WRITE */
#define	FDWRITE	0xc5
			fdcmd(FDWRITE);
			/* copy iobuffer to dma buffer */
			bcopy(fdma, dma_bufptr, fdcount);
		}
		/* end read/write if */

		/* setup DMA channel */
		r->dm_bufaddr = dma_bufptr;
		r->dm_length = maxbytes;
		r->dm_channel = CHAN2; /* channel 2 */
		r->dm_party = DM_THIRD;	/* third party DMA */
		r->dm_transfer = DM_SINGLE; /* single mode transfer mode */
		r->dm_operation = (io->i_flgs & F_RDDATA) ? DM_WRITE : DM_READ;

		dma_setup(r);


		/* common disk setup code for read and write */
		fdcmd((((sector / st->nsect) & 1) * 4) | unit&1);
		/* put head number at bit 2 */
		fdcmd((cylinder & 0xff)); /* cylinder */
		fdcmd((sector / st->nsect) & 1); /* put head number at bit 1 */
		fdcmd((sector % st->nsect) + 1); /* sector number (1-n) */
		fdcmd(0x02);	       /* sector size 02 * 0xff */
		fdcmd(st->eot);	       /* last sector on track */
		fdcmd(st->gpl);	       /* gap length */
		fdcmd(0xff);	       /* sector size 02 * 0xff */

		/* start DMA channel */
		dma_start(CHAN2);
		interrupt();	       /* wait for NEC interrupt */

		/* reset DMA channel */
		dma_done(CHAN2);

		if (io->i_flgs & F_RDDATA) {
			/* copy dma buffer to iobuffer */
			bcopy(dma_bufptr, fdma, fdcount);
		} else {
		}
		fdunload_results();

		/* update number of bytes transferred */
		fdma += fdcount;	      

		/* advance the sector pointer to next sector(and or cylinder) */
		sector = 0;    /* always start at sector 0 (n+1)!!!, conversion
			          to real device sectors is done when command is
			          issued stmt */
		cylinder++;

		retry_count = 0;

		fdseek(unit,(cylinder & 0xff), 0);
	}
	/* end main while */

	return (io->i_cc);
#endif IBMRTPC
}


fdopen(io)
	register struct iob *io;
{
#ifdef IBMRTPC
	int unit = io->i_unit;
	register struct fdst *st = &fdst[fdtype];
	char dma_buf[2048 + (30 * FDSECSIZE)];	 /* allocate on stack */

chg_density:
	fd_auto_density = 1;

	if ((unit > FD_MAX_DRIVE) || (io->i_boff != 0)) {
#ifndef SQUEEEEZE
		printf("FD: BAD DRIVE NUMBER OR INVALID PARTITION\n");
		printf("  : boff = %x, unit = %x\n",io->i_boff,unit);
#endif !SQUEEEEZE
		return (-ENXIO);
	}
	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	st = &fdst[fdtype];	       /* reset density */
	io->i_boff = 0;


	fdint = 0;

#define FDRST	0x00		       /* reset adapter card */
#define FDSPEC	0x03		       /* specify command */
#define FDLOAD	0x02		       /* head load time */
#define FDSENI	0x08		       /* sense interrupt command */
#define FDREST	0x07		       /* recalibrate - reset drive (cyl=0) */
	/* don't reset the ctlr if the other drive is on */
	if (!fd_on) {
		fdadapter->digout = FDRST;
		delay(500);
	}

	fdmotoron(unit);

	fdcmd(FDSPEC);
	fdcmd(st->step);
	fdcmd(FDLOAD);
	delay(500);

	fdwait(DsktRQM, DsktRQM);      /* wait for READY */

	fdcmd(FDSENI);
	fdwait(DsktRQM, DsktRQM);      /* wait for READY */

	/* get result */
	junk = fdresult();
	junk = fdresult();

	fdcmd(FDREST);
	fdcmd(unit&1);

	fdwait(DsktRQM, DsktRQM);      /* wait for READY */
	/* sense inturrupt command */
	fdcmd(FDSENI);		       /* */
	fdwait(DsktRQM, DsktRQM);      /* wait for READY */

	/* get result */
	fdstatus = fdresult();
	fdcylinder = fdresult();

	delay(500);		       /* give NEC thinking time? */


	/* this last piece of code tries to read sector 1 track 0.
	 * If it can then we are at the correct density, otherwise
	 * try different density.
	 */

	/* send seek commands to adapter */
	fdseek(unit,1, 0);
	fdcmd(FDREAD);

	/* get a buffer pointer that is on a 2k boundary */
	dma_bufptr = (char *)(((int)(dma_buf + 2048 - 1)) & ~((int)(2048 - 1)));

	/* setup DMA channel */
	r->dm_bufaddr = dma_bufptr;
	r->dm_length = FDSECSIZE;
	r->dm_channel = CHAN2;	       /* channel 2 */
	r->dm_party = DM_THIRD;	       /* third party DMA */
	r->dm_transfer = DM_SINGLE;    /* single mode transfer mode */
	r->dm_operation = (io->i_flgs & F_RDDATA) ? DM_WRITE : DM_READ;

	dma_setup(r);



	/* common disk setup code for read and write */
	fdcmd(unit&1);		       /* put head number at bit 2 */
	fdcmd(1);		       /* cylinder */
	fdcmd(0);		       /* put head number at bit 1 */
	fdcmd(1);		       /* sector number (1-n) */
	fdcmd(0x02);		       /* sector size 02 * 0xff */
	fdcmd(st->eot);		       /* last sector on track */
	fdcmd(st->gpl);		       /* gap length */
	fdcmd(0xff);		       /* sector size 02 * 0xff */

	/* don't start dma before passing parameters on a write!! */

	/* start DMA channel */
	dma_start(CHAN2);
	interrupt();		       /* wait for NEC interrupt */

	/* reset DMA channel */
	dma_done(CHAN2);

	fdunload_results();

	/* check command status register after operation */
	if (((DsktSr0 & 0xc0) == 0x40) || (DsktCyl != 1) || (DsktHd != 0)) {
		/* force two errors before changing den */
		if (++cmd_error > 1) {
			cmd_error = 0;
			if (++fdtype > FD_MAX_TYPES) {
				fdtype = 0;
			}
		}
		goto chg_density;
	}

	fd_auto_density = 1;
#ifdef DEBUG
	if (fddebug > 0) {
		switch (fdtype) {
		case 0:
			printf("fd: opening 1.2M drive for 360k diskette\n");
			break;
		case 1:
			printf("fd: opening 1.2M drive for 1.2M diskette\n");
			break;
		case 2:
			printf("fd: opening 360k drive for 360k diskette\n");
			break;
		default:
			printf("fd: opening invalid fdtype!\n");
		}
	}
#endif DEBUG
	return(0);
#endif IBMRTPC
}


/* There is more to closing the device than just turning it off ... */
fdclose(io)
	register struct iob *io;
{
#ifdef IBMRTPC
	fdmotoroff(io->i_unit);		       /* shut it down */
#endif IBMRTPC
}

interrupt()
{
#ifdef IBMRTPC
#define    ICW        0xF00088a0       /*   second 8259       */
#define    MASK		0x10
	char *icw = (char *)ICW;
	int forever = 499 /* 4999 */ ;


	/* maybe we only need to send the POLL command once ??? */
	*icw = 0x0c;
	while ((fdint == 0) && (--forever)) {
		delay(2);
		fdint = MASK & (*icw);
	}
	*icw = 0x64;		       /* reset??? */
	fdint = 0;		       /* reset fdint flag */

	if (forever) {
		;
	} else {
#ifndef SQUEEEEZE
		printf("fd: interrupt TIMEOUT\n");
#endif
		cmd_error++;	       /* signals that this operations should be
				          restarted */
	}
#endif IBMRTPC
}


fdseek(unit,cylinder, head)
	unsigned char cylinder, head;
	int	unit;
{
#ifdef IBMRTPC
#define FDSEEK	0x0f
	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	fdcmd(FDSEEK);
	fdcmd((head << 2) | unit&1);	/* shift bit 0 to bit 2 */

	/* double step on a 360k diskette in 1.2M drive */
	if (fdtype == 0)
		fdcmd(cylinder * 2);   /* cylinder */
	else
		fdcmd(cylinder);       /* cylinder */

	fdwait(DsktRQM, DsktRQM);      /* wait */
	interrupt();		       /* wait for interrupt */
	delay(60);		       /* wait for head to settle */

	fdcmd(FDSENI);		       /* */
	fdwait(DsktRQM, DsktRQM);      /* wait for READY */

	/* get result */
	DsktSr0 = fdresult();
	junk = fdresult();
#endif IBMRTPC
}


/* turn on motor */
fdmotoron(unit)
{

#ifdef IBMRTPC
/* 03 -drive select 0-3
 * 04 - enable controller
 * 08 - interrupts and DMA
 * 20 - drive 1 motor on
 * 10 - drive 0 motor on
 */
#define MOTOR_ON	0x0c
	fd_on |= (unit&1 ? 0x20 : 0x10);
	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	fdadapter->digout = MOTOR_ON | fd_on | unit&1;
	delay(500);		       /* wait for drive to get up to speed */
	fdadapter->digin = fdst[fdtype].xfer;
	fdadapter->digout = MOTOR_ON | fd_on | unit&1;
	fd_last_type = fdtype;
#endif IBMRTPC
}


/* turn off motor */
fdmotoroff(unit)
{
#ifdef IBMRTPC
#define MOTOR_OFF	0
	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	if (fd_on  &= ~(unit&1 ? 0x20 : 0x10)) {
		fdadapter->digout = MOTOR_ON|fd_on|(unit&1 ? unit&~1 : unit|1);
	} else {
		fdadapter->digout = MOTOR_OFF;
	}
#endif IBMRTPC
}

/* select drive */
fdselect(unit)
{
	fdadapter = (struct fddevice *)fdaddrs[unit >> 1];
	fdadapter->digout = MOTOR_ON | fd_on | unit&1;
}

/* send command to floppy disk controller */
fdcmd(cmd)
	unsigned char cmd;
{
#ifdef IBMRTPC
	fdwait(DsktRQM | DsktDIO, DsktRQM); /* wait for READY */
	fdadapter->data = cmd;
#endif IBMRTPC
}


/*
 *       Wait for disk I/O to complete
 */
fdwait(mask, compare)
	unsigned char mask;
	unsigned char compare;
{
#ifdef IBMRTPC
	int timeout = 10000;
	u_char status;

	while ((((status = fdadapter->status) & mask) != compare) && --timeout)
		DELAY(4);		   /* should loop about 5 times */

	if (!timeout) {
#ifndef SQUEEEEZE
		if (!fd_auto_density) {
			printf("fd: ERROR TIMEOUT --- fdwait(%x,%x) = %x  \n", 
							mask, compare, status);
		}
#endif
		cmd_error++;	       /* signals that this operations should be
				          restarted */
	}
#endif IBMRTPC
}


/* read result byte from the NEC diskette controller */
char fdresult()
{
#ifdef IBMRTPC
	char result;

	fdwait(DsktRQM | DsktDIO, DsktRQM | DsktDIO); /* wait for READY */
	result = fdadapter->data;

	return (result);
#endif IBMRTPC
}


/* unload command results */
fdunload_results()
{
#ifdef IBMRTPC
	char junk1, junk2;

	DsktSr0 = fdresult();
	DsktSr1 = fdresult();
	DsktSr2 = fdresult();
	DsktCyl = fdresult();
	DsktHd = fdresult();
	junk1 = fdresult();
	junk2 = fdresult();
#ifdef DEBUG
	if (fddebug > 1) {
		printf("\rFD: unload DsktSr0 = %x ", DsktSr0); /* */
		printf(" DSr1 = %x ", DsktSr1);	/* */
		printf(" DSr2 = %x ", DsktSr2);	/* */
		printf(" cyl = %x ", DsktCyl); /* */
		printf(" hd = %x ", DsktHd); /* */
		printf(" sec = %x ", junk1); /* */
		printf(" n = %x\n ", junk2); /* */
	}
#endif DEBUG
#endif IBMRTPC
}


/*ARGSUSED*/
fdioctl(io, cmd, arg)
	struct iob *io;
	int cmd;
	char *arg;
{
	int unit = io->i_unit;
#ifdef IBMRTPC

	switch (cmd) {

		/*
		 * Return Drive description table.
		 */
	case SAIODEVDATA:	       /* */
		{
			struct st *st = (struct st *)arg;

			st->nsect = (short)fdst[fdtype].nsect;
			st->ntrak = (short)fdst[fdtype].ntrak;
			st->nspc = (short)fdst[fdtype].nspc;
			st->ncyl = (short)fdst[fdtype].ncyl;
			st->steprate = (short)fdst[fdtype].steprate;
			st->precompcyl = (short)fdst[fdtype].precompcyl;
			/* this is the difference between st and hdst */
			st->off = (struct partab *)fdst[fdtype].off;
		}
		return (0);

	default:
		return (ENXIO);
	}
#endif IBMRTPC
}
