/*
 * 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 "a2091.h"
#include "wd33c93dma.h"
#include "wd33c93reg.h"
#include "a2091dmareg.h"

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

#define printf printk

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 DMAMAXIO	(MAXPHYS/NBPG+1)

struct	dma_chain {
	int	dc_count;
	char	*dc_addr;
};

static struct	dma_softc {
	struct	sdmac *sc_hwaddr;
	char	sc_dmaflags;
	u_short sc_cmd;
	struct	dma_chain *sc_cur;
	struct	dma_chain *sc_last;
	struct	dma_chain sc_chain[DMAMAXIO];
} dma_softc;

#define DMAF_NOINTR	0x01

static Scsi_Cmnd dmachan;

/* 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 */
static int scsi_int_mode;

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

static void dmatimeout (unsigned long);
static struct timer_list dmatimer;

static int	dmatimo;

static long	dmahits;
static long	dmamisses;
#if 0 /* XXX */
static long	dmabyte;
static long	dmaword;
static long	dmalword;
#endif
#endif

static struct Scsi_Host *a2091_host = NULL;

void a2091dmainit (struct Scsi_Host *host, caddr_t address, dmafree_t *dfree,
		   dmago_t *dgo, dmanext_t *dnext, dmastop_t *dstop)
{
  register struct sdmac *dma;

  a2091_host = host;

  dma = (struct sdmac *) address;

  /* make sure interrupts are disabled while we later on reset the scsi-chip */
  dma->CNTR = CNTR_PDMD;
  dma->DAWR = DAWR_A2091;
  dma_softc.sc_dmaflags = 0;
  dma_softc.sc_cmd = 0;
  dma_softc.sc_hwaddr = dma;
  dmachan.prev = dmachan.next = &dmachan;
#ifdef DEBUG
  /* make sure timeout is really not needed */
  dmatimer.expires = 30 * HZ;
  dmatimer.function = dmatimeout;
  dmatimer.data = 0;
  add_timer (&dmatimer);
#endif

  *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)
{
  register Scsi_Cmnd *SCp;
  unsigned long flags;

  save_flags (flags);
  cli ();

#ifdef DEBUG
  dmatimo = 0;
#endif
  if (dma_softc.sc_cmd)
    {
      if ((dma_softc.sc_cmd & (CNTR_TCEN | CNTR_DDIR)) == 0)
	{
	  /* only FLUSH if terminal count not enabled, and reading from peripheral */
	  dma_softc.sc_hwaddr->FLUSH = 1;
	  while (! (dma_softc.sc_hwaddr->ISTR & ISTR_FE_FLG)) ;
	}
      dma_softc.sc_hwaddr->CINT = 1;		/* clear possible interrupt */
      dma_softc.sc_hwaddr->SP_DMA = 1;	/* stop dma */
      dma_softc.sc_cmd = 0;
    }
  dma_softc.sc_hwaddr->CNTR = CNTR_PDMD;	/* disable interrupts from dma/scsi */
  scsi_int_mode = 0;

#if 0 /* XXX */
  SCpnt->prev->next = SCpnt->next;
  SCpnt->next->prev = SCpnt->prev;
#endif

#if 0
  for (SCp = dmachan.next;
       SCp != &dmachan;
       SCp = SCp->next)
    {
      if (SCp->host == a2091_host_nr)
	{
	  SCp->prev->next = SCp->next;
	  SCp->next->prev = SCp->prev;
#if 0 /* XXX */
	  insque ((caddr_t) dn, (caddr_t) dq->dq_back);
#endif
	  restore_flags (flags);
#if 0 /* XXX */
	  (dn->dq_driver->d_start)(unit);
#endif
	  return;
	}
    }
#endif

  restore_flags (flags);
}

static int
dmago(Scsi_Cmnd *SCpnt, int flags)
{
  register struct dma_chain *dcp;
  register char *dmaend = NULL;
  char *addr = SCpnt->buffer;
  int count = SCpnt->bufflen;
  register int tcount;

  if (count > MAXPHYS)
     panic("dmago: count > MAXPHYS");
#ifdef DEBUG
  if (dmadebug & DDB_FOLLOW)
     printf("dmago(%d, %x, %x, %x)\n", SCpnt->host->host_no, addr, count,
	    flags);
#endif
  /*
   * Build the DMA chain
   */
  for (dcp = dma_softc.sc_chain; count > 0; dcp++)
    {
#ifdef DEBUG
#if 0 /* XXX */
      if (! pmap_extract(pmap_kernel(), (vm_offset_t)addr))
	panic ("dmago: no physical page for address!");
#endif
#endif

      dcp->dc_addr = (char *) VTOP(addr);
      if (count < (tcount = NBPG - ((int)addr & PGOFSET)))
	tcount = count;
      dcp->dc_count = tcount;
      addr += tcount;
      count -= tcount;
      tcount >>= 1;	/* number of words (the sdmac wants 16bit values here) */
      if (dcp->dc_addr == dmaend)
	{
#ifdef DEBUG
	  dmahits++;
#endif
	  dmaend += dcp->dc_count;
	  (--dcp)->dc_count += tcount;
	}
      else
	{
#ifdef DEBUG
	  dmamisses++;
#endif
	  dmaend = dcp->dc_addr + dcp->dc_count;
	  dcp->dc_count = tcount;
	}
    }

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

  dma_softc.sc_hwaddr->CNTR = dma_softc.sc_cmd;
  scsi_int_mode = 1;
  dma_softc.sc_hwaddr->ACR = (u_int) dma_softc.sc_cur->dc_addr;
  dma_softc.sc_hwaddr->ST_DMA = 1;

  return dma_softc.sc_cur->dc_count << 1;
}

static void
dmastop(struct Scsi_Host *host)
{
  Scsi_Cmnd *SCp;
  unsigned long flags;

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

  /*
   * We may get this interrupt after a device service routine
   * has freed the dma channel.  So, ignore the intr if there's
   * nothing on the queue.
   */
  SCp = dmachan.next;
  if (SCp != &dmachan) {
    SCp->result = DID_ERROR << 16;
    (SCp->scsi_done)(SCp);
  }
}

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

  if (! dma_initialized)
    return;

  ++recurse;
  if (recurse > 1) {
    printk ("a2091dmaintr: recursive; level was %#x\n", fp->sr);
    printk ("a2091dmaintr: interrupted pc was %#lx\n", fp->pc);
    cli();
    for (;;)
      ;
  }

  stat = dma_softc.sc_hwaddr->ISTR;

  if (! (stat & (ISTR_INT_F|ISTR_INT_P))) {
    --recurse;
    return;
  }
#ifdef DEBUG
  if (dmadebug & DDB_FOLLOW)
    printf("a2091dmaintr (%d, 0x%x)\n", a2091_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++;

    printf ("A2091DMAINTR 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)
    {
      printf("a2091dmaintr: stat %x next %d\n",
	     stat, (dma_softc.sc_cur-dma_softc.sc_chain)+1);
    }
#endif
    if (++dma_softc.sc_cur <= dma_softc.sc_last)
    {
#ifdef DEBUG
      dmatimo = 1;
#endif
      /*
       * Last chain segment, disable DMA interrupt.
       */
      if (dma_softc.sc_cur == dma_softc.sc_last && (dma_softc.sc_dmaflags & DMAF_NOINTR))
	 dma_softc.sc_cmd &= ~CNTR_TCEN;

      dma_softc.sc_hwaddr->CINT = 1;		/* clear possible interrupt */
      dma_softc.sc_hwaddr->CNTR = dma_softc.sc_cmd;
      dma_softc.sc_hwaddr->ACR = (u_int) dma_softc.sc_cur->dc_addr;
#ifndef DONT_WTC
      if (dma_softc.sc_cmd & CNTR_TCEN)
	dma_softc.sc_hwaddr->WTC = dma_softc.sc_cur->dc_count;
#endif
      dma_softc.sc_hwaddr->ST_DMA = 1;
    }
    else
      dmastop (a2091_host);

    /* check for SCSI ints in the same go and eventually save an interrupt */
  }

  if (scsi_int_mode && (stat & ISTR_INTS))
    wd33c93intr (a2091_host);

  --recurse;
}


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

      return dma_softc.sc_cur->dc_count << 1;
    }
  else
    {
      /* shouldn't happen !! */
      printf ("dmanext at end !!!\n");
      dmastop (host);
      return 0;
    }
}




#ifdef DEBUG
static void dmatimeout(unsigned long data)
{
  unsigned long flags;

  save_flags (flags);
  cli ();

  if (dmatimo)
  {
	  if (dmatimo > 1)
		  printf("dma%d: timeout #%d\n", a2091_host->host_no, dmatimo-1);
	  dmatimo++;
  }
  restore_flags (flags);

  dmatimer.expires = 30 * HZ;
  dmatimer.data = 0;
  dmatimer.function = dmatimeout;
  add_timer (&dmatimer);
}
#endif
