/******************************************************************
 * Copyright 2005 Mentor Graphics Corporation
 * Copyright (C) 2005-2006 by Texas Instruments
 *
 * 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.
 ******************************************************************/

/*
 * DMA implementation for high-speed controllers.
 */

#include "musbdefs.h"


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

#define MGC_O_HSDMA_BASE    0x200
#define MGC_O_HSDMA_INTR    0x200

#define MGC_O_HSDMA_CONTROL 4
#define MGC_O_HSDMA_ADDRESS 8
#define MGC_O_HSDMA_COUNT   0xc

#define MGC_HSDMA_CHANNEL_OFFSET(_bChannel, _bOffset)		\
		(MGC_O_HSDMA_BASE + (_bChannel << 4) + _bOffset)

/* control register (16-bit): */
#define MGC_S_HSDMA_ENABLE	0
#define MGC_S_HSDMA_TRANSMIT	1
#define MGC_S_HSDMA_MODE1	2
#define MGC_S_HSDMA_IRQENABLE	3
#define MGC_S_HSDMA_ENDPOINT	4
#define MGC_S_HSDMA_BUSERROR	8
#define MGC_S_HSDMA_BURSTMODE	9
#define MGC_M_HSDMA_BURSTMODE	(3 << MGC_S_HSDMA_BURSTMODE)
#define MGC_HSDMA_BURSTMODE_UNSPEC  0
#define MGC_HSDMA_BURSTMODE_INCR4   1
#define MGC_HSDMA_BURSTMODE_INCR8   2
#define MGC_HSDMA_BURSTMODE_INCR16  3

#define MGC_HSDMA_CHANNELS 8

/******************************* Types ********************************/

struct _MGC_HsDmaController;

typedef struct {
	struct dma_channel Channel;
	struct _MGC_HsDmaController *pController;
	u32 dwStartAddress;
	u32 dwCount;
	u8 bIndex;
	u8 bEnd;
	u8 bTransmit;
} MGC_HsDmaChannel;

struct hsdma {
	struct dma_controller Controller;
	MGC_HsDmaChannel aChannel[MGC_HSDMA_CHANNELS];
	void *pDmaPrivate;
	void __iomem *pCoreBase;
	u8 bChannelCount;
	u8 bmUsedChannels;
};

/* FIXME remove typedef noise */
typedef struct hsdma MGC_HsDmaController;

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

static int MGC_HsDmaStartController(struct dma_controller *c)
{
	/* nothing to do */
	return 0;
}

static int MGC_HsDmaStopController(struct dma_controller *c)
{
	/* nothing to do */
	return 0;
}

static struct dma_channel *MGC_HsDmaAllocateChannel(
		struct dma_controller *c,
		struct musb_hw_ep *hw_ep,
		u8 bTransmit)
{
	u8 bBit;
	struct dma_channel *pChannel = NULL;
	MGC_HsDmaChannel *pImplChannel = NULL;
	MGC_HsDmaController *pController;

	pcontroller = container_of(c, struct hsdma, Controller);
	for (bBit = 0; bBit < MGC_HSDMA_CHANNELS; bBit++) {
		if (!(pController->bmUsedChannels & (1 << bBit))) {
			pController->bmUsedChannels |= (1 << bBit);
			pImplChannel = &(pController->aChannel[bBit]);
			pImplChannel->pController = pController;
			pImplChannel->bIndex = bBit;
			pImplChannel->bEnd = hw_ep->bLocalEnd;
			pImplChannel->bTransmit = bTransmit;
			pChannel = &(pImplChannel->Channel);
			pChannel->pPrivateData = pImplChannel;
			pChannel->bStatus = MGC_DMA_STATUS_FREE;
			pChannel->dwMaxLength = 0x10000;
			/* Tx => mode 1; Rx => mode 0 */
			pChannel->bDesiredMode = bTransmit;
			pChannel->dwActualLength = 0;
			break;
		}
	}
	return pChannel;
}

static void MGC_HsDmaReleaseChannel(struct dma_channel *pChannel)
{
	MGC_HsDmaChannel *pImplChannel =
	    (MGC_HsDmaChannel *) pChannel->pPrivateData;

	pImplChannel->pController->bmUsedChannels &=
	    ~(1 << pImplChannel->bIndex);
	pChannel->bStatus = MGC_DMA_STATUS_FREE;
}

static void clear_state(struct dma_channel *pChannel)
{
	MGC_HsDmaChannel *pImplChannel =
	    (MGC_HsDmaChannel *) pChannel->pPrivateData;
	MGC_HsDmaController *pController = pImplChannel->pController;
	u8 *pBase = pController->pCoreBase;
	u8 bChannel = pImplChannel->bIndex;

	musb_writew(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_CONTROL),
		    0);
	musb_writel(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_ADDRESS),
		    0);
	musb_writel(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_COUNT),
		    0);

	pChannel->dwActualLength = 0L;
	pImplChannel->dwStartAddress = 0;
	pImplChannel->dwCount = 0;
}

static u8 configure_channel(struct dma_channel *pChannel,
				  u16 wPacketSize, u8 bMode,
				  dma_addr_t dma_addr, u32 dwLength)
{
	MGC_HsDmaChannel *pImplChannel =
	    (MGC_HsDmaChannel *) pChannel->pPrivateData;
	MGC_HsDmaController *pController = pImplChannel->pController;
	u8 *pBase = pController->pCoreBase;
	u8 bChannel = pImplChannel->bIndex;
	u16 wCsr = 0;

	DBG(2, "%p, pkt_sz %d, addr 0x%x, len %d, mode %d\n",
	    pChannel, wPacketSize, dma_addr, dwLength, bMode);

	if (bMode) {
		wCsr |= 1 << MGC_S_HSDMA_MODE1;
		if (dwLength < wPacketSize) {
			return FALSE;
		}
		if (wPacketSize >= 64) {
			wCsr |=
			    MGC_HSDMA_BURSTMODE_INCR16 << MGC_S_HSDMA_BURSTMODE;
		} else if (wPacketSize >= 32) {
			wCsr |=
			    MGC_HSDMA_BURSTMODE_INCR8 << MGC_S_HSDMA_BURSTMODE;
		} else if (wPacketSize >= 16) {
			wCsr |=
			    MGC_HSDMA_BURSTMODE_INCR4 << MGC_S_HSDMA_BURSTMODE;
		}
	}

	wCsr |= (pImplChannel->bEnd << MGC_S_HSDMA_ENDPOINT)
		| (1 << MGC_S_HSDMA_ENABLE)
		| (1 << MGC_S_HSDMA_IRQENABLE)
		| (pImplChannel->bTransmit ? (1 << MGC_S_HSDMA_TRANSMIT) : 0);

	/* address/count */
	musb_writel(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_ADDRESS),
		    dma_addr);
	musb_writel(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_COUNT),
		    dwLength);

	/* control (this should start things) */
	musb_writew(pBase,
		    MGC_HSDMA_CHANNEL_OFFSET(bChannel, MGC_O_HSDMA_CONTROL),
		    wCsr);

	return TRUE;
}

static int MGC_HsDmaProgramChannel(struct dma_channel * pChannel,
				  u16 wPacketSize, u8 bMode,
				  dma_addr_t dma_addr, u32 dwLength)
{
	MGC_HsDmaChannel *pImplChannel =
	    (MGC_HsDmaChannel *) pChannel->pPrivateData;

	DBG(2, "pkt_sz %d, dma_addr 0x%x length %d, mode %d\n",
	       wPacketSize, dma_addr, dwLength, bMode);

	BUG_ON(pChannel->bStatus != MGC_DMA_STATUS_FREE);

	pChannel->dwActualLength = 0L;
	pImplChannel->dwStartAddress = dma_addr;
	pImplChannel->dwCount = dwLength;

	pChannel->bStatus = MGC_DMA_STATUS_BUSY;

	if ((bMode == 1) && (dwLength >= wPacketSize)) {

#if 0
		/* mode 1 sends an extra IN token at the end of
		 * full packet transfer in host Rx
		 */
		if (dwLength % wPacketSize == 0)
			dwLength -= wPacketSize;

		/* mode 1 doesn't give an interrupt on short packet */
		configure_channel(pChannel, wPacketSize, 1, dma_addr,
				  dwLength & ~(wPacketSize - 1));
		/* the rest (<= pkt_size) will be transferred in mode 0 */
#endif

		configure_channel(pChannel, wPacketSize, 1, dma_addr,
				  dwLength);

	} else
		configure_channel(pChannel, wPacketSize, 0, dma_addr,
				  dwLength);

	return TRUE;
}

// REVISIT...
static int MGC_HsDmaAbortChannel(struct dma_channel *pChannel)
{
	clear_state(pChannel);
	pChannel->bStatus = MGC_DMA_STATUS_FREE;
	return 0;
}

static irqreturn_t
hsdma_irq(int irq, void *pPrivateData, struct pt_regs *regs)
{
	u8 bChannel;
	u16 wCsr;
	u32 dwAddress;
	MGC_HsDmaChannel *pImplChannel;
	MGC_HsDmaController *pController = pPrivateData;
	u8 *pBase = pController->pCoreBase;
	struct dma_channel *pChannel;
	u8 bIntr = musb_readb(pBase, MGC_O_HSDMA_INTR);

	if (!bIntr)
		return IRQ_NONE;

	for (bChannel = 0; bChannel < MGC_HSDMA_CHANNELS; bChannel++) {
		if (bIntr & (1 << bChannel)) {

			pImplChannel = (MGC_HsDmaChannel *)
					&(pController->aChannel[bChannel]);
			pChannel = &pImplChannel->Channel;

			wCsr = musb_readw(pBase,
				       MGC_HSDMA_CHANNEL_OFFSET(bChannel,
							MGC_O_HSDMA_CONTROL));

			if (wCsr & (1 << MGC_S_HSDMA_BUSERROR)) {
				pImplChannel->Channel.bStatus =
				    MGC_DMA_STATUS_BUS_ABORT;
			} else {
				dwAddress = musb_readl(pBase,
						       MGC_HSDMA_CHANNEL_OFFSET
						       (bChannel,
							MGC_O_HSDMA_ADDRESS));
				pChannel->dwActualLength =
				    dwAddress - pImplChannel->dwStartAddress;

				DBG(2, "ch %p, 0x%x -> 0x%x (%d / %d) %s\n",
				    pChannel, pImplChannel->dwStartAddress,
				    dwAddress, pChannel->dwActualLength,
				    pImplChannel->dwCount,
				    (pChannel->dwActualLength <
					pImplChannel->dwCount) ?
					"=> reconfig 0": "=> complete");
#if 0
				if (pChannel->dwActualLength <
				    pImplChannel->dwCount) {
					/* mode 1 sends an extra IN request if
					the last packet is a complete packet */
					u16 newcsr = MGC_ReadCsr16(pBase,
							MGC_O_HDRC_RXCSR,
							pImplChannel->bEnd);
					newcsr &= ~(MGC_M_RXCSR_H_AUTOREQ |
						    MGC_M_RXCSR_H_REQPKT);
					MGC_WriteCsr16(pBase, MGC_O_HDRC_RXCSR,
						       pImplChannel->bEnd,
						       MGC_M_RXCSR_H_WZC_BITS |
								newcsr);

					configure_channel(pChannel,
						pImplChannel->wMaxPacketSize,
						0, dwAddress,
						pImplChannel->dwCount -
						    pChannel->dwActualLength);
				}
				else
#endif
				{
					pChannel->bStatus = MGC_DMA_STATUS_FREE;
					/* completed */
					musb_dma_completion(
						pController->pDmaPrivate,
						pImplChannel->bEnd,
						pImplChannel->bTransmit);
				}
			}
		}
	}
	return IRQ_HANDLED;
}

static void hsdma_controller_destroy(struct dma_controller *pController)
{
	MGC_HsDmaController *pHsController = pController->pPrivateData;

	if (pHsController) {
		pHsController->Controller.pPrivateData = NULL;
		kfree(pHsController);
	}
}

static struct dma_controller *
hsdma_controller_new(struct musb *pThis, void __iomem *pCoreBase)
{
	MGC_HsDmaController *pController;
	struct device *dev = pThis->controller;
	struct platform_device *pdev = to_platform_device(dev);
	int irq = platform_get_irq(pdev, 1);

	if (irq == 0) {
		dev_err(dev, "No DMA interrupt line!\n");
		return NULL;
	}

	if (!(pController = kzalloc(sizeof(MGC_HsDmaController), GFP_KERNEL)))
		return NULL;

	pController->bChannelCount = MGC_HSDMA_CHANNELS;
	pController->pDmaPrivate = pThis;
	pController->pCoreBase = pCoreBase;

	pController->Controller.pPrivateData = pController;
	pController->Controller.start = MGC_HsDmaStartController;
	pController->Controller.stop = MGC_HsDmaStopController;
	pController->Controller.channel_alloc = MGC_HsDmaAllocateChannel;
	pController->Controller.channel_release = MGC_HsDmaReleaseChannel;
	pController->Controller.channel_program = MGC_HsDmaProgramChannel;
	pController->Controller.channel_abort = MGC_HsDmaAbortChannel;

	if (request_irq(irq, hsdma_irq, SA_INTERRUPT,
			pThis->controller->bus_id, &pController->Controller)) {
		dev_err(dev, "request_irq %d failed!\n", irq);
		hsdma_controller_destroy(&pController->Controller);
		return NULL;
	}

	return &pController->Controller;
}

const struct dma_controller_factory dma_controller_factory = {
	.create =	hsdma_controller_new,
	.destroy =	hsdma_controller_destroy,
};
