Coherent4.2.10/conf/fdc/src/fdc.c

Compare this file to the similar file:
Show the results in this format:

/* $Header: /ker/io.386/RCS/fdc.c,v 2.5 93/08/19 10:38:44 nigel Exp Locker: nigel $ */
/*
 * Support 765-style controller for diskette and floppy tape
 *
 * $Log:	fdc.c,v $
 * Revision 2.5  93/08/19  10:38:44  nigel
 * r83 ioctl (), corefile, new headers
 * 
 * Revision 2.4  93/08/19  04:02:19  nigel
 * Nigel's R83
 */

#define _KERNEL 1

#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/stat.h>
#include <sys/coherent.h>

#include <kernel/trace.h>

#include <coh/i386lib.h>
#include <coh/md.h>
#include <coh/misc.h>
#include <coh/putchar.h>

#include <common/ccompat.h>

#include <sys/uproc.h>
#include <sys/buf.h>
#include <sys/con.h>
#include <sys/devices.h>
#include <sys/dmac.h>
#include <sys/fdc765.h>
#include <sys/cmn_err.h>


/* Number of ticks to busy-wait the kernel before timeout awaiting RQM. */
#define FDC_RQM_WAIT	2

int fdcTblk;
int OUTB_DB = 0;

enum {
	FL_TIMING = 1,
	FT_TIMING = 2
};

enum {
	FL_INTR = 1,
	FT_INTR = 2
};

void	fdcReset();

void		fdcRate();
static int	fdcWaitRQM();

static int	setFdcIntr();
static void	setFdcTiming();


/*
 * Two patchable pointers, for enabling diskette and/or tape device control.
 */

extern	CON * flCon;		/* Initialized in fl/Space.c */
extern	CON * ftCon;		/* Initialized in ft/Space.c */

/* Global struct "fdc" passes FDC status to diskette and tape drivers. */
struct FDC fdc;

void	(*flIntr)();
void	(*ftIntr)();

static int	fdcIntOwner;
static int	fdcTiming;

/******* FOR DEBUG PURPOSES ONLY ************/
#if __USE_PROTO__
void outbDb (int addr, int data)
#else
void
outbDb(addr, data)
int addr, data;
#endif
{
	static int oldAddr, oldData;
	int s = sphi();

	if (OUTB_DB) {
		outb(addr, data);
		if (addr != oldAddr) {
			cmn_err (CE_CONT, "!OUT[%x,%x]", addr, data);
			oldAddr = addr;
			oldData = data;
		} else if (data != oldData) {
			cmn_err (CE_CONT, "!/%x", data);
			oldData = data;
		} else
			putchar('=');
	} else
		outb(addr, data);
	spl(s);
}

/***************************************************************************/
/*
 * First part of fdc module.
 *
 * Kernel interface.
 */

/*
 * The load routine asks the
 * switches how many drives are present
 * in the machine, and sets up the field
 * in the floppy database. It also grabs
 * the level 6 interrupt vector.
 */

#if __USE_PROTO__
static void fdcload(void)
#else
static void
fdcload()
#endif
{
	register int	s;

	if (flCon == NULL && ftCon == NULL) {
		cmn_err (CE_WARN, "fdc has no target devices");
		return;
	}

	/*
	 * Ensure DMA channel 2 is turned off.
	 * The Computerland ROM does not disable DMA channel after autoboot
	 * from hard disk.  The Western Digital controller board appears to
	 * send a dma burst when the floppy controller chip is reset.
	 */
	dmaoff(DMA_CH2);

	if (flCon)
		(* flCon->c_load) ();
	if (ftCon)
		(* ftCon->c_load) ();

	/*
	 * Initialize the floppy disk controller (if we
	 * have any floppy drives).
	 */

	s = sphi();
	fdcReset ();
	spl(s);
}

/*
 * Release resources.
 */

#if __USE_PROTO__
static void fdcunload(void)
#else
static void
fdcunload()
#endif
{
	if (flCon == NULL && ftCon == NULL)
		return;
	
	if (flCon)
		(*flCon->c_uload) ();
	if (ftCon)
		(*ftCon->c_uload) ();

	/*
	 * Cancel periodic (1 second) invocation.
	 */
	drvl[FL_MAJOR].d_time = 0;
	fdcTiming = 0;

	/*
	 * Turn motors off.
	 */
	outbDb(FDCDOR, DORNMR);		/* Leave interrupts disabled. */

	/*
	 * Clear interrupt vector.
	 */
	clrivec(6);
}

#if __USE_PROTO__
static void fdcopen(o_dev_t dev, int mode, int flags, __cred_t * credp)
#else
static void
fdcopen (dev, mode, flags, credp)
o_dev_t		dev;
int		mode;
int		flags;
cred_t	      *	credp;
#endif
{
	if (FDC_DISKETTE (dev)) {
		if (flCon)
			(* flCon->c_open) (dev, mode, flags, credp);
		else
			SET_U_ERROR (ENXIO, "fdcopen()-no floppy");
	} else if (FDC_TAPE (dev)) {
		if (ftCon)
			(* ftCon->c_open) (dev, mode, flags, credp);
		else
			SET_U_ERROR (ENXIO, "fdcopen()-no tape");
	} else
		SET_U_ERROR (ENXIO, "fdcopen()-no device");
}

#if __USE_PROTO__
static void fdcclose(o_dev_t dev, int mode, int flags, __cred_t * credp)
#else
static void
fdcclose(dev, mode, flags, credp)
o_dev_t		dev;
int		mode;
int		flags;
cred_t	      *	credp;
#endif
{
	if (FDC_DISKETTE (dev)) {
		if (flCon)
			(* flCon->c_close) (dev, mode, flags, credp);
		else
			SET_U_ERROR (ENXIO, "fdcclose()-no floppy");
	} else if (FDC_TAPE (dev)) {
		if (ftCon)
			(* ftCon->c_close) (dev, mode, flags, credp);
		else
			SET_U_ERROR (ENXIO, "fdcclose()-no tape");
	} else
		SET_U_ERROR (ENXIO, "fdcclose()-no device");
}

#if __USE_PROTO__
static void fdcread(o_dev_t dev, IO * iop, __cred_t * credp)
#else
static void
fdcread(dev, iop, credp)
o_dev_t		dev;
IO	      *	iop;
cred_t	      *	credp;
#endif
{
	if (FDC_DISKETTE (dev)) {
		if (flCon)
			(* flCon->c_read) (dev, iop, credp);
		else
			SET_U_ERROR (ENXIO, "fdcread()-no floppy");
	} else if (FDC_TAPE (dev)) {
		if (ftCon)
			(* ftCon->c_read) (dev, iop, credp);
		else
			SET_U_ERROR (ENXIO, "fdcread()-no tape");
	} else
		SET_U_ERROR(ENXIO, "fdcread()-no device");
}

#if __USE_PROTO__
static void fdcwrite(o_dev_t dev, IO * iop, __cred_t * credp)
#else
static void
fdcwrite(dev, iop, credp)
o_dev_t		dev;
IO	      *	iop;
cred_t	      *	credp;
#endif
{
	if (FDC_DISKETTE (dev)) {
		if (flCon)
			(* flCon->c_write) (dev, iop, credp);
		else
			SET_U_ERROR (ENXIO, "fdcwrite()-no floppy");
	} else if (FDC_TAPE (dev)) {
		if (ftCon)
			(* ftCon->c_write) (dev, iop, credp);
		else
			SET_U_ERROR (ENXIO, "fdcwrite()-no tape");
	} else
		SET_U_ERROR (ENXIO, "fdcwrite()-no device");
}


#if __USE_PROTO__
static void fdcioctl(o_dev_t dev, int com, __VOID__ * par, int mode,
  __cred_t * credp, int * rvalp)
#else
static void
fdcioctl (dev, com, par, mode, credp, rvalp)
o_dev_t		dev;
int		com;
char	      *	par;
int		mode;
cred_t	      *	credp;
int	      *	rvalp;
#endif
{
	if (FDC_DISKETTE (dev)) {
		if (flCon)
			(* flCon->c_ioctl) (dev, com, par, mode, credp, rvalp);
		else
			SET_U_ERROR (ENXIO, "fdcioctl()-no floppy");
	} else if (FDC_TAPE(dev)) {
		if (ftCon)
			(* ftCon->c_ioctl) (dev, com, par, mode, credp, rvalp);
		else
			SET_U_ERROR (ENXIO, "fdcioctl()-no tape");
	} else
		SET_U_ERROR (ENXIO, "fdcioctl()-no device");
}

/* Can't set u.u_error inside block routine. */
static void
fdcblock (bp)
BUF	      *	bp;
{
	if (FDC_DISKETTE (bp->b_dev)) {
		if (flCon)
			(* flCon->c_block) (bp);
	} else if (FDC_TAPE (bp->b_dev))
		if (ftCon)
			(* ftCon->c_block) (bp);
}


/***************************************************************************/
/*
 * Second part of fdc module.
 *
 * Hardware interface.
 */

/*
 * Get status (if any) from last command
 */
void
fdcCmdStatus()
{
	register int	b;
	register int	n = 0;		/* # of status bytes read */
	register int	s;

	s = sphi();

	/*
	 * Read all the status bytes the controller will give us.
	 */

	for (;;) {
		b = fdcGet();
		if (b == -1)
			break;

		if (n < FDC_NUM_CMD_STAT)
			fdc.fdc_cmdstat[n++] = b;
	}

	fdc.fdc_ncmdstat = n;
	spl(s);
}

/*
 * fdcDrvSelect()
 *
 * Select the drive indicated by "drive" (0..3).
 * If "motorOn" is nonzero, turn the motor on as well.
 */
void
fdcDrvSelect(drive, motorOn)
int drive, motorOn;
{
	unsigned char motorBits = 0;

	if (drive >= 4) {
		cmn_err (CE_WARN, "fdcDrvSelect(**drive**=%d,%d)",
		  drive, motorOn);
		return;
	}

	/* If needed, generate motor on bit for current selected drive. */
	if (motorOn)
		motorBits = 0x10 << drive;

	outbDb(FDCDOR, DORNMR | DORIEN | drive | motorBits);
	fdcSense();				/* Just in case --- */
}

/*
 * Send Sense Drive Status command to FDC.
 */
void
fdcDrvStatus(drive, head)
int drive, head;
{
	fdcPut(FDC_CMD_SDRV);
	fdcPut(drive | (head << 2));
}

/*
 * Read NEC data register, doing needed handshake.
 * Return -1 in case of timeout before RQM or no data available.
 */
int
fdcGet()
{
	int ret = -1;

	/* Wait for RQM, then expect DIO true. */
	if (busyWait(fdcWaitRQM, FDC_RQM_WAIT) && (inb(FDCMSR) & MSRDIO))
		ret = inb(FDCDAT);

	return ret;
}

/*
 * Get Interrupt status
 */
void
fdcIntStatus()
{
	register int	b;
	register int	n = 0;		/* # of status bytes read */
	register int	s;

	s = sphi();

	/*
	 * Issue a sense interrupt command and stash result.
	 */
	fdcPut(FDC_CMD_SINT);

	n = 0;
	for (;;) {
		b = fdcGet();
		if (b == -1)
			break;

		if (n < FDC_NUM_INT_STAT)
			fdc.fdc_intstat[n++] = b;
	}
	fdc.fdc_nintstat = n;
	spl(s);
}

/*
 * Since the FDC is run in DMA mode, possible interrupt causes are:
 *
 * 1 Result phase of: Read Data, Read Track, Read ID, Read Deleted Data,
 *   Write Data, Format Cylinder, Write Deleted Data, Scan.
 *
 * 2 Ready line of diskette drive changes state.
 *
 * 3 End of Seek or Recalibrate.
 *
 * In case 1, the interrupt is cleared by read/write data to FDC.
 * In cases 2 and 3, a Sense Interrupt is needed to clear the interrupt
 * and determine its cause.
 *
 * The following comment is obsolete, but possibly of interest:
 ***********************************************
 * The interrupt routine gets all
 * the status bytes the controller chip
 * will give it, then issues a sense interrupt
 * status command (which is necessary for a seek
 * to complete!) and throws all of the status
 * bytes away.
 ***********************************************
 */

void
fdcintr ()
{
	int s;

	s = sphi ();

	/* Invalidate previously stored interrupt and command status. */
	fdc.fdc_nintstat = 0;
	fdc.fdc_ncmdstat = 0;

	/* Vector to diskette or tape device interrupt handler. */
	if ((fdcIntOwner & FL_INTR) && flIntr)
		(* flIntr)();
	else if ((fdcIntOwner & FT_INTR) && ftIntr)
		(* ftIntr)();
	else /* Just clear the interrupt status. */
		fdcSense ();
	spl (s);
}


/*
 * Send a command byte to the NEC chip, first waiting until the chip
 * says that it is ready.
 * Return 0 if able to send, or -1 if timed out or FDC had wrong I/O
 * direction.
 */
int
fdcPut(cmd)
int cmd;
{
	int ret = -1;

	/* Wait for RQM, then expect DIO false. */
	if (busyWait(fdcWaitRQM, FDC_RQM_WAIT)) {
		if ((inb(FDCMSR) & MSRDIO) == 0) {
			outbDb(FDCDAT, cmd);
			ret = 0;
		}
	}

	return ret;
}

/*
 * Send a command string, given as byte array and length.
 * Return the number of bytes sent.
 */
int
fdcPutStr(cmdStr, cmdLen)
unsigned char * cmdStr;
unsigned int cmdLen;
{
	int bytesSent;

	for (bytesSent = 0; bytesSent < cmdLen; bytesSent++) {
		if (fdcPut(*cmdStr++)) {
			cmn_err (CE_WARN, "fdcPutStr: only sent %d bytes of %d",
			  bytesSent, cmdLen);
			break;
		}
	}
	return bytesSent;
}

/*
 * Set transfer rate (FDC_RATE_250K/300K/500K/1MEG)
 */
void
fdcRate(rate)
int rate;
{
	outbDb(FDCRATE, rate);
}

/*
 * Given drive # (0..3), and head (0..1), send Read ID command to the FDC.
 */
void
fdcReadID(drive, head)
int drive;
int head;
{
	fdcPut(FDC_CMD_RDID);
	fdcPut(drive | (head << 2));
}

/*
 * Given drive # (0..3), send a Recalibrate command to the FDC.
 *
 * Sense interrupt - fdcIntStatus() - *must* be done when the
 * ensuing IRQ happens.
 */
void
fdcRecal(drive)
int drive;
{
	fdcPut(FDC_CMD_RCAL);
	fdcPut(drive);
}

/*
 * Given drive # (0..3), head (0..1), and cylinder (0..255),
 * send a seek command to the FDC.
 *
 * Sense interrupt - fdcIntStatus() - *must* be done when the
 * ensuing IRQ happens.
 */
void
fdcSeek(drive, head, cyl)
int drive, head, cyl;
{
	fdcPut(FDC_CMD_SEEK);
	fdcPut(drive | (head << 2));
	fdcPut(cyl);
}

/*
 * fdcSense() issues Sense Drive Status and Sense Interrupt Status,
 * saving information from the FDC into global struct "fdc".
 *
 * It is called in response to FDC interrupts.
 */
void
fdcSense()
{
	fdcCmdStatus();			/* Get command status. */
	fdcIntStatus();			/* Get int status, just in case. */
}

/*
 * Send Specify command and data bytes to FDC.
 * Always specify DMA mode (ND bit = 0).
 */
void
fdcSpecify(srt, hut, hlt)
int srt, hut, hlt;
{
	fdcPut(FDC_CMD_SPEC);
	fdcPut((srt << 4) | hut);
	fdcPut(hlt << 1);
}

/*
 * Dissassemble the floppy error status for user reference.
 */
void
fdcStatus()
{
	/* If using floppy tape, show block number, not drive & head. */
	if (fdcIntOwner & FT_INTR)
		cmn_err (CE_CONT,
		  "WARNING : FDC tape block=%d (u=%d h=%u)",
		  fdcTblk,
		  fdc.fdc_cmdstat[0] & 3, (fdc.fdc_cmdstat[0] & 4) >> 2);
	else
		cmn_err (CE_CONT, "WARNING : FDC unit=%d  head=%u",
		  fdc.fdc_cmdstat[0] & 3, (fdc.fdc_cmdstat[0] & 4) >> 2);

	/*
	 * Report on ST0 bits.
	 */
	if (fdc.fdc_ncmdstat >= 1) {
		if (fdc.fdc_cmdstat[0] & ST0_NR)
			cmn_err (CE_CONT, " <Not Ready>");

		if (fdc.fdc_cmdstat[0] & ST0_EC)
			cmn_err (CE_CONT, " <Equipment Check>");
	}

	/*
	 * Report on ST1 bits.
	 */
	if (fdc.fdc_ncmdstat >= 2) {
		if (fdc.fdc_cmdstat[1] & ST1_MA)
			cmn_err (CE_CONT, " <Missing Address Mark>");

		if (fdc.fdc_cmdstat[1] & ST1_NW)
			cmn_err (CE_CONT, " <Write Protected>");

		if (fdc.fdc_cmdstat[1] & ST1_ND)
			cmn_err (CE_CONT, " <No Data>");

		if (fdc.fdc_cmdstat[1] & ST1_OR)
			cmn_err (CE_CONT, " <Overrun>");

		if (fdc.fdc_cmdstat[1] & ST1_DE)
			cmn_err (CE_CONT, " <Data Error>");

		if (fdc.fdc_cmdstat[1] & ST1_EN)
			cmn_err (CE_CONT, " <End of Cyl>");
	}

	/*
	 * Report on ST2 bits.
	 */
	if (fdc.fdc_ncmdstat >= 3) {
		if (fdc.fdc_cmdstat[2] & ST2_MD)
			cmn_err (CE_CONT, " <Missing Data Address Mark>");

		if (fdc.fdc_cmdstat[2] & ST2_BC)
			cmn_err (CE_CONT, " <Bad Cylinder>");

		if (fdc.fdc_cmdstat[2] & ST2_WC)
			cmn_err (CE_CONT, " <Wrong Cylinder>");

		if (fdc.fdc_cmdstat[2] & ST2_DD)
			cmn_err (CE_CONT, " <Bad Data CRC>");

		if (fdc.fdc_cmdstat[2] & ST2_CM)
			cmn_err (CE_CONT, " <Data Deleted>");
	}

	cmn_err (CE_CONT, "\n");
}

/*
 * Wait for FDC Main Status Register to assert Request for Master.
 * This function is designed to be called by busyWait().
 * It returns nonzero if RQM is asserted, 0 if not.
 */
static int
fdcWaitRQM()
{
	return (inb(FDCMSR) & MSRRQM);
}

/*
 * If sw is nonzero, start the timer for diskette;
 * else, stop the timer.
 */
void
setFlTimer(sw)
int sw;
{
	setFdcTiming(sw, FL_TIMING);
}

/*
 * If sw is nonzero, start the timer for floppy tape;
 * else, stop the timer.
 */
void
setFtTimer(sw)
int sw;
{
	setFdcTiming(sw, FT_TIMING);
}

static void
setFdcTiming(sw, mask)
int sw, mask;
{
	int s = sphi();

	/*
	 * Only do something if current request changes status of
	 * timing for the device.
	 */
	if (sw && (fdcTiming & mask) == 0) {
		fdcTiming |= mask;
		drvl[FL_MAJOR].d_time = 1;
		goto setFdcTimingDone;
	}
	if (sw  == 0 && (fdcTiming & mask)) {
		fdcTiming &= ~mask;
		if (fdcTiming == 0)
			drvl[FL_MAJOR].d_time = 0;
		goto setFdcTimingDone;
	}

setFdcTimingDone:
	spl(s);
	return;
}

#if __USE_PROTO__
static void fdctimeout (o_dev_t dev)
#else
static void
fdctimeout (dev)
o_dev_t		dev;
#endif
{
	if ((fdcTiming & FL_TIMING) && flCon)
		(* flCon->c_timer) (dev);
	if ((fdcTiming & FT_TIMING) && ftCon)
		(* ftCon->c_timer) (dev);
}


/*
 * Reset the fdc.
 * Sets drive select bits to 00, motor on bits to 0000.
 */

void
fdcReset()
{
	/* "Not Reset FDC" must remain low for at least 3.5 usec */
	outbDb (FDCDOR, 0);
	busyWait2 (NULL, 4);
	outbDb (FDCDOR, DORNMR | DORIEN);
}


/*
 * Reset the fdc.
 * Maintain drive select and motor enable (as specified) during the reset.
 */

void
fdcResetSel(drive, motorOn)
int drive, motorOn;
{
	unsigned char motorBits = 0;
	unsigned char outByte;

	/* If needed, generate motor on bit for current selected drive. */
	if (motorOn)
		motorBits = 0x10 << drive;

	/*
	 * Send drive select, motor on if needed, interrupt enable.
	 * The "not reset" bit is zero, which is the point of this routine.
	 */
	outByte = motorBits | drive | DORIEN;
	outbDb(FDCDOR, outByte);

	/* "Not Reset FDC" must remain low for at least 3.5 usec */
	busyWait2(NULL, 4);

	outByte |= DORNMR;
	outbDb(FDCDOR, outByte);
}

/*
 * If sw is nonzero, try to seize the fdc interrupt for the diskette;
 * else, try to release it.
 *
 * Return 1 on success, 0 on failure.
 */
int
setFlIntr(sw)
int sw;
{
	return setFdcIntr(sw, FL_INTR);
}

/*
 * If sw is nonzero, try to seize the fdc interrupt for floppy tape;
 * else, try to release it.
 *
 * Return 1 on success, 0 on failure.
 */
int
setFtIntr(sw)
int sw;
{
	return setFdcIntr(sw, FT_INTR);
}

static int
setFdcIntr(sw, mask)
int sw, mask;
{
	int ret;

	/*
	 * if attaching
	 *   if fdc interrupt is free
	 *	attach as requested
	 *   else
	 *      return failure
	 * else
	 *   if fdc requesting device now owns fdc interrupt
	 *	detach as requested
	 *   else
	 *      return failure
	 */

	if (sw) {
		if (fdcIntOwner == 0) {
			fdcIntOwner = mask;
			ret = 1;
		} else if (fdcIntOwner == mask) {
			ret = 1;
		} else {
			ret = 0;
		}
	} else {
		if (fdcIntOwner == mask) {
			fdcIntOwner = 0;
			ret = 1;
		} else {
			ret = 0;
		}
	}

	return ret;
}


CON	fdccon	= {
	DFBLK | DFCHR,			/* Flags */
	FL_MAJOR,			/* Major index */
	fdcopen,			/* Open */
	fdcclose,			/* Close */
	fdcblock,			/* Block */
	fdcread,			/* Read */
	fdcwrite,			/* Write */
	fdcioctl,			/* Ioctl */
	NULL,				/* Powerfail */
	fdctimeout,			/* Timeout */
	fdcload,			/* Load */
	fdcunload,			/* Unload */
	NULL				/* Poll */
};