code:
/*	$OpenBSD: arcofi.c,v 1.13 2015/06/26 04:25:18 miod Exp $	*/

/*
 * Copyright (c) 2011 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.
 */

/*
 * Driver for the HP ``Audio1'' device, which is a FIFO layer around a
 * Siemens PSB 2160 ``ARCOFI'' phone quality audio chip.
 *
 * It is known to exist in two flavours: on-board the HP9000/425e as a DIO
 * device, an on-board the HP9000/{705,710,745,747} as a GIO device.
 *
 * The FIFO logic buffers up to 128 bytes. When using 8 bit samples and
 * the logic set to interrupt every half FIFO, the device will interrupt
 * 125 times per second.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/endian.h>

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

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

#include <dev/ic/arcofivar.h>

#if 0
#define	ARCOFI_DEBUG
#endif

/*
 * Siemens PSB2160 registers
 */

/* CMDR */
#define	CMDR_AD		0x80	/* SP1/SP2 address convention */
#define	CMDR_READ	0x40
#define	CMDR_WRITE	0x00
#define	CMDR_PU		0x20	/* Power Up */
#define	CMDR_RCS	0x10	/* Receive and transmit in CH B2 */
#define	CMDR_MASK	0x0f

	/* command	     length	data */
#define	SOP_0	0x00	/*	5	CR4 CR3 CR2 CR1 */
#define	COP_1	0x01	/*	5	t1_hi t1_lo f1_hi f1_lo */
#define	COP_2	0x02	/*	3	gr1 gr2 */
#define	COP_3	0x03	/*	3	t2_hi t2_lo f2_hi f2_lo */
#define	SOP_4	0x04	/*	2	CR1 */
#define	SOP_5	0x05	/*	2	CR2 */
#define	SOP_6	0x06	/*	2	CR3 */
#define	SOP_7	0x07	/*	2	CR4 */
#define	COP_8	0x08	/*	3	dtmf_hi dtmf_lo */
#define	COP_9	0x09	/*	5	gz a3 a2 a1 */
#define	COP_A	0x0a	/*	9	fx1 to fx8 */
#define	COP_B	0x0b	/*	3	gx1 gx2 */
#define	COP_C	0x0c	/*	9	fr1 to fr 8 */
#define	COP_D	0x0d	/*	5	fr9 fr10 fx9 fx10 */
#define	COP_E	0x0e	/*	5	t3_hi t3_lo f3_hi f3_lo */

/* CR1 */
#define	CR1_GR		0x80	/* GR gain loaded from CRAM vs 0dB */
#define	CR1_GZ		0x40	/* Z gain loaded from CRAM vs -18dB */
#define	CR1_FX		0x20	/* X filter loaded from CRAM vs 0dB flat */
#define	CR1_FR		0x10	/* R filter loaded from CRAM vs 0dB flat */
#define	CR1_GX		0x08	/* GX gain loaded from CRAM vs 0dB */
#define	CR1_T_MASK	0x07	/* test mode */
#define	CR1_DLP		0x07	/* digital loopback via PCM registers */
#define	CR1_DLM		0x06	/* D/A output looped back to A/D input */
#define	CR1_DLS		0x05	/* digital loopback via converter registers */
#define	CR1_IDR		0x04	/* data RAM initialization */
#define	CR1_BYP		0x03	/* bypass analog frontend */
#define	CR1_ALM		0x02	/* analog loopback via MUX */
#define	CR1_ALS		0x01	/* analog loopback via converter registers */

/* CR2 */
#define	CR2_SD		0x80	/* SD pin set to input vs output */
#define	CR2_SC		0x40	/* SC pin set to input vs output */
#define	CR2_SB		0x20	/* SB pin set to input vs output */
#define	CR2_SA		0x10	/* SA pin set to input vs output */
#define	CR2_ELS		0x08	/* non-input S pins tristate SIP vs sending 0 */
#define	CR2_AM		0x04	/* only one device on the SLD bus */
#define	CR2_TR		0x02	/* three party conferencing */
#define	CR2_EFC		0x01	/* enable feature control */

/* CR3 */
#define	CR3_MIC_G_MASK	0xe0		/* MIC input analog gain  */
#define	CR3_MIC_X_INPUT		0xe0	/* MIC disabled, X input 15.1 dB */
#define	CR3_MIC_G_17		0xc0	/* 17 dB */
#define	CR3_MIC_G_22		0xa0	/* 22 dB */
#define	CR3_MIC_G_28		0x80	/* 28 dB */
#define	CR3_MIC_G_34		0x60	/* 34 dB */
#define	CR3_MIC_G_40		0x40	/* 40 dB */
#define	CR3_MIC_G_46		0x20	/* 46 dB */
#define	CR3_MIC_G_52		0x00	/* 52 dB (reset default) */
#define	CR3_AFEC_MASK	0x1c
#define	CR3_AFEC_MUTE		0x18	/* mute: Hout */
#define	CR3_AFEC_HFS		0x14	/* hands free: FHM, LS out */
#define	CR3_AFEC_LH3		0x10	/* loud hearing 3: MIC, H out, LS out */
#define	CR3_AFEC_LH2		0x0c	/* loud hearing 2: MIC, LS out */
#define	CR3_AFEC_LH1		0x08	/* loud hearing 1: LS out */
#define	CR3_AFEC_RDY		0x04	/* ready: MIC, H out */
#define	CR3_AFEC_POR		0x00	/* power on reset: all off */
#define	CR3_OPMODE_MASK	0x03
#define	CR3_OPMODE_LINEAR	0x02	/* linear (16 bit) */
#define	CR3_OPMODE_MIXED	0x01	/* mixed */
#define	CR3_OPMODE_NORMAL	0x00	/* normal (A/u-Law) */

/* CR4 */
#define	CR4_DHF		0x80	/* TX digital high frequency enable */
#define	CR4_DTMF	0x40	/* DTMF generator enable */
#define	CR4_TG		0x20	/* tone ring enable */
#define	CR4_BT		0x10	/* beat tone generator enable */
#define	CR4_TM		0x08	/* incoming voice enable */
#define	CR4_BM		0x04	/* beat mode (3 tone vs 2 tone) */
#define	CR4_PM		0x02	/* tone sent to piezo vs loudspeaker */
#define	CR4_ULAW	0x01	/* u-Law vs A-Law */

/*
 * Glue logic registers
 * Note the register values here are symbolic, as actual addresses
 * depend upon the particular bus the device is connected to.
 */

#define	ARCOFI_ID		0	/* id (r) and reset (w) register */

#define	ARCOFI_CSR		1	/* status and control register */
#define	CSR_INTR_ENABLE			0x80
#define	CSR_INTR_REQUEST		0x40	/* unacknowledged interrupt */
/* 0x20 and 0x10 used in DIO flavours, to provide IPL */
#define	CSR_WIDTH_16			0x08	/* 16-bit samples */
#define	CSR_CTRL_FIFO_ENABLE		0x04	/* connect FIFO to CMDR */
#define	CSR_DATA_FIFO_ENABLE		0x01	/* connect FIFO to DU/DD */

#define	ARCOFI_FIFO_IR		2	/* FIFO interrupt register */
#define	FIFO_IR_ENABLE(ev)		((ev) << 4)
#define	FIFO_IR_EVENT(ev)		(ev)
#define	FIFO_IR_OUT_EMPTY		0x08
#define	FIFO_IR_CTRL_EMPTY		0x04
#define	FIFO_IR_OUT_HALF_EMPTY		0x02
#define	FIFO_IR_IN_HALF_EMPTY		0x01

#define	ARCOFI_FIFO_SR		3	/* FIFO status register (ro) */
#define	FIFO_SR_CTRL_FULL		0x20
#define	FIFO_SR_CTRL_EMPTY		0x10
#define	FIFO_SR_OUT_FULL		0x08
#define	FIFO_SR_OUT_EMPTY		0x04
#define	FIFO_SR_IN_FULL			0x02
#define	FIFO_SR_IN_EMPTY		0x01

#define	ARCOFI_FIFO_DATA	4	/* data FIFO port */

#define	ARCOFI_FIFO_CTRL	5	/* control FIFO port (wo) */

#define	ARCOFI_FIFO_SIZE	128

struct cfdriver arcofi_cd = {
	NULL, "arcofi", DV_DULL
};

#define	arcofi_read(sc, r) \
	bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[r])
#define	arcofi_write(sc, r, v) \
	bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[r], v)

int	arcofi_cmd(struct arcofi_softc *, uint8_t, const uint8_t *);
int	arcofi_cr3_to_portmask(uint, int);
int	arcofi_gain_to_mi(uint);
uint	arcofi_mi_to_gain(int);
uint	arcofi_portmask_to_cr3(int);
int	arcofi_set_param(struct arcofi_softc *, int, int, int,
	    struct audio_params *);

void	arcofi_close(void *);
int	arcofi_commit_settings(void *);
int	arcofi_drain(void *);
int	arcofi_getdev(void *, struct audio_device *);
int	arcofi_get_port(void *, mixer_ctrl_t *);
int	arcofi_get_props(void *);
int	arcofi_halt_input(void *);
int	arcofi_halt_output(void *);
int	arcofi_open(void *, int);
int	arcofi_query_devinfo(void *, mixer_devinfo_t *);
int	arcofi_query_encoding(void *, struct audio_encoding *);
int	arcofi_round_blocksize(void *, int);
int	arcofi_set_params(void *, int, int, struct audio_params *,
	    struct audio_params *);
int	arcofi_set_port(void *, mixer_ctrl_t *);
int	arcofi_start_input(void *, void *, int, void (*)(void *), void *);
int	arcofi_start_output(void *, void *, int, void (*)(void *), void *);

/* const */ struct audio_hw_if arcofi_hw_if = {
	.open = arcofi_open,
	.close = arcofi_close,
	.query_encoding = arcofi_query_encoding,
	.set_params = arcofi_set_params,
	.round_blocksize = arcofi_round_blocksize,
	.commit_settings = arcofi_commit_settings,
	.start_output = arcofi_start_output,
	.start_input = arcofi_start_input,
	.halt_output = arcofi_halt_output,
	.halt_input = arcofi_halt_input,
	.getdev = arcofi_getdev,
	.set_port = arcofi_set_port,
	.get_port = arcofi_get_port,
	.query_devinfo = arcofi_query_devinfo,
	.get_props = arcofi_get_props,
};

/* mixer items */
#define	ARCOFI_PORT_AUDIO_IN_VOLUME	0	/* line in volume (GR) */
#define	ARCOFI_PORT_AUDIO_OUT_VOLUME	1	/* line out volume (GX) */
#define	ARCOFI_PORT_AUDIO_SPKR_VOLUME	2	/* speaker volume (GX) */
#define	ARCOFI_PORT_AUDIO_IN_MUTE	3	/* line in mute (MIC) */
#define	ARCOFI_PORT_AUDIO_OUT_MUTE	4	/* line out mute (H out) */
#define	ARCOFI_PORT_AUDIO_SPKR_MUTE	5	/* line in mute (LS out) */
/* mixer classes */
#define	ARCOFI_CLASS_INPUT		6
#define	ARCOFI_CLASS_OUTPUT		7

/*
 * Gain programming formulae are a complete mystery to me, and of course
 * no two chips are compatible - not even the PSB 2163 and PSB 2165
 * later ARCOFI chips, from the same manufacturer as the PSB 2160!
 *
 * Of course, the PSB 2160 datasheet does not give any set of values.
 * The following table is taken from the HP-UX audio driver (audio_shared.o
 * private_audio_gain_tab).
 */

#define	NEGATIVE_GAINS	60
#define	POSITIVE_GAINS	14
static const uint16_t arcofi_gains[1 + NEGATIVE_GAINS + 1 + POSITIVE_GAINS] = {
	/* minus infinity */
	0x0988,

	0xf8b8, 0xf8b8, 0xf8b8, 0xf8b8, 0x099f, 0x099f, 0x099f, 0x099f,
	0x09af, 0x09af, 0x09af, 0x09cf, 0x09cf, 0x09cf, 0xf8a9, 0xf83a,
	0xf83a, 0xf82b, 0xf82d, 0xf8a3, 0xf8b2, 0xf8a1, 0xe8aa, 0xe84b,
	0xe89e, 0xe8d3, 0xe891, 0xe8b1, 0xd8aa, 0xd8cb, 0xd8a6, 0xd8b3,
	0xd842, 0xd8b1, 0xc8aa, 0xc8bb, 0xc888, 0xc853, 0xc852, 0xc8b1,
	0xb8aa, 0xb8ab, 0xb896, 0xb892, 0xb842, 0xb8b1, 0xa8aa, 0xa8bb,
	0x199f, 0x195b, 0x29c1, 0x2923, 0x29aa, 0x392b, 0xf998, 0xb988,
	0x1aac, 0x3aa1, 0xbaa1, 0xbb88,

	/* 0 */
	0x8888,

	0xd388, 0x5288, 0xb1a1, 0x31a1, 0x1192, 0x11d0, 0x30c0, 0x2050,
	0x1021, 0x1020, 0x1000, 0x0001, 0x0010, 0x0000
};

int
arcofi_open(void *v, int flags)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	if (sc->sc_open)
		return EBUSY;
	sc->sc_open = 1;
	KASSERT(sc->sc_mode == 0);

	return 0;
}

void
arcofi_close(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	arcofi_halt_input(v);
	arcofi_halt_output(v);
	sc->sc_open = 0;
}

int
arcofi_drain(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	mtx_enter(&audio_lock);
	if ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_OUT_EMPTY) == 0) {
		/* enable output FIFO empty interrupt... */
		arcofi_write(sc, ARCOFI_FIFO_IR,
		    arcofi_read(sc, ARCOFI_FIFO_IR) |
		    FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
		/* ...and wait for it to fire */
		if (msleep(&sc->sc_xmit, &audio_lock, 0, "arcofidr",
		    1 + (ARCOFI_FIFO_SIZE * hz) / 8000) != 0) {
			printf("%s: drain did not complete\n",
			    sc->sc_dev.dv_xname);
			arcofi_write(sc, ARCOFI_FIFO_IR,
			    arcofi_read(sc, ARCOFI_FIFO_IR) &
			    ~FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
		}
	}
	mtx_leave(&audio_lock);
	return 0;
}

int
arcofi_query_encoding(void *v, struct audio_encoding *ae)
{
	switch (ae->index) {
	/*
	 * 8-bit encodings: u-Law and A-Law are native
	 */
	case 0:
		strlcpy(ae->name, AudioEmulaw, sizeof ae->name);
		ae->precision = 8;
		ae->encoding = AUDIO_ENCODING_ULAW;
		ae->flags = 0;
		break;
	case 1:
		strlcpy(ae->name, AudioEalaw, sizeof ae->name);
		ae->precision = 8;
		ae->encoding = AUDIO_ENCODING_ALAW;
		ae->flags = 0;
		break;

	/*
	 * 16-bit encodings: slinear big-endian is native
	 */
	case 2:
		strlcpy(ae->name, AudioEslinear_be, sizeof ae->name);
		ae->precision = 16;
		ae->encoding = AUDIO_ENCODING_SLINEAR_BE;
		ae->flags = 0;
		break;

	default:
		return EINVAL;
	}

	ae->bps = AUDIO_BPS(ae->precision);
	ae->msb = 1;

	return 0;
}

/*
 * Compute proper sample and hardware settings. Invoked both for record
 * and playback, as we don't support independent settings.
 */
int
arcofi_set_param(struct arcofi_softc *sc, int set, int use, int mode,
    struct audio_params *ap)
{
	if ((set & mode) == 0)
		return 0;

#ifdef ARCOFI_DEBUG
	printf("%s: set_param, mode %d encoding %d precision %d\n",
	    sc->sc_dev.dv_xname, mode, ap->encoding, ap->precision);
#endif
	switch (ap->precision) {
	case 8:
		switch (ap->encoding) {
		case AUDIO_ENCODING_ULAW:
			sc->sc_shadow.cr4 |= CR4_ULAW;
			break;
		case AUDIO_ENCODING_ALAW:
			sc->sc_shadow.cr4 &= ~CR4_ULAW;
			break;
		default:
			return EINVAL;
		}
		sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
		    CR3_OPMODE_NORMAL;
		break;
	case 16:
		switch (ap->encoding) {
		case AUDIO_ENCODING_SLINEAR_BE:
			break;
		default:
			return EINVAL;
		}
		sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
		    CR3_OPMODE_LINEAR;
		break;
	default:
		return EINVAL;
	}

	ap->bps = AUDIO_BPS(ap->precision);
	ap->msb = 1;
	ap->channels = 1;
	ap->sample_rate = 8000;

	return 0;
}

int
arcofi_set_params(void *v, int set, int use, struct audio_params *p,
    struct audio_params *r)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	int rc;

	if (r != NULL) {
		rc = arcofi_set_param(sc, set, use, AUMODE_RECORD, r);
		if (rc != 0)
			return rc;
	}

	if (p != NULL) {
		rc = arcofi_set_param(sc, set, use, AUMODE_PLAY, p);
		if (rc != 0)
			return rc;
	}

	return 0;
}

int
arcofi_round_blocksize(void *v, int blksz)
{
	/*
	 * Round the size up to a multiple of half the FIFO, to favour
	 * smooth interrupt operation.
	 */
	return roundup(blksz, ARCOFI_FIFO_SIZE / 2);
}

int
arcofi_commit_settings(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	int rc;
	uint8_t cmd[2], csr, ocsr;

#ifdef ARCOFI_DEBUG
	printf("%s: commit_settings, gr %04x gx %04x cr3 %02x cr4 %02x mute %d\n",
	    sc->sc_dev.dv_xname,
	    arcofi_gains[sc->sc_shadow.gr_idx],
	    arcofi_gains[sc->sc_shadow.gx_idx],
	    sc->sc_shadow.cr3, sc->sc_shadow.cr4, sc->sc_shadow.output_mute);
#endif

	if (bcmp(&sc->sc_active, &sc->sc_shadow, sizeof(sc->sc_active)) == 0)
		return 0;

	mtx_enter(&audio_lock);

	if (sc->sc_active.gr_idx != sc->sc_shadow.gr_idx) {
		cmd[0] = arcofi_gains[sc->sc_shadow.gr_idx] >> 8;
		cmd[1] = arcofi_gains[sc->sc_shadow.gr_idx];
		if ((rc = arcofi_cmd(sc, COP_2, cmd)) != 0)
			goto error;
		sc->sc_active.gr_idx = sc->sc_shadow.gr_idx;
	}

	if (sc->sc_active.gx_idx != sc->sc_shadow.gx_idx ||
	    sc->sc_active.output_mute != sc->sc_shadow.output_mute) {
		if (sc->sc_shadow.output_mute) {
			cmd[0] = arcofi_gains[0] >> 8;
			cmd[1] = arcofi_gains[0];
		} else {
			cmd[0] = arcofi_gains[sc->sc_shadow.gx_idx] >> 8;
			cmd[1] = arcofi_gains[sc->sc_shadow.gx_idx];
		}
		if ((rc = arcofi_cmd(sc, COP_B, cmd)) != 0)
			goto error;
		sc->sc_active.gx_idx = sc->sc_shadow.gx_idx;
		sc->sc_active.output_mute = sc->sc_shadow.output_mute;
	}

	if (sc->sc_active.cr3 != sc->sc_shadow.cr3) {
		cmd[0] = sc->sc_shadow.cr3;
		if ((rc = arcofi_cmd(sc, SOP_6, cmd)) != 0)
			goto error;
		sc->sc_active.cr3 = sc->sc_shadow.cr3;

		ocsr = arcofi_read(sc, ARCOFI_CSR);
		if ((sc->sc_active.cr3 & CR3_OPMODE_MASK) != CR3_OPMODE_NORMAL)
			csr = ocsr | CSR_WIDTH_16;
		else
			csr = ocsr & ~CSR_WIDTH_16;
		if (csr != ocsr)
			arcofi_write(sc, ARCOFI_CSR, csr);
	}

	if (sc->sc_active.cr4 != sc->sc_shadow.cr4) {
		cmd[0] = sc->sc_shadow.cr4;
		if ((rc = arcofi_cmd(sc, SOP_7, cmd)) != 0)
			goto error;
		sc->sc_active.cr4 = sc->sc_shadow.cr4;
	}

	rc = 0;
error:
	mtx_leave(&audio_lock);
	return rc;
}

int
arcofi_start_input(void *v, void *rbuf, int rsz, void (*cb)(void *),
    void *cbarg)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
	printf("%s: start_input, mode %d\n",
	    sc->sc_dev.dv_xname, sc->sc_mode);
#endif

	/* enable data FIFO if becoming active */
	if (sc->sc_mode == 0)
		arcofi_write(sc, ARCOFI_CSR,
		    arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
	sc->sc_mode |= AUMODE_RECORD;

	sc->sc_recv.buf = (uint8_t *)rbuf;
	sc->sc_recv.past = (uint8_t *)rbuf + rsz;
	sc->sc_recv.cb = cb;
	sc->sc_recv.cbarg = cbarg;

	/* enable input FIFO interrupts */
	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
	    FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));

	return 0;
}

int
arcofi_start_output(void *v, void *wbuf, int wsz, void (*cb)(void *),
    void *cbarg)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
	printf("%s: start_output, mode %d\n",
	    sc->sc_dev.dv_xname, sc->sc_mode);
#endif

	/* enable data FIFO if becoming active */
	if (sc->sc_mode == 0)
		arcofi_write(sc, ARCOFI_CSR,
		    arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
	sc->sc_mode |= AUMODE_PLAY;

	sc->sc_xmit.buf = (uint8_t *)wbuf;
	sc->sc_xmit.past = (uint8_t *)wbuf + wsz;
	sc->sc_xmit.cb = cb;
	sc->sc_xmit.cbarg = cbarg;

	/* enable output FIFO interrupts */
	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
	    FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));

	return 0;
}

int
arcofi_halt_input(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	mtx_enter(&audio_lock);

	/* disable input FIFO interrupts */
	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
	    ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
	/* disable data FIFO if becoming idle */
	sc->sc_mode &= ~AUMODE_RECORD;
	if (sc->sc_mode == 0)
		arcofi_write(sc, ARCOFI_CSR,
		    arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);

	mtx_leave(&audio_lock);
	return 0;
}

int
arcofi_halt_output(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	mtx_enter(&audio_lock);

	/* disable output FIFO interrupts */
	arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
	    ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
	/* disable data FIFO if becoming idle */
	sc->sc_mode &= ~AUMODE_PLAY;
	if (sc->sc_mode == 0)
		arcofi_write(sc, ARCOFI_CSR,
		    arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);

	mtx_leave(&audio_lock);
	return 0;
}

int
arcofi_getdev(void *v, struct audio_device *ad)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;

	bcopy(&sc->sc_audio_device, ad, sizeof(*ad));
	return 0;
}

/*
 * Convert gain table index to AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale.
 */
int
arcofi_gain_to_mi(uint idx)
{
	if (idx == 0)
		return AUDIO_MIN_GAIN;
	if (idx == nitems(arcofi_gains) - 1)
		return AUDIO_MAX_GAIN;

	return ((idx - 1) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) /
	    (nitems(arcofi_gains) - 1) + AUDIO_MIN_GAIN + 1;
}

/*
 * Convert AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale to gain table index.
 */
uint
arcofi_mi_to_gain(int lvl)
{
	if (lvl <= AUDIO_MIN_GAIN)
		return 0;
	if (lvl >= AUDIO_MAX_GAIN)
		return nitems(arcofi_gains) - 1;

	return ((lvl - AUDIO_MIN_GAIN - 1) * (nitems(arcofi_gains) - 1)) /
	    (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN);
}

/*
 * Input and output ports definition (used to be in <audioio.h>
 */
#define	AUDIO_SPEAKER		0x01	/* built-in speaker */
#define	AUDIO_LINE_IN		0x02	/* line in	 */
#define	AUDIO_LINE_OUT		0x04	/* line out	 */

/*
 * The mapping between the available inputs and outputs, and CR3, is as
 * follows:
 * - the `line in' connector is the `MIC' input.
 * - the `line out' connector is the `H out' (heaphones) output.
 * - the internal `speaker' is the `LS out' (loudspeaker) output.
 *
 * Each of these can be enabled or disabled independently, except for
 * MIC enabled with H out and LS out disabled, which is not allowed
 * by the chip (and makes no sense for a chip which was intended to
 * be used in phones, not voice recorders); we cheat by keeping one
 * output source enabled, but with the output gain forced to minus
 * infinity to mute it.
 *
 * The truth table is thus:
 *
 *	MIC	LS out	H out	AFEC
 *	off	off	off	POR
 *	off	off	on	MUTE
 *	off	on	off	LH1
 *	off	on	on	LH3, X input enabled
 *	on	off	off	RDY, GX forced to minus infinity
 *	on	off	on	RDY
 *	on	on	off	LH2
 *	on	on	on	LH3
 */

/*
 * Convert logical port enable settings to a valid CR3 value.
 */
uint
arcofi_portmask_to_cr3(int mask)
{
	switch (mask) {
	default:
	case 0:
		return CR3_MIC_G_17 | CR3_AFEC_POR;
	case AUDIO_LINE_OUT:
		return CR3_MIC_G_17 | CR3_AFEC_MUTE;
	case AUDIO_SPEAKER:
		return CR3_MIC_G_17 | CR3_AFEC_LH1;
	case AUDIO_SPEAKER | AUDIO_LINE_OUT:
		return CR3_MIC_X_INPUT | CR3_AFEC_LH3;
	case AUDIO_LINE_IN:
		/* since we can't do this, just... */
		/* FALLTHROUGH */
	case AUDIO_LINE_IN | AUDIO_LINE_OUT:
		return CR3_MIC_G_17 | CR3_AFEC_RDY;
	case AUDIO_LINE_IN | AUDIO_SPEAKER:
		return CR3_MIC_G_17 | CR3_AFEC_LH2;
	case AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT:
		return CR3_MIC_G_17 | CR3_AFEC_LH3;
	}
}

/*
 * Convert CR3 to an enabled ports mask.
 */
int
arcofi_cr3_to_portmask(uint cr3, int output_mute)
{
	switch (cr3 & CR3_AFEC_MASK) {
	default:
	case CR3_AFEC_POR:
		return 0;
	case CR3_AFEC_RDY:
		return output_mute ?
		    AUDIO_LINE_IN : AUDIO_LINE_IN | AUDIO_LINE_OUT;
	case CR3_AFEC_HFS:
	case CR3_AFEC_LH1:
		return AUDIO_SPEAKER;
	case CR3_AFEC_LH2:
		return AUDIO_LINE_IN | AUDIO_SPEAKER;
	case CR3_AFEC_LH3:
		if ((cr3 & CR3_MIC_G_MASK) == CR3_MIC_X_INPUT)
			return AUDIO_SPEAKER | AUDIO_LINE_OUT;
		else
			return AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT;
	case CR3_AFEC_MUTE:
		return AUDIO_LINE_OUT;
	}
}

int
arcofi_set_port(void *v, mixer_ctrl_t *mc)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	int portmask;

	/* check for proper type */
	switch (mc->dev) {
	/* volume settings */
	case ARCOFI_PORT_AUDIO_IN_VOLUME:
	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
		if (mc->un.value.num_channels != 1)
			return EINVAL;
		break;
	/* mute settings */
	case ARCOFI_PORT_AUDIO_IN_MUTE:
	case ARCOFI_PORT_AUDIO_OUT_MUTE:
	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
		if (mc->type != AUDIO_MIXER_ENUM)
			return EINVAL;
		portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
		    sc->sc_shadow.output_mute);
#ifdef ARCOFI_DEBUG
		printf("%s: set_port cr3 %02x -> mask %02x\n",
		    sc->sc_dev.dv_xname, sc->sc_shadow.cr3, portmask);
#endif
		break;
	default:
		return EINVAL;
	}

	switch (mc->dev) {
	/* volume settings */
	case ARCOFI_PORT_AUDIO_IN_VOLUME:
		sc->sc_shadow.gr_idx =
		    arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
		return 0;
	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
		sc->sc_shadow.gx_idx =
		    arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
		return 0;

	/* mute settings */
	case ARCOFI_PORT_AUDIO_IN_MUTE:
		if (mc->un.ord)
			portmask &= ~AUDIO_LINE_IN;
		else
			portmask |= AUDIO_LINE_IN;
		break;
	case ARCOFI_PORT_AUDIO_OUT_MUTE:
		if (mc->un.ord)
			portmask &= ~AUDIO_LINE_OUT;
		else
			portmask |= AUDIO_LINE_OUT;
		break;
	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
		if (mc->un.ord)
			portmask &= ~AUDIO_SPEAKER;
		else
			portmask |= AUDIO_SPEAKER;
		break;
	}

	sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & CR3_OPMODE_MASK) |
	    arcofi_portmask_to_cr3(portmask);
	sc->sc_shadow.output_mute = (portmask == AUDIO_LINE_IN);
#ifdef ARCOFI_DEBUG
	printf("%s: set_port mask %02x -> cr3 %02x m %d\n",
	    sc->sc_dev.dv_xname, portmask,
	    sc->sc_shadow.cr3, sc->sc_shadow.output_mute);
#endif

	return 0;
}

int
arcofi_get_port(void *v, mixer_ctrl_t *mc)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	int portmask;

	/* check for proper type */
	switch (mc->dev) {
	/* volume settings */
	case ARCOFI_PORT_AUDIO_IN_VOLUME:
	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
		if (mc->un.value.num_channels != 1)
			return EINVAL;
		break;

	/* mute settings */
	case ARCOFI_PORT_AUDIO_IN_MUTE:
	case ARCOFI_PORT_AUDIO_OUT_MUTE:
	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
		if (mc->type != AUDIO_MIXER_ENUM)
			return EINVAL;
		portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
		    sc->sc_shadow.output_mute);
#ifdef ARCOFI_DEBUG
		printf("%s: get_port cr3 %02x -> mask %02x\n",
		    sc->sc_dev.dv_xname, sc->sc_shadow.cr3, portmask);
#endif
		break;
	default:
		return EINVAL;
	}

	switch (mc->dev) {
	/* volume settings */
	case ARCOFI_PORT_AUDIO_IN_VOLUME:
		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
		    arcofi_gain_to_mi(sc->sc_shadow.gr_idx);
		break;
	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
		    arcofi_gain_to_mi(sc->sc_shadow.gx_idx);
		break;

	/* mute settings */
	case ARCOFI_PORT_AUDIO_IN_MUTE:
		mc->un.ord = portmask & AUDIO_LINE_IN ? 0 : 1;
		break;
	case ARCOFI_PORT_AUDIO_OUT_MUTE:
		mc->un.ord = portmask & AUDIO_LINE_OUT ? 0 : 1;
		break;
	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
		mc->un.ord = portmask & AUDIO_SPEAKER ? 0 : 1;
		break;
	}

	return 0;
}

int
arcofi_query_devinfo(void *v, mixer_devinfo_t *md)
{
	switch (md->index) {
	default:
		return ENXIO;

	/* items */
	case ARCOFI_PORT_AUDIO_IN_VOLUME:
		md->type = AUDIO_MIXER_VALUE;
		md->mixer_class = ARCOFI_CLASS_INPUT;
		md->prev = AUDIO_MIXER_LAST;
		md->next = ARCOFI_PORT_AUDIO_IN_MUTE;
		strlcpy(md->label.name, AudioNline,
		    sizeof md->label.name);
		goto mono_volume;
	case ARCOFI_PORT_AUDIO_OUT_VOLUME:
		md->type = AUDIO_MIXER_VALUE;
		md->mixer_class = ARCOFI_CLASS_OUTPUT;
		md->prev = AUDIO_MIXER_LAST;
		md->next = ARCOFI_PORT_AUDIO_OUT_MUTE;
		strlcpy(md->label.name, AudioNline,
		    sizeof md->label.name);
		goto mono_volume;
	case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
		md->type = AUDIO_MIXER_VALUE;
		md->mixer_class = ARCOFI_CLASS_OUTPUT;
		md->prev = AUDIO_MIXER_LAST;
		md->next = ARCOFI_PORT_AUDIO_SPKR_MUTE;
		strlcpy(md->label.name, AudioNspeaker,
		    sizeof md->label.name);
		/* goto mono_volume; */
mono_volume:
		md->un.v.num_channels = 1;
		strlcpy(md->un.v.units.name, AudioNvolume,
		    sizeof md->un.v.units.name);
		break;

	case ARCOFI_PORT_AUDIO_IN_MUTE:
		md->type = AUDIO_MIXER_ENUM;
		md->mixer_class = ARCOFI_CLASS_INPUT;
		md->prev = ARCOFI_PORT_AUDIO_IN_VOLUME;
		md->next = AUDIO_MIXER_LAST;
		goto mute;
	case ARCOFI_PORT_AUDIO_OUT_MUTE:
		md->type = AUDIO_MIXER_ENUM;
		md->mixer_class = ARCOFI_CLASS_OUTPUT;
		md->prev = ARCOFI_PORT_AUDIO_OUT_VOLUME;
		md->next = AUDIO_MIXER_LAST;
		goto mute;
	case ARCOFI_PORT_AUDIO_SPKR_MUTE:
		md->type = AUDIO_MIXER_ENUM;
		md->mixer_class = ARCOFI_CLASS_OUTPUT;
		md->prev = ARCOFI_PORT_AUDIO_SPKR_VOLUME;
		md->next = AUDIO_MIXER_LAST;
		/* goto mute; */
mute:
		strlcpy(md->label.name, AudioNmute, sizeof md->label.name);
		md->un.e.num_mem = 2;
		strlcpy(md->un.e.member[0].label.name, AudioNoff,
		    sizeof md->un.e.member[0].label.name);
		md->un.e.member[0].ord = 0;
		strlcpy(md->un.e.member[1].label.name, AudioNon,
		    sizeof md->un.e.member[1].label.name);
		md->un.e.member[1].ord = 1;
		break;

	/* classes */
	case ARCOFI_CLASS_INPUT:
		md->type = AUDIO_MIXER_CLASS;
		md->mixer_class = ARCOFI_CLASS_INPUT;
		md->prev = AUDIO_MIXER_LAST;
		md->next = AUDIO_MIXER_LAST;
		strlcpy(md->label.name, AudioCinputs,
		    sizeof md->label.name);
		break;
	case ARCOFI_CLASS_OUTPUT:
		md->type = AUDIO_MIXER_CLASS;
		md->mixer_class = ARCOFI_CLASS_OUTPUT;
		md->prev = AUDIO_MIXER_LAST;
		md->next = AUDIO_MIXER_LAST;
		strlcpy(md->label.name, AudioCoutputs,
		    sizeof md->label.name);
		break;
	}

	return 0;
}

int
arcofi_get_props(void *v)
{
	return 0;
}

int
arcofi_hwintr(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	uint8_t *cur, *past;
	uint8_t csr, fir, data;
	int rc = 0;

	csr = arcofi_read(sc, ARCOFI_CSR);
	if ((csr & CSR_INTR_REQUEST) == 0)
		return 0;

	fir = arcofi_read(sc, ARCOFI_FIFO_IR);

	/* receive */
	if (fir & FIFO_IR_EVENT(FIFO_IR_IN_HALF_EMPTY)) {
		rc = 1;
		cur = sc->sc_recv.buf;
		past = sc->sc_recv.past;

		while ((arcofi_read(sc, ARCOFI_FIFO_SR) &
		    FIFO_SR_IN_EMPTY) == 0) {
			data = arcofi_read(sc, ARCOFI_FIFO_DATA);
			if (cur != NULL && cur != past) {
				*cur++ = data;
				if (cur == past) {
					softintr_schedule(sc->sc_sih);
					break;
				}
			}
		}
		sc->sc_recv.buf = cur;

		if (cur == NULL || cur == past) {
			/* underrun, disable further interrupts */
			arcofi_write(sc, ARCOFI_FIFO_IR,
			    arcofi_read(sc, ARCOFI_FIFO_IR) &
			    ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
		}
	}

	/* xmit */
	if (fir & FIFO_IR_EVENT(FIFO_IR_OUT_HALF_EMPTY)) {
		rc = 1;
		cur = sc->sc_xmit.buf;
		past = sc->sc_xmit.past;
		if (cur != NULL) {
			while ((arcofi_read(sc, ARCOFI_FIFO_SR) &
			    FIFO_SR_OUT_FULL) == 0) {
				if (cur != past)
					arcofi_write(sc, ARCOFI_FIFO_DATA,
					    *cur++);
				if (cur == past) {
					softintr_schedule(sc->sc_sih);
					break;
				}
			}
		}
		if (cur == NULL || cur == past) {
			/* disable further interrupts */
			arcofi_write(sc, ARCOFI_FIFO_IR,
			    arcofi_read(sc, ARCOFI_FIFO_IR) &
			    ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
		}
		sc->sc_xmit.buf = cur;
	}

	/* drain */
	if (fir & FIFO_IR_EVENT(FIFO_IR_OUT_EMPTY)) {
		rc = 1;
		arcofi_write(sc, ARCOFI_FIFO_IR,
		    arcofi_read(sc, ARCOFI_FIFO_IR) &
		    ~FIFO_IR_ENABLE(FIFO_IR_OUT_EMPTY));
		wakeup(&sc->sc_xmit);
	}

#ifdef ARCOFI_DEBUG
	if (rc == 0)
		printf("%s: unclaimed interrupt, csr %02x fir %02x fsr %02x\n",
		    sc->sc_dev.dv_xname, csr, fir,
		    arcofi_read(sc, ARCOFI_FIFO_SR));
#endif

	return rc;
}

void
arcofi_swintr(void *v)
{
	struct arcofi_softc *sc = (struct arcofi_softc *)v;
	int action;

	action = 0;
	mtx_enter(&audio_lock);
	if (sc->sc_recv.buf != NULL && sc->sc_recv.buf == sc->sc_recv.past)
		action |= AUMODE_RECORD;
	if (sc->sc_xmit.buf != NULL && sc->sc_xmit.buf == sc->sc_xmit.past)
		action |= AUMODE_PLAY;

	if (action & AUMODE_RECORD) {
		if (sc->sc_recv.cb)
			sc->sc_recv.cb(sc->sc_recv.cbarg);
	}
	if (action & AUMODE_PLAY) {
		if (sc->sc_xmit.cb)
			sc->sc_xmit.cb(sc->sc_xmit.cbarg);
	}
	mtx_leave(&audio_lock);
}

int
arcofi_cmd(struct arcofi_softc *sc, uint8_t cmd, const uint8_t *data)
{
	size_t len;
	uint8_t csr;
	int cnt;
	static const uint8_t cmdlen[] = {
	    [SOP_0] = 4,
	    [COP_1] = 4,
	    [COP_2] = 2,
	    [COP_3] = 2,
	    [SOP_4] = 1,
	    [SOP_5] = 1,
	    [SOP_6] = 1,
	    [SOP_7] = 1,
	    [COP_8] = 2,
	    [COP_9] = 4,
	    [COP_A] = 8,
	    [COP_B] = 2,
	    [COP_C] = 8,
	    [COP_D] = 4,
	    [COP_E] = 4
	};

	/*
	 * Compute command length.
	 */
	if (cmd >= nitems(cmdlen))
		return EINVAL;
	len = cmdlen[cmd];

	mtx_enter(&audio_lock);

	/*
	 * Disable all FIFO processing.
	 */
	csr = arcofi_read(sc, ARCOFI_CSR);
	arcofi_write(sc, ARCOFI_CSR,
	    csr & ~(CSR_DATA_FIFO_ENABLE | CSR_CTRL_FIFO_ENABLE));

	/*
	 * Fill the FIFO with the command bytes.
	 */
	arcofi_write(sc, ARCOFI_FIFO_CTRL, CMDR_PU | CMDR_WRITE | cmd);
	for (; len != 0; len--)
		arcofi_write(sc, ARCOFI_FIFO_CTRL, *data++);

	/*
	 * Enable command processing.
	 */
	arcofi_write(sc, ARCOFI_CSR,
	    (csr & ~CSR_DATA_FIFO_ENABLE) | CSR_CTRL_FIFO_ENABLE);

	/*
	 * Wait for the command FIFO to be empty.
	 */
	cnt = 100;
	while ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_CTRL_EMPTY) == 0) {
		if (cnt-- == 0) {
			mtx_leave(&audio_lock);
			return EBUSY;
		}
		delay(10);
	}

	arcofi_write(sc, ARCOFI_CSR, csr);

	mtx_leave(&audio_lock);
	return 0;
}

void
arcofi_attach(struct arcofi_softc *sc, const char *version)
{
	int rc;
	uint8_t cmd[4];

	/*
	 * Reset logic.
	 */
	arcofi_write(sc, ARCOFI_ID, 0);
	delay(100000);
	arcofi_write(sc, ARCOFI_CSR, 0);

	/*
	 * Initialize the chip to default settings (8 bit, u-Law).
	 */
	sc->sc_active.cr3 =
	    arcofi_portmask_to_cr3(AUDIO_SPEAKER) | CR3_OPMODE_NORMAL;
	sc->sc_active.cr4 = CR4_TM | CR4_ULAW;
	sc->sc_active.gr_idx = sc->sc_active.gx_idx = 1 + NEGATIVE_GAINS;
	sc->sc_active.output_mute = 0;
	bcopy(&sc->sc_active, &sc->sc_shadow, sizeof(sc->sc_active));

	/* clear CRAM */
	cmd[0] = CR1_IDR;
	if ((rc = arcofi_cmd(sc, SOP_4, cmd)) != 0)
		goto error;
	delay(1000);

	/* set gain values before enabling them in CR1 */
	cmd[0] = arcofi_gains[sc->sc_active.gr_idx] >> 8;
	cmd[1] = arcofi_gains[sc->sc_active.gr_idx];
	if ((rc = arcofi_cmd(sc, COP_2, cmd)) != 0)
		goto error;
	/* same value for gx... */
	if ((rc = arcofi_cmd(sc, COP_B, cmd)) != 0)
		goto error;

	/* set all CR registers at once */
	cmd[0] = sc->sc_active.cr4;
	cmd[1] = sc->sc_active.cr3;
	cmd[2] = CR2_SD | CR2_SC | CR2_SB | CR2_SA | CR2_ELS | CR2_AM | CR2_EFC;
	cmd[3] = CR1_GR | CR1_GX;
	if ((rc = arcofi_cmd(sc, SOP_0, cmd)) != 0)
		goto error;

	arcofi_write(sc, ARCOFI_FIFO_IR, 0);
	arcofi_write(sc, ARCOFI_CSR, CSR_INTR_ENABLE);

	strlcpy(sc->sc_audio_device.name, arcofi_cd.cd_name,
	    sizeof(sc->sc_audio_device.name));
	strlcpy(sc->sc_audio_device.version, version,
	    sizeof(sc->sc_audio_device.version));
	strlcpy(sc->sc_audio_device.config, sc->sc_dev.dv_xname,
	    sizeof(sc->sc_audio_device.config));

	audio_attach_mi(&arcofi_hw_if, sc, &sc->sc_dev);
	return;

error:
	arcofi_write(sc, ARCOFI_ID, 0);
	printf("%s: command failed, error %d\n", __func__, rc);
}

license: unknown


code:
/*	$OpenBSD: arcofivar.h,v 1.2 2011/12/25 00:07:27 miod Exp $	*/

/*
 * Copyright (c) 2011 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.
 */

#define	ARCOFI_NREGS		6

struct arcofi_softc {
	struct device		sc_dev;
	bus_addr_t		sc_reg[ARCOFI_NREGS];
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	struct audio_device	sc_audio_device;
	void			*sc_sih;

	int			sc_open;
	int			sc_mode;

	struct {
		uint8_t	cr3, cr4;
		uint	gr_idx, gx_idx;
		int	output_mute;
	}			sc_active,
				sc_shadow;

	struct {
		uint8_t	*buf;
		uint8_t	*past;
		void	(*cb)(void *);
		void	*cbarg;
	}			sc_recv,
				sc_xmit;
};

void	arcofi_attach(struct arcofi_softc *, const char *);
int	arcofi_hwintr(void *);
void	arcofi_swintr(void *);