/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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__
#include "driver.h"
#include "info.h"
#include "schedule.h"
#include "mixer.h"
#include "ac97_codec.h"

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

 */

#define AK4531_MIXS \
	  (sizeof(snd_ak4531_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define AK4531_PRIVATE( left, right, misc ) ((left<<24)|(right<<16)|misc)

static int snd_ak4531_input_mute(snd_kmixer_t * mixer,
				 snd_kmixer_channel_t * channel, unsigned int mute)
{
	ak4531_t *ak4531;
	unsigned char lin_sw1 = 0x00, rin_sw1 = 0x00, lin_sw2 = 0x00,
	 rin_sw2 = 0x00;

	ak4531 = (ak4531_t *) mixer->private_data;
	switch (channel->hw.priority) {
	case SND_MIXER_PRI_PCM1:
		lin_sw1 = 0x40;
		rin_sw1 = 0x20;
		break;
	case SND_MIXER_PRI_LINE:
		lin_sw1 = 0x10;
		rin_sw1 = 0x08;
		break;
	case SND_MIXER_PRI_CD:
		lin_sw1 = 0x04;
		rin_sw1 = 0x02;
		break;
	case SND_MIXER_PRI_MIC:
		lin_sw1 = rin_sw1 = 0x01;
		break;
	case SND_MIXER_PRI_AUXA:
		lin_sw2 = 0x10;
		rin_sw2 = 0x08;
		break;
	case SND_MIXER_PRI_PCM:
		lin_sw2 = rin_sw2 = 0x04;
		break;
	case SND_MIXER_PRI_MONO2:
		lin_sw2 = rin_sw2 = 0x02;
		break;
	case SND_MIXER_PRI_MONO1:
		lin_sw2 = rin_sw2 = 0x01;
		break;
	}
	snd_mutex_lock(ak4531, access);
	if (!(mute & SND_MIX_MUTE_LEFT)) {
		ak4531->lin_sw1 |= lin_sw1;
		ak4531->lin_sw2 |= lin_sw2;
	} else {
		ak4531->lin_sw1 &= ~lin_sw1;
		ak4531->lin_sw2 &= ~lin_sw2;
	}
	if (!(mute & SND_MIX_MUTE_RIGHT)) {
		ak4531->rin_sw1 |= rin_sw1;
		ak4531->rin_sw2 |= rin_sw2;
	} else {
		ak4531->rin_sw1 &= ~rin_sw1;
		ak4531->rin_sw2 &= ~rin_sw2;
	}
	ak4531->write(ak4531->private_data, AK4531_LIN_SW1, ak4531->lin_sw1);
	ak4531->write(ak4531->private_data, AK4531_RIN_SW1, ak4531->rin_sw1);
	ak4531->write(ak4531->private_data, AK4531_LIN_SW2, ak4531->lin_sw2);
	ak4531->write(ak4531->private_data, AK4531_RIN_SW2, ak4531->rin_sw2);
	snd_mutex_unlock(ak4531, access);
	return 0;
}

static int snd_ak4531_output_mute(snd_kmixer_t * mixer,
				  snd_kmixer_channel_t * channel,
				  unsigned int mute)
{
	ak4531_t *ak4531;
	unsigned char regl, regr;
	unsigned char lout_sw1 = 0x00, rout_sw1 = 0x00,
		      lout_sw2 = 0x00, rout_sw2 = 0x00;

	regl = (unsigned char) (channel->hw.private_value >> 24);
	regr = (unsigned char) (channel->hw.private_value >> 16);
	ak4531 = (ak4531_t *) mixer->private_data;
	switch (channel->hw.priority) {
	case SND_MIXER_PRI_PCM1:
		lout_sw1 = 0x40;
		rout_sw1 = 0x20;
		break;
	case SND_MIXER_PRI_LINE:
		lout_sw1 = 0x10;
		rout_sw1 = 0x08;
		break;
	case SND_MIXER_PRI_CD:
		lout_sw1 = 0x04;
		rout_sw1 = 0x02;
		break;
	case SND_MIXER_PRI_MIC:
		lout_sw1 = 0x01;
		break;
	case SND_MIXER_PRI_AUXA:
		lout_sw2 = 0x20;
		rout_sw2 = 0x10;
		break;
	case SND_MIXER_PRI_PCM:
		lout_sw2 = 0x08;
		rout_sw2 = 0x04;
		break;
	case SND_MIXER_PRI_MONO2:
		lout_sw2 = rout_sw2 = 0x02;
		break;
	case SND_MIXER_PRI_MONO1:
		lout_sw2 = rout_sw2 = 0x01;
		break;
	}
	snd_mutex_lock(ak4531, access);
	if (mute & SND_MIX_MUTE_LEFT) {
		if (regl < AK4531_LCD)
			ak4531->regs[regl] |= 0x80;
		ak4531->out_sw1 &= ~lout_sw1;
		ak4531->out_sw2 &= ~lout_sw2;
	} else {
		ak4531->regs[regl] &= ~0x80;
		ak4531->out_sw1 |= lout_sw1;
		ak4531->out_sw2 |= lout_sw2;
	}
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		if (mute & SND_MIX_MUTE_RIGHT) {
			if (regr < AK4531_RCD)
				ak4531->regs[regr] |= 0x80;
			ak4531->out_sw1 &= ~rout_sw1;
			ak4531->out_sw2 &= ~rout_sw2;
		} else {
			ak4531->regs[regr] &= ~0x80;
			ak4531->out_sw1 |= rout_sw1;
			ak4531->out_sw2 |= rout_sw2;
		}
	}
	ak4531->write(ak4531->private_data, regl, ak4531->regs[regl]);
	ak4531->write(ak4531->private_data, regr, ak4531->regs[regr]);
	ak4531->write(ak4531->private_data, AK4531_OUT_SW1, ak4531->out_sw1);
	ak4531->write(ak4531->private_data, AK4531_OUT_SW2, ak4531->out_sw2);
	snd_mutex_unlock(ak4531, access);
	return 0;
}

static int snd_ak4531_volume_level(snd_kmixer_t * mixer,
				    snd_kmixer_channel_t * channel,
				    int left, int right, int type)
{
	ak4531_t *ak4531;
	unsigned char regl, regr;
	
	regl = (unsigned char) (channel->hw.private_value >> 24);
	regr = (unsigned char) (channel->hw.private_value >> 16);
	ak4531 = (ak4531_t *) mixer->private_data;
	snd_mutex_lock(ak4531, access);
	ak4531->regs[regl] &= 0x80;
	ak4531->regs[regl] |= (channel->hw.omax - left);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		ak4531->regs[regr] &= 0x80;
		ak4531->regs[regr] |= (channel->hw.omax - right);
	}
	ak4531->write(ak4531->private_data, regl, ak4531->regs[regl]);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO)
		ak4531->write(ak4531->private_data, regr, ak4531->regs[regr]);
	snd_mutex_unlock(ak4531, access);
	snd_mixer_hardware_volume(mixer, channel->hw.priority, SND_MIX_HW_MATCH_OPPOSITE_CHANNEL, 
		type, 0, 0, 0);
	return 0;
}

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

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

static struct snd_stru_mixer_channel_hw snd_ak4531_mixs[] =
{
	{
		SND_MIXER_PRI_MASTER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER,	/* device name */
		SND_MIXER_OSS_VOLUME,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
                0, 31,			/* omin, omax value */
                -6200, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LMASTER, AK4531_RMASTER, 0)
	},
	{
		SND_MIXER_PRI_PCM,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PCM,	/* device name */
		SND_MIXER_OSS_PCM,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LVOICE, AK4531_RVOICE, 0)
	},
	{
		SND_MIXER_PRI_PCM1,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PCM1,	/* device name */
		SND_MIXER_OSS_SYNTH,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LFM, AK4531_RFM, 0)
	},
	{
		SND_MIXER_PRI_CD,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_CD,	/* device name */
		SND_MIXER_OSS_CD,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LCD, AK4531_RCD, 0)
	},
	{
		SND_MIXER_PRI_LINE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_LINE,	/* device name */
		SND_MIXER_OSS_LINE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LLINE, AK4531_RLINE, 0)
	},
	{
		SND_MIXER_PRI_AUXA,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_AUXA,	/* device name */
		SND_MIXER_OSS_RADIO,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LAUXA, AK4531_RAUXA, 0),
	},
	{
		SND_MIXER_PRI_MONO1,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MONO1,	/* device name */
		SND_MIXER_OSS_LINE1,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE |
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_LAUXA, AK4531_RAUXA, 0)
	},
	{
		SND_MIXER_PRI_MONO2,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MONO2,	/* device name */
		SND_MIXER_OSS_LINE2,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_MONO1, 0, 0)
	},
	{
		SND_MIXER_PRI_MIC,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MIC,	/* device name */
		SND_MIXER_OSS_MIC,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE | 
		SND_MIXER_CINFO_DCAP_RECORDBYMUTE,
                0, 31,			/* omin, omax value */
                -5000, 1200,		/* omin, omax dB */
                0, 31,			/* imin, imax value */
                -5000, 1200,		/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_MIC, 0, 0),
	},
	{
		SND_MIXER_PRI_MASTER_MONO, /* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER_MONO, /* device name */
		SND_MIXER_OSS_LINE3,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
                0, 7,			/* omin, omax value */
                -2800, 0,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		snd_ak4531_control,	/* set control */
		snd_ak4531_volume,	/* set volume level */
		AK4531_PRIVATE(AK4531_MONO_OUT, 0, 0)
	}
};

static int snd_ak4531_get_mic_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->micgain;
	return 0;
}

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

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_mutex_lock(ak4531, access);
	ak4531->micgain = uswitch->value.enable ? 1 : 0;
	if (ak4531->micgain) {
		ak4531->mic_channel->hw.omin_dB = -2000;
		ak4531->mic_channel->hw.omax_dB = 4200;
	} else {
		ak4531->mic_channel->hw.omin_dB = -5000;
		ak4531->mic_channel->hw.omax_dB = 1200;
	}
	ak4531->write(ak4531->private_data, AK4531_MIC_GAIN, ak4531->micgain);
	snd_mutex_unlock(ak4531, access);
	return 0;
}

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->adin;
	return 0;
}

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

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_mutex_lock(ak4531, access);
	ak4531->adin = uswitch->value.enable ? 1 : 0;
	ak4531->write(ak4531->private_data, AK4531_AD_IN, ak4531->adin);
	snd_mutex_unlock(ak4531, access);
	return 0;
}

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

static snd_kswitch_t snd_ak4531_switches[] =
{
	{
		SND_MIXER_SW_MIC_GAIN,
		(snd_get_switch_t *)snd_ak4531_get_mic_switch,
		(snd_set_switch_t *)snd_ak4531_set_mic_switch,
		0,
		NULL,
		NULL
	},
	{
		"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_mutex_destroy(ak4531, access);
		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 idx, mixs_count;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_hw *mixs;
	snd_kswitch_t *kswitch;

	if (!card || !ak4531)
		return NULL;
	snd_mutex_prepare(ak4531, access);
	mixer = snd_mixer_new(card, "AK4531");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, "Asahi Kasei AK4531");
	mixs = snd_ak4531_mixs;
	mixs_count = AK4531_MIXS;
	for (idx = 0; idx < mixs_count; idx++) {
		channel = snd_mixer_new_channel(mixer, &mixs[idx]);
		if (!channel) {
			snd_mutex_destroy(ak4531, access);
			snd_mixer_free(mixer);
			return NULL;
		}
		if (channel->hw.priority == SND_MIXER_PRI_MIC)
			ak4531->mic_channel = channel;
	}
	for (idx = 0; idx < AK4531_SWITCHES; idx++)
		kswitch = snd_mixer_switch_new(mixer, &snd_ak4531_switches[idx], ak4531);
	mixer->hw.caps = 0;
	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->adin = 0);	/* recording source is mixer */
	ak4531->write(ak4531->private_data, AK4531_MIC_GAIN, ak4531->micgain = 1);
	return mixer;
}

/*

 */
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->adin ? "external" : "mixer",
		    ak4531->micgain ? "+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;
	}
}
