/*
 *  arch/ppc/8220_io/bestdma/8220DmaApi.c
 *
 * Linux environment wrap routines for DMA API.
 *
 * Author: MontaVista Software, Inc.
 *         source@mvista.com
 *
 * Copyright 2003 Motorola Inc.
 * Copyright 2003-2004 MontaVista, Software, Inc.
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <MCD_dma.h>
#include "8220DmaApi.h"
#include <asm/mpc8220.h>
#include <asm/dma.h>

#define MAX_DMA_IPR 16

/* The on-chip registers */
static dmaRegs *my_sDmaRegs;

typedef struct channelInfo {
	void (*callBack) (void *);
	void (*handler) (int irq, void *dev_id, struct pt_regs * regs);
	MCD_bufDesc *pCurrBufDesc;	/* For chaining */
	char reserved;
	void *dev_id;
} channelInfo;

static channelInfo myChannelInfo[MAX_DMA_IPR];

/**********************************************************************************************
   MSD_ack() acknowledges an interrupt in the IPR
*/
void
MSD_ack (int channel)
{
	volatile u32 data;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	spin_lock_irqsave (irq_lock, flags);
	data = in_be32 (&my_sDmaRegs->intPending);
	data |= 1 << (channel & 0x1f);
	out_be32 (&my_sDmaRegs->intPending, data);
	spin_unlock_irqrestore (irq_lock, flags);
}

void
isrDma (int irq, void *dummy, struct pt_regs *fp)
{
	MCD_bufDesc *pNextBuf;
	MCD_XferProg prog;
	irq -= MPC8220_SDMA_IRQ_BASE;
	if (irq > MPC8220_SDMA_IRQ_NUM) {
		printk (KERN_INFO "isrDMA interrupt %d is unknown!\n", irq);
	return;
}

	/* Ack the interrupt for the channel */
	MSD_ack (irq);

	MCD_XferProgrQuery (irq, &prog);

	if (myChannelInfo[irq].pCurrBufDesc) {
		/* We're chaining - call the callback function if specified,
		 * with the just-completed descriptor's cookie as argument
		 */
		if (myChannelInfo[irq].callBack)
		(*myChannelInfo[irq].callBack) ((void *) myChannelInfo[irq].
			pCurrBufDesc->info);

		/* Update the notion of current buffer if chaining
		 *
		 * NOTE: the DMA controller needs the MCD_bufDesc structure
		 * chain pointers (next) to refer to PHYSICAL addresses.  If
		 * the memory containing the MCD_bufDesc structures is mapped,
		 * this driver isn't going to be able to access the data in
		 * any structure at pCurrBufDesc->next, since it points to
		 * a physical address.
		 */
		pNextBuf = myChannelInfo[irq].pCurrBufDesc->next;
		if (pNextBuf) {
			myChannelInfo[irq].pCurrBufDesc = pNextBuf;
		}
	} else {
		/* We're not chaining - call the callback function if specified,
		 * with a NULL argument
		 */
		if (myChannelInfo[irq].callBack)
			(*myChannelInfo[irq].callBack) (NULL);
		}
}

static int num_bufs __initdata = 64;
static int num_bufs_fec __initdata = 32;

static int __init
numbufs (char *str)
{
	get_option (&str, &num_bufs);
	return 1;
}

__setup ("numbufs=", numbufs);

static int __init
numbufsfec (char *str)
{
	get_option (&str, &num_bufs_fec);
	return 1;
}

__setup ("numbufsfec=", numbufsfec);

static void *MSD_freeSram = (void *) MPC8220_SRAM_START;
MCD_bufDesc *MSD_descriptors;
MCD_bufDescFec *MSD_descriptorsFec;
static int maxDescriptors;
static int maxDescriptorsFec;
static unsigned int *MSD_descUsed;
static unsigned int *MSD_descUsedFec;

void
MSD_initDescriptors (void)
{
	maxDescriptors = num_bufs;
	printk (KERN_INFO "Using %d DMA buffer descriptors\n", maxDescriptors);

	(char *) MSD_freeSram += MCD_getCodeSize ();
	MSD_freeSram =
		(void *) ((u32) ((char *) MSD_freeSram + 0xfff) & 0xfffff000);
	MSD_descUsed = MSD_freeSram;
	MSD_descriptors =
		(MCD_bufDesc *) (MSD_descUsed + (maxDescriptors + 31) / 32);
	MSD_freeSram = MSD_freeSram + sizeof (MSD_descriptors[0]) * maxDescriptors;
	printk (KERN_INFO "descUsed %p, descriptors %p freeSram %p\n",
		MSD_descUsed, MSD_descriptors, MSD_freeSram);
	memset (MSD_descUsed, 0, ((maxDescriptors + 31) / 32) * sizeof (int));

	maxDescriptorsFec = num_bufs_fec;
	printk (KERN_INFO "Using %d DMA buffer descriptors\n", maxDescriptorsFec);

	MSD_descUsedFec = MSD_freeSram;
	MSD_descriptorsFec =
		(MCD_bufDescFec *) (MSD_descUsedFec + (maxDescriptorsFec + 31) / 32);
	MSD_freeSram =
		MSD_freeSram + sizeof (MSD_descriptorsFec[0]) * maxDescriptorsFec;
	printk (KERN_INFO "descUsed %p, descriptors %p freeSram %p\n", MSD_descUsedFec,
			MSD_descriptorsFec, MSD_freeSram);
	memset (MSD_descUsedFec, 0, ((maxDescriptorsFec + 31) / 32) * sizeof (int));
}

void
initDma (void)
{
	int ret;

	/* Set up the world around the chip: */
	my_sDmaRegs = (dmaRegs *) MPC8220_SDMA;

	ret =
		MCD_initDma ((dmaRegs *) my_sDmaRegs, (void *) MPC8220_SRAM_START,
			MCD_RELOC_TASKS);

	/* initialize the descriptor array */
	MSD_initDescriptors ();

	/* Clear out my local storage for channel information */
	memset ((void *) myChannelInfo, 0, sizeof (myChannelInfo));

	/* Enable interrupt from *none* the DMA channels */
	out_be32 (&my_sDmaRegs->intMask, 0xffffffff);
}

static char *isrDmaName[] = {
	"DMA channel 0",
	"DMA channel 1",
	"DMA channel 2",
	"DMA channel 3",
	"DMA channel 4",
	"DMA channel 5",
	"DMA channel 6",
	"DMA channel 7",
	"DMA channel 8",
	"DMA channel 9",
	"DMA channel 10",
	"DMA channel 11",
	"DMA channel 12",
	"DMA channel 13",
	"DMA channel 14",
	"DMA channel 15",
};

/* Reserve a DMA channel */
int
MSD_reserveChannel (int channel,
		void (*handler) (int irq, void *dev_id, struct pt_regs * regs),
		const char *handlerName, void *dev_id)
{
	int i;
	int ret;
	u32 data;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	if (channel >= MAX_DMA_IPR)
		return -ECHRNG;

	if (channel < 0) {
		/* Channel wasn't specified, find one */
		for (i = 0; i < MAX_DMA_IPR; ++i)
		if (!myChannelInfo[i].reserved) {
			channel = i;
		break;
		}
		if (channel < 0)
			return -ECHRNG;		/* none available */
	}

	if (myChannelInfo[channel].reserved)
		return -EBUSY;

	if ((ret =
		request_dma (channel,
			handlerName ? handlerName : isrDmaName[channel])) < 0)
				return ret;

	/* reserve it */
	myChannelInfo[channel].reserved = 1;
	myChannelInfo[channel].dev_id = dev_id;
	if (handler) {
		if ((ret =
			request_irq (channel + MPC8220_SDMA_IRQ_BASE, handler, 0,
				handlerName, dev_id)) < 0)
					return ret;
		myChannelInfo[channel].handler = handler;
	} else {
		if ((ret = request_irq (channel + MPC8220_SDMA_IRQ_BASE, isrDma, 0,
			handlerName ? handlerName : isrDmaName[channel],
			dev_id)) < 0)
					return ret;
	}

	/* Enable the interrupt */
	spin_lock_irqsave (irq_lock, flags);
	data = in_be32 (&my_sDmaRegs->intMask);
	data &= ~(1 << (channel));
	out_be32 (&my_sDmaRegs->intMask, data);
	spin_unlock_irqrestore (irq_lock, flags);

	return channel;
}

int
MSD_releaseChannel (int channel)
{
	u32 data;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	if (channel < 0 || channel >= MAX_DMA_IPR)
		return -ECHRNG;

	if (!myChannelInfo[channel].reserved)
		return -EBUSY;

	/* Disable the interrupt */
	spin_lock_irqsave (irq_lock, flags);
	data = in_be32 (&my_sDmaRegs->intMask);
	data |= (1 << (channel));
	out_be32 (&my_sDmaRegs->intMask, data);
	spin_unlock_irqrestore (irq_lock, flags);

	/* Free the irq, whether it is an explicit, or the default */
	free_irq (channel + MPC8220_SDMA_IRQ_BASE, myChannelInfo[channel].dev_id);
	free_dma (channel);
	myChannelInfo[channel].reserved = 0;

	return 0;
}

int
startDma (int channel, void *srcAddr, s16 srcIncr, s8 * destAddr, s16 destIncr,
		u32 dmaSize, u32 xferSize, u32 initiator, int priority, u32 flags,
		u32 funcDesc, void *cookie, void (*callBack) (void *),
		void *bufDesc)
{
	if (channel < 0 || channel >= MAX_DMA_IPR)
		return -ECHRNG;

	if (!myChannelInfo[channel].reserved)
		return -ECHRNG;

	myChannelInfo[channel].callBack = callBack;

	if (flags & MCD_SINGLE_DMA) {
		/* Not chaining - clear current chain pointer */
		myChannelInfo[channel].pCurrBufDesc = NULL;
	} else {
		/* Chaining - set current chain pointer to first descriptor in chain */
		myChannelInfo[channel].pCurrBufDesc = (MCD_bufDesc *) bufDesc;
	}

	MCD_startDma (channel, srcAddr, srcIncr, destAddr, destIncr, dmaSize,
		xferSize, initiator, priority, flags, funcDesc);

	return 0;
}

void
progressDma (int channel, void **progress)
{
	MCD_XferProg prog;

	MCD_XferProgrQuery (channel, &prog);
	if (prog.currBufDesc)
		*progress = prog.currBufDesc->lastDestAddr;
	else
		*progress = 0;
}

MCD_bufDesc *
MSD_allocBufDescriptors (int n)
{
	int i, j;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	spin_lock_irqsave (irq_lock, flags);
	for (i = 0; i < maxDescriptors; ++i) {
		for (j = i; j < i + n; ++j)
			if (j == maxDescriptors || (MSD_descUsed[j / 32] & (1 << (j % 32))))
				break;
		if (j == i + n) {
			for (j = i; j < i + n; ++j)
				MSD_descUsed[j / 32] |= 1 << (j % 32);
			spin_unlock_irqrestore (irq_lock, flags);
			return &MSD_descriptors[i];
		}
	}
	spin_unlock_irqrestore (irq_lock, flags);
	return (0);
}

int
MSD_freeBufDescriptors (MCD_bufDesc * p, int n)
{
	int i;
	unsigned int l;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	l = (unsigned int) p;
	l -= (unsigned int) MSD_descriptors;
	if (l % sizeof (MSD_descriptors[0]))
		return -1;
	l /= sizeof (MSD_descriptors[0]);
	if (l + n >= maxDescriptors)
		return -2;

	for (i = l; i < l + n; ++i)
		if (!(MSD_descUsed[i / 32] & (1 << (i % 32))))
			return -3;

	spin_lock_irqsave (irq_lock, flags);
	for (i = l; i < l + n; ++i)
		MSD_descUsed[i / 32] &= ~(1 << (i % 32));
	spin_unlock_irqrestore (irq_lock, flags);
	return 0;
}

MCD_bufDescFec *
MSD_allocBufDescriptorsFec (int n)
{
	int i, j;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	spin_lock_irqsave (irq_lock, flags);
	for (i = 0; i < maxDescriptorsFec; ++i) {
		for (j = i; j < i + n; ++j)
			if (j == maxDescriptorsFec
				|| (MSD_descUsedFec[j / 32] & (1 << (j % 32))))
					break;
		if (j == i + n) {
			for (j = i; j < i + n; ++j)
				MSD_descUsedFec[j / 32] |= 1 << (j % 32);
			spin_unlock_irqrestore (irq_lock, flags);
			return &MSD_descriptorsFec[i];
		}
	}
	spin_unlock_irqrestore (irq_lock, flags);
	return (0);
}

int
MSD_freeBufDescriptorsFec (MCD_bufDescFec * p, int n)
{
	int i;
	unsigned int l;
	spinlock_t irq_lock = SPIN_LOCK_UNLOCKED;
	unsigned long flags;

	l = (unsigned int) p;
	l -= (unsigned int) MSD_descriptorsFec;
	if (l % sizeof (MSD_descriptorsFec[0]))
		return -1;

	l /= sizeof (MSD_descriptorsFec[0]);
	if (l + n > maxDescriptorsFec)
		return -2;

	for (i = l; i < l + n; ++i)
		if (!(MSD_descUsedFec[i / 32] & (1 << (i % 32))))
			return -3;

	spin_lock_irqsave (irq_lock, flags);
	for (i = l; i < l + n; ++i)
		MSD_descUsedFec[i / 32] &= ~(1 << (i % 32));
	spin_unlock_irqrestore (irq_lock, flags);
	return 0;
}

EXPORT_SYMBOL (MSD_reserveChannel);
EXPORT_SYMBOL (startDma);
EXPORT_SYMBOL (MSD_releaseChannel);
EXPORT_SYMBOL (MCD_killDma);
EXPORT_SYMBOL (MCD_XferProgrQuery);
EXPORT_SYMBOL (MCD_continDma);
EXPORT_SYMBOL (MCD_startDma);
EXPORT_SYMBOL (MSD_freeBufDescriptorsFec);
EXPORT_SYMBOL (MSD_allocBufDescriptorsFec);
EXPORT_SYMBOL (MSD_ack);
