root/sys/arch/emips/ebus/dz_ebus.c
/*      $NetBSD: dz_ebus.c,v 1.10 2019/12/27 06:52:43 msaitoh Exp $     */

/*-
 * Copyright (c) 2010 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code was written by Alessandro Forin and Neil Pittman
 * at Microsoft Research and contributed to The NetBSD Foundation
 * by Microsoft Corporation.
 *
 * 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.
 *
 * 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dz_ebus.c,v 1.10 2019/12/27 06:52:43 msaitoh Exp $");

#include "opt_ddb.h"

#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 <machine/bus.h>
#include <machine/emipsreg.h>

#include <dev/cons.h>


#include <emips/ebus/ebusvar.h>
#include <emips/emips/cons.h>
#if 0
#include <emips/emips/machdep.h>
#endif

#include "ioconf.h" /* for dz_cd */

#define DZ_C2I(c)       ((c) << 3)      /* convert controller # to index */
#define DZ_I2C(c)       ((c) >> 3)      /* convert minor to controller # */
#define DZ_PORT(u)      ((u) & 07)      /* extract the port # */

struct  dz_softc {
        device_t        sc_dev;         /* Autoconf blaha */
        struct  evcnt   sc_rintrcnt;    /* receive interrupt counts */
        struct  evcnt   sc_tintrcnt;    /* transmit interrupt counts */
        struct  _Usart  *sc_dr;         /* reg pointers */
        bus_space_tag_t sc_iot;
        bus_space_handle_t sc_ioh;
        int             sc_consline;    /* console line, or -1 */
        int             sc_rxint;       /* Receive interrupt count XXX */
        u_char          sc_brk;         /* Break asserted on some lines */
        u_char          sc_dsr;         /* DSR set bits if no mdm ctrl */
        struct dz_linestate {
                struct  dz_softc *dz_sc;        /* backpointer to softc */
                int             dz_line;        /* channel number */
                struct  tty     *dz_tty;        /* what we work on */
        } sc_dz;
};

void    dzrint(struct dz_softc *, uint32_t);
void    dzxint(struct dz_softc *, uint32_t);

#ifndef TIOCM_BRK
#define TIOCM_BRK               0100000         /* no equivalent */

static void     dzstart(struct tty *);
static int      dzparam(struct tty *, struct termios *);
static unsigned dzmctl(struct dz_softc *sc, int line,
                       int bits, /* one of the TIOCM_xx */
                       int how); /* one of the DMSET/BIS.. */

#include <dev/dec/dzkbdvar.h>
#endif

dev_type_open(dzopen);
dev_type_close(dzclose);
dev_type_read(dzread);
dev_type_write(dzwrite);
dev_type_ioctl(dzioctl);
dev_type_stop(dzstop);
dev_type_tty(dztty);
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
};

int
dzopen(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct tty *tp;
        int unit, line;
        struct dz_softc *sc;
        int s, error = 0;

        unit = DZ_I2C(minor(dev));
        sc = device_lookup_private(&dz_cd, unit);
        if (sc == NULL)
                return ENXIO;

        line = DZ_PORT(minor(dev));
        if (line > 0) /* FIXME for more than one line */
                return ENXIO;

        tp = sc->sc_dz.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);
        }
        /* we have no modem control but..*/
        if (dzmctl(sc, line, TIOCM_DTR, DMBIS) & TIOCM_CD)
                tp->t_state |= TS_CARR_ON;
        s = spltty();
        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;
        }
        (void)splx(s);
        if (error)
                return error;
        return (*tp->t_linesw->l_open)(dev, tp);
}

int
dzclose(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct dz_softc *sc;
        struct tty *tp;
        int unit, line;

        unit = DZ_I2C(minor(dev));
        sc = device_lookup_private(&dz_cd, unit);
        line = DZ_PORT(minor(dev));

        tp = sc->sc_dz.dz_tty;

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

        /* Make sure a BREAK state is not left enabled. */
        (void)dzmctl(sc, line, TIOCM_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 tty *tp;
        struct dz_softc *sc;

        sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));

        tp = sc->sc_dz.dz_tty;
        return (*tp->t_linesw->l_read)(tp, uio, flag);
}

int
dzwrite(dev_t dev, struct uio *uio, int flag)
{
        struct tty *tp;
        struct dz_softc *sc;

        sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));

        tp = sc->sc_dz.dz_tty;
        return (*tp->t_linesw->l_write)(tp, uio, flag);
}

/*ARGSUSED*/
int
dzioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
        struct  dz_softc *sc;
        struct tty *tp;
        int unit, line;
        int error;

        unit = DZ_I2C(minor(dev));
        line = 0;
        sc = device_lookup_private(&dz_cd, unit);
        tp = sc->sc_dz.dz_tty;

        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, TIOCM_BRK, DMBIS);
                break;

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

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

        case TIOCCDTR:
                (void)dzmctl(sc, line, TIOCM_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) & ~TIOCM_BRK;
                break;

        default:
                return EPASSTHROUGH;
        }
        return 0;
}

/*ARGSUSED*/
void
dzstop(struct tty *tp, int flag)
{

        if (tp->t_state & TS_BUSY)
                if (!(tp->t_state & TS_TTSTOP))
                        tp->t_state |= TS_FLUSH;
}

struct tty *
dztty(dev_t dev)
{
        struct dz_softc *sc;
        struct tty *tp;

        sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));
        tp = sc->sc_dz.dz_tty;

        return tp;
}

int
dzpoll(dev_t dev, int events, struct lwp *l)
{
        struct dz_softc *sc;
        struct tty *tp;

        sc = device_lookup_private(&dz_cd, DZ_I2C(minor(dev)));

        tp = sc->sc_dz.dz_tty;
        return (*tp->t_linesw->l_poll)(tp, events, l);
}

void
dzstart(struct tty *tp)
{
        struct dz_softc *sc;
        struct clist *cl;
        int unit, s;

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

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

        tp->t_state |= TS_BUSY;

        /* was idle, get it started */
        dzxint(sc,USI_TXRDY);
        splx(s);
}

static int rclk = 25000000; /* BUGBUGBUGBUG */

static int
dzdivisor(int baudrate)
{
        int act_baud, divisor, error;

        if (baudrate <= 0)
                return 0;

        divisor = (rclk / 8) / (baudrate);
        divisor = (divisor / 2) + (divisor & 1);

        if (divisor <= 0)
                return -1;
        act_baud = rclk / (divisor * 16);

        /* 10 times error in percent: */
        error = ((act_baud - baudrate) * 2000 / baudrate + 1) >> 1;

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

        return divisor;
}

static int
dzparam(struct tty *tp, struct termios *t)
{
        struct  dz_softc *sc;
        int cflag = t->c_cflag;
        int unit, line;
        int speed;
        unsigned lpr;
        int s;
        struct _Usart *dzr;

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

        /* check requested parameters */
        if (t->c_ispeed != t->c_ospeed)
                return EINVAL;
        speed = dzdivisor(t->c_ispeed);
        if (speed < 0)
                return EINVAL;

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

        {
                /* XXX */
                static int didit = 0;
                if (!didit && t->c_ispeed != 38400)
                        printf("dzparam: c_ispeed %d ignored, keeping 38400\n",
                            t->c_ispeed);
                didit = 1;
        }
        speed = dzdivisor(38400);

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

        switch (cflag & CSIZE) {
        case CS5:
                lpr = USC_BPC_5;
                break;
        case CS6:
                lpr = USC_BPC_6;
                break;
        case CS7:
                lpr = USC_BPC_7;
                break;
        default:
                lpr = USC_BPC_8;
                break;
        }
        if (cflag & CSTOPB)
                lpr |= USC_2STOP;

        if (cflag & PARENB) {
                if (cflag & PARODD)
                        lpr |= USC_ODD;
                else
                        lpr |= USC_EVEN;
        } else
                lpr |= USC_NONE;

        s = spltty();

        dzr = sc->sc_dr;

        dzr->Baud = speed;
        dzr->Control = USC_CLKDIV_4 | USC_TXEN | USC_RXEN | lpr;
#define USI_INTRS (USI_RXRDY|USI_RXBRK|USI_OVRE|USI_FRAME|USI_PARE)
        dzr->IntrEnable = USI_INTRS;

        (void)splx(s);
        return 0;
}

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

        mbits = 0;

        s = spltty();

        dzr = sc->sc_dr;

        /* we have no modem control bits (CD,RI,DTR,DSR,..) */
        mbits |= TIOCM_CD;
        mbits |= TIOCM_DTR;

        if (dzr->ChannelStatus & USI_RXBRK)
                mbits |= TIOCM_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;
        }

        /* BUGBUG work in progress */
        if (mbits & TIOCM_BRK) {
                sc->sc_brk |= (1 << line);
                dzr->Control |= USC_STTBRK;
        } else {
                sc->sc_brk &= ~(1 << line);
                dzr->Control |= USC_STPBRK;
        }

        (void)splx(s);
        return mbits;
}


#if defined(DDB)
int dz_ddb = 0;
#endif

/* Receiver Interrupt */

void
dzrint(struct dz_softc *sc, uint32_t csr)
{
        struct tty *tp;
        int cc;
        struct _Usart *dzr;

        sc->sc_rxint++;
        dzr = sc->sc_dr;

        cc = dzr->RxData;
        tp = sc->sc_dz.dz_tty;

        /* clear errors before we print or bail out */
        if (csr & (USI_OVRE|USI_FRAME|USI_PARE))
                dzr->Control = USC_RSTSTA;

        if (!(tp->t_state & TS_ISOPEN)) {
                wakeup(&tp->t_rawq);
                return;
        }

        if (csr & USI_OVRE) {
                log(LOG_WARNING, "%s: silo overflow, line %d\n",
                    device_xname(sc->sc_dev), 0);
        }

        if (csr & USI_FRAME)
                cc |= TTY_FE;
        if (csr & USI_PARE)
                cc |= TTY_PE;

#if defined(DDB)
        /* ^P drops into DDB */
        if (dz_ddb && (cc == 0x10))
                Debugger();
#endif
        (*tp->t_linesw->l_rint)(cc, tp);
}

/* Transmitter Interrupt */

void
dzxint(struct dz_softc *sc, uint32_t csr)
{
        struct tty *tp;
        struct clist *cl;
        int ch;
        struct _Usart *dzr;

        dzr = sc->sc_dr;

        tp = sc->sc_dz.dz_tty;
        cl = &tp->t_outq;
        tp->t_state &= ~TS_BUSY;

        /* Just send out a char if we have one */
        if (cl->c_cc) {
                tp->t_state |= TS_BUSY;
                ch = getc(cl);
                dzr->TxData = ch;
                dzr->IntrEnable = USI_TXRDY;
                return;
        }

        /* Nothing to send; turn off intr */
        dzr->IntrDisable = USI_TXRDY;

        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);
}

/*
 * Machdep part of the driver
 */
int     dz_ebus_match(device_t, cfdata_t, void *);
void    dz_ebus_attach(device_t, device_t, void *);
int     dz_ebus_intr(void *, void *);

void    dz_ebus_cnsetup(paddr_t);
void    dz_ebus_cninit(struct consdev *);
int     dz_ebus_cngetc(dev_t);
void    dz_ebus_cnputc(dev_t, int);
void    dz_ebus_cnpollc(dev_t, int);

static int      dz_ebus_getmajor(void);

CFATTACH_DECL_NEW(dz_ebus, sizeof(struct dz_softc),
    dz_ebus_match, dz_ebus_attach, NULL, NULL);

struct consdev dz_ebus_consdev = {
        NULL, dz_ebus_cninit, dz_ebus_cngetc, dz_ebus_cnputc,
        dz_ebus_cnpollc, NULL, NULL, NULL, NODEV, CN_NORMAL,
};

/*
 * Points to the console regs. Special mapping until VM is turned on.
 */
struct _Usart *dzcn;

int
dz_ebus_match(device_t parent, cfdata_t cf, void *aux)
{
        struct ebus_attach_args *iba;
        struct _Usart *us;

        iba = aux;

        if (strcmp(iba->ia_name, "dz") != 0)
                return 0;

        us = (struct _Usart *)iba->ia_vaddr;
        if ((us == NULL) ||
            (us->Tag != PMTTAG_USART))
                return 0;

        return 1;
}

void
dz_ebus_attach(device_t parent, device_t self, void *aux)
{
        struct ebus_attach_args *iba;
        struct dz_softc *sc;

        sc = device_private(self);
        iba = aux;

        sc->sc_dev = self;
        sc->sc_dr = (struct _Usart *)iba->ia_vaddr;
#if DEBUG
        printf(" virt=%p ", (void *)sc->sc_dr);
#endif

        printf(": neilsart 1 line");
        ebus_intr_establish(parent, (void *)iba->ia_cookie, IPL_TTY,
            dz_ebus_intr, sc);

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

        /* Initialize our softc structure. Should be done in open? */

        sc->sc_dz.dz_sc = sc;
        sc->sc_dz.dz_line = 0;
        sc->sc_dz.dz_tty = tty_alloc();

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

        /* Initialize hw regs */
#if 0
        DZ_WRITE_WORD(dr_csr, DZ_CSR_MSE | DZ_CSR_RXIE | DZ_CSR_TXIE);
        DZ_WRITE_BYTE(dr_dtr, 0);
        DZ_WRITE_BYTE(dr_break, 0);
#endif

        /* Switch the console to virtual mode */
        dzcn = sc->sc_dr;
        /* And test it */
        printf("\n");
}

static int
dz_ebus_getmajor(void)
{
        extern const struct cdevsw dz_cdevsw;
        static int cache = -1;

        if (cache != -1)
                return cache;

        return cache = cdevsw_lookup_major(&dz_cdevsw);
}

int
dz_ebus_intr(void *cookie, void *f)
{
        struct dz_softc *sc;
        struct _Usart *dzr;
        uint32_t csr;

        sc = cookie;
        dzr = sc->sc_dr;

#define USI_INTERRUPTS (USI_INTRS|USI_TXRDY)

        for (; ((csr = (dzr->ChannelStatus & dzr->IntrMask)) &
            USI_INTERRUPTS) != 0;) {
                if ((csr & USI_INTRS) != 0)
                        dzrint(sc, csr);
                if ((csr & USI_TXRDY) != 0)
                        dzxint(sc, csr);
        }

        return 0;
}

void
dz_ebus_cnsetup(paddr_t addr)
{

        dzcn = (struct _Usart *)addr;

#if 0
        /*
         * Initialize enough to xmit/recv via polling.
         * Bootloader might or might not have done it.
         */
        dzcn->Control =
            USC_RXEN |
            USC_TXEN |
            USC_BPC_8 |
            USC_NONE |
            USC_1STOP |
            USC_CLKDIV_4;
        dzcn->Baud = 0x29; /* 38400 */
#endif

        /*
         * Point the console at us
         */
        cn_tab = &dz_ebus_consdev;
        cn_tab->cn_pri = CN_NORMAL;/*CN_REMOTE?*/
        cn_tab->cn_dev = makedev(dz_ebus_getmajor(), 0);
}

void
dz_ebus_cninit(struct consdev *cn)
{
}

int
dz_ebus_cngetc(dev_t dev)
{
        int c, s;

        c = 0;
        s = spltty();

        while ((dzcn->ChannelStatus & USI_RXRDY) == 0)
                DELAY(10);
        c = dzcn->RxData;

        splx(s);
        if (c == 13) /* map cr->ln */
                c = 10;
        return c;
}

int dzflipped = 0;
void
dz_ebus_cnputc(dev_t dev, int ch)
{
        int timeout, s;

        /* Don't hang the machine! */
        timeout = 1 << 15;

        s = spltty();

#if 1
        /* Keep wired to hunt for a bug */
        if (dzcn && (dzcn != (struct _Usart *)0xfff90000)) {
                dzcn = (struct _Usart *)0xfff90000;
                dzflipped++;
        }
#endif

        /* Wait until ready */
        while ((dzcn->ChannelStatus & USI_TXRDY) == 0)
                if (--timeout < 0)
                        break;

        /* Put the character */
        dzcn->TxData = ch;

        splx(s);
}

/*
 * Called before/after going into poll mode
 */
void
dz_ebus_cnpollc(dev_t dev, int on)
{
}