/* pcbri.c: Driver for the Combinet Everyware 1000 Series of ISDN BRI cards */
/*

    Written 1995 : Joel Katz
    Copyright (C) 1995 -- Joel Katz and Automated Business Corp.
    Copyright (C) 1995 -- Combinet, Inc.
    All Rights Reserved
    
    Based heavily upon the NEx000 driver, written 1992-94 by Donald Becker and
    Copyright 1993 United States Government as represented by the
    Director, National Security Agency.

	TODO:
	
	1) According to DOS NDIS driver, some BRI cards have no address PROM,
	    how do we detect and configure them?

*/

static char *version =
    "pcbri.c:V1.02 **BETA** (C)1995 - Joel Katz <Stimpson@Panix.COM>\n";
#include <linux/config.h>

#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <asm/system.h>
#include <asm/io.h>

#include <linux/netdevice.h>
#include "bri.h"

static unsigned int IRQ=0;
static unsigned int PORT=0;

static unsigned char err_mode=0;
static unsigned char time_count=0;

/* A zero-terminated list of I/O addresses to be probed. */
static unsigned int netcard_portlist[] =
{ 0x300, 0x320, 0x340, 0x360, 0};

#define BRI_BASE	(dev->base_addr)
#define BRI_CMD	 	0x00
#define BRI_DATAPORT	0x10	/* NatSemi-defined port window offset. */
#define BRI_RESET	0x1f	/* Issue a read to reset, a write to clear. */
#define BRI_IO_EXTENT	0x20

#ifdef MODULE

static int bri_open(struct device *dev)
{
 MOD_INC_USE_COUNT;
 return bri_ei_open(dev);
}

static int bri_close(struct device *dev)
{
 MOD_DEC_USE_COUNT;
 dev->start=0;
 return 0;
}

#endif

void timeout(void)
{
 if(err_mode) printk("PCBRI timeout\n");
 time_count++;
}

static void wait_for_em(unsigned short rdyport)
{
 int i=0;
 while(inb(rdyport)&1)
 {
  i++;
  if(i>3000) { timeout(); return; } /* Redo using loop_per_sec FIXME */
 }
}

static inline void bri_outb(unsigned short rdyport,
 unsigned char val, unsigned short port)
{
 outb(val,port);
 wait_for_em(rdyport);
}

static inline unsigned short bri_inb(unsigned short rdyport,
 unsigned short port)
{
 unsigned short j;
 unsigned long f;
 save_flags(f); /* interrupts might already be disabled! */
 cli();
 inb(port);
 wait_for_em(rdyport);
 j=inb(port);
 wait_for_em(rdyport);
 restore_flags(f);
 return j;
}

int pcbri_probe(struct device *dev);
static int pcbri_probe1(struct device *dev, int ioaddr);

static void pcbri_reset_bri(struct device *dev);
static int pcbri_block_input(struct device *dev, int count,
			  char *buf, int ring_offset);
static void pcbri_block_output(struct device *dev, const int count,
		const unsigned char *buf, const int start_page);


/*  Probe

   The PCBRI emulates a Station Address PROM (SAPROM) in the packet
   buffer memory space.  It puts 0x57,0x57 in bytes 0x0e,0x0f of
   the SAPROM. Unfortunately, so does an NEx000.

   But, the PCBRI's emulation is imperfect, it's a bit slow.
   We can use this speed difference to tell a PCBRI from an NE or clone.

*/

#ifdef HAVE_DEVLIST
struct netdev_entry netcard_drv =
{"pcbri", pcbri_probe1, PCBRI_IO_EXTENT, netcard_portlist};
#else

int pcbri_probe(struct device *dev)
{
    int i;
    int base_addr = dev ? dev->base_addr : 0;

    if (base_addr > 0x1ff)	/* Check a single specified location. */
	return pcbri_probe1(dev, base_addr);
    else if (base_addr != 0)	/* Don't probe at all. */
	return ENXIO;

    for (i = 0; netcard_portlist[i]; i++) {
	int ioaddr = netcard_portlist[i];
	if (check_region(ioaddr, BRI_IO_EXTENT))
	{
	#ifdef LOG_ALL
	    printk("BRI: %#3x: is snarfed\n",ioaddr);
	#endif
	    continue;
	}
	if (pcbri_probe1(dev, ioaddr) == 0)
	    return 0;
    }
    printk("No PCBRI card found.\n");
    return ENODEV;
}
#endif

#ifdef MODULE

char kernel_version[] = UTS_RELEASE;
static struct device dev_pcbri = {
        "             ",0,0,0,0,0,0,0,0,0,NULL,pcbri_probe };
       
       
int init_module(void)
{
 if(IRQ==0)
  {
   printk("A module can't autoprobe, please set IRQ\n");
   return -EIO;
  }
 if((IRQ<2)||(IRQ>15))
 {
  printk("Illegal IRQ\n");
  return -EIO;
 }
 if(register_netdev(&dev_pcbri)!=0)
 {
  return -EIO;
 }
 printk(version);
 return 0;
}

void cleanup_module(void)
{
 if(MOD_IN_USE) printk("BRI: CANNOT REMOVE\n");
 else
 {
  if(dev_pcbri.start) 
   { printk("BRI: FORCED SHUTDOWN\n"); dev_pcbri.start=0; }
  unregister_netdev(&dev_pcbri);
  release_region(PORT,BRI_IO_EXTENT);
  free_irq(IRQ);
 }
}

#endif

static int pcbri_probe1(struct device *dev, int ioaddr)
{
    int i;
    int a,b,c,d;
    unsigned char SA_prom[32];
    int wordlength = 2;
    char *name = NULL;
    int ok;
    int reg0 = bri_inb(ioaddr+28,ioaddr);
#ifdef LOG_ALL 
    printk("BRI: probing #%3x: ",ioaddr);
#endif
    time_count=0;
    if ( reg0 == 0xFF)
    {
#ifdef LOG_ALL
        printk("Hopeless\n");
#endif
	return ENODEV;
    }
    a=inb(ioaddr+28)&1;
    inb(ioaddr);
    b=inb(ioaddr+28)&1;
    c=inb(ioaddr+28)&1;
    for(d=0; d<200; d++) { d++; d--; }
    d=inb(ioaddr+28)&1;
    if((a==b)&&(b==c)&(c==d))
    {
#ifdef LOG_ALL
     printk("Bad ready indicator, not BRI\n");
#endif
     return ENODEV;
    }
    time_count=0;
    
    /* Attempt to talk to emulator */
    {	int regd;
	bri_outb(ioaddr+28,Ebri_NODMA+Ebri_PAGE1+Ebri_STOP, ioaddr + Ebri_CMD);
	regd = bri_inb(ioaddr+28,ioaddr + 0x0d);
	bri_outb(ioaddr+28, 0xff, ioaddr + 0x0d);
	bri_outb(ioaddr+28, Ebri_NODMA+Ebri_PAGE0, ioaddr + Ebri_CMD);
	bri_inb(ioaddr+28, ioaddr + EN0_COUNTER0); /* Clear the counter by reading. */
	if (bri_inb(ioaddr+28, ioaddr + EN0_COUNTER0) != 0) {
	    bri_outb(ioaddr+28, reg0, ioaddr);
	    bri_outb(ioaddr+28, regd, ioaddr + 0x0d);	/* Restore the old values. */
#ifdef LOG_ALL
	    printk("emulator failed test\n");
#endif
	    return ENODEV;
	}
    }

    if(time_count>2) 
    { 
#ifdef LOG_ALL
     printk("pcbri: too many timeouts at %#3x:\n",ioaddr); 
#endif
     return ENODEV;
    }

    printk("PCBRI at %#3x : ", ioaddr);
    PORT=ioaddr;
    
    i=bri_inb(ioaddr+28,ioaddr+BRI_RESET);
    for(a=0; a<(loops_per_sec>>6); a++);
    bri_outb(ioaddr+28,i,ioaddr+BRI_RESET);
    
    /* Read the 16 bytes of station address PROM.
       We must first initialize registers.
       We may not be able to reliably read the SAPROM address without this.
    */
    {
	struct {unsigned char value, offset; } program_seq[] = {
	    {Ebri_NODMA+Ebri_PAGE0+Ebri_STOP, Ebri_CMD}, /* Select page 0*/
	    {0x49,	EN0_DCFG},	/* Don't Set byte-wide (0x48) access. */
	    {0x00,	EN0_RCNTLO},	/* Clear the count regs. */
	    {0x00,	EN0_RCNTHI},
	    {0x00,	EN0_IMR},	/* Mask completion irq. */
	    {0xFF,	EN0_ISR},
	    {Ebri_RXOFF,EN0_RXCR},	/* 0x20  Set to monitor */
	    {0x06,	EN0_TXCR},	/* 0x02  and loopback mode. */
	    {0x20,	EN0_RCNTLO},
	    {0x00,	EN0_RCNTHI},
	    {0x00,	EN0_RSARLO},	/* DMA starting at 0x0000. */
	    {0x00,	EN0_RSARHI},
	    {0x01,	EN0_STARTPG},
	    {0x02,	EN0_STOPPG},
	    {0x03,	EN0_BOUNDARY},
	    {0x20,	EN0_RCNTLO},
	    {0x00,	EN0_RCNTHI},
	    {Ebri_RREAD+Ebri_START, Ebri_CMD},
	};
	for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++)
	    bri_outb(ioaddr+28, program_seq[i].value, ioaddr + program_seq[i].offset);
    }
    for(i = 0; i < 16; i++) {
	SA_prom[i] = inb(ioaddr + BRI_DATAPORT);
    }

	/* We must set the bri for word mode. */
	bri_outb(ioaddr+28, 0x49, ioaddr + EN0_DCFG);
	/* We used to reset the ethercard here, but it doesn't seem
	   to be necessary. */

    for(i = 0; i < ETHER_ADDR_LEN; i++) {
	dev->dev_addr[i] = SA_prom[i];
	printk(" %2.2x", SA_prom[i]);
    }
    ok = (SA_prom[14] == 0x57  &&  SA_prom[15] == 0x57);
    /* Set up the rest of the parameters. */
    if (ok) {
	name="PCBRI";
	dev->base_addr=ioaddr;
    }
     else
    {
     printk("No Signature\n");
     /* Should setup default address FIXME */
     return ENODEV;
    }

    ether_setup(dev);

#ifndef MODULE
    if (dev->irq < 2) {
	autoirq_setup(0);
	bri_outb(ioaddr+28, 0x50, ioaddr + EN0_IMR);	/* Enable one interrupt. */
	bri_outb(ioaddr+28, 0x00, ioaddr + EN0_RCNTLO);
	bri_outb(ioaddr+28, 0x00, ioaddr + EN0_RCNTHI);
	bri_outb(ioaddr+28, Ebri_RREAD+Ebri_START, ioaddr); /* Trigger it... */
	bri_outb(ioaddr+28, 0x00, ioaddr + EN0_IMR); 	/* Mask it again. */
	dev->irq = autoirq_report(0);
	if (bri_ei_debug > 2)
	    printk(" autoirq is %d\n", dev->irq);
    } else if (dev->irq == 2)
	/* Fixup for users that don't know that IRQ 2 is really IRQ 9,
	   or don't know which one to set. */
	dev->irq = 9;
	IRQ=dev->irq;
#else
 if(IRQ==2) IRQ=9;
 dev->irq=IRQ;
#endif
    
    /* Snarf the interrupt now.  There's no point in waiting since we cannot
       share and the board will usually be enabled. */
    {
	int irqval = request_irq (dev->irq, bri_ei_interrupt, 0, "pcbri");
	if (irqval) {
	    printk (" unable to get IRQ %d (irqval=%d).\n", dev->irq, irqval);
	    return EAGAIN;
	}
    }

    dev->base_addr = ioaddr;

#ifdef MODULE
    dev->open = &bri_open;
    dev->stop = &bri_close;
#endif

    request_region(ioaddr, BRI_IO_EXTENT, "pcbri");

    err_mode=1;

    for(i = 0; i < ETHER_ADDR_LEN; i++)
	dev->dev_addr[i] = SA_prom[i];

    bri_ethdev_init(dev);
    printk("\n%s: %s found at %#x, using IRQ %d.\n",
	   dev->name, name, ioaddr, dev->irq);

    if (bri_ei_debug > 0)
	printk(version);

    bri_ei_status.name = name;
    bri_ei_status.tx_start_page = 0x40;
    bri_ei_status.stop_page = 0x80;
    bri_ei_status.rx_start_page = 0x4d; /* Should be 0x4c */
    bri_ei_status.word16 = 1;

    bri_ei_status.reset_bri = &pcbri_reset_bri;
    bri_ei_status.block_input = &pcbri_block_input;
    bri_ei_status.block_output = &pcbri_block_output;
    
    bri_init(dev, 0);
    
    return 0;
}

/* Hard reset the emulator */
static void 
pcbri_reset_bri(struct device *dev)
{
    int tmp = inb(BRI_BASE + BRI_RESET);
    int i;
#ifdef RESET_CHECK
    int reset_start_time = jiffies;
#endif
    for(i=0; i<50; i++) /*delay*/;
    printk("resetting the bri t=%ld...\n", jiffies);
    bri_ei_status.txing = 0;

    outb(tmp, BRI_BASE + BRI_RESET);
    
 #ifdef RESET_CHECK
    /* This check is dangerous */
    while ((bri_inb(BRI_BASE+28, BRI_BASE+EN0_ISR) & ENISR_RESET) == 0)
	if (jiffies - reset_start_time > 3) {
	    printk("%s: pcbri_reset_bri() did not complete.\n", dev->name);
	    break;
	}
 #endif
	
    bri_outb(BRI_BASE+28, 0x49, EN0_DCFG);
    bri_outb(BRI_BASE+28, 0xff, BRI_BASE + EN0_ISR);
    bri_outb(BRI_BASE+28, ENISR_ALL, BRI_BASE + EN0_IMR);
    bri_outb(BRI_BASE+28, Ebri_NODMA+Ebri_PAGE0+Ebri_START, BRI_BASE);
    bri_outb(BRI_BASE+28, Ebri_TXCONFIG, BRI_BASE + EN0_TXCR); /* xmit on. */
    bri_outb(BRI_BASE+28, Ebri_RXCONFIG, BRI_BASE + EN0_RXCR); /* rx on,  */

}

/* Block input and output, similar to the Crynwr packet driver.  If you
   porting to a new ethercard look at the packet driver source for hints.
   The BRI doesn't share it on-board packet memory -- you have to put
   the packet out through the "remote DMA" dataport using outb. */

static int
pcbri_block_input(struct device *dev, int count, char *buf, int ring_offset)
{
    int xfer_count = count;
    int nic_base = dev->base_addr;
 #ifdef LOG_ALL
    printk("Receive: %d bytes at %xh\n",count,ring_offset);
 #endif
    bri_ei_status.dmaing |= 0x02;
    if(count&1) count++;
    bri_outb(nic_base+28,ENISR_RDC, nic_base+EN0_ISR);
    bri_outb(nic_base+28,Ebri_NODMA+Ebri_PAGE0+Ebri_START, nic_base+ BRI_CMD);
    bri_outb(nic_base+28,count & 0xff, nic_base + EN0_RCNTLO);
    bri_outb(nic_base+28,count >> 8, nic_base + EN0_RCNTHI);
    bri_outb(nic_base+28,ring_offset & 0xff, nic_base + EN0_RSARLO);
    bri_outb(nic_base+28,ring_offset >> 8, nic_base + EN0_RSARHI);
    bri_outb(nic_base+28,Ebri_RREAD+Ebri_START, nic_base + BRI_CMD);
    insw(BRI_BASE + BRI_DATAPORT,buf,count>>1);
    bri_outb(nic_base+28,ENISR_RDC,nic_base+EN0_ISR);
    bri_ei_status.dmaing &= ~0x03;
    return ring_offset + count;
}

static void
pcbri_block_output(struct device *dev, int count,
		const unsigned char *buf, const int start_page)
{
    int nic_base = BRI_BASE;
    int i=0,j,k;

#ifdef LOG_ALL
    printk("Bri: tx: %d bytes at page %xh\n",count,start_page);
#endif
    if (count & 0x01) count++;
    bri_ei_status.dmaing |= 0x04;
#ifdef ALL_BUGFIX
   /* ALERT -- brain damage ahead */
   /* Is this really necessary? */
   bri_outb(nic_base+28,0x0C,nic_base+EN0_RCNTLO);
   bri_outb(nic_base+28,0x00,nic_base+EN0_RCNTHI);
   bri_outb(nic_base+28,0x00,nic_base+EN0_RSARLO);
   bri_outb(nic_base+28,start_page,nic_base+EN0_RSARHI);
   bri_outb(nic_base+28,Ebri_RREAD+Ebri_START,nic_base+BRI_CMD);
   while(i<1000)
   {
    i++;
    j=bri_inb(nic_base+28,nic_base+EN0_RSARLO);
    k=bri_inb(nic_base+28,nic_base+EN0_RSARHI);
    if(k) break;
   }
    
   if((j!=1)||(k!=start_page)) printk("BRI: Send didn't increment\n");
 #endif
 
   bri_outb(nic_base+28,count & 0xff, nic_base + EN0_RCNTLO);
   bri_outb(nic_base+28,count >> 8,   nic_base + EN0_RCNTHI);
   bri_outb(nic_base+28,0x00,nic_base+EN0_RSARLO);
   bri_outb(nic_base+28,start_page,nic_base+EN0_RSARHI);

   bri_outb(nic_base+28,Ebri_RWRITE+Ebri_START, nic_base + BRI_CMD);
    
   outsw(nic_base + BRI_DATAPORT, buf, count>>1);

   bri_ei_status.dmaing &= ~0x05;
   return;
}
