/*	$NetBSD: rl.c,v 1.24 2005/02/26 12:45:06 simonb Exp $	*/

/*
 * Copyright (c) 2000 Ludd, University of Lule}, Sweden. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed at Ludd, University of
 *      Lule}, Sweden and its contributors.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * RL11/RLV11/RLV12 disk controller driver and
 * RL01/RL02 disk device driver.
 *
 * TODO:
 *	Handle disk errors more gracefully
 *	Do overlapping seeks on multiple drives
 *
 * Implementation comments:
 *
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rl.c,v 1.24 2005/02/26 12:45:06 simonb Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/stat.h>
#include <sys/dkio.h>
#include <sys/fcntl.h>
#include <sys/event.h>

#include <ufs/ufs/dinode.h>
#include <ufs/ffs/fs.h>

#include <machine/bus.h>

#include <dev/qbus/ubavar.h>
#include <dev/qbus/rlreg.h>
#include <dev/qbus/rlvar.h>

#include "ioconf.h"
#include "locators.h"

static	int rlcmatch(struct device *, struct cfdata *, void *);
static	void rlcattach(struct device *, struct device *, void *);
static	int rlcprint(void *, const char *);
static	void rlcintr(void *);
static	int rlmatch(struct device *, struct cfdata *, void *);
static	void rlattach(struct device *, struct device *, void *);
static	void rlcstart(struct rlc_softc *, struct buf *);
static	void waitcrdy(struct rlc_softc *);
static	void rlcreset(struct device *);

CFATTACH_DECL(rlc, sizeof(struct rlc_softc),
    rlcmatch, rlcattach, NULL, NULL);

CFATTACH_DECL(rl, sizeof(struct rl_softc),
    rlmatch, rlattach, NULL, NULL);

dev_type_open(rlopen);
dev_type_close(rlclose);
dev_type_read(rlread);
dev_type_write(rlwrite);
dev_type_ioctl(rlioctl);
dev_type_strategy(rlstrategy);
dev_type_dump(rldump);
dev_type_size(rlsize);

const struct bdevsw rl_bdevsw = {
	rlopen, rlclose, rlstrategy, rlioctl, rldump, rlsize, D_DISK
};

const struct cdevsw rl_cdevsw = {
	rlopen, rlclose, rlread, rlwrite, rlioctl,
	nostop, notty, nopoll, nommap, nokqfilter, D_DISK
};

#define	MAXRLXFER (RL_BPS * RL_SPT)

#define	RL_WREG(reg, val) \
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, (reg), (val))
#define RL_RREG(reg) \
	bus_space_read_2(sc->sc_iot, sc->sc_ioh, (reg))

static char *rlstates[] = {
	"drive not loaded",
	"drive spinning up",
	"drive brushes out",
	"drive loading heads",
	"drive seeking",
	"drive ready",
	"drive unloading heads",
	"drive spun down",
};

static struct dkdriver rldkdriver = {
	rlstrategy, minphys
};

static char *
rlstate(struct rlc_softc *sc, int unit)
{
	int i = 0;

	do {
		RL_WREG(RL_DA, RLDA_GS);
		RL_WREG(RL_CS, RLCS_GS|(unit << RLCS_USHFT));
		waitcrdy(sc);
	} while (((RL_RREG(RL_CS) & RLCS_ERR) != 0) && i++ < 10);
	if (i == 10)
		return NULL;
	i = RL_RREG(RL_MP) & RLMP_STATUS;
	return rlstates[i];
}

void
waitcrdy(struct rlc_softc *sc)
{
	int i;

	for (i = 0; i < 1000; i++) {
		DELAY(10000);
		if (RL_RREG(RL_CS) & RLCS_CRDY)
			return;
	}
	printf("%s: never got ready\n", sc->sc_dev.dv_xname); /* ?panic? */
}

int
rlcprint(void *aux, const char *name)
{
	struct rlc_attach_args *ra = aux;

	if (name)
		aprint_normal("RL0%d at %s",
		    ra->type & RLMP_DT ? '2' : '1', name);
	aprint_normal(" drive %d", ra->hwid);
	return UNCONF;
}

/*
 * Force the controller to interrupt.
 */
int
rlcmatch(struct device *parent, struct cfdata *cf, void *aux)
{
	struct uba_attach_args *ua = aux;
	struct rlc_softc ssc, *sc = &ssc;
	int i;

	sc->sc_iot = ua->ua_iot;
	sc->sc_ioh = ua->ua_ioh;
	/* Force interrupt by issuing a "Get Status" command */
	RL_WREG(RL_DA, RLDA_GS);
	RL_WREG(RL_CS, RLCS_GS|RLCS_IE);

	for (i = 0; i < 100; i++) {
		DELAY(100000);
		if (RL_RREG(RL_CS) & RLCS_CRDY)
			return 1;
	}
	return 0;
}

void
rlcattach(struct device *parent, struct device *self, void *aux)
{
	struct rlc_softc *sc = (struct rlc_softc *)self;
	struct uba_attach_args *ua = aux;
	struct rlc_attach_args ra;
	int i, error;

	sc->sc_iot = ua->ua_iot;
	sc->sc_ioh = ua->ua_ioh;
	sc->sc_dmat = ua->ua_dmat;
	uba_intr_establish(ua->ua_icookie, ua->ua_cvec,
		rlcintr, sc, &sc->sc_intrcnt);
	evcnt_attach_dynamic(&sc->sc_intrcnt, EVCNT_TYPE_INTR, ua->ua_evcnt,
		sc->sc_dev.dv_xname, "intr");
	uba_reset_establish(rlcreset, self);

	printf("\n");

	/*
	 * The RL11 can only have one transfer going at a time,
	 * and max transfer size is one track, so only one dmamap
	 * is needed.
	 */
	error = bus_dmamap_create(sc->sc_dmat, MAXRLXFER, 1, MAXRLXFER, 0,
	    BUS_DMA_ALLOCNOW, &sc->sc_dmam);
	if (error) {
		printf(": Failed to allocate DMA map, error %d\n", error);
		return;
	}
	bufq_alloc(&sc->sc_q, BUFQ_DISKSORT|BUFQ_SORT_CYLINDER);
	for (i = 0; i < RL_MAXDPC; i++) {
		waitcrdy(sc);
		RL_WREG(RL_DA, RLDA_GS|RLDA_RST);
		RL_WREG(RL_CS, RLCS_GS|(i << RLCS_USHFT));
		waitcrdy(sc);
		ra.type = RL_RREG(RL_MP);
		ra.hwid = i;
		if ((RL_RREG(RL_CS) & RLCS_ERR) == 0)
			config_found(&sc->sc_dev, &ra, rlcprint);
	}
}

int
rlmatch(struct device *parent, struct cfdata *cf, void *aux)
{
	struct rlc_attach_args *ra = aux;

	if (cf->cf_loc[RLCCF_DRIVE] != RLCCF_DRIVE_DEFAULT &&
	    cf->cf_loc[RLCCF_DRIVE] != ra->hwid)
		return 0;
	return 1;
}

void
rlattach(struct device *parent, struct device *self, void *aux)
{
	struct rl_softc *rc = (struct rl_softc *)self;
	struct rlc_attach_args *ra = aux;
	struct disklabel *dl;

	rc->rc_hwid = ra->hwid;
	rc->rc_disk.dk_name = rc->rc_dev.dv_xname;
	rc->rc_disk.dk_driver = &rldkdriver;
	disk_attach(&rc->rc_disk);
	dl = rc->rc_disk.dk_label;
	dl->d_npartitions = 3;
	strcpy(dl->d_typename, "RL01");
	if (ra->type & RLMP_DT)
		dl->d_typename[3] = '2';
	dl->d_secsize = DEV_BSIZE; /* XXX - wrong, but OK for now */
	dl->d_nsectors = RL_SPT/2;
	dl->d_ntracks = RL_SPD;
	dl->d_ncylinders = ra->type & RLMP_DT ? RL_TPS02 : RL_TPS01;
	dl->d_secpercyl = dl->d_nsectors * dl->d_ntracks;
	dl->d_secperunit = dl->d_ncylinders * dl->d_secpercyl;
	dl->d_partitions[0].p_size = dl->d_partitions[2].p_size =
	    dl->d_secperunit;
	dl->d_partitions[0].p_offset = dl->d_partitions[2].p_offset = 0;
	dl->d_interleave = dl->d_headswitch = 1;
	dl->d_bbsize = BBSIZE;
	dl->d_sbsize = SBLOCKSIZE;
	dl->d_rpm = 2400;
	dl->d_type = DTYPE_DEC;
	printf(": %s, %s\n", dl->d_typename,
	    rlstate((struct rlc_softc *)parent, ra->hwid));

	/*
	 * XXX We should try to discovery wedges here, but
	 * XXX that would mean loading up the pack and being
	 * XXX able to do I/O.  Should use config_defer() here.
	 */
}

int
rlopen(dev_t dev, int flag, int fmt, struct proc *p)
{
	int error, part, unit, mask;
	struct disklabel *dl;
	struct rlc_softc *sc;
	struct rl_softc *rc;
	const char *msg;

	/*
	 * Make sure this is a reasonable open request.
	 */
	unit = DISKUNIT(dev);
	if (unit >= rl_cd.cd_ndevs)
		return ENXIO;
	rc = rl_cd.cd_devs[unit];
	if (rc == 0)
		return ENXIO;

	part = DISKPART(dev);

	if ((error = lockmgr(&rc->rc_disk.dk_openlock, LK_EXCLUSIVE,
			     NULL)) != 0)
		return (error);

	/*
	 * If there are wedges, and this is not RAW_PART, then we
	 * need to fail.
	 */
	if (rc->rc_disk.dk_nwedges != 0 && part != RAW_PART) {
		error = EBUSY;
		goto bad1;
	}

	sc = (struct rlc_softc *)rc->rc_dev.dv_parent;
	/* Check that the disk actually is useable */
	msg = rlstate(sc, rc->rc_hwid);
	if (msg == NULL || msg == rlstates[RLMP_UNLOAD] ||
	    msg == rlstates[RLMP_SPUNDOWN]) {
		error = ENXIO;
		goto bad1;
	}
	/*
	 * If this is the first open; read in where on the disk we are.
	 */
	dl = rc->rc_disk.dk_label;
	if (rc->rc_state == DK_CLOSED) {
		u_int16_t mp;
		int maj;
		RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT));
		waitcrdy(sc);
		mp = RL_RREG(RL_MP);
		rc->rc_head = ((mp & RLMP_HS) == RLMP_HS);
		rc->rc_cyl = (mp >> 7) & 0777;
		rc->rc_state = DK_OPEN;
		/* Get disk label */
		printf("%s: ", rc->rc_dev.dv_xname);
		maj = cdevsw_lookup_major(&rl_cdevsw);
		if ((msg = readdisklabel(MAKEDISKDEV(maj,
		    rc->rc_dev.dv_unit, RAW_PART), rlstrategy, dl, NULL)))
			printf("%s: ", msg);
		printf("size %d sectors\n", dl->d_secperunit);
	}
	if (part >= dl->d_npartitions) {
		error = ENXIO;
		goto bad1;
	}

	mask = 1 << part;
	switch (fmt) {
	case S_IFCHR:
		rc->rc_disk.dk_copenmask |= mask;
		break;
	case S_IFBLK:
		rc->rc_disk.dk_bopenmask |= mask;
		break;
	}
	rc->rc_disk.dk_openmask |= mask;
	(void) lockmgr(&rc->rc_disk.dk_openlock, LK_RELEASE, NULL);
	return 0;

 bad1:
	(void) lockmgr(&rc->rc_disk.dk_openlock, LK_RELEASE, NULL);
	return (error);
}

int
rlclose(dev_t dev, int flag, int fmt, struct proc *p)
{
	int error, unit = DISKUNIT(dev);
	struct rl_softc *rc = rl_cd.cd_devs[unit];
	int mask = (1 << DISKPART(dev));

	if ((error = lockmgr(&rc->rc_disk.dk_openlock, LK_EXCLUSIVE,
			     NULL)) != 0)
		return (error);

	switch (fmt) {
	case S_IFCHR:
		rc->rc_disk.dk_copenmask &= ~mask;
		break;
	case S_IFBLK:
		rc->rc_disk.dk_bopenmask &= ~mask;
		break;
	}
	rc->rc_disk.dk_openmask =
	    rc->rc_disk.dk_copenmask | rc->rc_disk.dk_bopenmask;

	if (rc->rc_disk.dk_openmask == 0)
		rc->rc_state = DK_CLOSED; /* May change pack */
	(void) lockmgr(&rc->rc_disk.dk_openlock, LK_RELEASE, NULL);
	return 0;
}

void
rlstrategy(struct buf *bp)
{
	struct disklabel *lp;
	struct rlc_softc *sc;
	struct rl_softc *rc;
	int unit, s, err;
	/*
	 * Make sure this is a reasonable drive to use.
	 */
	unit = DISKUNIT(bp->b_dev);
	if (unit > rl_cd.cd_ndevs || (rc = rl_cd.cd_devs[unit]) == NULL) {
		bp->b_error = ENXIO;
		bp->b_flags |= B_ERROR;
		goto done;
	}
	if (rc->rc_state != DK_OPEN) /* How did we end up here at all? */
		panic("rlstrategy: state impossible");

	lp = rc->rc_disk.dk_label;
	if ((err = bounds_check_with_label(&rc->rc_disk, bp, 1)) <= 0)
		goto done;

	if (bp->b_bcount == 0)
		goto done;

	bp->b_rawblkno =
	    bp->b_blkno + lp->d_partitions[DISKPART(bp->b_dev)].p_offset;
	bp->b_cylinder = bp->b_rawblkno / lp->d_secpercyl;
	sc = (struct rlc_softc *)rc->rc_dev.dv_parent;

	s = splbio();
	BUFQ_PUT(&sc->sc_q, bp);
	rlcstart(sc, 0);
	splx(s);
	return;

done:	biodone(bp);
}

int
rlioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
{
	struct rl_softc *rc = rl_cd.cd_devs[DISKUNIT(dev)];
	struct disklabel *lp = rc->rc_disk.dk_label;
	int err = 0;
#ifdef __HAVE_OLD_DISKLABEL
	struct disklabel newlabel;
#endif

	switch (cmd) {
	case DIOCGDINFO:
		bcopy(lp, addr, sizeof (struct disklabel));
		break;

#ifdef __HAVE_OLD_DISKLABEL
	case ODIOCGDINFO:
		newlabel = *lp;
		if (newlabel.d_npartitions > OLDMAXPARTITIONS)
			return ENOTTY;
		bcopy(&newlabel, addr, sizeof (struct olddisklabel));
		break;
#endif

	case DIOCGPART:
		((struct partinfo *)addr)->disklab = lp;
		((struct partinfo *)addr)->part =
		    &lp->d_partitions[DISKPART(dev)];
		break;

	case DIOCSDINFO:
	case DIOCWDINFO:
#ifdef __HAVE_OLD_DISKLABEL
	case ODIOCWDINFO:
	case ODIOCSDINFO:
#endif
	{
		struct disklabel *tp;

#ifdef __HAVE_OLD_DISKLABEL
		if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) {
			memset(&newlabel, 0, sizeof newlabel);
			memcpy(&newlabel, addr, sizeof (struct olddisklabel));
			tp = &newlabel;
		} else
#endif
		tp = (struct disklabel *)addr;

		if ((flag & FWRITE) == 0)
			err = EBADF;
		else {
			if ((err = lockmgr(&rc->rc_disk.dk_openlock,
					   LK_EXCLUSIVE, NULL)) != 0)
				break;
			err = ((
#ifdef __HAVE_OLD_DISKLABEL
			       cmd == ODIOCSDINFO ||
#endif
			       cmd == DIOCSDINFO) ?
			    setdisklabel(lp, tp, 0, 0) :
			    writedisklabel(dev, rlstrategy, lp, 0));
			(void) lockmgr(&rc->rc_disk.dk_openlock,
				       LK_RELEASE, NULL);
		}
		break;
	}

	case DIOCWLABEL:
		if ((flag & FWRITE) == 0)
			err = EBADF;
		break;

	case DIOCAWEDGE:
	    {
		struct dkwedge_info *dkw = (void *) addr;

		if ((flag & FWRITE) == 0)
			return (EBADF);

		/* If the ioctl happens here, the parent is us. */
		strcpy(dkw->dkw_parent, rc->rc_dev.dv_xname);
		return (dkwedge_add(dkw));
	    }

	case DIOCDWEDGE:
	    {
		struct dkwedge_info *dkw = (void *) addr;

		if ((flag & FWRITE) == 0)
			return (EBADF);

		/* If the ioctl happens here, the parent is us. */
		strcpy(dkw->dkw_parent, rc->rc_dev.dv_xname);
		return (dkwedge_del(dkw));
	    }

	case DIOCLWEDGES:
	    {
		struct dkwedge_list *dkwl = (void *) addr;

		return (dkwedge_list(&rc->rc_disk, dkwl, p));
	    }

	default:
		err = ENOTTY;
	}
	return err;
}

int
rlsize(dev_t dev)
{
	struct disklabel *dl;
	struct rl_softc *rc;
	int size, unit = DISKUNIT(dev);

	if ((unit >= rl_cd.cd_ndevs) || ((rc = rl_cd.cd_devs[unit]) == 0))
		return -1;
	dl = rc->rc_disk.dk_label;
	size = dl->d_partitions[DISKPART(dev)].p_size *
	    (dl->d_secsize / DEV_BSIZE);
	return size;
}

int
rldump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
{
	/* Not likely... */
	return 0;
}

int
rlread(dev_t dev, struct uio *uio, int ioflag)
{
	return (physio(rlstrategy, NULL, dev, B_READ, minphys, uio));
}

int
rlwrite(dev_t dev, struct uio *uio, int ioflag)
{
	return (physio(rlstrategy, NULL, dev, B_WRITE, minphys, uio));
}

static char *rlerr[] = {
	"no",
	"operation incomplete",
	"read data CRC",
	"header CRC",
	"data late",
	"header not found",
	"",
	"",
	"non-existent memory",
	"memory parity error",
	"",
	"",
	"",
	"",
	"",
	"",
};

void
rlcintr(void *arg)
{
	struct rlc_softc *sc = arg;
	struct buf *bp;
	u_int16_t cs;

	bp = sc->sc_active;
	if (bp == 0) {
		printf("%s: strange interrupt\n", sc->sc_dev.dv_xname);
		return;
	}
	bus_dmamap_unload(sc->sc_dmat, sc->sc_dmam);
	sc->sc_active = 0;
	cs = RL_RREG(RL_CS);
	if (cs & RLCS_ERR) {
		int error = (cs & RLCS_ERRMSK) >> 10;

		printf("%s: %s\n", sc->sc_dev.dv_xname, rlerr[error]);
		bp->b_flags |= B_ERROR;
		bp->b_error = EIO;
		bp->b_resid = bp->b_bcount;
		sc->sc_bytecnt = 0;
	}
	if (sc->sc_bytecnt == 0) /* Finished transfer */
		biodone(bp);
	rlcstart(sc, sc->sc_bytecnt ? bp : 0);
}

/*
 * Start routine. First position the disk to the given position,
 * then start reading/writing. An optimization would be to be able
 * to handle overlapping seeks between disks.
 */
void
rlcstart(struct rlc_softc *sc, struct buf *ob)
{
	struct disklabel *lp;
	struct rl_softc *rc;
	struct buf *bp;
	int bn, cn, sn, tn, blks, err;

	if (sc->sc_active)
		return;	/* Already doing something */

	if (ob == 0) {
		bp = BUFQ_GET(&sc->sc_q);
		if (bp == NULL)
			return;	/* Nothing to do */
		sc->sc_bufaddr = bp->b_data;
		sc->sc_diskblk = bp->b_rawblkno;
		sc->sc_bytecnt = bp->b_bcount;
		bp->b_resid = 0;
	} else
		bp = ob;
	sc->sc_active = bp;

	rc = rl_cd.cd_devs[DISKUNIT(bp->b_dev)];
	bn = sc->sc_diskblk;
	lp = rc->rc_disk.dk_label;
	if (bn) {
		cn = bn / lp->d_secpercyl;
		sn = bn % lp->d_secpercyl;
		tn = sn / lp->d_nsectors;
		sn = sn % lp->d_nsectors;
	} else
		cn = sn = tn = 0;

	/*
	 * Check if we have to position disk first.
	 */
	if (rc->rc_cyl != cn || rc->rc_head != tn) {
		u_int16_t da = RLDA_SEEK;
		if (cn > rc->rc_cyl)
			da |= ((cn - rc->rc_cyl) << RLDA_CYLSHFT) | RLDA_DIR;
		else
			da |= ((rc->rc_cyl - cn) << RLDA_CYLSHFT);
		if (tn)
			da |= RLDA_HSSEEK;
		waitcrdy(sc);
		RL_WREG(RL_DA, da);
		RL_WREG(RL_CS, RLCS_SEEK | (rc->rc_hwid << RLCS_USHFT));
		waitcrdy(sc);
		rc->rc_cyl = cn;
		rc->rc_head = tn;
	}
	RL_WREG(RL_DA, (cn << RLDA_CYLSHFT) | (tn ? RLDA_HSRW : 0) | (sn << 1));
	blks = sc->sc_bytecnt/DEV_BSIZE;

	if (sn + blks > RL_SPT/2)
		blks = RL_SPT/2 - sn;
	RL_WREG(RL_MP, -(blks*DEV_BSIZE)/2);
	err = bus_dmamap_load(sc->sc_dmat, sc->sc_dmam, sc->sc_bufaddr,
	    (blks*DEV_BSIZE), (bp->b_flags & B_PHYS ? bp->b_proc : 0),
	    BUS_DMA_NOWAIT);
	if (err)
		panic("%s: bus_dmamap_load failed: %d",
		    sc->sc_dev.dv_xname, err);
	RL_WREG(RL_BA, (sc->sc_dmam->dm_segs[0].ds_addr & 0xffff));

	/* Count up vars */
	sc->sc_bufaddr += (blks*DEV_BSIZE);
	sc->sc_diskblk += blks;
	sc->sc_bytecnt -= (blks*DEV_BSIZE);

	if (bp->b_flags & B_READ)
		RL_WREG(RL_CS, RLCS_IE|RLCS_RD|(rc->rc_hwid << RLCS_USHFT));
	else
		RL_WREG(RL_CS, RLCS_IE|RLCS_WD|(rc->rc_hwid << RLCS_USHFT));
}

/*
 * Called once per controller when an ubareset occurs.
 * Retracts all disks and restarts active transfers.
 */
void
rlcreset(struct device *dev)
{
	struct rlc_softc *sc = (struct rlc_softc *)dev;
	struct rl_softc *rc;
	int i;
	u_int16_t mp;

	for (i = 0; i < rl_cd.cd_ndevs; i++) {
		if ((rc = rl_cd.cd_devs[i]) == NULL)
			continue;
		if (rc->rc_state != DK_OPEN)
			continue;

		printf(" %s", rc->rc_dev.dv_xname);
		RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT));
		waitcrdy(sc);
		mp = RL_RREG(RL_MP);
		rc->rc_head = ((mp & RLMP_HS) == RLMP_HS);
		rc->rc_cyl = (mp >> 7) & 0777;
	}
	if (sc->sc_active == 0)
		return;

	BUFQ_PUT(&sc->sc_q, sc->sc_active);
	sc->sc_active = 0;
	rlcstart(sc, 0);
}
