[BACK]Return to ad1848.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / dev / ic

File: [cvs.NetBSD.org] / src / sys / dev / ic / ad1848.c (download)

Revision 1.31, Wed Nov 23 23:07:32 2011 UTC (2 years, 10 months ago) by jmcneill
Branch: MAIN
CVS Tags: yamt-pagecache-base9, yamt-pagecache-base8, yamt-pagecache-base7, yamt-pagecache-base6, yamt-pagecache-base5, yamt-pagecache-base4, tls-maxphys-base, tls-maxphys, tls-earlyentropy-base, tls-earlyentropy, rmind-smpnet-nbase, rmind-smpnet-base, rmind-smpnet, riastradh-xf86-video-intel-2-7-1-pre-2-21-15, riastradh-drm2-base3, riastradh-drm2-base2, riastradh-drm2-base1, riastradh-drm2-base, riastradh-drm2, netbsd-7-base, netbsd-7, netbsd-6-base, netbsd-6-1-RELEASE, netbsd-6-1-RC4, netbsd-6-1-RC3, netbsd-6-1-RC2, netbsd-6-1-RC1, netbsd-6-1-5-RELEASE, netbsd-6-1-4-RELEASE, netbsd-6-1-3-RELEASE, netbsd-6-1-2-RELEASE, netbsd-6-1-1-RELEASE, netbsd-6-1, netbsd-6-0-RELEASE, netbsd-6-0-RC2, netbsd-6-0-RC1, netbsd-6-0-6-RELEASE, netbsd-6-0-5-RELEASE, netbsd-6-0-4-RELEASE, netbsd-6-0-3-RELEASE, netbsd-6-0-2-RELEASE, netbsd-6-0-1-RELEASE, netbsd-6-0, netbsd-6, matt-nb6-plus-nbase, matt-nb6-plus-base, matt-nb6-plus, khorben-n900, jmcneill-usbmp-pre-base2, jmcneill-usbmp-base9, jmcneill-usbmp-base8, jmcneill-usbmp-base7, jmcneill-usbmp-base6, jmcneill-usbmp-base5, jmcneill-usbmp-base4, jmcneill-usbmp-base3, jmcneill-usbmp-base2, jmcneill-usbmp-base10, jmcneill-usbmp-base, jmcneill-usbmp, agc-symver-base, agc-symver, HEAD
Changes since 1.30: +33 -10 lines

Merge jmcneill-audiomp3 branch, which is derived from ad-audiomp2. From
the original ad-audiomp branch notes:

  Add MP locking to the audio drivers.

  Making the audio drivers MP safe is necessary before efforts
  can be made to make the VM system MP safe.

  The are two locks per device instance, an ISR lock and
  a character device lock. The ISR lock replaces calls to
  splaudio()/splx(), and will be held across calls to device
  methods which were called at splaudio() before (e.g.
  trigger_output). The character device lock is held across
  calls to nearly all of the methods, excluding some only
  used for initialization, e.g. get_locks.

Welcome to 5.99.57.

/*	$NetBSD: ad1848.c,v 1.31 2011/11/23 23:07:32 jmcneill Exp $	*/

/*-
 * Copyright (c) 1999, 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Ken Hornstein and John Kohl.
 *
 * 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 1994 John Brezak
 * Copyright (c) 1991-1993 Regents of the University of California.
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/*
 * Copyright by Hannu Savolainen 1994
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */
/*
 * Portions of this code are from the VOXware support for the ad1848
 * by Hannu Savolainen <hannu@voxware.pp.fi>
 *
 * Portions also ripped from the SoundBlaster driver for NetBSD.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ad1848.c,v 1.31 2011/11/23 23:07:32 jmcneill Exp $");

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

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

#include <sys/audioio.h>

#include <dev/audio_if.h>
#include <dev/auconv.h>

#include <dev/ic/ad1848reg.h>
#include <dev/ic/cs4231reg.h>
#include <dev/ic/cs4237reg.h>
#include <dev/ic/ad1848var.h>
#if 0
#include <dev/isa/cs4231var.h>
#endif

/*
 * AD1845 on some machines don't match the AD1845 doc
 * and defining AD1845_HACK to 1 works around the problems.
 * options AD1845_HACK=0  should work if you have ``correct'' one.
 */
#ifndef AD1845_HACK
#define AD1845_HACK	1	/* weird mixer, can't play slinear_be */
#endif

#ifdef AUDIO_DEBUG
#define DPRINTF(x)	if (ad1848debug) printf x
int	ad1848debug = 0;
void ad1848_dump_regs(struct ad1848_softc *);
#else
#define DPRINTF(x)
#endif

/*
 * Initial values for the indirect registers of CS4248/AD1848.
 */
static const int ad1848_init_values[] = {
    GAIN_12|INPUT_MIC_GAIN_ENABLE,	/* Left Input Control */
    GAIN_12|INPUT_MIC_GAIN_ENABLE,	/* Right Input Control */
    ATTEN_12,				/* Left Aux #1 Input Control */
    ATTEN_12,				/* Right Aux #1 Input Control */
    ATTEN_12,				/* Left Aux #2 Input Control */
    ATTEN_12,				/* Right Aux #2 Input Control */
    /* bits 5-0 are attenuation select */
    ATTEN_12,				/* Left DAC output Control */
    ATTEN_12,				/* Right DAC output Control */
    CLOCK_XTAL1|FMT_PCM8,		/* Clock and Data Format */
    SINGLE_DMA|AUTO_CAL_ENABLE,		/* Interface Config */
    INTERRUPT_ENABLE,			/* Pin control */
    0x00,				/* Test and Init */
    MODE2,				/* Misc control */
    ATTEN_0<<2,				/* Digital Mix Control */
    0,					/* Upper base Count */
    0,					/* Lower base Count */

    /* These are for CS4231 &c. only (additional registers): */
    0,					/* Alt feature 1 */
    0,					/* Alt feature 2 */
    ATTEN_12,				/* Left line in */
    ATTEN_12,				/* Right line in */
    0,					/* Timer low */
    0,					/* Timer high */
    0,					/* unused */
    0,					/* unused */
    0,					/* IRQ status */
    0,					/* unused */
			/* Mono input (a.k.a speaker) (mic) Control */
    MONO_INPUT_MUTE|ATTEN_6,		/* mute speaker by default */
    0,					/* unused */
    0,					/* record format */
    0,					/* Crystal Clock Select */
    0,					/* upper record count */
    0					/* lower record count */
};


int
ad1848_to_vol(mixer_ctrl_t *cp, struct ad1848_volume *vol)
{

	if (cp->un.value.num_channels == 1) {
		vol->left =
		vol->right = cp->un.value.level[AUDIO_MIXER_LEVEL_MONO];
		return 1;
	}
	else if (cp->un.value.num_channels == 2) {
		vol->left  = cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
		vol->right = cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
		return 1;
	}
	return 0;
}

int
ad1848_from_vol(mixer_ctrl_t *cp, struct ad1848_volume *vol)
{

	if (cp->un.value.num_channels == 1) {
		cp->un.value.level[AUDIO_MIXER_LEVEL_MONO] = vol->left;
		return 1;
	}
	else if (cp->un.value.num_channels == 2) {
		cp->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = vol->left;
		cp->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = vol->right;
		return 1;
	}
	return 0;
}


int
ad_read(struct ad1848_softc *sc, int reg)
{
	int x;

	ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit);
	x = ADREAD(sc, AD1848_IDATA);
	/*  printf("(%02x<-%02x) ", reg|sc->MCE_bit, x); */
	return x;
}

void
ad_write(struct ad1848_softc *sc, int reg, int data)
{

	ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit);
	ADWRITE(sc, AD1848_IDATA, data & 0xff);
	/* printf("(%02x->%02x) ", reg|sc->MCE_bit, data); */
}

/*
 * extended registers (mode 3) require an additional level of
 * indirection through CS_XREG (I23).
 */

int
ad_xread(struct ad1848_softc *sc, int reg)
{
	int x;

	ADWRITE(sc, AD1848_IADDR, CS_XREG | sc->MCE_bit);
	ADWRITE(sc, AD1848_IDATA, (reg | ALT_F3_XRAE) & 0xff);
	x = ADREAD(sc, AD1848_IDATA);

	return x;
}

void
ad_xwrite(struct ad1848_softc *sc, int reg, int val)
{

	ADWRITE(sc, AD1848_IADDR, CS_XREG | sc->MCE_bit);
	ADWRITE(sc, AD1848_IDATA, (reg | ALT_F3_XRAE) & 0xff);
	ADWRITE(sc, AD1848_IDATA, val & 0xff);
}

static void
ad_set_MCE(struct ad1848_softc *sc, int state)
{

	if (state)
		sc->MCE_bit = MODE_CHANGE_ENABLE;
	else
		sc->MCE_bit = 0;
	ADWRITE(sc, AD1848_IADDR, sc->MCE_bit);
}

static void
wait_for_calibration(struct ad1848_softc *sc)
{
	int timeout;

	DPRINTF(("ad1848: Auto calibration started.\n"));
	/*
	 * Wait until the auto calibration process has finished.
	 *
	 * 1) Wait until the chip becomes ready (reads don't return 0x80).
	 * 2) Wait until the ACI bit of I11 gets on and then off.
	 *    Because newer chips are fast we may never see the ACI
	 *    bit go on.  Just delay a little instead.
	 */
	timeout = 10000;
	while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) {
		delay(10);
		timeout--;
	}
	if (timeout <= 0) {
		DPRINTF(("ad1848: Auto calibration timed out(1).\n"));
	}

	/* Set register addr */
	ADWRITE(sc, AD1848_IADDR, SP_TEST_AND_INIT);
	/* Wait for address to appear when read back. */
	timeout = 100000;
	while (timeout > 0 &&
	       (ADREAD(sc, AD1848_IADDR)&SP_IADDR_MASK) != SP_TEST_AND_INIT) {
		delay(10);
		timeout--;
	}
	if (timeout <= 0) {
		DPRINTF(("ad1848: Auto calibration timed out(1.5).\n"));
	}

	if (!(ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)) {
		if (sc->mode > 1) {
			/* A new chip, just delay a little. */
			delay(100);	/* XXX what should it be? */
		} else {
			timeout = 10000;
			while (timeout > 0 &&
			       !(ad_read(sc, SP_TEST_AND_INIT) &
				 AUTO_CAL_IN_PROG)) {
				delay(10);
				timeout--;
			}
			if (timeout <= 0) {
				DPRINTF(("ad1848: Auto calibration timed out(2).\n"));
			}
		}
	}

	timeout = 10000;
	while (timeout > 0 &&
	       ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG) {
		delay(10);
		timeout--;
	}
	if (timeout <= 0) {
		DPRINTF(("ad1848: Auto calibration timed out(3).\n"));
	}
}

#ifdef AUDIO_DEBUG
void
ad1848_dump_regs(struct ad1848_softc *sc)
{
	int i;
	u_char r;

	printf("ad1848 status=%02x", ADREAD(sc, AD1848_STATUS));
	printf(" regs: ");
	for (i = 0; i < 16; i++) {
		r = ad_read(sc, i);
		printf("%02x ", r);
	}
	if (sc->mode >= 2) {
		for (i = 16; i < 32; i++) {
			r = ad_read(sc, i);
			printf("%02x ", r);
		}
	}
	printf("\n");
}
#endif /* AUDIO_DEBUG */


/*
 * Attach hardware to driver, attach hardware driver to audio
 * pseudo-device driver .
 */
void
ad1848_attach(struct ad1848_softc *sc)
{
	static struct ad1848_volume vol_mid = {220, 220};
	static struct ad1848_volume vol_0   = {0, 0};
	int i;
	int timeout;

	/* Initialize the ad1848... */
	for (i = 0; i < 0x10; i++) {
		ad_write(sc, i, ad1848_init_values[i]);
		timeout = 100000;
		while (timeout > 0 && ADREAD(sc, AD1848_IADDR) & SP_IN_INIT)
			timeout--;
	}
	/* ...and additional CS4231 stuff too */
	if (sc->mode >= 2) {
		ad_write(sc, SP_INTERFACE_CONFIG, 0); /* disable SINGLE_DMA */
		for (i = 0x10; i < 0x20; i++)
			if (ad1848_init_values[i] != 0) {
				ad_write(sc, i, ad1848_init_values[i]);
				timeout = 100000;
				while (timeout > 0 &&
				       ADREAD(sc, AD1848_IADDR) & SP_IN_INIT)
					timeout--;
			}
	}
	ad1848_reset(sc);

	/* Set default gains */
	ad1848_set_rec_gain(sc, &vol_mid);
	ad1848_set_channel_gain(sc, AD1848_DAC_CHANNEL, &vol_mid);
	ad1848_set_channel_gain(sc, AD1848_MONITOR_CHANNEL, &vol_0);
	ad1848_set_channel_gain(sc, AD1848_AUX1_CHANNEL, &vol_mid);	/* CD volume */
	sc->mute[AD1848_MONITOR_CHANNEL] = MUTE_ALL;
	if (sc->mode >= 2
#if AD1845_HACK
	    && sc->is_ad1845 == 0
#endif
		) {
		ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_mid); /* CD volume */
		ad1848_set_channel_gain(sc, AD1848_LINE_CHANNEL, &vol_mid);
		ad1848_set_channel_gain(sc, AD1848_MONO_CHANNEL, &vol_0);
		sc->mute[AD1848_MONO_CHANNEL] = MUTE_ALL;
	} else
		ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_0);

	/* Set default port */
	ad1848_set_rec_port(sc, MIC_IN_PORT);

	printf(": %s", sc->chip_name);
}

/*
 * Various routines to interface to higher level audio driver
 */
static const struct ad1848_mixerinfo {
	int  left_reg;
	int  right_reg;
	int  atten_bits;
	int  atten_mask;
} mixer_channel_info[] =
{
  { SP_LEFT_AUX2_CONTROL, SP_RIGHT_AUX2_CONTROL, AUX_INPUT_ATTEN_BITS,
    AUX_INPUT_ATTEN_MASK },
  { SP_LEFT_AUX1_CONTROL, SP_RIGHT_AUX1_CONTROL, AUX_INPUT_ATTEN_BITS,
    AUX_INPUT_ATTEN_MASK },
  { SP_LEFT_OUTPUT_CONTROL, SP_RIGHT_OUTPUT_CONTROL,
    OUTPUT_ATTEN_BITS, OUTPUT_ATTEN_MASK },
  { CS_LEFT_LINE_CONTROL, CS_RIGHT_LINE_CONTROL, LINE_INPUT_ATTEN_BITS,
    LINE_INPUT_ATTEN_MASK },
  { CS_MONO_IO_CONTROL, 0, MONO_INPUT_ATTEN_BITS, MONO_INPUT_ATTEN_MASK },
  { CS_MONO_IO_CONTROL, 0, 0, 0 },
  { SP_DIGITAL_MIX, 0, OUTPUT_ATTEN_BITS, MIX_ATTEN_MASK }
};

/*
 *  This function doesn't set the mute flags but does use them.
 *  The mute flags reflect the mutes that have been applied by the user.
 *  However, the driver occasionally wants to mute devices (e.g. when chaing
 *  sampling rate). These operations should not affect the mute flags.
 */

void
ad1848_mute_channel(struct ad1848_softc *sc, int device, int mute)
{
	u_char reg;

	reg = ad_read(sc, mixer_channel_info[device].left_reg);

	if (mute & MUTE_LEFT) {
		if (device == AD1848_MONITOR_CHANNEL) {
			if (sc->open_mode & FREAD)
				ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 0);
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg & ~DIGITAL_MIX1_ENABLE);
		} else if (device == AD1848_OUT_CHANNEL)
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg | MONO_OUTPUT_MUTE);
		else
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg | 0x80);
	} else if (!(sc->mute[device] & MUTE_LEFT)) {
		if (device == AD1848_MONITOR_CHANNEL) {
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg | DIGITAL_MIX1_ENABLE);
			if (sc->open_mode & FREAD)
				ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 1);
		} else if (device == AD1848_OUT_CHANNEL)
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg & ~MONO_OUTPUT_MUTE);
		else
			ad_write(sc, mixer_channel_info[device].left_reg,
				 reg & ~0x80);
	}

	if (!mixer_channel_info[device].right_reg)
		return;

	reg = ad_read(sc, mixer_channel_info[device].right_reg);

	if (mute & MUTE_RIGHT) {
		ad_write(sc, mixer_channel_info[device].right_reg, reg | 0x80);
	} else if (!(sc->mute[device] & MUTE_RIGHT)) {
		ad_write(sc, mixer_channel_info[device].right_reg, reg &~0x80);
	}
}

int
ad1848_set_channel_gain(struct ad1848_softc *sc, int device,
    struct ad1848_volume *gp)
{
	const struct ad1848_mixerinfo *info;
	u_char reg;
	u_int atten;

	info = &mixer_channel_info[device];
	sc->gains[device] = *gp;

	atten = (AUDIO_MAX_GAIN - gp->left) * (info->atten_bits + 1) /
		(AUDIO_MAX_GAIN + 1);

	reg = ad_read(sc, info->left_reg) & (info->atten_mask);
	if (device == AD1848_MONITOR_CHANNEL)
		reg |= ((atten & info->atten_bits) << 2);
	else
		reg |= ((atten & info->atten_bits));

	ad_write(sc, info->left_reg, reg);

	if (!info->right_reg)
		return 0;

	atten = (AUDIO_MAX_GAIN - gp->right) * (info->atten_bits + 1) /
		(AUDIO_MAX_GAIN + 1);
	reg = ad_read(sc, info->right_reg);
	reg &= info->atten_mask;
	ad_write(sc, info->right_reg, (atten & info->atten_bits) | reg);

	return 0;
}

int
ad1848_get_device_gain(struct ad1848_softc *sc, int device,
    struct ad1848_volume *gp)
{

	*gp = sc->gains[device];
	return 0;
}

int
ad1848_get_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{

	*gp = sc->rec_gain;
	return 0;
}

int
ad1848_set_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
	u_char reg, gain;

	DPRINTF(("ad1848_set_rec_gain: %d:%d\n", gp->left, gp->right));

	sc->rec_gain = *gp;

	gain = (gp->left * (GAIN_22_5 + 1)) / (AUDIO_MAX_GAIN + 1);
	reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
	reg &= INPUT_GAIN_MASK;
	ad_write(sc, SP_LEFT_INPUT_CONTROL, (gain & 0x0f) | reg);

	gain = (gp->right * (GAIN_22_5 + 1)) / (AUDIO_MAX_GAIN + 1);
	reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL);
	reg &= INPUT_GAIN_MASK;
	ad_write(sc, SP_RIGHT_INPUT_CONTROL, (gain & 0x0f) | reg);

	return 0;
}

void
ad1848_mute_wave_output(struct ad1848_softc *sc, int mute, int set)
{
	int m;

	DPRINTF(("ad1848_mute_wave_output: %d, %d\n", mute, set));

	if (mute == WAVE_MUTE2_INIT) {
		sc->wave_mute_status = 0;
		mute = WAVE_MUTE2;
	}
	if (set)
		m = sc->wave_mute_status |= mute;
	else
		m = sc->wave_mute_status &= ~mute;

	if (m & WAVE_MUTE0 || ((m & WAVE_UNMUTE1) == 0 && m & WAVE_MUTE2))
		ad1848_mute_channel(sc, AD1848_DAC_CHANNEL, MUTE_ALL);
	else
		ad1848_mute_channel(sc, AD1848_DAC_CHANNEL,
					    sc->mute[AD1848_DAC_CHANNEL]);
}

int
ad1848_set_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
	u_char reg;

	DPRINTF(("cs4231_set_mic_gain: %d\n", gp->left));

	if (gp->left > AUDIO_MAX_GAIN/2) {
		sc->mic_gain_on = 1;
		reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
		ad_write(sc, SP_LEFT_INPUT_CONTROL,
			 reg | INPUT_MIC_GAIN_ENABLE);
	} else {
		sc->mic_gain_on = 0;
		reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
		ad_write(sc, SP_LEFT_INPUT_CONTROL,
			 reg & ~INPUT_MIC_GAIN_ENABLE);
	}

	return 0;
}

int
ad1848_get_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
	if (sc->mic_gain_on)
		gp->left = gp->right = AUDIO_MAX_GAIN;
	else
		gp->left = gp->right = AUDIO_MIN_GAIN;
	return 0;
}

static const ad1848_devmap_t *
ad1848_mixer_find_dev(const ad1848_devmap_t *map, int cnt, mixer_ctrl_t *cp)
{
	int i;

	for (i = 0; i < cnt; i++) {
		if (map[i].id == cp->dev) {
			return (&map[i]);
		}
	}
	return 0;
}

int
ad1848_mixer_get_port(struct ad1848_softc *ac, const struct ad1848_devmap *map,
    int cnt, mixer_ctrl_t *cp)
{
	const ad1848_devmap_t *entry;
	struct ad1848_volume vol;
	int error;
	int dev;

	error = EINVAL;
	if (!(entry = ad1848_mixer_find_dev(map, cnt, cp)))
		return ENXIO;

	dev = entry->dev;

	switch (entry->kind) {
	case AD1848_KIND_LVL:
		if (cp->type != AUDIO_MIXER_VALUE)
			break;

		if (dev < AD1848_AUX2_CHANNEL ||
		    dev > AD1848_MONITOR_CHANNEL)
			break;

		if (cp->un.value.num_channels != 1 &&
		    mixer_channel_info[dev].right_reg == 0)
			break;

		error = ad1848_get_device_gain(ac, dev, &vol);
		if (!error)
			ad1848_from_vol(cp, &vol);

		break;

	case AD1848_KIND_MUTE:
		if (cp->type != AUDIO_MIXER_ENUM) break;

		cp->un.ord = ac->mute[dev] ? 1 : 0;
		error = 0;
		break;

	case AD1848_KIND_RECORDGAIN:
		if (cp->type != AUDIO_MIXER_VALUE) break;

		error = ad1848_get_rec_gain(ac, &vol);
		if (!error)
			ad1848_from_vol(cp, &vol);

		break;

	case AD1848_KIND_MICGAIN:
		if (cp->type != AUDIO_MIXER_VALUE) break;

		error = ad1848_get_mic_gain(ac, &vol);
		if (!error)
			ad1848_from_vol(cp, &vol);

		break;

	case AD1848_KIND_RECORDSOURCE:
		if (cp->type != AUDIO_MIXER_ENUM) break;
		cp->un.ord = ad1848_get_rec_port(ac);
		error = 0;
		break;

	default:
		printf ("Invalid kind\n");
		break;
	}

	return error;
}

int
ad1848_mixer_set_port(struct ad1848_softc *ac, const struct ad1848_devmap *map,
    int cnt, mixer_ctrl_t *cp)
{
	const ad1848_devmap_t *entry;
	struct ad1848_volume vol;
	int error;
	int dev;

	error = EINVAL;
	if (!(entry = ad1848_mixer_find_dev(map, cnt, cp)))
		return ENXIO;

	dev = entry->dev;

	switch (entry->kind) {
	case AD1848_KIND_LVL:
		if (cp->type != AUDIO_MIXER_VALUE)
			break;

		if (dev < AD1848_AUX2_CHANNEL ||
		    dev > AD1848_MONITOR_CHANNEL)
			break;

		if (cp->un.value.num_channels != 1 &&
		    mixer_channel_info[dev].right_reg == 0)
			break;

		ad1848_to_vol(cp, &vol);
		error = ad1848_set_channel_gain(ac, dev, &vol);
		break;

	case AD1848_KIND_MUTE:
		if (cp->type != AUDIO_MIXER_ENUM) break;

		ac->mute[dev] = (cp->un.ord ? MUTE_ALL : 0);
		ad1848_mute_channel(ac, dev, ac->mute[dev]);
		error = 0;
		break;

	case AD1848_KIND_RECORDGAIN:
		if (cp->type != AUDIO_MIXER_VALUE) break;

		ad1848_to_vol(cp, &vol);
		error = ad1848_set_rec_gain(ac, &vol);
		break;

	case AD1848_KIND_MICGAIN:
		if (cp->type != AUDIO_MIXER_VALUE) break;

		ad1848_to_vol(cp, &vol);
		error = ad1848_set_mic_gain(ac, &vol);
		break;

	case AD1848_KIND_RECORDSOURCE:
		if (cp->type != AUDIO_MIXER_ENUM) break;

		error = ad1848_set_rec_port(ac,  cp->un.ord);
		break;

	default:
		printf ("Invalid kind\n");
		break;
	}

	return error;
}

int
ad1848_query_encoding(void *addr, struct audio_encoding *fp)
{
	struct ad1848_softc *sc;

	sc = addr;
	switch (fp->index) {
	case 0:
		strcpy(fp->name, AudioEmulaw);
		fp->encoding = AUDIO_ENCODING_ULAW;
		fp->precision = 8;
		fp->flags = 0;
		break;
	case 1:
		strcpy(fp->name, AudioEalaw);
		fp->encoding = AUDIO_ENCODING_ALAW;
		fp->precision = 8;
		fp->flags = 0;
		break;
	case 2:
		strcpy(fp->name, AudioEslinear_le);
		fp->encoding = AUDIO_ENCODING_SLINEAR_LE;
		fp->precision = 16;
		fp->flags = 0;
		break;
	case 3:
		strcpy(fp->name, AudioEulinear);
		fp->encoding = AUDIO_ENCODING_ULINEAR;
		fp->precision = 8;
		fp->flags = 0;
		break;

	case 4: /* only on CS4231 */
		strcpy(fp->name, AudioEslinear_be);
		fp->encoding = AUDIO_ENCODING_SLINEAR_BE;
		fp->precision = 16;
		fp->flags = sc->mode == 1
#if AD1845_HACK
		    || sc->is_ad1845
#endif
			? AUDIO_ENCODINGFLAG_EMULATED : 0;
		break;

		/* emulate some modes */
	case 5:
		strcpy(fp->name, AudioEslinear);
		fp->encoding = AUDIO_ENCODING_SLINEAR;
		fp->precision = 8;
		fp->flags = AUDIO_ENCODINGFLAG_EMULATED;
		break;
	case 6:
		strcpy(fp->name, AudioEulinear_le);
		fp->encoding = AUDIO_ENCODING_ULINEAR_LE;
		fp->precision = 16;
		fp->flags = AUDIO_ENCODINGFLAG_EMULATED;
		break;
	case 7:
		strcpy(fp->name, AudioEulinear_be);
		fp->encoding = AUDIO_ENCODING_ULINEAR_BE;
		fp->precision = 16;
		fp->flags = AUDIO_ENCODINGFLAG_EMULATED;
		break;

	case 8: /* only on CS4231 */
		if (sc->mode == 1 || sc->is_ad1845)
			return EINVAL;
		strcpy(fp->name, AudioEadpcm);
		fp->encoding = AUDIO_ENCODING_ADPCM;
		fp->precision = 4;
		fp->flags = 0;
		break;
	default:
		return EINVAL;
		/*NOTREACHED*/
	}
	return 0;
}

int
ad1848_set_params(void *addr, int setmode, int usemode,
    audio_params_t *p, audio_params_t *r, stream_filter_list_t *pfil,
    stream_filter_list_t *rfil)
{
	audio_params_t phw, rhw;
	struct ad1848_softc *sc;
	int error, bits, enc;
	stream_filter_factory_t *pswcode;
	stream_filter_factory_t *rswcode;

	DPRINTF(("ad1848_set_params: %u %u %u %u\n",
		 p->encoding, p->precision, p->channels, p->sample_rate));

	sc = addr;
	enc = p->encoding;
	pswcode = rswcode = 0;
	phw = *p;
	rhw = *r;
	switch (enc) {
	case AUDIO_ENCODING_SLINEAR_LE:
		if (p->precision == 8) {
			enc = AUDIO_ENCODING_ULINEAR_LE;
			phw.encoding = AUDIO_ENCODING_ULINEAR_LE;
			rhw.encoding = AUDIO_ENCODING_ULINEAR_LE;
			pswcode = rswcode = change_sign8;
		}
		break;
	case AUDIO_ENCODING_SLINEAR_BE:
		if (p->precision == 16 && (sc->mode == 1
#if AD1845_HACK
		    || sc->is_ad1845
#endif
			)) {
			enc = AUDIO_ENCODING_SLINEAR_LE;
			phw.encoding = AUDIO_ENCODING_SLINEAR_LE;
			rhw.encoding = AUDIO_ENCODING_SLINEAR_LE;
			pswcode = rswcode = swap_bytes;
		}
		break;
	case AUDIO_ENCODING_ULINEAR_LE:
		if (p->precision == 16) {
			enc = AUDIO_ENCODING_SLINEAR_LE;
			phw.encoding = AUDIO_ENCODING_SLINEAR_LE;
			rhw.encoding = AUDIO_ENCODING_SLINEAR_LE;
			pswcode = rswcode = change_sign16;
		}
		break;
	case AUDIO_ENCODING_ULINEAR_BE:
		if (p->precision == 16) {
			if (sc->mode == 1
#if AD1845_HACK
			    || sc->is_ad1845
#endif
				) {
				enc = AUDIO_ENCODING_SLINEAR_LE;
				phw.encoding = AUDIO_ENCODING_SLINEAR_LE;
				rhw.encoding = AUDIO_ENCODING_SLINEAR_LE;
				pswcode = swap_bytes_change_sign16;
				rswcode = swap_bytes_change_sign16;
			} else {
				enc = AUDIO_ENCODING_SLINEAR_BE;
				phw.encoding = AUDIO_ENCODING_SLINEAR_BE;
				rhw.encoding = AUDIO_ENCODING_SLINEAR_BE;
				pswcode = rswcode = change_sign16;
			}
		}
		break;
	}
	switch (enc) {
	case AUDIO_ENCODING_ULAW:
		bits = FMT_ULAW >> 5;
		break;
	case AUDIO_ENCODING_ALAW:
		bits = FMT_ALAW >> 5;
		break;
	case AUDIO_ENCODING_ADPCM:
		bits = FMT_ADPCM >> 5;
		break;
	case AUDIO_ENCODING_SLINEAR_LE:
		if (p->precision == 16)
			bits = FMT_TWOS_COMP >> 5;
		else
			return EINVAL;
		break;
	case AUDIO_ENCODING_SLINEAR_BE:
		if (p->precision == 16)
			bits = FMT_TWOS_COMP_BE >> 5;
		else
			return EINVAL;
		break;
	case AUDIO_ENCODING_ULINEAR_LE:
		if (p->precision == 8)
			bits = FMT_PCM8 >> 5;
		else
			return EINVAL;
		break;
	default:
		return EINVAL;
	}

	if (p->channels < 1 || p->channels > 2)
		return EINVAL;

	error = ad1848_set_speed(sc, &p->sample_rate);
	if (error)
		return error;
	phw.sample_rate = p->sample_rate;

	if (pswcode != NULL)
		pfil->append(pfil, pswcode, &phw);
	if (rswcode != NULL)
		rfil->append(rfil, rswcode, &rhw);

	sc->format_bits = bits;
	sc->channels = p->channels;
	sc->precision = p->precision;
	sc->need_commit = 1;

	DPRINTF(("ad1848_set_params succeeded, bits=%x\n", bits));
	return 0;
}

int
ad1848_set_rec_port(struct ad1848_softc *sc, int port)
{
	u_char inp, reg;

	DPRINTF(("ad1848_set_rec_port: 0x%x\n", port));

	if (port == MIC_IN_PORT)
		inp = MIC_INPUT;
	else if (port == LINE_IN_PORT)
		inp = LINE_INPUT;
	else if (port == DAC_IN_PORT)
		inp = MIXED_DAC_INPUT;
	else if (sc->mode >= 2 && port == AUX1_IN_PORT)
		inp = AUX_INPUT;
	else
		return EINVAL;

	reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
	reg &= INPUT_SOURCE_MASK;
	ad_write(sc, SP_LEFT_INPUT_CONTROL, (inp|reg));

	reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL);
	reg &= INPUT_SOURCE_MASK;
	ad_write(sc, SP_RIGHT_INPUT_CONTROL, (inp|reg));

	sc->rec_port = port;

	return 0;
}

int
ad1848_get_rec_port(struct ad1848_softc *sc)
{
	return sc->rec_port;
}

int
ad1848_round_blocksize(void *addr, int blk,
    int mode, const audio_params_t *param)
{

	/* Round to a multiple of the biggest sample size. */
	return blk &= -4;
}

int
ad1848_open(void *addr, int flags)
{
	struct ad1848_softc *sc;
	u_char reg;

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

	sc->open_mode = flags;

	/* Enable interrupts */
	DPRINTF(("ad1848_open: enable intrs\n"));
	reg = ad_read(sc, SP_PIN_CONTROL);
	ad_write(sc, SP_PIN_CONTROL, reg | INTERRUPT_ENABLE);

	/* If recording && monitoring, the playback part is also used. */
	if (flags & FREAD && sc->mute[AD1848_MONITOR_CHANNEL] == 0)
		ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 1);

#ifdef AUDIO_DEBUG
	if (ad1848debug)
		ad1848_dump_regs(sc);
#endif

	return 0;
}

void
ad1848_close(void *addr)
{
	struct ad1848_softc *sc;
	u_char reg;

	sc = addr;
	sc->open_mode = 0;

	ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 0);

	/* Disable interrupts */
	DPRINTF(("ad1848_close: disable intrs\n"));
	reg = ad_read(sc, SP_PIN_CONTROL);
	ad_write(sc, SP_PIN_CONTROL, reg & ~INTERRUPT_ENABLE);

#ifdef AUDIO_DEBUG
	if (ad1848debug)
		ad1848_dump_regs(sc);
#endif
}

/*
 * Lower-level routines
 */
int
ad1848_commit_settings(void *addr)
{
	struct ad1848_softc *sc;
	int timeout;
	u_char fs;

	sc = addr;
	if (!sc->need_commit)
		return 0;

	mutex_spin_enter(&sc->sc_intr_lock);

	ad1848_mute_wave_output(sc, WAVE_MUTE0, 1);

	ad_set_MCE(sc, 1);	/* Enables changes to the format select reg */

	fs = sc->speed_bits | (sc->format_bits << 5);

	if (sc->channels == 2)
		fs |= FMT_STEREO;

	/*
	 * OPL3-SA2 (YMF711) is sometimes busy here.
	 * Wait until it becomes ready.
	 */
	for (timeout = 0;
	    timeout < 1000 && ADREAD(sc, AD1848_IADDR) & SP_IN_INIT; timeout++)
		delay(10);

	ad_write(sc, SP_CLOCK_DATA_FORMAT, fs);

	/*
	 * If mode >= 2 (CS4231), set I28 also.
	 * It's the capture format register.
	 */
	if (sc->mode >= 2) {
		/*
		 * Gravis Ultrasound MAX SDK sources says something about
		 * errata sheets, with the implication that these inb()s
		 * are necessary.
		 */
		(void)ADREAD(sc, AD1848_IDATA);
		(void)ADREAD(sc, AD1848_IDATA);
		/* Write to I8 starts resynchronization. Wait for completion. */
		timeout = 100000;
		while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
			timeout--;

		ad_write(sc, CS_REC_FORMAT, fs);
		(void)ADREAD(sc, AD1848_IDATA);
		(void)ADREAD(sc, AD1848_IDATA);
		/* Now wait for resync for capture side of the house */
	}
	/*
	 * Write to I8 starts resynchronization. Wait until it completes.
	 */
	timeout = 100000;
	while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT) {
		delay(10);
		timeout--;
	}

	if (ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
		printf("ad1848_commit: Auto calibration timed out\n");

	/*
	 * Starts the calibration process and
	 * enters playback mode after it.
	 */
	ad_set_MCE(sc, 0);
	wait_for_calibration(sc);

	ad1848_mute_wave_output(sc, WAVE_MUTE0, 0);

	mutex_spin_exit(&sc->sc_intr_lock);

	sc->need_commit = 0;

	return 0;
}

void
ad1848_reset(struct ad1848_softc *sc)
{
	u_char r;

	DPRINTF(("ad1848_reset\n"));

	/* Clear the PEN and CEN bits */
	r = ad_read(sc, SP_INTERFACE_CONFIG);
	r &= ~(CAPTURE_ENABLE | PLAYBACK_ENABLE);
	ad_write(sc, SP_INTERFACE_CONFIG, r);

	if (sc->mode >= 2) {
		ADWRITE(sc, AD1848_IADDR, CS_IRQ_STATUS);
		ADWRITE(sc, AD1848_IDATA, 0);
	}
	/* Clear interrupt status */
	ADWRITE(sc, AD1848_STATUS, 0);
#ifdef AUDIO_DEBUG
	if (ad1848debug)
		ad1848_dump_regs(sc);
#endif
}

int
ad1848_set_speed(struct ad1848_softc *sc, u_int *argp)
{
	/*
	 * The sampling speed is encoded in the least significant nible of I8.
	 * The LSB selects the clock source (0=24.576 MHz, 1=16.9344 MHz) and
	 * other three bits select the divisor (indirectly):
	 *
	 * The available speeds are in the following table. Keep the speeds in
	 * the increasing order.
	 */
	typedef struct {
		int	speed;
		u_char	bits;
	} speed_struct;
	u_long arg;

	static const speed_struct speed_table[] =  {
		{5510, (0 << 1) | 1},
		{5510, (0 << 1) | 1},
		{6620, (7 << 1) | 1},
		{8000, (0 << 1) | 0},
		{9600, (7 << 1) | 0},
		{11025, (1 << 1) | 1},
		{16000, (1 << 1) | 0},
		{18900, (2 << 1) | 1},
		{22050, (3 << 1) | 1},
		{27420, (2 << 1) | 0},
		{32000, (3 << 1) | 0},
		{33075, (6 << 1) | 1},
		{37800, (4 << 1) | 1},
		{44100, (5 << 1) | 1},
		{48000, (6 << 1) | 0}
	};

	int i, n, selected;

	arg = *argp;
	selected = -1;
	n = sizeof(speed_table) / sizeof(speed_struct);

	if (arg < speed_table[0].speed)
		selected = 0;
	if (arg > speed_table[n - 1].speed)
		selected = n - 1;

	for (i = 1 /*really*/ ; selected == -1 && i < n; i++)
		if (speed_table[i].speed == arg)
			selected = i;
		else if (speed_table[i].speed > arg) {
			int diff1, diff2;

			diff1 = arg - speed_table[i - 1].speed;
			diff2 = speed_table[i].speed - arg;

			if (diff1 < diff2)
				selected = i - 1;
			else
				selected = i;
		}

	if (selected == -1) {
		printf("ad1848: Can't find speed???\n");
		selected = 3;
	}

	sc->speed_bits = speed_table[selected].bits;
	sc->need_commit = 1;
	*argp = speed_table[selected].speed;

	return 0;
}

/*
 * Halt I/O
 */
int
ad1848_halt_output(void *addr)
{
	struct ad1848_softc *sc;
	u_char reg;

	DPRINTF(("ad1848: ad1848_halt_output\n"));
	sc = addr;
	reg = ad_read(sc, SP_INTERFACE_CONFIG);
	ad_write(sc, SP_INTERFACE_CONFIG, reg & ~PLAYBACK_ENABLE);

	return 0;
}

int
ad1848_halt_input(void *addr)
{
	struct ad1848_softc *sc;
	u_char reg;

	DPRINTF(("ad1848: ad1848_halt_input\n"));
	sc = addr;
	reg = ad_read(sc, SP_INTERFACE_CONFIG);
	ad_write(sc, SP_INTERFACE_CONFIG, reg & ~CAPTURE_ENABLE);

	return 0;
}

void
ad1848_get_locks(void *addr, kmutex_t **intr, kmutex_t **thread)
{
	struct ad1848_softc *sc;

	sc = addr;
	*intr = &sc->sc_intr_lock;
	*thread = &sc->sc_lock;
}

void
ad1848_init_locks(struct ad1848_softc *sc, int ipl)
{

	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, ipl);
}

void
ad1848_destroy_locks(struct ad1848_softc *sc)
{

	mutex_destroy(&sc->sc_lock);
	mutex_destroy(&sc->sc_intr_lock);
}