/*
 * Copyright (c) 1982, 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)dma.c
 */

/*
 * DMA driver
 */

#include <linux/config.h>

#include <linux/types.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/traps.h>
#include <linux/timer.h>

#include "../block/blk.h"
#include "scsi.h"
#include "hosts.h"
#include "a3000.h"
#include "wd33c93dma.h"
#include "wd33c93var.h"
#include "wd33c93reg.h"
#include "a3000dmareg.h"

#include <linux/interrupt.h>
#include <linux/amigaints.h>

static void dmafree (Scsi_Cmnd *SCpnt);
static int dmago (Scsi_Cmnd *SCpnt, int flags);
static int dmanext (struct Scsi_Host *host);
static void dmastop (struct Scsi_Host *host);
static void dmaintr (struct intframe *fp, void *data);

/*
 * The largest single request will be MAXPHYS bytes which will require
 * at most MAXPHYS/NBPG+1 chain elements to describe, i.e. if none of
 * the buffer pages are physically contiguous (MAXPHYS/NBPG) and the
 * buffer is not page aligned (+1).
 */
#ifndef MAXPHYS
#define MAXPHYS 1024*8
#endif
#ifndef NBPG
#define NBPG PAGE_SIZE
#define PGOFSET 0xfff
#endif

#define DMAF_NOINTR	0x01

#define DMAHW(host) ((struct sdmac *)((host)->base))

/* because keybord and dma share same interrupt priority... */
static int dma_initialized = 0;
/* if not in int-mode, don't pay attention for possible scsi interrupts */

#ifdef DEBUG
#define DDB_FOLLOW	0x04
#define DDB_IO		0x08
static int	dmadebug = DDB_FOLLOW | DDB_IO;
#endif

static struct Scsi_Host *a3000_host = NULL;

void a3000dmainit (struct Scsi_Host *host, caddr_t address, dmafree_t *dfree,
		   dmago_t *dgo, dmanext_t *dnext, dmastop_t *dstop)
{
  a3000_host = host;

  host->base = (unsigned char *)address;

  /* make sure interrupts are disabled while we later on reset the scsi-chip */
  DMAHW(host)->CNTR = CNTR_PDMD;
  DMAHW(host)->DAWR = DAWR_A3000;

  *dfree = dmafree;
  *dgo	 = dmago;
  *dnext = dmanext;
  *dstop = dmastop;

  dma_initialized = 1;

  /* setup interrupt handler */
  add_isr (IRQ_AMIGA_PORTS, dmaintr, 0, NULL);
}

static void
dmafree(Scsi_Cmnd *SCpnt)
{
	unsigned long flags;
	struct scsi_softc *dev = HOSTDATA(SCpnt->host);

	save_flags (flags);
	cli ();
	if (dev->sc_dmacmd)
	{
		if ((dev->sc_dmacmd & (CNTR_TCEN | CNTR_DDIR)) == 0)
		{
			/* only FLUSH if terminal count not enabled, and
			   reading from peripheral */
			DMAHW(SCpnt->host)->FLUSH = 1;
			while (! (DMAHW(SCpnt->host)->ISTR & ISTR_FE_FLG)) ;
		}
		DMAHW(SCpnt->host)->CINT = 1;	/* clear possible interrupt */
		DMAHW(SCpnt->host)->SP_DMA = 1;	/* stop dma */
		dev->sc_dmacmd = 0;
	}
	/* disable interrupts from dma/scsi */
	DMAHW(SCpnt->host)->CNTR = CNTR_PDMD;

	restore_flags (flags);
}

static int
dmago(Scsi_Cmnd *SCpnt, int flags)
{
	char *addr = SCpnt->buffer;
	struct scsi_softc *dev = HOSTDATA(SCpnt->host);
	int count = SCpnt->bufflen;
	register int tcount;

	if (count > MAXPHYS)
		panic("dmago: count > MAXPHYS");
#ifdef DEBUG
	if (dmadebug & DDB_FOLLOW)
		printk("dmago(%d, %p, %x, %x)\n", SCpnt->host->host_no, addr, count, flags);
#endif
	SCpnt->SCp.ptr = (char *) VTOP(addr);
	
	if ((flags & DMAGO_READ) == 0)
		/* invalidate any cache */
		cache_clear ((ulong)SCpnt->SCp.ptr, count);
	else
		/* push any dirty cache */
		cache_push ((ulong)SCpnt->SCp.ptr, count);

	if (count < (tcount = NBPG - ((int)addr & PGOFSET)))
		tcount = count;

	/* number of words (the sdmac wants 16bit values here) */
	SCpnt->SCp.this_residual = tcount >> 1;

	dev->sc_dmaflags = 0;
	/*
	 * Set up the command word based on flags
	 */
	dev->sc_dmacmd = CNTR_PDMD | CNTR_INTEN;
	if ((flags & DMAGO_READ) == 0)
		dev->sc_dmacmd |= CNTR_DDIR;
#ifdef DEBUG
	if (dmadebug & DDB_IO)
	{
		printk("dmago: cmd %x, flags %x\n", dev->sc_cmd, dev->sc_dmaflags);
#if 0
		for (dcp = dma_softc.sc_chain; dcp <= dma_softc.sc_last; dcp++)
			printk("  %d: %d@%p\n", dcp-dma_softc.sc_chain,
			       dcp->dc_count, dcp->dc_addr);
#endif
	}
#endif

	DMAHW(SCpnt->host)->CNTR = dev->sc_dmacmd;
	DMAHW(SCpnt->host)->ACR = (u_int) SCpnt->SCp.ptr;
	DMAHW(SCpnt->host)->ST_DMA = 1;

	return SCpnt->SCp.this_residual << 1;
}

static void
dmastop(struct Scsi_Host *host)
{
	unsigned long flags;
	struct scsi_softc *dev = HOSTDATA(host);

#ifdef DEBUG
	if (dmadebug & DDB_FOLLOW)
		printk("dmastop()\n");
#endif
	if (dev->sc_dmacmd)
	{
		save_flags (flags);
		cli ();
		if ((dev->sc_dmacmd & (CNTR_TCEN | CNTR_DDIR)) == 0)
		{
			/* only FLUSH if terminal count not enabled, and reading from peripheral */
			DMAHW(host)->FLUSH = 1;
			while (! (DMAHW(host)->ISTR & ISTR_FE_FLG)) ;
		}
		DMAHW(host)->CINT = 1;		/* clear possible interrupt */
		DMAHW(host)->SP_DMA = 1;	/* stop dma */
		dev->sc_dmacmd = 0;
		restore_flags (flags);
	}
}

static void dmaintr (struct intframe *fp, void *data)
{
	unsigned int stat;
	int found = 0;
	struct scsi_softc *dev;
	static int recurse = 0;

	if (! dma_initialized)
		return;

	if (++recurse > 1)
		panic ("a3000dmaintr: recursive; level was %#x, interrupted"
		       " pc was %#lx\n", fp->sr, fp->pc);

	stat = DMAHW(a3000_host)->ISTR;
	dev = HOSTDATA(a3000_host);

	if (! (stat & (ISTR_INT_F|ISTR_INT_P))) {
		--recurse;
		return;
	}

#ifdef DEBUG
	if (dmadebug & DDB_FOLLOW)
		printk("a3000dmaintr (%d, 0x%x)\n", a3000_host->host_no,
		       stat);
#endif

	/* both, SCSI and DMA interrupts arrive here. I chose arbitrarily that
	 * DMA interrupts should have higher precedence than SCSI interrupts.
	 */
	if (stat & ISTR_E_INT) {
		found++;

		printk ("a3000DMAINTR should no longer be entered!!\n");

		/*
		 * End-of-process interrupt, means the DMA Terminal Counter
		 * reached 0, and we have to reload it. SCSI transfer should
		 * not be interrupted by this event (we'll see..)
		 */
#ifdef DEBUG
		if (dmadebug & DDB_IO)
		{
			printk("a3000dmaintr: stat %x next %d\n",
			       stat, (dma_softc.sc_cur-dma_softc.sc_chain)+1);
		}
#endif
		/* disable DMA interrupt. */
		if (dev->sc_dmaflags & DMAF_NOINTR)
			dev->sc_dmacmd &= ~CNTR_TCEN;

		DMAHW(a3000_host)->CINT = 1;		/* clear possible interrupt */
		DMAHW(a3000_host)->CNTR = dev->sc_dmacmd;
		DMAHW(a3000_host)->ACR = (u_int) dev->sc_sq->SCp.ptr;
#ifndef DONT_WTC
		if (dev->sc_dmacmd & CNTR_TCEN) {
			DMAHW(a3000_host)->WTC = dev->sc_sq->SCp.this_residual;
#endif
			DMAHW(a3000_host)->ST_DMA = 1;
		} else
			dmastop (a3000_host);
	}

	/* check for SCSI ints in the same go and save an interrupt */
	if (stat & ISTR_INTS)
		wd33c93intr (a3000_host);

	--recurse;
}


static int
dmanext (struct Scsi_Host *host)
{
	struct scsi_softc *dev = HOSTDATA(host);

#ifdef DEBUG
	if (dmadebug & DDB_IO)
	{
		printk("dmanext(%d): next %d\n", host->host_no,
		       0 /* , (dma_softc.sc_cur-dma_softc.sc_chain)+1*/);
	}
#endif
	if (0 /* ++dma_softc.sc_cur <= dma_softc.sc_last*/)
	{
		if ((dev->sc_dmacmd & (CNTR_TCEN | CNTR_DDIR)) == 0)
		{
			/* only FLUSH if terminal count not enabled,
			   and reading from peripheral */
			DMAHW(host)->FLUSH = 1;
			while (! (DMAHW(host)->ISTR & ISTR_FE_FLG)) ;
		}
		DMAHW(host)->CINT = 1;		/* clear possible interrupt */
		DMAHW(host)->SP_DMA = 1;	/* stop dma */
		DMAHW(host)->CNTR = dev->sc_dmacmd;
		DMAHW(host)->ACR = (u_int) dev->sc_sq->SCp.ptr;
		DMAHW(host)->ST_DMA = 1;

		return dev->sc_sq->SCp.this_residual << 1;
	}
	else
	{
		/* shouldn't happen !! */
		printk ("dmanext at end !!!\n");
		dmastop (host);
		return 0;
	}
}
