OpenSolaris_b135/uts/common/io/fdc.c

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

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * Floppy Disk Controller Driver
 *
 *   for the standard PC architecture using the Intel 8272A fdc.
 *   Note that motor control and drive select use a latch external
 *   to the fdc.
 *
 *   This driver is EISA capable, and uses DMA buffer chaining if available.
 *   If this driver is attached to the ISA bus nexus (or if the EISA bus driver
 *   does not support DMA buffer chaining), then the bus driver must ensure
 *   that dma mapping (breakup) and dma engine requests are properly degraded.
 */

/*
 * hack for bugid 1160621:
 * workaround compiler optimization bug by turning on DEBUG
 */
#ifndef DEBUG
#define	DEBUG	1
#endif

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/open.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/note.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/stat.h>

#include <sys/autoconf.h>
#include <sys/dkio.h>
#include <sys/vtoc.h>
#include <sys/kstat.h>

#include <sys/fdio.h>
#include <sys/fdc.h>
#include <sys/i8272A.h>
#include <sys/fd_debug.h>
#include <sys/promif.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

/*
 * bss (uninitialized data)
 */
static void *fdc_state_head;		/* opaque handle top of state structs */
static ddi_dma_attr_t fdc_dma_attr;
static ddi_device_acc_attr_t fdc_accattr = {DDI_DEVICE_ATTR_V0,
	DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC};

/*
 * Local static data
 */
#define	OURUN_TRIES	12
static uchar_t rwretry = 4;
static uchar_t skretry = 3;
static uchar_t configurecmd[4] = {FO_CNFG, 0, 0x0F, 0};
static uchar_t recalcmd[2] = {FO_RECAL, 0};
static uchar_t senseintcmd = FO_SINT;

/*
 * error handling
 *
 * for debugging, set rwretry and skretry = 1
 *		set fcerrlevel to 1
 *		set fcerrmask  to 224  or 644
 *
 * after debug, set rwretry to 4, skretry to 3, and fcerrlevel to 5
 * set fcerrmask to FDEM_ALL
 * or remove the define DEBUG
 */
static uint_t fcerrmask = FDEM_ALL;
static int fcerrlevel = 6;

#define	KIOIP	KSTAT_INTR_PTR(fcp->c_intrstat)


static xlate_tbl_t drate_mfm[] = {
	{  250, 2},
	{  300, 1},
	{  417, 0},
	{  500, 0},
	{ 1000, 3},
	{    0, 0}
};

static xlate_tbl_t sector_size[] = {
	{  256, 1},
	{  512, 2},
	{ 1024, 3},
	{    0, 2}
};

static xlate_tbl_t motor_onbits[] = {
	{  0, 0x10},
	{  1, 0x20},
	{  2, 0x40},
	{  3, 0x80},
	{  0, 0x80}
};

static xlate_tbl_t step_rate[] = {
	{  10, 0xF0},		/* for 500K data rate */
	{  20, 0xE0},
	{  30, 0xD0},
	{  40, 0xC0},
	{  50, 0xB0},
	{  60, 0xA0},
	{  70, 0x90},
	{  80, 0x80},
	{  90, 0x70},
	{ 100, 0x60},
	{ 110, 0x50},
	{ 120, 0x40},
	{ 130, 0x30},
	{ 140, 0x20},
	{ 150, 0x10},
	{ 160, 0x00},
	{   0, 0x00}
};

#ifdef notdef
static xlate_tbl_t head_unld[] = {
	{  16, 0x1},		/* for 500K data rate */
	{  32, 0x2},
	{  48, 0x3},
	{  64, 0x4},
	{  80, 0x5},
	{  96, 0x6},
	{ 112, 0x7},
	{ 128, 0x8},
	{ 144, 0x9},
	{ 160, 0xA},
	{ 176, 0xB},
	{ 192, 0xC},
	{ 208, 0xD},
	{ 224, 0xE},
	{ 240, 0xF},
	{ 256, 0x0},
	{   0, 0x0}
};
#endif

static struct fdcmdinfo {
	char *cmdname;		/* command name */
	uchar_t ncmdbytes;	/* number of bytes of command */
	uchar_t nrsltbytes;	/* number of bytes in result */
	uchar_t cmdtype;		/* characteristics */
} fdcmds[] = {
	"", 0, 0, 0,			/* - */
	"", 0, 0, 0,			/* - */
	"read_track", 9, 7, 1,		/* 2 */
	"specify", 3, 0, 3,		/* 3 */
	"sense_drv_status", 2, 1, 3,	/* 4 */
	"write", 9, 7, 1,		/* 5 */
	"read", 9, 7, 1,		/* 6 */
	"recalibrate", 2, 0, 2,		/* 7 */
	"sense_int_status", 1, 2, 3,	/* 8 */
	"write_del", 9, 7, 1,		/* 9 */
	"read_id", 2, 7, 2,		/* A */
	"", 0, 0, 0,			/* - */
	"read_del", 9, 7, 1,		/* C */
	"format_track", 10, 7, 1,	/* D */
	"dump_reg", 1, 10, 4,		/* E */
	"seek", 3, 0, 2,		/* F */
	"version", 1, 1, 3,		/* 10 */
	"", 0, 0, 0,			/* - */
	"perp_mode", 2, 0, 3,		/* 12 */
	"configure", 4, 0, 4,		/* 13 */
	/* relative seek */
};


static int
fdc_bus_ctl(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *);
static int get_ioaddr(dev_info_t *dip, int *ioaddr);
static int get_unit(dev_info_t *dip, int *cntrl_num);

struct bus_ops fdc_bus_ops = {
	BUSO_REV,
	nullbusmap,
	0,	/* ddi_intrspec_t (*bus_get_intrspec)(); */
	0,	/* int 	(*bus_add_intrspec)(); */
	0,	/* void (*bus_remove_intrspec)(); */
	i_ddi_map_fault,
	ddi_dma_map,
	ddi_dma_allochdl,
	ddi_dma_freehdl,
	ddi_dma_bindhdl,
	ddi_dma_unbindhdl,
	ddi_dma_flush,
	ddi_dma_win,
	ddi_dma_mctl,
	fdc_bus_ctl,
	ddi_bus_prop_op,
};

static int fdc_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int fdc_probe(dev_info_t *);
static int fdc_attach(dev_info_t *, ddi_attach_cmd_t);
static int fdc_detach(dev_info_t *, ddi_detach_cmd_t);
static int fdc_quiesce(dev_info_t *);
static int fdc_enhance_probe(struct fdcntlr *fcp);

struct dev_ops	fdc_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	fdc_getinfo,		/* getinfo */
	nulldev,		/* identify */
	fdc_probe,		/* probe */
	fdc_attach,		/* attach */
	fdc_detach,		/* detach */
	nodev,			/* reset */
	(struct cb_ops *)0,	/* driver operations */
	&fdc_bus_ops,		/* bus operations */
	NULL,			/* power */
	fdc_quiesce,		/* quiesce */
};

/*
 * This is the loadable module wrapper.
 */
#include <sys/modctl.h>

extern struct mod_ops mod_driverops;

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module. This one is a driver */
	"Floppy Controller",	/* Name of the module. */
	&fdc_ops,		/* Driver ops vector */
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modldrv, NULL
};

int
_init(void)
{
	int retval;

	if ((retval = ddi_soft_state_init(&fdc_state_head,
	    sizeof (struct fdcntlr) + NFDUN * sizeof (struct fcu_obj), 0)) != 0)
		return (retval);

	if ((retval = mod_install(&modlinkage)) != 0)
		ddi_soft_state_fini(&fdc_state_head);
	return (retval);
}

int
_fini(void)
{
	int retval;

	if ((retval = mod_remove(&modlinkage)) != 0)
		return (retval);
	ddi_soft_state_fini(&fdc_state_head);
	return (retval);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


int fdc_abort(struct fcu_obj *);
int fdc_dkinfo(struct fcu_obj *, struct dk_cinfo *);
int fdc_select(struct fcu_obj *, int, int);
int fdgetchng(struct fcu_obj *, int);
int fdresetchng(struct fcu_obj *, int);
int fdrecalseek(struct fcu_obj *, int, int, int);
int fdrw(struct fcu_obj *, int, int, int, int, int, caddr_t, uint_t);
int fdtrkformat(struct fcu_obj *, int, int, int, int);
int fdrawioctl(struct fcu_obj *, int, caddr_t);

static struct fcobjops fdc_iops = {
		fdc_abort,	/* controller abort */
		fdc_dkinfo,	/* get disk controller info */

		fdc_select,	/* select / deselect unit */
		fdgetchng,	/* get media change */
		fdresetchng,	/* reset media change */
		fdrecalseek,	/* recal / seek */
		NULL,		/* read /write request (UNUSED) */
		fdrw,		/* read /write sector */
		fdtrkformat,	/* format track */
		fdrawioctl	/* raw ioctl */
};


/*
 * Function prototypes
 */
void encode(xlate_tbl_t *tablep, int val, uchar_t *rcode);
int decode(xlate_tbl_t *, int, int *);
static int fdc_propinit1(struct fdcntlr *, int);
static void fdc_propinit2(struct fdcntlr *);
void fdcquiesce(struct fdcntlr *);
int fdcsense_chng(struct fdcntlr *, int);
int fdcsense_drv(struct fdcntlr *, int);
int fdcsense_int(struct fdcntlr *, int *, int *);
int fdcspecify(struct fdcntlr *, int, int, int);
int fdcspdchange(struct fdcntlr *, struct fcu_obj *, int);
static int fdc_exec(struct fdcntlr *, int, int);
int fdcheckdisk(struct fdcntlr *, int);
static uint_t fdc_intr(caddr_t arg);
static void fdwatch(void *arg);
static void fdmotort(void *arg);
static int fdrecover(struct fdcntlr *);
static int fdc_motorsm(struct fcu_obj *, int, int);
static int fdc_statemach(struct fdcntlr *);
int fdc_docmd(struct fdcntlr *, uchar_t *, uchar_t);
int fdc_result(struct fdcntlr *, uchar_t *, uchar_t);


static int
fdc_bus_ctl(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop,
    void *arg, void *result)
{
	struct 	fdcntlr *fcp;
	struct	fcu_obj *fjp;

	_NOTE(ARGUNUSED(result));

	FCERRPRINT(FDEP_L0, FDEM_ATTA,
	    (CE_CONT, "fdc_bus_ctl: cmd= %x\n", ctlop));

	if ((fcp = ddi_get_driver_private(dip)) == NULL)
		return (DDI_FAILURE);

	switch (ctlop) {

	case DDI_CTLOPS_REPORTDEV:
		cmn_err(CE_CONT, "?%s%d at %s%d\n",
		    ddi_get_name(rdip), ddi_get_instance(rdip),
		    ddi_get_name(dip), ddi_get_instance(dip));
		FCERRPRINT(FDEP_L3, FDEM_ATTA,
		    (CE_WARN, "fdc_bus_ctl: report %s%d at %s%d",
		    ddi_get_name(rdip), ddi_get_instance(rdip),
		    ddi_get_name(dip), ddi_get_instance(dip)));
		return (DDI_SUCCESS);

	case DDI_CTLOPS_INITCHILD:
	{
		dev_info_t *udip = (dev_info_t *)arg;
		int cntlr;
		int len;
		int unit;
		char name[MAXNAMELEN];

		FCERRPRINT(FDEP_L3, FDEM_ATTA,
		    (CE_WARN, "fdc_bus_ctl: init child 0x%p", (void*)udip));
		cntlr = fcp->c_number;

		len = sizeof (unit);
		if (ddi_prop_op(DDI_DEV_T_ANY, udip, PROP_LEN_AND_VAL_BUF,
		    DDI_PROP_DONTPASS, "unit", (caddr_t)&unit, &len)
		    != DDI_PROP_SUCCESS ||
		    cntlr != FDCTLR(unit) ||
		    (fcp->c_unit[FDUNIT(unit)])->fj_dip)
			return (DDI_NOT_WELL_FORMED);

		(void) sprintf(name, "%d,%d", cntlr, FDUNIT(unit));
		ddi_set_name_addr(udip, name);

		fjp = fcp->c_unit[FDUNIT(unit)];
		fjp->fj_unit = unit;
		fjp->fj_dip = udip;
		fjp->fj_ops = &fdc_iops;
		fjp->fj_fdc = fcp;
		fjp->fj_iblock = &fcp->c_iblock;

		ddi_set_driver_private(udip, fjp);

		return (DDI_SUCCESS);
	}
	case DDI_CTLOPS_UNINITCHILD:
	{
		dev_info_t *udip = (dev_info_t *)arg;

		FCERRPRINT(FDEP_L3, FDEM_ATTA,
		    (CE_WARN, "fdc_bus_ctl: uninit child 0x%p", (void *)udip));
		fjp = ddi_get_driver_private(udip);
		ddi_set_driver_private(udip, NULL);
		fjp->fj_dip = NULL;
		ddi_set_name_addr(udip, NULL);
		return (DDI_SUCCESS);
	}
	default:
		return (DDI_FAILURE);
	}
}

static int
fdc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
	struct fdcntlr *fcp;
	int rval;

	_NOTE(ARGUNUSED(dip));

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (fcp = ddi_get_soft_state(fdc_state_head, (dev_t)arg)) {
			*result = fcp->c_dip;
			rval = DDI_SUCCESS;
			break;
		} else {
			rval = DDI_FAILURE;
			break;
		}
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(uintptr_t)getminor((dev_t)arg);
		rval = DDI_SUCCESS;
		break;
	default:
		rval = DDI_FAILURE;
	}
	return (rval);
}

static int
fdc_probe(dev_info_t *dip)
{
	int	debug[2];
	int ioaddr;
	int	len;
	uchar_t	stat;

	len = sizeof (debug);
	if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
	    DDI_PROP_DONTPASS, "debug", (caddr_t)debug, &len) ==
	    DDI_PROP_SUCCESS) {
		fcerrlevel = debug[0];
		fcerrmask = (uint_t)debug[1];
	}

	FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_probe: dip %p",
	    (void*)dip));

	if (get_ioaddr(dip, &ioaddr) != DDI_SUCCESS)
		return (DDI_PROBE_FAILURE);

	stat = inb(ioaddr + FCR_MSR);
	if ((stat & (MS_RQM | MS_DIO | MS_CB)) != MS_RQM &&
	    (stat & ~MS_DIO) != MS_CB)
		return (DDI_PROBE_FAILURE);

	return (DDI_PROBE_SUCCESS);
}

static int
fdc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	struct fdcntlr *fcp;
	struct fcu_obj *fjp;
	int cntlr_num, ctlr, unit;
	int intr_set = 0;
	int len;
	char name[MAXNAMELEN];

	FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_attach: dip %p",
	    (void*)dip));

	switch (cmd) {
	case DDI_ATTACH:
		if (ddi_getprop
		    (DDI_DEV_T_ANY, dip, 0, "ignore-hardware-nodes", 0)) {
			len = sizeof (cntlr_num);
			if (ddi_prop_op(DDI_DEV_T_ANY, dip,
			    PROP_LEN_AND_VAL_BUF, DDI_PROP_DONTPASS, "unit",
			    (caddr_t)&cntlr_num, &len) != DDI_PROP_SUCCESS) {
				FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN,
				    "fdc_attach failed: dip %p", (void*)dip));
				return (DDI_FAILURE);
			}
		} else {
			if (get_unit(dip, &cntlr_num) != DDI_SUCCESS)
				return (DDI_FAILURE);
		}

		ctlr = ddi_get_instance(dip);
		if (ddi_soft_state_zalloc(fdc_state_head, ctlr) != 0)
			return (DDI_FAILURE);
		fcp = ddi_get_soft_state(fdc_state_head, ctlr);

		for (unit = 0, fjp = (struct fcu_obj *)(fcp+1);
		    unit < NFDUN; unit++) {
			fcp->c_unit[unit] = fjp++;
		}
		fcp->c_dip = dip;

		if (fdc_propinit1(fcp, cntlr_num) != DDI_SUCCESS)
			goto no_attach;

		/* get iblock cookie to initialize mutex used in the ISR */
		if (ddi_get_iblock_cookie(dip, (uint_t)0, &fcp->c_iblock) !=
		    DDI_SUCCESS) {
			cmn_err(CE_WARN,
			    "fdc_attach: cannot get iblock cookie");
			goto no_attach;
		}
		mutex_init(&fcp->c_lock, NULL, MUTEX_DRIVER, fcp->c_iblock);
		intr_set = 1;

		/* setup interrupt handler */
		if (ddi_add_intr(dip, (uint_t)0, NULL,
		    (ddi_idevice_cookie_t *)0, fdc_intr, (caddr_t)fcp) !=
		    DDI_SUCCESS) {
			cmn_err(CE_WARN, "fdc: cannot add intr");
			goto no_attach;
		}
		intr_set++;

		/*
		 * acquire the DMA channel
		 * this assumes that the chnl is not shared; else allocate
		 * and free the chnl with each fdc request
		 */
		if (ddi_dmae_alloc(dip, fcp->c_dmachan, DDI_DMA_DONTWAIT, NULL)
		    != DDI_SUCCESS) {
			cmn_err(CE_WARN, "fdc: cannot acquire dma%d",
			    fcp->c_dmachan);
			goto no_attach;
		}
		(void) ddi_dmae_getattr(dip, &fdc_dma_attr);
		fdc_dma_attr.dma_attr_align = MMU_PAGESIZE;

		mutex_init(&fcp->c_dorlock, NULL, MUTEX_DRIVER, fcp->c_iblock);
		cv_init(&fcp->c_iocv, NULL, CV_DRIVER, fcp->c_iblock);
		sema_init(&fcp->c_selsem, 1, NULL, SEMA_DRIVER, NULL);

		(void) sprintf(name, "fdc%d", ctlr);
		fcp->c_intrstat = kstat_create("fdc", ctlr, name,
		    "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT);
		if (fcp->c_intrstat) {
			kstat_install(fcp->c_intrstat);
		}

		ddi_set_driver_private(dip, fcp);

		/*
		 * reset the controller
		 */
		sema_p(&fcp->c_selsem);
		mutex_enter(&fcp->c_lock);
		fcp->c_csb.csb_xstate = FXS_RESET;
		fcp->c_flags |= FCFLG_WAITING;
		fdcquiesce(fcp);

		/* first test for mode == Model 30 */
		fcp->c_mode = (inb(fcp->c_regbase + FCR_SRB) & 0x1c) ?
		    FDCMODE_AT : FDCMODE_30;

		while (fcp->c_flags & FCFLG_WAITING) {
			cv_wait(&fcp->c_iocv, &fcp->c_lock);
		}
		mutex_exit(&fcp->c_lock);
		sema_v(&fcp->c_selsem);

		fdc_propinit2(fcp);

		ddi_report_dev(dip);
		return (DDI_SUCCESS);

	case DDI_RESUME:

		fcp = ddi_get_driver_private(dip);

		mutex_enter(&fcp->c_lock);
		fcp->c_suspended = B_FALSE;
		fcp->c_csb.csb_xstate = FXS_RESET;
		fcp->c_flags |= FCFLG_WAITING;
		fdcquiesce(fcp);

		while (fcp->c_flags & FCFLG_WAITING) {
			cv_wait(&fcp->c_iocv, &fcp->c_lock);
		}
		mutex_exit(&fcp->c_lock);

		/* should be good to go now */
		sema_v(&fcp->c_selsem);

		return (DDI_SUCCESS);
		/* break; */

	default:
		return (DDI_FAILURE);
	}

no_attach:
	if (intr_set) {
		if (intr_set > 1)
			ddi_remove_intr(dip, 0, fcp->c_iblock);
		mutex_destroy(&fcp->c_lock);
	}
	ddi_soft_state_free(fdc_state_head, cntlr_num);
	return (DDI_FAILURE);
}

static int
fdc_propinit1(struct fdcntlr *fcp, int cntlr)
{
	dev_info_t *dip;
	int len;
	int value;

	dip = fcp->c_dip;
	len = sizeof (value);

	if (get_ioaddr(dip, &value) != DDI_SUCCESS)
		return (DDI_FAILURE);

	fcp->c_regbase = (ushort_t)value;

	if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
	    DDI_PROP_DONTPASS, "dma-channels", (caddr_t)&value, &len)
	    != DDI_PROP_SUCCESS) {
			cmn_err(CE_WARN,
			    "fdc_attach: Error, could not find a dma channel");
			return (DDI_FAILURE);
	}
	fcp->c_dmachan = (ushort_t)value;
	fcp->c_number = cntlr;
	return (DDI_SUCCESS);
}

static void
fdc_propinit2(struct fdcntlr *fcp)
{
	dev_info_t *dip;
	int ccr;
	int len;
	int value;

	dip = fcp->c_dip;
	len = sizeof (value);

	if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
	    DDI_PROP_DONTPASS, "chip", (caddr_t)&value, &len)
	    == DDI_PROP_SUCCESS)
		fcp->c_chip = value;
	else {
		static uchar_t perpindcmd[2] = {FO_PERP, 0};
		static uchar_t versioncmd = FO_VRSN;
		uchar_t result;

		fcp->c_chip = i8272A;
		(void) fdc_docmd(fcp, &versioncmd, 1);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 * fdc_results retrieves the controller/drive status
		 */
		if (!fdc_result(fcp, &result, 1) && result == 0x90) {
			/*
			 * try a perpendicular_mode cmd to ensure
			 * that we really have an enhanced controller
			 */
			if (fdc_docmd(fcp, perpindcmd, 2) ||
			    fdc_docmd(fcp, configurecmd, 4))
				/*
				 * perpindicular_mode will be rejected by
				 * older controllers; make sure we don't hang.
				 */
				(void) fdc_result(fcp, &result, 1);
				/*
				 * Ignored return. If failed, warning was
				 * issued by fdc_result.
				 */
			else
				/* enhanced type controller */

				if ((fcp->c_chip = fdc_enhance_probe(fcp)) == 0)
					/* default enhanced cntlr */
					fcp->c_chip = i82077;
		}
		(void) ddi_prop_update_int(DDI_DEV_T_NONE, dip,
		    "chip", fcp->c_chip);
		/*
		 * Ignoring return value because, for passed arguments, only
		 * DDI_SUCCESS is returned.
		 */
	}
	if (fcp->c_chip >= i82077 && fcp->c_mode == FDCMODE_30 &&
	    (inb(fcp->c_regbase + FCR_DIR) & 0x70) == 0)
		for (ccr = 0; ccr <= (FCC_NOPREC | FCC_DRATE); ccr++) {
			/*
			 * run through all the combinations of NOPREC and
			 * datarate selection, and see if they show up in the
			 * Model 30 DIR
			 */
			outb(fcp->c_regbase + FCR_CCR, ccr);
			drv_usecwait(5);
			if ((inb(fcp->c_regbase + FCR_DIR) &
			    (FCC_NOPREC | FCC_DRATE)) != ccr) {
				fcp->c_mode = FDCMODE_AT;
				break;
			}
		}
	else
		fcp->c_mode = FDCMODE_AT;
	outb(fcp->c_regbase + FCR_CCR, 0);
}

static int
fdc_enhance_probe(struct fdcntlr *fcp)
{
	static uchar_t nsccmd = FO_NSC;
	uint_t	ddic;
	int	retcode = 0;
	uchar_t	result;
	uchar_t	save;

	/*
	 * Try to identify the enhanced floppy controller.
	 * This is required so that we can program the DENSEL output to
	 * control 3D mode (1.0 MB, 1.6 MB and 2.0 MB unformatted capacity,
	 * 720 KB, 1.2 MB, and 1.44 MB formatted capacity) 3.5" dual-speed
	 * floppy drives.  Refer to bugid 1195155.
	 */

	(void) fdc_docmd(fcp, &nsccmd, 1);
	/*
	 * Ignored return. If failed, warning was issued by fdc_docmd.
	 * fdc_results retrieves the controller/drive status
	 */
	if (!fdc_result(fcp, &result, 1) && result != S0_IVCMD) {
		/*
		 * only enhanced National Semi PC8477 core
		 * should respond to this command
		 */
		if ((result & 0xf0) == 0x70) {
			/* low 4 bits may change */
			fcp->c_flags |= FCFLG_3DMODE;
			retcode = PC87322;
		} else
			cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, National Semiconductor cntlr %x\n", result);
	} else {
		save = inb(fcp->c_regbase + FCR_SRA);

		do {
			/* probe for motherboard version of SMC cntlr */

			/* try to enable configuration mode */
			ddic = ddi_enter_critical();
			outb(fcp->c_regbase + FCR_SRA, FSA_ENA5);
			outb(fcp->c_regbase + FCR_SRA, FSA_ENA5);
			ddi_exit_critical(ddic);

			outb(fcp->c_regbase + FCR_SRA, 0x0F);
			if (inb(fcp->c_regbase + FCR_SRB) != 0x00)
				/* always expect 0 from config reg F */
				break;
			outb(fcp->c_regbase + FCR_SRA, 0x0D);
			if (inb(fcp->c_regbase + FCR_SRB) != 0x65)
				/* expect 0x65 from config reg D */
				break;
			outb(fcp->c_regbase + FCR_SRA, 0x0E);
			result = inb(fcp->c_regbase + FCR_SRB);
			if (result != 0x02) {
				/* expect revision level 2 from config reg E */
				cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, SMC cntlr revision %x\n", result);
				/* break;	*/
			}
			fcp->c_flags |= FCFLG_3DMODE;
			retcode = FDC37C665;
		} while (retcode == 0);
		outb(fcp->c_regbase + FCR_SRA, FSA_DISB);

		while (retcode == 0) {
			/* probe for adapter version of SMC cntlr */
			ddic = ddi_enter_critical();
			outb(fcp->c_regbase + FCR_SRA, FSA_ENA6);
			outb(fcp->c_regbase + FCR_SRA, FSA_ENA6);
			ddi_exit_critical(ddic);

			outb(fcp->c_regbase + FCR_SRA, 0x0F);
			if (inb(fcp->c_regbase + FCR_SRB) != 0x00)
				/* always expect 0 from config reg F */
				break;
			outb(fcp->c_regbase + FCR_SRA, 0x0D);
			if (inb(fcp->c_regbase + FCR_SRB) != 0x66)
				/* expect 0x66 from config reg D */
				break;
			outb(fcp->c_regbase + FCR_SRA, 0x0E);
			result = inb(fcp->c_regbase + FCR_SRB);
			if (result != 0x02) {
				/* expect revision level 2 from config reg E */
				cmn_err(CE_CONT,
"?fdc: unidentified, enhanced, SMC cntlr revision %x\n", result);
				/* break;	*/
			}
			fcp->c_flags |= FCFLG_3DMODE;
			retcode = FDC37C666;
		}
		outb(fcp->c_regbase + FCR_SRA, FSA_DISB);

		drv_usecwait(10);
		outb(fcp->c_regbase + FCR_SRA, save);
	}
	return (retcode);
}

static int
fdc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	struct fdcntlr *fcp;
	int unit;
	int rval = 0;

	FCERRPRINT(FDEP_L3, FDEM_ATTA, (CE_WARN, "fdc_detach: dip %p",
	    (void*)dip));

	fcp = ddi_get_driver_private(dip);

	switch (cmd) {
	case DDI_DETACH:
		for (unit = 0; unit < NFDUN; unit++)
			if ((fcp->c_unit[unit])->fj_dip) {
				rval = EBUSY;
				break;
			}
		kstat_delete(fcp->c_intrstat);
		fcp->c_intrstat = NULL;
		ddi_remove_intr(fcp->c_dip, 0, fcp->c_iblock);
		if (ddi_dmae_release(fcp->c_dip, fcp->c_dmachan) !=
		    DDI_SUCCESS)
			cmn_err(CE_WARN, "fdc_detach: dma release failed, "
			    "dip %p, dmachan %x",
			    (void*)fcp->c_dip, fcp->c_dmachan);
		ddi_prop_remove_all(fcp->c_dip);
		ddi_set_driver_private(fcp->c_dip, NULL);

		mutex_destroy(&fcp->c_lock);
		mutex_destroy(&fcp->c_dorlock);
		cv_destroy(&fcp->c_iocv);
		sema_destroy(&fcp->c_selsem);
		ddi_soft_state_free(fdc_state_head, ddi_get_instance(dip));
		break;

	case DDI_SUSPEND:
		/*
		 * For suspend, we just use the semaphore to
		 * keep any child devices from accessing any of our
		 * hardware routines, and then shutdown the hardware.
		 *
		 * On resume, we'll reinit the hardware and release the
		 * semaphore.
		 */
		sema_p(&fcp->c_selsem);

		if (ddi_dmae_disable(fcp->c_dip, fcp->c_dmachan) !=
		    DDI_SUCCESS) {
			cmn_err(CE_WARN, "fdc_suspend: dma disable failed, "
			    "dip %p, dmachan %x", (void *)fcp->c_dip,
			    fcp->c_dmachan);
			/* give it back on failure */
			sema_v(&fcp->c_selsem);
			return (DDI_FAILURE);
		}

		mutex_enter(&fcp->c_lock);
		fcp->c_suspended = B_TRUE;
		mutex_exit(&fcp->c_lock);

		rval = DDI_SUCCESS;
		break;

	default:
		rval = EINVAL;
		break;
	}
	return (rval);
}


int
fdc_abort(struct fcu_obj *fjp)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	int unit = fjp->fj_unit & 3;

	FCERRPRINT(FDEP_L3, FDEM_RESE, (CE_WARN, "fdc_abort"));
	if (fcp->c_curunit == unit) {
		mutex_enter(&fcp->c_lock);
		if (fcp->c_flags & FCFLG_WAITING) {
			/*
			 * this can cause data corruption !
			 */
			fdcquiesce(fcp);
			fcp->c_csb.csb_xstate = FXS_RESET;
			fcp->c_flags |= FCFLG_TIMEOUT;
			if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) !=
			    DDI_SUCCESS)
				cmn_err(CE_WARN,
				    "fdc_detach: dma release failed, "
				    "dip %p, dmachan %x",
				    (void*)fcp->c_dip, fcp->c_dmachan);
		}
		mutex_exit(&fcp->c_lock);
		drv_usecwait(500);
		return (DDI_SUCCESS);
	}
	return (DDI_FAILURE);
}

int
fdc_dkinfo(struct fcu_obj *fjp, struct dk_cinfo *dcp)
{
	struct fdcntlr *fcp = fjp->fj_fdc;

	(void) strncpy((char *)&dcp->dki_cname, ddi_get_name(fcp->c_dip),
	    DK_DEVLEN);
	dcp->dki_ctype = DKC_UNKNOWN; /* no code for generic PC/AT fdc */
	dcp->dki_flags = DKI_FMTTRK;
	dcp->dki_addr = fcp->c_regbase;
	dcp->dki_space = 0;
	dcp->dki_prio = fcp->c_intprio;
	dcp->dki_vec = fcp->c_intvec;
	(void) strncpy((char *)&dcp->dki_dname, ddi_driver_name(fjp->fj_dip),
	    DK_DEVLEN);
	dcp->dki_slave = fjp->fj_unit & 3;
	dcp->dki_maxtransfer = maxphys / DEV_BSIZE;
	return (DDI_SUCCESS);
}

/*
 * on=> non-zero = select, 0 = de-select
 */
int
fdc_select(struct fcu_obj *fjp, int funit, int on)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	int unit = funit & 3;

	if (on) {
		/* possess controller */
		sema_p(&fcp->c_selsem);
		FCERRPRINT(FDEP_L2, FDEM_DSEL,
		    (CE_NOTE, "fdc_select unit %d: on", funit));

		if (fcp->c_curunit != unit || !(fjp->fj_flags & FUNIT_CHAROK)) {
			fcp->c_curunit = unit;
			fjp->fj_flags |= FUNIT_CHAROK;
			if (fdcspecify(fcp,
			    fjp->fj_chars->fdc_transfer_rate,
			    fjp->fj_drive->fdd_steprate, 40))
				cmn_err(CE_WARN,
				    "fdc_select: controller setup rejected "
				    "fdcntrl %p transfer rate %x step rate %x"
				    " head load time 40", (void*)fcp,
				    fjp->fj_chars->fdc_transfer_rate,
				    fjp->fj_drive->fdd_steprate);
		}

		mutex_enter(&fcp->c_dorlock);

		/* make sure drive is not selected in case we change speed */
		fcp->c_digout = (fcp->c_digout & ~FD_DRSEL) |
		    (~unit & FD_DRSEL);
		outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

		(void) fdc_motorsm(fjp, FMI_STARTCMD,
		    fjp->fj_drive->fdd_motoron);
		/*
		 * Return value ignored - fdcmotort deals with failure.
		 */
		if (fdcspdchange(fcp, fjp, fjp->fj_attr->fda_rotatespd)) {
			/* 3D drive requires 500 ms for speed change */
			(void) fdc_motorsm(fjp, FMI_RSTARTCMD, 5);
			/*
			 * Return value ignored - fdcmotort deals with failure.
			 */
		}

		fcp->c_digout = (fcp->c_digout & ~FD_DRSEL) | (unit & FD_DRSEL);
		outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

		mutex_exit(&fcp->c_dorlock);
		fcp->c_csb.csb_drive = (uchar_t)unit;
	} else {
		FCERRPRINT(FDEP_L2, FDEM_DSEL,
		    (CE_NOTE, "fdc_select unit %d: off", funit));

		mutex_enter(&fcp->c_dorlock);

		fcp->c_digout |= FD_DRSEL;
		outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
		(void) fdc_motorsm(fjp, FMI_IDLECMD,
		    fjp->fj_drive->fdd_motoroff);
		/*
		 * Return value ignored - fdcmotort deals with failure.
		 */

		mutex_exit(&fcp->c_dorlock);

		/* give up controller */
		sema_v(&fcp->c_selsem);
	}
	return (0);
}


int
fdgetchng(struct fcu_obj *fjp, int funit)
{
	if (fdcsense_drv(fjp->fj_fdc, funit & 3))
		cmn_err(CE_WARN, "fdgetchng: write protect check failed");
	return (fdcsense_chng(fjp->fj_fdc, funit & 3));
}


int
fdresetchng(struct fcu_obj *fjp, int funit)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	int unit = funit & 3;
	int newcyl;			/* where to seek for reset of DSKCHG */

	FCERRPRINT(FDEP_L2, FDEM_CHEK, (CE_NOTE, "fdmediachng unit %d", funit));

	if (fcp->c_curpcyl[unit])
		newcyl = fcp->c_curpcyl[unit] - 1;
	else
		newcyl = 1;
	return (fdrecalseek(fjp, funit, newcyl, 0));
}


/*
 * fdrecalseek
 */
int
fdrecalseek(struct fcu_obj *fjp, int funit, int arg, int execflg)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	struct fdcsb *csb;
	int unit = funit & 3;
	int rval;

	FCERRPRINT(FDEP_L2, FDEM_RECA, (CE_NOTE, "fdrecalseek unit %d to %d",
	    funit, arg));

	csb = &fcp->c_csb;
	csb->csb_cmd[1] = (uchar_t)unit;
	if (arg < 0) {			/* is recal... */
		*csb->csb_cmd = FO_RECAL;
		csb->csb_ncmds = 2;
		csb->csb_timer = 28;
	} else {
		*csb->csb_cmd = FO_SEEK;
		csb->csb_cmd[2] = (uchar_t)arg;
		csb->csb_ncmds = 3;
		csb->csb_timer = 10;
	}
	csb->csb_nrslts = 2;	/* 2 for SENSE INTERRUPTS */
	csb->csb_opflags = CSB_OFINRPT;
	csb->csb_maxretry = skretry;
	csb->csb_dmahandle = NULL;
	csb->csb_handle_bound = 0;
	csb->csb_dmacookiecnt = 0;
	csb->csb_dmacurrcookie = 0;
	csb->csb_dmawincnt = 0;
	csb->csb_dmacurrwin = 0;

	/* send cmd off to fdc_exec */
	if (rval = fdc_exec(fcp, 1, execflg))
		goto out;

	if (!(*csb->csb_rslt & S0_SEKEND) ||
	    (*csb->csb_rslt & S0_ICMASK) ||
	    ((*csb->csb_rslt & S0_ECHK) && arg < 0) ||
	    csb->csb_cmdstat)
		rval = ENODEV;

	if (fdcsense_drv(fcp, unit))
		cmn_err(CE_WARN, "fdgetchng: write protect check failed");
out:
	return (rval);
}


/*
 * fdrw- used only for read/writing sectors into/from kernel buffers.
 */
int
fdrw(struct fcu_obj *fjp, int funit, int rw, int cyl, int head,
    int sector, caddr_t bufp, uint_t len)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	struct fdcsb *csb;
	uint_t dmar_flags = 0;
	int unit = funit & 3;
	int rval;
	ddi_acc_handle_t mem_handle = NULL;
	caddr_t aligned_buf;
	size_t real_size;

	FCERRPRINT(FDEP_L1, FDEM_RW, (CE_CONT, "fdrw unit %d\n", funit));

	csb = &fcp->c_csb;
	if (rw) {
		dmar_flags = DDI_DMA_READ;
		csb->csb_opflags = CSB_OFDMARD | CSB_OFINRPT;
		*csb->csb_cmd = FO_MT | FO_MFM | FO_SK | FO_RDDAT;
	} else { /* write */
		dmar_flags = DDI_DMA_WRITE;
		csb->csb_opflags = CSB_OFDMAWT | CSB_OFINRPT;
		*csb->csb_cmd = FO_MT | FO_MFM | FO_WRDAT;
	}
	csb->csb_cmd[1] = (uchar_t)(unit | ((head & 0x1) << 2));
	csb->csb_cmd[2] = (uchar_t)cyl;
	csb->csb_cmd[3] = (uchar_t)head;
	csb->csb_cmd[4] = (uchar_t)sector;
	encode(sector_size, fjp->fj_chars->fdc_sec_size,
	    &csb->csb_cmd[5]);
	csb->csb_cmd[6] = (uchar_t)max(fjp->fj_chars->fdc_secptrack, sector);
	csb->csb_cmd[7] = fjp->fj_attr->fda_gapl;
	csb->csb_cmd[8] = 0xFF;

	csb->csb_ncmds = 9;
	csb->csb_nrslts = 7;
	csb->csb_timer = 36;
	if (rw == FDRDONE)
		csb->csb_maxretry = 1;
	else
		csb->csb_maxretry = rwretry;

	csb->csb_dmahandle = NULL;
	csb->csb_handle_bound = 0;
	csb->csb_dmacookiecnt = 0;
	csb->csb_dmacurrcookie = 0;
	csb->csb_dmawincnt = 0;
	csb->csb_dmacurrwin = 0;
	dmar_flags |= (DDI_DMA_STREAMING | DDI_DMA_PARTIAL);

	if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr, DDI_DMA_SLEEP,
	    0, &csb->csb_dmahandle) != DDI_SUCCESS) {
		rval = EINVAL;
		goto out;
	}

	/*
	 * allocate a page aligned buffer to dma to/from. This way we can
	 * ensure the cookie is a whole multiple of granularity and avoids
	 * any alignment issues.
	 */
	rval = ddi_dma_mem_alloc(csb->csb_dmahandle, len, &fdc_accattr,
	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &aligned_buf,
	    &real_size, &mem_handle);
	if (rval != DDI_SUCCESS) {
		rval = EINVAL;
		goto out;
	}

	if (dmar_flags & DDI_DMA_WRITE) {
		bcopy(bufp, aligned_buf, len);
	}

	rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL, aligned_buf,
	    len, dmar_flags, DDI_DMA_SLEEP, 0, &csb->csb_dmacookie,
	    &csb->csb_dmacookiecnt);

	if (rval == DDI_DMA_MAPPED) {
		csb->csb_dmawincnt = 1;
		csb->csb_handle_bound = 1;
	} else if (rval == DDI_DMA_PARTIAL_MAP) {
		csb->csb_handle_bound = 1;
		if (ddi_dma_numwin(csb->csb_dmahandle, &csb->csb_dmawincnt) !=
		    DDI_SUCCESS) {
			cmn_err(CE_WARN, "fdrw: dma numwin failed");
			rval = EINVAL;
			goto out;
		}
	} else {
		cmn_err(CE_WARN,
		    "fdrw: dma addr bind handle failed, rval = %d", rval);
		rval = EINVAL;
		goto out;
	}
	rval = fdc_exec(fcp, 1, 1);

	if (dmar_flags & DDI_DMA_READ) {
		bcopy(aligned_buf, bufp, len);
	}

out:
	if (csb->csb_dmahandle) {
		if (csb->csb_handle_bound) {
			if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
			    DDI_SUCCESS)
				cmn_err(CE_WARN, "fdrw: "
				    "dma unbind handle failed");
			csb->csb_handle_bound = 0;
		}
		if (mem_handle != NULL) {
			ddi_dma_mem_free(&mem_handle);
		}
		ddi_dma_free_handle(&csb->csb_dmahandle);
		csb->csb_dmahandle = NULL;
	}
	return (rval);
}


int
fdtrkformat(struct fcu_obj *fjp, int funit, int cyl, int head, int filldata)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	struct fdcsb *csb;
	int unit = funit & 3;
	int fmdatlen, lsector, lstart;
	int interleave, numsctr, offset, psector;
	uchar_t *dp;
	int rval;
	ddi_acc_handle_t mem_handle = NULL;
	caddr_t aligned_buf;
	size_t real_size;

	FCERRPRINT(FDEP_L2, FDEM_FORM,
	    (CE_NOTE, "fdformattrk unit %d cyl=%d, hd=%d", funit, cyl, head));

	csb = &fcp->c_csb;

	csb->csb_opflags = CSB_OFDMAWT | CSB_OFINRPT;

	*csb->csb_cmd = FO_FRMT | FO_MFM;
	csb->csb_cmd[1] = (head << 2) | unit;
	encode(sector_size, fjp->fj_chars->fdc_sec_size,
	    &csb->csb_cmd[2]);
	csb->csb_cmd[3] = numsctr = fjp->fj_chars->fdc_secptrack;
	csb->csb_cmd[4] = fjp->fj_attr->fda_gapf;
	csb->csb_cmd[5] = (uchar_t)filldata;

	csb->csb_npcyl = (uchar_t)(cyl * fjp->fj_chars->fdc_steps);

	csb->csb_dmahandle = NULL;
	csb->csb_handle_bound = 0;
	csb->csb_dmacookiecnt = 0;
	csb->csb_dmacurrcookie = 0;
	csb->csb_dmawincnt = 0;
	csb->csb_dmacurrwin = 0;
	csb->csb_ncmds = 6;
	csb->csb_nrslts = 7;
	csb->csb_timer = 32;
	csb->csb_maxretry = rwretry;

	/*
	 * alloc space for format track cmd
	 */
	/*
	 * NOTE: have to add size of fifo also - for dummy format action
	 */
	fmdatlen = 4 * numsctr;

	if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr, DDI_DMA_SLEEP,
	    0, &csb->csb_dmahandle) != DDI_SUCCESS) {
		rval = EINVAL;
		goto out;
	}

	/*
	 * allocate a page aligned buffer to dma to/from. This way we can
	 * ensure the cookie is a whole multiple of granularity and avoids
	 * any alignment issues.
	 */
	rval = ddi_dma_mem_alloc(csb->csb_dmahandle, fmdatlen, &fdc_accattr,
	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &aligned_buf,
	    &real_size, &mem_handle);
	if (rval != DDI_SUCCESS) {
		rval = EINVAL;
		goto out;
	}
	dp = (uchar_t *)aligned_buf;

	interleave = fjp->fj_attr->fda_intrlv;
	offset = (numsctr + interleave - 1) / interleave;
	for (psector = lstart = 1;
	    psector <= numsctr; psector += interleave, lstart++) {
		for (lsector = lstart; lsector <= numsctr; lsector += offset) {
			*dp++ = (uchar_t)cyl;
			*dp++ = (uchar_t)head;
			*dp++ = (uchar_t)lsector;
			*dp++ = csb->csb_cmd[2];
		}
	}

	rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL, aligned_buf,
	    fmdatlen, DDI_DMA_WRITE | DDI_DMA_STREAMING | DDI_DMA_PARTIAL,
	    DDI_DMA_SLEEP, 0, &csb->csb_dmacookie, &csb->csb_dmacookiecnt);

	if (rval == DDI_DMA_MAPPED) {
		csb->csb_dmawincnt = 1;
		csb->csb_handle_bound = 1;
	} else if (rval == DDI_DMA_PARTIAL_MAP) {
		csb->csb_handle_bound = 1;
		if (ddi_dma_numwin(csb->csb_dmahandle, &csb->csb_dmawincnt) !=
		    DDI_SUCCESS) {
			cmn_err(CE_WARN, "fdtrkformat: dma numwin failed");
			rval = EINVAL;
			goto out;
		}
	} else {
		cmn_err(CE_WARN,
		    "fdtrkformat: dma buf bind handle failed, rval = %d",
		    rval);
		rval = EINVAL;
		goto out;
	}

	rval = fdc_exec(fcp, 1, 1);
out:
	if (csb->csb_dmahandle) {
		if (csb->csb_handle_bound) {
			if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
			    DDI_SUCCESS)
				cmn_err(CE_WARN, "fdtrkformat: "
				    "dma unbind handle failed");
			csb->csb_handle_bound = 0;
		}
		if (mem_handle != NULL) {
			ddi_dma_mem_free(&mem_handle);
		}
		ddi_dma_free_handle(&csb->csb_dmahandle);
		csb->csb_dmahandle = NULL;
	}
	return (rval);
}

int
fdrawioctl(struct fcu_obj *fjp, int funit, caddr_t arg)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	struct fd_raw *fdrp = (struct fd_raw *)arg;
	struct fdcsb *csb;
	uint_t dmar_flags = 0;
	int i;
	int change = 1;
	int sleep = 1;
	int rval = 0;
	int rval_exec = 0;
	ddi_acc_handle_t mem_handle = NULL;
	caddr_t aligned_buf;
	size_t real_size;

	_NOTE(ARGUNUSED(funit));

	FCERRPRINT(FDEP_L2, FDEM_RAWI,
	    (CE_NOTE, "fdrawioctl: cmd[0]=0x%x", fdrp->fdr_cmd[0]));

	csb = &fcp->c_csb;

	/* copy cmd bytes into csb */
	for (i = 0; i <= fdrp->fdr_cnum; i++)
		csb->csb_cmd[i] = fdrp->fdr_cmd[i];
	csb->csb_ncmds = (uchar_t)fdrp->fdr_cnum;

	csb->csb_maxretry = 0;	/* let the application deal with errors */
	csb->csb_opflags = CSB_OFRAWIOCTL;
	csb->csb_nrslts = 0;
	csb->csb_timer = 50;

	switch (fdrp->fdr_cmd[0] & 0x0f) {

	case FO_SEEK:
		change = 0;
		/* FALLTHROUGH */
	case FO_RECAL:
		csb->csb_opflags |= CSB_OFINRPT;
		break;

	case FO_FRMT:
		csb->csb_npcyl = *(uchar_t *)(fdrp->fdr_addr) *
		    fjp->fj_chars->fdc_steps;
		/* FALLTHROUGH */
	case FO_WRDAT:
	case FO_WRDEL:
		csb->csb_opflags |= CSB_OFDMAWT | CSB_OFRESLT | CSB_OFINRPT;
		csb->csb_nrslts = 7;
		if (fdrp->fdr_nbytes == 0)
			return (EINVAL);
		dmar_flags = DDI_DMA_WRITE;
		break;

	case FO_RDDAT:
	case FO_RDDEL:
	case FO_RDTRK:
		csb->csb_opflags |= CSB_OFDMARD | CSB_OFRESLT | CSB_OFINRPT;
		csb->csb_nrslts = 7;
		dmar_flags = DDI_DMA_READ;
		break;

	case FO_RDID:
		csb->csb_opflags |= CSB_OFRESLT | CSB_OFINRPT;
		csb->csb_nrslts = 7;
		break;

	case FO_SDRV:
		sleep = 0;
		csb->csb_nrslts = 1;
		break;

	case FO_SINT:
		sleep = 0;
		change = 0;
		csb->csb_nrslts = 2;
		break;

	case FO_SPEC:
		sleep = 0;
		change = 0;
		break;

	default:
		return (EINVAL);
	}

	csb->csb_dmahandle = NULL;
	csb->csb_handle_bound = 0;
	csb->csb_dmacookiecnt = 0;
	csb->csb_dmacurrcookie = 0;
	csb->csb_dmawincnt = 0;
	csb->csb_dmacurrwin = 0;

	if (csb->csb_opflags & (CSB_OFDMARD | CSB_OFDMAWT)) {
		if (ddi_dma_alloc_handle(fcp->c_dip, &fdc_dma_attr,
		    DDI_DMA_SLEEP, 0, &csb->csb_dmahandle) != DDI_SUCCESS) {
			rval = EINVAL;
			goto out;
		}

		/*
		 * allocate a page aligned buffer to dma to/from. This way we
		 * can ensure the cookie is a whole multiple of granularity and
		 * avoids any alignment issues.
		 */
		rval = ddi_dma_mem_alloc(csb->csb_dmahandle,
		    (uint_t)fdrp->fdr_nbytes, &fdc_accattr, DDI_DMA_CONSISTENT,
		    DDI_DMA_SLEEP, NULL, &aligned_buf, &real_size, &mem_handle);
		if (rval != DDI_SUCCESS) {
			rval = EINVAL;
			goto out;
		}

		if (dmar_flags & DDI_DMA_WRITE) {
			bcopy(fdrp->fdr_addr, aligned_buf,
			    (uint_t)fdrp->fdr_nbytes);
		}

		dmar_flags |= (DDI_DMA_STREAMING | DDI_DMA_PARTIAL);
		rval = ddi_dma_addr_bind_handle(csb->csb_dmahandle, NULL,
		    aligned_buf, (uint_t)fdrp->fdr_nbytes, dmar_flags,
		    DDI_DMA_SLEEP, 0, &csb->csb_dmacookie,
		    &csb->csb_dmacookiecnt);

		if (rval == DDI_DMA_MAPPED) {
			csb->csb_dmawincnt = 1;
			csb->csb_handle_bound = 1;
		} else if (rval == DDI_DMA_PARTIAL_MAP) {
			csb->csb_handle_bound = 1;
			if (ddi_dma_numwin(csb->csb_dmahandle,
			    &csb->csb_dmawincnt) != DDI_SUCCESS) {
				cmn_err(CE_WARN,
				    "fdrawioctl: dma numwin failed");
				rval = EINVAL;
				goto out;
			}
		} else {
			cmn_err(CE_WARN, "fdrawioctl: "
			    "dma buf bind handle failed, rval = %d", rval);
			rval = EINVAL;
			goto out;
		}
	}

	FCERRPRINT(FDEP_L1, FDEM_RAWI,
	    (CE_CONT, "cmd: %x %x %x %x %x %x %x %x %x %x\n", csb->csb_cmd[0],
	    csb->csb_cmd[1], csb->csb_cmd[2], csb->csb_cmd[3],
	    csb->csb_cmd[4], csb->csb_cmd[5], csb->csb_cmd[6],
	    csb->csb_cmd[7], csb->csb_cmd[8], csb->csb_cmd[9]));
	FCERRPRINT(FDEP_L1, FDEM_RAWI,
	    (CE_CONT, "nbytes: %x, opflags: %x, addr: %p, len: %x\n",
	    csb->csb_ncmds, csb->csb_opflags, (void *)fdrp->fdr_addr,
	    fdrp->fdr_nbytes));

	/*
	 * Note that we ignore any error returns from fdexec.
	 * This is the way the driver has been, and it may be
	 * that the raw ioctl senders simply don't want to
	 * see any errors returned in this fashion.
	 */

	/*
	 * VP/ix sense drive ioctl call checks for the error return.
	 */

	rval_exec = fdc_exec(fcp, sleep, change);

	if (dmar_flags & DDI_DMA_READ) {
		bcopy(aligned_buf, fdrp->fdr_addr, (uint_t)fdrp->fdr_nbytes);
	}

	FCERRPRINT(FDEP_L1, FDEM_RAWI,
	    (CE_CONT, "rslt: %x %x %x %x %x %x %x %x %x %x\n", csb->csb_rslt[0],
	    csb->csb_rslt[1], csb->csb_rslt[2], csb->csb_rslt[3],
	    csb->csb_rslt[4], csb->csb_rslt[5], csb->csb_rslt[6],
	    csb->csb_rslt[7], csb->csb_rslt[8], csb->csb_rslt[9]));

	/* copy results into fdr */
	for (i = 0; i <= (int)csb->csb_nrslts; i++)
		fdrp->fdr_result[i] = csb->csb_rslt[i];
/*	fdrp->fdr_nbytes = fdc->c_csb.csb_rlen;  return resid */

out:
	if (csb->csb_dmahandle) {
		if (csb->csb_handle_bound) {
			if (ddi_dma_unbind_handle(csb->csb_dmahandle) !=
			    DDI_SUCCESS)
				cmn_err(CE_WARN, "fdrawioctl: "
				    "dma unbind handle failed");
			csb->csb_handle_bound = 0;
		}
		if (mem_handle != NULL) {
			ddi_dma_mem_free(&mem_handle);
		}
		ddi_dma_free_handle(&csb->csb_dmahandle);
		csb->csb_dmahandle = NULL;
	}
	if ((fdrp->fdr_cmd[0] & 0x0f) == FO_SDRV) {
		return (rval_exec);
	}
	return (rval);
}

void
encode(xlate_tbl_t *tablep, int val, uchar_t *rcode)
{
	do {
		if (tablep->value >= val) {
			*rcode = tablep->code;
			return;
		}
	} while ((++tablep)->value);
	*rcode = tablep->code;
	cmn_err(CE_WARN, "fdc encode failed, table %p val %x code %x",
	    (void *)tablep, val, (uint_t)*rcode);
}

int
decode(xlate_tbl_t *tablep, int kode, int *rvalue)
{
	do  {
		if (tablep->code == kode) {
			*rvalue = tablep->value;
			return (0);
		}
	} while ((++tablep)->value);
	return (-1);
}

/*
 * quiesce(9E) entry point.
 *
 * This function is called when the system is single-threaded at high
 * PIL with preemption disabled. Therefore, this function must not be
 * blocked.
 *
 * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
 * DDI_FAILURE indicates an error condition and should almost never happen.
 */
int
fdc_quiesce(dev_info_t *dip)
{
	struct fdcntlr *fcp;
	int ctlr = ddi_get_instance(dip);
	int unit;

	fcp = ddi_get_soft_state(fdc_state_head, ctlr);

	if (fcp == NULL)
		return (DDI_FAILURE);

	/*
	 * If no FD units are attached, there is no need to quiesce.
	 */
	for (unit = 0; unit < NFDUN; unit++) {
		struct fcu_obj *fjp = fcp->c_unit[unit];
		if (fjp->fj_flags & FUNIT_DRVATCH) {
			break;
		}
	}

	if (unit == NFDUN)
		return (DDI_SUCCESS);

	(void) ddi_dmae_disable(fcp->c_dip, fcp->c_dmachan);

	fcp->c_digout = (fcp->c_digout & (FD_DMTREN | FD_DRSEL)) | FD_ENABLE;
	outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
	drv_usecwait(20);
	fcp->c_digout |= FD_RSETZ;
	outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

	if (fcp->c_chip >= i82077) {
		int count = 4;
		uchar_t *oplistp = configurecmd;
		do {
			int ntries = FDC_RQM_RETRY;
			do {
				if ((inb(fcp->c_regbase + FCR_MSR) &
				    (MS_RQM|MS_DIO)) == MS_RQM)
					break;
				else
					drv_usecwait(1);
			} while (--ntries);
			if (ntries == 0) {
				break;
			}
			outb(fcp->c_regbase + FCR_DATA, *oplistp++);
			drv_usecwait(16); /* See comment in fdc_result() */
		} while (--count);
	}

	return (DDI_SUCCESS);
}

void
fdcquiesce(struct fdcntlr *fcp)
{
	int unit;

	FCERRPRINT(FDEP_L2, FDEM_RESE, (CE_NOTE, "fdcquiesce fcp %p",
	    (void*)fcp));

	ASSERT(MUTEX_HELD(&fcp->c_lock));
	mutex_enter(&fcp->c_dorlock);

	if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) != DDI_SUCCESS)
		cmn_err(CE_WARN, "fdcquiesce: dmae stop failed, "
		    "dip %p, dmachan %x",
		    (void*)fcp->c_dip, fcp->c_dmachan);

	fcp->c_digout = (fcp->c_digout & (FD_DMTREN | FD_DRSEL)) | FD_ENABLE;
	outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
	drv_usecwait(20);
	fcp->c_digout |= FD_RSETZ;
	outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

	mutex_exit(&fcp->c_dorlock);

	/* count resets */
	fcp->fdstats.reset++;
	fcp->c_curunit = -1;
	for (unit = 0; unit < NFDUN; unit++)
		fcp->c_curpcyl[unit] = -1;

	if (fcp->c_chip >= i82077) {
		(void) fdc_docmd(fcp, configurecmd, 4);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 */
	}
}

void
fdcreadid(struct fdcntlr *fcp, struct fdcsb *csb)
{
	static uchar_t readidcmd[2] = {FO_RDID | FO_MFM, 0};

	readidcmd[1] = csb->csb_cmd[1];
	(void) fdc_docmd(fcp, readidcmd, 2);
}

int
fdcseek(struct fdcntlr *fcp, int unit, int cyl)
{
	static uchar_t seekabscmd[3] = {FO_SEEK, 0, 0};

	FCERRPRINT(FDEP_L0, FDEM_RECA, (CE_CONT, "fdcseek unit %d to cyl %d\n",
	    unit, cyl));
	seekabscmd[1] = (uchar_t)unit;
	seekabscmd[2] = (uchar_t)cyl;
	return (fdc_docmd(fcp, seekabscmd, 3));
}

/*
 * Returns status of disk change line of selected drive.
 *	= 0 means diskette is present
 *	!= 0 means diskette was removed and current state is unknown
 */
int
fdcsense_chng(struct fdcntlr *fcp, int unit)
{
	int digital_input;

	FCERRPRINT(FDEP_L0, FDEM_SCHG,
	    (CE_CONT, "fdcsense_chng unit %d\n", unit));
	digital_input = inb(fcp->c_regbase + FCR_DIR);
	if (fcp->c_mode == FDCMODE_30)
		digital_input ^= FDI_DKCHG;
	return (digital_input & FDI_DKCHG);
}

int
fdcsense_drv(struct fdcntlr *fcp, int unit)
{
	static uchar_t sensedrvcmd[2] = {FO_SDRV, 0};
	uchar_t senser;
	int rval;

	sensedrvcmd[1] = (uchar_t)unit;
	(void) fdc_docmd(fcp, sensedrvcmd, 2);
	/*
	 * Ignored return. If failed, warning was issued by fdc_docmd.
	 * fdc_results retrieves the controller/drive status
	 */
	if (rval = fdc_result(fcp, &senser, 1))
		goto done;
	if (senser & S3_WPROT)
		fcp->c_unit[unit]->fj_flags |= FUNIT_WPROT;
	else
		fcp->c_unit[unit]->fj_flags &= ~FUNIT_WPROT;
done:
	return (rval);
}

int
fdcsense_int(struct fdcntlr *fcp, int *unitp, int *cylp)
{
	uchar_t senser[2];
	int rval;

	(void) fdc_docmd(fcp, &senseintcmd, 1);
	/*
	 * Ignored return. If failed, warning was issued by fdc_docmd.
	 * fdc_results retrieves the controller/drive status
	 */

	if (!(rval = fdc_result(fcp, senser, 2))) {
		if ((*senser & (S0_IVCMD | S0_SEKEND | S0_ECHK)) != S0_SEKEND)
			rval = 1;
		if (unitp)
			*unitp = *senser & 3;
		if (cylp)
			*cylp = senser[1];
	}
	return (rval);
}

int
fdcspecify(struct fdcntlr *fcp, int xferrate, int steprate, int hlt)
{
	static uchar_t perpindcmd[2] = {FO_PERP, 0};
	static uchar_t specifycmd[3] = {FO_SPEC, 0, 0};

	encode(drate_mfm, xferrate, &fcp->c_config);
	outb(fcp->c_regbase + FCR_CCR, fcp->c_config);

	if (fcp->c_chip >= i82077) {
		/*
		 * Use old style perpendicular mode command of 82077.
		 */
		if (xferrate == 1000) {
			/* Set GAP and WGATE */
			perpindcmd[1] = 3;
			/* double step rate because xlate table is for 500Kb */
			steprate <<= 1;
			hlt <<= 1;
		} else
			perpindcmd[1] = 0;
		(void) fdc_docmd(fcp, perpindcmd, 2);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 */
	}
	encode(step_rate, steprate, &fcp->c_hutsrt);
	specifycmd[1] = fcp->c_hutsrt |= 0x0F;	/* use max head unload time */
	hlt = (hlt >= 256) ? 0 : (hlt >> 1);	/* encode head load time */
	specifycmd[2] = fcp->c_hlt = hlt << 1;	/* make room for DMA bit */
	return (fdc_docmd(fcp, specifycmd, 3));
}

int
fdcspdchange(struct fdcntlr *fcp, struct fcu_obj *fjp, int rpm)
{
	int	retcode = 0;
	uint_t	ddic;
	uchar_t	deselect = 0;
	uchar_t	ds_code;
	uchar_t	enable_code;
	uchar_t	save;

	if (((fcp->c_flags & FCFLG_DSOUT) == 0 && rpm <= fjp->fj_rotspd) ||
	    ((fcp->c_flags & FCFLG_DSOUT) && (fjp->fj_flags & FUNIT_3DMODE) &&
	    rpm > fjp->fj_rotspd)) {
		return (0);
	}

	FCERRPRINT(FDEP_L1, FDEM_SCHG,
	    (CE_CONT, "fdcspdchange: %d rpm\n", rpm));
	ASSERT(MUTEX_HELD(&fcp->c_dorlock));

	switch (fcp->c_chip) {
	default:
		break;
	case i82077:
		break;

	case PC87322:
		{
		uchar_t nscmodecmd[5] = {FO_MODE, 0x02, 0x00, 0xC8, 0x00};

		if (rpm > fjp->fj_rotspd) {
			nscmodecmd[3] ^= 0xC0;
			retcode = (fcp->c_flags ^ FCFLG_DSOUT) ||
			    (fjp->fj_flags ^ FUNIT_3DMODE);
			fcp->c_flags |= FCFLG_DSOUT;
			fjp->fj_flags |= FUNIT_3DMODE;
		} else {
			/* program DENSEL to default output */
			fcp->c_flags &= ~FCFLG_DSOUT;
			retcode = fjp->fj_flags & FUNIT_3DMODE;
			fjp->fj_flags &= ~FUNIT_3DMODE;
		}
		if (retcode && (fcp->c_digout & FD_DRSEL) == fcp->c_curunit) {
			/* de-select drive while changing speed */
			deselect = fcp->c_digout ^ FD_DRSEL;
			outb(fcp->c_regbase + FCR_DOR, deselect);
		}

		(void) fdc_docmd(fcp, nscmodecmd, 5);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 */
		break;
		}

	case FDC37C665:
		enable_code = FSA_ENA5;
		goto SMC_config;

	case FDC37C666:
		enable_code = FSA_ENA6;
SMC_config:
		if (rpm > fjp->fj_rotspd) {
			/* force DENSEL output to active LOW */
			ds_code = FSB_DSHI;
			retcode = (fcp->c_flags ^ FCFLG_DSOUT) ||
			    (fjp->fj_flags ^ FUNIT_3DMODE);
			fcp->c_flags |= FCFLG_DSOUT;
			fjp->fj_flags |= FUNIT_3DMODE;
		} else {
			/* program DENSEL to default output */
			ds_code = 0;
			fcp->c_flags &= ~FCFLG_DSOUT;
			retcode = fjp->fj_flags & FUNIT_3DMODE;
			fjp->fj_flags &= ~FUNIT_3DMODE;
		}
		if (retcode && (fcp->c_digout & FD_DRSEL) == fcp->c_curunit) {
			/* de-select drive while changing speed */
			deselect = fcp->c_digout ^ FD_DRSEL;
			outb(fcp->c_regbase + FCR_DOR, deselect);
		}
		save = inb(fcp->c_regbase + FCR_SRA);

		/* enter configuration mode */
		ddic = ddi_enter_critical();
		outb(fcp->c_regbase + FCR_SRA, enable_code);
		outb(fcp->c_regbase + FCR_SRA, enable_code);
		ddi_exit_critical(ddic);

		outb(fcp->c_regbase + FCR_SRA, FSA_CR5);
		enable_code = inb(fcp->c_regbase + FCR_SRB) & FSB_DSDEF;
		/* update DENSEL mode bits */
		outb(fcp->c_regbase + FCR_SRB, enable_code | ds_code);

		/* exit configuration mode */
		outb(fcp->c_regbase + FCR_SRA, FSA_DISB);
		drv_usecwait(10);
		outb(fcp->c_regbase + FCR_SRA, save);
		break;
	}
	if (deselect)
		/* reselect drive */
		outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
	return (retcode);
}

static int
fdc_motorsm(struct fcu_obj *fjp, int input, int timeval)
{
	struct fdcntlr *fcp = fjp->fj_fdc;
	int unit = fjp->fj_unit & 3;
	int old_mstate;
	int rval = 0;
	uchar_t motorbit;

	ASSERT(MUTEX_HELD(&fcp->c_dorlock));
	old_mstate = fcp->c_mtrstate[unit];
	encode(motor_onbits, unit, &motorbit);

	switch (input) {
	case FMI_TIMER:		/* timer expired */
		fcp->c_motort[unit] = 0;
		switch (old_mstate) {
		case FMS_START:
		case FMS_DELAY:
			fcp->c_mtrstate[unit] = FMS_ON;
			break;
		case FMS_KILLST:
			fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
			    drv_usectohz(1000000));
			fcp->c_mtrstate[unit] = FMS_IDLE;
			break;
		case FMS_IDLE:
			fcp->c_digout &= ~motorbit;
			outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);
			fcp->c_mtrstate[unit] = FMS_OFF;
			fjp->fj_flags &= ~FUNIT_3DMODE;
			break;
		case 86:
			rval = -1;
			break;
		case FMS_OFF:
		case FMS_ON:
		default:
			rval = -2;
		}
		break;

	case FMI_STARTCMD:	/* start command */
		switch (old_mstate) {
		case FMS_IDLE:
			fcp->c_mtrstate[unit] = 86;
			mutex_exit(&fcp->c_dorlock);
			(void) untimeout(fcp->c_motort[unit]);
			mutex_enter(&fcp->c_dorlock);
			fcp->c_motort[unit] = 0;
			fcp->c_mtrstate[unit] = FMS_ON;
			break;
		case FMS_OFF:
			fcp->c_digout |= motorbit;
			outb(fcp->c_regbase + FCR_DOR, fcp->c_digout);

			/* start motor_spinup_timer */
			ASSERT(timeval > 0);
			fcp->c_motort[unit] = timeout(fdmotort,  (void *)fjp,
			    drv_usectohz(100000 * timeval));
			/* FALLTHROUGH */
		case FMS_KILLST:
			fcp->c_mtrstate[unit] = FMS_START;
			break;
		default:
			rval = -2;
		}
		break;

	case FMI_RSTARTCMD:	/* restart command */
		if (fcp->c_motort[unit] != 0) {
			fcp->c_mtrstate[unit] = 86;
			mutex_exit(&fcp->c_dorlock);
			(void) untimeout(fcp->c_motort[unit]);
			mutex_enter(&fcp->c_dorlock);
		}
		ASSERT(timeval > 0);
		fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
		    drv_usectohz(100000 * timeval));
		fcp->c_mtrstate[unit] = FMS_START;
		break;

	case FMI_DELAYCMD:	/* delay command */
		if (fcp->c_motort[unit] == 0)
			fcp->c_motort[unit] = timeout(fdmotort,  (void *)fjp,
			    drv_usectohz(15000));
		fcp->c_mtrstate[unit] = FMS_DELAY;
		break;

	case FMI_IDLECMD:	/* idle command */
		switch (old_mstate) {
		case FMS_DELAY:
			fcp->c_mtrstate[unit] = 86;
			mutex_exit(&fcp->c_dorlock);
			(void) untimeout(fcp->c_motort[unit]);
			mutex_enter(&fcp->c_dorlock);
			/* FALLTHROUGH */
		case FMS_ON:
			ASSERT(timeval > 0);
			fcp->c_motort[unit] = timeout(fdmotort, (void *)fjp,
			    drv_usectohz(100000 * timeval));
			fcp->c_mtrstate[unit] = FMS_IDLE;
			break;
		case FMS_START:
			fcp->c_mtrstate[unit] = FMS_KILLST;
			break;
		default:
			rval = -2;
		}
		break;

	default:
		rval = -3;
	}
	if (rval) {
		FCERRPRINT(FDEP_L4, FDEM_EXEC, (CE_WARN,
		    "fdc_motorsm: unit %d  bad input %d or bad state %d",
		    (int)fjp->fj_unit, input, old_mstate));
#if 0
		cmn_err(CE_WARN,
		    "fdc_motorsm: unit %d  bad input %d or bad state %d",
		    (int)fjp->fj_unit, input, old_mstate);
		fcp->c_mtrstate[unit] = FMS_OFF;
		if (fcp->c_motort[unit] != 0) {
			mutex_exit(&fcp->c_dorlock);
			(void) untimeout(fcp->c_motort[unit]);
			mutex_enter(&fcp->c_dorlock);
			fcp->c_motort[unit] = 0;
		}
#endif
	} else
		FCERRPRINT(FDEP_L0, FDEM_EXEC,
		    (CE_CONT, "fdc_motorsm unit %d: input %d,  %d -> %d\n",
		    (int)fjp->fj_unit, input, old_mstate,
		    fcp->c_mtrstate[unit]));
	return (rval);
}

/*
 * fdmotort
 *	is called from timeout() when a motor timer has expired.
 */
static void
fdmotort(void *arg)
{
	struct fcu_obj *fjp = (struct fcu_obj *)arg;
	struct fdcntlr *fcp = fjp->fj_fdc;
	struct fdcsb *csb = &fcp->c_csb;
	int unit = fjp->fj_unit & 3;
	int mval;
	int newxstate = 0;

	mutex_enter(&fcp->c_dorlock);
	mval = fdc_motorsm(fjp, FMI_TIMER, 0);
	mutex_exit(&fcp->c_dorlock);
	if (mval < 0)
		return;

	mutex_enter(&fcp->c_lock);

	if ((fcp->c_flags & FCFLG_WAITING) &&
	    fcp->c_mtrstate[unit] == FMS_ON &&
	    (csb->csb_xstate == FXS_MTRON || csb->csb_xstate == FXS_HDST ||
	    csb->csb_xstate == FXS_DKCHGX))
		newxstate = fdc_statemach(fcp);
		if (newxstate == -1) {
			FCERRPRINT(FDEP_L3, FDEM_EXEC,
			    (CE_WARN,
			    "fdc_motort unit %d: motor ready but bad xstate",
			    (int)fjp->fj_unit));
			fcp->c_csb.csb_cmdstat = EIO;
		}
		if (newxstate == -1 || newxstate == FXS_END) {
			fcp->c_flags ^= FCFLG_WAITING;
			cv_signal(&fcp->c_iocv);
		}
	mutex_exit(&fcp->c_lock);
}

/*
 * DMA interrupt service routine
 *
 *	Called by EISA dma interrupt service routine when buffer chaining
 *	is required.
 */

ddi_dma_cookie_t *
fdc_dmae_isr(struct fdcntlr *fcp)
{
	struct fdcsb *csb = &fcp->c_csb;
	off_t off;
	size_t len;

	if (csb->csb_dmahandle && !csb->csb_cmdstat) {
		if (++csb->csb_dmacurrcookie < csb->csb_dmacookiecnt) {
			ddi_dma_nextcookie(csb->csb_dmahandle,
			    &csb->csb_dmacookie);
			return (&csb->csb_dmacookie);
		} else if (++csb->csb_dmacurrwin < csb->csb_dmawincnt) {
			if (ddi_dma_getwin(csb->csb_dmahandle,
			    csb->csb_dmacurrwin, &off, &len,
			    &csb->csb_dmacookie,
			    &csb->csb_dmacookiecnt) != DDI_SUCCESS) {
				return (NULL);
			}
			csb->csb_dmacurrcookie = 0;
			return (&csb->csb_dmacookie);
		}
	} else
		cmn_err(CE_WARN, "fdc: unsolicited DMA interrupt");
	return (NULL);
}


/*
 * returns:
 *	0 if all ok,
 *	ENXIO - diskette not in drive
 *	ETIMEDOUT - for immediate operations that timed out
 *	EBUSY - if stupid chip is locked busy???
 *	ENOEXEC - for timeout during sending cmds to chip
 *
 * to sleep: set sleep
 * to check for disk changed: set change
 */
static int
fdc_exec(struct fdcntlr *fcp, int sleep, int change)
{
	struct ddi_dmae_req dmaereq;
	struct fcu_obj *fjp;
	struct fdcsb *csb;
	off_t off;
	size_t len;
	int unit;

	mutex_enter(&fcp->c_lock);
	FCERRPRINT(FDEP_L0, FDEM_EXEC,
	    (CE_CONT, "fdc_exec: sleep %x change %x\n", sleep, change));
	csb = &fcp->c_csb;
	unit = csb->csb_drive;
	fjp = fcp->c_unit[unit];

	if (csb->csb_opflags & CSB_OFINRPT) {
		if (*csb->csb_cmd == FO_RECAL)
			csb->csb_npcyl = 0;
		else if ((*csb->csb_cmd & ~FO_MFM) != FO_FRMT)
			csb->csb_npcyl =
			    csb->csb_cmd[2] * fjp->fj_chars->fdc_steps;
		csb->csb_xstate = FXS_START;
	} else
		csb->csb_xstate = FXS_DOIT;
	csb->csb_retrys = 0;
	csb->csb_ourtrys = 0;

	if (csb->csb_dmahandle) {
		/* ensure that entire format xfer is in one cookie */
		/*
		 * The change from  ddi_dma_buf/addr_setup() to
		 * ddi_dma_buf/addr_bind_handle() has already loaded
		 * the first DMA window and cookie.
		 */
		if ((*csb->csb_cmd & ~FO_MFM) == FO_FRMT &&
		    (4 * csb->csb_cmd[3]) != csb->csb_dmacookie.dmac_size) {
			mutex_exit(&fcp->c_lock);
			return (EINVAL);
		}
	}

retry:
	if (fcp->c_curunit != unit || !(fjp->fj_flags & FUNIT_CHAROK)) {
		fcp->c_curunit = unit;
		fjp->fj_flags |= FUNIT_CHAROK;
		if (fjp->fj_chars->fdc_transfer_rate == 417) {
			/* XXX hack for fdformat */
			/* fjp->fj_chars->fdc_transfer_rate == 500;	*/
			fjp->fj_attr->fda_rotatespd = 360;
		}
		if (fdcspecify(fcp, fjp->fj_chars->fdc_transfer_rate,
		    fjp->fj_drive->fdd_steprate, 40))
			cmn_err(CE_WARN,
			    "fdc_select: controller setup rejected "
			    "fdcntrl %p transfer rate %x step rate %x "
			    "head load time 40", (void*)fcp,
			    fjp->fj_chars->fdc_transfer_rate,
			    fjp->fj_drive->fdd_steprate);

		mutex_enter(&fcp->c_dorlock);
		if (fdcspdchange(fcp, fjp, fjp->fj_attr->fda_rotatespd)) {
			/* 3D drive requires 500 ms for speed change */
			(void) fdc_motorsm(fjp, FMI_RSTARTCMD, 5);
			/*
			 * Return value ignored - fdcmotort deals with failure.
			 */
		}
		mutex_exit(&fcp->c_dorlock);
	}

	/*
	 * If checking for disk_change is enabled
	 * (i.e. not seeking in fdresetchng),
	 * we sample the DSKCHG line to see if the diskette has wandered away.
	 */
	if (change && fdcsense_chng(fcp, unit)) {
		FCERRPRINT(FDEP_L3, FDEM_EXEC,
		    (CE_WARN, "diskette %d changed!!!", csb->csb_drive));
		fcp->c_unit[unit]->fj_flags |= FUNIT_CHANGED;
		/*
		 * If the diskette is still gone... so are we, adios!
		 */
		if (fdcheckdisk(fcp, unit)) {
			mutex_exit(&fcp->c_lock);

			/* VP/ix expects an EBUSY return here */
			if (*csb->csb_cmd == FO_SDRV) {
				return (EBUSY);
			}
			return (ENXIO);
		}
		/*
		 * delay to ensure that new diskette is up to speed
		 */
		mutex_enter(&fcp->c_dorlock);
		(void) fdc_motorsm(fjp, FMI_RSTARTCMD,
		    fjp->fj_drive->fdd_motoron);
		/*
		 * Return value ignored - fdcmotort deals with failure.
		 */
		mutex_exit(&fcp->c_dorlock);
	}

	/*
	 * gather some statistics
	 */
	switch (csb->csb_cmd[0] & 0x1f) {
	case FO_RDDAT:
		fcp->fdstats.rd++;
		break;
	case FO_WRDAT:
		fcp->fdstats.wr++;
		break;
	case FO_RECAL:
		fcp->fdstats.recal++;
		break;
	case FO_FRMT:
		fcp->fdstats.form++;
		break;
	default:
		fcp->fdstats.other++;
		break;
	}

	bzero(csb->csb_rslt, 10);
	csb->csb_cmdstat = 0;

	if (csb->csb_dmahandle) {
		bzero(&dmaereq, sizeof (struct ddi_dmae_req));
		dmaereq.der_command = (csb->csb_opflags & CSB_OFDMAWT) ?
		    DMAE_CMD_WRITE : DMAE_CMD_READ;
		/*
		 * setup for dma buffer chaining regardless of bus capability
		 */
		dmaereq.der_bufprocess = DMAE_BUF_CHAIN;
		dmaereq.proc = fdc_dmae_isr;
		dmaereq.procparms = (void *)fcp;
		if (ddi_dmae_prog(fcp->c_dip, &dmaereq, &csb->csb_dmacookie,
		    fcp->c_dmachan) != DDI_SUCCESS)
			cmn_err(CE_WARN, "fdc_exec: dmae prog failed, "
			    "dip %p, dmachan %x",
			    (void*)fcp->c_dip, fcp->c_dmachan);
	}

	if ((fdc_statemach(fcp) == FXS_DOWT) && !sleep) {
		/*
		 * If the operation has no results - then just return
		 */
		if (!csb->csb_nrslts) {
			mutex_exit(&fcp->c_lock);
			return (0);
		}
		/*
		 * this operation has no interrupt and an immediate result
		 * so wait for the results and stuff them into the csb
		 */
		if (fdc_statemach(fcp) == -1) {
			mutex_exit(&fcp->c_lock);
			return (EIO);
		}
	} else {
		fcp->c_flags |= FCFLG_WAITING;
		/*
		 * wait for completion interrupt
		 */
		while (fcp->c_flags & FCFLG_WAITING) {
			cv_wait(&fcp->c_iocv, &fcp->c_lock);
		}
	}

	/*
	 * See if there was an error detected, if so, fdrecover()
	 * will check it out and say what to do.
	 *
	 * Don't do this, though, if this was the Sense Drive Status
	 * or the Dump Registers command.
	 */
	if (csb->csb_cmdstat && *csb->csb_cmd != FO_SDRV) {
		/* if it can restarted OK, then do so, else return error */
		if (fdrecover(fcp)) {
			mutex_exit(&fcp->c_lock);
			return (EIO);
		}
		/* ASSUMES that cmd is still intact in csb */
		if (csb->csb_xstate == FXS_END)
			csb->csb_xstate = FXS_START;
		if (fdc_dma_attr.dma_attr_sgllen > 1 && csb->csb_dmahandle) {
			/*
			 * restarted read/write operation requires
			 * first DMA cookie of current window
			 */
			if (ddi_dma_getwin(csb->csb_dmahandle,
			    csb->csb_dmacurrwin, &off, &len,
			    &csb->csb_dmacookie,
			    &csb->csb_dmacookiecnt) != DDI_SUCCESS) {

				mutex_exit(&fcp->c_lock);
				return (EIO);
			}
			csb->csb_dmacurrcookie = 0;
		}
		goto retry;
	}
	/* things went ok */
	mutex_exit(&fcp->c_lock);
	return (0);
}

/*
 * fdcheckdisk
 *	called by fdc_exec to check if the disk is still there - do a seek
 *	then see if DSKCHG line went away; if so, diskette is in; else
 *	it's (still) out.
 */
int
fdcheckdisk(struct fdcntlr *fcp, int unit)
{
	struct fdcsb *csb = &fcp->c_csb;
	int newcyl;			/* where to seek for reset of DSKCHG */
	int rval;
	enum fxstate save_xstate;
	uchar_t save_cmd, save_cd1, save_npcyl;

	ASSERT(MUTEX_HELD(&fcp->c_lock));
	FCERRPRINT(FDEP_L1, FDEM_CHEK,
	    (CE_CONT, "fdcheckdisk unit %d\n", unit));

	if (fcp->c_curpcyl[unit])
		newcyl = fcp->c_curpcyl[unit] - 1;
	else
		newcyl = 1;

	save_cmd = *csb->csb_cmd;
	save_cd1 = csb->csb_cmd[1];
	save_npcyl = csb->csb_npcyl;
	save_xstate = csb->csb_xstate;

	*csb->csb_cmd = FO_SEEK;
	csb->csb_cmd[1] = (uchar_t)unit;
	csb->csb_npcyl = (uchar_t)newcyl;
	fcp->c_flags |= FCFLG_WAITING;

	if (fcp->c_mtrstate[unit] != FMS_ON && fcp->c_motort[unit] != 0)
		/*
		 * wait for motor to get up to speed,
		 * and let motor_timer issue seek cmd
		 */
		csb->csb_xstate = FXS_DKCHGX;
	else {
		/*
		 * motor is up to speed; issue seek cmd now
		 */
		csb->csb_xstate = FXS_SEEK;
		if (rval = fdcseek(fcp, unit, newcyl)) {
			/*
			 * any recal/seek errors are too serious to attend to
			 */
			FCERRPRINT(FDEP_L3, FDEM_CHEK,
			    (CE_WARN, "fdcheckdisk err %d", rval));
			fcp->c_flags ^= FCFLG_WAITING;
		}
	}
	/*
	 * wait for completion interrupt
	 * XXX This should be backed up with a watchdog timer!
	 */
	while (fcp->c_flags & FCFLG_WAITING) {
		cv_wait(&fcp->c_iocv, &fcp->c_lock);
	}

	/*
	 * if disk change still asserted, no diskette in drive!
	 */
	if (rval = fdcsense_chng(fcp, unit)) {
		FCERRPRINT(FDEP_L3, FDEM_CHEK,
		    (CE_WARN, "fdcheckdisk no disk %d", unit));
	}

	*csb->csb_cmd = save_cmd;
	csb->csb_cmd[1] = save_cd1;
	csb->csb_npcyl = save_npcyl;
	csb->csb_xstate = save_xstate;
	return (rval);
}

static int
fdrecover(struct fdcntlr *fcp)
{
	struct fcu_obj *fjp;
	struct fdcsb *csb = &fcp->c_csb;
	int residual;
	int unit;
	char *failure;

	FCERRPRINT(FDEP_L2, FDEM_RECO,
	    (CE_NOTE, "fdrecover unit %d", csb->csb_drive));

	unit = csb->csb_drive;
	fjp = fcp->c_unit[unit];
	if (fcp->c_flags & FCFLG_TIMEOUT) {
		fcp->c_flags ^= FCFLG_TIMEOUT;
		csb->csb_rslt[1] |= 0x08;
		FCERRPRINT(FDEP_L3, FDEM_RECO,
		    (CE_WARN, "fd unit %d: %s timed out", csb->csb_drive,
		    fdcmds[*csb->csb_cmd & 0x1f].cmdname));
	}

	if (csb->csb_status & S0_SEKEND)
		fcp->c_curpcyl[unit] = -1;

	switch (csb->csb_oldxs) {
	case FXS_RCAL:		/* recalibrate */
	case FXS_SEEK:		/* seek */
	case FXS_RESET:		/* cntlr reset */
		FCERRPRINT(FDEP_L4, FDEM_RECO, (CE_WARN,
		    "fd unit %d: %s error: st0=0x%x pcn=%d", csb->csb_drive,
		    fdcmds[*csb->csb_cmd & 0x1f].cmdname,
		    *csb->csb_rslt, csb->csb_rslt[1]));
		if (csb->csb_retrys++ < skretry &&
		    !(csb->csb_opflags & CSB_OFRAWIOCTL))
			return (0);
		break;

	case FXS_RDID:		/* read ID */
		if (!(csb->csb_status & S0_SEKEND))
			csb->csb_xstate = FXS_HDST;
		/* FALLTHROUGH */
	case FXS_DOIT:		/* original operation */
	case FXS_DOWT:		/* waiting on operation */
		if (csb->csb_opflags & (CSB_OFDMARD | CSB_OFDMAWT)) {
			if (ddi_dmae_getcnt(fcp->c_dip, fcp->c_dmachan,
			    &residual) != DDI_SUCCESS)
				cmn_err(CE_WARN,
				    "fdc_recover: dmae getcnt failed, "
				    "dip %p dmachan %x residual %x",
				    (void*)fcp->c_dip, fcp->c_dmachan,
				    residual);
			FCERRPRINT(FDEP_L2, FDEM_RECO,
			    (CE_NOTE,
			    "fd unit %d: %s error: "
			    "dma count=0x%lx residual=0x%x",
			    csb->csb_drive,
			    fdcmds[*csb->csb_cmd & 0x1f].cmdname,
			    csb->csb_dmacookie.dmac_size, residual));
		}
		if (csb->csb_rslt[1] == S1_OVRUN)
			/*
			 * handle retries of over/underrun
			 * with a secondary retry counter
			 */
			if (++csb->csb_ourtrys <= OURUN_TRIES) {
				FCERRPRINT(FDEP_L2, FDEM_RECO,
				    (CE_NOTE,
				    "fd unit %d: %s error: over/under-run",
				    csb->csb_drive,
				    fdcmds[*csb->csb_cmd & 0x1f].cmdname));
				return (0);
			} else
				/*
				 * count 1 set of over/underruns
				 * as 1 primary retry effort
				 */
				csb->csb_ourtrys = 0;

		if ((fjp->fj_flags & (FUNIT_UNLABELED | FUNIT_LABELOK)) &&
		    !(csb->csb_opflags & CSB_OFRAWIOCTL)) {
			/*
			 * device is open so keep trying and
			 * gather statistics on errors
			 */
			if (csb->csb_rslt[1] & S1_CRCER)
				fcp->fdstats.de++;
			if (csb->csb_rslt[1] & S1_OVRUN)
				fcp->fdstats.run++;
			if (csb->csb_rslt[1] & (S1_NODATA | S1_MADMK))
				fcp->fdstats.bfmt++;
			if (csb->csb_rslt[1] & 0x08)
				fcp->fdstats.to++;

			/*
			 * if we have not run out of retries, return 0
			 */
			if (csb->csb_retrys++ < csb->csb_maxretry &&
			    (*csb->csb_cmd & ~FO_MFM) != FO_FRMT) {
				if (csb->csb_opflags &
				    (CSB_OFDMARD | CSB_OFDMAWT)) {
					FCERRPRINT(FDEP_L4, FDEM_RECO,
					    (CE_WARN,
					    "fd unit %d: %s error: "
					    "st0=0x%x st1=0x%x st2=0x%x",
					    csb->csb_drive,
					    fdcmds[*csb->csb_cmd &
					    0x1f].cmdname,
					    *csb->csb_rslt, csb->csb_rslt[1],
					    csb->csb_rslt[2]));
				}
				if ((csb->csb_retrys & 1) &&
				    csb->csb_xstate == FXS_END)
					csb->csb_xstate = FXS_DOIT;
				else if (csb->csb_retrys == 3)
					csb->csb_xstate = FXS_RESTART;
				return (0);
			}
			if (csb->csb_rslt[1] & S1_CRCER)
				failure = "crc error";
			else if (csb->csb_rslt[1] & S1_OVRUN)
				failure = "over/under-run";
			else if (csb->csb_rslt[1] & (S1_NODATA | S1_MADMK))
				failure = "bad format";
			else if (csb->csb_rslt[1] & 0x08)
				failure = "timeout";
			else
				failure = "failed";
			cmn_err(CE_NOTE, "!fd unit %d: %s %s (%x %x %x)",
			    csb->csb_drive,
			    fdcmds[*csb->csb_cmd & 0x1f].cmdname, failure,
			    *csb->csb_rslt, csb->csb_rslt[1], csb->csb_rslt[2]);
		} else {
			FCERRPRINT(FDEP_L2, FDEM_RECO,
			    (CE_NOTE, "fd unit %d: %s failed (%x %x %x)",
			    csb->csb_drive,
			    fdcmds[*csb->csb_cmd & 0x1f].cmdname,
			    *csb->csb_rslt, csb->csb_rslt[1],
			    csb->csb_rslt[2]));
		}
		break;

	default:
		FCERRPRINT(FDEP_L4, FDEM_RECO, (CE_WARN,
		    "fd unit %d: %s failed: st0=0x%x st1=0x%x st2=0x%x",
		    csb->csb_drive, fdcmds[*csb->csb_cmd & 0x1f].cmdname,
		    *csb->csb_rslt, csb->csb_rslt[1], csb->csb_rslt[2]));
		break;
	}
	return (1);
}


/*	Autovector Interrupt Entry Point	*/
static uint_t
fdc_intr(caddr_t arg)
{
	struct fdcntlr *fcp = (struct fdcntlr *)arg;
	struct fdcsb *csb;
	off_t off;
	size_t blklen;
	int drive;
	int newstate;
	int pendstate;
	int rval = DDI_DMA_DONE;
	int state;
	int maxspin = 10;

	csb = &fcp->c_csb;

	mutex_enter(&fcp->c_lock);
	if (fcp->c_suspended) {
		mutex_exit(&fcp->c_lock);
		return (DDI_INTR_UNCLAIMED);
	}

	/*
	 * Wait for the RQM bit to be set, or until we've tested it
	 * a bunch of times (which may imply this isn't our interrupt).
	 */
	state = inb(fcp->c_regbase + FCR_MSR);
	pendstate = state & (MS_RQM | MS_DIO | MS_CB);
	while (((pendstate & MS_RQM) == 0) && (maxspin-- > 0)) {
		/* Small pause in between reading the status port */
		drv_usecwait(10);
		/* Reread the status port */
		state = inb(fcp->c_regbase + FCR_MSR);
		pendstate = state & (MS_RQM | MS_DIO | MS_CB);
	}
	FCERRPRINT(FDEP_L0, FDEM_INTR,
	    (CE_CONT, "fdc_intr unit %d: xstate=%d MSR=0x%x\n",
	    csb->csb_drive, csb->csb_xstate, state));

	/*
	 * If there is an operation outstanding AND the controller is ready
	 * to receive a command or send us the result of a command (OR if the
	 * controller is ready to accept a new command), AND if
	 * someone has been waiting for a command to finish AND (if no unit
	 * is BUSY OR if the unit that we're waiting for is BUSY (i.e. it's in
	 * the middle of a seek/recalibrate)) then this interrupt is for us.
	 */
	if ((pendstate == (MS_RQM | MS_DIO | MS_CB) || pendstate == MS_RQM) &&
	    (fcp->c_flags & FCFLG_WAITING) &&
	    (!(state & 0x0f) || ((1 << csb->csb_drive) & state))) {
		/*
		 * Remove one of the conditions for entering this code.
		 * The state_machine will release the c_lock if it
		 * calls untimeout()
		 */
		fcp->c_flags ^= FCFLG_WAITING;

		if ((newstate = fdc_statemach(fcp)) == -1) {
			/* restore waiting flag */
			fcp->c_flags |= FCFLG_WAITING;
			mutex_exit(&fcp->c_lock);
			return (DDI_INTR_CLAIMED);
		}

		if (fcp->c_intrstat)
			KIOIP->intrs[KSTAT_INTR_HARD]++;
		if (newstate == FXS_END) {

			if (csb->csb_dmahandle && !csb->csb_cmdstat &&
				/*
				 * read/write operation may have multiple DMA
				 * cookies: process next one
				 */
			    ((csb->csb_dmacurrcookie <
			    (csb->csb_dmacookiecnt - 1)) ||
			    (csb->csb_dmacurrwin) < (csb->csb_dmawincnt - 1))) {
				/*
				 * read/write operation requires another
				 * DMA cookie: process next one
				 */

				if (++csb->csb_dmacurrcookie <
				    csb->csb_dmacookiecnt) {
					ddi_dma_nextcookie(csb->csb_dmahandle,
					    &csb->csb_dmacookie);
				} else if (++csb->csb_dmacurrwin <
				    csb->csb_dmawincnt) {
					if (ddi_dma_getwin(csb->csb_dmahandle,
					    csb->csb_dmacurrwin, &off, &blklen,
					    &csb->csb_dmacookie,
					    &csb->csb_dmacookiecnt) !=
					    DDI_SUCCESS) {
						cmn_err(CE_WARN,
						    "fdc_intr: "
						    "dma getwin failed");
					}
					csb->csb_dmacurrcookie = 0;
				}

				if (ddi_dmae_prog(fcp->c_dip, NULL,
				    &csb->csb_dmacookie, fcp->c_dmachan) !=
				    DDI_SUCCESS)
					cmn_err(CE_WARN,
					    "fdc_intr: dmae prog failed, "
					    "dip %p dmachannel %x",
					    (void*)fcp->c_dip,
					    fcp->c_dmachan);

				/*
				 * status of last operation has disk
				 * address for continuation
				 */
				csb->csb_cmd[2] = csb->csb_rslt[3];
				csb->csb_cmd[3] = csb->csb_rslt[4];
				csb->csb_cmd[4] = csb->csb_rslt[5];
				csb->csb_cmd[1] = (csb->csb_cmd[1] & ~0x04) |
				    (csb->csb_cmd[3] << 2);

				csb->csb_xstate = FXS_START;
				(void) fdc_statemach(fcp);
				/*
				 * Ignored return.  If failed, warning already
				 * posted.  Returned state irrelevant.
				 */
				/* restore waiting flag */
				fcp->c_flags |= FCFLG_WAITING;
				goto fi_exit;
			}
			if (rval != DDI_DMA_DONE)
				csb->csb_cmdstat = EIO;
			/*
			 * somebody's waiting for completion of fdcntlr/csb,
			 * wake them
			 */
			cv_signal(&fcp->c_iocv);
		}
		else
			/* restore waiting flag */
			fcp->c_flags |= FCFLG_WAITING;
fi_exit:
		mutex_exit(&fcp->c_lock);
		return (DDI_INTR_CLAIMED);
	}

	if (state & MS_RQM) {
		(void) fdcsense_int(fcp, &drive, NULL);
		/*
		 * Ignored return - senser state already saved
		 */
		FCERRPRINT(FDEP_L4, FDEM_INTR,
		    (CE_WARN, "fdc_intr unit %d: nobody sleeping 0x%x",
		    drive, state));
	} else {
		FCERRPRINT(FDEP_L4, FDEM_INTR,
		    (CE_WARN, "fdc_intr: nobody sleeping on %d 0x%x",
		    csb->csb_drive, state));
	}
	/*
	 * This should probably be protected, but, what the
	 * heck...the cost isn't worth the accuracy for this
	 * statistic.
	 */
	if (fcp->c_intrstat)
		KIOIP->intrs[KSTAT_INTR_SPURIOUS]++;
	mutex_exit(&fcp->c_lock);
	return (DDI_INTR_UNCLAIMED);
}

/*
 * fdwatch
 *	is called from timeout() when a floppy operation timer has expired.
 */
static void
fdwatch(void *arg)
{
	struct fdcntlr *fcp = (struct fdcntlr *)arg;
	struct fdcsb *csb;

	mutex_enter(&fcp->c_lock);

	if (fcp->c_timeid == 0) {
		/*
		 * fdc_intr got here first, ergo, no timeout condition..
		 */
		mutex_exit(&fcp->c_lock);
		return;
	}

	if (fcp->c_flags & FCFLG_WAITING) {
		if (ddi_dmae_stop(fcp->c_dip, fcp->c_dmachan) != DDI_SUCCESS)
			cmn_err(CE_WARN, "fdwatch: dmae stop failed, "
			    "dip %p, dmachan %x",
			    (void*)fcp->c_dip, fcp->c_dmachan);
		csb = &fcp->c_csb;
		FCERRPRINT(FDEP_L3, FDEM_WATC,
		    (CE_WARN, "fdcwatch unit %d: xstate = %d",
		    csb->csb_drive, csb->csb_xstate));
		drv_usecwait(50);

		if (inb(fcp->c_regbase + FCR_MSR) != MS_RQM) {
			/*
			 * cntlr is still busy, so reset it
			 */
			csb->csb_xstate = FXS_KILL;
			(void) fdc_statemach(fcp);
			/*
			 * Ignored return.  If failed, warning already
			 * posted.  Returned state irrelevant.
			 */
		} else {
			csb->csb_xstate = FXS_END;
			fcp->c_timeid = 0;
			fcp->c_flags ^= FCFLG_WAITING;
			cv_signal(&fcp->c_iocv);
		}
		csb->csb_cmdstat = EIO;
		fcp->c_flags |= FCFLG_TIMEOUT;
	} else {
		FCERRPRINT(FDEP_L4, FDEM_INTR,
		    (CE_WARN, "fdcwatch: not sleeping for unit %d",
		    fcp->c_csb.csb_drive));
	}
	if (fcp->c_intrstat)
		KIOIP->intrs[KSTAT_INTR_WATCHDOG]++;
	mutex_exit(&fcp->c_lock);
}


static int
fdc_statemach(struct fdcntlr *fcp)
{
	struct fcu_obj *fjp;
	struct fdcsb *csb = &fcp->c_csb;
	int backoff;
	clock_t time;
	int unit;

	ASSERT(MUTEX_HELD(&fcp->c_lock));

	unit = csb->csb_drive;
	fjp = fcp->c_unit[unit];

	csb->csb_oldxs = csb->csb_xstate;
	switch (csb->csb_xstate) {

	case FXS_START:		/* start of operation */
		ASSERT(fcp->c_timeid == 0);
		time = drv_usectohz(100000 * (unsigned int)csb->csb_timer);
		if (time == 0)
			time = drv_usectohz(2000000);
		fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);

		if (fcp->c_mtrstate[unit] == FMS_START) {
			/*
			 * wait for motor to get up to speed
			 */
			csb->csb_xstate = FXS_MTRON;
			break;
		}
		/* FALLTHROUGH */

	case FXS_MTRON:		/* motor is at speed */
		if (fcp->c_mtrstate[unit] != FMS_ON) {
			/* how did we get here ?? */
			cmn_err(CE_WARN, "fdc: selected but motor off");
			return (-1);
		}
		if (fcp->c_curpcyl[unit] != -1 && *csb->csb_cmd != FO_RECAL)
			goto nxs_seek;
		recalcmd[1] = (uchar_t)unit;
		if (fdc_docmd(fcp, recalcmd, 2) == -1) {
			/* cntlr did not accept command bytes */
			fdcquiesce(fcp);
			csb->csb_cmdstat = EIO;
			csb->csb_xstate = FXS_RESET;
			break;
		}
		fcp->c_sekdir[unit] = 0;
		csb->csb_xstate = FXS_RCAL;
		break;

	case FXS_RCAL:		/* forced recalibrate is complete */
#if 0	/* #ifdef _VPIX */
	/* WARNING: this code breaks SPARC compatibility */
		if (csb->csb_opflags & CSB_OFRAWIOCTL &&
		    *csb->csb_cmd == FO_RECAL) {
			fcp->c_curpcyl[unit] = 0;
			csb->csb_status = 0;
			goto nxs_cmpl;
		}
#endif
		(void) fdc_docmd(fcp, &senseintcmd, 1);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 * fdc_results retrieves the controller/drive status
		 */
		(void) fdc_result(fcp, csb->csb_rslt, 2);
		/*
		 * Ignored return. If failed, warning was issued by fdc_result.
		 * Actual results checked below
		 */
		if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
		    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0) {
			FCERRPRINT(FDEP_L3, FDEM_EXEC,
			    (CE_WARN, "fdc_statemach unit %d: recal result %x",
			    csb->csb_drive, *csb->csb_rslt));
			fdcquiesce(fcp);
			csb->csb_cmdstat = EIO;
			csb->csb_xstate = FXS_RESET;
			break;
		}
		if (unit != (*csb->csb_rslt & 3) || csb->csb_rslt[1]) {
			csb->csb_status = S0_SEKEND;
			goto nxs_cmpl;
		}
		fcp->c_curpcyl[unit] = csb->csb_rslt[1];
		if (*csb->csb_cmd == FO_RECAL)
			goto nxs_cmpl;
nxs_seek:
		if (*csb->csb_cmd != FO_SEEK &&
		    csb->csb_npcyl == fcp->c_curpcyl[unit])
			goto nxs_doit;
		fcp->c_sekdir[unit] = csb->csb_npcyl - fcp->c_curpcyl[unit];
		/* FALLTHROUGH */

	case FXS_DKCHGX:	/* reset Disk-Change latch */
		(void) fdcseek(fcp, csb->csb_cmd[1], csb->csb_npcyl);
		/*
		 * Ignored return.  If command rejected, warnig already posted
		 * by fdc_docmd().
		 */
		csb->csb_xstate = FXS_SEEK;
		break;

	case FXS_RESTART:	/* special restart of read/write operation */
		ASSERT(fcp->c_timeid == 0);
		time = drv_usectohz(100000 * csb->csb_timer);
		if (time == 0)
			time = drv_usectohz(2000000);
		fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);

		if (fcp->c_mtrstate[unit] != FMS_ON) {
			cmn_err(CE_WARN, "fdc: selected but motor off");
			return (-1);
		}
		if ((csb->csb_npcyl == 0 || fcp->c_sekdir[unit] >= 0) &&
		    (int)csb->csb_cmd[2] < (fjp->fj_chars->fdc_ncyl - 1))
			backoff = csb->csb_npcyl + 1;
		else
			backoff = csb->csb_npcyl - 1;
		(void) fdcseek(fcp, csb->csb_cmd[1], backoff);
		/*
		 * Ignored return.  If command rejected, warnig already posted
		 * by fdc_docmd().
		 */
		csb->csb_xstate = FXS_RESEEK;
		break;

	case FXS_RESEEK:	/* seek to backoff-cyl complete */
		(void) fdc_docmd(fcp, &senseintcmd, 1);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 * fdc_results retrieves the controller/drive status
		 */
		(void) fdc_result(fcp, csb->csb_rslt, 2);
		/*
		 * Ignored return. If failed, warning was issued by fdc_result.
		 * Actual results checked below
		 */
		if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
		    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0)
			goto nxs_cmpl;
		(void) fdcseek(fcp, csb->csb_cmd[1], csb->csb_npcyl);
		/*
		 * Ignored return.  If command rejected, warnig already posted
		 * by fdc_docmd().
		 */
		csb->csb_xstate = FXS_SEEK;
		break;

	case FXS_SEEK:		/* seek complete */
#if 0	/* #ifdef _VPIX */
	/* WARNING: this code breaks SPARC compatibility and */
	/* rawioctls in fdformat */
		if (csb->csb_opflags & CSB_OFRAWIOCTL) {
			fcp->c_curpcyl[unit] = csb->csb_npcyl;
			csb->csb_status = 0;
			goto nxs_cmpl;
		}
#endif
		(void) fdc_docmd(fcp, &senseintcmd, 1);
		/*
		 * Ignored return. If failed, warning was issued by fdc_docmd.
		 * fdc_results retrieves the controller/drive status
		 */
		(void) fdc_result(fcp, csb->csb_rslt, 2);
		/*
		 * Ignored return. If failed, warning was issued by fdc_result.
		 * Actual results checked below
		 */
		if ((csb->csb_status = ((*csb->csb_rslt ^ S0_SEKEND) &
		    (S0_ICMASK | S0_SEKEND | S0_ECHK | S0_NOTRDY))) != 0)
			goto nxs_cmpl;
		if (unit != (*csb->csb_rslt & 3) ||
		    csb->csb_rslt[1] != csb->csb_npcyl) {
			csb->csb_status = S0_SEKEND;
			goto nxs_cmpl;
		};
		fcp->c_curpcyl[unit] = csb->csb_rslt[1];
		/* use motor_timer to delay for head settle */
		mutex_enter(&fcp->c_dorlock);
		(void) fdc_motorsm(fjp, FMI_DELAYCMD,
		    fjp->fj_drive->fdd_headsettle / 1000);
		/*
		 * Return value ignored - fdcmotort deals with failure.
		 */
		mutex_exit(&fcp->c_dorlock);
		csb->csb_xstate = FXS_HDST;
		break;

	case FXS_HDST:		/* head settle */
		if (*csb->csb_cmd == FO_SEEK)
			goto nxs_cmpl;
		if ((*csb->csb_cmd & ~FO_MFM) == FO_FRMT)
			goto nxs_doit;
		fdcreadid(fcp, csb);
		csb->csb_xstate = FXS_RDID;
		break;

	case FXS_RDID:		/* read ID complete */
		(void) fdc_result(fcp, csb->csb_rslt, 7);
		/*
		 * Ignored return. If failed, warning was issued by fdc_result.
		 * Actual results checked below
		 */
		if ((csb->csb_status = (*csb->csb_rslt &
		    (S0_ICMASK | S0_ECHK | S0_NOTRDY))) != 0)
			goto nxs_cmpl;
		if (csb->csb_cmd[2] != csb->csb_rslt[3]) {
			/* at wrong logical cylinder */
			csb->csb_status = S0_SEKEND;
			goto nxs_cmpl;
		};
		goto nxs_doit;

	case FXS_DOIT:		/* do original operation */
		ASSERT(fcp->c_timeid == 0);
		time = drv_usectohz(100000 * csb->csb_timer);
		if (time == 0)
			time = drv_usectohz(2000000);
		fcp->c_timeid = timeout(fdwatch, (void *)fcp, time);
nxs_doit:
		if (fdc_docmd(fcp, csb->csb_cmd, csb->csb_ncmds) == -1) {
			/* cntlr did not accept command bytes */
			fdcquiesce(fcp);
			csb->csb_xstate = FXS_RESET;
			csb->csb_cmdstat = EIO;
			break;
		}
		csb->csb_xstate = FXS_DOWT;
		break;

	case FXS_DOWT:		/* operation complete */
		(void) fdc_result(fcp, csb->csb_rslt, csb->csb_nrslts);
		/*
		 * Ignored return. If failed, warning was issued by fdc_result.
		 * Actual results checked below.
		 */
		if (*csb->csb_cmd == FO_SDRV) {
			csb->csb_status =
			    (*csb->csb_rslt ^ (S3_DRRDY | S3_2SIDE)) &
			    ~(S3_HEAD | S3_UNIT);
		} else {
			csb->csb_status = *csb->csb_rslt &
			    (S0_ICMASK | S0_ECHK | S0_NOTRDY);
		}
nxs_cmpl:
		if (csb->csb_status)
			csb->csb_cmdstat = EIO;
		csb->csb_xstate = FXS_END;

		/*  remove watchdog timer if armed and not already triggered */
		if (fcp->c_timeid != 0) {
			timeout_id_t timeid;
			timeid = fcp->c_timeid;
			fcp->c_timeid = 0;
			mutex_exit(&fcp->c_lock);
			(void) untimeout(timeid);
			mutex_enter(&fcp->c_lock);
		}
		break;

	case FXS_KILL:		/* quiesce cntlr by reset */
		fdcquiesce(fcp);
		fcp->c_timeid = timeout(fdwatch, (void *)fcp,
		    drv_usectohz(2000000));
		csb->csb_xstate = FXS_RESET;
		break;

	case FXS_RESET:		/* int from reset */
		for (unit = 0; unit < NFDUN; unit++) {
			(void) fdcsense_int(fcp, NULL, NULL);
			fcp->c_curpcyl[unit] = -1;
		}
		if (fcp->c_timeid != 0) {
			timeout_id_t timeid;
			timeid = fcp->c_timeid;
			fcp->c_timeid = 0;
			mutex_exit(&fcp->c_lock);
			(void) untimeout(timeid);
			mutex_enter(&fcp->c_lock);
		}
		csb->csb_xstate = FXS_END;
		break;

	default:
		cmn_err(CE_WARN, "fdc: statemach, unknown state");
		return (-1);
	}
	FCERRPRINT(FDEP_L1, FDEM_EXEC,
	    (CE_CONT, "fdc_statemach unit %d: %d -> %d\n",
	    csb->csb_drive, csb->csb_oldxs, csb->csb_xstate));
	return (csb->csb_xstate);
}


/*
 * routine to program a command into the floppy disk controller.
 */
int
fdc_docmd(struct fdcntlr *fcp, uchar_t *oplistp, uchar_t count)
{
	int ntries;

	ASSERT(count >= 1);
	FCERRPRINT(FDEP_L0, FDEM_EXEC,
	    (CE_CONT, "fdc_docmd: %x %x %x %x %x %x %x %x %x\n",
	    oplistp[0], oplistp[1], oplistp[2], oplistp[3], oplistp[4],
	    oplistp[5], oplistp[6], oplistp[7], oplistp[8]));

	do {
		ntries = FDC_RQM_RETRY;
		do {
			if ((inb(fcp->c_regbase + FCR_MSR) & (MS_RQM|MS_DIO))
			    == MS_RQM)
				break;
			else
				drv_usecwait(1);
		} while (--ntries);
		if (ntries == 0) {
			FCERRPRINT(FDEP_L3, FDEM_EXEC,
			    (CE_WARN, "fdc_docmd: ctlr not ready"));
			return (-1);
		}
		outb(fcp->c_regbase + FCR_DATA, *oplistp++);
		drv_usecwait(16);	/* See comment in fdc_result() */
	} while (--count);
	return (0);
}


/*
 * Routine to return controller/drive status information.
 * The diskette-controller data-register is read the
 * requested number of times and the results are placed in
 * consecutive memory locations starting at the passed
 * address.
 */
int
fdc_result(struct fdcntlr *fcp, uchar_t *rsltp, uchar_t rcount)
{
	int ntries;
	uchar_t *abresultp = rsltp;
	uchar_t stat;
	int laxative = 7;

	ntries = 10 * FDC_RQM_RETRY;
	do {
		do {
			if ((inb(fcp->c_regbase + FCR_MSR) &
			    (MS_RQM | MS_DIO)) == (MS_RQM | MS_DIO))
				break;
			else
				drv_usecwait(10);
		} while (--ntries);
		if (!ntries) {
			FCERRPRINT(FDEP_L3, FDEM_EXEC,
			    (CE_WARN, "fdc_result: ctlr not ready"));
			return (-2);
		}
		*rsltp++ = inb(fcp->c_regbase + FCR_DATA);

		/*
		 * The PRM suggests waiting for 14.5 us.
		 * Adding a bit more to cover the case of bad calibration
		 * of drv_usecwait().
		 */
		drv_usecwait(16);
		ntries = FDC_RQM_RETRY;
	} while (--rcount);
	while ((inb(fcp->c_regbase + FCR_MSR) & MS_CB) && laxative--) {
		FCERRPRINT(FDEP_L3, FDEM_EXEC,
		    (CE_WARN, "fdc_result: ctlr still busy"));
		/*
		 * try to complete Result phase by purging
		 * result bytes queued for reading
		 */
		*abresultp = S0_IVCMD;
		do {
			stat = inb(fcp->c_regbase + FCR_MSR) &
			    (MS_RQM | MS_DIO);
			if (stat == MS_RQM) {
				/*
				 * Result phase is complete
				 * but did we get the results corresponding to
				 * the command we think we executed?
				 */
				return (-1);
			}
			if (stat == (MS_RQM | MS_DIO))
				break;
			else
				drv_usecwait(10);
		} while (--ntries);
		if (!ntries || !laxative) {
			FCERRPRINT(FDEP_L3, FDEM_EXEC,
			    (CE_WARN,
			    "fdc_result: ctlr still busy and not ready"));
			return (-3);
		}
		(void) inb(fcp->c_regbase + FCR_DATA);

		drv_usecwait(16);	/* See comment above */
		ntries = FDC_RQM_RETRY;
	}
	return (0);
}

/*
 *  Function: get_unit()
 *
 *  Assumptions:  ioaddr is either 0x3f0 or 0x370
 */
static int
get_unit(dev_info_t *dip, int *cntrl_num)
{
	int ioaddr;

	if (get_ioaddr(dip, &ioaddr) != DDI_SUCCESS)
		return (DDI_FAILURE);

	switch (ioaddr) {
	case 0x3f0:
		*cntrl_num = 0;
		break;

	case 0x370:
		*cntrl_num = 1;
		break;

	default:
		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

static int
get_ioaddr(dev_info_t *dip, int *ioaddr)
{
	int reglen, nregs, i;
	int status = DDI_FAILURE;
	struct {
		int bustype;
		int base;
		int size;
	} *reglist;

	if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
	    "reg", (caddr_t)&reglist, &reglen) != DDI_PROP_SUCCESS) {
		cmn_err(CE_WARN, "fdc: reg property not found");
		return (DDI_FAILURE);
	}

	nregs = reglen / sizeof (*reglist);
	for (i = 0; i < nregs; i++) {
		if (reglist[i].bustype == 1) {
			*ioaddr = reglist[i].base;
			status = DDI_SUCCESS;
			break;
		}
	}
	kmem_free(reglist, reglen);

	if (status == DDI_SUCCESS) {
		if (*ioaddr == 0x3f2 || *ioaddr == 0x372) {
			/*
			 * Some BIOS's (ASUS is one) don't include first
			 * two IO ports in the floppy controller resources.
			 */

			*ioaddr -= 2; /* step back to 0x3f0 or 0x370 */

			/*
			 * It would be nice to update the regs property as well
			 * so device pathname contains 3f0 instead of 3f2, but
			 * updating the regs now won't have this effect as that
			 * component of the device pathname has already been
			 * constructed by the ISA nexus driver.
			 *
			 * reglist[i].base -= 2;
			 * reglist[i].size += 2;
			 * dev = makedevice(ddi_driver_major(dip), 0);
			 * ddi_prop_update_int_array(dev, dip, "reg",
			 *    (int *)reglist, reglen / sizeof (int));
			 */
		}
	}

	return (status);
}