/*--------------------------------------------------------------------------*
 * drivers/usb/mcd/usb_mcd_dma.h
 * 
 * USB controller driver
 * 
 * Copyright 2005,2006 Sony Corporation
 * 
 * This code is based on dma.h,
 * Copyright 2005 Mentor Graphics Corporation
 *
 * This file is part of the Inventra Controller Driver for Linux.
 *
 * The Inventra Controller Driver for Linux is free software; you
 * can redistribute it and/or modify it under the terms of the GNU
 * General Public License version 2 as published by the Free Software
 * Foundation.
 *
 * The Inventra Controller Driver for Linux is distributed in
 * the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with The Inventra Controller Driver for Linux ; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA  02111-1307  USA
 *
 * ANY DOWNLOAD, USE, REPRODUCTION, MODIFICATION OR DISTRIBUTION
 * OF THIS DRIVER INDICATES YOUR COMPLETE AND UNCONDITIONAL ACCEPTANCE
 * OF THOSE TERMS.THIS DRIVER IS PROVIDED "AS IS" AND MENTOR GRAPHICS
 * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, RELATED TO THIS DRIVER.
 * MENTOR GRAPHICS SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY; FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  MENTOR GRAPHICS DOES NOT PROVIDE SUPPORT
 * SERVICES OR UPDATES FOR THIS DRIVER, EVEN IF YOU ARE A MENTOR
 * GRAPHICS SUPPORT CUSTOMER.
 *---------------------------------------------------------------------------*/

#ifndef __USB_MCD_DMA_H__
#define __USB_MCD_DMA_H__

/**
 * Introduction.
 * The purpose of the DMA Controller Abstraction (DCA) is to allow the ICD
 * to use any DMA controller, 
 * since this is an option in the Inventra USB cores.
 * The assumptions are:
 * <ul>
 * <li>A DMA controller will be tied to an Inventra USB core in the
 * way specified in the Inventra core product specification.
 * <li>A DMA controller's base address in the memory map correlates
 * somehow to the Inventra USB core it serves.
 * </ul>
 * The responsibilities of an implementation include:
 * <ul>
 * <li>Allocating/releasing buffers for use with DMA
 * (this may be specific to a DMA controller, intervening busses, 
 * and a target's capabilities,
 * so the ICD cannot make assumptions or provide services here)
 * <li>Handling the details of moving multiple USB packets 
 * in cooperation with the Inventra USB core.
 * <li>Knowing the correlation between channels and the
 * Inventra core's local endpoint resources and data direction,
 * and maintaining a list of allocated/available channels.
 * <li>Updating channel status on interrupts,
 * whether shared with the Inventra core or separate.
 * <li>If the DMA interrupt is shared with the Inventra core,
 * handling it when called, and reporting whether it was the
 * source of interrupt.
 * </ul>
 */

/*************************** CONSTANTS ****************************/

/**
 * DMA channel status.
 */
enum mcd_dma_channel_status {
    /** A channel's status is unknown */
	MCD_DMA_STATUS_UNKNOWN,
    /** A channel is available (not busy and no errors) */
	MCD_DMA_STATUS_FREE,
    /** A channel is busy (not finished attempting its transactions) */
	MCD_DMA_STATUS_BUSY,
    /** A channel aborted its transactions due to a local bus error */
	MCD_DMA_STATUS_BUS_ABORT,
    /** A channel aborted its transactions due to a core error */
	MCD_DMA_STATUS_CORE_ABORT
};

/***************************** TYPES ******************************/

/**
 * DMA callback
 */
struct mcd_dma_channel;
typedef void (*mcd_dma_cb_t)(struct mcd_dma_channel *,
			     uint8_t, uint8_t, uint8_t, void *);

/************************** STRUCTURE *****************************/

/**
 * mcd_dma_channel.
 * A DMA channel.
 * @field pPrivateData channel-private data;
 * @field pController the DMA controller pointer
 * @field wMaxLength the maximum number of bytes the channel can move
 * in one transaction (typically representing many USB maximum-sized packets)
 * @field dwActualLength how many bytes have been transferred
 * @field bStatus current channel status (updated e.g. on interrupt)
 * @field pfDmaCallback completion callback
 * @field pData channel user data
 */
struct mcd_dma_channel {
	void *pPrivateData;
	struct mcd_dma_controller *pController;
	uint32_t dwMaxLength;
	uint32_t dwActualLength;
	enum mcd_dma_channel_status bStatus;
	mcd_dma_cb_t pfDmaCallback;
	void *pData;
};

/**
 * struct mcd_dma_controller.
 * A DMA Controller.
 * This is in a struct to allow the ICD to support
 * multiple cores of different types, 
 * since each may use a different type of DMA controller.
 * @field pPrivateData controller-private data;
s * not to be interpreted by the UCD
 * @field nIrq IRQ number
 * @field pfDmaStartController ICD calls this to start a DMA controller
 * @field pfDmaStopController ICD calls this to stop a DMA controller
 * @field pfDmaAllocateChannel ICD calls this to allocate a DMA channel
 * @field pfDmaReleaseChannel ICD calls this to release a DMA channel
 * @field pfDmaAllocateBuffer ICD calls this to allocate a DMA buffer
 * @field pfDmaReleaseBuffer ICD calls this to release a DMA buffer
 * @field pfDmaProgramChannel ICD calls this to program a DMA channel
 * @field pfDmaEnableIntChannel ICD calls this to be enable a interrupt of DMA
 *        channel
 * @field pfDmaDisableIntChannel ICD calls this to be disable a interrupt of DMA
 *        channel
 * @field pfDmaStartChannel ICD calls this to start a DMA channel
 * @field pfDmaStopChannel ICD calls this to stop a DMA channel
 * @field pfDmaGetChannelStatus ICD calls this to get a DMA channel's status
 * @field pfDmaControllerIsr ICD calls this (if non-NULL) from its ISR
 */
struct mcd_dma_controller {
	void *pPrivateData;
	int nIrq;
	 uint8_t(*pfDmaStartController) (struct mcd_dma_controller *
					 pController);
	 uint8_t(*pfDmaStopController) (struct mcd_dma_controller *
					pController);
	struct mcd_dma_channel *(*pfDmaAllocateChannel) (struct
							 mcd_dma_controller *
							 pController,
							 uint8_t bLocalEnd,
							 uint8_t bTransmit,
							 uint8_t bProtocol,
							 uint16_t
							 wMaxPacketSize);
	void (*pfDmaReleaseChannel) (struct mcd_dma_channel * pChannel);
	uint8_t *(*pfDmaAllocateBuffer) (struct mcd_dma_channel * pChannel,
					 uint32_t dwLength);
	 uint8_t(*pfDmaReleaseBuffer) (struct mcd_dma_channel * pChannel,
				       uint8_t * pBuffer);
	 uint8_t(*pfDmaProgramChannel) (struct mcd_dma_channel * pChannel,
					mcd_dma_cb_t pfDmaCallback,
					void *pData, uint16_t wPacketSize,
					uint8_t bMode, const uint8_t * pBuffer,
					uint32_t dwLength);
	 uint8_t(*pfDmaEnableIntChannel) (struct mcd_dma_channel * pChannel);
	 uint8_t(*pfDmaDisableIntChannel) (struct mcd_dma_channel * pChannel);
	 uint8_t(*pfDmaStartChannel) (struct mcd_dma_channel * pChannel);
	 uint8_t(*pfDmaStopChannel) (struct mcd_dma_channel * pChannel);
	enum mcd_dma_channel_status (*pfDmaGetChannelStatus) (struct
							      mcd_dma_channel *
							      pChannel);
	 irqreturn_t(*pfDmaControllerIsr) (int irq, void *__pController);
};

/**
 * struct mcd_dma_controller_factory.
 * A DMA controller factory.
 * To allow for multi-core implementations and different
 * types of cores and DMA controllers to co-exist,
 * it is necessary to create them from factories.
 * @field wCoreRegistersExtent the total size of the core's
 * register region with the DMA controller present,
 * for use in mapping the core into system memory.
 * For example, the MHDRC core takes 0x200 bytes of address space.
 * If your DMA controller starts at 0x200 and takes 0x100 bytes,
 * set this to 0x300.
 * @field pfNewDmaController create a DMA controller
 * @field pfDestroyDmaController destroy a DMA controller
 */
struct mcd_dma_controller_factory {
	uint16_t wCoreRegistersExtent;
	struct mcd_dma_controller *(*pfNewDmaController) (uint8_t * pCoreBase,
							  uint8_t bChannelCount,
							  int nIrq);
	void (*pfDestroyDmaController) (struct mcd_dma_controller *
					pController);
};

extern struct mcd_dma_controller_factory mcd_hdrc_dma_controller_factory;

/************************** FUNCTIONS *****************************/

/**
 * Instantiate a DMA controller.
 * Instantiate a software object representing a DMA controller.
 * @param pCoreBase the base address (in kernel space) of the core
 * It is assumed the DMA controller's registers' base address will be related
 * to this in some way.
 * @param bChannelCount number of DMA channels
 * @param nIrq IRQ number
 * @return non-NULL pointer on success
 * @return NULL on failure (out of memory or exhausted 
 * a fixed number of controllers)
 */
inline static struct mcd_dma_controller *mcd_dma_new_controller(uint8_t *
								pCoreBase,
								uint8_t
								bChannelCount,
								int nIrq)
{
	return mcd_hdrc_dma_controller_factory.pfNewDmaController(pCoreBase,
								  bChannelCount,
								  nIrq);
}

/**
 * Destroy DMA controller.
 * Destroy a previously-instantiated DMA controller.
 */
inline static void mcd_dma_destroy_controller(struct mcd_dma_controller
					      *pController)
{
	mcd_hdrc_dma_controller_factory.pfDestroyDmaController(pController);
}

/**
 * Start a DMA controller.
 * @param pController controller pointer return from mcd_dma_new_controller
 * @return TRUE on success
 * @return FALSE on failure (e.g. no DMAC appears present)
 */
inline static uint8_t mcd_dma_start_controller(struct mcd_dma_controller
					       *pController)
{
	return pController->pfDmaStartController(pController);
}

/**
 * Stop a DMA controller.
 * @param pController controller pointer return from mcd_dma_new_controller
 * @return TRUE on success
 * @return FALSE on failure; the ICD may try again
 */
inline static uint8_t mcd_dma_stop_controller(struct mcd_dma_controller
					      *pController)
{
	return pController->pfDmaStopController(pController);
}

/**
 * Allocate a DMA channel.
 * Allocate a DMA channel suitable for the given conditions.
 * @param bLocalEnd the local endpoint index (1-15)
 * @param bTransmit TRUE for transmit; FALSE for receive
 * @param bProtocol the USB protocol, as per USB 2.0 chapter 9
 * (0 => control, 1 => isochronous, 2 => bulk, 3 => interrupt)
 * @param wMaxPacketSize maximum packet size
 * @return a non-NULL pointer on success
 * @return NULL on failure (no channel available)
 */
inline static struct mcd_dma_channel *
    mcd_dma_allocate_channel(struct mcd_dma_controller *pController,
			     uint8_t bLocalEnd, uint8_t bTransmit,
			     uint8_t bProtocol, uint16_t wMaxPacketSize)
{
	return pController->pfDmaAllocateChannel(pController, bLocalEnd,
						 bTransmit, bProtocol,
						 wMaxPacketSize);
}

/**
 * Release a DMA channel.
 * Release a previously-allocated DMA channel.
 * The ICD guarantess to no longer reference this channel.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 */
inline static void mcd_dma_release_channel(struct mcd_dma_channel *pChannel)
{
	pChannel->pController->pfDmaReleaseChannel(pChannel);
}

/**
 * Allocate DMA buffer.
 * Allocate a buffer suitable for DMA operations with the given channel.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @param dwLength length, in bytes, desired for the buffer
 * @return a non-NULL pointer to a suitable region (in processor space)
 * on success
 * @return NULL on failure
 */
inline static uint8_t *mcd_dma_allocate_buffer(struct mcd_dma_channel *pChannel,
					       uint32_t dwLength)
{
	return pChannel->pController->pfDmaAllocateBuffer(pChannel, dwLength);
}

/**
 * Release DMA buffer.
 * Release a DMA buffer previously acquiring by a successful call
 * to pController->pfDmaAllocateBuffer.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @param pBuffer the buffer pointer
 * @return TRUE on success
 * @return FALSE on failure (e.g. the controller owns the buffer at present)
 */
inline static uint8_t mcd_dma_release_buffer(struct mcd_dma_channel *pChannel,
					     uint8_t * pBuffer)
{
	return pChannel->pController->pfDmaReleaseBuffer(pChannel, pBuffer);
}

/**
 * Program a DMA channel.
 * Program a DMA channel to move data at the core's request.
 * The local core endpoint and direction should already be known,
 * since they are specified in the pfDmaAllocateChannel call.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @param pfDmaCallback completion callback
 * @param pData a user data pointer for callback
 * @param wPacketSize the packet size
 * @param bMode TRUE if mode 1; FALSE if mode 0
 * @param pBuffer base address of data (in processor space)
 * @param dwLength the number of bytes to transfer;
 * guaranteed by the ICD to be no larger than the channel's reported dwMaxLength
 * @return TRUE on success
 * @return FALSE on error
 */
inline static uint8_t mcd_dma_program_channel(struct mcd_dma_channel *pChannel,
					      mcd_dma_cb_t pfDmaCallback,
					      void *pData, uint16_t wPacketSize,
					      uint8_t bMode,
					      const uint8_t * pBuffer,
					      uint32_t dwLength)
{
	return pChannel->pController->pfDmaProgramChannel(pChannel,
							  pfDmaCallback,
							  pData,
							  wPacketSize,
							  bMode,
							  pBuffer, dwLength);
}

/**
 * Enable a interrupt channel.
 * After programming a DMA channel, be enable a interrupt of DMA channel.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @return TRUE on success
 * @return FALSE on error
 */
inline static uint8_t mcd_dma_enable_int_channel(struct mcd_dma_channel
						 *pChannel)
{
	return pChannel->pController->pfDmaEnableIntChannel(pChannel);
}

/**
 * Disable a interrupt channel.
 * Be disable a interrupt of DMA channel.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @return TRUE on success
 * @return FALSE on error
 */
inline static uint8_t mcd_dma_disable_int_channel(struct mcd_dma_channel
						  *pChannel)
{
	return pChannel->pController->pfDmaDisableIntChannel(pChannel);
}

/**
 * Start a DMA channel.
 * After programming a DMA channel, start DMA.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @return TRUE on success
 * @return FALSE on error
 */
inline static uint8_t mcd_dma_start_channel(struct mcd_dma_channel *pChannel)
{
	return pChannel->pController->pfDmaStartChannel(pChannel);
}

/**
 * Stop a DMA channel.
 * Stop active DMA channel.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->pfDmaAllocateChannel
 * @return TRUE on success
 * @return FALSE on error
 */
inline static uint8_t mcd_dma_stop_channel(struct mcd_dma_channel *pChannel)
{
	return pChannel->pController->pfDmaStopChannel(pChannel);
}

/**
 * Get DMA channel status.
 * Get the current status of a DMA channel, if the hardware allows.
 * @param pChannel pointer to a channel obtained by 
 * a successful call to pController->DmaAllocateChannel
 * @return current status 
 * (MCD_DMA_STATUS_UNKNOWN if hardware does not have readable status)
 */
inline static enum mcd_dma_channel_status
mcd_dma_get_channel_status(struct mcd_dma_channel *pChannel)
{
	return pChannel->pController->pfDmaGetChannelStatus(pChannel);
}

#endif				/* multiple inclusion protection */
