/*
 *      FarSync X21 driver for Linux (2.2.x kernel version)
 *
 *      Actually sync driver for X.21, V.35 and V.24 on FarSync T-series cards
 *
 *      Copyright (C) 2001 FarSite Communications Ltd.
 *      www.farsite.co.uk
 *
 *      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.
 *
 *      File:   kernel2.2/farsync.c
 *      Author: R.J.Dunlop      <bob.dunlop@farsite.co.uk>
 */
/*      Revision History
 *      $Log: farsync.c,v $
 *      Revision 1.10  2002/01/03 16:49:54  kevinc
 *      Two issues fixed with one stone
 *      1) Typo so that four port cards detected
 *      2) increment card count when cards are detected
 *      This second problems fixes the module unload/load problem
 *
 *      Revision 1.9  2001/09/10 11:13:52  rjd
 *      Correct ioctl parameter checking for security problem highlighted on
 *      kernel developer list.
 *
 *      Revision 1.8  2001/08/21 11:44:57  rjd
 *      Brought code into line with that used for the official 2.4.9 kernel
 *
 *      Revision 1.7  2001/07/03 15:55:51  rjd
 *      First non-beta version.
 *      Started preparations for the generic-HDLC version.
 *
 *      Revision 1.6  2001/05/29 09:03:28  rjd
 *      Improved carrier detection handling and removed interrupt loop
 *      optimisation as unused.
 *
 *      Revision 1.5  2001/05/11 12:13:08  rjd
 *      Corrections to printing macros for later compilers.
 *      Fixed MTU changing code.
 *
 *      Revision 1.4  2001/05/10 08:26:30  rjd
 *      Added configurable MTU support
 *
 *      Revision 1.3  2001/05/02 09:29:20  rjd
 *      Reboot with noisy lines causing Opps fixed.
 *      Tx buffer reordering problem fixed.
 *      Port configuration code simplified.
 *
 *      Revision 1.2  2001/04/05 16:36:24  rjd
 *      Corrected module compilation and upgraded to SMC version 11
 *
 *      Revision 1.1.1.1  2001/03/30 13:46:42  rjd
 *      Import after first Beta shipped.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/init.h>
#include <linux/if_arp.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include "syncppp.h"
#include "farsync.h"


/*
 *      2.4.x compatability macros
 *
 *      This driver is effectivly a backport of the 2.4.x kernel driver to the
 *      2.2.x kernel. Differences are enough that I think trying to use one
 *      source would have become increasingly difficult and I wanted a clean
 *      2.4 implementation. However some things can be made compatable via a
 *      few simple macros.
 */
#define netif_wake_queue(dev)       clear_bit(0, &dev->tbusy)
#define netif_stop_queue(dev)       set_bit(0, &dev->tbusy)
#define netif_start_queue(dev)      do { dev->tbusy = 0; dev->interrupt = 0; \
                                        dev->start = 1; } while (0)
#define netif_carrier_ok(dev)       ((dev)->flags & IFF_RUNNING )
#define netif_carrier_on(dev)       ((dev)->flags |= IFF_RUNNING )
#define netif_carrier_off(dev)      ((dev)->flags &= ~IFF_RUNNING )

#define pci_resource_start(dev,bar) ((dev)->base_address[bar] & ~0x00000001)


/*
 *      Module info
 */
MODULE_AUTHOR("R.J.Dunlop <bob.dunlop@farsite.co.uk>");
MODULE_DESCRIPTION("FarSync T-Series X21 driver. FarSite Communications Ltd.");

EXPORT_NO_SYMBOLS;


/*      Driver configuration and global parameters
 *      ==========================================
 */

/*      Maximum number of cards supported
 */
#define FST_MAX_CARDS           8

/*      Number of ports (per card) supported
 */
#define FST_MAX_PORTS           4


/*      PCI vendor and device IDs
 */
#define FSC_PCI_VENDOR_ID       0x1619  /* FarSite Communications Ltd */
#define T2P_PCI_DEVICE_ID       0x0400  /* T2P X21 2 port card */
#define T4P_PCI_DEVICE_ID       0x0440  /* T4P X21 4 port card */


/*      Default parameters for the link
 */
#define FST_TX_QUEUE_LEN        100     /* At 8Mbps a longer queue length is
                                         * useful, the syncppp module forces
                                         * this down assuming a slower line I
                                         * guess.
                                         */
#define FST_MAX_MTU             8000    /* Huge but possible */
#define FST_DEF_MTU             1500    /* Common sane value */

#define FST_TX_TIMEOUT          (2*HZ)


#ifdef ARPHRD_RAWHDLC
#define ARPHRD_MYTYPE   ARPHRD_RAWHDLC  /* Raw frames */
#else
#define ARPHRD_MYTYPE   ARPHRD_HDLC     /* Cisco-HDLC (keepalives etc) */
#endif


/*      Card shared memory layout
 *      =========================
 */
#pragma pack(1)

/*      This information is derived in part from the FarSite FarSync Smc.h
 *      file. Unfortunately various name clashes and the non-portability of the
 *      bit field declarations in that file have meant that I have chosen to
 *      recreate the information here.
 *
 *      The SMC (Shared Memory Configuration) has a version number that is
 *      incremented every time there is a significant change. This number can
 *      be used to check that we have not got out of step with the firmware
 *      contained in the .CDE files.
 */
#define SMC_VERSION 11

#define FST_MEMSIZE 0x100000    /* Size of card memory (1Mb) */

#define SMC_BASE 0x00002000L    /* Base offset of the shared memory window main
                                 * configuration structure */
#define BFM_BASE 0x00010000L    /* Base offset of the shared memory window DMA
                                 * buffers */

#define LEN_TX_BUFFER 8192      /* Size of packet buffers */
#define LEN_RX_BUFFER 8192

#define LEN_SMALL_TX_BUFFER 256 /* Size of obsolete buffs used for DOS diags */
#define LEN_SMALL_RX_BUFFER 256

#define NUM_TX_BUFFER 2         /* Must be power of 2. Fixed by firmware */
#define NUM_RX_BUFFER 8

/* Interrupt retry time in milliseconds */
#define INT_RETRY_TIME 2


/*      The Am186CH/CC processors support a SmartDMA mode using circular pools
 *      of buffer descriptors. The structure is almost identical to that used
 *      in the LANCE Ethernet controllers. Details available as PDF from the
 *      AMD web site: http://www.amd.com/products/epd/processors/\
 *                    2.16bitcont/3.am186cxfa/a21914/21914.pdf
 */
struct txdesc {                 /* Transmit descriptor */
        volatile u16 ladr;      /* Low order address of packet. This is a
                                 * linear address in the Am186 memory space
                                 */
        volatile u8  hadr;      /* High order address. Low 4 bits only, high 4
                                 * bits must be zero
                                 */
        volatile u8  bits;      /* Status and config */
        volatile u16 bcnt;      /* 2s complement of packet size in low 15 bits.
                                 * Transmit terminal count interrupt enable in
                                 * top bit.
                                 */
                 u16 unused;    /* Not used in Tx */
};

struct rxdesc {                 /* Receive descriptor */
        volatile u16 ladr;      /* Low order address of packet */
        volatile u8  hadr;      /* High order address */
        volatile u8  bits;      /* Status and config */
        volatile u16 bcnt;      /* 2s complement of buffer size in low 15 bits.
                                 * Receive terminal count interrupt enable in
                                 * top bit.
                                 */
        volatile u16 mcnt;      /* Message byte count (15 bits) */
};

/* Convert a length into the 15 bit 2's complement */
/* #define cnv_bcnt(len)   (( ~(len) + 1 ) & 0x7FFF ) */
/* Since we need to set the high bit to enable the completion interrupt this
 * can be made a lot simpler
 */
#define cnv_bcnt(len)   (-(len))

/* Status and config bits for the above */
#define DMA_OWN         0x80            /* SmartDMA owns the descriptor */
#define TX_STP          0x02            /* Tx: start of packet */
#define TX_ENP          0x01            /* Tx: end of packet */
#define RX_ERR          0x40            /* Rx: error (OR of next 4 bits) */
#define RX_FRAM         0x20            /* Rx: framing error */
#define RX_OFLO         0x10            /* Rx: overflow error */
#define RX_CRC          0x08            /* Rx: CRC error */
#define RX_HBUF         0x04            /* Rx: buffer error */
#define RX_STP          0x02            /* Rx: start of packet */
#define RX_ENP          0x01            /* Rx: end of packet */


/* Interrupts from the card are caused by various events and these are presented
 * in a circular buffer as several events may be processed on one physical int
 */
#define MAX_CIRBUFF     32

struct cirbuff {
        u8 rdindex;             /* read, then increment and wrap */
        u8 wrindex;             /* write, then increment and wrap */
        u8 evntbuff[MAX_CIRBUFF];
};

/* Interrupt event codes.
 * Where appropriate the two low order bits indicate the port number
 */
#define CTLA_CHG        0x18    /* Control signal changed */
#define CTLB_CHG        0x19
#define CTLC_CHG        0x1A
#define CTLD_CHG        0x1B

#define INIT_CPLT       0x20    /* Initialisation complete */
#define INIT_FAIL       0x21    /* Initialisation failed */

#define ABTA_SENT       0x24    /* Abort sent */
#define ABTB_SENT       0x25
#define ABTC_SENT       0x26
#define ABTD_SENT       0x27

#define TXA_UNDF        0x28    /* Transmission underflow */
#define TXB_UNDF        0x29
#define TXC_UNDF        0x2A
#define TXD_UNDF        0x2B


/* Port physical configuration. See farsync.h for field values */
struct port_cfg {
        u16  lineInterface;     /* Physical interface type */
        u8   x25op;             /* Unused at present */
        u8   internalClock;     /* 1 => internal clock, 0 => external */
        u32  lineSpeed;         /* Speed in bps */
};

/* Finally sling all the above together into the shared memory structure.
 * Sorry it's a hodge podge of arrays, structures and unused bits, it's been
 * evolving under NT for some time so I guess we're stuck with it.
 * The structure starts at offset SMC_BASE.
 * See farsync.h for some field values.
 */
struct fst_shared {
                                /* DMA descriptor rings */
        struct rxdesc rxDescrRing[FST_MAX_PORTS][NUM_RX_BUFFER];
        struct txdesc txDescrRing[FST_MAX_PORTS][NUM_TX_BUFFER];

                                /* Obsolete small buffers */
        u8  smallRxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_SMALL_RX_BUFFER];
        u8  smallTxBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_SMALL_TX_BUFFER];

        u8  taskStatus;         /* 0x00 => initialising, 0x01 => running,
                                 * 0xFF => halted
                                 */

        u8  interruptHandshake; /* Set to 0x01 by adapter to signal interrupt,
                                 * set to 0xEE by host to acknowledge interrupt
                                 */

        u16 smcVersion;         /* Must match SMC_VERSION */

        u32 smcFirmwareVersion; /* 0xIIVVRRBB where II = product ID, VV = major
                                 * version, RR = revision and BB = build
                                 */

        u16 txa_done;           /* Obsolete completion flags */
        u16 rxa_done;
        u16 txb_done;
        u16 rxb_done;
        u16 txc_done;
        u16 rxc_done;
        u16 txd_done;
        u16 rxd_done;

        u16 mailbox[4];         /* Diagnostics mailbox. Not used */

        struct cirbuff interruptEvent;  /* interrupt causes */

        u32 v24IpSts[FST_MAX_PORTS];    /* V.24 control input status */
        u32 v24OpSts[FST_MAX_PORTS];    /* V.24 control output status */

        struct port_cfg portConfig[FST_MAX_PORTS];

        u16 clockStatus[FST_MAX_PORTS]; /* lsb: 0=> present, 1=> absent */

        u16 cableStatus;                /* lsb: 0=> present, 1=> absent */

        u16 txDescrIndex[FST_MAX_PORTS]; /* transmit descriptor ring index */
        u16 rxDescrIndex[FST_MAX_PORTS]; /* receive descriptor ring index */

        u16 portMailbox[FST_MAX_PORTS][2];      /* command, modifier */
        u16 cardMailbox[4];                     /* Not used */

                                /* Number of times that card thinks the host has
                                 * missed an interrupt by not acknowledging
                                 * within 2mS (I guess NT has problems)
                                 */
        u32 interruptRetryCount;

                                /* Driver private data used as an ID. We'll not
                                 * use this on Linux I'd rather keep such things
                                 * in main memory rather than on the PCI bus
                                 */
        u32 portHandle[FST_MAX_PORTS];

                                /* Count of Tx underflows for stats */
        u32 transmitBufferUnderflow[FST_MAX_PORTS];

                                /* Debounced V.24 control input status */
        u32 v24DebouncedSts[FST_MAX_PORTS];

                                /* Adapter debounce timers. Don't touch */
        u32 ctsTimer[FST_MAX_PORTS];
        u32 ctsTimerRun[FST_MAX_PORTS];
        u32 dcdTimer[FST_MAX_PORTS];
        u32 dcdTimerRun[FST_MAX_PORTS];

        u32 numberOfPorts;      /* Number of ports detected at startup */

        u16 _reserved[64];

        u16 cardMode;           /* Bit-mask to enable features:
                                 * Bit 0: 1 enables LED identify mode
                                 */

        u16 portScheduleOffset;

        u32 endOfSmcSignature;  /* endOfSmcSignature MUST be the last member of
                                 * the structure and marks the end of the shared
                                 * memory. Adapter code initializes its value as
                                 * END_SIG.
                                 */
};

/* endOfSmcSignature value */
#define END_SIG                 0x12345678

/* Mailbox values. (portMailbox) */
#define NOP             0       /* No operation */
#define ACK             1       /* Positive acknowledgement to PC driver */
#define NAK             2       /* Negative acknowledgement to PC driver */
#define STARTPORT       3       /* Start an HDLC port */
#define STOPPORT        4       /* Stop an HDLC port */
#define ABORTTX         5       /* Abort the transmitter for a port */
#define SETV24O         6       /* Set V24 outputs */


/* Larger buffers are positioned in memory at offset BFM_BASE */
struct buf_window {
        u8 txBuffer[FST_MAX_PORTS][NUM_TX_BUFFER][LEN_TX_BUFFER];
        u8 rxBuffer[FST_MAX_PORTS][NUM_RX_BUFFER][LEN_RX_BUFFER];
};

/* Calculate offset of a buffer object within the shared memory window */
#define BUF_OFFSET(X)   ((unsigned int)&(((struct buf_window *)BFM_BASE)->X))

#pragma pack()


/*      Device driver private information
 *      =================================
 */
/*      Per port (line or channel) information
 */
struct fst_port_info {
        void                   *if_ptr; /* Some drivers describe this as a
                                         * general purpose pointer. However if
                                         * using syncPPP it has a very specific
                                         * purpose: it must be the first item in
                                         * the structure pointed to by dev->priv
                                         * and must in turn point to the
                                         * associated ppp_device structure.
                                         */
        struct fst_card_info   *card;   /* Card we're associated with */
        int                     index;  /* Port index on the card */
        int                     proto;  /* Protocol we are running */
        int                     hwif;   /* Line hardware (lineInterface copy) */
        int                     run;    /* Port is running */
        int                     rxpos;  /* Next Rx buffer to use */
        int                     txpos;  /* Next Tx buffer to use */
        int                     txipos; /* Next Tx buffer to check for free */
        int                     txcnt;  /* Count of Tx buffers in use */
        struct device           *dev;   /* Kernel network device entry */
        struct net_device_stats stats;  /* Standard statistics */
        struct ppp_device       pppdev; /* Link to syncPPP */
        char name[8];                   /* syncXX\0 == 7 */
};

/*      Per card information
 */
struct fst_card_info {
        char          *mem;             /* Card memory mapped to kernel space */
        char          *ctlmem;          /* Control memory for PCI cards */
        unsigned int   phys_mem;        /* Physical memory window address */
        unsigned int   phys_ctlmem;     /* Physical control memory address */
        unsigned int   irq;             /* Interrupt request line number */
        unsigned int   nports;          /* Number of serial ports */
        unsigned int   type;            /* Type index of card */
        unsigned int   state;           /* State of card */
        spinlock_t     card_lock;       /* Lock for SMP access */
        unsigned short pci_conf;        /* PCI card config in I/O space */
                                        /* Per port info */
        struct fst_port_info ports[ FST_MAX_PORTS ];
};

                                        /* Pointers to allocated storage info */
static struct fst_card_info *fst_card_info[ FST_MAX_CARDS ];
static int fst_ncards = 0;              /* Number of cards we have detected */



/*
 *      Shared memory window access macros
 *
 *      We have a nice memory based structure above, which could be directly
 *      mapped on i386 but might not work on other architectures unless we use
 *      the readb,w,l and writeb,w,l macros. Unfortunately these macros take
 *      physical offsets so we have to convert. The only saving grace is that
 *      this should all collapse back to a simple indirection eventually.
 */
#define WIN_OFFSET(X)   ((long)&(((struct fst_shared *)SMC_BASE)->X))

#define FST_RDB(C,E)    readb ((C)->mem + WIN_OFFSET(E))
#define FST_RDW(C,E)    readw ((C)->mem + WIN_OFFSET(E))
#define FST_RDL(C,E)    readl ((C)->mem + WIN_OFFSET(E))

#define FST_WRB(C,E,B)  writeb ((B), (C)->mem + WIN_OFFSET(E))
#define FST_WRW(C,E,W)  writew ((W), (C)->mem + WIN_OFFSET(E))
#define FST_WRL(C,E,L)  writel ((L), (C)->mem + WIN_OFFSET(E))


/*
 *      Debug support
 */
#if FST_DEBUG

static int fst_debug_mask = { FST_DEBUG };

/* Most common debug activity is to print something if the corresponding bit
 * is set in the debug mask. Note: this uses a non-ANSI extension in GCC to
 * support variable numbers of macro parameters. The inverted if prevents us
 * eating someone else's else clause.
 */
#define dbg(F,fmt,A...) if ( ! ( fst_debug_mask & (F))) \
                                ; \
                        else \
                                printk ( KERN_DEBUG FST_NAME ": " fmt, ## A )

#else
# define dbg(X...)      /* NOP */
#endif


/*      Printing short cuts
 */
#define printk_err(fmt,A...)    printk ( KERN_ERR     FST_NAME ": " fmt, ## A )
#define printk_warn(fmt,A...)   printk ( KERN_WARNING FST_NAME ": " fmt, ## A )
#define printk_info(fmt,A...)   printk ( KERN_INFO    FST_NAME ": " fmt, ## A )


/*      Card control functions
 *      ======================
 */
/*      Place the processor in reset state
 *
 * Used to be a simple write to card control space but a glitch in the latest
 * AMD Am186CH processor means that we now have to do it by asserting and de-
 * asserting the PLX chip PCI Adapter Software Reset. Bit 30 in CNTRL register
 * at offset 0x50.
 */
static inline void
fst_cpureset ( struct fst_card_info *card )
{
        unsigned int regval;

        regval = inl ( card->pci_conf + 0x50 );

        outl ( regval |  0x40000000, card->pci_conf + 0x50 );
        outl ( regval & ~0x40000000, card->pci_conf + 0x50 );
}

/*      Release the processor from reset
 */
static inline void
fst_cpurelease ( struct fst_card_info *card )
{
        (void) readb ( card->ctlmem );
}

/*      Clear the cards interrupt flag
 */
static inline void
fst_clear_intr ( struct fst_card_info *card )
{
        /* Poke the appropriate PLX chip register (same as enabling interrupts)
         */
        outw ( 0x0543, card->pci_conf + 0x4C );
}

/*      Disable card interrupts
 */
static inline void
fst_disable_intr ( struct fst_card_info *card )
{
        outw ( 0x0000, card->pci_conf + 0x4C );
}


/*      Issue a Mailbox command for a port.
 *      Note we issue them on a fire and forget basis, not expecting to see an
 *      error and not waiting for completion.
 */
static void
fst_issue_cmd ( struct fst_port_info *port, unsigned short cmd )
{
        struct fst_card_info *card;
        unsigned short mbval;
        unsigned long flags;
        int safety;

        card = port->card;
        spin_lock_irqsave ( &card->card_lock, flags );
        mbval = FST_RDW ( card, portMailbox[port->index][0]);

        safety = 0;
        /* Wait for any previous command to complete */
        while ( mbval > NAK )
        {
                spin_unlock_irqrestore ( &card->card_lock, flags );
                schedule_timeout ( 1 );
                spin_lock_irqsave ( &card->card_lock, flags );

                if ( ++safety > 1000 )
                {
                        printk_err ("Mailbox safety timeout\n");
                        break;
                }

                mbval = FST_RDW ( card, portMailbox[port->index][0]);
        }
        if ( safety > 0 )
        {
                dbg ( DBG_CMD,"Mailbox clear after %d jiffies\n", safety );
        }
        if ( mbval == NAK )
        {
                dbg ( DBG_CMD,"issue_cmd: previous command was NAK'd\n");
        }

        FST_WRW ( card, portMailbox[port->index][0], cmd );

        if ( cmd == ABORTTX || cmd == STARTPORT )
        {
                port->txpos  = 0;
                port->txipos = 0;
                port->txcnt  = 0;
        }

        spin_unlock_irqrestore ( &card->card_lock, flags );
}


/*      Port output signals control
 */
static inline void
fst_op_raise ( struct fst_port_info *port, unsigned int outputs )
{
        outputs |= FST_RDL ( port->card, v24OpSts[port->index]);
        FST_WRL ( port->card, v24OpSts[port->index], outputs );

        if ( port->run )
                fst_issue_cmd ( port, SETV24O );
}

static inline void
fst_op_lower ( struct fst_port_info *port, unsigned int outputs )
{
        outputs = ~outputs & FST_RDL ( port->card, v24OpSts[port->index]);
        FST_WRL ( port->card, v24OpSts[port->index], outputs );

        if ( port->run )
                fst_issue_cmd ( port, SETV24O );
}


/*
 *      Setup port Rx buffers
 */
static void
fst_rx_config ( struct fst_port_info *port )
{
        int i;
        int pi;
        unsigned int offset;
        unsigned long flags;
        struct fst_card_info *card;

        pi   = port->index;
        card = port->card;
        spin_lock_irqsave ( &card->card_lock, flags );
        for ( i = 0 ; i < NUM_RX_BUFFER ; i++ )
        {
                offset = BUF_OFFSET ( rxBuffer[pi][i][0]);

                FST_WRW ( card, rxDescrRing[pi][i].ladr, (u16) offset );
                FST_WRB ( card, rxDescrRing[pi][i].hadr, (u8)( offset >> 16 ));
                FST_WRW ( card, rxDescrRing[pi][i].bcnt,
                                        cnv_bcnt ( LEN_RX_BUFFER ));
                FST_WRW ( card, rxDescrRing[pi][i].mcnt, 0 );
                FST_WRB ( card, rxDescrRing[pi][i].bits, DMA_OWN );
        }
        port->rxpos  = 0;
        spin_unlock_irqrestore ( &card->card_lock, flags );
}


/*
 *      Setup port Tx buffers
 */
static void
fst_tx_config ( struct fst_port_info *port )
{
        int i;
        int pi;
        unsigned int offset;
        unsigned long flags;
        struct fst_card_info *card;

        pi   = port->index;
        card = port->card;
        spin_lock_irqsave ( &card->card_lock, flags );
        for ( i = 0 ; i < NUM_TX_BUFFER ; i++ )
        {
                offset = BUF_OFFSET ( txBuffer[pi][i][0]);

                FST_WRW ( card, txDescrRing[pi][i].ladr, (u16) offset );
                FST_WRB ( card, txDescrRing[pi][i].hadr, (u8)( offset >> 16 ));
                FST_WRW ( card, txDescrRing[pi][i].bcnt, 0 );
                FST_WRB ( card, txDescrRing[pi][i].bits, 0 );
        }
        port->txpos  = 0;
        port->txipos = 0;
        port->txcnt  = 0;
        spin_unlock_irqrestore ( &card->card_lock, flags );
}


/*      Control signal change interrupt event
 */
static void
fst_intr_ctlchg ( struct fst_card_info *card, struct fst_port_info *port )
{
        int signals;

        signals = FST_RDL ( card, v24DebouncedSts[port->index]);

        if ( signals & (( port->hwif == X21 ) ? IPSTS_INDICATE : IPSTS_DCD ))
        {
                if ( ! netif_carrier_ok ( port->dev ))
                {
                        dbg ( DBG_INTR,"DCD active\n");

                        /* Poke sPPP to renegotiate */
                        if ( port->proto == FST_HDLC || port->proto == FST_PPP )
                        {
                                sppp_reopen ( port->dev );
                        }

                        netif_carrier_on ( port->dev );
                }
        }
        else
        {
                if ( netif_carrier_ok ( port->dev ))
                {
                        dbg ( DBG_INTR,"DCD lost\n");
                        netif_carrier_off ( port->dev );
                }
        }
}


/*      Rx complete interrupt
 */
static void
fst_intr_rx ( struct fst_card_info *card, struct fst_port_info *port )
{
        unsigned char dmabits;
        int pi;
        int rxp;
        unsigned short len;
        struct sk_buff *skb;
        int i;


        /* Check we have a buffer to process */
        pi  = port->index;
        rxp = port->rxpos;
        dmabits = FST_RDB ( card, rxDescrRing[pi][rxp].bits );
        if ( dmabits & DMA_OWN )
        {
                dbg ( DBG_RX | DBG_INTR,"intr_rx: No buffer port %d pos %d\n",
                                        pi, rxp );
                return;
        }

        /* Get buffer length */
        len = FST_RDW ( card, rxDescrRing[pi][rxp].mcnt );
        /* Discard the CRC */
        len -= 2;

        /* Check buffer length and for other errors. We insist on one packet
         * in one buffer. This simplifies things greatly and since we've
         * allocated 8K it shouldn't be a real world limitation
         */
        dbg ( DBG_RX,"intr_rx: %d,%d: flags %x len %d\n", pi, rxp, dmabits,
                                        len );
        if ( dmabits != ( RX_STP | RX_ENP ) || len > LEN_RX_BUFFER - 2 )
        {
                port->stats.rx_errors++;

                /* Update error stats and discard buffer */
                if ( dmabits & RX_OFLO )
                {
                        port->stats.rx_fifo_errors++;
                }
                if ( dmabits & RX_CRC )
                {
                        port->stats.rx_crc_errors++;
                }
                if ( dmabits & RX_FRAM )
                {
                        port->stats.rx_frame_errors++;
                }
                if ( dmabits == ( RX_STP | RX_ENP ))
                {
                        port->stats.rx_length_errors++;
                }

                /* Discard buffer descriptors until we see the end of packet
                 * marker
                 */
                i = 0;
                while (( dmabits & ( DMA_OWN | RX_ENP )) == 0 )
                {
                        FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN );
                        if ( ++rxp >= NUM_RX_BUFFER )
                                rxp = 0;
                        if ( ++i > NUM_RX_BUFFER )
                        {
                                dbg ( DBG_ASS,"intr_rx: Discarding more bufs"
                                                " than we have\n");
                                break;
                        }
                        dmabits = FST_RDB ( card, rxDescrRing[pi][rxp].bits );
                }

                /* Discard the terminal buffer */
                if ( ! ( dmabits & DMA_OWN ))
                {
                        FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN );
                        if ( ++rxp >= NUM_RX_BUFFER )
                                rxp = 0;
                }
                port->rxpos = rxp;
                return;
        }

        /* Allocate SKB */
        if (( skb = dev_alloc_skb ( len )) == NULL )
        {
                dbg ( DBG_RX,"intr_rx: can't allocate buffer\n");

                port->stats.rx_dropped++;

                /* Return descriptor to card */
                FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN );

                if ( ++rxp >= NUM_RX_BUFFER )
                        port->rxpos = 0;
                else
                        port->rxpos = rxp;
                return;
        }

        memcpy_fromio ( skb_put ( skb, len ),
                                card->mem + BUF_OFFSET ( rxBuffer[pi][rxp][0]),
                                len );

        /* Reset buffer descriptor */
        FST_WRB ( card, rxDescrRing[pi][rxp].bits, DMA_OWN );
        if ( ++rxp >= NUM_RX_BUFFER )
                port->rxpos = 0;
        else
                port->rxpos = rxp;

        /* Update stats */
        port->stats.rx_packets++;
        port->stats.rx_bytes += len;

        /* Push upstream */
        if ( port->proto == FST_HDLC || port->proto == FST_PPP )
        {
                /* Mark for further processing by sPPP module */
                skb->protocol = htons ( ETH_P_WAN_PPP );
        }
        else
        {
                /* DEC customer specific protocol (since nothing defined for
                 * marking raw data), at least one other driver uses this value
                 * for this purpose.
                 */
                skb->protocol = htons ( ETH_P_CUST );
                skb->pkt_type = PACKET_HOST;
        }
        skb->mac.raw = skb->data;
        skb->dev = port->dev;
        netif_rx ( skb );
}


/*
 *      The interrupt service routine
 *      Dev_id is our fst_card_info pointer
 */
static void
fst_intr ( int irq, void *dev_id, struct pt_regs *regs )
{
        struct fst_card_info *card;
        struct fst_port_info *port;
        int rdidx;                      /* Event buffer indices */
        int wridx;
        int event;                      /* Actual event for processing */
        int pi;

        if (( card = dev_id ) == NULL )
        {
                dbg ( DBG_INTR,"intr: spurious %d\n", irq );
                return;
        }

        dbg ( DBG_INTR,"intr: %d %p\n", irq, card );

        spin_lock ( &card->card_lock );

        /* Clear and reprime the interrupt source */
        fst_clear_intr ( card );

        /* Set the software acknowledge */
        FST_WRB ( card, interruptHandshake, 0xEE );

        /* Drain the event queue */
        rdidx = FST_RDB ( card, interruptEvent.rdindex );
        wridx = FST_RDB ( card, interruptEvent.wrindex );
        while ( rdidx != wridx )
        {
                event = FST_RDB ( card, interruptEvent.evntbuff[rdidx]);

                port = &card->ports[event & 0x03];

                dbg ( DBG_INTR,"intr: %x\n", event );

                switch ( event )
                {
                case CTLA_CHG:
                case CTLB_CHG:
                case CTLC_CHG:
                case CTLD_CHG:
                        if ( port->run && port->dev != NULL )
                                fst_intr_ctlchg ( card, port );
                        break;

                case ABTA_SENT:
                case ABTB_SENT:
                case ABTC_SENT:
                case ABTD_SENT:
                        dbg ( DBG_TX,"Abort complete port %d\n", event & 0x03 );
                        break;

                case TXA_UNDF:
                case TXB_UNDF:
                case TXC_UNDF:
                case TXD_UNDF:
                        /* Difficult to see how we'd get this given that we
                         * always load up the entire packet for DMA.
                         */
                        dbg ( DBG_TX,"Tx underflow port %d\n", event & 0x03 );

                        port->stats.tx_errors++;
                        port->stats.tx_fifo_errors++;
                        break;

                case INIT_CPLT:
                        dbg ( DBG_INIT,"Card init OK intr\n");
                        break;

                case INIT_FAIL:
                        dbg ( DBG_INIT,"Card init FAILED intr\n");
                        card->state = FST_IFAILED;
                        break;

                default:
                        printk_err ("intr: unknown card event code. ignored\n");
                        break;
                }

                /* Bump and wrap the index */
                if ( ++rdidx >= MAX_CIRBUFF )
                        rdidx = 0;
        }
        FST_WRB ( card, interruptEvent.rdindex, rdidx );

        for ( pi = 0, port = card->ports ; pi < card->nports ; pi++, port++ )
        {
                if ( port->dev == NULL || ! port->run )
                        continue;

                /* Check for rx completions */
                while ( ! ( FST_RDB ( card, rxDescrRing[pi][port->rxpos].bits )
                                                                & DMA_OWN ))
                {
                        fst_intr_rx ( card, port );
                }

                /* Check for Tx completions */
                while ( port->txcnt > 0 && ! ( FST_RDB ( card,
                        txDescrRing[pi][port->txipos].bits ) & DMA_OWN ))
                {
                        --port->txcnt;
                        if ( ++port->txipos >= NUM_TX_BUFFER )
                                port->txipos = 0;
                        netif_wake_queue ( port->dev );
                }
        }

        spin_unlock ( &card->card_lock );
}


/*      Check that the shared memory configuration is one that we can handle
 *      and that some basic parameters are correct
 */
static void
check_started_ok ( struct fst_card_info *card )
{
        int i;

        /* Check structure version and end marker */
        if ( FST_RDW ( card, smcVersion ) != SMC_VERSION )
        {
                printk_err ("Bad shared memory version %d expected %d\n",
                                FST_RDW ( card, smcVersion ), SMC_VERSION );
                card->state = FST_BADVERSION;
                return;
        }
        if ( FST_RDL ( card, endOfSmcSignature ) != END_SIG )
        {
                printk_err ("Missing shared memory signature\n");
                card->state = FST_BADVERSION;
                return;
        }
        /* Firmware status flag, 0x00 = initialising, 0x01 = OK, 0xFF = fail */
        if (( i = FST_RDB ( card, taskStatus )) == 0x01 )
        {
                card->state = FST_RUNNING;
        }
        else if ( i == 0xFF )
        {
                printk_err ("Firmware initialisation failed. Card halted\n");
                card->state = FST_HALTED;
                return;
        }
        else if ( i != 0x00 )
        {
                printk_err ("Unknown firmware status 0x%x\n", i );
                card->state = FST_HALTED;
                return;
        }

        /* Finally check the number of ports reported by firmware against the
         * number we assumed at card detection. Should never happen with
         * existing firmware etc so we just report it for the moment.
         */
        if ( FST_RDL ( card, numberOfPorts ) != card->nports )
        {
                printk_warn ("Port count mismatch."
                                " Firmware thinks %d we say %d\n",
                                FST_RDL ( card, numberOfPorts ), card->nports );
        }
}


static int
fst_change_mtu ( struct device *dev, int new_mtu )
{
        if ( new_mtu < 128 || new_mtu > FST_MAX_MTU )
                return -EINVAL;
        dev->mtu = new_mtu;
        return 0;
}


/* Sooner or later you can't avoid a forward declaration */
static int fst_ioctl ( struct device *dev, struct ifreq *ifr, int cmd );

static int
switch_proto ( struct fst_port_info *port, int new_proto )
{
        int err;
        int orig_mtu;
        struct device *dev;

        dev = port->dev;

        /* Turn off sPPP module ? */
        if (( new_proto != FST_HDLC && new_proto != FST_PPP )
                && ( port->proto == FST_HDLC || port->proto == FST_PPP ))
        {
                sppp_close ( port->pppdev.dev );
                sppp_detach ( port->pppdev.dev );

                /* Reset some fields overwritten by sPPP */
                dev->hard_header     = NULL;
                dev->rebuild_header  = NULL;
                dev->tx_queue_len    = FST_TX_QUEUE_LEN;
                dev->type            = ARPHRD_MYTYPE;
                dev->addr_len        = 0;
                dev->hard_header_len = 0;
                dev->do_ioctl        = fst_ioctl;
                dev->change_mtu      = fst_change_mtu;
                dev->flags           = IFF_POINTOPOINT|IFF_NOARP;

                return 0;
        }

        /* Turn on sPPP ? */
        if (( new_proto == FST_HDLC || new_proto == FST_PPP )
                && ( port->proto != FST_HDLC && port->proto != FST_PPP ))
        {
                orig_mtu = dev->mtu;

                /* Attach to sync PPP module */
                port->pppdev.dev = dev;
                sppp_attach ( &port->pppdev );

                if ( orig_mtu < dev->mtu )
                        dev->change_mtu ( dev, orig_mtu );

                /* Claw back the ioctl routine. We promise to be good and call
                 * the sync PPP routines once we've eliminated our functions.
                 */
                dev->do_ioctl = fst_ioctl;

                /* Set the mode */
                if ( new_proto == FST_HDLC )
                {
                        err = sppp_do_ioctl ( port->pppdev.dev, NULL,
                                                                SPPPIOCCISCO );
                }
                else
                {
                        err = sppp_do_ioctl ( port->pppdev.dev, NULL,
                                                                SPPPIOCPPP );
                }

                /* Open the device */
                if ( err == 0 )
                {
                        (void) sppp_open ( port->dev );
                }

                return err;
        }

        /* Switch sPPP mode to that desired */
        err = 0;
        if ( new_proto == FST_HDLC && port->pppdev.dev != NULL )
        {
                err = sppp_do_ioctl ( port->pppdev.dev, NULL, SPPPIOCCISCO );
        }
        else if ( new_proto == FST_PPP && port->pppdev.dev != NULL )
        {
                err = sppp_do_ioctl ( port->pppdev.dev, NULL, SPPPIOCPPP );
        }

        /* Anything else is switching from one raw mode to another which is
         * basically a NOP
         */

        return err;
}


static int
set_conf_from_info ( struct fst_card_info *card, struct fst_port_info *port,
                struct fstioc_info *info )
{
        int err;

        /* Set things according to the user set valid flags */
        err = 0;
        if ( info->valid & FSTVAL_PROTO )
        {
                if ( port->proto != info->proto )
                {
                        err = switch_proto ( port, info->proto );
                        if ( err == 0 )
                                port->proto = info->proto;
                }
        }
        if ( info->valid & FSTVAL_CABLE )
        {
                FST_WRB ( card, portConfig[port->index].lineInterface,
                                        info->lineInterface );
                port->hwif = info->lineInterface;
        }
        if ( info->valid & FSTVAL_SPEED )
        {
                FST_WRL ( card, portConfig[port->index].lineSpeed,
                                        info->lineSpeed );
                FST_WRB ( card, portConfig[port->index].internalClock,
                                        info->internalClock );
        }
        if ( info->valid & FSTVAL_MODE )
        {
                FST_WRW ( card, cardMode, info->cardMode );
        }
#if FST_DEBUG
        if ( info->valid & FSTVAL_DEBUG )
        {
                fst_debug_mask = info->debug;
        }
#endif

        return err;
}

static void
gather_conf_info ( struct fst_card_info *card, struct fst_port_info *port,
                struct fstioc_info *info )
{
        int i;

        memset ( info, 0, sizeof ( struct fstioc_info ));

        i = port->index;
        info->nports = card->nports;
        info->type   = card->type;
        info->state  = card->state;
        info->proto  = port->proto;
        info->index  = i;
#if FST_DEBUG
        info->debug  = fst_debug_mask;
#endif

        /* Only mark information as valid if card is running.
         * Copy the data anyway in case it is useful for diagnostics
         */
        info->valid
                = (( card->state == FST_RUNNING ) ? FSTVAL_ALL : FSTVAL_CARD )
#if FST_DEBUG
                | FSTVAL_DEBUG
#endif
                ;

        info->lineInterface = FST_RDW ( card, portConfig[i].lineInterface );
        info->internalClock = FST_RDB ( card, portConfig[i].internalClock );
        info->lineSpeed     = FST_RDL ( card, portConfig[i].lineSpeed );
        info->v24IpSts      = FST_RDL ( card, v24IpSts[i] );
        info->v24OpSts      = FST_RDL ( card, v24OpSts[i] );
        info->clockStatus   = FST_RDW ( card, clockStatus[i] );
        info->cableStatus   = FST_RDW ( card, cableStatus );
        info->cardMode      = FST_RDW ( card, cardMode );
        info->smcFirmwareVersion  = FST_RDL ( card, smcFirmwareVersion );
}


static int
fst_ioctl ( struct device *dev, struct ifreq *ifr, int cmd )
{
        struct fst_card_info *card;
        struct fst_port_info *port;
        struct fstioc_write wrthdr;
        struct fstioc_info info;
        unsigned long flags;

        dbg ( DBG_IOCTL,"ioctl: %x, %p\n", cmd, ifr->ifr_data );

        port = dev->priv;
        card = port->card;

        if ( !capable ( CAP_NET_ADMIN ))
                return -EPERM;

        switch ( cmd )
        {
        case FSTCPURESET:
                fst_cpureset ( card );
                card->state = FST_RESET;
                return 0;

        case FSTCPURELEASE:
                fst_cpurelease ( card );
                card->state = FST_STARTING;
                return 0;

        case FSTWRITE:                  /* Code write (download) */

                /* First copy in the header with the length and offset of data
                 * to write
                 */
                if ( ifr->ifr_data == NULL )
                {
                        return -EINVAL;
                }
                if ( copy_from_user ( &wrthdr, ifr->ifr_data,
                                        sizeof ( struct fstioc_write )))
                {
                        return -EFAULT;
                }

                /* Sanity check the parameters. We don't support partial writes
                 * when going over the top
                 */
                if ( wrthdr.size > FST_MEMSIZE || wrthdr.offset > FST_MEMSIZE
                                || wrthdr.size + wrthdr.offset > FST_MEMSIZE )
                {
                        return -ENXIO;
                }

                /* Now copy the data to the card.
                 * This will probably break on some architectures.
                 * I'll fix it when I have something to test on.
                 */
                if ( copy_from_user ( card->mem + wrthdr.offset,
                                ifr->ifr_data + sizeof ( struct fstioc_write ),
                                wrthdr.size ))
                {
                        return -EFAULT;
                }

                /* Writes to the memory of a card in the reset state constitute
                 * a download
                 */
                if ( card->state == FST_RESET )
                {
                        card->state = FST_DOWNLOAD;
                }
                return 0;

        case FSTGETCONF:

                /* If card has just been started check the shared memory config
                 * version and marker
                 */
                if ( card->state == FST_STARTING )
                {
                        check_started_ok ( card );

                        /* If everything checked out enable card interrupts */
                        if ( card->state == FST_RUNNING )
                        {
                                spin_lock_irqsave ( &card->card_lock, flags );
                                fst_clear_intr ( card );
                                FST_WRB ( card, interruptHandshake, 0xEE );
                                spin_unlock_irqrestore ( &card->card_lock,
                                                                flags );
                        }
                }

                if ( ifr->ifr_data == NULL )
                {
                        return -EINVAL;
                }

                gather_conf_info ( card, port, &info );

                if ( copy_to_user ( ifr->ifr_data, &info, sizeof ( info )))
                {
                        return -EFAULT;
                }
                return 0;

        case FSTSETCONF:

                if ( copy_from_user ( &info,  ifr->ifr_data, sizeof ( info )))
                {
                        return -EFAULT;
                }

                return set_conf_from_info ( card, port, &info );

        default:
                /* Not one of ours. Pass it through to sPPP package */
                if ( port->proto == FST_HDLC || port->proto == FST_PPP )
                        return sppp_do_ioctl ( dev, ifr, cmd );
                else
                        return -EINVAL;
        }
}


static void
fst_openport ( struct fst_port_info *port )
{
        int signals;

        /* Only init things if card is actually running. This allows open to
         * succeed for downloads etc.
         */
        if ( port->card->state == FST_RUNNING )
        {
                if ( port->run )
                {
                        dbg ( DBG_OPEN,"open: found port already running\n");

                        fst_issue_cmd ( port, STOPPORT );
                        port->run = 0;
                }

                fst_rx_config ( port );
                fst_tx_config ( port );
                fst_op_raise ( port, OPSTS_RTS | OPSTS_DTR );

                fst_issue_cmd ( port, STARTPORT );
                port->run = 1;

                signals = FST_RDL ( port->card, v24DebouncedSts[port->index]);
                if ( signals & (( port->hwif == X21 ) ? IPSTS_INDICATE
                                                      : IPSTS_DCD ))
                        netif_carrier_on ( port->dev );
                else
                        netif_carrier_off ( port->dev );
        }
}

static void
fst_closeport ( struct fst_port_info *port )
{
        if ( port->card->state == FST_RUNNING )
        {
                if ( port->run )
                {
                        port->run = 0;
                        fst_op_lower ( port, OPSTS_RTS | OPSTS_DTR );

                        fst_issue_cmd ( port, STOPPORT );
                }
                else
                {
                        dbg ( DBG_OPEN,"close: port not running\n");
                }
        }
}


static int
fst_open ( struct device *dev )
{
        struct fst_card_info *card;
        struct fst_port_info *port;
        int orig_mtu;
        int err;

        MOD_INC_USE_COUNT;

        port = dev->priv;
        card = port->card;

        switch ( port->proto )
        {
        case FST_HDLC:
        case FST_PPP:
                orig_mtu = dev->mtu;

                /* Attach to sync PPP module */
                port->pppdev.dev = dev;
                sppp_attach ( &port->pppdev );

                if ( orig_mtu < dev->mtu )
                        dev->change_mtu ( dev, orig_mtu );

                /* Claw back the ioctl routine. We promise to be good and call
                 * the sync PPP routines once we've eliminated our functions.
                 */
                dev->do_ioctl = fst_ioctl;

                err = sppp_do_ioctl ( dev, NULL, port->proto == FST_HDLC
                                                ? SPPPIOCCISCO : SPPPIOCPPP );
                if ( err )
                {
                        sppp_detach ( dev );
                        MOD_DEC_USE_COUNT;
                        return err;
                }

                err = sppp_open ( dev );
                if ( err )
                {
                        sppp_detach ( dev );
                        MOD_DEC_USE_COUNT;
                        return err;
                }
                break;

        case FST_MONITOR:
        case FST_RAW:
                break;

        default:
                dbg ( DBG_OPEN,"open: Unknown proto %d\n", port->proto );
                break;
        }

        fst_openport ( port );

        netif_wake_queue ( dev );
        return 0;
}

static int
fst_close ( struct device *dev )
{
        struct fst_port_info *port;

        port = dev->priv;

        dev->start = 0;
        netif_stop_queue ( dev );
        switch ( port->proto )
        {
        case FST_HDLC:
        case FST_PPP:
                sppp_close ( dev );
                sppp_detach ( dev );
                break;

        case FST_MONITOR:
        case FST_RAW:
                break;

        default:
                dbg ( DBG_OPEN,"close: Unknown proto %d\n", port->proto );
                break;
        }

        fst_closeport ( port );

        MOD_DEC_USE_COUNT;
        return 0;
}


#if 0
static void
fst_tx_timeout ( struct device *dev )
{
        struct fst_port_info *port;

        dbg ( DBG_INTR | DBG_TX,"tx_timeout\n");

        port = dev->priv;

        port->stats.tx_errors++;
        port->stats.tx_aborted_errors++;

        if ( port->txcnt > 0 )
                fst_issue_cmd ( port, ABORTTX );

        dev->trans_start = jiffies;
        netif_wake_queue ( dev );
}
#endif


static int
fst_start_xmit ( struct sk_buff *skb, struct device *dev )
{
        struct fst_card_info *card;
        struct fst_port_info *port;
        unsigned char dmabits;
        unsigned long flags;
        int pi;
        int txp;

        port = dev->priv;
        card = port->card;

        /* Drop packet if in monitor (rx only) mode */
        if ( port->proto == FST_MONITOR )
        {
                dev_kfree_skb ( skb );
                return 0;
        }

        /* Drop packet with error if we don't have carrier */
        if ( ! netif_carrier_ok ( dev ))
        {
                dev_kfree_skb ( skb );
                port->stats.tx_errors++;
                port->stats.tx_carrier_errors++;
                return 0;
        }

        /* Drop it if it's too big! MTU failure ? */
        if ( skb->len > LEN_TX_BUFFER )
        {
                dbg ( DBG_TX,"Packet too large %d vs %d\n", skb->len,
                                                LEN_TX_BUFFER );
                dev_kfree_skb ( skb );
                port->stats.tx_errors++;
                return 0;
        }

        /* Check we have a buffer */
        pi = port->index;
        spin_lock_irqsave ( &card->card_lock, flags );
        txp = port->txpos;
        dmabits = FST_RDB ( card, txDescrRing[pi][txp].bits );
        if ( dmabits & DMA_OWN )
        {
                spin_unlock_irqrestore ( &card->card_lock, flags );
                dbg ( DBG_TX,"Out of Tx buffers\n");
                dev_kfree_skb ( skb );
                port->stats.tx_errors++;
                return 0;
        }
        if ( ++port->txpos >= NUM_TX_BUFFER )
                port->txpos = 0;

        if ( ++port->txcnt >= NUM_TX_BUFFER )
                netif_stop_queue ( dev );

        /* Release the card lock before we copy the data as we now have
         * exclusive access to the buffer.
         */
        spin_unlock_irqrestore ( &card->card_lock, flags );

        /* Enqueue the packet */
        memcpy_toio ( card->mem + BUF_OFFSET ( txBuffer[pi][txp][0]),
                                                skb->data, skb->len );
        FST_WRW ( card, txDescrRing[pi][txp].bcnt, cnv_bcnt ( skb->len ));
        FST_WRB ( card, txDescrRing[pi][txp].bits, DMA_OWN | TX_STP | TX_ENP );

        port->stats.tx_packets++;
        port->stats.tx_bytes += skb->len;

        dev_kfree_skb ( skb );

        dev->trans_start = jiffies;
        return 0;
}


static struct net_device_stats *
fst_get_stats ( struct device *dev )
{
        struct fst_port_info *port;

        if (( port = dev->priv ) != NULL )
                return &port->stats;
        else
                return NULL;
}


/*
 *      Card setup having checked hardware resources.
 *      Should be pretty bizarre if we get an error here (kernel memory
 *      exhaustion is one possibility). If we do see a problem we report it
 *      via a printk and leave the corresponding interface and all that follow
 *      disabled.
 */
static char *type_strings[] = {
        "no hardware",                  /* Should never be seen */
        "FarSync T2P",
        "FarSync T4P"
};

static void __init
fst_init_card ( struct fst_card_info *card )
{
        int i;
        int err;
        struct device *dev;
#ifndef MODULE
        static int init_syncppp = 0;

        if ( ! init_syncppp )
        {
                sync_ppp_init();
                init_syncppp = 1;
        }
#endif

        /* We're working on a number of ports based on the card ID. If the
         * firmware detects something different later (should never happen)
         * we'll have to revise it in some way then.
         */
        for ( i = 0 ; i < card->nports ; i++ )
        {
                card->ports[i].if_ptr = &card->ports[i].pppdev;
                card->ports[i].card   = card;
                card->ports[i].index  = i;
                card->ports[i].proto  = FST_HDLC;
                card->ports[i].run    = 0;

                dev = kmalloc ( sizeof ( struct device ), GFP_KERNEL );
                if ( dev == NULL )
                {
                        printk_err ("Cannot allocate net device for port %d\n",
                                        i );
                        /* No point going any further */
                        card->nports = i;
                        break;
                }
                memset ( dev, 0, sizeof ( struct device ));
                card->ports[i].dev = dev;

                dev->name = &card->ports[i].name[0];
                if ( dev_alloc_name ( dev, FST_NDEV_NAME "%d") < 0 )
                {
                        printk_err ("Cannot allocate i/f name for port %d\n",
                                        i );
                        kfree ( dev );
                        card->nports = i;
                        break;
                }

                /* Fill in remainder of the net device info */
                                /* Since this is a PCI setup this is purely
                                 * informational. Give them the buffer addresses
                                 * and basic card I/O.
                                 */
                dev->mem_start   = card->phys_mem
                                 + BUF_OFFSET ( txBuffer[i][0][0]);
                dev->mem_end     = card->phys_mem
                                 + BUF_OFFSET ( txBuffer[i][NUM_TX_BUFFER][0]);
                dev->rmem_start  = card->phys_mem
                                 + BUF_OFFSET ( rxBuffer[i][0][0]);
                dev->rmem_end    = card->phys_mem
                                 + BUF_OFFSET ( rxBuffer[i][NUM_RX_BUFFER][0]);
                dev->base_addr   = card->pci_conf;
                dev->irq         = card->irq;

                dev->get_stats          = fst_get_stats;
                dev->mtu                = FST_DEF_MTU;
                dev->change_mtu         = fst_change_mtu;
                dev->priv               = &card->ports[i];
                dev->tx_queue_len       = FST_TX_QUEUE_LEN;
                dev->type               = ARPHRD_MYTYPE;
                dev->addr_len           = 0;
                dev->open               = fst_open;
                dev->stop               = fst_close;
                dev->hard_start_xmit    = fst_start_xmit;
                dev->do_ioctl           = fst_ioctl;
#if 0
                dev->watchdog_timeo     = FST_TX_TIMEOUT;
                dev->tx_timeout         = fst_tx_timeout;
#endif
                dev->flags              = IFF_POINTOPOINT|IFF_NOARP;

                dev_init_buffers ( dev );

                if (( err = register_netdev ( dev )) < 0 )
                {
                        printk_err ("Cannot register %s (errno %d)\n",
                                        dev->name, -err );
                        kfree ( dev );
                        card->nports = i;
                        break;
                }
        }

        spin_lock_init ( &card->card_lock );

        printk ( KERN_INFO "%s-%s: %s IRQ%d, %d ports\n",
                        card->ports[0].dev->name,
                        card->ports[card->nports-1].dev->name,
                        type_strings[card->type], card->irq, card->nports );
}


/*
 *      Initialise card when detected.
 *      Returns 0 to indicate success, or errno otherwise.
 */
static int __init
fst_add_one ( struct pci_dev *pdev, int card_index )
{
        static int firsttime_done = 0;
        struct fst_card_info *card;
        int err = 0;

        if ( ! firsttime_done )
        {
                printk ( KERN_INFO "FarSync X21 driver " FST_USER_VERSION
                                " (c) 2001 FarSite Communications Ltd.\n");
                firsttime_done = 1;
        }

        /* Allocate driver private data */
        card = kmalloc ( sizeof ( struct fst_card_info ),  GFP_KERNEL);
        if (card == NULL)
        {
                printk_err ("FarSync card found but insufficient memory for"
                                " driver storage\n");
                return -ENOMEM;
        }
        memset ( card, 0, sizeof ( struct fst_card_info ));

        /* Record info we need*/
        card->irq         = pdev->irq;
        card->pci_conf    = pci_resource_start ( pdev, 1 );
        card->phys_mem    = pci_resource_start ( pdev, 2 );
        card->phys_ctlmem = pci_resource_start ( pdev, 3 );

        if ( pdev->device == T2P_PCI_DEVICE_ID )
        {
                card->type   = FST_TYPE_T2P;
                card->nports = 2;
        }
        else
        {
                card->type   = FST_TYPE_T4P;
                card->nports = 4;
        }

        card->state       = FST_UNINIT;

        dbg ( DBG_PCI,"type %d nports %d irq %d\n", card->type,
                        card->nports, card->irq );
        dbg ( DBG_PCI,"conf %04x mem %08x ctlmem %08x\n",
                        card->pci_conf, card->phys_mem, card->phys_ctlmem );

        /* Check we can get access to the I/O area */
        if (( err = check_region ( card->pci_conf, 0x80 )) < 0 )
        {
                printk_err ("Unable to get config I/O @ 0x%04X\n",
                                                card->pci_conf );
                goto error_free_card;
        }
        request_region ( card->pci_conf, 0x80,"PLX config regs");
#if 0
        if ( ! request_mem_region ( card->phys_mem, FST_MEMSIZE,"Shared RAM"))
        {
                printk_err ("Unable to get main memory @ 0x%08X\n",
                                                card->phys_mem );
                err = -ENODEV;
                goto error_release_io;
        }
        if ( ! request_mem_region ( card->phys_ctlmem, 0x10,"Control memory"))
        {
                printk_err ("Unable to get control memory @ 0x%08X\n",
                                                card->phys_ctlmem );
                err = -ENODEV;
                goto error_release_mem;
        }

        /* Try to enable the device */
        if (( err = pci_enable_device ( pdev )) != 0 )
        {
                printk_err ("Failed to enable card. Err %d\n", -err );
                goto error_release_ctlmem;
        }
#endif

        /* Get virtual addresses of memory regions */
        if (( card->mem = ioremap ( card->phys_mem, FST_MEMSIZE )) == NULL )
        {
                printk_err ("Physical memory remap failed\n");
                err = -ENODEV;
                goto error_release_ctlmem;
        }
        if (( card->ctlmem = ioremap ( card->phys_ctlmem, 0x10 )) == NULL )
        {
                printk_err ("Control memory remap failed\n");
                err = -ENODEV;
                goto error_unmap_mem;
        }
        dbg ( DBG_PCI,"kernel mem %p, ctlmem %p\n", card->mem, card->ctlmem);

        /* Reset the card's processor */
        fst_cpureset ( card );
        card->state = FST_RESET;

        /* Register the interrupt handler */
        if ( request_irq ( card->irq, fst_intr, SA_SHIRQ, FST_DEV_NAME, card ))
        {

                printk_err ("Unable to register interrupt %d\n", card->irq );
                err = -ENODEV;
                goto error_unmap_ctlmem;
        }

        /* Record driver data for later use */
        fst_card_info[card_index] = card;

        /* Remainder of card setup */
        fst_init_card ( card );

        return 0;                       /* Success */


                                        /* Failure. Release resources */
error_unmap_ctlmem:
        iounmap ( card->ctlmem );

error_unmap_mem:
        iounmap ( card->mem );

error_release_ctlmem:
#if 0
        release_mem_region ( card->phys_ctlmem, 0x10 );

error_release_mem:
        release_mem_region ( card->phys_mem, FST_MEMSIZE );

error_release_io:
#endif
        release_region ( card->pci_conf, 0x80 );

error_free_card:
        kfree ( card );
        return err;
}


int __init
fst_init(void)
{
        struct pci_dev *pdev;
        int err;

        err = 0;

        /* Minor problem with 2.4 compatability here. pci_register_driver()
         * searches the device list once looking for and initialising cards
         * with any matching ID from a table. Under 2.2 we would normally use
         * pci_find_device() to search the list for each ID in turn. This would
         * give us a different sequence of cards under the different versions
         * which would be confusing for the user. The following code is an
         * unwinding of pci_find_device() to better match the 2.4 sequence.
         */
        for ( pdev = pci_devices ; pdev != NULL ; pdev = pdev->next )
        {
                if ( pdev->vendor == FSC_PCI_VENDOR_ID
                        && ( pdev->device == T2P_PCI_DEVICE_ID
                                || pdev->device == T4P_PCI_DEVICE_ID ))
                {
                        err = fst_add_one ( pdev, fst_ncards );
			fst_ncards++;
                }
        }

        return err;
}


#ifdef MODULE
/*
 *      Cleanup and close down a card
 */
static void
fst_remove_one ( struct fst_card_info *card )
{
        int i;

        for ( i = 0 ; i < card->nports ; i++ )
        {
                unregister_netdev ( card->ports[i].dev );
                kfree ( card->ports[i].dev );
        }

        fst_disable_intr ( card );
        free_irq ( card->irq, card );

        iounmap ( card->ctlmem );
        iounmap ( card->mem );

#if 0
        release_mem_region ( card->phys_ctlmem, 0x10 );
        release_mem_region ( card->phys_mem, FST_MEMSIZE );
#endif
        release_region ( card->pci_conf, 0x80 );

        kfree ( card );
}


void
cleanup_module ( void )
{
        while ( fst_ncards > 0 )
        {
                fst_remove_one ( fst_card_info[fst_ncards-1] );
                fst_card_info[--fst_ncards] = NULL;
        }
}

int
init_module ( void )
{
        return fst_init ();
}

#endif  /* MODULE */

/* End $Id: farsync.c,v 1.10 2002/01/03 16:49:54 kevinc Exp $ */
