#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;
}
cduart_write(sc, CDUART_IDR, ~0U);
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) {
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");
}
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;
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);
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();
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;
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;
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)
{
}