/*
 *  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 Ouput (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 prerecorded 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 "driver.h"
#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(snd_pcm1_t * pcm1, cs4231_t * codec, unsigned int rate)
{
	unsigned int rrate;

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

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

	snd_spin_lock(codec, reg, &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(pcm1->playback.real_rate, NULL));
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_cs4236_record_format(snd_pcm1_t * pcm1, cs4231_t * codec, unsigned char cdfr)
{
	unsigned long flags;

	snd_spin_lock(codec, reg, &flags);
	/* set fast record format change and clean record 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(pcm1->record.real_rate, NULL));
	snd_spin_unlock(codec, reg, &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_t *pcm1;
	cs4231_t *codec;
	unsigned char ver;

	pcm = snd_cs4231_new_device(card, port, irqptr, dmaptr1, dmaptr2, hardware, timer_dev);
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (cs4231_t *) pcm1->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) {
			pcm1->playback.hw.formats =
			    pcm1->record.hw.formats = SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pcm1->playback.hw.hw_formats =
			    pcm1->record.hw.hw_formats = SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE;
			pcm1->playback.hw.dma = snd_pcm1_playback_dma_ulaw_loud;
			pcm1->record.hw.dma = snd_pcm1_record_dma_ulaw_loud;
		}
	}
	pcm->info_flags &= ~SND_PCM_INFO_DUPLEX_LIMIT;
	codec->set_playback_rate =
	    codec->set_record_rate = snd_cs4236_xrate;
	codec->set_playback_format = snd_cs4236_playback_format;
	codec->set_record_format = snd_cs4236_record_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(int w_flag, int *voices, cs4231_t *codec,
					  int max, int invert, int shift,
					  unsigned char left_reg,
					  unsigned char right_reg)
{
	int change = 0, left, right;
	unsigned long flags;

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

#if 0

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

	snd_spin_lock(codec, reg, &flags);
	val = (codec->eimage[CS4236_REG(reg)] >> bit) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, invert ? val ^ 1 : val);
	} else {
		change = (invert ? val ^ 1 : val) != snd_mixer_get_bit(bitmap, 0);
		val = snd_mixer_get_bit(bitmap, 0);
		if (invert)
			val ^= 1;
		snd_cs4236_ext_outm(codec, reg, ~(1 << bit), val << bit);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

#endif

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

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

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

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

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

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

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

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

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

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

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

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

	snd_spin_lock(codec, reg, &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));
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

	snd_spin_lock(codec, reg, &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));
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

	snd_spin_lock(codec, reg, &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]));
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

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

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

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

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

static int snd_cs4236_mixer_wavetable_input_switch(int w_flag, unsigned int *bitmap, cs4231_t *codec)
{
	return snd_cs4236_mixer_stereo_switch(w_flag, bitmap, codec,
						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(int w_flag, int *voices, cs4231_t *codec)
{
	int change = 0, left, right;
	unsigned long flags;

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

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

	snd_spin_lock(codec, reg, &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));
	}
	snd_spin_unlock(codec, reg, &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(int w_flag, int *voices, cs4231_t *codec)
{
	int change = 0, left, right;
	unsigned long flags;

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);
	return change;	
}

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

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

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

static int snd_cs4235_mixer_master_switch(int w_flag, unsigned int *bitmap, cs4231_t *codec)
{
	return snd_cs4231_mixer_stereo_switch(w_flag, bitmap, codec,
						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;
	snd_spin_lock(codec, reg, &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;
	snd_spin_unlock(codec, reg, &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

	snd_spin_lock(codec, reg, &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);
	}
	snd_spin_unlock(codec, reg, &flags);

	snd_mutex_down(codec, mce);
	snd_cs4231_mce_up(codec);
	snd_spin_lock(codec, reg, &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]);
	snd_spin_unlock(codec, reg, &flags);
	snd_cs4231_mce_down(codec);
	snd_mutex_up(codec, mce);
	
	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) {
			snd_schedule(codec, iec958, time);
			time = snd_timevalue(codec, iec958);
		}

		snd_spin_lock(codec, reg, &flags);
		if (setflag) {
			snd_cs4236_ctrl_outm(codec, 4, 0x0f, (ctrl & 0xff) | 0x40);
		} else {
			snd_cs4236_ctrl_outm(codec, 4, 0x3f, (ctrl & 0xff) | 0x40);
		}
		snd_spin_unlock(codec, reg, &flags);

		time = HZ / 5;
		while (time > 0) {

			time = snd_timevalue(codec, iec958);
		}

		snd_spin_lock(codec, reg, &flags);
		if (setflag) {
			snd_cs4236_ctrl_outm(codec, 4, 0x0f, (ctrl & 0xff) & ~0x40);
		} else {
			snd_cs4236_ctrl_outm(codec, 4, 0x3f, (ctrl & 0xff) & ~0x40);
		}
		snd_spin_unlock(codec, reg, &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_create_digital_input(snd_kmixer_t *mixer,
					snd_kmixer_element_t *dig_accu,
					char *name,
					snd_mixer_volume1_control_t *volume,
					snd_mixer_sw1_control_t *sw,
					int pcm_dev,
					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}
	};

	if ((group = snd_mixer_lib_group(mixer, name, 0)) == 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, 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, dig_accu) < 0)
		goto __error;
	return 0;

      __error:
      	return -EINVAL;
}

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

	snd_spin_lock(codec, reg, &flags);
	val1 = snd_cs4236_ctrl_in(codec, 2) >> 4;
	val2 = snd_cs4236_ctrl_in(codec, 2) & 0x0f;
	sw = (snd_cs4236_ctrl_in(codec, 3) & 0x80) ? 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;
		} else if (codec->hardware == CS4231_HW_CS4238B) {
			change |= effect1->volume != val2;
		}
		snd_cs4236_ctrl_out(codec, 2, (val1 << 4) | val2);
		snd_cs4236_ctrl_outm(codec, 3, 0x3f, (sw << 7) | (mono_sw << 6));
	}
	snd_spin_unlock(codec, reg, &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;
	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) {
		info.effect = SND_MIXER_EFF1_VOLUME |
			      SND_MIXER_EFF1_SPACE;
	} else if (codec->hardware == CS4231_HW_CS4235 ||
		   codec->hardware == CS4231_HW_CS4239) {
		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_mixer_3d_effect1_control_t *)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_element_t *in_accu;	/* input accumulator */
	snd_kmixer_element_t *out_accu;	/* output accumulator */
	snd_kmixer_element_t *dig_accu;	/* digital accumulator */
	snd_kmixer_element_t *mono_accu; /* mono output accumulator */
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	snd_kmixer_group_t *group;
	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 ((in_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 2, table8_range, (snd_mixer_accu3_control_t *)snd_cs4236_mixer_input_accu_volume, codec)) == NULL)
		goto __error;
	if ((out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((dig_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_DIGITAL_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((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(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, table13_range, (snd_mixer_volume1_control_t *)snd_cs4236_mixer_mic_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mic_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mic_out_switch, 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 ((element4 = snd_mixer_lib_volume1(mixer, "MIC Volume Output", 0, 2, mic_range, (snd_mixer_volume1_control_t *)snd_cs4236_mixer_mic_out_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build AUX1 input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "Aux Input Bypass Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_aux1_bypass_switch, 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;
	if (snd_mixer_element_route_add(mixer, element2, in_accu) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_aux1_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_aux1_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_aux1_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build AUX2 input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 1)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 1, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_aux2_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_aux2_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_aux2_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build LINE input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_LINE, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "Line Input Bypass Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_line_bypass_switch, 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;
	if (snd_mixer_element_route_add(mixer, element2, in_accu) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_line_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Line Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_line_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Line Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_line_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build master output */
	if ((element1 = 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, out_accu, element1) < 0)
		goto __error;
	/* output loopback */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_ANALOG_LOOPBACK, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_sw1(mixer, "Analog Loopback Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_analog_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, in_accu) < 0)
		goto __error;
	/* mono output */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER_MONO, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_sw1(mixer, "Mono Master Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mono_master_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, mono_accu) < 0)
		goto __error;
	if ((element2 = 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, mono_accu, element2) < 0)
		goto __error;
	/* mono input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MONO, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Mono Input Volume", 0, 1, table7_1_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_monoin_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mono_in_switch, 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, out_accu) < 0)
		goto __error;
	/* mono bypass */
	if ((group = snd_mixer_lib_group(mixer, "Mono Bypass", 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "Mono Bypass Switch", 0, 1, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_mono_bypass_switch, 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;
	if ((element3 = snd_mixer_lib_accu1(mixer, "Mono Bypass Attenuation", 0, -900)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, mono_accu) < 0)
		goto __error;
	/* input gain */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_IGAIN, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, table7_0_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_igain_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, in_accu, element1) < 0)
		goto __error;
	/* build ADC */
	if ((element2 = 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, element1, element2) < 0)
		goto __error;
	/* Capture endpoint */
	if ((element3 = 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, element2, element3) < 0)
		goto __error;
	/* DSP output */
	if ((element3 = 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, element2, element3) < 0)
		goto __error;
	/* digital loopback */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_DIGITAL_LOOPBACK, 0)) == NULL)
		goto __error;
	if ((element3 = snd_mixer_lib_volume1(mixer, "Digital Loopback Volume", 0, 2, table7_1_range, (snd_mixer_volume1_control_t *)snd_cs4236_mixer_digital_loopback_volume, 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 ((element4 = snd_mixer_lib_sw2(mixer, "Digital Loopback Switch", 0, (snd_mixer_sw2_control_t *)snd_cs4236_mixer_digital_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, dig_accu) < 0)
		goto __error;
	/* DSP input */
	if (snd_cs4236_create_digital_input(mixer, dig_accu, SND_MIXER_IN_DSP,
					(snd_mixer_volume1_control_t *)snd_cs4236_mixer_dsp_input_volume,
					(snd_mixer_sw1_control_t *)snd_cs4236_mixer_dsp_input_switch,
					-1, codec) < 0)
		goto __error;
	/* PCM input */
	if (snd_cs4236_create_digital_input(mixer, dig_accu, SND_MIXER_IN_PCM,
					(snd_mixer_volume1_control_t *)snd_cs4231_mixer_dac_volume,
					(snd_mixer_sw1_control_t *)snd_cs4231_mixer_dac_switch,
					0, codec) < 0)
		goto __error;
	/* FM input */
	if (snd_cs4236_create_digital_input(mixer, dig_accu, SND_MIXER_IN_FM,
					(snd_mixer_volume1_control_t *)snd_cs4236_mixer_fm_input_volume,
					(snd_mixer_sw1_control_t *)snd_cs4236_mixer_fm_input_switch,
					-1, codec) < 0)
		goto __error;
	/* Wavetable input */
	if (snd_cs4236_create_digital_input(mixer, dig_accu, SND_MIXER_IN_SYNTHESIZER,
					(snd_mixer_volume1_control_t *)snd_cs4236_mixer_wavetable_input_volume,
					(snd_mixer_sw1_control_t *)snd_cs4236_mixer_wavetable_input_switch,
					-1, codec) < 0)
		goto __error;
	/* 3D effect */
	element1 = snd_cs4236_create_3d_element(mixer, codec);
	if (element1) {
		if (snd_mixer_element_route_add(mixer, dig_accu, element1) < 0)
			goto __error;
	}
	/* build DAC */
	if ((element2 = 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, element1 ? element1 : dig_accu, element2) < 0)
		goto __error;
	/* master digital */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER_DIGITAL, 0)) == NULL)
		goto __error;
	if ((element3 = snd_mixer_lib_volume1(mixer, "Master Digital Volume", 0, 2, table12_range, (snd_mixer_volume1_control_t *)snd_cs4236_mixer_master_digital_volume, 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 ((element4 = snd_mixer_lib_sw1(mixer, "Master Digital In-SW", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_master_digital_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Master Digital Out-SW", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_master_digital_output_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;

	return 0;	

      __error:
      	return -ENOMEM;
}

static int snd_cs4235_build_mixer(cs4231_t * codec, snd_kmixer_t * mixer, int pcm_device)
{
	snd_kmixer_element_t *in_accu;	/* input accumulator */
	snd_kmixer_element_t *out_accu;	/* output accumulator */
	snd_kmixer_element_t *dig_accu;	/* digital accumulator */
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	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 ((in_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 2, table8_range, (snd_mixer_accu3_control_t *)snd_cs4236_mixer_input_accu_volume, codec)) == NULL)
		goto __error;
	if ((out_accu = snd_mixer_lib_accu3(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 2, table9_A_range, (snd_mixer_accu3_control_t *)snd_cs4235_mixer_output_accu_volume, codec)) == NULL)
		goto __error;
	if ((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(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, table9_1_range, (snd_mixer_volume1_control_t *)snd_cs4235_mixer_mic_mono_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;
	if ((element3 = snd_mixer_lib_volume1(mixer, "MIC Volume Boost", 0, 1, mic_range, (snd_mixer_volume1_control_t *)snd_cs4235_mixer_mic_boost_volume, 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 ((element4 = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mic_in_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mic_out_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build AUX1 input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_aux1_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_aux1_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_aux1_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build AUX2 input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 1)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 1, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_aux2_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Aux Input Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_aux2_in_switch, 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, in_accu) < 0)
		goto __error;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Aux Output Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_aux2_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, out_accu) < 0)
		goto __error;
	/* build master output */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Master Volume", 1, 2, table9_range, (snd_mixer_volume1_control_t *)snd_cs4235_mixer_master_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "Master Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_cs4235_mixer_master_switch, 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;
	if ((element3 = 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, element2, element3) < 0)
		goto __error;
	/* output loopback */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_ANALOG_LOOPBACK, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_sw1(mixer, "Analog Loopback Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_analog_loopback_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, in_accu) < 0)
		goto __error;
	/* mono input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MONO, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Mono Input Volume", 0, 1, table7_1_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_monoin_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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_mono_in_switch, 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, out_accu) < 0)
		goto __error;
	/* build ADC */
	if ((element1 = 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, in_accu, element1) < 0)
		goto __error;
	/* Capture endpoint */
	if ((element2 = 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, element1, element2) < 0)
		goto __error;
	/* DSP output */
	if ((element2 = 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, element1, element2) < 0)
		goto __error;
	/* PCM input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_device)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, table6_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_dac_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;
	/* 3D effect */
	if ((element3 = snd_cs4236_create_3d_element(mixer, codec)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	/* build DAC */
	if ((element4 = 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, element3, element4) < 0)
		goto __error;
	/* PCM output/input switches */
	if ((element1 = snd_mixer_lib_sw1(mixer, "PCM Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_dac_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "PCM Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_master_digital_input_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element4, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, in_accu) < 0)
		goto __error;
	/* DSP input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_DSP, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_DSP, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "DSP Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_dsp_input_switch, 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;
	if (snd_mixer_element_route_add(mixer, element2, dig_accu) < 0)
		goto __error;
	/* FM input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_FM, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "FM Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_fm_input_switch, 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;
	if (snd_mixer_element_route_add(mixer, element2, dig_accu) < 0)
		goto __error;
	/* WaveTable input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_SYNTHESIZER, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_sw1(mixer, "Synth Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_wavetable_input_switch, 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;
	if (snd_mixer_element_route_add(mixer, element2, dig_accu) < 0)
		goto __error;
	/* build DAC2 */
	if ((group = snd_mixer_lib_group(mixer, "Synth DAC", 1)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Synth DAC Volume", 1, 2, table10_range, (snd_mixer_volume1_control_t *)snd_cs4231_mixer_line_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dig_accu, element1) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_converter(mixer, "Synth DAC", 0, SND_MIXER_ETYPE_DAC, 16)) == 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;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Synth DAC Output", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4231_mixer_line_switch, 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, out_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Synth DAC Input", 0, 2, (snd_mixer_sw1_control_t *)snd_cs4236_mixer_line_in_switch, 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, 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;
	snd_pcm1_t *pcm1;
	cs4231_t *codec;

	if (!pcm || !pcm->card)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (cs4231_t *) pcm1->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;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_cs4236_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_cs4236_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
