/*****************************************************************************

 @(#) $Id: acb56.c,v 0.7.2.4 2000/10/09 02:40:40 brian Exp $

 -----------------------------------------------------------------------------

 Copyright (C) 1997, 1998, 1999, 2000  Brian Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 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.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 675 Mass
 Ave, Cambridge, MA 02139, USA.

 Last Modified $Date: 2000/10/09 02:40:40 $ by $Author: brian $

 -----------------------------------------------------------------------------

 $Log: acb56.c,v $
 Revision 0.7.2.4  2000/10/09 02:40:40  brian
 Pre-release 1.

 Revision 0.7.2.3  2000/09/08 11:12:32  brian
 Added some minor stuff: modules load and unload fine.

 Revision 0.7.2.2  2000/09/08 10:45:09  brian
 Now it compiles.

 Revision 0.7.2.1  2000/09/08 10:03:08  brian
 Compiles.

 Revision 0.7  2000/09/08 08:07:05  brian
 Initial port of ACB56 driver for OpenSS7 stack.

 *****************************************************************************/

static char const ident[] = "$Name: Prerelease1 $($Revision: 0.7.2.4 $) $Date: 2000/10/09 02:40:40 $";

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/modversions.h>
#include <linux/ioport.h>   /* check_ request_ and release_region */
#include <asm/dma.h>
#include <net/sock.h>
#include "ss7if.h"
#include <net/pkt_sched.h>  /* for TC_PRIO_CONTROL */

#define ACB56_DESCRIP   "ACB56: SS7 LEVEL 2 DRIVER FOR THE SEALEVEL ACB56(TM) BOARD."
#define ACB56_COPYRIGHT "Copyright (C) 1997-2000  Brian Bidulock  All Rights Reserved."
#define ACB56_DEVICES   "Supports the Sealevel ACB56(tm) V.35 boards."
#define ACB56_CONTACT   "Brian F. G. Bidulock <bidulock@dallas.net>"
#define ACB56_BANNER    ACB56_DESCRIP "\n" \
                        ACB56_COPYRIGHT "\n" \
                        ACB56_DEVICES "\n" \
                        ACB56_CONTACT "\n" 

MODULE_AUTHOR(ACB56_CONTACT);
MODULE_DESCRIPTION(ACB56_DESCRIP);
MODULE_SUPPORTED_DEVICE(ACB56_DEVICES);

#ifdef ACB56_DEBUG
int acb56_debug = ACB56_DEBUG;
#else
int acb56_debug = 2;
#endif

#define outb writeb
#define inb  readb

struct acb56_dev;
struct acb56_dev* acb56_devices = NULL;

static int debug = -1;
static int io    [] = { -1, -1, -1, -1 };
static int irq   [] = { -1, -1, -1, -1 };
static int dma_rx[] = { -1, -1, -1, -1 };
static int dma_tx[] = { -1, -1, -1, -1 };
static int mode  [] = { -1, -1, -1, -1 };
static int clock [] = { -1, -1, -1, -1 };

MODULE_PARM(debug, "i");    /* debug flag */
MODULE_PARM(io ,   "1-4i"); /* i/o port for the i'th card */
MODULE_PARM(irq,   "1-4i"); /* irq for the i'th card */
MODULE_PARM(dma_rx,"1-4i"); /* dma for the i'th card */
MODULE_PARM(dma_tx,"1-4i"); /* dma for the i'th card */
MODULE_PARM(mode,  "i-4i");    /* interface mode */
MODULE_PARM(clock, "i-4i");    /* clock source */

enum { ACB56_MODE_DTE,  ACB56_MODE_DCE,  ACB56_MODE_LOOP,
       ACB56_MODE_JACK, ACB56_MODE_ECHO, ACB56_MODE_BOTH };
enum { ACB56_CLOCK_EXT, ACB56_CLOCK_INT, ACB56_CLOCK_DPLL };

struct acb56_stats {
    unsigned long transmitted_sus;    /* count of SUs   setn */
    unsigned long sent_fisus;         /* count of FISUs sent */
    unsigned long sent_lssus;         /* count of LSSUs sent */
    unsigned long sent_msus;          /* count of MSUs  sent */
    unsigned long repeated_sus;       /* count of sus repeated for lack of data */
    unsigned long tx_underruns;       /* count of destructive transmitter underruns */
    unsigned long breaks_or_aborts;   /* count of breaks or aborts received */
    unsigned long dcd_transitions;    /* count of transitions on the DCD lead */
    unsigned long cts_transitions;    /* count of transitions on the CTS lead */
    unsigned long received_sus;       /* count of SUs received */
    unsigned long recv_fisus;         /* count of received fisus */
    unsigned long recv_lssus;         /* count of received lssus */
    unsigned long recv_msus;          /* count of received msus */
    unsigned long sus_in_error;       /* count of SUs received in error */
    unsigned long bits_octet_counted; /* count of bits counted in octet mode */
    unsigned long sync_transitions;   /* count of flag detection transitions */
    unsigned long su_length_errors;   /* count of SUs with erroneous lengths */
    unsigned long parity_errors;      /* count of parity errors */
    unsigned long rx_overruns;        /* count of reciver overruns */
    unsigned long crc_errors;         /* count of reciver CRC errors */
    unsigned long frame_errors;       /* count of reciver frame errors */
    unsigned long residue_errors;     /* count of reciver residue errors */
    unsigned long rx_buffer_overflows;/* count of RB overflows */
    unsigned long rx_frame_overflows; /* count of bytes overflowing frame */
    unsigned long compressed_sus;     /* count of sus compressed */
    unsigned long cha_rx_sp_cond;     /* count of this vectored interrupt */
    unsigned long cha_rx_char_avail;  /* count of this vectored interrupt */
    unsigned long cha_ext_status;     /* count of this vectored interrupt */
    unsigned long cha_tx_buf_empty;   /* count of this vectored interrupt */
    unsigned long chb_rx_sp_cond;     /* count of this vectored interrupt */
    unsigned long chb_rx_char_avail;  /* count of this vectored interrupt */
    unsigned long chb_ext_status;     /* count of this vectored interrupt */
    unsigned long chb_tx_buf_empty;   /* count of this vectored interrupt */
    unsigned long interrupts;         /* count of interrupts */
    unsigned long rx_bytes;           /* count of bytes received */
    unsigned long tx_bytes;           /* count of bytes transmitted */
    unsigned long tx_eoms;            /* count of normally completed frames */
    unsigned long tx_aborts;          /* count of aborted frames */
    unsigned long buffer_errors;      /* count of times could get no buffer */
};

struct acb56_dev {
    struct device dev;
    char name[sizeof("acb56%d  ")];
    struct device* next;        /* next acb56 device */
    int index;                  /* which card? */
    int if_mode;                /* interface mode */
    int if_clock;               /* interface clock */
    int dma_tx;                 /* Tx DMA Channel Number */
    int rx_octet_mode;          /* Is octet counting on? */
    int rx_octet_count;         /* bits counted */
    unsigned char regs[16];     /* Register Images */
    unsigned char rr0;          /* Image of Read Register 0 (RR0) */
    struct sk_buff* tx_skb;     /* transmit buffer */
    struct sk_buff* rx_skb;     /* receive buffer */
    struct sk_buff* comp_skb;   /* compressed su buffer */
    struct sk_buff_head input_q;/* transmits queueud */
    struct acb56_stats stats;
    unsigned char *tx_buf;
    unsigned char *tx_max;
    int tx_error;
    unsigned char *rx_buf;
    unsigned char *rx_max;
    int rx_error;
    struct net_device_stats nstats; /* NET4 version of stats */
};

static struct net_device_stats *acb56_get_stats(struct device *dev) {
    struct acb56_dev *p = (struct acb56_dev *)dev;
    p->nstats.rx_packets          = p->stats.received_sus;
    p->nstats.tx_packets          = p->stats.transmitted_sus;
    p->nstats.rx_bytes            = p->stats.rx_bytes;
    p->nstats.tx_bytes            = p->stats.tx_bytes;
    p->nstats.rx_errors           = p->stats.sus_in_error;
    p->nstats.tx_errors           = p->stats.tx_underruns;
    p->nstats.rx_dropped          = 0;
    p->nstats.tx_dropped          = 0;
    p->nstats.rx_length_errors    = p->stats.su_length_errors;
    p->nstats.rx_over_errors      = p->stats.rx_buffer_overflows;
    p->nstats.rx_crc_errors       = p->stats.crc_errors;
    p->nstats.rx_frame_errors     = p->stats.frame_errors;
    p->nstats.rx_fifo_errors      = p->stats.rx_overruns;
    p->nstats.rx_missed_errors    = 0;
    p->nstats.tx_aborted_errors   = p->stats.tx_aborts;
    p->nstats.tx_carrier_errors   = p->stats.dcd_transitions;
    p->nstats.tx_fifo_errors      = p->stats.tx_underruns;
    p->nstats.tx_heartbeat_errors = 0;
    p->nstats.tx_window_errors    = 0;
    p->nstats.rx_compressed       = p->stats.compressed_sus;
    p->nstats.tx_compressed       = p->stats.repeated_sus;
    return &p->nstats;
}

static int acb56_hard_start_xmit(struct sk_buff* skb, struct device *dev)
{
    struct acb56_dev *p = (struct acb56_dev *)dev;
    int ret = skb_queue_len(&p->input_q);
    skb_queue_tail(&p->input_q,skb);
    return ret;
}

static __inline void acb56_tx_setup_next_frame(struct acb56_dev *p) {
    p->stats.tx_eoms++;
    p->stats.tx_bytes+=4;
    if (skb_queue_len(&p->input_q)) {
        kfree_skb(p->tx_skb);
        p->tx_skb = skb_dequeue(&p->input_q);
    } else {
        if (p->tx_skb->len>5 || (p->tx_skb->len>3 && p->tx_skb->data[3]==LSSU_SIB)) {
            skb_trim(p->tx_skb,3);  /* if it's an MSU or SIB */
            p->tx_skb->data[2]=0;   /* make a FISU out of it */
            p->stats.repeated_sus++;
        }
    }
    p->tx_buf = p->tx_skb->data;
    p->tx_max = p->tx_skb->tail;
}

static __inline void acb56_tx_underrun_eom (struct acb56_dev *p) {
    if (p->rr0&0x40) {  /* set */
        p->tx_buf = p->tx_skb->data;
        p->tx_error = 1;
        p->stats.tx_aborts++;
        p->stats.tx_underruns++;
        p->stats.tx_bytes+=3;
        p->rr0&=~0x04;  /* clear indication */
        return;
    } else {            /* cleared */
        return acb56_tx_setup_next_frame(p);
    }
}
static __inline void acb56_sync_hunt(struct acb56_dev *p) {
    int actrl = p->dev.base_addr+1;
    if (p->rx_octet_mode && !p->rr0&0x10) {
        p->rx_octet_mode = 0;  /* we synced */
        outb(0x0f,actrl); outb(p->regs[0x0f]&=~0x02,actrl); /* turn of octet mode */
    }
    else if ((p->rr0&0x10) && (p->if_clock!=2)) {
        p->rx_octet_count = 0;
        p->rx_octet_mode  = 1;
        outb(0x0f,actrl); outb(p->regs[0x0f]|=0x02,actrl); /* turn on octet mode */
    }
    p->stats.sync_transitions++;
}
static __inline void acb56_break_abort(struct acb56_dev *p) {
    p->stats.breaks_or_aborts++;
}
static __inline void acb56_dcd(struct acb56_dev *p) {
    p->stats.dcd_transitions++;
}
static __inline void acb56_cts(struct acb56_dev *p) {
    p->stats.cts_transitions++;
}
static __inline void acb56_zero_count(struct acb56_dev *p) {
    if (!p->rx_octet_count++&0x0000007f || (p->if_mode==0)) {
        p->stats.sus_in_error++;
        ss7if_err(&p->dev);
    }
    p->stats.bits_octet_counted++;
}

static __inline void acb56_frame_error(struct acb56_dev *p) {
    p->rx_error = 0;
    p->rx_buf = p->rx_skb->data;
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->comp_skb->len=0;
    p->stats.received_sus++;
    p->stats.rx_bytes+=2;
    p->stats.sus_in_error++;
    ss7if_err(&p->dev);
}
static __inline void acb56_parity_error(struct acb56_dev *p) {
    p->stats.parity_errors++;
    p->rx_error = 1;
} 
static __inline void acb56_rx_overrun(struct acb56_dev *p) {
    p->stats.rx_overruns++;
    p->rx_error = 1;
} 
static __inline void acb56_crc_error(struct acb56_dev *p) {
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->stats.crc_errors++;
    acb56_frame_error(p);
} 
static __inline void acb56_buffer_error(struct acb56_dev *p) {
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->stats.buffer_errors++;
    acb56_frame_error(p);
} 
static __inline void acb56_length_error(struct acb56_dev *p) {
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->stats.su_length_errors++;
    acb56_frame_error(p);
} 
static __inline void acb56_rx_setup_next_frame(struct acb56_dev *p) {
    int len = p->rx_buf - p->rx_skb->data;
    p->rx_buf = p->rx_skb->data;
    p->stats.received_sus++;
    if (len==3) p->stats.recv_fisus++;
    if (len>=6) p->stats.recv_msus++;
    if (len>=4) p->stats.recv_lssus++;
    p->stats.rx_bytes+=2;
} 
static __inline void acb56_residue_error(struct acb56_dev *p) {
    p->stats.residue_errors++;
    acb56_frame_error(p);
}
static __inline void acb56_end_of_frame(struct acb56_dev *p) {
    if (p->rx_error) return acb56_frame_error(p);
    {
        struct sk_buff *skb2, *skb = p->rx_skb, *skbc = p->comp_skb;
        unsigned int len = (unsigned int)(p->rx_buf - skb->data);
        unsigned char li = skb->data[2]&0x3f;
        if ((len<3)||(li!=(len>63+3?63+3:len)))
            return acb56_length_error(p);
        skb_trim(skb,len);
        if (len<6) {
            if (len==skbc->len && !memcmp(skbc->data,skb->data,len)) {
                p->stats.compressed_sus++;
                acb56_rx_setup_next_frame(p);
                ss7if_rpt(&p->dev);
                return;
            } else {
                memcpy(skbc->data,skb->data,len);
                skbc->len = len;
            }
        } else skbc->len = 0;
        p->rx_octet_mode = 0;
        if ((skb2 = skb_clone(skb,GFP_KERNEL))==NULL)
            return acb56_buffer_error(p);
        acb56_rx_setup_next_frame(p);
        ss7if_rx(skb2);
        return;
    }
} 
static __inline void acb56_frame_overflow_check(struct acb56_dev *p) {
    int actrl = p->dev.base_addr+1;
    /* check for frame overflow */
    if (p->rx_buf>p->rx_max) {
        /* FIGURE 11/Q.703 (sheet 1 of 2) "m+7 octets without flags" */
        if (!p->rx_octet_mode && p->if_clock!=2) { /* can't octet count on DPLL! */
            p->rx_octet_count = 0;
            p->rx_octet_mode  = 1;
            outb(0x0f,actrl); outb(p->regs[0x0f]|=0x02,actrl);  /* octet counting isr */
            outb(0x03,actrl); outb(p->regs[0x03]|=0x10,actrl);  /* force flag hunt */
            acb56_rx_setup_next_frame(p);
            ss7if_los(&p->dev);
        }
    }
} 

static void acb56_isr_cha_tx_buf_empty(struct acb56_dev *p) {
    p->stats.cha_tx_buf_empty++;
} 

static void acb56_isr_cha_ext_status(struct acb56_dev *p) {
    unsigned char rr0;
    int actrl = p->dev.base_addr+1;
    
    rr0 = p->rr0&~0x02 ;
    outb(0x00,actrl); p->rr0=inb(actrl);
    outb(0x00,actrl); outb(0x10,actrl);
    outb(0x00,actrl); outb(0x10,actrl); /* debounce */
    rr0 ^= p->rr0 & 0xfa;
    if (rr0) {
        if (rr0 & 0x40) acb56_tx_underrun_eom (p);
        if (rr0 & 0x10) acb56_sync_hunt       (p);
        if (rr0 & 0x80) acb56_break_abort     (p);
        if (rr0 & 0x08) acb56_dcd             (p);
        if (rr0 & 0x20) acb56_cts             (p);
    } else              acb56_zero_count      (p);
    p->stats.cha_ext_status++;
} 

static void acb56_isr_cha_rx_char_avail(struct acb56_dev *p) {
    register int adata = p->dev.base_addr;
    register int i=0;
    /* collect bytes */
    for (i=0; i<4; i++) *(p->rx_buf++) = inb(adata);
    acb56_frame_overflow_check(p);
    p->stats.rx_bytes+=4;
    p->stats.cha_rx_char_avail++;
} 
static void acb56_isr_cha_rx_sp_cond(struct acb56_dev *p) {
    unsigned char rr1 = 0;
    register int adata = p->dev.base_addr;
    register int actrl = p->dev.base_addr+1;
    register int i=0;
    p->stats.cha_rx_sp_cond++;
    /* collect bytes */
    for (i=0; i<4 && (outb(0x00,actrl)||(inb(actrl)&0x01)); i++) {
        *(p->rx_buf++) = inb(adata);
        p->stats.rx_bytes++;
    }
    acb56_frame_overflow_check(p);
    /* check for special conditions */
    outb(0x07,actrl); if (inb(actrl)&0x40) {
    outb(0x01,actrl); if ((rr1=inb(actrl))&0xf0) {
        outb(0x00,actrl); outb(0x30,actrl); /* reset error */
        if (rr1 & 0x10 ) { return acb56_parity_error  (p); }
        if (rr1 & 0x20 ) { return acb56_rx_overrun    (p); }
        if (rr1 & 0x80 ) {
        if (rr1 & 0x40 ) { return acb56_crc_error     (p); }
        else             {
        if((rr1&0xe)^0x6){ return acb56_residue_error (p); }
        else             { return acb56_end_of_frame  (p); } } }
    } }
}


static void acb56_isr_donothing(struct acb56_dev *p) { (void)p; };

static void acb56_isr_chb_tx_buf_empty(struct acb56_dev* p) { p->stats.chb_tx_buf_empty++; }
static void acb56_isr_chb_ext_status(struct acb56_dev* p)   { p->stats.chb_ext_status++; }
static void acb56_isr_chb_rx_char_avail(struct acb56_dev* p){ p->stats.chb_rx_char_avail++; }
static void acb56_isr_chb_rx_sp_cond(struct acb56_dev* p)   { p->stats.chb_rx_sp_cond++; }

static void acb56_isr(int irq, void *dev_id, struct pt_regs *regs) {
    static void (*vector_map[])(struct acb56_dev *) = {
        acb56_isr_chb_tx_buf_empty,  acb56_isr_chb_ext_status,
        acb56_isr_chb_rx_char_avail, acb56_isr_chb_rx_sp_cond,
        acb56_isr_cha_tx_buf_empty,  acb56_isr_cha_ext_status,
        acb56_isr_cha_rx_char_avail, acb56_isr_cha_rx_sp_cond
    };
    unsigned char rr3;
    register int i;
    register int actrl = ((struct device *)dev_id)->base_addr+1;
    for (i=0,outb(0x03,actrl),rr3=inb(actrl);i<4&&rr3;i++,outb(0x03,actrl),rr3=inb(actrl))
    {
        outb(0x02,actrl+2);
        (*vector_map[inb(actrl+2)>>1])(dev_id);
        /* reset highest interrupt under service */
        outb(0x00,actrl); outb(0x38,actrl);
    }
    ((struct acb56_dev *)dev_id)->stats.interrupts++;
};

static int acb56_close(struct device *dev) {
    struct acb56_dev *p = (struct acb56_dev *)dev;
    int actrl = dev->base_addr+1;
    outb(0x09,actrl); outb(0xc0,actrl);                   /* force hw reset */
    outb(0x09,actrl); outb(p->regs[0x09]&=~0x08,actrl); /* stop interrupts */
    free_irq(p->dev.irq,NULL);
    if (p->dma_tx) {
        outb(0x0e,actrl); outb(p->regs[0x0e]&=~0x04,actrl); /* disable dma */
        disable_dma(p->dma_tx); free_dma(p->dma_tx); }
    if (p->dev.dma) {
        outb(0x01,actrl); outb(p->regs[0x01]&=~0xc0,actrl); /* disable dma */
        disable_dma(p->dev.dma); free_dma(p->dev.dma); }
    outb(0x09,actrl); outb(0xc0,actrl);                   /* force hw reset */

    skb_queue_purge(&p->input_q);
    return(0);
}

static const unsigned char preamble[] = {
    0x09, 0xC0, 0x0F, 0x01, 0x07, 0x6B, 0x0F, 0x00, 0x00, 0x00, 0x04, 0x20,
    0x01, 0x00, 0x02, 0x00, 0x03, 0xCA, 0x05, 0x63, 0x06, 0x00, 0x07, 0x7e, 
    0x09, 0x00, 0x0A, 0x00, 0x0B, 0x16, 0x0C, 0x40, 0x0D, 0x00, 0x0E, 0x02,
    0x0E, 0x02, 0x0E, 0x02, 0x0E, 0x03, 0x03, 0xCB, 0x05, 0x6B, 0x00, 0x80, 
    0x00, 0x30, 0x01, 0x00, 0x0F, 0x00, 0x00, 0x10, 0x00, 0x10, 0x01, 0x00, 
    0x09, 0x00 };
static const unsigned char preset[] = {
    0x09, 0xc0, 0x00, 0x00, 0x04, 0x20, 0x03, 0xca, 0x05, 0xe3, 0x07, 0x7e,
    0x06, 0x00, 0x0F, 0x01, 0x07, 0x6b, 0x0F, 0x00, 0x01, 0x00, 0x02, 0x00,
    0x09, 0x00, 0x0A, 0x80 };
static const unsigned char mode_clock[6][3] = {
    { 0x08, 0x05, 0x7f }, { 0x08, 0x56, 0x7f }, { 0x50, 0x50, 0x78 },
    { 0x16, 0x50, 0x1f }, { 0x50, 0x50, 0x78 }, { 0x50, 0x50, 0x78 } };

static int acb56_open(struct device *dev)
{
    int err = 0, i, actrl;
    struct acb56_dev *p = (struct acb56_dev*)dev;
    if (dev->dma  && request_dma(dev->dma ,"acb56")) dev->dma  = 0;
    if (p->dma_tx && request_dma(p->dma_tx,"acb56")) p->dma_tx = 0;
    if ((err=request_irq(dev->irq,acb56_isr,SA_SHIRQ,"acb56",p))) goto irqerror;
    actrl = dev->base_addr+1;
    for (i=0;i<16;i++) p->regs[i] = 0; /* register images */
    /* setup chip */
    for (i=0;i<sizeof(preset);) {
        outb(preset[i],actrl); i++; outb(p->regs[i>>1]=preset[i],actrl); i++; }
    /* setup interface and clock modes */
    outb(0x0b,actrl); outb(p->regs[0x0b]=mode_clock[p->if_mode][p->if_clock],actrl);
    /* setup baud rate generator */
    if (p->if_mode==ACB56_MODE_DTE) {
        outb(0x0c,actrl);outb(p->regs[0xc]=0xca,actrl);
        outb(0x0d,actrl);outb(p->regs[0xd]=0x1c,actrl);
    } else if (p->if_mode==ACB56_MODE_LOOP) {
        outb(0x0c,actrl);outb(p->regs[0xc]=0x00,actrl);
        outb(0x0d,actrl);outb(p->regs[0xd]=0x00,actrl);
    } else {
        outb(0x0c,actrl);outb(p->regs[0xc]=0x40,actrl);
        outb(0x0d,actrl);outb(p->regs[0xd]=0x00,actrl);
    }
    /* special DPLL modes */
    if (p->if_clock==ACB56_CLOCK_DPLL) {
        outb(0x0e,actrl);outb(0x60,actrl);
        outb(0x0e,actrl);outb(0xe0,actrl);
        if (p->if_mode==ACB56_MODE_DTE) { outb(0x0e,actrl);outb(0xa0,actrl); }
        else                            { outb(0x0e,actrl);outb(0x80,actrl); }
        outb(0x0e,actrl);outb(0x20,actrl); }
    outb(0x0e,actrl);outb(p->regs[0x0e]=0x02,actrl);
    /* setup loopback and echo modes */
    switch(p->if_mode) {
        case ACB56_MODE_LOOP: outb(0x0e,actrl); outb(p->regs[0x0e]|=0x10,actrl); break;
        case ACB56_MODE_ECHO: outb(0x0e,actrl); outb(p->regs[0x0e]|=0x08,actrl); break;
        case ACB56_MODE_BOTH: outb(0x0e,actrl); outb(p->regs[0x0e]|=0x18,actrl); break;
    }
    /* set up dma registers */
    if (dev->dma||p->dma_tx) {
        outb(0x0e,actrl); outb(p->regs[0x0e]|=0x04,actrl);
        if (dev->dma&&p->dma_tx) outb(0x01,actrl); outb(p->regs[0x01]|=0xf9,actrl);
        if (p->dma_tx)           outb(0x01,actrl); outb(p->regs[0x01]|=0xc1,actrl);
        if (p->dma_tx)           outb(0x01,actrl); outb(p->regs[0x01]|=0xfb,actrl);
        outb(0x80,actrl+3);
    } else {
        outb(0x0e,actrl); outb(p->regs[0x0e]&=~0x04,actrl);
        outb(0x01,actrl); outb(p->regs[0x01]|=0x13,actrl);
        outb(0x00,actrl+3);
    }
    /* disable status fifo */
    outb(0x0f,actrl); outb(0xfc,actrl);
    outb(0x09,actrl); outb(0x02,actrl);
    /* reset and enable transmitters and receivers */
    outb(0x0E,actrl); outb(p->regs[0x0e]|=0x01,actrl);
    outb(0x03,actrl); outb(p->regs[0x0e]|=0x01,actrl);
    outb(0x00,actrl); outb(0x30,actrl);
    outb(0x05,actrl); outb(p->regs[0x0e]|=0x08,actrl);
    outb(0x00,actrl); outb(0x80,actrl);
    outb(0x00,actrl); outb(0xC0,actrl);
    outb(0x00,actrl); outb(0x10,actrl);
    outb(0x00,actrl); outb(0x10,actrl);

    if ((p->tx_skb = alloc_skb(SS7_MTU+4,GFP_ATOMIC)) == NULL) return -ENOBUFS;
    p->tx_skb->protocol = AF_SS7;
    p->tx_skb->pkt_type = ETH_P_SS7;
    p->tx_skb->priority = TC_PRIO_BESTEFFORT;
    p->tx_skb->h.raw = p->tx_skb->nh.raw = p->tx_skb->mac.raw = p->tx_skb->data;
    p->rx_skb = skb_copy(p->tx_skb,GFP_ATOMIC);
    p->comp_skb = skb_copy(p->tx_skb,GFP_ATOMIC);
    p->comp_skb->len = 0;
    p->tx_skb->len = 4;
    p->tx_skb->data[0] = 0x80;  /* Initial SIOS */
    p->tx_skb->data[1] = 0x80;
    p->tx_skb->data[2] = 0x01;
    p->tx_skb->data[3] = 0x00;

    /* enable master interrupt bit */
    outb(0x09,actrl); outb(p->regs[0x09]|=0x08,actrl);
    /* we're running! phew! */
    return(0);
irqerror:
    if (dev->dma ) free_dma(dev->dma );
    if (p->dma_tx) free_dma(p->dma_tx);
    return err;
}

static int acb56_init(struct device *dev)
{
    dev->type = ARPHRD_SS7;             /* identifies us to ss7 link module */
    dev->tx_queue_len = TX_Q_MAX;       /* sets up buffers */
    dev->open = &acb56_open;            /* service routines for ss7 link module */
    dev->hard_start_xmit = &acb56_hard_start_xmit; /* service routines for ss7 link module */
    dev->stop = &acb56_close;           /* service routines for ss7 link module */
    dev->get_stats = &acb56_get_stats;  /* service routines for ss7 link module */
    return (0);
#if 0
    dev->do_ioctl = &acb56_ioctl;       /* io controls */
#endif
}

static void acb56_destructor(struct device *dev) {
    release_region(dev->base_addr,8);
    kfree(dev);
}

static unsigned char irqprobe[] = {
    0x01, 0x19, 0x0F, 0xFA, 0x00, 0x10, 0x00, 0x10, 0x09, 0x08, 0x0E, 0x03
};

static void dummy_isr(int irq, void *dev_id, struct pt_regs *regs) {
    volatile int *p; (void)irq; (void)dev_id; (void)regs; p = NULL; p++;
}

static int board = 0;
static int ports[] = { 0x238, 0x280, 0x2A0, 0x300, 0x328, 0 };

int acb56_probe(struct device* dev)
{
    int *test, iobase, _irq, actrl, _dma_rx, _dma_tx, i, err;
    unsigned long time, cookie;
    struct acb56_dev *p = NULL;
    
    if ((iobase=io[board])!=-1 && !(err=check_region(iobase,8))) goto probe_irq;
    if (iobase!=-1) return err;
    /* probe for iobase */
    for (test = &ports[0]; *test; test++) {
        iobase = *test; actrl = iobase+1;
        if (check_region(iobase,8)) continue;   /* device conflict */
        outb(0x02,actrl); outb(0x55,actrl); /* write to unallocated 8530 bit in WR2 */
        outb(0x02,actrl); if (inb(actrl)!=0x55) continue; /* probably an 8530 */
        outb(0x09,actrl); outb(0xc0,actrl); /* force hardware reset */
        outb(0x0f,actrl); outb(0x01,actrl); /* Access W7P register */
        outb(0x0f,actrl); if (!inb(actrl)&0x01) continue; /* probably an 8530 */
        outb(0x0f,actrl); outb(0x00,actrl); /* Remove accss to W7P register */
        outb(0x0f,actrl); if (inb(actrl)&0x01) continue; /* probably an 8530 */
        goto probe_irq;
    }
    return -ENODEV;
probe_irq:
    if ((_irq=irq[board])!=-1 &&
            !(err = request_irq(_irq,dummy_isr,SA_SHIRQ,"acb56_dummy",NULL)))
        goto probe_dma;
    if (_irq!=-1) return err;
    /* probe for irq */
    actrl = iobase+1;
    for (i=0;i<sizeof(preamble);) { /* setup chip */
        outb(preamble[i],actrl); i++; outb(preamble[i],actrl); i++; }
    cookie = probe_irq_on();
    for (i=0;i<sizeof(irqprobe);) { /* setup for guaranteed interrupt */
        outb(irqprobe[i],actrl); i++; outb(irqprobe[i],actrl); i++; }
    /* fill tx fifo to get an interrupt */
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    outb(0x55,iobase); outb(0x55,iobase); outb(0x55,iobase);
    for (time=jiffies;jiffies-time<100;i++);
    if (!(_irq = probe_irq_off(cookie))) return -ENODEV;   /* no irq! */
    outb(0x03,actrl); if (!inb(actrl)) return -ENODEV;    /* it wasn't us */
    outb(0x09,actrl); outb(0x00,actrl);
    outb(0x09,actrl); outb(0xc0,actrl); /* force hardware reset */
    if ((err=request_irq(_irq,dummy_isr,SA_SHIRQ,"acb56_dummy",NULL))) return err;
probe_dma:
    free_irq(_irq,NULL);
    /* check for dma */
    if ((_dma_rx=dma_rx[board])&&_dma_rx!=-1&&!(request_dma(_dma_rx,"acb56_probe")))
        free_dma(_dma_rx); else _dma_rx=0;
    if ((_dma_tx=dma_tx[board])&&_dma_tx!=-1&&!(request_dma(_dma_tx,"acb56_probe")))
        free_dma(_dma_tx); else _dma_tx=0;

    if (!dev) {
        if (!(dev = kmalloc(sizeof(struct acb56_dev),GFP_KERNEL))) return -ENOBUFS;
        dev->name = (char *)(dev+1);
        if ((err=dev_alloc_name(dev,"escc%d"))) { kfree(dev); return err; }
        if ((err=register_netdev(dev))) { kfree(dev); return err; }
    }
    request_region(iobase,8,"acb56");
    /* setup major operating modes */
    p = (struct acb56_dev *)dev;
    if ((p->if_mode =mode [board])==-1) p->if_mode  = ACB56_MODE_LOOP;
    if ((p->if_clock=clock[board])==-1) p->if_clock = ACB56_CLOCK_EXT;
    {
        static const char *if_modes [] = { "DTE", "DCE", "LOOP", "JACK", "ECHO", "LOOP&/ECHO" };
        static const char *if_clocks[] = { "EXT", "INT", "DPLL" };
        printk("%s: acb56 at %04xH, irq %d, dma %d rx %d tx, %s clk %s\n",
            dev->name, iobase, _irq, _dma_rx, _dma_tx,
            if_modes[p->if_mode], if_clocks[p->if_clock]);
    }
    p->index  = board;
    p->dma_tx = _dma_tx;

    dev->priv = p;
    dev->base_addr = iobase;
    dev->irq = _irq;
    dev->dma = _dma_rx;
    dev->init = &acb56_init;
    dev->destructor = &acb56_destructor;

    if ((err=register_netdev(dev))) { kfree(dev); return err; }

    acb56_devices = p;
    return(0);
}

int
init_module(void)
{
#ifdef CONGIG_KMOD
    request_module("ss7link");
#endif
    printk(KERN_INFO ACB56_BANNER);
    board = 0;
    if (debug >= 0) acb56_debug = debug;
    while (acb56_probe(0) == 0) { board++; }
    if (!board)
        printk(KERN_INFO "No acb56 devices found.\n");
    return board ? 0 : -ENODEV;
}

void
cleanup_module(void)
{
    struct acb56_dev *card_next = acb56_devices;
    while ((acb56_devices = card_next)) {
        card_next = (struct acb56_dev *)acb56_devices->next;
        unregister_netdevice((struct device *)acb56_devices);
    }
}



