/*
 * @(#)xd.c 1.1 94/10/31 Copyright (c) 1985 by Sun Microsystems, Inc.
 */
#ifndef lint
static        char sccsid[] = "@(#)xd.c 1.1 94/10/31 SMI";
#endif

/* Standalone driver for Xylogics 7053 (formerly 751) */

#include <mon/cpu.addrs.h>
#include <stand/saio.h>
#include <stand/param.h>
#include <sys/dkbad.h>
#include <sun/dklabel.h>
#include <sun/dkio.h>
#include <ufs/fsdir.h>
#include <sundev/xdcreg.h>
#include <sundev/xdreg.h>
#include <stand/xderr.h>		/* shorter error messages */

extern char	msg_nolabel[];

struct xdparam {
	unsigned short	xd_boff;	/* Cyl # starting partition */
 	unsigned short	xd_nsect;	/* Sect/track */
 	unsigned short	xd_ncyl;	/* Cyl/disk */
 	unsigned short	xd_nhead;	/* Heads/cyl */
 	unsigned short	xd_bhead;	/* Base head # for removable disk */
 	unsigned short	xd_drive;	/* Xylogics drive type ID */
	int	xd_nblk;
	int	xd_badaddr;		/* Disk block # of bad-block table */
	struct dkbad xd_bad;
	unsigned char	xd_unit;
};

#define CYL(p)  (p*xdp->xd_nsect*xdp->xd_nhead)     /* cyl to block conv */

#define NSTD 	4
#define TIMEOUT	0xfffff
unsigned long xdstd[NSTD] = { 0xee80, 0xee90, 0xeea0, 0xeeb0 };

#ifdef BOOTBLOCK
#define XD_MAXSIZE	DEV_BSIZE
#else BOOTBLOCK
#define XD_MAXSIZE	(4 * MAXBSIZE)
#endif BOOTBLOCK
/*
 * Structure of our DMA area
 */
struct xddma {
	struct xdiopb	xdiopb;
	char	xdblock[XD_MAXSIZE];	/* R/W data */
};

/*
 * What resources we need to run
 */
struct devinfo xdinfo = {
	sizeof (struct xddevice), 
	sizeof (struct xddma),
	sizeof (struct xdparam),
	NSTD,
	xdstd,
	MAP_MBIO,
	XD_MAXSIZE,	/* transfer size */
};

/*
 * What facilities we export to the world
 */
int	xdopen(), xdstrategy();
extern int	xxboot(), xxprobe();
extern int	nullsys();

struct boottab xddriver = {
	"xd",	xxprobe, xxboot, xdopen, nullsys, xdstrategy,
	"xd: Xylogics 7053 disk", &xdinfo,
}; 


/*
 * Open a xdlogics disk.
 */
xdopen(sip)
	register struct saioreq *sip;
{
	register struct xdparam *xdp;
	register struct xddevice *xdaddr;
	register struct xdiopb *xd;
	struct dk_label *label;
	u_short ppart;
#ifndef BOOTBLOCK
	int xdspin();
#endif !BOOTBLOCK

	xdp = (struct xdparam *)sip->si_devdata;
	xdaddr = (struct xddevice *)sip->si_devaddr;
	xd = &((struct xddma *)sip->si_dmaaddr)->xdiopb;

	xdp->xd_unit = sip->si_unit & 0x03;
	ppart = (sip->si_unit >> 2) & 1;

#ifndef BOOTBLOCK
	/* Verify that xd is really present */
	if (peekc((char *)&xdaddr->xd_csr) == -1)
		return (-1);
#endif	!BOOTBLOCK

	/* reset controller */
	xdaddr->xd_csr = XD_RST; 
	DELAY(300);

	xdp->xd_boff  = 0;		/* Don't offset block numbers */
	xdp->xd_nsect = 2;
	xdp->xd_nhead = 2;
	xdp->xd_ncyl = 2;
	xdp->xd_bad.bt_mbz = -1;

#ifndef BOOTBLOCK
	/*
	 * Wait for disk to spin up, if necessary.
	 */
	if (!isspinning(xdspin, (char *)sip, 0))
		return (-1);
#endif !BOOTBLOCK

	label = (struct dk_label *)
		((struct xddma *)sip->si_dmaaddr)->xdblock;
	label->dkl_magic = 0;
	if (xdcmd(XD_READ, sip, 0, (char *)label, 1)) 
		goto badopen;
	if (chklabel(label)) 
		goto badopen;
	if (ppart != label->dkl_ppart)
		goto badopen;
	goto foundlabel;

badopen:
	printf(msg_nolabel);
	return (-1);

foundlabel:
	xdp->xd_nhead = label->dkl_nhead;
	xdp->xd_nsect = label->dkl_nsect;
	xdp->xd_ncyl  = label->dkl_ncyl + label->dkl_acyl;
	xdp->xd_boff  = label->dkl_map[sip->si_boff].dkl_cylno;
	xdp->xd_nblk  = label->dkl_map[sip->si_boff].dkl_nblk;
#ifndef	BOOTBLOCK
	if (xdp->xd_nblk == 0)
		return (-1);
#endif	BOOTBLOCK

	if (xdcmd(XD_WPAR, sip, 0, 0, 0))
		printf("xd: init error %x\n", xd->xd_errno);
	/*
	 * Fetch bad block info.
	 */
	xdp->xd_badaddr = ((int)xdp->xd_ncyl * xdp->xd_nhead - 1)
			  * xdp->xd_nsect;
	if (xdcmd(XD_READ, sip, xdp->xd_badaddr, (char *)&xdp->xd_bad, 1))
		printf("xd: no bad block info\n");
	return (0);
}

xdstrategy(sip, rw)
	struct saioreq *sip;
	int rw;
{
	register int cmd = (rw == WRITE) ? XD_WRITE : XD_READ;
	register int boff;
	register struct xdparam *xdp = (struct xdparam *)sip->si_devdata;
	register short nsect;
#ifndef BOOTBLOCK
	char *ma;
	int i, bn;
#endif BOOTBLOCK

	boff = CYL(xdp->xd_boff);
#ifdef BOOTBLOCK
	/* assert si_cc == DEV_BSIZE */
	if (xdcmd(cmd, sip, sip->si_bn + boff, sip->si_ma, 1))
		return (-1);
	return (sip->si_cc);
#else BOOTBLOCK
	nsect = sip->si_cc / DEV_BSIZE;
	if (sip->si_bn + nsect > xdp->xd_nblk)
		nsect = xdp->xd_nblk - sip->si_bn;
	if (nsect <= 0)
		return (0);
	if (xdcmd(cmd, sip, sip->si_bn+boff, sip->si_ma, nsect) == 0)
		return (nsect*DEV_BSIZE);
	/*
	 * Large transfer failed, now do one at a time
	 */
	bn = sip->si_bn + boff;
	ma = sip->si_ma;
	for (i = 0; i < nsect; i++) {
		if (xdcmd(cmd, sip, bn, ma, 1))
			return (-1);
		bn++;
		ma += DEV_BSIZE;
	}
	return (nsect*DEV_BSIZE);
#endif	BOOTBLOCK
}

#ifndef BOOTBLOCK
/*
 * This routine is called from isspinning() as the test condition.
 */
/*ARGSUSED*/
int
xdspin(sip, dummy)
	struct saioreq *sip;
	int dummy;
{
#ifdef notdef
	register struct xdparam *xdp = (struct xdparam *)sip->si_devdata;
#endif notdef
	register struct xdiopb *xd = &((struct xddma *)sip->si_dmaaddr)->xdiopb;

	(void) xdcmd(XD_RESTORE, sip, 0, (char *)0, 0);
	return ((xd->xd_dstat & XD_READY) ? 1 : 0);
}
#endif !BOOTBLOCK


xdcmd(cmd, sip, bno, buf, nsect)
	int cmd;
	struct saioreq *sip;
	register int bno;
	char *buf;
	int nsect;
{
	register int i, t;
	register struct xdparam *xdp = (struct xdparam *)sip->si_devdata;
	register struct xdiopb *xd = &((struct xddma *)sip->si_dmaaddr)->xdiopb;
	register struct xddevice *xdaddr = (struct xddevice *)sip->si_devaddr;
	register char *bp = ((struct xddma *)sip->si_dmaaddr)->xdblock;
	unsigned short cylno, sect, head;
	int error, errcnt = 0;
	int timeout = 0;

	cylno = bno / CYL(1);
	sect = bno % xdp->xd_nsect;
	head = (bno / xdp->xd_nsect) % xdp->xd_nhead;

	/* If destination buffer is in DVMA space, use it directly */
	if (buf >= DVMA_BASE) {
		bp = buf;

	/* Non-DVMA Buffer, if writing copy it into our DVMA buffer. */
	} else if (cmd == XD_WRITE) {
		bcopy(buf, bp, nsect*DEV_BSIZE);
	}

retry:
	bzero((char *)xd, sizeof (struct xdiopb));
	xd->xd_cmd = cmd >> 8;
	switch (cmd) {
		case XD_WPAR:
		    xd->xd_subfunc = XD_DRIVE;
		    xd->xd_drparam = 0;
		    xd->xd_nsect = (short) ((xdp->xd_nsect - 1) << 8);
		    xd->xd_cylinder = xdp->xd_ncyl - 1;
		    xd->xd_head = xdp->xd_nhead - 1;
		    xd->xd_sector = xdp->xd_nsect - 1;
		    break;

		default:
		    xd->xd_subfunc = 0;
		    xd->xd_nsect = nsect;
		    xd->xd_cylinder = cylno;
		    xd->xd_head = head;
		    xd->xd_sector = sect;
		    xd->xd_bufaddr = (int) (MB_DMA_ADDR(bp));
	}
	xd->xd_unit = xdp->xd_unit & 3;
	xd->xd_intpri = 0;			/* disable interrupt */
	xd->xd_bufmod = XD_ADDRMOD24;		/* standard supervisory */

	t = (int) (MB_DMA_ADDR(xd));
	xdaddr->xd_iopbaddr1 = (int) t;
	xdaddr->xd_iopbaddr2 = (int) t >> 8;
	xdaddr->xd_iopbaddr3 = (int) t >> 16;
	xdaddr->xd_iopbaddr4 = (int) t >> 24;
	xdaddr->xd_modifier = XD_ADDRMOD24;
	xdaddr->xd_csr = XD_AIO;

	while (!(xdaddr->xd_csr & XD_RIO) && timeout < TIMEOUT)
		timeout++;	
	
	if (timeout == TIMEOUT) {
		printf("disk ctlr timeout\n");
		xdaddr->xd_csr = XD_RST;	 /* reset the controller */
		return(-1);
	}

	if (xdaddr->xd_csr & XD_FERR) {
	    xdaddr->xd_csr = XD_RST;
	    DELAY(300);
	    xd->xd_iserr = 1;
	}

	xdaddr->xd_csr = XD_CLRIO;	/* clear rio bit */
	xdaddr->xd_csr = XD_CLRBS;	/* release register */

	if (xd->xd_iserr && xd->xd_errno != XDE_FECC) {
		if (nsect != 1)		/* only try hard on single sectors */
			return (-1);
		error = xd->xd_errno;
		if ((i = xdfwd(xdp, cylno, head, sect)) != 0)
			return xdcmd(cmd, sip, i, buf, 1);
		/* Attempt to reset the error condition */
		if (cmd != XD_RESTORE) {
			xdaddr->xd_csr = XD_RST; /* reset the controller */
			DELAY(300);		 /* and then the drive */
			(void) xdcmd(XD_RESTORE, sip, 0, (char *)0, 1);
		}
		if (++errcnt < 5)
			goto retry;
		if (bno != 0)	/* drive type probe */
			printf("xd: error %x bno %d\n", error, bno);
		return (-1);			/* Error */
	}

	/* If user buffer not in DVMA space, copy data to it */
	if (cmd == XD_READ  &&  buf < DVMA_BASE)
		bcopy(bp, buf, nsect*DEV_BSIZE);

	return (0);
}

xdfwd(xdp, cn, tn, sn)
	register struct xdparam *xdp;
	int cn, tn, sn;
{
	register struct dkbad *bt = &xdp->xd_bad;
	register int i;

	if (bt->bt_mbz != 0)	/* not initialized */
		return (0);
	for (i=0; i<126; i++)		/* FIXME constant */
		if (bt->bt_bad[i].bt_cyl == cn &&
		    bt->bt_bad[i].bt_trksec == (tn<<8)+sn) {
			return (xdp->xd_badaddr - i - 1);
		}
	return (0);
}
