root/sys/dev/dec/dz.c
/*      $NetBSD: dz.c,v 1.44 2024/02/02 15:44:43 tsutsui Exp $  */
/*
 * Copyright (c) 1992, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Ralph Campbell and Rick Macklem.
 *
 * 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. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

/*
 * Copyright (c) 1996  Ken C. Wellsch.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Ralph Campbell and Rick Macklem.
 *
 * 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 University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dz.c,v 1.44 2024/02/02 15:44:43 tsutsui Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/kauth.h>

#include <sys/bus.h>

#include <dev/dec/dzreg.h>
#include <dev/dec/dzvar.h>

#include <dev/cons.h>

#ifdef __mips__
#define DZ_DELAY(x)     DELAY(x)
#define control         __noinline
#else   /* presumably vax */
#define DZ_DELAY(x)     /* nothing */
#define control         inline
#endif

static control uint
dz_read1(struct dz_softc *sc, u_int off)
{
        u_int rv;

        rv = bus_space_read_1(sc->sc_iot, sc->sc_ioh, off);
        DZ_DELAY(1);
        return rv;
}

static control u_int
dz_read2(struct dz_softc *sc, u_int off)
{
        u_int rv;

        rv = bus_space_read_2(sc->sc_iot, sc->sc_ioh, off);
        DZ_DELAY(1);
        return rv;
}

static control void
dz_write1(struct dz_softc *sc, u_int off, u_int val)
{

        bus_space_write_1(sc->sc_iot, sc->sc_ioh, off, val);
        bus_space_barrier(sc->sc_iot, sc->sc_ioh, sc->sc_dr.dr_firstreg,
            sc->sc_dr.dr_winsize, BUS_SPACE_BARRIER_WRITE |
            BUS_SPACE_BARRIER_READ);
        DZ_DELAY(10);
}

static control void
dz_write2(struct dz_softc *sc, u_int off, u_int val)
{

        bus_space_write_2(sc->sc_iot, sc->sc_ioh, off, val);
        bus_space_barrier(sc->sc_iot, sc->sc_ioh, sc->sc_dr.dr_firstreg,
            sc->sc_dr.dr_winsize, BUS_SPACE_BARRIER_WRITE |
            BUS_SPACE_BARRIER_READ);
        DZ_DELAY(10);
}

#include "ioconf.h"

/* Flags used to monitor modem bits, make them understood outside driver */

#define DML_DTR         TIOCM_DTR
#define DML_DCD         TIOCM_CD
#define DML_RI          TIOCM_RI
#define DML_BRK         0100000         /* no equivalent, we will mask */

static const struct speedtab dzspeedtab[] =
{
  {       0,    0               },
  {      50,    DZ_LPR_B50      },
  {      75,    DZ_LPR_B75      },
  {     110,    DZ_LPR_B110     },
  {     134,    DZ_LPR_B134     },
  {     150,    DZ_LPR_B150     },
  {     300,    DZ_LPR_B300     },
  {     600,    DZ_LPR_B600     },
  {    1200,    DZ_LPR_B1200    },
  {    1800,    DZ_LPR_B1800    },
  {    2000,    DZ_LPR_B2000    },
  {    2400,    DZ_LPR_B2400    },
  {    3600,    DZ_LPR_B3600    },
  {    4800,    DZ_LPR_B4800    },
  {    7200,    DZ_LPR_B7200    },
  {    9600,    DZ_LPR_B9600    },
  {   19200,    DZ_LPR_B19200   },
  {      -1,    -1              }
};

static void     dzstart(struct tty *);
static int      dzparam(struct tty *, struct termios *);
static unsigned dzmctl(struct dz_softc *, int, int, int);
static void     dzscan(void *);

static dev_type_open(dzopen);
static dev_type_close(dzclose);
static dev_type_read(dzread);
static dev_type_write(dzwrite);
static dev_type_ioctl(dzioctl);
static dev_type_stop(dzstop);
static dev_type_tty(dztty);
static dev_type_poll(dzpoll);

const struct cdevsw dz_cdevsw = {
        .d_open = dzopen,
        .d_close = dzclose,
        .d_read = dzread,
        .d_write = dzwrite,
        .d_ioctl = dzioctl,
        .d_stop = dzstop,
        .d_tty = dztty,
        .d_poll = dzpoll,
        .d_mmap = nommap,
        .d_kqfilter = ttykqfilter,
        .d_discard = nodiscard,
        .d_flag = D_TTY
};

/*
 * The DZ series doesn't interrupt on carrier transitions,
 * so we have to use a timer to watch it.
 */
int     dz_timer;       /* true if timer started */
struct callout dzscan_ch;
static struct cnm_state dz_cnm_state;

void
dzattach(struct dz_softc *sc, struct evcnt *parent_evcnt, int consline)
{
        int n;

        /* Initialize our softc structure. */

        for (n = 0; n < sc->sc_type; n++) {
                sc->sc_dz[n].dz_sc = sc;
                sc->sc_dz[n].dz_line = n;
                sc->sc_dz[n].dz_tty = tty_alloc();
        }

        evcnt_attach_dynamic(&sc->sc_rintrcnt, EVCNT_TYPE_INTR, parent_evcnt,
            device_xname(sc->sc_dev), "rintr");
        evcnt_attach_dynamic(&sc->sc_tintrcnt, EVCNT_TYPE_INTR, parent_evcnt,
            device_xname(sc->sc_dev), "tintr");

        /* Console magic keys */
        cn_init_magic(&dz_cnm_state);
        cn_set_magic("\047\001"); /* default magic is BREAK */
                                  /* VAX will change it in MD code */

        sc->sc_rxint = sc->sc_brk = 0;
        sc->sc_consline = consline;

        sc->sc_dr.dr_tcrw = sc->sc_dr.dr_tcr;
        dz_write2(sc, sc->sc_dr.dr_csr, DZ_CSR_MSE | DZ_CSR_RXIE | DZ_CSR_TXIE);
        dz_write1(sc, sc->sc_dr.dr_dtr, 0);
        dz_write1(sc, sc->sc_dr.dr_break, 0);
        DELAY(10000);

        /* Alas no interrupt on modem bit changes, so we manually scan */
        if (dz_timer == 0) {
                dz_timer = 1;
                callout_init(&dzscan_ch, 0);
                callout_reset(&dzscan_ch, hz, dzscan, NULL);
        }
        aprint_normal("\n");
}

/* Receiver Interrupt */

void
dzrint(void *arg)
{
        struct dz_softc *sc = arg;
        struct tty *tp;
        int cc, mcc, line;
        unsigned c;
        int overrun = 0;

        sc->sc_rxint++;

        while ((c = dz_read2(sc, sc->sc_dr.dr_rbuf)) & DZ_RBUF_DATA_VALID) {
                cc = c & 0xFF;
                line = DZ_PORT(c>>8);
                tp = sc->sc_dz[line].dz_tty;

                /* Must be caught early */
                if (sc->sc_dz[line].dz_catch &&
                    (*sc->sc_dz[line].dz_catch)(sc->sc_dz[line].dz_private, cc))
                        continue;

                if ((c & (DZ_RBUF_FRAMING_ERR | 0xff)) == DZ_RBUF_FRAMING_ERR)
                        mcc = CNC_BREAK;
                else
                        mcc = cc;

                cn_check_magic(tp->t_dev, mcc, dz_cnm_state);

                if (!(tp->t_state & TS_ISOPEN)) {
                        cv_broadcast(&tp->t_rawcv);
                        continue;
                }

                if ((c & DZ_RBUF_OVERRUN_ERR) && overrun == 0) {
                        log(LOG_WARNING, "%s: silo overflow, line %d\n",
                            device_xname(sc->sc_dev), line);
                        overrun = 1;
                }

                if (c & DZ_RBUF_FRAMING_ERR)
                        cc |= TTY_FE;
                if (c & DZ_RBUF_PARITY_ERR)
                        cc |= TTY_PE;

                (*tp->t_linesw->l_rint)(cc, tp);
        }
}

/* Transmitter Interrupt */

void
dzxint(void *arg)
{
        struct dz_softc *sc = arg;
        struct tty *tp;
        struct clist *cl;
        int line, ch, csr;
        u_char tcr;

        /*
         * Switch to POLLED mode.
         *   Some simple measurements indicated that even on
         *  one port, by freeing the scanner in the controller
         *  by either providing a character or turning off
         *  the port when output is complete, the transmitter
         *  was ready to accept more output when polled again.
         *   With just two ports running the game "worms,"
         *  almost every interrupt serviced both transmitters!
         *   Each UART is double buffered, so if the scanner
         *  is quick enough and timing works out, we can even
         *  feed the same port twice.
         *
         * Ragge 980517:
         * Do not need to turn off interrupts, already at interrupt level.
         * Remove the pdma stuff; no great need of it right now.
         */

        for (;;) {
                csr = dz_read2(sc, sc->sc_dr.dr_csr);
                if ((csr & DZ_CSR_TX_READY) == 0)
                        break;

                line = DZ_PORT(csr >> 8);
                tp = sc->sc_dz[line].dz_tty;
                cl = &tp->t_outq;
                tp->t_state &= ~TS_BUSY;

                /* Just send out a char if we have one */
                /* As long as we can fill the chip buffer, we just loop here */
                if (cl->c_cc) {
                        tp->t_state |= TS_BUSY;
                        ch = getc(cl);
                        dz_write1(sc, sc->sc_dr.dr_tbuf, ch);
                        continue;
                }

                /* Nothing to send; clear the scan bit */
                /* Clear xmit scanner bit; dzstart may set it again */
                tcr = dz_read2(sc, sc->sc_dr.dr_tcrw);
                tcr &= 255;
                tcr &= ~(1 << line);
                dz_write1(sc, sc->sc_dr.dr_tcr, tcr);
                if (sc->sc_dz[line].dz_catch)
                        continue;

                if (tp->t_state & TS_FLUSH)
                        tp->t_state &= ~TS_FLUSH;
                else
                        ndflush (&tp->t_outq, cl->c_cc);

                (*tp->t_linesw->l_start)(tp);
        }
}

int
dzopen(dev_t dev, int flag, int mode, struct lwp *l)
{
        const int line = DZ_PORT(minor(dev));
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        struct tty *tp;
        int error = 0;

        if (sc == NULL || line >= sc->sc_type)
                return ENXIO;

        /* if some other device is using the line, it's busy */
        if (sc->sc_dz[line].dz_catch)
                return EBUSY;

        tp = sc->sc_dz[line].dz_tty;
        if (tp == NULL)
                return (ENODEV);
        tp->t_oproc = dzstart;
        tp->t_param = dzparam;
        tp->t_dev = dev;

        if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp))
                return (EBUSY);

        if ((tp->t_state & TS_ISOPEN) == 0) {
                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 = TTYDEF_SPEED;
                }
                (void) dzparam(tp, &tp->t_termios);
                ttsetwater(tp);
        }

        /* Use DMBIS and *not* DMSET or else we clobber incoming bits */
        if (dzmctl(sc, line, DML_DTR, DMBIS) & DML_DCD)
                tp->t_state |= TS_CARR_ON;
        ttylock(tp);
        while (!(flag & O_NONBLOCK) && !(tp->t_cflag & CLOCAL) &&
               !(tp->t_state & TS_CARR_ON)) {
                tp->t_wopen++;
                error = ttysleep(tp, &tp->t_rawcv, true, 0);
                tp->t_wopen--;
                if (error)
                        break;
        }
        ttyunlock(tp);
        if (error)
                return (error);
        return ((*tp->t_linesw->l_open)(dev, tp));
}

/*ARGSUSED*/
int
dzclose(dev_t dev, int flag, int mode, struct lwp *l)
{
        const int line = DZ_PORT(minor(dev));
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        struct tty *tp = sc->sc_dz[line].dz_tty;

        (*tp->t_linesw->l_close)(tp, flag);

        /* Make sure a BREAK state is not left enabled. */
        (void) dzmctl(sc, line, DML_BRK, DMBIC);

        /* Do a hangup if so required. */
        if ((tp->t_cflag & HUPCL) || tp->t_wopen || !(tp->t_state & TS_ISOPEN))
                (void) dzmctl(sc, line, 0, DMSET);

        return ttyclose(tp);
}

int
dzread(dev_t dev, struct uio *uio, int flag)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        struct tty *tp = sc->sc_dz[DZ_PORT(minor(dev))].dz_tty;

        return ((*tp->t_linesw->l_read)(tp, uio, flag));
}

int
dzwrite(dev_t dev, struct uio *uio, int flag)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        struct tty *tp = sc->sc_dz[DZ_PORT(minor(dev))].dz_tty;

        return ((*tp->t_linesw->l_write)(tp, uio, flag));
}

int
dzpoll(dev_t dev, int events, struct lwp *l)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        struct tty *tp = sc->sc_dz[DZ_PORT(minor(dev))].dz_tty;

        return ((*tp->t_linesw->l_poll)(tp, events, l));
}

/*ARGSUSED*/
int
dzioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        const int line = DZ_PORT(minor(dev));
        struct tty *tp = sc->sc_dz[line].dz_tty;
        int error;

        error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
        if (error >= 0)
                return (error);

        error = ttioctl(tp, cmd, data, flag, l);
        if (error >= 0)
                return (error);

        switch (cmd) {
        case TIOCSBRK:
                (void) dzmctl(sc, line, DML_BRK, DMBIS);
                break;

        case TIOCCBRK:
                (void) dzmctl(sc, line, DML_BRK, DMBIC);
                break;

        case TIOCSDTR:
                (void) dzmctl(sc, line, DML_DTR, DMBIS);
                break;

        case TIOCCDTR:
                (void) dzmctl(sc, line, DML_DTR, DMBIC);
                break;

        case TIOCMSET:
                (void) dzmctl(sc, line, *(int *)data, DMSET);
                break;

        case TIOCMBIS:
                (void) dzmctl(sc, line, *(int *)data, DMBIS);
                break;

        case TIOCMBIC:
                (void) dzmctl(sc, line, *(int *)data, DMBIC);
                break;

        case TIOCMGET:
                *(int *)data = (dzmctl(sc, line, 0, DMGET) & ~DML_BRK);
                break;

        default:
                return (EPASSTHROUGH);
        }
        return (0);
}

struct tty *
dztty(dev_t dev)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));

        return sc->sc_dz[DZ_PORT(minor(dev))].dz_tty;
}

/*ARGSUSED*/
void
dzstop(struct tty *tp, int flag)
{
        if ((tp->t_state & (TS_BUSY | TS_TTSTOP)) == TS_BUSY)
                tp->t_state |= TS_FLUSH;
}

void
dzstart(struct tty *tp)
{
        struct dz_softc *sc;
        int line;
        int s;
        char state;

        s = spltty();
        if (tp->t_state & (TS_TIMEOUT|TS_BUSY|TS_TTSTOP)) {
                splx(s);
                return;
        }
        if (!ttypull(tp)) {
                splx(s);
                return;
        }

        line = DZ_PORT(minor(tp->t_dev));
        sc = device_lookup_private(&dz_cd, DZ_I2C(minor(tp->t_dev)));

        tp->t_state |= TS_BUSY;
        state = dz_read2(sc, sc->sc_dr.dr_tcrw) & 255;
        if ((state & (1 << line)) == 0)
                dz_write1(sc, sc->sc_dr.dr_tcr, state | (1 << line));
        dzxint(sc);
        splx(s);
}

static int
dzparam(struct tty *tp, struct termios *t)
{
        struct dz_softc *sc = device_lookup_private(&dz_cd, DZ_I2C(minor(tp->t_dev)));
        const int line = DZ_PORT(minor(tp->t_dev));
        int cflag = t->c_cflag;
        int ispeed = ttspeedtab(t->c_ispeed, dzspeedtab);
        int ospeed = ttspeedtab(t->c_ospeed, dzspeedtab);
        unsigned lpr;
        int s;

        /* check requested parameters */
        if (ospeed < 0 || ispeed < 0 || ispeed != ospeed)
                return (EINVAL);

        tp->t_ispeed = t->c_ispeed;
        tp->t_ospeed = t->c_ospeed;
        tp->t_cflag = cflag;

        if (ospeed == 0) {
                (void) dzmctl(sc, line, 0, DMSET);      /* hang up line */
                return (0);
        }

        s = spltty();

        /* XXX This is wrong.  Flush output or the chip gets very confused. */
        ttywait(tp);

        lpr = DZ_LPR_RX_ENABLE | ((ispeed&0xF)<<8) | line;

        switch (cflag & CSIZE)
        {
          case CS5:
                lpr |= DZ_LPR_5_BIT_CHAR;
                break;
          case CS6:
                lpr |= DZ_LPR_6_BIT_CHAR;
                break;
          case CS7:
                lpr |= DZ_LPR_7_BIT_CHAR;
                break;
          default:
                lpr |= DZ_LPR_8_BIT_CHAR;
                break;
        }
        if (cflag & PARENB)
                lpr |= DZ_LPR_PARENB;
        if (cflag & PARODD)
                lpr |= DZ_LPR_OPAR;
        if (cflag & CSTOPB)
                lpr |= DZ_LPR_2_STOP;

        dz_write2(sc, sc->sc_dr.dr_lpr, lpr);
        (void) splx(s);
        DELAY(10000);

        return (0);
}

static unsigned
dzmctl(struct dz_softc *sc, int line, int bits, int how)
{
        unsigned status;
        unsigned mbits;
        unsigned bit;
        int s;

        s = spltty();
        mbits = 0;
        bit = (1 << line);

        /* external signals as seen from the port */
        status = dz_read1(sc, sc->sc_dr.dr_dcd) | sc->sc_dsr;
        if (status & bit)
                mbits |= DML_DCD;
        status = dz_read1(sc, sc->sc_dr.dr_ring);
        if (status & bit)
                mbits |= DML_RI;

        /* internal signals/state delivered to port */
        status = dz_read1(sc, sc->sc_dr.dr_dtr);
        if (status & bit)
                mbits |= DML_DTR;
        if (sc->sc_brk & bit)
                mbits |= DML_BRK;

        switch (how)
        {
          case DMSET:
                mbits = bits;
                break;

          case DMBIS:
                mbits |= bits;
                break;

          case DMBIC:
                mbits &= ~bits;
                break;

          case DMGET:
                (void) splx(s);
                return (mbits);
        }

        if (mbits & DML_DTR) {
                dz_write1(sc, sc->sc_dr.dr_dtr, dz_read1(sc, sc->sc_dr.dr_dtr) | bit);
        } else {
                dz_write1(sc, sc->sc_dr.dr_dtr, dz_read1(sc, sc->sc_dr.dr_dtr) & ~bit);
        }

        if (mbits & DML_BRK) {
                sc->sc_brk |= bit;
                dz_write1(sc, sc->sc_dr.dr_break, sc->sc_brk);
        } else {
                sc->sc_brk &= ~bit;
                dz_write1(sc, sc->sc_dr.dr_break, sc->sc_brk);
        }

        (void) splx(s);

        return (mbits);
}

/*
 * This is called by timeout() periodically.
 * Check to see if modem status bits have changed.
 */
static void
dzscan(void *arg)
{
        struct dz_softc *sc;
        struct tty *tp;
        int n, bit, port;
        unsigned int csr;
        unsigned int tmp;
        int s;

        s = spltty();
        for (n = 0; n < dz_cd.cd_ndevs; n++) {
                if ((sc = device_lookup_private(&dz_cd, n)) == NULL)
                        continue;

                for (port = 0; port < sc->sc_type; port++) {
                        tp = sc->sc_dz[port].dz_tty;
                        bit = (1 << port);

                        if ((dz_read1(sc, sc->sc_dr.dr_dcd) | sc->sc_dsr) & bit) {
                                if (!(tp->t_state & TS_CARR_ON))
                                        (*tp->t_linesw->l_modem) (tp, 1);
                        } else if ((tp->t_state & TS_CARR_ON) &&
                            (*tp->t_linesw->l_modem)(tp, 0) == 0) {
                                tmp = dz_read2(sc, sc->sc_dr.dr_tcrw) & 255;
                                dz_write1(sc, sc->sc_dr.dr_tcr, tmp & ~bit);
                        }
                }

                /*
                 *  If the RX interrupt rate is this high, switch
                 *  the controller to Silo Alarm - which means don't
                 *  interrupt until the RX silo has 16 characters in
                 *  it (the silo is 64 characters in all).
                 *  Avoid oscillating SA on and off by not turning
                 *  if off unless the rate is appropriately low.
                 */
                csr = dz_read2(sc, sc->sc_dr.dr_csr);
                tmp = csr;
                if (sc->sc_rxint > 16*10)
                        csr |= DZ_CSR_SAE;
                else if (sc->sc_rxint < 10)
                        csr &= ~DZ_CSR_SAE;
                if (csr != tmp)
                        dz_write2(sc, sc->sc_dr.dr_csr, csr);
                sc->sc_rxint = 0;

                dzxint(sc);
                dzrint(sc);
        }
        (void) splx(s);
        callout_reset(&dzscan_ch, hz, dzscan, NULL);
}

/*
 * Called after an ubareset. The DZ card is reset, but the only thing
 * that must be done is to start the receiver and transmitter again.
 * No DMA setup to care about.
 */
void
dzreset(device_t dev)
{
        struct dz_softc *sc = device_private(dev);
        struct tty *tp;
        int i;

        for (i = 0; i < sc->sc_type; i++) {
                tp = sc->sc_dz[i].dz_tty;

                if (((tp->t_state & TS_ISOPEN) == 0) || (tp->t_wopen == 0))
                        continue;

                dzparam(tp, &tp->t_termios);
                dzmctl(sc, i, DML_DTR, DMSET);
                tp->t_state &= ~TS_BUSY;
                dzstart(tp);    /* Kick off transmitter again */
        }
}