/* $NetBSD: clmpcc.c,v 1.16 2001/05/02 10:32:09 scw Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Steve C. Woodford. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 THE FOUNDATION OR CONTRIBUTORS * 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. */ /* * Cirrus Logic CD2400/CD2401 Four Channel Multi-Protocol Comms. Controller. */ #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CLMPCC_ONLY_BYTESWAP_LOW) && defined(CLMPCC_ONLY_BYTESWAP_HIGH) #error "CLMPCC_ONLY_BYTESWAP_LOW and CLMPCC_ONLY_BYTESWAP_HIGH are mutually exclusive." #endif static int clmpcc_init __P((struct clmpcc_softc *sc)); static void clmpcc_shutdown __P((struct clmpcc_chan *)); static int clmpcc_speed __P((struct clmpcc_softc *, speed_t, int *, int *)); static int clmpcc_param __P((struct tty *, struct termios *)); static void clmpcc_set_params __P((struct clmpcc_chan *)); static void clmpcc_start __P((struct tty *)); static int clmpcc_modem_control __P((struct clmpcc_chan *, int, int)); cdev_decl(clmpcc); #define CLMPCCUNIT(x) (minor(x) & 0x7fffc) #define CLMPCCCHAN(x) (minor(x) & 0x00003) #define CLMPCCDIALOUT(x) (minor(x) & 0x80000) /* * These should be in a header file somewhere... */ #define ISSET(v, f) (((v) & (f)) != 0) #define ISCLR(v, f) (((v) & (f)) == 0) #define SET(v, f) (v) |= (f) #define CLR(v, f) (v) &= ~(f) extern struct cfdriver clmpcc_cd; /* * Make this an option variable one can patch. */ u_int clmpcc_ibuf_size = CLMPCC_RING_SIZE; /* * Things needed when the device is used as a console */ static struct clmpcc_softc *cons_sc = NULL; static int cons_chan; static int cons_rate; static int clmpcc_common_getc __P((struct clmpcc_softc *, int)); static void clmpcc_common_putc __P((struct clmpcc_softc *, int, int)); int clmpcccngetc __P((dev_t)); void clmpcccnputc __P((dev_t, int)); /* * Convenience functions, inlined for speed */ #define integrate static inline integrate u_int8_t clmpcc_rdreg __P((struct clmpcc_softc *, u_int)); integrate void clmpcc_wrreg __P((struct clmpcc_softc *, u_int, u_int)); integrate u_int8_t clmpcc_rdreg_odd __P((struct clmpcc_softc *, u_int)); integrate void clmpcc_wrreg_odd __P((struct clmpcc_softc *, u_int, u_int)); integrate void clmpcc_wrtx_multi __P((struct clmpcc_softc *, u_int8_t *, u_int)); integrate u_int8_t clmpcc_select_channel __P((struct clmpcc_softc *, u_int)); integrate void clmpcc_channel_cmd __P((struct clmpcc_softc *,int,int)); integrate void clmpcc_enable_transmitter __P((struct clmpcc_chan *)); #define clmpcc_rd_msvr(s) clmpcc_rdreg_odd(s,CLMPCC_REG_MSVR) #define clmpcc_wr_msvr(s,r,v) clmpcc_wrreg_odd(s,r,v) #define clmpcc_wr_pilr(s,r,v) clmpcc_wrreg_odd(s,r,v) #define clmpcc_rd_rxdata(s) clmpcc_rdreg_odd(s,CLMPCC_REG_RDR) #define clmpcc_wr_txdata(s,v) clmpcc_wrreg_odd(s,CLMPCC_REG_TDR,v) integrate u_int8_t clmpcc_rdreg(sc, offset) struct clmpcc_softc *sc; u_int offset; { #if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= sc->sc_byteswap; #elif defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= CLMPCC_BYTESWAP_HIGH; #endif return bus_space_read_1(sc->sc_iot, sc->sc_ioh, offset); } integrate void clmpcc_wrreg(sc, offset, val) struct clmpcc_softc *sc; u_int offset; u_int val; { #if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= sc->sc_byteswap; #elif defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= CLMPCC_BYTESWAP_HIGH; #endif bus_space_write_1(sc->sc_iot, sc->sc_ioh, offset, val); } integrate u_int8_t clmpcc_rdreg_odd(sc, offset) struct clmpcc_softc *sc; u_int offset; { #if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (sc->sc_byteswap & 2); #elif defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (CLMPCC_BYTESWAP_HIGH & 2); #endif return bus_space_read_1(sc->sc_iot, sc->sc_ioh, offset); } integrate void clmpcc_wrreg_odd(sc, offset, val) struct clmpcc_softc *sc; u_int offset; u_int val; { #if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (sc->sc_byteswap & 2); #elif defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (CLMPCC_BYTESWAP_HIGH & 2); #endif bus_space_write_1(sc->sc_iot, sc->sc_ioh, offset, val); } integrate void clmpcc_wrtx_multi(sc, buff, count) struct clmpcc_softc *sc; u_int8_t *buff; u_int count; { u_int offset = CLMPCC_REG_TDR; #if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (sc->sc_byteswap & 2); #elif defined(CLMPCC_ONLY_BYTESWAP_HIGH) offset ^= (CLMPCC_BYTESWAP_HIGH & 2); #endif bus_space_write_multi_1(sc->sc_iot, sc->sc_ioh, offset, buff, count); } integrate u_int8_t clmpcc_select_channel(sc, new_chan) struct clmpcc_softc *sc; u_int new_chan; { u_int old_chan = clmpcc_rdreg_odd(sc, CLMPCC_REG_CAR); clmpcc_wrreg_odd(sc, CLMPCC_REG_CAR, new_chan); return old_chan; } integrate void clmpcc_channel_cmd(sc, chan, cmd) struct clmpcc_softc *sc; int chan; int cmd; { int i; for (i = 5000; i; i--) { if ( clmpcc_rdreg(sc, CLMPCC_REG_CCR) == 0 ) break; delay(1); } if ( i == 0 ) printf("%s: channel %d command timeout (idle)\n", sc->sc_dev.dv_xname, chan); clmpcc_wrreg(sc, CLMPCC_REG_CCR, cmd); } integrate void clmpcc_enable_transmitter(ch) struct clmpcc_chan *ch; { u_int old; int s; old = clmpcc_select_channel(ch->ch_sc, ch->ch_car); s = splserial(); clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER, clmpcc_rdreg(ch->ch_sc, CLMPCC_REG_IER) | CLMPCC_IER_TX_EMPTY); SET(ch->ch_tty->t_state, TS_BUSY); splx(s); clmpcc_select_channel(ch->ch_sc, old); } static int clmpcc_speed(sc, speed, cor, bpr) struct clmpcc_softc *sc; speed_t speed; int *cor, *bpr; { int c, co, br; for (co = 0, c = 8; c <= 2048; co++, c *= 4) { br = ((sc->sc_clk / c) / speed) - 1; if ( br < 0x100 ) { *cor = co; *bpr = br; return 0; } } return -1; } void clmpcc_attach(sc) struct clmpcc_softc *sc; { struct clmpcc_chan *ch; struct tty *tp; int chan; if ( cons_sc != NULL && sc->sc_iot == cons_sc->sc_iot && sc->sc_ioh == cons_sc->sc_ioh ) cons_sc = sc; /* Initialise the chip */ clmpcc_init(sc); printf(": Cirrus Logic CD240%c Serial Controller\n", (clmpcc_rd_msvr(sc) & CLMPCC_MSVR_PORT_ID) ? '0' : '1'); #ifndef __HAVE_GENERIC_SOFT_INTERRUPTS sc->sc_soft_running = 0; #else sc->sc_softintr_cookie = softintr_establish(IPL_SOFTSERIAL, clmpcc_softintr, sc); #ifdef DEBUG if (sc->sc_softintr_cookie == NULL) panic("clmpcc_attach: softintr_establish"); #endif #endif memset(&(sc->sc_chans[0]), 0, sizeof(sc->sc_chans)); for (chan = 0; chan < CLMPCC_NUM_CHANS; chan++) { ch = &sc->sc_chans[chan]; ch->ch_sc = sc; ch->ch_car = chan; tp = ttymalloc(); tp->t_oproc = clmpcc_start; tp->t_param = clmpcc_param; ch->ch_tty = tp; ch->ch_ibuf = malloc(clmpcc_ibuf_size * 2, M_DEVBUF, M_NOWAIT); if ( ch->ch_ibuf == NULL ) { printf("%s(%d): unable to allocate ring buffer\n", sc->sc_dev.dv_xname, chan); return; } ch->ch_ibuf_end = &(ch->ch_ibuf[clmpcc_ibuf_size * 2]); ch->ch_ibuf_rd = ch->ch_ibuf_wr = ch->ch_ibuf; tty_attach(tp); } printf("%s: %d channels available", sc->sc_dev.dv_xname, CLMPCC_NUM_CHANS); if ( cons_sc == sc ) { printf(", console on channel %d.\n", cons_chan); SET(sc->sc_chans[cons_chan].ch_flags, CLMPCC_FLG_IS_CONSOLE); SET(sc->sc_chans[cons_chan].ch_openflags, TIOCFLAG_SOFTCAR); } else printf(".\n"); } static int clmpcc_init(sc) struct clmpcc_softc *sc; { u_int tcor, tbpr; u_int rcor, rbpr; u_int msvr_rts, msvr_dtr; u_int ccr; int is_console; int i; /* * All we're really concerned about here is putting the chip * into a quiescent state so that it won't do anything until * clmpccopen() is called. (Except the console channel.) */ /* * If the chip is acting as console, set all channels to the supplied * console baud rate. Otherwise, plump for 9600. */ if ( cons_sc && sc->sc_ioh == cons_sc->sc_ioh && sc->sc_iot == cons_sc->sc_iot ) { clmpcc_speed(sc, cons_rate, &tcor, &tbpr); clmpcc_speed(sc, cons_rate, &rcor, &rbpr); is_console = 1; } else { clmpcc_speed(sc, 9600, &tcor, &tbpr); clmpcc_speed(sc, 9600, &rcor, &rbpr); is_console = 0; } /* Allow any pending output to be sent */ delay(10000); /* Send the Reset All command to channel 0 (resets all channels!) */ clmpcc_channel_cmd(sc, 0, CLMPCC_CCR_T0_RESET_ALL); delay(1000); /* * The chip will set it's firmware revision register to a non-zero * value to indicate completion of reset. */ for (i = 10000; clmpcc_rdreg(sc, CLMPCC_REG_GFRCR) == 0 && i; i--) delay(1); if ( i == 0 ) { /* * Watch out... If this chip is console, the message * probably won't be sent since we just reset it! */ printf("%s: Failed to reset chip\n", sc->sc_dev.dv_xname); return -1; } for (i = 0; i < CLMPCC_NUM_CHANS; i++) { clmpcc_select_channel(sc, i); /* All interrupts are disabled to begin with */ clmpcc_wrreg(sc, CLMPCC_REG_IER, 0); /* Make sure the channel interrupts on the correct vectors */ clmpcc_wrreg(sc, CLMPCC_REG_LIVR, sc->sc_vector_base); clmpcc_wr_pilr(sc, CLMPCC_REG_RPILR, sc->sc_rpilr); clmpcc_wr_pilr(sc, CLMPCC_REG_TPILR, sc->sc_tpilr); clmpcc_wr_pilr(sc, CLMPCC_REG_MPILR, sc->sc_mpilr); /* Receive timer prescaler set to 1ms */ clmpcc_wrreg(sc, CLMPCC_REG_TPR, CLMPCC_MSEC_TO_TPR(sc->sc_clk, 1)); /* We support Async mode only */ clmpcc_wrreg(sc, CLMPCC_REG_CMR, CLMPCC_CMR_ASYNC); /* Set the required baud rate */ clmpcc_wrreg(sc, CLMPCC_REG_TCOR, CLMPCC_TCOR_CLK(tcor)); clmpcc_wrreg(sc, CLMPCC_REG_TBPR, tbpr); clmpcc_wrreg(sc, CLMPCC_REG_RCOR, CLMPCC_RCOR_CLK(rcor)); clmpcc_wrreg(sc, CLMPCC_REG_RBPR, rbpr); /* Always default to 8N1 (XXX what about console?) */ clmpcc_wrreg(sc, CLMPCC_REG_COR1, CLMPCC_COR1_CHAR_8BITS | CLMPCC_COR1_NO_PARITY | CLMPCC_COR1_IGNORE_PAR); clmpcc_wrreg(sc, CLMPCC_REG_COR2, 0); clmpcc_wrreg(sc, CLMPCC_REG_COR3, CLMPCC_COR3_STOP_1); clmpcc_wrreg(sc, CLMPCC_REG_COR4, CLMPCC_COR4_DSRzd | CLMPCC_COR4_CDzd | CLMPCC_COR4_CTSzd); clmpcc_wrreg(sc, CLMPCC_REG_COR5, CLMPCC_COR5_DSRod | CLMPCC_COR5_CDod | CLMPCC_COR5_CTSod | CLMPCC_COR5_FLOW_NORM); clmpcc_wrreg(sc, CLMPCC_REG_COR6, 0); clmpcc_wrreg(sc, CLMPCC_REG_COR7, 0); /* Set the receive FIFO timeout */ clmpcc_wrreg(sc, CLMPCC_REG_RTPRl, CLMPCC_RTPR_DEFAULT); clmpcc_wrreg(sc, CLMPCC_REG_RTPRh, 0); /* At this point, we set up the console differently */ if ( is_console && i == cons_chan ) { msvr_rts = CLMPCC_MSVR_RTS; msvr_dtr = CLMPCC_MSVR_DTR; ccr = CLMPCC_CCR_T0_RX_EN | CLMPCC_CCR_T0_TX_EN; } else { msvr_rts = 0; msvr_dtr = 0; ccr = CLMPCC_CCR_T0_RX_DIS | CLMPCC_CCR_T0_TX_DIS; } clmpcc_wrreg(sc, CLMPCC_REG_MSVR_RTS, msvr_rts); clmpcc_wrreg(sc, CLMPCC_REG_MSVR_DTR, msvr_dtr); clmpcc_channel_cmd(sc, i, CLMPCC_CCR_T0_INIT | ccr); delay(100); } return 0; } static void clmpcc_shutdown(ch) struct clmpcc_chan *ch; { int oldch; oldch = clmpcc_select_channel(ch->ch_sc, ch->ch_car); /* Turn off interrupts. */ clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER, 0); if ( ISCLR(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) { /* Disable the transmitter and receiver */ clmpcc_channel_cmd(ch->ch_sc, ch->ch_car, CLMPCC_CCR_T0_RX_DIS | CLMPCC_CCR_T0_TX_DIS); /* Drop RTS and DTR */ clmpcc_modem_control(ch, TIOCM_RTS | TIOCM_DTR, DMBIS); } clmpcc_select_channel(ch->ch_sc, oldch); } int clmpccopen(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { struct clmpcc_softc *sc; struct clmpcc_chan *ch; struct tty *tp; int oldch; int error; sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); if (sc == NULL) return (ENXIO); ch = &sc->sc_chans[CLMPCCCHAN(dev)]; tp = ch->ch_tty; if ( ISSET(tp->t_state, TS_ISOPEN) && ISSET(tp->t_state, TS_XCLUDE) && p->p_ucred->cr_uid != 0 ) return EBUSY; /* * Do the following iff this is a first open. */ if ( ISCLR(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 ) { ttychars(tp); tp->t_dev = dev; tp->t_iflag = TTYDEF_IFLAG; tp->t_oflag = TTYDEF_OFLAG; tp->t_lflag = TTYDEF_LFLAG; tp->t_cflag = TTYDEF_CFLAG; tp->t_ospeed = tp->t_ispeed = TTYDEF_SPEED; if ( ISSET(ch->ch_openflags, TIOCFLAG_CLOCAL) ) SET(tp->t_cflag, CLOCAL); if ( ISSET(ch->ch_openflags, TIOCFLAG_CRTSCTS) ) SET(tp->t_cflag, CRTSCTS); if ( ISSET(ch->ch_openflags, TIOCFLAG_MDMBUF) ) SET(tp->t_cflag, MDMBUF); /* * Override some settings if the channel is being * used as the console. */ if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) { tp->t_ospeed = tp->t_ispeed = cons_rate; SET(tp->t_cflag, CLOCAL); CLR(tp->t_cflag, CRTSCTS); CLR(tp->t_cflag, HUPCL); } ch->ch_control = 0; clmpcc_param(tp, &tp->t_termios); ttsetwater(tp); /* Clear the input ring */ ch->ch_ibuf_rd = ch->ch_ibuf_wr = ch->ch_ibuf; /* Select the channel */ oldch = clmpcc_select_channel(sc, ch->ch_car); /* Reset it */ clmpcc_channel_cmd(sc, ch->ch_car, CLMPCC_CCR_T0_CLEAR | CLMPCC_CCR_T0_RX_EN | CLMPCC_CCR_T0_TX_EN); /* Enable receiver and modem change interrupts. */ clmpcc_wrreg(sc, CLMPCC_REG_IER, CLMPCC_IER_MODEM | CLMPCC_IER_RET | CLMPCC_IER_RX_FIFO); /* Raise RTS and DTR */ clmpcc_modem_control(ch, TIOCM_RTS | TIOCM_DTR, DMBIS); clmpcc_select_channel(sc, oldch); } else if ( ISSET(tp->t_state, TS_XCLUDE) && p->p_ucred->cr_uid != 0 ) return EBUSY; error = ttyopen(tp, CLMPCCDIALOUT(dev), ISSET(flag, O_NONBLOCK)); if (error) goto bad; error = (*tp->t_linesw->l_open)(dev, tp); if (error) goto bad; return 0; bad: if ( ISCLR(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 ) { /* * We failed to open the device, and nobody else had it opened. * Clean up the state as appropriate. */ clmpcc_shutdown(ch); } return error; } int clmpccclose(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(dev)]; struct tty *tp = ch->ch_tty; int s; if ( ISCLR(tp->t_state, TS_ISOPEN) ) return 0; (*tp->t_linesw->l_close)(tp, flag); s = spltty(); if ( ISCLR(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 ) { /* * Although we got a last close, the device may still be in * use; e.g. if this was the dialout node, and there are still * processes waiting for carrier on the non-dialout node. */ clmpcc_shutdown(ch); } ttyclose(tp); splx(s); return 0; } int clmpccread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); struct tty *tp = sc->sc_chans[CLMPCCCHAN(dev)].ch_tty; return ((*tp->t_linesw->l_read)(tp, uio, flag)); } int clmpccwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); struct tty *tp = sc->sc_chans[CLMPCCCHAN(dev)].ch_tty; return ((*tp->t_linesw->l_write)(tp, uio, flag)); } int clmpccpoll(dev, events, p) dev_t dev; int events; struct proc *p; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); struct tty *tp = sc->sc_chans[CLMPCCCHAN(dev)].ch_tty; return ((*tp->t_linesw->l_poll)(tp, events, p)); } struct tty * clmpcctty(dev) dev_t dev; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); return (sc->sc_chans[CLMPCCCHAN(dev)].ch_tty); } int clmpccioctl(dev, cmd, data, flag, p) dev_t dev; u_long cmd; caddr_t data; int flag; struct proc *p; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(dev)); struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(dev)]; struct tty *tp = ch->ch_tty; int error; error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, p); if (error >= 0) return error; error = ttioctl(tp, cmd, data, flag, p); if (error >= 0) return error; error = 0; switch (cmd) { case TIOCSBRK: SET(ch->ch_flags, CLMPCC_FLG_START_BREAK); clmpcc_enable_transmitter(ch); break; case TIOCCBRK: SET(ch->ch_flags, CLMPCC_FLG_END_BREAK); clmpcc_enable_transmitter(ch); break; case TIOCSDTR: clmpcc_modem_control(ch, TIOCM_DTR, DMBIS); break; case TIOCCDTR: clmpcc_modem_control(ch, TIOCM_DTR, DMBIC); break; case TIOCMSET: clmpcc_modem_control(ch, *((int *)data), DMSET); break; case TIOCMBIS: clmpcc_modem_control(ch, *((int *)data), DMBIS); break; case TIOCMBIC: clmpcc_modem_control(ch, *((int *)data), DMBIC); break; case TIOCMGET: *((int *)data) = clmpcc_modem_control(ch, 0, DMGET); break; case TIOCGFLAGS: *((int *)data) = ch->ch_openflags; break; case TIOCSFLAGS: error = suser(p->p_ucred, &p->p_acflag); if ( error ) break; ch->ch_openflags = *((int *)data) & (TIOCFLAG_SOFTCAR | TIOCFLAG_CLOCAL | TIOCFLAG_CRTSCTS | TIOCFLAG_MDMBUF); if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) SET(ch->ch_openflags, TIOCFLAG_SOFTCAR); break; default: error = ENOTTY; break; } return error; } int clmpcc_modem_control(ch, bits, howto) struct clmpcc_chan *ch; int bits; int howto; { struct clmpcc_softc *sc = ch->ch_sc; struct tty *tp = ch->ch_tty; int oldch; int msvr; int rbits = 0; oldch = clmpcc_select_channel(sc, ch->ch_car); switch ( howto ) { case DMGET: msvr = clmpcc_rd_msvr(sc); if ( sc->sc_swaprtsdtr ) { rbits |= (msvr & CLMPCC_MSVR_RTS) ? TIOCM_DTR : 0; rbits |= (msvr & CLMPCC_MSVR_DTR) ? TIOCM_RTS : 0; } else { rbits |= (msvr & CLMPCC_MSVR_RTS) ? TIOCM_RTS : 0; rbits |= (msvr & CLMPCC_MSVR_DTR) ? TIOCM_DTR : 0; } rbits |= (msvr & CLMPCC_MSVR_CTS) ? TIOCM_CTS : 0; rbits |= (msvr & CLMPCC_MSVR_CD) ? TIOCM_CD : 0; rbits |= (msvr & CLMPCC_MSVR_DSR) ? TIOCM_DSR : 0; break; case DMSET: if ( sc->sc_swaprtsdtr ) { if ( ISCLR(tp->t_cflag, CRTSCTS) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, bits & TIOCM_RTS ? CLMPCC_MSVR_DTR : 0); clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, bits & TIOCM_DTR ? CLMPCC_MSVR_RTS : 0); } else { if ( ISCLR(tp->t_cflag, CRTSCTS) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, bits & TIOCM_RTS ? CLMPCC_MSVR_RTS : 0); clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, bits & TIOCM_DTR ? CLMPCC_MSVR_DTR : 0); } break; case DMBIS: if ( sc->sc_swaprtsdtr ) { if ( ISCLR(tp->t_cflag, CRTSCTS) && ISSET(bits, TIOCM_RTS) ) clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_DTR, CLMPCC_MSVR_DTR); if ( ISSET(bits, TIOCM_DTR) ) clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_RTS, CLMPCC_MSVR_RTS); } else { if ( ISCLR(tp->t_cflag, CRTSCTS) && ISSET(bits, TIOCM_RTS) ) clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_RTS, CLMPCC_MSVR_RTS); if ( ISSET(bits, TIOCM_DTR) ) clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_DTR, CLMPCC_MSVR_DTR); } break; case DMBIC: if ( sc->sc_swaprtsdtr ) { if ( ISCLR(tp->t_cflag, CRTSCTS) && ISCLR(bits, TIOCM_RTS) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, 0); if ( ISCLR(bits, TIOCM_DTR) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, 0); } else { if ( ISCLR(tp->t_cflag, CRTSCTS) && ISCLR(bits, TIOCM_RTS) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, 0); if ( ISCLR(bits, TIOCM_DTR) ) clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, 0); } break; } clmpcc_select_channel(sc, oldch); return rbits; } static int clmpcc_param(tp, t) struct tty *tp; struct termios *t; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(tp->t_dev)); struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)]; u_char cor; u_char oldch; int oclk, obpr; int iclk, ibpr; int s; /* Check requested parameters. */ if ( t->c_ospeed && clmpcc_speed(sc, t->c_ospeed, &oclk, &obpr) < 0 ) return EINVAL; if ( t->c_ispeed && clmpcc_speed(sc, t->c_ispeed, &iclk, &ibpr) < 0 ) return EINVAL; /* * For the console, always force CLOCAL and !HUPCL, so that the port * is always active. */ if ( ISSET(ch->ch_openflags, TIOCFLAG_SOFTCAR) || ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) { SET(t->c_cflag, CLOCAL); CLR(t->c_cflag, HUPCL); } CLR(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS); /* If ospeed it zero, hangup the line */ clmpcc_modem_control(ch, TIOCM_DTR, t->c_ospeed == 0 ? DMBIC : DMBIS); if ( t->c_ospeed ) { ch->ch_tcor = CLMPCC_TCOR_CLK(oclk); ch->ch_tbpr = obpr; } else { ch->ch_tcor = 0; ch->ch_tbpr = 0; } if ( t->c_ispeed ) { ch->ch_rcor = CLMPCC_RCOR_CLK(iclk); ch->ch_rbpr = ibpr; } else { ch->ch_rcor = 0; ch->ch_rbpr = 0; } /* Work out value to use for COR1 */ cor = 0; if ( ISSET(t->c_cflag, PARENB) ) { cor |= CLMPCC_COR1_NORM_PARITY; if ( ISSET(t->c_cflag, PARODD) ) cor |= CLMPCC_COR1_ODD_PARITY; } if ( ISCLR(t->c_cflag, INPCK) ) cor |= CLMPCC_COR1_IGNORE_PAR; switch ( t->c_cflag & CSIZE ) { case CS5: cor |= CLMPCC_COR1_CHAR_5BITS; break; case CS6: cor |= CLMPCC_COR1_CHAR_6BITS; break; case CS7: cor |= CLMPCC_COR1_CHAR_7BITS; break; case CS8: cor |= CLMPCC_COR1_CHAR_8BITS; break; } ch->ch_cor1 = cor; /* * The only interesting bit in COR2 is 'CTS Automatic Enable' * when hardware flow control is in effect. */ ch->ch_cor2 = ISSET(t->c_cflag, CRTSCTS) ? CLMPCC_COR2_CtsAE : 0; /* COR3 needs to be set to the number of stop bits... */ ch->ch_cor3 = ISSET(t->c_cflag, CSTOPB) ? CLMPCC_COR3_STOP_2 : CLMPCC_COR3_STOP_1; /* * COR4 contains the FIFO threshold setting. * We adjust the threshold depending on the input speed... */ if ( t->c_ispeed <= 1200 ) ch->ch_cor4 = CLMPCC_COR4_FIFO_LOW; else if ( t->c_ispeed <= 19200 ) ch->ch_cor4 = CLMPCC_COR4_FIFO_MED; else ch->ch_cor4 = CLMPCC_COR4_FIFO_HIGH; /* * If chip is used with CTS and DTR swapped, we can enable * automatic hardware flow control. */ if ( sc->sc_swaprtsdtr && ISSET(t->c_cflag, CRTSCTS) ) ch->ch_cor5 = CLMPCC_COR5_FLOW_NORM; else ch->ch_cor5 = 0; s = splserial(); oldch = clmpcc_select_channel(sc, ch->ch_car); /* * COR2 needs to be set immediately otherwise we might never get * a Tx EMPTY interrupt to change the other parameters. */ if ( clmpcc_rdreg(sc, CLMPCC_REG_COR2) != ch->ch_cor2 ) clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2); if ( ISCLR(ch->ch_tty->t_state, TS_BUSY) ) clmpcc_set_params(ch); else SET(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS); clmpcc_select_channel(sc, oldch); splx(s); return 0; } static void clmpcc_set_params(ch) struct clmpcc_chan *ch; { struct clmpcc_softc *sc = ch->ch_sc; u_char r1; u_char r2; if ( ch->ch_tcor || ch->ch_tbpr ) { r1 = clmpcc_rdreg(sc, CLMPCC_REG_TCOR); r2 = clmpcc_rdreg(sc, CLMPCC_REG_TBPR); /* Only write Tx rate if it really has changed */ if ( ch->ch_tcor != r1 || ch->ch_tbpr != r2 ) { clmpcc_wrreg(sc, CLMPCC_REG_TCOR, ch->ch_tcor); clmpcc_wrreg(sc, CLMPCC_REG_TBPR, ch->ch_tbpr); } } if ( ch->ch_rcor || ch->ch_rbpr ) { r1 = clmpcc_rdreg(sc, CLMPCC_REG_RCOR); r2 = clmpcc_rdreg(sc, CLMPCC_REG_RBPR); /* Only write Rx rate if it really has changed */ if ( ch->ch_rcor != r1 || ch->ch_rbpr != r2 ) { clmpcc_wrreg(sc, CLMPCC_REG_RCOR, ch->ch_rcor); clmpcc_wrreg(sc, CLMPCC_REG_RBPR, ch->ch_rbpr); } } if ( clmpcc_rdreg(sc, CLMPCC_REG_COR1) != ch->ch_cor1 ) { clmpcc_wrreg(sc, CLMPCC_REG_COR1, ch->ch_cor1); /* Any change to COR1 requires an INIT command */ SET(ch->ch_flags, CLMPCC_FLG_NEED_INIT); } if ( clmpcc_rdreg(sc, CLMPCC_REG_COR3) != ch->ch_cor3 ) clmpcc_wrreg(sc, CLMPCC_REG_COR3, ch->ch_cor3); r1 = clmpcc_rdreg(sc, CLMPCC_REG_COR4); if ( ch->ch_cor4 != (r1 & CLMPCC_COR4_FIFO_MASK) ) { /* * Note: If the FIFO has changed, we always set it to * zero here and disable the Receive Timeout interrupt. * It's up to the Rx Interrupt handler to pick the * appropriate moment to write the new FIFO length. */ clmpcc_wrreg(sc, CLMPCC_REG_COR4, r1 & ~CLMPCC_COR4_FIFO_MASK); r1 = clmpcc_rdreg(sc, CLMPCC_REG_IER); clmpcc_wrreg(sc, CLMPCC_REG_IER, r1 & ~CLMPCC_IER_RET); SET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR); } r1 = clmpcc_rdreg(sc, CLMPCC_REG_COR5); if ( ch->ch_cor5 != (r1 & CLMPCC_COR5_FLOW_MASK) ) { r1 &= ~CLMPCC_COR5_FLOW_MASK; clmpcc_wrreg(sc, CLMPCC_REG_COR5, r1 | ch->ch_cor5); } } static void clmpcc_start(tp) struct tty *tp; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(tp->t_dev)); struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)]; u_int oldch; int s; s = spltty(); if ( ISCLR(tp->t_state, TS_TTSTOP | TS_TIMEOUT | TS_BUSY) ) { if ( tp->t_outq.c_cc <= tp->t_lowat ) { if ( ISSET(tp->t_state, TS_ASLEEP) ) { CLR(tp->t_state, TS_ASLEEP); wakeup(&tp->t_outq); } selwakeup(&tp->t_wsel); } if ( ISSET(ch->ch_flags, CLMPCC_FLG_START_BREAK | CLMPCC_FLG_END_BREAK) || tp->t_outq.c_cc > 0 ) { if ( ISCLR(ch->ch_flags, CLMPCC_FLG_START_BREAK | CLMPCC_FLG_END_BREAK) ) { ch->ch_obuf_addr = tp->t_outq.c_cf; ch->ch_obuf_size = ndqb(&tp->t_outq, 0); } /* Enable TX empty interrupts */ oldch = clmpcc_select_channel(ch->ch_sc, ch->ch_car); clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER, clmpcc_rdreg(ch->ch_sc, CLMPCC_REG_IER) | CLMPCC_IER_TX_EMPTY); clmpcc_select_channel(ch->ch_sc, oldch); SET(tp->t_state, TS_BUSY); } } splx(s); } /* * Stop output on a line. */ void clmpccstop(tp, flag) struct tty *tp; int flag; { struct clmpcc_softc *sc = device_lookup(&clmpcc_cd, CLMPCCUNIT(tp->t_dev)); struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)]; int s; s = splserial(); if ( ISSET(tp->t_state, TS_BUSY) ) { if ( ISCLR(tp->t_state, TS_TTSTOP) ) SET(tp->t_state, TS_FLUSH); ch->ch_obuf_size = 0; } splx(s); } /* * RX interrupt routine */ int clmpcc_rxintr(arg) void *arg; { struct clmpcc_softc *sc = (struct clmpcc_softc *)arg; struct clmpcc_chan *ch; u_int8_t *put, *end, rxd; u_char errstat; u_char fc, tc; u_char risr; u_char rir; #ifdef DDB int saw_break = 0; #endif /* Receive interrupt active? */ rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR); /* * If we're using auto-vectored interrupts, we have to * verify if the chip is generating the interrupt. */ if ( sc->sc_vector_base == 0 && (rir & CLMPCC_RIR_RACT) == 0 ) return 0; /* Get pointer to interrupting channel's data structure */ ch = &sc->sc_chans[rir & CLMPCC_RIR_RCN_MASK]; /* Get the interrupt status register */ risr = clmpcc_rdreg(sc, CLMPCC_REG_RISRl); if ( risr & CLMPCC_RISR_TIMEOUT ) { u_char reg; /* * Set the FIFO threshold to zero, and disable * further receive timeout interrupts. */ reg = clmpcc_rdreg(sc, CLMPCC_REG_COR4); clmpcc_wrreg(sc, CLMPCC_REG_COR4, reg & ~CLMPCC_COR4_FIFO_MASK); reg = clmpcc_rdreg(sc, CLMPCC_REG_IER); clmpcc_wrreg(sc, CLMPCC_REG_IER, reg & ~CLMPCC_IER_RET); clmpcc_wrreg(sc, CLMPCC_REG_REOIR, CLMPCC_REOIR_NO_TRANS); SET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR); return 1; } /* How many bytes are waiting in the FIFO? */ fc = tc = clmpcc_rdreg(sc, CLMPCC_REG_RFOC) & CLMPCC_RFOC_MASK; #ifdef DDB /* * Allow BREAK on the console to drop to the debugger. */ if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) && risr & CLMPCC_RISR_BREAK ) { saw_break = 1; } #endif if ( ISCLR(ch->ch_tty->t_state, TS_ISOPEN) && fc ) { /* Just get rid of the data */ while ( fc-- ) (void) clmpcc_rd_rxdata(sc); goto rx_done; } put = ch->ch_ibuf_wr; end = ch->ch_ibuf_end; /* * Note: The chip is completely hosed WRT these error * conditions; there seems to be no way to associate * the error with the correct character in the FIFO. * We compromise by tagging the first character we read * with the error. Not perfect, but there's no other way. */ errstat = 0; if ( risr & CLMPCC_RISR_PARITY ) errstat |= TTY_PE; if ( risr & (CLMPCC_RISR_FRAMING | CLMPCC_RISR_BREAK) ) errstat |= TTY_FE; /* * As long as there are characters in the FIFO, and we * have space for them... */ while ( fc > 0 ) { *put++ = rxd = clmpcc_rd_rxdata(sc); *put++ = errstat; if ( put >= end ) put = ch->ch_ibuf; if ( put == ch->ch_ibuf_rd ) { put -= 2; if ( put < ch->ch_ibuf ) put = end - 2; } errstat = 0; fc--; } ch->ch_ibuf_wr = put; #if 0 if ( sc->sc_swaprtsdtr == 0 && ISSET(cy->cy_tty->t_cflag, CRTSCTS) && cc < ch->ch_r_hiwat) { /* * If RTS/DTR are not physically swapped, we have to * do hardware flow control manually */ clmpcc_wr_msvr(sc, CLMPCC_MSVR_RTS, 0); } #endif rx_done: if ( fc != tc ) { if ( ISSET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR) ) { u_char reg; /* * Set the FIFO threshold to the preset value, * and enable receive timeout interrupts. */ reg = clmpcc_rdreg(sc, CLMPCC_REG_COR4); reg = (reg & ~CLMPCC_COR4_FIFO_MASK) | ch->ch_cor4; clmpcc_wrreg(sc, CLMPCC_REG_COR4, reg); reg = clmpcc_rdreg(sc, CLMPCC_REG_IER); clmpcc_wrreg(sc, CLMPCC_REG_IER, reg | CLMPCC_IER_RET); CLR(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR); } clmpcc_wrreg(sc, CLMPCC_REG_REOIR, 0); #ifndef __HAVE_GENERIC_SOFT_INTERRUPTS if ( sc->sc_soft_running == 0 ) { sc->sc_soft_running = 1; (sc->sc_softhook)(sc); } #else softintr_schedule(sc->sc_softintr_cookie); #endif } else clmpcc_wrreg(sc, CLMPCC_REG_REOIR, CLMPCC_REOIR_NO_TRANS); #ifdef DDB /* * Only =after= we write REOIR is it safe to drop to the debugger. */ if ( saw_break ) Debugger(); #endif return 1; } /* * Tx interrupt routine */ int clmpcc_txintr(arg) void *arg; { struct clmpcc_softc *sc = (struct clmpcc_softc *)arg; struct clmpcc_chan *ch; struct tty *tp; u_char ftc, oftc; u_char tir, teoir; int etcmode = 0; /* Tx interrupt active? */ tir = clmpcc_rdreg(sc, CLMPCC_REG_TIR); /* * If we're using auto-vectored interrupts, we have to * verify if the chip is generating the interrupt. */ if ( sc->sc_vector_base == 0 && (tir & CLMPCC_TIR_TACT) == 0 ) return 0; /* Get pointer to interrupting channel's data structure */ ch = &sc->sc_chans[tir & CLMPCC_TIR_TCN_MASK]; tp = ch->ch_tty; /* Dummy read of the interrupt status register */ (void) clmpcc_rdreg(sc, CLMPCC_REG_TISR); /* Make sure embedded transmit commands are disabled */ clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2); ftc = oftc = clmpcc_rdreg(sc, CLMPCC_REG_TFTC); /* Handle a delayed parameter change */ if ( ISSET(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS) ) { CLR(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS); clmpcc_set_params(ch); } if ( ch->ch_obuf_size > 0 ) { u_int n = min(ch->ch_obuf_size, ftc); clmpcc_wrtx_multi(sc, ch->ch_obuf_addr, n); ftc -= n; ch->ch_obuf_size -= n; ch->ch_obuf_addr += n; } else { /* * Check if we should start/stop a break */ if ( ISSET(ch->ch_flags, CLMPCC_FLG_START_BREAK) ) { CLR(ch->ch_flags, CLMPCC_FLG_START_BREAK); /* Enable embedded transmit commands */ clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2 | CLMPCC_COR2_ETC); clmpcc_wr_txdata(sc, CLMPCC_ETC_MAGIC); clmpcc_wr_txdata(sc, CLMPCC_ETC_SEND_BREAK); ftc -= 2; etcmode = 1; } if ( ISSET(ch->ch_flags, CLMPCC_FLG_END_BREAK) ) { CLR(ch->ch_flags, CLMPCC_FLG_END_BREAK); /* Enable embedded transmit commands */ clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2 | CLMPCC_COR2_ETC); clmpcc_wr_txdata(sc, CLMPCC_ETC_MAGIC); clmpcc_wr_txdata(sc, CLMPCC_ETC_STOP_BREAK); ftc -= 2; etcmode = 1; } } tir = clmpcc_rdreg(sc, CLMPCC_REG_IER); if ( ftc != oftc ) { /* * Enable/disable the Tx FIFO threshold interrupt * according to how much data is in the FIFO. * However, always disable the FIFO threshold if * we've left the channel in 'Embedded Transmit * Command' mode. */ if ( etcmode || ftc >= ch->ch_cor4 ) tir &= ~CLMPCC_IER_TX_FIFO; else tir |= CLMPCC_IER_TX_FIFO; teoir = 0; } else { /* * No data was sent. * Disable transmit interrupt. */ tir &= ~(CLMPCC_IER_TX_EMPTY|CLMPCC_IER_TX_FIFO); teoir = CLMPCC_TEOIR_NO_TRANS; /* * Request Tx processing in the soft interrupt handler */ ch->ch_tx_done = 1; #ifndef __HAVE_GENERIC_SOFT_INTERRUPTS if ( sc->sc_soft_running == 0 ) { sc->sc_soft_running = 1; (sc->sc_softhook)(sc); } #else softintr_schedule(sc->sc_softintr_cookie); #endif } clmpcc_wrreg(sc, CLMPCC_REG_IER, tir); clmpcc_wrreg(sc, CLMPCC_REG_TEOIR, teoir); return 1; } /* * Modem change interrupt routine */ int clmpcc_mdintr(arg) void *arg; { struct clmpcc_softc *sc = (struct clmpcc_softc *)arg; u_char mir; /* Modem status interrupt active? */ mir = clmpcc_rdreg(sc, CLMPCC_REG_MIR); /* * If we're using auto-vectored interrupts, we have to * verify if the chip is generating the interrupt. */ if ( sc->sc_vector_base == 0 && (mir & CLMPCC_MIR_MACT) == 0 ) return 0; /* Dummy read of the interrupt status register */ (void) clmpcc_rdreg(sc, CLMPCC_REG_MISR); /* Retrieve current status of modem lines. */ sc->sc_chans[mir & CLMPCC_MIR_MCN_MASK].ch_control |= clmpcc_rd_msvr(sc) & CLMPCC_MSVR_CD; clmpcc_wrreg(sc, CLMPCC_REG_MEOIR, 0); #ifndef __HAVE_GENERIC_SOFT_INTERRUPTS if ( sc->sc_soft_running == 0 ) { sc->sc_soft_running = 1; (sc->sc_softhook)(sc); } #else softintr_schedule(sc->sc_softintr_cookie); #endif return 1; } void clmpcc_softintr(arg) void *arg; { struct clmpcc_softc *sc = (struct clmpcc_softc *)arg; struct clmpcc_chan *ch; struct tty *tp; int (*rint) __P((int, struct tty *)); u_char *get; u_char reg; u_int c; int chan; #ifndef __HAVE_GENERIC_SOFT_INTERRUPTS sc->sc_soft_running = 0; #endif /* Handle Modem state changes too... */ for (chan = 0; chan < CLMPCC_NUM_CHANS; chan++) { ch = &sc->sc_chans[chan]; tp = ch->ch_tty; get = ch->ch_ibuf_rd; rint = tp->t_linesw->l_rint; /* Squirt buffered incoming data into the tty layer */ while ( get != ch->ch_ibuf_wr ) { c = get[0]; c |= ((u_int)get[1]) << 8; if ( (rint)(c, tp) == -1 ) { ch->ch_ibuf_rd = ch->ch_ibuf_wr; break; } get += 2; if ( get == ch->ch_ibuf_end ) get = ch->ch_ibuf; ch->ch_ibuf_rd = get; } /* * Is the transmitter idle and in need of attention? */ if ( ch->ch_tx_done ) { ch->ch_tx_done = 0; if ( ISSET(ch->ch_flags, CLMPCC_FLG_NEED_INIT) ) { clmpcc_channel_cmd(sc, ch->ch_car, CLMPCC_CCR_T0_INIT | CLMPCC_CCR_T0_RX_EN | CLMPCC_CCR_T0_TX_EN); CLR(ch->ch_flags, CLMPCC_FLG_NEED_INIT); /* * Allow time for the channel to initialise. * (Empirically derived duration; there must * be another way to determine the command * has completed without busy-waiting...) */ delay(800); /* * Update the tty layer's idea of the carrier * bit, in case we changed CLOCAL or MDMBUF. * We don't hang up here; we only do that by * explicit request. */ reg = clmpcc_rd_msvr(sc) & CLMPCC_MSVR_CD; (*tp->t_linesw->l_modem)(tp, reg != 0); } CLR(tp->t_state, TS_BUSY); if ( ISSET(tp->t_state, TS_FLUSH) ) CLR(tp->t_state, TS_FLUSH); else ndflush(&tp->t_outq, (int)(ch->ch_obuf_addr - tp->t_outq.c_cf)); (*tp->t_linesw->l_start)(tp); } } } /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/ /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/ /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/ /* * Following are all routines needed for a cd240x channel to act as console */ int clmpcc_cnattach(sc, chan, rate) struct clmpcc_softc *sc; int chan; int rate; { cons_sc = sc; cons_chan = chan; cons_rate = rate; return 0; } /* * The following functions are polled getc and putc routines, for console use. */ static int clmpcc_common_getc(sc, chan) struct clmpcc_softc *sc; int chan; { u_char old_chan; u_char old_ier; u_char ch, rir, risr; int s; s = splhigh(); /* Save the currently active channel */ old_chan = clmpcc_select_channel(sc, chan); /* * We have to put the channel into RX interrupt mode before * trying to read the Rx data register. So save the previous * interrupt mode. */ old_ier = clmpcc_rdreg(sc, CLMPCC_REG_IER); clmpcc_wrreg(sc, CLMPCC_REG_IER, CLMPCC_IER_RX_FIFO); /* Loop until we get a character */ for (;;) { /* * The REN bit will be set in the Receive Interrupt Register * when the CD240x has a character to process. Remember, * the RACT bit won't be set until we generate an interrupt * acknowledge cycle via the MD front-end. */ rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR); if ( (rir & CLMPCC_RIR_REN) == 0 ) continue; /* Acknowledge the request */ if ( sc->sc_iackhook ) (sc->sc_iackhook)(sc, CLMPCC_IACK_RX); /* * Determine if the interrupt is for the required channel * and if valid data is available. */ rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR); risr = clmpcc_rdreg(sc, CLMPCC_REG_RISR); if ( (rir & CLMPCC_RIR_RCN_MASK) != chan || risr != 0 ) { /* Rx error, or BREAK */ clmpcc_wrreg(sc, CLMPCC_REG_REOIR, CLMPCC_REOIR_NO_TRANS); } else { /* Dummy read of the FIFO count register */ (void) clmpcc_rdreg(sc, CLMPCC_REG_RFOC); /* Fetch the received character */ ch = clmpcc_rd_rxdata(sc); clmpcc_wrreg(sc, CLMPCC_REG_REOIR, 0); break; } } /* Restore the original IER and CAR register contents */ clmpcc_wrreg(sc, CLMPCC_REG_IER, old_ier); clmpcc_select_channel(sc, old_chan); splx(s); return ch; } static void clmpcc_common_putc(sc, chan, c) struct clmpcc_softc *sc; int chan; int c; { u_char old_chan; int s = splhigh(); /* Save the currently active channel */ old_chan = clmpcc_select_channel(sc, chan); /* * Since we can only access the Tx Data register from within * the interrupt handler, the easiest way to get console data * onto the wire is using one of the Special Transmit Character * registers. */ clmpcc_wrreg(sc, CLMPCC_REG_SCHR4, c); clmpcc_wrreg(sc, CLMPCC_REG_STCR, CLMPCC_STCR_SSPC(4) | CLMPCC_STCR_SND_SPC); /* Wait until the "Send Special Character" command is accepted */ while ( clmpcc_rdreg(sc, CLMPCC_REG_STCR) != 0 ) ; /* Restore the previous channel selected */ clmpcc_select_channel(sc, old_chan); splx(s); } int clmpcccngetc(dev) dev_t dev; { return clmpcc_common_getc(cons_sc, cons_chan); } /* * Console kernel output character routine. */ void clmpcccnputc(dev, c) dev_t dev; int c; { if ( c == '\n' ) clmpcc_common_putc(cons_sc, cons_chan, '\r'); clmpcc_common_putc(cons_sc, cons_chan, c); }