/* API - Any Device - Any OS */
/* $Workfile: gsc_dma.c $ */
/* $Revision: 1 $ */
/* $Date: 11/15/07 11:13a $ */
/* $Log: /NEW SOURCESAFE DATABASE/SDK/common/api/gsc_dma.c $
 * 
 * 7     11/15/07 11:13a Dnewbold
 * Removed all references to PLX.
 * 
 * 6     10/27/07 10:05a Dnewbold
 * Ported to Linux.
 * 
 * 5     4/02/07 4:49p Dnewbold
 * Added #include <string.h>
 * 
 * 4     3/01/07 5:13p Dnewbold
 * Linted and migrated to 32-bit and 64-bit Linux.
 * 
 * 3     5/09/06 1:31p Dnewbold
 * Increased code modularity.
 * 
 * 2     5/04/06 3:36p Dnewbold
 * Removed key validation code and increased modularity.
 * 
 * 1     8/22/05 11:48a Dnewbold
*/
/*
*	Implement common DMA services.
*
*	USING DMA:
*
*		1. Call the CREATE function when the device is being opened.
*		2. Call the INIT function when the device is being opened.
*		3. Call the OPEN function before performing DMA based I/O.
*		4. Call the TRANSFER function to perform the DMA operation.
*		5. Call the DONE function to perform cleanup after a DMA operation.
*		6. Call the CLOSE function when the I/O operation is complete.
*		7. Continue with TRANSFER as needed.
*		8. Continue with OPEN/TRANSFER/DONE/CLOSE as needed.
*		9. Call the DESTROY function when the device is being closed.
*
*		NOTE: The OPEN function can be called repeatedly without calling the
*				DONE/CLOSE functions, even if the Block vs Scatter Gather
*				mode changes.
*
*		NOTE: The DONE function may perform a CLOSE, depending on settings.
*
*		NOTE: There are no negative consequences to calling OPEN repeatedly.
*
*		NOTE: There are no negative consequences to calling CLOSE repeatedly.
*
*		NOTE: There are no negative consequences to calling CLOSE when the
*				DONE function has performed a CLOSE.
*/

#include <string.h>

#include "gsc_dma.h"

#include "gsc_drv.h"
#include "gsc_sleep.h"



/*****************************************************************************
*
*	Function:	_dma_channel_acquire
*
*	Purpose:
*
*		Acquire a DMA channel with the given characteristics.
*
*	Arguments:
*
*		dma			The DMA structure for the device.
*
*		need		The capability needs for the request.
*
*		channel		The channel selected in recorded here.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

static U32 _dma_channel_acquire(gsc_dma_t*			dma,
								U32					need,
								gsc_dma_channel_t**	channel)
{
	U32	status;

	if (channel[0])
	{
		// A DMA channel is already selected.
		status	= (U32) GSC_SUCCESS;
	}
	else if (	(dma->ch1.in_use == 0) &&
				((dma->ch1.capabilities & need) == need))
	{
		dma->ch1.in_use	= 1;
		channel[0]		= &dma->ch1;
		status			= (U32) GSC_SUCCESS;
	}
	else if (	(dma->ch0.in_use == 0) &&
				((dma->ch0.capabilities & need) == need))
	{
		dma->ch0.in_use	= 1;
		channel[0]		= &dma->ch0;
		status			= (U32) GSC_SUCCESS;
	}
	else
	{
		status	= (U32) GSC_DMA_CHANNEL_UNAVAILABLE;
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	_dma_channel_close
*
*	Purpose:
*
*		Close a DMA channel, assuming we have exclusive access to the
*		structures.
*
*	Arguments:
*
*		dma			The DMA structure for the device.
*
*		channel		The structure for the channel to close.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

static U32 _dma_channel_close(gsc_dma_t* dma, gsc_dma_channel_t* channel)
{
	U32	status;

	if (channel == NULL)
	{
		status	= (U32) GSC_SUCCESS;
	}
	else if (channel->type == GSC_DMA_TYPE_CHAINED)
	{
		status	= gsc_drv_dma_status(dma->device, channel->index);

		if ((status == (U32) GSC_DMA_PAUSED) ||
			(status == (U32) GSC_DMA_IN_PROGRESS))
		{
			gsc_drv_dma_abort(dma->device, channel->index);
		}

		status			= gsc_drv_dma_sgl_close(dma->device, channel->index);
		channel->type	= GSC_DMA_TYPE_NONE;
	}
	else if (channel->type == GSC_DMA_TYPE_BLOCK)
	{
		status	= gsc_drv_dma_status(dma->device, channel->index);

		if ((status == (U32) GSC_DMA_PAUSED) ||
			(status == (U32) GSC_DMA_IN_PROGRESS))
		{
			gsc_drv_dma_abort(dma->device, channel->index);
		}

		status			= gsc_drv_dma_block_close(dma->device, channel->index);
		channel->type	= GSC_DMA_TYPE_NONE;
	}
	else
	{
		channel->type	= GSC_DMA_TYPE_NONE;
		status			= (U32) GSC_SUCCESS;
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	_dma_channel_open
*
*	Purpose:
*
*		Open a DMA channel. It is assumed that the channel is now closed.
*
*	Arguments:
*
*		dma			The DMA structure for the device.
*
*		channel		The structure for the channel to open.
*
*		type		The DMA type desired.
*
*		fn_desc		Call this function to initialize a main DMA description
*					structure.
*
*		io			The I/O structure for the respective I/O operation.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

static U32 _dma_channel_open(
					gsc_dma_t*			dma,
					gsc_dma_channel_t*	channel,
					U32					type,
					U32					(*fn_desc)(	gsc_io_t*		io,
													gsc_dma_info_t*	info),
					gsc_io_t*			io)
{
	gsc_dma_info_t	info;
	U32				status;

	status	= (fn_desc)(io, &info);

	if (status != (U32) GSC_SUCCESS)
	{
	}
	else if (type == GSC_DMA_TYPE_CHAINED)
	{
		status	= gsc_drv_dma_sgl_open(	dma->device,
										channel->index,
										&info);
	}
	else if (type == GSC_DMA_TYPE_BLOCK)
	{
		status	=  gsc_drv_dma_block_open(	dma->device,
											channel->index, 
											&info);
	}
	else
	{
		status	= (U32) GSC_FAILED;
	}

	if (status == (U32) GSC_SUCCESS)
		channel->type	= type;
	else
		channel->type	= GSC_DMA_TYPE_NONE;

	return(status);
}



/*****************************************************************************
*
*	Function:	_dma_channel_release
*
*	Purpose:
*
*		Release a DMA channel from use.
*
*	Arguments:
*
*		channel		The channel to release.
*
*	Returned:
*
*		None.
*
*****************************************************************************/

static void _dma_channel_release(gsc_dma_channel_t** channel)
{
	if (channel[0])
	{
		channel[0]->in_use	= 0;
		channel[0]			= NULL;
	}
}



/*****************************************************************************
*
*	Function:	gsc_dma_abort
*
*	Purpose:
*
*		Abort an ongoing DMA operation.
*
*	Arguments:
*
*		io			The I/O channel data for the operation.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_abort(gsc_io_t* io)
{
	int	i;
	U32	status;

	for (i = 0; i < 5; i++)
	{
		status	= gsc_drv_dma_abort(io->device, io->dma_channel->index);
		status	= gsc_drv_dma_status(	io->device,
										io->dma_channel->index);
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	gsc_dma_channel_close
*
*	Purpose:
*
*		Close and release a DMA channel from use.
*
*	Arguments:
*
*		dma			The DMA structure for the device.
*
*		channel		The structure for the channel of interest.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_channel_close(gsc_dma_t* dma, gsc_dma_channel_t** channel)
{
	U32	status;

	status	= gsc_sem_lock(&dma->sem);

	if (status == (U32) GSC_SUCCESS)
	{
		status	= _dma_channel_close(dma, channel[0]);

		if (status == (U32) GSC_SUCCESS)
			_dma_channel_release(channel);

		gsc_sem_unlock(&dma->sem);
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	gsc_dma_channel_open
*
*	Purpose:
*
*		Open a DMA channel. It is assumed that the channel is now closed.
*
*	Arguments:
*
*		dma			The DMA structure for the device.
*
*		channel		The structure for the channel to close.
*
*		type		The DMA type desired.
*
*		need		The needed capabilities of the channel. This means
*					Rx or Tx and DMA or DMDMA.
*
*		fn_desc		Call this function to initialize a main DMA description
*					structure.
*
*		io			The I/O structure for the current I/O operation.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_channel_open(	gsc_dma_t*			dma,
							gsc_dma_channel_t**	channel,
							U32					type,
							U32					need,
							U32					(*fn_desc)(
													gsc_io_t*		io,
													gsc_dma_info_t*	info),
							gsc_io_t*			io)
{
	U32	status;

	status	= gsc_sem_lock(&dma->sem);

	if (status != (U32) GSC_SUCCESS)
	{
	}
	else if (channel[0] == 0)
	{
		// Acquire and open a channel.
		status	= _dma_channel_acquire(dma, need, channel);

		if (status == (U32) GSC_SUCCESS)
			status	= _dma_channel_open(dma, channel[0], type, fn_desc, io);

		if (status != (U32) GSC_SUCCESS)
			_dma_channel_release(channel);

		gsc_sem_unlock(&dma->sem);
	}
	else if (channel[0]->type == type)
	{
		// Use the existing channel as is.
		status	= (U32) GSC_SUCCESS;
		gsc_sem_unlock(&dma->sem);
	}
	else
	{
		// Close then re-open the existing channel.
		status	= _dma_channel_close(dma, channel[0]);

		if (status == (U32) GSC_SUCCESS)
			status	= _dma_channel_open(dma, channel[0], type, fn_desc, io);

		gsc_sem_unlock(&dma->sem);
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	gsc_dma_create
*
*	Purpose:
*
*		Setup the referenced DMA structure for initial device use.
*
*	Arguments:
*
*		dma			The DMA structure of interest.
*
*		device		The specific data used to access the device.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_create(gsc_dma_t* dma, gsc_device_t* device)
{
	U32	stat;
	U32	status	= (U32) GSC_SUCCESS;

	memset(dma, 0, sizeof(gsc_dma_t));
	dma->device		= device;
	stat			= gsc_sem_create(&dma->sem, "GSC DMA");
	status			= (status == (U32) GSC_SUCCESS) ? stat : status;

	dma->ch0.index	= 0;

	dma->ch1.index	= 1;

	return(status);
}



/*****************************************************************************
*
*	Function:	gsc_dma_destroy
*
*	Purpose:
*
*		Cleanup the DMA structure as the device is no longer in use.
*
*	Arguments:
*
*		dma			The DMA structure of interest.
*
*	Returned:
*
*		None.
*
*****************************************************************************/

void gsc_dma_destroy(gsc_dma_t* dma)
{
	gsc_sem_destroy(&dma->sem);
	memset(dma, 0, sizeof(gsc_dma_t));
}



/******************************************************************************
*
*	Function:	gsc_dma_done
*
*	Purpose:
*
*		Perform any post processing of a DMA request.
*
*	Arguments:
*
*		io			The common I/O parameter structure.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
******************************************************************************/

U32 gsc_dma_done(gsc_io_t* io)
{
	U32	status;

	if (io->xfered_tot >= io->xfer_tot)
	{
		// The DMA transfer did complete.
		status	= (U32) GSC_SUCCESS;
	}
	else if (io->dma_channel)
	{
		// The transfer did not fully complete, so we may need to cancel a DMA.
		status	= gsc_drv_dma_status(io->device, io->dma_channel->index);

		if (status == (U32) GSC_DMA_DONE)
		{
			status	= (U32) GSC_SUCCESS;
		}
		else if (	(status == (U32) GSC_DMA_PAUSED) ||
					(status == (U32) GSC_DMA_IN_PROGRESS))
		{
			// The DMA request is still active.
			status	= gsc_dma_abort(io);
		}
	}
	else
	{
		// We don't have a DMA channel to access.
		status	= (U32) GSC_SUCCESS;
	}

	if (io->dma_channel == NULL)
	{
		// We don't have a channel to close.
	}
	else if (status == (U32) GSC_SUCCESS)
	{
		if (io->dma_dynamic)
			status	= gsc_dma_channel_close(io->dma, &io->dma_channel);
	}
	else if (io->dma_dynamic)
	{
		gsc_dma_channel_close(io->dma, &io->dma_channel);
	}

	return(status);
}



/*****************************************************************************
*
*	Function:	gsc_dma_init
*
*	Purpose:
*
*		Initialize the referenced structure to the device's initla open()
*		state.
*
*	Arguments:
*
*		dma			The DMA structure of interest.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_init(gsc_dma_t* dma, U32 ch0, U32 ch1)
{
	dma->ch0.in_use			= 0;
	dma->ch0.capabilities	= ch0;
	dma->ch0.type			= GSC_DMA_TYPE_NONE;

	dma->ch1.in_use			= 0;
	dma->ch1.capabilities	= ch1;
	dma->ch1.type			= GSC_DMA_TYPE_NONE;
	return((U32) GSC_SUCCESS);
}



/*****************************************************************************
*
*	Function:	gsc_dma_transfer
*
*	Purpose:
*
*		Perform a DMA data transfer per the given data.
*
*	Arguments:
*
*		dma			The DMA structure of interest.
*
*	Returned:
*
*		GSC_SUCCESS	All went well.
*		else		An appropriate error status.
*
*****************************************************************************/

U32 gsc_dma_transfer(gsc_io_t* io, U32 LocToPci)
{
	U32				status;
	int				retries	= 0;	
	gsc_dma_xfer_t	xfer;

	for (;;)	// A retry loop.
	{
		memset(&xfer, 0, sizeof(xfer));

		if (io->fifo_like)
			xfer.local_adrs			= GSC_REG_OFFSET(io->reg_adj);
		else
			xfer.local_adrs			= io->loc_adrs_adj;

		xfer.bytes				= io->xfer;
		xfer.local_to_pci		= (int) LocToPci;
		xfer.done_irq			= 1;

		if (io->data_size == 32)
		{
			if (xfer.bytes > 0x7FFFFC)
				xfer.bytes	= 0x7FFFFC;
		}
		else if (io->data_size == 16)
		{
			if (xfer.bytes > 0x7FFFFE)
				xfer.bytes	= 0x7FFFFE;
		}
		else	// 8-bits
		{
			if (xfer.bytes > 0x7FFFFF)
				xfer.bytes	= 0x7FFFFF;
		}

		io->xfer	= xfer.bytes;

		if (io->dma_channel->type == GSC_DMA_TYPE_BLOCK)
		{
			xfer.phys_adrs	= (UL) io->xfer_buf;
			status			= gsc_drv_dma_block_xfer(	io->device,
														io->dma_channel->index,
														&xfer);
		}
		else
		{
			xfer.user_adrs	= (UL) io->xfer_buf;
			status			= gsc_drv_dma_sgl_xfer(	io->device,
													io->dma_channel->index,
													&xfer);
		}

		if (status == (U32) GSC_DMA_IN_PROGRESS)
		{
			/*
			*	There have been cases where this status is returned repeatedly
			*	over a period of time. There was one case where it was
			*	returned over 10,000,000 time before I just quit. Don
			*/

			if ((retries % 10) == 9)
			{
				// Every 10 calls do a wait.
				gsc_sleep_ms(1);
				retries++;
				continue;
			}
			else if (retries <= 100)
			{
				retries++;
				continue;
			}
			else
			{
				// Things just are not working out. :(
				break;
			}
		}

		break;
	}

	return(status);
}


