code:
/*	$OpenBSD: iockbc.c,v 1.11 2015/02/11 07:05:39 dlg Exp $	*/
/*
 * Copyright (c) 2013, Miodrag Vallat
 * Copyright (c) 2006, 2007, 2009 Joel Sing <jsing@openbsd.org>
 *
 * Permission to use, copy, modify, and 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.
 */

/*
 * 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 IOC3 and IOC4 PS/2 Controllers (iockbc)
 */

#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/pci/iocreg.h>
#include <sgi/pci/iocvar.h>
#include <sgi/pci/iofreg.h>
#include <sgi/pci/iofvar.h>

#include <dev/ic/i8042reg.h>
#include <dev/ic/pckbcvar.h>
#include <dev/pckbc/pckbdreg.h>
#define KBC_DEVCMD_ACK		KBR_ACK
#define KBC_DEVCMD_RESEND	KBR_RESEND
#include <dev/pckbc/pckbdvar.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcidevs.h>

#include <sgi/dev/iockbcreg.h>
#include <sgi/dev/iockbcvar.h>

#include "iockbc.h"

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

/* #define IOCKBC_DEBUG */
#ifdef IOCKBC_DEBUG
#define DPRINTF(x...)           do { printf(x); } while(0)
#else
#define DPRINTF(x...)
#endif

struct iockbc_reginfo {
	bus_addr_t rx;
	bus_addr_t tx;
	bus_addr_t cs;
	uint32_t busy;
};

struct iockbc_softc {
	struct pckbc_softc		 sc_pckbc;
	bus_space_tag_t			 iot;
	bus_space_handle_t		 ioh;
	const struct iockbc_reginfo	*reginfo;
	int				 console;
};

int	iockbc_match(struct device *, void *, void *);
void	iockbc_ioc_attach(struct device *, struct device *, void *);
void	iockbc_iof_attach(struct device *, struct device *, void *);

#if NIOCKBC_IOC > 0
struct cfattach iockbc_ioc_ca = {
	sizeof(struct iockbc_softc), iockbc_match, iockbc_ioc_attach
};
#endif
#if NIOCKBC_IOF > 0
struct cfattach iockbc_iof_ca = {
	sizeof(struct iockbc_softc), iockbc_match, iockbc_iof_attach
};
#endif

struct cfdriver iockbc_cd = {
	NULL, "iockbc", 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];
	int rx_queue[3];
	int rx_index;
	const struct iockbc_reginfo *reginfo;
};

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

enum iockbc_slottype { EMPTY, KBD, MOUSE };

static struct pckbc_internal iockbc_consdata;
static struct pckbc_slotdata iockbc_cons_slotdata;

int	iockbc_is_ioc_console(struct ioc_attach_args *);
int	iockbc_is_iof_console(struct iof_attach_args *);
void	iockbc_attach_common(struct iockbc_softc *, bus_addr_t, int,
	    const struct iockbc_reginfo *, const struct iockbc_reginfo *);
void	iockbc_start(struct pckbc_internal *, pckbc_slot_t);
int	iockbc_attach_slot(struct iockbc_softc *, pckbc_slot_t);
void	iockbc_init_slotdata(struct pckbc_slotdata *,
	    const struct iockbc_reginfo *);
int	iockbc_submatch(struct device *, void *, void *);
int	iockbcprint(void *, const char *);
int	iockbcintr(void *);
int	iockbcintr_internal(struct pckbc_internal *, struct pckbc_softc *);
void	iockbc_cleanqueue(struct pckbc_slotdata *);
void	iockbc_cleanup(void *);
void	iockbc_poll(void *);
int	iockbc_cmdresponse(struct pckbc_internal *, pckbc_slot_t, u_char);
int	iockbc_poll_read(struct pckbc_internal *, pckbc_slot_t);
int	iockbc_poll_write(struct pckbc_internal *, pckbc_slot_t, int);
void	iockbc_process_input(struct pckbc_softc *, struct pckbc_internal *,
	    int, uint);
enum iockbc_slottype
	iockbc_probe_slot(struct pckbc_internal *, pckbc_slot_t);

int
iockbc_match(struct device *parent, void *cf, void *aux)
{
	/*
	 * We expect ioc and iof NOT to attach us if there are no PS/2 ports.
	 */
	return 1;
}

#if NIOCKBC_IOC > 0
/*
 * Register assignments
 */

const struct iockbc_reginfo iockbc_ioc[PCKBC_NSLOTS] = {
	[PCKBC_KBD_SLOT] = {
		.rx = IOC3_KBC_KBD_RX,
		.tx = IOC3_KBC_KBD_TX,
		.cs = IOC3_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_KBD_WRITE_PENDING
	},
	[PCKBC_AUX_SLOT] = {
		.rx = IOC3_KBC_AUX_RX,
		.tx = IOC3_KBC_AUX_TX,
		.cs = IOC3_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_AUX_WRITE_PENDING
	}
};

const struct iockbc_reginfo iockbc_ioc_inverted[PCKBC_NSLOTS] = {
	[PCKBC_KBD_SLOT] = {
		.rx = IOC3_KBC_AUX_RX,
		.tx = IOC3_KBC_AUX_TX,
		.cs = IOC3_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_AUX_WRITE_PENDING
	},
	[PCKBC_AUX_SLOT] = {
		.rx = IOC3_KBC_KBD_RX,
		.tx = IOC3_KBC_KBD_TX,
		.cs = IOC3_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_KBD_WRITE_PENDING
	}
};

void
iockbc_ioc_attach(struct device *parent, struct device *self, void *aux)
{
	struct iockbc_softc *isc = (void*)self;
	struct ioc_attach_args *iaa = aux;

	/* Setup bus space mapping. */
	isc->iot = iaa->iaa_memt;
	isc->ioh = iaa->iaa_memh;

	/* Establish interrupt handler. */
	if (ioc_intr_establish(parent, iaa->iaa_dev, IPL_TTY, iockbcintr,
	    (void *)isc, self->dv_xname))
		printf("\n");
	else
		printf(": unable to establish interrupt\n");

	iockbc_attach_common(isc, iaa->iaa_base, iockbc_is_ioc_console(iaa),
	    iockbc_ioc, iockbc_ioc_inverted);
}
#endif

#if NIOCKBC_IOF > 0
/*
 * Register assignments
 */

const struct iockbc_reginfo iockbc_iof[PCKBC_NSLOTS] = {
	[PCKBC_KBD_SLOT] = {
		.rx = IOC4_KBC_KBD_RX,
		.tx = IOC4_KBC_KBD_TX,
		.cs = IOC4_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_KBD_WRITE_PENDING
	},
	[PCKBC_AUX_SLOT] = {
		.rx = IOC4_KBC_AUX_RX,
		.tx = IOC4_KBC_AUX_TX,
		.cs = IOC4_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_AUX_WRITE_PENDING
	}
};

const struct iockbc_reginfo iockbc_iof_inverted[PCKBC_NSLOTS] = {
	[PCKBC_KBD_SLOT] = {
		.rx = IOC4_KBC_AUX_RX,
		.tx = IOC4_KBC_AUX_TX,
		.cs = IOC4_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_AUX_WRITE_PENDING
	},
	[PCKBC_AUX_SLOT] = {
		.rx = IOC4_KBC_KBD_RX,
		.tx = IOC4_KBC_KBD_TX,
		.cs = IOC4_KBC_CTRL_STATUS,
		.busy = IOC3_KBC_STATUS_KBD_WRITE_PENDING
	}
};

void
iockbc_iof_attach(struct device *parent, struct device *self, void *aux)
{
	struct iockbc_softc *isc = (void*)self;
	struct iof_attach_args *iaa = aux;

	/* Setup bus space mapping. */
	isc->iot = iaa->iaa_memt;
	isc->ioh = iaa->iaa_memh;

	/* Establish interrupt handler. */
	if (iof_intr_establish(parent, iaa->iaa_dev, IPL_TTY, iockbcintr,
	    (void *)isc, self->dv_xname))
		printf("\n");
	else
		printf(": unable to establish interrupt\n");

	iockbc_attach_common(isc, iaa->iaa_base, iockbc_is_iof_console(iaa),
	    iockbc_iof, iockbc_iof_inverted);
}
#endif

void
iockbc_init_slotdata(struct pckbc_slotdata *q,
    const struct iockbc_reginfo *reginfo)
{
	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;
	q->rx_index = -1;

	q->reginfo = reginfo;
}

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

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

	return (QUIET);
}

int
iockbc_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, pa));
}

/*
 * Figure out what kind of device is connected to the given slot, if any.
 */
enum iockbc_slottype
iockbc_probe_slot(struct pckbc_internal *t, pckbc_slot_t slot)
{
	int rc, i, tries;

	/* reset device */
	pckbc_flush(t, slot);
	for (tries = 0; tries < 5; tries++) {
		rc = iockbc_poll_write(t, slot, KBC_RESET);
		if (rc < 0) {
			DPRINTF(("%s: slot %d write failed\n", __func__, slot));
			return EMPTY;
		}
		for (i = 10; i != 0; i--) {
			rc = iockbc_poll_read(t, slot);
			if (rc >= 0)
				break;
		}
		if (rc < 0) {
			DPRINTF(("%s: slot %d no answer to reset\n",
			    __func__, slot));
			return EMPTY;
		}
		if (rc == KBC_DEVCMD_ACK)
			break;
		if (rc == KBC_DEVCMD_RESEND)
			continue;
		DPRINTF(("%s: slot %d bogus reset ack %02x\n",
		    __func__, slot, rc));
		return EMPTY;
	}

	/* get answer byte */
	for (i = 10; i != 0; i--) {
		rc = iockbc_poll_read(t, slot);
		if (rc >= 0)
			break;
	}
	if (rc < 0) {
		DPRINTF(("%s: slot %d no answer to reset after ack\n",
		    __func__, slot));
		return EMPTY;
	}
	if (rc != KBR_RSTDONE) {
		DPRINTF(("%s: slot %d bogus reset answer %02x\n",
		    __func__, slot, rc));
		return EMPTY;
	}
	/* mice send an extra byte */
	(void)iockbc_poll_read(t, slot);

	/* ask for device id */
	for (tries = 0; tries < 5; tries++) {
		rc = iockbc_poll_write(t, slot, KBC_READID);
		if (rc == -1) {
			DPRINTF(("%s: slot %d write failed\n", __func__, slot));
			return EMPTY;
		}
		for (i = 10; i != 0; i--) {
			rc = iockbc_poll_read(t, slot);
			if (rc >= 0)
				break;
		}
		if (rc < 0) {
			DPRINTF(("%s: slot %d no answer to command\n",
			    __func__, slot));
			return EMPTY;
		}
		if (rc == KBC_DEVCMD_ACK)
			break;
		if (rc == KBC_DEVCMD_RESEND)
			continue;
		DPRINTF(("%s: slot %d bogus command ack %02x\n",
		    __func__, slot, rc));
		return EMPTY;
	}

	/* get first answer byte */
	for (i = 10; i != 0; i--) {
		rc = iockbc_poll_read(t, slot);
		if (rc >= 0)
			break;
	}
	if (rc < 0) {
		DPRINTF(("%s: slot %d no answer to command after ack\n",
		    __func__, slot));
		return EMPTY;
	}

	switch (rc) {
	case KCID_KBD1:		/* keyboard */
		/* get second answer byte */
		rc = iockbc_poll_read(t, slot);
		if (rc < 0) {
			DPRINTF(("%s: slot %d truncated keyboard answer\n",
			    __func__, slot));
			return EMPTY;
		}
		if (rc != KCID_KBD2) {
			DPRINTF(("%s: slot %d unexpected keyboard answer"
			    " 0x%02x 0x%02x\n", __func__, slot, KCID_KBD1, rc));
			/* return EMPTY; */
		}
		return KBD;
	case KCID_MOUSE:	/* mouse */
		return MOUSE;
	default:
		DPRINTF(("%s: slot %d unknown device answer 0x%02x\n",
		    __func__, slot, rc));
		return EMPTY;
	}
}

int
iockbc_attach_slot(struct iockbc_softc *isc, pckbc_slot_t slot)
{
	struct pckbc_softc *sc = &isc->sc_pckbc;
	struct pckbc_internal *t = sc->id;
	struct pckbc_attach_args pa;
	int found;

	iockbc_init_slotdata(t->t_slotdata[slot], &isc->reginfo[slot]);

	pa.pa_tag = t;
	pa.pa_slot = slot;

	found = (config_found_sm((struct device *)sc, &pa,
	    iockbcprint, iockbc_submatch) != NULL);

	return (found);
}

void
iockbc_attach_common(struct iockbc_softc *isc, bus_addr_t addr, int console,
    const struct iockbc_reginfo *reginfo,
    const struct iockbc_reginfo *reginfo_inverted)
{
	struct pckbc_softc *sc = &isc->sc_pckbc;
	struct pckbc_internal *t;
	bus_addr_t cs;
	uint32_t csr;
	pckbc_slot_t slot;

	if (console) {
		iockbc_consdata.t_sc = sc;
		sc->id = t = &iockbc_consdata;
		isc->console = 1;
		if (&reginfo[PCKBC_KBD_SLOT] == iockbc_cons_slotdata.reginfo)
			isc->reginfo = reginfo;
		else
			isc->reginfo = reginfo_inverted;
	} else {
		/*
		 * Setup up controller: do not force pull clock and data lines
		 * low, clamp clocks after one byte received.
		 */
		cs = reginfo[PCKBC_KBD_SLOT].cs;
		csr = bus_space_read_4(isc->iot, isc->ioh, cs);
		csr &= ~(IOC3_KBC_CTRL_KBD_PULL_DATA_LOW |
		    IOC3_KBC_CTRL_KBD_PULL_CLOCK_LOW |
		    IOC3_KBC_CTRL_AUX_PULL_DATA_LOW |
		    IOC3_KBC_CTRL_AUX_PULL_CLOCK_LOW |
		    IOC3_KBC_CTRL_KBD_CLAMP_3 | IOC3_KBC_CTRL_AUX_CLAMP_3);
		csr |= IOC3_KBC_CTRL_KBD_CLAMP_1 | IOC3_KBC_CTRL_AUX_CLAMP_1;
		bus_space_write_4(isc->iot, isc->ioh, cs, csr);

		/* Setup pckbc_internal structure. */
		t = malloc(sizeof(struct pckbc_internal), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		t->t_iot = isc->iot;
		t->t_ioh_d = isc->ioh;
		t->t_ioh_c = isc->ioh;
		t->t_addr = addr;
		t->t_sc = sc;
		sc->id = t;

		timeout_set(&t->t_cleanup, iockbc_cleanup, t);
		timeout_set(&t->t_poll, iockbc_poll, t);

		isc->reginfo = reginfo;
	}

	for (slot = 0; slot < PCKBC_NSLOTS; slot++) {
		if (t->t_slotdata[slot] == NULL) {
			t->t_slotdata[slot] =
			    malloc(sizeof(struct pckbc_slotdata),
			      M_DEVBUF, M_WAITOK);
		}
	}

	if (!console) {
		enum iockbc_slottype slottype;
		int mouse_on_main = 0;

		/*
		 * Probe for a keyboard. If none is found at the regular
		 * keyboard port, but one is found at the mouse port, then
		 * it is likely that this particular system has both ports
		 * inverted (or incorrect labels on the chassis), unless
		 * this is a human error. In any case, try to get the
		 * keyboard to attach to the `keyboard' port and the
		 * pointing device to the `mouse' port.
		 */

		for (slot = 0; slot < PCKBC_NSLOTS; slot++) {
			iockbc_init_slotdata(t->t_slotdata[slot],
			    &isc->reginfo[slot]);
			slottype = iockbc_probe_slot(t, slot);
			if (slottype == KBD)
				break;
			if (slottype == MOUSE)
				mouse_on_main = slot == PCKBC_KBD_SLOT;
		}
		if (slot == PCKBC_NSLOTS) {
			/*
			 * We could not identify a keyboard. Let's assume
			 * none is connected; if a mouse has been found on
			 * the keyboard port and none on the aux port, the
			 * ports are likely to be inverted.
			 */
			if (mouse_on_main)
				slot = PCKBC_AUX_SLOT;
			else
				slot = PCKBC_KBD_SLOT;
		}
		if (slot == PCKBC_AUX_SLOT) {
			/*
			 * Either human error or inverted wiring; use
			 * the inverted port settings.
			 * iockbc_attach_slot() below will call
			 * iockbc_init_slotdata() again.
			 */
			isc->reginfo = reginfo_inverted;
		}
	}

	/*
	 * Attach "slots".
	 */
	iockbc_attach_slot(isc, PCKBC_KBD_SLOT);
	iockbc_attach_slot(isc, PCKBC_AUX_SLOT);
}

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

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

int
iockbcintr(void *vsc)
{
	struct iockbc_softc *isc = (struct iockbc_softc *)vsc;
	struct pckbc_softc *sc = &isc->sc_pckbc;
	struct pckbc_internal *t = sc->id;

	return iockbcintr_internal(t, sc);
}

int
iockbcintr_internal(struct pckbc_internal *t, struct pckbc_softc *sc)
{
	pckbc_slot_t slot;
	struct pckbc_slotdata *q;
	int served = 0;
	uint32_t data;
	uint32_t val;

	/* 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.
	 */
	for (slot = 0; slot < PCKBC_NSLOTS; slot++) {
		q = t->t_slotdata[slot];

		for (;;) {
			if (!q) {
				DPRINTF("iockbcintr: no slot%d data!\n", slot);
				break;
			}

			if (q->polling) {
				served = 1;
				break; /* pckbc_poll_data() will get it */
			}

			val = bus_space_read_4(t->t_iot, t->t_ioh_d,
			    q->reginfo->rx);
			if ((val & IOC3_KBC_DATA_VALID) == 0)
        	        	break;

			served = 1;

			/* Process received data. */
			if (val & IOC3_KBC_DATA_0_VALID) {
				data = (val & IOC3_KBC_DATA_0_MASK) >>
				    IOC3_KBC_DATA_0_SHIFT;
				iockbc_process_input(sc, t, slot, data);
			}

			if (val & IOC3_KBC_DATA_1_VALID) {
				data = (val & IOC3_KBC_DATA_1_MASK) >>
				    IOC3_KBC_DATA_1_SHIFT;
				iockbc_process_input(sc, t, slot, data);
			}

			if (val & IOC3_KBC_DATA_2_VALID) {
				data = (val & IOC3_KBC_DATA_2_MASK) >>
				    IOC3_KBC_DATA_2_SHIFT;
				iockbc_process_input(sc, t, slot, data);
			}
		}
	}

	return (served);
}

void
iockbc_process_input(struct pckbc_softc *sc, struct pckbc_internal *t,
    int slot, uint data)
{
	struct pckbc_slotdata *q;

	q = t->t_slotdata[slot];
	if (CMD_IN_QUEUE(q) && iockbc_cmdresponse(t, slot, data))
		return;

	if (sc->inputhandler[slot])
		(*sc->inputhandler[slot])(sc->inputarg[slot], data);
	else
		DPRINTF("iockbcintr: slot %d lost %d\n", slot, data);
}

int
iockbc_poll_write(struct pckbc_internal *t, pckbc_slot_t slot, int val)
{
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	bus_space_tag_t iot = t->t_iot;
	bus_space_handle_t ioh = t->t_ioh_d;
	u_int64_t stat;
	int timeout = 10000;

	/* Attempt to write a value to the controller. */
	while (timeout--) {
		stat = bus_space_read_4(iot, ioh, q->reginfo->cs);
		if ((stat & q->reginfo->busy) == 0) {
			bus_space_write_4(iot, ioh, q->reginfo->tx, val & 0xff);
			return 0;
		}
		delay(50);
	}

	DPRINTF("iockbc_poll_write: timeout, sts %08x\n", stat);
	return -1;
}

int
iockbc_poll_read(struct pckbc_internal *t, pckbc_slot_t slot)
{
	struct pckbc_slotdata *q = t->t_slotdata[slot];
	int timeout = 10000;
	u_int32_t val;

	/* See if we already have bytes queued. */
	if (q->rx_index >= 0)
		return q->rx_queue[q->rx_index--];

        /* Poll input from controller. */
        while (timeout--) {
		val = bus_space_read_4(t->t_iot, t->t_ioh_d, q->reginfo->rx);
		if (val & IOC3_KBC_DATA_VALID)
                	break;
                delay(50);
        }
	if ((val & IOC3_KBC_DATA_VALID) == 0) {
		DPRINTF("iockbc_poll_read: timeout, wx %08x\n", val);
		return -1;
	}

	/* Process received data. */
	if (val & IOC3_KBC_DATA_2_VALID)
		q->rx_queue[++q->rx_index] =
		    (val & IOC3_KBC_DATA_2_MASK) >> IOC3_KBC_DATA_2_SHIFT;

	if (val & IOC3_KBC_DATA_1_VALID)
		q->rx_queue[++q->rx_index] =
		    (val & IOC3_KBC_DATA_1_MASK) >> IOC3_KBC_DATA_1_SHIFT;

	if (val & IOC3_KBC_DATA_0_VALID)
		q->rx_queue[++q->rx_index] =
		    (val & IOC3_KBC_DATA_0_MASK) >> IOC3_KBC_DATA_0_SHIFT;

	if (q->rx_index >= 0)
		return q->rx_queue[q->rx_index--];
	else
		return -1;
}

/*
 * Pass command to device, poll for ACK and data.
 * to be called at spltty()
 */
static void
iockbc_poll_cmd(struct pckbc_internal *t, pckbc_slot_t slot,
    struct pckbc_devcmd *cmd)
{
	int i, c = 0;

	while (cmd->cmdidx < cmd->cmdlen) {
		if (iockbc_poll_write(t, slot, cmd->cmd[cmd->cmdidx]) == -1) {
			DPRINTF("iockbc_poll_cmd: send error\n");
			cmd->status = EIO;
			return;
		}
		for (i = 10; i; i--) { /* 1s ??? */
			c = iockbc_poll_read(t, slot);
			if (c != -1)
				break;
		}
		if (c == KBC_DEVCMD_ACK) {
			cmd->cmdidx++;
			continue;
		}
		if (c == KBC_DEVCMD_RESEND) {
			DPRINTF("iockbc_cmd: RESEND\n");
			if (cmd->retries++ < 5)
				continue;
			else {
				DPRINTF("iockbc: cmd failed\n");
				cmd->status = EIO;
				return;
			}
		}
		if (c == -1) {
			DPRINTF("iockbc_cmd: timeout\n");
			cmd->status = EIO;
			return;
		}
		DPRINTF("iockbc_cmd: lost 0x%x\n", c);
	}

	while (cmd->responseidx < cmd->responselen) {
		if (cmd->flags & KBC_CMDFLAG_SLOW)
			i = 100; /* 10s ??? */
		else
			i = 10; /* 1s ??? */
		while (i--) {
			c = iockbc_poll_read(t, slot);
			if (c != -1)
				break;
		}
		if (c == -1) {
			DPRINTF("iockbc_poll_cmd: no data\n");
			cmd->status = ETIMEDOUT;
			return;
		} else
			cmd->response[cmd->responseidx++] = c;
	}
}

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

	while ((cmd = TAILQ_FIRST(&q->cmdqueue))) {
		TAILQ_REMOVE(&q->cmdqueue, cmd, next);
#ifdef IOCKBC_DEBUG
		printf("iockbc_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
iockbc_cleanup(void *self)
{
	struct pckbc_internal *t = self;
	int s;

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

	s = spltty();

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

	while (iockbc_poll_read(t, PCKBC_KBD_SLOT)
	       != -1) ;
	while (iockbc_poll_read(t, PCKBC_AUX_SLOT)
	       != -1) ;

	/* Reset KBC? */

	splx(s);
}

/*
 * Pass command to device during normal operation.
 * to be called at spltty()
 */
void
iockbc_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 {
			iockbc_poll_cmd(t, slot, cmd);
			if (cmd->status)
				printf("iockbc_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 (iockbc_poll_write(t, slot, cmd->cmd[cmd->cmdidx])) {
		printf("iockbc_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
iockbc_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("iockbc_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 {
				DPRINTF("iockbc: cmd failed\n");
				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:
	iockbc_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();
	iockbc_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) iockbc_poll_read(t, slot);
}

/*
 * 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.
		 */
		iockbc_cleanqueue(q);
	}

	isactive = CMD_IN_QUEUE(q);
	TAILQ_INSERT_TAIL(&q->cmdqueue, nc, next);
	if (!isactive)
		iockbc_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);
			iockbc_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 = iockbc_poll_read(t, slot);
	if (c != -1 && q && CMD_IN_QUEUE(q)) {
		/* We jumped into a running command - try to deliver the
		   response. */
		if (iockbc_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;
	struct iockbc_softc *isc = (struct iockbc_softc *)sc;

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

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

	if ((isc == NULL || isc->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;

	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();
			iockbcintr(t->t_sc);
			splx(s);
		}
	}
}

/*
 * Console support.
 */

static int iockbc_console;

int
iockbc_cnattach()
{
	bus_space_tag_t iot = &sys_config.console_io;
	bus_space_handle_t ioh = (bus_space_handle_t)iot->bus_base;
	struct pckbc_internal *t = &iockbc_consdata;
	const struct iockbc_reginfo *reginfo = NULL, *reginfo_inverted;
	enum iockbc_slottype slottype;
	pckbc_slot_t slot;
	uint32_t csr;
	int is_ioc;
	int rc;

	is_ioc = console_input.specific ==
	    PCI_ID_CODE(PCI_VENDOR_SGI, PCI_PRODUCT_SGI_IOC3);
	if (is_ioc) {
#if NIOCKBC_IOC > 0
		reginfo = iockbc_ioc;
		reginfo_inverted = iockbc_ioc_inverted;
#endif
	} else {
#if NIOCKBC_IOF > 0
		reginfo = iockbc_iof;
		reginfo_inverted = iockbc_iof_inverted;
#endif
	}
	if (reginfo == NULL)
		return ENXIO;

	/*
	 * Setup up controller: do not force pull clock and data lines
	 * low, clamp clocks after one byte received.
	 */
	csr = bus_space_read_4(iot, ioh, reginfo->cs);
	csr &= ~(IOC3_KBC_CTRL_KBD_PULL_DATA_LOW |
	    IOC3_KBC_CTRL_KBD_PULL_CLOCK_LOW |
	    IOC3_KBC_CTRL_AUX_PULL_DATA_LOW |
	    IOC3_KBC_CTRL_AUX_PULL_CLOCK_LOW |
	    IOC3_KBC_CTRL_KBD_CLAMP_3 | IOC3_KBC_CTRL_AUX_CLAMP_3);
	csr |= IOC3_KBC_CTRL_KBD_CLAMP_1 | IOC3_KBC_CTRL_AUX_CLAMP_1;
	bus_space_write_4(iot, ioh, reginfo->cs, csr);

	/* Setup pckbc_internal structure. */
	t->t_iot = iot;
	t->t_ioh_d = (bus_space_handle_t)iot->bus_base;
	t->t_addr = 0;	/* unused */

	timeout_set(&t->t_cleanup, iockbc_cleanup, t);
	timeout_set(&t->t_poll, iockbc_poll, t);

	/*
	 * Probe for a keyboard. There must be one connected, for the PROM
	 * would not have advertized glass console if none had been
	 * detected.
	 */

	for (slot = 0; slot < PCKBC_NSLOTS; slot++) {
		iockbc_init_slotdata(&iockbc_cons_slotdata, &reginfo[slot]);
		t->t_slotdata[slot] = &iockbc_cons_slotdata;
		slottype = iockbc_probe_slot(t, slot);
		t->t_slotdata[slot] = NULL;
		if (slottype == KBD)
			break;
	}
	if (slot == PCKBC_NSLOTS) {
		/*
		 * We could not identify a keyboard, but the PROM did;
		 * let's assume it's a fluke and assume it exists and
		 * is connected to the first connector.
		 */
		slot = PCKBC_KBD_SLOT;
		/*
		 * For some reason keyboard and mouse ports are inverted on
		 * Fuel. They also are inverted on some IO9 boards, but
		 * we can't tell both IO9 flavour apart, yet.
		 */
		if (is_ioc && sys_config.system_type == SGI_IP35)
			slot = PCKBC_AUX_SLOT;
	}

	if (slot == PCKBC_AUX_SLOT) {
		/*
		 * Either human error when plugging the keyboard, or the
		 * physical connectors on the chassis are inverted.
		 * Compensate by switching in software (pckbd relies upon
		 * being at PCKBC_KBD_SLOT).
		 */
		reginfo = reginfo_inverted;
	}

	iockbc_init_slotdata(&iockbc_cons_slotdata, &reginfo[PCKBC_KBD_SLOT]);
	t->t_slotdata[PCKBC_KBD_SLOT] = &iockbc_cons_slotdata;

	rc = pckbd_cnattach(t);
	if (rc == 0)
		iockbc_console = 1;

	return rc;
}

#if NIOCKBC_IOC > 0
int
iockbc_is_ioc_console(struct ioc_attach_args *iaa)
{
	if (iockbc_console == 0)
		return 0;

	return location_match(&iaa->iaa_location, &console_input);
}
#endif

#if NIOCKBC_IOF > 0
int
iockbc_is_iof_console(struct iof_attach_args *iaa)
{
	if (iockbc_console == 0)
		return 0;

	return location_match(&iaa->iaa_location, &console_input);
}
#endif


code:
/*	$OpenBSD: iockbcreg.h,v 1.1 2009/11/18 19:03:27 miod Exp $	*/

/*
 * Copyright (c) 2008 Joel Sing.
 *
 * Permission to use, copy, modify, and 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.
 */

/*
 * Register definitions for the PS/2 controller part of SGI IOC3 and IOC4 ASICS.
 */

/* bits in KBC_CTRL_STATUS */
#define	IOC3_KBC_STATUS_KBD_WRITE_PENDING	0x00000001
#define	IOC3_KBC_STATUS_AUX_WRITE_PENDING	0x00000002
#define	IOC3_KBC_STATUS_KBD_DATA		0x00000010
#define	IOC3_KBC_STATUS_KBD_CLOCK		0x00000020
#define	IOC3_KBC_CTRL_KBD_PULL_DATA_LOW		0x00000040
#define	IOC3_KBC_CTRL_KBD_PULL_CLOCK_LOW	0x00000080
#define	IOC3_KBC_STATUS_AUX_DATA		0x00000100
#define	IOC3_KBC_STATUS_AUX_CLOCK		0x00000200
#define	IOC3_KBC_CTRL_AUX_PULL_DATA_LOW		0x00000400
#define	IOC3_KBC_CTRL_AUX_PULL_CLOCK_LOW	0x00000800
#define	IOC3_KBC_CTRL_KBD_CLAMP_1		0x00100000
#define	IOC3_KBC_CTRL_AUX_CLAMP_1		0x00200000
#define	IOC3_KBC_CTRL_KBD_CLAMP_3		0x00400000
#define	IOC3_KBC_CTRL_AUX_CLAMP_3		0x00800000

/* bits in KBC_*_RX */
#define	IOC3_KBC_DATA_0_VALID		0x80000000
#define	IOC3_KBC_DATA_1_VALID		0x40000000
#define	IOC3_KBC_DATA_2_VALID		0x20000000
#define	IOC3_KBC_DATA_VALID		(IOC3_KBC_DATA_0_VALID | \
					 IOC3_KBC_DATA_1_VALID | \
					 IOC3_KBC_DATA_2_VALID)
#define	IOC3_KBC_DATA_0_MASK		0x00ff0000
#define	IOC3_KBC_DATA_0_SHIFT			16
#define	IOC3_KBC_DATA_1_MASK		0x0000ff00
#define	IOC3_KBC_DATA_1_SHIFT			8
#define	IOC3_KBC_DATA_2_MASK		0x000000ff
#define	IOC3_KBC_DATA_2_SHIFT			0

license: unknown


code:
/*	$OpenBSD: iockbcvar.h,v 1.4 2010/12/04 11:23:43 jsing Exp $	*/

/*
 * Copyright (c) 2010 Miodrag Vallat.
 *
 * Permission to use, copy, modify, and 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.
 */

int	iockbc_cnattach(void);


code:
/*	$OpenBSD: mavb.c,v 1.18 2015/05/11 06:46:21 ratchov Exp $	*/

/*
 * Copyright (c) 2005 Mark Kettenis
 *
 * Permission to use, copy, modify, and 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/timeout.h>

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

#include <sys/audioio.h>
#include <dev/audio_if.h>

#include <sgi/localbus/macebus.h>
#include <sgi/localbus/macebusvar.h>
#include <sgi/dev/mavbreg.h>

#include <dev/ic/ad1843reg.h>

#undef MAVB_DEBUG

#ifdef MAVB_DEBUG
#define DPRINTF(l,x)	do { if (mavb_debug & (l)) printf x; } while (0)
#define MAVB_DEBUG_INTR		0x0100
int mavb_debug = ~MAVB_DEBUG_INTR;
#else
#define DPRINTF(l,x)	/* nothing */
#endif

/* Repeat delays for volume buttons.  */
#define MAVB_VOLUME_BUTTON_REPEAT_DEL1	400	/* 400ms to start repeating */
#define MAVB_VOLUME_BUTTON_REPEAT_DELN  100	/* 100ms between repeats */

/* XXX We need access to some of the MACE ISA registers.  */
#define MAVB_ISA_NREGS				0x20

#define MAVB_ISA_RING_SIZE	0x4000 /* Mace ISA DMA ring size. */
#define MAVB_CHAN_RING_SIZE	0x1000 /* DMA buffer size per channel. */
#define MAVB_CHAN_INTR_SIZE	0x0800 /* Interrupt on 50% buffer transfer. */
#define MAVB_CHAN_CHUNK_SIZE	0x0400 /* Move data in 25% buffer chunks. */

/*
 * AD1843 Mixer.
 */

enum {
	AD1843_RECORD_CLASS,
	AD1843_ADC_SOURCE,	/* ADC Source Select */
	AD1843_ADC_GAIN,	/* ADC Input Gain */
	AD1843_ADC_MIC_GAIN,	/* ADC Mic Input Gain */

	AD1843_INPUT_CLASS,
	AD1843_DAC1_GAIN,	/* DAC1 Analog/Digital Gain/Attenuation */
	AD1843_DAC1_MUTE,	/* DAC1 Analog Mute */
	AD1843_DAC2_GAIN,	/* DAC2 Mix Gain */
	AD1843_AUX1_GAIN,	/* Auxilliary 1 Mix Gain */
	AD1843_AUX2_GAIN,	/* Auxilliary 2 Mix Gain */
	AD1843_AUX3_GAIN,	/* Auxilliary 3 Mix Gain */
	AD1843_MIC_GAIN,	/* Microphone Mix Gain */
	AD1843_MONO_GAIN,	/* Mono Mix Gain */
	AD1843_DAC2_MUTE,	/* DAC2 Mix Mute */
	AD1843_AUX1_MUTE,	/* Auxilliary 1 Mix Mute */
	AD1843_AUX2_MUTE,	/* Auxilliary 2 Mix Mute */
	AD1843_AUX3_MUTE,	/* Auxilliary 3 Mix Mute */
	AD1843_MIC_MUTE,	/* Microphone Mix Mute */
	AD1843_MONO_MUTE,	/* Mono Mix Mute */
	AD1843_SUM_MUTE,	/* Sum Mute */

	AD1843_OUTPUT_CLASS,
	AD1843_MNO_MUTE,	/* Mono Output Mute */
	AD1843_HPO_MUTE		/* Headphone Output Mute */
};

/* ADC Source Select.  The order matches the hardware bits.  */
const char *ad1843_source[] = {
	AudioNline,
	AudioNmicrophone,
	AudioNaux "1",
	AudioNaux "2",
	AudioNaux "3",
	AudioNmono,
	AudioNdac "1",
	AudioNdac "2"
};

/* Mix Control.  The order matches the hardware register numbering.  */
const char *ad1843_input[] = {
	AudioNdac "2",		/* AD1843_DAC2__TO_MIXER */
	AudioNaux "1",
	AudioNaux "2",
	AudioNaux "3",
	AudioNmicrophone,
	AudioNmono		/* AD1843_MISC_SETTINGS */
};

struct mavb_chan {
	caddr_t hw_start;
	caddr_t sw_start;
	caddr_t sw_end;
	caddr_t sw_cur;
	void (*intr)(void *);
	void *intrarg;
	u_long rate;
	u_int format;
	int blksize;
};

struct mavb_softc {
	struct device sc_dev;
	bus_space_tag_t sc_st;
	bus_space_handle_t sc_sh;
	bus_dma_tag_t sc_dmat;
	bus_dmamap_t sc_dmamap;

	/* XXX We need access to some of the MACE ISA registers.  */
	bus_space_handle_t sc_isash;

	caddr_t sc_ring;

	struct mavb_chan play;
	struct mavb_chan rec;

	struct timeout sc_volume_button_to;
};

typedef u_long ad1843_addr_t;

u_int16_t ad1843_reg_read(struct mavb_softc *, ad1843_addr_t);
u_int16_t ad1843_reg_write(struct mavb_softc *, ad1843_addr_t, u_int16_t);
void ad1843_dump_regs(struct mavb_softc *);

int mavb_match(struct device *, void *, void *);
void mavb_attach(struct device *, struct device *, void *);

struct cfattach mavb_ca = {
	sizeof(struct mavb_softc), mavb_match, mavb_attach
};

struct cfdriver mavb_cd = {
	NULL, "mavb", DV_DULL
};

int mavb_open(void *, int);
void mavb_close(void *);
int mavb_query_encoding(void *, struct audio_encoding *);
int mavb_set_params(void *, int, int, struct audio_params *,
		    struct audio_params *);
int mavb_round_blocksize(void *hdl, int bs);
int mavb_halt_output(void *);
int mavb_halt_input(void *);
int mavb_getdev(void *, struct audio_device *);
int mavb_set_port(void *, struct mixer_ctrl *);
int mavb_get_port(void *, struct mixer_ctrl *);
int mavb_query_devinfo(void *, struct mixer_devinfo *);
int mavb_get_props(void *);
int mavb_trigger_output(void *, void *, void *, int, void (*)(void *),
			void *, struct audio_params *);
int mavb_trigger_input(void *, void *, void *, int, void (*)(void *),
		       void *, struct audio_params *);
void mavb_get_default_params(void *, int, struct audio_params *);

struct audio_hw_if mavb_sa_hw_if = {
	mavb_open,
	mavb_close,
	0,
	mavb_query_encoding,
	mavb_set_params,
	mavb_round_blocksize,
	0,
	0,
	0,
	0,
	0,
	mavb_halt_output,
	mavb_halt_input,
	0,
	mavb_getdev,
	0,
	mavb_set_port,
	mavb_get_port,
	mavb_query_devinfo,
	0,
	0,
	0,
	0,
	mavb_get_props,
	mavb_trigger_output,
	mavb_trigger_input,
	mavb_get_default_params
};

struct audio_device mavb_device = {
	"A3",
	"",
	"mavb"
};

int
mavb_open(void *hdl, int flags)
{
	return (0);
}

void
mavb_close(void *hdl)
{
}

int
mavb_query_encoding(void *hdl, struct audio_encoding *ae)
{
	switch (ae->index) {
	case 0:
		/* 24-bit Signed Linear PCM LSB-aligned.  */
		strlcpy(ae->name, AudioEslinear_be, sizeof ae->name);
		ae->encoding = AUDIO_ENCODING_SLINEAR_BE;
		ae->precision = 24;
		ae->flags = 0;
		break;
	default:
		return (EINVAL);
	}
	ae->bps = AUDIO_BPS(ae->precision);
	ae->msb = 0;
	return (0);
}

void
mavb_get_default_params(void *hdl, int mode, struct audio_params *p)
{
	p->sample_rate = 48000;
	p->encoding = AUDIO_ENCODING_SLINEAR_BE;
	p->precision = 24;
	p->bps = 4;
	p->msb = 0;
	p->channels = 2;
}

static int
mavb_set_play_rate(struct mavb_softc *sc, u_long sample_rate)
{
	if (sample_rate < 4000 || sample_rate > 48000)
		return (EINVAL);

	if (sc->play.rate != sample_rate) {
		ad1843_reg_write(sc, AD1843_CLOCK2_SAMPLE_RATE, sample_rate);
		sc->play.rate = sample_rate;
	}
	return (0);
}

static int
mavb_set_rec_rate(struct mavb_softc *sc, u_long sample_rate)
{
	if (sample_rate < 4000 || sample_rate > 48000)
		return (EINVAL);

	if (sc->rec.rate != sample_rate) {
		ad1843_reg_write(sc, AD1843_CLOCK1_SAMPLE_RATE, sample_rate);
		sc->rec.rate = sample_rate;
	}
	return (0);
}

static int
mavb_get_format(u_int encoding, u_int *format)
{
	switch(encoding) {
	case AUDIO_ENCODING_ULINEAR_BE:
		*format = AD1843_PCM8;
		break;
	case AUDIO_ENCODING_SLINEAR_BE:
		*format = AD1843_PCM16;
		break;
	case AUDIO_ENCODING_ULAW:
		*format = AD1843_ULAW;
		break;
	case AUDIO_ENCODING_ALAW:
		*format = AD1843_ALAW;
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

static int
mavb_set_play_format(struct mavb_softc *sc, u_int encoding)
{
	u_int16_t value;
	u_int format;
	int err;

	err = mavb_get_format(encoding, &format);
	if (err)
		return (err);

	if (sc->play.format != format) {
		value = ad1843_reg_read(sc, AD1843_SERIAL_INTERFACE);
		value &= ~AD1843_DA1F_MASK;
		value |= (format << AD1843_DA1F_SHIFT);
		ad1843_reg_write(sc, AD1843_SERIAL_INTERFACE, value);
		sc->play.format = format;
	}
	return (0);
}

static int
mavb_set_rec_format(struct mavb_softc *sc, u_int encoding)
{
	u_int16_t value;
	u_int format;
	int err;

	err = mavb_get_format(encoding, &format);
	if (err)
		return (err);

	if (sc->rec.format != format) {
		value = ad1843_reg_read(sc, AD1843_SERIAL_INTERFACE);
		value &= ~(AD1843_ADRF_MASK | AD1843_ADLF_MASK);
		value |= (format << AD1843_ADRF_SHIFT) |
		    (format << AD1843_ADLF_SHIFT);
		ad1843_reg_write(sc, AD1843_SERIAL_INTERFACE, value);
		sc->rec.format = format;
	}
	return (0);
}

int
mavb_set_params(void *hdl, int setmode, int usemode,
    struct audio_params *play, struct audio_params *rec)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;
	int error;

	DPRINTF(1, ("%s: mavb_set_params: sample=%ld precision=%d "
	    "channels=%d\n", sc->sc_dev.dv_xname, play->sample_rate,
	    play->precision, play->channels));

	if (setmode & AUMODE_PLAY) {
		play->encoding = AUDIO_ENCODING_SLINEAR_BE;
		play->channels = 2;
		play->precision = 24;
		play->bps = AUDIO_BPS(play->precision);
		play->msb = 0;
		error = mavb_set_play_rate(sc, play->sample_rate);
		if (error)
			return (error);

		error = mavb_set_play_format(sc, play->encoding);
		if (error)
			return (error);

	}

	if (setmode & AUMODE_RECORD) {
		rec->encoding = AUDIO_ENCODING_SLINEAR_BE;
		rec->channels = 2;
		rec->precision = 24;
		rec->bps = AUDIO_BPS(rec->precision);
		rec->msb = 0;

		error = mavb_set_rec_rate(sc, rec->sample_rate);
		if (error)
			return (error);

		error = mavb_set_rec_format(sc, rec->encoding);
		if (error)
			return (error);
	}

	return (0);
}

int
mavb_round_blocksize(void *hdl, int bs)
{
	if (bs == 0)
		bs = MAVB_CHAN_INTR_SIZE;
	else
		bs = (bs + MAVB_CHAN_INTR_SIZE - 1) &
		    ~(MAVB_CHAN_INTR_SIZE - 1);
	return (bs);
}

int
mavb_halt_output(void *hdl)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;

	DPRINTF(1, ("%s: mavb_halt_output called\n", sc->sc_dev.dv_xname));
	mtx_enter(&audio_lock);
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL2_CONTROL, 0);
	mtx_leave(&audio_lock);
	return (0);
}

int
mavb_halt_input(void *hdl)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;

	DPRINTF(1, ("%s: mavb_halt_input called\n", sc->sc_dev.dv_xname));
	mtx_enter(&audio_lock);
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL1_CONTROL, 0);
	mtx_leave(&audio_lock);
	return (0);
}

int
mavb_getdev(void *hdl, struct audio_device *ret)
{
	*ret = mavb_device;
	return (0);
}

int
mavb_set_port(void *hdl, struct mixer_ctrl *mc)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;
	u_char left, right;
	ad1843_addr_t reg;
	u_int16_t value;

	DPRINTF(1, ("%s: mavb_set_port: dev=%d\n", sc->sc_dev.dv_xname,
	    mc->dev));

	switch (mc->dev) {
	case AD1843_ADC_SOURCE:
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		value &= ~(AD1843_LSS_MASK | AD1843_RSS_MASK);
		value |= ((mc->un.ord << AD1843_LSS_SHIFT) & AD1843_LSS_MASK);
		value |= ((mc->un.ord << AD1843_RSS_SHIFT) & AD1843_RSS_MASK);
		ad1843_reg_write(sc, AD1843_ADC_SOURCE_GAIN, value);
		break;
	case AD1843_ADC_GAIN:
		left = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
		right = mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		value &= ~(AD1843_LIG_MASK | AD1843_RIG_MASK);
		value |= ((left >> 4) << AD1843_LIG_SHIFT);
		value |= ((right >> 4) << AD1843_RIG_SHIFT);
		ad1843_reg_write(sc, AD1843_ADC_SOURCE_GAIN, value);
		break;
	case AD1843_ADC_MIC_GAIN:
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		if (mc->un.ord == 0)
			value &= ~(AD1843_LMGE | AD1843_RMGE);
		else
			value |= (AD1843_LMGE | AD1843_RMGE);
		ad1843_reg_write(sc, AD1843_ADC_SOURCE_GAIN, value);
		break;

	case AD1843_DAC1_GAIN:
		left = AUDIO_MAX_GAIN -
		    mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
		right = AUDIO_MAX_GAIN -
                    mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
		value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
		value &= ~(AD1843_LDA1G_MASK | AD1843_RDA1G_MASK);
		value |= ((left >> 2) << AD1843_LDA1G_SHIFT);
		value |= ((right >> 2) << AD1843_RDA1G_SHIFT);
		ad1843_reg_write(sc, AD1843_DAC1_ANALOG_GAIN, value);
		break;
	case AD1843_DAC1_MUTE:
		value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
		if (mc->un.ord == 0)
			value &= ~(AD1843_LDA1GM | AD1843_RDA1GM);
		else
			value |= (AD1843_LDA1GM | AD1843_RDA1GM);
		ad1843_reg_write(sc, AD1843_DAC1_ANALOG_GAIN, value);
		break;

	case AD1843_DAC2_GAIN:
	case AD1843_AUX1_GAIN:
	case AD1843_AUX2_GAIN:
	case AD1843_AUX3_GAIN:
	case AD1843_MIC_GAIN:
		left = AUDIO_MAX_GAIN -
		    mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
		right = AUDIO_MAX_GAIN -
                    mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
		reg = AD1843_DAC2_TO_MIXER + mc->dev - AD1843_DAC2_GAIN;
		value = ad1843_reg_read(sc, reg);
		value &= ~(AD1843_LD2M_MASK | AD1843_RD2M_MASK);
		value |= ((left >> 3) << AD1843_LD2M_SHIFT);
		value |= ((right >> 3) << AD1843_RD2M_SHIFT);
		ad1843_reg_write(sc, reg, value);
		break;
	case AD1843_MONO_GAIN:
		left = AUDIO_MAX_GAIN -
		    mc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		value &= ~AD1843_MNM_MASK;
		value |= ((left >> 3) << AD1843_MNM_SHIFT);
		ad1843_reg_write(sc, AD1843_MISC_SETTINGS, value);
		break;
	case AD1843_DAC2_MUTE:
	case AD1843_AUX1_MUTE:
	case AD1843_AUX2_MUTE:
	case AD1843_AUX3_MUTE:
	case AD1843_MIC_MUTE:
	case AD1843_MONO_MUTE:	/* matches left channel */
		reg = AD1843_DAC2_TO_MIXER + mc->dev - AD1843_DAC2_MUTE;
		value = ad1843_reg_read(sc, reg);
		if (mc->un.ord == 0)
			value &= ~(AD1843_LD2MM | AD1843_RD2MM);
		else
			value |= (AD1843_LD2MM | AD1843_RD2MM);
		ad1843_reg_write(sc, reg, value);
		break;

	case AD1843_SUM_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		if (mc->un.ord == 0)
			value &= ~AD1843_SUMM;
		else
			value |= AD1843_SUMM;
		ad1843_reg_write(sc, AD1843_MISC_SETTINGS, value);
		break;

	case AD1843_MNO_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		if (mc->un.ord == 0)
			value &= ~AD1843_MNOM;
		else
			value |= AD1843_MNOM;
		ad1843_reg_write(sc, AD1843_MISC_SETTINGS, value);
		break;

	case AD1843_HPO_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		if (mc->un.ord == 0)
			value &= ~AD1843_HPOM;
		else
			value |= AD1843_HPOM;
		ad1843_reg_write(sc, AD1843_MISC_SETTINGS, value);
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		break;

	default:
		return (EINVAL);
	}

	return (0);
}

int
mavb_get_port(void *hdl, struct mixer_ctrl *mc)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;
	u_char left, right;
	ad1843_addr_t reg;
	u_int16_t value;

	DPRINTF(1, ("%s: mavb_get_port: dev=%d\n", sc->sc_dev.dv_xname,
	    mc->dev));

	switch (mc->dev) {
	case AD1843_ADC_SOURCE:
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		mc->un.ord = (value & AD1843_LSS_MASK) >> AD1843_LSS_SHIFT;
		break;
	case AD1843_ADC_GAIN:
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		left = (value & AD1843_LIG_MASK) >> AD1843_LIG_SHIFT;
		right = (value & AD1843_RIG_MASK) >> AD1843_RIG_SHIFT;
		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
		    (left << 4) | left;
		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
		    (right << 4) | right;
		break;
	case AD1843_ADC_MIC_GAIN:
		value = ad1843_reg_read(sc, AD1843_ADC_SOURCE_GAIN);
		mc->un.ord = (value & AD1843_LMGE) ? 1 : 0;
		break;

	case AD1843_DAC1_GAIN:
		value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
		left = (value & AD1843_LDA1G_MASK) >> AD1843_LDA1G_SHIFT;
		right = (value & AD1843_RDA1G_MASK) >> AD1843_RDA1G_SHIFT;
		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
		    AUDIO_MAX_GAIN - (left << 2);
		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
		    AUDIO_MAX_GAIN - (right << 2);
		break;
	case AD1843_DAC1_MUTE:
		value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
		mc->un.ord = (value & AD1843_LDA1GM) ? 1 : 0;
		break;

	case AD1843_DAC2_GAIN:
	case AD1843_AUX1_GAIN:
	case AD1843_AUX2_GAIN:
	case AD1843_AUX3_GAIN:
	case AD1843_MIC_GAIN:
		reg = AD1843_DAC2_TO_MIXER + mc->dev - AD1843_DAC2_GAIN;
		value = ad1843_reg_read(sc, reg);
		left = (value & AD1843_LD2M_MASK) >> AD1843_LD2M_SHIFT;
		right = (value & AD1843_RD2M_MASK) >> AD1843_RD2M_SHIFT;
		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
		    AUDIO_MAX_GAIN - (left << 3);
		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
		    AUDIO_MAX_GAIN - (right << 3);
		break;
	case AD1843_MONO_GAIN:
		if (mc->un.value.num_channels != 1)
			return (EINVAL);

		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		left = (value & AD1843_MNM_MASK) >> AD1843_MNM_SHIFT;
		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
		    AUDIO_MAX_GAIN - (left << 3);
		break;
	case AD1843_DAC2_MUTE:
	case AD1843_AUX1_MUTE:
	case AD1843_AUX2_MUTE:
	case AD1843_AUX3_MUTE:
	case AD1843_MIC_MUTE:
	case AD1843_MONO_MUTE:	/* matches left channel */
		reg = AD1843_DAC2_TO_MIXER + mc->dev - AD1843_DAC2_MUTE;
		value = ad1843_reg_read(sc, reg);
		mc->un.ord = (value & AD1843_LD2MM) ? 1 : 0;
		break;

	case AD1843_SUM_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		mc->un.ord = (value & AD1843_SUMM) ? 1 : 0;
		break;

	case AD1843_MNO_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		mc->un.ord = (value & AD1843_MNOM) ? 1 : 0;
		break;

	case AD1843_HPO_MUTE:
		value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
		mc->un.ord = (value & AD1843_HPOM) ? 1 : 0;
		break;

	default:
		return (EINVAL);
	}

	return (0);
}

int
mavb_query_devinfo(void *hdl, struct mixer_devinfo *di)
{
	int i;

	di->prev = di->next = AUDIO_MIXER_LAST;

	switch (di->index) {
	case AD1843_RECORD_CLASS:
		di->type = AUDIO_MIXER_CLASS;
		di->mixer_class = AD1843_RECORD_CLASS;
		strlcpy(di->label.name, AudioCrecord, sizeof di->label.name);
		break;

	case AD1843_ADC_SOURCE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_RECORD_CLASS;
		di->next = AD1843_ADC_GAIN;
		strlcpy(di->label.name, AudioNsource, sizeof di->label.name);
		di->un.e.num_mem =
			sizeof ad1843_source / sizeof ad1843_source[1];
		for (i = 0; i < di->un.e.num_mem; i++) {
			strlcpy(di->un.e.member[i].label.name,
                            ad1843_source[i],
			    sizeof di->un.e.member[0].label.name);
			di->un.e.member[i].ord = i;
		}
		break;
	case AD1843_ADC_GAIN:
		di->type = AUDIO_MIXER_VALUE;
		di->mixer_class = AD1843_RECORD_CLASS;
		di->prev = AD1843_ADC_SOURCE;
		strlcpy(di->label.name, AudioNvolume, sizeof di->label.name);
		di->un.v.num_channels = 2;
		strlcpy(di->un.v.units.name, AudioNvolume,
		    sizeof di->un.v.units.name);
		break;
	case AD1843_ADC_MIC_GAIN:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_RECORD_CLASS;
		strlcpy(di->label.name, AudioNmicrophone "." AudioNpreamp,
		    sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	case AD1843_INPUT_CLASS:
		di->type = AUDIO_MIXER_CLASS;
		di->mixer_class = AD1843_INPUT_CLASS;
		strlcpy(di->label.name, AudioCinputs, sizeof di->label.name);
		break;

	case AD1843_DAC1_GAIN:
		di->type = AUDIO_MIXER_VALUE;
		di->mixer_class = AD1843_INPUT_CLASS;
		di->next = AD1843_DAC1_MUTE;
		strlcpy(di->label.name, AudioNdac "1", sizeof di->label.name);
		di->un.v.num_channels = 2;
		strlcpy(di->un.v.units.name, AudioNvolume,
		    sizeof di->un.v.units.name);
		break;
	case AD1843_DAC1_MUTE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_INPUT_CLASS;
		di->prev = AD1843_DAC1_GAIN;
		strlcpy(di->label.name, AudioNmute, sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	case AD1843_DAC2_GAIN:
	case AD1843_AUX1_GAIN:
	case AD1843_AUX2_GAIN:
	case AD1843_AUX3_GAIN:
	case AD1843_MIC_GAIN:
	case AD1843_MONO_GAIN:
		di->type = AUDIO_MIXER_VALUE;
		di->mixer_class = AD1843_INPUT_CLASS;
		di->next = di->index + AD1843_DAC2_MUTE - AD1843_DAC2_GAIN;
		strlcpy(di->label.name,
                    ad1843_input[di->index - AD1843_DAC2_GAIN],
		    sizeof di->label.name);
		if (di->index == AD1843_MONO_GAIN)
			di->un.v.num_channels = 1;
		else
			di->un.v.num_channels = 2;
		strlcpy(di->un.v.units.name, AudioNvolume,
		    sizeof di->un.v.units.name);
		break;
	case AD1843_DAC2_MUTE:
	case AD1843_AUX1_MUTE:
	case AD1843_AUX2_MUTE:
	case AD1843_AUX3_MUTE:
	case AD1843_MIC_MUTE:
	case AD1843_MONO_MUTE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_INPUT_CLASS;
		di->prev = di->index + AD1843_DAC2_GAIN - AD1843_DAC2_MUTE;
		strlcpy(di->label.name, AudioNmute, sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	case AD1843_SUM_MUTE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_INPUT_CLASS;
		strlcpy(di->label.name, "sum." AudioNmute,
		    sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	case AD1843_OUTPUT_CLASS:
		di->type = AUDIO_MIXER_CLASS;
		di->mixer_class = AD1843_OUTPUT_CLASS;
		strlcpy(di->label.name, AudioCoutputs, sizeof di->label.name);
		break;

	case AD1843_MNO_MUTE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_OUTPUT_CLASS;
		strlcpy(di->label.name, AudioNmono "." AudioNmute,
		    sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	case AD1843_HPO_MUTE:
		di->type = AUDIO_MIXER_ENUM;
		di->mixer_class = AD1843_OUTPUT_CLASS;
		strlcpy(di->label.name, AudioNheadphone "." AudioNmute,
		    sizeof di->label.name);
		di->un.e.num_mem = 2;
		strlcpy(di->un.e.member[0].label.name, AudioNoff,
		    sizeof di->un.e.member[0].label.name);
		di->un.e.member[0].ord = 0;
		strlcpy(di->un.e.member[1].label.name, AudioNon,
		    sizeof di->un.e.member[1].label.name);
		di->un.e.member[1].ord = 1;
		break;

	default:
		return (EINVAL);
	}

	return (0);
}

int
mavb_get_props(void *hdl)
{
	return (AUDIO_PROP_FULLDUPLEX | AUDIO_PROP_INDEPENDENT);
}

static void
mavb_dma_output(struct mavb_softc *sc)
{
	bus_space_tag_t st = sc->sc_st;
	bus_space_handle_t sh = sc->sc_sh;
	u_int64_t write_ptr;
	caddr_t src, dst, end;
	int count;

	write_ptr = bus_space_read_8(st, sh, MAVB_CHANNEL2_WRITE_PTR);

	end = sc->play.hw_start + MAVB_CHAN_RING_SIZE;
	dst = sc->play.hw_start + write_ptr;
	src = sc->play.sw_cur;

	if (write_ptr % MAVB_CHAN_CHUNK_SIZE) {
		printf("%s: write_ptr=%lld\n", sc->sc_dev.dv_xname, write_ptr);
		return;
	}
	if ((src - sc->play.sw_start) % MAVB_CHAN_CHUNK_SIZE) {
		printf("%s: src=%ld\n", sc->sc_dev.dv_xname,
		    src - sc->play.sw_start);
		return;
	}

	count = MAVB_CHAN_INTR_SIZE / MAVB_CHAN_CHUNK_SIZE;
	while (--count >= 0) {
		memcpy(dst, src, MAVB_CHAN_CHUNK_SIZE);
		dst += MAVB_CHAN_CHUNK_SIZE;
		src += MAVB_CHAN_CHUNK_SIZE;
		if (dst >= end)
			dst = sc->play.hw_start;
		if (src >= sc->play.sw_end)
			src = sc->play.sw_start;
		if (!((src - sc->play.sw_start) % sc->play.blksize)) {
			if (sc->play.intr)
				sc->play.intr(sc->play.intrarg);
		}
	}
	write_ptr = dst - sc->play.hw_start;
	bus_space_write_8(st, sh, MAVB_CHANNEL2_WRITE_PTR, write_ptr);
	sc->play.sw_cur = src;
}

static void
mavb_dma_input(struct mavb_softc *sc)
{
	bus_space_tag_t st = sc->sc_st;
	bus_space_handle_t sh = sc->sc_sh;
	u_int64_t read_ptr;
	caddr_t src, dst, end;
	int count;

	read_ptr = bus_space_read_8(st, sh, MAVB_CHANNEL1_READ_PTR);

	end = sc->rec.hw_start + MAVB_CHAN_RING_SIZE;
	src = sc->rec.hw_start + read_ptr;
	dst = sc->rec.sw_cur;

	if (read_ptr % MAVB_CHAN_CHUNK_SIZE) {
		printf("%s: read_ptr=%lld\n", sc->sc_dev.dv_xname, read_ptr);
		return;
	}
	if ((dst - sc->rec.sw_start) % MAVB_CHAN_CHUNK_SIZE) {
		printf("%s: dst=%ld\n", sc->sc_dev.dv_xname,
		    dst - sc->rec.sw_start);
		return;
	}

	count = MAVB_CHAN_INTR_SIZE / MAVB_CHAN_CHUNK_SIZE;
	while (--count >= 0) {
		memcpy(dst, src, MAVB_CHAN_CHUNK_SIZE);
		dst += MAVB_CHAN_CHUNK_SIZE;
		src += MAVB_CHAN_CHUNK_SIZE;
		if (src >= end)
			src = sc->rec.hw_start;
		if (dst >= sc->rec.sw_end)
			dst = sc->rec.sw_start;
		if (!((dst - sc->rec.sw_start) % sc->rec.blksize)) {
			if (sc->rec.intr)
				sc->rec.intr(sc->rec.intrarg);
		}
	}
	read_ptr = src - sc->rec.hw_start;
	bus_space_write_8(st, sh, MAVB_CHANNEL1_READ_PTR, read_ptr);
	sc->rec.sw_cur = dst;
}

int
mavb_trigger_output(void *hdl, void *start, void *end, int blksize,
    void (*intr)(void *), void *intrarg, struct audio_params *param)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;

	DPRINTF(1, ("%s: mavb_trigger_output: start=%p end=%p "
	    "blksize=%d intr=%p(%p)\n", sc->sc_dev.dv_xname,
	    start, end, blksize, intr, intrarg));

	mtx_enter(&audio_lock);
	sc->play.blksize = blksize;
	sc->play.intr = intr;
	sc->play.intrarg = intrarg;

	sc->play.sw_start = sc->play.sw_cur = start;
	sc->play.sw_end = end;

	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL2_CONTROL,
	    MAVB_CHANNEL_RESET);
	delay(1000);
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL2_CONTROL, 0);

	/* Fill first 25% of buffer with silence. */
	bzero(sc->play.hw_start, MAVB_CHAN_CHUNK_SIZE);
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL2_WRITE_PTR,
	    MAVB_CHAN_CHUNK_SIZE);

	/* Fill next 50% of buffer with audio data. */
	mavb_dma_output(sc);

	/* The buffer is now 75% full.  Start DMA and get interrupts
	 * when the buffer is 25% full.  The interrupt handler fills
	 * in 50% of the buffer size, putting it back to 75% full.
	 */
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL2_CONTROL,
	    MAVB_CHANNEL_DMA_ENABLE | MAVB_CHANNEL_INT_25);
	mtx_leave(&audio_lock);
	return (0);
}

int
mavb_trigger_input(void *hdl, void *start, void *end, int blksize,
    void (*intr)(void *), void *intrarg, struct audio_params *param)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;

	DPRINTF(1, ("%s: mavb_trigger_output: start=%p end=%p "
	    "blksize=%d intr=%p(%p)\n", sc->sc_dev.dv_xname,
	    start, end, blksize, intr, intrarg));

	mtx_enter(&audio_lock);
	sc->rec.blksize = blksize;
	sc->rec.intr = intr;
	sc->rec.intrarg = intrarg;

	sc->rec.sw_start = sc->rec.sw_cur = start;
	sc->rec.sw_end = end;

	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL1_CONTROL,
	    MAVB_CHANNEL_RESET);
	delay(1000);
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL1_CONTROL, 0);

	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CHANNEL1_CONTROL,
	    MAVB_CHANNEL_DMA_ENABLE | MAVB_CHANNEL_INT_50);
	mtx_leave(&audio_lock);
	return (0);
}

static void
mavb_button_repeat(void *hdl)
{
	struct mavb_softc *sc = (struct mavb_softc *)hdl;
	u_int64_t intmask, control;
	u_int16_t value, left, right;

	DPRINTF(1, ("%s: mavb_repeat called\n", sc->sc_dev.dv_xname));

#define  MAVB_CONTROL_VOLUME_BUTTONS \
    (MAVB_CONTROL_VOLUME_BUTTON_UP | MAVB_CONTROL_VOLUME_BUTTON_DOWN)

	control = bus_space_read_8(sc->sc_st, sc->sc_sh, MAVB_CONTROL);
	if (control & MAVB_CONTROL_VOLUME_BUTTONS) {
		value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
		left = (value & AD1843_LDA1G_MASK) >> AD1843_LDA1G_SHIFT;
		right = (value & AD1843_RDA1G_MASK) >> AD1843_RDA1G_SHIFT;
		if (control & MAVB_CONTROL_VOLUME_BUTTON_UP) {
			control &= ~MAVB_CONTROL_VOLUME_BUTTON_UP;
			if (left > 0)
				left--;		/* attenuation! */
			if (right > 0)
				right--;
		}
		if (control & MAVB_CONTROL_VOLUME_BUTTON_DOWN) {
			control &= ~MAVB_CONTROL_VOLUME_BUTTON_DOWN;
			if (left < 63)
				left++;
			if (right < 63)
				right++;
		}
		bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CONTROL, control);

		value &= ~(AD1843_LDA1G_MASK | AD1843_RDA1G_MASK);
		value |= (left << AD1843_LDA1G_SHIFT);
		value |= (right << AD1843_RDA1G_SHIFT);
		ad1843_reg_write(sc, AD1843_DAC1_ANALOG_GAIN, value);

		timeout_add_msec(&sc->sc_volume_button_to,
		    MAVB_VOLUME_BUTTON_REPEAT_DELN);
	} else {
		/* Enable volume button interrupts again.  */
		intmask = bus_space_read_8(sc->sc_st, sc->sc_isash,
		     MACE_ISA_INT_MASK);
		bus_space_write_8(sc->sc_st, sc->sc_isash, MACE_ISA_INT_MASK,
		     intmask | MACE_ISA_INT_AUDIO_SC);
	}
}

static int
mavb_intr(void *arg)
{
	struct mavb_softc *sc = arg;
	u_int64_t intstat, intmask;

	mtx_enter(&audio_lock);
	intstat = bus_space_read_8(sc->sc_st, sc->sc_isash, MACE_ISA_INT_STAT);
	DPRINTF(MAVB_DEBUG_INTR, ("%s: mavb_intr: intstat = 0x%lx\n",
            sc->sc_dev.dv_xname, intstat));

	if (intstat & MACE_ISA_INT_AUDIO_SC) {
		/* Disable volume button interrupts.  */
		intmask = bus_space_read_8(sc->sc_st, sc->sc_isash,
		     MACE_ISA_INT_MASK);
		bus_space_write_8(sc->sc_st, sc->sc_isash, MACE_ISA_INT_MASK,
		     intmask & ~MACE_ISA_INT_AUDIO_SC);

		timeout_add_msec(&sc->sc_volume_button_to,
		    MAVB_VOLUME_BUTTON_REPEAT_DEL1);
	}

	if (intstat & MACE_ISA_INT_AUDIO_DMA1)
		mavb_dma_input(sc);

	if (intstat & MACE_ISA_INT_AUDIO_DMA2)
		mavb_dma_output(sc);
	mtx_leave(&audio_lock);
	return 1;
}

int
mavb_match(struct device *parent, void *match, void *aux)
{
	struct macebus_attach_args *maa = aux;
	bus_space_handle_t ioh;
	u_int64_t control;

	if (bus_space_map(maa->maa_iot, maa->maa_baseaddr, MAVB_NREGS, 0,
	    &ioh) != 0)
		return (0);
	control = bus_space_read_8(maa->maa_iot, ioh, MAVB_CONTROL);
	bus_space_unmap(maa->maa_iot, ioh, MAVB_NREGS);

	return ((control & MAVB_CONTROL_CODEC_PRESENT) != 0);
}

void
mavb_attach(struct device *parent, struct device *self, void *aux)
{
	struct mavb_softc *sc = (void *)self;
	struct macebus_attach_args *maa = aux;
	bus_dma_segment_t seg;
	u_int16_t value;
	int rseg;

	sc->sc_st = maa->maa_iot;
	if (bus_space_map(sc->sc_st, maa->maa_baseaddr, MAVB_NREGS, 0,
	    &sc->sc_sh) != 0) {
		printf(": can't map i/o space\n");
		return;
	}

	/* XXX We need access to some of the MACE ISA registers.  */
	extern bus_space_handle_t mace_h;
	bus_space_subregion(sc->sc_st, mace_h, 0, MAVB_ISA_NREGS,
	    &sc->sc_isash);

	/* Set up DMA structures.  */
	sc->sc_dmat = maa->maa_dmat;
	if (bus_dmamap_create(sc->sc_dmat, MAVB_ISA_RING_SIZE, 1,
	    MAVB_ISA_RING_SIZE, 0, 0, &sc->sc_dmamap)) {
		printf(": can't create MACE ISA DMA map\n");
		return;
	}

	if (bus_dmamem_alloc(sc->sc_dmat, MAVB_ISA_RING_SIZE,
	    MACE_ISA_RING_ALIGN, 0, &seg, 1, &rseg, BUS_DMA_NOWAIT)) {
		printf(": can't allocate ring buffer\n");
		return;
	}

	if (bus_dmamem_map(sc->sc_dmat, &seg, rseg, MAVB_ISA_RING_SIZE,
	    &sc->sc_ring, BUS_DMA_COHERENT)) {
		printf(": can't map ring buffer\n");
		return;
	}

	if (bus_dmamap_load(sc->sc_dmat, sc->sc_dmamap, sc->sc_ring,
	    MAVB_ISA_RING_SIZE, NULL, BUS_DMA_NOWAIT)) {
		printf(": can't load MACE ISA DMA map\n");
		return;
	}

	sc->rec.hw_start = sc->sc_ring;
	sc->play.hw_start = sc->sc_ring + MAVB_CHAN_RING_SIZE;

	bus_space_write_8(sc->sc_st, sc->sc_isash, MACE_ISA_RING_BASE,
	    sc->sc_dmamap->dm_segs[0].ds_addr);

	/* Establish interrupt.  */
	macebus_intr_establish(maa->maa_intr, maa->maa_mace_intr,
	    IST_EDGE, IPL_AUDIO, mavb_intr, sc, sc->sc_dev.dv_xname);

	/* 2. Assert the RESET signal.  */
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CONTROL,
	    MAVB_CONTROL_RESET);
	delay(1);		/* at least 100 ns */

	/* 3. Deassert the RESET signal and enter a wait period to
              allow the AD1843 internal clocks and the external
              crystal oscillator to stabilize.  */
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CONTROL, 0);
	delay(800);		/* typically 400 us to 800 us */
	if (ad1843_reg_read(sc, AD1843_CODEC_STATUS) & AD1843_INIT) {
		printf(": codec not ready\n");
		return;
	}

	/* 4. Put the conversion sources into standby.  */
	value = ad1843_reg_read(sc, AD1843_FUNDAMENTAL_SETTINGS);
	ad1843_reg_write(sc, AD1843_FUNDAMENTAL_SETTINGS,
	    value & ~AD1843_PDNI);
	delay (500000);		/* approximately 474 ms */
	if (ad1843_reg_read(sc, AD1843_CODEC_STATUS) & AD1843_PDNO) {
		printf(": can't power up conversion resources\n");
		return;
	}

	/* 5. Power up the clock generators and enable clock output pins.  */
	value = ad1843_reg_read(sc, AD1843_FUNDAMENTAL_SETTINGS);
	ad1843_reg_write(sc, AD1843_FUNDAMENTAL_SETTINGS,
	    value | AD1843_C1EN | AD1843_C2EN);

	/* 6. Configure conversion resources while they are in standby.  */
	value = ad1843_reg_read(sc, AD1843_SERIAL_INTERFACE);
	ad1843_reg_write(sc, AD1843_SERIAL_INTERFACE, value | AD1843_ADTLK);
	value = ad1843_reg_read(sc, AD1843_CHANNEL_SAMPLE_RATE);
	ad1843_reg_write(sc, AD1843_CHANNEL_SAMPLE_RATE,
	    value | (2 << AD1843_DA1C_SHIFT) |
	    (1 << AD1843_ADRC_SHIFT) | (1 << AD1843_ADLC_SHIFT));

	/* 7. Enable conversion resources.  */
	value = ad1843_reg_read(sc, AD1843_CHANNEL_POWER_DOWN);
	ad1843_reg_write(sc, AD1843_CHANNEL_POWER_DOWN,
	    value | (AD1843_DA1EN | AD1843_ANAEN | AD1843_AAMEN |
	    AD1843_ADREN | AD1843_ADLEN));

	/* 8. Configure conversion resources while they are enabled.  */
	value = ad1843_reg_read(sc, AD1843_DAC1_ANALOG_GAIN);
	ad1843_reg_write(sc, AD1843_DAC1_ANALOG_GAIN,
            value & ~(AD1843_LDA1GM | AD1843_RDA1GM));
	value = ad1843_reg_read(sc, AD1843_DAC1_DIGITAL_GAIN);
	ad1843_reg_write(sc, AD1843_DAC1_DIGITAL_GAIN,
            value & ~(AD1843_LDA1AM | AD1843_RDA1AM));
	value = ad1843_reg_read(sc, AD1843_MISC_SETTINGS);
	ad1843_reg_write(sc, AD1843_MISC_SETTINGS,
            value & ~(AD1843_HPOM | AD1843_MNOM));

	value = ad1843_reg_read(sc, AD1843_CODEC_STATUS);
	printf(": AD1843 rev %d\n", (u_int)value & AD1843_REVISION_MASK);

	sc->play.rate = sc->rec.rate = 48000;
	sc->play.format = sc->rec.format = AD1843_PCM8;

	timeout_set(&sc->sc_volume_button_to, mavb_button_repeat, sc);

	audio_attach_mi(&mavb_sa_hw_if, sc, &sc->sc_dev);

	return;
}

u_int16_t
ad1843_reg_read(struct mavb_softc *sc, ad1843_addr_t addr)
{
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CODEC_CONTROL,
            (addr & MAVB_CODEC_ADDRESS_MASK) << MAVB_CODEC_ADDRESS_SHIFT |
	    MAVB_CODEC_READ);
	delay(200);
	return bus_space_read_8(sc->sc_st, sc->sc_sh, MAVB_CODEC_STATUS);
}

u_int16_t
ad1843_reg_write(struct mavb_softc *sc, ad1843_addr_t addr, u_int16_t value)
{
	bus_space_write_8(sc->sc_st, sc->sc_sh, MAVB_CODEC_CONTROL,
	    (addr & MAVB_CODEC_ADDRESS_MASK) << MAVB_CODEC_ADDRESS_SHIFT |
	    (value & MAVB_CODEC_WORD_MASK) << MAVB_CODEC_WORD_SHIFT);
	delay(200);
	return bus_space_read_8(sc->sc_st, sc->sc_sh, MAVB_CODEC_STATUS);
}

void
ad1843_dump_regs(struct mavb_softc *sc)
{
	u_int16_t addr;

	for (addr = 0; addr < AD1843_NREGS; addr++)
		printf("%d: 0x%04x\n", addr, ad1843_reg_read(sc, addr));
}


code:
/*	$OpenBSD: mavbreg.h,v 1.1 2005/01/02 19:25:41 kettenis Exp $	*/

/*
 * Copyright (c) 2005 Mark Kettenis
 *
 * Permission to use, copy, modify, and 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.
 */

/*
 * MACE Moosehead A/V Board register definitions.
 */

#define MAVB_CONTROL				0x00
#define  MAVB_CONTROL_RESET			0x0000000000000001
#define  MAVB_CONTROL_CODEC_PRESENT		0x0000000000000002
#define  MAVB_CONTROL_VOLUME_BUTTON_UP		0x0000000001000000
#define  MAVB_CONTROL_VOLUME_BUTTON_DOWN	0x0000000000800000

#define MAVB_CODEC_CONTROL			0x08
#define  MAVB_CODEC_READ			0x0000000000010000
#define  MAVB_CODEC_WORD_SHIFT			0
#define  MAVB_CODEC_WORD_MASK			0x000000000000ffff
#define  MAVB_CODEC_ADDRESS_SHIFT		17
#define  MAVB_CODEC_ADDRESS_MASK		0x000000000000001f

#define MAVB_CODEC_STATUS			0x18
#define MAVB_CHANNEL1_CONTROL			0x20
#define MAVB_CHANNEL2_CONTROL			0x40
#define MAVB_CHANNEL3_CONTROL			0x60
#define  MAVB_CHANNEL_RESET			0x0000000000000400
#define  MAVB_CHANNEL_DMA_ENABLE		0x0000000000000200
#define  MAVB_CHANNEL_INT_DISABLED		0x0000000000000000
#define  MAVB_CHANNEL_INT_25			0x0000000000000020
#define  MAVB_CHANNEL_INT_50			0x0000000000000040
#define  MAVB_CHANNEL_INT_75			0x0000000000000060
#define  MAVB_CHANNEL_INT_EMPTY			0x0000000000000080
#define  MAVB_CHANNEL_INT_NOT_EMPTY		0x00000000000000a0
#define  MAVB_CHANNEL_INT_FULL			0x00000000000000c0
#define  MAVB_CHANNEL_INT_NOT_FULL		0x00000000000000e0

#define MAVB_CHANNEL1_READ_PTR			0x28
#define MAVB_CHANNEL1_WRITE_PTR			0x30
#define MAVB_CHANNEL1_DEPTH			0x38
#define MAVB_CHANNEL2_READ_PTR			0x48
#define MAVB_CHANNEL2_WRITE_PTR			0x50
#define MAVB_CHANNEL2_DEPTH			0x58

#define MAVB_NREGS				0x80


code:
/*	$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);
}


code:
/*	$OpenBSD: mkbcreg.h,v 1.3 2010/12/03 18:29:56 shadchin Exp $  */

/*
 * Copyright (c) 2006, 2007, Joel Sing
 *
 * Permission to use, copy, modify, and 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.
 */

#include <dev/ic/pckbcvar.h>

/*
 * MACE PS/2 controller register definitions.
 */

#define MKBC_PORTSIZE			0x20

#define MKBC_TX_PORT			0x00
#define MKBC_RX_PORT			0x08
#define MKBC_CONTROL			0x10
#define MKBC_STATUS			0x18

/*
 * Controller status flags
 */
#define MKBC_STATUS_CLOCK_SIGNAL  	0x01
#define MKBC_STATUS_CLOCK_INHIBIT	0x02
#define MKBC_STATUS_TX_INPROGRESS	0x04
#define MKBC_STATUS_TX_EMPTY		0x08
#define MKBC_STATUS_RX_FULL		0x10
#define MKBC_STATUS_RX_INPROGRESS	0x20
#define MKBC_STATUS_ERROR_PARITY	0x40
#define MKBC_STATUS_ERROR_FRAMING	0x80

/*
 * Control bits
 */
#define MKBC_CONTROL_TX_CLOCK_DISABLE	0x01
#define MKBC_CONTROL_TX_ENABLE		0x02
#define MKBC_CONTROL_TX_INT_ENABLE	0x04
#define MKBC_CONTROL_RX_INT_ENABLE	0x08
#define MKBC_CONTROL_RX_CLOCK_ENABLE	0x10
#define MKBC_CONTROL_RESET		0x20

int	mkbc_cnattach(bus_space_tag_t, bus_addr_t);