/*
 * cyclades cyclom-y serial driver
 *	Andrew Herbert <andrew@werple.apana.org.au>, 17 August 1993
 *
 * Copyright (c) 1993 Andrew Herbert.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name Andrew Herbert may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *	$Id: cy.c,v 1.7 1994/05/24 07:31:12 mycroft Exp $
 */

/*
 * Device minor number encoding:
 *
 *	c c x x u u u u		- bits in the minor device number
 *
 *	bits	meaning
 *	----	-------
 *	uuuu	physical serial line (i.e. unit) to use
 *			0-7 on a cyclom-8Y, 0-15 on a cyclom-16Y
 *	xx	unused
 *	cc	carrier control mode
 *			00	complete hardware carrier control of the tty.
 *				DCD must be high for the open(2) to complete.
 *			01	dialin pseudo-device (not yet implemented)
 *			10	carrier ignored until a high->low transition
 *			11	carrier completed ignored
 */

/*
 * Known deficiencies:
 *
 *	* no BREAK handling - breaks are ignored, and can't be sent either
 *	* no support for bad-char reporting, except via PARMRK
 *	* no support for dialin + dialout devices
 */

#include "cy.h"
#if NCY > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/syslog.h>

#include <machine/cpu.h>
#include <machine/pio.h>
#include <machine/cpufunc.h>

#include <i386/isa/isa_device.h>
#include <i386/isa/ic/cd1400.h>

#define RxFifoThreshold	3	/* 3 characters (out of 12) in the receive
				 * FIFO before an interrupt is generated
				 */
#define	FastRawInput	/* bypass the regular char-by-char canonical input
			 * processing whenever possible
			 */
#define	PollMode	/* use polling-based irq service routine, not the
			 * hardware svcack lines.  Must be defined for
			 * cyclom-16y boards.
			 * 
			 * XXX cyclom-8y doesn't work without this defined
			 * either (!)
			 */
#undef	LogOverruns	/* log receive fifo overruns */
#undef	TxBuffer	/* buffer driver output, to be slightly more
			 * efficient
			 *
			 * XXX presently buggy
			 */
#undef	Smarts		/* enable slightly more CD1400 intelligence.  Mainly
			 * the output CR/LF processing, plus we can avoid a
			 * few checks usually done in ttyinput().
			 *
			 * XXX not yet implemented, and not particularly
			 * worthwhile either.
			 */
#define CyDebug		/* include debugging code (minimal effect on
			 * performance)
			 */

#define CY_RX_BUFS		2	/* two receive buffers per port */
#define	CY_RX_BUF_SIZE		256	/* bytes per receive buffer */
#define	CY_TX_BUF_SIZE		512	/* bytes per transmit buffer */

/* #define CD1400s_PER_CYCLOM	1 */	/* cyclom-4y */
#define CD1400s_PER_CYCLOM	2	/* cyclom-8y */
/* #define CD1400s_PER_CYCLOM	4 */	/* cyclom-16y */

#if CD1400s_PER_CYCLOM < 4
#define CD1400_MEMSIZE		0x400	/* 4*256 bytes per chip: cyclom-[48]y */
#else
#define CD1400_MEMSIZE		0x100	/* 256 bytes per chip: cyclom-16y */
					/* XXX or is it 0x400 like the rest? */
#endif

#define PORTS_PER_CYCLOM	(CD1400_NO_OF_CHANNELS * CD1400s_PER_CYCLOM)
#define CYCLOM_RESET_16		0x1400			/* cyclom-16y reset */
#define CYCLOM_CLEAR_INTR	0x1800			/* intr ack address */
#define CYCLOM_CLOCK		25000000		/* baud rate clock */

#define	CY_UNITMASK		0x0f
#define	CY_CARRIERMASK		0xC0
#define CY_CARRIERSHIFT		6

#define	UNIT(x)		(minor(x) & CY_UNITMASK)
#define	CARRIER_MODE(x)	((minor(x) & CY_CARRIERMASK) >> CY_CARRIERSHIFT)

typedef u_char * volatile cy_addr;

int		cyprobe(struct isa_device *dev);
int		cyattach(struct isa_device *isdp);
void		cystart(struct tty *tp);
int		cyparam(struct tty *tp, struct termios *t);
int		cyspeed(int speed, int *prescaler_io);
static void	cy_channel_init(dev_t dev, int reset);
static void	cd1400_channel_cmd(cy_addr base, u_char cmd);

void		delay(int delay);

extern unsigned int	delaycount;	/* calibrated 1 ms cpu-spin delay */

struct	isa_driver cydriver = {
	cyprobe, cyattach, "cy"
};

/* low-level ping-pong buffer structure */

struct cy_buf {
	u_char		*next_char;	/* location of next char to write */
	u_int		free;		/* free chars remaining in buffer */
	struct cy_buf	*next_buf;	/* circular, you know */
	u_char		buf[CY_RX_BUF_SIZE];	/* start of the buffer */
};

/* low-level ring buffer */

#ifdef TxBuffer
struct cy_ring {
	u_char		buf[CY_TX_BUF_SIZE];
	u_char		*head;
	u_char		*tail;		/* next pos. to insert char */
	u_char		*endish;	/* physical end of buf */
	u_int		used;		/* no. of chars in queue */
};
#endif


/*
 * define a structure to keep track of each serial line
 */

struct cy {
	cy_addr		base_addr;	/* base address of this port's cd1400 */
	struct tty	*tty;
	u_int		dtrwait;	/* time (in ticks) to hold dtr low after close */
	u_int		recv_exception;	/* exception chars received */
	u_int		recv_normal;	/* normal chars received */
	u_int		xmit;		/* chars transmitted */
	u_int		mdm;		/* modem signal changes */
#ifdef CyDebug
	u_int		start_count;	/* no. of calls to cystart() */
	u_int		start_real;	/* no. of calls that did something */
#endif
	u_char		carrier_mode;	/* hardware carrier handling mode */
					/*
					 * 0 = always use
					 * 1 = always use (dialin port)
					 * 2 = ignore during open, then use it
					 * 3 = ignore completely
					 */
	u_char		carrier_delta;	/* true if carrier has changed state */
	u_char		fifo_overrun;	/* true if cd1400 receive fifo has... */
	u_char		rx_buf_overrun;	/* true if low-level buf overflow */
	u_char		intr_enable;	/* CD1400 SRER shadow */
	u_char		modem_sig;	/* CD1400 modem signal shadow */
	u_char		channel_control;/* CD1400 CCR control command shadow */
	u_char		cor[3];		/* CD1400 COR1-3 shadows */
#ifdef Smarts
	u_char		spec_char[4];	/* CD1400 SCHR1-4 shadows */
#endif
	struct cy_buf	*rx_buf;		/* current receive buffer */
	struct cy_buf	rx_buf_pool[CY_RX_BUFS];/* receive ping-pong buffers */
#ifdef TxBuffer
	struct cy_ring	tx_buf;		/* transmit buffer */
#endif
};

int	cydefaultrate = TTYDEF_SPEED;
cy_addr	cyclom_base;			/* base address of the card */
static	struct cy *info[NCY*PORTS_PER_CYCLOM];
struct	tty *cy_tty[NCY*PORTS_PER_CYCLOM];
static	volatile u_char timeout_scheduled = 0;	/* true if a timeout has been scheduled */

#ifdef CyDebug
u_int	cy_svrr_probes = 0;		/* debugging */
u_int	cy_timeouts = 0;
u_int	cy_timeout_req = 0;
#endif

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

int
cyprobe(struct isa_device *dev)
{
    int		i, j;
    u_char	version = 0;	/* firmware version */

    /* Cyclom-16Y hardware reset (Cyclom-8Ys don't care) */
    i = *(cy_addr)(dev->id_maddr + CYCLOM_RESET_16);

    delay(500);	/* wait for the board to get its act together (500 us) */

    for (i = 0; i < CD1400s_PER_CYCLOM; i++) {
	cy_addr	base = dev->id_maddr + i * CD1400_MEMSIZE;

	/* wait for chip to become ready for new command */
	for (j = 0; j < 100; j += 50) {
	    delay(50);	/* wait 50 us */

	    if (!*(base + CD1400_CCR))
	    	break;
	}

	/* clear the GFRCR register */
	*(base + CD1400_GFRCR) = 0;

	/* issue a reset command */
	*(base + CD1400_CCR) = CD1400_CMD_RESET;

	/* wait for the CD1400 to initialise itself */
	for (j = 0; j < 1000; j += 50) {
	    delay(50);	/* wait 50 us */

	    /* retrieve firmware version */
	    version = *(base + CD1400_GFRCR);
	    if (version)
		break;
	}

	/* anything in the 40-4f range is fine */
	if ((version & 0xf0) != 0x40) {
	    return 0;
	}
    }

    return 1;	/* found */
}


int
cyattach(struct isa_device *isdp)
{
/*    u_char	unit = UNIT(isdp->id_unit); */
    int		i, j, k;

    /* global variable used various routines */
    cyclom_base = (cy_addr)isdp->id_maddr;

    for (i = 0, k = 0; i < CD1400s_PER_CYCLOM; i++) {
	cy_addr	base = cyclom_base + i * CD1400_MEMSIZE;

	/* setup a 1ms clock tick */
	*(base + CD1400_PPR) = CD1400_CLOCK_25_1MS;

	for (j = 0; j < CD1400_NO_OF_CHANNELS; j++, k++) {
	    struct cy	*ip;

	    /*
	     * grab some space.  it'd be more polite to do this in cyopen(),
	     * but hey.
	     */
	    info[k] = ip = malloc(sizeof(struct cy), M_DEVBUF, M_WAITOK);

	    /* clear all sorts of junk */
	    bzero(ip, sizeof(struct cy));

	    ip->base_addr = base;

	    /* initialise the channel, without resetting it first */
	    cy_channel_init(k, 0);
	}
    }

    /* clear interrupts */
    *(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;

    return 1;
}


int
cyopen(dev_t dev, int flag, int mode, struct proc *p)
{
	u_int		unit = UNIT(dev);
	struct cy	*infop;
	cy_addr		base;
	struct tty	*tp;
	int		error = 0;
	u_char		carrier;

	if (unit >= PORTS_PER_CYCLOM)
		return (ENXIO);

	infop = info[unit];
	base = infop->base_addr;
	if (!cy_tty[unit])
	    infop->tty = cy_tty[unit] = ttymalloc();
	tp = infop->tty;

	tp->t_oproc = cystart;
	tp->t_param = cyparam;
	tp->t_dev = dev;
	if (!(tp->t_state & TS_ISOPEN)) {
		tp->t_state |= TS_WOPEN;
		ttychars(tp);
		if (tp->t_ispeed == 0) {
			tp->t_iflag = TTYDEF_IFLAG;
			tp->t_oflag = TTYDEF_OFLAG;
			tp->t_cflag = TTYDEF_CFLAG;
			tp->t_lflag = TTYDEF_LFLAG;
			tp->t_ispeed = tp->t_ospeed = cydefaultrate;
		}

		(void) spltty();
		cy_channel_init(unit, 1);	/* reset the hardware */

		/*
		 * raise dtr and generally set things up correctly.  this
		 * has the side-effect of selecting the appropriate cd1400
		 * channel, to help us with subsequent channel control stuff
		 */
		cyparam(tp, &tp->t_termios);

		/* check carrier, and set t_state's TS_CARR_ON flag accordingly */
		infop->modem_sig = *(base + CD1400_MSVR);
		carrier = infop->modem_sig & CD1400_MSVR_CD;

		if (carrier || (infop->carrier_mode >= 2))
			tp->t_state |= TS_CARR_ON;
		else
			tp->t_state &=~ TS_CARR_ON;

		/*
		 * enable modem & rx interrupts - relies on cyparam()
		 * having selected the appropriate cd1400 channel
		 */
		infop->intr_enable = (1 << 7) | (1 << 4);
		*(base + CD1400_SRER) = infop->intr_enable;

		ttsetwater(tp);
	} else if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0)
		return (EBUSY);

	if (!(flag & O_NONBLOCK))
		while (!(tp->t_cflag & CLOCAL) &&
		       !(tp->t_state & TS_CARR_ON) && !error)
			error = ttysleep(tp, (caddr_t)&tp->t_rawq,
		       			TTIPRI|PCATCH, ttopen, 0);
	(void) spl0();

	if (!error)
		error = (*linesw[(u_char)tp->t_line].l_open)(dev, tp);
	return (error);
} /* end of cyopen() */


void
cyclose_wakeup(caddr_t arg)
{
	wakeup(arg);
} /* end of cyclose_wakeup() */


int
cyclose(dev_t dev, int flag, int mode, struct proc *p)
{
	u_int		unit = UNIT(dev);
	struct cy	*infop = info[unit];
	struct tty	*tp = infop->tty;
	cy_addr		base = infop->base_addr;
	int		s;

	(*linesw[(u_char)tp->t_line].l_close)(tp, flag);

	s = spltty();
	/* select the appropriate channel on the CD1400 */
	*(base + CD1400_CAR) = (u_char)(unit & 0x03);

	/* disable this channel and lower DTR */
	infop->intr_enable = 0;
	*(base + CD1400_SRER) = (u_char)0;			/* no intrs */
	*(base + CD1400_DTR) = (u_char)CD1400_DTR_CLEAR;	/* no DTR */
	infop->modem_sig &= ~CD1400_MSVR_DTR;

	/* disable receiver (leave transmitter enabled) */
	infop->channel_control = (1 << 4) | (1 << 3) | 1;
	cd1400_channel_cmd(base, infop->channel_control);
	splx(s);

	ttyclose(tp);
#ifdef broken /* session holds a ref to the tty; can't deallocate */
	ttyfree(tp);
	infop->tty = cy_tty[unit] = (struct tty *)NULL;
#endif

	if (infop->dtrwait) {
		int error;

		timeout(cyclose_wakeup, (caddr_t)&infop->dtrwait, infop->dtrwait);
		do {
			error = tsleep((caddr_t)&infop->dtrwait,
					TTIPRI|PCATCH, "cyclose", 0);
		} while (error == ERESTART);
	}

	return 0;
} /* end of cyclose() */


int
cyread(dev_t dev, struct uio *uio, int flag)
{
	u_int		unit = UNIT(dev);
	struct tty	*tp = info[unit]->tty;

	return (*linesw[(u_char)tp->t_line].l_read)(tp, uio, flag);
} /* end of cyread() */


int
cywrite(dev_t dev, struct uio *uio, int flag)
{
	u_int		unit = UNIT(dev);
	struct tty	*tp = info[unit]->tty;

#ifdef Smarts
	/* XXX duplicate ttwrite(), but without so much output processing on
	 * CR & LF chars.  Hardly worth the effort, given that high-throughput
	 * sessions are raw anyhow.
	 */
#else
	return (*linesw[(u_char)tp->t_line].l_write)(tp, uio, flag);
#endif
} /* end of cywrite() */


#ifdef Smarts
/* standard line discipline input routine */
int
cyinput(int c, struct tty *tp)
{
	/* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK
	 * bits, as they are done by the CD1400.  Hardly worth the effort,
	 * given that high-throughput sessions are raw anyhow.
	 */
} /* end of cyinput() */
#endif /* Smarts */


inline static void
service_upper_rx(int unit)
{
	struct	cy *ip = info[unit];
	struct	tty *tp = ip->tty;
	struct	cy_buf *buf;
	int		i;
	u_char	*ch;

	buf = ip->rx_buf;

	/* give service_rx() a new one */
	disable_intr();		/* faster than spltty() */
	ip->rx_buf = buf->next_buf;
	enable_intr();

	if (tp->t_state & TS_ISOPEN) {
	    ch = buf->buf;
	    i = buf->next_char - buf->buf;

#ifdef FastRawInput
	    /* try to avoid calling the line discipline stuff if we can */
	    if ((tp->t_line == 0) &&
		    !(tp->t_iflag & (ICRNL | IMAXBEL | INLCR)) && 
		    !(tp->t_lflag & (ECHO | ECHONL | ICANON | IEXTEN |
			ISIG | PENDIN)) &&
		    !(tp->t_state & (TS_CNTTB | TS_LNCH))) {

		i = b_to_q(ch, i, &tp->t_rawq);
		if (i) {
			/*
			 * we have no RTS flow control support on cy-8
			 * boards, so this is really just tough luck
			 */

			log(LOG_WARNING, "cy%d: tty input queue overflow\n",
			    unit);
		}

		ttwakeup(tp);	/* notify any readers */
	    }
	    else
#endif /* FastRawInput */
	    {
		while (i--)
		    (*linesw[(u_char)tp->t_line].l_rint)((int)*ch++, tp);
	    }
	}

	/* clear the buffer we've just processed */
	buf->next_char = buf->buf;
	buf->free = CY_RX_BUF_SIZE;
} /* end of service_upper_rx() */


#ifdef TxBuffer
static void
service_upper_tx(int unit)
{
	struct	cy *ip = info[unit];
	struct	tty *tp = ip->tty;

	tp->t_state &=~ (TS_BUSY|TS_FLUSH);

	if (tp->t_outq.c_cc <= tp->t_lowat) {
		if (tp->t_state&TS_ASLEEP) {
			tp->t_state &= ~TS_ASLEEP;
			wakeup((caddr_t)&tp->t_outq);
		}
		selwakeup(&tp->t_wsel);
	}

	if (tp->t_outq.c_cc > 0) {
		struct	cy_ring *txq = &ip->tx_buf;
		int	free_count = CY_TX_BUF_SIZE - ip->tx_buf.used;
		u_char	*cp = txq->tail;
		int	count;
		int	chars_done;

		tp->t_state |= TS_BUSY;

		/* find the largest contig. copy we can do */
		count = ((txq->endish - cp) > free_count) ?
			    free_count : txq->endish - cp;

		count = ((cp + free_count) > txq->endish) ?
			    txq->endish - cp : free_count;

		/* copy the first slab */
		chars_done = q_to_b(&tp->t_outq, cp, count);

		/* check for wrap-around time */
		cp += chars_done;
		if (cp == txq->endish)
			cp = txq->buf;		/* back to the start */

		/* copy anything else, after we've wrapped around */
		if ((chars_done == count) && (count != free_count)) {
			/* copy the second slab */
			count = q_to_b(&tp->t_outq, cp, free_count - count);
			cp += count;
			chars_done += count;
		}

		/*
		 * update queue, protecting ourselves from any rampant
		 * lower-layers
		 */
		disable_intr();
		txq->tail = cp;
		txq->used += chars_done;
		enable_intr();
	}

	if (!tp->t_outq.c_cc)
		tp->t_state &=~ TS_BUSY;
} /* end of service_upper_tx() */
#endif /* TxBuffer */


inline static void
service_upper_mdm(int unit)
{
	struct	cy *ip = info[unit];

	if (ip->carrier_delta) {
		int	carrier = ip->modem_sig & CD1400_MSVR_CD;
		struct	tty *tp = ip->tty;

		if (!(*linesw[(u_char)tp->t_line].l_modem)(tp, carrier)) {
			cy_addr	base = ip->base_addr;

			/* clear DTR */
			disable_intr();
			*(base + CD1400_CAR) = (u_char)(unit & 0x03);
			*(base + CD1400_DTR) = (u_char)CD1400_DTR_CLEAR;
			ip->modem_sig &= ~CD1400_MSVR_DTR;
			ip->carrier_delta = 0;
			enable_intr();
		}
		else {
			disable_intr();
			ip->carrier_delta = 0;
			enable_intr();
		}
	}
} /* end of service_upper_mdm() */


/* upper level character processing routine */
static void
cytimeout(caddr_t ptr)
{
	int	unit;

	timeout_scheduled = 0;

#ifdef CyDebug
	cy_timeouts++;
#endif

	/* check each port in turn */
	for (unit = 0; unit < NCY*PORTS_PER_CYCLOM; unit++) {
		struct	cy *ip = info[unit];
#ifndef TxBuffer
		struct	tty *tp = ip->tty;
#endif

		/* ignore anything that is not open */
		if (!ip->tty)
			continue;

		/*
		 * any received chars to handle? (doesn't matter if intr routine
		 * kicks in while we're testing this)
		 */
		if (ip->rx_buf->free != CY_RX_BUF_SIZE)
			service_upper_rx(unit);

#ifdef TxBuffer
		/* anything to add to the transmit buffer (low-water mark)? */
		if (ip->tx_buf.used < CY_TX_BUF_SIZE/2)
			service_upper_tx(unit);
#else
		if (tp->t_outq.c_cc <= tp->t_lowat) {
			if (tp->t_state&TS_ASLEEP) {
				tp->t_state &= ~TS_ASLEEP;
				wakeup((caddr_t)&tp->t_outq);
			}
			selwakeup(&tp->t_wsel);
		}
#endif

		/* anything modem signals altered? */
		service_upper_mdm(unit);

		/* any overruns to log? */
#ifdef LogOverruns
		if (ip->fifo_overrun) {
			/*
			 * turn off the alarm - not important enough to bother
			 * with interrupt protection.
			 */
			ip->fifo_overrun = 0;

			log(LOG_WARNING, "cy%d: receive fifo overrun\n", unit);
		}
#endif
		if (ip->rx_buf_overrun) {
			/*
			 * turn off the alarm - not important enough to bother
			 * with interrupt protection.
			 */
			ip->rx_buf_overrun = 0;

			log(LOG_WARNING, "cy%d: receive buffer full\n", unit);
		}
	}
} /* cytimeout() */


inline static void
schedule_upper_service(void)
{
#ifdef CyDebug
    cy_timeout_req++;
#endif

    if (!timeout_scheduled) {
	timeout(cytimeout, (caddr_t)0, 1);	/* call next tick */
	timeout_scheduled = 1;
    }
} /* end of schedule_upper_service() */


/* initialise a channel on the cyclom board */

static void
cy_channel_init(dev_t dev, int reset)
{
	u_int	unit = UNIT(dev);
	int	carrier_mode = CARRIER_MODE(dev);
	struct	cy *ip = info[unit];
	cy_addr	base = ip->base_addr;
	struct	tty *tp = ip->tty;
	struct	cy_buf *buf, *next_buf;
	int	i;
#ifndef PollMode
	u_char	cd1400_unit;
#endif

	/* clear the structure and refill it */
	bzero(ip, sizeof(struct cy));
	ip->base_addr = base;
	ip->tty = tp;
	ip->carrier_mode = carrier_mode;

	/* select channel of the CD1400 */
	*(base + CD1400_CAR) = (u_char)(unit & 0x03);

	if (reset)
		cd1400_channel_cmd(base, 0x80);	/* reset the channel */

	/* set LIVR to 0 - intr routines depend on this */
	*(base + CD1400_LIVR) = 0;

#ifndef PollMode
	/* set top four bits of {R,T,M}ICR to the cd1400
	 * number, cd1400_unit
	 */
	cd1400_unit = unit / CD1400_NO_OF_CHANNELS;
	*(base + CD1400_RICR) = (u_char)(cd1400_unit << 4);
	*(base + CD1400_TICR) = (u_char)(cd1400_unit << 4);
	*(base + CD1400_MICR) = (u_char)(cd1400_unit << 4);
#endif

	ip->dtrwait = hz/4;	/* quarter of a second */

	/* setup low-level buffers */
	i = CY_RX_BUFS;
	ip->rx_buf = next_buf = &ip->rx_buf_pool[0];
	while (i--) {
		buf = &ip->rx_buf_pool[i];

		buf->next_char = buf->buf;	/* first char to use */
		buf->free = CY_RX_BUF_SIZE;	/* i.e. empty */
		buf->next_buf = next_buf;	/* where to go next */
		next_buf = buf;
	}

#ifdef TxBuffer
	ip->tx_buf.endish = ip->tx_buf.buf + CY_TX_BUF_SIZE;

	/* clear the low-level tx buffer */
	ip->tx_buf.head = ip->tx_buf.tail = ip->tx_buf.buf;
	ip->tx_buf.used = 0;
#endif

	/* clear the low-level rx buffer */
	ip->rx_buf->next_char = ip->rx_buf->buf;	/* first char to use */
	ip->rx_buf->free = CY_RX_BUF_SIZE;		/* completely empty */
} /* end of cy_channel_init() */


/* service a receive interrupt */
inline static void
service_rx(int cd, caddr_t base)
{
	struct cy	*infop;
	unsigned	count;
	int		ch;
	u_char		serv_type, channel;
#ifdef PollMode
	u_char		save_rir, save_car;
#endif

	/* setup */
#ifdef PollMode
	save_rir = *(base + CD1400_RIR);
	channel = cd * CD1400_NO_OF_CHANNELS + (save_rir & 0x3);
	save_car = *(base + CD1400_CAR);
	*(base + CD1400_CAR) = save_rir;	/* enter modem service */
	serv_type = *(base + CD1400_RIVR);
#else
	serv_type = *(base + CD1400_SVCACKR);	/* ack receive service */
	channel = ((u_char)*(base + CD1400_RICR)) >> 2;	/* get cyclom channel # */

#ifdef CyDebug
	if (channel >= PORTS_PER_CYCLOM) {
	    printf("cy: service_rx - channel %02x\n", channel);
	    panic("cy: service_rx - bad channel");
	}
#endif
#endif

	infop = info[channel];

	/* read those chars */
	if (serv_type & CD1400_RIVR_EXCEPTION) {
		/* read the exception status */
		u_char status = *(base + CD1400_RDSR);

		/* XXX is it a break?  Do something if it is! */

		/* XXX is IGNPAR not set?  Store a null in the buffer. */

#ifdef LogOverruns
		if (status & CD1400_RDSR_OVERRUN) {
#if 0
			ch |= TTY_PE;		/* for SLIP */
#endif
			infop->fifo_overrun++;
		}
#endif
		infop->recv_exception++;
	}
	else {
		struct	cy_buf *buf = infop->rx_buf;

		count = (u_char)*(base + CD1400_RDCR);	/* how many to read? */
		infop->recv_normal += count;
		if (buf->free < count) {
			infop->rx_buf_overrun += count;

			/* read & discard everything */
			while (count--)
				ch = (u_char)*(base + CD1400_RDSR);
		}
		else {
			/* slurp it into our low-level buffer */
			buf->free -= count;
			while (count--) {
				ch = (u_char)*(base + CD1400_RDSR);	/* read the char */
				*(buf->next_char++) = ch;
			}
		}
	}

#ifdef PollMode
	*(base + CD1400_RIR) = (u_char)(save_rir & 0x3f);	/* terminate service context */
#else
	*(base + CD1400_EOSRR) = (u_char)0;	/* terminate service context */
#endif
} /* end of service_rx */


/* service a transmit interrupt */
inline static void
service_tx(int cd, caddr_t base)
{
	struct cy	*ip;
#ifdef TxBuffer
	struct cy_ring	*txq;
#else
	struct tty	*tp;
#endif
	u_char		channel;
#ifdef PollMode
	u_char		save_tir, save_car;
#else
	u_char		vector;
#endif

	/* setup */
#ifdef PollMode
	save_tir = *(base + CD1400_TIR);
	channel = cd * CD1400_NO_OF_CHANNELS + (save_tir & 0x3);
	save_car = *(base + CD1400_CAR);
	*(base + CD1400_CAR) = save_tir;	/* enter tx service */
#else
	vector = *(base + CD1400_SVCACKT);	/* ack transmit service */
	channel = ((u_char)*(base + CD1400_TICR)) >> 2;	/* get cyclom channel # */

#ifdef CyDebug
	if (channel >= PORTS_PER_CYCLOM) {
	    printf("cy: service_tx - channel %02x\n", channel);
	    panic("cy: service_tx - bad channel");
	}
#endif
#endif

	ip = info[channel];
#ifdef TxBuffer
	txq = &ip->tx_buf;

	if (txq->used > 0) {
		cy_addr	base = ip->base_addr;
		int	count = min(CD1400_FIFOSIZE, txq->used);
		int	chars_done = count;
		u_char	*cp = txq->head;
		u_char	*buf_end = txq->endish;

		/* ip->state |= CY_BUSY; */
		while (count--) {
			*(base + CD1400_TDR) = *cp++;
			if (cp >= buf_end)
				cp = txq->buf;
		};
		txq->head = cp;
		txq->used -= chars_done; /* important that this is atomic */
		ip->xmit += chars_done;
	}

	/*
	 * disable tx intrs if no more chars to send.  we re-enable
	 * them in cystart()
	 */
	if (!txq->used) {
		ip->intr_enable &=~ (1 << 2);
		*(base + CD1400_SRER) = ip->intr_enable;
		/* ip->state &= ~CY_BUSY; */
	}
#else
	tp = ip->tty;

	if (!(tp->t_state & TS_TTSTOP) && (tp->t_outq.c_cc > 0)) {
		cy_addr	base = ip->base_addr;
		int	count = min(CD1400_FIFOSIZE, tp->t_outq.c_cc);

		ip->xmit += count;
		tp->t_state |= TS_BUSY;
		while (count--)
			*(base + CD1400_TDR) = getc(&tp->t_outq);
	}

	/*
	 * disable tx intrs if no more chars to send.  we re-enable them
	 * in cystart()
	 */
	if (!tp->t_outq.c_cc) {
		ip->intr_enable &=~ (1 << 2);
		*(base + CD1400_SRER) = ip->intr_enable;
		tp->t_state &= ~TS_BUSY;
	}
#endif

#ifdef PollMode
	*(base + CD1400_TIR) = (u_char)(save_tir & 0x3f);	/* terminate service context */
#else
	*(base + CD1400_EOSRR) = (u_char)0;	/* terminate service context */
#endif
} /* end of service_tx */


/* service a modem status interrupt */
inline static void
service_mdm(int cd, caddr_t base)
{
	struct cy	*infop;
	u_char		channel, deltas;
#ifdef PollMode
	u_char		save_mir, save_car;
#else
	u_char		vector;
#endif

	/* setup */
#ifdef PollMode
	save_mir = *(base + CD1400_MIR);
	channel = cd * CD1400_NO_OF_CHANNELS + (save_mir & 0x3);
	save_car = *(base + CD1400_CAR);
	*(base + CD1400_CAR) = save_mir;	/* enter modem service */
#else
	vector = *(base + CD1400_SVCACKM);	/* ack modem service */
	channel = ((u_char)*(base + CD1400_MICR)) >> 2;	/* get cyclom channel # */

#ifdef CyDebug
	if (channel >= PORTS_PER_CYCLOM) {
	    printf("cy: service_mdm - channel %02x\n", channel);
	    panic("cy: service_mdm - bad channel");
	}
#endif
#endif

	infop = info[channel];

	/* read the siggies and see what's changed */
	infop->modem_sig = (u_char)*(base + CD1400_MSVR);
	deltas = (u_char)*(base + CD1400_MISR);

	if ((infop->carrier_mode <= 2) && (deltas & CD1400_MISR_CDd))
		/* something for the upper layer to deal with */
		infop->carrier_delta = 1;

	infop->mdm++;

	/* terminate service context */
#ifdef PollMode
	*(base + CD1400_MIR) = (u_char)(save_mir & 0x3f);
#else
	*(base + CD1400_EOSRR) = (u_char)0;
#endif
} /* end of service_mdm */


int
cyintr(int unit)
{
    int		cd;
    u_char	status;

    /* check each CD1400 in turn */
    for (cd = 0; cd < CD1400s_PER_CYCLOM; cd++) {
	cy_addr	base = cyclom_base + cd*CD1400_MEMSIZE;

	/* poll to see if it has any work */
	while (status = (u_char)*(base + CD1400_SVRR)) {
#ifdef CyDebug
	    cy_svrr_probes++;
#endif
	    /* service requests as appropriate, giving priority to RX */
	    if (status & CD1400_SVRR_RX)
		    service_rx(cd, base);
	    if (status & CD1400_SVRR_TX)
		    service_tx(cd, base);
	    if (status & CD1400_SVRR_MDM)
		    service_mdm(cd, base);
	}
    }

    /* request upper level service to deal with whatever happened */
    schedule_upper_service();

    /* re-enable interrupts on the cyclom */
    *(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;

    return 1;
}


int
cyioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
{
	int		unit = UNIT(dev);
	struct cy	*infop = info[unit];
	struct tty	*tp = infop->tty;
	int		error;

	error = (*linesw[(u_char)tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
	if (error >= 0)
		return (error);
	error = ttioctl(tp, cmd, data, flag, p);
	if (error >= 0)
		return (error);

	switch (cmd) {
#ifdef notyet	/* sigh - more junk to do XXX */
	case TIOCSBRK:
		break;
	case TIOCCBRK:
		break;
	case TIOCSDTR:
		break;
	case TIOCCDTR:
		break;

	case TIOCMSET:
		break;
	case TIOCMBIS:
		break;
	case TIOCMBIC:
		break;
#endif /* notyet */

	case TIOCMGET: {
		int	bits = 0;
		u_char	status = infop->modem_sig;

		if (status & CD1400_MSVR_DTR) bits |= TIOCM_DTR | TIOCM_RTS;
		if (status & CD1400_MSVR_CD) bits |= TIOCM_CD;
		if (status & CD1400_MSVR_CTS) bits |= TIOCM_CTS;
		if (status & CD1400_MSVR_DSR) bits |= TIOCM_DSR;
#ifdef CYCLOM_16
		if (status & CD1400_MSVR_RI) bits |= TIOCM_RI;
#endif
		if (infop->channel_control & 0x02) bits |= TIOCM_LE;
		*(int *)data = bits;
		break;
	}

#ifdef TIOCMSBIDIR
	case TIOCMSBIDIR:
		return (ENOTTY);
#endif /* TIOCMSBIDIR */

#ifdef TIOCMGBIDIR
	case TIOCMGBIDIR:
		return (ENOTTY);
#endif /* TIOCMGBIDIR */

#ifdef TIOCMSDTRWAIT
	case TIOCMSDTRWAIT:
		/* must be root to set dtr delay */
			if (p->p_ucred->cr_uid != 0)
				return(EPERM);

			infop->dtrwait = *(u_int *)data;
			break;
#endif /* TIOCMSDTRWAIT */

#ifdef TIOCMGDTRWAIT
	case TIOCMGDTRWAIT:
		*(u_int *)data = infop->dtrwait;
		break;
#endif /* TIOCMGDTRWAIT */

	default:
		return (ENOTTY);
	}

	return 0;
} /* end of cyioctl() */


int
cyparam(struct tty *tp, struct termios *t)
{
	u_char		unit = UNIT(tp->t_dev);
	struct cy	*infop = info[unit];
	cy_addr		base = infop->base_addr;
	int		cflag = t->c_cflag;
	int		iflag = t->c_iflag;
	int		ispeed, ospeed;
	int		itimeout;
	int		iprescaler, oprescaler;
	int		s;
	u_char		cor_change = 0;
	u_char		opt;

	if (!t->c_ispeed)
	    t->c_ispeed = t->c_ospeed;

	s = spltty();

	/* select the appropriate channel on the CD1400 */
	*(base + CD1400_CAR) = unit & 0x03;

	/* handle DTR drop on speed == 0 trick */
	if (t->c_ospeed == 0) {
	    *(base + CD1400_DTR) = CD1400_DTR_CLEAR;
	    infop->modem_sig &= ~CD1400_MSVR_DTR;
	}
	else {
	    *(base + CD1400_DTR) = CD1400_DTR_SET;
	    infop->modem_sig |= CD1400_MSVR_DTR;
	}

	/* set baud rates if they've changed from last time */

	if ((ospeed = cyspeed(t->c_ospeed, &oprescaler)) < 0)
	    return EINVAL;
	*(base + CD1400_TBPR) = (u_char)ospeed;
	*(base + CD1400_TCOR) = (u_char)oprescaler;

	if ((ispeed = cyspeed(t->c_ispeed, &iprescaler)) < 0)
	    return EINVAL;
	*(base + CD1400_RBPR) = (u_char)ispeed;
	*(base + CD1400_RCOR) = (u_char)iprescaler;

	/*
	 * set receive time-out period
	 *	generate a rx interrupt if no new chars are received in
	 *	this many ticks
	 * don't bother comparing old & new VMIN, VTIME and ispeed - it
	 * can't be much worse just to calculate and set it each time!
	 * certainly less hassle. :-)
	 */

	/*
	 * calculate minimum timeout period:
	 *     5 ms or the time it takes to receive 1 char, rounded up to the
	 *     next ms, whichever is greater
	 */
	if (t->c_ispeed > 0) {
	    itimeout = (t->c_ispeed > 2200) ? 5 : (10000/t->c_ispeed + 1);

	    /* if we're using VTIME as an inter-char timeout, and it is set to
	     * be longer than the minimum calculated above, go for it
	     */
	    if (t->c_cc[VMIN] && t->c_cc[VTIME] && t->c_cc[VTIME]*10 > itimeout)
		itimeout = t->c_cc[VTIME]*10;

	    /* store it, taking care not to overflow the byte-sized register */
	    *(base + CD1400_RTPR) = (u_char)((itimeout <= 255) ? itimeout : 255);
	}


	/*
	 * channel control
	 *	receiver enable
	 *	transmitter enable (always set)
	 */
	opt = (1 << 4) | (1 << 3) | ((cflag & CREAD) ? (1 << 1) : 1);
	if (opt != infop->channel_control) {
	    infop->channel_control = opt;
	    cd1400_channel_cmd(base, opt);
	}

#ifdef Smarts
	/* set special chars */
	if (t->c_cc[VSTOP] != _POSIX_VDISABLE &&
		(t->c_cc[VSTOP] != infop->spec_char[0])) {
	    *(base + CD1400_SCHR1) = infop->spec_char[0] = t->c_cc[VSTOP];
	}
	if (t->c_cc[VSTART] != _POSIX_VDISABLE &&
		(t->c_cc[VSTART] != infop->spec_char[1])) {
	    *(base + CD1400_SCHR2) = infop->spec_char[0] = t->c_cc[VSTART];
	}
	if (t->c_cc[VINTR] != _POSIX_VDISABLE &&
		(t->c_cc[VINTR] != infop->spec_char[2])) {
	    *(base + CD1400_SCHR3) = infop->spec_char[0] = t->c_cc[VINTR];
	}
	if (t->c_cc[VSUSP] != _POSIX_VDISABLE &&
		(t->c_cc[VSUSP] != infop->spec_char[3])) {
	    *(base + CD1400_SCHR4) = infop->spec_char[0] = t->c_cc[VSUSP];
	}
#endif

	/*
	 * set channel option register 1 -
	 *	parity mode
	 *	stop bits
	 *	char length
	 */
	opt = 0;
	/* parity */
	if (cflag & PARENB) {
	    if (cflag & PARODD)
		opt |= 1 << 7;
	    opt |= 2 << 5;		/* normal parity mode */
	}
	if (!(iflag & INPCK))
	    opt |= 1 << 4;		/* ignore parity */
	/* stop bits */
	if (cflag & CSTOPB)
	    opt |= 2 << 2;
	/* char length */
	opt |= (cflag & CSIZE) >> 8;	/* nasty, but fast */
	if (opt != infop->cor[0]) {
	    cor_change |= 1 << 1;
	    *(base + CD1400_COR1) = opt;
	}

	/*
	 * set channel option register 2 -
	 *	flow control
	 */
	opt = 0;
#ifdef Smarts
	if (iflag & IXANY)
	    opt |= 1 << 7;		/* auto output restart on any char after XOFF */
	if (iflag & IXOFF)
	    opt |= 1 << 6;		/* auto XOFF output flow-control */
#endif
	if (cflag & CCTS_OFLOW)
	    opt |= 1 << 1;		/* auto CTS flow-control */
	if (opt != infop->cor[1]) {
	    cor_change |= 1 << 2;
	    *(base + CD1400_COR2) = opt;
	}

	/*
	 * set channel option register 3 -
	 *	receiver FIFO interrupt threshold
	 *	flow control
	 */
	opt = RxFifoThreshold;	/* rx fifo threshold */
#ifdef Smarts
	if (t->c_lflag & ICANON)
	    opt |= 1 << 6;		/* detect INTR & SUSP chars */
	if (iflag & IXOFF)
	    opt |= (1 << 5) | (1 << 4);	/* transparent in-band flow control */
#endif
	if (opt != infop->cor[2]) {
	    cor_change |= 1 << 3;
	    *(base + CD1400_COR3) = opt;
	}


	/* notify the CD1400 if COR1-3 have changed */
	if (cor_change) {
	    cor_change |= 1 << 6;	/* COR change flag */
	    cd1400_channel_cmd(base, cor_change);
	}

	/*
	 * set channel option register 4 -
	 *	CR/NL processing
	 *	break processing
	 *	received exception processing
	 */
	opt = 0;
	if (iflag & IGNCR)
	    opt |= 1 << 7;
#ifdef Smarts
	/*
	 * we need a new ttyinput() for this, as we don't want to
	 * have ICRNL && INLCR being done in both layers, or to have
	 * synchronisation problems
	 */
	if (iflag & ICRNL)
	    opt |= 1 << 6;
	if (iflag & INLCR)
	    opt |= 1 << 5;
#endif
	if (iflag & IGNBRK)
	    opt |= 1 << 4;
	if (!(iflag & BRKINT))
	    opt |= 1 << 3;
	if (iflag & IGNPAR)
#ifdef LogOverruns
	    opt |= 0;		/* broken chars cause receive exceptions */
#else
	    opt |= 2;		/* discard broken chars */
#endif
	else {
	    if (iflag & PARMRK)
		opt |= 4;	/* precede broken chars with 0xff 0x0 */
	    else
#ifdef LogOverruns
		opt |= 0;	/* broken chars cause receive exceptions */
#else
		opt |= 3;	/* convert framing/parity errs to nulls */
#endif
	}
	*(base + CD1400_COR4) = opt;

	/*
	 * set channel option register 5 -
	 */
	opt = 0;
	if (iflag & ISTRIP)
	    opt |= 1 << 7;
	if (t->c_iflag & IEXTEN) {
	    opt |= 1 << 6;	/* enable LNEXT (e.g. ctrl-v quoting) handling */
	}
#ifdef Smarts
	if (t->c_oflag & ONLCR)
	    opt |= 1 << 1;
	if (t->c_oflag & OCRNL)
	    opt |= 1;
#endif
	*(base + CD1400_COR5) = opt;

	/*
	 * set modem change option register 1
	 *	generate modem interrupts on which 1 -> 0 input transitions
	 *	also controls auto-DTR output flow-control, which we don't use
	 */
	opt = (cflag & CLOCAL) ? 0 : 1 << 4;	/* CD */
	*(base + CD1400_MCOR1) = opt;

	/*
	 * set modem change option register 2
	 *	generate modem interrupts on specific 0 -> 1 input transitions
	 */
	opt = (cflag & CLOCAL) ? 0 : 1 << 4;	/* CD */
	*(base + CD1400_MCOR2) = opt;

	splx(s);

	return 0;
} /* end of cyparam */


void
cystart(struct tty *tp)
{
	u_char		unit = UNIT(tp->t_dev);
	struct cy	*infop = info[unit];
	cy_addr		base = infop->base_addr;
	int		s;

#ifdef CyDebug
	infop->start_count++;
#endif

	/* check the flow-control situation */
	if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))
		return;

	if (tp->t_outq.c_cc <= tp->t_lowat) {
		if (tp->t_state&TS_ASLEEP) {
			tp->t_state &= ~TS_ASLEEP;
			wakeup((caddr_t)&tp->t_outq);
		}
		selwakeup(&tp->t_wsel);
	}

#ifdef TxBuffer
	service_upper_tx(unit);		/* feed the monster */
#endif

	s = spltty();

	if (!(infop->intr_enable & (1 << 2))) {
	    /* select the channel */
	    *(base + CD1400_CAR) = unit & (u_char)3;

	    /* (re)enable interrupts to set things in motion */
	    infop->intr_enable |= (1 << 2);
	    *(base + CD1400_SRER) = infop->intr_enable;

	    infop->start_real++;
	}

	splx(s);
} /* end of cystart() */


int
cystop(struct tty *tp, int flag)
{
	u_char		unit = UNIT(tp->t_dev);
	struct cy	*ip = info[unit];
	cy_addr		base = ip->base_addr;
	int		s;

	s = spltty();

	/* select the channel */
	*(base + CD1400_CAR) = unit & 3;

	/* halt output by disabling transmit interrupts */
	ip->intr_enable &=~ (1 << 2);
	*(base + CD1400_SRER) = ip->intr_enable;

	splx(s);

	return 0;
}


int
cyselect(dev_t dev, int rw, struct proc *p)
{
	struct tty	*tp = info[UNIT(dev)]->tty;
	int		s = spltty();
	int		nread;

	switch (rw) {

	case FREAD:
		nread = ttnread(tp);
		if (nread > 0 || 
		   ((tp->t_cflag&CLOCAL) == 0 && (tp->t_state&TS_CARR_ON) == 0))
			goto win;
		selrecord(p, &tp->t_rsel);
		break;

	case FWRITE:
		if (tp->t_outq.c_cc <= tp->t_lowat)
			goto win;
		selrecord(p, &tp->t_wsel);
		break;
	}
	splx(s);
	return (0);
  win:
	splx(s);
	return (1);
} /* end of cyselect() */


int
cyspeed(int speed, int *prescaler_io)
{
    int		actual;
    int		error;
    int		divider;
    int		prescaler;
    int		prescaler_unit;

    if (speed == 0)
	return 0;

    if (speed < 0 || speed > 150000)
	return -1;

    /* determine which prescaler to use */
    for (prescaler_unit = 4, prescaler = 2048; prescaler_unit;
	    prescaler_unit--, prescaler >>= 2) {
	if (CYCLOM_CLOCK/prescaler/speed > 63)
	    break;
    }

    divider = (CYCLOM_CLOCK/prescaler*2/speed + 1)/2;	/* round off */
    if (divider > 255)
	divider = 255;
    actual = CYCLOM_CLOCK/prescaler/divider;
    error = ((actual-speed)*2000/speed +1)/2;	/* percentage */

    /* 3.0% max error tolerance */
    if (error < -30 || error > 30)
	return -1;

#if 0
    printf("prescaler = %d (%d)\n", prescaler, prescaler_unit);
    printf("divider = %d (%x)\n", divider, divider);
    printf("actual = %d\n", actual);
    printf("error = %d\n", error);
#endif

    *prescaler_io = prescaler_unit;
    return divider;
} /* end of cyspeed() */


static void
cd1400_channel_cmd(cy_addr base, u_char cmd)
{
	unsigned maxwait = delaycount * 5;	/* approx. 5 ms */

	/* wait for processing of previous command to complete */
	while (*(base + CD1400_CCR) && maxwait--)
		;

	if (!maxwait)
		log(LOG_ERR, "cy: channel command timeout (%d loops) - arrgh\n",
		    delaycount * 5);

	*(base + CD1400_CCR) = cmd;
} /* end of cd1400_channel_cmd() */


#ifdef CyDebug
/* useful in ddb */
void
cyclear(void)
{
    /* clear the timeout request */
    disable_intr();
    timeout_scheduled = 0;
    enable_intr();
}

void
cyclearintr(void)
{
    /* clear interrupts */
    *(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;
}

int
cyparam_dummy(struct tty *tp, struct termios *t)
{
    return 0;
}

void
cyset(int unit, int active)
{
    if (unit < 0 || unit > PORTS_PER_CYCLOM) {
	printf("bad unit number %d\n", unit);
	return;
    }
    cy_tty[unit]->t_param = active ? cyparam : cyparam_dummy;
}


/* useful in ddb */
void
cystatus(int unit)
{
	struct cy	*infop = info[unit];
	struct tty	*tp = infop->tty;
	cy_addr		base = infop->base_addr;

	printf("info for channel %d\n", unit);
	printf("------------------\n");

	printf("cd1400 base address:\t0x%x\n", (int)infop->base_addr);

	/* select the port */
	*(base + CD1400_CAR) = (u_char)unit;

	printf("saved channel_control:\t%02x\n", infop->channel_control);
	printf("saved cor1:\t\t%02x\n", infop->cor[0]);
	printf("service request enable reg:\t%02x (%02x cached)\n",
		(u_char)*(base + CD1400_SRER), infop->intr_enable);
	printf("service request register:\t%02x\n",
		(u_char)*(base + CD1400_SVRR));
	printf("\n");
	printf("modem status:\t\t\t%02x (%02x cached)\n",
		(u_char)*(base + CD1400_MSVR), infop->modem_sig);
	printf("rx/tx/mdm interrupt registers:\t%02x %02x %02x\n",
		(u_char)*(base + CD1400_RIR), (u_char)*(base + CD1400_TIR),
		(u_char)*(base + CD1400_MIR));
	printf("\n");
	if (tp) {
		printf("tty state:\t\t\t%04x\n", tp->t_state);
		printf("upper layer queue lengths:\t%d raw, %d canon, %d output\n",
		    tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc);
	}
	else
		printf("tty state:\t\t\tclosed\n");
	printf("\n");

	printf("calls to cystart():\t\t%d (%d useful)\n",
		infop->start_count, infop->start_real);
	printf("\n");
	printf("total cyclom service probes:\t%d\n", cy_svrr_probes);
	printf("calls to upper layer:\t\t%d\n", cy_timeouts);
	printf("rx buffer chars free:\t\t%d\n", infop->rx_buf->free);
#ifdef TxBuffer
	printf("tx buffer chars used:\t\t%d\n", infop->tx_buf.used);
#endif
	printf("received chars:\t\t\t%d good, %d exception\n",
		infop->recv_normal, infop->recv_exception);
	printf("transmitted chars:\t\t%d\n", infop->xmit);
	printf("modem signal deltas:\t\t%d\n", infop->mdm);
	printf("\n");
} /* end of cystatus() */
#endif
#endif /* NCY > 0 */
