/*
 * Sample dma driver for Solaris 2.1
 */

/*
 * This file is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify this file without charge, but are not authorized to
 * license or distribute it to anyone else except as part of a product
 * or program developed by the user.
 *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 * This file is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 *
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS FILE
 * OR ANY PART THEREOF.
 *
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even
 * if Sun has been advised of the possibility of such damages.
 *
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

#pragma	ident	"@(#)dma.c 1.3	94/06/22 SMI"

/*
 * This driver is for a fictional device that is capable of doing DMA.
 *
 * The device has three registers (see dma_reg.h):
 *  - a control/status register (CSR) for control and status bits
 *  - a device address register, to specify the data offset within
 *    the device's internal address space
 *  - a transfer length register.
 * It is assumed that the registers start at "ioaddr" as given in
 * the dma.conf file, that they are all two bytes wide, and are
 * contiguous in x86 I/O space.
 *
 * The device address and transfer length registers are set to program
 * the area of the device involved in DMA transfers, and a bit in the CSR
 * is set to start the I/O, which uses the system's DMA engine.
 *
 * By not specifying the DDI_DMA_PARTIAL flag in the call to
 * ddi_dma_setup, we insure that all transfers requested here will fit
 * within one DMA "window" to simplify the looping code.  The fictional
 * dma device has no DMA hardware of its own, so we must use the system
 * board's DMA hardware via the ddi_dmae(9F) functions.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/uio.h>
#include <sys/map.h>
#include <sys/debug.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/dma_engine.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

#include "dma_io.h"
#include "dma_reg.h"

static	void *state_head;	/* opaque handle top of state structs */

/*
 * These are the entry points into our driver that are called when the
 * driver is loaded, during a system call, or in response to an interrupt.
 */
static	int	dma_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
		    void **result);
static	int	dma_identify(dev_info_t *dip);
static	int	dma_probe(dev_info_t *dip);
static	int	dma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static	int	dma_open(dev_t *dev, int openflags, int otyp, cred_t *credp);
static	int	dma_close(dev_t dev, int openflags, int otyp, cred_t *credp);
static	int	dma_read(dev_t dev, struct uio *uiop, cred_t *credp);
static	int	dma_write(dev_t dev, struct uio *uiop, cred_t *credp);
static	int	dma_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp,
		    int *rvalp);
static	u_int	dma_intr();
static	int	dma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);

/*
 * other driver routines
 */
static	int	dma_strategy(register struct buf *bp);
static	void	dma_minphys(struct buf *bp);

/*
 * When our driver is loaded or unloaded, the system calls our _init or
 * _fini routine with a modlinkage structure.  The modlinkage structure
 * contains:
 *
 *	modlinkage->
 *		modldrv->
 *			dev_ops->
 * 				cb_ops
 *
 * cb_ops contains the normal driver entry points and replaces
 * the cdevsw & bdevsw structures in previous releases.
 *
 * dev_ops contains, in addition to the pointer to cb_ops, the routines
 * that support loading and unloading our driver.
 */
static struct cb_ops	dma_cb_ops = {
	dma_open,
	dma_close,
	nodev,		/* not a block driver, strategy not an entry point */
	nodev,		/* no print routine */
	nodev,		/* no dump routine */
	dma_read,
	dma_write,
	dma_ioctl,
	nodev,		/* no devmap routine */
	nodev,		/* no mmap routine */
	nodev,		/* no segmap routine */
	nochpoll,	/* no chpoll routine */
	ddi_prop_op,
	0,		/* not a STREAMS driver, no cb_str routine */
	D_NEW | D_MP,	/* safe for multi-thread/multi-processor */
};

static struct dev_ops dma_ops = {
	DEVO_REV,		/* DEVO_REV indicated by manual	*/
	0,			/* device reference count	*/
	dma_getinfo,
	dma_identify,
	dma_probe,
	dma_attach,
	dma_detach,
	nodev,			/* device reset routine		*/
	&dma_cb_ops,
	(struct bus_ops *)0,	/* bus operations		*/
};

extern	struct	mod_ops mod_driverops;
static	struct modldrv modldrv = {
	&mod_driverops,
	"Sample DMA driver, version @(#)dma.c	1.2",
	&dma_ops,
};

static	struct modlinkage modlinkage = {
	MODREV_1,		/* MODREV_1 indicated by manual */
	(void *)&modldrv,
	NULL,			/* termination of list of linkage structures */
};

static ddi_dma_lim_t dma_lim;

/*
 * _init, _info, and _fini support loading and unloading the driver.
 */
int
_init(void)
{
	register int	error;

	if ((error = ddi_soft_state_init(&state_head, sizeof (Dma), 1)) != 0)
		return (error);

	if ((error = mod_install(&modlinkage)) != 0)
		ddi_soft_state_fini(&state_head);

	return (error);
}

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

int
_fini(void)
{
	int status;

	if ((status = mod_remove(&modlinkage)) != 0)
		return (status);

	ddi_soft_state_fini(&state_head);

	return (status);
}

/*
 * When our driver is loaded, dma_identify() is called with a dev_info_t
 * full of information from the hardware configuration file.
 */
static int
dma_identify(dev_info_t *dip)
{
	char *dev_name;
	dev_name = ddi_get_name(dip);

	if (strcmp(dev_name, DMA_NAME) == 0) {
		return (DDI_IDENTIFIED);
	} else {
		return (DDI_NOT_IDENTIFIED);
	}
}

/*ARGSUSED*/
static int
dma_probe(dev_info_t *dip)
{
	/*
	 * We have no way to check if the dma device actually exists,
	 * so return DONTCARE, which will cause the system to proceed
	 * as if the device *does* exist.
	 */
	return (DDI_PROBE_DONTCARE);
}

/*
 * This is a pretty generic getinfo routine as described in the manual.
 */
/*ARGSUSED*/
static int
dma_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	register int 	error;
	register Dma	*dma_p;

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		dma_p = (Dma *)ddi_get_soft_state(state_head,
		    getminor((dev_t)arg));
		if (dma_p == NULL) {
			error = DDI_FAILURE;
		} else {
			*result = dma_p->dip;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)getminor((dev_t)arg);
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}

	return (error);
}

/*
 * dma_attach gets called if dma_identify and dma_probe succeed
 *
 * This is a very paranoid attach routine.  We take all the knowledge we
 * have about our board and check it against what has been filled in for
 * us from our FCode.
 */
static int
dma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int			instance;
	register Dma		*dma_p;
	int			error;

	if (cmd != DDI_ATTACH)
		return (DDI_FAILURE);

	instance = ddi_get_instance(dip);

	if (ddi_soft_state_zalloc(state_head, instance) != 0)
		return (DDI_FAILURE);

	dma_p = (Dma *)ddi_get_soft_state(state_head, instance);
	dma_p->attach_flags |= SOFT_STATE_ALLOCATED;
	dma_p->dip = dip;

	/*
	 * interrupt number is 0 (we only use one interrupt level)
	 * ddi_idevice_cookie_t is not used
	 * a magic cookie for later use in MUTEXes will be returned in
	 * 	dma_p->iblock_cookie
	 * if we were truly paranoid we would use ddi_dev_nintrs() to check
	 *	things out
	 * Don't try to handle hi-level interrupts
	 */
	if (ddi_intr_hilevel(dip, 0) ||
	    ddi_add_intr(dip, 0, &dma_p->iblock_cookie,
	    (ddi_idevice_cookie_t *)NULL, dma_intr,
	    (caddr_t)dma_p) != DDI_SUCCESS) {
		goto bailout;
	} else {
		dma_p->attach_flags |= INTERRUPT_ADDED;
	}

	/*
	 * initialize MUTEX; we will use this MUTEX later on to lock
	 * our instance structure
	 */
	mutex_init(&dma_p->mutex, "dma mutex",
		MUTEX_DRIVER, (void *)&dma_p->iblock_cookie);
	dma_p->attach_flags |= MUTEX_ADDED;

	/*
	 * we have a CSR, device address reg, and a count reg;
	 * CSR is at 'ioaddr', and address and count regs follow
	 */

	error = ddi_getprop(DDI_DEV_T_ANY, dip, 0, "ioaddr", -1);
	if (error == -1)
		goto bailout;
	dma_p->csr = (Dma_csr)error;
	dma_p->addr = dma_p->csr + sizeof (dma_p->csr);
	dma_p->count = dma_p->addr + sizeof (dma_p->addr);

	/*
	 * Get our dmachan from the dev node property; die if not there.
	 */
	dma_p->dmachan = ddi_getprop(DDI_DEV_T_ANY, dip, 0, "dmachan", -1);
	if (dma_p->dmachan == -1)
		goto bailout;

	/*
	 * ddi_create_minor_node creates an entry in an internal kernel
	 * table; the actual entry in the file system is created by
	 * drvconfig(1) when you run add_drv(1);
	 */
	if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, instance,
	    NULL, 0) == DDI_FAILURE) {
		ddi_remove_minor_node(dip, NULL);
		goto bailout;
	} else {
		dma_p->attach_flags |= MINOR_NODE_CREATED;
	}

	/*
	 * reset device (including disabling interrupts)
	 */
	outw(dma_p->csr, DMA_RESET);

	/* Fill in DMA limit structure from our bus-nexus parent */
	if (ddi_dmae_getlim(dip, &dma_lim) != 0)
		return (DDI_FAILURE);

	/* We don't support EISA DMA chaining; set sgllen to 1 */
	dma_lim.dlim_sgllen = 1;

	ddi_report_dev(dip);

	return (DDI_SUCCESS);

/* Unsuccessful - undo anything we did and return failure */

bailout:
	if (dma_p->attach_flags & SOFT_STATE_ALLOCATED)
		ddi_soft_state_free(state_head, instance);
	if (dma_p->attach_flags & INTERRUPT_ADDED)
		ddi_remove_intr(dma_p->dip, 0, dma_p->iblock_cookie);
	if (dma_p->attach_flags & MUTEX_ADDED)
		mutex_destroy(&dma_p->mutex);
	if (dma_p->attach_flags & MINOR_NODE_CREATED)
		ddi_remove_minor_node(dip, NULL);

	return (DDI_FAILURE);
}

/*
 * When our driver is unloaded, dma_detach cleans up and frees the resources
 * we allocated in dma_attach.
 */
static int
dma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	register Dma	*dma_p;	/* will point to this */
	int		instance;

	if (cmd != DDI_DETACH)
		return (DDI_FAILURE);

	instance = ddi_get_instance(dip);
	dma_p = (Dma *)ddi_get_soft_state(state_head, instance);

	/*
	 * turn off the device (including interrupts)
	 */
	outw(dma_p->csr, DMA_RESET);

	/*
	 * Remove the minor node created in attach
	 */
	ddi_remove_minor_node(dip, NULL);


	/*
	 * deallocate resources from attach
	 */
	ddi_remove_intr(dma_p->dip, 0, dma_p->iblock_cookie);
	mutex_destroy(&dma_p->mutex);
	ddi_soft_state_free(state_head, instance);

	return (DDI_SUCCESS);
}

/*
 * dma_open is called in response to the open(2) system call
 */
/*ARGSUSED*/
static	int
dma_open(dev_t *dev, int openflags, int otyp, cred_t *credp)
{
	int		retval = 0;
	register Dma	*dma_p;

	dma_p = (Dma *)ddi_get_soft_state(state_head, getminor(*dev));

	/*
	 * Verify instance structure
	 */
	if (dma_p == NULL)
		return (ENXIO);

	/*
	 * Verify we are being opened as a character device
	 */
	if (otyp != OTYP_CHR)
		return (EINVAL);

	/*
	 * lock control status register and exclusive open flag
	 */
	mutex_enter(&dma_p->mutex);			/* start MUTEX */

	/*
	 * Only allow a single open
	 */
	if (dma_p->flags & DMA_DEV_OPEN) {
		retval = EBUSY;
	} else {
		/*
		 * mark device as open
		 */
		dma_p->flags |= DMA_DEV_OPEN;

		/*
		 * start device except for interrupts
		 */
		outw(dma_p->csr, (inw(dma_p->csr) | DMA_DEV_ENABLE));
	}

	mutex_exit(&dma_p->mutex);			/* end MUTEX */

	return (retval);
}

/*
 * dma_close is called after the last process that has the device open
 * calls close(2)
 *
 * since we enforce exclusive open, dma_close will always be called when
 * the user process calls close(2)
 */
/*ARGSUSED*/
static	int
dma_close(dev_t dev, int openflags, int otyp, cred_t *credp)
{
	register Dma	*dma_p;

	dma_p = (Dma *)ddi_get_soft_state(state_head, getminor(dev));

	/*
	 * lock control status register and exclusive open flag
	 */
	mutex_enter(&dma_p->mutex);			/* start MUTEX */

	/*
	 * let the next person open the device
	 */
	dma_p->flags &= ~DMA_DEV_OPEN;

	/*
	 * shut down the device
	 */
	outw(dma_p->csr, DMA_RESET);

	mutex_exit(&dma_p->mutex);			/* end MUTEX */

	return (0);
}

/*ARGSUSED*/
static	int
dma_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
	return (physio(dma_strategy, (struct buf *)NULL, dev, B_READ,
		dma_minphys, uiop));
}

/*ARGSUSED*/
static	int
dma_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
	return (physio(dma_strategy, (struct buf *)NULL, dev, B_WRITE,
		dma_minphys, uiop));
}

/*ARGSUSED*/
static	int
dma_strategy(struct buf *bp)
{
	Dma			*dma_p;
	ddi_dma_cookie_t	dma_cookie;
	int			flags;
	off_t			offset, len;

	dma_p = (Dma *)ddi_get_soft_state(state_head, getminor(bp->b_edev));

	/*
	 * lock control/status, address, & count registers
	 */
	mutex_enter(&dma_p->mutex);			/* start MUTEX */

	/*
	 * wait for other I/O, if any, to complete; then set the busy flag
	 */
	while (dma_p->flags & DMA_DEV_BUSY) {
		cv_wait(&dma_p->cv, &dma_p->mutex);
	}
	dma_p->flags |= DMA_DEV_BUSY;

	/*
	 * drop MUTEX now because ddi_dma_buf_setup may go to sleep and
	 * we don't want to sleep while holding the mutex
	 */
	mutex_exit(&dma_p->mutex);			/* end MUTEX */

	/*
	 * allocate DMA resources
	 */
	flags = DDI_DMA_READ;
	if (bp->b_flags & B_WRITE) flags = DDI_DMA_WRITE;
	if (ddi_dma_buf_setup(dma_p->dip, bp, flags, DDI_DMA_SLEEP, NULL,
		&dma_lim, dma_p->handle) != DDI_DMA_MAPPED) {
		bp->b_error |= EIO;
		bp->b_flags |= B_ERROR;
		biodone(bp);
		mutex_enter(&dma_p->mutex);
		dma_p->flags &= ~DMA_DEV_BUSY;
		cv_signal(&dma_p->cv);
		mutex_exit(&dma_p->mutex);
		return (0);
	}

	/*
	 * Get first DMA window and segment; save in *dma_p
	 */

	(void) ddi_dma_nextwin(dma_p->handle, (ddi_dma_win_t)NULL,
	    &dma_p->dmawin);
	(void) ddi_dma_nextseg(dma_p->dmawin, (ddi_dma_seg_t)NULL,
	    &dma_p->dmaseg);

	/*
	 * The variables offset and len will be useful for programming
	 * the device.  Len is also inside the cookie, but we're using the
	 * ddi_dmae_prog() function to program the system DMA controller,
	 * so we needn't interpret the cookie at all.
	 */

	if (ddi_dma_segtocookie(dma_p->dmaseg, &offset, &len, &dma_cookie)
	    != DDI_SUCCESS) {
		bp->b_error |= EIO;
		bp->b_flags |= B_ERROR;
		biodone(bp);
		(void) ddi_dma_free(dma_p->handle);
		mutex_enter(&dma_p->mutex);
		dma_p->flags &= ~DMA_DEV_BUSY;
		cv_signal(&dma_p->cv);
		mutex_exit(&dma_p->mutex);
		return (0);
	}

	/*
	 * set up device and go!
	 *
	 * Note: we don't need to acquire the MUTEX here for two reasons:
	 * 1) we are the only top half routine that touches this stuff, and
	 * the mutex & conditional variable stuff surrounding DMA_DEV_BUSY
	 * at the start of this routine single threads us, therefore 2) only
	 * the interrupt routine can touch this stuff and interrupts are not
	 * yet enabled.
	 */
	dma_p->bp = bp;

	/* Allocate DMA channel, and wait until available */
	(void) ddi_dmae_alloc(dma_p->dip, dma_p->dmachan, DDI_DMA_SLEEP, NULL);
	dma_p->dmaereq.der_command =
		(bp->b_flags & B_READ) ? DMAE_CMD_READ : DMAE_CMD_WRITE;

	/* Nothing special at end of transfer */
	dma_p->dmaereq.der_bufprocess = 0;
	/* Move forward through the memory */
	dma_p->dmaereq.der_step = DMAE_STEP_INC;
	/* Do one transfer for each bus cycle */
	dma_p->dmaereq.der_trans = DMAE_TRANS_SNGL;
	/* ISA compatible: channel 0-3 are byte mode, 5-7 word mode */
	dma_p->dmaereq.der_path = DMAE_PATH_DEF;
	/* ISA compatible timing */
	dma_p->dmaereq.der_cycles = DMAE_CYCLES_1;
	/* No nextcookie proc */
	dma_p->dmaereq.proc = 0;

	/*
	 * Okay, start the DMA engine with our request and cookie.
	 */
	(void) ddi_dmae_prog(dma_p->dip, &dma_p->dmaereq, &dma_cookie,
	    dma_p->dmachan);

	/*
	 * Now set up the device to perform I/O, using offset and len
	 * from the ddi_dma_nextcookie above.
	 * When the device is done with the I/O, it will cause an
	 * interrupt, and we'll get called at dma_intr, where we'll
	 * set up the next DMA segment in this same way.
	 */

	outw(dma_p->addr, offset);
	outw(dma_p->count, len);
	outw(dma_p->csr, (inw(dma_p->csr) | DMA_DMA_ENABLE));

	/*
	 * Note: we don't have to wait for the transfer to finish; physio
	 * will wait for us.  It might be worth putting in a timeout in
	 * case our device goes to out lunch.
	 */

	return (0);
}

static void
dma_minphys(struct buf *bp)
{
	/*
	 * We'll be good citizens and limit ourselves to the system
	 * minphys.  Drivers for some devices may have to use a
	 * larger minphys.
	 */
	minphys(bp);
	bp->b_bcount = (min(bp->b_bcount, DMA_MINPHYS));
}

/*
 * This is a sample ioctl routine to show the use of copyin/copyout.
 */
/*ARGSUSED*/
static	int
dma_ioctl(dev_t dev, int cmd, int arg, int flag, cred_t *credp, int *rvalp)
{
	register Dma	*dma_p;
	u_short		retval = 0;

	dma_p = (Dma *)ddi_get_soft_state(state_head, getminor(dev));

	switch (cmd) {
	    case DMA_GET_CSR:
		mutex_enter(&dma_p->mutex);
		retval = inw(dma_p->csr);
		if (copyout((caddr_t)retval, (caddr_t)arg,
		    sizeof (Dma_csr)) == -1) {
			retval = EIO;
		}
		mutex_exit(&dma_p->mutex);
		break;
	    default:
		retval = ENOTTY;
		break;
	}

	return (retval);
}

/*
 * Whenever the system sees an interrupt at the level we specified with
 * add_intr in dma_attach, we get a chance to determine if it is our
 * interrupt or somebody else's.
 *
 * This routine will get called for each instance of each driver that uses
 * that level interrupt.
 */
static u_int
dma_intr(register Dma *dma_p)
{
	Dma_csr			csr;
	u_int   		int_serviced = DDI_INTR_UNCLAIMED;
	int			ns;
	ddi_dma_cookie_t	dma_cookie;
	off_t			offset, len;

	csr = dma_p->csr;

	/* Don't try processing the interrupt if we don't have a mutex yet */
	if ((dma_p->attach_flags & MUTEX_ADDED) == 0)
		return (DDI_INTR_UNCLAIMED);

	/*
	 * lock the control status register
	 */
	mutex_enter(&dma_p->mutex);			/* start MUTEX */

	if (inw(csr) & DMA_DONE_INTR) {
		/*
		 * we are interrupting
		 */
		int_serviced = DDI_INTR_CLAIMED;

		/*
		 * clear the interrupt
		 */
		outw(csr, (inw(csr) & ~DMA_INTR_ENABLE));
		outw(csr, (inw(csr) | DMA_INTR_CLEAR));

		/* Get next segment and reprogram DMA engine */
		ns = ddi_dma_nextseg(dma_p->dmawin, dma_p->dmaseg,
		    &dma_p->dmaseg);

		/*
		 * If now done with window, return.  (We're assuming
		 * no transfer will require more than one window.)
		 */
		if (ns == DDI_DMA_DONE) {
			/*
			 * release DMA resources
			 */
			(void) ddi_dmae_release(dma_p->dip, dma_p->dmachan);
			(void) ddi_dma_free(dma_p->handle);
			biodone(dma_p->bp);

			/*
			 * "wake up" the top half
			 */
			dma_p->flags &= ~DMA_DEV_BUSY;
			cv_signal(&dma_p->cv);
			goto exitintr;
		}

		/*
		 * If error in getting nextseg or cookie, mark buffer and
		 * return
		 */
		if (ns != DDI_SUCCESS ||
		    ddi_dma_segtocookie(dma_p->dmaseg, &offset,
					&len, &dma_cookie) != DDI_SUCCESS) {
			(void) ddi_dmae_release(dma_p->dip, dma_p->dmachan);
			(void) ddi_dma_free(dma_p->handle);
			dma_p->bp->b_error |= EIO;
			dma_p->bp->b_flags |= B_ERROR;
			biodone(dma_p->bp);
			dma_p->flags &= ~DMA_DEV_BUSY;
			cv_signal(&dma_p->cv);
			goto exitintr;
		}

		/* Start DMA engine for next segment... */

		(void) ddi_dmae_prog(dma_p->dip, &dma_p->dmaereq, &dma_cookie,
		    dma_p->dmachan);

		/*
		 * ... and start device I/O again.  Note that we're not
		 * yet done with the buffer, so don't do the biodone()
		 * here.
		 */

		outw(dma_p->addr, offset);
		outw(dma_p->count, len);
		outw(dma_p->csr, (inw(dma_p->csr) | DMA_DMA_ENABLE));
	}

exitintr:
	mutex_exit(&dma_p->mutex);			/* end MUTEX */

	return (int_serviced);
}
