/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of CS4232/4232A/4235/4236B/4237B/4238B/4239 chips
 *
 *  Note:
 *     -----
 *
 *  Bugs:
 *     -----
 *
 *   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.
 *
 */

/*
 *  Indirect control registers (CS4236B+)
 * 
 *  C0
 *     D8: WSS reset (all chips)
 *
 *  C1 (all chips except CS4236)
 *     D7-D5: version 
 *     D4-D0: chip id
 *             11101 - CS4235
 *             01011 - CS4236B
 *             01000 - CS4237B
 *             01001 - CS4238B
 *             11110 - CS4239
 *
 *  C2
 *     D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239)
 *     D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B)
 * 
 *  C3
 *     D7: 3D Enable (CS4237B)
 *     D6: 3D Mono Enable (CS4237B)
 *     D5: 3D Serial Output (CS4237B,CS4238B)
 *     D4: 3D Enable (CS4235,CS4238B,CS4239)
 *
 *  C4
 *     D7: consumer serial port enable (CS4237B,CS4238B)
 *     D6: channels status block reset (CS4237B,CS4238B)
 *     D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B)
 *     D4: validity bit bit in sub-frame of digital audio data (CS4237B,CS4238B)
 * 
 *  C5  lower channel status (digital serial data description) (CS4237B,CS4238B)
 *     D7-D6: first two bits of category code
 *     D5: lock
 *     D4-D3: pre-emphasis (0 = none, 1 = 50/15us)
 *     D2: copy/copyright (0 = copy inhibited)
 *     D1: 0 = digital audio / 1 = non-digital audio
 *     
 *  C6  upper channel status (digital serial data description) (CS4237B,CS4238B)
 *     D7-D6: sample frequency (0 = 44.1kHz)
 *     D5: generation status (0 = no indication, 1 = original/commercially precaptureed data)
 *     D4-D0: category code (upper bits)
 *
 *  C7  reserved (must write 0)
 *
 *  C8  wavetable control
 *     D7: volume control interrupt enable (CS4235,CS4239)
 *     D6: hardware volume control format (CS4235,CS4239)
 *     D3: wavetable serial port enable (all chips)
 *     D2: DSP serial port switch (all chips)
 *     D1: disable MCLK (all chips)
 *     D0: force BRESET low (all chips)
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/cs4231.h"

/*

 */

static void snd_cs4236_ext_outm(cs4231_t * codec, unsigned char reg, unsigned char mask, unsigned char val)
{
#if 1
	outb(codec->mce_bit | 0x17, CS4231P(codec, REGSEL));
	outb(reg | (codec->image[CS4236_EXT_REG] & 0x01), CS4231P(codec, REG));
	val |= inb(CS4231P(codec, REG)) & mask;
	outb(codec->mce_bit | 0x17, CS4231P(codec, REGSEL));
	outb(reg | (codec->image[CS4236_EXT_REG] & 0x01), CS4231P(codec, REG));
	outb(val, CS4231P(codec, REG));
#else
	unsigned char res, val1;
	outb(codec->mce_bit | 0x17, CS4231P(codec, REGSEL));
	outb(reg | (codec->image[CS4236_EXT_REG] & 0x01), CS4231P(codec, REG));
	res = inb(CS4231P(codec, REG));
	val1 = val;
	val |= res & mask;
	outb(codec->mce_bit | 0x17, CS4231P(codec, REGSEL));
	outb(reg | (codec->image[CS4236_EXT_REG] & 0x01), CS4231P(codec, REG));
	outb(val, CS4231P(codec, REG));
	printk("ext outm : reg = 0x%x, mask = 0x%x, val = 0x%x, in = 0x%x, out = 0x%x\n", reg, mask, val1, res, val);
#endif
	codec->eimage[CS4236_REG(reg)] = val;
}

static void snd_cs4236_ctrl_out(cs4231_t * codec, unsigned char reg, unsigned char val)
{
	outb(reg, codec->cport + 3);
	outb(val, codec->cport + 4);
}

static unsigned char snd_cs4236_ctrl_in(cs4231_t * codec, unsigned char reg)
{
	outb(reg, codec->cport + 3);
	return inb(codec->cport + 4);
}

static void snd_cs4236_ctrl_outm(cs4231_t * codec, unsigned char reg, unsigned char mask, unsigned char val)
{
	unsigned char res;

	outb(reg, codec->cport + 3);
	res = inb(codec->cport + 4);
	outb(reg, codec->cport + 3);
	outb(val | (res & mask), codec->cport + 4);
}

/*
 *  PCM
 */

static unsigned char snd_cs4236_rate(unsigned int rate, unsigned int *real_rate)
{
	static struct {
		unsigned char val;
		unsigned int divider;
		unsigned int rate;
	} dividers[] = {
		{	1,	353,	48000	},
		{	2,	529,	32000	},
		{	3,	617,	27420	},
		{	4,	1058,	16000	},
		{	5,	1764,	9600	},
		{	6,	2117,	8000	},
		{	7,	2558,	6620	},
	};
	int idx;
	unsigned int divider;
	unsigned char val = 21;

	divider = 16934400U / rate;
	for (idx = 0; idx < 7; idx++) {
		if (dividers[idx].divider == divider || dividers[idx].rate == rate) {
			if (real_rate)
				*real_rate = dividers[idx].rate;
			return dividers[idx].val;
		}
	}

	if (divider > 3072) {
		val = 192;
	} else {
		if (divider < 336) {
			val = 21;
		} else {
			val = divider >> 4;
		}
	}
	if (real_rate)
		*real_rate = 16934400U / ((unsigned int) val << 4);
	return val;
}

static unsigned int snd_cs4236_xrate(cs4231_t * codec, unsigned int rate)
{
	unsigned int rrate;

	snd_cs4236_rate(rate, &rrate);
	return rrate;
}

static void snd_cs4236_playback_format(cs4231_t * codec, unsigned char pdfr)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	/* set fast playback format change and clean playback FIFO */
	snd_cs4231_out(codec, CS4231_ALT_FEATURE_1, codec->image[CS4231_ALT_FEATURE_1] | 0x10);
	snd_cs4231_out(codec, CS4231_PLAYBK_FORMAT, pdfr & 0xf0);
	snd_cs4231_out(codec, CS4231_ALT_FEATURE_1, codec->image[CS4231_ALT_FEATURE_1] & ~0x10);
	snd_cs4236_ext_out(codec, CS4236_DAC_RATE, snd_cs4236_rate(codec->playback_subchn1->real_rate, NULL));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_cs4236_capture_format(cs4231_t * codec, unsigned char cdfr)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	/* set fast capture format change and clean capture FIFO */
	snd_cs4231_out(codec, CS4231_ALT_FEATURE_1, codec->image[CS4231_ALT_FEATURE_1] | 0x20);
	snd_cs4231_out(codec, CS4231_REC_FORMAT, cdfr & 0xf0);
	snd_cs4231_out(codec, CS4231_ALT_FEATURE_1, codec->image[CS4231_ALT_FEATURE_1] & ~0x20);
	snd_cs4236_ext_out(codec, CS4236_ADC_RATE, snd_cs4236_rate(codec->capture_subchn1->real_rate, NULL));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

snd_pcm_t *snd_cs4236_new_device(snd_card_t * card,
				 unsigned short port,
				 unsigned short cport,	/* control port */
				 snd_irq_t * irqptr,
				 snd_dma_t * dmaptr1,
				 snd_dma_t * dmaptr2,
				 unsigned short hardware,
				 int timer_dev)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;
	cs4231_t *codec;
	unsigned char ver;

	pcm = snd_cs4231_new_device(card, port, irqptr, dmaptr1, dmaptr2, hardware, timer_dev);
	if (!pcm)
		return NULL;
	codec = (cs4231_t *) pcm->private_data;
	if (!(codec->hardware & CS4231_HW_CS4236_MASK)) {
		snd_pcm_free(pcm);
		return NULL;
	}
	strcpy(pcm->id, "CS4236");
	codec->cport = cport;
	if (codec->hardware != CS4231_HW_CS4236) {
#if 0
		int idx;
		for (idx = 0; idx < 8; idx++)
			snd_printk("CD%i = 0x%x\n", idx, inb(codec->cport + idx));
		for (idx = 0; idx < 9; idx++)
			snd_printk("C%i = 0x%x\n", idx, snd_cs4236_ctrl_in(codec, idx));
#endif
		ver = snd_cs4236_ctrl_in(codec, 1);
		snd_printdd("CS4236: [0x%x] C1 (version) = 0x%x\n", cport, ver);
		if (ver == 0xff || (ver & 0x1f) == 0) {
			snd_printk("CS4236+ chip detected, but control port 0x%x is bad\n", cport);
			snd_pcm_free(pcm);
			return NULL;
		}
		snd_cs4236_ctrl_out(codec, 0, 0x00);
		snd_cs4236_ctrl_out(codec, 2, 0x00);
		snd_cs4236_ctrl_out(codec, 3, 0x00);
		snd_cs4236_ctrl_out(codec, 4, 0x00);
		snd_cs4236_ctrl_out(codec, 5, 0x00);
		snd_cs4236_ctrl_out(codec, 6, 0x00);
		snd_cs4236_ctrl_out(codec, 7, 0x00);
		/* 0x07 for C8 is valid for Turtle Beach Malibu - the IEC-958 output */
		/* is working with this setup, other hardware should have */
		/* different signal paths and this value should be selectable */
		/* in the future */
		snd_cs4236_ctrl_out(codec, 8, 0x07);
		if (codec->hardware == CS4231_HW_CS4235 ||
		    codec->hardware == CS4231_HW_CS4239) {
		    	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
			pchn1->hw.formats = SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pchn1->hw.hw_formats = SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pchn1->hw.dma = snd_pcm1_playback_dma_ulaw_loud;
		    	pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
			pchn1->hw.formats = SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pchn1->hw.hw_formats = SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pchn1->hw.dma = snd_pcm1_capture_dma_ulaw_loud;
		}
	}
	pcm->info_flags &= ~SND_PCM_INFO_DUPLEX_LIMIT;
	codec->set_playback_rate =
	  codec->set_capture_rate = snd_cs4236_xrate;
	codec->set_playback_format = snd_cs4236_playback_format;
	codec->set_capture_format = snd_cs4236_capture_format;
	snd_cs4236_ext_out(codec, CS4236_DAC_MUTE, 0xe0);	/* IFSE enable, digital master mute */
	return pcm;
}

/*
 *  MIXER
 */

static int snd_cs4236_mixer_stereo_volume(cs4231_t *codec,
					  int w_flag, int *voices,
					  int max, int invert, int shift,
					  unsigned char left_reg,
					  unsigned char right_reg)
{
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = (codec->eimage[CS4236_REG(left_reg)] >> shift) & max;
	right = (codec->eimage[CS4236_REG(right_reg)] >> shift) & max;
	if (!w_flag) {
		if (invert) {
			voices[0] = max - left;
			voices[1] = max - right;
		} else {
			voices[0] = left;
			voices[1] = right;
		}
	} else {
		if (invert) {
			change = max - left != voices[0] || max - right != voices[1];
		} else {
			change = left != voices[0] || right != voices[1];
		}
		left = voices[0]; right = voices[1];
		if (invert) {
			left = max - left;
			right = max - right;
		}
		snd_cs4236_ext_outm(codec, left_reg, ~(max << shift), left << shift);
		snd_cs4236_ext_outm(codec, right_reg, ~(max << shift), right << shift);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_mono_volume(cs4231_t *codec, int w_flag, int *voices,
					int max, int invert, int shift,
					unsigned char reg)
{
	int change = 0, val;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	val = (codec->eimage[CS4236_REG(reg)] >> shift) & max;
	if (!w_flag) {
		voices[0] = invert ? max - val : val;
	} else {
		change = (invert ? max - val : val) != voices[0];
		val = voices[0];
		if (invert)
			val = max - val;
		snd_cs4236_ext_outm(codec, reg, ~(max << shift), val << shift);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_stereo_switch(cs4231_t *codec,
					  int w_flag, unsigned int *bitmap,
					  int bit, int invert,
					  unsigned char left_reg,
					  unsigned char right_reg)
{
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = (codec->eimage[CS4236_REG(left_reg)] >> bit) & 1;
	right = (codec->eimage[CS4236_REG(right_reg)] >> bit) & 1;
	if (!w_flag) {
		if (invert) {
			snd_mixer_set_bit(bitmap, 0, left ^ 1);
			snd_mixer_set_bit(bitmap, 1, right ^ 1);
		} else {
			snd_mixer_set_bit(bitmap, 0, left);
			snd_mixer_set_bit(bitmap, 1, right);
		}
	} else {
		if (invert) {
			change = (left ^ 1) != snd_mixer_get_bit(bitmap, 0) ||
			         (right ^ 1) != snd_mixer_get_bit(bitmap, 1);
		} else {
			change = left != snd_mixer_get_bit(bitmap, 0) ||
			         right != snd_mixer_get_bit(bitmap, 1);
		}
		left = snd_mixer_get_bit(bitmap, 0);
		right = snd_mixer_get_bit(bitmap, 1);
		if (invert) {
			left ^= 1;
			right ^= 1;
		}
		snd_cs4236_ext_outm(codec, left_reg, ~(1 << bit), left << bit);
		snd_cs4236_ext_outm(codec, right_reg, ~(1 << bit), right << bit);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

#if 0

static int snd_cs4236_mixer_mono_switch(cs4231_t *codec,
				        int w_flag, int *value,
					int bit, int invert,
					unsigned char reg)
{
	int change = 0, val;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	val = (codec->eimage[CS4236_REG(reg)] >> bit) & 1;
	if (!w_flag) {
		*value = invert ? val ^ 1 : val;
	} else {
		change = (invert ? val ^ 1 : val) != (*value != 0);
		val = *value != 0;
		if (invert)
			val ^= 1;
		snd_cs4236_ext_outm(codec, reg, ~(1 << bit), val << bit);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

#endif

static int snd_cs4236_mixer_input_accu_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      3, 1, 5,
					      CS4236_LEFT_MIX_CTRL,
					      CS4236_RIGHT_MIX_CTRL);
}

static int snd_cs4236_mixer_mic_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      31, 1, 0,
					      CS4236_LEFT_MIC,
					      CS4236_RIGHT_MIC);
}

static int snd_cs4236_mixer_mic_in_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4236_LEFT_MIC,
					      CS4236_RIGHT_MIC);
}

static int snd_cs4236_mixer_mic_out_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      1, 0, 5,
					      CS4236_LEFT_MIC,
					      CS4236_RIGHT_MIC);
}

static int snd_cs4236_mixer_mic_out_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      6, 1,
					      CS4236_LEFT_MIC,
					      CS4236_RIGHT_MIC);
}

static int snd_cs4236_mixer_aux1_in_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      6, 1,
					      CS4231_AUX1_LEFT_INPUT,
					      CS4231_AUX1_RIGHT_INPUT);
}

static int snd_cs4236_mixer_aux1_bypass_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      5, 0,
					      CS4231_LEFT_LINE_IN,
					      CS4231_LEFT_LINE_IN);
}

static int snd_cs4236_mixer_aux2_in_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      6, 1,
					      CS4231_AUX2_LEFT_INPUT,
					      CS4231_AUX2_RIGHT_INPUT);
}

static int snd_cs4236_mixer_line_in_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      6, 1,
					      CS4231_LEFT_LINE_IN,
					      CS4231_RIGHT_LINE_IN);
}

static int snd_cs4236_mixer_line_bypass_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data, w_flag, bitmap,
					      5, 0,
					      CS4231_LEFT_LINE_IN,
					      CS4231_RIGHT_LINE_IN);
}

static int snd_cs4236_mixer_analog_loopback_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4231_LEFT_INPUT,
					      CS4231_RIGHT_INPUT);
}

static int snd_cs4236_mixer_mono_master_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = ((codec->image[CS4231_MONO_CTRL] >> 6) & 1) ^ 1;
	right = ((codec->eimage[CS4236_REG(CS4236_RIGHT_MIX_CTRL)] >> 7) & 1) ^ 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = left != snd_mixer_get_bit(bitmap, 0) ||
			 right != snd_mixer_get_bit(bitmap, 1);
		left = snd_mixer_get_bit(bitmap, 0);
		right = snd_mixer_get_bit(bitmap, 1);
		snd_cs4231_outm(codec, CS4231_MONO_CTRL, (unsigned char)~(1 << 6), (unsigned char)((left ^ 1) << 6));
		snd_cs4236_ext_outm(codec, CS4236_RIGHT_MIX_CTRL, (unsigned char)~(1 << 7), (unsigned char)((right ^ 1) << 7));
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_mono_in_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = ((codec->image[CS4231_MONO_CTRL] >> 7) & 1) ^ 1;
	right = ((codec->eimage[CS4236_REG(CS4236_LEFT_MIX_CTRL)] >> 7) & 1) ^ 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = left != snd_mixer_get_bit(bitmap, 0) ||
			 right != snd_mixer_get_bit(bitmap, 1);
		left = snd_mixer_get_bit(bitmap, 0);
		right = snd_mixer_get_bit(bitmap, 1);
		snd_cs4231_outm(codec, CS4231_MONO_CTRL, (unsigned char)~(1 << 7), (unsigned char)((left ^ 1) << 7));
		snd_cs4236_ext_outm(codec, CS4236_LEFT_MIX_CTRL, (unsigned char)~(1 << 7), (unsigned char)((right ^ 1) << 7));
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_digital_loopback_volume(void *private_data, int w_flag, int *voices)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = 63 - ((codec->image[CS4231_LOOPBACK] >> 2) & 63);
	right = 63 - (codec->eimage[CS4236_REG(CS4236_RIGHT_LOOPBACK)] & 63);
	if (!w_flag) {
		voices[0] = left;
		voices[1] = right;
	} else {
		change = left != voices[0] || right != voices[1];
		snd_cs4231_outm(codec, CS4231_LOOPBACK, 0x03, (unsigned char)((63 - voices[0]) << 2));
		snd_cs4236_ext_outm(codec, CS4236_RIGHT_LOOPBACK, 0xc0, (unsigned char)(63 - voices[1]));
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_digital_loopback_switch(void *private_data, int w_flag, int *value)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, val;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	val = snd_cs4231_in(codec, CS4231_LOOPBACK) & 1;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		snd_cs4231_outm(codec, CS4231_LOOPBACK, 0xfe, *value ? 1 : 0);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_dsp_input_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      63, 1, 0,
					      CS4236_LEFT_DSP,
					      CS4236_RIGHT_DSP);
}

static int snd_cs4236_mixer_dsp_input_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4236_LEFT_DSP,
					      CS4236_RIGHT_DSP);
}

static int snd_cs4236_mixer_fm_input_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      63, 1, 0,
					      CS4236_LEFT_FM,
					      CS4236_RIGHT_FM);
}

static int snd_cs4236_mixer_fm_input_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4236_LEFT_FM,
					      CS4236_RIGHT_FM);
}

static int snd_cs4236_mixer_wavetable_input_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      63, 1, 0,
					      CS4236_LEFT_WAVE,
					      CS4236_RIGHT_WAVE);
}

static int snd_cs4236_mixer_wavetable_input_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4236_LEFT_WAVE,
					      CS4236_RIGHT_WAVE);
}

static int snd_cs4236_mixer_master_digital_invert_volume(int vol)
{
	return (vol < 64) ? 63 - vol : 64 + (71 - vol);
}

static int snd_cs4236_mixer_master_digital_volume(void *private_data, int w_flag, int *voices)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = snd_cs4236_mixer_master_digital_invert_volume(codec->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f);
	right = snd_cs4236_mixer_master_digital_invert_volume(codec->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f);
	if (!w_flag) {
		voices[0] = left;
		voices[1] = right;
	} else {
		change = left != voices[0] || right != voices[1];
		left = voices[0]; right = voices[1];
		left = snd_cs4236_mixer_master_digital_invert_volume(left);
		right = snd_cs4236_mixer_master_digital_invert_volume(right);
		snd_cs4236_ext_outm(codec, CS4236_LEFT_MASTER, 0x80, left);
		snd_cs4236_ext_outm(codec, CS4236_RIGHT_MASTER, 0x80, right);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4236_mixer_master_digital_output_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4236_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4236_LEFT_MASTER,
					      CS4236_RIGHT_MASTER);
}

static int snd_cs4236_mixer_master_digital_input_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = ((codec->eimage[CS4236_REG(CS4236_DAC_MUTE)] >> 7) & 1) ^ 1;
	right = ((codec->eimage[CS4236_REG(CS4236_DAC_MUTE)] >> 6) & 1) ^ 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, left);
		snd_mixer_set_bit(bitmap, 1, right);
	} else {
		change = left != snd_mixer_get_bit(bitmap, 0) ||
			 right != snd_mixer_get_bit(bitmap, 1);
		left = snd_mixer_get_bit(bitmap, 0);
		right = snd_mixer_get_bit(bitmap, 1);
		snd_cs4236_ext_outm(codec, CS4236_DAC_MUTE, (unsigned char)~(1 << 7), (unsigned char)((left ^ 1) << 7));
		snd_cs4236_ext_outm(codec, CS4236_DAC_MUTE, (unsigned char)~(1 << 6), (unsigned char)((right ^ 1) << 6));
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4235_mixer_output_accu_get_volume(int vol)
{
	switch ((vol >> 5) & 3) {
	case 0: return 1;
	case 1: return 3;
	case 2: return 2;
	case 3: return 0;
 	}
	return 3;
}

static int snd_cs4235_mixer_output_accu_set_volume(int vol)
{
	switch (vol & 3) {
	case 0: return 3 << 5;
	case 1: return 0 << 5;
	case 2: return 2 << 5;
	case 3: return 1 << 5;
	}
	return 1 << 5;
}

static int snd_cs4235_mixer_output_accu_volume(void *private_data, int w_flag, int *voices)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int change = 0, left, right;
	unsigned long flags;

	spin_lock_irqsave(&codec->reg_lock, flags);
	left = snd_cs4235_mixer_output_accu_get_volume(codec->image[CS4235_LEFT_MASTER]);
	right = snd_cs4235_mixer_output_accu_get_volume(codec->image[CS4235_RIGHT_MASTER]);
	if (!w_flag) {
		voices[0] = left;
		voices[1] = right;
	} else {
		change = left != voices[0] || right != voices[1];
		left = voices[0]; right = voices[1];
		left = snd_cs4235_mixer_output_accu_set_volume(left);
		right = snd_cs4235_mixer_output_accu_set_volume(right);
		snd_cs4231_outm(codec, CS4235_LEFT_MASTER, ~0x60, left);
		snd_cs4231_outm(codec, CS4235_RIGHT_MASTER, ~0x60, right);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;	
}

static int snd_cs4235_mixer_mic_mono_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_mono_volume((cs4231_t *)private_data,
					    w_flag, voices,
					    31, 1, 0,
					    CS4236_LEFT_MIC);
}

static int snd_cs4235_mixer_mic_boost_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4236_mixer_mono_volume((cs4231_t *)private_data,
					    w_flag, voices,
					    1, 1, 5,
					    CS4236_LEFT_MIC);
}

static int snd_cs4235_mixer_master_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4231_mixer_stereo_volume((cs4231_t *)private_data,
					      w_flag, voices,
					      31, 1, 0,
					      CS4235_LEFT_MASTER,
					      CS4235_RIGHT_MASTER);
}

static int snd_cs4235_mixer_master_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch((cs4231_t *)private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4235_LEFT_MASTER,
					      CS4235_RIGHT_MASTER);
}

static int snd_cs4236_iec958_get(snd_kmixer_t * mixer, snd_kswitch_t * kswitch, snd_switch_t * uswitch)
{
	unsigned long flags;
	cs4231_t *codec = (cs4231_t *) mixer->private_data;
	unsigned short ctrl, val;

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&codec->reg_lock, flags);
	uswitch->value.enable = codec -> image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0;
	val = snd_cs4236_ctrl_in(codec, 5);
	val |= ((unsigned short) snd_cs4236_ctrl_in(codec, 6)) << 8;
	ctrl = snd_cs4236_ctrl_in(codec, 4);
	ctrl |= ((unsigned short) snd_cs4236_ctrl_in(codec, 3) & 0x20) << 8;
	spin_unlock_irqrestore(&codec->reg_lock, flags);
#if 0
	printk( "get: feature = 0x%x, val = 0x%x, ctrl = 0x%x\n", snd_cs4231_in(codec, CS4231_ALT_FEATURE_1), val, ctrl);
#endif
	uswitch->value.data32[1] = ('C' << 8) | 'S';
	uswitch->value.data16[4] = ctrl;
	uswitch->value.data16[5] = val;
	return 0;
}

static int snd_cs4236_iec958_set(snd_kmixer_t * mixer, snd_kswitch_t * kswitch, snd_switch_t * uswitch)
{
	unsigned long flags;
	cs4231_t *codec = (cs4231_t *) mixer->private_data;
	unsigned short ctrl, val;
        signed long time;
        int change = 1, setflag;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;

	/* main IEC-958 output setup */
		
	ctrl = uswitch->value.enable ? 0x80 : 0; /* enable/disable IEC-958 */
	val = 0;
	setflag = 0;
	/* check if the signature for the Cirrus Logic IEC-958 setup is present */
	if (uswitch->value.data32[1] == (('C' << 8) | 'S')) {
		/* 0x40 = Channel Status Block Reset */
		/* 0x20 = User Bit in Sub Frame */
		/* 0x10 = Validity Bit in Sub Frame */
		ctrl |= uswitch->value.data16[4] & 0x2070;
		val = uswitch->value.data16[5] & ~0xc001;
		setflag = 1;
	}
#if 0
	printk("set: val = 0x%x, ctrl = 0x%x, feature = 0x%x\n", val, ctrl, (ctrl >> 6) & 0x02);
#endif

	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_cs4236_ctrl_outm(codec, 4, 0x7f, 0);
	if (setflag) {
		snd_cs4236_ctrl_out(codec, 5, val & 0xff);
		snd_cs4236_ctrl_out(codec, 6, (val >> 8) & 0xff);
		snd_cs4236_ctrl_outm(codec, 3, ~0x20, (ctrl >> 8) & 0xff);
		snd_cs4236_ctrl_outm(codec, 4, 0x0f, ctrl & 0x70);
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);

	down(&codec->mce_mutex);
	snd_cs4231_mce_up(codec);
	spin_lock_irqsave(&codec->reg_lock, flags);
	codec->image[CS4231_ALT_FEATURE_1] &= ~(0x02 | 0x0c);
	codec->image[CS4231_ALT_FEATURE_1] |= (ctrl >> 6) & 0x02;
	snd_cs4231_out(codec, CS4231_ALT_FEATURE_1, codec->image[CS4231_ALT_FEATURE_1]);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	snd_cs4231_mce_down(codec);
	up(&codec->mce_mutex);
	
	if (ctrl & 0x80) {

		/* for unknown reason seems to be important turn off IEC958 */
		/* output for a while - tested with Sony STR-DB925 receiver */

		time = HZ / 5;
		while (time > 0) {
			current->state = TASK_INTERRUPTIBLE;
			time = schedule_timeout(time);
		}

		spin_lock_irqsave(&codec->reg_lock, flags);
		if (setflag) {
			snd_cs4236_ctrl_outm(codec, 4, 0x0f, (ctrl & 0xff) | 0x40);
		} else {
			snd_cs4236_ctrl_outm(codec, 4, 0x3f, (ctrl & 0xff) | 0x40);
		}
		spin_unlock_irqrestore(&codec->reg_lock, flags);

		time = HZ / 5;
		while (time > 0) {
			current->state = TASK_INTERRUPTIBLE;
			time = schedule_timeout(time);
		}

		spin_lock_irqsave(&codec->reg_lock, flags);
		if (setflag) {
			snd_cs4236_ctrl_outm(codec, 4, 0x0f, (ctrl & 0xff) & ~0x40);
		} else {
			snd_cs4236_ctrl_outm(codec, 4, 0x3f, (ctrl & 0xff) & ~0x40);
		}
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	}
	
#if 0
	printk("set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n",
			snd_cs4231_in(codec, CS4231_ALT_FEATURE_1),
			snd_cs4236_ctrl_in(codec, 3),
			snd_cs4236_ctrl_in(codec, 4),
			snd_cs4236_ctrl_in(codec, 5),
			snd_cs4236_ctrl_in(codec, 6),
			snd_cs4236_ctrl_in(codec, 8));
#endif
	return change;
}

static snd_kswitch_t snd_cs4236_iec958 =
{
	SND_MIXER_SW_IEC958_OUTPUT,
	(snd_get_switch_t *)snd_cs4236_iec958_get,
	(snd_set_switch_t *)snd_cs4236_iec958_set,
	0,
	NULL,
	NULL
};

static int snd_cs4236_mixer_group_ctrl1(cs4231_t *codec,
					snd_kmixer_file_t * file,
					int w_flag,
					snd_mixer_group_t * ugroup,
					snd_mixer_volume1_control_t *volume1,
					snd_kmixer_element_t *volume1_element,
					int max,
					snd_mixer_sw1_control_t *sw1_out,
					snd_kmixer_element_t *sw1_out_element,
					snd_mixer_sw1_control_t *sw1_in,
					snd_kmixer_element_t *sw1_in_element)
{
	int voices[2];
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1 == snd_cs4231_mixer_monoin_volume)
			ugroup->caps = SND_MIXER_GRPCAP_JOINTLY_VOLUME;
		ugroup->mute = 0;
		ugroup->capture = 0;
		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(codec, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			if (volume1 == snd_cs4231_mixer_monoin_volume) {
				ugroup->volume.names.front_right = voices[0];
			} else {
				ugroup->volume.names.front_right = voices[1];
			}
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw1_out) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1_out(codec, 0, &bitmap);
			if (!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (sw1_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw1_in(codec, 0, &bitmap);
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voices[0] = ugroup->volume.names.front_left % (max+1);
			voices[1] = ugroup->volume.names.front_right % (max+1);
			if (volume1(codec, 1, voices) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw1_out) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_out(codec, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_out_element, 0);
				change = 1;
			}
		}
		if (sw1_in) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_in(codec, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_in_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_cs4236_mixer_group_mic(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4236_mixer_mic_volume,
					    codec->me_vol_mic,
					    31,
					    snd_cs4236_mixer_mic_in_switch,
					    codec->me_sw_mic_in,
					    snd_cs4236_mixer_mic_out_switch,
					    codec->me_sw_mic_out);
}

static int snd_cs4236_mixer_group_aux1(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_aux1_volume,
					    codec->me_vol_aux1,
					    31,
					    snd_cs4236_mixer_aux1_in_switch,
					    codec->me_sw_aux1_in,
					    snd_cs4231_mixer_aux1_switch,
					    codec->me_sw_aux1);
}

static int snd_cs4236_mixer_group_aux2(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_aux2_volume,
					    codec->me_vol_aux2,
					    31,
					    snd_cs4236_mixer_aux2_in_switch,
					    codec->me_sw_aux2_in,
					    snd_cs4231_mixer_aux2_switch,
					    codec->me_sw_aux2);
}
				     
static int snd_cs4236_mixer_group_line(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_line_volume,
					    codec->me_vol_line,
					    31,
					    snd_cs4236_mixer_line_in_switch,
					    codec->me_sw_line_in,
					    snd_cs4231_mixer_line_switch,
					    codec->me_sw_line);
}
				     
static int snd_cs4236_mixer_group_loop_out(void *private_data,
				           snd_kmixer_file_t * file,
				           int w_flag,
				           snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    NULL,
					    NULL,
					    0,
					    snd_cs4236_mixer_analog_loopback_switch,
					    codec->me_sw_loop_analog,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_mono_master(void *private_data,
				              snd_kmixer_file_t * file,
				              int w_flag,
				              snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    NULL,
					    NULL,
					    0,
					    snd_cs4236_mixer_mono_master_switch,
					    codec->me_sw_master_mono,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_mono(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_monoin_volume,
					    codec->me_vol_mono,
					    15,
					    snd_cs4236_mixer_mono_in_switch,
					    codec->me_sw_mono,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_igain(void *private_data,
				        snd_kmixer_file_t * file,
				        int w_flag,
				        snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_igain_volume,
					    codec->me_vol_igain,
					    15,
					    NULL,
					    NULL,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_loop(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	int voices[2];
	int value;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME |
		               SND_MIXER_GRPCAP_MUTE |
		               SND_MIXER_GRPCAP_JOINTLY_MUTE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->mute = 0;
		snd_cs4236_mixer_digital_loopback_volume(codec, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 63;
		snd_cs4236_mixer_digital_loopback_switch(codec, 0, &value);
		if (!value)
			ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
	} else {
		voices[0] = ugroup->volume.names.front_left & 63;
		voices[1] = ugroup->volume.names.front_right & 63;
		if (snd_cs4236_mixer_digital_loopback_volume(codec, 1, voices) > 0) {
			snd_mixer_element_value_change(file, codec->me_vol_loop, 0);
			change = 1;
		}
		value = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_STEREO))
			value = 1;
		if (snd_cs4236_mixer_digital_loopback_switch(codec, 1, &value) > 0) {
			snd_mixer_element_value_change(file, codec->me_sw_loop, 0);
			change = 1;
		}
	}
	return change;
}
				     
static int snd_cs4236_mixer_group_dsp(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4236_mixer_dsp_input_volume,
					    codec->me_vol_dsp,
					    63,
					    snd_cs4236_mixer_dsp_input_switch,
					    codec->me_sw_dsp,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_pcm(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_dac_volume,
					    codec->me_vol_pcm,
					    63,
					    snd_cs4231_mixer_dac_switch,
					    codec->me_sw_pcm,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_fm(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4236_mixer_fm_input_volume,
					    codec->me_vol_fm,
					    63,
					    snd_cs4236_mixer_fm_input_switch,
					    codec->me_sw_fm,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_wavetable(void *private_data,
					    snd_kmixer_file_t * file,
					    int w_flag,
					    snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4236_mixer_wavetable_input_volume,
					    codec->me_vol_wavetable,
					    63,
					    snd_cs4236_mixer_wavetable_input_switch,
					    codec->me_sw_wavetable,
					    NULL,
					    NULL);
}
				     
static int snd_cs4236_mixer_group_master_dig(void *private_data,
					     snd_kmixer_file_t * file,
					     int w_flag,
					     snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4236_mixer_master_digital_volume,
					    codec->me_vol_master_dig,
					    71,
					    snd_cs4236_mixer_master_digital_input_switch,
					    codec->me_sw_master_dig_in,
					    snd_cs4236_mixer_master_digital_output_switch,
					    codec->me_sw_master_dig_out);
}
				     
static int snd_cs4236_create_digital_input(snd_kmixer_t *mixer,
					char *name,
					snd_mixer_volume1_control_t *volume,
					snd_mixer_sw1_control_t *sw,
					int pcm_dev,
					int oss_dev,
					snd_kmixer_group_control_t *gcontrol,
					snd_kmixer_element_t **element_io,
					snd_kmixer_element_t **element_vol,
					snd_kmixer_element_t **element_sw,
					cs4231_t *codec)
{
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *element1, *element2, *element3;
	char xname[32];
	static struct snd_mixer_element_volume1_range table6_range[2] = {
		{0, 63, -9450, 0},
		{0, 63, -9450, 0}
	};
	static struct snd_mixer_element_volume1_range table6_0_range[2] = {
		{0, 63, -8250, 1200},
		{0, 63, -8250, 1200},
	};

	if ((group = snd_mixer_lib_group_ctrl(mixer, name, 0, oss_dev, gcontrol, codec)) == NULL)
		goto __error;
	if (pcm_dev >= 0) {
		if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
			goto __error;
	} else {
		if ((element1 = snd_mixer_lib_io_stereo(mixer, name, 0, SND_MIXER_ETYPE_INPUT, SND_MIXER_EIO_DIGITAL)) == NULL)
			goto __error;
	}
	sprintf(xname, "%s Volume", name);
	if ((element2 = snd_mixer_lib_volume1(mixer, xname, 0, 2, 
				*element_io == codec->me_in_wavetable ?
					table6_0_range : table6_range,
				volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	sprintf(xname, "%s Switch", name);
	if ((element3 = snd_mixer_lib_sw1(mixer, xname, 0, 2, sw, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, codec->me_dig_accu) < 0)
		goto __error;
	*element_io = element1;
	*element_vol = element2;
	*element_sw = element3;
	return 0;

      __error:
      	return -EINVAL;
}

static int snd_cs4236_mixer_3d_effect1(void *private_data, int w_flag, struct snd_mixer_element_3d_effect1 *effect1)
{
	cs4231_t *codec = (cs4231_t *)private_data;
	unsigned long flags;
	int val1, val2, change = 0;
	unsigned int sw, mono_sw;

	spin_lock_irqsave(&codec->reg_lock, flags);
	val1 = snd_cs4236_ctrl_in(codec, 2) >> 4;
	val2 = snd_cs4236_ctrl_in(codec, 2) & 0x0f;
	sw = (snd_cs4236_ctrl_in(codec, 3) & (1 << codec->sw_3d_bit)) ? 1 : 0;
	mono_sw = (snd_cs4236_ctrl_in(codec, 3) & 0x40) ? 1 : 0;
	if (!w_flag) {
		effect1->sw = sw;
		effect1->mono_sw = mono_sw;
		effect1->space = val1;
		if (codec->hardware == CS4231_HW_CS4237B) {
			effect1->center = val2;
		} else if (codec->hardware == CS4231_HW_CS4238B) {
			effect1->volume = val2;
		}
	} else {
		if (codec->hardware == CS4231_HW_CS4237B) {
			if (effect1->mono_sw) {
				val1 = 0; val2 = 2;	/* forced values */
			}
		}
		change = effect1->sw != sw || effect1->space != val1;
		if (codec->hardware == CS4231_HW_CS4237B) {
			change |= effect1->mono_sw != mono_sw ||
				  effect1->center != val2;
			mono_sw = effect1->mono_sw;
			val2 = effect1->center;
		} else if (codec->hardware == CS4231_HW_CS4238B) {
			change |= effect1->volume != val2;
			val2 = effect1->volume;
		}
		snd_cs4236_ctrl_out(codec, 2, (effect1->space << 4) | val2);
		snd_cs4236_ctrl_outm(codec, 3, 0xbf & ~(1 << codec->sw_3d_bit), (effect1->sw << codec->sw_3d_bit) | (mono_sw << 6));
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;
}

static snd_kmixer_element_t *snd_cs4236_create_3d_element(snd_kmixer_t *mixer, cs4231_t *codec)
{
	snd_kmixer_element_t *element;
	struct snd_mixer_element_3d_effect1_info info;
	
	if (codec->hardware != CS4231_HW_CS4237B &&
	    codec->hardware != CS4231_HW_CS4238B &&
	    codec->hardware != CS4231_HW_CS4235 &&
	    codec->hardware != CS4231_HW_CS4239)
		return NULL;
	memset(&info, 0, sizeof(info));
	info.effect = SND_MIXER_EFF1_SW;
	codec->sw_3d_bit = 7;
	if (codec->hardware == CS4231_HW_CS4237B) {
		info.effect |= SND_MIXER_EFF1_MONO_SW | 
		               SND_MIXER_EFF1_SPACE |
		               SND_MIXER_EFF1_CENTER;
	} else if (codec->hardware == CS4231_HW_CS4238B) {
		codec->sw_3d_bit = 4;
		info.effect |= SND_MIXER_EFF1_VOLUME |
			       SND_MIXER_EFF1_SPACE;
	} else if (codec->hardware == CS4231_HW_CS4235 ||
		   codec->hardware == CS4231_HW_CS4239) {
		codec->sw_3d_bit = 4;
		info.effect |= SND_MIXER_EFF1_SPACE;
	}
	info.max_center = info.max_space = 15;
	element = snd_mixer_lib_3d_effect1(mixer, SND_MIXER_GRP_EFFECT, 0,
					   &info,
					   snd_cs4236_mixer_3d_effect1,
					   codec);
	return element;
}

static int snd_cs4236_build_mixer(cs4231_t * codec, snd_kmixer_t * mixer, int pcm_device)
{
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range table6_range[2] = {
		{0, 63, -9450, 0},
		{0, 63, -9450, 0},
	};
	static struct snd_mixer_element_volume1_range table7_0_range[2] = {
		{0, 15, 0, 2250},
		{0, 15, 0, 2250},
	};
	static struct snd_mixer_element_volume1_range table7_1_range[2] = {
		{0, 15, -4500, 0},
		{0, 15, -4500, 0},
	};
	static struct snd_mixer_element_accu3_range table8_range[2] = {
		{0, 3, -1800, 0},
		{0, 3, -1800, 0},
	};
	static struct snd_mixer_element_volume1_range table10_range[2] = {
		{0, 31, -3450, 1200},
		{0, 31, -3450, 1200}
	};
	static struct snd_mixer_element_volume1_range table12_range[2] = {
		{0, 71, -9450, 1200},
		{0, 71, -9450, 1200}
	};
	static struct snd_mixer_element_volume1_range table13_range[2] = {
		{0, 31, -2400, 2250},
		{0, 31, -2400, 2250}
	};
	static struct snd_mixer_element_volume1_range mic_range[2] = {
		{0, 1, 0, 2000},
		{0, 1, 0, 2000},
	};

	/* build input, output and digital accumulators */
	if ((codec->me_in_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 2, table8_range, snd_cs4236_mixer_input_accu_volume, codec)) == NULL)
		goto __error;
	if ((codec->me_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((codec->me_dig_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_DIGITAL_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((codec->me_mono_accu = snd_mixer_lib_accu2(mixer, SND_MIXER_ELEMENT_MONO_OUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_cs4236_mixer_group_mic, codec)) == NULL)
		goto __error;
	if ((codec->me_in_mic = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, table13_range, snd_cs4236_mixer_mic_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_in_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mic, codec->me_vol_mic) < 0)
		goto __error;
	if ((codec->me_sw_mic_in = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, snd_cs4236_mixer_mic_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_sw_mic_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_mic_out = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 2, snd_cs4236_mixer_mic_out_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_sw_mic_out) < 0)
		goto __error;
	if ((codec->me_vol_mic_out = snd_mixer_lib_volume1(mixer, "MIC Volume Output", 0, 2, mic_range, snd_cs4236_mixer_mic_out_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_out, codec->me_vol_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic_out, codec->me_accu) < 0)
		goto __error;
	/* build AUX1 input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_OSS_LINE1, snd_cs4236_mixer_group_aux1, codec)) == NULL)
		goto __error;
	if ((codec->me_in_aux1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_sw_aux1_bypass = snd_mixer_lib_sw1(mixer, "Aux Input Bypass Switch", 0, 2, snd_cs4236_mixer_aux1_bypass_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux1_bypass) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_aux1, codec->me_sw_aux1_bypass) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux1_bypass, codec->me_accu) < 0)
		goto __error;
	if ((codec->me_vol_aux1 = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, table10_range, snd_cs4231_mixer_aux1_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_aux1, codec->me_vol_aux1) < 0)
		goto __error;
	if ((codec->me_sw_aux1_in = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 0, 2, snd_cs4236_mixer_aux1_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux1_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux1, codec->me_sw_aux1_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux1_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_aux1 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 0, 2, snd_cs4231_mixer_aux1_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux1, codec->me_sw_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux1, codec->me_accu) < 0)
		goto __error;
	/* build AUX2 input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_OSS_LINE2, snd_cs4236_mixer_group_aux2, codec)) == NULL)
		goto __error;
	if ((codec->me_in_aux2 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_aux2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 1, 2, table10_range, snd_cs4231_mixer_aux2_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_aux2, codec->me_vol_aux2) < 0)
		goto __error;
	if ((codec->me_sw_aux2_in = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 1, 2, snd_cs4236_mixer_aux2_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux2, codec->me_sw_aux2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux2_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_aux2 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 1, 2, snd_cs4231_mixer_aux2_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux2, codec->me_sw_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux2, codec->me_accu) < 0)
		goto __error;
	/* build LINE input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_cs4236_mixer_group_line, codec)) == NULL)
		goto __error;
	if ((codec->me_in_line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_sw_line_bypass = snd_mixer_lib_sw1(mixer, "Line Input Bypass Switch", 0, 2, snd_cs4236_mixer_line_bypass_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_line_bypass) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_line, codec->me_sw_line_bypass) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_line_bypass, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, table10_range, snd_cs4231_mixer_line_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_line, codec->me_vol_line) < 0)
		goto __error;
	if ((codec->me_sw_line_in = snd_mixer_lib_sw1(mixer, "Line Input Switch", 0, 2, snd_cs4236_mixer_line_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_line_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_line, codec->me_sw_line_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_line_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_line = snd_mixer_lib_sw1(mixer, "Line Output Switch", 0, 2, snd_cs4231_mixer_line_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_line, codec->me_sw_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_line, codec->me_accu) < 0)
		goto __error;
	/* build master output */
	if ((codec->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_out_master) < 0)
		goto __error;
	/* output loopback */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_ANALOG_LOOPBACK, 0, SND_MIXER_OSS_UNKNOWN, snd_cs4236_mixer_group_loop_out, codec)) == NULL)
		goto __error;
	if ((codec->me_sw_loop_analog = snd_mixer_lib_sw1(mixer, "Analog Loopback Switch", 0, 2, snd_cs4236_mixer_analog_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_loop_analog) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_sw_loop_analog) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_loop_analog, codec->me_in_accu) < 0)
		goto __error;
	/* mono output */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER_MONO, 0, SND_MIXER_OSS_UNKNOWN, snd_cs4236_mixer_group_mono_master, codec)) == NULL)
		goto __error;
	if ((codec->me_sw_master_mono = snd_mixer_lib_sw1(mixer, "Mono Master Switch", 0, 2, snd_cs4236_mixer_mono_master_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_sw_master_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master_mono, codec->me_mono_accu) < 0)
		goto __error;
	if ((codec->me_out_master_mono = snd_mixer_lib_io_mono(mixer, SND_MIXER_OUT_MASTER_MONO, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_mono_accu, codec->me_out_master_mono) < 0)
		goto __error;
	/* mono input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_OSS_SPEAKER, snd_cs4236_mixer_group_mono, codec)) == NULL)
		goto __error;
	if ((codec->me_in_mono = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_mono = snd_mixer_lib_volume1(mixer, "Mono Input Volume", 0, 1, table7_1_range, snd_cs4231_mixer_monoin_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mono, codec->me_vol_mono) < 0)
		goto __error;
	if ((codec->me_sw_mono = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 0, 2, snd_cs4236_mixer_mono_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mono, codec->me_sw_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mono, codec->me_accu) < 0)
		goto __error;
	/* mono bypass */
	if ((group = snd_mixer_lib_group(mixer, "Mono Bypass", 0)) == NULL)
		goto __error;
	if ((codec->me_sw_mono_bypass = snd_mixer_lib_sw1(mixer, "Mono Bypass Switch", 0, 1, snd_cs4231_mixer_mono_bypass_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mono_bypass) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mono, codec->me_sw_mono_bypass) < 0)
		goto __error;
	if ((codec->me_sw_mono_bypass_atten = snd_mixer_lib_accu1(mixer, "Mono Bypass Attenuation", 0, -900)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mono_bypass, codec->me_sw_mono_bypass_atten) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mono_bypass_atten, codec->me_mono_accu) < 0)
		goto __error;
	/* input gain */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN, 0, SND_MIXER_OSS_IGAIN, snd_cs4236_mixer_group_igain, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_igain = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, table7_0_range, snd_cs4231_mixer_igain_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_igain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_accu, codec->me_vol_igain) < 0)
		goto __error;
	/* build ADC */
	if ((codec->me_adc = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_ADC, 0, SND_MIXER_ETYPE_ADC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_igain, codec->me_adc) < 0)
		goto __error;
	/* Capture endpoint */
	if ((codec->me_capture = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, &pcm_device)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc, codec->me_capture) < 0)
		goto __error;
	/* DSP output */
	if ((codec->me_out_dsp = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_DSP, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc, codec->me_out_dsp) < 0)
		goto __error;
	/* digital loopback */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_DIGITAL_LOOPBACK, 0, SND_MIXER_OSS_UNKNOWN, snd_cs4236_mixer_group_loop, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_loop = snd_mixer_lib_volume1(mixer, "Digital Loopback Volume", 0, 2, table6_range, snd_cs4236_mixer_digital_loopback_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_loop) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc, codec->me_vol_loop) < 0)
		goto __error;
	if ((codec->me_sw_loop = snd_mixer_lib_sw2(mixer, "Digital Loopback Switch", 0, snd_cs4236_mixer_digital_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_loop) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_loop, codec->me_sw_loop) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_loop, codec->me_dig_accu) < 0)
		goto __error;
	/* DSP input */
	if (snd_cs4236_create_digital_input(mixer, SND_MIXER_IN_DSP,
					snd_cs4236_mixer_dsp_input_volume,
					snd_cs4236_mixer_dsp_input_switch,
					-1,
					SND_MIXER_OSS_DIGITAL1,
					snd_cs4236_mixer_group_dsp,
					&codec->me_in_dsp,
					&codec->me_vol_dsp,
					&codec->me_sw_dsp,
					codec) < 0)
		goto __error;
	/* PCM input */
	if (snd_cs4236_create_digital_input(mixer, SND_MIXER_IN_PCM,
					snd_cs4231_mixer_dac_volume,
					snd_cs4231_mixer_dac_switch,
					0,
					SND_MIXER_OSS_PCM,
					snd_cs4236_mixer_group_pcm,
					&codec->me_playback,
					&codec->me_vol_pcm,
					&codec->me_sw_pcm,
					codec) < 0)
		goto __error;
	/* FM input */
	if (snd_cs4236_create_digital_input(mixer, SND_MIXER_IN_FM,
					snd_cs4236_mixer_fm_input_volume,
					snd_cs4236_mixer_fm_input_switch,
					-1,
					SND_MIXER_OSS_SYNTH,
						snd_cs4236_mixer_group_fm,
					&codec->me_in_fm,
					&codec->me_vol_fm,
					&codec->me_sw_fm,
					codec) < 0)
		goto __error;
	/* Wavetable input */
	if (snd_cs4236_create_digital_input(mixer, SND_MIXER_IN_SYNTHESIZER,
					snd_cs4236_mixer_wavetable_input_volume,
					snd_cs4236_mixer_wavetable_input_switch,
					-1,
					SND_MIXER_OSS_DIGITAL2,
					snd_cs4236_mixer_group_wavetable,
					&codec->me_in_wavetable,
					&codec->me_vol_wavetable,
					&codec->me_sw_wavetable,
					codec) < 0)
		goto __error;
	/* 3D effect */
	codec->me_3d = snd_cs4236_create_3d_element(mixer, codec);
	if (codec->me_3d) {
		if (snd_mixer_element_route_add(mixer, codec->me_dig_accu, codec->me_3d) < 0)
			goto __error;
	}
	/* build DAC */
	if ((codec->me_dac = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_DAC, 0, SND_MIXER_ETYPE_DAC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_3d ? codec->me_3d : codec->me_dig_accu, codec->me_dac) < 0)
		goto __error;
	/* master digital */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER_DIGITAL, 0, SND_MIXER_OSS_VOLUME, snd_cs4236_mixer_group_master_dig, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_master_dig = snd_mixer_lib_volume1(mixer, "Master Digital Volume", 0, 2, table12_range, snd_cs4236_mixer_master_digital_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_master_dig) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac, codec->me_vol_master_dig) < 0)
		goto __error;
	if ((codec->me_sw_master_dig_in = snd_mixer_lib_sw1(mixer, "Master Digital In-SW", 0, 2, snd_cs4236_mixer_master_digital_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master_dig_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master_dig, codec->me_sw_master_dig_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master_dig_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_master_dig_out = snd_mixer_lib_sw1(mixer, "Master Digital Out-SW", 0, 2, snd_cs4236_mixer_master_digital_output_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master_dig_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master_dig, codec->me_sw_master_dig_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master_dig_out, codec->me_accu) < 0)
		goto __error;

	return 0;	

      __error:
      	return -ENOMEM;
}

static int snd_cs4235_mixer_group_master(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4235_mixer_master_volume,
					    codec->me_vol_master,
					    31,
					    snd_cs4235_mixer_master_switch,
					    codec->me_sw_master,
					    NULL,
					    NULL);
}

static int snd_cs4235_mixer_group_pcm(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_dac_volume,
					    codec->me_vol_pcm,
					    63,
					    snd_cs4231_mixer_dac_switch,
					    codec->me_sw_pcm,
					    snd_cs4236_mixer_master_digital_input_switch,
					    codec->me_sw_pcm_in);
}

static int snd_cs4235_mixer_group_dsp(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    NULL,
					    NULL,
					    0,
					    snd_cs4236_mixer_dsp_input_switch,
					    codec->me_sw_dsp,
					    NULL,
					    NULL);
}

static int snd_cs4235_mixer_group_fm(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    NULL,
					    NULL,
					    0,
					    snd_cs4236_mixer_fm_input_switch,
					    codec->me_sw_fm,
					    NULL,
					    NULL);
}

static int snd_cs4235_mixer_group_wavetable(void *private_data,
					    snd_kmixer_file_t * file,
					    int w_flag,
					    snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    NULL,
					    NULL,
					    0,
					    snd_cs4236_mixer_wavetable_input_switch,
					    codec->me_sw_wavetable,
					    NULL,
					    NULL);
}

static int snd_cs4235_mixer_group_synth_dac(void *private_data,
					    snd_kmixer_file_t * file,
					    int w_flag,
					    snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4236_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_cs4231_mixer_line_volume,
					    codec->me_vol_dac2,
					    31,
					    snd_cs4231_mixer_line_switch,
					    codec->me_sw_dac2_out,
					    snd_cs4236_mixer_line_in_switch,
					    codec->me_sw_dac2_in);
}

static int snd_cs4235_build_mixer(cs4231_t * codec, snd_kmixer_t * mixer, int pcm_device)
{
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range table6_range[2] = {
		{0, 63, -9450, 0},
		{0, 63, -9450, 0}
	};
	static struct snd_mixer_element_volume1_range table7_1_range[2] = {
		{0, 15, -4500, 0},
		{0, 15, -4500, 0},
	};
	static struct snd_mixer_element_accu3_range table8_range[2] = {
		{0, 3, -1800, 0},
		{0, 3, -1800, 0},
	};
	static struct snd_mixer_element_volume1_range table9_range[2] = {
		{0, 31, -5600, 600},
		{0, 31, -5600, 600},
	};
	static struct snd_mixer_element_accu3_range table9_A_range[2] = {
		{0, 3, -2400, 0},
		{0, 3, -2400, 0},
	};
	static struct snd_mixer_element_volume1_range table10_range[2] = {
		{1, 31, -3300, 1200},
		{1, 31, -3300, 1200}
	};
	static struct snd_mixer_element_volume1_range table9_1_range[2] = {
		{1, 31, -2250, 2250},
		{1, 31, -2250, 2250}
	};
	static struct snd_mixer_element_volume1_range mic_range[2] = {
		{0, 1, 0, 2000},
		{0, 1, 0, 2000},
	};

	/* build input, output and digital accumulators */
	if ((codec->me_in_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 2, table8_range, snd_cs4236_mixer_input_accu_volume, codec)) == NULL)
		goto __error;
	if ((codec->me_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 2, table9_A_range, snd_cs4235_mixer_output_accu_volume, codec)) == NULL)
		goto __error;
	if ((codec->me_dig_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_DIGITAL_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_cs4236_mixer_group_mic, codec)) == NULL)
		goto __error;
	if ((codec->me_in_mic = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, table9_1_range, snd_cs4235_mixer_mic_mono_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mic, codec->me_vol_mic) < 0)
		goto __error;
	if ((codec->me_vol_mic_boost = snd_mixer_lib_volume1(mixer, "MIC Volume Boost", 0, 1, mic_range, snd_cs4235_mixer_mic_boost_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mic_boost) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_vol_mic_boost) < 0)
		goto __error;
	if ((codec->me_sw_mic_in = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, snd_cs4236_mixer_mic_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic_boost, codec->me_sw_mic_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_mic_out = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 2, snd_cs4236_mixer_mic_out_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic_boost, codec->me_sw_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_out, codec->me_accu) < 0)
		goto __error;
	/* build AUX1 input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_OSS_LINE1, snd_cs4236_mixer_group_aux1, codec)) == NULL)
		goto __error;
	if ((codec->me_in_aux1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_aux1 = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, table10_range, snd_cs4231_mixer_aux1_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_aux1, codec->me_vol_aux1) < 0)
		goto __error;
	if ((codec->me_sw_aux1_in = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 0, 2, snd_cs4236_mixer_aux1_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux1_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux1, codec->me_sw_aux1_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux1_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_aux1 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 0, 2, snd_cs4231_mixer_aux1_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux1, codec->me_sw_aux1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux1, codec->me_accu) < 0)
		goto __error;
	/* build AUX2 input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_OSS_LINE2, snd_cs4236_mixer_group_aux2, codec)) == NULL)
		goto __error;
	if ((codec->me_in_aux2 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_aux2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 1, 2, table10_range, snd_cs4231_mixer_aux2_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_aux2, codec->me_vol_aux2) < 0)
		goto __error;
	if ((codec->me_sw_aux2_in = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 1, 2, snd_cs4236_mixer_aux2_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux2, codec->me_sw_aux2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux2_in, codec->me_in_accu) < 0)
		goto __error;
	if ((codec->me_sw_aux2 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 1, 2, snd_cs4231_mixer_aux2_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux2, codec->me_sw_aux2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_aux2, codec->me_accu) < 0)
		goto __error;
	/* build master output */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_cs4235_mixer_group_master, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 1, 2, table9_range, snd_cs4235_mixer_master_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_vol_master) < 0)
		goto __error;
	if ((codec->me_sw_master = snd_mixer_lib_sw1(mixer, "Master Switch", 1, 2, snd_cs4235_mixer_master_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master, codec->me_sw_master) < 0)
		goto __error;
	if ((codec->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master, codec->me_out_master) < 0)
		goto __error;
	/* output loopback */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_ANALOG_LOOPBACK, 0, SND_MIXER_OSS_UNKNOWN, snd_cs4236_mixer_group_loop_out, codec)) == NULL)
		goto __error;
	if ((codec->me_sw_loop_analog = snd_mixer_lib_sw1(mixer, "Analog Loopback Switch", 0, 2, snd_cs4236_mixer_analog_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_loop_analog) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master, codec->me_sw_loop_analog) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_loop_analog, codec->me_in_accu) < 0)
		goto __error;
	/* mono input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_OSS_SPEAKER, snd_cs4236_mixer_group_mono, codec)) == NULL)
		goto __error;
	if ((codec->me_in_mono = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_mono = snd_mixer_lib_volume1(mixer, "Mono Input Volume", 0, 1, table7_1_range, snd_cs4231_mixer_monoin_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mono, codec->me_vol_mono) < 0)
		goto __error;
	if ((codec->me_sw_mono = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 0, 2, snd_cs4236_mixer_mono_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mono, codec->me_sw_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mono, codec->me_accu) < 0)
		goto __error;
	/* build ADC */
	if ((codec->me_adc = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_ADC, 0, SND_MIXER_ETYPE_ADC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_accu, codec->me_adc) < 0)
		goto __error;
	/* Capture endpoint */
	if ((codec->me_capture = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, &pcm_device)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc, codec->me_capture) < 0)
		goto __error;
	/* DSP output */
	if ((codec->me_out_dsp = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_DSP, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc, codec->me_out_dsp) < 0)
		goto __error;
	/* PCM input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_cs4235_mixer_group_pcm, codec)) == NULL)
		goto __error;
	if ((codec->me_playback = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_device)) == NULL)
		goto __error;
	if ((codec->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, table6_range, snd_cs4231_mixer_dac_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_playback, codec->me_vol_pcm) < 0)
		goto __error;
	/* 3D effect */
	if ((codec->me_3d = snd_cs4236_create_3d_element(mixer, codec)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_pcm, codec->me_3d) < 0)
		goto __error;
	/* build DAC */
	if ((codec->me_dac = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_DAC, 0, SND_MIXER_ETYPE_DAC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_3d, codec->me_dac) < 0)
		goto __error;
	/* PCM output/input switches */
	if ((codec->me_sw_pcm = snd_mixer_lib_sw1(mixer, "PCM Output Switch", 0, 2, snd_cs4231_mixer_dac_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac, codec->me_sw_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_pcm, codec->me_accu) < 0)
		goto __error;
	if ((codec->me_sw_pcm_in = snd_mixer_lib_sw1(mixer, "PCM Input Switch", 0, 2, snd_cs4236_mixer_master_digital_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_pcm_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac, codec->me_sw_pcm_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_pcm_in, codec->me_in_accu) < 0)
		goto __error;
	/* DSP input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_DSP, 0, SND_MIXER_OSS_DIGITAL1, snd_cs4235_mixer_group_dsp, codec)) == NULL)
		goto __error;
	if ((codec->me_in_dsp = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_DSP, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_sw_dsp = snd_mixer_lib_sw1(mixer, "DSP Switch", 0, 2, snd_cs4236_mixer_dsp_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_dsp) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_dsp, codec->me_sw_dsp) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_dsp, codec->me_dig_accu) < 0)
		goto __error;
	/* FM input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_OSS_DIGITAL2, snd_cs4235_mixer_group_fm, codec)) == NULL)
		goto __error;
	if ((codec->me_in_fm = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_sw_fm = snd_mixer_lib_sw1(mixer, "FM Switch", 0, 2, snd_cs4236_mixer_fm_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_fm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_fm, codec->me_sw_fm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_fm, codec->me_dig_accu) < 0)
		goto __error;
	/* WaveTable input */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_DIGITAL3, snd_cs4235_mixer_group_wavetable, codec)) == NULL)
		goto __error;
	if ((codec->me_in_wavetable = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_sw_wavetable = snd_mixer_lib_sw1(mixer, "Synth Switch", 0, 2, snd_cs4236_mixer_wavetable_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_wavetable) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_wavetable, codec->me_sw_wavetable) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_wavetable, codec->me_dig_accu) < 0)
		goto __error;
	/* build DAC2 */
	if ((group = snd_mixer_lib_group_ctrl(mixer, "Synth DAC", 0, SND_MIXER_OSS_LINE3, snd_cs4235_mixer_group_synth_dac, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_dac2 = snd_mixer_lib_volume1(mixer, "Synth DAC Volume", 1, 2, table10_range, snd_cs4231_mixer_line_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_dac2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dig_accu, codec->me_vol_dac2) < 0)
		goto __error;
	if ((codec->me_dac2 = snd_mixer_lib_converter(mixer, "Synth DAC", 0, SND_MIXER_ETYPE_DAC, 16)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_dac2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_dac2, codec->me_dac2) < 0)
		goto __error;
	if ((codec->me_sw_dac2_out = snd_mixer_lib_sw1(mixer, "Synth DAC Output", 0, 2, snd_cs4231_mixer_line_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_dac2_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac2, codec->me_sw_dac2_out) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_dac2_out, codec->me_accu) < 0)
		goto __error;
	if ((codec->me_sw_dac2_in = snd_mixer_lib_sw1(mixer, "Synth DAC Input", 0, 2, snd_cs4236_mixer_line_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_dac2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac2, codec->me_sw_dac2_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_dac2_in, codec->me_in_accu) < 0)
		goto __error;

	return 0;	

      __error:
      	return -ENOMEM;
}

snd_kmixer_t *snd_cs4236_new_mixer(snd_pcm_t * pcm, int pcm_device)
{
	snd_kmixer_t *mixer;
	cs4231_t *codec;

	if (!pcm || !pcm->card)
		return NULL;
	codec = (cs4231_t *) pcm->private_data;
	if (!codec)
		return NULL;
	mixer = snd_mixer_new(pcm->card, pcm->id);
	if (!mixer)
		return NULL;
	strcpy(mixer->name, pcm->name);
	
	/* initialize some mixer registers */
	snd_cs4236_ext_out(codec, CS4236_LEFT_MIX_CTRL, 0x80 | 0x18);
	snd_cs4236_ext_out(codec, CS4236_RIGHT_MIX_CTRL, 0x80);
	snd_cs4236_ext_out(codec, CS4236_RIGHT_LOOPBACK, 0xbf);
	snd_cs4231_outm(codec, CS4231_LEFT_INPUT, 0x3f, 0x40);
	snd_cs4231_outm(codec, CS4231_RIGHT_INPUT, 0x3f, 0x40);

	if (codec->hardware == CS4231_HW_CS4235 ||
	    codec->hardware == CS4231_HW_CS4239) {
		if (snd_cs4235_build_mixer(codec, mixer, pcm_device) < 0)
			goto __error;
	} else {
		if (snd_cs4236_build_mixer(codec, mixer, pcm_device) < 0)
			goto __error;
	}

	/* build a IEC958 control switch */
	if (codec->hardware == CS4231_HW_CS4237B ||
	    codec->hardware == CS4231_HW_CS4238B) {
		snd_mixer_switch_new(mixer, &snd_cs4236_iec958, codec);
	}
	mixer->private_data = codec;
	codec->mixer = mixer;
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

EXPORT_SYMBOL(snd_cs4236_new_device);
EXPORT_SYMBOL(snd_cs4236_new_mixer);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
