/* $NetBSD: $ */

/*-
  * Copyright (c) 2009 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * 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.
  */

/* driver for soclib's multitty device */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: machdep.c,v 1.223 2008/07/02 17:28:56 ad Exp $");

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/kauth.h>

#include <machine/autoconf.h>
#include <machine/bus.h>

#include <machine/vmparam.h>

#include <dev/cons.h>

uint32_t mtty_consolep = 0;
const vaddr_t mtty_consolev = VM_MAX_KERNEL_ADDRESS;
static struct mtty_softc *mtty_console_device = NULL;

#define MTTY_UNIT(x) (minor(x))

static int mtty_match(device_t, cfdata_t, void *);
static void mtty_attach(device_t, device_t, void *);
static int mtty_intr(void *) __unused;

struct mtty_softc {
	device_t sc_dev;
	struct  tty *sc_tty;
	paddr_t sc_p; /* console mapping informations */
	vaddr_t sc_psize;
	bus_space_tag_t sc_tag;
	bus_space_handle_t sc_handle;
	irq_t sc_ih;
	volatile uint32_t *sc_regwr;
	volatile uint32_t *sc_regst;
	volatile uint32_t *sc_regrd;
	int sc_polling;
};

/* mtty devices definition: only 3 32bits registers */
#define TTY_WRITE  0
#define TTY_STATUS 1
#define TTY_READ 2

CFATTACH_DECL_NEW(mtty, sizeof(struct mtty_softc),
    mtty_match, mtty_attach, NULL, NULL);

extern struct cfdriver mtty_cd;

dev_type_open(mtty_open);
dev_type_close(mtty_close);
dev_type_read(mtty_read);
dev_type_write(mtty_write);
dev_type_ioctl(mtty_ioctl);
dev_type_stop(mtty_stop);
dev_type_tty(mtty_tty);
dev_type_poll(mtty_poll);

const struct cdevsw mtty_cdevsw = {
        .d_open = mtty_open,
        .d_close = mtty_close,
        .d_read = mtty_read,
        .d_write = mtty_write,
        .d_ioctl = mtty_ioctl,
        .d_stop = mtty_stop,
        .d_tty = mtty_tty,
        .d_poll = mtty_poll,
        .d_mmap = nommap,
        .d_kqfilter = ttykqfilter,
        .d_discard = nodiscard,
        .d_flag = D_TTY
};

int mttycn_getc(dev_t);
void mttycn_putc(dev_t, int);
void mttycn_pollc(dev_t, int);

static struct consdev mtty = {
        NULL, NULL, mttycn_getc, mttycn_putc, mttycn_pollc,
	NULL, NULL, NULL, NODEV, CN_NORMAL
};

static struct cnm_state mtty_cnm_state;

void    mtty_start (struct tty *);
int     mtty_param (struct tty *, struct termios *);

int
mtty_match(device_t parent, cfdata_t match, void *aux)
{
	struct tsardevs_attach_args *tsd = aux;
	if (strcmp(tsd->tsd_devtype, "soclib:tty") != 0 &&
	    strcmp(tsd->tsd_devtype, "vci:tty") != 0)
		return 0;
	return 1;
}

void
mtty_attach(device_t parent, device_t self, void *aux)
{
	struct mtty_softc *sc = device_private(self);
	struct tsardevs_attach_args *tsd = aux;
	u_int32_t *reg;
	char intstr[40];

	aprint_normal(": SocLib MultiTty Driver\n");

	sc->sc_dev = self;
	sc->sc_p = tsd->tsd_reg[0].reg_addr;
	sc->sc_psize = tsd->tsd_reg[0].reg_size;
	sc->sc_tag = tsd->tsd_tag;

	sc->sc_tty = tty_alloc();
	tty_attach(sc->sc_tty);
	sc->sc_tty->t_oproc = mtty_start;
	sc->sc_tty->t_param = mtty_param;

	if (sc->sc_p == mtty_consolep) {
		int maj;
		/* found console, it's already mapped */
		sc->sc_handle = mtty_consolev;

		/* Locate the major number. */
		maj = cdevsw_lookup_major(&mtty_cdevsw);

		/* There can be only one, but it can have any unit number. */
		cn_tab->cn_dev = makedev(maj, device_unit(self));

		aprint_verbose_dev(self, "console major %d, unit %d\n",
		    maj, device_unit(self));

		sc->sc_tty->t_dev = cn_tab->cn_dev;
		sc->sc_polling = 1;
		mtty_console_device = sc;
	} else {
		/* map the device */
		if (bus_space_map(sc->sc_tag, sc->sc_p, sc->sc_psize,
		    0, &sc->sc_handle)) {
			aprint_error_dev(self, "couldn't map registers\n");
			return;
		}
	}
	reg = (void *)sc->sc_handle;
	sc->sc_regwr = &reg[TTY_WRITE];
	sc->sc_regst = &reg[TTY_STATUS];
	sc->sc_regrd = &reg[TTY_READ];
	sc->sc_ih = intr_establish(tsd->tsd_irq, IPL_TTY, 
	    device_xname(self), intstr, mtty_intr, sc, NULL);
	if (sc->sc_ih == NULL) {
		aprint_error_dev(self, "can't establish interrupt\n");
		sc->sc_polling = 1;
	} else {
		sc->sc_polling = 0;
		aprint_normal_dev(self, "interrupting at %s\n", intstr);
	}
}

int
mtty_open(dev_t dev, int flag, int mode, struct lwp *l)
{
	struct mtty_softc *sc;
	struct tty *tp;

	sc = device_lookup_private(&mtty_cd, MTTY_UNIT(dev));
	if (sc == NULL)
		return (ENXIO);

	tp = sc->sc_tty;

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

	if ((tp->t_state & TS_ISOPEN) == 0 && tp->t_wopen == 0) {
		tp->t_dev = dev;
		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 = tp->t_ospeed = TTYDEF_SPEED;
		mtty_param(tp, &tp->t_termios);
		ttsetwater(tp);
	}
	tp->t_state |= TS_CARR_ON;

	return ((*tp->t_linesw->l_open)(dev, tp));
}

int
mtty_close(dev_t dev, int flag, int mode, struct lwp *l)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;

	if (tp == NULL)
		return (0);
	(*tp->t_linesw->l_close)(tp, flag);
	ttyclose(tp);
#ifdef notyet /* XXX */
	ttyfree(tp);
#endif
	return (0);
}

int
mtty_read(dev_t dev, struct uio *uio, int flag)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;

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

int
mtty_write(dev_t dev, struct uio *uio, int flag)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;

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

int
mtty_poll(dev_t dev, int events, struct lwp *l)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;
 
	return ((*tp->t_linesw->l_poll)(tp, events, l));
}

struct tty *
mtty_tty(dev_t dev)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;

	return (tp);
}

int
mtty_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
	struct mtty_softc *sc = device_lookup_private(&mtty_cd,
	    MTTY_UNIT(dev));
	struct tty *tp = sc->sc_tty;
	int error;

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

	error = ttioctl(tp, cmd, data, flag, l);
	if (error != EPASSTHROUGH)
		return (error);

	switch (cmd) {
	default:
		return (EPASSTHROUGH);
	}

#ifdef DIAGNOSTIC
	panic("mtty_ioctl: impossible");
#endif
}

void
mtty_start(struct tty *tp)
{
	struct mtty_softc *sc =
	    device_lookup_private(&mtty_cd, MTTY_UNIT(tp->t_dev));
	struct clist *cl;
	int s;
	int len, i;
#define MTTY_BURST 80
	u_char buf[MTTY_BURST+1];

	s = spltty();
	if (tp->t_state & (TS_TIMEOUT | TS_BUSY | TS_TTSTOP))
		goto out;
	tp->t_state |= TS_BUSY;

	cl = &tp->t_outq;
	len = q_to_b(cl, buf, MTTY_BURST);
	for (i = 0; i < len; i++) {
		*sc->sc_regwr = buf[i];
	}
	tp->t_state &= ~TS_BUSY;
	if (ttypull(tp)) {
		tp->t_state |= TS_TIMEOUT;
		callout_schedule(&tp->t_rstrt_ch, 1);
	}
out:
	splx(s);
}

void
mtty_stop(struct tty *tp, int flag)
{

}


/* interrupt routine */
static int 
mtty_intr(void *arg)
{
	struct mtty_softc *sc = arg;
	struct tty *tp = sc->sc_tty;
	u_char c;

	if (sc->sc_polling) {
		(void)(*sc->sc_regst);
		return 1;
	}
	if ((*sc->sc_regst & 0x1) == 0)
		printf("tty intr but no data\n");

	while((*sc->sc_regst & 0x1) != 0) {
		c = *sc->sc_regrd;
		if (c == '\n')
			continue;
		if (c == '\r')
			c = '\n';
		cn_check_magic(sc->sc_tty->t_dev, c, mtty_cnm_state);
		if (tp)
			(*tp->t_linesw->l_rint)(c, tp);
	}
	return 1;
}

void
mttycn_attach(void)
{

	cn_tab = &mtty;

	cn_init_magic(&mtty_cnm_state);
	cn_set_magic("+++++");
}

int
mttycn_getc(dev_t dev)
{
	char c;
	volatile uint32_t *regs = (void *)mtty_consolev;
	int s = spltty();
	//int i;

	if (mtty_console_device && mtty_console_device->sc_polling == 0) {
		printf("mttycn_getc() but not polling\n");
		splx(s);
		return 0;
	}

	for (;;) {
		if ((regs[TTY_STATUS] & 0x1) != 0) {
			c = regs[TTY_READ];
			if (c == '\n')
				continue;
			if (c == '\r')
				c = '\n';
			break;
		}
	}
#if 0
	for(i = 0; i < 1024; i++)
		;
	while (regs[TTY_STATUS] != 0)
		c = regs[TTY_READ];
#endif

	cn_check_magic(dev, c, mtty_cnm_state);
	splx(s);
	return c;
}

void
mttycn_putc(dev_t dev, int c)
{
	volatile uint32_t *regs = (void *)mtty_consolev;
	regs[TTY_WRITE] = c;
}

void
mttycn_pollc(dev_t dev, int on)
{
	printf("mttycn_pollc %d ipl 0x%x\n", on, curcpu()->ci_cpl);
	if (mtty_console_device)
		mtty_console_device->sc_polling = on;
}

/*
 * Set line parameters.
 */
int
mtty_param(struct tty *tp, struct termios *t)
{

	tp->t_ispeed = t->c_ispeed;
	tp->t_ospeed = t->c_ospeed;
	tp->t_cflag = t->c_cflag;
	return (0);
}
