/* $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 #include #include #include #include #include #include #include #include #include #include #include #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) { }