diff options
author | jkh <jkh@FreeBSD.org> | 1995-02-09 09:47:31 +0000 |
---|---|---|
committer | jkh <jkh@FreeBSD.org> | 1995-02-09 09:47:31 +0000 |
commit | e0092e86d6996fb1f1c53e3886ae04f44724fdc8 (patch) | |
tree | fa69beb33330f900df8ee32fefa2bc184a2a30de /sys/dev/cy | |
parent | 75ef70cb02684d8a5c6e856e94b546fd491731ea (diff) | |
download | FreeBSD-src-e0092e86d6996fb1f1c53e3886ae04f44724fdc8.zip FreeBSD-src-e0092e86d6996fb1f1c53e3886ae04f44724fdc8.tar.gz |
Add the Cyclades serial driver code (ALPHA) from Andrew Werple and
adapted to FreeBSD by Heikki Suonsivu <hsu@cs.hut.fi>.
Submitted by: Andrew Werple <andrew@werple.apana.org.au> and
Heikki Suonsivu <hsu@cs.hut.fi>
Obtained from: NetBSD
Diffstat (limited to 'sys/dev/cy')
-rw-r--r-- | sys/dev/cy/cy.c | 1682 | ||||
-rw-r--r-- | sys/dev/cy/cy_isa.c | 1682 |
2 files changed, 3364 insertions, 0 deletions
diff --git a/sys/dev/cy/cy.c b/sys/dev/cy/cy.c new file mode 100644 index 0000000..86a9604 --- /dev/null +++ b/sys/dev/cy/cy.c @@ -0,0 +1,1682 @@ +/* + * 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 + +/* This disgusing hack because we actually have 16 units on one controller */ +#if NCY < 2 +#undef NCY +#define NCY (16) +#endif + +#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> +#ifdef NetBSD +#include <machine/pio.h> +#endif +#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 (!) + */ +#define 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 */ + +/* FreeBSD's getty doesn't know option for setting RTS/CTS handshake. Its + getty, like a lot of other old cruft, should be replaced with something + which used POSIX tty interfaces which at least allow enabling it. In the + meantime, use the force. */ +#define ALWAYS_RTS_CTS 1 + +#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? */ +#define CYCLOM_16 1 /* This is a cyclom-16Y */ +#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); + +/* hsu@clinet.fi: sigh */ +#ifdef __NetBSD__ +#define DELAY(foo) delay(foo) +void delay(int delay); +#endif + +/* Better get rid of this until the core people agree on kernel interfaces. + At least it will then compile on both WhichBSDs. + */ +#if 0 +extern unsigned int delaycount; /* calibrated 1 ms cpu-spin delay */ +#endif + +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 */ +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 +#ifdef NetBSD + , p +#endif + ); + 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 +#ifndef ALWAYS_RTS_CTS + if (cflag & CCTS_OFLOW) +#endif + 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) +{ + /* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed, + as the card is probed every round? Replaced delaycount with 8k. + Either delaycount has to be implemented in FreeBSD or more sensible + way of doing these should be implemented. DELAY isn't enough here. + */ + unsigned maxwait = 5 * 8 * 1024; /* 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", + 5 * 8 * 1024); + + *(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 */ diff --git a/sys/dev/cy/cy_isa.c b/sys/dev/cy/cy_isa.c new file mode 100644 index 0000000..86a9604 --- /dev/null +++ b/sys/dev/cy/cy_isa.c @@ -0,0 +1,1682 @@ +/* + * 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 + +/* This disgusing hack because we actually have 16 units on one controller */ +#if NCY < 2 +#undef NCY +#define NCY (16) +#endif + +#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> +#ifdef NetBSD +#include <machine/pio.h> +#endif +#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 (!) + */ +#define 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 */ + +/* FreeBSD's getty doesn't know option for setting RTS/CTS handshake. Its + getty, like a lot of other old cruft, should be replaced with something + which used POSIX tty interfaces which at least allow enabling it. In the + meantime, use the force. */ +#define ALWAYS_RTS_CTS 1 + +#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? */ +#define CYCLOM_16 1 /* This is a cyclom-16Y */ +#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); + +/* hsu@clinet.fi: sigh */ +#ifdef __NetBSD__ +#define DELAY(foo) delay(foo) +void delay(int delay); +#endif + +/* Better get rid of this until the core people agree on kernel interfaces. + At least it will then compile on both WhichBSDs. + */ +#if 0 +extern unsigned int delaycount; /* calibrated 1 ms cpu-spin delay */ +#endif + +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 */ +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 +#ifdef NetBSD + , p +#endif + ); + 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 +#ifndef ALWAYS_RTS_CTS + if (cflag & CCTS_OFLOW) +#endif + 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) +{ + /* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed, + as the card is probed every round? Replaced delaycount with 8k. + Either delaycount has to be implemented in FreeBSD or more sensible + way of doing these should be implemented. DELAY isn't enough here. + */ + unsigned maxwait = 5 * 8 * 1024; /* 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", + 5 * 8 * 1024); + + *(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 */ |