/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Universal routines for AK4531 codec
 *
 *
 *   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.
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "mixer.h"
#include "ak4531_codec.h"

static void snd_ak4531_proc_init(snd_card_t * card, ak4531_t * ak4531);
static void snd_ak4531_proc_done(ak4531_t * ak4531);

/*
 *
 */
 
static int snd_ak4531_stereo_mute(int w_flag, unsigned int *bitmap, ak4531_t *ak4531,
				    int reg)
{
	unsigned long flags;
	int change = 0;
	int left, right;

	snd_spin_lock(ak4531, access, &flags);
	left = ak4531->regs[reg + 0] & 0x80 ? 0 : 1;
	right = ak4531->regs[reg + 1] & 0x80 ? 0 : 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);
		ak4531->regs[reg + 0] &= 0x7f;
		if (!snd_mixer_get_bit(bitmap, 0))
			ak4531->regs[reg + 0] |= 0x80;
		ak4531->regs[reg + 1] &= 0x7f;
		if (!snd_mixer_get_bit(bitmap, 1))
			ak4531->regs[reg + 1] |= 0x80;
		ak4531->write(ak4531->private_data, reg + 0, ak4531->regs[reg + 0]);
		ak4531->write(ak4531->private_data, reg + 1, ak4531->regs[reg + 1]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_master_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LMASTER);
}

static int snd_ak4531_voice_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LVOICE);
}

static int snd_ak4531_fm_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LFM);
}

static int snd_ak4531_cd_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LCD);
}

static int snd_ak4531_line_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LLINE);
}

static int snd_ak4531_aux_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_mute(w_flag, volume, ak4531, AK4531_LAUXA);
}

static int snd_ak4531_mono_mute(int w_flag, int *value, ak4531_t *ak4531,
				int reg)
{
	unsigned long flags;
	int change = 0;
	int val;

	snd_spin_lock(ak4531, access, &flags);
	val = ak4531->regs[reg] & 0x80 ? 0 : 1;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		ak4531->regs[reg] &= 0x7f;
		if (!*value)
			ak4531->regs[reg] |= 0x80;
		ak4531->write(ak4531->private_data, reg, ak4531->regs[reg]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_mono1_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_mute(w_flag, volume, ak4531, AK4531_MONO1);
}

static int snd_ak4531_mono2_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_mute(w_flag, volume, ak4531, AK4531_MONO2);
}

static int snd_ak4531_mic_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_mute(w_flag, volume, ak4531, AK4531_MIC);
}

static int snd_ak4531_master_mono_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_mute(w_flag, volume, ak4531, AK4531_MONO_OUT);
}

static int snd_ak4531_stereo_volume(int w_flag, int *volume, ak4531_t *ak4531,
				    int reg)
{
	unsigned long flags;
	int change = 0, left, right;

	snd_spin_lock(ak4531, access, &flags);
	left = 31 - (ak4531->regs[reg + 0] & 31);
	right = 31 - (ak4531->regs[reg + 1] & 31);
	if (!w_flag) {
		volume[0] = left;
		volume[1] = right;
	} else {
		change = left != volume[0] || right != volume[1];
		ak4531->regs[reg + 0] &= ~31;
		ak4531->regs[reg + 0] |= 31 - volume[0];
		ak4531->regs[reg + 1] &= ~31;
		ak4531->regs[reg + 1] |= 31 - volume[1];
		ak4531->write(ak4531->private_data, reg + 0, ak4531->regs[reg + 0]);
		ak4531->write(ak4531->private_data, reg + 1, ak4531->regs[reg + 1]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_master_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LMASTER);
}

static int snd_ak4531_voice_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LVOICE);
}

static int snd_ak4531_fm_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LFM);
}

static int snd_ak4531_cd_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LCD);
}

static int snd_ak4531_line_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LLINE);
}

static int snd_ak4531_aux_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_stereo_volume(w_flag, volume, ak4531, AK4531_LAUXA);
}

static int snd_ak4531_mono_volume(int w_flag, int *volume, ak4531_t *ak4531,
				         int reg)
{
	unsigned long flags;
	int change = 0, val;

	snd_spin_lock(ak4531, access, &flags);
	val = 31 - (ak4531->regs[reg] & 31);
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = val != volume[0];
		ak4531->regs[reg] &= ~31;
		ak4531->regs[reg] |= 31 - volume[0];
		ak4531->write(ak4531->private_data, reg, ak4531->regs[reg]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_mono1_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_mono_volume(w_flag, volume, ak4531, AK4531_MONO1);
}

static int snd_ak4531_mono2_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_mono_volume(w_flag, volume, ak4531, AK4531_MONO2);
}

static int snd_ak4531_mic_volume(int w_flag, int *volume, ak4531_t * ak4531)
{
	return snd_ak4531_mono_volume(w_flag, volume, ak4531, AK4531_MIC);
}

static int snd_ak4531_mic_boost(int w_flag, int *volume, ak4531_t *ak4531,
				int reg)
{
	unsigned long flags;
	int change = 0, val;

	snd_spin_lock(ak4531, access, &flags);
	val = ak4531->regs[AK4531_MIC_GAIN] & 1;
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = val != volume[0];
		ak4531->regs[AK4531_MIC_GAIN] &= ~1;
		ak4531->regs[AK4531_MIC_GAIN] |= volume[0] & 1;
		ak4531->write(ak4531->private_data, AK4531_MIC_GAIN, ak4531->regs[AK4531_MIC_GAIN]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_master_mono_volume(int w_flag, int *volume, ak4531_t *ak4531,
				         int reg)
{
	unsigned long flags;
	int change = 0, val;

	snd_spin_lock(ak4531, access, &flags);
	val = 7 - (ak4531->regs[AK4531_MONO_OUT] & 7);
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = val != volume[0];
		ak4531->regs[AK4531_MONO_OUT] &= ~7;
		ak4531->regs[AK4531_MONO_OUT] |= 7 - volume[0];
		ak4531->write(ak4531->private_data, AK4531_MONO_OUT, ak4531->regs[AK4531_MONO_OUT]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_stereo_out_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531,
				    int reg, int bit)
{
	unsigned long flags;
	int change = 0;
	int left, right;

	bit = 1 << bit;
	snd_spin_lock(ak4531, access, &flags);
	left = ak4531->regs[reg] & (bit >> 0) ? 1 : 0;
	right = ak4531->regs[reg] & (bit >> 1) ? 1 : 0;
	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);
		ak4531->regs[reg + 0] &= ~((bit >> 0) | (bit >> 1));
		if (snd_mixer_get_bit(bitmap, 0))
			ak4531->regs[reg] |= bit >> 0;
		if (snd_mixer_get_bit(bitmap, 1))
			ak4531->regs[reg] |= bit >> 1;
		ak4531->write(ak4531->private_data, reg, ak4531->regs[reg]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_voice_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW2, 3);
}

static int snd_ak4531_fm_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW1, 6);
}

static int snd_ak4531_cd_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW1, 2);
}

static int snd_ak4531_line_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW1, 4);
}

static int snd_ak4531_aux_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW2, 5);
}

static int snd_ak4531_mono_out_sw(int w_flag, int *value, ak4531_t *ak4531,
				  int reg, int bit)
{
	unsigned long flags;
	int change = 0;
	int val;

	bit = 1 << bit;
	snd_spin_lock(ak4531, access, &flags);
	val = ak4531->regs[reg] & bit ? 1 : 0;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		ak4531->regs[reg] &= ~bit;
		if (*value)
			ak4531->regs[reg] |= bit;
		ak4531->write(ak4531->private_data, reg, ak4531->regs[reg]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}                                                                                                                                                                                                                                                                                                            

static int snd_ak4531_mono1_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW2, 0);
}

static int snd_ak4531_mono2_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW2, 1);
}

static int snd_ak4531_mic_out_mute(int w_flag, int *volume, ak4531_t *ak4531)
{
	return snd_ak4531_mono_out_sw(w_flag, volume, ak4531, AK4531_OUT_SW1, 0);
}

static int snd_ak4531_input_route(int w_flag, unsigned int *prsw, ak4531_t *ak4531,
				  int reg, int bit)
{
	unsigned long flags;
	int change = 0;
	int ll, rl, lr, rr;

	bit = 1 << bit;
	snd_spin_lock(ak4531, access, &flags);
	ll = ak4531->regs[reg + 0] & (bit >> 0) ? 1 : 0;
	rl = ak4531->regs[reg + 0] & (bit >> 1) ? 1 : 0;
	lr = ak4531->regs[reg + 1] & (bit >> 0) ? 1 : 0;
	rr = ak4531->regs[reg + 1] & (bit >> 1) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(prsw, 0, ll);
		snd_mixer_set_bit(prsw, 1, rl);
		snd_mixer_set_bit(prsw, 2, lr);
		snd_mixer_set_bit(prsw, 3, rr);
	} else {
		change = ll != snd_mixer_get_bit(prsw, 0) ||
		         rl != snd_mixer_get_bit(prsw, 1) ||
		         lr != snd_mixer_get_bit(prsw, 2) ||
		         rr != snd_mixer_get_bit(prsw, 3);
		ak4531->regs[reg + 0] &= ~((bit >> 0) | (bit >> 1));
		ak4531->regs[reg + 1] &= ~((bit >> 0) | (bit >> 1));
		if (snd_mixer_get_bit(prsw, 0))
			ak4531->regs[reg + 0] |= bit >> 0;
		if (snd_mixer_get_bit(prsw, 1))
			ak4531->regs[reg + 0] |= bit >> 1;
		if (snd_mixer_get_bit(prsw, 2))
			ak4531->regs[reg + 1] |= bit >> 0;
		if (snd_mixer_get_bit(prsw, 3))
			ak4531->regs[reg + 1] |= bit >> 1;
		ak4531->write(ak4531->private_data, reg + 0, ak4531->regs[reg + 0]);
		ak4531->write(ak4531->private_data, reg + 1, ak4531->regs[reg + 1]);
	}
	snd_spin_unlock(ak4531, access, &flags);	
	return change;
}

static int snd_ak4531_fm_input_route(int w_flag, unsigned int *prsw, ak4531_t *ak4531)
{
	return snd_ak4531_input_route(w_flag, prsw, ak4531,
				      AK4531_LIN_SW1, 6);
}

static int snd_ak4531_cd_input_route(int w_flag, unsigned int *prsw, ak4531_t *ak4531)
{
	return snd_ak4531_input_route(w_flag, prsw, ak4531,
				      AK4531_LIN_SW1, 2);
}

static int snd_ak4531_line_input_route(int w_flag, unsigned int *prsw, ak4531_t *ak4531)
{
	return snd_ak4531_input_route(w_flag, prsw, ak4531,
				      AK4531_LIN_SW1, 4);
}

static int snd_ak4531_aux_input_route(int w_flag, unsigned int *prsw, ak4531_t *ak4531)
{
	return snd_ak4531_input_route(w_flag, prsw, ak4531,
				      AK4531_LIN_SW2, 4);
}

static int snd_ak4531_stereo_in_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531,
				   int reg, int bit)
{
	unsigned long flags;
	int change = 0;
	int left, right;

	bit = 1 << bit;
	snd_spin_lock(ak4531, access, &flags);
	left = ak4531->regs[reg + 0] & bit ? 0 : 1;
	right = ak4531->regs[reg + 1] & bit ? 0 : 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);
		ak4531->regs[reg + 0] &= ~bit;
		ak4531->regs[reg + 1] &= ~bit;
		if (!snd_mixer_get_bit(bitmap, 0))
			ak4531->regs[reg + 0] |= bit;
		if (!snd_mixer_get_bit(bitmap, 1))
			ak4531->regs[reg + 1] |= bit;
		ak4531->write(ak4531->private_data, reg + 0, ak4531->regs[reg + 0]);
		ak4531->write(ak4531->private_data, reg + 1, ak4531->regs[reg + 1]);
	}
	snd_spin_unlock(ak4531, access, &flags);
	return change;  
}

static int snd_ak4531_voice_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 2);
}

static int snd_ak4531_mono1_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 0);
}

static int snd_ak4531_mono2_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 1);
}

static int snd_ak4531_mic_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW1, 0);
}

static int snd_ak4531_tmono1_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 6);
}

static int snd_ak4531_tmono2_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 5);
}

static int snd_ak4531_tmic_sw(int w_flag, unsigned int *bitmap, ak4531_t *ak4531)
{
	return snd_ak4531_stereo_in_sw(w_flag, bitmap, ak4531,
				       AK4531_LIN_SW2, 7);
}

static int snd_ak4531_get_mix_switch(snd_kmixer_t * mixer,
				     snd_kswitch_t * kswitch,
				     snd_switch_t * uswitch)
{
	ak4531_t *ak4531 = (ak4531_t *) mixer->private_data;

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	uswitch->value.enable = ak4531->regs[AK4531_AD_IN] & 1;
	return 0;
}

static int snd_ak4531_set_mix_switch(snd_kmixer_t * mixer,
				     snd_kswitch_t * kswitch,
				     snd_switch_t * uswitch)
{
	unsigned long flags;
	ak4531_t *ak4531 = (ak4531_t *) mixer->private_data;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_spin_lock(ak4531, access, &flags);
	ak4531->regs[AK4531_AD_IN] &= ~1;
	if (uswitch->value.enable)
		ak4531->regs[AK4531_AD_IN] |= 1;
	ak4531->write(ak4531->private_data, AK4531_AD_IN, ak4531->regs[AK4531_AD_IN]);
	snd_spin_unlock(ak4531, access, &flags);
	return 0;
}

#define AK4531_SWITCHES \
		(sizeof(snd_ak4531_switches)/sizeof(snd_kswitch_t))

static snd_kswitch_t snd_ak4531_switches[] =
{
	{
		"AK4531 Recording Source",
		(snd_get_switch_t *)snd_ak4531_get_mix_switch,
		(snd_set_switch_t *)snd_ak4531_set_mix_switch,
		0,
		NULL,
		NULL
	},
};

static void snd_ak4531_mixer_free(void *data)
{
	ak4531_t *ak4531 = (ak4531_t *) data;

	if (ak4531) {
		snd_ak4531_proc_done(ak4531);
		if (ak4531->private_free)
			ak4531->private_free(ak4531);
	}
}

snd_kmixer_t *snd_ak4531_mixer(snd_card_t * card, ak4531_t * ak4531, int pcm_count, int *pcm_devs)
{
	int idx;
	snd_kmixer_t *mixer;
	snd_kswitch_t *kswitch;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *in_accu, *out_accu, *mono_accu;
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	static struct snd_mixer_element_volume1_range master_range[2] = {
		{0, 31, -6200, 0},
		{0, 31, -6200, 0}
	};
	static struct snd_mixer_element_volume1_range master_mono_range[1] = {
		{0, 7, -2800, 0},
	};
	static struct snd_mixer_element_volume1_range stereo_range[2] = {
		{0, 31, -5000, 1200},
		{0, 31, -5000, 1200}
	};
	static struct snd_mixer_element_volume1_range mic_gain_range[1] = {
		{0, 1, 0, 3000},
	};
	static snd_mixer_voice_t stereo_voices[2] = {
		{SND_MIXER_VOICE_LEFT, 0},
		{SND_MIXER_VOICE_RIGHT, 0}
	};

	if (!card || !ak4531)
		return NULL;
	snd_spin_prepare(ak4531, access);
	mixer = snd_mixer_new(card, "AK4531");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, "Asahi Kasei AK4531");
	for (idx = 0; idx < AK4531_SWITCHES; idx++)
		kswitch = snd_mixer_switch_new(mixer, &snd_ak4531_switches[idx], ak4531);
	mixer->private_data = ak4531;
	mixer->private_free = snd_ak4531_mixer_free;
	snd_ak4531_proc_init(card, ak4531);
	ak4531->write(ak4531->private_data, AK4531_RESET, 0x03);	/* no RST, PD */
	snd_delay(10);
	ak4531->write(ak4531->private_data, AK4531_CLOCK, 0x00);	/* CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off LRCLK2 PLL */
	ak4531->write(ak4531->private_data, AK4531_AD_IN, ak4531->regs[AK4531_AD_IN] = 0);	/* recording source is mixer */
	ak4531->write(ak4531->private_data, AK4531_MIC_GAIN, ak4531->regs[AK4531_MIC_GAIN] = 1);

	/* build accumulators */
	if ((in_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((mono_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_MONO_OUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, mono_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", 0, 2, master_range, (snd_mixer_volume1_control_t *)snd_ak4531_master_volume, ak4531)) == 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", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_master_mute, ak4531)) == 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;
	/* build mono output */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER_MONO, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Master Mono Volume", 0, 1, master_mono_range, (snd_mixer_volume1_control_t *)snd_ak4531_master_mono_volume, ak4531)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mono_accu, element1) < 0)
		goto __error;
	if ((element2 = snd_mixer_lib_sw2(mixer, "Master Mono Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_master_mono_mute, ak4531)) == 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_mono(mixer, SND_MIXER_OUT_MASTER_MONO, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	/* build 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_devs)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_voice_volume, ak4531)) == 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, "PCM Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_voice_mute, ak4531)) == 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, "PCM Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_voice_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw1(mixer, "PCM Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_voice_sw, ak4531)) == 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;
	/* build FM (second PCM) input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 1)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 1, SND_MIXER_ETYPE_PLAYBACK, 1, pcm_devs + 1)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 1, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_fm_volume, ak4531)) == 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, "PCM Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_ak4531_fm_mute, ak4531)) == 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, "PCM Output Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_ak4531_fm_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw3(mixer, "PCM Input Switch", 1, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_ak4531_fm_input_route, ak4531)) == 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;
	/* build CD input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_cd_volume, ak4531)) == 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, "CD Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_cd_mute, ak4531)) == 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, "CD Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_cd_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw3(mixer, "CD Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_ak4531_cd_input_route, ak4531)) == 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;
	/* 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_volume1(mixer, "Line Volume", 0, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_line_volume, ak4531)) == 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 Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_line_mute, ak4531)) == 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, "Line Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_line_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw3(mixer, "Line Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_ak4531_line_input_route, ak4531)) == 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;
	/* build AUX 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, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_aux_volume, ak4531)) == 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 Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_aux_mute, ak4531)) == 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, "Aux Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_aux_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw3(mixer, "Aux Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_ak4531_aux_input_route, ak4531)) == 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;
	/* build mono1 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_sw2(mixer, "Mono Bypass Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_tmono1_sw, ak4531)) == 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, "Mono Volume", 0, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_mono1_volume, ak4531)) == 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_sw2(mixer, "Mono Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_mono1_mute, ak4531)) == 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, "Mono Output Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_mono1_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_mono1_sw, ak4531)) == 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;
	/* build mono2 input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MONO, 1)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MONO, 1, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;

	if ((element2 = snd_mixer_lib_sw2(mixer, "Mono Bypass Switch", 1, (snd_mixer_sw2_control_t *)snd_ak4531_tmono2_sw, ak4531)) == 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, "Mono Volume", 1, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_mono2_volume, ak4531)) == 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_sw2(mixer, "Mono Switch", 1, (snd_mixer_sw2_control_t *)snd_ak4531_mono2_mute, ak4531)) == 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, "Mono Output Switch", 1, (snd_mixer_sw2_control_t *)snd_ak4531_mono2_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw1(mixer, "Mono Input Switch", 1, 2, (snd_mixer_sw1_control_t *)snd_ak4531_mono2_sw, ak4531)) == 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;
	/* build mic input */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;

	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Gain", 0, 1, mic_gain_range, (snd_mixer_volume1_control_t *)snd_ak4531_mic_boost, ak4531)) == 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;
	element1 = element2;

	if ((element2 = snd_mixer_lib_sw2(mixer, "MIC Bypass Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_tmic_sw, ak4531)) == 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, "MIC Volume", 0, 2, stereo_range, (snd_mixer_volume1_control_t *)snd_ak4531_mic_volume, ak4531)) == 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_sw2(mixer, "MIC Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_mic_mute, ak4531)) == 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, "MIC Output Switch", 0, (snd_mixer_sw2_control_t *)snd_ak4531_mic_out_mute, ak4531)) == 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;
	if ((element4 = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_ak4531_mic_sw, ak4531)) == 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;

	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

/*

 */

static void snd_ak4531_proc_read(snd_info_buffer_t * buffer,
                                 void *private_data)
{
	ak4531_t *ak4531 = (ak4531_t *) private_data;

	snd_iprintf(buffer, "Asahi Kasei AK4531\n\n");
	snd_iprintf(buffer, "Recording source   : %s\n"
		    "MIC gain           : %s\n",
		    ak4531->regs[AK4531_AD_IN] & 1 ? "external" : "mixer",
		    ak4531->regs[AK4531_MIC_GAIN] & 1 ? "+30dB" : "+0dB");
}

static void snd_ak4531_proc_init(snd_card_t * card, ak4531_t * ak4531)
{
	snd_info_entry_t *entry;

	if ((entry = snd_info_create_entry(card, "ak4531")) != NULL) {
		entry->private_data = ak4531;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 256;
		entry->t.text.read = snd_ak4531_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	ak4531->proc_entry = entry;
}

static void snd_ak4531_proc_done(ak4531_t * ak4531)
{
	if (ak4531->proc_entry) {
		snd_info_unregister(ak4531->proc_entry);
		ak4531->proc_entry = NULL;
	}
}
