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

 @(#) $Id: sdt_acb56.c,v 0.7.2.4 2001/02/03 05:48:48 brian Test $

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

 Copyright (C) 1997-2001  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: 2001/02/03 05:48:48 $ by $Author: brian $

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

static char const ident[] = "$Id: sdt_acb56.c,v 0.7.2.4 2001/02/03 05:48:48 brian Test $";

/*
 *  This is an implementation of the Signalling Data Link for the SeaLevel
 *  ACB56[tm] V.35 ISA card.  It provides the driver routines necessary for
 *  interface to the SDT driver kernel module.  Because the ACB56 is a low
 *  speed link, it performs octet counting and is not suitable for an SDL
 *  implementation (for performance reasons).
 */

#define ACB56_DESCRIP   "ACB56: SS7/SDT (Signalling Data Terminal) STREAMS DRIVER."
#define ACB56_COPYRIGHT "Copyright (c) 1997-2001 Brian Bidulock.  All Rights Reserved."
#define ACB56_DEVICES   "Supports the SeaLevel ACB56(tm) V.35 boards."
#define ACB56_CONTACT   "Brian Bidulock <bidulock@openss7.org>"
#define ACB56_BANNER    ACB56_DESCRIP   "\n" \
                        ACB56_COPYRIGHT "\n" \
                        ACB56_DEVICES   "\n" \
                        ACB56_CONTACT   "\n"

#ifdef MODULE
MODULE_AUTHOR(ACB56_CONTACT);
MODULE_DESCRIPTION(ACB56_DESCRIP);
MODULE_SUPPORTED_DEVICES(ACB56_DEVICES);
#define MODULE_STATIC static
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#define MODULE_STATIC
#endif

#ifndef ACB56_DEBUG
int acb56_debug = ACB56_DEBUG
#else
int acb56_debug = 2
#endif

#define ACB56_MOD_ID      0x1111
#define ACB56_MOD_NAME    "acb56"
#define ACB56_C_MAJOR     0   /* FIXME: pick something    */
#define ACB56_N_MINOR     256 /* as many as possible      */

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 };

static const int mode_map[] = {
    -1,                 /* SDL_MODE_NONE    */
    ACB56_MODE_DTE,     /* SDL_MODE_DSU     */
    ACB56_MODE_DCE,     /* SDL_MODE_CSU     */
    ACB56_MODE_DTE,     /* SDL_MODE_DTE     */
    ACB56_MODE_DCE,     /* SDL_MODE_DCE     */
    -1,                 /* SDL_MODE_CLIENT  */
    -1,                 /* SDL_MODE_SERVER  */
    -1,                 /* SDL_MODE_PEER    */
    ACB56_MODE_ECHO,    /* SDL_MODE_ECHO    */
    ACB56_MODE_LOOP,    /* SDL_MODE_LOC_LB  */
    ACB56_MODE_BOTH,    /* SDL_MODE_LB_ECHO */
    ACB56_MODE_BOTH     /* SDL_MODE_TEST    */
};

enum { ACB56_CLOCK_EXT, ACB56_CLOCK_INT, ACB56_CLOCK_DPLL };

static const int clock_map[] = {
    -1,                 /* SDL_CLOCK_NONE   */
    ACB56_CLOCK_INT,    /* SDL_CLOCK_INT    */
    ACB56_CLOCK_EXT,    /* SDL_CLOCK_EXT    */
    -1,                 /* SDL_CLOCK_LOOP   */
    -1,                 /* SDL_CLOCK_MASTER */
    -1,                 /* SDL_CLOCK_SLAVE  */
    ACB56_CLOCK_DPLL,   /* SDL_CLOCK_DPLL   */
    -1,                 /* SDL_CLOCK_ABR    */
    -1                  /* SDL_CLOCK_SHAPER */
};

static int acb56_major = ACB56_C_MAJOR;

typedef struct {
    mblk_t  *head;
    mblk_t  **tail;
    int     count;
} bufq_t;

static __inline void bufq_init(bufq_t *q)
{
    q->head = NULL;
    q->tail = &q->head;
    q->count = 0;
}

static __inline int bufq_queue_len(bufq_t *q)
{
    return q->count;
}

static __inline void bufq_queue(bufq_t *q, mblk_t *mp)
{
    mp->b_next = NULL;
    *(q->tail) = mp;
    q->tail = &mp->b_cont;
    q->count++;
}

static __inline mblk_t *bufq_dequeue(bufq_t *q)
{
    mblk_t *mp = q->head;
    q->head = q->head->b_next;
    q->count--;
}

static __inline void bufq_resupply(bufq_t *sq, bufq_t *rq, mblk_t *mp)
{
    if ( bufq_queue_len(sq) < ACB56_SUPPLYQ_MAX )
        bufq_queue(sq, mp);
    else
        bufq_queue(rq, mp);

}

static __inline void bufq_purge(bufq_t *q)
{
    while ( q->count ) freemsg(bufq_dequeue(q));
}

static __inline void mblen(mblk_t *mp)
{
    return mp->b_wptr - mp->b_rptr;
}

typedef struct acb56_dev {
    sdl_t           *sdl;           /* signalling data link      */
    sdl_device_t    *dev;           /* device pointer            */
    spinlock_t      lock;           /* spin lock for driver      */
    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  */
    mblk_t          *tx_msg;        /* transmit buffer           */
    mblk_t          *rx_msg;        /* receive  buffer           */
    mblk_t          *cp_msg;        /* compressed su buffer      */
    bufq_t          tinputq;        /* transmit queue            */
    bufq_t          supplyq;        /* buffer supply queue       */
    bufq_t          returnq;        /* buffer return queue       */
    acb56_stats_t   stats;          /* device private stats      */
    unsigned char   *tx_buf;        /* current tx buffer pointer */
    unsigned char   *tx_max;        /* end of  tx buffer pointer */
    int             tx_error;       /* transmission error        */
    unsigned char   *rx_buf;        /* current rx buffer pointer */
    unsigned char   *rx_max;        /* end of  rx buffer pointer */
    int             rx_error;       /* reception error           */
} acb56_dev_t;

static int acb56_xmit(sdl_t *sdl, mblk_t *mp)
{
    acb56_dev_t *p = (acb56_dev_t *)sdl->device.priv;
    int ret;

    spin_lock(&prv->lock);
    {
        ret = bufq_queue_len(&p->tinputq):;
        bufq_queue(&p->tinputq, mp);
    }
    spin_unlock(&prv->lock);

    return (ret);
}

static __inline void acb56_tx_setup_next_frame(struct acb56_dev *p) {
    p->stats.tx_eoms++;
    p->sdl->stats.tx_bytes+=4;
    if ( bufq_queue_len(&p->tinputq) ) {
        bufq_resupply(&p->supplyq, &p->returnq, p->tx_msg);
        p->tx_msg = bufq_dequeue(&p->tinputq);
    } else {
        int len = msgdlen(p->tx_msg);
        if ( len>5 || ( len >3 && p->tx_msg->b_rptr[3] == LSSU_SIB ) )
        {   /* its an MSU or SIB, make FISU out of it */
            p->tx_msg->b_wptr = p->tx_msg->b_rptr+3;
            *(p->tx_msg->b_rptr[2]) = 0;
            p->stats.repeated_sus++;
        }
    }
    p->tx_buf = p->tx_msg->b_rptr;
    p->tx_max = p->tx_msg->b_wptr;
}

static __inline void acb56_tx_underrun_eom(struct acb56_dev *p)
    if ( p->rr0&0x40 ) { /* set */
        p->tx_buf = p->tx_msg->b_rptr;
        p->tx_error = 1;
        p->sdl->stats.tx_aborts++;
        p->sdl->stats.tx_underruns++;
        p->sdl->stats.tx_sus_in_error++;
        p->sdl->stats.tx_bytes+=3;
        p->rr0&=~0x04;  /* clear indication */
        return;
    } else {
        return acb56_tx_setup_next_frame(p);
    }
}

static __inline void acb56_sync_hunt(struct acb56_dev *p)
{
    int actrl = p->dev.iobase+1;
    (void)sdl;
    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->sdl->stats.rx_aborts++;
}
static __inline void acb56_dcd(struct acb56_dev *p) {
    if ( (++p->stats.dcd_transitions) & 0x1 )
        p->sdl->stats.lead_dcd_lost;
}
static __inline void acb56_cts(struct acb56_dev *p) {
    if ( (++p->stats.cts_transitions) & 0x1 )
        p->sdl->stats.lead_cts_lost;
}
static __inline void acb56_zero_count(struct acb56_dev *p) {
    if (!p->rx_octet_count++&0x0000007f ||
            (mode_map[p->dev.ifmode]==ACB56_MODE_DTE)) {
        p->sdl->stats.rx_sus_in_error++;
        p->sdl->dcalls->daedr_error_frame(p->sdl);
    }
    p->sdl->stats.rx_bits_octet_counted++;
}

static __inline void acb56_frame_error(struct acb56_dev *p) {
    p->rx_error = 0;
    p->rx_buf = p->rx_msg->b_rptr;
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->cp_msg->b_wptr = p->cp_msg->b_rptr;
    p->sdl->stats.rx_sus++;
    p->sdl->stats.rx_bytes+=2;
    p->sdl->stats.rx_sus_in_error++;
    p->sdl->dcalls->daedr_error_frame(p->sdl);
}
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->sdl->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->sdl->stats.rx_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->sdl->stats.rx_buffer_errors++;
    acb56_frame_error(p);
} 
static __inline void acb56_short_error(struct acb56_dev *p) {
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->sdl->stats.rx_frame_short++;
    acb56_frame_error(p);
} 
static __inline void acb56_long_error(struct acb56_dev *p) {
    if (p->rx_octet_mode) return;  /* only a good frame counts in octet mode */
    p->sdl->stats.rx_frame_long++;
    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->sdl->stats.rx_length_error++;
    acb56_frame_error(p);
} 
static __inline void acb56_rx_setup_next_frame(struct acb56_dev *p) {
    int len = p->rx_buf - p->rx_msg->b_rptr;
    p->rx_buf = p->rx_msg->b_rptr;
    p->sdl->stats.rx_sus++;
    if (len==3) p->stats.recv_fisus++;
    if (len>=6) p->stats.recv_msus++;
    if (len>=4) p->stats.recv_lssus++;
    p->sdl->stats.rx_bytes+=2;
} 
static __inline void acb56_residue_error(struct acb56_dev *p) {
    p->sdl->stats.rx_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);
    {
        mblk_t *m2, *mp = p->rx_msg, *mc = p->cp_msg;
        unsigned int len = (unsigned int)(p->rx_buf - mp->b_rptr);
        unsigned char li = mp->b_rptr[2]&0x3f;
        if (len<3)
            return acb56_short_error(p);
        if (len>p->config.m+4)
            return acb56_long_error(p);
        if (li!=(len>63+3?63+3:len))
            return acb56_length_error(p);
        mp->b_wptr = mp->b_rptr + len;
        if (len<6) {
            int clen = mc->b_wptr - mc->b_rptr;
            if (len==clen && !memcmp(mc->b_rptr,mp->b_rptr,len)) {
                p->stats.compressed_sus++;
                acb56_rx_setup_next_frame(p);
                p->sdl->dcalls->daedr_comp_frame(p->sdl);
                return;
            } else {
                memcpy(mc->b_rptr,mp->b_rptr,len);
                mc->b_wptr = mc->b_rptr + len;
            }
        } else mc->b_wptr = mc->b_rptr;
        p->rx_octet_mode = 0;
        acb56_rx_setup_next_frame(p);
        if ( !bufq_queue_len(&p->supplyq) )
            return acb56_buffer_error(p);
        p->rx_msg = bufq_dequeue(&p->supplyq);
        p->rx_buf = p->rx_msg->b_wptr = p->rx_msg->b_rptr;
        p->rx_max = p->rx_msg->b_datap->db_limit;
        p->sdl->dcalls->daedr_recvd_frame(p->sdl, mp);
        return;
    }
} 
static __inline void acb56_frame_overflow_check(struct acb56_dev *p) {
    int actrl = p->dev.iobase+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->dev.ifclock!=SDL_CLOCK_DPLL) { /* 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);
            p->sdl->dcalls->daedr_loss_of_sync(p->sdl);
        }
    }
} 

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;
    register int actrl = p->dev.iobase+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 actrl = p->dev.iobase+1;
    register int i=0;
    /* collect bytes */
    for (i=0; i<4; i++) *(p->rx_buf++) = inb(adata);
    acb56_frame_overflow_check(p);
    p->sdl->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.iobase;
    register int actrl = p->dev.iobase+1;
    register int i=0;
    p->stats.cha_rx_sp_cond++;
    /* collect bytes */
    outb(0x00,actrl);
    for (i=0; i<4 && (inb(actrl)&0x01); i++) {
        *(p->rx_buf++) = inb(adata);
        p->sdl->stats.rx_bytes++;
        outb(0x00,actrl);
    }
    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 acb56_dev *)dev_id)->dev.iobase+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 sdl *sdl) {
    struct sdl_device *dev = (struct sdl_device *)&sdl->device;
    struct acb56_dev *p = (struct acb56_dev *)dev->priv;
    int actrl = dev->iobase+1;
    printk("%s: closing device\n",dev->name);
    outb(0x09,actrl); outb(0xc0,actrl);                   /* force hw reset */
    outb(0x09,actrl); outb(p->regs[0x09]&=~0x08,actrl); /* stop interrupts */
    free_irq(dev->irq,NULL);
    if (dev->dma_tx) {
        outb(0x0e,actrl); outb(p->regs[0x0e]&=~0x04,actrl); /* disable dma */
        disable_dma(dev->dma_tx); free_dma(dev->dma_tx); }
    if (dev->dma_rx) {
        outb(0x01,actrl); outb(p->regs[0x01]&=~0xc0,actrl); /* disable dma */
        disable_dma(dev->dma_rx); free_dma(dev->dma_rx); }
    outb(0x09,actrl); outb(0xc0,actrl);                   /* force hw reset */

    bufq_purge(&p->supplyq);
    bufq_purge(&p->returnq);
    bufq_purge(&p->tinputq);
    MOD_DEC_USE_COUNT;
    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 sdl *sdl)
{
    int err = 0, i, actrl;
    struct sdl_device *dev = (struct sdl_device *)&sdl->device;
    struct acb56_dev *p = (struct acb56_dev *)dev->priv;
    printk("%s: opening device\n",dev->name);
    if (dev->dma_rx && request_dma(dev->dma_rx,"acb56")) dev->dma_rx = 0;
    if (dev->dma_tx && request_dma(dev->dma_tx,"acb56")) dev->dma_tx = 0;
    if ((err=request_irq(dev->irq,acb56_isr,SA_SHIRQ,"acb56",p))) goto irqerror;
    actrl = dev->iobase+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 (mode_map[dev->ifmode]==ACB56_MODE_DTE) {
        outb(0x0c,actrl);outb(p->regs[0xc]=0xca,actrl);
        outb(0x0d,actrl);outb(p->regs[0xd]=0x1c,actrl);
    } else if (mode_map[dev->ifmode]==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 (dev->ifclock==SDL_CLOCK_DPLL) {
        outb(0x0e,actrl);outb(0x60,actrl);
        outb(0x0e,actrl);outb(0xe0,actrl);
        if (mode_map[dev->ifmode]==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(mode_map[dev->ifmode]) {
        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_rx||dev->dma_tx) {
        outb(0x0e,actrl); outb(p->regs[0x0e]|=0x04,actrl);
        if (dev->dma_rx&&dev->dma_tx) outb(0x01,actrl); outb(p->regs[0x01]|=0xf9,actrl);
        if (dev->dma_tx)              outb(0x01,actrl); outb(p->regs[0x01]|=0xc1,actrl);
        if (dev->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);

    bufq_init(&p->supplyq);
    bufq_init(&p->returnq);
    bufq_init(&p->tinputq);

    for (i=0; i<ACB56_SUPPLYQ_MAX; i++) {
        mblk_t *mp;
        if ( (mp = allocb(SS7_MTU+4, BPRI_LO)) )
            bufq_queue(&p->supplyq, mp);
        else {
            bufq_purge(&p->supplyq);
            return -ENOBUFS;
        }
    }
    p->tx_msg = bufq_dequeue(&p->supplyq);
    p->rx_msg = bufq_dequeue(&p->supplyq);
    p->cp_msg = bufq_dequeue(&p->supplyq);

    *(p->tx_msg->b_wptr)++ = 0x80;  /* Initial SIOS */
    *(p->tx_msg->b_wptr)++ = 0x80;  /* Initial SIOS */
    *(p->tx_msg->b_wptr)++ = 0x01;  /* Initial SIOS */
    *(p->tx_msg->b_wptr)++ = 0x00;  /* Initial SIOS */

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

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

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;
        memset(dev, 0x00, sizeof(struct acb56_dev));
        dev->name = (char *)(dev+1);
        if ((err=dev_alloc_name(dev,"escc%d"))) { kfree(dev); return err; }
    }
    /* 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))) {
        printk(KERN_INFO "%s: couldn't register net device: error (%d)\n",dev->name, err);
        kfree(dev);
        return err;
    }
    request_region(iobase,8,"acb56");

    acb56_devices = p;
    return(0);
}

int
init_module(void)
{
#ifdef CONFIG_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 = acb56_devices->next;
        unregister_netdevice((struct device *)acb56_devices);
    }
}




/*
 *  =========================================
 *
 *  DEVICE SPECIFIC Procedures
 *
 *  =========================================
 */

/*
 *  -----------------------------------------
 *
 *  IOCTL
 *
 *  -----------------------------------------
 */

static int acb56_ioctl(sdl_t *sdl, int cmd, void *arg, int size)
{
    sdl_device_t *dev = &sdl->device;
    acb56_dev_t *prv = dev->priv;
    sdl_config_t *ureq = NULL;
    sdl_ulong uarg = 0;

    switch (cmd) {
        case SDL_IOCTCONFIG:
        case SDL_IOCSCONFIG:
            if ( !arg || size < sizeof(sdl_config_t) ) return EINVAL;
            ureq = arg;
            break;
        case SDL_IOCSIFTYPE:
        case SDL_IOCSGRPTYPE:
        case SDL_IOCSIFMODE:
        case SDL_IOCSIFRATE:
        case SDL_IOCSIFCLOCK:
        case SDL_IOCSIFCODING:
        case SDL_IOCSIFLEADS:
        case SDL_IOCCIFLEADS:
            if ( !arg || size < sizeof(sdl_ulong) ) return EINVAL;
            uarg = *(sdl_ulong *)arg;
            break;
        case SDL_IOCCCONFIG:
        case SDL_IOCCMRESET:
        case SDL_IOCIFRESET:
        case SDL_IOCCDISCTX:
        case SDL_IOCCCONNTX:
            break;
    }
    switch (cmd) {
        case SDL_IOCTCONFIG:
        case SDL_IOCSCONFIG:
        case SDL_IOCCCONFIG:
        case SDL_IOCCMRESET:
        case SDL_IOCIFRESET:

        case SDL_IOCSIFTYPE:
            if ( uarg != SDL_TYPE_PACKET ) return EINVAL;
            return(0);
        case SDL_IOCSGRPTYPE:
            if ( uarg != SDL_GTYPE_UDP ) return EINVAL;
            return(0);
        case SDL_IOCSIFMODE:
            if ( uarg != SDL_MODE_PEER ) return EINVAL;
            return(0);
        case SDL_IOCSIFRATE:
            {
                int tdiff;
                if ( uarg < 800L || uarg > 100000000L ) return EINVAL;
                dev->config.ifrate = uarg;
                prv->config.ifrate = uarg;
                if ( (tdiff = prv->timestamp - jiffies) > 0 )
                    prv->bytecount += prv->tickbytes * tdiff;
                else
                    prv->bytecount = 0;
                prv->timestamp = jiffies;
                prv->tickbytes = uarg/HZ/8;
                while ( prv->bytecount >= prv->tickbytes ) {
                    prv->bytecount -= prv->tickbytes;
                    prv->timestamp++;
                }
                return(0);
            }
        case SDL_IOCSIFCLOCK:
            if ( uarg != SDL_CLOCK_SHAPER ) return EINVAL;
            return(0);
        case SDL_IOCSIFCODING:
            if ( uarg != SDL_CODING_NONE ) return EINVAL;
            return(0);
        case SDL_IOCSIFLEADS:
        case SDL_IOCCIFLEADS:
            if ( uarg ) return EINVAL;
            return(0);
        case SDL_IOCCDISCTX:
            dev->config.ifflags &= ~SDL_FLAGS_IFTX;
            prv->config.ifflags &= ~SDL_FLAGS_IFTX;
            return(0);
        case SDL_IOCCCONNTX:
            dev->config.ifflags |= SDL_FLAGS_IFTX;
            prv->config.ifflags |= SDL_FLAGS_IFTX;
            return(0);
    }
    return EOPNOTSUPP;
}


static sdl_device_t acb56_device_default
{
    SDL_TYPE_V35,       /* iftype   */
    SDL_GTYPE_NONE,     /* ifgtype  */
    SDL_MODE_DTE,       /* ifmode   */
    56000,              /* ifrate   */
    SDL_CLOCK_DPLL,     /* ifclock  */
    SDL_CODING_NRZI,    /* ifcoding */
    0,                  /* ifleads  */
    0,                  /* ifindex  */
    0,                  /* irq      */
    0,                  /* iobase   */
    0,                  /* dma_rx   */
    0                   /* dma_tx   */
};

static inline int
acb56_open(sdl_device_t *dev)
{
    if ( (dev->priv = kmalloc(sizeof(acb56_dev_t), GFP_KERNEL)) )
    {
        acb56_dev_t *devp = dev->priv;
        bzero(devp, sizeof(*devp));
        bcopy(dev, &acb56_device_default, sizeof(*dev));
        devp->timestamp = jiffies;
        devp->tickbytes = 1544000/HZ/8;
        devp->bytecount = 0;
        return(0);
    }
    return ENOMEM;
}

static void
acb56_close(sdl_t *sdl)
{
    if (sdl->device.priv) kfree(sdl->device.priv);
    sdl->device.priv = NULL;
}

static void
acb56_attach(sdl_t *sdl)
{
}

static void
acb56_detach(sdl_t *sdl)
{
}

static void acb56_data_ready(struct sock *, int);

static int
acb56_enable(sdl_t *sdl)
{
    int err;
    struct socket *sock = NULL;
    sdl_device_t *dev = &sdl->device;
    acb56_dev_t *prv = dev->priv;
    struct sockaddr *sa = (struct sockaddr *)prv->loc_addr;

    if ( (err = sock_create(PF_INET, SOCK_DGRAM, 0, &sock)) < 0 )
        return err;

    sock->sk->reuse = 1;    /* FIXME: check this */
    sock->sk->allocation = GFP_ATOMIC;

    if ( (err = sock->bind(sock, sa, sizeof(*sa))) < 0 ) {
        sock_release(sock);
        return err;
    }

    sock->protinfo.destruct_hook = sdl; /* might be bad idea */
    sock->sk->data_ready = acb56_data_ready;

    prv->udpsock = sock;
    sdl->state = SDL_ENABLED;
    return (0);
}


static int
acb56_disable(sdl_t *sdl)
{
    acb56_dev_t *prv = sdl->device.priv;
    struct socket *sock = prv->udpsock;

    prv->config.ifflags &= ~SDL_FLAGS_IFTX;
    sdl->statem.deadt_state = SDL_STATE_IDLE;
    if ( sock ) {
        sock->protinfo.destruct_hook = NULL;
        sock_release(sock);
        prv->udpsock = NULL;
    }
    prv->config.ifflags &= ~SDL_FLAGS_IFRX;
    sdl->statem.deadr_state = SDL_STATE_IDLE;
    sdl->state = SDL_DISABLED;
    return(0);
}

/*
 *  Trick to link mblks to sk_bufs
 */

static void acb56_freebuf(char *arg)
{
    struct sk_buff *skb = (struct sk_buff *)arg;
    struct sock *sk = skb->sk;

    skb_free_datagram(sk, skb);
}

static mblk_t *acb56_esballoc(struct sk_buff *skb, unsigned char* base, int size)
{
    struct free_rtn f;

    f.free_func = acb56_freebuf;
    f.free_arg  = (char *)skb;

    return esballoc(base, size, BRPI_MED, &f);
}

/*
 *  This is equivalent of RxISR.
 */
static void
acb56_data_ready(struct sock *sk, int bytes)
{
    int err;
    struct sk_buff *skb;
    sdl_t *sdl = (sdl_t *)sock->protinfo.destruct_hook;
    acb56_dev_t *prv = sdl->device.priv;
    int rx_on = ( sdl && sdl->statem.daedr_state == SDL_STATE_IN_SERVICE );

    (void)bytes;
    while ( (skb = skb_recv_datagram(sk, 0, 1, &err)) ) {
        if ( rx_on ) {
            mblk_t *mp;
            caddr_t data = skb->h.raw + sizeof(struct udphdr);
            size_t  len  = skb->len   + sizeof(struct udphdr);

            sdl->stats.rx_sus++;
            sdl->stats.rx_bytes += len;

            skb->sk = sk;   /* just to be sure */

            if ( canputq(sdl->rq) && (mp = acb56_allocb(skb, data, len)) ) {
                putq(sdl->rq, mp);
            } else {
                sdl->stats.rx_overruns++;
                sdl->stats.rx_buffer_overflows++;
                priv->dcalls.daedr_error_frame(sdl);
            }
        }
    }
    if ( err && rx_on && abs(err) != EAGAIN )
        /* FIXME: analyze this error */
        priv->dcalls.daedr_error_frame(sdl);
    return;
}

/*
 *  This is equivalent of TxISR.
 */
static int
acb56_xmit(sdl_t *sdl, mblk_t *mp)
{
    int n, size, tdiff;
    mblk_t *db;
    sdl_device_t *dev = &sdl->device;
    acb56_dev_t *prv = dev->priv;

    if ( (tdiff = jiffies - prv->timestamp) < 0 )
        return EAGAIN; /* throttle */
    if ( tdiff > 0 ) {
        prv->bytecount = 0;
        prv->timestamp = jiffies;
    }
    for ( size=0, n=0, db=mp; db; db=db->b_cont )
        if ( db->b_data->db_type == M_DATA && db->b_wptr > db->b_rptr ) {
            size += db->b_wptr - db->b_rptr;
            n++;
        }
    if ( n ) {
        int err=0, size;
        struct msghdr udpmsg;
        struct iovec iov[n];

        for ( n=0, db=mp; db; db=db->b_cont ) {
            if ( db->b_data->db_type == M_DATA && db->b_wptr > db->b_rptr ) {
                ioc[n].iov_base = db->b_rptr;
                iov[n].iov_len  = db->b_wptr - db->b_rptr;
            }
        }
        udpmsg.msg_name = (void *)&prv->rem_addr;
        udpmsg.msg_namelen = sizeof(prv->rem_addr);
        udpmsg.msg_iov = iov;
        udpmsg.msg_iovlen = n;
        udpmsg.msg_control = NULL;
        udpmsg.msg_controllen = 0;
        udpmsg.msg_flags = 0;
        {
            mm_segment_t fs = get_fs();
            set_fs(KERNEL_DS);
            err = sock_sendmsg(prv->udpsock, &udpmsg, size);
            set_fs(fs);
        }
        if ( err ) {
            switch ( abs(err) ) {
                case ENOMEM:    /* ran out of memory */
                case ENOBUFS:   /* ran out of memory */
                case EAGAIN:    /* no space on non-blocking socket */
                case ERESTARTSYS:   /* signal */
                    return EAGAIN;  /* temporary, try again later */
                case EINVAL:    /* bad address */
                case EACCES:    /* tried to send on broadcast address from nonbroadcast socket */
                case ENODEV:    /* bad index on iface directed output */
                case EFAULT:    /* bad user space address */
                    return abs(err);  /* bad error, link unusable */
                case EPERM:     /* blocked by firewall */
                case EMSGSIZE:  /* message bigger than MTU */
                    sdl->stats.tx_sus_in_error++;
                    freemsg(mp);
                    return(0);

                case EPIPE:     /* socket closed */
                case ENOTCONN:  /* sk->state != TCP_ESTABLISHED connected socket */
                case ECONNREFUSED:  /* no receiver */

                case ENETDOWN:  /* couldn't route to destination */
                case ENETUNREACH:   /* cant route to dest */
                case EHOSTUNREACH: /* no routing entry to dest */
                    return EAGAIN;
            }
            freemsg(mp);
            return(abs(err));
        }
        sdl->stats.tx_bytes += size;
        sdl->stats.tx_sus++;
        prv->bytecount += size;
        while ( prv->bytecount >= prv->tickbytes ) {
            prv->bytecount -= prv->tickbytes;
            prv->timestamp++;
        }
    }
    return(0);
}

/*
 *  --------------------------------------
 *
 *  STREAMS OPEN and CLOSE routines
 *
 *  --------------------------------------
 */

static sdl_t *sdl_list = NULL;

static sdl_config_t sdl_config_default =
{
    SS7_PVAR_ITUT_96,               /* pvar     */
    SS7_POPT_HSL | SS7_POPT_XSN,    /* popt     */
    5,                              /* M        */
    4,                              /* Tin      */
    1,                              /* Tie      */
    32,                             /* T        */
    256,                            /* D        */
    16,                             /* N        */
    577169,                         /* Te       */
    144292,                         /* De       */
    9308,                           /* Ue       */
    10                              /* t8       */
};

static sdl_devops_t sdl_device_operations =
{
    NULL,               /* probe    */
    acb56_open,       /* open     */
    acb56_close,      /* close    */
    acb56_attach,     /* attach   */
    acb56_detach,     /* detach   */
    acb56_enable,     /* enable   */
    acb56_disable,    /* disable  */
    acb56_xmit,       /* xmit     */
    NULL,               /* rtnbuf   */
    acb56_ioctl       /* ioctl    */
};

/*
 *  =======================================================================
 *
 *  Kernel Module Initialization   (For unregistered driver.)
 *
 *  =======================================================================
 */

static int acb56_initialized = 0;

#ifdef MODULE

int init_module(void)
{
    int err;
    printk(KERN_INFO ACB56_BANNER);   /* console splash */
    acb56_initialized
        = sdl_register_driver(
                ACB56_C_MAJOR, ACB56_N_MINOR, ACB56_MOD_ID, ACB56_MOD_NAME,
                &sdl_device_operations);
    if ( acb56_initialized  < 0 ) return acb56_initialized;
    if ( acb56_initialized  > 0 ) acb56_major = acb56_initialized;
    if ( acb56_initialized == 0 ) acb56_initialized = acb56_major;
    return(0);
}

void cleanup_module(void)
{
    acb56_initialized = sdl_unregister_driver(acb56_major);
};

#endif

/*
 *  =======================================================================
 *
 *  LiS Module Initialization   (For registered driver.)
 *
 *  =======================================================================
 */


void acb56_init(void)
{
    int err;
    if ( acb56_initialized > 0 ) return;
    if ( (err = init_module()) < 0 ) return;
    acb56_initialized = acb56_major;
};

void acb56_terminate(void)
{
    if ( acb56_initialized <= 0 ) return;
    cleanup_module();
};
