/* 
 * ni6510 (am7990 'lance' chip) driver for Linux-net-2 by MH
 * Alphacode for pl11 
 * this code doesn't work on machines with more than 16MB!!
 * 
 * copyright (c) 1993 M.Hipp
 *
 * This is an extension to the Linux operating system, and is covered by the
 * same Gnu Public License that covers that work.
 * 
 * comments/bugs/suggestions can be sent to:
 * Michael Hipp
 * email: mhipp@student.uni-tuebingen.de
 * 
 * sources:
 *  some things are from the 'ni6510-packet-driver for dos'
 *  and from the original drivers by D.Becker
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/string.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <errno.h>
#include <linux/interrupt.h>

#include "dev.h"
#include "eth.h"
#include "timer.h"
#include "ip.h"
#include "protocol.h"
#include "tcp.h"
#include "skbuff.h"
#include "sock.h"
#include "arp.h"

#include "am7990.h"

#define RMDNUM 8
#define RMDNUMMASK 0x6000 /* log2(RMDNUM)<<13 */
#define TMDNUM 8
#define TMDNUMMASK 0x6000 /* log2(TMDNUM)<<13 */

#define R_BUF_SIZE 1518
#define T_BUF_SIZE 1518

#define L_DATAREG 0x00
#define L_ADDRREG 0x02

#define L_RESET   0x04
#define L_CONFIG  0x05
#define L_EBASE   0x08

/* 
 * to access the 7990-regs, you have to write
 * reg-number into L_ADDRREG, then you can access it using L_DATAREG
 */
#define CSR0 0x00
#define CSR1 0x01
#define CSR2 0x02
#define CSR3 0x03

#define PORT (dev->base_addr)
#define IRQ  (dev->irq)
#define DMA  (p->dma)
#define DMANUM 3 /* default-DMA */

  /*
#define NO_STATIC 
  */
/* if you define NO_STATIC, the driver does't copy the datas to his static buffer. */
/* on my machine this has caused problems, so it is undefined as default. */

#define ni_etheradd() {int i;for(i=0;i<6;i++)dev->dev_addr[i]=inb(PORT+L_EBASE+i);}

#define writereg(val,reg) {outw(reg,PORT+L_ADDRREG);inw(PORT+L_ADDRREG);outw(val,PORT+L_DATAREG);inw(PORT+L_DATAREG);}
#define readreg(reg) (outw(reg,PORT+L_ADDRREG),inw(PORT+L_ADDRREG),inw(PORT+L_DATAREG))
#define writedatareg(val) {outw(val,PORT+L_DATAREG);inw(PORT+L_DATAREG);}

static inline void outw(unsigned short value, unsigned short port)
{
__asm__ __volatile__ ("outw %%al,%%dx"
                :
                :"a" ((unsigned short) value),"d" ((unsigned short) port));
}

static inline unsigned short inw(unsigned short port)
{
        unsigned short _v;
__asm__ __volatile__ ("inw %%dx,%%al"
                :"=a" (_v)
                :"d" ((unsigned short) port),"0" (0));
        return _v;
}

static void intr7990(int ptr);
static int  ni6510_open(struct device *dev);
static int  am7990_init(struct device *dev);
static int  am7990_reinit(struct device *dev);
static int  ni6510_probe(struct device *dev);
static int  xmit7990(struct sk_buff *skb, struct device *dev);
static void recv_intr(struct device *dev);
static void xmit_intr(struct device *dev);
static int  ni6510_close(struct device *dev);

extern struct device *irq2dev_map[16];

struct sigaction sig7990 = { intr7990,0,0,NULL };

#define memsize 8+RMDNUM*8+TMDNUM*8

struct priv 
{
  struct init_block ib; 
  int dma;
  void *memptr;
  struct rmd *rmdhead;
  struct tmd *tmdhead;
  int rmdnum;
  int tmdnum,tmdlast;
  struct sk_buff *recv_skb[RMDNUM];
  void *tmdbufs[TMDNUM];
  struct sk_buff *skbfree; 
}; 

unsigned long membuff[(memsize)/4+1];

int irqtab[] = { 9,12,15,5 }; /* irq config-translate */
int dmatab[] = { 0,3,5,6 };   /* dma config-translate */

/*
 * init ni6510 (and the am7990)
 */ 

int ni6510_init(struct device *dev)
{
  int i;
 
  dev->priv = (void *) kmalloc(sizeof(struct priv),GFP_KERNEL);
  memset((char *) dev->priv,0,sizeof(struct priv));

  if(!ni6510_probe(dev))
    return 1;

  irq2dev_map[dev->irq] = dev;

  if(am7990_init(dev))
  {
    for(i=0;i<6;i++)
      dev->broadcast[i] = 0xff;
    dev->queue_xmit     = dev_queue_xmit;
    dev->open           = &ni6510_open;
    dev->stop           = &ni6510_close;
    dev->hard_start_xmit= &xmit7990;

    dev->type           = ARPHRD_ETHER;
    dev->mtu            = 1500; /* eth_mtu */
    dev->hard_header    = eth_header;
    dev->add_arp        = eth_add_arp;
    dev->rebuild_header = eth_rebuild_header;
    dev->type_trans     = eth_type_trans;
    dev->hard_header_len= ETH_HLEN;
    dev->addr_len       = ETH_ALEN;

    dev->flags          = 0;
    dev->family         = AF_INET;
    dev->pa_addr        = 0;
    dev->pa_brdaddr     = 0;
    dev->pa_mask        = 0;
    dev->pa_alen        = sizeof(unsigned long);

    dev->tbusy = 0;
    dev->interrupt = 0;
    dev->start = 0;

    return 0; /* OK */
  }
  else
  {
    return 1;
  }
}

/*
 * open (most done by init) 
 */

static int ni6510_open(struct device *dev)
{
  if(am7990_reinit(dev))
  {
    dev->tbusy = 0;
    dev->interrupt = 0;
    dev->start = 1;
    return 0;
  }
  else
  {
    dev->start = 0;
    return 1;
  }
}

static int ni6510_close(struct device *dev)
{
  outw(0,PORT+L_RESET); /* that's the hard way */
  dev->start = 0;
  return 0; 
}

/* 
 * Probe The Card (not the lance-chip) 
 * and set hardaddress
 */ 

static int ni6510_probe(struct device *dev)
{
  int ioaddr[] = {0x300,0x320,0x340,0x360};
  int i,j=4;
  struct priv *p = (struct priv *) dev->priv;

  if(dev->base_addr != 0)
  {
    j = 1;
    ioaddr[0] = dev->base_addr;
  }

  for(i=0;i<j;i++)
  {
    dev->base_addr = ioaddr[i];
    if((inb(PORT+L_EBASE+6) == 0x00) && (inb(PORT+L_EBASE+7) == 0x55)) break; /* check cardident */
  }
  if(i == j)
  {
    printk("%s: Can't find IO-port\n",dev->name);
    return 0; 
  }
  ni_etheradd(); 
  if((dev->dev_addr[0] != 0x02) || (dev->dev_addr[1]!= 0x07)) /* check interlan code */
  {
    printk("%s: wrong Hardaddress \n",dev->name);
    return 0;
  }
  if(dev->irq == 0) 
  {
    dev->irq = irqtab[(inw(PORT+L_CONFIG)>>2)&3];
    p->dma   = dmatab[inw(PORT+L_CONFIG)&3];
  }
  else
    p->dma = DMANUM;

  return 1; /* we've found everyting -> TRUE */
}

/* 
 * init lance (malloc/dma-request)
 */

static int am7990_init(struct device *dev)
{
   int i;
   struct priv *p = (struct priv *) dev->priv; 

   if(request_dma(DMA) != 0)
   {
     printk("%s: Can't request dma-channel %d\n",dev->name,(int) DMA);
     return 0;
   }

   if (irqaction (dev->irq, &sig7990))
   {
     printk ("%s: err: Unable to get IRQ %d!\n",dev->name,dev->irq);
     free_dma(DMA);
     return 0;
   }

   p->memptr = (void *) membuff;
   p->tmdhead = (struct tmd *) ((( (unsigned long)p->memptr ) + 8) & 0xfffffff8); /* align 8 */
   p->rmdhead = (struct rmd *) (p->tmdhead + TMDNUM);   

#ifndef NO_STATIC
   for(i=0;i<TMDNUM;i++)
   {
     p->tmdbufs[i] = kmalloc(T_BUF_SIZE,GFP_ATOMIC);
     if(p->tmdbufs[i] == NULL)
     {
       printk("%s: Can't alloc Xmit-Mem\n",dev->name);
       return 0;
     }
   }
#endif

   for(i=0;i<RMDNUM;i++)
   {
     p->recv_skb[i] =(struct sk_buff *)  kmalloc(sizeof(struct sk_buff)+R_BUF_SIZE,GFP_ATOMIC);
     if(p->recv_skb[i] == NULL)
     {
       printk("%s: unable to alloc recv-mem\n",dev->name);
       return 0;
     }
   }

   if(am7990_reinit(dev))
   {
     printk("%s: dma: %d irq %d hardaddr: ",dev->name,p->dma,dev->irq);
     for(i=0;i<6;i++)
     {
       printk("%02x",(int) dev->dev_addr[i]);
       if(i != 5) printk(":");
     }
     printk("\n");
     return 1;
   }
   else return 0;
}

/* 
 * init lance (write init-values .. init-buffers)
 */

static int am7990_reinit(struct device *dev)
{
   int i,j;
   struct tmd *tmdp;
   struct rmd *rmdp;
   struct priv *p = (struct priv *) dev->priv;

   disable_dma(DMA); /* i've never worked with dma, but we do it like the packetdriver */
   set_dma_mode(DMA,DMA_MODE_CASCADE);
   enable_dma(DMA); 

   outw(0,PORT+L_RESET); /* first: reset the card */
   if(inw(PORT+L_DATAREG) != 0x4)
   {
     printk("%s: can't RESET ni6510 card: %04x\n",dev->name,(int) inw(PORT+L_DATAREG));
     disable_dma(DMA);
     free_dma(DMA);
     return 0;
   }

   /* here: memset all buffs to zero */

   memset(p->memptr,0,memsize);

   p->tmdnum = 0; p->tmdlast = 0;
   for(i=0;i<TMDNUM;i++)
   {
     tmdp = p->tmdhead + i;
#ifndef NO_STATIC
     tmdp->u.buffer = (unsigned long) p->tmdbufs[i];     
#endif
     tmdp->u.s.status = XMIT_START | XMIT_END;
   }

   p->rmdnum = 0;
   for(i=0;i<RMDNUM;i++)
   {
     rmdp = p->rmdhead + i;
     p->recv_skb[i]->mem_len = sizeof(struct sk_buff) + R_BUF_SIZE;
     p->recv_skb[i]->mem_addr = p->recv_skb[i];
     p->recv_skb[i]->lock = 0;
     rmdp->u.buffer = (unsigned long) p->recv_skb[i] + sizeof(struct sk_buff);
     rmdp->u.s.status = RCV_OWN;
     rmdp->blen = -R_BUF_SIZE;
     rmdp->mlen = 0;
   }
   
   for(i=0;i<6;i++)
   {
     p->ib.eaddr[i] = dev->dev_addr[i];
   }
   p->ib.mode = 0;
   for(i=0;i<8;i++) p->ib.filter[i] = 0;
   p->ib.trplow = (unsigned short) (( (unsigned long) p->tmdhead ) & 0xffff);
   p->ib.trphigh = (unsigned short) ((( (unsigned long) p->tmdhead )>>16) & 0xffff) | TMDNUMMASK; 
   p->ib.rrplow = (unsigned short) (( (unsigned long) p->rmdhead ) & 0xffff);
   p->ib.rrphigh = (unsigned short) ((( (unsigned long) p->rmdhead )>>16) & 0xffff) | RMDNUMMASK;

   writereg(0,CSR3);  /* busmaster/no word-swap */
   writereg((unsigned short) (((unsigned long) &(p->ib)) & 0xffff),CSR1);
   writereg((unsigned short) (((unsigned long) &(p->ib))>>16),CSR2);
   
   writereg(CSR0_INIT,CSR0); /* this changes L_ADDRREG to CSR0 */

  /*
   * NOW, WE NEVER CHANGE THE L_ADDRREG, CSR0 IS ALWAYS SELECTED 
   */

    for(i=0;i<5;i++)
    {
      for(j=0;j<2000000;j++); /* wait a while */
      if(inw(PORT+L_DATAREG) & CSR0_IDON) break; /* init ok ? */
    }
    if(i == 20) 
    {
      printk("%s: can't init am7990, status: %04x\n",dev->name,(int) inw(PORT+L_DATAREG));
      disable_dma(DMA);
      free_dma(DMA);
      return 0; /* false */
    } 

    writedatareg(CSR0_CLRALL | CSR0_INEA | CSR0_STRT); /* start lance , enable interrupts */

    return 1; /* OK */
}
 
/* 
 * interrupt handler  
 */

static void intr7990(int reg_ptr)
{
  int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2);
  int csr0;
  struct device *dev = irq2dev_map[irq];

  csr0 = inw(PORT+L_DATAREG);
  writedatareg(csr0 & CSR0_CLRALL); /* ack & disable interrupt */

  dev->interrupt = 1;
  sti();

  if(csr0 & CSR0_ERR)
  {
     printk("%s: error: %04x\n",dev->name,csr0); 
     /*  */
  }

  if(csr0 & CSR0_RINT) /* RECV-int? */
  { 
    recv_intr(dev);
  }
  if(csr0 & CSR0_TINT) /* XMIT-int? */
  {  
    xmit_intr(dev);
  }

  dev->interrupt = 0;
  writedatareg(CSR0_INEA);  /* reenable inter. */
}

/*
 * We have received an Xmit-Interrupt ..
 * send a new packet if necessary
 */

static void xmit_intr(struct device *dev)
{
  int tmdstat;
  struct tmd *tmdp;
  struct priv *p = (struct priv *) dev->priv;

#ifdef NO_STATIC
  struct sk_buff *skb;
#endif

  tmdp = p->tmdhead + p->tmdlast;
  p->tmdlast++; p->tmdlast &= TMDNUM-1;

#ifdef NO_STATIC
  skb = (struct sk_buff *) (tmdp->u.buffer & 0x00ffffff);
  if( (skb-1)->free) kfree_skb((skb-1),FREE_WRITE); 
#endif

  tmdstat = tmdp->u.s.status & 0xff00;
  if(tmdstat & XMIT_ERR)
  {
    printk("%s: xmit-error: %04x %04x\n",dev->name,(int) tmdstat,(int) tmdp->status2);
    if(tmdp->status2 & XMIT_TDRMASK) 
      printk("%s: tdr-problems (e.g. no resistor)\n",dev->name);

    tmdp->status2 = 0;
  }

  dev->tbusy = 0;
  mark_bh(INET_BH);
}

/*
 * We have received a packet
 */

static void recv_intr(struct device *dev)
{
  struct rmd *rmdp; 
  int sksize,len,rmdstat;
  struct sk_buff *skb,*skb1;
  struct priv *p = (struct priv *) dev->priv;

  rmdp = p->rmdhead + p->rmdnum;
  while(!( (rmdstat = rmdp->u.s.status) & RCV_OWN))
  {
    if(rmdstat & (RCV_START | RCV_END) != (RCV_START | RCV_END) ) /* is packet start & end? */ 
    {
      if(rmdstat & RCV_START)
        printk("%s: packet too long\n",dev->name);
      rmdp->u.s.status = RCV_OWN; /* change owner */
      p->rmdnum++; p->rmdnum %= RMDNUM; 
      rmdp = p->rmdhead + p->rmdnum;
      continue; /* ignore all packets until RCV_END */
    }
    if(rmdstat & RCV_ERR)
    {
      printk("%s: reveice-error: %04x\n",dev->name,(int) (rmdstat & 0xff00));
      continue; /* ignore frame */
    }
    sksize = sizeof(struct sk_buff) + (len = ((rmdp->mlen & 0x0fff) - 4)); /* -4: ignore FCS */ 
    skb = (struct sk_buff *) kmalloc(sizeof(struct sk_buff)+R_BUF_SIZE,GFP_ATOMIC);
    if(skb != NULL)
    {
      skb1 = p->recv_skb[p->rmdnum];
      skb->lock = 0;
      skb->mem_len = sizeof(struct sk_buff)+R_BUF_SIZE;
      skb->mem_addr = p->recv_skb[p->rmdnum] = skb;
      rmdp->u.buffer = (unsigned long) (skb+1);
/*      memcpy((void *) (skb+1),(void *) (rmdp->u.buffer & 0x00ffffff),len); */
      rmdp->u.s.status = RCV_OWN;
      dev_rint((unsigned char *) skb1,len,IN_SKBUFF,dev);
    }
    else 
    {
      rmdp->u.s.status = RCV_OWN;
      printk("%s: can't alloc new sk_buff\n",dev->name);
    }
    p->rmdnum++; p->rmdnum %= RMDNUM;
    rmdp = p->rmdhead + p->rmdnum;
  } 
}

/*
 * kick xmitter .. 
 */

static int xmit7990(struct sk_buff *skb, struct device *dev)
{
  int len;
  struct priv *p = (struct priv *) dev->priv;
  struct tmd *tmdp;

  if(dev->tbusy) 
  {
    int tickssofar = jiffies - dev->trans_start;
    if (tickssofar < 15)
      return 1;

    printk("%s: xmitter timed out, try to restart!\n",dev->name);
    am7990_reinit(dev);
  }

  if(skb == NULL)
  {
    dev_tint(dev);
    return 0;
  }

  if (!skb->arp)
  {
    if(dev->rebuild_header(skb+1, dev)) 
    {
      skb->dev = dev;
      arp_queue (skb);
      return 0;
    }
  }

  dev->tbusy = 1;

  tmdp = p->tmdhead + p->tmdnum;

  if( (len = skb->len) < 60) len = 60; /* ether-min-frame: 60+FCS(4) = 64 */

#ifdef NO_STATIC
  tmdp->u.buffer = (unsigned long) (skb+1);
#else
  memcpy((char *) (tmdp->u.buffer & 0x00ffffff),(char *) (skb+1),skb->len);
#endif

  tmdp->blen = -len;
  tmdp->u.s.status = XMIT_OWN | XMIT_START | XMIT_END;

  writedatareg(CSR0_TDMD | CSR0_INEA); /* enable xmit & interrupt */
  dev->trans_start = jiffies;
  p->tmdnum++; p->tmdnum &= TMDNUM-1;
  
  if( !((p->tmdhead + p->tmdnum)->u.s.status & XMIT_OWN) ) dev->tbusy = 0;

#ifndef NO_STATIC
  if(skb->free) kfree_skb(skb,FREE_WRITE);
#endif

  return 0;
}




