/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of SoundBlaster mixer
 *  TODO: Routines for CTL1335 mixer (Sound Blaster 2.0 CD).
 *        If you have this soundcard, mail me!!!
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"
#include "sb.h"

void snd_sbmixer_write(sbmixer_t * mixer,
		       unsigned char reg, unsigned char data)
{
	outb(reg, SBP1(mixer->port, MIXER_ADDR));
	snd_delay(1);
	outb(data, SBP1(mixer->port, MIXER_DATA));
	snd_delay(1);
}

unsigned char snd_sbmixer_read(sbmixer_t * mixer, unsigned char reg)
{
	unsigned char result;

	outb(reg, SBP1(mixer->port, MIXER_ADDR));
	snd_delay(1);
	result = inb(SBP1(mixer->port, MIXER_DATA));
	snd_delay(1);
	return result;
}

/*
 *    private_value
 *              0x000000ff - register
 *              0x00000f00 - left shift
 *              0x0000f000 - right shift
 */

static int snd_sbmixer_input_mute(snd_kmixer_t * mixer,
				  snd_kmixer_channel_t * channel,
				  unsigned int mute)
{
	unsigned char mixs = SB_DSP_MIXS_NONE, mask;
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	switch (channel->hw.priority) {
	case SND_MIXER_PRI_MIC:
		mask = 1;
		break;
	case SND_MIXER_PRI_CD:
		mask = 2;
		break;
	case SND_MIXER_PRI_LINE:
		mask = 4;
		break;
	default:
		mask = 0;
		break;		/* master */
	}
	if ((mute & SND_MIX_MUTE) == SND_MIX_MUTE)
		mixer->private_value &= ~mask;
	else
		mixer->private_value |= mask;

	if (mixer->private_value == 0)
		mixs = SB_DSP_MIXS_NONE;
	else if (mixer->private_value & 1)
		mixs = SB_DSP_MIXS_MIC;
	else if (mixer->private_value & 2)
		mixs = SB_DSP_MIXS_CD;
	else
		mixs = SB_DSP_MIXS_LINE;

	snd_mutex_lock(sbmix, mixer);
	mixs |= snd_sbmixer_read(sbmix, SB_DSP_RECORD_SOURCE) & ~6;
	snd_sbmixer_write(sbmix, SB_DSP_RECORD_SOURCE, mixs);
	snd_mutex_unlock(sbmix, mixer);
	return 0;
}

static int snd_sbmixer_volume_level(snd_kmixer_t * mixer,
				    snd_kmixer_channel_t * channel,
				    int left, int right, int type)
{
	unsigned char reg, left_shift, right_shift, data, mask;
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	reg = (unsigned char) channel->hw.private_value;
	left_shift = (unsigned char) ((channel->hw.private_value >> 8) & 0x0f);
	right_shift = (unsigned char) ((channel->hw.private_value >> 12) & 0x0f);
#if 0
	snd_printk("volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift);
#endif
	left <<= left_shift;
	mask = channel->hw.omax << left_shift;
	if (left_shift != right_shift) {
		left |= right << right_shift;
		mask |= channel->hw.omax << right_shift;
	}
	snd_mutex_lock(sbmix, mixer);
	data = snd_sbmixer_read(sbmix, reg) & ~mask;
	snd_sbmixer_write(sbmix, reg, left | data);
	snd_mutex_unlock(sbmix, mixer);
	snd_mixer_hardware_volume(mixer, channel->hw.priority, SND_MIX_HW_MATCH_OPPOSITE_CHANNEL, 
		type, 0, 0, 0);
	return 0;
}

static int snd_sbmixer_control(snd_kmixer_t * mixer,
			       snd_kmixer_channel_t * channel,
			       unsigned int ctrl, long val)
{
	switch (ctrl) {
	case SND_MIX_CTRL_MUTE_IN:
		return snd_sbmixer_input_mute(mixer, channel, val);
	}
	return -EINVAL;
}

static int snd_sbmixer_volume(snd_kmixer_t * mixer,
			      snd_kmixer_channel_t * channel,
			      unsigned int type, int left, int right)
{
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
	case SND_MIX_VOL_INPUT:
		return snd_sbmixer_volume_level(mixer, channel, left, right, type);
	}
	return -EINVAL;
}

#define SB_DSP_MIXS \
	(sizeof(snd_sbmixer_pro_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define SB_DSP_PRIVATE( reg, left_shift, right_shift ) \
	(reg|(left_shift<<8)|(right_shift<<12))

static struct snd_stru_mixer_channel_hw snd_sbmixer_pro_mixs[] =
{
	{
		SND_MIXER_PRI_MASTER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER,	/* device name */
		SND_MIXER_OSS_VOLUME,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 7,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_MASTER_DEV, 5, 1)
	},
	{
		SND_MIXER_PRI_PCM,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PCM,	/* device name */
		SND_MIXER_OSS_PCM,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 7,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_PCM_DEV, 5, 1),
	},
	{
		SND_MIXER_PRI_LINE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_LINE,	/* device name */
		SND_MIXER_OSS_LINE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_EXCLUSIVE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_JOINMUTE,
		0, 7,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
        0, 7,			/* imin, imax value */
        -4600, 0,		/* imin, imax dB */
		snd_sbmixer_control,	/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_LINE_DEV, 5, 1)
	},
	{
		SND_MIXER_PRI_CD,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_CD,	/* device name */
		SND_MIXER_OSS_CD,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_EXCLUSIVE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_JOINMUTE,
		0, 7,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
        0, 7,			/* imin, imax value */
        -4600, 0,		/* imin, imax dB */
		snd_sbmixer_control,	/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_CD_DEV, 5, 1)
	},
	{
		SND_MIXER_PRI_FM,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_FM,	/* device name */
		SND_MIXER_OSS_SYNTH,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_EXCLUSIVE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_JOINMUTE,
		0, 7,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
        0, 7,			/* imin, imax value */
        -4600, 0,		/* imin, imax dB */
		snd_sbmixer_control,	/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_FM_DEV, 5, 1),
	},
	{
		SND_MIXER_PRI_MIC,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MIC,	/* device name */
		SND_MIXER_OSS_MIC,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_EXCLUSIVE,
		0, 3,			/* omin, omax value */
		-4600, 0,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,		/* imin, imax dB */
		snd_sbmixer_control,	/* set control */
		snd_sbmixer_volume,	/* set volume level*/
		SB_DSP_PRIVATE(SB_DSP_MIC_DEV, 1, 1)
	}
};

/*
 *    private_value
 *              0x000000ff - register
 *              0x00000f00 - left shift
 *              0x0000f000 - right shift
 */

static void snd_sb16mixer_update_inputs(sbmixer_t * sbmix)
{
	unsigned char left, right;

	left = right = sbmix->record_source;
	left &= sbmix->left_input_mask;
	right &= sbmix->right_input_mask;
	if (sbmix->mono) {
		if ((left & 0x60) || (right & 0x60))
			left |= 0x60;	/* MIDI.L, MIDI.R */
		if ((left & 0x18) || (right & 0x60))
			left |= 0x18;	/* Line.L, Line.R */
		if ((left & 0x06) || (right & 0x06))
			left |= 0x06;	/* CD.L, CD.R */
	}
	snd_mutex_lock(sbmix, mixer);
	snd_sbmixer_write(sbmix, SB_DSP4_INPUT_LEFT, left);
	snd_sbmixer_write(sbmix, SB_DSP4_INPUT_RIGHT, right);
	snd_mutex_unlock(sbmix, mixer);
}

static int snd_sb16mixer_input_mute(snd_kmixer_t * mixer,
				    snd_kmixer_channel_t * channel,
				    unsigned int mute)
{
	sbmixer_t *sbmix;
	unsigned char mask;

	switch (channel->hw.priority) {
	case SND_MIXER_PRI_MIC:
		mask = 0x01;
		break;
	case SND_MIXER_PRI_CD:
		mask = 0x04;
		break;
	case SND_MIXER_PRI_LINE:
		mask = 0x10;
		break;
	case SND_MIXER_PRI_SYNTHESIZER:
		mask = 0x40;
		break;
	default:
		return 0;		/* master */
	}
	sbmix = (sbmixer_t *) mixer->private_data;
	if (mute & SND_MIX_MUTE_LEFT)
		sbmix->record_source &= ~mask;
	else
		sbmix->record_source |= mask;
	if (channel->hw.priority != SND_MIXER_PRI_MIC) {
		mask >>= 1;
		if (mute & SND_MIX_MUTE_RIGHT)
			sbmix->record_source &= ~mask;
		else
			sbmix->record_source |= mask;
	}
	snd_sb16mixer_update_inputs(sbmix);
	return 0;
}

static int snd_sb16mixer_route(snd_kmixer_t * mixer,
			       snd_kmixer_channel_t * channel,
			       unsigned int route)
{
	sbmixer_t *sbmix;
	unsigned char mask;

	switch (channel->hw.priority) {
	case SND_MIXER_PRI_CD:
		mask = 0x04;
		break;
	case SND_MIXER_PRI_LINE:
		mask = 0x10;
		break;
	case SND_MIXER_PRI_SYNTHESIZER:
		mask = 0x40;
		break;
	default:
		return 0;		/* unknown */
	}
	sbmix = (sbmixer_t *) mixer->private_data;
	if (route & SND_MIX_ROUTE_LTOR) {
		sbmix->left_input_mask &= ~mask;
		sbmix->right_input_mask |= mask;
	} else {
		sbmix->left_input_mask |= mask;
		sbmix->right_input_mask &= ~mask;
	}
	mask >>= 1;
	if (route & SND_MIX_ROUTE_RTOL) {
		sbmix->left_input_mask |= mask;
		sbmix->right_input_mask &= ~mask;
	} else {
		sbmix->left_input_mask &= ~mask;
		sbmix->right_input_mask |= mask;
	}
	snd_sb16mixer_update_inputs(sbmix);
	return 0;
}

static int snd_sb16mixer_output_mute(snd_kmixer_t * mixer,
				     snd_kmixer_channel_t * channel,
				     unsigned int mute)
{
	unsigned int lmask, rmask;
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	switch (channel->hw.priority) {
	case SND_MIXER_PRI_MIC:
		lmask = rmask = 0x01000000;
		break;
	case SND_MIXER_PRI_CD:
		lmask = 0x04000000;
		rmask = 0x02000000;
		break;
	case SND_MIXER_PRI_LINE:
		lmask = 0x10000000;
		rmask = 0x08000000;
		break;
	default:
		lmask = rmask = 0;
	}
	if (mute & SND_MIX_MUTE_LEFT)
		mixer->private_value &= ~lmask;
	else
		mixer->private_value |= lmask;
	if (mute & SND_MIX_MUTE_RIGHT)
		mixer->private_value &= ~rmask;
	else
		mixer->private_value |= rmask;
#if 0
	snd_printk("private_value = 0x%x\n", mixer->private_value);
#endif
	snd_mutex_lock(sbmix, mixer);
	snd_sbmixer_write(sbmix, SB_DSP4_OUTPUT_SW, (mixer->private_value >> 24) & 0xff);
	snd_mutex_unlock(sbmix, mixer);
	return 0;
}

static int snd_sb16mixer_volume_level(snd_kmixer_t * mixer,
				      snd_kmixer_channel_t * channel,
				      int left, int right, int type)
{
	unsigned char reg, left_shift, right_shift, data, mask;
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	reg = (unsigned char) channel->hw.private_value;
	left_shift = (unsigned char) ((channel->hw.private_value >> 8) & 0x0f);
	right_shift = (unsigned char) ((channel->hw.private_value >> 12) & 0x0f);
#if 0
	snd_printk("volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift);
#endif
	left <<= left_shift;
	mask = channel->hw.omax << left_shift;
	right <<= right_shift;
	snd_mutex_lock(sbmix, mixer);
	data = snd_sbmixer_read(sbmix, reg) & ~mask;
	snd_sbmixer_write(sbmix, reg, left | data);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		data = snd_sbmixer_read(sbmix, reg + 1) & ~mask;
		snd_sbmixer_write(sbmix, reg + 1, right | data);
	}
	snd_mutex_unlock(sbmix, mixer);
	snd_mixer_hardware_volume(mixer, channel->hw.priority, SND_MIX_HW_MATCH_OPPOSITE_CHANNEL, 
		type, 0, 0, 0);
	return 0;
}

static int snd_sb16mixer_control(snd_kmixer_t * mixer,
				 snd_kmixer_channel_t * channel,
				 unsigned int ctrl, long val)
{
	switch (ctrl) {
	case SND_MIX_CTRL_MUTE_OUT:
		return snd_sb16mixer_output_mute(mixer, channel, val);
	case SND_MIX_CTRL_MUTE_IN:
		return snd_sb16mixer_input_mute(mixer, channel, val);
	case SND_MIX_CTRL_ROUTE_IN:
		return snd_sb16mixer_route(mixer, channel, val);
	}
	return -EINVAL;
}

static int snd_sb16mixer_volume(snd_kmixer_t * mixer,
				snd_kmixer_channel_t * channel,
				unsigned int type, int left, int right)
{
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
	case SND_MIX_VOL_INPUT:
		return snd_sb16mixer_volume_level(mixer, channel, left, right, type);
	}
	return -EINVAL;
}

#define SB_DSP4_MIXS \
	(sizeof(snd_sb16mixer_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define SB_DSP4_PRIVATE( reg, left_shift, right_shift ) \
	(reg|(left_shift<<8)|(right_shift<<12))

static struct snd_stru_mixer_channel_hw snd_sb16mixer_mixs[] =
{
	{
		SND_MIXER_PRI_MASTER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER,	/* device name */
		SND_MIXER_OSS_VOLUME,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_MASTER_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_BASS,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_BASS,	/* device name */
		SND_MIXER_OSS_BASS,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		-1400, 1400,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_BASS_DEV, 4, 4)
	},
	{
		SND_MIXER_PRI_TREBLE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_TREBLE,	/* device name */
		SND_MIXER_OSS_TREBLE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		-1400, 1400,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_TREBLE_DEV, 4, 4)
	},
	{
		SND_MIXER_PRI_SYNTHESIZER, /* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_SYNTHESIZER, /* device name */
		SND_MIXER_OSS_SYNTH,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_ROUTE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
        0, 31,			/* imin, imax value */
        -6200, 0,		/* imin, imax dB */
		snd_sb16mixer_control,	/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_SYNTH_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_PCM,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PCM,	/* device name */
		SND_MIXER_OSS_PCM,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_PCM_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_SPEAKER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_SPEAKER,	/* device name */
		SND_MIXER_OSS_SPEAKER,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 3,			/* omin, omax value */
		-1800, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_SPEAKER_DEV, 6, 6)
	},
	{
		SND_MIXER_PRI_LINE,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_LINE,	/* device name */
		SND_MIXER_OSS_LINE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_ROUTE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
        0, 31,			/* imin, imax value */
        -6200, 0,		/* imin, imax dB */
		snd_sb16mixer_control,	/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_LINE_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_MIC,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MIC,	/* device name */
		SND_MIXER_OSS_MIC,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
        0, 31,			/* imin, imax value */
        -6200, 0,		/* imin, imax dB */
		snd_sb16mixer_control,	/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_MIC_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_CD,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_CD,	/* device name */
		SND_MIXER_OSS_CD,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_ROUTE | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
		0, 31,			/* omin, omax value */
		-6200, 0,		/* omin, omax dB */
        0, 31,			/* imin, imax value */
        -6200, 0,		/* imin, imax dB */
		snd_sb16mixer_control,	/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_CD_DEV, 3, 3)
	},
	{
		SND_MIXER_PRI_HIDDEN,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_IGAIN,	/* device name */
		SND_MIXER_OSS_IGAIN,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		0,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
			0, 0,		/* omin, omax value */
			0, 0,		/* omin, omax dB */
        0, 3,			/* imin, imax value */
        -1800, 0,		/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_IGAIN_DEV, 6, 6)
	},
	{
		SND_MIXER_PRI_HIDDEN,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_OGAIN,	/* device name */
		SND_MIXER_OSS_OGAIN,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 3,			/* omin, omax value */
		-1800, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_sb16mixer_volume,	/* set volume level*/
		SB_DSP4_PRIVATE(SB_DSP4_OGAIN_DEV, 6, 6)
	},
};

static int snd_sb16mixer_get_switch(snd_kmixer_t * mixer,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	uswitch->type = SND_SW_TYPE_BOOLEAN;
	snd_mutex_lock(sbmix, mixer);
	uswitch->value.enable =
	    snd_sbmixer_read(sbmix, SB_DSP4_MIC_AGC) & 0x01 ? 1 : 0;
	snd_mutex_unlock(sbmix, mixer);
	return 0;
}

static int snd_sb16mixer_set_switch(snd_kmixer_t * mixer,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) mixer->private_data;
	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_mutex_lock(sbmix, mixer);
	snd_sbmixer_write(sbmix, SB_DSP4_MIC_AGC,
		     (snd_sbmixer_read(sbmix, SB_DSP4_MIC_AGC) & ~0x01) |
			  (uswitch->value.enable ? 0x01 : 0x00));
	snd_mutex_unlock(sbmix, mixer);
	return 0;
}

snd_kswitch_t snd_sb16mixer_switch =
{
	SND_MIXER_SW_MIC_AGC,
	(snd_get_switch_t *)snd_sb16mixer_get_switch,
	(snd_set_switch_t *)snd_sb16mixer_set_switch,
	0,
	NULL,
	NULL
};

snd_kmixer_t *snd_sbdsp_new_mixer(snd_card_t * card,
				  sbmixer_t * sbmix,
				  unsigned short hardware)
{
	int idx, count;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_hw *phw;
	char *name;

	switch (hardware) {
	case SB_HW_PRO:
		count = SB_DSP_MIXS;
		phw = snd_sbmixer_pro_mixs;
		name = "CTL1345";
		break;
	case SB_HW_16:
		count = SB_DSP4_MIXS;
		phw = snd_sb16mixer_mixs;
		name = "CTL1745";
		break;
	default:
		return NULL;
	}
	if (!card)
		return NULL;
	mixer = snd_mixer_new(card, name);
	if (!mixer)
		return NULL;
	strcpy(mixer->name, mixer->id);
	mixer->private_data = sbmix;
	for (idx = 0; idx < count; idx++) {
		channel = snd_mixer_new_channel(mixer, &phw[idx]);
		if (!channel) {
			snd_mixer_free(mixer);
			return NULL;
		}
	}
	if (hardware == SB_HW_16) {
		snd_mixer_switch_new(mixer, &snd_sb16mixer_switch, sbmix);
		sbmix->update_inputs = snd_sb16mixer_update_inputs;
	}
	snd_mutex_lock(sbmix, mixer);
	snd_sbmixer_write(sbmix, 0x00, 0x00);	/* mixer reset */
	snd_mutex_unlock(sbmix, mixer);
	sbmix->record_source = 0;
	sbmix->left_input_mask = 0x55;		/* 01010101b */
	sbmix->right_input_mask = 0x2b;		/* 00101011b */
	return mixer;
}
