summaryrefslogtreecommitdiffstats
path: root/drivers/char/scc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/scc.c')
-rw-r--r--drivers/char/scc.c2305
1 files changed, 2305 insertions, 0 deletions
diff --git a/drivers/char/scc.c b/drivers/char/scc.c
new file mode 100644
index 000000000..81cbd9e5e
--- /dev/null
+++ b/drivers/char/scc.c
@@ -0,0 +1,2305 @@
+#include <linux/autoconf.h> /* fastest method */
+#ifdef CONFIG_SCC
+
+#define RCS_ID "$Id: scc.c,v 1.17 1995/03/15 23:28:12 JReuter Exp JReuter $"
+
+#define BANNER "Z8530 SCC driver v1.8.dl1bke (beta) by dl1bke\n"
+
+/* ******************************************************************** */
+/* * SCC.C - Linux driver for Z8530 based HDLC cards for AX.25 * */
+/* ******************************************************************** */
+
+/* ********************************************************************
+
+ (c) 1995 by Joerg Reuter DL1BKE
+
+ portions (c) 1994 Hans Alblas PE1AYX
+ and (c) 1993 Guido ten Dolle PE1NNZ
+
+ for the complete copyright notice see >> Copying.Z8530DRV <<
+
+ ******************************************************************** */
+
+/*
+
+ 940913 - started to rewrite the drive
+ 950131 - changed copyright notice to GPL without limitations.
+ 950228 - (hopefully) fixed the reason for kernel panics in
+ chk_rcv_queue() [stupid error]
+ 950304 - fixed underrun/zcount handling
+ 950305 - the driver registers port addresses now
+ 950314 - fixed underrun interrupt handling again
+
+ Thanks to:
+
+ PE1CHL Rob - for a lot of good ideas from his SCC driver for DOS
+ PE1NNZ Guido - for his port of the original driver to Linux
+ KA9Q Phil - from whom we stole the mbuf-structure
+ PA3AYX Hans - who rewrote the memory management and some minor,
+ but nevertheless useful changes
+ DL8MBT Flori - for support
+ DG0FT Rene - for the BayCom USCC support
+ PA3AOU Harry - for ESCC testing, information supply and support
+
+ PE1KOX Rob, DG1RTF Thomas, ON5QK Roland,
+
+ and all who sent me bug reports and ideas...
+
+
+ NB -- if you find errors, change something, please let me know
+ first before you distribute it... And please don't touch
+ the version number. Just replace my callsign in
+ "v1.8.dl1bke" with your own. Just to avoid confusion...
+
+ Jörg Reuter DL1BKE
+
+*/
+
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/tqueue.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+#include <linux/termios.h>
+#include <linux/serial.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/malloc.h>
+#include <linux/scc.h>
+#include <linux/delay.h>
+#include "scc_config.h"
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <linux/kernel.h>
+
+
+long scc_init(long kmem_start);
+
+int scc_open(struct tty_struct *tty, struct file *filp);
+static void scc_close(struct tty_struct *tty, struct file *filp);
+int scc_write(struct tty_struct *tty, int from_user, unsigned char *buf, int count);
+static void scc_put_char(struct tty_struct *tty, unsigned char ch);
+static void scc_flush_chars(struct tty_struct *tty);
+static int scc_write_room(struct tty_struct *tty);
+static int scc_chars_in_buffer(struct tty_struct *tty);
+static void scc_flush_buffer(struct tty_struct *tty);
+static int scc_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void scc_set_termios(struct tty_struct *tty, struct termios *old_termios);
+static void scc_throttle(struct tty_struct *tty);
+static void scc_unthrottle(struct tty_struct *tty);
+static void scc_start(struct tty_struct *tty);
+static void scc_stop(struct tty_struct *tty);
+
+static void z8530_init(void);
+
+static void scc_change_speed(struct scc_channel *scc);
+static void kiss_encode(struct scc_channel *scc);
+
+static void init_channel(struct scc_channel *scc);
+static void scc_key_trx (struct scc_channel *scc, char tx);
+static void scc_txint(register struct scc_channel *scc);
+static void scc_exint(register struct scc_channel *scc);
+static void scc_rxint(register struct scc_channel *scc);
+static void scc_spint(register struct scc_channel *scc);
+static void scc_isr(int irq, struct pt_regs *regs);
+static void scc_timer(void);
+static void scc_init_timer(struct scc_channel *scc);
+
+/* from serial.c */
+
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 0 };
+
+
+struct tty_driver scc_driver; /* new in 1.1.xx */
+static int scc_refcount;
+static struct tty_struct *scc_table[2*MAXSCC];
+static struct termios scc_termios[2 * MAXSCC];
+static struct termios scc_termios_locked[2 * MAXSCC];
+
+struct scc_channel SCC_Info[2 * MAXSCC]; /* information per channel */
+
+unsigned char Random = 0; /* random number for p-persist */
+unsigned char Driver_Initialized = 0;
+static struct sccbuf *sccfreelist[MAX_IBUFS] = {0};
+static int allocated_ibufs = 0;
+
+
+/* ******************************************************************** */
+/* * Port Access Functions * */
+/* ******************************************************************** */
+
+static inline unsigned char
+InReg(register io_port port, register unsigned char reg)
+{
+#ifdef SCC_LDELAY
+ register unsigned char r;
+ Outb(port, reg);
+ udelay(5);
+ r=Inb(port);
+ udelay(5);
+ return r;
+#else
+ Outb(port, reg);
+ return Inb(port);
+#endif
+}
+
+static inline void
+OutReg(register io_port port, register unsigned char reg, register unsigned char val)
+{
+#ifdef SCC_LDELAY
+ Outb(port, reg); udelay(5);
+ Outb(port, val); udelay(5);
+#else
+ Outb(port, reg);
+ Outb(port, val);
+#endif
+}
+
+static inline void
+wr(register struct scc_channel *scc, register unsigned char reg, register unsigned char val)
+{
+ OutReg(scc->ctrl, reg, (scc->wreg[reg] = val));
+}
+
+static inline void
+or(register struct scc_channel *scc, register unsigned char reg, register unsigned char val)
+{
+ OutReg(scc->ctrl, reg, (scc->wreg[reg] |= val));
+}
+
+static inline void
+cl(register struct scc_channel *scc, register unsigned char reg, register unsigned char val)
+{
+ OutReg(scc->ctrl, reg, (scc->wreg[reg] &= ~val));
+}
+
+/* ******************************************************************** */
+/* * Memory Buffer Management */
+/* ******************************************************************** */
+
+/* mbuf concept lent from KA9Q. Tnx PE1AYX for the buffer pool concept */
+/* (sorry, do you have any better ideas?) */
+
+
+/* allocate memory for the interrupt buffer pool */
+
+void scc_alloc_buffer_pool(void)
+{
+ int i;
+ struct sccbuf *sccb;
+ struct mbuf *bp;
+
+ for (i = 0 ; i < MAX_IBUFS ; i++)
+ {
+ sccb = (struct sccbuf *)kmalloc(sizeof(struct sccbuf), GFP_ATOMIC);
+ bp = (struct mbuf *)kmalloc(sizeof(struct mbuf), GFP_ATOMIC);
+
+ if ( !(sccb && bp) )
+ {
+ allocated_ibufs = --i;
+
+ if (allocated_ibufs < 0)
+ panic("scc_alloc_buffer_pool() - can't get buffer space");
+ else
+ printk("Warning: scc_alloc_buffer_pool() - allocated only %i buffers\n",i);
+
+ return;
+ }
+
+ sccfreelist[i] = sccb;
+ sccfreelist[i]->bp = bp;
+ memset(sccfreelist[i]->bp ,0,sizeof(struct mbuf));
+ sccfreelist[i]->inuse = 0;
+ sccfreelist[i]->bp->type = 0;
+ sccfreelist[i]->bp->refcnt = 0;
+ sccfreelist[i]->bp->size = BUFSIZE;
+ }
+ allocated_ibufs = MAX_IBUFS;
+}
+
+unsigned int scc_count_used_buffers(unsigned int * rx, unsigned int * tx)
+{
+ unsigned int i, used = 0;
+
+ if (rx) *rx = 0;
+ if (tx) *tx = 0;
+
+ for (i = 0 ; i < allocated_ibufs ; i++)
+ {
+ if (sccfreelist[i]->inuse)
+ {
+ switch (sccfreelist[i]->bp->type)
+ {
+ case BT_RECEIVE:
+ if (rx) (*rx)++; break;
+ case BT_TRANSMIT:
+ if (tx) (*tx)++; break;
+ }
+
+ used++;
+ }
+ }
+
+ return used;
+}
+
+
+/* Allocate mbuf */
+struct mbuf *
+scc_get_buffer(char type)
+{
+ int i;
+ unsigned long flags;
+
+ save_flags(flags); cli(); /* just to be sure */
+
+ for (i = 0 ; i < allocated_ibufs ; i++)
+ {
+ if(sccfreelist[i]->inuse == 0)
+ {
+ sccfreelist[i]->inuse = 1;
+ sccfreelist[i]->bp->type = type;
+ sccfreelist[i]->bp->next = NULLBUF;
+ sccfreelist[i]->bp->anext = NULLBUF;
+ sccfreelist[i]->bp->dup = NULLBUF;
+ sccfreelist[i]->bp->size = BUFSIZE;
+ sccfreelist[i]->bp->refcnt = 1;
+ sccfreelist[i]->bp->cnt = 0;
+ sccfreelist[i]->bp->in_use = 0;
+
+ restore_flags(flags);
+ return sccfreelist[i]->bp;
+ }
+ }
+
+ printk("\nSCC scc_get_buffer(): buffer pool empty\n"); /* should never happen */
+ restore_flags(flags);
+ return NULLBUF;
+}
+
+
+/* Decrement the reference pointer in an mbuf. If it goes to zero,
+ * free all resources associated with mbuf.
+ * Return pointer to next mbuf in packet chain
+ */
+struct mbuf *
+scc_return_buffer(register struct mbuf *bp, char type)
+{
+ struct mbuf *bpnext;
+ int i;
+ unsigned long flags;
+
+ if(!bp)
+ return NULLBUF;
+
+ save_flags(flags); cli();
+ bpnext = bp->next;
+
+ if (bp->dup)
+ {
+ for(i = 0 ; i < allocated_ibufs ; i++)
+ {
+ if(sccfreelist[i]->bp == bp->dup)
+ {
+ if (sccfreelist[i]->bp->type != type)
+ {
+ printk("scc_return_buffer(bp->dup, %i): wrong buffer type %i",
+ type,sccfreelist[i]->bp->type);
+ }
+
+ sccfreelist[i]->bp->cnt = 0;
+ sccfreelist[i]->bp->refcnt = 0;
+ sccfreelist[i]->bp->in_use = 0;
+ sccfreelist[i]->inuse = 0;
+ bp->dup = NULLBUF;
+ }
+ }
+ }
+
+ /* Decrement reference count. If it has gone to zero, free it. */
+ if(--bp->refcnt <= 0)
+ {
+ for(i = 0 ; i < allocated_ibufs ; i++)
+ {
+ if(sccfreelist[i]->bp == bp)
+ {
+ if (sccfreelist[i]->bp->type != type)
+ {
+ printk("scc_return_buffer(bp, %i): wrong buffer type %i",
+ type,sccfreelist[i]->bp->type);
+ }
+
+ sccfreelist[i]->bp->cnt = 0;
+ sccfreelist[i]->bp->refcnt = 0;
+ sccfreelist[i]->inuse = 0;
+ restore_flags(flags);
+ return bpnext;
+ }
+ }
+ }
+
+ printk("\nscc_return_buffer(): bogus pointer %p\n",bp);
+ restore_flags(flags);
+ return bpnext;
+}
+
+
+/* Free packet (a chain of mbufs). Return pointer to next packet on queue,
+ * if any
+ */
+struct mbuf *
+scc_free_chain(register struct mbuf *bp, char type)
+{
+ register struct mbuf *abp;
+ unsigned long flags;
+
+ if(!bp)
+ return NULLBUF;
+
+ save_flags(flags); cli();
+
+ abp = bp->anext;
+ while (bp) bp = scc_return_buffer(bp, type);
+
+ restore_flags(flags); cli();
+ return abp;
+}
+
+
+/* Append mbuf to end of mbuf chain */
+void
+scc_append_to_chain(struct mbuf **bph,struct mbuf *bp)
+{
+ register struct mbuf *p;
+ unsigned long flags;
+
+ if(bph == NULLBUFP || bp == NULLBUF)
+ return;
+
+ save_flags(flags); cli();
+
+ if(*bph == NULLBUF)
+ {
+ /* First one on chain */
+ *bph = bp;
+ } else {
+ for(p = *bph ; p->next != NULLBUF ; p = p->next)
+ ;
+ p->next = bp;
+ }
+
+ restore_flags(flags);
+}
+
+
+/* Append packet (chain of mbufs) to end of packet queue */
+void
+scc_enqueue(struct mbuf **queue,struct mbuf *bp)
+{
+ register struct mbuf *p;
+ unsigned long flags;
+
+ if(queue == NULLBUFP || bp == NULLBUF)
+ return;
+
+ save_flags(flags); cli();
+
+ if(*queue == NULLBUF)
+ {
+ /* List is empty, stick at front */
+ *queue = bp;
+ } else {
+ for(p = *queue ; p->anext != NULLBUF ; p = p->anext)
+ ;
+ p->anext = bp;
+ }
+ restore_flags(flags);
+}
+
+
+/* ******************************************************************** */
+/* * Interrupt Service Routines * */
+/* ******************************************************************** */
+
+/* ----> interrupt service routine for the 8530 <---- */
+
+/* it's recommendet to keep this function "inline" ;-) */
+
+static inline void
+scc_isr_dispatch(register struct scc_channel *scc, register int vector)
+{
+ switch (vector & VECTOR_MASK)
+ {
+ case TXINT: scc_txint(scc); break;
+ case EXINT: scc_exint(scc); break;
+ case RXINT: scc_rxint(scc); break;
+ case SPINT: scc_spint(scc); break;
+ default : printk("scc_isr(): unknown interrupt status (addr %4.4x, state %2.2x)\n",scc->ctrl,vector);
+ }
+}
+
+
+
+
+
+/* If the card has a latch for the interrupt vector (like the PA0HZP card)
+ use it to get the number of the chip that generated the int.
+ If not: poll all defined chips.
+ */
+
+static void
+scc_isr(int irq, struct pt_regs *regs)
+{
+ register unsigned char vector;
+ register struct scc_channel *scc;
+ register io_port q;
+ register io_port *p;
+ register int k;
+
+
+ cli();
+
+ if (Vector_Latch)
+ {
+ while(1) /* forever...? */
+ {
+ Outb(Vector_Latch, 0); /* Generate INTACK */
+
+ /* Read the vector */
+ if((vector=Inb(Vector_Latch)) >= 16 * Nchips) break;
+ /* ...not forever! */
+
+ /* Extract channel number and status from vector. */
+ /* Isolate channel nummer */
+ /* Call handler */
+
+ if (vector & 0x01) break;
+
+ scc=&SCC_Info[(((vector>>1)&0x7c)^0x04) >> 2];
+
+ if (!scc->tty) break;
+
+ scc_isr_dispatch(scc, vector);
+
+ Outb(scc->ctrl,0x38); /* Reset Highest IUS" opcode to WR0 */
+ }
+
+ sti();
+ return;
+ }
+
+
+ /* Find the SCC generating the interrupt by polling all attached SCCs
+ * reading RR3A (the interrupt pending register)
+ */
+
+ k = 0;
+ p = SCC_ctrl;
+
+ while (k++ < Nchips)
+ {
+ if (!(q=*p++)) break;
+
+ Outb(q,3);
+
+ if (Inb(q))
+ {
+ if (!(q=*p++)) break;
+
+ Outb(q,2);
+ vector=Inb(q); /* Read the vector */
+
+ /* Extract channel number and status from vector. */
+ /* Isolate channel nummer */
+ /* Call handler */
+
+
+ if (vector & 1) break;
+
+ scc = &SCC_Info[(((vector >> 1) & 0x7c) ^ 0x04) >> 2];
+
+ if (!scc->tty) break;
+
+ /* Isolate status info from vector, call handler */
+
+ scc_isr_dispatch(scc, vector);
+
+ k = 0;
+ p = SCC_ctrl;
+
+ } else p++;
+ }
+
+ sti();
+}
+
+
+
+/* ----> four different interrupt handlers for Tx, Rx, changing of */
+/* DCD/CTS and Rx/Tx errors */
+
+
+static inline void prepare_next_txframe(register struct scc_channel *scc)
+{
+ if ((scc->tbp = scc->sndq))
+ {
+ scc->sndq = scc->sndq->anext;
+ scc->stat.tx_state = TXS_NEWFRAME;
+
+ } else {
+ scc->stat.tx_state = TXS_BUSY;
+ scc->t_tail = scc->kiss.tailtime;
+ }
+}
+
+
+/* Transmitter interrupt handler */
+static void
+scc_txint(register struct scc_channel *scc)
+{
+ register struct mbuf *bp;
+
+ scc->stat.txints++;
+
+ bp = scc->tbp;
+
+ while (bp && !bp->cnt) /* find next buffer */
+ bp = scc_return_buffer(bp, BT_TRANSMIT);
+
+ if (bp == NULLBUF) /* no more buffers in this frame */
+ {
+ if (--scc->stat.tx_queued < 0)
+ scc->stat.tx_queued = 0;
+
+ Outb(scc->ctrl,RES_Tx_P); /* reset pending int */
+ cl(scc,R10,ABUNDER); /* frame complete, allow CRC transmit */
+ prepare_next_txframe(scc);
+
+ } else { /* okay, send byte */
+
+ if (scc->stat.tx_state == TXS_NEWFRAME)
+ { /* first byte ? */
+ Outb(scc->ctrl, RES_Tx_CRC); /* reset CRC generator */
+ or(scc,R10,ABUNDER); /* re-install underrun protection */
+ Outb(scc->data,bp->data[bp->in_use++]);
+ /* send byte */
+ if (!scc->enhanced) /* reset EOM latch */
+ Outb(scc->ctrl, RES_EOM_L);
+
+ scc->stat.tx_state = TXS_ACTIVE;/* next byte... */
+ } else {
+ Outb(scc->data,bp->data[bp->in_use++]);
+ }
+
+ bp->cnt--; /* decrease byte count */
+ scc->tbp=bp; /* store buffer address */
+ }
+}
+
+/* Throw away received mbuf(s) when an error occurred */
+
+static inline void
+scc_toss_buffer(register struct scc_channel *scc)
+{
+ register struct mbuf *bp;
+
+ if((bp = scc->rbp) != NULLBUF)
+ {
+ scc_free_chain(bp->next, BT_RECEIVE);
+ bp->next = NULLBUF;
+ scc->rbp1 = bp; /* Don't throw this one away */
+ bp->cnt = 0; /* Simply rewind it */
+ bp->in_use = 0;
+ }
+}
+
+static inline void
+flush_FIFO(register struct scc_channel *scc)
+{
+ register int k;
+
+ for (k=0; k<3; k++)
+ Inb(scc->data);
+
+ if(scc->rbp != NULLBUF) /* did we receive something? */
+ {
+ if(scc->rbp->next != NULLBUF || scc->rbp->cnt > 0)
+ scc->stat.rxerrs++; /* then count it as an error */
+
+ scc_toss_buffer(scc); /* throw away buffer */
+ }
+}
+
+
+
+/* External/Status interrupt handler */
+static void
+scc_exint(register struct scc_channel *scc)
+{
+ register unsigned char status,changes,chg_and_stat;
+
+ scc->stat.exints++;
+
+ status = InReg(scc->ctrl,R0);
+ changes = status ^ scc->status;
+ chg_and_stat = changes & status;
+
+ /* ABORT: generated whenever DCD drops while receiving */
+
+ if (chg_and_stat & BRK_ABRT) /* Received an ABORT */
+ flush_FIFO(scc);
+
+
+ /* DCD: on = start to receive packet, off = ABORT condition */
+ /* (a successfully received packet generates a special condition int) */
+
+ if(changes & DCD) /* DCD input changed state */
+ {
+ if(status & DCD) /* DCD is now ON */
+ {
+ if (scc->modem.clocksrc != CLK_EXTERNAL)
+ OutReg(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
+
+ or(scc,R3,ENT_HM|RxENABLE); /* enable the receiver, hunt mode */
+ } else { /* DCD is now OFF */
+ cl(scc,R3,ENT_HM|RxENABLE); /* disable the receiver */
+ flush_FIFO(scc);
+ }
+ }
+
+
+ /* CTS: use external TxDelay (what's that good for?!) */
+
+ if (chg_and_stat & CTS) /* CTS is now ON */
+ {
+ if (!Running(t_txdel) && scc->kiss.txdelay == 0) /* zero TXDELAY = wait for CTS */
+ scc->t_txdel = 0; /* kick it! */
+
+ }
+
+ if ((scc->stat.tx_state == TXS_ACTIVE) && (status & TxEOM))
+ {
+ scc->stat.tx_under++; /* oops, an underrun! count 'em */
+ Outb(scc->ctrl, RES_Tx_P);
+ Outb(scc->ctrl, RES_EXT_INT); /* reset ext/status interrupts */
+ scc->t_maxk = 1;
+ scc->tbp = scc_free_chain(scc->tbp, BT_TRANSMIT);
+
+ if (--scc->stat.tx_queued < 0) scc->stat.tx_queued = 0;
+ or(scc,R10,ABUNDER);
+ }
+
+ if (status & ZCOUNT) /* Oops? */
+ {
+ scc->stat.tx_under = 9999; /* errr... yes. */
+ Outb(scc->ctrl, RES_Tx_P); /* just to be sure */
+ scc->t_maxk = 1;
+ scc->tbp = scc_free_chain(scc->tbp, BT_TRANSMIT);
+ if (--scc->stat.tx_queued < 0) scc->stat.tx_queued = 0;
+ scc->kiss.tx_inhibit = 1; /* don't try it again! */
+ }
+
+
+ scc->status = status;
+ Outb(scc->ctrl,RES_EXT_INT);
+}
+
+
+/* Receiver interrupt handler */
+static void
+scc_rxint(register struct scc_channel *scc)
+{
+ register struct mbuf *bp;
+
+ scc->stat.rxints++;
+
+ if( Running(t_maxk) && !(scc->kiss.fulldup))
+ {
+ Inb(scc->data); /* discard char */
+ or(scc,R3,ENT_HM); /* enter hunt mode for next flag */
+ return;
+ }
+
+ if ((bp = scc->rbp1) == NULLBUF || bp->cnt >= bp->size)
+ { /* no buffer available or buffer full */
+ if (scc->rbp == NULLBUF)
+ {
+ if ((bp = scc_get_buffer(BT_RECEIVE)) != NULLBUF)
+ scc->rbp = scc->rbp1 = bp;
+
+ }
+ else if ((bp = scc_get_buffer(BT_RECEIVE)))
+ {
+ scc_append_to_chain(&scc->rbp, bp);
+ scc->rbp1 = bp;
+ }
+
+ if (bp == NULLBUF) /* no buffer available? */
+ {
+ Inb(scc->data); /* discard character */
+ or(scc,R3,ENT_HM); /* enter hunt mode */
+ scc_toss_buffer(scc); /* throw away buffers */
+ scc->stat.nospace++; /* and count this error */
+ return;
+ }
+ }
+
+ /* now, we have a buffer. read character and store it */
+ bp->data[bp->cnt++] = Inb(scc->data);
+}
+
+
+/* Receive Special Condition interrupt handler */
+static void
+scc_spint(register struct scc_channel *scc)
+{
+ register unsigned char status;
+ register struct mbuf *bp;
+
+ scc->stat.spints++;
+
+ status = InReg(scc->ctrl,R1); /* read receiver status */
+
+ Inb(scc->data); /* throw away Rx byte */
+
+ if(status & Rx_OVR) /* receiver overrun */
+ {
+ scc->stat.rx_over++; /* count them */
+ or(scc,R3,ENT_HM); /* enter hunt mode for next flag */
+ scc_toss_buffer(scc); /* rewind the buffer and toss */
+ }
+
+ if(status & END_FR && scc->rbp != NULLBUF) /* end of frame */
+ {
+ /* CRC okay, frame ends on 8 bit boundary and received something ? */
+
+ if (!(status & CRC_ERR) && (status & 0xe) == RES8 && scc->rbp->cnt)
+ {
+ /* ignore last received byte (first of the CRC bytes) */
+
+ for (bp = scc->rbp; bp->next != NULLBUF; bp = bp->next) ;
+ bp->cnt--; /* last byte is first CRC byte */
+
+ scc_enqueue(&scc->rcvq,scc->rbp);
+ scc->rbp = scc->rbp1 = NULLBUF;
+ scc->stat.rxframes++;
+ scc->stat.rx_queued++;
+ } else { /* a bad frame */
+ scc_toss_buffer(scc); /* throw away frame */
+ scc->stat.rxerrs++;
+ }
+ }
+
+ Outb(scc->ctrl,ERR_RES);
+}
+
+
+/* ******************************************************************** */
+/* * Init Channel */
+/* ******************************************************************** */
+
+
+/* ----> set SCC channel speed <---- */
+
+static inline void set_brg(register struct scc_channel *scc, unsigned int tc)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli(); /* 2-step register accesses... */
+
+ cl(scc,R14,BRENABL); /* disable baudrate generator */
+ wr(scc,R12,tc & 255); /* brg rate LOW */
+ wr(scc,R13,tc >> 8); /* brg rate HIGH */
+ or(scc,R14,BRENABL); /* enable baudrate generator */
+
+ restore_flags(flags);
+}
+
+static inline void set_speed(register struct scc_channel *scc)
+{
+ set_brg(scc, (unsigned) (Clock / (scc->modem.speed * 64)) - 2);
+}
+
+
+/* ----> initialize a SCC channel <---- */
+
+static inline void init_brg(register struct scc_channel *scc)
+{
+ wr(scc, R14, BRSRC); /* BRG source = PCLK */
+ OutReg(scc->ctrl, R14, SSBR|scc->wreg[R14]); /* DPLL source = BRG */
+ OutReg(scc->ctrl, R14, SNRZI|scc->wreg[R14]); /* DPLL NRZI mode */
+}
+
+static void
+init_channel(register struct scc_channel *scc)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ wr(scc,R1,0); /* no W/REQ operation */
+ wr(scc,R3,Rx8|RxCRC_ENAB); /* RX 8 bits/char, CRC, disabled */
+ wr(scc,R4,X1CLK|SDLC); /* *1 clock, SDLC mode */
+ wr(scc,R5,Tx8|DTR|TxCRC_ENAB); /* TX 8 bits/char, disabled, DTR */
+ wr(scc,R6,0); /* SDLC address zero (not used) */
+ wr(scc,R7,FLAG); /* SDLC flag value */
+ wr(scc,R9,VIS); /* vector includes status */
+ wr(scc,R10,(scc->modem.nrz? NRZ : NRZI)|CRCPS|ABUNDER); /* abort on underrun, preset CRC generator, NRZ(I) */
+ wr(scc,R14, 0);
+
+
+/* set clock sources:
+
+ CLK_DPLL: normal halfduplex operation
+
+ RxClk: use DPLL
+ TxClk: use DPLL
+ TRxC mode DPLL output
+
+ CLK_EXTERNAL: external clocking (G3RUH or DF9IC modem)
+
+ BayCom: others:
+
+ TxClk = pin RTxC TxClk = pin TRxC
+ RxClk = pin TRxC RxClk = pin RTxC
+
+
+ CLK_DIVIDER:
+ RxClk = use DPLL
+ TxClk = pin RTxC
+
+ BayCom: others:
+ pin TRxC = DPLL pin TRxC = BRG
+ (RxClk * 1) (RxClk * 32)
+*/
+
+
+ switch(scc->modem.clocksrc)
+ {
+ case CLK_DPLL:
+ wr(scc, R11, RCDPLL|TCDPLL|TRxCOI|TRxCDP);
+ init_brg(scc);
+ break;
+
+ case CLK_DIVIDER:
+ wr(scc, R11, ((Board & BAYCOM)? TRxCDP : TRxCBR) | RCDPLL|TCRTxCP|TRxCOI);
+ init_brg(scc);
+ break;
+
+ case CLK_EXTERNAL:
+ wr(scc, R11, (Board & BAYCOM)? RCTRxCP|TCRTxCP : RCRTxCP|TCTRxCP);
+ OutReg(scc->ctrl, R14, DISDPLL);
+ break;
+
+ }
+
+ /* enable CTS (not for Baycom), ABORT & DCD interrupts */
+ wr(scc,R15,((Board & BAYCOM) ? 0 : CTSIE)|BRKIE|DCDIE|TxUIE);
+
+ if(scc->enhanced)
+ {
+ or(scc,R15,SHDLCE|FIFOE); /* enable FIFO, SDLC/HDLC Enhancements (From now R7 is R7') */
+ wr(scc,R7,AUTOEOM);
+ }
+
+ if((InReg(scc->ctrl,R0)) & DCD) /* DCD is now ON */
+ {
+ if (scc->modem.clocksrc != CLK_EXTERNAL)
+ or(scc,R14, SEARCH);
+
+ or(scc,R3,ENT_HM|RxENABLE); /* enable the receiver, hunt mode */
+ }
+
+ Outb(scc->ctrl,RES_EXT_INT); /* reset ext/status interrupts */
+ Outb(scc->ctrl,RES_EXT_INT); /* must be done twice */
+
+ scc->status = InReg(scc->ctrl,R0); /* read initial status */
+
+ or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
+ or(scc,R9,MIE); /* master interrupt enable */
+
+ restore_flags(flags);
+
+ set_speed(scc);
+}
+
+
+
+
+/* ******************************************************************** */
+/* * SCC timer functions * */
+/* ******************************************************************** */
+
+
+/* ----> scc_key_trx sets the time constant for the baudrate
+ generator and keys the transmitter <---- */
+
+static void
+scc_key_trx(struct scc_channel *scc, char tx)
+{
+ unsigned int time_const;
+
+ if (scc->modem.speed < baud_table[1])
+ scc->modem.speed = 1200;
+
+ if (Board & PRIMUS)
+ Outb(scc->ctrl + 4, Option | (tx? 0x80 : 0));
+
+ time_const = (unsigned) (Clock / (scc->modem.speed * (tx? 2:64))) - 2;
+
+ if (scc->modem.clocksrc == CLK_DPLL)
+ { /* simplex operation */
+ if (tx)
+ {
+ cl(scc,R3,RxENABLE|ENT_HM); /* then switch off receiver */
+
+ set_brg(scc, time_const); /* reprogram baudrate generator */
+
+ /* DPLL -> Rx clk, BRG -> Tx CLK, TRxC mode output, TRxC = BRG */
+ wr(scc, R11, RCDPLL|TCBR|TRxCOI|TRxCBR);
+
+ or(scc,R5,RTS|TxENAB); /* set the RTS line and enable TX */
+ } else {
+ cl(scc,R5,RTS|TxENAB);
+
+ set_brg(scc, time_const); /* reprogram baudrate generator */
+
+ /* DPLL -> Rx clk, DPLL -> Tx CLK, TRxC mode output, TRxC = DPLL */
+ wr(scc, R11, RCDPLL|TCDPLL|TRxCOI|TRxCDP);
+
+ or(scc,R3,RxENABLE|ENT_HM);
+ }
+ } else {
+ if (tx)
+ or(scc,R5,RTS|TxENAB); /* enable tx */
+ else
+ cl(scc,R5,RTS|TxENAB); /* disable tx */
+ }
+}
+
+
+/* ----> SCC timer interrupt handler and friends. Will be called every 1/TPS s <---- */
+
+static unsigned char Rand = 17;
+
+static inline int is_grouped(register struct scc_channel *scc)
+{
+ int k;
+ struct scc_channel *scc2;
+ unsigned char grp1, grp2;
+
+ grp1 = scc->kiss.group;
+
+ for (k = 0; k < (Nchips * 2); k++)
+ {
+ scc2 = &SCC_Info[k];
+ grp2 = scc2->kiss.group;
+
+ if (scc2 == scc || !(scc2->tty && grp2))
+ return 0;
+
+ if (grp1 & 0x3f == grp2 & 0x3f)
+ {
+ if ( (grp1 & TXGROUP) && (scc2->wreg[R5] & RTS) )
+ return 1;
+
+ if ( (grp1 & RXGROUP) && (scc2->status & DCD) )
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static inline void dw_slot_timeout(register struct scc_channel *scc)
+{
+ scc->t_dwait = TIMER_STOPPED;
+ scc->t_slot = TIMER_STOPPED;
+
+ if (!scc->kiss.fulldup)
+ {
+ Rand = Rand * 17 + 31;
+
+ if ( (scc->kiss.softdcd? !(scc->status & SYNC_HUNT):(scc->status & DCD)) || (scc->kiss.persist) < Rand || (scc->kiss.group && is_grouped(scc)) )
+ {
+ if (scc->t_mbusy == TIMER_STOPPED)
+ scc->t_mbusy = TPS * scc->kiss.maxdefer;
+
+ scc->t_slot = scc->kiss.slottime;
+ return ;
+
+ }
+ }
+
+ if ( !(scc->wreg[R5] & RTS) )
+ {
+ scc->t_txdel = scc->kiss.txdelay;
+ scc_key_trx(scc, TX_ON);
+ } else {
+ scc->t_txdel = 0;
+ }
+}
+
+
+
+
+static inline void txdel_timeout(register struct scc_channel *scc)
+{
+ scc->t_txdel = TIMER_STOPPED;
+
+ scc->t_maxk = TPS * scc->kiss.maxkeyup;
+ prepare_next_txframe(scc);
+
+ if (scc->stat.tx_state != TXS_BUSY)
+ scc_txint(scc);
+}
+
+
+
+static inline void tail_timeout(register struct scc_channel *scc)
+{
+ scc->t_tail = TIMER_STOPPED;
+
+ /* when fulldup is 0 or 1, switch off the transmitter.
+ * when frames are still queued (because of transmit time limit),
+ * restart the procedure to get the channel after MINTIME.
+ * when fulldup is 2, the transmitter remains keyed and we
+ * continue sending after waiting for waittime. IDLETIME is an
+ * idle timeout in this case.
+ */
+
+ if (scc->kiss.fulldup < 2)
+ {
+ if (scc->sndq) /* we had a timeout? */
+ {
+ scc->stat.tx_state = TXS_BUSY;
+ scc->t_dwait = TPS * scc->kiss.mintime; /* try again */
+ }
+
+ scc->stat.tx_state = TXS_IDLE;
+ scc->t_maxk = TIMER_STOPPED;
+ scc_key_trx(scc, TX_OFF);
+ return;
+ }
+
+ if (scc->sndq) /* maxkeyup expired */ /* ?! */
+ {
+ scc->stat.tx_state = TXS_BUSY;
+ scc->t_txdel = TPS * scc->kiss.waittime;
+ }
+ else
+
+ scc->t_idle = TPS * scc->kiss.idletime;
+}
+
+
+static inline void busy_timeout(register struct scc_channel *scc)
+{
+#ifdef THROW_AWAY_AFTER_BUSY_TIMEOUT
+ register struct mbuf *bp; /* not tested */
+
+ bp = scc->sndq;
+
+ while (bp) bp = scc_free_chain(bp, BT_TRANSMIT);
+
+ scc->sndq = NULLBUF;
+ scc->stat.tx_state = TXS_IDLE;
+
+#else
+ scc->t_txdel = scc->kiss.txdelay; /* brute force ... */
+#endif
+ scc->t_mbusy = TIMER_STOPPED;
+
+}
+
+
+static inline void maxk_idle_timeout(register struct scc_channel *scc)
+{
+ scc->t_maxk = TIMER_STOPPED;
+ scc->t_idle = TIMER_STOPPED;
+
+ scc->stat.tx_state = TXS_BUSY;
+ scc->t_tail = scc->kiss.tailtime;
+}
+
+static inline void check_rcv_queue(register struct scc_channel *scc)
+{
+ register struct mbuf *bp;
+
+ if (scc->stat.rx_queued > QUEUE_THRES)
+ {
+ if (scc->rcvq == NULLBUF)
+ {
+ printk("z8530drv: Warning - scc->stat.rx_queued shows overflow"
+ " (%d) but queue is empty\n", scc->stat.rx_queued);
+
+ scc->stat.rx_queued = 0; /* correct it */
+ scc->stat.nospace = 12345; /* draw attention to it */
+ return;
+ }
+
+ bp = scc->rcvq->anext; /* don't use the one we currently use */
+
+ while (bp && (scc->stat.rx_queued > QUEUE_HYST))
+ {
+ bp = scc_free_chain(bp, BT_RECEIVE);
+ scc->stat.rx_queued--;
+ scc->stat.nospace++;
+ }
+
+ scc->rcvq->anext = bp;
+ }
+}
+
+static void
+scc_timer(void)
+{
+ register struct scc_channel *scc;
+ register int chan;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ for (chan = 0; chan < (Nchips * 2); chan++)
+ {
+ scc = &SCC_Info[chan];
+
+ if (scc->tty && scc->init)
+ {
+ kiss_encode(scc);
+ check_rcv_queue(scc);
+
+ /* KISS-TNC emulation */
+
+ if (Expired(t_dwait)) dw_slot_timeout(scc) ; else
+ if (Expired(t_slot)) dw_slot_timeout(scc) ; else
+ if (Expired(t_txdel)) txdel_timeout(scc) ; else
+ if (Expired(t_tail)) tail_timeout(scc) ;
+
+ /* watchdogs */
+
+ if (Expired(t_mbusy)) busy_timeout(scc);
+ if (Expired(t_maxk)) maxk_idle_timeout(scc);
+ if (Expired(t_idle)) maxk_idle_timeout(scc);
+ }
+ }
+
+ timer_table[SCC_TIMER].fn = scc_timer;
+ timer_table[SCC_TIMER].expires = jiffies + HZ/TPS;
+ timer_active |= 1 << SCC_TIMER;
+
+ restore_flags(flags);
+}
+
+
+static void
+scc_init_timer(struct scc_channel *scc)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ Stop_Timer(t_dwait);
+ Stop_Timer(t_slot);
+ Stop_Timer(t_txdel);
+ Stop_Timer(t_tail);
+ Stop_Timer(t_mbusy);
+ Stop_Timer(t_maxk);
+ Stop_Timer(t_idle);
+ scc->stat.tx_state = TXS_IDLE;
+
+ restore_flags(flags);
+}
+
+
+
+/* ******************************************************************** */
+/* * KISS interpreter * */
+/* ******************************************************************** */
+
+
+/*
+ * this will set the "kiss" parameters through kiss itself
+ */
+
+static void
+kiss_set_param(struct scc_channel *scc,char cmd, unsigned int val)
+{
+
+#define VAL val=val*TPS/100
+#define SVAL val? val:TIMER_STOPPED
+
+ switch(cmd){
+ case PARAM_TXDELAY:
+ scc->kiss.txdelay = VAL; break;
+ case PARAM_PERSIST:
+ scc->kiss.persist = val; break;
+ case PARAM_SLOTTIME:
+ scc->kiss.slottime = VAL; break;
+ case PARAM_TXTAIL:
+ scc->kiss.tailtime = VAL; break;
+ case PARAM_FULLDUP:
+ scc->kiss.fulldup = val; break;
+ case PARAM_WAIT:
+ scc->kiss.waittime = VAL; break;
+ case PARAM_MAXKEY:
+ scc->kiss.maxkeyup = SVAL; break;
+ case PARAM_MIN:
+ scc->kiss.mintime = SVAL; break;
+ case PARAM_IDLE:
+ scc->kiss.idletime = val; break;
+ case PARAM_MAXDEFER:
+ scc->kiss.maxdefer = SVAL; break;
+ case PARAM_GROUP:
+ scc->kiss.group = val; break;
+ case PARAM_TX:
+ scc->kiss.tx_inhibit = val;
+ case PARAM_SOFTDCD:
+ scc->kiss.softdcd = val;
+ }
+ return;
+
+#undef VAL
+#undef SVAL
+}
+
+
+/* interpret frame: strip CRC and decode KISS */
+
+static void kiss_interpret_frame(struct scc_channel * scc)
+{
+ unsigned char kisscmd;
+ unsigned long flags;
+
+ if (scc->sndq1->cnt < 2)
+ {
+ if (scc->sndq1)
+ scc_free_chain(scc->sndq1, BT_TRANSMIT);
+ else
+ scc->sndq1 = NULLBUF;
+
+ scc->sndq2 = NULLBUF;
+ return;
+ }
+
+
+
+ if (scc->kiss.not_slip)
+ {
+ kisscmd = scc->sndq1->data[scc->sndq1->in_use++];
+ scc->sndq1->cnt--;
+ } else {
+ kisscmd = 0;
+ }
+
+ if (kisscmd & 0xa0)
+ {
+ if (scc->sndq1->cnt > 2)
+ scc->sndq1->cnt -= 2;
+ else
+ {
+ scc_free_chain(scc->sndq1, BT_TRANSMIT);
+ scc->sndq2 = NULLBUF;
+ return;
+ }
+ }
+
+
+ kisscmd &= 0x1f;
+
+
+ if (kisscmd)
+ {
+ kiss_set_param(scc, kisscmd, scc->sndq1->data[scc->sndq1->in_use]);
+ scc->sndq1->cnt=0;
+ scc->sndq1->in_use=0;
+
+ scc_free_chain(scc->sndq1, BT_TRANSMIT);
+ scc->sndq2 = NULLBUF;
+ return;
+ }
+
+ scc_enqueue(&scc->sndq,scc->sndq1); /* scc_enqueue packet */
+ scc->stat.txframes++;
+ scc->stat.tx_queued++;
+ scc->sndq2 = NULLBUF; /* acquire a new buffer next time */
+
+ save_flags(flags); cli();
+
+ if(scc->stat.tx_state == TXS_IDLE)
+ { /* when transmitter is idle */
+ scc_init_timer(scc);
+ scc->stat.tx_state = TXS_BUSY;
+ scc->t_dwait = (scc->kiss.fulldup? 0:scc->kiss.waittime);
+ }
+
+ restore_flags(flags);
+}
+
+static void kiss_store_byte(struct scc_channel *scc, unsigned char ch)
+{
+ if (scc->sndq2 == NULLBUF) return;
+
+ if(scc->sndq2->cnt == scc->sndq2->size) /* buffer full? */
+ {
+ if((scc->sndq2 = scc_get_buffer(BT_TRANSMIT)) == NULLBUF)
+ {
+ printk("\nsccdrv: running out of memory\n");
+ return;
+ }
+ scc_append_to_chain(&scc->sndq1,scc->sndq2); /* add buffer */
+ }
+
+ scc->sndq2->data[scc->sndq2->cnt++] = ch;
+}
+
+static inline int kiss_decode(struct scc_channel *scc, unsigned char ch)
+{
+ switch (scc->stat.tx_kiss_state)
+ {
+ case KISS_IDLE:
+ if (ch == FEND)
+ {
+ if (!(scc->sndq2 = scc->sndq1 = scc_get_buffer(BT_TRANSMIT)))
+ return 0;
+
+ scc->stat.tx_kiss_state = KISS_DATA;
+ } else scc->stat.txerrs++;
+ break;
+
+ case KISS_DATA:
+ if (ch == FESC)
+ scc->stat.tx_kiss_state = KISS_ESCAPE;
+ else if (ch == FEND)
+ {
+ kiss_interpret_frame(scc);
+ scc->stat.tx_kiss_state = KISS_IDLE;
+ }
+ else kiss_store_byte(scc, ch);
+ break;
+
+ case KISS_ESCAPE:
+ if (ch == TFEND)
+ {
+ kiss_store_byte(scc, FEND);
+ scc->stat.tx_kiss_state = KISS_DATA;
+ }
+ else if (ch == TFESC)
+ {
+ kiss_store_byte(scc, FESC);
+ scc->stat.tx_kiss_state = KISS_DATA;
+ }
+ else
+ {
+ scc_free_chain(scc->sndq1, BT_TRANSMIT);
+ scc->sndq2 = NULLBUF;
+ scc->stat.txerrs++;
+ scc->stat.tx_kiss_state = KISS_IDLE;
+ }
+ break;
+ } /* switch */
+
+ return 0;
+
+}
+
+/* ----> Encode received data and write it to the flip-buffer <---- */
+
+/* receive raw frame from SCC. used for AX.25 */
+static void
+kiss_encode(register struct scc_channel *scc)
+{
+ struct mbuf *bp,*bp2;
+ struct tty_struct * tty = scc->tty;
+ unsigned long flags;
+ unsigned char ch;
+
+ if(!scc->rcvq)
+ {
+ scc->stat.rx_kiss_state = KISS_IDLE;
+ return;
+ }
+
+ /* worst case: FEND 0 FESC TFEND -> 4 bytes */
+
+ while(tty->flip.count < TTY_FLIPBUF_SIZE-3)
+ {
+ if (scc->rcvq->cnt)
+ {
+ bp = scc->rcvq;
+
+ if (scc->stat.rx_kiss_state == KISS_IDLE)
+ {
+ tty_insert_flip_char(tty, FEND, 0);
+
+ if (scc->kiss.not_slip)
+ tty_insert_flip_char(tty, 0, 0);
+
+ scc->stat.rx_kiss_state = KISS_RXFRAME;
+ }
+
+ switch(ch = bp->data[bp->in_use++])
+ {
+ case FEND:
+ tty_insert_flip_char(tty, FESC, 0);
+ tty_insert_flip_char(tty, TFEND, 0);
+ break;
+ case FESC:
+ tty_insert_flip_char(tty, FESC, 0);
+ tty_insert_flip_char(tty, TFESC, 0);
+ break;
+ default:
+ tty_insert_flip_char(tty, ch, 0);
+ }
+
+ bp->cnt--;
+
+ } else {
+ save_flags(flags); cli();
+
+ while (!scc->rcvq->cnt)
+ { /* buffer empty? */
+ bp = scc->rcvq->next; /* next buffer */
+ bp2 = scc->rcvq->anext; /* next packet */
+
+
+ scc_return_buffer(scc->rcvq, BT_RECEIVE);
+
+ if (!bp) /* end of frame ? */
+ {
+ scc->rcvq = bp2;
+
+ if (--scc->stat.rx_queued < 0)
+ scc->stat.rx_queued = 0;
+
+ if (scc->stat.rx_kiss_state == KISS_RXFRAME) /* new packet? */
+ {
+ tty_insert_flip_char(tty, FEND, 0); /* send FEND for old frame */
+ scc->stat.rx_kiss_state = KISS_IDLE; /* generate FEND for new frame */
+ }
+
+ restore_flags(flags);
+ queue_task(&tty->flip.tqueue, &tq_timer);
+ return;
+
+ } else scc->rcvq = bp; /* next buffer */
+ }
+
+ restore_flags(flags);
+ }
+
+ }
+
+ queue_task(&tty->flip.tqueue, &tq_timer); /* kick it... */
+}
+
+
+/* ******************************************************************* */
+/* * Init channel structures, special HW, etc... * */
+/* ******************************************************************* */
+
+
+static void
+z8530_init(void)
+{
+ struct scc_channel *scc;
+ int chip,chan;
+ unsigned long flags;
+ int k;
+
+ /* reset and pre-init all chips in the system */
+ for (chip = 0; chip < Nchips; chip++)
+ {
+ /* Special SCC cards */
+
+ if(Board & EAGLE) /* this is an EAGLE card */
+ Outb(Special_Port,0x08); /* enable interrupt on the board */
+
+ if(Board & (PC100 | PRIMUS)) /* this is a PC100/EAGLE card */
+ Outb(Special_Port,Option); /* set the MODEM mode (22H normally) */
+
+ /* Init SCC */
+
+ scc=&SCC_Info[2*chip];
+ if (!scc->ctrl) continue;
+
+ save_flags(flags); cli();
+
+ Outb(scc->ctrl, 0);
+ OutReg(scc->ctrl,R9,FHWRES); /* force hardware reset */
+ for (k=1; k < 1000; k++) ;
+
+ for (chan = 0; chan < 2; chan++)
+ {
+ scc=&SCC_Info[2*chip+chan];
+
+ wr(scc,R1, 0);
+ wr(scc,R2, chip*16); /* No of chip is vector */
+ wr(scc,R9,VIS); /* vector includes status */
+ }
+
+ restore_flags(flags);
+ }
+
+ if (Ivec == 2) Ivec = 9; /* this f... IBM AT-design! */
+ request_irq(Ivec, scc_isr, SA_INTERRUPT, "AX.25 SCC");
+
+ Driver_Initialized = 1;
+}
+
+
+/* ******************************************************************** */
+/* * Filesystem Routines: open, close, ioctl, settermios, etc * */
+/* ******************************************************************** */
+
+
+
+/* scc_paranoia_check(): warn user if something went wrong */
+
+static inline int scc_paranoia_check(struct scc_channel *scc, dev_t device, const char *routine)
+{
+#ifdef SCC_PARANOIA_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for Z8530 SCC struct (%d, %d) in %s\n";
+ static const char *badinfo =
+ "Warning: Z8530 not found for (%d, %d) in %s\n";
+
+ if (!scc->init)
+ {
+ printk(badinfo, MAJOR(device), MINOR(device), routine);
+ return 1;
+ }
+ if (scc->magic != SCC_MAGIC) {
+ printk(badmagic, MAJOR(device), MINOR(device), routine);
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+
+/* ----> this one is called whenever you open the device <---- */
+
+int scc_open(struct tty_struct *tty, struct file * filp)
+{
+ struct scc_channel *scc;
+ int chan;
+
+ chan = MINOR(tty->device) - tty->driver.minor_start;
+ if ((chan < 0) || (chan >= (Nchips * 2)))
+ return -ENODEV;
+
+ scc = &SCC_Info[chan];
+
+ tty->driver_data = scc;
+ tty->termios->c_cflag &= ~CBAUD;
+
+ if (!Driver_Initialized)
+ return 0;
+
+ if (scc->magic != SCC_MAGIC)
+ {
+ printk("ERROR: scc_open(): bad magic number for device (%d, %d)", MAJOR(tty->device), MINOR(tty->device));
+ return -ENODEV;
+ }
+
+ if(scc->tty != NULL)
+ {
+ scc->tty_opened++;
+ return 0;
+ }
+
+ if(!scc->init) return 0;
+
+ scc->tty = tty;
+ init_channel(scc);
+
+ scc->stat.tx_kiss_state = KISS_IDLE; /* don't change this... */
+ scc->stat.rx_kiss_state = KISS_IDLE; /* ...or this */
+
+ scc_init_timer(scc);
+
+ timer_table[SCC_TIMER].fn = scc_timer;
+ timer_table[SCC_TIMER].expires = 0; /* now! */
+ timer_active |= 1 << SCC_TIMER;
+
+ return 0;
+}
+
+
+/* ----> and this whenever you close the device <---- */
+
+static void
+scc_close(struct tty_struct *tty, struct file * filp)
+{
+ struct scc_channel *scc = tty->driver_data;
+ unsigned long flags;
+
+ if (!scc || (scc->magic != SCC_MAGIC))
+ return;
+
+ if(scc->tty_opened)
+ {
+ scc->tty_opened--;
+ return;
+ }
+
+ tty->driver_data = NULLBUF;
+
+ save_flags(flags); cli();
+
+ Outb(scc->ctrl,0); /* Make sure pointer is written */
+ wr(scc,R1,0); /* disable interrupts */
+ wr(scc,R3,0);
+
+ scc->tty = NULL;
+
+ restore_flags(flags);
+ tty->stopped = 0;
+}
+
+
+
+
+/*
+ * change scc_speed
+ */
+
+static void
+scc_change_speed(struct scc_channel * scc)
+{
+ if (scc->tty == NULL)
+ return;
+
+ scc->modem.speed = baud_table[scc->tty->termios->c_cflag & CBAUD];
+
+ if (scc->stat.tx_state == 0) /* only switch baudrate on rx... ;-) */
+ set_speed(scc);
+}
+
+
+/* ----> ioctl-routine of the driver <---- */
+
+/* perform ioctl on SCC (sdlc) channel
+ * this is used for AX.25 mode, and will set the "kiss" parameters
+ */
+
+/* TIOCMGET - get modem status arg: (unsigned long *) arg
+ * TIOCMBIS - set PTT arg: ---
+ * TIOCMBIC - reset PTT arg: ---
+ * TIOCMBIC - set PTT arg: ---
+ * TIOCSCCINI - initialize driver arg: ---
+ * TIOCCHANINI - initialize channel arg: (struct scc_modem *) arg
+ * TIOCGKISS - get level 1 parameter arg: (struct ioctl_command *) arg
+ * TIOCSKISS - set level 1 parameter arg: (struct ioctl_command *) arg
+ * TIOCSCCSTAT - get driver status arg: (struct scc_stat *) arg
+ */
+
+
+static int
+scc_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg)
+{
+ struct scc_channel * scc = tty->driver_data;
+ unsigned long flags, r;
+ unsigned int result;
+ unsigned int value;
+ struct ioctl_command kiss_cmd;
+ int error;
+
+ if (scc->magic != SCC_MAGIC)
+ {
+ printk("ERROR: scc_ioctl(): bad magic number for device %d,%d",
+ MAJOR(tty->device), MINOR(tty->device));
+
+ return -ENODEV;
+ }
+
+ r = NO_SUCH_PARAM;
+
+ if (!Driver_Initialized)
+ {
+ if (cmd == TIOCSCCINI)
+ {
+ if (!suser())
+ return -EPERM;
+
+ scc_alloc_buffer_pool();
+ z8530_init();
+ return 0;
+ }
+
+ return -EINVAL; /* confuse the user */
+ }
+
+ if (!scc->init)
+ {
+
+ if (cmd == TIOCCHANINI)
+ {
+ if (!arg)
+ return -EFAULT;
+
+ if (!suser())
+ return -EPERM;
+
+ memcpy_fromfs(&scc->modem, (void *) arg, sizeof(struct scc_modem));
+
+ /* default KISS Params */
+
+ if (scc->modem.speed < 4800)
+ {
+ scc->kiss.txdelay = 36*TPS/100; /* 360 ms */
+ scc->kiss.persist = 42; /* 25% persistence */ /* was 25 */
+ scc->kiss.slottime = 16*TPS/100; /* 160 ms */
+ scc->kiss.tailtime = 4; /* minimal reasonable value */
+ scc->kiss.fulldup = 0; /* CSMA */
+ scc->kiss.waittime = 50*TPS/100; /* 500 ms */
+ scc->kiss.maxkeyup = 10; /* 10 s */
+ scc->kiss.mintime = 3; /* 3 s */
+ scc->kiss.idletime = 30; /* 30 s */
+ scc->kiss.maxdefer = 120; /* 2 min */
+ scc->kiss.not_slip = 1; /* KISS mode */
+ scc->kiss.softdcd = 0; /* hardware dcd */
+ } else {
+ scc->kiss.txdelay = 10*TPS/100; /* 100 ms */
+ scc->kiss.persist = 64; /* 25% persistence */ /* was 25 */
+ scc->kiss.slottime = 8*TPS/100; /* 160 ms */
+ scc->kiss.tailtime = 1; /* minimal reasonable value */
+ scc->kiss.fulldup = 0; /* CSMA */
+ scc->kiss.waittime = 50*TPS/100; /* 500 ms */
+ scc->kiss.maxkeyup = 7; /* 7 s */
+ scc->kiss.mintime = 3; /* 3 s */
+ scc->kiss.idletime = 30; /* 30 s */
+ scc->kiss.maxdefer = 120; /* 2 min */
+ scc->kiss.not_slip = 1; /* KISS mode */
+ scc->kiss.softdcd = 0; /* hardware dcd */
+ }
+
+ scc->init = 1;
+
+ return 0;
+ }
+
+ return -EINVAL;
+ }
+
+ switch(cmd){
+ case TCSBRK:
+ return 0;
+ case TIOCMGET:
+ error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(unsigned int *));
+ if (error)
+ return error;
+
+ save_flags(flags); cli();
+
+ result = ((scc->wreg[R5] & RTS) ? TIOCM_RTS : 0)
+ | ((scc->wreg[R5] & DTR) ? TIOCM_DTR : 0)
+ | ((InReg(scc->ctrl,R0) & DCD) ? TIOCM_CAR : 0)
+ | ((InReg(scc->ctrl,R0) & CTS) ? TIOCM_CTS : 0);
+
+ restore_flags(flags);
+
+ put_fs_long(result,(unsigned long *) arg);
+ return 0;
+ case TIOCMBIS:
+ case TIOCMBIC:
+ case TIOCMSET:
+ switch (cmd) {
+ case TIOCMBIS:
+ scc->wreg[R5] |= DTR;
+ scc->wreg[R5] |= RTS;
+ break;
+ case TIOCMBIC:
+ scc->wreg[R5] &= ~DTR;
+ scc->wreg[R5] &= ~RTS;
+ break;
+ case TIOCMSET:
+ value = get_fs_long((unsigned long *) arg);
+
+ if(value & TIOCM_DTR)
+ scc->wreg[R5] |= DTR;
+ else
+ scc->wreg[R5] &= ~DTR;
+ if(value & TIOCM_RTS)
+ scc->wreg[R5] |= RTS;
+ else
+ scc->wreg[R5] &= ~RTS;
+ break;
+ }
+
+ save_flags(flags); cli();
+
+ if(scc->stat.tx_state == TXS_IDLE && !Running(t_idle))
+ maxk_idle_timeout(scc);
+
+ restore_flags(flags);
+
+ return 0;
+
+ case TCGETS:
+ error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct termios));
+ if (error)
+ return error;
+ if (!arg)
+ return -EFAULT;
+
+ memcpy_tofs((void *) arg, scc->tty->termios, sizeof(struct termios));
+ return 0;
+
+ case TCSETS:
+ case TCSETSF: /* should flush first, but... */
+ case TCSETSW: /* should wait 'till flush, but... */
+ if (!suser())
+ return -EPERM;
+ if (!arg)
+ return -EFAULT;
+
+ memcpy_fromfs(scc->tty->termios, (void *) arg, sizeof(struct termios));
+ scc_change_speed(scc);
+ return 0;
+
+
+ case TIOCSCCSTAT:
+ error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(struct scc_stat));
+ if (error)
+ return error;
+
+ if (!arg)
+ return -EFAULT;
+
+ scc->stat.used_buf = scc_count_used_buffers(&scc->stat.rx_alloc,
+ &scc->stat.tx_alloc);
+
+ memcpy_tofs((void *) arg, &scc->stat, sizeof(struct scc_stat));
+ return 0;
+
+#define TICKS (100/TPS)
+#define CAST(x) (unsigned long)(x)
+#define Val kiss_cmd.param
+#define VAL kiss_cmd.param*TPS/100
+#define SVAL kiss_cmd.param? kiss_cmd.param:TIMER_STOPPED
+
+ case TIOCGKISS:
+ error = verify_area(VERIFY_WRITE, (void *) arg,sizeof(struct ioctl_command));
+ if (error)
+ return error;
+
+ if (!arg)
+ return -EFAULT;
+
+ memcpy_fromfs(&kiss_cmd, (void *) arg, sizeof(struct ioctl_command));
+
+ switch (kiss_cmd.command)
+ {
+ case PARAM_TXDELAY: r = CAST(scc->kiss.txdelay*TICKS); break;
+ case PARAM_PERSIST: r = CAST(scc->kiss.persist); break;
+ case PARAM_SLOTTIME: r = CAST(scc->kiss.slottime*TICKS); break;
+ case PARAM_TXTAIL: r = CAST(scc->kiss.tailtime*TICKS); break;
+ case PARAM_FULLDUP: r = CAST(scc->kiss.fulldup); break;
+ case PARAM_SOFTDCD: r = CAST(scc->kiss.softdcd); break;
+ case PARAM_DTR: r = CAST((scc->wreg[R5] & DTR)? 1:0); break;
+ case PARAM_RTS: r = CAST((scc->wreg[R5] & RTS)? 1:0); break;
+ case PARAM_SPEED: r = CAST(scc->modem.speed); break;
+ case PARAM_GROUP: r = CAST(scc->kiss.group); break;
+ case PARAM_IDLE: r = CAST(scc->kiss.idletime); break;
+ case PARAM_MIN: r = CAST(scc->kiss.mintime); break;
+ case PARAM_MAXKEY: r = CAST(scc->kiss.maxkeyup); break;
+ case PARAM_WAIT: r = CAST(scc->kiss.waittime); break;
+ case PARAM_MAXDEFER: r = CAST(scc->kiss.maxdefer); break;
+ case PARAM_TX: r = CAST(scc->kiss.tx_inhibit); break;
+ case PARAM_SLIP: r = CAST(!scc->kiss.not_slip); break;
+ default: r = NO_SUCH_PARAM;
+ }
+
+ kiss_cmd.param = r;
+
+ memcpy_tofs((void *) arg, &kiss_cmd, sizeof(struct ioctl_command));
+ return 0;
+ break;
+
+ case TIOCSKISS:
+ if (!arg)
+ return -EFAULT;
+
+ if (!suser())
+ return -EPERM;
+
+ memcpy_fromfs(&kiss_cmd, (void *) arg, sizeof(struct ioctl_command));
+
+ switch (kiss_cmd.command)
+ {
+ case PARAM_TXDELAY: scc->kiss.txdelay=VAL; break;
+ case PARAM_PERSIST: scc->kiss.persist=Val; break;
+ case PARAM_SLOTTIME: scc->kiss.slottime=VAL; break;
+ case PARAM_TXTAIL: scc->kiss.tailtime=VAL; break;
+ case PARAM_FULLDUP: scc->kiss.fulldup=Val; break;
+ case PARAM_SOFTDCD: scc->kiss.softdcd=Val; break;
+ case PARAM_DTR: break; /* does someone need this? */
+ case PARAM_RTS: break; /* or this? */
+ case PARAM_SPEED: scc->modem.speed=Val; break;
+ case PARAM_GROUP: scc->kiss.group=Val; break;
+ case PARAM_IDLE: scc->kiss.idletime=Val; break;
+ case PARAM_MIN: scc->kiss.mintime=SVAL; break;
+ case PARAM_MAXKEY: scc->kiss.maxkeyup=SVAL; break;
+ case PARAM_WAIT: scc->kiss.waittime=Val; break;
+ case PARAM_MAXDEFER: scc->kiss.maxdefer=SVAL; break;
+ case PARAM_TX: scc->kiss.tx_inhibit=Val; break;
+ case PARAM_SLIP: scc->kiss.not_slip=!Val; break;
+ default: return -ENOIOCTLCMD;
+ }
+
+ return 0;
+ break;
+#undef TICKS
+#undef CAST
+#undef VAL
+#undef SVAL
+#undef Val
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+
+/* ----- TERMIOS function ----- */
+
+static void
+scc_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+ if (tty->termios->c_cflag == old_termios->c_cflag)
+ return;
+ scc_change_speed(tty->driver_data);
+}
+
+
+static inline void check_tx_queue(register struct scc_channel *scc)
+{
+ register struct mbuf *bp;
+
+ if (scc->stat.tx_queued > QUEUE_THRES)
+ {
+ if (scc->sndq1 == NULLBUF)
+ {
+ printk("z8530drv: Warning - scc->stat.tx_queued shows overflow"
+ " (%d) but queue is empty\n", scc->stat.tx_queued);
+
+ scc->stat.tx_queued = 0; /* correct it */
+ scc->stat.nospace = 54321; /* draw attention to it */
+ return;
+ }
+
+ bp = scc->sndq1->anext; /* don't use the one we currently use */
+
+ while (bp && (scc->stat.tx_queued > QUEUE_HYST))
+ {
+ bp = scc_free_chain(bp, BT_TRANSMIT);
+ scc->stat.tx_queued--;
+ scc->stat.nospace++;
+ }
+
+ scc->sndq1->anext = bp;
+ }
+}
+
+
+
+/* ----> tx routine: decode KISS data and scc_enqueue it <---- */
+
+/* send raw frame to SCC. used for AX.25 */
+int scc_write(struct tty_struct *tty, int from_user, unsigned char *buf, int count)
+{
+ struct scc_channel * scc = tty->driver_data;
+ unsigned char tbuf[BUFSIZE], *p;
+ int cnt, cnt2;
+
+ if (!tty) return count;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_write"))
+ return 0;
+
+ if (scc->kiss.tx_inhibit) return count;
+
+ check_tx_queue(scc);
+
+ cnt2 = count;
+
+ while (cnt2)
+ {
+ cnt = cnt2 > BUFSIZE? BUFSIZE:cnt2;
+ cnt2 -= cnt;
+
+ if (from_user)
+ memcpy_fromfs(tbuf, buf, cnt);
+ else
+ memcpy(tbuf, buf, cnt);
+
+ buf += cnt;
+
+ p=tbuf;
+
+ while(cnt--)
+ if (kiss_decode(scc, *p++))
+ {
+ scc->stat.nospace++;
+ return 0;
+ }
+
+ } /* while cnt2 */
+
+ return count;
+}
+
+
+/* put a single char into the buffer */
+
+static void scc_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct scc_channel *scc = tty->driver_data;
+ unsigned char ch2;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_put_char"))
+ return;
+
+ ch2 = ch;
+ scc_write(tty, 0, &ch2, 1); /* that's all */
+}
+
+static void scc_flush_chars(struct tty_struct * tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ scc_paranoia_check(scc, tty->device, "scc_flush_chars"); /* just to annoy the user... */
+
+ return; /* no flush needed */
+}
+
+/* the kernel does NOT use this routine yet... */
+
+static int scc_write_room(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_write_room"))
+ return 0;
+
+ if (scc->stat.tx_alloc >= QUEUE_THRES)
+ {
+ printk("scc_write_room(): buffer full (ignore)\n");
+ return 0;
+ }
+
+ return BUFSIZE;
+}
+
+static int scc_chars_in_buffer(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc && scc->sndq2)
+ return scc->sndq2->cnt;
+ else
+ return 0;
+}
+
+static void scc_flush_buffer(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_flush_buffer"))
+ return;
+
+ scc->stat.tx_kiss_state = KISS_IDLE;
+
+ wake_up_interruptible(&tty->write_wait);
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup)
+ (tty->ldisc.write_wakeup)(tty);
+}
+
+static void scc_throttle(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_throttle"))
+ return;
+
+
+ /* dummy */
+}
+
+static void scc_unthrottle(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_unthrottle"))
+ return;
+
+ /* dummy */
+}
+
+static void scc_start(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_start"))
+ return;
+
+ /* dummy */
+}
+
+
+static void scc_stop(struct tty_struct *tty)
+{
+ struct scc_channel *scc = tty->driver_data;
+
+ if (scc_paranoia_check(scc, tty->device, "scc_stop"))
+ return;
+
+ /* dummy */
+}
+
+
+/* ******************************************************************** */
+/* * Init SCC driver * */
+/* ******************************************************************** */
+
+long scc_init (long kmem_start)
+{
+ int chip, chan;
+ register io_port ctrl;
+ long flags;
+
+
+ memset(&scc_driver, 0, sizeof(struct tty_driver));
+ scc_driver.magic = TTY_DRIVER_MAGIC;
+ scc_driver.name = "sc";
+ scc_driver.major = TTY_MAJOR;
+ scc_driver.minor_start = 96;
+ scc_driver.num = Nchips*2;
+ scc_driver.type = TTY_DRIVER_TYPE_SERIAL;
+ scc_driver.subtype = 0; /* not needed */
+ scc_driver.init_termios = tty_std_termios;
+ scc_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ scc_driver.flags = TTY_DRIVER_REAL_RAW;
+ scc_driver.refcount = &scc_refcount; /* not needed yet */
+ scc_driver.table = scc_table;
+ scc_driver.termios = (struct termios **) scc_termios;
+ scc_driver.termios_locked = (struct termios **) scc_termios_locked;
+ scc_driver.open = scc_open;
+ scc_driver.close = scc_close;
+ scc_driver.write = scc_write;
+ scc_driver.start = scc_start;
+ scc_driver.stop = scc_stop;
+
+ scc_driver.put_char = scc_put_char;
+ scc_driver.flush_chars = scc_flush_chars;
+ scc_driver.write_room = scc_write_room;
+ scc_driver.chars_in_buffer = scc_chars_in_buffer;
+ scc_driver.flush_buffer = scc_flush_buffer;
+
+ scc_driver.throttle = scc_throttle;
+ scc_driver.unthrottle = scc_unthrottle;
+
+ scc_driver.ioctl = scc_ioctl;
+ scc_driver.set_termios = scc_set_termios;
+
+ if (tty_register_driver(&scc_driver))
+ panic("Couldn't register Z8530 SCC driver\n");
+
+ printk (BANNER);
+
+ if (Nchips > MAXSCC) Nchips = MAXSCC; /* to avoid the "DAU" (duemmster anzunehmender User) */
+
+ /* reset and pre-init all chips in the system */
+
+ for (chip = 0; chip < Nchips; chip++)
+ {
+ memset((char *) &SCC_Info[2*chip ], 0, sizeof(struct scc_channel));
+ memset((char *) &SCC_Info[2*chip+1], 0, sizeof(struct scc_channel));
+
+ ctrl = SCC_ctrl[chip * 2];
+ if (!ctrl) continue;
+
+ save_flags(flags); cli(); /* because of 2-step accesses */
+
+
+/* Hmm... this may fail on fast systems with cards who don't delay the INTACK */
+/* If you are sure you specified the right port addresses and the driver doesn't */
+/* recognize the chips, define DONT_CHECK in scc_config.h */
+
+#ifndef DONT_CHECK
+ check_region(ctrl, 1);
+
+ Outb(ctrl, 0);
+ OutReg(ctrl,R13,0x55); /* is this chip realy there? */
+
+ if (InReg(ctrl,R13) != 0x55 )
+ {
+ restore_flags(flags);
+ continue;
+ }
+#endif
+
+ SCC_Info[2*chip ].magic = SCC_MAGIC;
+ SCC_Info[2*chip ].ctrl = SCC_ctrl[2*chip];
+ SCC_Info[2*chip ].data = SCC_data[2*chip];
+ SCC_Info[2*chip ].enhanced = SCC_Enhanced[chip];
+
+ SCC_Info[2*chip+1].magic = SCC_MAGIC;
+ SCC_Info[2*chip+1].ctrl = SCC_ctrl[2*chip+1];
+ SCC_Info[2*chip+1].data = SCC_data[2*chip+1];
+ SCC_Info[2*chip+1].enhanced = SCC_Enhanced[chip];
+
+
+ restore_flags(flags);
+ }
+
+#ifdef VERBOSE_BOOTMSG
+ printk("Init Z8530 driver: %u channels, using irq %u\n",Nchips*2,Ivec);
+
+
+ for (chan = 0; chan < Nchips * 2 ; chan++)
+ {
+ printk("/dev/%s%i: data port = 0x%3.3x control port = 0x%3.3x -- %s\n",
+ scc_driver.name, chan, SCC_data[chan], SCC_ctrl[chan],
+ SCC_Info[chan].ctrl? "found" : "missing");
+
+ if (SCC_Info[chan].ctrl == 0)
+ {
+ SCC_ctrl[chan] = 0;
+ } else {
+ request_region(SCC_ctrl[chan], 1, "scc ctrl");
+ request_region(SCC_data[chan], 1, "scc data");
+ }
+ }
+#else
+ printk("Init Z8530 driver: %u channels\n",Nchips*2);
+#endif
+
+
+ return kmem_start;
+}
+#endif