/*
   s5scc.c: Driver for the S5SCC/DMA interface.
   Copyright (c) 1999 Ivo Simicevic : ivo@ultra.hr 

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2, as
   published by the Free Software Foundation.

   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.

   The files pi2.c by David Perry and skeleton.c by Donald Becker 
   were used as a starting point for this driver.

   Revision History

   1999/06/01 (is) Created version 0.1 ALPHA

*/



/* The following #define invokes a hack that will improve performance (baud)
   for the B port. The up side is it makes 9600 baud work ok on the B port.
   It may do 38400, depending on the host. The down side is it burns up
   CPU cycles with ints locked for up to 1 character time, at the beginning
   of each transmitted packet. If this causes you to lose sleep, #undefine it.
*/

/*#define STUFF2 1*/

/* The default configuration */

#define DEF_A_SPEED 0           /* 0 means external clock */
#define DEF_A_TXDELAY 4         /* 15 mS transmit delay */
#define DEF_A_PERSIST 192       /* 75% persistence */
#define DEF_A_SLOTIME 15        /* 15 mS slot time */
#define DEF_A_SQUELDELAY 2      /* 2 mS squelch delay - allows fcs and flag */
#define DEF_A_CLOCKMODE 1       /* clock mode - 0 is normal */

#define DEF_B_SPEED 19200       /* 19200 baud */
#define DEF_B_TXDELAY 25        /* 250 mS */
#define DEF_B_PERSIST 192       /* 75% */
#define DEF_B_SLOTIME 50        /* 50 mS */
#define DEF_B_SQUELDELAY 2      /* 2 mS */
#define DEF_B_CLOCKMODE 0       /* Normal clock mode */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/segment.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/timer.h>
#include <linux/if_arp.h>
#include <net/ax25.h>
#include "s5scc.h"
#include "z8530.h"

short iobase = 0x340;
short dma = 1;
short irq = 5;

struct mbuf {
    struct mbuf *next;
    int cnt;
    char data[0];
};

/*
 *	S5SCC device declarations.
 */
 
static int s5scc_preprobe(struct device *dev){return 0;} /* Dummy probe function */
static struct device s5scca = { "s5scca", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, s5scc_preprobe };
static struct device s5sccb = { "s5sccb", 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, s5scc_preprobe };

 
/* The number of low I/O ports used by the card. */
#define S5SCC_TOTAL_SIZE	8


/* Index to functions, as function prototypes. */

static int s5scc_probe(struct device *dev);
static int s5scc_open(struct device *dev);
static int s5scc_send_packet(struct sk_buff *skb, struct device *dev);
static void s5scc_interrupt(int reg_ptr, void *dev_id, struct pt_regs *regs);
static int s5scc_close(struct device *dev);
static int s5scc_ioctl(struct device *dev, struct ifreq *ifr, int cmd);
static struct enet_statistics *s5scc_get_stats(struct device *dev);
static void rts(struct s5scc_local *lp, int x);
static void b_rxint(struct device *dev, struct s5scc_local *lp);
static void b_txint(struct s5scc_local *lp);
static void b_exint(struct s5scc_local *lp);
static void a_rxint(struct device *dev, struct s5scc_local *lp);
static void a_txint(struct s5scc_local *lp);
static void a_exint(struct s5scc_local *lp);
static char *get_dma_buffer(unsigned long *mem_ptr);
static int valid_dma_page(unsigned long addr, unsigned long dev_buffsize);

static char ax25_bcast[7] =
{'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1};
static char ax25_test[7] =
{'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1};

static int ext2_secrm_seed = 152;	/* Random generator base */

static inline unsigned char random(void)
{
  return (unsigned char) (ext2_secrm_seed = ext2_secrm_seed * 69069l + 1);
}

static inline void wrtscc(int cbase, int ctl, int sccreg, int val)
{
  /* assume caller disables interrupts! */
  outb_p(sccreg, ctl);		/* Select register */
  outb_p(val, ctl);		/* Output value */
}

static inline int rdscc(int cbase, int ctl, int sccreg)
{
int retval;

  /* assume caller disables interrupts! */
  outb_p(sccreg, ctl);		/* Select register */
  retval = inb_p(ctl);
  return retval;
}

static void switchbuffers(struct s5scc_local *lp)
{
  if (lp->rcvbuf == lp->rxdmabuf1)
    lp->rcvbuf = lp->rxdmabuf2;
  else
    lp->rcvbuf = lp->rxdmabuf1;
}

static void hardware_send_packet(struct s5scc_local *lp, struct sk_buff *skb)
{
char kickflag;
unsigned long flags;

  lp->stats.tx_packets++;

  save_flags(flags);
  cli();
  kickflag = (skb_peek(&lp->sndq) == NULL) && (lp->sndbuf == NULL);
  restore_flags(flags);

  skb_queue_tail(&lp->sndq, skb);
  if (kickflag) {
    /* simulate interrupt to xmit */
    switch (lp->base & 2) {
      case 2: a_txint(lp);	/* process interrupt */
	      break;
      case 0: save_flags(flags);
	      cli();
	      if (lp->tstate == IDLE)
		b_txint(lp);
	      restore_flags(flags);
	      break;
    }
  }
}

static void setup_rx_dma(struct s5scc_local *lp)
{
unsigned long flags;
int cmd;
unsigned long dma_abs;
unsigned dmachan;

  save_flags(flags);
  cli();

  dma_abs = (unsigned long) (lp->rcvbuf->data);
  dmachan = lp->dmachan;
  cmd = lp->base + CTL;

  if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf)))
	panic("S5SCC: RX buffer violates DMA boundary!");

  /* Get ready for RX DMA */
  wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB);

  disable_dma(dmachan);
  clear_dma_ff(dmachan);

  /* Set DMA mode register to single transfers, incrementing address,
   *  auto init, writes
   */
  set_dma_mode(dmachan, DMA_MODE_READ | 0x10);
  set_dma_addr(dmachan, dma_abs);
  set_dma_count(dmachan, lp->bufsiz);
  enable_dma(dmachan);

  /* If a packet is already coming in, this line is supposed to
	   avoid receiving a partial packet.
   */
  wrtscc(lp->cardbase, cmd, R0, RES_Rx_CRC);

  /* Enable RX dma */
  wrtscc(lp->cardbase, cmd, R1, WT_RDY_ENAB | WT_FN_RDYFN | WT_RDY_RT | INT_ERR_Rx | EXT_INT_ENAB);

  restore_flags(flags);
}

static void setup_tx_dma(struct s5scc_local *lp, int length)
{
    unsigned long dma_abs;
    unsigned long flags;
    unsigned long dmachan;

    save_flags(flags);
    cli();

    dmachan = lp->dmachan;
    dma_abs = (unsigned long) (lp->txdmabuf);

    if(!valid_dma_page(dma_abs, DMA_BUFF_SIZE + sizeof(struct mbuf)))
	panic("S5SCC: TX buffer violates DMA boundary!");

    disable_dma(dmachan);
    /* Set DMA mode register to single transfers, incrementing address,
     *  no auto init, reads
     */
    set_dma_mode(dmachan, DMA_MODE_WRITE);
    clear_dma_ff(dmachan);
    set_dma_addr(dmachan, dma_abs);
    /* output byte count */
    set_dma_count(dmachan, length);

    restore_flags(flags);
}

static void tdelay(struct s5scc_local *lp, int time)
{
    int port;
    unsigned int t1;
    unsigned char sc;

    if (lp->base & 2) {		/* If A channel */
	sc = SC1;
	t1 = time;
	port = lp->cardbase + TMR1;
    } else {
	sc = SC2;
        if (lp->speed < 19200)
          t1 = 10 * time; /* 10s of milliseconds for the B channel */
        else 
          t1 = time;      /* 1s of millisec for the B channel if speed > 19200 bps */
	port = lp->cardbase + TMR2;
	wrtscc(lp->cardbase, lp->base + CTL, R1, INT_ALL_Rx | EXT_INT_ENAB);
    }

    /* Setup timer sc */
    outb_p(sc | LSB_MSB | MODE0, lp->cardbase + TMRCMD);

    /* times 2 to make millisecs */
    outb_p((t1 << 1) & 0xFF, port);
    outb_p((t1 >> 7) & 0xFF, port);

    /* Enable correct int for timeout */
    wrtscc(lp->cardbase, lp->base + CTL, R15, CTSIE);
    wrtscc(lp->cardbase, lp->base + CTL, R0, RES_EXT_INT);
}

static void free_p(struct sk_buff *skb)
{
	dev_kfree_skb(skb, FREE_WRITE);
}

static void a_txint(struct s5scc_local *lp)
{
    int cmd;
    unsigned long flags;

    save_flags(flags);
    cli();

    cmd = CTL + lp->base;

    switch (lp->tstate) {
    case IDLE:
	/* Transmitter idle. Find a frame for transmission */
	if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) {
	    rts(lp, OFF);
	    restore_flags(flags);
	    return;
	}
	/* If a buffer to send, we drop thru here */
    case DEFER:
	/* we may have deferred prev xmit attempt */
	/* Check DCD - debounce it */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) {
	    lp->tstate = DEFER;
            tdelay(lp, 50);
	    /* defer until DCD transition or timeout */
	    wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE);
	    restore_flags(flags);
	    return;
	}
	if (random() > lp->persist) {
	    lp->tstate = DEFER;
	    tdelay(lp, lp->slotime);
	    restore_flags(flags);
	    return;
	}
	/* Assert RTS early minimize collision window */
	wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8);
	rts(lp, ON);		/* Transmitter on */
	lp->tstate = ST_TXDELAY;
	tdelay(lp, lp->txdelay);
	restore_flags(flags);
	return;
    default:
	break;
    }				/* end switch(lp->state) */

    restore_flags(flags);
}				/*a_txint */

static void a_exint(struct s5scc_local *lp)
{
    unsigned long flags;
    int cmd;
    char st;
    int length;

    save_flags(flags);
    cli();			/* disable interrupts */

    st = rdscc(lp->cardbase, lp->base + CTL, R0);	/* Fetch status */

    /* reset external status latch */
    wrtscc(lp->cardbase, CTL + lp->base, R0, RES_EXT_INT);
    cmd = lp->base + CTL;

    if ((lp->rstate >= ACTIVE) && (st & BRK_ABRT)) {
	setup_rx_dma(lp);
	lp->rstate = ACTIVE;
    }
    switch (lp->tstate) {
    case ACTIVE:
	free_p(lp->sndbuf);
	lp->sndbuf = NULL;
	lp->tstate = FLAGOUT;
	tdelay(lp, lp->squeldelay);
	break;
    case FLAGOUT:
	if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) {
	    /* Nothing to send - return to receive mode */
	    lp->tstate = IDLE;
	    rts(lp, OFF);
	    restore_flags(flags);
	    return;
	}
	/* NOTE - fall through if more to send */
    case ST_TXDELAY:
	/* Disable DMA chan */
	disable_dma(lp->dmachan);

	/* Set up for TX dma */
	wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB);


	/* Get all chars */
	/* Strip KISS control byte */
	length = lp->sndbuf->len - 1;
	memcpy(lp->txdmabuf, &lp->sndbuf->data[1], length);


	/* Setup DMA controller for tx */
	setup_tx_dma(lp, length);

	/* select transmit interrupts to enable */
	/* Allow DMA on chan */
	enable_dma(lp->dmachan);

	/* reset CRC, Txint pend*/
	wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC | RES_Tx_P);

	/* allow Underrun int only */
	wrtscc(lp->cardbase, cmd, R15, TxUIE);

	/* Enable TX DMA */
	wrtscc(lp->cardbase, cmd, R1, WT_RDY_ENAB | WT_FN_RDYFN | EXT_INT_ENAB);

	/* Send CRC on underrun */
	wrtscc(lp->cardbase, cmd, R0, RES_EOM_L);


	/* packet going out now */
	lp->tstate = ACTIVE;
	break;
    case DEFER:
	/* we have deferred prev xmit attempt
         * See Intel Microcommunications Handbook, p2-308
         */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) {
	    lp->tstate = DEFER;
	    tdelay(lp, 50);
	    /* Defer until dcd transition or 100mS timeout */
	    wrtscc(lp->cardbase, CTL + lp->base, R15, CTSIE | DCDIE);
	    restore_flags(flags);
	    return;
	}
	if (random() > lp->persist) {
	    lp->tstate = DEFER;
	    tdelay(lp, lp->slotime);
	    restore_flags(flags);
	    return;
	}
	/* Assert RTS early minimize collision window */
	wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | Tx8);
	rts(lp, ON);		/* Transmitter on */
	lp->tstate = ST_TXDELAY;
	tdelay(lp, lp->txdelay);
	restore_flags(flags);
	return;
    }				/* switch(lp->tstate) */

    restore_flags(flags);
}				/* a_exint() */

/* Receive interrupt handler for the A channel
 */
static void a_rxint(struct device *dev, struct s5scc_local *lp)
{
    unsigned long flags;
    int cmd;
    int bytecount;
    char rse;
    struct sk_buff *skb;
    int sksize, pkt_len;
    struct mbuf *cur_buf;
    unsigned char *cfix;

    save_flags(flags);
    cli();			/* disable interrupts */
    cmd = lp->base + CTL;

    rse = rdscc(lp->cardbase, cmd, R1);	/* Get special condition bits from R1 */
    if (rse & Rx_OVR)
	lp->rstate = RXERROR;

    if (rse & END_FR) {
	/* If end of frame */
	/* figure length of frame from 8237 */
	clear_dma_ff(lp->dmachan);
	bytecount = lp->bufsiz - get_dma_residue(lp->dmachan);

	if ((rse & CRC_ERR) || (lp->rstate > ACTIVE) || (bytecount < 10)) {
	    if ((bytecount >= 10) && (rse & CRC_ERR)) {
		lp->stats.rx_crc_errors++;
	    }
	    if (lp->rstate == RXERROR) {
		lp->stats.rx_errors++;
		lp->stats.rx_over_errors++;
	    }
	    /* Reset buffer pointers */
	    lp->rstate = ACTIVE;
	    setup_rx_dma(lp);
	} else {
	    /* Here we have a valid frame */
	    /* Toss 2 crc bytes , add one for KISS */
	    pkt_len = lp->rcvbuf->cnt = bytecount - 2 + 1;

	    /* Get buffer for next frame */
	    cur_buf = lp->rcvbuf;
	    switchbuffers(lp);
	    setup_rx_dma(lp);


	    /* Malloc up new buffer. */
	    sksize = pkt_len;

	    skb = dev_alloc_skb(sksize);
	    if (skb == NULL) {
		printk(KERN_ERR "S5SCC: %s: Memory squeeze, dropping packet.\n", dev->name);
		lp->stats.rx_dropped++;
		restore_flags(flags);
		return;
	    }
	    skb->dev = dev;

	    /* KISS kludge -  prefix with a 0 byte */
	    cfix=skb_put(skb,pkt_len);
	    *cfix++=0;
	    /* 'skb->data' points to the start of sk_buff data area. */
	    memcpy(cfix, (char *) cur_buf->data,
		   pkt_len - 1);
	    skb->protocol=htons(ETH_P_AX25);
	    skb->mac.raw=skb->data;
	    IS_SKB(skb);
	    netif_rx(skb);
	    lp->stats.rx_packets++;
	}			/* end good frame */
    }				/* end EOF check */
    wrtscc(lp->cardbase, lp->base + CTL, R0, ERR_RES);	/* error reset */
    restore_flags(flags);
}

static void b_rxint(struct device *dev, struct s5scc_local *lp)
{
    unsigned long flags;
    int cmd;
    char rse;
    struct sk_buff *skb;
    int sksize;
    int pkt_len;
    unsigned char *cfix;

    save_flags(flags);
    cli();			/* disable interrupts */
    cmd = CTL + lp->base;

    rse = rdscc(lp->cardbase, cmd, R1);	/* get status byte from R1 */

    if ((rdscc(lp->cardbase, cmd, R0)) & Rx_CH_AV) {
	/* there is a char to be stored
         * read special condition bits before reading the data char
         */
	if (rse & Rx_OVR) {
	    /* Rx overrun - toss buffer */
	    /* reset buffer pointers */
	    lp->rcp = lp->rcvbuf->data;
	    lp->rcvbuf->cnt = 0;

	    lp->rstate = RXERROR;	/* set error flag */
	    lp->stats.rx_errors++;
	    lp->stats.rx_over_errors++;
	} else if (lp->rcvbuf->cnt >= lp->bufsiz) {
	    /* Too large -- toss buffer */
	    /* reset buffer pointers */
	    lp->rcp = lp->rcvbuf->data;
	    lp->rcvbuf->cnt = 0;
	    lp->rstate = TOOBIG;/* when set, chars are not stored */
	}
	/* ok, we can store the received character now */
	if (lp->rstate == ACTIVE) {	/* If no errors... */
	    *lp->rcp++ = rdscc(lp->cardbase, cmd, R8);	/* char to rcv buff */
	    lp->rcvbuf->cnt++;	/* bump count */
	} else {
	    /* got to empty FIFO */
	    (void) rdscc(lp->cardbase, cmd, R8);
	    wrtscc(lp->cardbase, cmd, R0, ERR_RES);	/* reset err latch */
	    lp->rstate = ACTIVE;
	}
    }
    if (rse & END_FR) {
	/* END OF FRAME -- Make sure Rx was active */
	if (lp->rcvbuf->cnt > 0) {
	    if ((rse & CRC_ERR) || (lp->rstate > ACTIVE) || (lp->rcvbuf->cnt < 10)) {
		if ((lp->rcvbuf->cnt >= 10) && (rse & CRC_ERR)) {
		    lp->stats.rx_crc_errors++;
		}
		lp->rcp = lp->rcvbuf->data;
		lp->rcvbuf->cnt = 0;
	    } else {
		/* Here we have a valid frame */
		pkt_len = lp->rcvbuf->cnt -= 2;	/* Toss 2 crc bytes */
		pkt_len += 1;	/* Make room for KISS control byte */

		/* Malloc up new buffer. */
		sksize = pkt_len;
		skb = dev_alloc_skb(sksize);
		if (skb == NULL) {
		    printk(KERN_ERR "S5SCC: %s: Memory squeeze, dropping packet.\n", dev->name);
		    lp->stats.rx_dropped++;
		    restore_flags(flags);
		    return;
		}
		skb->dev = dev;

		/* KISS kludge -  prefix with a 0 byte */
		cfix=skb_put(skb,pkt_len);
		*cfix++=0;
		/* 'skb->data' points to the start of sk_buff data area. */
		memcpy(cfix, lp->rcvbuf->data, pkt_len - 1);
		skb->protocol=ntohs(ETH_P_AX25);
		skb->mac.raw=skb->data;
		IS_SKB(skb);
		netif_rx(skb);
		lp->stats.rx_packets++;
		/* packet queued - initialize buffer for next frame */
		lp->rcp = lp->rcvbuf->data;
		lp->rcvbuf->cnt = 0;

	    }			/* end good frame queued */
	}			/* end check for active receive upon EOF */
	lp->rstate = ACTIVE;	/* and clear error status */
    }				/* end EOF check */
    restore_flags(flags);
}


static void b_txint(struct s5scc_local *lp)
{
    unsigned long flags;
    int cmd;
    unsigned char c;

    save_flags(flags);
    cli();
    cmd = CTL + lp->base;

    switch (lp->tstate) {
    case CRCOUT:
	lp->tstate = FLAGOUT;
	tdelay(lp, lp->squeldelay);
	restore_flags(flags);
	return;
    case IDLE:
	/* Transmitter idle. Find a frame for transmission */
	if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) {
	    /* Nothing to send - return to receive mode
             * Tx OFF now - flag should have gone
             */
	    rts(lp, OFF);
	 
	    restore_flags(flags);
	    return;
	}
	lp->txptr = lp->sndbuf->data;
	lp->txptr++;		/* Ignore KISS control byte */
	lp->txcnt = (int) lp->sndbuf->len - 1;
	/* If a buffer to send, we drop thru here */
    case DEFER:		/* we may have deferred prev xmit attempt */
	/* Check DCD - debounce it */
	/* See Intel Microcommunications Handbook, p2-308 */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) {
	    lp->tstate = DEFER;
	    tdelay(lp, 50);
	    /* defer until DCD transition or timeout */
	    wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE);
	    restore_flags(flags);
	    return;
	}
	if (random() > lp->persist) {
	    lp->tstate = DEFER;
	    tdelay(lp, lp->slotime);
	    restore_flags(flags);
	    return;
	}
	rts(lp, ON);		/* Transmitter on */
	lp->tstate = ST_TXDELAY;
	tdelay(lp, lp->txdelay);
	restore_flags(flags);
	return;

    case ACTIVE:
	/* Here we are actively sending a frame */
	if (lp->txcnt--) {
	    c = *lp->txptr++;
	    /* next char is gone */
	    wrtscc(lp->cardbase, cmd, R8, c);
	    /* stuffing a char satisfies Interrupt condition */
	} else {
	    /* No more to send */
	    free_p(lp->sndbuf);
	    lp->sndbuf = NULL;
	    if ((rdscc(lp->cardbase, cmd, R0) & 0x40)) {
		/* Did we underrun? */
		/* unexpected underrun */
		lp->stats.tx_errors++;
		lp->stats.tx_fifo_errors++;
		wrtscc(lp->cardbase, cmd, R0, SEND_ABORT);
		lp->tstate = FLAGOUT;
		tdelay(lp, lp->squeldelay);
		restore_flags(flags);
		return;
	    }
	    lp->tstate = UNDERRUN;	/* Now we expect to underrun */
	    /* Send flags on underrun */
	    if (lp->speed) {	/* If internally clocked */
		wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI);
	    } else {
		wrtscc(lp->cardbase, cmd, R10, CRCPS);
	    }
	    wrtscc(lp->cardbase, cmd, R0, RES_Tx_P);	/* reset Tx Int Pend */
	}
	restore_flags(flags);
	return;			/* back to wait for interrupt */
    }				/* end switch */
    restore_flags(flags);
}

/* Pi SIO External/Status interrupts (for the B channel)
 * This can be caused by a receiver abort, or a Tx UNDERRUN/EOM.
 * Receiver automatically goes to Hunt on an abort.
 *
 * If the Tx Underrun interrupt hits, change state and
 * issue a reset command for it, and return.
 */
static void b_exint(struct s5scc_local *lp)
{
    unsigned long flags;
    char st;
    int cmd;
    char c;

    cmd = CTL + lp->base;
    save_flags(flags);
    cli();			/* disable interrupts */
    st = rdscc(lp->cardbase, cmd, R0);	/* Fetch status */
    /* reset external status latch */
    wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);


    switch (lp->tstate) {
    case ACTIVE:		/* Unexpected underrun */
	free_p(lp->sndbuf);
	lp->sndbuf = NULL;
	wrtscc(lp->cardbase, cmd, R0, SEND_ABORT);
	lp->tstate = FLAGOUT;
	lp->stats.tx_errors++;
	lp->stats.tx_fifo_errors++;
	tdelay(lp, lp->squeldelay);
	restore_flags(flags);
	return;
    case UNDERRUN:
	lp->tstate = CRCOUT;
	restore_flags(flags);
	return;
    case FLAGOUT:
	/* Find a frame for transmission */
	if ((lp->sndbuf = skb_dequeue(&lp->sndq)) == NULL) {
	    /* Nothing to send - return to receive mode
             * Tx OFF now - flag should have gone
             */
	    rts(lp, OFF);
	    lp->tstate = IDLE;
	    restore_flags(flags);
	    return;
	}
	lp->txptr = lp->sndbuf->data;
	lp->txptr++;		/* Ignore KISS control byte */
	lp->txcnt = (int) lp->sndbuf->len - 1;
	/* Get first char to send */
	lp->txcnt--;
	c = *lp->txptr++;
	wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC);	/* reset for next frame */

	/* Send abort on underrun */
	if (lp->speed) {	/* If internally clocked */
	    wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI | ABUNDER);
	} else {
	    wrtscc(lp->cardbase, cmd, R10, CRCPS | ABUNDER);
	}

	wrtscc(lp->cardbase, cmd, R8, c);	/* First char out now */
	wrtscc(lp->cardbase, cmd, R0, RES_EOM_L);	/* Reset end of message latch */

#ifdef STUFF2
        /* stuff an extra one if we can */
	if (lp->txcnt) {
	    lp->txcnt--;
	    c = *lp->txptr++;
	    /* Wait for tx buffer empty */
	    while((rdscc(lp->cardbase, cmd, R0) & 0x04) == 0)
		;   
	    wrtscc(lp->cardbase, cmd, R8, c);
	}
#endif

	/* select transmit interrupts to enable */

	wrtscc(lp->cardbase, cmd, R15, TxUIE);	/* allow Underrun int only */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	wrtscc(lp->cardbase, cmd, R1, TxINT_ENAB | EXT_INT_ENAB);	/* Tx/Ext ints */

	lp->tstate = ACTIVE;	/* char going out now */
	restore_flags(flags);
	return;

    case DEFER:
	/* Check DCD - debounce it
         * See Intel Microcommunications Handbook, p2-308
         */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	if ((rdscc(lp->cardbase, cmd, R0) & DCD) != 0) {
	    lp->tstate = DEFER;
	    tdelay(lp, 50);
	    /* defer until DCD transition or timeout */
	    wrtscc(lp->cardbase, cmd, R15, CTSIE | DCDIE);
	    restore_flags(flags);
	    return;
	}
	if (random() > lp->persist) {
	    lp->tstate = DEFER;
	    tdelay(lp, lp->slotime);
	    restore_flags(flags);
	    return;
	}
	rts(lp, ON);		/* Transmitter on */
	lp->tstate = ST_TXDELAY;
	tdelay(lp, lp->txdelay);
	restore_flags(flags);
	return;

    case ST_TXDELAY:

	/* Get first char to send */
	lp->txcnt--;
	c = *lp->txptr++;
	wrtscc(lp->cardbase, cmd, R0, RES_Tx_CRC);	/* reset for next frame */

	/* Send abort on underrun */
	if (lp->speed) {	/* If internally clocked */
	    wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI | ABUNDER);
	} else {
	    wrtscc(lp->cardbase, cmd, R10, CRCPS | ABUNDER);
	}

	wrtscc(lp->cardbase, cmd, R8, c);	/* First char out now */
	wrtscc(lp->cardbase, cmd, R0, RES_EOM_L);	/* Reset end of message latch */

#ifdef STUFF2
        /* stuff an extra one if we can */
	if (lp->txcnt) {
	    lp->txcnt--;
	    c = *lp->txptr++;
	    /* Wait for tx buffer empty */
	    while((rdscc(lp->cardbase, cmd, R0) & 0x04) == 0)
		;   
	    wrtscc(lp->cardbase, cmd, R8, c);
	}
#endif

	/* select transmit interrupts to enable */

	wrtscc(lp->cardbase, cmd, R15, TxUIE);	/* allow Underrun int only */
	wrtscc(lp->cardbase, cmd, R0, RES_EXT_INT);
	/* Tx/Extern ints on */
	wrtscc(lp->cardbase, cmd, R1, TxINT_ENAB | EXT_INT_ENAB);

	lp->tstate = ACTIVE;	/* char going out now */
	restore_flags(flags);
	return;
    }

    /* Receive Mode only
     * This triggers when hunt mode is entered, & since an ABORT
     * automatically enters hunt mode, we use that to clean up
     * any waiting garbage
     */
    if ((lp->rstate == ACTIVE) && (st & BRK_ABRT)) {
	(void) rdscc(lp->cardbase, cmd, R8);
	(void) rdscc(lp->cardbase, cmd, R8);
	(void) rdscc(lp->cardbase, cmd, R8);
	lp->rcp = lp->rcvbuf->data;
	lp->rcvbuf->cnt = 0;	/* rewind on DCD transition */
    }
    restore_flags(flags);
}

/* Probe for a S5SCC card. */
/* This routine also initializes the timer chip */

static int hw_probe(int ioaddr)
{
    int time = 1000;		/* Number of milliseconds for test */
    unsigned long start_time, end_time;

    int base, tmr0, tmr1, tmrcmd;
    int a = 1;
    int b = 1;

    base = ioaddr & 0x3f0;
    tmr0 = TMR0 + base;
    tmr1 = TMR1 + base;
    tmrcmd = TMRCMD + base;

    /* Set up counter chip timer 0 for 500 uS period square wave */
    /* assuming a 3.68 mhz clock for now */
    outb_p(SC0 | LSB_MSB | MODE3, tmrcmd);
    outb_p(922 & 0xFF, tmr0);
    outb_p(922 >> 8, tmr0);

    /* Setup timer control word for timer 1*/
    outb_p(SC1 | LSB_MSB | MODE0, tmrcmd);
    outb_p((time << 1) & 0xFF, tmr1);
    outb_p((time >> 7) & 0XFF, tmr1);

    /* wait until counter reg is loaded */
    do {
	/* Latch count for reading */
	outb_p(SC1, tmrcmd);
	a = inb_p(tmr1);
	b = inb_p(tmr1);
    } while (b == 0);
    start_time = jiffies;
    while (b != 0) {
	/* Latch count for reading */
	outb_p(SC1, tmrcmd);
	a = inb_p(tmr1);
	b = inb_p(tmr1);
	end_time = jiffies;
	/* Don't wait forever - there may be no card here */
	if ((end_time - start_time) > 33)
	    return 0;		/* No S5SCC card found */
    }
    end_time = jiffies;

    /* 87 jiffies, for a 3.68 mhz clock, half that for a double speed clock */

    /* Faster crystal - tmr0 needs adjusting */
    /* Set up counter chip */
    /* 500 uS square wave */
    outb_p(SC0 | LSB_MSB | MODE3, tmrcmd);
    outb_p(2450 & 0xFF, tmr0);
    outb_p(2450 >> 8, tmr0);
    return (1);             /* S5SCC/DMA card found */
}

static void rts(struct s5scc_local *lp, int x)
{
    int tc;
    long br;
    int cmd;
    int dummy;

    /* assumes interrupts are off */
    cmd = CTL + lp->base;

    /* Reprogram BRG and turn on transmitter to send flags */
    if (x == ON) {		/* Turn Tx ON and Receive OFF */
	/* Disable External/Status interrupts first to avoid abort int */
	wrtscc(lp->cardbase, cmd, R15, 0);
	wrtscc(lp->cardbase, cmd, R3, Rx8);	/* Rx off */
	lp->rstate = IDLE;
	if (cmd & 2) {		/* if channel a */
	    /* Set up for TX dma */
	    wrtscc(lp->cardbase, cmd, R1, WT_FN_RDYFN | EXT_INT_ENAB);
	} else {
	    wrtscc(lp->cardbase, cmd, R1, 0);	/* No interrupts */
	}

	if (!lp->clockmode) {
	    if (lp->speed) {	/* if internally clocked */
		br = lp->speed;	/* get desired speed */
		tc = (lp->xtal / br) - 2;	/* calc 1X BRG divisor */
		wrtscc(lp->cardbase, cmd, R12, tc & 0xFF);	/* lower byte */
		wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF);	/* upper byte */
	    }
	}
	wrtscc(lp->cardbase, cmd, R5, TxCRC_ENAB | RTS | TxENAB | Tx8 | DTR);
	/* Transmitter now on */
    } else {			/* Tx OFF and Rx ON */
	lp->tstate = IDLE;
	wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR);	/*  TX off */

	if (!lp->clockmode) {
	    if (lp->speed) {	/* if internally clocked */
		/* Reprogram BRG for 32x clock for receive DPLL */
		/* BRG off, keep Pclk source */
		wrtscc(lp->cardbase, cmd, R14, BRSRC);
		br = lp->speed;	/* get desired speed */
		/* calc 32X BRG divisor */
		tc = ((lp->xtal / 32) / br) - 2;
		wrtscc(lp->cardbase, cmd, R12, tc & 0xFF);	/* lower byte */
		wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF);	/* upper byte */
		/* SEARCH mode, BRG source */
		wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH);
		/* Enable the BRG */
		wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL);
	    }
	}
	/* Flush rx fifo */
	wrtscc(lp->cardbase, cmd, R3, Rx8);	/* Make sure rx is off */
	wrtscc(lp->cardbase, cmd, R0, ERR_RES);	/* reset err latch */
	dummy = rdscc(lp->cardbase, cmd, R1);	/* get status byte from R1 */
	(void) rdscc(lp->cardbase, cmd, R8);
	(void) rdscc(lp->cardbase, cmd, R8);

	(void) rdscc(lp->cardbase, cmd, R8);

	/* Now, turn on the receiver and hunt for a flag */
	wrtscc(lp->cardbase, cmd, R3, RxENABLE | Rx8);
	lp->rstate = ACTIVE;	/* Normal state */

	if (cmd & 2) {		/* if channel a */
	    setup_rx_dma(lp);
	} else {
	    /* reset buffer pointers */
	    lp->rcp = lp->rcvbuf->data;
	    lp->rcvbuf->cnt = 0;
	    wrtscc(lp->cardbase, cmd, R1, (INT_ALL_Rx | EXT_INT_ENAB));
	}
	wrtscc(lp->cardbase, cmd, R15, BRKIE);	/* allow ABORT int */
    }
}

/* Fill in the MAC-level header. */
static int s5scc_header(struct sk_buff *skb, struct device *dev, unsigned short type,
	     void *daddr, void *saddr, unsigned len)
{
    return ax25_encapsulate(skb, dev, type, daddr, saddr, len);
}

/* Rebuild the MAC-level header. */
static int s5scc_rebuild_header(void *buff, struct device *dev, unsigned long raddr,
			     struct sk_buff *skb)
{
    return ax25_rebuild_header(buff, dev, raddr, skb);
}

static void scc_init(struct device *dev)
{
    unsigned long flags;
    struct s5scc_local *lp = (struct s5scc_local *) dev->priv;

    int tc;
    long br;
    register int cmd;

    /* Initialize 8530 channel for SDLC operation */

    cmd = CTL + lp->base;
    save_flags(flags);
    cli();

    switch (cmd & CHANA) {
    case CHANA:
	wrtscc(lp->cardbase, cmd, R9, CHRA);	/* Reset channel A */
	wrtscc(lp->cardbase, cmd, R2, 0xff);	/* Initialize interrupt vector */
	break;
    default:
	wrtscc(lp->cardbase, cmd, R9, CHRB);	/* Reset channel B */
	break;
    }

    /* Deselect all Rx and Tx interrupts */
    wrtscc(lp->cardbase, cmd, R1, 0);

    /* Turn off external interrupts (like CTS/CD) */
    wrtscc(lp->cardbase, cmd, R15, 0);

    /* X1 clock, SDLC mode */
    wrtscc(lp->cardbase, cmd, R4, SDLC | X1CLK);

    /* Tx/Rx parameters */
    if (lp->speed) {		/* Use internal clocking */
	wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI);
	if (!lp->clockmode)
	    /* Tx Clk from BRG. Rcv Clk from DPLL, TRxC pin outputs DPLL */
	    wrtscc(lp->cardbase, cmd, R11, TCBR | RCDPLL | TRxCDP | TRxCOI);
	else
	    /* Tx Clk from DPLL, Rcv Clk from DPLL, TRxC Outputs BRG */
	    wrtscc(lp->cardbase, cmd, R11, TCDPLL | RCDPLL | TRxCBR | TRxCOI);
    } else {			/* Use external clocking */
        if (!lp->clockmode)
	  wrtscc(lp->cardbase, cmd, R10, CRCPS);
        else 
          wrtscc(lp->cardbase, cmd, R10, CRCPS | NRZI);
	/* Tx Clk from Trxcl. Rcv Clk from Rtxcl, TRxC pin is input */
	wrtscc(lp->cardbase, cmd, R11, TCTRxCP);
    }

    /* Null out SDLC start address */
    wrtscc(lp->cardbase, cmd, R6, 0);

    /* SDLC flag */
    wrtscc(lp->cardbase, cmd, R7, FLAG);

    /* Set up the Transmitter but don't enable it
     *  DTR, 8 bit TX chars only
     */
    wrtscc(lp->cardbase, cmd, R5, Tx8 | DTR);

    /* Receiver initial setup */
    wrtscc(lp->cardbase, cmd, R3, Rx8);	/* 8 bits/char */

    /* Setting up BRG now - turn it off first */
    wrtscc(lp->cardbase, cmd, R14, BRSRC);	/* BRG off, keep Pclk source */

    /* set the 32x time constant for the BRG in Receive mode */

    if (lp->speed) {
	br = lp->speed;		/* get desired speed */
	tc = ((lp->xtal / 32) / br) - 2;	/* calc 32X BRG divisor */
    } else {
	tc = 14;
    }

    wrtscc(lp->cardbase, cmd, R12, tc & 0xFF);	/* lower byte */
    wrtscc(lp->cardbase, cmd, R13, (tc >> 8) & 0xFF);	/* upper byte */

    /* Following subroutine sets up and ENABLES the receiver */
    rts(lp, OFF);		/* TX OFF and RX ON */

    if (lp->speed) {
	/* DPLL frm BRG, BRG src PCLK */
	wrtscc(lp->cardbase, cmd, R14, BRSRC | SSBR);
    } else {
	/* DPLL frm rtxc,BRG src PCLK */
	wrtscc(lp->cardbase, cmd, R14, BRSRC | SSRTxC);
    }
    wrtscc(lp->cardbase, cmd, R14, BRSRC | SEARCH);	/* SEARCH mode, keep BRG src */
    wrtscc(lp->cardbase, cmd, R14, BRSRC | BRENABL);	/* Enable the BRG */

    if (!(cmd & 2))		/* if channel b */
	wrtscc(lp->cardbase, cmd, R1, (INT_ALL_Rx | EXT_INT_ENAB));

    wrtscc(lp->cardbase, cmd, R15, BRKIE);	/* ABORT int */

    /* Now, turn on the receiver and hunt for a flag */
    wrtscc(lp->cardbase, cmd, R3, RxENABLE | RxCRC_ENAB | Rx8);

    restore_flags(flags);
}

static void chipset_init(struct device *dev)
{
int cardbase;
unsigned long flags;

  cardbase = dev->base_addr & 0x3f0;

  save_flags(flags);
  cli();
  wrtscc(cardbase, dev->base_addr + CTL, R9, FHWRES);	/* Hardware reset */
  /* Disable interrupts with master interrupt ctrl reg */
  wrtscc(cardbase, dev->base_addr + CTL, R9, 0);
  restore_flags(flags);
}

int s5scc_init(void)
{
int result;
int ioaddr = 0;
int found = 0;

    printk(KERN_INFO "S5SCC: V0.1 ALPHA 1999/06/01 Ivo Simicevic (ivo@ultra.hr)\n");

    /* Only one card supported for now */

  ioaddr = iobase;

  if (check_region(ioaddr, S5SCC_TOTAL_SIZE) == 0) {
    printk(KERN_INFO "S5SCC: Checking for card at address %#3x\n",ioaddr);
	}

  found = hw_probe(ioaddr);
  if (!found) {
	printk(KERN_ERR "S5SCC: ERROR: No card found\n");
	return -EIO;
  }
  printk(KERN_INFO "S5SCC: Found a S5SCC/DMA card at address %#3x\n", ioaddr);

    /* Link a couple of device structures into the chain */
    /* For the A port */
    /* Allocate space for 4 buffers even though we only need 3,
       because one of them may cross a DMA page boundary and
       be rejected by get_dma_buffer().
    */
    
    s5scca.priv = kmalloc(sizeof(struct s5scc_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA);

    if (s5scca.priv==NULL) {
      printk(KERN_ERR "S5SCC: Not enough memory for a.priv!\n");
      return -ENOMEM;
    }
			
    s5scca.dma = dma;
    s5scca.base_addr = ioaddr + 2;
    s5scca.irq = irq;

    /* And the B port */
    s5sccb.base_addr = ioaddr;
    s5sccb.irq = irq;

    s5sccb.priv = kmalloc(sizeof(struct s5scc_local) + (DMA_BUFF_SIZE + sizeof(struct mbuf)) * 4, GFP_KERNEL | GFP_DMA);

    if (s5sccb.priv==NULL) {
      printk(KERN_ERR "S5SCC: Not enough memory for b.priv!\n");
      return -ENOMEM;
    }
			
    /* Now initialize them */
    result = s5scc_probe(&s5scca);
    if (result) return result;
    result = s5scc_probe(&s5sccb);
    if (result) return result;

    register_netdev(&s5scca);
    register_netdev(&s5sccb);
    return 0;
}

static int valid_dma_page(unsigned long addr, unsigned long dev_buffsize)
{
    if (((addr & 0xffff) + dev_buffsize) <= 0x10000)
	return 1;
    else
	return 0;
}

static int s5scc_set_mac_address(struct device *dev, void *addr)
{
    struct sockaddr *sa = (struct sockaddr *)addr;
    memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);	/* addr is an AX.25 shifted ASCII */
    return 0;						/* mac address */
}

/* Allocate a buffer which does not cross a DMA page boundary */
static char *
get_dma_buffer(unsigned long *mem_ptr)
{
    char *ret;

    ret = (char *)*mem_ptr;

    if(!valid_dma_page(*mem_ptr, DMA_BUFF_SIZE + sizeof(struct mbuf))){
	*mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf));
	ret = (char *)*mem_ptr;
    }
    *mem_ptr += (DMA_BUFF_SIZE + sizeof(struct mbuf));
    return (ret);
}

static int s5scc_probe(struct device *dev)
{
short ioaddr;
struct s5scc_local *lp;
int i,irqval;
unsigned long mem_ptr;

  ioaddr = dev->base_addr;

    /* Initialize the device structure. */
    /* Must be done before chipset_init */
    /* Make certain the data structures used by the S5SCC are aligned. */
  dev->priv = (void *) (((int) dev->priv + 7) & ~7);
  lp = (struct s5scc_local *) dev->priv;

  memset(dev->priv, 0, sizeof(struct s5scc_local));

    /* Allocate some buffers which do not cross DMA page boundaries */
  mem_ptr = (unsigned long) dev->priv + sizeof(struct s5scc_local);
  lp->txdmabuf = get_dma_buffer(&mem_ptr);
  lp->rxdmabuf1 = (struct mbuf *) get_dma_buffer(&mem_ptr);
  lp->rxdmabuf2 = (struct mbuf *) get_dma_buffer(&mem_ptr);

    /* Initialize rx buffer */
  lp->rcvbuf = lp->rxdmabuf1;
  lp->rcp = lp->rcvbuf->data;
  lp->rcvbuf->cnt = 0;

    /* Initialize the transmit queue head structure */
  skb_queue_head_init(&lp->sndq);

    /* These need to be initialized before scc_init is called. */
  lp->xtal = (unsigned long) S5SCCC / 2;
  lp->base = dev->base_addr;
  lp->cardbase = dev->base_addr & 0x3f0;
  if (dev->base_addr & CHANA) {
	lp->speed = DEF_A_SPEED;
	/* default channel access Params */
	lp->txdelay = DEF_A_TXDELAY;
	lp->persist = DEF_A_PERSIST;
	lp->slotime = DEF_A_SLOTIME;
	lp->squeldelay = DEF_A_SQUELDELAY;
	lp->clockmode = DEF_A_CLOCKMODE;

    } else {
	lp->speed = DEF_B_SPEED;
	/* default channel access Params */
	lp->txdelay = DEF_B_TXDELAY;
	lp->persist = DEF_B_PERSIST;
	lp->slotime = DEF_B_SLOTIME;
	lp->squeldelay = DEF_B_SQUELDELAY;
	lp->clockmode = DEF_B_CLOCKMODE;
    }
    lp->bufsiz = DMA_BUFF_SIZE;
    lp->tstate = IDLE;

  /* Grab the region */
  request_region(ioaddr & 0x3f0, S5SCC_TOTAL_SIZE, "s5scc" );

    chipset_init(dev);

    if (dev->base_addr & CHANA) {	/* Do these things only for the A port */
	/* Note that a single IRQ services 2 devices (A and B channels) */

	lp->dmachan = dev->dma;
	if (lp->dmachan < 1 || lp->dmachan > 3)
	    printk(KERN_ERR "S5SCC: DMA channel %d out of range\n", lp->dmachan);

	/* chipset_init() was already called */

    irqval = request_irq(dev->irq, &s5scc_interrupt,0, "s5scc", NULL);
    if (irqval) {
	printk(KERN_ERR "S5SCC: unable to get IRQ %d (irqval=%d).\n",
       dev->irq, irqval);
	return EAGAIN;
    }


    }				/* Only for A port */
    dev->open = s5scc_open;
    dev->stop = s5scc_close;
    dev->do_ioctl = s5scc_ioctl;
    dev->hard_start_xmit = s5scc_send_packet;
    dev->get_stats = s5scc_get_stats;

    /* Fill in the fields of the device structure */
    for (i = 0; i < DEV_NUMBUFFS; i++)
	skb_queue_head_init(&dev->buffs[i]);


    dev->hard_header = s5scc_header;
    dev->rebuild_header = s5scc_rebuild_header;
    dev->set_mac_address = s5scc_set_mac_address;

    dev->type = ARPHRD_AX25;			/* AF_AX25 device */
    dev->hard_header_len = 73;			/* We do digipeaters now */
    dev->mtu = 1500;				/* eth_mtu is the default */
    dev->addr_len = 7;				/* sizeof an ax.25 address */
    memcpy(dev->broadcast, ax25_bcast, 7);
    memcpy(dev->dev_addr, ax25_test, 7);

    /* New-style flags. */
    dev->flags = 0;
    dev->family = AF_INET;

#ifdef CONFIG_INET
    dev->pa_addr    = in_aton("192.168.0.1");
    dev->pa_brdaddr = in_aton("192.168.0.255");
    dev->pa_mask    = in_aton("255.255.255.0");
    dev->pa_alen    = 4;
#endif

    return 0;
}

/* Open/initialize the board.  This is called (in the current kernel)
   sometime after booting when the 'ifconfig' program is run.

   This routine should set everything up anew at each open, even
   registers that "should" only need to be set once at boot, so that
   there is non-reboot way to recover if something goes wrong.
   */
static int s5scc_open(struct device *dev)
{
unsigned long flags;
static int first_time = 1;
struct s5scc_local *lp;

    lp = (struct s5scc_local *) dev->priv;

    if (dev->base_addr & 2) {	/* if A channel */
	if (first_time) {
	    if (request_dma(dev->dma,"s5scc")) {
		free_irq(dev->irq, NULL);
		return -EAGAIN;
	    }
	    irq2dev_map[dev->irq] = dev;
	}
	/* Reset the hardware here. */
	chipset_init(dev);
    }
    lp->tstate = IDLE;

    if (dev->base_addr & 2) {	/* if A channel */
	scc_init(dev);		/* Called once for each channel */
	scc_init(dev->next);
    }
    /* master interrupt enable */
    save_flags(flags);
    cli();
    wrtscc(lp->cardbase, CTL + lp->base, R9, MIE | NV);
    restore_flags(flags);

    lp->open_time = jiffies;

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

    MOD_INC_USE_COUNT;

    return 0;
}

static int s5scc_send_packet(struct sk_buff *skb, struct device *dev)
{
    struct s5scc_local *lp = (struct s5scc_local *) dev->priv;

    /* If some higher layer thinks we've missed an tx-done interrupt
	   we are passed NULL. Caution: dev_tint() handles the cli()/sti()
	   itself. */
    if (skb == NULL) {
	dev_tint(dev);
	return 0;
    }
    hardware_send_packet(lp, skb);
    dev->trans_start = jiffies;

    return 0;
}

/* The typical workload of the driver:
   Handle the network interface interrupts. */
static void s5scc_interrupt(int reg_ptr, void *dev_id, struct pt_regs *regs)
{
/*    int irq = -(((struct pt_regs *) reg_ptr)->orig_eax + 2);*/
    struct s5scc_local *lp;
    int st;
    unsigned long flags;

/*    dev_b = dev_a->next;	 Relies on the order defined in Space.c */

#if 0
    if (dev_a == NULL) {
	printk(KERN_ERR "S5SCC: s5scc_interrupt(): irq %d for unknown device.\n", irq);
	return;
    }
#endif    
    /* Read interrupt status register (only valid from channel A)
     * Process all pending interrupts in while loop
     */
    lp = (struct s5scc_local *) s5scca.priv;	/* Assume channel A */
    while ((st = rdscc(lp->cardbase, s5scca.base_addr | CHANA | CTL, R3)) != 0) {
	if (st & CHBTxIP) {
	    /* Channel B Transmit Int Pending */
	    lp = (struct s5scc_local *) s5sccb.priv;
	    b_txint(lp);
	} else if (st & CHARxIP) {
	    /* Channel A Rcv Interrupt Pending */
	    lp = (struct s5scc_local *) s5scca.priv;
	    a_rxint(&s5scca, lp);
	} else if (st & CHATxIP) {
	    /* Channel A Transmit Int Pending */
	    lp = (struct s5scc_local *) s5scca.priv;
	    a_txint(lp);
	} else if (st & CHAEXT) {
	    /* Channel A External Status Int */
	    lp = (struct s5scc_local *) s5scca.priv;
	    a_exint(lp);
	} else if (st & CHBRxIP) {
	    /* Channel B Rcv Interrupt Pending */
	    lp = (struct s5scc_local *) s5sccb.priv;
	    b_rxint(&s5sccb, lp);
	} else if (st & CHBEXT) {
	    /* Channel B External Status Int */
	    lp = (struct s5scc_local *) s5sccb.priv;
	    b_exint(lp);
	}
	/* Reset highest interrupt under service */
	save_flags(flags);
	cli();
	wrtscc(lp->cardbase, lp->base + CTL, R0, RES_H_IUS);
	restore_flags(flags);
    }				/* End of while loop on int processing */
    return;
}

/* The inverse routine to s5scc_open(). */
static int s5scc_close(struct device *dev)
{
    unsigned long flags;
    struct s5scc_local *lp;
    struct sk_buff *ptr;

    save_flags(flags);
    cli();

    lp = (struct s5scc_local *) dev->priv;
    ptr = NULL;

    chipset_init(dev);		/* reset the scc */
    disable_dma(lp->dmachan);

    lp->open_time = 0;

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

    /* Free any buffers left in the hardware transmit queue */
    while ((ptr = skb_dequeue(&lp->sndq)) != NULL)
	free_p(ptr);

    restore_flags(flags);

    MOD_DEC_USE_COUNT;

    return 0;
}

static int s5scc_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
{
    unsigned long flags;
    struct s5scc_req rq;
    struct s5scc_local *lp = (struct s5scc_local *) dev->priv;

    int ret = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(struct s5scc_req));
    if (ret)
	return ret;
	
    if(cmd!=SIOCDEVPRIVATE)
    	return -EINVAL;

    memcpy_fromfs(&rq, ifr->ifr_data, sizeof(struct s5scc_req));

    switch (rq.cmd) {
    case SIOCSS5SCCPARAM:

	if (!suser())
	    return -EPERM;
	save_flags(flags);
	cli();
	lp->txdelay = rq.txdelay;
	lp->persist = rq.persist;
	lp->slotime = rq.slotime;
	lp->squeldelay = rq.squeldelay;
	lp->clockmode = rq.clockmode;
	lp->speed = rq.speed;
	s5scc_open(&s5scca); /* both channels get reset %%% */
	restore_flags(flags);
	ret = 0;
	break;

    case SIOCSS5SCCDMA:

	if (!suser())
	    return -EPERM;
	ret = 0;
	if (dev->base_addr & 2) {   /* if A channel */
	   if (rq.dmachan < 1 || rq.dmachan > 3)
		return -EINVAL;
	   save_flags(flags);
	   cli();
	   s5scc_close(dev);
	   free_dma(lp->dmachan);
	   dma = dev->dma = lp->dmachan = rq.dmachan;
	   if (request_dma(lp->dmachan,"s5scc")) 
		ret = -EAGAIN;
	   s5scc_open(dev);
	   restore_flags(flags);
	}
	break;

    case SIOCSS5SCCIRQ:
	ret = -EINVAL;      /* add this later */
	break;

    case SIOCGS5SCCPARAM:
    case SIOCGS5SCCDMA:
    case SIOCGS5SCCIRQ:

	rq.speed = lp->speed;
	rq.txdelay = lp->txdelay;
	rq.persist = lp->persist;
	rq.slotime = lp->slotime;
	rq.squeldelay = lp->squeldelay;
	rq.clockmode = lp->clockmode;
	rq.dmachan = lp->dmachan;
	rq.irq = dev->irq;
	memcpy_tofs(ifr->ifr_data, &rq, sizeof(struct s5scc_req));
	ret = 0;
	break;

    default:
	ret = -EINVAL;
    }
    return ret;
}

/* Get the current statistics.	This may be called with the card open or
   closed. */
static struct netstats *
 s5scc_get_stats(struct device *dev)
{
    struct s5scc_local *lp = (struct s5scc_local *) dev->priv;

    return &lp->stats;
}

#ifdef MODULE
int init_module(void)
{
    register_symtab(NULL);
    return s5scc_init();
}

void cleanup_module(void)
{
    free_irq(s5scca.irq, NULL);	/* IRQs and IO Ports are shared */
    release_region(s5scca.base_addr & 0x3f0, S5SCC_TOTAL_SIZE);
    irq2dev_map[s5scca.irq] = NULL;
    free_dma (dma);

    if (s5scca.priv != NULL) kfree(s5scca.priv);
    s5scca.priv = NULL;
    unregister_netdev(&s5scca);

    if (s5sccb.priv != NULL) kfree(s5sccb.priv);
    s5sccb.priv = NULL;
    unregister_netdev(&s5sccb);
}
#endif
