
/*
    ad1816a.c - lowlevel code for Analog Devices AD1816A chip.
    Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it>

    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 "../../include/driver.h"
#include "../../include/ad1816a.h"

#define snd_ad1816a_printk(args...)	snd_printk(__FILE__": " ##args)


static inline int snd_ad1816a_busy_wait(ad1816a_t *codec)
{
	int timeout;

	for (timeout = 1000; timeout-- > 0; udelay(10))
		if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY)
			return 0;

	snd_ad1816a_printk("chip busy.\n");
	return -EBUSY;
}

inline unsigned char snd_ad1816a_in(ad1816a_t *codec, unsigned char reg)
{
	snd_ad1816a_busy_wait(codec);
	return inb(AD1816A_REG(reg));
}

inline void snd_ad1816a_out(ad1816a_t *codec, unsigned char reg,
			    unsigned char value)
{
	snd_ad1816a_busy_wait(codec);
	outb(value, AD1816A_REG(reg));
}

inline void snd_ad1816a_out_mask(ad1816a_t *codec, unsigned char reg,
				 unsigned char mask, unsigned char value)
{
	snd_ad1816a_out(codec, reg,
		(value & mask) | (snd_ad1816a_in(codec, reg) & ~mask));
}

static unsigned short snd_ad1816a_read(ad1816a_t *codec, unsigned char reg)
{
	snd_ad1816a_out(codec, AD1816A_INDIR_ADDR, reg & 0x3f);
	return snd_ad1816a_in(codec, AD1816A_INDIR_DATA_LOW) |
		(snd_ad1816a_in(codec, AD1816A_INDIR_DATA_HIGH) << 8);
}

static void snd_ad1816a_write(ad1816a_t *codec, unsigned char reg,
			      unsigned short value)
{
	snd_ad1816a_out(codec, AD1816A_INDIR_ADDR, reg & 0x3f);
	snd_ad1816a_out(codec, AD1816A_INDIR_DATA_LOW, value & 0xff);
	snd_ad1816a_out(codec, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff);
}

static void snd_ad1816a_write_mask(ad1816a_t *codec, unsigned char reg,
				   unsigned short mask, unsigned short value)
{
	snd_ad1816a_write(codec, reg,
		(value & mask) | (snd_ad1816a_read(codec, reg) & ~mask));
}


static unsigned char snd_ad1816a_get_format(ad1816a_t *codec,
					    unsigned int format, int voices)
{
	unsigned char retval = AD1816A_FMT_LINEAR_8;

	switch (format) {
	case SND_PCM_SFMT_MU_LAW:
		retval = AD1816A_FMT_ULAW_8;
		break;
	case SND_PCM_SFMT_A_LAW:
		retval = AD1816A_FMT_ALAW_8;
		break;
	case SND_PCM_SFMT_S16_LE:
		retval = AD1816A_FMT_LINEAR_16_LIT;
		break;
	case SND_PCM_SFMT_S16_BE:
		retval = AD1816A_FMT_LINEAR_16_BIG;
	}
	return (voices > 1) ? (retval | AD1816A_FMT_STEREO) : retval;
}

static unsigned int snd_ad1816a_get_freq(unsigned int freq)
{
	freq = (freq < 4000) ? 4000 : freq;
	freq = (freq > 55200) ? 55200 : freq;
	return freq;
}


static int snd_ad1816a_playback_ioctl(void *private_data,
				      snd_pcm_subchn_t *subchn,
				      unsigned int cmd, unsigned long *arg)
{
	int error;

	if ((error = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg)))
		return error;

	if (cmd == SND_PCM_IOCTL1_PARAMS)
		subchn->runtime->format.rate = snd_ad1816a_get_freq(
			subchn->runtime->format.rate);
	return 0;
}

static int snd_ad1816a_capture_ioctl(void *private_data,
				     snd_pcm_subchn_t *subchn,
				     unsigned int cmd, unsigned long *arg)
{
	return snd_ad1816a_playback_ioctl(private_data, subchn, cmd, arg);
}


static void snd_ad1816a_open(ad1816a_t *codec, unsigned int mode)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->lock, flags);

	switch ((mode &= AD1816A_MODE_OPEN)) {
	case AD1816A_MODE_PLAYBACK:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff);
		break;
	case AD1816A_MODE_CAPTURE:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_CAPTURE_IRQ_ENABLE, 0xffff);
		break;
	case AD1816A_MODE_TIMER:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_TIMER_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_TIMER_IRQ_ENABLE, 0xffff);
	}
	codec->mode |= mode;

	spin_unlock_irqrestore(&codec->lock, flags);
}

static void snd_ad1816a_close(ad1816a_t *codec, unsigned int mode)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->lock, flags);

	switch ((mode &= AD1816A_MODE_OPEN)) {
	case AD1816A_MODE_PLAYBACK:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_PLAYBACK_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000);
		break;
	case AD1816A_MODE_CAPTURE:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_CAPTURE_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_CAPTURE_IRQ_ENABLE, 0x0000);
		break;
	case AD1816A_MODE_TIMER:
		snd_ad1816a_out_mask(codec, AD1816A_INTERRUPT_STATUS,
			AD1816A_TIMER_IRQ_PENDING, 0x00);
		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_TIMER_IRQ_ENABLE, 0x0000);
	}
	if (!((codec->mode &= ~mode) & AD1816A_MODE_OPEN))
		codec->mode = 0;

	spin_unlock_irqrestore(&codec->lock, flags);
}


static int snd_ad1816a_trigger(ad1816a_t *codec, unsigned char what,
			       int channel, int cmd)
{
	int error = 0;
	unsigned long flags;
	snd_pcm_subchn_t *psubchn = NULL;
	snd_pcm_subchn_t *csubchn = NULL;

	if (cmd == SND_PCM_TRIGGER_SYNC_GO) {
		cmd = SND_PCM_TRIGGER_GO;
		psubchn = codec->playback_subchn;
		csubchn = codec->capture_subchn;

		if (psubchn == NULL || csubchn == NULL ||
				memcmp(&psubchn->runtime->sync_group,
					&csubchn->runtime->sync_group,
					sizeof(snd_pcm_sync_t))) {
		    	if (what & AD1816A_PLAYBACK_ENABLE)
				csubchn = NULL;
			else
				psubchn = NULL;
		    	goto __trigger;
		}

		if (*psubchn->runtime->status != SND_PCM_STATUS_PREPARED)
		    	return -EINVAL;
		if (*csubchn->runtime->status != SND_PCM_STATUS_PREPARED)
		    	return -EINVAL;

		if (!snd_pcm_playback_data(psubchn) ||
		    !snd_pcm_capture_empty(csubchn))
			return -EINVAL;

		if ((error = snd_pcm_channel_go_pre(psubchn,
				SND_PCM_TRIGGER_GO)))
			goto __cleanup;
		if ((error = snd_pcm_channel_go_pre(csubchn,
				SND_PCM_TRIGGER_GO)))
			goto __cleanup;

		what = AD1816A_PLAYBACK_ENABLE | AD1816A_CAPTURE_ENABLE;
	}

__trigger:
	spin_lock_irqsave(&codec->lock, flags);

	switch (cmd) {
	case SND_PCM_TRIGGER_GO:
	case SND_PCM_TRIGGER_STOP:
		cmd = (cmd == SND_PCM_TRIGGER_GO) ? 0xff: 0x00;
		if (what & AD1816A_PLAYBACK_ENABLE)
			snd_ad1816a_out_mask(codec, AD1816A_PLAYBACK_CONFIG,
				AD1816A_PLAYBACK_ENABLE, cmd);
		if (what & AD1816A_CAPTURE_ENABLE)
			snd_ad1816a_out_mask(codec, AD1816A_CAPTURE_CONFIG,
				AD1816A_CAPTURE_ENABLE, cmd);
		break;
	default:
		snd_ad1816a_printk("invalid trigger mode 0x%x.\n", what);
		error = -EINVAL;
	}

	spin_unlock_irqrestore(&codec->lock, flags);

__cleanup:
	if (psubchn)
		snd_pcm_channel_go_post(psubchn, SND_PCM_TRIGGER_GO, error);
	if (csubchn)
		snd_pcm_channel_go_post(csubchn, SND_PCM_TRIGGER_GO, error);

	return error;
}

static int snd_ad1816a_playback_trigger(void *private_data,
					snd_pcm_subchn_t *subchn, int cmd)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	return snd_ad1816a_trigger(codec, AD1816A_PLAYBACK_ENABLE,
		SND_PCM_CHANNEL_PLAYBACK, cmd);
}

static int snd_ad1816a_capture_trigger(void *private_data,
				       snd_pcm_subchn_t *subchn, int cmd)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	return snd_ad1816a_trigger(codec, AD1816A_CAPTURE_ENABLE,
		SND_PCM_CHANNEL_CAPTURE, cmd);
}


static int snd_ad1816a_playback_prepare(void *private_data,
					snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);
	unsigned long flags;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned int size;

	spin_lock_irqsave(&codec->lock, flags);

	codec->p_dma_size = size = snd_pcm_lib_transfer_size(subchn);
	snd_ad1816a_out_mask(codec, AD1816A_PLAYBACK_CONFIG,
		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);

	snd_dma_program(codec->dma1, runtime->dma_area->buf, size,
		DMA_MODE_WRITE | DMA_AUTOINIT);

	snd_ad1816a_write(codec, AD1816A_PLAYBACK_SAMPLE_RATE,
		snd_ad1816a_get_freq(runtime->format.rate));
	snd_ad1816a_out_mask(codec, AD1816A_PLAYBACK_CONFIG,
		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
		snd_ad1816a_get_format(codec, runtime->format.format,
			runtime->format.voices));

	snd_ad1816a_write(codec, AD1816A_PLAYBACK_BASE_COUNT,
		snd_pcm_lib_transfer_fragment(subchn) / 4 - 1);

	spin_unlock_irqrestore(&codec->lock, flags);
	return 0;
}

static int snd_ad1816a_capture_prepare(void *private_data,
				       snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);
	unsigned long flags;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	unsigned int size;

	spin_lock_irqsave(&codec->lock, flags);

	codec->c_dma_size = size = snd_pcm_lib_transfer_size(subchn);
	snd_ad1816a_out_mask(codec, AD1816A_CAPTURE_CONFIG,
		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);

	snd_dma_program(codec->dma2, runtime->dma_area->buf, size,
		DMA_MODE_READ | DMA_AUTOINIT);

	snd_ad1816a_write(codec, AD1816A_CAPTURE_SAMPLE_RATE,
		snd_ad1816a_get_freq(runtime->format.rate));
	snd_ad1816a_out_mask(codec, AD1816A_CAPTURE_CONFIG,
		AD1816A_FMT_ALL | AD1816A_FMT_STEREO,
		snd_ad1816a_get_format(codec, runtime->format.format,
			runtime->format.voices));

	snd_ad1816a_write(codec, AD1816A_CAPTURE_BASE_COUNT,
		snd_pcm_lib_transfer_fragment(subchn) / 4 - 1);

	spin_unlock_irqrestore(&codec->lock, flags);
	return 0;
}


static unsigned int snd_ad1816a_playback_pointer(void *private_data,
						 snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	return (codec->mode & AD1816A_MODE_PLAYBACK) ?
		(codec->p_dma_size - snd_dma_residue(codec->dma1)) : 0;
}

static unsigned int snd_ad1816a_capture_pointer(void *private_data,
					        snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	return (codec->mode & AD1816A_MODE_CAPTURE) ?
		(codec->c_dma_size - snd_dma_residue(codec->dma2)) : 0;
}


void snd_ad1816a_interrupt(snd_pcm_t *pcm, unsigned char status)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, pcm->private_data, );

	spin_lock(&codec->lock);
	status = snd_ad1816a_in(codec, AD1816A_INTERRUPT_STATUS);
	spin_unlock(&codec->lock);

	if (status & AD1816A_PLAYBACK_IRQ_PENDING)
		snd_pcm_transfer_done(codec->playback_subchn);

	if (status & AD1816A_CAPTURE_IRQ_PENDING)
		snd_pcm_transfer_done(codec->capture_subchn);

	if (status & AD1816A_TIMER_IRQ_PENDING)
		snd_timer_interrupt(codec->timer, codec->timer->sticks);

	spin_lock(&codec->lock);
	snd_ad1816a_out(codec, AD1816A_INTERRUPT_STATUS, 0x00);
	spin_unlock(&codec->lock);
}


static snd_pcm_hardware_t snd_ad1816a_playback = {
	/* flags */
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,
	/* formats */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW |
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S16_BE,
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	4000,			/* min rate */
	55200,			/* max rate */
	1,			/* min voices */
	2,			/* max voices */
	64,			/* min fragment */
	(128*1024),		/* max fragment */
	3,			/* frag. align */
	0,			/* FIFO size */
	4,			/* transfer block size */
	snd_ad1816a_playback_ioctl,
	snd_ad1816a_playback_prepare,
	snd_ad1816a_playback_trigger,
	snd_ad1816a_playback_pointer,
};

static snd_pcm_hardware_t snd_ad1816a_capture = {
	/* flags */
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,
	/* formats */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW |
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S16_BE,
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	4000,			/* min rate */
	55200,			/* max rate */
	1,			/* min voices */
	2,			/* max voices */
	64,			/* min fragment */
	(128*1024),		/* max fragment */
	3,			/* frag. align */
	0,			/* FIFO size */
	4,			/* transfer block size */
	snd_ad1816a_capture_ioctl,
	snd_ad1816a_capture_prepare,
	snd_ad1816a_capture_trigger,
	snd_ad1816a_capture_pointer,
};


static int snd_ad1816a_timer_close(snd_timer_t *timer)
{
	ad1816a_t *codec;

	codec = snd_magic_cast(ad1816a_t, timer->private_data, -ENXIO);
	snd_ad1816a_close(codec, AD1816A_MODE_TIMER);
	return 0;
}

static int snd_ad1816a_timer_open(snd_timer_t *timer)
{
	ad1816a_t *codec;

	codec = snd_magic_cast(ad1816a_t, timer->private_data, -ENXIO);
	snd_ad1816a_open(codec, AD1816A_MODE_TIMER);
	return 0;
}

static unsigned long snd_ad1816a_timer_resolution(snd_timer_t *timer)
{
	snd_debug_check(timer == NULL, 0);

	return 10000;
}

static void snd_ad1816a_timer_start(snd_timer_t *timer)
{
	unsigned short bits;
	unsigned long flags;
	ad1816a_t *codec;

	codec = snd_magic_cast(ad1816a_t, timer->private_data, );

	spin_lock_irqsave(&codec->lock, flags);
	bits = snd_ad1816a_read(codec, AD1816A_INTERRUPT_ENABLE);

	if (!(bits & AD1816A_TIMER_ENABLE)) {
		snd_ad1816a_write(codec, AD1816A_TIMER_BASE_COUNT,
			timer->sticks & 0xffff);

		snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
			AD1816A_TIMER_ENABLE, 0xffff);
	}
	spin_unlock_irqrestore(&codec->lock, flags);
}

static void snd_ad1816a_timer_stop(snd_timer_t *timer)
{
	unsigned long flags;
	ad1816a_t *codec;

	codec = snd_magic_cast(ad1816a_t, timer->private_data, );
	spin_lock_irqsave(&codec->lock, flags);

	snd_ad1816a_write_mask(codec, AD1816A_INTERRUPT_ENABLE,
		AD1816A_TIMER_ENABLE, 0x0000);

	spin_unlock_irqrestore(&codec->lock, flags);
}

static struct snd_stru_timer_hardware snd_ad1816a_timer = {
	SND_TIMER_HW_AUTO,		/* flags */
	10000,				/* resolution (us * 1000) */
	65535,				/* high timer mark */
	snd_ad1816a_timer_open,		/* open */
	snd_ad1816a_timer_close,	/* close */
	snd_ad1816a_timer_resolution,	/* resolution */
	snd_ad1816a_timer_start,	/* start */
	snd_ad1816a_timer_stop,		/* stop */
};


static int snd_ad1816a_playback_open(void *private_data,
				     snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int error;

	if (!(runtime->hw = (snd_pcm_hardware_t *) snd_kmalloc(
			sizeof(*runtime->hw), GFP_KERNEL)))
		return -ENOMEM;
	memcpy(runtime->hw, &snd_ad1816a_playback, sizeof(*runtime->hw));
	runtime->hw_free = _snd_kfree;

	snd_pcm_set_sync(subchn);
	snd_pcm_set_mixer(subchn, codec->mixer->device, codec->me_playback);

	error = snd_pcm_dma_alloc(subchn, codec->dma1ptr, "AD1816A (playback)");
	if (!error) {
		snd_ad1816a_open(codec, AD1816A_MODE_PLAYBACK);
		codec->playback_subchn = subchn;
	}
	return error;
}

static int snd_ad1816a_capture_open(void *private_data,
				    snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int error;

	if (!(runtime->hw = (snd_pcm_hardware_t *) snd_kmalloc(
			sizeof(*runtime->hw), GFP_KERNEL)))
		return -ENOMEM;
	memcpy(runtime->hw, &snd_ad1816a_capture, sizeof(*runtime->hw));
	runtime->hw_free = _snd_kfree;

	snd_pcm_set_sync(subchn);
	snd_pcm_set_mixer(subchn, codec->mixer->device, codec->me_capture);

	error = snd_pcm_dma_alloc(subchn, codec->dma2ptr, "AD1816A (capture)");
	if (!error) {
		snd_ad1816a_open(codec, AD1816A_MODE_CAPTURE);
		codec->capture_subchn = subchn;
	}
	return error;
}

static int snd_ad1816a_playback_close(void *private_data,
				      snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	codec->playback_subchn = NULL;
	snd_ad1816a_close(codec, AD1816A_MODE_PLAYBACK);
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_ad1816a_capture_close(void *private_data,
				     snd_pcm_subchn_t *subchn)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, -ENXIO);

	codec->capture_subchn = NULL;
	snd_ad1816a_close(codec, AD1816A_MODE_CAPTURE);
	snd_pcm_dma_free(subchn);
	return 0;
}


static void snd_ad1816a_init(ad1816a_t *codec)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->lock, flags);

	snd_ad1816a_out(codec, AD1816A_INTERRUPT_STATUS, 0x00);
	snd_ad1816a_out_mask(codec, AD1816A_PLAYBACK_CONFIG,
		AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00);
	snd_ad1816a_out_mask(codec, AD1816A_CAPTURE_CONFIG,
		AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00);
	snd_ad1816a_write(codec, AD1816A_INTERRUPT_ENABLE, 0x0000);
	snd_ad1816a_write_mask(codec, AD1816A_CHIP_CONFIG,
		AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff);
	snd_ad1816a_write(codec, AD1816A_DSP_CONFIG, 0x0000);
	snd_ad1816a_write(codec, AD1816A_POWERDOWN_CTRL, 0x0000);

	spin_unlock_irqrestore(&codec->lock, flags);
}

static int snd_ad1816a_probe(ad1816a_t *codec)
{
	unsigned long flags;

	spin_lock_irqsave(&codec->lock, flags);

	switch (codec->version = snd_ad1816a_read(codec, AD1816A_VERSION_ID)) {
	case 0:
		codec->hardware = AD1816A_HW_AD1815;
		break;
	case 1:
		codec->hardware = AD1816A_HW_AD18MAX10;
		break;
	case 3:
		codec->hardware = AD1816A_HW_AD1816A;
		break;
	default:
		codec->hardware = AD1816A_HW_AUTO;
	}

	spin_unlock_irqrestore(&codec->lock, flags);
	return 0;
}

static void snd_ad1816a_free(void *private_data)
{
	ad1816a_t *codec = snd_magic_cast(ad1816a_t, private_data, );

	if (codec->timer)
		snd_device_free(codec->card, codec->timer);

	snd_magic_kfree(codec);
}

int snd_ad1816a_new_pcm(snd_card_t *card, int device,
			unsigned long port, snd_irq_t *irqptr,
			snd_dma_t *dma1ptr, snd_dma_t *dma2ptr,
			int timer_dev, snd_pcm_t **rpcm)
{
	int error;
	ad1816a_t *codec;
	snd_pcm_t *pcm;
	snd_timer_t *timer;

	*rpcm = NULL;
	if ((error = snd_pcm_new(card, "AD1816A", device, 1, 1, &pcm)))
		return error;

	codec = snd_magic_kcalloc(ad1816a_t, 0, GFP_KERNEL);
	if (!codec) {
		snd_device_free(card, pcm);
		return -ENOMEM;
	}

	codec->pcm = pcm;
	codec->card = pcm->card;
	codec->port = port;
	codec->irq = irqptr->irq;
	codec->dma1 = dma1ptr->dma;
	codec->dma2 = dma2ptr->dma;
	codec->dma1ptr = dma1ptr;
	codec->dma2ptr = dma2ptr;
	spin_lock_init(&codec->lock);

	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_ad1816a_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_ad1816a_playback_close;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = codec;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_ad1816a_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_ad1816a_capture_close;

	pcm->private_data = codec;
	pcm->private_free = snd_ad1816a_free;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
		((codec->dma1 == codec->dma2 ) ? 0 : SND_PCM_INFO_DUPLEX);

	if ((error = snd_ad1816a_probe(codec))) {
		snd_device_free(card, pcm);
		return error;
	}

	switch (codec->hardware) {
	case AD1816A_HW_AD1816A:
		strcpy(pcm->name, "AD1816A");
		break;
	case AD1816A_HW_AD1815:
		strcpy(pcm->name, "AD1815");
		break;
	case AD1816A_HW_AD18MAX10:
		strcpy(pcm->name, "AD18max10");
		break;
	default:
		strcpy(pcm->name, "AD1816A - unknown");

		snd_ad1816a_printk("Unknown chip version %d:%d.\n",
			codec->version, codec->hardware);
	}
	snd_ad1816a_init(codec);

	if ((error = snd_timer_new(card, pcm->name, timer_dev, &timer)) < 0) {
		snd_device_free(card, pcm);

		return error;
	}
	strcpy(timer->name, pcm->name);

	timer->private_data = codec;
	codec->timer = timer;
	memcpy(&timer->hw, &snd_ad1816a_timer, sizeof(snd_ad1816a_timer));

	*rpcm = pcm;
	return 0;
}


static int snd_ad1816a_get_src_bits(ad1816a_t *codec,
				    snd_kmixer_element_t *element)
{
	if (element == codec->me_mux_cd)
		return AD1816A_SRC_CD;
	if (element == codec->me_mux_line)
		return AD1816A_SRC_LINE;
	if (element == codec->me_mux_mic)
		return AD1816A_SRC_MIC;
	if (element == codec->me_mux_out)
		return AD1816A_SRC_OUT;
	if (element == codec->me_mux_phone_in)
		return AD1816A_SRC_PHONE_IN;
	if (element == codec->me_mux_synth)
		return AD1816A_SRC_SYNTH;
	if (element == codec->me_mux_video)
		return AD1816A_SRC_VIDEO;
	return -EINVAL;
}

static snd_kmixer_element_t *snd_ad1816a_get_src_element(ad1816a_t *codec,
							 int bits)
{
	switch (bits & AD1816A_SRC_MASK) {
	case AD1816A_SRC_CD:
		return codec->me_mux_cd;
	case AD1816A_SRC_LINE:
		return codec->me_mux_line;
	case AD1816A_SRC_MIC:
		return codec->me_mux_mic;
	case AD1816A_SRC_OUT:
		return codec->me_mux_out;
	case AD1816A_SRC_PHONE_IN:
		return codec->me_mux_phone_in;
	case AD1816A_SRC_SYNTH:
		return codec->me_mux_synth;
	case AD1816A_SRC_VIDEO:
		return codec->me_mux_video;
	}
	return NULL;
}

static int snd_ad1816a_mixer_mux(snd_kmixer_element_t *element, int writing,
						snd_kmixer_element_t **elements)
{
	int bits, left, right, nleft, nright;
	ad1816a_t *codec = (ad1816a_t *) element->private_data;
	unsigned long flags;
	int changed = 0;

	spin_lock_irqsave(&codec->lock, flags);

	bits = snd_ad1816a_read(codec, AD1816A_ADC_SOURCE_SEL);
	left = (bits >> 8) & AD1816A_SRC_MASK;
	right = bits & AD1816A_SRC_MASK;

	if (!writing) {
		elements[0] = snd_ad1816a_get_src_element(codec, left);
		elements[1] = snd_ad1816a_get_src_element(codec, right);
	}
	else {
		nleft = snd_ad1816a_get_src_bits(codec, elements[0]);
		nright = snd_ad1816a_get_src_bits(codec, elements[1]);

		if (nleft < 0 || nright < 0)
			changed = -EINVAL;
		else if ((changed = (nleft != left || nright != right)))
			snd_ad1816a_write_mask(codec, AD1816A_ADC_SOURCE_SEL,
				(AD1816A_SRC_MASK << 8) | AD1816A_SRC_MASK,
 				(nleft << 8) | nright);
	}

	spin_unlock_irqrestore(&codec->lock, flags);
	return changed;
}

static int snd_ad1816a_mixer_mono_volume(snd_kmixer_element_t *element,
					 int writing, int *voices,
					 int max, int shift, unsigned char reg)
{
	int bits, nbits;
	ad1816a_t *codec = (ad1816a_t *) element->private_data;
	unsigned long flags;
	int changed = 0;

	spin_lock_irqsave(&codec->lock, flags);
	bits = (snd_ad1816a_read(codec, reg) >> shift) & max;

	if (writing) {
		nbits = (max - *voices) & max;

		if ((changed = (bits != nbits)))
			snd_ad1816a_write_mask(codec, reg, max << shift,
				nbits << shift);
	}
	else
		*voices = max - bits;

	spin_unlock_irqrestore(&codec->lock, flags);
	return changed;	
}

static int snd_ad1816a_mixer_stereo_volume(snd_kmixer_element_t *element,
					   int writing, int *voices,
					   int max, int invert,
					   unsigned char reg)
{
	int bits, left, right, nleft, nright;
	ad1816a_t *codec = (ad1816a_t *) element->private_data;
	unsigned long flags;
	int changed = 0;

	spin_lock_irqsave(&codec->lock, flags);

	bits = snd_ad1816a_read(codec, reg);
	left = (bits >> 8) & max;
	right = bits & max;

	if (!writing) {
		voices[0] = invert ? (max - left) : left;
		voices[1] = invert ? (max - right) : right;
	}
	else {
		nleft = invert ? (max - voices[0]) : voices[0];
		nright = invert ? (max - voices[1]) : voices[1];

		if ((changed = (left != nleft || right != nright)))
			snd_ad1816a_write_mask(codec, reg, (max << 8) | max,
				(nleft << 8) | nright);
	}

	spin_unlock_irqrestore(&codec->lock, flags);
	return changed;	
}

static int snd_ad1816a_mixer_mono_switch(snd_kmixer_element_t *element,
					   int writing, unsigned int *bitmap,
					   int bit, int invert,
					   unsigned char reg)
{
	int oldval, newval;
	ad1816a_t *codec = (ad1816a_t *) element->private_data;
	unsigned long flags;
	int changed = 0;

	spin_lock_irqsave(&codec->lock, flags);

	oldval = (snd_ad1816a_read(codec, reg) >> bit) & 1;

	if (!writing)
		snd_mixer_set_bit(bitmap, 0, invert ? oldval ^ 1 : oldval);
	else {
		newval = snd_mixer_get_bit(bitmap, 0);
		newval = invert ? newval ^ 1 : newval;

		if ((changed = (oldval != newval)))
			snd_ad1816a_write_mask(codec, reg,  1 << bit, newval);
	}

	spin_unlock_irqrestore(&codec->lock, flags);
	return changed;	
}

static int snd_ad1816a_mixer_stereo_switch(snd_kmixer_element_t *element,
					   int writing, unsigned int *bitmap,
					   unsigned char reg)
{
	int bits, left, right, nleft, nright;
	ad1816a_t *codec = (ad1816a_t *) element->private_data;
	unsigned long flags;
	int changed = 0;

	spin_lock_irqsave(&codec->lock, flags);

	bits = snd_ad1816a_read(codec, reg);
	left = (bits & 0x8000) >> 15;
	right = (bits & 0x0080) >> 7;

	if (!writing) {
		snd_mixer_set_bit(bitmap, 0, left ^ 1);
		snd_mixer_set_bit(bitmap, 1, right ^ 1);
	}
	else {
		nleft = snd_mixer_get_bit(bitmap, 0) ^ 1;
		nright = snd_mixer_get_bit(bitmap, 1) ^ 1;

		if ((changed = (left != nleft || right != nright)))
			snd_ad1816a_write_mask(codec, reg, 0x8080,
				(nleft << 8) | nright);
	}

	spin_unlock_irqrestore(&codec->lock, flags);
	return changed;
}


static int snd_ad1816a_mixer_adc_switch(snd_kmixer_element_t *element,
					int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_ADC_PGA);
}

static int snd_ad1816a_mixer_adc_volume(snd_kmixer_element_t *element,
					int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		15, 0, AD1816A_ADC_PGA);
}

static int snd_ad1816a_mixer_cd_switch(snd_kmixer_element_t *element,
				       int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_CD_GAIN_ATT);
}

static int snd_ad1816a_mixer_cd_volume(snd_kmixer_element_t *element,
				       int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		31, 1, AD1816A_CD_GAIN_ATT);
}

/*
static int snd_ad1816a_mixer_fm_switch(snd_kmixer_element_t *element,
				       int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_FM_ATT);
}

static int snd_ad1816a_mixer_fm_volume(snd_kmixer_element_t *element,
				       int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		63, 1, AD1816A_FM_ATT);
}
*/

static int snd_ad1816a_mixer_line_switch(snd_kmixer_element_t *element,
				       int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_LINE_GAIN_ATT);
}

static int snd_ad1816a_mixer_line_volume(snd_kmixer_element_t *element,
				       int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		31, 1, AD1816A_LINE_GAIN_ATT);
}

static int snd_ad1816a_mixer_master_switch(snd_kmixer_element_t *element,
					int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_MASTER_ATT);
}

static int snd_ad1816a_mixer_master_volume(snd_kmixer_element_t *element,
				       int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		31, 1, AD1816A_MASTER_ATT);
}

static int snd_ad1816a_mixer_mic_switch(snd_kmixer_element_t *element,
					int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_mono_switch(element, writing, bitmap,
		15, 1, AD1816A_MIC_GAIN_ATT);
}

static int snd_ad1816a_mixer_mic_volume(snd_kmixer_element_t *element,
					int writing, int *voices)
{
	return snd_ad1816a_mixer_mono_volume(element, writing, voices,
		63, 8, AD1816A_MIC_GAIN_ATT);
}

static int snd_ad1816a_mixer_mic_gain_switch(snd_kmixer_element_t *element,
					     int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_mono_switch(element, writing, bitmap,
		14, 0, AD1816A_MIC_GAIN_ATT);
}

static int snd_ad1816a_mixer_phone_in_switch(snd_kmixer_element_t *element,
					     int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_mono_switch(element, writing, bitmap,
		15, 1, AD1816A_PHONE_IN_GAIN_ATT);
}

static int snd_ad1816a_mixer_phone_in_volume(snd_kmixer_element_t *element,
					     int writing, int *voices)
{
	return snd_ad1816a_mixer_mono_volume(element, writing, voices,
		15, 1, AD1816A_PHONE_IN_GAIN_ATT);
}

static int snd_ad1816a_mixer_phone_out_switch(snd_kmixer_element_t *element,
					      int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_mono_switch(element, writing, bitmap,
		7, 1, AD1816A_PHONE_OUT_ATT);
}

static int snd_ad1816a_mixer_phone_out_volume(snd_kmixer_element_t *element,
					      int writing, int *voices)
{
	return snd_ad1816a_mixer_mono_volume(element, writing, voices,
		31, 0, AD1816A_PHONE_OUT_ATT);
}

static int snd_ad1816a_mixer_synth_switch(snd_kmixer_element_t *element,
					  int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_SYNTH_GAIN_ATT);
}

static int snd_ad1816a_mixer_synth_volume(snd_kmixer_element_t *element,
					  int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		31, 1, AD1816A_SYNTH_GAIN_ATT);
}

static int snd_ad1816a_mixer_video_switch(snd_kmixer_element_t *element,
					  int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_VID_GAIN_ATT);
}

static int snd_ad1816a_mixer_video_volume(snd_kmixer_element_t *element,
					  int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		31, 1, AD1816A_VID_GAIN_ATT);
}

static int snd_ad1816a_mixer_voice_switch(snd_kmixer_element_t *element,
					  int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_stereo_switch(element, writing, bitmap,
		AD1816A_VOICE_ATT);
}

static int snd_ad1816a_mixer_voice_volume(snd_kmixer_element_t *element,
					  int writing, int *voices)
{
	return snd_ad1816a_mixer_stereo_volume(element, writing, voices,
		63, 1, AD1816A_VOICE_ATT);
}

static int snd_ad1816a_mixer_3d_phat_switch(snd_kmixer_element_t *element,
					    int writing, unsigned int *bitmap)
{
	return snd_ad1816a_mixer_mono_switch(element, writing, bitmap,
		15, 1, AD1816A_3D_PHAT_CTRL);
}

static int snd_ad1816a_mixer_3d_phat_depth(snd_kmixer_element_t *element,
					   int writing, int *voices)
{
	return snd_ad1816a_mixer_mono_volume(element, writing, voices,
		15, 0, AD1816A_3D_PHAT_CTRL);
}


static int snd_ad1816a_mixer_group_stereo(snd_kmixer_group_t *group,
					  snd_kmixer_file_t *file,
					  int writing,
					  snd_mixer_group_t *ugroup,
					  snd_mixer_volume1_control_t *volume1,
					  snd_kmixer_element_t *volume1_element,
					  int max,
					  snd_mixer_sw1_control_t *sw1,
					  snd_kmixer_element_t *sw1_element,
					  snd_kmixer_element_t *mux_in)
{
	unsigned int bitmap;
	ad1816a_t *codec = (ad1816a_t *) group->private_data;
	snd_kmixer_element_t *elements[2];
	int voices[2];
	int changed = 0;

	if (!writing) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;

		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(volume1_element, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = max;
		}

		if (sw1) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1(sw1_element, 0, &bitmap);
			ugroup->mute = (!snd_mixer_get_bit(&bitmap, 0)) ?
				SND_MIXER_CHN_MASK_FRONT_LEFT : 0;
			ugroup->mute |= (!snd_mixer_get_bit(&bitmap, 1)) ?
				SND_MIXER_CHN_MASK_FRONT_RIGHT : 0;
		}

		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			ugroup->caps |= SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_ad1816a_mixer_mux(codec->me_mux, 0, elements);
			ugroup->capture = (elements[0] == mux_in) ?
				SND_MIXER_CHN_MASK_FRONT_LEFT : 0;
			ugroup->capture |= (elements[1] == mux_in) ?
				SND_MIXER_CHN_MASK_FRONT_RIGHT : 0;
		}
	}
	else {
		if (volume1) {
			voices[0] = ugroup->volume.names.front_left & max;
			voices[1] = ugroup->volume.names.front_right & max;
			if (volume1(volume1_element, 1, voices)) {
				snd_mixer_element_value_change(file,
					volume1_element, 0);
				changed = 1;
			}
		}

		if (sw1) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1(sw1_element, 1, &bitmap)) {
				snd_mixer_element_value_change(file,
					sw1_element, 0);
				changed = 1;
			}
		}

		if (mux_in) {
			snd_ad1816a_mixer_mux(codec->me_mux, 0, elements);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				elements[0] = mux_in;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				elements[1] = mux_in;
			if (snd_ad1816a_mixer_mux(codec->me_mux, 1, elements)) {
				snd_mixer_element_value_change(file,
					codec->me_mux, 0);
				changed = 1;
			}
		}
	}
	return changed;
}

static int snd_ad1816a_mixer_group_mono(snd_kmixer_group_t *group,
					snd_kmixer_file_t *file,
		 			int writing,
		 			snd_mixer_group_t *ugroup,
		 			snd_mixer_volume1_control_t *volume1,
		 			snd_kmixer_element_t *volume1_element,
		 			int max,
		 			snd_mixer_sw1_control_t *sw1,
		 			snd_kmixer_element_t *sw1_element,
					snd_kmixer_element_t *mux_in)
{
	unsigned int bitmap;
	ad1816a_t *codec = (ad1816a_t *) group->private_data;
	snd_kmixer_element_t *elements[2];
	int voice;
	int changed = 0;

	if (!writing) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;

		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(volume1_element, 0, &voice);
			ugroup->volume.names.front_left = voice;
			ugroup->min = 0;
			ugroup->max = max;
		}

		if (sw1) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1(sw1_element, 0, &bitmap);
			ugroup->mute = (!snd_mixer_get_bit(&bitmap, 0)) ?
				SND_MIXER_CHN_MASK_FRONT_LEFT : 0;
		}

		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			ugroup->caps |= SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_ad1816a_mixer_mux(codec->me_mux, 0, elements);
			ugroup->capture = (elements[0] == mux_in) ?
				SND_MIXER_CHN_MASK_FRONT_LEFT : 0;
		}
	}
	else {
		if (volume1) {
			voice = ugroup->volume.names.front_left & max;
			if (volume1(volume1_element, 1, &voice)) {
				snd_mixer_element_value_change(file,
					volume1_element, 0);
				changed = 1;
			}
		}

		if (sw1) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (sw1(sw1_element, 1, &bitmap)) {
				snd_mixer_element_value_change(file,
					sw1_element, 0);
				changed = 1;
			}
		}

		if (mux_in) {
			snd_ad1816a_mixer_mux(codec->me_mux, 0, elements);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				elements[0] = mux_in;
			if (snd_ad1816a_mixer_mux(codec->me_mux, 1, elements)) {
				snd_mixer_element_value_change(file,
					codec->me_mux, 0);
				changed = 1;
			}
		}
	}
	return changed;
}

static int snd_ad1816a_mixer_group_adc(snd_kmixer_group_t *group,
				       snd_kmixer_file_t *file,
				       int writing,
				       snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_adc_volume,
		codec->me_vol_adc,
		15,
		snd_ad1816a_mixer_adc_switch,
		codec->me_sw_adc,
		NULL);
}

static int snd_ad1816a_mixer_group_cd(snd_kmixer_group_t *group,
				      snd_kmixer_file_t *file,
				      int writing,
				      snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_cd_volume,
		codec->me_vol_cd,
		31,
		snd_ad1816a_mixer_cd_switch,
		codec->me_sw_cd,
		codec->me_mux_cd);
}

/*
static int snd_ad1816a_mixer_group_fm(snd_kmixer_group_t *group,
				      snd_kmixer_file_t *file,
				      int writing,
				      snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_fm_volume,
		codec->me_vol_fm,
		63,
		snd_ad1816a_mixer_fm_switch,
		codec->me_sw_fm,
		NULL);
}
*/

static int snd_ad1816a_mixer_group_line(snd_kmixer_group_t *group,
					snd_kmixer_file_t *file,
					int writing,
					snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_line_volume,
		codec->me_vol_line,
		31,
		snd_ad1816a_mixer_line_switch,
		codec->me_sw_line,
		codec->me_mux_line);
}

static int snd_ad1816a_mixer_group_master(snd_kmixer_group_t *group,
					  snd_kmixer_file_t *file,
					  int writing,
					  snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_master_volume,
		codec->me_vol_master,
		31,
		snd_ad1816a_mixer_master_switch,
		codec->me_sw_master,
		NULL);
}

static int snd_ad1816a_mixer_group_mic(snd_kmixer_group_t *group,
				       snd_kmixer_file_t *file,
				       int writing,
				       snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_mono(group, file, writing, ugroup,
		snd_ad1816a_mixer_mic_volume,
		codec->me_vol_mic,
		31,
		snd_ad1816a_mixer_mic_switch,
		codec->me_sw_mic,
		codec->me_mux_mic);
}

static int snd_ad1816a_mixer_group_phone_in(snd_kmixer_group_t *group,
					    snd_kmixer_file_t *file,
					    int writing,
					    snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_mono(group, file, writing, ugroup,
		snd_ad1816a_mixer_phone_in_volume,
		codec->me_vol_phone_in,
		15,
		snd_ad1816a_mixer_phone_in_switch,
		codec->me_sw_phone_in,
		codec->me_mux_phone_in);
}

static int snd_ad1816a_mixer_group_phone_out(snd_kmixer_group_t *group,
					     snd_kmixer_file_t *file,
					     int writing,
					     snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_mono(group, file, writing, ugroup,
		snd_ad1816a_mixer_phone_out_volume,
		codec->me_vol_phone_out,
		15,
		snd_ad1816a_mixer_phone_out_switch,
		codec->me_sw_phone_out,
		NULL);
}

static int snd_ad1816a_mixer_group_synth(snd_kmixer_group_t *group,
					 snd_kmixer_file_t *file,
					 int writing,
					 snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_synth_volume,
		codec->me_vol_synth,
		31,
		snd_ad1816a_mixer_synth_switch,
		codec->me_sw_synth,
		codec->me_mux_synth);
}

static int snd_ad1816a_mixer_group_video(snd_kmixer_group_t *group,
					 snd_kmixer_file_t *file,
					 int writing,
					 snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_video_volume,
		codec->me_vol_video,
		31,
		snd_ad1816a_mixer_video_switch,
		codec->me_sw_video,
		codec->me_mux_video);
}

static int snd_ad1816a_mixer_group_voice(snd_kmixer_group_t *group,
					 snd_kmixer_file_t *file,
					 int writing,
					 snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_stereo(group, file, writing, ugroup,
		snd_ad1816a_mixer_voice_volume,
		codec->me_vol_voice,
		63,
		snd_ad1816a_mixer_voice_switch,
		codec->me_sw_voice,
		codec->me_mux_out);
}

static int snd_ad1816a_mixer_group_3d_phat(snd_kmixer_group_t *group,
					   snd_kmixer_file_t *file,
					   int writing,
					   snd_mixer_group_t *ugroup)
{
	ad1816a_t *codec = (ad1816a_t *) group->private_data;

	return snd_ad1816a_mixer_group_mono(group, file, writing, ugroup,
		snd_ad1816a_mixer_3d_phat_depth,
		codec->me_vol_3d_phat,
		15,
		snd_ad1816a_mixer_3d_phat_switch,
		codec->me_sw_3d_phat,
		NULL);
}


int snd_ad1816a_new_mixer(snd_pcm_t *pcm, int device,
				 snd_kmixer_t **rmixer)
{
	int error;
	ad1816a_t *codec;
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;

	static struct snd_mixer_element_volume1_range adc_range[2] = {
		{0, 15, 0, 2250},
		{0, 15, 0, 2250}
	};
	static struct snd_mixer_element_volume1_range cd_range[2] = {
		{0, 31, -3450, 1200},
		{0, 31, -3450, 1200},
	};
	static struct snd_mixer_element_volume1_range master_range[2] = {
		{0, 31, -4650, 0},
		{0, 31, -4650, 0}
	};
	static struct snd_mixer_element_volume1_range mic_range[1] = {
		{0, 31, -3450, 1200}
	};
	static struct snd_mixer_element_volume1_range phat_range[1] = {
		{0, 15, 0, 1000}
	};
	static struct snd_mixer_element_volume1_range phin_range[1] = {
		{0, 15, -4500, 0}
	};
	static struct snd_mixer_element_volume1_range phout_range[1] = {
		{0, 31, -4650, 0}
	};
	static struct snd_mixer_element_volume1_range voice_range[2] = {
		{0, 63, -9450, 0},
		{0, 63, -9450, 0}
	};

	snd_debug_check(rmixer == NULL, -EINVAL);
	*rmixer = NULL;

	snd_debug_check(pcm == NULL || pcm->card == NULL, -EINVAL);

	codec = snd_magic_cast(ad1816a_t, pcm->private_data, -ENXIO);

	if ((error = snd_mixer_new(pcm->card, pcm->id, device, &mixer)))
		return error;
	strcpy(mixer->name, pcm->name);

	if (!(codec->me_mux = snd_mixer_lib_mux1(mixer,
			SND_MIXER_ELEMENT_INPUT_MUX, 0, 0, 2,
			snd_ad1816a_mixer_mux, codec)))
		goto __on_error;
	if (!(codec->me_accu = snd_mixer_lib_accu1(mixer,
			SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)))
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_mux))
		goto __on_error;
	codec->me_mux_out = codec->me_accu;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER,
			0, SND_MIXER_OSS_VOLUME, snd_ad1816a_mixer_group_master,
			codec)) == NULL)
		goto __on_error;
	if ((codec->me_out_master = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT,
			0)) == NULL)
		goto __on_error;
	if ((codec->me_vol_master = snd_mixer_lib_volume1(mixer,
			"Master Volume", 0, 2, master_range,
			snd_ad1816a_mixer_master_volume, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_master) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master,
			codec->me_out_master) < 0)
		goto __on_error;
	if ((codec->me_sw_master = snd_mixer_lib_sw1(mixer, "Master Switch",
			0, 2, snd_ad1816a_mixer_master_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master,
			codec->me_vol_master) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_EFFECT_3D,
			0, SND_MIXER_OSS_UNKNOWN,
			snd_ad1816a_mixer_group_3d_phat, codec)) == NULL)
		goto __on_error;
	if ((codec->me_vol_3d_phat = snd_mixer_lib_volume1(mixer,
			"3D Phat Depth", 0, 1, phat_range,
			snd_ad1816a_mixer_3d_phat_depth, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group,
			codec->me_vol_3d_phat) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_3d_phat,
			codec->me_sw_master) < 0)
		goto __on_error;
	if ((codec->me_sw_3d_phat = snd_mixer_lib_sw1(mixer, "3D Phat Switch",
			0, 1, snd_ad1816a_mixer_3d_phat_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_3d_phat) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_3d_phat,
			codec->me_vol_3d_phat) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu,
			codec->me_sw_3d_phat) < 0)
		goto __on_error;

	if ((codec->me_out_phone = snd_mixer_lib_io_mono(mixer,
			SND_MIXER_OUT_PHONE, 0, SND_MIXER_ETYPE_OUTPUT,
			0)) == NULL)
		goto __on_error;
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_PHONE,
			0, SND_MIXER_OSS_PHONEOUT,
			snd_ad1816a_mixer_group_phone_out, codec)) == NULL)
		goto __on_error;
	if ((codec->me_vol_phone_out = snd_mixer_lib_volume1(mixer,
			"Phone Out Volume", 0, 1, phout_range,
			snd_ad1816a_mixer_phone_out_volume, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group,
			codec->me_vol_phone_out) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_phone_out,
			codec->me_out_phone) < 0)
		goto __on_error;
	if ((codec->me_sw_phone_out = snd_mixer_lib_sw1(mixer,
			"Phone Out Switch", 0, 1,
			snd_ad1816a_mixer_phone_out_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group,
			codec->me_sw_phone_out) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_phone_out,
			codec->me_vol_phone_out) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu,
			codec->me_sw_phone_out) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0,
			SND_MIXER_OSS_CD, snd_ad1816a_mixer_group_cd,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD,
			0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer,
			codec->me_in_cd, codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_cd = codec->me_in_cd;
	if ((codec->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Volume", 0,
			2, cd_range, snd_ad1816a_mixer_cd_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_cd) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_cd,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_cd = snd_mixer_lib_sw1(mixer, "CD Switch",
			0, 2, snd_ad1816a_mixer_cd_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_cd) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_cd,
			codec->me_vol_cd) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_cd,
			codec->me_sw_cd) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0,
			SND_MIXER_OSS_LINE, snd_ad1816a_mixer_group_line,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_line = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT,
			0)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer,
			codec->me_in_line, codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_line = codec->me_in_line;
	if ((codec->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Volume", 0,
			2, cd_range, snd_ad1816a_mixer_line_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_line) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_line,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_line = snd_mixer_lib_sw1(mixer, "Line Switch",
			0, 2, snd_ad1816a_mixer_line_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_line) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_line,
			codec->me_vol_line) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_line,
			codec->me_sw_line) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC,
			0, SND_MIXER_OSS_MIC, snd_ad1816a_mixer_group_mic,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_mic = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __on_error;
	if ((codec->me_sw_mic_gain = snd_mixer_lib_sw1(mixer,
			"MIC Gain Switch", 0, 1,
			snd_ad1816a_mixer_mic_gain_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group,
			codec->me_sw_mic_gain) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mic,
			codec->me_sw_mic_gain) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_gain,
			codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_mic = codec->me_in_mic;
	if ((codec->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0,
			1, mic_range, snd_ad1816a_mixer_mic_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mic) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_mic = snd_mixer_lib_sw1(mixer, "MIC Switch",
			0, 1, snd_ad1816a_mixer_mic_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic_gain,
			codec->me_sw_mic) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_mic,
			codec->me_vol_mic) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PHONE,
			0, SND_MIXER_OSS_PHONEIN,
			snd_ad1816a_mixer_group_phone_in, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_phone = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_IN_PHONE, 0, SND_MIXER_ETYPE_INPUT,
			0)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_phone,
			codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_phone_in = codec->me_in_phone;
	if ((codec->me_vol_phone_in = snd_mixer_lib_volume1(mixer,
			"Phone In Volume", 0, 1,
			phin_range, snd_ad1816a_mixer_phone_in_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer,
			group, codec->me_vol_phone_in) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_phone_in,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_phone_in = snd_mixer_lib_sw1(mixer, "Phone In Switch",
			0, 1, snd_ad1816a_mixer_phone_in_switch,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group,
			codec->me_sw_phone_in) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_phone,
			codec->me_sw_phone_in) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_phone_in,
			codec->me_vol_phone_in) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER,
			0, SND_MIXER_OSS_SYNTH, snd_ad1816a_mixer_group_synth,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_synth = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT,
			0)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer,
			codec->me_in_synth, codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_synth = codec->me_in_synth;
	if ((codec->me_vol_synth = snd_mixer_lib_volume1(mixer, "Synth Volume",
			0, 2, cd_range, snd_ad1816a_mixer_synth_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_synth) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_synth,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_synth = snd_mixer_lib_sw1(mixer, "Synth Switch",
			0, 2, snd_ad1816a_mixer_synth_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_synth) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_synth,
			codec->me_vol_synth) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_synth,
			codec->me_sw_synth) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_VIDEO,
			0, SND_MIXER_OSS_VIDEO, snd_ad1816a_mixer_group_video,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __on_error;
	if ((codec->me_in_video = snd_mixer_lib_io_stereo(mixer,
			SND_MIXER_IN_VIDEO, 0, SND_MIXER_ETYPE_INPUT,
			0)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer,
			codec->me_in_video, codec->me_mux) < 0)
		goto __on_error;
	codec->me_mux_video = codec->me_in_video;
	if ((codec->me_vol_video = snd_mixer_lib_volume1(mixer, "Video Volume",
			0, 2, cd_range, snd_ad1816a_mixer_video_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_video) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_video,
			codec->me_accu) < 0)
		goto __on_error;
	if ((codec->me_sw_video = snd_mixer_lib_sw1(mixer, "Video Switch",
			0, 2, snd_ad1816a_mixer_video_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_video) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_video,
			codec->me_vol_video) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_video,
			codec->me_sw_video) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN,
			0, SND_MIXER_OSS_IGAIN, snd_ad1816a_mixer_group_adc,
			codec)) == NULL)
		goto __on_error;
	if ((codec->me_sw_adc = snd_mixer_lib_sw1(mixer, "Auto Gain Switch",
			0, 2, snd_ad1816a_mixer_adc_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_adc) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_mux,
			codec->me_sw_adc) < 0)
		goto __on_error;
	if ((codec->me_vol_adc = snd_mixer_lib_volume1(mixer,
			"Input Gain Volume", 0, 2, adc_range,
			 snd_ad1816a_mixer_adc_volume, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_adc) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_adc,
			codec->me_vol_adc) < 0)
		goto __on_error;

	if ((codec->me_adc = snd_mixer_lib_converter(mixer,
			SND_MIXER_ELEMENT_ADC, 0, SND_MIXER_ETYPE_ADC,
			16)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_adc,
			codec->me_adc) < 0)
		goto __on_error;

	if ((codec->me_capture = snd_mixer_lib_pcm1(mixer,
			SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE1,
			1, &pcm->device)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_adc,
			codec->me_capture) < 0)
		goto __on_error;

	if ((codec->me_dig_accu = snd_mixer_lib_accu1(mixer,
			SND_MIXER_ELEMENT_DIGITAL_ACCU, 0, 0)) == NULL)
		goto __on_error;

	if ((codec->me_playback = snd_mixer_lib_pcm1(mixer,
			SND_MIXER_ELEMENT_PLAYBACK, 0,
			SND_MIXER_ETYPE_PLAYBACK1, 1, &pcm->device)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_playback,
			codec->me_dig_accu) < 0)
		goto __on_error;

	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0,
			SND_MIXER_OSS_PCM, snd_ad1816a_mixer_group_voice,
			codec)) == NULL)
		goto __on_error;
	if ((codec->me_dac = snd_mixer_lib_converter(mixer,
			SND_MIXER_ELEMENT_DAC, 0, SND_MIXER_ETYPE_DAC,
			16)) == NULL)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_dig_accu,
			codec->me_dac) < 0)
		goto __on_error;
	if ((codec->me_sw_voice = snd_mixer_lib_sw1(mixer, "PCM Switch", 0,
			2, snd_ad1816a_mixer_voice_switch, codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_voice) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_dac,
			codec->me_sw_voice) < 0)
		goto __on_error;
	if ((codec->me_vol_voice = snd_mixer_lib_volume1(mixer, "PCM Volume",
			0, 2, voice_range, snd_ad1816a_mixer_voice_volume,
			codec)) == NULL)
		goto __on_error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_voice) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_voice,
			codec->me_vol_voice) < 0)
		goto __on_error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_voice,
			codec->me_accu) < 0)
		goto __on_error;

	mixer->private_data = codec;
	*rmixer = codec->mixer = mixer;
	return 0;

      __on_error:
      	snd_device_free(pcm->card, mixer);
      	return -ENOMEM;
}

EXPORT_SYMBOL(snd_ad1816a_interrupt);
EXPORT_SYMBOL(snd_ad1816a_new_pcm);
EXPORT_SYMBOL(snd_ad1816a_new_mixer);

static int __init alsa_ad1816a_init(void)
{
	return 0;
}

static void __exit alsa_ad1816a_exit(void)
{
}

module_init(alsa_ad1816a_init)
module_exit(alsa_ad1816a_exit)

