/* Generic driver for Z8530 boards, modified from the PE1CHL
 * driver for use with NOS. This version also supports the NRS
 * mode when used as an asynch port. Device setup is similar to
 * that of the PE1CHL version, with the addition of user specification
 * of buffer size (bufsize). See the file "scc.txt" for general
 * information on the use of this driver and setup procedures.
 *
 * General differences between this driver and the original version:
 *
 * 1) Slip encoding and decoding is not done in the driver, but
 *    using the routines in slip.c, and these routines are supported
 *    in a manner similar to the asynch routines for the 8250. The
 *    input is handled via fifo buffer, while output is direct. The
 *    routines scc_send and get_scc are called via pointers in the
 *    Slip and Nrs structs for the parcticular channel.
 *
 * 2) The timer routine, scctim, is not installed directly in the
 *    timer interrupt chain, but is called through the systick routine
 *    in pc.c.
 *
 * 3) Facilities of nos are used whenever possible in place of direct
 *    structure or variable manipulation. Mbuf management is handled
 *    this way, along with interface initialization.
 *
 * 4) Nrs mode support is added in a manner similar to that of the
 *    Slip support. I have not had an opportunity to test this, but
 *    it is essentially identical to the way the 8250 version works.
 *
 * 5) Callsign specification on radio modes (kiss,nrs,ax25) is an
 *    option. If not supplied, the value of Mycall will be used.
 *
 * 6) Bufsize specification is now a parameter on setup of each channel.
 *    This is the size of the fifo on asynch input, and the size of
 *    mbuf buffers for sdlc mode. Since the fifo buffer can fill up,
 *    this value should be reasonably large for asynch mode. Mbufs
 *    are chained when they fill up, so having a small bufsize with
 *    sdlc modes (ax25) does not result in loss of characters.
 *
 * 7) Because slip and nrs decoding is handled outside the driver,
 *    sccstat cannot be used to report sent and receive packet counts
 *    in asynch mode, and these fields are blanked on display in asynch
 *    modes.
 *
 *
 * I am interested in setting up some default initializations for
 * the popular Z8530 boards, to minimize user problems in constructing
 * the proper attach init entries. These would allow for shortened
 * entries to use the defaults, such as "attach scc 1 init drsi" to
 * attach a DRSI board in standard configuration at its default address.
 * Since I do not have complete technical information on all such boards,
 * I would very much appreciate any information that users can provide
 * me regarding particular boards.
 *
 * 1/25/90
 *
 * Modifications:
 *
 * 2/17/90:
 *
 * 1) Added mods from PE1CHL which reflect relevent changes to the
 *    scc driver in his version of net between 10/89 and 1/90. Changes
 *    incorporated include additional delays in sccvec.asm, addition
 *    of external clock mode, and initialization for the 8536 as a
 *    clock divider on the DRSI board. "INLINE" is a slight delay
 *    for register access incorporated for use with the inline i/o
 *    code in MSC. This may not be useful or necessary with TURBO.
 *    Changes making "TPS" a variable were not added, since the
 *    scc timer does not install itself on the hardware interrupt
 *    in this version.
 * 
 *
 * Ken Mitchum, KY3B       km@cs.pitt.edu  km@dsl.pitt.edu
 *                             or mail to the tcpip group
 *
 */

/* Added ANSI-style prototypes, reformatted source, minor delinting.
 * Integrated into standard 900201 NOS by KA9Q.
 */

/*
 * Generic driver for Z8530 SCC chip in SLIP, KISS or AX.25 mode.
 *
 * Written by R.E. Janssen (PE1CHL) using material from earlier
 * EAGLE and PC100 drivers in this package.
 *
 * The driver has initially been written for my own Atari SCC interface
 * board, but it could eventually replace the other SCC drivers.
 *
 * Unfortunately, there is little consistency between the different interface
 * boards, as to the use of a clock source, the solution for the fullduplex
 * clocking problem, and most important of all: the generation of the INTACK
 * signal.	Most designs do not even support the generation of an INTACK and
 * the read of the interrupt vector provided by the chip.
 * This results in lots of configuration parameters, and a fuzzy
 * polltable to be able to support multiple chips connected at one interrupt
 * line...
 *
 */


#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <dos.h>
#include "global.h"
#include "mbuf.h"
#include "config.h"
#include "netuser.h"
#include "proc.h"
#include "iface.h"
#include "pktdrvr.h"
#include "slip.h"
#include "nrs.h"
#include "8250.h"
#include "scc.h"
#include "8530.h"
#include "8536.h"
#include "ax25.h"
#include "trace.h"
#include "pc.h"
#include "kiss.h"

/* interrupt handlers */
extern INTERRUPT sccvec();
extern INTERRUPT sccnovec();

/* variables used by the SCC interrupt handler in sccvec.asm */
static INTERRUPT (*Orgivec)();	/* original interrupt vector */

struct sccinfo Sccinfo = {0};		/* global info about SCCs */
struct sccchan *Sccchan[2 * MAXSCC] = {0}; /* information per channel */
ioaddr Sccvecloc = {0};			/* location to access for SCC vector */
unsigned char Sccmaxvec = {0};		/* maximum legal vector from SCC */
ioaddr Sccpolltab[MAXSCC+1][2] = {0};	/* polling table when no vectoring */

#if defined(INLINE)
static unsigned scc_delay __ARGS((unsigned v));

static unsigned scc_delay (v)		/* delay for about 5 PCLK cycles */
   unsigned v;				/* pass-through used for input */

{
   register int i,j;			/* it takes time to save them */

   return v;				/* return the passed parameter */
}
#endif

unsigned char Random = 0;		/* random number for p-persist */

static int scc_call __ARGS((struct iface *ifp,char *call));
static int scc_init __ARGS((int nchips,ioaddr iobase,int space,int aoff,
 int boff,int doff,ioaddr intack,int ivec,long clk,int pclk,int hwtype,
 int hwparam));
static int scc_raw __ARGS((struct iface *ifp,struct mbuf *bp));
static int scc_stop __ARGS((struct iface *ifp));
static int get_scc __ARGS((int dev));
static int scc_send __ARGS((int dev,struct mbuf *bp));
static int scc_async __ARGS((struct sccchan *scc));

static void scc_sdlc __ARGS((struct sccchan *scc));
static void scc_tossb __ARGS((struct sccchan *scc));
static void scc_txon __ARGS((struct sccchan *scc));
static void scc_txoff __ARGS((struct sccchan *scc));
static int scc_aioctl __ARGS((struct iface *ifp,int argc,char *argv[]));
static int scc_sioctl __ARGS((struct iface *ifp,int argc,char *argv[]));
static void scc_sstart __ARGS((struct sccchan *scc));
static unsigned int scc_speed __ARGS((struct sccchan *scc,
  unsigned int clkmode,long speed));
static void scc_asytx __ARGS((struct sccchan *scc));
static void scc_asyex __ARGS((struct sccchan *scc));
static void scc_asyrx __ARGS((struct sccchan *scc));
static void scc_asysp __ARGS((struct sccchan *scc));
static void scc_sdlctx __ARGS((struct sccchan *scc));
static void scc_sdlcex __ARGS((struct sccchan *scc));
static void scc_sdlcrx __ARGS((struct sccchan *scc));
static void scc_sdlcsp __ARGS((struct sccchan *scc));

/* Attach an SCC channel to the system, or initialize SCC driver.
 * operation depends on argv[2]:
 * when "init", the SCC driver is initialized, and global information about
 * the hardware is set up.
 * argv[0]: hardware type, must be "scc"
 * argv[1]: number of SCC chips we will support
 * argv[2]: mode, must be: "init" in this case
 * argv[3]: base address of SCC chip #0 (hex)
 * argv[4]: spacing between SCC chip base addresses
 * argv[5]: offset from chip base address to channel A control register
 * argv[6]: offset from chip base address to channel B control register
 * argv[7]: offset from each channel's control register to data register
 * argv[8]: address of INTACK/Read Vector port. 0 to read from RR3A/RR2B
 * argv[9]: CPU interrupt vector number for all connected SCCs
 * argv[10]: clock frequency (PCLK/RTxC) of all SCCs in cycles per second
 *		 prefix with "p" for PCLK, "r" for RTxC clock (for baudrate gen)
 * argv[11]: optional hardware type (for special features)
 * argv[12]: optional extra parameter for special hardware
 *
 * otherwise, a single channel is attached using the specified parameters:
 * argv[0]: hardware type, must be "scc"
 * argv[1]: SCC channel number to attach, 0/1 for first chip A/B, 2/3 for 2nd...
 * argv[2]: mode, can be:
 *		"slip", "kiss", "ax25"
 * argv[3]: interface label, e.g., "sl0"
 * argv[4]: maximum transmission unit, bytes
 * argv[5]: interface speed, e.g, "1200". prefix with "d" when an external
 *		divider is available to generate the TX clock.	When the clock
 *		source is PCLK, this can be a /32 divider between TRxC and RTxC.
 *		When the clock is at RTxC, the TX rate must be supplied at TRxC.
 *		This is needed only for AX.25 fullduplex.
 *	    When this arg is given as "ext", the transmit and receive clock
 *	    are external, and the BRG and DPLL are not used.
 * argv[6]: buffer size
 * argv[7]: callsign used on the radio channels (optional)
 */

int
scc_attach(argc,argv)
int argc;
char *argv[];
{
	register struct iface *ifp;
	struct sccchan *scc;
	unsigned int chan,brgrate;
	int pclk = 0,hwtype = 0,hwparam = 0;
	int xdev;

	/* first handle the special "init" mode, to initialize global stuff */

	if(!strcmp(argv[2],"init")){
	if(argc < 11)			/* need at least argv[1]..argv[10] */
		return -1;

	if(isupper(argv[10][0]))
		argv[10][0] = tolower(argv[10][0]);

	if(argv[10][0] == 'p'){	/* wants to use PCLK as clock? */
		pclk = 1;
		argv[10]++;
	} else {
		if(argv[10][0] == 'r')	/* wants to use RTxC? */
		argv[10]++;		/* that's the default */
	}
	if(argc > 11)			/* optional hardware type */
		hwtype = htoi(argv[11]);	/* it is given in hex */

	if(argc > 12)			/* optional hardware param */
		hwparam = htoi(argv[12]);	/* also in hex */

	return scc_init(atoi(argv[1]),(ioaddr) htol(argv[3]),atoi(argv[4]),
		atoi(argv[5]),atoi(argv[6]),atoi(argv[7]),
		(ioaddr) htol(argv[8]),atoi(argv[9]),
		atol(argv[10]),pclk,hwtype,hwparam);
	}
	/* not "init", so it must be a valid mode to attach a channel */
	if(strcmp(argv[2],"ax25") && strcmp(argv[2],"kiss") &&
	 strcmp(argv[2],"slip")){
		printf("Mode %s unknown for SCC\n",argv[2]);
		return -1;
	}
	if(strcmp(argv[2],"slip") == 0 || strcmp(argv[2],"kiss") == 0){
		for(xdev = 0;xdev < SLIP_MAX;xdev++)
			if(Slip[xdev].iface == NULLIF)
				break;
		if(xdev >= SLIP_MAX){
			tprintf("Too many slip devices\n");
			return -1;
		}
	}
	if(strcmp(argv[2],"nrs") == 0){
		for(xdev = 0;xdev < NRS_MAX;xdev++)
			if(Nrs[xdev].iface == NULLIF)
				break;

		if(xdev >= NRS_MAX){
			tprintf("Too many nrs devices\n");
			return -1;
		}
	}
	if(!Sccinfo.init){
		printf("First init SCC driver\n");
		return -1;
	}
	if((chan = atoi(argv[1])) > Sccinfo.maxchan){
		printf("SCC channel %d out of range\n",chan);
		return -1;
	}
	if(Sccchan[chan] != NULLCHAN){
		printf("SCC channel %d already attached\n",chan);
		return -1;
	}
	/* create interface structure and fill in details */
	ifp = (struct iface *) callocw(1,sizeof(struct iface));
	ifp->name = mallocw(strlen(argv[3]) + 1);
	strcpy(ifp->name,argv[3]);

	ifp->mtu = atoi(argv[4]);
	ifp->dev = chan;
	ifp->stop = scc_stop;

	scc = (struct sccchan *) callocw(1,sizeof(struct sccchan));
	scc->ctrl = Sccinfo.iobase + (chan / 2) * Sccinfo.space + Sccinfo.off[chan % 2];
	scc->data = scc->ctrl + Sccinfo.doff;
	scc->iface = ifp;

	if(isupper(argv[5][0]))
		argv[5][0] = tolower(argv[5][0]);

    switch (argv[5][0]) {
    case 'd':				/* fulldup divider installed? */
		scc->fulldup = 1;		/* set appropriate flag */
		argv[5]++;			/* skip the 'd' */
		break;

    case 'e':				/* external clocking? */
		scc->extclock = 1;		/* set the flag */
		break;
    }

	scc->bufsiz = atoi(argv[6]);
	ifp->addr = Ip_addr; 
	Sccchan[chan] = scc;		/* put addr in table for interrupts */

	switch(argv[2][0]){ 			/* mode already checked above */
#ifdef AX25
	case 'a':	/* AX.25 */
		scc_sdlc(scc);				/* init SCC in SDLC mode */

		if (!scc->extclock) {
			brgrate = scc_speed(scc,32,atol(argv[5]));/* init SCC speed */
			scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
		}

		brgrate = scc_speed(scc,32,atol(argv[5]));/* init SCC speed */
		scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
		setencap(ifp,"AX25");
		scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);	/* set the callsign */
			ifp->ioctl = scc_sioctl;
		ifp->raw = scc_raw;

		/* default KISS Params */
		scc->a.params[TXDELAY] = 36*TPS/100;	/* 360 ms */
		scc->a.params[PERSIST] = 25;		/* 10% persistence */
		scc->a.params[SLOTTIME] = 16*TPS/100; /* 160 ms */
#if TPS > 67
		scc->a.params[TAILTIME] = 3*TPS/100;	/* 30 ms */
#else
		scc->a.params[TAILTIME] = 2;		/* minimal reasonable value */
#endif
		scc->a.params[FULLDUP] = 0;		/* CSMA */
		scc->a.params[TNCSPECIF] = 1;		/* DTR set */
		scc->a.params[WAITTIME] = 50*TPS/100; /* 500 ms */
		scc->a.params[MAXKEYUP] = 7;		/* 7 s */
		scc->a.params[MINTIME] = 3;		/* 3 s */
		scc->a.params[IDLETIME] = 120;	/* 120 s */
		break;
	case 'k':	/* kiss */
		scc_async(scc);				/* init SCC in async mode */
		brgrate = scc_speed(scc,16,atol(argv[5]));
		scc->speed = Sccinfo.clk / (32L * (brgrate + 2));

		setencap(ifp,"AX25");
		scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);	/* set the callsign */

		ifp->ioctl = kiss_ioctl;
		ifp->raw = kiss_raw;

		for(xdev = 0;xdev < SLIP_MAX;xdev++){
			if(Slip[xdev].iface == NULLIF)
				break;
		}
		ifp->xdev = xdev;
		Slip[xdev].iface = ifp;
		Slip[xdev].type = CL_KISS;
		Slip[xdev].send = scc_send;
		Slip[xdev].get = get_scc;
		ifp->rxproc = newproc("ascc rx",256,asy_rx,xdev,NULL,NULL,0);
		break;
#endif
#ifdef SLIP
	case 's':	/* slip */
		scc_async(scc);				/* init SCC in async mode */
		brgrate = scc_speed(scc,16,atol(argv[5]));
		scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
		setencap(ifp,"SLIP");
		ifp->ioctl = scc_aioctl;
		ifp->raw = slip_raw;
		for(xdev = 0;xdev < SLIP_MAX;xdev++){
			if(Slip[xdev].iface == NULLIF)
				break;
		}
		ifp->xdev = xdev;
		Slip[xdev].iface = ifp;
		Slip[xdev].type = CL_SERIAL_LINE;
		Slip[xdev].send = scc_send;
		Slip[xdev].get = get_scc;
		ifp->rxproc = newproc("ascc rx",256,asy_rx,xdev,NULL,NULL,0);
		break;
#endif
#ifdef NRS
	case 'n':	/* nrs */
		scc_async(scc);				/* init SCC in async mode */
		brgrate = scc_speed(scc,16,atol(argv[5]));
		scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
		setencap(ifp,"AX25");
		scc_call(ifp,argc > 7 ? argv[7] : (char *) 0);	/* set the callsign */
		ifp->ioctl = scc_aioctl;
		ifp->raw = nrs_raw;
	
		for(xdev = 0;xdev < NRS_MAX;xdev++)
			if(Nrs[xdev].iface == NULLIF)
				break;

		ifp->xdev = xdev;
		Nrs[xdev].iface = ifp;
		Nrs[xdev].send = scc_send;
		Nrs[xdev].get = get_scc;
		ifp->rxproc = newproc("nscc rx",256,nrs_recv,xdev,NULL,NULL,0);
		break;
#endif
	}
	ifp->next = Ifaces;			/* link interface in list */
	Ifaces = ifp;
	return 0;
}

/* SCC driver initialisation. called on "attach scc <num> init ..." */
static int
scc_init(nchips,iobase,space,aoff,boff,doff,intack,ivec,clk,pclk,hwtype,hwparam)
int nchips;			/* number of chips */
ioaddr iobase;			/* base of first chip */
int space,aoff,boff,doff;
ioaddr intack;			/* INTACK ioport or 0 for no INTACK */
int ivec;			/* interrupt vector number */
long clk;			/* clock frequency */
int pclk;			/* PCLK or RTxC for clock */
int hwtype;			/* selection of special hardware types */
int hwparam;			/* extra parameter for special hardware */
{
	int chip,chan;
	ioaddr chipbase;
	register ioaddr ctrl;
    int i_state,d;
    int dum = 1;

#define z 0

	if(Sccinfo.init){
		printf("SCC driver already initialized - nothing done\n");
		return 1;
	}
	Sccinfo.init = 1;
	Sccinfo.nchips = nchips;
	Sccinfo.maxchan = (2 * nchips) - 1;
	Sccinfo.iobase = iobase;
	Sccinfo.space = space;
	Sccinfo.off[0] = aoff;
	Sccinfo.off[1] = boff;
	Sccinfo.doff = doff;
	Sccinfo.ivec = ivec;
	Sccinfo.clk = clk;
	Sccinfo.pclk = pclk;
	Sccinfo.hwtype = hwtype;
	Sccinfo.hwparam = hwparam;

	/* reset and pre-init all chips in the system */
	for(chip = 0; chip < nchips; chip++){
		chipbase = iobase + chip * space;
		ctrl = chipbase + Sccinfo.off[0];
		i_state = dirps();		/* because of 2-step accesses */
		VOID(RDREG(ctrl));		/* make sure pointer is written */
		WRSCC(ctrl,R9,FHWRES);		/* force hardware reset */
		for (d = 0; d < 1000; d++)	/* wait a while to be sure */
			dum *= 10;
		for(chan = 0; chan < 2; chan++){
			ctrl = chipbase + Sccinfo.off[chan];

			/* initialize a single channel to no-op */
			VOID(RDREG(ctrl));		/* make sure pointer is written */
			WRSCC(ctrl,R4,z);		/* no mode selected yet */
			WRSCC(ctrl,R1,z);		/* no W/REQ operation */
			WRSCC(ctrl,R2,16 * chip);	/* chip# in upper 4 bits of vector */
			WRSCC(ctrl,R3,z);		/* disable rx */
			WRSCC(ctrl,R5,z);		/* disable tx */
			WRSCC(ctrl,R9,VIS);		/* vector includes status, MIE off */
			Sccpolltab[chip][chan] = ctrl; /* store ctrl addr for polling */
		}
		if(hwtype & HWEAGLE)		/* this is an EAGLE card */
			WRREG(chipbase + 4,0x08);	/* enable interrupt on the board */

		if(hwtype & HWPC100)		/* this is a PC100 card */
			WRREG(chipbase,hwparam);	/* set the MODEM mode (22H normally) */

		if(hwtype & HWPRIMUS)		/* this is a PRIMUS-PC */
			WRREG(chipbase + 4,hwparam); /* set the MODEM mode (02H normally) */

		if (hwtype & HWDRSI) {		/* this is a DRSI PC*Packet card */
			ioaddr z8536 = chipbase + 7; /* point to 8536 master ctrl reg */

			/* Initialize 8536 to perform its divide-by-32 function */
			/* This part copied from N6TTO DRSI-driver */

			/* Start by forcing chip into known state */

			VOID(RDREG(z8536));		/* make sure pointer is written */
			WRSCC(z8536,CIO_MICR,0x01); /* force hardware reset */

			for (d = 0; d < 1000; d++)	/* wait a while to be sure */
				dum *= 10;

			WRSCC(z8536,CIO_MICR,0x00); /* Clear reset and start */

			/* Wait for chip to come ready */

			while (RDSCC(z8536,CIO_MICR) != 0x02)
				dum *= 10;

			WRSCC(z8536,CIO_MICR,0x26); /* NV|CT_VIS|RJA */
			WRSCC(z8536,CIO_MCCR,0xf4); /* PBE|CT1E|CT2E|CT3E|PAE */

			WRSCC(z8536,CIO_CTMS1,0xe2);/* Continuous, EOE, ECE, Pulse output */
			WRSCC(z8536,CIO_CTMS2,0xe2);/* Continuous, EOE, ECE, Pulse output */
			
	    WRSCC(z8536,CIO_CT1MSB,0x00); /* Load time constant CTC #1 */
			WRSCC(z8536,CIO_CT1LSB,0x10);
			WRSCC(z8536,CIO_CT2MSB,0x00); /* Load time constant CTC #2 */
			WRSCC(z8536,CIO_CT2LSB,0x10);

			WRSCC(z8536,CIO_IVR,0x06);

			/* Set port direction bits in port A and B		     */
			/* Data is input on bits d1 and d5, output on d0 and d4. */
			/* The direction is set by 1 for input and 0 for output  */

			WRSCC(z8536,CIO_PDCA,0x22);
			WRSCC(z8536,CIO_PDCB,0x22);

			WRSCC(z8536,CIO_CSR1,CIO_GCB|CIO_TCB); /* Start CTC #1 running */
			WRSCC(z8536,CIO_CSR2,CIO_GCB|CIO_TCB); /* Start CTC #2 running */
		}

		restore(i_state);
	}
	Sccpolltab[chip][0] = 0;	/* terminate the polling table */
	Sccvecloc = intack;		/* location of INTACK/vector read */
	Sccmaxvec = 16 * nchips;	/* upper limit on valid vector */
	/* save original interrupt vector */
	Orgivec = getirq(ivec);

	if(intack){	/* INTACK method selected? */
		/* set interrupt vector to INTACK-generating routine  */
		setirq(ivec,sccvec);
	} else {
		/* set interrupt vector to polling routine */
		setirq(ivec,sccnovec);
	}
	/* enable the interrupt  */
	maskon(ivec);
	return 0;
}

/* initialize an SCC channel in asynchronous mode */
static int
scc_async(scc)
register struct sccchan *scc;
{
	int i_state;
	register struct fifo *fp = &(scc->fifo);
	
	if((fp->buf = malloc(scc->bufsiz)) == NULLCHAR){
		tprintf("scc%d: No space for rx buffer\n",scc->iface->dev);
		return -1;
	}
	fp->bufsize = scc->bufsiz;
	fp->wp = fp->rp = fp->buf;
	fp->cnt = 0;

	scc->int_transmit = scc_asytx;	/* set interrupt handlers */
	scc->int_extstat = scc_asyex;
	scc->int_receive = scc_asyrx;
	scc->int_special = scc_asysp;

	i_state = dirps();

	wr(scc,R4,X16CLK|SB1);		/* *16 clock, 1 stopbit, no parity */
	wr(scc,R1,z);			/* no W/REQ operation */
	wr(scc,R3,Rx8);			/* RX 8 bits/char, disabled */
	wr(scc,R5,Tx8|DTR|RTS);		/* TX 8 bits/char, disabled, DTR RTS */
	wr(scc,R9,VIS);			/* vector includes status */
	wr(scc,R10,NRZ|z);			/* select NRZ */
	wr(scc,R11,RCBR|TCBR);		/* clocks are BR generator */
	wr(scc,R14,Sccinfo.pclk? BRSRC:z);	/* brg source = PCLK/RTxC */
	wr(scc,R15,BRKIE);			/* enable BREAK ext/status int */

	or(scc,R3,RxENABLE);		/* enable receiver */
	or(scc,R5,TxENAB);			/* enable transmitter */

	WRREG(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
	WRREG(scc->ctrl,RES_EXT_INT);	/* must be done twice */
	scc->status = RDREG(scc->ctrl);	/* read initial status */

	or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
	or(scc,R9,MIE);			/* master interrupt enable */

	restore(i_state);
	return 0;
}

/* initialize an SCC channel in SDLC mode */
static void
scc_sdlc(scc)
register struct sccchan *scc;
{
	int i_state;

	scc->int_transmit = scc_sdlctx;	/* set interrupt handlers */
	scc->int_extstat = scc_sdlcex;
	scc->int_receive = scc_sdlcrx;
	scc->int_special = scc_sdlcsp;

	i_state = dirps();

	wr(scc,R4,X1CLK|SDLC);		/* *1 clock, SDLC mode */
	wr(scc,R1,z);			/* no W/REQ operation */
	wr(scc,R3,Rx8|RxCRC_ENAB);	/* RX 8 bits/char, CRC, disabled */
	wr(scc,R5,Tx8|DTR|TxCRC_ENAB);	/* TX 8 bits/char, disabled, DTR */
	wr(scc,R6,z);			/* SDLC address zero (not used) */
	wr(scc,R7,FLAG);		/* SDLC flag value */
	wr(scc,R9,VIS);			/* vector includes status */
	wr(scc,R10,CRCPS|NRZI|ABUNDER);	/* CRC preset 1, select NRZI, ABORT on underrun */

    if (scc->extclock){			/* when using external clocks */
		/* RXclk RTxC, TXclk TRxC. */
		wr(scc,R11,RCRTxCP|TCTRxCP);
		wr(scc,R14,z);			/* No BRG options */
		WRSCC(scc->ctrl,R14,DISDPLL|scc->wreg[R14]); /* No DPLL operation */
    } else {
		if(scc->fulldup){		/* when external clock divider */
			if(Sccinfo.pclk){	/* when using PCLK as clock source */
				/* RXclk DPLL, TXclk RTxC, out=BRG.	 external /32 TRxC->RTxC */
				wr(scc,R11,RCDPLL|TCRTxCP|TRxCOI|TRxCBR);
				} else {
					/* RXclk DPLL, TXclk TRxC.	external TX clock to TRxC */
					wr(scc,R11,RCDPLL|TCTRxCP);
					}
			} else {			/* only half-duplex operation */
				/* RXclk DPLL, TXclk BRG. BRG reprogrammed at every TX/RX switch */
#ifdef	notdef	/* KA9Q - for PSK modem */
				wr(scc,R11,RCDPLL|TCBR);
#else
				/* DPLL -> Rx clk, DPLL -> Tx CLK, TxCLK -> TRxC pin */
				wr(scc,R11,RCDPLL|TCDPLL|TRxCOI|TRxCDP);
#endif
			}
		wr(scc,R14,Sccinfo.pclk? BRSRC:z);	/* BRG source = PCLK/RTxC */
		WRSCC(scc->ctrl,R14,SSBR|scc->wreg[R14]); /* DPLL source = BRG */
		WRSCC(scc->ctrl,R14,SNRZI|scc->wreg[R14]); /* DPLL NRZI mode */
	}
	wr(scc,R15,BRKIE|CTSIE|DCDIE);	/* enable ABORT, CTS & DCD interrupts */

	if(RDREG(scc->ctrl) & DCD){	/* DCD is now ON */
		if (!scc->extclock)
			WRSCC(scc->ctrl,R14,SEARCH|scc->wreg[R14]); /* DPLL: enter search mode */
		or(scc,R3,ENT_HM|RxENABLE);	/* enable the receiver, hunt mode */
	}
	WRREG(scc->ctrl,RES_EXT_INT);	/* reset ext/status interrupts */
	WRREG(scc->ctrl,RES_EXT_INT);	/* must be done twice */
	scc->status = RDREG(scc->ctrl);	/* read initial status */

	or(scc,R1,INT_ALL_Rx|TxINT_ENAB|EXT_INT_ENAB); /* enable interrupts */
	or(scc,R9,MIE);			/* master interrupt enable */

	restore(i_state);
}

/* set SCC channel speed
 * clkmode specifies the division rate (1,16,32) inside the SCC
 * returns the selected brgrate for "real speed" calculation
 */
static unsigned int
scc_speed(scc,clkmode,speed)
register struct sccchan *scc;
unsigned int clkmode;
long speed;				/* the desired baudrate */
{
	unsigned int brgrate;
	long spdclkm;
	int i_state;

	/* calculate baudrate generator value */

    if ((spdclkm = speed * clkmode) == 0)
	return 65000U;			/* avoid divide-by-zero */

	brgrate = (unsigned) ((Sccinfo.clk + spdclkm) / (spdclkm * 2)) - 2;

	i_state = dirps();		/* 2-step register accesses... */

	cl(scc,R14,BRENABL);		/* disable baudrate generator */
	wr(scc,R12,brgrate);		/* brg rate LOW */
	wr(scc,R13,brgrate >> 8);		/* brg rate HIGH */
	or(scc,R14,BRENABL);		/* enable baudrate generator */

	restore(i_state);
	return brgrate;
}

/* de-activate SCC channel */
static int
scc_stop(ifp)
struct iface *ifp;
{
	struct sccchan *scc = Sccchan[ifp->dev];
	int i_state;

	i_state = dirps();

	VOID(RDREG(scc->ctrl));		/* make sure pointer is written */
	wr(scc,R9,(ifp->dev % 2)? CHRB : CHRA); /* reset the channel */

	switch(ifp->type){
	case CL_SERIAL_LINE:
	case CL_KISS:
		free(scc->fifo.buf);
	default:
		break;
	}
	free(scc);
	Sccchan[ifp->dev] = NULLCHAN;
	restore(i_state);
	return 0;
}

/* de-activate SCC driver on program exit */
void
sccstop()
{
	if(Sccinfo.init){			/* was it initialized? */
		maskoff(Sccinfo.ivec);		/* disable the interrupt */
		setirq(Sccinfo.ivec,Orgivec);	/* restore original interrupt vector */
	}
}

/* perform ioctl on SCC (async) channel
 * this is used for SLIP mode only, and will read/set the line speed
 */
static int
scc_aioctl(ifp,argc,argv)
struct iface *ifp;
int argc;
char *argv[];
{
	struct sccchan *scc;
	unsigned int brgrate;

	scc = Sccchan[ifp->dev];

	if(argc < 1){
		printf("%ld\n",scc->speed);
		return 0;
	}
	brgrate = scc_speed(scc,16,atol(argv[0]));
	scc->speed = Sccinfo.clk / (32L * (brgrate + 2));
	return 0;
}

/* perform ioctl on SCC (sdlc) channel
 * this is used for AX.25 mode only, and will set the "kiss" parameters
 */
static int
scc_sioctl(ifp,argc,argv)
struct iface *ifp;
int argc;
char *argv[];
{
	struct sccchan *scc;
	int param,i_state;
    unsigned int brgrate;
	
	scc = Sccchan[ifp->dev];

	if(argc == 0){
		if (scc->extclock)
			printf("ext.clk");
		else
			printf("speed=%s%ld",scc->fulldup? "d":"",scc->speed);

		printf("group=%03x tx=%c",scc->group,scc->tx_inhibit? 'n':'y');

		for(param = 1; param < sizeof(scc->a.params); param++)
			printf(" %d=%d",param,scc->a.params[param]);

		printf("\n");
		return 0;
	}
	if(argc < 2){
		printf("Data field missing\n");
		return 1;
	}
	switch(argv[0][0]){			/* check special param setting */
	case 'g':				/* setting of group */
		scc->group = htoi(argv[1]);
		return 0;
    case 's':				/* setting of speed */
	if (!scc->extclock) {
	    brgrate = scc_speed(scc,32,atol(argv[1]));/* init SCC speed */
	    scc->speed = Sccinfo.clk / (64L * (brgrate + 2));/* calc real speed */
	}
	return 0;	
	case 't':				/* set tx inhibit */
	if (tolower(argv[1][0]) == 'n')
	    scc->tx_inhibit = 1;	/* no tx = inhibit it */
	else
	    scc->tx_inhibit = 0;
	return 0;
	}

	if((param = atoi(argv[0])) <= 0 || param >= sizeof(scc->a.params)){
		printf("Param number out of range\n");
		return 1;
	}
	scc->a.params[param] = atoi(argv[1]);

	/* and now, a special hack to allow setting of DTR with param #6 */
	if(param == TNCSPECIF){
		i_state = dirps();

		if(scc->a.params[TNCSPECIF]) /* set or clear DTR image bit */
			scc->wreg[R5] |= DTR;
		else
			scc->wreg[R5] &= ~DTR;
	
		if(scc->a.tstate == IDLE && scc->timercount == 0)
			scc->timercount = 1;	/* force an update */

		restore(i_state);
	}
	return 0;
}

/* start SCC transmitter when it is idle (SLIP/KISS mode only) */
static void
scc_sstart(scc)
register struct sccchan *scc;
{
	if(scc->tbp != NULLBUF ||	/* busy */
	 scc->sndq == NULLBUF)	/* no work */
		return;

	scc->tbp = dequeue(&scc->sndq);
	WRREG(scc->data,FR_END);
}

/* show SCC status */
int
dosccstat()
{
	register struct sccchan *scc;
	int i;

	if(!Sccinfo.init){
		printf("SCC driver not initialized\n");
		return 0;
	}
	printf("Ch Iface    Sent   Rcvd   Error Space Overr   Rxints   Txints   Exints   Spints\n");

	for(i = 0; i <= Sccinfo.maxchan; i++){
		if((scc = Sccchan[i]) == NULLCHAN)
			continue;

		if(scc->int_receive == scc_asyrx)
			printf("%2d %-6s  ** asynch ** %7lu %5u %5u %8lu %8lu %8lu %8lu\n",i,scc->iface->name,
			    scc->rxerrs,scc->nospace,scc->rovers,
			    scc->rxints,scc->txints,scc->exints,scc->spints);
		else
			printf("%2d %-6s %6lu %6lu %7lu %5u %5u %8lu %8lu %8lu %8lu\n",i,scc->iface->name,
			    scc->enqueued,scc->rxframes,scc->rxerrs,scc->nospace,scc->rovers,
			    scc->rxints,scc->txints,scc->exints,scc->spints);
	}
	return 0;
}

/* send raw frame to SCC. used for AX.25 */
static int
scc_raw(ifp,bp)
struct iface *ifp;
struct mbuf *bp;
{
	struct sccchan *scc;
	int i_state;

	dump(ifp,IF_TRACE_OUT,CL_AX25,bp);
	ifp->rawsndcnt++;
	ifp->lastsent = secclock();

	scc = Sccchan[ifp->dev];

	if (scc->tx_inhibit){		/* transmitter inhibit */
		free_p(bp);
		return -1;
    }

	enqueue(&scc->sndq,bp);		/* enqueue packet */
	scc->enqueued++;

	i_state = dirps();

	if(scc->a.tstate == IDLE){	/* when transmitter is idle */
		scc->a.tstate = DEFER;	/* start the key-up sequence */
		scc->a.maxdefer = TPS * scc->a.params[IDLETIME] /
			scc->a.params[SLOTTIME];
		scc->timercount = scc->a.params[WAITTIME];
	}
	restore(i_state);
	return 0;
}

static int
scc_send(dev,bp)
int dev;
struct mbuf *bp;
{
	struct sccchan *scc;

	scc = Sccchan[dev];
	enqueue(&scc->sndq,bp);

	if(scc->tbp == NULLBUF)
		scc_sstart(scc);
	return(0);
}

/* initialize interface for AX.25 use */
static int
scc_call(ifp,call)
register struct iface *ifp;
char *call;
{
	char out[AXALEN];

	ifp->hwaddr = mallocw(AXALEN);
	if(setcall(out,call) == 0)
		memcpy(ifp->hwaddr,out,AXALEN);
	else
		memcpy(ifp->hwaddr,Mycall,AXALEN);
	return 0;
}

/* Interrupt handlers for asynchronous modes (kiss, slip) */

/* Transmitter interrupt handler */
/* This routine sends data from mbufs in SLIP format */
static void
scc_asytx(scc)
register struct sccchan *scc;
{
	register struct mbuf *bp;

	scc->txints++;

	if(scc->txchar != 0){		/* a character pending for transmit? */
		WRREG(scc->data,scc->txchar);	/* send it now */
		scc->txchar = 0;		/* next time, ignore it */
		return;
	}

	if(scc->tbp == NULLBUF){	/* nothing to send? */
		if((scc->tbp = scc->sndq) != NULLBUF){ /* dequeue next frame */
			scc->sndq = scc->sndq->anext;
			WRREG(scc->data,FR_END);	/* send FR_END to flush line garbage */
		} else {
			WRREG(scc->ctrl,RES_Tx_P);	/* else only reset pending int */
		}
		return;
	}
	while ((bp = scc->tbp)->cnt == 0){ /* nothing left in this mbuf? */
		bp = bp->next;			/* save link to next */

		free_mbuf(scc->tbp);

		if((scc->tbp = bp) == NULLBUF){ /* see if more mbufs follow */
			WRREG(scc->data,FR_END);	/* frame complete, send FR_END */
			return;
		}
	}
	/* now bp = scc->tbp (either from while or from if stmt above) */

	WRREG(scc->data,*(bp->data));	/* just send the character */
	bp->cnt--;				/* decrease mbuf byte count */
	bp->data++;				/* and increment the data pointer */
}

/* External/Status interrupt handler */
static void
scc_asyex(scc)
register struct sccchan *scc;
{
	register unsigned char status,changes;

	scc->exints++;
	status = RDREG(scc->ctrl);
	changes = status ^ scc->status;

	if(changes & BRK_ABRT){		/* BREAK? */
		if((status & BRK_ABRT) == 0)	/* BREAK now over? */
			VOID(RDREG(scc->data));	/* read the NUL character */
	}
	scc->status = status;
	WRREG(scc->ctrl,RES_EXT_INT);
}

/* Receiver interrupt handler under NOS.
 * Since the higher serial protocol routines are all written to work
 * well with the routines in 8250.c, it makes sense to handle
 * asynch i/o with the 8530 in a similar manner. Therefore, these
 * routines are as close to their counterparts in 8250.c as possible.
 */
 
static void
scc_asyrx(scc)
register struct sccchan *scc;
{
	register struct fifo *fp;
	char c;

	scc->rxints++;

	fp = &(scc->fifo);
	do {
		c = RDREG(scc->data);
		if(fp->cnt != fp->bufsize){
			*fp->wp++ = c;
			if(fp->wp >= &fp->buf[fp->bufsize])
				fp->wp = fp->buf;
			fp->cnt++;
		} else
			scc->nospace++;
	} while(RDREG(scc->ctrl) & Rx_CH_AV);
	psignal(fp,1);	/* eventually move this to timer routine */
}

/* Blocking read from asynch input.
 * Essentially the same as get_asy() in 8250.c
 * See comments in asy_rxint().
 */
static int
get_scc(dev)
int dev;
{
	char i_state;
	register struct fifo *fp;
	char c;

	fp = &(Sccchan[dev]->fifo);

	i_state = dirps();
	while(fp->cnt == 0)
		pwait(fp);
	fp->cnt--;
	restore(i_state);

	c = *fp->rp++;
	if(fp->rp >= &fp->buf[fp->bufsize])
		fp->rp = fp->buf;

	return uchar(c);
}

int
scc_frameup(dev)
int dev;
{
	Sccchan[dev]->rxframes++;
	return 0;
}

/* Receive Special Condition interrupt handler */
static void
scc_asysp(scc)
register struct sccchan *scc;
{
	register unsigned char status;

	scc->spints++;

	status = rd(scc,R1);		/* read receiver status */
	VOID(RDREG(scc->data));		/* flush offending character */

	if(status & (CRC_ERR | Rx_OVR))		/* did a framing error or overrun occur ? */
		scc->rovers++;			/* report as overrun */

	WRREG(scc->ctrl,ERR_RES);
}

/* Interrupt handlers for sdlc mode (AX.25) */

/* Transmitter interrupt handler */
static void
scc_sdlctx(scc)
register struct sccchan *scc;
{
	register struct mbuf *bp;

	scc->txints++;

	switch(scc->a.tstate){		/* look at transmitter state */
	case ACTIVE:			/* busy sending data bytes */
		while ((bp = scc->tbp)->cnt == 0){	/* nothing left in this mbuf? */
			bp = bp->next;			/* save link to next */
			free_mbuf(scc->tbp);	/*KM*/
			if((scc->tbp = bp) == NULLBUF){/* see if more mbufs follow */
				if(RDREG(scc->ctrl) & TxEOM){	/* check tx underrun status */
					scc->rovers++;		/* oops, an underrun! count them */
					WRREG(scc->ctrl,SEND_ABORT);/* send an abort to be sure */
					scc->a.tstate = TAIL;	/* key down tx after TAILTIME */
					scc->timercount = scc->a.params[TAILTIME];
					return;
				}
				cl(scc,R10,ABUNDER);		/* frame complete, allow CRC transmit */
				scc->a.tstate = FLUSH;
				WRREG(scc->ctrl,RES_Tx_P);	/* reset pending int */
				return;
			}
		}
		/* now bp = scc->tbp (either from while or from if stmt above) */
		WRREG(scc->data,*(bp->data++)); /* send the character */
		bp->cnt--;			/* decrease mbuf byte count */
		return;
	case FLUSH:	/* CRC just went out, more to send? */
		or(scc,R10,ABUNDER);		/* re-install underrun protection */
		/* verify that we are not exeeding max tx time (if defined) */
		if((scc->timercount != 0 || scc->a.params[MAXKEYUP] == 0) &&
		 (scc->tbp = scc->sndq) != NULLBUF){ /* dequeue a frame */
			scc->sndq = scc->sndq->anext;
			WRREG(scc->ctrl,RES_Tx_CRC); /* reset the TX CRC generator */
			scc->a.tstate = ACTIVE;
			scc_sdlctx(scc);		/* write 1st byte */
			WRREG(scc->ctrl,RES_EOM_L); /* reset the EOM latch */
			return;
		}
		scc->a.tstate = TAIL;		/* no more, key down tx after TAILTIME */
		scc->timercount = scc->a.params[TAILTIME];
		WRREG(scc->ctrl,RES_Tx_P);
		return;
	default:				/* another state */
		WRREG(scc->ctrl,RES_Tx_P);	/* then don't send anything */
		return;
	}
}

/* External/Status interrupt handler */
static void
scc_sdlcex(scc)
register struct sccchan *scc;
{
	register unsigned char status,changes;

	scc->exints++;
	status = RDREG(scc->ctrl);
	changes = status ^ scc->status;

	if(changes & BRK_ABRT){		/* Received an ABORT */
		if(status & BRK_ABRT){		/* is this the beginning? */
			if(scc->rbp != NULLBUF){/* did we receive something? */
				/* check if a significant amount of data came in */
				/* this is because the drop of DCD tends to generate an ABORT */
				if(scc->rbp->next != NULLBUF || scc->rbp->cnt > sizeof(struct phdr))
				scc->rxerrs++;	/* then count it as an error */
				scc_tossb(scc);		/* throw away buffer */
			}
			VOID(RDREG(scc->data));	/* flush the FIFO */
			VOID(RDREG(scc->data));
			VOID(RDREG(scc->data));
		}
	}
	if(changes & CTS){			/* CTS input changed state */
		if(status & CTS){		/* CTS is now ON */
			if(scc->a.tstate == KEYWT &&
				scc->a.params[TXDELAY] == 0) /* zero TXDELAY = wait for CTS */
			scc->timercount = 1;	/* it will start within 10 ms */
		}
	}
	if(changes & DCD){			/* DCD input changed state */
		if(status & DCD){		/* DCD is now ON */
			if (!scc->extclock)
				WRSCC(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 */
			VOID(RDREG(scc->data));	/* flush the FIFO */
			VOID(RDREG(scc->data));
			VOID(RDREG(scc->data));
			if(scc->rbp != NULLBUF){/* did we receive something? */
				/* check if a significant amount of data came in */
				/* this is because some characters precede the drop of DCD */
				if(scc->rbp->next != NULLBUF || scc->rbp->cnt > sizeof(struct phdr))
				scc->rxerrs++;	/* then count it as an error */
				scc_tossb(scc);		/* throw away buffer */
			}
		}
	}
	scc->status = status;
	WRREG(scc->ctrl,RES_EXT_INT);
}

/* Receiver interrupt handler */
static void
scc_sdlcrx(scc)
register struct sccchan *scc;
{
	register struct mbuf *bp;

	scc->rxints++;

	if((bp = scc->rbp1) == NULLBUF){ /* no buffer available now */
		if(scc->rbp == NULLBUF){
			if((bp = alloc_mbuf(scc->bufsiz+sizeof(struct phdr))) != NULLBUF){
				scc->rbp = scc->rbp1 = bp;
				bp->cnt = sizeof(struct phdr);	/* get past the header */
			}
		} else if((bp = alloc_mbuf(scc->bufsiz)) != NULLBUF){
			scc->rbp1 = bp;
			for(bp = scc->rbp; bp->next != NULLBUF; bp = bp->next);
			bp->next = scc->rbp1;
			bp = scc->rbp1;
		}
		if(bp == NULLBUF){
			VOID(RDREG(scc->data));	/* so we have to discard the char */
			or(scc,R3,ENT_HM);		/* enter hunt mode for next flag */
			scc_tossb(scc);		/* put buffers back on pool */
			scc->nospace++;		/* count these events */
			return;
		}
	}

	/* now, we have a buffer (at bp). read character and store it */
	bp->data[bp->cnt++] = RDREG(scc->data);

	if(bp->cnt == bp->size)		/* buffer full? */
		scc->rbp1 = NULLBUF;	/* acquire a new one next time */
}

/* Receive Special Condition interrupt handler */
static void
scc_sdlcsp(scc)
register struct sccchan *scc;
{
	register unsigned char status;
	register struct mbuf *bp;
	struct phdr phdr;

	scc->spints++;

	status = rd(scc,R1);		/* read receiver status */
	VOID(RDREG(scc->data));		/* flush offending character */

	if(status & Rx_OVR){		/* receiver overrun */
		scc->rovers++;			/* count them */
		or(scc,R3,ENT_HM);		/* enter hunt mode for next flag */
		scc_tossb(scc);			/* rewind the buffer and toss */
	}
	if(status & END_FR &&		/* end of frame */
	scc->rbp != NULLBUF){	/* at least received something */
		if((status & CRC_ERR) == 0 &&	/* no CRC error is indicated */
		(status & 0xe) == RES8 &&	/* 8 bits in last byte */
		scc->rbp->cnt > sizeof(phdr)){

			/* we seem to have a good frame. but the last byte received */
			/* from rx interrupt is in fact a CRC byte, so discard it */
			if(scc->rbp1 != NULLBUF){
				scc->rbp1->cnt--;	/* current mbuf was not full */
			} else {
				for(bp = scc->rbp; bp->next != NULLBUF; bp = bp->next);
					/* find last mbuf */

				bp->cnt--;		/* last byte is first CRC byte */
			}

			phdr.iface = scc->iface;
			phdr.type = CL_AX25;
			memcpy(&scc->rbp->data[0],(char *)&phdr,sizeof(phdr));
			enqueue(&Hopper,scc->rbp);

			scc->rbp = scc->rbp1 = NULLBUF;
			scc->rxframes++;
		} else {			/* a bad frame */
			scc_tossb(scc);		/* throw away frame */
			scc->rxerrs++;
		}
	}
	WRREG(scc->ctrl,ERR_RES);
}

/* Throw away receive mbuf(s) when an error occurred */
static void
scc_tossb (scc)
register struct sccchan *scc;
{
	register struct mbuf *bp;
	
	if((bp = scc->rbp) != NULLBUF){
		free_p(bp->next);
		free_p(bp->dup);	/* Should be NULLBUF */
		bp->next = NULLBUF;
		scc->rbp1 = bp;		/* Don't throw this one away */
		bp->cnt = sizeof(struct phdr);	/* Simply rewind it */
	}
}

/* Switch the SCC to "transmit" mode */
/* Only to be called from an interrupt handler, while in AX.25 mode */
static void
scc_txon(scc)
register struct sccchan *scc;
{
    if (!scc->fulldup && !scc->extclock){ /* no fulldup divider? */
		cl(scc,R3,RxENABLE);		/* then switch off receiver */
		cl(scc,R5,TxENAB);		/* transmitter off during switch */
		scc_speed(scc,1,scc->speed);	/* reprogram baudrate generator */
	}
	or(scc,R5,RTS|TxENAB);		/* set the RTS line and enable TX */
	if(Sccinfo.hwtype & HWPRIMUS)	/* PRIMUS has another PTT bit... */
		WRREG(scc->ctrl + 4,Sccinfo.hwparam | 0x80); /* set that bit! */
}

/* Switch the SCC to "receive" mode (or: switch off transmitter)
 * Only to be called from an interrupt handler, while in AX.25 mode
 */
static void
scc_txoff(scc)
register struct sccchan *scc;
{
	cl(scc,R5,RTS);			/* turn off RTS line */
	if(Sccinfo.hwtype & HWPRIMUS)	/* PRIMUS has another PTT bit... */
		WRREG(scc->ctrl + 4,Sccinfo.hwparam); /* clear that bit! */

    if (!scc->fulldup && !scc->extclock){ /* no fulldup divider? */
		cl(scc,R5,TxENAB);		/* then disable the transmitter */
		scc_speed(scc,32,scc->speed);	/* back to receiver baudrate */
	}
}

/* SCC timer interrupt handler. Will be called every 1/TPS s by the 
 * routine systick in pc.c
 */
void scctimer()
{
	register struct sccchan *scc;
	register struct sccchan **sccp;
	char i_state;

	i_state = dirps();
	for(sccp = Sccchan + Sccinfo.maxchan; sccp >= Sccchan; sccp--){
		if((scc = *sccp) != NULLCHAN &&
		  scc->timercount != 0 &&
		  --(scc->timercount) == 0){
			/* handle an SCC timer event for this SCC channel
			 * this can only happen when the channel is AX.25 type
			 * (the SLIP/KISS driver does not use timers)
			 */
			switch(scc->a.tstate){
			case IDLE:			/* it was idle, this is FULLDUP2 timeout */
				scc_txoff(scc);		/* switch-off the transmitter */
				break;
			case DEFER:			/* trying to get the channel */
				/* operation is as follows:
				 * CSMA: when channel clear AND persistence randomgenerator
				 *	 wins, AND group restrictions allow it:
				 *		keyup the transmitter
				 *	 if not, delay one SLOTTIME and try again
				 * FULL: always keyup the transmitter
				 */
				if(scc->a.params[FULLDUP] == 0){
					Random = 21 * Random + 53;
					if(scc->status & DCD || scc->a.params[PERSIST] < Random){
						/* defer transmission again. check for limit */
defer_it:					if(--(scc->a.maxdefer) == 0){
							/* deferred too long. choice is to:
							 * - throw away pending frames, or
							 * - smash-on the transmitter and send them.
							 * the first would be the choice in a clean
							 * environment, but in the amateur radio world
							 * a distant faulty station could tie us up
							 * forever, so the second may be better...
							*/
#ifdef THROW_AWAY_AFTER_DEFER_TIMEOUT
							struct mbuf *bp,*bp1;

							while ((bp = scc->sndq) != NULLBUF){
								scc->sndq = scc->sndq->anext;
								free_p(bp);
							}
#else
							goto keyup; /* just keyup the transmitter... */
#endif
						}
						scc->timercount = scc->a.params[SLOTTIME];
						break;
					}
					if(uchar(scc->group) != NOGROUP){
						int i;
						struct sccchan *scc2;

						for(i = 0; i <= Sccinfo.maxchan; i++)
							if((scc2 = Sccchan[i]) != NULLCHAN &&
							 scc2 != scc &&
							 uchar(scc2->group) & uchar(scc->group) &&
							 ((scc->group & TXGROUP && scc2->wreg[R5] & RTS) ||
							 (scc->group & RXGROUP && scc2->status & DCD))){
								goto defer_it;
							}
					}
				}
			case KEYUP:			/* keyup transmitter (note fallthrough) */
keyup:				if((scc->wreg[R5] & RTS) == 0){ /* when not yet keyed */
					scc->a.tstate = KEYWT;
					scc->timercount = scc->a.params[TXDELAY]; /* 0 if CTSwait */
					scc_txon(scc);
					break;
				}
				/* when already keyed, directly fall through */
			case KEYWT:			/* waited for CTS or TXDELAY */
				/* when a frame is available (it should be...):
				 * - dequeue it from the send queue
				 * - reset the transmitter CRC generator
				 * - set a timeout on transmission length, if defined
				 * - send the first byte of the frame
				 * - reset the EOM latch
				 * when no frame available, proceed to TAIL handling
				 */
				if((scc->tbp = scc->sndq) != NULLBUF){
					scc->sndq = scc->sndq->anext;
					WRREG(scc->ctrl,RES_Tx_CRC);
					scc->a.tstate = ACTIVE;
					scc->timercount = TPS * scc->a.params[MAXKEYUP];
					scc_sdlctx(scc);
					WRREG(scc->ctrl,RES_EOM_L);
					break;
				}
				/* when no frame queued, fall through to TAIL case */
			case TAIL:			/* at end of frame */
				/* 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.	IDLETIME is an idle timeout in this case.
				 */	
				if(scc->a.params[FULLDUP] < 2){
					scc->a.tstate = IDLE;
					scc_txoff(scc);

					if(scc->sndq != NULLBUF){
						scc->a.tstate = DEFER;
						scc->a.maxdefer = TPS * scc->a.params[IDLETIME] /
						 scc->a.params[SLOTTIME];
						scc->timercount = TPS * scc->a.params[MINTIME];
					}
					break;
				}
				if(scc->sndq != NULLBUF){ /* still frames on the queue? */
					scc->a.tstate = KEYWT; /* continue sending */
					scc->timercount = TPS * scc->a.params[MINTIME]; /* after mintime */
				} else {
					scc->a.tstate = IDLE;
					scc->timercount = TPS * scc->a.params[IDLETIME];
				}
				break;
			case ACTIVE:	/* max keyup time expired */
			case FLUSH:	/* same while in flush mode */
				break;	/* no action required yet */
			default:			/* unexpected state */
				scc->a.tstate = IDLE; /* that should not happen, but... */
				scc_txoff(scc);		/* at least stop the transmitter */
				break;
			}
		}
	}
	restore(i_state);
}
