/*	$OpenBSD: mkbc.c,v 1.13 2015/02/11 07:05:39 dlg Exp $  */

/*
 * Copyright (c) 2006, 2007, Joel Sing
 * 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 ``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 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 MIND,
 * 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.
 */

/*
 * Derived from sys/dev/ic/pckbc.c under the following terms:
 * $NetBSD: pckbc.c,v 1.5 2000/06/09 04:58:35 soda Exp $ */

/*
 * Copyright (c) 1998
 *	Matthias Drochner.  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 ``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 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 Moosehead PS/2 Controllers (mkbc)
 *
 * There are actually two separate controllers attached to the macebus.
 * However in the interest of reusing code, we want to act like a pckbc(4)
 * so that we can directly attach pckbd(4) and pms(4). As a result, we make 
 * each controller look like a "slot" and combine them into a single device.
 *
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/timeout.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/errno.h>
#include <sys/queue.h>

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

#include <mips64/archtype.h>

#include <sgi/localbus/crimebus.h>
#include <sgi/localbus/macebusvar.h>

#include <dev/ic/pckbcvar.h>
#include <dev/pckbc/pckbdvar.h>

#include "mkbcreg.h"

const char *mkbc_slot_names[] = { "kbd", "mouse" };

#define KBC_DEVCMD_ACK 0xfa
#define KBC_DEVCMD_RESEND 0xfe

#define KBD_DELAY       DELAY(8)

struct mkbc_softc {
	struct pckbc_softc sc_pckbc;
	bus_space_tag_t iot;
	bus_space_handle_t ioh;
};

int	mkbc_match(struct device *, void *, void *);
void	mkbc_attach(struct device *, struct device *, void *);

struct cfattach mkbc_ca = {
	sizeof(struct mkbc_softc), mkbc_match, mkbc_attach
};

struct cfdriver mkbc_cd = {
	NULL, "mkbc", DV_DULL
};

/* Descriptor for one device command. */
struct pckbc_devcmd {
	TAILQ_ENTRY(pckbc_devcmd) next;
	int flags;
#define KBC_CMDFLAG_SYNC 1 /* Give descriptor back to caller. */
#define KBC_CMDFLAG_SLOW 2
	u_char cmd[4];
	int cmdlen, cmdidx, retries;
	u_char response[4];
	int status, responselen, responseidx;
};

/* Data per slave device. */
struct pckbc_slotdata {
	int polling; /* Don't read data port in interrupt handler. */
	TAILQ_HEAD(, pckbc_devcmd) cmdqueue; /* Active commands. */
	TAILQ_HEAD(, pckbc_devcmd) freequeue; /* Free commands. */
#define NCMD 5
	struct pckbc_devcmd cmds[NCMD];
	bus_space_handle_t ioh;
};

#define CMD_IN_QUEUE(q) (TAILQ_FIRST(&(q)->cmdqueue) != NULL)

static int mkbc_console;
static struct pckbc_slotdata mkbc_cons_slotdata;
struct	pckbc_internal mkbc_consdata;

void	mkbc_start(struct pckbc_internal *, pckbc_slot_t);
int	mkbc_attach_slot(struct mkbc_softc *, pckbc_slot_t);
void	mkbc_init_slotdata(struct pckbc_slotdata *);
int	mkbc_submatch(struct device *, void *, void *);
int	mkbcprint(void *, const char *);
int	mkbcintr(void *);
int	mkbcintr_internal(struct pckbc_internal *, struct pckbc_softc *);
void	mkbc_cleanqueue(struct pckbc_slotdata *);
void	mkbc_cleanup(void *);
void	mkbc_poll(void *);
int	mkbc_cmdresponse(struct pckbc_internal *, pckbc_slot_t, u_char);
int	mkbc_poll_read(bus_space_tag_t, bus_space_handle_t);
int	mkbc_poll_write(bus_space_tag_t, bus_space_handle_t, int);

int
mkbc_match(struct device *parent, void *cf, void *aux)
{
	return 1;
}

void
mkbc_init_slotdata(struct pckbc_slotdata *q)
{
	int i;
	TAILQ_INIT(&q->cmdqueue);
	TAILQ_INIT(&q->freequeue);

	for (i = 0; i < NCMD; i++) {
		TAILQ_INSERT_TAIL(&q->freequeue, &(q->cmds[i]), next);
	}
	q->polling = 0;
}

int
mkbcprint(void *aux, const char *pnp)
{
	struct pckbc_attach_args *pa = aux;

	if (!pnp)
		printf(" (%s slot)", mkbc_slot_names[pa->pa_slot]);

	return (QUIET);
}

int
mkbc_submatch(struct device *parent, void *match, void *aux)
{
	struct cfdata *cf = match;
	struct pckbc_attach_args *pa = aux;

	if (cf->cf_loc[PCKBCCF_SLOT] != PCKBCCF_SLOT_DEFAULT &&
	    cf->cf_loc[PCKBCCF_SLOT] != pa->pa_slot)
		return (0);
	return ((*cf->cf_attach->ca_match)(parent, cf, aux));
}

int
mkbc_attach_slot(struct mkbc_softc *msc, pckbc_slot_t slot)
{
	struct pckbc_softc *sc = &msc->sc_pckbc;
	struct pckbc_internal *t = sc->id;
	struct pckbc_attach_args pa;
	bus_space_handle_t ioh;
	int found;

	if (!t->t_slotdata[slot]) {

		t->t_slotdata[slot] = malloc(sizeof(struct pckbc_slotdata),
		    M_DEVBUF, M_NOWAIT);

		if (t->t_slotdata[slot] == NULL) {
			printf("Failed to allocate slot data!\n");
			return 0;
		}
		mkbc_init_slotdata(t->t_slotdata[slot]);

		/* Map subregion of bus space for this "slot". */
		if (bus_space_subregion(msc->iot, msc->ioh, 
		    MKBC_PORTSIZE * slot, MKBC_PORTSIZE, &ioh)) {
			printf("Unable to map slot subregion!\n");
			return 0;
		}
		t->t_slotdata[slot]->ioh = ioh;

		/* Initialise controller. */
		bus_space_write_8(msc->iot, ioh, MKBC_CONTROL,
		    MKBC_CONTROL_TX_CLOCK_DISABLE | MKBC_CONTROL_RESET);
		delay(100); /* 100us */

		/* Enable controller. */
		bus_space_write_8(t->t_iot, t->t_slotdata[slot]->ioh, 
		    MKBC_CONTROL,
		    MKBC_CONTROL_RX_CLOCK_ENABLE | MKBC_CONTROL_TX_ENABLE);

	}

	pa.pa_tag = t;
	pa.pa_slot = slot;
	found = (config_found_sm((struct device *)msc, &pa,
	    mkbcprint, mkbc_submatch) != NULL);

	return (found);
}

void
mkbc_attach(struct device *parent, struct device *self, void *aux)
{
	struct mkbc_softc *msc = (void*)self;
	struct macebus_attach_args *maa = aux;
	struct pckbc_softc *sc = &msc->sc_pckbc;
	struct pckbc_internal *t;

	if (mkbc_console == 0) {

		/* Setup bus space mapping. */
		msc->iot = maa->maa_iot;
		if (bus_space_map(msc->iot, maa->maa_baseaddr,
		    MKBC_PORTSIZE * 2, 0, &msc->ioh)) {
			printf(": unable to map bus space!\n");
			return;
		}

		/* Setup pckbc_internal structure. */
		t = malloc(sizeof(struct pckbc_internal), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		t->t_iot = msc->iot;
		t->t_ioh_d = 0;
		t->t_ioh_c = 0;
		t->t_addr = maa->maa_baseaddr;
		t->t_sc = (struct pckbc_softc *)msc;
		sc->id = t;

		timeout_set(&t->t_cleanup, mkbc_cleanup, t);
		timeout_set(&t->t_poll, mkbc_poll, t);

	} else {

		/* Things have already been setup in mkbc_cnattach. */
		msc->iot = mkbc_consdata.t_iot;
		msc->ioh = mkbc_consdata.t_ioh_d;
		mkbc_consdata.t_sc = (struct pckbc_softc *)msc;
		sc->id = &mkbc_consdata;

	}

	/* Establish interrupt handler. */
	if (macebus_intr_establish(maa->maa_intr, maa->maa_mace_intr,
	    IST_EDGE, IPL_TTY, mkbcintr, msc, sc->sc_dv.dv_xname))
		printf("\n");
	else
		printf(": unable to establish interrupt\n");

	/*
	 * Attach "slots" - technically these are separate controllers
	 * in the same bus space, however we want to act like pckbc(4) so
	 * that we can attach pckbd(4) and pms(4). 
	 */
	mkbc_attach_slot(msc, PCKBC_KBD_SLOT);
	mkbc_attach_slot(msc, PCKBC_AUX_SLOT);
}

int
mkbcintr(void *vsc)
{
	struct mkbc_softc *msc = (struct mkbc_softc *)vsc;
	struct pckbc_softc *sc = &msc->sc_pckbc;
	struct pckbc_internal *t = sc->id;

	return mkbcintr_internal(t, sc);
}

int
mkbcintr_internal(struct pckbc_internal *t, struct pckbc_softc *sc)
{
	pckbc_slot_t slot;
	struct pckbc_slotdata *q;
	int served = 0;
	u_int64_t stat;
	u_int64_t data;

	/* Reschedule timeout further into the idle times. */
	if (timeout_pending(&t->t_poll))
		timeout_add_sec(&t->t_poll, 1);

	/*
	 * Need to check both "slots" since interrupt could be from
	 * either controller.
	 */ 
	slot = PCKBC_KBD_SLOT;
	q = t->t_slotdata[slot];
	for(;;) {

		if (!q) {
			printf("mkbcintr: no kbd slot data!\n");
			break;
		}

		stat = bus_space_read_8(t->t_iot, q->ioh, MKBC_STATUS);
		if (!(stat & MKBC_STATUS_RX_FULL))
			break;

		served = 1;

		if (q->polling)
			break; /* pckbc_poll_data() will get it */

		KBD_DELAY;
		data = bus_space_read_8(t->t_iot, q->ioh, MKBC_RX_PORT) & 0xff;
		if (CMD_IN_QUEUE(q) && mkbc_cmdresponse(t, slot, data))
			continue;

		if (sc->inputhandler[slot])
			(*sc->inputhandler[slot])(sc->inputarg[slot], data);
#ifdef MKBCDEBUG
		else
			printf("mkbcintr: slot %d lost %d\n", slot, data);
#endif
	}
	
	/* Mouse controller/slot. */
	slot = PCKBC_AUX_SLOT;
	q = t->t_slotdata[slot];
	for(;;) {

		if (!q) {
			printf("mkbcintr: no mouse slot data!\n");
			break;
		}

		stat = bus_space_read_8(t->t_iot, q->ioh, MKBC_STATUS);
		if (!(stat & MKBC_STATUS_RX_FULL))
			break;

		served = 1;

		if (q->polling)
			break; /* pckbc_poll_data() will get it. */

		KBD_DELAY;
		data = bus_space_read_8(t->t_iot, q->ioh, MKBC_RX_PORT) & 0xff;
		if (CMD_IN_QUEUE(q) && mkbc_cmdresponse(t, slot, data))
			continue;

		if (sc->inputhandler[slot])
			(*sc->inputhandler[slot])(sc->inputarg[slot], data);
#ifdef MKBCDEBUG
		else
			printf("mkbcintr: slot %d lost %d\n", slot, data);
#endif
	}

	return (served);
}

int
mkbc_poll_write(bus_space_tag_t iot, bus_space_handle_t ioh, int val)
{
	int timeout = 10000;
	u_int64_t stat;

	/* Attempt to write a value to the controller. */
	while (timeout--) {
		stat = bus_space_read_8(iot, ioh, MKBC_STATUS);
		if (stat & MKBC_STATUS_TX_EMPTY) {
			bus_space_write_8(iot, ioh, MKBC_TX_PORT, val & 0xff);
			return 0;
		}
		delay(50);
	}
	return -1;
}

int
mkbc_poll_read(bus_space_tag_t iot, bus_space_handle_t ioh)
{
	int timeout = 10000;
	u_int64_t stat, val;

	/* Poll input from controller. */
	while (timeout--) {
		stat = bus_space_read_8(iot, ioh, MKBC_STATUS);
		if (stat & MKBC_STATUS_RX_FULL) {
			val = bus_space_read_8(iot, ioh, MKBC_RX_PORT);
			return val & 0xff;
		}
		delay(50);
	}
	return -1;
}

/*
 * Pass command to device, poll for ACK and data.
 * to be called at spltty()
 */
static void
mkbc_poll_cmd(struct pckbc_internal *t, pckbc_slot_t slot, 
    struct pckbc_devcmd *cmd)
{
	bus_space_tag_t iot = t->t_iot;
	bus_space_handle_t ioh = t->t_slotdata[slot]->ioh;

	int i, c = 0;

	while (cmd->cmdidx < cmd->cmdlen) {
		if (mkbc_poll_write(iot, ioh, cmd->cmd[cmd->cmdidx]) == -1) {
			printf("mkbc_poll_cmd: send error\n");
			cmd->status = EIO;
			return;
		}
		for (i = 10; i; i--) { /* 1s ??? */
			c = mkbc_poll_read(iot, ioh);
			if (c != -1)
				break;
		}

		if (c == KBC_DEVCMD_ACK) {
			cmd->cmdidx++;
			continue;
		}
		if (c == KBC_DEVCMD_RESEND) {
#ifdef MKBCDEBUG
			printf("mkbc_cmd: RESEND\n");
#endif
			if (cmd->retries++ < 5)
				continue;
			else {
#ifdef MKBCDEBUG
				printf("mkbc: cmd failed\n");
#endif
				cmd->status = EIO;
				return;
			}
		}
		if (c == -1) {
#ifdef MKBCDEBUG
			printf("mkbc_cmd: timeout\n");
#endif
			cmd->status = EIO;
			return;
		}
#ifdef MKBCDEBUG
		printf("mkbc_cmd: lost 0x%x\n", c);
#endif
	}

	while (cmd->responseidx < cmd->responselen) {
		if (cmd->flags & KBC_CMDFLAG_SLOW)
			i = 100; /* 10s ??? */
		else
			i = 10; /* 1s ??? */
		while (i--) {
			c = mkbc_poll_read(iot, ioh);
			if (c != -1)
				break;
		}
		if (c == -1) {
#ifdef MKBCDEBUG
			printf("mkbc_poll_cmd: no data\n");
#endif
			cmd->status = ETIMEDOUT;
			return;
		} else
			cmd->response[cmd->responseidx++] = c;
	}
}

/*
 * Clean up a command queue, throw away everything.
 */
void
mkbc_cleanqueue(struct pckbc_slotdata *q)
{
	struct pckbc_devcmd *cmd;
#ifdef MKBCDEBUG
	int i;
#endif

	while ((cmd = TAILQ_FIRST(&q->cmdqueue))) {
		TAILQ_REMOVE(&q->cmdqueue, cmd, next);
#ifdef MKBCDEBUG
		printf("mkbc_cleanqueue: removing");
		for (i = 0; i < cmd->cmdlen; i++)
			printf(" %02x", cmd->cmd[i]);
		printf("\n");
#endif
		TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
	}
}

/*
 * Timeout error handler: clean queues and data port.
 * XXX could be less invasive.
 */
void
mkbc_cleanup(void *self)
{
	struct pckbc_internal *t = self;
	int s;

	printf("mkbc: command timeout\n");

	s = spltty();

	if (t->t_slotdata[PCKBC_KBD_SLOT])
		mkbc_cleanqueue(t->t_slotdata[PCKBC_KBD_SLOT]);
	if (t->t_slotdata[PCKBC_AUX_SLOT])
		mkbc_cleanqueue(t->t_slotdata[PCKBC_AUX_SLOT]);

	while (mkbc_poll_read(t->t_iot, t->t_slotdata[PCKBC_KBD_SLOT]->ioh) 
	       != -1) ;
	while (mkbc_poll_read(t->t_iot, t->t_slotdata[PCKBC_AUX_SLOT]->ioh) 
	       != -1) ;

	/* Reset KBC? */

	splx(s);
}

/*
 * Pass command to device during normal operation.
 * to be called at spltty()
 */
void
mkbc_start(struct pckbc_internal *t, pckbc_slot_t slot)
{
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	struct pckbc_devcmd *cmd = TAILQ_FIRST(&q->cmdqueue);

	if (q->polling) {
		do {
			mkbc_poll_cmd(t, slot, cmd);
			if (cmd->status)
				printf("mkbc_start: command error\n");

			TAILQ_REMOVE(&q->cmdqueue, cmd, next);
			if (cmd->flags & KBC_CMDFLAG_SYNC)
				wakeup(cmd);
			else {
				timeout_del(&t->t_cleanup);
				TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
			}
			cmd = TAILQ_FIRST(&q->cmdqueue);
		} while (cmd);
		return;
	}

	if (mkbc_poll_write(t->t_iot, t->t_slotdata[slot]->ioh, 
	    cmd->cmd[cmd->cmdidx])) {
		printf("mkbc_start: send error\n");
		/* XXX what now? */
		return;
	}
}

/*
 * Handle command responses coming in asynchronously,
 * return nonzero if valid response.
 * to be called at spltty()
 */
int
mkbc_cmdresponse(struct pckbc_internal *t, pckbc_slot_t slot, u_char data)
{
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	struct pckbc_devcmd *cmd = TAILQ_FIRST(&q->cmdqueue);
#ifdef DIAGNOSTIC
	if (!cmd)
		panic("mkbc_cmdresponse: no active command");
#endif
	if (cmd->cmdidx < cmd->cmdlen) {
		if (data != KBC_DEVCMD_ACK && data != KBC_DEVCMD_RESEND)
			return (0);

		if (data == KBC_DEVCMD_RESEND) {
			if (cmd->retries++ < 5) {
				/* try again last command */
				goto restart;
			} else {
#ifdef MKBCDEBUG
				printf("mkbc: cmd failed\n");
#endif
				cmd->status = EIO;
				/* dequeue */
			}
		} else {
			if (++cmd->cmdidx < cmd->cmdlen)
				goto restart;
			if (cmd->responselen)
				return (1);
			/* else dequeue */
		}
	} else if (cmd->responseidx < cmd->responselen) {
		cmd->response[cmd->responseidx++] = data;
		if (cmd->responseidx < cmd->responselen)
			return (1);
		/* else dequeue */
	} else
		return (0);

	/* dequeue: */
	TAILQ_REMOVE(&q->cmdqueue, cmd, next);
	if (cmd->flags & KBC_CMDFLAG_SYNC)
		wakeup(cmd);
	else {
		timeout_del(&t->t_cleanup);
		TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
	}
	if (!CMD_IN_QUEUE(q))
		return (1);
restart:
	mkbc_start(t, slot);
	return (1);
}

/*
 * Interfaces to act like pckbc(4).
 */

int
pckbc_xt_translation(pckbc_tag_t self)
{
	/* Translation isn't supported... */
	return (-1);
}

/* For use in autoconfiguration. */
int
pckbc_poll_cmd(pckbc_tag_t self, pckbc_slot_t slot, u_char *cmd, int len, 
	       int responselen, u_char *respbuf, int slow)
{
	struct pckbc_devcmd nc;
	int s;

	if ((len > 4) || (responselen > 4))
		return (EINVAL);

	bzero(&nc, sizeof(nc));
	bcopy(cmd, nc.cmd, len);
	nc.cmdlen = len;
	nc.responselen = responselen;
	nc.flags = (slow ? KBC_CMDFLAG_SLOW : 0);

	s = spltty();
	mkbc_poll_cmd(self, slot, &nc);
	splx(s);

	if (nc.status == 0 && respbuf)
		bcopy(nc.response, respbuf, responselen);

	return (nc.status);
}

void
pckbc_flush(pckbc_tag_t self, pckbc_slot_t slot)
{
	/* Read any data and discard. */
	struct pckbc_internal *t = self;
	(void) mkbc_poll_read(t->t_iot, t->t_slotdata[slot]->ioh);
}

/*
 * Put command into the device's command queue, return zero or errno.
 */
int
pckbc_enqueue_cmd(pckbc_tag_t self, pckbc_slot_t slot, u_char *cmd, int len, 
    int responselen, int sync, u_char *respbuf)
{
	struct pckbc_internal *t = self;
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	struct pckbc_devcmd *nc;
	int s, isactive, res = 0;

	if ((len > 4) || (responselen > 4))
		return (EINVAL);
	s = spltty();
	nc = TAILQ_FIRST(&q->freequeue);
	if (nc) {
		TAILQ_REMOVE(&q->freequeue, nc, next);
	}
	splx(s);
	if (!nc)
		return (ENOMEM);

	bzero(nc, sizeof(*nc));
	bcopy(cmd, nc->cmd, len);
	nc->cmdlen = len;
	nc->responselen = responselen;
	nc->flags = (sync ? KBC_CMDFLAG_SYNC : 0);

	s = spltty();

	if (q->polling && sync) {
		/*
		 * XXX We should poll until the queue is empty.
		 * But we don't come here normally, so make
		 * it simple and throw away everything.
		 */
		mkbc_cleanqueue(q);
	}

	isactive = CMD_IN_QUEUE(q);
	TAILQ_INSERT_TAIL(&q->cmdqueue, nc, next);
	if (!isactive)
		mkbc_start(t, slot);

	if (q->polling)
		res = (sync ? nc->status : 0);
	else if (sync) {
		if ((res = tsleep(nc, 0, "kbccmd", 1*hz))) {
			TAILQ_REMOVE(&q->cmdqueue, nc, next);
			mkbc_cleanup(t);
		} else
			res = nc->status;
	} else
		timeout_add_sec(&t->t_cleanup, 1);

	if (sync) {
		if (respbuf)
			bcopy(nc->response, respbuf, responselen);
		TAILQ_INSERT_TAIL(&q->freequeue, nc, next);
	}

	splx(s);

	return (res);
}

int
pckbc_poll_data(pckbc_tag_t self, pckbc_slot_t slot)
{
	struct pckbc_internal *t = self;
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	int c;

	c = mkbc_poll_read(t->t_iot, q->ioh);
	if (c != -1 && q && CMD_IN_QUEUE(q)) {
		/* We jumped into a running command - try to deliver the 
		   response. */
		if (mkbc_cmdresponse(t, slot, c))
			return (-1);
	}
	return (c);
}

void
pckbc_set_inputhandler(pckbc_tag_t self, pckbc_slot_t slot, pckbc_inputfcn func,
    void *arg, char *name)
{
	struct pckbc_internal *t = (struct pckbc_internal *)self;
	struct pckbc_softc *sc = t->t_sc;

	if (slot >= PCKBC_NSLOTS)
		panic("mkbc_set_inputhandler: bad slot %d", slot);

	sc->inputhandler[slot] = func;
	sc->inputarg[slot] = arg;
	sc->subname[slot] = name;

	if (mkbc_console && slot == PCKBC_KBD_SLOT)
		timeout_add_sec(&t->t_poll, 1);
}

void
pckbc_slot_enable(pckbc_tag_t self, pckbc_slot_t slot, int on)
{
	struct pckbc_internal *t = (struct pckbc_internal *)self;

	/*
	 * Should we also enable/disable the controller?? 
	 * If we did then none of the poll_ functions would work...
	 */

	if (on) {
	
		/* Enable controller interrupts. */
		bus_space_write_8(t->t_iot, t->t_slotdata[slot]->ioh, 
		    MKBC_CONTROL,
		    MKBC_CONTROL_RX_CLOCK_ENABLE | MKBC_CONTROL_TX_ENABLE
		    | MKBC_CONTROL_RX_INT_ENABLE);

	} else {

		/* Disable controller interrupts. */
		bus_space_write_8(t->t_iot, t->t_slotdata[slot]->ioh, 
		    MKBC_CONTROL,
		    MKBC_CONTROL_RX_CLOCK_ENABLE | MKBC_CONTROL_TX_ENABLE);

	}

	if (slot == PCKBC_KBD_SLOT) {
		if (on)
			timeout_add_sec(&t->t_poll, 1);
		else
			timeout_del(&t->t_poll);
	}
}

void
pckbc_set_poll(pckbc_tag_t self, pckbc_slot_t slot, int on)
{
	struct pckbc_internal *t = (struct pckbc_internal *)self;

	t->t_slotdata[slot]->polling = on;

	if (!on) {
		int s;

		/*
		 * If disabling polling on a device that's been configured,
		 * make sure there are no bytes left in the FIFO, holding up
		 * the interrupt line.  Otherwise we won't get any further
		 * interrupts.
		 */
		if (t->t_sc) {
			s = spltty();
			mkbcintr(t->t_sc);
			splx(s);
		}
	}
}

int
mkbc_cnattach(bus_space_tag_t iot, bus_addr_t addr)
{
	bus_space_handle_t ioh, slot_ioh;
	int res = 0;

	/* Ensure that we're on an O2. */
	if (sys_config.system_type != SGI_O2)
		return (ENXIO);

	if (bus_space_map(iot, addr, MKBC_PORTSIZE * 2, 0, &ioh))
                return (ENXIO);

	mkbc_consdata.t_addr = addr;
	mkbc_consdata.t_iot = iot;
	mkbc_consdata.t_ioh_d = ioh;

	/* Map subregion of bus space for this "slot". */
	if (bus_space_subregion(iot, ioh, 0, MKBC_PORTSIZE, &slot_ioh)) {
		bus_space_unmap(iot, ioh, MKBC_PORTSIZE * 2);
		return (ENXIO);
	}

	mkbc_cons_slotdata.ioh = slot_ioh;
	mkbc_init_slotdata(&mkbc_cons_slotdata);
	mkbc_consdata.t_slotdata[PCKBC_KBD_SLOT] = &mkbc_cons_slotdata;

	/* Initialise controller. */
	bus_space_write_8(iot, slot_ioh, MKBC_CONTROL,
	    MKBC_CONTROL_TX_CLOCK_DISABLE | MKBC_CONTROL_RESET);
	delay(100); /* 100us */

	/* Enable controller. */
	bus_space_write_8(iot, slot_ioh, MKBC_CONTROL,
	    MKBC_CONTROL_RX_CLOCK_ENABLE | MKBC_CONTROL_TX_ENABLE
	    | MKBC_CONTROL_RX_INT_ENABLE);

	timeout_set(&mkbc_consdata.t_cleanup, mkbc_cleanup, &mkbc_consdata);
	timeout_set(&mkbc_consdata.t_poll, mkbc_poll, &mkbc_consdata);

	/* Flush input buffer. */
	(void) mkbc_poll_read(iot, slot_ioh);

	res = pckbd_cnattach(&mkbc_consdata);

	if (res) {
		bus_space_unmap(iot, ioh, MKBC_PORTSIZE * 2);
	} else {
		mkbc_console = 1;
	}

	return (res);
}

void
mkbc_poll(void *self)
{
	struct pckbc_internal *t = self;
        int s;

	s = spltty();
	(void)mkbcintr_internal(t, t->t_sc);
	timeout_add_sec(&t->t_poll, 1);
	splx(s);
}
