/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of ESS ES1688/688/488 chip
 *
 *
 *   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/es1688.h"

static int snd_es1688_dsp_command(es1688_t * codec, unsigned char val)
{
	int i;

	for (i = 10000; i; i--)
		if ((inb(ES1688P(codec, STATUS)) & 0x80) == 0) {
			outb(val, ES1688P(codec, COMMAND));
			return 1;
		}
#ifdef CONFIG_SND_DEBUG
	printk("snd_es1688_dsp_command: timeout (0x%x)\n", val);
#endif
	return 0;
}

static int snd_es1688_dsp_get_byte(es1688_t * codec)
{
	int i;

	for (i = 1000; i; i--)
		if (inb(ES1688P(codec, DATA_AVAIL)) & 0x80)
			return inb(ES1688P(codec, READ));
	snd_printd("es1688 get byte failed: 0x%x = 0x%x!!!\n", ES1688P(codec, DATA_AVAIL), inb(ES1688P(codec, DATA_AVAIL)));
	return -ENODEV;
}

static int snd_es1688_write(es1688_t * codec,
			    unsigned char reg, unsigned char data)
{
	if (!snd_es1688_dsp_command(codec, reg))
		return 0;
	return snd_es1688_dsp_command(codec, data);
}

int snd_es1688_read(es1688_t * codec, unsigned char reg)
{
	/* Read a byte from an extended mode register of ES1688 */
	if (!snd_es1688_dsp_command(codec, 0xc0))
		return -1;
	if (!snd_es1688_dsp_command(codec, reg))
		return -1;
	return snd_es1688_dsp_get_byte(codec);
}

void snd_es1688_mixer_write(es1688_t * codec,
			    unsigned char reg, unsigned char data)
{
	outb(reg, ES1688P(codec, MIXER_ADDR));
	snd_delay(1);
	outb(data, ES1688P(codec, MIXER_DATA));
	snd_delay(1);
}

unsigned char snd_es1688_mixer_read(es1688_t * codec, unsigned char reg)
{
	unsigned char result;

	outb(reg, ES1688P(codec, MIXER_ADDR));
	snd_delay(1);
	result = inb(ES1688P(codec, MIXER_DATA));
	snd_delay(1);
	return result;
}

static int snd_es1688_reset(es1688_t * codec)
{
	int i;

	outb(3, ES1688P(codec, RESET));		/* valid only for ESS chips, SB -> 1 */
	snd_delay(1);
	outb(0, ES1688P(codec, RESET));
	snd_delay(3);
	for (i = 0; i < 1000 && !(inb(ES1688P(codec, DATA_AVAIL)) & 0x80); i++);
	if (inb(ES1688P(codec, READ)) != 0xaa) {
		snd_printd("ess_reset at 0x%x: failed!!!\n", codec->port);
		return -ENODEV;
	}
	snd_es1688_dsp_command(codec, 0xc6);	/* enable extended mode */
	return 0;
}

static int snd_es1688_probe(snd_pcm_t * pcm)
{
	unsigned long flags;
	unsigned short major, minor, hw;
	es1688_t *codec;
	char *str;
	int i;

	/*
	 *  initialization sequence
	 */

	codec = (es1688_t *) pcm->private_data;
	spin_lock_irqsave(&codec->reg_lock, flags);	/* Some ESS1688 cards need this */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE2));	/* ENABLE2 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE2));	/* ENABLE2 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE2));	/* ENABLE2 */
	inb(ES1688P(codec, ENABLE1));	/* ENABLE1 */
	inb(ES1688P(codec, ENABLE0));	/* ENABLE0 */

	if (snd_es1688_reset(codec) < 0) {
		snd_printdd("ESS: [0x%x] reset failed... 0x%x\n", codec->port, inb(ES1688P(codec, READ)));
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return -ENODEV;
	}
	snd_es1688_dsp_command(codec, 0xe7);	/* return identification */

	for (i = 1000, major = minor = 0; i; i--) {
		if (inb(ES1688P(codec, DATA_AVAIL)) & 0x80) {
			if (major == 0) {
				major = inb(ES1688P(codec, READ));
			} else {
				minor = inb(ES1688P(codec, READ));
			}
		}
	}

	spin_unlock_irqrestore(&codec->reg_lock, flags);

	snd_printdd("ESS: [0x%x] found.. major = 0x%x, minor = 0x%x\n", codec->port, major, minor);

	codec->version = (major << 8) | minor;
	if (!codec->version)
		return -ENODEV;	/* probably SB */

	hw = ES1688_HW_AUTO;
	switch (codec->version & 0xfff0) {
	case 0x4880:
		snd_printk("[0x%x] ESS: AudioDrive ES488 detected, but driver is in another place\n", codec->port);
		return -ENODEV;
	case 0x6880:
		hw = (codec->version & 0x0f) >= 8 ? ES1688_HW_1688 : ES1688_HW_688;
		break;
	default:
		snd_printk("[0x%x] ESS: unknown AudioDrive chip with version 0x%x (Jazz16 soundcard?)\n", codec->port, codec->version);
		return -ENODEV;
	}
	switch (codec->hardware = hw) {
	case ES1688_HW_688:
		str = "ESS AudioDrive ES688";
		break;
	case ES1688_HW_1688:
		str = "ESS AudioDrive ES1688";
		break;
	default:
		str = "???";
	}
	strcpy(pcm->name, str);

	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_write(codec, 0xb1, 0x10);	/* disable IRQ */
	snd_es1688_write(codec, 0xb2, 0x00);	/* disable DMA */
	spin_unlock_irqrestore(&codec->reg_lock, flags);

	/* enable joystick, but disable OPL3 */
	spin_lock_irqsave(&codec->mixer_lock, flags);
	snd_es1688_mixer_write(codec, 0x40, 0x01);
	spin_unlock_irqrestore(&codec->mixer_lock, flags);

	return 0;
}

int snd_es1688_init(snd_pcm_t * pcm, int enable)
{
	static int irqs[16] =
	{-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1};
	es1688_t *codec;
	unsigned long flags;
	int cfg, irq_bits, dma, dma_bits, tmp, tmp1;

	codec = (es1688_t *) pcm->private_data;
	/* ok.. setup MPU-401 port and joystick and OPL3 */
	cfg = 0x01;		/* enable joystick, but disable OPL3 */
	if (enable && codec->mpu_port >= 0x300 && codec->mpu_irq > 0 && codec->hardware != ES1688_HW_688) {
		tmp = (codec->mpu_port & 0x0f0) >> 4;
		if (tmp <= 3) {
			switch (codec->mpu_irq) {
			case 9:
				tmp1 = 4;
				break;
			case 5:
				tmp1 = 5;
				break;
			case 7:
				tmp1 = 6;
				break;
			case 10:
				tmp1 = 7;
				break;
			default:
				tmp1 = 0;
			}
			if (tmp1) {
				cfg |= (tmp << 3) | (tmp1 << 5);
			}
		}
	}
#if 0
	snd_printk("mpu cfg = 0x%x\n", cfg);
#endif
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_mixer_write(codec, 0x40, cfg);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	/* --- */
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_read(codec, 0xb1);
	snd_es1688_read(codec, 0xb2);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	if (enable) {
		cfg = 0xf0;	/* enable only DMA counter interrupt */
		irq_bits = irqs[codec->irq & 0x0f];
		if (irq_bits < 0) {
			snd_printk("[0x%x] ESS: bad IRQ %d for ES1688 chip!!\n", codec->port, codec->irq);
#if 0
			irq_bits = 0;
			cfg = 0x10;
#endif
			return -EINVAL;
		}
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_es1688_write(codec, 0xb1, cfg | (irq_bits << 2));
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		cfg = 0xf0;	/* extended mode DMA enable */
		dma = codec->dma8;
		if (dma > 3 || dma == 2) {
			snd_printk("[0x%x] ESS: bad DMA channel %d for ES1688 chip!!\n", codec->port, dma);
#if 0
			dma_bits = 0;
			cfg = 0x00;	/* disable all DMA */
#endif
			return -EINVAL;
		} else {
			dma_bits = dma;
			if (dma != 3)
				dma_bits++;
		}
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_es1688_write(codec, 0xb2, cfg | (dma_bits << 2));
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	} else {
		spin_lock_irqsave(&codec->reg_lock, flags);
		snd_es1688_write(codec, 0xb1, 0x10);	/* disable IRQ */
		snd_es1688_write(codec, 0xb2, 0x00);	/* disable DMA */
		spin_unlock_irqrestore(&codec->reg_lock, flags);
	}
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_read(codec, 0xb1);
	snd_es1688_read(codec, 0xb2);
	snd_es1688_reset(codec);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return 0;
}

/*

 */

static void snd_es1688_set_rate(es1688_t * codec, int rate)
{
	int divider;
	unsigned char bits;

	if (rate < 4000)
		rate = 4000;
	if (rate > 48000)
		rate = 48000;
	if (rate > 22000) {
		bits = 0x80;
		divider = 256 - (795444 + (rate >> 1)) / rate;
	} else {
		bits = 0x00;
		divider = 128 - (397722 + (rate >> 1)) / rate;
	}
	bits |= divider;
	snd_es1688_write(codec, 0xa1, bits);
	/* set filter register */
	rate = (rate * 9) / 20;
	divider = 256 - 7160000 / (rate * 82);
	snd_es1688_write(codec, 0xa2, divider);
}

static unsigned int snd_es1688_real_rate(unsigned int rate)
{
	unsigned int divider;

	if (rate < 4000)
		rate = 4000;
	if (rate > 44100)
		rate = 44100;
	/* return a possible value */
	if (rate > 22000) {
		divider = 256 - (795444 + (rate >> 1)) / rate;
		rate = 795444400 / (256000 - (divider * 1000));
	} else {
		divider = 128 - (397722 + (rate >> 1)) / rate;
		rate = 397722200 / (128000 - (divider * 1000));
	}
	return rate;
}

static int snd_es1688_playback_ioctl(void *private_data,
				     snd_pcm_subchn_t * subchn,
				     unsigned int cmd, unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = snd_es1688_real_rate(subchn1->rate);
		return 0;
	}

	return -ENXIO;
}

static int snd_es1688_capture_ioctl(void *private_data,
				   snd_pcm_subchn_t * subchn,
				   unsigned int cmd, unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = snd_es1688_real_rate(subchn1->rate);
		return 0;
	}
	return -ENXIO;
}

static void snd_es1688_trigger(es1688_t * codec, unsigned char value)
{
	unsigned long flags;
	int val;

	spin_lock_irqsave(&codec->reg_lock, flags);
	codec->trigger_value = value;
	val = snd_es1688_read(codec, 0xb8);
	if ((val < 0) || (val & 0x0f) == value) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return;		/* something is wrong */
	}
#if 0
	printk("trigger: val = 0x%x, value = 0x%x\n", val, value);
	printk("trigger: residue = 0x%x\n", get_dma_residue(codec->dma8));
#endif
	snd_es1688_write(codec, 0xb8, (val & 0xf0) | value);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_es1688_playback_prepare(void *private_data,
					snd_pcm_subchn_t * subchn,
					unsigned char *buffer,
					unsigned int size,
					unsigned int offset,
					unsigned int count)
{
	unsigned long flags;
	es1688_t *codec = (es1688_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_reset(codec);
	snd_es1688_set_rate(codec, subchn1->rate);
	snd_es1688_write(codec, 0xb8, 4);	/* auto init DMA mode */
	snd_es1688_write(codec, 0xa8, (snd_es1688_read(codec, 0xa8) & ~0x03) | (3 - subchn1->voices));
	snd_es1688_write(codec, 0xb9, 2);	/* demand mode (4 bytes/request) */
	if (subchn1->voices == 1) {
		if (!(subchn1->mode & SND_PCM1_MODE_16)) {	/* 8. bit mono */
			snd_es1688_write(codec, 0xb6, 0x80);
			snd_es1688_write(codec, 0xb7, 0x51);
			snd_es1688_write(codec, 0xb7, 0xd0);
		} else {	/* 16. bit stereo */
			snd_es1688_write(codec, 0xb6, 0x00);
			snd_es1688_write(codec, 0xb7, 0x71);
			snd_es1688_write(codec, 0xb7, 0xf4);
		}
	} else {
		if (!(subchn1->mode & SND_PCM1_MODE_16)) {	/* 8. bit stereo */
			snd_es1688_write(codec, 0xb6, 0x80);
			snd_es1688_write(codec, 0xb7, 0x51);
			snd_es1688_write(codec, 0xb7, 0x98);
		} else {	/* 16. bit stereo */
			snd_es1688_write(codec, 0xb6, 0x00);
			snd_es1688_write(codec, 0xb7, 0x71);
			snd_es1688_write(codec, 0xb7, 0xbc);
		}
	}
	snd_es1688_write(codec, 0xb1, (snd_es1688_read(codec, 0xb1) & 0x0f) | 0x50);
	snd_es1688_write(codec, 0xb2, (snd_es1688_read(codec, 0xb2) & 0x0f) | 0x50);
	snd_es1688_dsp_command(codec, ES1688_DSP_CMD_SPKON);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	/* --- */
	count = -count;
	snd_dma_program(codec->dma8, buffer, size, DMA_MODE_WRITE | DMA_AUTOINIT);
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_write(codec, 0xa4, (unsigned char) count);
	snd_es1688_write(codec, 0xa5, (unsigned char) (count >> 8));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_es1688_playback_trigger(void *private_data,
					snd_pcm_subchn_t * subchn,
					int up)
{
	es1688_t *codec = (es1688_t *) private_data;
	
	snd_es1688_trigger(codec, up ? 0x05 : 0x00);
}

static void snd_es1688_capture_prepare(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned char *buffer,
				      unsigned int size,
				      unsigned int offset,
				      unsigned int count)
{
	unsigned long flags;
	es1688_t *codec = (es1688_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_reset(codec);
	snd_es1688_set_rate(codec, subchn1->rate);
	snd_es1688_dsp_command(codec, ES1688_DSP_CMD_SPKOFF);
	snd_es1688_write(codec, 0xb8, 0x0e);	/* auto init DMA mode */
	snd_es1688_write(codec, 0xa8, (snd_es1688_read(codec, 0xa8) & ~0x03) | (3 - subchn1->voices));
	snd_es1688_write(codec, 0xb9, 2);	/* demand mode (4 bytes/request) */
	if (subchn1->voices == 1) {
		if (!(subchn1->mode & SND_PCM1_MODE_16)) {	/* 8. bit mono */
			snd_es1688_write(codec, 0xb7, 0x51);
			snd_es1688_write(codec, 0xb7, 0xd0);
		} else {	/* 16. bit stereo */
			snd_es1688_write(codec, 0xb7, 0x71);
			snd_es1688_write(codec, 0xb7, 0xf4);
		}
	} else {
		if (!(subchn1->mode & SND_PCM1_MODE_16)) {	/* 8. bit stereo */
			snd_es1688_write(codec, 0xb7, 0x51);
			snd_es1688_write(codec, 0xb7, 0x98);
		} else {	/* 16. bit stereo */
			snd_es1688_write(codec, 0xb7, 0x71);
			snd_es1688_write(codec, 0xb7, 0xbc);
		}
	}
	snd_es1688_write(codec, 0xb1, (snd_es1688_read(codec, 0xb1) & 0x0f) | 0x50);
	snd_es1688_write(codec, 0xb2, (snd_es1688_read(codec, 0xb2) & 0x0f) | 0x50);
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	/* --- */
	count = -count;
	snd_dma_program(codec->dma8, buffer, size, DMA_MODE_READ | DMA_AUTOINIT);
	spin_lock_irqsave(&codec->reg_lock, flags);
	snd_es1688_write(codec, 0xa4, (unsigned char) count);
	snd_es1688_write(codec, 0xa5, (unsigned char) (count >> 8));
	spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_es1688_capture_trigger(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      int up)
{
	es1688_t *codec = (es1688_t *) private_data;
	
	snd_es1688_trigger(codec, up ? 0x0f : 0x00);
}

void snd_es1688_interrupt(snd_pcm_t * pcm)
{
	es1688_t *codec = (es1688_t *) pcm->private_data;

	if (codec->trigger_value == 0x05) {	/* ok.. playback is active */
		if (codec->playback_subchn1->ack)
			codec->playback_subchn1->ack(codec->playback_subchn);
	}
	if (codec->trigger_value == 0x0f) {	/* ok.. capture is active */
		if (codec->capture_subchn1->ack)
			codec->capture_subchn1->ack(codec->capture_subchn);
	}

	inb(ES1688P(codec, DATA_AVAIL));	/* ack interrupt */
}

/*

 */

static int snd_es1688_playback_open(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	es1688_t *codec = (es1688_t *)private_data;
	int err;

	if ((err = snd_pcm1_dma_alloc(subchn, codec->dma8ptr, "ESx688 (playback)")) < 0)
		return err;
	codec->playback_subchn = subchn;
	codec->playback_subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	return 0;
}

static int snd_es1688_capture_open(void *private_data,
				  snd_pcm_subchn_t * subchn)
{
	es1688_t *codec = (es1688_t *)private_data;
	int err;

	if ((err = snd_pcm1_dma_alloc(subchn, codec->dma8ptr, "ESx688 (capture)")) < 0)
		return err;
	codec->capture_subchn = subchn;
	codec->capture_subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	return 0;
}

static void snd_es1688_playback_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	es1688_t *codec = (es1688_t *) private_data;

	codec->playback_subchn = NULL;
	codec->playback_subchn1 = NULL;
	snd_pcm1_dma_free(subchn);
}

static void snd_es1688_capture_close(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	es1688_t *codec = (es1688_t *) private_data;

	codec->capture_subchn = NULL;
	codec->capture_subchn1 = NULL;
	snd_pcm1_dma_free(subchn);
}

static unsigned int snd_es1688_playback_pointer(void *private_data,
						snd_pcm_subchn_t * subchn,
						unsigned int used_size)
{
	es1688_t *codec = (es1688_t *) private_data;
	
	if (codec->trigger_value != 0x05)
		return 0;
	return used_size - snd_dma_residue(codec->dma8);
}

static unsigned int snd_es1688_capture_pointer(void *private_data,
					       snd_pcm_subchn_t * subchn,
					       unsigned int used_size)
{
	es1688_t *codec = (es1688_t *) private_data;
	
	if (codec->trigger_value != 0x0f)
		return 0;
	return used_size - snd_dma_residue(codec->dma8);
}

/*

 */

static void snd_es1688_free(void *private_data);

static struct snd_stru_pcm1_hardware snd_es1688_playback =
{
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_es1688_playback_open,
	snd_es1688_playback_close,
	snd_es1688_playback_ioctl,
	snd_es1688_playback_prepare,
	snd_es1688_playback_trigger,
	snd_es1688_playback_pointer,
	snd_pcm1_playback_dma_ulaw,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_es1688_capture =
{
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	0,			/* align value */
	6,			/* minimal fragment */
	4000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_es1688_capture_open,
	snd_es1688_capture_close,
	snd_es1688_capture_ioctl,
	snd_es1688_capture_prepare,
	snd_es1688_capture_trigger,
	snd_es1688_capture_pointer,
	snd_pcm1_capture_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static void snd_es1688_free(void *private_data)
{
	snd_kfree(private_data);
}

snd_pcm_t *snd_es1688_new_device(snd_card_t * card,
				 unsigned short port,
				 unsigned short mpu_port,
				 snd_irq_t * irqptr,
				 snd_irq_t * mpu_irqptr,
				 snd_dma_t * dma8ptr,
				 unsigned short hardware)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;
	es1688_t *codec;

	pcm = snd_pcm1_new_device(card, "ESx688", 1, 1);
	if (!pcm)
		return NULL;
	codec = (es1688_t *) snd_kcalloc(sizeof(es1688_t), GFP_KERNEL);
	if (!codec)
		return NULL;
	codec->reg_lock = SPIN_LOCK_UNLOCKED;
	codec->mixer_lock = SPIN_LOCK_UNLOCKED;
	codec->pcm = pcm;
	codec->card = pcm->card;
	codec->port = port;
	codec->mpu_port = mpu_port;
	codec->irqptr = irqptr;
	codec->irq = irqptr->irq;
	codec->mpu_irqptr = mpu_irqptr;
	if (mpu_irqptr)
		codec->mpu_irq = mpu_irqptr->irq;
	codec->dma8ptr = dma8ptr;
	codec->dma8 = dma8ptr->dma;
	codec->hardware = hardware;
	/* playback setup */
	pchn1 = (snd_pcm1_channel_t *)pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_es1688_playback, sizeof(snd_es1688_playback));
	pchn1->private_data = codec;
	/* capture setup */
	pchn1 = (snd_pcm1_channel_t *)pcm->capture.private_data;
	memcpy(&pchn1->hw, &snd_es1688_capture, sizeof(snd_es1688_capture));
	pchn1->private_data = codec;
	/* global setup */
	pcm->private_data = codec;
	pcm->private_free = snd_es1688_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
			  SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE;
	sprintf(pcm->name, "ES%s688 rev %i", codec->hardware == ES1688_HW_688 ? "" : "1", codec->version & 0x0f);
	if (snd_es1688_probe(pcm) < 0) {
		snd_pcm_free(pcm);
		return NULL;
	}
	return pcm;
}

/*
 *  MIXER part
 */

static int snd_es1688_mixer_input_mux(void *private_data,
				      int w_flag,
				      snd_kmixer_element_t **element)
{
	es1688_t *codec = (es1688_t *)private_data;
	unsigned long flags;
	unsigned char oval, nval;
	int change = 0;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	oval = codec->rec_src;
	if (w_flag) {
		nval = 0;
		if (*element == codec->me_mux_mic) {
			nval = ES1688_MIXS_MIC;
		} else if (*element == codec->me_mux_line) {
			nval = ES1688_MIXS_LINE;
		} else if (*element == codec->me_mux_cd) {
			nval = ES1688_MIXS_CD;
		} else if (*element == codec->me_mux_mix) {
			nval = ES1688_MIXS_MASTER;
		} else {
			nval = ES1688_MIXS_MUTE;
		}
		change = nval != oval;
		snd_es1688_mixer_write(codec, ES1688_REC_DEV, codec->rec_src = (oval & ~ES1688_MIXS_MASK) | nval);
	} else {
		switch (oval) {
		case ES1688_MIXS_LINE:
			*element = codec->me_mux_line;
			break;
		case ES1688_MIXS_CD:
			*element = codec->me_mux_cd;
			break;
		case ES1688_MIXS_MIC:
			*element = codec->me_mux_mic;
			break;
		case ES1688_MIXS_MASTER:
			*element = codec->me_mux_mix;
			break;
		default:
			*element = NULL;
		}
	}	
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1688_mixer_stereo_volume_level(es1688_t *codec,
						int w_flag,
					        int *voices,
					        unsigned char reg,
					        unsigned char max,
					        unsigned char left_shift,
					        unsigned char right_shift)
{
	unsigned long flags;
	int change = 0;
	unsigned char oval;
	int left, right;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	oval = snd_es1688_mixer_read(codec, reg);
	left = (oval >> left_shift) & max;
	right = (oval >> right_shift) & max;
	if (!w_flag) {
		voices[0] = left;
		voices[1] = right;
	} else {
		change = voices[0] != left && voices[1] != left;
		oval &= ~(max << left_shift);
		oval &= ~(max << right_shift);
		oval |= voices[0] << left_shift;
		oval |= voices[1] << right_shift;
		snd_es1688_mixer_write(codec, reg, oval);
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1688_mixer_mono_volume_level(es1688_t *codec,
					      int w_flag,
					      int *voices,
					      unsigned char reg,
					      unsigned char max,
					      unsigned char shift)
{
	unsigned long flags;
	int change = 0;
	unsigned char oval;
	int val;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	oval = snd_es1688_mixer_read(codec, reg);
	val = (oval >> shift) & max;
	if (!w_flag) {
		voices[0] = val;
	} else {
		change = voices[0] != val;
		oval &= ~(max << shift);
		oval |= voices[0] << shift;
		snd_es1688_mixer_write(codec, reg, oval);
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1688_mixer_master_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
						    ES1688_MASTER_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_pcm_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
						    ES1688_PCM_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_line_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
						    ES1688_LINE_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_cd_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
						    ES1688_CD_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_fm_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
						    ES1688_FM_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_mic_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
					            ES1688_MIC_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_aux_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
					            ES1688_AUX_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_igain_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_stereo_volume_level((es1688_t *)private_data,
						    w_flag, voices,
					            ES1688_RECLEV_DEV, 15, 4, 0);
}

static int snd_es1688_mixer_speaker_volume(void *private_data, int w_flag, int *voices)
{
	return snd_es1688_mixer_mono_volume_level((es1688_t *)private_data,
						  w_flag, voices,
					          ES1688_SPEAKER_DEV, 15, 0);
}

static int snd_es1688_mixer_group_ctrl1(es1688_t *codec,
					snd_kmixer_file_t * file,
					int w_flag,
					snd_mixer_group_t * ugroup,
					snd_mixer_volume1_control_t *volume1,
					snd_kmixer_element_t *volume1_element,
					int max,
					snd_kmixer_element_t *mux_in)
{
	int voices[2];
	snd_kmixer_element_t *element;
	int change = 0;

	if (!w_flag) {
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		volume1(codec, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = max;
		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE |
				        SND_MIXER_GRPCAP_JOINTLY_CAPTURE |
			                SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_es1688_mixer_input_mux(codec, 0, &element);
			ugroup->capture = 0;
			if (element == mux_in)
				ugroup->capture |= SND_MIXER_CHN_MASK_STEREO;
		}
	} else {
		voices[0] = ugroup->volume.names.front_left & max;
		voices[1] = ugroup->volume.names.front_right & max;
		if (volume1(codec, 1, voices) > 0) {
			snd_mixer_element_value_change(file, volume1_element, 0);
			change = 1;
		}
		if (mux_in) {
			snd_es1688_mixer_input_mux(codec, 0, &element);
			if (ugroup->capture & SND_MIXER_CHN_MASK_STEREO)
				element = mux_in;
			if (snd_es1688_mixer_input_mux(codec, 1, &element) > 0) {
				snd_mixer_element_value_change(file, codec->me_mux, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_es1688_mixer_group_ctrl2(es1688_t *codec,
				        snd_kmixer_file_t * file,
				        int w_flag,
				        snd_mixer_group_t * ugroup,
				        snd_mixer_volume1_control_t *volume1,
				        snd_kmixer_element_t *volume1_element,
				        int max)
{
	int voice;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		volume1(codec, 0, &voice);
		ugroup->volume.names.front_left = voice;
		ugroup->min = 0;
		ugroup->max = max;
	} else {
		voice = ugroup->volume.names.front_left & max;
		if (volume1(codec, 1, &voice) > 0) {
			snd_mixer_element_value_change(file, volume1_element, 0);
			change = 1;
		}
	}
	return change;
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_igain_volume,
					    codec->me_vol_igain,
					    15,
					    NULL);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_master_volume,
					    codec->me_vol_master,
					    15,
					    codec->me_mux_mix);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_pcm_volume,
					    codec->me_vol_pcm,
					    15,
					    NULL);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_line_volume,
					    codec->me_vol_line,
					    15,
					    codec->me_mux_line);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_cd_volume,
					    codec->me_vol_cd,
					    15,
					    codec->me_mux_cd);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_fm_volume,
					    codec->me_vol_fm,
					    15,
					    NULL);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_mic_volume,
					    codec->me_vol_mic,
					    15,
					    codec->me_mux_mic);
}

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

	return snd_es1688_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_aux_volume,
					    codec->me_vol_aux,
					    15,
					    NULL);
}

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

	return snd_es1688_mixer_group_ctrl2(codec, file, w_flag, ugroup,
					    snd_es1688_mixer_speaker_volume,
					    codec->me_vol_speaker,
					    15);
}

snd_kmixer_t *snd_es1688_new_mixer(snd_pcm_t * pcm, int pcm_dev)
{
	es1688_t *codec;
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *element;
	static struct snd_mixer_element_volume1_range db_range1[2] = {
		{0, 15, -4600, 0},
		{0, 15, -4600, 0}
	}; 

	if (!pcm || !pcm->card)
		return NULL;
	codec = (es1688_t *) pcm->private_data;
	if (!codec)
 		return NULL;
	mixer = snd_mixer_new(pcm->card, pcm->id);
	if (!mixer)
		return NULL;
	strcpy(mixer->name, pcm->name);
	codec->rec_src = 0x10;
	snd_es1688_mixer_write(codec, ES1688_REC_DEV, codec->rec_src);

	/* build input mux and accumulator */
	if ((codec->me_mux = snd_mixer_lib_mux2(mixer, SND_MIXER_ELEMENT_INPUT_MUX, 0, SND_MIXER_MUX2_NONE, snd_es1688_mixer_input_mux, codec)) == NULL)
		goto __error;
	if ((codec->me_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	codec->me_mux_mix = codec->me_accu;
	/* build gain control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN, 0, SND_MIXER_OSS_IGAIN, snd_es1688_mixer_group_igain, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_igain = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, db_range1, snd_es1688_mixer_igain_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_igain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_mux, codec->me_vol_igain) < 0)
		goto __error;
	/* buid capture endpoint */
	if ((element = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_ADC, 0, SND_MIXER_ETYPE_ADC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_igain, element) < 0)
		goto __error;
 	if ((codec->me_capture = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, &pcm_dev)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_capture) < 0)
		goto __error;
	/* build master volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_es1688_mixer_group_master, codec)) == NULL)
		goto __error;
 	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range1, snd_es1688_mixer_master_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master, element) < 0)
		goto __error;
	/* build PCM volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_es1688_mixer_group_pcm, codec)) == NULL)
		goto __error;
 	if ((codec->me_playback = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
		goto __error;
	if ((element = snd_mixer_lib_converter(mixer, SND_MIXER_ELEMENT_DAC, 0, SND_MIXER_ETYPE_DAC, 16)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_playback, element) < 0)
		goto __error;
	if ((codec->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, db_range1, snd_es1688_mixer_pcm_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_pcm, codec->me_accu) < 0)
		goto __error;
	/* build LINE volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_es1688_mixer_group_line, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
 	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range1, snd_es1688_mixer_line_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_line, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_line, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_line) < 0)
		goto __error;
	codec->me_mux_line = codec->me_vol_line;
	/* build CD volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_es1688_mixer_group_cd, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
 	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range1, snd_es1688_mixer_cd_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_cd) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_cd, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_cd, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_cd) < 0)
		goto __error;
	codec->me_mux_cd = codec->me_vol_cd;
	/* build FM volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_OSS_SYNTH, snd_es1688_mixer_group_fm, codec)) == NULL)
		goto __error;
 	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_fm = snd_mixer_lib_volume1(mixer, "FM Volume", 0, 2, db_range1, snd_es1688_mixer_fm_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_fm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_fm, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_fm, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_fm) < 0)
		goto __error;
	/* build MIC volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_es1688_mixer_group_mic, codec)) == NULL)
		goto __error;
 	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, db_range1, snd_es1688_mixer_mic_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_mic) < 0)
		goto __error;
	codec->me_mux_mic = codec->me_vol_mic;
	/* build AUX volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_OSS_LINE2, snd_es1688_mixer_group_aux, codec)) == NULL)
		goto __error;
 	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
	if ((element = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_aux = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, db_range1, snd_es1688_mixer_aux_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_aux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_aux, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_aux) < 0)
		goto __error;
	/* build speaker volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_OSS_SPEAKER, snd_es1688_mixer_group_speaker, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_mux) < 0)
		goto __error;
 	if ((element = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((codec->me_vol_speaker = snd_mixer_lib_volume1(mixer, "PC Speaker Volume", 0, 1, db_range1, snd_es1688_mixer_speaker_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_speaker) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_speaker, codec->me_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_speaker, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, codec->me_vol_speaker) < 0)
		goto __error;

	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

EXPORT_SYMBOL(snd_es1688_mixer_write);
EXPORT_SYMBOL(snd_es1688_mixer_read);
EXPORT_SYMBOL(snd_es1688_interrupt);
EXPORT_SYMBOL(snd_es1688_new_device);
EXPORT_SYMBOL(snd_es1688_init);
EXPORT_SYMBOL(snd_es1688_new_mixer);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
