/*	$OpenBSD: nec86hw.c,v 1.3 2015/06/25 06:43:45 ratchov Exp $	*/
/*	$NecBSD: nec86hw.c,v 1.13 1998/03/14 07:04:54 kmatsuda Exp $	*/
/*	$NetBSD$	*/

/*
 * [NetBSD for NEC PC-98 series]
 *  Copyright (c) 1996, 1997, 1998
 *	NetBSD/pc98 porting staff. 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.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 * 
 * 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.
 */

/*
 * nec86hw.c
 *
 * NEC PC-9801-86 SoundBoard PCM driver for NetBSD/pc98.
 * Written by NAGAO Tadaaki, Feb 10, 1996.
 *
 * Modified by N. Honda, Mar 7, 1998
 */
/*
 * TODO:
 * - Add PC-9801-73 support.
 * - Fake the mixer device with electric volumes.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/proc.h>

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

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

#if 0
#include <dev/ic/ym2203reg.h>
#endif
#include <luna88k/cbus/nec86reg.h>
#include <luna88k/cbus/nec86hwvar.h>
#include <luna88k/cbus/nec86var.h>

#ifdef AUDIO_DEBUG
extern void Dprintf(const char *, ...);
#define DPRINTF(x)	if (nec86hwdebug) printf x
#define DPRINTF2(l, x)	if (nec86hwdebug >= l) printf x
int	nec86hwdebug = 3;
#else	/* !AUDIO_DEBUG */
#define DPRINTF(x)
#define DPRINTF2(l, x)
#endif	/* AUDIO_DEBUG */

#ifndef VOLUME_DELAY
/*
 * XXX -  Delaytime in microsecond after an access
 *	  to the volume I/O port.
 */
#define VOLUME_DELAY	10
#endif	/* !VOLUME_DELAY */

/*
 * Sampling rates supported by the hardware.
 */
static int nec86hw_rate_table[NEC86HW_NRATE_TYPE][NEC86_NRATE] = {
	/* NEC PC-9801-86 or its full compatibles */
	{ 44100, 33075, 22050, 16538, 11025, 8269, 5513, 4134 },
	/* Some earlier versions of Q-Vision's WaveMaster */
	{ 44100, 33075, 22050, 16000, 11025, 8000, 5513, 4000 },
};

static struct audio_params nec86hw_audio_default =
	{44100, AUDIO_ENCODING_SLINEAR_LE, 16, 2, 1, 2};

int nec86hw_set_output_block(struct nec86hw_softc *, int);
int nec86hw_set_input_block(struct nec86hw_softc *, int);

/*
 * Function tables.
 */
static struct nec86hw_functable_entry nec86hw_functable[] = {
	/* precision, channels,
	   output function, input function (without resampling),
	   output function, input function (with resampling) */
	{ 8, 1, 
	    nec86fifo_output_mono_8_direct, nec86fifo_input_mono_8_direct },
	{ 16, 1,
	    nec86fifo_output_mono_16_direct, nec86fifo_input_mono_16_direct },
	{ 8, 2,
	    nec86fifo_output_stereo_8_direct, nec86fifo_input_stereo_8_direct },
	{ 16, 2,
	    nec86fifo_output_stereo_16_direct, nec86fifo_input_stereo_16_direct },
};
#define NFUNCTABLEENTRY	(sizeof(nec86hw_functable) / sizeof(nec86hw_functable[0]))

/*
 * Attach hardware to driver, attach hardware driver to audio
 * pseudo-device driver.
 */
void
nec86hw_attach(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	/* Set default encoding. */
	sc->func_fifo_output = nec86fifo_output_mono_8_direct;
	sc->func_fifo_input = nec86fifo_input_mono_8_direct;
	(void) nec86hw_set_params(sc, AUMODE_RECORD, 0,
	    &nec86hw_audio_default, &nec86hw_audio_default);
	(void) nec86hw_set_params(sc, AUMODE_PLAY,   0,
	    &nec86hw_audio_default, &nec86hw_audio_default);

	/* Set default ports. */
	(void) nec86hw_set_in_port(sc, NEC86HW_INPUT_MIXER);
	(void) nec86hw_set_out_port(sc, NEC86HW_OUTPUT_MIXER);

	/* Set default gains. */
	nec86hw_set_volume(sc, NEC86_VOLUME_PORT_OPNAD, NEC86_MAXVOL);
	nec86hw_set_volume(sc, NEC86_VOLUME_PORT_OPNAI, NEC86_MAXVOL);
	nec86hw_set_volume(sc, NEC86_VOLUME_PORT_LINED, NEC86_MAXVOL);
	nec86hw_set_volume(sc, NEC86_VOLUME_PORT_LINEI, NEC86_MAXVOL);
	nec86hw_set_volume(sc, NEC86_VOLUME_PORT_PCMD, NEC86_MAXVOL);

	/* Internal Speaker ON */
	nec86hw_speaker_ctl(sc, SPKR_ON);

	/* Set miscellanous stuffs. */
	data = bus_space_read_1(iot, ioh, NEC86_CTRL);
	data &= NEC86_CTRL_MASK_PAN | NEC86_CTRL_MASK_PORT;
	data |= NEC86_CTRL_PAN_L | NEC86_CTRL_PAN_R;
	data |= NEC86_CTRL_PORT_STD;
	bus_space_write_1(iot, ioh, NEC86_CTRL, data);
}

/*
 * Various routines to interface to higher level audio driver.
 */

int
nec86hw_open(void *arg, int flags)
{
	struct nec86hw_softc *sc = arg;
	DPRINTF(("nec86hw_open: sc=%p\n", sc));

	if (sc->sc_open != 0 || nec86hw_reset(sc) != 0)
		return ENXIO;

	sc->sc_open = 1;
	sc->sc_intr = NULL;
	sc->sc_arg = NULL;

	sc->conv_acc = 0;
	sc->conv_last0 = 0;
	sc->conv_last0_l = 0;
	sc->conv_last0_r = 0;
	sc->conv_last1 = 0;
	sc->conv_last1_l = 0;
	sc->conv_last1_r = 0;

	/*
	 * Leave most things including sampling format and rate as they were.
	 * (See audio_open() in audio.c)
	 */

	DPRINTF(("nec86hw_open: opened\n"));

	return 0;
}

void
nec86hw_close(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_close: sc=%p\n", sc));

	sc->sc_open = 0;
	sc->sc_intr = NULL;
	(void) nec86hw_reset(sc);

	DPRINTF(("nec86hw_close: closed\n"));
}

int
nec86hw_set_params(void *addr, int mode, int usemode, struct audio_params *p,
    struct audio_params *r)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;
	int rate_type = NEC86HW_RATE_TYPE(sc->sc_cfgflags);
	u_int prec, enc;

	if ((p->channels != 1) && (p->channels != 2))
		return EINVAL;

	if (p->precision == 8)
		p->encoding = AUDIO_ENCODING_ULINEAR_LE;
	else {
		p->precision = 16;
		p->encoding = AUDIO_ENCODING_SLINEAR_LE;
	}
	sc->channels = p->channels;
	sc->precision = prec;
	sc->encoding = enc;
	sc->hw_orate_bits = nec86hw_rate_bits(sc, p->sample_rate);
	sc->sc_orate = p->sample_rate = sc->hw_orate =
	    nec86hw_rate_table[rate_type][sc->hw_orate_bits];
	sc->hw_irate_bits = nec86hw_rate_bits(sc, r->sample_rate);
	sc->sc_irate = r->sample_rate = sc->hw_irate =
	    nec86hw_rate_table[rate_type][sc->hw_irate_bits];
	return 0;
}

int
nec86hw_query_encoding(void *addr, struct audio_encoding *fp)
{

	switch (fp->index) {
	case 0:
		strlcpy(fp->name, AudioEulinear, sizeof(fp->name));
		fp->encoding = AUDIO_ENCODING_ULINEAR;
		fp->precision = 8;
		fp->bps = 1;
		fp->msb = 1;
		fp->flags = 0;
		break;
	case 1:
		strlcpy(fp->name, AudioEslinear_le, sizeof(fp->name));
		fp->encoding = AUDIO_ENCODING_SLINEAR_LE;
		fp->precision = 16;
		fp->bps = 2;
		fp->msb = 1;	/* is this OK? */
		fp->flags = 0;
		break;
	default:
		return EINVAL;
		/*NOTREACHED*/
	}

	return 0;
}

int
nec86hw_round_blocksize(void *addr, int blk)
{
	u_int base = NEC86_INTRBLK_UNIT;

	if (blk < NEC86_INTRBLK_UNIT * 2)
		return NEC86_INTRBLK_UNIT * 2;

	for ( ; base <= blk; base *= 2)
		;
	return base / 2;
}

int
nec86hw_set_out_port(void *addr, int port)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_set_out_port:\n"));

	if (port != NEC86HW_OUTPUT_MIXER)
		return EINVAL;

	sc->out_port = port;

	return 0;
}

int
nec86hw_get_out_port(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_get_out_port:\n"));

	return sc->out_port;
}

int
nec86hw_set_in_port(void *addr, int port)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_set_in_port:\n"));

	if (port != NEC86HW_INPUT_MIXER)
		return EINVAL;

	sc->in_port = port;

	return 0;
}

int
nec86hw_get_in_port(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_get_in_port:\n"));

	return sc->in_port;
}

int
nec86hw_commit_settings(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;
	int i;

	/*
	 * Determine which function should be used to write to/read from
	 * the FIFO ring buffer.
	 */

	for (i = 0; i < NFUNCTABLEENTRY; i++) {
		if ((nec86hw_functable[i].precision == sc->precision)
		    && (nec86hw_functable[i].channels == sc->channels))
			break;
	}

	if (i >= NFUNCTABLEENTRY) {
		/* ??? -  This should never happen. */
		return EINVAL;
	}

	sc->func_fifo_output =
	    nec86hw_functable[i].func_fifo_output_direct;
	sc->func_fifo_input =
	    nec86hw_functable[i].func_fifo_input_direct;
	return 0;
}

int
nec86hw_setfd(void *addr, int flag)
{
	DPRINTF(("nec86hw_setfd:\n"));

	/* Can't do full-duplex */
	return ENOTTY;
}

int
nec86hw_mixer_set_port(void *addr, mixer_ctrl_t *cp)
{
	DPRINTF(("nec86hw_mixer_set_port:\n"));

	/* not yet implemented */
	return ENXIO;
}

int
nec86hw_mixer_get_port(void *addr, mixer_ctrl_t *cp)
{
	DPRINTF(("nec86hw_mixer_get_port:\n"));

	/* not yet implemented */
	return ENXIO;
}

int
nec86hw_mixer_query_devinfo(void *addr, mixer_devinfo_t *dip)
{
	DPRINTF(("nec86hw_mixer_query_devinfo:\n"));

	/* not yet implemented */
	return ENXIO;
}

int
nec86hw_set_output_block(struct nec86hw_softc *sc, int cc)
{
	int bpf, hw_blocksize, watermark;

	bpf = (sc->channels * sc->precision) / NBBY;	/* bytes per frame */
	sc->pdma_count = cc / bpf;

	/* Size of the block */
	hw_blocksize = sc->pdma_count * (sc->precision / NBBY * 2);

	/* How many chunks the block should be divided into. */
	sc->pdma_nchunk =
	    ((hw_blocksize * (WATERMARK_MAX_RATIO + WATERMARK_RATIO_OUT))
	    + (NEC86_BUFFSIZE * WATERMARK_MAX_RATIO - 1))
	    / (NEC86_BUFFSIZE * WATERMARK_MAX_RATIO);

	/* Calculate the watermark. */
	watermark =
	    (hw_blocksize * WATERMARK_RATIO_OUT)
	    / (sc->pdma_nchunk * WATERMARK_MAX_RATIO);
	sc->pdma_watermark = nec86hw_round_watermark(watermark);

	/*
	 * If the formula (*1) does not hold, watermark may be less
	 * than the minimum watermark (NEC86_INTRBLK_UNIT) and then
	 * nec86hw_round_watermark() returns that minimum value.
	 * In this case, the ring buffer on the hardware will potentially
	 * overflow.
	 * To avoid such a case, here calculate pdma_nchunk again.
	 *
	 * (*1)  NEC86_BUFFSIZE / NEC86_INTRBLK_UNIT
	 *           >= WATERMARK_MAX_RATIO / WATERMARK_RATIO_OUT + 1
	 */
	if (hw_blocksize + sc->pdma_watermark > NEC86_BUFFSIZE) {
		sc->pdma_nchunk =
		    (hw_blocksize + (NEC86_BUFFSIZE - sc->pdma_watermark - 1))
		    / (NEC86_BUFFSIZE - sc->pdma_watermark);
	}

	DPRINTF2(2, ("nec86hw_pdma_output: cc=%d count=%d hw_blocksize=%d "
	    "watermark=%d nchunk=%d ptr=%p\n",
	    cc, sc->pdma_count, hw_blocksize, sc->pdma_watermark,
	    sc->pdma_nchunk, sc->pdma_ptr));
	return 0;
}

int
nec86hw_set_input_block(struct nec86hw_softc *sc, int cc)
{
	int bpf, hw_blocksize, watermark, maxwatermark;

	bpf = (sc->channels * sc->precision) / NBBY;	/* bytes per frame */
	sc->pdma_count = cc / bpf;

	/* Maximum watermark. */
	watermark =
	    (NEC86_BUFFSIZE * WATERMARK_RATIO_IN) / WATERMARK_MAX_RATIO;
	maxwatermark = nec86hw_round_watermark(watermark);

	/* Size of the block */
	hw_blocksize = sc->pdma_count * (sc->precision / NBBY * 2);

	/* How many chunks the block should be divided into. */
	sc->pdma_nchunk = (hw_blocksize + (maxwatermark - 1)) / maxwatermark;

	/* Calculate the watermark. */
	watermark = (hw_blocksize / sc->pdma_nchunk) + (NEC86_INTRBLK_UNIT - 1);
	sc->pdma_watermark = nec86hw_round_watermark(watermark);

	DPRINTF2(2, ("nec86hw_pdma_input: cc=%d count=%d hw_blocksize=%d "
	    "watermark=%d nchunk=%d ptr=%p\n",
	    cc, sc->pdma_count, hw_blocksize, sc->pdma_watermark,
	    sc->pdma_nchunk, sc->pdma_ptr));
	return 0;
}

int
nec86hw_pdma_init_output(void *addr, void *buf, int cc)
{
	struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	nec86hw_halt_pdma(addr);
	nec86hw_reset_fifo(sc);
	nec86hw_set_mode_playing(sc);
	nec86hw_set_rate_real(sc, sc->hw_orate_bits);
	nec86hw_set_precision_real(sc, sc->precision);

	nec86hw_set_output_block(sc, cc);
	nec86hw_reset_fifo(sc);

	nec86hw_enable_fifointr(sc);
	nec86hw_set_watermark(sc, sc->pdma_watermark);
	nec86hw_disable_fifointr(sc);

	return 0;
}

int
nec86hw_pdma_init_input(void *addr, void *buf, int cc)
{
	struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	nec86hw_halt_pdma(addr);
	nec86hw_reset_fifo(sc);
	nec86hw_set_mode_recording(sc);
	nec86hw_set_rate_real(sc, sc->hw_irate_bits);
	nec86hw_set_precision_real(sc, sc->precision);

	nec86hw_set_input_block(sc, cc);
	nec86hw_reset_fifo(sc);

	nec86hw_enable_fifointr(sc);
	nec86hw_set_watermark(sc, sc->pdma_watermark);
	nec86hw_disable_fifointr(sc);

	return 0;
}

int
nec86hw_pdma_output(void *addr, void *p, int cc, void (*intr)(void *),
    void *arg)
{
	struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;
	int bpf;

	bpf = (sc->channels * sc->precision) / NBBY;	/* bytes per frame */
	if ((cc % bpf) != 0) {
		DPRINTF(("nec86hw_pdma_output: odd bytes\n"));
		return EIO;
	}

	sc->sc_intr = intr;
	sc->sc_arg = arg;

	/*
	 * We divide the data block into some relatively small chunks if the
	 * block is so large that the ring buffer on the hardware would
	 * overflow.
	 * In this case, we send the first chunk now and the rest in the
	 * interrupt handler.
	 */

	/* Set up for pseudo-DMA. */
	sc->pdma_ptr = (u_char *) p;
	sc->pdma_padded = 0;
	sc->pdma_mode = PDMA_MODE_OUTPUT;
	if (sc->pdma_count != cc / bpf)
		nec86hw_set_output_block(sc, cc);

	if (!sc->intr_busy) {
		nec86hw_set_precision_real(sc, sc->precision);
		nec86hw_set_rate_real(sc, sc->hw_orate_bits);
		nec86hw_set_mode_playing(sc);
		nec86hw_reset_fifo(sc);
	}

	/*
	 * Send the first chunk.  The rest will be sent in the interrupt
	 * handler nec86hw_intr(), if any.
	 */
	nec86hw_disable_fifointr(sc);
	nec86hw_output_chunk(sc);
	nec86hw_enable_fifointr(sc);

	if (!sc->intr_busy) {
		/* Now start playing. */
		nec86hw_start_fifo(sc);

		sc->intr_busy = 1;
	}

	nec86hw_set_watermark(sc, sc->pdma_watermark);
	return 0;
}

int
nec86hw_pdma_input(void *addr, void *p, int cc, void (*intr)(void *),
    void *arg)
{
	struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;
	int bpf;

	bpf = (sc->channels * sc->precision) / NBBY;	/* bytes per frame */
	if ((cc % bpf) != 0) {
		DPRINTF(("nec86hw_pdma_input: odd bytes\n"));
		return EIO;
	}

	sc->sc_intr = intr;
	sc->sc_arg = arg;

	/* Set up for pseudo-DMA. */
	sc->pdma_ptr = (u_int8_t *) p;
	sc->pdma_padded = 0;	/* Never padded in recording, though. */
	sc->pdma_mode = PDMA_MODE_INPUT;

	if (sc->pdma_count != cc / bpf)
		nec86hw_set_input_block(sc, cc);

	if (!sc->intr_busy) {
		nec86hw_set_precision_real(sc, sc->precision);
		nec86hw_set_rate_real(sc, sc->hw_irate_bits);
		nec86hw_set_mode_recording(sc);
		nec86hw_reset_fifo(sc);

		/* Now start recording. */
		nec86hw_enable_fifointr(sc);
		nec86hw_start_fifo(sc);

		sc->intr_busy = 1;
	}

	nec86hw_set_watermark(sc, sc->pdma_watermark);

	return 0;
}

int
nec86hw_halt_pdma(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_halt_pdma: sc=%p\n", sc));

	nec86hw_stop_fifo(sc);
	nec86hw_disable_fifointr(sc);

	sc->intr_busy = 0;

	return 0;
}

int
nec86hw_cont_pdma(void *addr)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;

	DPRINTF(("nec86hw_cont_pdma: sc=%p\n", sc));

	nec86hw_enable_fifointr(sc);
	nec86hw_start_fifo(sc);

	sc->intr_busy = 1;

	return 0;
}

int
nec86hw_speaker_ctl(void *addr, int onoff)
{
	register struct nec86hw_softc *sc = (struct nec86hw_softc *) addr;
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;

	DPRINTF(("nec86hw_speaker_ctl:\n"));

	switch (onoff) {
	case SPKR_ON:
		bus_space_write_1(iot, ioh, NEC86_VOLUME, 0x0d1);
		delay(VOLUME_DELAY);
	break;
	case SPKR_OFF:
		bus_space_write_1(iot, ioh, NEC86_VOLUME, 0x0d0);
		delay(VOLUME_DELAY);
	break;
	default:
		return EINVAL;
	}

	return 0;
}

u_int8_t
nec86hw_rate_bits(struct nec86hw_softc *sc, u_long sr)
{
	int i;
	u_long rval, hr, min;
	int rate_type = NEC86HW_RATE_TYPE(sc->sc_cfgflags);

	/* Look for the minimum hardware rate equal to or more than sr. */
	min = 0;
	rval = 0;
	for (i = 0; i < NEC86_NRATE; i++) {
		hr = nec86hw_rate_table[rate_type][i];
		if ((hr >= sr) && ((min == 0) || (min > hr))) {
			min = hr;
		 	rval = (u_int8_t) i;
		}
	}

	return rval;
}

int
nec86hw_round_watermark(int wm)
{
	wm = (wm / NEC86_INTRBLK_UNIT) * NEC86_INTRBLK_UNIT;

	if (wm < NEC86_INTRBLK_UNIT)
		wm = NEC86_INTRBLK_UNIT;

	return wm;
}

/*
 * Lower-level routines.
 */

int
nec86hw_reset(struct nec86hw_softc *sc)
{
	nec86hw_stop_fifo(sc);
	nec86hw_disable_fifointr(sc);
	nec86hw_clear_intrflg(sc);
	nec86hw_reset_fifo(sc);

	sc->intr_busy = 0;
	sc->pdma_mode = PDMA_MODE_NONE;

	if (nec86hw_seeif_intrflg(sc))
		return -1;	/* The hardware vanished? */

	return 0;
}

/*
 * Set the mode for playing/recording.
 */
void
nec86hw_set_mode_playing(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data &= ~NEC86_FIFOCTL_RECMODE;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_set_mode_recording(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data |= NEC86_FIFOCTL_RECMODE;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

/*
 * Set the electric volumes.
 */
void
nec86hw_set_volume(struct nec86hw_softc *sc, int port, u_int8_t vol)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;

	bus_space_write_1(iot, ioh, NEC86_VOLUME,
	    NEC86_VOL_TO_BITS(port, vol));
	delay(VOLUME_DELAY);
}

/*
 * Control the FIFO ring buffer on the board.
 */
void
nec86hw_start_fifo(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	/* Start playing/recording. */
	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data |= NEC86_FIFOCTL_RUN;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_stop_fifo(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	/* Stop playing/recording. */
	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data &= ~NEC86_FIFOCTL_RUN;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_enable_fifointr(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data |= NEC86_FIFOCTL_ENBLINTR;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_disable_fifointr(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data &= ~NEC86_FIFOCTL_ENBLINTR;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

int
nec86hw_seeif_intrflg(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);

	return (data & NEC86_FIFOCTL_INTRFLG);
}

void
nec86hw_clear_intrflg(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data &= ~NEC86_FIFOCTL_INTRFLG;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
	data |= NEC86_FIFOCTL_INTRFLG;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_reset_fifo(struct nec86hw_softc *sc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data |= NEC86_FIFOCTL_INIT;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
	data &= ~NEC86_FIFOCTL_INIT;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

void
nec86hw_set_watermark(struct nec86hw_softc *sc, int wm)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	/*
	 * Must be called after nec86hw_start_fifo() and
	 * nec86hw_enable_fifointr() are both called.
	 */

#ifdef DIAGNOSTIC
	if ((wm < NEC86_INTRBLK_UNIT) || (wm > NEC86_BUFFSIZE)
	    || ((wm % NEC86_INTRBLK_UNIT) != 0))
		printf("nec86hw_set_watermark: invalid watermark %d\n", wm);
#endif	/* DIAGNOSTIC */

	/*
	 * The interrupt occurs when the number of bytes in the FIFO ring
	 * buffer exceeds this watarmark.
	 */
	bus_space_write_1(iot, ioh, NEC86_FIFOINTRBLK,
	    (wm / NEC86_INTRBLK_UNIT) - 1);
}

void
nec86hw_set_precision_real(struct nec86hw_softc *sc, u_int prec)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_CTRL);
	data &= ~NEC86_CTRL_8BITS;
	if (prec == 8)
		data |= NEC86_CTRL_8BITS;
	bus_space_write_1(iot, ioh, NEC86_CTRL, data);
}

void
nec86hw_set_rate_real(struct nec86hw_softc *sc, u_int8_t bits)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t data;

	data = bus_space_read_1(iot, ioh, NEC86_FIFOCTL);
	data &= ~NEC86_FIFOCTL_MASK_RATE;
	data |= bits & NEC86_FIFOCTL_MASK_RATE;
	bus_space_write_1(iot, ioh, NEC86_FIFOCTL, data);
}

/*
 * Write data to the FIFO ring buffer on the board.
 */
void
nec86hw_output_chunk(struct nec86hw_softc *sc)
{
	int cc, nbyte;

	if (sc->pdma_nchunk > 0) {
		/* Update chunksize and then send the chunk to the board. */

		/* chunksize in frames */
		cc = sc->pdma_count / sc->pdma_nchunk;

		nbyte = (*sc->func_fifo_output)(sc, cc);

		DPRINTF2(3, ("nec86hw_output_chunk: sc->pdma_count=%d "
		    "sc->pdma_nchunk=%d cc=%d nbyte=%d ptr=%p\n",
		    sc->pdma_count, sc->pdma_nchunk, cc, nbyte, sc->pdma_ptr));

		sc->pdma_nchunk--;
		sc->pdma_count -= cc;
		sc->pdma_ptr += nbyte; 
	} else {
		/* ??? -  This should never happen. */
		nbyte = 0;
		DPRINTF(("nec86hw_output_chunk: sc->pdma_nchunk=%d\n",
		    sc->pdma_nchunk));
	}

	/*
	 * If size of the sent chunk is not enough, then pad out the buffer
	 * with zero's.
	 */
	if (nbyte <= sc->pdma_watermark) {
		nec86fifo_padding(sc, sc->pdma_watermark);
		sc->pdma_padded = 1;

		DPRINTF(("nec86hw_output_chunk: write padding zero's\n"));
	}
}

/*
 * Routines to write data directly.
 */
int
nec86fifo_output_mono_8_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;
	register u_int8_t d;

	for (i = 0; i < cc; i++) {
		d = *p++;
		d ^= 0x80;	/* unsigned -> signed */
		/* Fake monoral playing by duplicating a sample. */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, d);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, d);
	}

	return cc * 2;
}

int
nec86fifo_output_mono_16_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;

	for (i = 0; i < cc; i++) {
		/* Fake monoral playing by duplicating a sample. */
#if BYTE_ORDER == BIG_ENDIAN
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
#else	/* little endian -> big endian */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
#endif
		p += 2;
	}

	return cc * 4;
}

int
nec86fifo_output_stereo_8_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;

	for (i = 0; i < cc; i++) {
		/* unsigned -> signed (L) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, (*p++) ^ 0x80);
		/* unsigned -> signed (R) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, (*p++) ^ 0x80);
	}

	return cc * 2;
}

int
nec86fifo_output_stereo_16_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;

	for (i = 0; i < cc; i++) {
#if BYTE_ORDER == BIG_ENDIAN
		/* (L) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		p += 2;
		/* (R) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		p += 2;
#else
		/* little endian -> big endian */
		/* (L) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		p += 2;
		/* (R) */
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *(p + 1));
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, *p);
		p += 2;
#endif
	}

	return cc * 4;
}

/*
 * Routines to write data with resampling. (linear interpolation)
 */
int
nec86fifo_output_mono_8_resamp(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;
	register int rval;
	register u_int8_t d;
	register u_int8_t d0, d1;
	register u_long acc, orate, hw_orate;

	rval = 0;

	orate = sc->sc_orate;
	hw_orate = sc->hw_orate;
	acc = sc->conv_acc;
	d0 = (u_int8_t) sc->conv_last0;
	d1 = (u_int8_t) sc->conv_last1;

	for (i = 0; i < cc; i++) {
		d0 = d1;
		d1 = *p++;

		while (acc <= hw_orate) {
			/* Linear interpolation. */
			d = ((d0 * (hw_orate - acc)) + (d1 * acc)) / hw_orate;
			/* unsigned -> signed */
			d ^= 0x80;

			/* Fake monoral playing by duplicating a sample. */
			bus_space_write_1(iot, ioh, NEC86_FIFODATA, d);
			bus_space_write_1(iot, ioh, NEC86_FIFODATA, d);

			acc += orate;
			rval += 2;
		}

		acc -= hw_orate;
	}

	sc->conv_acc = acc;
	sc->conv_last0 = (u_short) d0;
	sc->conv_last1 = (u_short) d1;

	return rval;
}

int
nec86fifo_output_mono_16_resamp(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;
	register int rval;
	register u_short d, d0, d1;
	register u_long acc, orate, hw_orate;

	rval = 0;

	orate = sc->sc_orate;
	hw_orate = sc->hw_orate;
	acc = sc->conv_acc;
	d0 = sc->conv_last0;
	d1 = sc->conv_last1;

	for (i = 0; i < cc; i++) {
		d0 = d1;
		/* little endian signed -> unsigned */
		d1 = (*p | (*(p + 1) << 8)) ^ 0x8000;
		p += 2;

		while (acc <= hw_orate) {
			/* Linear interpolation. */
			d = ((d0 * (hw_orate - acc)) + (d1 * acc)) / hw_orate;
			/* unsigned -> signed */
			d ^= 0x8000;

			/* Fake monoral playing by duplicating a sample. */
			bus_space_write_1(iot, ioh, NEC86_FIFODATA,
			    (d >> 8) & 0xff); /* -> big endian */
			bus_space_write_1(iot, ioh, NEC86_FIFODATA, d & 0xff);
			bus_space_write_1(iot, ioh, NEC86_FIFODATA,
			    (d >> 8) & 0xff);
			bus_space_write_1(iot, ioh, NEC86_FIFODATA, d & 0xff);

			acc += orate;
			rval += 4;
		}

		acc -= hw_orate;
	}

	sc->conv_acc = acc;
	sc->conv_last0 = d0;
	sc->conv_last1 = d1;

	return rval;
}

/*
 * Read data from the FIFO ring buffer on the board.
 */
void
nec86hw_input_chunk(struct nec86hw_softc *sc)
{
	int cc, bpf;

	bpf = (sc->channels * sc->precision) / NBBY;
	if (sc->pdma_nchunk > 0) {
		/* Update chunksize and then receive the chunk from the board. */
		/* chunksize in frames */
		cc = sc->pdma_count / sc->pdma_nchunk;
 		(*sc->func_fifo_input)(sc, cc);

		DPRINTF2(3, ("nec86hw_input_chunk: sc->pdma_count=%d "
		    "sc->pdma_nchunk=%d cc=%d ptr=%p\n",
		    sc->pdma_count, sc->pdma_nchunk, cc, sc->pdma_ptr));

		sc->pdma_nchunk--;
		sc->pdma_count -= cc;
		sc->pdma_ptr += (cc * bpf);
	} else {
		/* ??? -  This should never happen. */
		cc = 0;
		DPRINTF(("nec86hw_input_chunk: ??? sc->pdma_nchunk=%d ???",
		    sc->pdma_nchunk));
	}
}

/*
 * Routines to read data directly.
 */
void
nec86fifo_input_mono_8_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;
	register u_int8_t d_l, d_r;

	for (i = 0; i < cc; i++) {
		/* signed -> unsigned (L) */
		d_l = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80;
		/* signed -> unsigned (R) */
		d_r = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80;

		/* Fake monoral recording by taking arithmetical mean. */
		*p++ = (d_l + d_r) / 2;
	}
}

void
nec86fifo_input_mono_16_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;
	register u_short d, d_l, d_r;

	for (i = 0; i < cc; i++) {
		/* big endian signed -> unsigned (L) */
		d_l = (bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80) << 8;
		d_l |= bus_space_read_1(iot, ioh, NEC86_FIFODATA);
		/* big endian signed -> unsigned (R) */
		d_r = (bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80) << 8;
		d_r |= bus_space_read_1(iot, ioh, NEC86_FIFODATA);

		/* Fake monoral recording by taking arithmetical mean. */
		d = (d_l + d_r) / 2;

#if BYTE_ORDER == BIG_ENDIAN
		/* -> big endian signed */
		*p++ = ((d >> 8) & 0xff) ^ 0x80;
		*p++ = d & 0xff;
#else
		/* -> little endian signed */
		*p++ = d & 0xff;
		*p++ = ((d >> 8) & 0xff) ^ 0x80;
#endif
	}
}

void
nec86fifo_input_stereo_8_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;

	for (i = 0; i < cc; i++) {
		/* signed -> unsigned (L) */
		*p++ = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80;
		/* signed -> unsigned (R) */
		*p++ = bus_space_read_1(iot, ioh, NEC86_FIFODATA) ^ 0x80;
	}
}

void
nec86fifo_input_stereo_16_direct(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	u_int8_t *p = sc->pdma_ptr;
	int i;

	for (i = 0; i < cc; i++) {
#if BYTE_ORDER == BIG_ENDIAN
		*p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (L) */
		*(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);
		p += 2;
		*p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (R) */
		*(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);
		p += 2;
#else	/* big endian -> little endian */
		*(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (L) */
		*p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);
		p += 2;
		*(p + 1) = bus_space_read_1(iot, ioh, NEC86_FIFODATA);/* (R) */
		*p = bus_space_read_1(iot, ioh, NEC86_FIFODATA);
		p += 2;
#endif
	}
}

/*
 * Write padding zero's to the FIFO ring buffer on the board.
 */
void
nec86fifo_padding(struct nec86hw_softc *sc, int cc)
{
	bus_space_tag_t iot = sc->sc_iot;
	bus_space_handle_t ioh = sc->sc_ioh;
	register int i;

	DPRINTF2(2, ("nec86fifo_padding: %d\n", cc));

	for (i = 0; i < cc; i++)
		bus_space_write_1(iot, ioh, NEC86_FIFODATA, 0);
}

/*
 * Interrupt handler.
 */
int
nec86hw_intr(void *arg)
{
	struct nec86hw_softc *sc = (struct nec86hw_softc *) arg;

	if (!nec86hw_seeif_intrflg(sc)) {
		/* Seems to be an FM sound interrupt. */
		DPRINTF(("nec86hw_intr: ??? FM sound interrupt ???\n"));
		return 0;
	}

	mtx_enter(&audio_lock);
	nec86hw_clear_intrflg(sc);

	switch(sc->pdma_mode) {
	case PDMA_MODE_OUTPUT:
		if (sc->pdma_padded) {
			/* Clear the padding zero's. */
			nec86hw_reset_fifo(sc);
			sc->pdma_padded = 0;
			DPRINTF(("nec86hw_intr: clear padding zero's\n"));
		}
		if (sc->pdma_count > 0) {
			/* Send the next chunk. */
			nec86hw_disable_fifointr(sc);
			nec86hw_output_chunk(sc);
			nec86hw_enable_fifointr(sc);
		} else
			(*sc->sc_intr)(sc->sc_arg);
		break;
	case PDMA_MODE_INPUT:
		if (sc->pdma_count > 0) {
			/* Receive the next chunk. */
			nec86hw_disable_fifointr(sc);
			nec86hw_input_chunk(sc);
			nec86hw_enable_fifointr(sc);
		}
		if (sc->pdma_count <= 0)
			(*sc->sc_intr)(sc->sc_arg);
		break;
	default:
		/* This should never happen. */
		nec86hw_stop_fifo(sc);
		nec86hw_disable_fifointr(sc);
		sc->intr_busy = 0;

		DPRINTF(("nec86hw_intr: ??? unexpected interrupt ???\n"));

		mtx_leave(&audio_lock);
		return 0;
	}

    	mtx_leave(&audio_lock);
	return 1;
}

int
nec86_get_props(void *addr)
{
	return 0; 
}
