Source to arch/i386/isa/wd7000.c


Enter a symbol's name here to quickly find it.

/*
 * UNFINISHED! UNFINISHED! UNFINISHED! UNFINISHED! UNFINISHED! UNFINISHED!
 * 
 * [email protected] 93/04/02
 * 
 * I was writing this driver for a wd7000-ASC. Yeah, the "-ASC" not the
 * "-FASST2". The difference is that the "-ASC" is missing scatter gather
 * support.
 * 
 * In any case, the real reason why I never finished it is because the
 * motherboard I have has broken DMA. This card wants 8MHz 1 wait state
 * operation, and my board munges about 30% of the words transferred.
 * 
 * Hopefully someone can finish this for the wd7000-FASST2. It should be
 * quite easy to do. Look at the Linux wd7000 device driver to see how
 * scatter gather is done by the board, then look at one of the Adaptec
 * drivers to finish off the job..
 *
 *	wd7000.c,v 1.7 1993/06/10 04:50:42 deraadt Exp
 */
#include "wds.h"
#if NWDS > 0

#include "sys/types.h"
#include "sys/param.h"
#include "sys/systm.h"
#include "sys/errno.h"
#include "sys/ioctl.h"
#include "sys/buf.h"
#include "sys/proc.h"
#include "sys/user.h"

#include "i386/isa/isa_device.h"
#include "sys/dkbad.h"
#include "sys/disklabel.h"
#include "scsi/scsi_all.h"
#include "scsi/scsiconf.h"

extern int delaycount;  /* from clock setup code */

#define PHYSTOKV(x)	( (u_long)(x) | 0xFE000000)
#define KVTOPHYS(x)	vtophys(x)
#define PAGESIZ 	4096


/* WD7000 registers */
#define WDS_STAT		0	/* read */
#define WDS_IRQSTAT		1	/* read */

#define WDS_CMD			0	/* write */
#define WDS_IRQACK		1	/* write */
#define WDS_HCR			2	/* write */

/* WDS_STAT (read) defs */
#define WDS_IRQ			0x80
#define WDS_RDY			0x40
#define WDS_REJ			0x20
#define WDS_INIT		0x10

/* WDS_IRQSTAT (read) defs */
#define WDSI_MASK		0xc0
#define WDSI_ERR		0x00
#define WDSI_MFREE		0x80
#define WDSI_MSVC		0xc0

/* WDS_CMD (write) defs */
#define WDSC_NOOP		0x00
#define WDSC_INIT		0x01
#define WDSC_DISUNSOL		0x02
#define WDSC_ENAUNSOL		0x03
#define WDSC_IRQMFREE		0x04
#define WDSC_SCSIRESETSOFT	0x05
#define WDSC_SCSIRESETHARD	0x06
#define WDSC_MSTART(m)		(0x80 + (m))
#define WDSC_MMSTART(m)		(0xc0 + (m))

/* WDS_HCR (write) defs */
#define WDSH_IRQEN		0x08
#define WDSH_DRQEN		0x04
#define WDSH_SCSIRESET		0x02
#define WDSH_ASCRESET		0x01

struct wds_cmd {
	u_char cmd;
	u_char targ;
	struct scsi_generic scb;		/*u_char scb[12];*/
	u_char stat;
	u_char venderr;
	u_char len[3];
	u_char data[3];
	u_char next[3];
	u_char write;
	u_char xx[6];
};

struct wds_req {
	struct wds_cmd cmd;
	struct wds_cmd sense;
	struct scsi_xfer *sxp;
	int busy, polled;
	int done, ret, ombn;
};

#define WDSX_SCSICMD		0x00
#define WDSX_OPEN_RCVBUF	0x80
#define WDSX_RCV_CMD		0x81
#define WDSX_RCV_DATA		0x82
#define WDSX_RCV_DATASTAT	0x83
#define WDSX_SND_DATA		0x84
#define WDSX_SND_DATASTAT	0x85
#define WDSX_SND_CMDSTAT	0x86
#define WDSX_READINIT		0x88
#define WDSX_READSCSIID		0x89
#define WDSX_SETUNSOLIRQMASK	0x8a
#define WDSX_GETUNSOLIRQMASK	0x8b
#define WDSX_GETFIRMREV		0x8c
#define WDSX_EXECDIAG		0x8d
#define WDSX_SETEXECPARM	0x8e
#define WDSX_GETEXECPARM	0x8f

struct wds_mb {
	u_char stat;
	u_char addr[3];
};
/* ICMB status value */
#define ICMB_OK			0x01
#define ICMB_OKERR		0x02
#define ICMB_ETIME		0x04
#define ICMB_ERESET		0x05
#define ICMB_ETARCMD		0x06
#define ICMB_ERESEL		0x80
#define ICMB_ESEL		0x81
#define ICMB_EABORT		0x82
#define ICMB_ESRESET		0x83
#define ICMB_EHRESET		0x84

struct wds_setup {
	u_char cmd;
	u_char scsi_id;
	u_char buson_t;
	u_char busoff_t;
	u_char xx;
	u_char mbaddr[3];
	u_char nomb;
	u_char nimb;
};

#define WDS_NOMB	16
#define WDS_NIMB	8
#define MAXSIMUL	8
struct wds {
	u_short addr;
	struct wds_req wdsr[MAXSIMUL];
	struct wds_mb ombs[WDS_NOMB], imbs[WDS_NIMB];
	int devs;
} wds[NWDS];

static int wdsunit = 0;
int wds_debug = 0;

void p2x(u_char *, u_long);
u_char *x2p(u_char *);
int wdsprobe(struct isa_device *);
void wds_minphys(struct buf *);
struct wds_req *wdsr_alloc(int);
int wds_scsi_cmd(struct scsi_xfer *);
long wds_adapter_info(int);
int wdsintr(int);
int wds_done(int, struct wds_cmd *, u_char);
int wdsattach(struct isa_device *);
int wds_init(struct isa_device *);
int wds_cmd(u_short, u_char *, int);
void wds_wait(int, int, int);


struct scsi_switch wds_switch = {
	"wds",
	wds_scsi_cmd,
	wds_minphys,
	0, 0,
	wds_adapter_info,
	0, 0, 0,
};

struct isa_driver wdsdriver = {
	wdsprobe,
	wdsattach,
	"wds",
};


void
flushcache(void)
{
	extern main();
	volatile char *p, c;
	int i;

	for(p=(char *)main, i=0; i<256*1024; i++)
		c = *p++;
}

void
p2x(u_char *p, u_long x)
{
	p[0] = (x & 0x00ff0000) >> 16;
	p[1] = (x & 0x0000ff00) >> 8;
	p[2] = (x & 0x000000ff);
}

u_char *
x2p(u_char *x)
{
	u_long q;

	q = ((x[0]<<16) & 0x00ff0000) + ((x[1]<<8) & 0x0000ff00) + (x[2] & 0x000000ff);
	return (u_char *)q;
}

int
wdsprobe(struct isa_device *dev)
{
	/*scsi_debug = PRINTROUTINES | TRACEOPENS | TRACEINTERRUPTS |
		SHOWREQUESTS | SHOWSCATGATH | SHOWINQUIRY | SHOWCOMMANDS;*/

	if(wdsunit > NWDS)
		return 0;

	dev->id_unit = wdsunit;
	wds[wdsunit].addr = dev->id_iobase;

	if(wds_init(dev) != 0)
		return 0;
	wdsunit++;
	return 8;
}

void
wds_minphys(struct buf *bp)
{
	int base = (int)bp->b_un.b_addr & (PAGESIZ-1);

	if(base + bp->b_bcount > PAGESIZ)
		bp->b_bcount = PAGESIZ - base;
}

struct wds_req *
wdsr_alloc(int unit)
{
	struct wds_req *r;
	int x;
	int i;

	r = NULL;
	x = splbio();
	for(i=0; i<MAXSIMUL; i++)
		if(wds[unit].wdsr[i].busy == 0) {
			r = &wds[unit].wdsr[i];
			r->busy = 1;
			break;
		}
	if(r == NULL) {
		splx(x);
		return NULL;
	}

	r->ombn = -1;
	for(i=0; i<WDS_NOMB; i++)
		if(wds[unit].ombs[i].stat==0) {
			wds[unit].ombs[i].stat = 1;
			r->ombn = i;
			break;
		}
	if(r->ombn == -1 ) {
		r->busy = 0;
		splx(x);
		return NULL;
	}
	splx(x);
	return r;
}

int
wds_scsi_cmd(struct scsi_xfer *sxp)
{
	struct wds_req *r;
	int unit = sxp->adapter;
	u_short base;
	u_char c, *p;
	int i;

	base = wds[unit].addr;

	/*printf("scsi_cmd\n");*/

	if( sxp->flags & SCSI_RESET) {
		printf("reset!\n");
		return COMPLETE;
	}

	r = wdsr_alloc(unit);
	if(r==NULL) {
		printf("no request slot available!\n");
		sxp->error = XS_DRIVER_STUFFUP;
		return TRY_AGAIN_LATER;
	}
	r->done = 0;
	r->sxp = sxp;

	printf("wds%d: target %d/%d req %8x flags %08x len %d: ", unit,
		sxp->targ, sxp->lu, r, sxp->flags, sxp->cmdlen);
	for(i=0, p=(u_char *)sxp->cmd; i<sxp->cmdlen; i++)
		printf("%02x ", p[i]);
	printf("\n");
	printf("       data %08x datalen %08x\n", sxp->data, sxp->datalen);

	if(sxp->flags & SCSI_DATA_UIO) {
		printf("UIO!\n");
		sxp->error = XS_DRIVER_STUFFUP;
		return TRY_AGAIN_LATER;
	}

	p2x(&wds[unit].ombs[r->ombn].addr[0], KVTOPHYS(&r->cmd));
	printf("%08x/%08x [email protected]%08x: %02x %02x %02x %02x\n",
		&r->cmd, KVTOPHYS(&r->cmd), &wds[unit].ombs[0], 
		wds[unit].ombs[r->ombn].stat, wds[unit].ombs[r->ombn].addr[0],
		wds[unit].ombs[r->ombn].addr[1], wds[unit].ombs[r->ombn].addr[2]);

	bzero(&r->cmd, sizeof r->cmd);
	r->cmd.cmd = WDSX_SCSICMD;
	r->cmd.targ = (sxp->targ << 5) | sxp->lu;
	bcopy(sxp->cmd, &r->cmd.scb, sxp->cmdlen<12 ? sxp->cmdlen : 12);
	p2x(&r->cmd.len[0], sxp->datalen);
	p2x(&r->cmd.data[0], sxp->datalen ? KVTOPHYS(sxp->data) : 0);
	r->cmd.write = (sxp->flags&SCSI_DATA_IN)? 0x80 : 0x00;
	p2x(&r->cmd.next[0], KVTOPHYS(&r->sense));

	bzero(&r->sense, sizeof r->sense);
	r->sense.cmd = r->cmd.cmd;
	r->sense.targ = r->cmd.targ;
	r->sense.scb.opcode = REQUEST_SENSE;
	p2x(&r->sense.data[0], KVTOPHYS(&sxp->sense));
	p2x(&r->sense.len[0], sizeof sxp->sense);
	r->sense.write = 0x80;

	/*printf("wdscmd: ");
	for(i=0, p=(u_char *)&r->cmd; i<sizeof r->cmd; i++)
		printf("%02x ", p[i]);
	printf("\n");*/

	if(sxp->flags & SCSI_NOMASK) {
		outb(base+WDS_HCR, WDSH_DRQEN);
		r->polled = 1;
	} else
		r->polled = 0;

	c = WDSC_MSTART(r->ombn);
	flushcache();
	if( wds_cmd(base, &c, sizeof c) != 0) {
		printf("wds%d: unable to start outgoing mbox\n", unit);
		r->busy = 0;
		/* XXX need to free mailbox */
		return TRY_AGAIN_LATER;
	}

	DELAY(10000);
	/*printf("%08x/%08x mbox: %02x %02x %02x %02x\n", &r->cmd, KVTOPHYS(&r->cmd),
		wds[unit].ombs[r->ombn].stat, wds[unit].ombs[r->ombn].addr[0],
		wds[unit].ombs[r->ombn].addr[1], wds[unit].ombs[r->ombn].addr[2]);*/

	if(sxp->flags & SCSI_NOMASK) {
repoll:		printf("wds%d: polling.", unit);
		i = 0;
		while( (inb(base+WDS_STAT) & WDS_IRQ) == 0) {
			printf(".");
			DELAY(10000);
			if(++i == 10) {
				printf("failed %02x\n", inb(base+WDS_IRQSTAT));
				/*r->busy = 0;*/
				sxp->error = XS_TIMEOUT;
				return HAD_ERROR;
			}
		}
		flushcache();
		printf("got one!\n");
		wdsintr(unit);
		if(r->done) {
			r->sxp->flags |= ITSDONE;
			if(r->sxp->when_done)
				(*r->sxp->when_done)(r->sxp->done_arg,
					r->sxp->done_arg2);
			r->busy = 0;
			return r->ret;
		}
		goto repoll;
	}

	outb(base+WDS_HCR, WDSH_IRQEN|WDSH_DRQEN);
	printf("wds%d: successfully queued\n", unit);
	return SUCCESSFULLY_QUEUED;
}

long
wds_adapter_info(int unit)
{
	return 1;
}

int
wdsintr(int unit)
{
	struct wds_cmd *pc, *vc;
	struct wds_mb *in;
	u_char stat;
	u_char c;

	/*printf("stat=%02x\n", inb(wds[unit].addr + WDS_STAT));*/
	DELAY(1000);
	c = inb(wds[unit].addr + WDS_IRQSTAT);
	printf("wdsintr: %02x\n", c);
	if( (c&WDSI_MASK) == WDSI_MSVC) {
		DELAY(1000);
		c = c & ~WDSI_MASK;
		flushcache();
		in = &wds[unit].imbs[c];

		printf("incoming mailbox %[email protected]%08x: ", c, in);
		printf("%02x %02x %02x %02x\n",
			in->stat, in->addr[0], in->addr[1], in->addr[2]);
		pc = (struct wds_cmd *)x2p(&in->addr[0]);
		vc = (struct wds_cmd *)PHYSTOKV(pc);
		stat = in->stat;
		printf("p=%08x v=%08x stat %02x\n", pc, vc, stat);
		wds_done(unit, vc, stat);
		in->stat = 0;

		outb(wds[unit].addr + WDS_IRQACK, 0xff);
	}
	return 1;
}

int
wds_done(int unit, struct wds_cmd *c, u_char stat)
{
	struct wds_req *r;
	int i;

	r = (struct wds_req *)NULL;
	for(i=0; i<MAXSIMUL; i++)
		if( c == &wds[unit].wdsr[i].cmd ) {
			/*printf("found at req slot %d\n", i);*/
			r = &wds[unit].wdsr[i];
			break;
		}
	if(r == (struct wds_req *)NULL) {
		printf("failed to find request!\n");
		return 1;
	}

	printf("wds%d: cmd %8x stat %2x/%2x %2x/%2x\n", unit, c,
		r->cmd.stat, r->cmd.venderr, r->sense.stat, r->sense.venderr);

	r->done = 1;
	/* XXX need to free mailbox */
	r->ret = HAD_ERROR;
	switch(r->cmd.stat) {
	case ICMB_OK:
		/*XXX r->sxp->sense.valid = 0;
		r->sxp->error = 0;*/
		r->ret = COMPLETE;
		break;
	case ICMB_OKERR:
 		printf("scsi err %02x\n", c->venderr);
		/*XXX r->sxp->sense.error_code = c->venderr;
		r->sxp->sense.valid = 1;*/
		r->ret = COMPLETE;
		break;
	case ICMB_ETIME:
		r->sxp->error = XS_TIMEOUT;
		r->ret = HAD_ERROR;
		break;
	case ICMB_ERESET:
	case ICMB_ETARCMD:
	case ICMB_ERESEL:
	case ICMB_ESEL:
	case ICMB_EABORT:
	case ICMB_ESRESET:
	case ICMB_EHRESET:
		r->sxp->error = XS_DRIVER_STUFFUP;
		r->ret = HAD_ERROR;
		break;
	}
	if(r->polled==0) {
		r->sxp->flags |= ITSDONE;
		if(r->sxp->when_done)
			(*r->sxp->when_done)(r->sxp->done_arg, r->sxp->done_arg2);
		r->busy = 0;
	}
	return 0;
}

int
wds_getvers(int unit)
{
	struct wds_req *r;
	u_short base;
	u_char c, *p;
	int i;

	base = wds[unit].addr;

	/*printf("scsi_cmd\n");*/

	r = wdsr_alloc(unit);
	if(r==NULL) {
		printf("wds%d: no request slot available!\n", unit);
		return -1;
	}
	r->done = 0;
	r->sxp = NULL;

	printf("wds%d: getvers req %8x\n", unit, r);

	p2x(&wds[unit].ombs[r->ombn].addr[0], KVTOPHYS(&r->cmd));
	printf("%08x/%08x [email protected]%08x: %02x %02x %02x %02x\n",
		&r->cmd, KVTOPHYS(&r->cmd), &wds[unit].ombs[0], 
		wds[unit].ombs[r->ombn].stat, wds[unit].ombs[r->ombn].addr[0],
		wds[unit].ombs[r->ombn].addr[1], wds[unit].ombs[r->ombn].addr[2]);

	bzero(&r->cmd, sizeof r->cmd);
	r->cmd.cmd = WDSX_GETFIRMREV;
	r->cmd.write = 0x80;

	printf("wdscmd: ");
	for(i=0, p=(u_char *)&r->cmd; i<sizeof r->cmd; i++)
		printf("%02x ", p[i]);
	printf("\n");

	outb(base+WDS_HCR, WDSH_DRQEN);
	r->polled = 1;

	c = WDSC_MSTART(r->ombn);
	flushcache();
	if( wds_cmd(base, &c, sizeof c) != 0) {
		printf("wds%d: unable to start outgoing mbox\n", unit);
		r->busy = 0;
		/* XXX need to free mailbox */
		return -1;
	}

	DELAY(10000);
	/*printf("%08x/%08x mbox: %02x %02x %02x %02x\n", &r->cmd, KVTOPHYS(&r->cmd),
		wds[unit].ombs[r->ombn].stat, wds[unit].ombs[r->ombn].addr[0],
		wds[unit].ombs[r->ombn].addr[1], wds[unit].ombs[r->ombn].addr[2]);*/

	while(1) {
		printf("wds%d: polling.", unit);
		i = 0;
		while( (inb(base+WDS_STAT) & WDS_IRQ) == 0) {
			printf(".");
			DELAY(10000);
			if(++i == 10) {
				printf("failed %02x\n", inb(base+WDS_IRQSTAT));
				/*r->busy = 0;*/
				return -1;
			}
		}
		flushcache();
		printf("got one!\n");
		wdsintr(unit);
		if(r->done) {
			printf("wds%d: version %02x %02x\n", unit,
				r->cmd.targ, r->cmd.scb.opcode);
			r->busy = 0;
			return 0;
		}
	}
}

int
wdsattach(struct isa_device *dev)
{
	int masunit = dev->id_masunit;
	static u_long versprobe		/* max 32 controllers */
	int r;

	if( !(versprobe & (1<<masunit))) {
		versprobe |= (1<<masunit);
		if(wds_getvers(masunit)==-1)
			printf("wds%d: getvers failed\n", masunit);
	}

	r = scsi_attach(masunit, wds[masunit].devs, &wds_switch,
		&dev->id_physid, &dev->id_unit, dev->id_flags);
	return r;
}

int
wds_init(struct isa_device *dev)
{
	struct wds_setup init;
	u_short base;
	u_char *p, c;
	int unit, i;

	unit = dev->id_unit;
	base = wds[unit].addr;

	/*
	 * Sending a command causes the CMDRDY bit to clear.
 	 */
	c = inb(base+WDS_STAT);
	for(i=0; i<4; i++)
		if( (inb(base+WDS_STAT) & WDS_RDY) != 0) {
			goto ready;
		DELAY(10);
	}
	return 1;

ready:
	outb(base+WDS_CMD, WDSC_NOOP);
	if( inb(base+WDS_STAT) & WDS_RDY)
		return 1;

	/*
	 * the controller exists. reset and init.
	 */
	outb(base+WDS_HCR, WDSH_SCSIRESET|WDSH_ASCRESET);
	DELAY(3);
	outb(base+WDS_HCR, WDSH_DRQEN);
	DELAY(20000);

#if 1
	outb(0xd6, 0xc3);
	outb(0xd4, 0x03);
#else
	isa_dmacascade(dev->id_drq);
#endif

	if( (inb(base+WDS_STAT) & (WDS_RDY)) != WDS_RDY) {
		printf("wds%d: waiting for controller to become ready", unit);
		for(i=0; i<6; i++) {
			if( (inb(base+WDS_STAT) & (WDS_RDY)) == WDS_RDY)
				break;
			printf(".");
			DELAY(10000);
		}
		if( (inb(base+WDS_STAT) & (WDS_RDY)) != WDS_RDY) {
			printf("failed\n");
			return 1;
		}
	}

	bzero(&init, sizeof init);
	init.cmd = WDSC_INIT;
	init.scsi_id = 0;
	init.buson_t = 24;
	init.busoff_t = 48;
	p2x(&init.mbaddr[0], KVTOPHYS(&wds[unit].ombs[0]));
	init.xx = 0;
	init.nomb = WDS_NOMB;
	init.nimb = WDS_NIMB;

	/*p = (u_char *)&init;
	printf("wds%d: %08x %08x init: ", unit,
		&wds[unit].ombs[0], KVTOPHYS(&wds[unit].ombs[0]));
	for(i=0; i<sizeof init; i++)
		printf("%02x ", p[i]);
	printf("\n");*/

	wds_wait(base+WDS_STAT, WDS_RDY, WDS_RDY);
	flushcache();
	if( wds_cmd(base, (u_char *)&init, sizeof init) != 0) {
		printf("wds%d: wds_cmd failed\n", unit);
		return 1;
	}
	wds_wait(base+WDS_STAT, WDS_INIT, WDS_INIT);

	wds_wait(base+WDS_STAT, WDS_RDY, WDS_RDY);
	c = WDSC_DISUNSOL;
	if( wds_cmd(base, &c, sizeof c) != 0) {
		printf("wds%d: wds_cmd failed\n", unit);
		return 1;
	}

	return 0;
}

int
wds_cmd(u_short base, u_char *p, int l)
{
	int i;
	u_char c;

	i = 0;
	while(i < l) {
		while( ((c=inb(base+WDS_STAT)) & WDS_RDY) == 0)
			;

		outb(base+WDS_CMD, *p);

		while( ((c=inb(base+WDS_STAT)) & WDS_RDY) == 0)
			;

		if(c & WDS_REJ)
			return 1;
		p++;
		i++;
	}
	while( ((c=inb(base+WDS_STAT)) & WDS_RDY) == 0)
		;
	if(c & WDS_REJ)
		return 1;
	/*printf("wds_cmd: %02x\n", inb(base+WDS_STAT));*/
	return 0;
}

void
wds_wait(int reg, int mask, int val)
{
	while( (inb(reg) & mask) != val)
		;
}

#endif