/* This software is covered by the GNU GENERAL PUBLIC LICENSE (GPL). */
/* Any Device - Driver - Linux */
/* $Workfile: dma.c $ */
/* $Revision: 1268 $ */
/* $Date: 6/19/08 8:19a $ */
/* $Log: /NEW SOURCESAFE DATABASE/SDK/common/driver/linux/dma.c $
 * 
 * 4     6/19/08 8:19a Dnewbold
 * Added DPR code to _dma_xfer_sgl().
 * 
 * 3     10/27/07 10:32a Dnewbold
 * Additional driver porting updates.
 *
 * 2     3/29/07 2:40p Dnewbold
 *
 * 1     5/10/06 1:02p Dnewbold
*/

#include "main.h"



/* #defines	*****************************************************************/

#define	DATA_SIZE(d)	((d)->flags & GSC_DMA_OPEN_SIZE_MASK)



/******************************************************************************
*
*	Function:	_dma_close_sgl
*
*	Purpose:
*
*		Perform the work needed to clean up after a DMA channel has been opened
*		in SGL mode and possibly used.
*
*	Arguments:
*
*		dma		The data for the DMA channel to close.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _dma_close_sgl(dev_dma_t* dma)
{
	if (dma->sgl.user_mapped)
	{
		dma_sgl_user_mem_unmap(dma);
		dma->sgl.user_mapped	= 0;
	}
}



/******************************************************************************
*
*	Function:	_dma_open_config
*
*	Purpose:
*
*		Configure the DMA channel per the options just given, as the channel
*		is being opened.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		dma		The data for the DMA channel of interest.
*
*		ws		The number of wait states to utilize.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _dma_open_config(dev_data_t* dev, dev_dma_t* dma, __u32 ws)
{
	u32	v;

	// DMAARB (MARBR) ---------------------------------------------------------
	v	= readl(dev->vaddr.plx_dmaarb_32);
	v	&= ~DMA_MARBR_PRIORITY_MASK;

	switch (dma->flags & GSC_DMA_OPEN_PRI_MASK)
	{
		case GSC_DMA_OPEN_PRI_CH_0:

			v	|= DMA_MARBR_PRIORITY_DMA0;
			break;

		case GSC_DMA_OPEN_PRI_CH_1:

			v	|= DMA_MARBR_PRIORITY_DMA1;
			break;

		case GSC_DMA_OPEN_PRI_SHARE:

			v	|= DMA_MARBR_PRIORITY_ROTATE;
			break;
	}

	writel(v, dev->vaddr.plx_dmaarb_32);	// DMAARB

	// DMATHR -----------------------------------------------------------------
	writel(0, dev->vaddr.plx_dmathr_32);	// DMATHR

	// DMAMODEx ---------------------------------------------------------------
	v	= 0;
	v	|= DMA_MODE_DONE_INTERRUPT_ENABLE;
	v	|= DMA_MODE_CHANNEL_INT_SELECT;
	v	|= (ws << 2);

	switch (dma->flags & GSC_DMA_OPEN_SIZE_MASK)
	{
		case GSC_DMA_OPEN_SIZE_8_BITS:

			v	|= DMA_MODE_SIZE_8_BITS;
			break;

		case GSC_DMA_OPEN_SIZE_16_BITS:

			v	|= DMA_MODE_SIZE_16_BITS;
			break;

		case GSC_DMA_OPEN_SIZE_32_BITS:

			v	|= DMA_MODE_SIZE_32_BITS;
			break;
	}

	switch (dma->flags & GSC_DMA_OPEN_MEM_MASK)
	{
		case GSC_DMA_OPEN_MEM_BLOCK:

			break;

		case GSC_DMA_OPEN_MEM_SGL:

			v	|= DMA_MODE_CHAINING;
			break;
	}

	switch (dma->flags & GSC_DMA_OPEN_XFER_MASK)
	{
		case GSC_DMA_OPEN_XFER_DEMAND:

			v	|= DMA_MODE_DEMAND_MODE;
			break;

		case GSC_DMA_OPEN_XFER_FORCE:

			break;
	}


	switch (dma->flags & GSC_DMA_OPEN_LADRS_MASK)
	{
		case GSC_DMA_OPEN_LADRS_FIXED:

			v	|= DMA_MODE_LOCAL_ADDRESS_MODE;
			break;

		case GSC_DMA_OPEN_LADRS_INC:

			break;
	}

	if (dma->flags & GSC_DMA_OPEN_BTERM_INPUT)
		v	|= DMA_MODE_BTERM_INPUT_ENABLE;

	if (dma->flags & GSC_DMA_OPEN_EOT_PIN)
		v	|= DMA_MODE_EOT_ENABLE;

	if (dma->flags & GSC_DMA_OPEN_LOCAL_BURST)
		v	|= DMA_MODE_LOCAL_BURST_ENABLE;

	if (dma->flags & GSC_DMA_OPEN_READY_INPUT)
		v	|= DMA_MODE_READY_INPUT_ENABLE;

	if (dma->flags & GSC_DMA_OPEN_STOP_XFER_MODE)
		v	|= DMA_MODE_STOP_TRANSFER_MODE;

	if (dma->flags & GSC_DMA_OPEN_WR_INVAL_MODE)
		v	|= DMA_MODE_WRITE_INVALIDATE_MODE;

	writel(v, dma->vaddr.mode_32);			// DMAMODEx
}



/******************************************************************************
*
*	Function:	_dma_status
*
*	Purpose:
*
*		Return the status of the given DMA channel.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		chan	The structure for the channel to access.
*
*		dma		The data for the DMA request.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _dma_status(dev_data_t* dev, dev_dma_t* chan, gsc_drv_dma_t* dma)
{
	u8	csr;

	csr					= readb(chan->vaddr.csr_8);
	dma->u.status.flags	= chan->flags;

	if (csr & DMA_CSR_DONE)
		dma->u.status.flags	|= GSC_DMA_STATUS_IDLE;
	else
		dma->u.status.flags	|= GSC_DMA_STATUS_BUSY;
}



/******************************************************************************
*
*	Function:	_sgl_buf_free
*
*	Purpose:
*
*		Free the SGL buffer from the given DMA structure.
*
*	Arguments:
*
*		dma		The structure from which to free the SGL buffer.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _sgl_buf_free(dev_dma_t* dma)
{
	s32				bytes;
	unsigned long	ul;

	if (dma)
	{
		if (dma->sgl.alloc)
		{
			bytes	= dma->sgl.bytes;
			ul		= (unsigned long) dma->sgl.alloc;

			for (; bytes >= PAGE_SIZE; bytes -= PAGE_SIZE, ul += PAGE_SIZE)
				PAGE_UNRESERVE(ul);

			kfree(dma->sgl.alloc);
		}

		dma->sgl.bytes	= 0;
		dma->sgl.size	= 0;
		dma->sgl.alloc	= NULL;
		dma->sgl.list	= NULL;
		dma->sgl.dpr_32	= 0;
	}
}



/******************************************************************************
*
*	Function:	_sgl_buf_alloc
*
*	Purpose:
*
*		Allocate an SGL buffer for the given DMA structure.
*
*	Arguments:
*
*		dma		The structure to allocate the SGL buffer for.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _sgl_buf_alloc(dev_dma_t* dma)
{
	unsigned long	adrs_bus;
	unsigned long	adrs_limit;
	s32				bytes;
	long			l;
	s32				size;
	unsigned long	ul;

	// We limit DMA to 8MB, but it won't likely start on a page boundary.
	// Therefore we add an extra page to accomodate that case.
	size	= ((8L * 1024 * 1024) + PAGE_SIZE) / PAGE_SIZE;
	bytes	= (size * sizeof(dev_dpr_t)) + 15;	// Quad word allignment.

	if (bytes > (KMALLOC_LIMIT - 1024))
	{
		bytes	= KMALLOC_LIMIT - 1024;
		size	= bytes / sizeof(dev_dpr_t);
		bytes	= (size * sizeof(dev_dpr_t)) + 15;	// Quad word allignment.
	}

	_sgl_buf_free(dma);
	adrs_limit		= 0xFFFFFFFFUL - bytes + 1;
	dma->sgl.alloc	= kmalloc(bytes, GFP_KERNEL | __GFP_DMA);
	adrs_bus		= virt_to_bus(dma->sgl.alloc);

	if (adrs_bus > adrs_limit)
	{
		// The buffer must be in 32-bit memory.
		_sgl_buf_free(dma);
	}

	if (dma->sgl.alloc)
	{
		dma->sgl.bytes	= bytes;
		dma->sgl.size	= size;
		dma->sgl.list	= (void*)
						(((unsigned long) dma->sgl.alloc + 15UL) & ~15UL);
		dma->sgl.dpr_32	= virt_to_bus(dma->sgl.list);
		ul				= dma->sgl.dpr_32;

		for (l = 0; l < size; l++)
		{
			ul	+= sizeof(dev_dpr_t);
			dma->sgl.list[l].dpr_32	= ul;
		}

		ul	= (unsigned long) dma->sgl.alloc;

		for (; bytes >= PAGE_SIZE; bytes -= PAGE_SIZE, ul += PAGE_SIZE)
			PAGE_RESERVE(ul);
	}
}



/******************************************************************************
*
*	Function:	dma_abort
*
*	Purpose:
*
*		Abort a DMA for the caller.
*
*	Arguments:
*
*		dma		The structure for the DMA channel to abort.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for a failed attempt.
*
******************************************************************************/

int dma_abort(dev_dma_t* dma)
{
	u8		csr;
	int		i;
	int		status	= -ETIMEDOUT;

	// Disable the DMA channel.
	csr	= readb(dma->vaddr.csr_8);
	csr	= csr & ~DMA_CSR_ENABLE;
	writeb(csr, dma->vaddr.csr_8);

	// Set the ABORT bit and clear the START bit.
	csr	= (csr | DMA_CSR_ABORT) & ~DMA_CSR_START;
	writeb(csr, dma->vaddr.csr_8);

	// Wait for the DMA done bit.
	udelay(1);

	for (i = 0; i < 100; i++)
	{
		csr	= readb(dma->vaddr.csr_8);

		if (csr & DMA_CSR_DONE)
		{
			// The abort was successful.
			status	= 0;
			break;
		}

		SET_CURRENT_STATE(TASK_INTERRUPTIBLE);
		schedule_timeout(1);
	}

	// Enable the DMA channel again.
	writeb(DMA_CSR_ENABLE, dma->vaddr.csr_8);
	return(status);
}



/******************************************************************************
*
*	Function:	dma_abort_ioctl
*
*	Purpose:
*
*		Abort a DMA for the caller. It is OK if a DMA is not active. This can
*		be done only if the caller is the one who opened the channel.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		dma		The data for the DMA request.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int dma_abort_ioctl(dev_data_t* dev, gsc_drv_dma_t* dma)
{
	dev_pid_t*	pid;
	dev_dma_t*	ptr;
	int			status;

	for (;;)	// A convenience loop.
	{
		if (dma->index >= 2)
		{
			status		= -EINVAL;
			dma->status	= GSC_INVALID_INDEX;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			status		= -ENODEV;
			dma->status	= GSC_INVALID_DEVICE_INFO;
			break;
		}

		ptr	= &dev->dma.channel[dma->index];

		if (ptr->pid != pid)
		{
			status		= -ENODEV;
			dma->status	= GSC_ACCESS_DENIED;
			break;
		}

		// Abort the DMA.
		status		= dma_abort(ptr);
		dma->status	= status ? GSC_FAILED : GSC_SUCCESS;
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	dma_close_ioctl
*
*	Purpose:
*
*		Close a DMA channel for the caller. This can be done only if the
*		caller is the one who opened the channel.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		dma		The data for the DMA request.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int dma_close_ioctl(dev_data_t* dev, gsc_drv_dma_t* dma)
{
	dev_pid_t*		pid;
	dev_dma_t*		ptr;
	int				status;
	gsc_drv_dma_t	tmp;

	for (;;)	// A convenience loop.
	{
		if (dma->index >= 2)
		{
			status		= -EINVAL;
			dma->status	= GSC_INVALID_INDEX;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			status		= -ENODEV;
			dma->status	= GSC_INVALID_DEVICE_INFO;
			break;
		}

		ptr	= &dev->dma.channel[dma->index];

		if (ptr->pid != pid)
		{
			status		= -ENODEV;
			dma->status	= GSC_ACCESS_DENIED;
			break;
		}

		// If a DMA is still in progress, then abort it.
		memset(&tmp, 0, sizeof(tmp));
		tmp.index	= dma->index;
		_dma_status(dev, ptr, &tmp);

		if (tmp.u.status.flags & GSC_DMA_STATUS_BUSY)
		{
			dma_abort_ioctl(dev, dma);
			_dma_status(dev, ptr, &tmp);

			if (tmp.u.status.flags & GSC_DMA_STATUS_BUSY)
			{
				status		= -EBUSY;
				dma->status	= GSC_DMA_IN_PROGRESS;
				break;
			}
		}

		// Everything seems to be in order, so close the channel.
		_dma_close_sgl(ptr);
		writel(0, ptr->vaddr.mode_32);
		ptr->pid	= NULL;
		ptr->flags	= 0;
		status		= 0;
		dma->status	= GSC_SUCCESS;
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	dma_open_ioctl
*
*	Purpose:
*
*		Open a DMA channel for the caller. A channel cannot be opened if it
*		is already open.
*
*	Arguments:
*
*		dev	The data for the device of interest.
*
*		dma	The data for the DMA request.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int dma_open_ioctl(dev_data_t* dev, gsc_drv_dma_t* dma)
{
	u32			flags;
	dev_pid_t*	pid;
	dev_dma_t*	ptr;
	int			status;

	for (;;)	// A convenience loop.
	{
		if (dma->index >= 2)
		{
			status		= -EINVAL;
			dma->status	= GSC_INVALID_INDEX;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			status		= -ENODEV;
			dma->status	= GSC_INVALID_DEVICE_INFO;
			break;
		}

		ptr	= & dev->dma.channel[dma->index];

		if (ptr->pid)
		{
			status		= -EBUSY;
			dma->status	= GSC_ACCESS_DENIED;
			break;
		}

		flags	= 0;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_BTERM_INPUT;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_EOT_PIN;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_LOCAL_BURST;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_READY_INPUT;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_STOP_XFER_MODE;
		flags	|= dma->u.open.flags & GSC_DMA_OPEN_WR_INVAL_MODE;

		switch (dma->u.open.flags & GSC_DMA_OPEN_PRI_MASK)
		{
			case GSC_DMA_OPEN_PRI_CH_0:
			case GSC_DMA_OPEN_PRI_CH_1:
			case GSC_DMA_OPEN_PRI_SHARE:

				flags	|= dma->u.open.flags & GSC_DMA_OPEN_PRI_MASK;
				break;
		}

		switch (dma->u.open.flags & GSC_DMA_OPEN_SIZE_MASK)
		{
			case GSC_DMA_OPEN_SIZE_8_BITS:
			case GSC_DMA_OPEN_SIZE_16_BITS:
			case GSC_DMA_OPEN_SIZE_32_BITS:

				flags	|= dma->u.open.flags & GSC_DMA_OPEN_SIZE_MASK;
				break;
		}

		switch (dma->u.open.flags & GSC_DMA_OPEN_MEM_MASK)
		{
			case GSC_DMA_OPEN_MEM_BLOCK:
			case GSC_DMA_OPEN_MEM_SGL:

				flags	|= dma->u.open.flags & GSC_DMA_OPEN_MEM_MASK;
				break;
		}

		switch (dma->u.open.flags & GSC_DMA_OPEN_XFER_MASK)
		{
			case GSC_DMA_OPEN_XFER_DEMAND:
			case GSC_DMA_OPEN_XFER_FORCE:

				flags	|= dma->u.open.flags & GSC_DMA_OPEN_XFER_MASK;
				break;
		}

		switch (dma->u.open.flags & GSC_DMA_OPEN_LADRS_MASK)
		{
			case GSC_DMA_OPEN_LADRS_FIXED:
			case GSC_DMA_OPEN_LADRS_INC:

				flags	|= dma->u.open.flags & GSC_DMA_OPEN_LADRS_MASK;
				break;
		}

		if ((flags != dma->u.open.flags) || (dma->u.open.wait_states > 15))
		{
			status		= -EINVAL;
			dma->status	= GSC_INVALID_DATA;
			break;
		}

		ptr->pid	= pid;
		ptr->flags	= flags;
		_dma_open_config(dev, ptr, dma->u.open.wait_states);
		status		= 0;
		dma->status	= GSC_SUCCESS;
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	dma_status_ioctl
*
*	Purpose:
*
*		Return the status of the given DMA channel.
*
*	Arguments:
*
*		dev	The data for the device of interest.
*
*		dma	The data for the DMA request.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int dma_status_ioctl(dev_data_t* dev, gsc_drv_dma_t* dma)
{
	dev_pid_t*	pid;
	int			status;

	for (;;)	// A convenience loop.
	{
		if (dma->index >= 2)
		{
			status		= -EINVAL;
			dma->status	= GSC_INVALID_INDEX;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			status		= -ENODEV;
			dma->status	= GSC_INVALID_DEVICE_INFO;
			break;
		}

		status		= 0;
		dma->status	= GSC_SUCCESS;
		_dma_status(dev, &dev->dma.channel[dma->index], dma);
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	_dma_perform
*
*	Purpose:
*
*		Perform the actual work of a DMA transfer.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		dma		The driver data for the DMA request.
*
*		xfer	The user data for the transfer.
*
*		dpr		The value to write to the DMADPR register.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for the problem seen.
*
******************************************************************************/

static int _dma_perform(
	dev_data_t*		dev,
	dev_dma_t*		dma,
	gsc_drv_dma_t*	xfer,
	u32				dpr)
{
	gsc_drv_irq_t	irq;
	int				status;

	for (;;)	// A convenience loop.
	{
		// Enable the interrupt.
		memset(&irq, 0, sizeof(irq));
		irq.enable	= GSC_IRQ_PCI;
		irq.enable	|= xfer->index ? GSC_IRQ_DMA1 : GSC_IRQ_DMA0;
		status		= irq_enable_ioctl(dev, &irq);

		if (status)
		{
			xfer->status	= GSC_FAILED;
			break;
		}

		// Configure the DMA registers.

		if (dma->flags & GSC_DMA_OPEN_MEM_BLOCK)
		{
			writel(xfer->u.xfer.phys_adrs, dma->vaddr.padr_32);
			writel(xfer->u.xfer.local_adrs, dma->vaddr.ladr_32);
			writel(xfer->u.xfer.bytes, dma->vaddr.siz_32);
		}

		writel(dpr, dma->vaddr.dpr_32);

		// Start the DMA, don't wait around for completion.
		writeb(0, dma->vaddr.csr_8);
		writeb(DMA_CSR_ENABLE, dma->vaddr.csr_8);
		writeb(DMA_CSR_ENABLE | DMA_CSR_START, dma->vaddr.csr_8);
		xfer->status	= GSC_SUCCESS;
		status			= 0;
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	_dma_xfer_block
*
*	Purpose:
*
*		Perform a BLOCK mode DMA transfer. This mode uses a buffer previously
*		allocated by the driver.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		pid		The data for the calling process.
*
*		dma		The data for the DMA request.
*
*		xfer	The user data for the transfer.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for the problem seen.
*
******************************************************************************/

static int _dma_xfer_block(
	dev_data_t*		dev,
	dev_pid_t*		pid,
	dev_dma_t*		dma,
	gsc_drv_dma_t*	xfer)
{
	u32			dpr;
	u32			end_m;
	u32			end_x;
	dev_mem_t*	mem;
	int			status;

	for (;;)	// A convenience loop.
	{
		mem	= mem_dma_locate(pid, xfer->u.xfer.phys_adrs);

		if (mem == NULL)
		{
			// We couldn't find the allocated buffer information.
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		end_m	= mem->phy_adrs + mem->size - 1;
		end_x	= xfer->u.xfer.phys_adrs + xfer->u.xfer.bytes - 1;

		if (end_x > end_m)
		{
			// The request extends past the end of the allocated block.
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		dpr	= DMA_DPR_END_OF_CHAIN;

		if (xfer->u.xfer.flags & GSC_DMA_XFER_DIR_TO_HOST)
			dpr	|= DMA_DPR_BOARD_TO_HOST;
		else
			dpr	|= DMA_DPR_HOST_TO_BOARD;

		status= _dma_perform(dev, dma, xfer, dpr);
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	_dma_xfer_sgl
*
*	Purpose:
*
*		Perform an SGL mode DMA transfer. This mode uses an application
*		provided data buffer rather than a buffer allocated by the driver.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		pid		The data for the calling process.
*
*		dma		The data for the DMA request.
*
*		xfer	The user data for the transfer.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for the problem seen.
*
******************************************************************************/

static int _dma_xfer_sgl(
	dev_data_t*		dev,
	dev_pid_t*		pid,
	dev_dma_t*		dma,
	gsc_drv_dma_t*	xfer)
{
	u32	dpr;
	u32	ds		= DATA_SIZE(dma);
	int	status;

	for (;;)	// A convenience loop.
	{
		if (((ds == GSC_DMA_OPEN_SIZE_32_BITS) &&
			 (xfer->u.xfer.user_adrs & 0x3))		||
			((ds == GSC_DMA_OPEN_SIZE_16_BITS) &&
			 (xfer->u.xfer.user_adrs & 0x1)))
		{
			// The user pointer must be approiately alligned.
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		status	= dma_sgl_user_mem_map(dma, xfer);

		if (status)
		{
			_dma_close_sgl(dma);
			break;
		}

		dpr	= dma->sgl.dpr_32
			| DMA_DPR_DESC_LOC_PCI;

		if (xfer->u.xfer.flags & GSC_DMA_XFER_DIR_TO_HOST)
			dpr	|= DMA_DPR_BOARD_TO_HOST;
		else
			dpr	|= DMA_DPR_HOST_TO_BOARD;

		status	= _dma_perform(dev, dma, xfer, dpr);
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	dma_transfer_ioctl
*
*	Purpose:
*
*		Perform an actual DMA transfer.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		cfer	The data for the DMA request.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int dma_transfer_ioctl(dev_data_t* dev, gsc_drv_dma_t* xfer)
{
	dev_dma_t*		dma;
	u32				ds;
	u32				flags;
	dev_pid_t*		pid;
	int				status;
	gsc_drv_dma_t	tmp;

	for (;;)	// A convenience loop.
	{
		if (xfer->index >= 2)
		{
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_INDEX;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			status			= -ENODEV;
			xfer->status	= GSC_INVALID_DEVICE_INFO;
			break;
		}

		dma	= &dev->dma.channel[xfer->index];

		if (dma->pid != pid)
		{
			status			= -ENODEV;
			xfer->status	= GSC_ACCESS_DENIED;
			break;
		}

		if (xfer->u.xfer.flags & GSC_DMA_XFER_DIR_TO_DEVICE)
			flags	= GSC_DMA_XFER_DIR_TO_DEVICE;
		else
			flags	= GSC_DMA_XFER_DIR_TO_HOST;

		if (flags != xfer->u.xfer.flags)
		{
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		if (xfer->u.xfer.bytes == 0)
		{
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		ds	= DATA_SIZE(dma);

		if (((ds == GSC_DMA_OPEN_SIZE_32_BITS) &&
			 (xfer->u.xfer.bytes % 4)) ||
			((ds == GSC_DMA_OPEN_SIZE_16_BITS) &&
			 (xfer->u.xfer.bytes % 2)))
		{
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		if (xfer->u.xfer.bytes >= (8L * 1024L * 1024L))
		{
			// The PLX chip can do no more than (8M - 1) bytes.
			status			= -EINVAL;
			xfer->status	= GSC_INVALID_DATA;
			break;
		}

		memset(&tmp, 0, sizeof(tmp));
		tmp.index	= xfer->index;
		_dma_status(dev, &dev->dma.channel[xfer->index], &tmp);

		if (tmp.u.xfer.flags & GSC_DMA_STATUS_BUSY)
		{
			status			= -EBUSY;
			xfer->status	= GSC_DMA_IN_PROGRESS;
			break;
		}

		_dma_close_sgl(dma);	// Make sure the last one is cleaned up.

		if (dma->flags & GSC_DMA_OPEN_MEM_BLOCK)
		{
			if ((xfer->u.xfer.phys_adrs == 0) || (xfer->u.xfer.user_adrs))
			{
				status			= -EINVAL;
				xfer->status	= GSC_INVALID_DATA;
				break;
			}
			else
			{
				status	= _dma_xfer_block(dev, pid, dma, xfer);
				break;
			}
		}

		if (dma->flags & GSC_DMA_OPEN_MEM_SGL)
		{
			if ((xfer->u.xfer.user_adrs == 0) || (xfer->u.xfer.phys_adrs))
			{
				status			= -EINVAL;
				xfer->status	= GSC_INVALID_DATA;
				break;
			}
			else
			{
				status	= _dma_xfer_sgl(dev, pid, dma, xfer);
				break;
			}
		}

		status			= -EINVAL;
		xfer->status	= GSC_INVALID_DATA;
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	dma_channel_init
*
*	Purpose:
*
*		Initialize the given DMA channel structure.
*
*	Arguments:
*
*		dev		The device structure containing the dma structure to initialize.
*
*		dma		The structure to initialize.
*
*		index	The index of the channel: 0 or 1
*
*	Returned:
*
*		None.
*
******************************************************************************/

void dma_channel_init(dev_data_t* dev, dev_dma_t* dma, int index)
{
	u32	reg;

	memset(dma, 0, sizeof(dma[0]));
	dma->index			= index;
	dma->pid			= NULL;

	reg					= index ? GSC_PLX_DMAMODE1 : GSC_PLX_DMAMODE0;
	dma->vaddr.mode_32	= reg_vaddr(dev, reg);

	reg					= index ? GSC_PLX_DMAPADR1 : GSC_PLX_DMAPADR0;
	dma->vaddr.padr_32	= reg_vaddr(dev, reg);

	reg					= index ? GSC_PLX_DMALADR1 : GSC_PLX_DMALADR0;
	dma->vaddr.ladr_32	= reg_vaddr(dev, reg);

	reg					= index ? GSC_PLX_DMASIZ1 : GSC_PLX_DMASIZ0;
	dma->vaddr.siz_32	= reg_vaddr(dev, reg);

	reg					= index ? GSC_PLX_DMADPR1 : GSC_PLX_DMADPR0;
	dma->vaddr.dpr_32	= reg_vaddr(dev, reg);

	reg					= index ? GSC_PLX_DMACSR1 : GSC_PLX_DMACSR0;
	dma->vaddr.csr_8	= reg_vaddr(dev, reg);

	_sgl_buf_alloc(dma);
	dma_sgl_user_mem_init(dma);
}



/******************************************************************************
*
*	Function:	dma_channel_reset
*
*	Purpose:
*
*		Reset the given DMA channel structure.
*
*	Arguments:
*
*		dma		The structure to initialize.
*
*	Returned:
*
*		None.
*
******************************************************************************/

void dma_channel_reset(dev_dma_t* dma)
{
	dma_sgl_user_mem_reset(dma);
	_sgl_buf_free(dma);
	memset(dma, 0, sizeof(dma[0]));
}



