/*-
 * Copyright (c) 1998 Doug Rabson
 * All rights reserved.
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 * $FreeBSD: src/sys/alpha/tlsb/zs_tlsb.c,v 1.25 2000/01/29 16:57:03 mjacob Exp $
 */
/*
 * This driver is a hopeless hack to get the SimOS console working.  A real
 * driver would use the zs driver source from NetBSD.
 */

#include "opt_ddb.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/ucred.h>
#include <sys/cons.h>
#include <machine/clock.h>

#include <alpha/tlsb/gbusvar.h>
#include <alpha/tlsb/tlsbreg.h>		/* XXX */
#include <alpha/tlsb/zsreg.h>
#include <alpha/tlsb/zsvar.h>

#define	KV(_addr)	((caddr_t)ALPHA_PHYS_TO_K0SEG((_addr)))

static int		zsc_get_channel(device_t dev);
static caddr_t		zsc_get_base(device_t dev);

struct zs_softc {
	struct tty	tty;
	int		channel;
	caddr_t		base;
};
#define ZS_SOFTC(unit)	\
	((struct zs_softc*)devclass_get_softc(zs_devclass, (unit)))

static	d_open_t	zsopen;
static	d_close_t	zsclose;
static	d_ioctl_t	zsioctl;

#define CDEV_MAJOR 135
static struct cdevsw zs_cdevsw = {
	/* open */	zsopen,
	/* close */	zsclose,
	/* read */	ttyread,
	/* write */	ttywrite,
	/* ioctl */	zsioctl,
	/* poll */	ttypoll,
	/* mmap */	nommap,
	/* strategy */	nostrategy,
	/* name */	"zs",
	/* maj */	CDEV_MAJOR,
	/* dump */	nodump,
	/* psize */	nopsize,
	/* flags */	0,
	/* bmaj */	-1
};

static void	zsstart __P((struct tty *));
static int	zsparam __P((struct tty *, struct termios *));
static void	zsstop __P((struct tty *tp, int flag));

/*
 * Helpers for console support.
 */

int		zs_cngetc __P((dev_t));
void		zs_cnputc __P((dev_t, int));

struct consdev zs_cons = {
	NULL, NULL, NULL, zs_cngetc, NULL, zs_cnputc, NULL, NULL, 0, CN_NORMAL
};

static caddr_t zs_console_addr;
static int zs_console;

static int zs_probe(device_t);
static int zs_attach(device_t);

static devclass_t zs_devclass;
static devclass_t zsc_devclass;

static device_method_t zs_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		zs_probe),
	DEVMETHOD(device_attach,	zs_attach),
	{ 0, 0 }
};

static driver_t zs_driver = {
	"zs", zs_methods, sizeof (struct zs_softc),
};

static int
zs_probe(device_t dev)
{
	return 0;
}

static int
zs_attach(device_t dev)
{
	struct zs_softc *sc = device_get_softc(dev);

	sc->channel = zsc_get_channel(dev);
	sc->base = zsc_get_base(dev);

	return 0;
}

static caddr_t
zs_statusreg(caddr_t base, int chan)
{
    if (chan == 0)
	return base + ZSC_CHANNELA + ZSC_STATUS;
    if (chan == 1)
	return base + ZSC_CHANNELB + ZSC_STATUS;
    panic("zs_statusreg: bogus channel");
}

static caddr_t
zs_datareg(caddr_t base, int chan)
{
    if (chan == 0)
	return base + ZSC_CHANNELA + ZSC_DATA;
    if (chan == 1)
	return base + ZSC_CHANNELB + ZSC_DATA;
    panic("zs_statusreg: bogus channel");
}

static int
zs_get_status(caddr_t base, int chan)
{
    return *(u_int32_t*) zs_statusreg(base, chan) & 0xff;
}

static void
zs_put_status(caddr_t base, int chan, int v)
{
    *(u_int32_t*) zs_statusreg(base, chan) = v;
    alpha_mb();
}

static int
zs_get_rr3(caddr_t base, int chan)
{
    if (chan != 0)
	panic("zs_get_rr3: bad channel");
    zs_put_status(base, chan, 3);
    return zs_get_status(base, chan);
}

static int
zs_get_data(caddr_t base, int chan)
{
    return *(u_int32_t*) zs_datareg(base, chan) & 0xff;
}

static void
zs_put_data(caddr_t base, int chan, int v)
{
    *(u_int32_t*) zs_datareg(base, chan) = v;
    alpha_mb();
}

static int
zs_getc(caddr_t base, int chan)
{
    while (!(zs_get_status(base, chan) & 1))
	DELAY(5);
    return zs_get_data(base, chan);
}

static void
zs_putc(caddr_t base, int chan, int c)
{
    while (!(zs_get_status(base, chan) & 4))
	DELAY(5);
    zs_put_data(base, chan, c);
}

int
zs_cnattach(vm_offset_t base, vm_offset_t offset)
{
    zs_console_addr = (caddr_t) ALPHA_PHYS_TO_K0SEG(base + offset);
    zs_console = 1;

    zs_cons.cn_dev = makedev(CDEV_MAJOR, 0);
    cn_tab = &zs_cons;
    return 0;
}

int
zs_cngetc(dev_t dev)
{
    int s = spltty();
    int c = zs_getc(zs_console_addr, minor(dev));
    splx(s);
    return c;
}

void
zs_cnputc(dev_t dev, int c)
{
    int s = spltty();
    zs_putc(zs_console_addr, minor(dev), c);
    splx(s);
}


static int
zsopen(dev_t dev, int flag, int mode, struct proc *p)
{
	struct zs_softc* sc = ZS_SOFTC(minor(dev));
	struct tty *tp;
	int s;
	int error = 0;
 
	if (!sc)
		return ENXIO;

	s = spltty();

	tp = &sc->tty;
	dev->si_tty = tp;

	tp->t_oproc = zsstart;
	tp->t_param = zsparam;
	tp->t_stop = zsstop;
	tp->t_dev = dev;
	if ((tp->t_state & TS_ISOPEN) == 0) {
		tp->t_state |= TS_CARR_ON;
		ttychars(tp);
		tp->t_iflag = TTYDEF_IFLAG;
		tp->t_oflag = TTYDEF_OFLAG;
		tp->t_cflag = TTYDEF_CFLAG|CLOCAL;
		tp->t_lflag = TTYDEF_LFLAG;
		tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
		ttsetwater(tp);
	} else if (tp->t_state & TS_XCLUDE &&
	     suser(p)) {
		splx(s);
		return EBUSY;
	}

	splx(s);

	error = (*linesw[tp->t_line].l_open)(dev, tp);

	return error;
}
 
static int
zsclose(dev_t dev, int flag, int mode, struct proc *p)
{
	struct tty *tp = &ZS_SOFTC(minor(dev))->tty;

	(*linesw[tp->t_line].l_close)(tp, flag);
	ttyclose(tp);
	return 0;
}
 
static int
zsioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
	struct tty *tp = &ZS_SOFTC(minor(dev))->tty;
	int error;
	
	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
	if (error != ENOIOCTL)
		return error;
	error = ttioctl(tp, cmd, data, flag);
	if (error != ENOIOCTL)
		return error;

	return ENOTTY;
}

static int
zsparam(struct tty *tp, struct termios *t)
{

	return 0;
}

static void
zsstart(struct tty *tp)
{
	struct zs_softc* sc = (struct zs_softc*) tp;
	int s;

	s = spltty();

	if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
		ttwwakeup(tp);
		splx(s);
		return;
	}

	tp->t_state |= TS_BUSY;
	while (tp->t_outq.c_cc != 0)
		zs_putc(sc->base, minor(tp->t_dev), getc(&tp->t_outq));
	tp->t_state &= ~TS_BUSY;

	ttwwakeup(tp);
	splx(s);
}

/*
 * Stop output on a line.
 */
static void
zsstop(struct tty *tp, int flag)
{
	int s;

	s = spltty();
	if (tp->t_state & TS_BUSY)
		if ((tp->t_state & TS_TTSTOP) == 0)
			tp->t_state |= TS_FLUSH;
	splx(s);
}

DRIVER_MODULE(zs, zsc, zs_driver, zs_devclass, 0, 0);

/*
 * The zsc bus holds two zs devices, one for channel A, one for channel B.
 */

struct zsc_softc {
	caddr_t base;
	struct zs_softc* sc_a;
	struct zs_softc* sc_b;
	void *intr;
};

static int zsc_tlsb_probe(device_t dev);
static int zsc_tlsb_attach(device_t dev);
static int zsc_tlsb_print_child(device_t dev, device_t child);
static driver_intr_t zsc_tlsb_intr;


static device_method_t zsc_tlsb_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		zsc_tlsb_probe),
	DEVMETHOD(device_attach,	zsc_tlsb_attach),
	DEVMETHOD(device_detach,	bus_generic_detach),
	DEVMETHOD(device_shutdown,	bus_generic_shutdown),

	/* Bus interface */
	DEVMETHOD(bus_print_child,	zsc_tlsb_print_child),
	DEVMETHOD(bus_read_ivar,	bus_generic_read_ivar),
	DEVMETHOD(bus_write_ivar,	bus_generic_write_ivar),
	DEVMETHOD(bus_setup_intr,	bus_generic_setup_intr),
	DEVMETHOD(bus_teardown_intr,	bus_generic_teardown_intr),

	{ 0, 0 }
};

static driver_t zsc_tlsb_driver = {
	"zsc",
	zsc_tlsb_methods,
	sizeof(struct zsc_softc),
};

static int
zsc_get_channel(device_t dev)
{
	return (long) device_get_ivars(dev);
}

static caddr_t
zsc_get_base(device_t dev)
{
	device_t bus = device_get_parent(dev);
	struct zsc_softc* sc = device_get_softc(bus);
	return sc->base;
}

static int
zsc_tlsb_probe(device_t dev)
{
	struct zsc_softc* sc = device_get_softc(dev);

	device_set_desc(dev, "Z8530 uart");

	sc->base = (caddr_t) ALPHA_PHYS_TO_K0SEG(TLSB_GBUS_BASE
						 + gbus_get_offset(dev));

	/*
	 * Add channel A and channel B
	 */
	device_add_child(dev, "zs", 0);
	device_add_child(dev, "zs", 1);

	return 0;
}

static int
zsc_tlsb_attach(device_t dev)
{
	struct zsc_softc* sc = device_get_softc(dev);
	device_t parent = device_get_parent(dev);

	cdevsw_add(&zs_cdevsw);

	bus_generic_attach(dev);
	
	/* XXX */
	sc->sc_a = ZS_SOFTC(0);
	sc->sc_b = ZS_SOFTC(1);

	/* XXX should use resource argument to communicate vector */
	return BUS_SETUP_INTR(parent, dev, NULL, INTR_TYPE_TTY,
			      zsc_tlsb_intr, sc, &sc->intr);

	return 0;
}

static int
zsc_tlsb_print_child(device_t bus, device_t dev)
{
	int retval = 0;

	retval += bus_print_child_header(bus, dev);
	retval += printf(" on %s channel %c\n", device_get_nameunit(bus),
			 'A' + (device_get_unit(dev) & 1));

	return (retval);
}

static void
zsc_tlsb_intr(void* arg)
{
	struct zsc_softc* sc = arg;
	caddr_t base = sc->base;

	int rr3 = zs_get_rr3(base, 0);
	if (rr3 & 0x20) {
		struct tty* tp = &sc->sc_a->tty;
		int c;

		while (zs_get_status(base, 0) & 1) {
			c = zs_get_data(base, 0);
#ifdef DDB
			if (c == CTRL('\\'))
				Debugger("manual escape to debugger");
#endif
			if (tp->t_state & TS_ISOPEN)
				(*linesw[tp->t_line].l_rint)(c, tp);
			DELAY(5);
		}
	}
	if (rr3 & 0x04) {
		struct tty* tp = &sc->sc_b->tty;
		int c;

		while (zs_get_status(base, 1) & 1) {
			c = zs_get_data(base, 1);
#ifdef DDB
			if (c == CTRL('\\'))
				Debugger("manual escape to debugger");
#endif
			if (tp->t_state & TS_ISOPEN)
				(*linesw[tp->t_line].l_rint)(c, tp);
			DELAY(5);
		}
	}
}

DRIVER_MODULE(zsc, gbus, zsc_tlsb_driver, zsc_devclass, 0, 0);
