--- /dev/null
+/* $OpenBSD: cduart.c,v 1.1 2021/04/24 07:49:11 visa Exp $ */
+
+/*
+ * Copyright (c) 2021 Visa Hankala
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Driver for Cadence UART.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/fcntl.h>
+#include <sys/tty.h>
+#include <sys/syslog.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+
+#include <dev/cons.h>
+
+#include <dev/ofw/fdt.h>
+#include <dev/ofw/openfirm.h>
+
+#define CDUART_CR 0x0000
+#define CDUART_CR_STOPBRK (1 << 8)
+#define CDUART_CR_STARTBRK (1 << 7)
+#define CDUART_CR_TORST (1 << 6)
+#define CDUART_CR_TXDIS (1 << 5)
+#define CDUART_CR_TXEN (1 << 4)
+#define CDUART_CR_RXDIS (1 << 3)
+#define CDUART_CR_RXEN (1 << 2)
+#define CDUART_CR_TXRST (1 << 1)
+#define CDUART_CR_RXRST (1 << 0)
+#define CDUART_MR 0x0004
+#define CDUART_MR_CHMODE_MASK (0x3 << 8)
+#define CDUART_MR_NBSTOP_MASK (0x3 << 6)
+#define CDUART_MR_PAR_MASK (0x7 << 3)
+#define CDUART_MR_PAR_NO (0x4 << 3)
+#define CDUART_MR_PAR_FORCED1 (0x3 << 3)
+#define CDUART_MR_PAR_FORCED0 (0x2 << 3)
+#define CDUART_MR_PAR_ODD (0x1 << 3)
+#define CDUART_MR_PAR_EVEN (0x0 << 3)
+#define CDUART_MR_CHRL_MASK (0x3 << 1)
+#define CDUART_MR_CHRL_C6 (0x3 << 1)
+#define CDUART_MR_CHRL_C7 (0x2 << 1)
+#define CDUART_MR_CHRL_C8 (0x0 << 1)
+#define CDUART_MR_CLKSEL (1 << 0)
+#define CDUART_IER 0x0008
+#define CDUART_IDR 0x000c
+#define CDUART_IMR 0x0010
+#define CDUART_ISR 0x0014
+#define CDUART_ISR_TXOVR (1 << 12)
+#define CDUART_ISR_TNFUL (1 << 11)
+#define CDUART_ISR_TTRIG (1 << 10)
+#define CDUART_IXR_DMS (1 << 9)
+#define CDUART_IXR_TOUT (1 << 8)
+#define CDUART_IXR_PARITY (1 << 7)
+#define CDUART_IXR_FRAMING (1 << 6)
+#define CDUART_IXR_RXOVR (1 << 5)
+#define CDUART_IXR_TXFULL (1 << 4)
+#define CDUART_IXR_TXEMPTY (1 << 3)
+#define CDUART_IXR_RXFULL (1 << 2)
+#define CDUART_IXR_RXEMPTY (1 << 1)
+#define CDUART_IXR_RTRIG (1 << 0)
+#define CDUART_RXTOUT 0x001c
+#define CDUART_RXWM 0x0020
+#define CDUART_SR 0x002c
+#define CDUART_SR_TNFUL (1 << 14)
+#define CDUART_SR_TTRIG (1 << 13)
+#define CDUART_SR_FLOWDEL (1 << 12)
+#define CDUART_SR_TACTIVE (1 << 11)
+#define CDUART_SR_RACTIVE (1 << 10)
+#define CDUART_SR_TXFULL (1 << 4)
+#define CDUART_SR_TXEMPTY (1 << 3)
+#define CDUART_SR_RXFULL (1 << 2)
+#define CDUART_SR_RXEMPTY (1 << 1)
+#define CDUART_SR_RXOVR (1 << 0)
+#define CDUART_FIFO 0x0030
+
+#define CDUART_SPACE_SIZE 0x0048
+
+#define CDUART_FIFOSIZE 64
+#define CDUART_IBUFSIZE 128
+
+struct cduart_softc {
+ struct device sc_dev;
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh;
+ void *sc_ih;
+ void *sc_si;
+
+ struct tty *sc_tty;
+ uint8_t sc_cua;
+
+ struct timeout sc_diag_tmo;
+ int sc_overflows;
+ int sc_floods;
+ int sc_errors;
+
+ int sc_ibufs[2][CDUART_IBUFSIZE];
+ int *sc_ibuf;
+ int *sc_ibufend;
+ int *sc_ibufp;
+};
+
+int cduart_match(struct device *, void *, void *);
+void cduart_attach(struct device *, struct device *, void *);
+void cduart_diag(void *);
+int cduart_intr(void *);
+void cduart_softintr(void *);
+
+struct tty *cduarttty(dev_t);
+struct cduart_softc *cduart_sc(dev_t);
+
+int cduartparam(struct tty *, struct termios *);
+void cduartstart(struct tty *);
+
+int cduartcnattach(bus_space_tag_t, bus_addr_t, int, tcflag_t);
+void cduartcnprobe(struct consdev *);
+void cduartcninit(struct consdev *);
+int cduartcngetc(dev_t);
+void cduartcnputc(dev_t, int);
+void cduartcnpollc(dev_t, int);
+
+cdev_decl(com);
+cdev_decl(cduart);
+
+const struct cfattach cduart_ca = {
+ sizeof(struct cduart_softc), cduart_match, cduart_attach
+};
+
+struct cfdriver cduart_cd = {
+ NULL, "cduart", DV_DULL
+};
+
+bus_space_tag_t cduartconsiot;
+bus_space_handle_t cduartconsioh;
+int cduartconsrate;
+tcflag_t cduartconscflag;
+struct cdevsw cduartdev = cdev_tty_init(3, cduart);
+
+#define DEVUNIT(x) (minor(x) & 0x7f)
+#define DEVCUA(x) (minor(x) & 0x80)
+
+static inline uint32_t
+cduart_read(struct cduart_softc *sc, uint32_t reg)
+{
+ return bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg);
+}
+
+static inline void
+cduart_write(struct cduart_softc *sc, uint32_t reg, uint32_t val)
+{
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg, val);
+}
+
+void
+cduart_init_cons(void)
+{
+ struct fdt_reg reg;
+ void *node;
+
+ if ((node = fdt_find_cons("cdns,uart-r1p8")) == NULL &&
+ (node = fdt_find_cons("cdns,uart-r1p12")) == NULL)
+ return;
+ if (fdt_get_reg(node, 0, ®) != 0)
+ return;
+
+ cduartcnattach(fdt_cons_bs_tag, reg.addr, B115200, TTYDEF_CFLAG);
+}
+
+int
+cduart_match(struct device *parent, void *match, void *aux)
+{
+ struct fdt_attach_args *faa = aux;
+
+ return OF_is_compatible(faa->fa_node, "cdns,uart-r1p8") ||
+ OF_is_compatible(faa->fa_node, "cdns,uart-r1p12");
+}
+
+void
+cduart_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct fdt_attach_args *faa = aux;
+ struct cduart_softc *sc = (struct cduart_softc *)self;
+ uint32_t cr, isr;
+ int maj;
+
+ if (faa->fa_nreg < 1) {
+ printf(": no registers\n");
+ return;
+ }
+
+ sc->sc_iot = faa->fa_iot;
+ if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
+ faa->fa_reg[0].size, 0, &sc->sc_ioh) != 0) {
+ printf(": can't map registers\n");
+ return;
+ }
+
+ /* Disable all interrupts. */
+ cduart_write(sc, CDUART_IDR, ~0U);
+
+ /* Clear any pending interrupts. */
+ isr = cduart_read(sc, CDUART_ISR);
+ cduart_write(sc, CDUART_ISR, isr);
+
+ sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, 0, IPL_TTY,
+ cduart_intr, sc, sc->sc_dev.dv_xname);
+ if (sc->sc_ih == NULL) {
+ printf(": can't establish interrupt\n");
+ goto fail;
+ }
+
+ timeout_set(&sc->sc_diag_tmo, cduart_diag, sc);
+ sc->sc_si = softintr_establish(IPL_TTY, cduart_softintr, sc);
+ if (sc->sc_si == NULL) {
+ printf(": can't establish soft interrupt\n");
+ goto fail;
+ }
+
+ if (faa->fa_node == stdout_node) {
+ /* Locate the major number. */
+ for (maj = 0; maj < nchrdev; maj++) {
+ if (cdevsw[maj].d_open == cduartopen)
+ break;
+ }
+ KASSERT(maj < nchrdev);
+ cn_tab->cn_dev = makedev(maj, sc->sc_dev.dv_unit);
+ printf(": console");
+ }
+
+ /* Enable transmitter and receiver. */
+ cr = cduart_read(sc, CDUART_CR);
+ cr &= ~(CDUART_CR_TXDIS | CDUART_CR_RXDIS);
+ cr |= CDUART_CR_TXEN | CDUART_CR_RXEN;
+ cduart_write(sc, CDUART_CR, cr);
+
+ printf("\n");
+
+ return;
+
+fail:
+ if (sc->sc_si != NULL)
+ softintr_disestablish(sc->sc_si);
+ if (sc->sc_ih != NULL)
+ fdt_intr_disestablish(sc->sc_ih);
+ if (sc->sc_ioh != 0)
+ bus_space_unmap(sc->sc_iot, sc->sc_ioh, CDUART_SPACE_SIZE);
+}
+
+int
+cduart_intr(void *arg)
+{
+ struct cduart_softc *sc = arg;
+ struct tty *tp = sc->sc_tty;
+ int *ibufp;
+ uint32_t isr, sr;
+ int c, handled = 0;
+
+ if (tp == NULL)
+ return 0;
+
+ isr = cduart_read(sc, CDUART_ISR);
+ cduart_write(sc, CDUART_ISR, isr);
+
+ if ((isr & CDUART_IXR_TXEMPTY) && (tp->t_state & TS_BUSY)) {
+ tp->t_state &= ~TS_BUSY;
+ (*linesw[tp->t_line].l_start)(tp);
+ handled = 1;
+ }
+
+ if (isr & (CDUART_IXR_TOUT | CDUART_IXR_RTRIG)) {
+ ibufp = sc->sc_ibufp;
+ for (;;) {
+ sr = cduart_read(sc, CDUART_SR);
+ if (sr & CDUART_SR_RXEMPTY)
+ break;
+ c = cduart_read(sc, CDUART_FIFO) & 0xff;
+
+ if (ibufp < sc->sc_ibufend) {
+ *ibufp++ = c;
+ } else {
+ sc->sc_floods++;
+ if (sc->sc_errors++ == 0)
+ timeout_add_sec(&sc->sc_diag_tmo, 60);
+ }
+ }
+ if (sc->sc_ibufp != ibufp) {
+ sc->sc_ibufp = ibufp;
+ softintr_schedule(sc->sc_si);
+ }
+ handled = 1;
+ }
+
+ if (isr & CDUART_IXR_RXOVR) {
+ sc->sc_overflows++;
+ if (sc->sc_errors++ == 0)
+ timeout_add_sec(&sc->sc_diag_tmo, 60);
+ handled = 1;
+ }
+
+ return handled;
+}
+
+void
+cduart_softintr(void *arg)
+{
+ struct cduart_softc *sc = arg;
+ struct tty *tp = sc->sc_tty;
+ int *ibufend, *ibufp;
+ int s;
+
+ s = spltty();
+
+ ibufp = sc->sc_ibuf;
+ ibufend = sc->sc_ibufp;
+
+ if (ibufp == ibufend) {
+ splx(s);
+ return;
+ }
+
+ sc->sc_ibufp = sc->sc_ibuf = (ibufp == sc->sc_ibufs[0]) ?
+ sc->sc_ibufs[1] : sc->sc_ibufs[0];
+ sc->sc_ibufend = sc->sc_ibuf + CDUART_IBUFSIZE;
+
+ if (tp == NULL || (tp->t_state & TS_ISOPEN) == 0) {
+ splx(s);
+ return;
+ }
+
+ splx(s);
+
+ while (ibufp < ibufend)
+ (*linesw[tp->t_line].l_rint)(*ibufp++, tp);
+}
+
+void
+cduart_diag(void *arg)
+{
+ struct cduart_softc *sc = arg;
+ int overflows, floods;
+ int s;
+
+ s = spltty();
+ sc->sc_errors = 0;
+ overflows = sc->sc_overflows;
+ sc->sc_overflows = 0;
+ floods = sc->sc_floods;
+ sc->sc_floods = 0;
+ splx(s);
+ log(LOG_WARNING, "%s: %d silo overflow%s, %d ibuf overflow%s\n",
+ sc->sc_dev.dv_xname,
+ overflows, overflows == 1 ? "" : "s",
+ floods, floods == 1 ? "" : "s");
+}
+
+int
+cduartopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct cduart_softc *sc;
+ struct tty *tp;
+ uint32_t sr;
+ int error, s;
+
+ sc = cduart_sc(dev);
+ if (sc == NULL)
+ return ENXIO;
+
+ s = spltty();
+
+ if (sc->sc_tty == NULL)
+ sc->sc_tty = ttymalloc(0);
+
+ tp = sc->sc_tty;
+ tp->t_oproc = cduartstart;
+ tp->t_param = cduartparam;
+ tp->t_dev = dev;
+ if ((tp->t_state & TS_ISOPEN) == 0) {
+ tp->t_state |= TS_WOPEN | TS_CARR_ON;
+ ttychars(tp);
+ tp->t_iflag = TTYDEF_IFLAG;
+ tp->t_oflag = TTYDEF_OFLAG;
+ tp->t_cflag = TTYDEF_CFLAG;
+ tp->t_lflag = TTYDEF_LFLAG;
+ tp->t_ispeed = B115200; /* XXX */
+ tp->t_ospeed = tp->t_ispeed;
+ ttsetwater(tp);
+
+ sc->sc_ibufp = sc->sc_ibuf = sc->sc_ibufs[0];
+ sc->sc_ibufend = sc->sc_ibuf + CDUART_IBUFSIZE;
+
+ cduart_write(sc, CDUART_RXTOUT, 10);
+ cduart_write(sc, CDUART_RXWM, CDUART_FIFOSIZE / 2);
+
+ /* Clear any pending I/O. */
+ for (;;) {
+ sr = cduart_read(sc, CDUART_SR);
+ if (sr & CDUART_SR_RXEMPTY)
+ break;
+ (void)cduart_read(sc, CDUART_FIFO);
+ }
+
+ cduart_write(sc, CDUART_IER,
+ CDUART_IXR_TOUT | CDUART_IXR_RXOVR | CDUART_IXR_RTRIG);
+ } else if ((tp->t_state & TS_XCLUDE) && suser(p) != 0) {
+ splx(s);
+ return EBUSY;
+ }
+
+ if (DEVCUA(dev)) {
+ if (tp->t_state & TS_ISOPEN) {
+ splx(s);
+ return EBUSY;
+ }
+ sc->sc_cua = 1;
+ } else {
+ if ((flag & O_NONBLOCK) && sc->sc_cua) {
+ splx(s);
+ return EBUSY;
+ } else {
+ while (sc->sc_cua) {
+ tp->t_state |= TS_WOPEN;
+ error = ttysleep(tp, &tp->t_rawq,
+ TTIPRI | PCATCH, ttopen);
+ if (error != 0 && (tp->t_state & TS_WOPEN)) {
+ tp->t_state &= ~TS_WOPEN;
+ splx(s);
+ return error;
+ }
+ }
+ }
+ }
+
+ splx(s);
+
+ return (*linesw[tp->t_line].l_open)(dev, tp, p);
+}
+
+int
+cduartclose(dev_t dev, int flag, int mode, struct proc *p)
+{
+ struct cduart_softc *sc;
+ struct tty *tp;
+ int s;
+
+ sc = cduart_sc(dev);
+ tp = sc->sc_tty;
+
+ if ((tp->t_state & TS_ISOPEN) == 0)
+ return 0;
+
+ (*linesw[tp->t_line].l_close)(tp, flag, p);
+ s = spltty();
+ /* Disable interrupts. */
+ cduart_write(sc, CDUART_IDR, ~0U);
+ sc->sc_cua = 0;
+ splx(s);
+ ttyclose(tp);
+
+ return 0;
+}
+
+int
+cduartread(dev_t dev, struct uio *uio, int flag)
+{
+ struct tty *tp;
+
+ tp = cduarttty(dev);
+ if (tp == NULL)
+ return ENODEV;
+
+ return (*linesw[tp->t_line].l_read)(tp, uio, flag);
+}
+
+int
+cduartwrite(dev_t dev, struct uio *uio, int flag)
+{
+ struct tty *tp;
+
+ tp = cduarttty(dev);
+ if (tp == NULL)
+ return ENODEV;
+
+ return (*linesw[tp->t_line].l_write)(tp, uio, flag);
+}
+
+int
+cduartioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
+{
+ struct cduart_softc *sc;
+ struct tty *tp;
+ int error;
+
+ sc = cduart_sc(dev);
+ if (sc == NULL)
+ return ENODEV;
+
+ tp = sc->sc_tty;
+ if (tp == NULL)
+ return ENXIO;
+
+ error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
+ if (error >= 0)
+ return error;
+
+ error = ttioctl(tp, cmd, data, flag, p);
+ if (error >= 0)
+ return error;
+
+ /* XXX */
+ switch (cmd) {
+ case TIOCSBRK:
+ case TIOCCBRK:
+ case TIOCSDTR:
+ case TIOCCDTR:
+ case TIOCMSET:
+ case TIOCMBIC:
+ case TIOCMGET:
+ case TIOCGFLAGS:
+ break;
+ case TIOCSFLAGS:
+ error = suser(p);
+ if (error != 0)
+ return EPERM;
+ break;
+ default:
+ return ENOTTY;
+ }
+
+ return 0;
+}
+
+int
+cduartparam(struct tty *tp, struct termios *t)
+{
+ return 0;
+}
+
+void
+cduartstart(struct tty *tp)
+{
+ struct cduart_softc *sc;
+ uint32_t sr;
+ int s;
+
+ sc = cduart_sc(tp->t_dev);
+
+ s = spltty();
+ if (tp->t_state & TS_BUSY)
+ goto out;
+ if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))
+ goto stopped;
+ ttwakeupwr(tp);
+ if (tp->t_outq.c_cc == 0)
+ goto stopped;
+ tp->t_state |= TS_BUSY;
+
+ cduart_write(sc, CDUART_ISR, CDUART_IXR_TXEMPTY);
+
+ sr = cduart_read(sc, CDUART_SR);
+ while ((sr & CDUART_SR_TXFULL) == 0 && tp->t_outq.c_cc != 0) {
+ cduart_write(sc, CDUART_FIFO, getc(&tp->t_outq));
+ sr = cduart_read(sc, CDUART_SR);
+ }
+
+ cduart_write(sc, CDUART_IER, CDUART_IXR_TXEMPTY);
+out:
+ splx(s);
+ return;
+stopped:
+ cduart_write(sc, CDUART_IDR, CDUART_IXR_TXEMPTY);
+ splx(s);
+}
+
+int
+cduartstop(struct tty *tp, int flag)
+{
+ return 0;
+}
+
+struct tty *
+cduarttty(dev_t dev)
+{
+ struct cduart_softc *sc;
+
+ sc = cduart_sc(dev);
+ if (sc == NULL)
+ return NULL;
+
+ return sc->sc_tty;
+}
+
+struct cduart_softc *
+cduart_sc(dev_t dev)
+{
+ int unit = DEVUNIT(dev);
+
+ if (unit >= cduart_cd.cd_ndevs)
+ return NULL;
+ return (struct cduart_softc *)cduart_cd.cd_devs[unit];
+}
+
+struct consdev cduartcons = {
+ .cn_probe = NULL,
+ .cn_init = NULL,
+ .cn_getc = cduartcngetc,
+ .cn_putc = cduartcnputc,
+ .cn_pollc = cduartcnpollc,
+ .cn_bell = NULL,
+ .cn_dev = NODEV,
+ .cn_pri = CN_MIDPRI,
+};
+
+int
+cduartcnattach(bus_space_tag_t iot, bus_addr_t iobase, int rate,
+ tcflag_t cflag)
+{
+ bus_space_handle_t ioh;
+ int maj;
+
+ /* Look for major of com(4) to replace. */
+ for (maj = 0; maj < nchrdev; maj++) {
+ if (cdevsw[maj].d_open == comopen)
+ break;
+ }
+ if (maj == nchrdev)
+ return ENXIO;
+
+ if (bus_space_map(iot, iobase, CDUART_SPACE_SIZE, 0, &ioh) != 0)
+ return ENOMEM;
+
+ cn_tab = &cduartcons;
+ cn_tab->cn_dev = makedev(maj, 0);
+ cdevsw[maj] = cduartdev;
+
+ cduartconsiot = iot;
+ cduartconsioh = ioh;
+ cduartconsrate = rate;
+ cduartconscflag = cflag;
+
+ return 0;
+}
+
+void
+cduartcnprobe(struct consdev *cp)
+{
+}
+
+void
+cduartcninit(struct consdev *cp)
+{
+}
+
+int
+cduartcngetc(dev_t dev)
+{
+ int s;
+ uint8_t c;
+
+ s = splhigh();
+ while (bus_space_read_4(cduartconsiot, cduartconsioh,
+ CDUART_SR) & CDUART_SR_RXEMPTY)
+ CPU_BUSY_CYCLE();
+ c = bus_space_read_4(cduartconsiot, cduartconsioh, CDUART_FIFO);
+ splx(s);
+
+ return c;
+}
+
+void
+cduartcnputc(dev_t dev, int c)
+{
+ int s;
+
+ s = splhigh();
+ while (bus_space_read_4(cduartconsiot, cduartconsioh,
+ CDUART_SR) & CDUART_SR_TXFULL)
+ CPU_BUSY_CYCLE();
+ bus_space_write_4(cduartconsiot, cduartconsioh, CDUART_FIFO, c);
+ splx(s);
+}
+
+void
+cduartcnpollc(dev_t dev, int on)
+{
+}