/*--------------------------------------------------------------------------*
 * drivers/usb/mcd/usb_mcd_dma.c
 * 
 * USB controller driver
 * 
 * Copyright 2005,2006 Sony Corporation
 * 
 * This code is based on musbhsdma.c,
 * 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.
 *---------------------------------------------------------------------------*/

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/interrupt.h>

#include "usb_mcd_cnf.h"
#include "usb_mcd_dma.h"
#include "usb_mcd_debug.h"
#include "usb_mcd_access.h"
#include "usb_mcd_musbhdrc.h"

#define MCD_HSDMA
/****************************** CONSTANTS ********************************/

#define MCD_O_HSDMA_BASE    0x200
#define MCD_O_HSDMA_INTR    0x200

#define MCD_O_HSDMA_CONTROL 4
#define MCD_O_HSDMA_ADDRESS 8
#define MCD_O_HSDMA_COUNT   0xc

#define MCD_HSDMA_CHANNEL_OFFSET(_bChannel, _bOffset) (MCD_O_HSDMA_BASE + (_bChannel << 4) + _bOffset)

/* control register (16-bit): */
#define MCD_S_HSDMA_ENABLE	0
#define MCD_S_HSDMA_TRANSMIT	1
#define MCD_S_HSDMA_MODE1	2
#define MCD_S_HSDMA_IRQENABLE	3
#define MCD_S_HSDMA_ENDPOINT	4
#define MCD_S_HSDMA_BUSERROR	8
#define MCD_S_HSDMA_BURSTMODE	9

/****************************** STRUCTURES *******************************/

struct mcd_hsdma_channel {
	struct mcd_dma_channel Channel;
	struct mcd_hsdma_controller *pController;
	uint32_t dwStartAddress;
	uint32_t dwCount;
	uint8_t bIndex;
	uint8_t bEnd;
	uint8_t bProtocol;
	uint8_t bTransmit;
};

struct mcd_hsdma_controller {
	struct mcd_dma_controller Controller;
	struct mcd_hsdma_channel *paChannel;
	uint8_t *pCoreBase;
	uint8_t bChannelCount;
	uint8_t bmUsedChannels;
};

/******************************* FORWARDS ********************************/

static uint8_t mcd_hsdma_start_controller(struct mcd_dma_controller
					  *pController);

static uint8_t mcd_hsdma_stop_controller(struct mcd_dma_controller
					 *pController);

static struct mcd_dma_channel *mcd_hsdma_allocate_channel(struct
							  mcd_dma_controller
							  *pController,
							  uint8_t bLocalEnd,
							  uint8_t bTransmit,
							  uint8_t bProtocol,
							  uint16_t
							  wMaxPacketSize);

static void mcd_hsdma_release_channel(struct mcd_dma_channel *pChannel);

static uint8_t *mcd_hsdma_allocate_buffer(struct mcd_dma_channel *pChannel,
					  uint32_t dwLength);

static uint8_t mcd_hsdma_release_buffer(struct mcd_dma_channel *pChannel,
					uint8_t * pBuffer);

static uint8_t mcd_hsdma_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);

static uint8_t mcd_hsdma_enable_int_channel(struct mcd_dma_channel *pChannel);

static uint8_t mcd_hsdma_disable_int_channel(struct mcd_dma_channel *pChannel);

static uint8_t mcd_hsdma_start_channel(struct mcd_dma_channel *pChannel);

static uint8_t mcd_hsdma_stop_channel(struct mcd_dma_channel *pChannel);

static enum mcd_dma_channel_status mcd_hsdma_get_channel_status(struct
								mcd_dma_channel
								*pChannel);

static irqreturn_t mcd_hsdma_controller_isr(int irq, void *__pController);

static struct mcd_dma_controller *mcd_hsdma_new_controller(uint8_t * pCoreBase,
							   uint8_t
							   bChannelCount,
							   int nIrq);

static void mcd_hsdma_destroy_controller(struct mcd_dma_controller
					 *pController);

/******************************* GLOBALS *********************************/

struct mcd_dma_controller_factory mcd_hdrc_dma_controller_factory = {
	0x300,
	mcd_hsdma_new_controller,
	mcd_hsdma_destroy_controller
};

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

#ifdef MCD_HSDMA

static uint8_t mcd_hsdma_start_controller(struct mcd_dma_controller
					  *pController)
{
	struct mcd_hsdma_controller *pHsController
	    = (struct mcd_hsdma_controller *)pController->pPrivateData;
	mcd_hsdma_stop_controller(pController);
	pHsController->bmUsedChannels = 0;	/* all channels are not used */

	return true;
}

static uint8_t mcd_hsdma_stop_controller(struct mcd_dma_controller *pController)
{
	uint8_t bBit;
	struct mcd_hsdma_controller *pHsController
	    = (struct mcd_hsdma_controller *)pController->pPrivateData;

	for (bBit = 0; bBit < pHsController->bChannelCount; bBit++) {
		mcd_hsdma_disable_int_channel(&pHsController->paChannel[bBit].
					      Channel);
		mcd_hsdma_stop_channel(&pHsController->paChannel[bBit].Channel);
	}

	return true;
}

static struct mcd_dma_channel *mcd_hsdma_allocate_channel(struct
							  mcd_dma_controller
							  *pController,
							  uint8_t bLocalEnd,
							  uint8_t bTransmit,
							  uint8_t bProtocol,
							  uint16_t
							  wMaxPacketSize)
{
	uint8_t bBit;
	struct mcd_dma_channel *pChannel = NULL;
	struct mcd_hsdma_channel *pImplChannel = NULL;
	struct mcd_hsdma_controller *pHsController
	    = (struct mcd_hsdma_controller *)pController->pPrivateData;

	for (bBit = 0; bBit < pHsController->bChannelCount; bBit++) {
		if (!(pHsController->bmUsedChannels & (1 << bBit))) {
			pHsController->bmUsedChannels |= (1 << bBit);
			pImplChannel = &(pHsController->paChannel[bBit]);
			pImplChannel->bEnd = bLocalEnd;
			pImplChannel->bProtocol = bProtocol;
			pImplChannel->bTransmit = bTransmit;
			pChannel = &(pImplChannel->Channel);
			pChannel->bStatus = MCD_DMA_STATUS_FREE;
			pChannel->dwMaxLength = 0x10000;
			break;
		}
	}
	return pChannel;
}

static void mcd_hsdma_release_channel(struct mcd_dma_channel *pChannel)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;

	pImplChannel->pController->bmUsedChannels &=
	    ~(1 << pImplChannel->bIndex);
	pImplChannel->Channel.bStatus = MCD_DMA_STATUS_FREE;

	if (pImplChannel->Channel.pfDmaCallback) {
		pImplChannel->Channel.pfDmaCallback = NULL;
	}
}

static uint8_t *mcd_hsdma_allocate_buffer(struct mcd_dma_channel *pChannel,
					  uint32_t dwLength)
{
	/* nothing to do */
	return (uint8_t *) 0;
}

static uint8_t
mcd_hsdma_release_buffer(struct mcd_dma_channel *pChannel, uint8_t * pBuffer)
{
	/* nothing to do */
	return false;
}

static uint8_t mcd_hsdma_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)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint8_t bChannel = pImplChannel->bIndex;
	uint16_t wCsr;

	/* EP number */
	wCsr = pImplChannel->bEnd << MCD_S_HSDMA_ENDPOINT;
	/* DMA mode */
	if (bMode) {
		wCsr |= 1 << MCD_S_HSDMA_MODE1;
	}
#if !defined(CONFIG_ARCH_CXD4105)
	/* BURST mode3 */
	wCsr |= 3 << MCD_S_HSDMA_BURSTMODE;
#endif				/* defined(CONFIG_ARCH_CXD4108) */
	/* Direction */
	if (pImplChannel->bTransmit) {
		wCsr |= 1 << MCD_S_HSDMA_TRANSMIT;
	}
	/* control */
	MCD_WRITE16(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_CONTROL),
		    wCsr);

	/* address/count */
	MCD_WRITE32(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_ADDRESS),
		    (uint32_t) pBuffer /* pBuffer is bus address */ );
	MCD_WRITE32(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_COUNT),
		    dwLength);

	/* Channel parameters */
	pChannel->dwActualLength = 0L;
	pChannel->pfDmaCallback = pfDmaCallback;
	pChannel->pData = pData;
	pImplChannel->dwStartAddress = (uint32_t) pBuffer;
	pImplChannel->dwCount = dwLength;

	return true;
}

static uint8_t mcd_hsdma_enable_int_channel(struct mcd_dma_channel *pChannel)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint16_t wCsr;
	uint8_t bChannel = pImplChannel->bIndex;

	/* read control register */
	wCsr = MCD_READ16(pBase,
			  MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						   MCD_O_HSDMA_CONTROL));

	/* set IRQ Enable */
	wCsr |= 1 << MCD_S_HSDMA_IRQENABLE;

	/* control (this should start things) */
	MCD_WRITE16(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_CONTROL),
		    wCsr);

	return true;
}

static uint8_t mcd_hsdma_disable_int_channel(struct mcd_dma_channel *pChannel)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint16_t wCsr;
	uint8_t bChannel = pImplChannel->bIndex;

	/* read control register */
	wCsr = MCD_READ16(pBase,
			  MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						   MCD_O_HSDMA_CONTROL));

	/* clear IRQ Enable */
	wCsr &= ~(1 << MCD_S_HSDMA_IRQENABLE);

	/* control (this should start things) */
	MCD_WRITE16(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_CONTROL),
		    wCsr);
	return true;
}

static uint8_t mcd_hsdma_start_channel(struct mcd_dma_channel *pChannel)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint16_t wCsr;
	uint8_t bChannel = pImplChannel->bIndex;

	/* read control register */
	wCsr = MCD_READ16(pBase,
			  MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						   MCD_O_HSDMA_CONTROL));

	/* set DMA Enable */
	wCsr |= 1 << MCD_S_HSDMA_ENABLE;

	/* control (this should start things) */
	MCD_WRITE16(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_CONTROL),
		    wCsr);

	return true;
}

static uint8_t mcd_hsdma_stop_channel(struct mcd_dma_channel *pChannel)
{
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint16_t wCsr;
	uint8_t bChannel = pImplChannel->bIndex;

	/* read control register */
	wCsr = MCD_READ16(pBase,
			  MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						   MCD_O_HSDMA_CONTROL));

	/* clear DMA Enable */
	wCsr &= ~(1 << MCD_S_HSDMA_ENABLE);

	/* control (this should start things) */
	MCD_WRITE16(pBase,
		    MCD_HSDMA_CHANNEL_OFFSET(bChannel, MCD_O_HSDMA_CONTROL),
		    wCsr);

	return true;
}

static enum mcd_dma_channel_status mcd_hsdma_get_channel_status(struct
								mcd_dma_channel
								*pChannel)
{
	uint32_t dwAddress;
	struct mcd_hsdma_channel *pImplChannel =
	    (struct mcd_hsdma_channel *)pChannel->pPrivateData;
	struct mcd_hsdma_controller *pController = pImplChannel->pController;
	uint8_t *pBase = pController->pCoreBase;
	uint8_t bChannel = pImplChannel->bIndex;
	uint16_t wCsr;

	/* Read Control register */
	wCsr = MCD_READ16(pBase,
			  MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						   MCD_O_HSDMA_CONTROL));

	/* most DMA controllers would update the count register for simplicity... */
	dwAddress
	    = MCD_READ32(pBase,
			 MCD_HSDMA_CHANNEL_OFFSET(bChannel,
						  MCD_O_HSDMA_ADDRESS));
	pImplChannel->Channel.dwActualLength =
	    dwAddress - pImplChannel->dwStartAddress;

	/* Bus Error */
	if (wCsr & (1 << MCD_S_HSDMA_BUSERROR)) {
		return MCD_DMA_STATUS_BUS_ABORT;
	}
	/* Now DMA working */
	if (dwAddress < (pImplChannel->dwStartAddress + pImplChannel->dwCount)) {
		return MCD_DMA_STATUS_BUSY;
	}
	/* DMA is IDLE */
	return MCD_DMA_STATUS_FREE;
}

static irqreturn_t
mcd_hsdma_controller_isr(int irq, void *__pController)
{
	uint8_t bChannel;
	uint16_t wCsr;
	uint32_t dwAddress;
	struct mcd_hsdma_channel *pImplChannel;
	struct mcd_hsdma_controller *pHsController =
	    (struct mcd_hsdma_controller *)__pController;
	uint8_t *pBase = pHsController->pCoreBase;
	uint8_t bIntr = MCD_READ8(pBase, MCD_O_HSDMA_INTR);

	if (!bIntr) {
		return IRQ_NONE;
	}
	for (bChannel = 0; bChannel < pHsController->bChannelCount; bChannel++) {
		if (bIntr & (1 << bChannel)) {
			pImplChannel
			    =
			    (struct mcd_hsdma_channel *)&(pHsController->
							  paChannel[bChannel]);
			/* Read Control register */
			wCsr = MCD_READ16(pBase,
				 MCD_HSDMA_CHANNEL_OFFSET(bChannel,
							  MCD_O_HSDMA_CONTROL));
			/* most DMA controllers would update the count register
			   for simplicity... */
			dwAddress = MCD_READ32(pBase,
				 MCD_HSDMA_CHANNEL_OFFSET(bChannel,
							  MCD_O_HSDMA_ADDRESS));
			pImplChannel->Channel.dwActualLength
			    = dwAddress - pImplChannel->dwStartAddress;
			/* Bus Error */
			if (wCsr & (1 << MCD_S_HSDMA_BUSERROR)) {
				pImplChannel->Channel.bStatus =
				    MCD_DMA_STATUS_BUS_ABORT;
			}
			/* DMA is completed */
			else {
				pImplChannel->Channel.bStatus =
				    MCD_DMA_STATUS_FREE;
			}
			/* If callback exists, call it and set NULL */
			if (pImplChannel->Channel.pfDmaCallback) {
				(pImplChannel->Channel.
				 pfDmaCallback) (&pImplChannel->Channel,
						 pImplChannel->bEnd,
						 pImplChannel->bTransmit,
						 pImplChannel->bProtocol,
						 pImplChannel->Channel.pData);
			}
		}
	}
	return IRQ_HANDLED;
}

#endif				/* MCD_HSDMA */

static struct mcd_dma_controller *mcd_hsdma_new_controller(uint8_t * pCoreBase,
							   uint8_t
							   bChannelCount,
							   int nIrq)
{
	uint8_t bBit;
	struct mcd_dma_controller *pResult = NULL;
#ifdef MCD_HSDMA
	struct mcd_dma_controller *pController;
	struct mcd_hsdma_controller *pHsController;

	if (bChannelCount == 0) {
		return pResult;
	}

	pHsController =
	    (struct mcd_hsdma_controller *)
	    kmalloc(sizeof(struct mcd_hsdma_controller), GFP_KERNEL);

	if (pHsController) {
		/* Set params to mcd_hsdma_controller */
		memset(pHsController, 0, sizeof(struct mcd_hsdma_controller));
		pHsController->paChannel
		    =
		    (struct mcd_hsdma_channel *)
		    kmalloc(sizeof(struct mcd_hsdma_channel) * bChannelCount,
			    GFP_KERNEL);
		if (!pHsController->paChannel) {
			kfree(pHsController);
			return pResult;
		}
		memset(pHsController->paChannel, 0,
		       sizeof(struct mcd_hsdma_channel) * bChannelCount);

		pHsController->bChannelCount = bChannelCount;
		pHsController->pCoreBase = pCoreBase;
		/* Set params to struct mcd_dma_controller */
		pController = &pHsController->Controller;
		pController->pPrivateData = (void *)pHsController;
		pController->nIrq = nIrq;
		pController->pfDmaStartController = mcd_hsdma_start_controller;
		pController->pfDmaStopController = mcd_hsdma_stop_controller;
		pController->pfDmaAllocateChannel = mcd_hsdma_allocate_channel;
		pController->pfDmaReleaseChannel = mcd_hsdma_release_channel;
		pController->pfDmaAllocateBuffer = mcd_hsdma_allocate_buffer;
		pController->pfDmaReleaseBuffer = mcd_hsdma_release_buffer;
		pController->pfDmaProgramChannel = mcd_hsdma_program_channel;
		pController->pfDmaEnableIntChannel =
		    mcd_hsdma_enable_int_channel;
		pController->pfDmaDisableIntChannel =
		    mcd_hsdma_disable_int_channel;
		pController->pfDmaStartChannel = mcd_hsdma_start_channel;
		pController->pfDmaStopChannel = mcd_hsdma_stop_channel;
		pController->pfDmaGetChannelStatus =
		    mcd_hsdma_get_channel_status;
		pController->pfDmaControllerIsr = mcd_hsdma_controller_isr;
		pResult = &(pHsController->Controller);

		for (bBit = 0; bBit < bChannelCount; bBit++) {
			pHsController->paChannel[bBit].bIndex = bBit;
			pHsController->paChannel[bBit].Channel.pPrivateData
			    = (void *)&pHsController->paChannel[bBit];
			pHsController->paChannel[bBit].Channel.pController =
			    pController;
			pHsController->paChannel[bBit].pController =
			    pHsController;
		}
	}
#endif
	return pResult;
}

static void mcd_hsdma_destroy_controller(struct mcd_dma_controller *pController)
{
#ifdef MCD_HSDMA
	struct mcd_hsdma_controller *pHsController =
	    (struct mcd_hsdma_controller *)pController->pPrivateData;

	if (pHsController) {
		kfree(pHsController->paChannel);
		kfree(pHsController);
	}
#endif
}
