/*
 *  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 "driver.h"
#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_pcm1_t * pcm1)
{
	unsigned long flags;
	unsigned short major, minor, hw;
	es1688_t *codec;
	char *str;
	int i;

	/*
	 *  initialization sequence
	 */

	codec = (es1688_t *) pcm1->private_data;
	snd_spin_lock(codec, reg, &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)));
		snd_spin_unlock(codec, reg, &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));
			}
		}
	}

	snd_spin_unlock(codec, reg, &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(pcm1->pcm->name, str);

	snd_spin_lock(codec, reg, &flags);
	snd_es1688_write(codec, 0xb1, 0x10);	/* disable IRQ */
	snd_es1688_write(codec, 0xb2, 0x00);	/* disable DMA */
	snd_spin_unlock(codec, reg, &flags);

	/* enable joystick, but disable OPL3 */
	snd_spin_lock(codec, mixer, &flags);
	snd_es1688_mixer_write(codec, 0x40, 0x01);
	snd_spin_unlock(codec, mixer, &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};
	snd_pcm1_t *pcm1;
	es1688_t *codec;
	unsigned long flags;
	int cfg, irq_bits, dma, dma_bits, tmp, tmp1;

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (es1688_t *) pcm1->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
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_mixer_write(codec, 0x40, cfg);
	snd_spin_unlock(codec, reg, &flags);
	/* --- */
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_read(codec, 0xb1);
	snd_es1688_read(codec, 0xb2);
	snd_spin_unlock(codec, reg, &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;
		}
		snd_spin_lock(codec, reg, &flags);
		snd_es1688_write(codec, 0xb1, cfg | (irq_bits << 2));
		snd_spin_unlock(codec, reg, &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++;
		}
		snd_spin_lock(codec, reg, &flags);
		snd_es1688_write(codec, 0xb2, cfg | (dma_bits << 2));
		snd_spin_unlock(codec, reg, &flags);
	} else {
		snd_spin_lock(codec, reg, &flags);
		snd_es1688_write(codec, 0xb1, 0x10);	/* disable IRQ */
		snd_es1688_write(codec, 0xb2, 0x00);	/* disable DMA */
		snd_spin_unlock(codec, reg, &flags);
	}
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_read(codec, 0xb1);
	snd_es1688_read(codec, 0xb2);
	snd_es1688_reset(codec);
	snd_spin_unlock(codec, reg, &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(snd_pcm1_t * pcm1,
				     unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = snd_es1688_real_rate(pcm1->playback.rate);
		return 0;
	}

	return -ENXIO;
}

static int snd_es1688_record_ioctl(snd_pcm1_t * pcm1,
				   unsigned int cmd, unsigned long *arg)
{
	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->record.real_rate = snd_es1688_real_rate(pcm1->record.rate);
		return 0;
	}
	return -ENXIO;
}

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

	snd_spin_lock(codec, reg, &flags);
	codec->trigger_value = value;
	val = snd_es1688_read(codec, 0xb8);
	if ((val < 0) || (val & 0x0f) == value) {
		snd_spin_unlock(codec, reg, &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);
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_es1688_playback_prepare(snd_pcm1_t * pcm1,
					unsigned char *buffer,
					unsigned int size,
					unsigned int offset,
					unsigned int count)
{
	unsigned long flags;
	es1688_t *codec;
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->playback;
	codec = (es1688_t *) pcm1->private_data;
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_reset(codec);
	snd_es1688_set_rate(codec, pchn->rate);
	snd_es1688_write(codec, 0xb8, 4);	/* auto init DMA mode */
	snd_es1688_write(codec, 0xa8, (snd_es1688_read(codec, 0xa8) & ~0x03) | (3 - pchn->voices));
	snd_es1688_write(codec, 0xb9, 2);	/* demand mode (4 bytes/request) */
	if (pchn->voices == 1) {
		if (!(pchn->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 (!(pchn->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);
	snd_spin_unlock(codec, reg, &flags);
	/* --- */
	count = -count;
	snd_dma_program(codec->dma8, buffer, size, DMA_MODE_WRITE | DMA_MODE_AUTOINIT);
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_write(codec, 0xa4, (unsigned char) count);
	snd_es1688_write(codec, 0xa5, (unsigned char) (count >> 8));
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_es1688_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->private_data;
	snd_es1688_trigger(codec, up ? 0x05 : 0x00);
}

static void snd_es1688_record_prepare(snd_pcm1_t * pcm1,
				      unsigned char *buffer,
				      unsigned int size,
				      unsigned int offset,
				      unsigned int count)
{
	unsigned long flags;
	es1688_t *codec;
	snd_pcm1_channel_t *pchn;

	pchn = &pcm1->record;
	codec = (es1688_t *) pcm1->private_data;
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_reset(codec);
	snd_es1688_set_rate(codec, pchn->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 - pchn->voices));
	snd_es1688_write(codec, 0xb9, 2);	/* demand mode (4 bytes/request) */
	if (pchn->voices == 1) {
		if (!(pchn->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 (!(pchn->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);
	snd_spin_unlock(codec, reg, &flags);
	/* --- */
	count = -count;
	snd_dma_program(codec->dma8, buffer, size, DMA_MODE_READ | DMA_MODE_AUTOINIT);
	snd_spin_lock(codec, reg, &flags);
	snd_es1688_write(codec, 0xa4, (unsigned char) count);
	snd_es1688_write(codec, 0xa5, (unsigned char) (count >> 8));
	snd_spin_unlock(codec, reg, &flags);
}

static void snd_es1688_record_trigger(snd_pcm1_t * pcm1, int up)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->private_data;
	snd_es1688_trigger(codec, up ? 0x0f : 0x00);
}

void snd_es1688_interrupt(snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;
	es1688_t *codec;

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (es1688_t *) pcm1->private_data;
	if (codec->trigger_value == 0x05)	/* ok.. playback is active */
		pcm1->playback.ack(pcm1);
	if (codec->trigger_value == 0x0f)	/* ok.. record is active */
		pcm1->record.ack(pcm1);

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

/*

 */

static int snd_es1688_playback_open(snd_pcm1_t * pcm1)
{
	snd_card_t *card;
	es1688_t *codec;
	int err;

	card = pcm1->card;
	codec = (es1688_t *) pcm1->private_data;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, codec->dma8ptr, "ESx688 (playback)")) < 0)
		return err;
	return 0;
}

static int snd_es1688_record_open(snd_pcm1_t * pcm1)
{
	snd_card_t *card;
	es1688_t *codec;
	int err;

	card = pcm1->card;
	codec = (es1688_t *) pcm1->private_data;
	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, codec->dma8ptr, "ESx688 (record)")) < 0)
		return err;
	return 0;
}

static void snd_es1688_playback_close(snd_pcm1_t * pcm1)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->private_data;
	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma8ptr);
}

static void snd_es1688_record_close(snd_pcm1_t * pcm1)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->private_data;
	snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma8ptr);
}

static unsigned int snd_es1688_playback_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->private_data;
	if (codec->trigger_value != 0x05)
		return 0;
	return used_size - snd_dma_residue(codec->dma8);
}

static unsigned int snd_es1688_record_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	es1688_t *codec;

	codec = (es1688_t *) pcm1->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 =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	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_record =
{
	NULL,			/* private data */
	NULL,			/* private free */
	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_record_open,
	snd_es1688_record_close,
	snd_es1688_record_ioctl,
	snd_es1688_record_prepare,
	snd_es1688_record_trigger,
	snd_es1688_record_pointer,
	snd_pcm1_record_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static void snd_es1688_free(void *private_data)
{
	snd_free(private_data, sizeof(es1688_t));
}

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_t *pcm1;
	es1688_t *codec;

	pcm = snd_pcm1_new_device(card, "ESx688");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (es1688_t *) snd_calloc(sizeof(es1688_t));
	if (!codec)
		return NULL;
	snd_spin_prepare(codec, reg);
	snd_spin_prepare(codec, mixer);
	codec->pcm = pcm;
	codec->card = pcm1->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;
	memcpy(&pcm1->playback.hw, &snd_es1688_playback, sizeof(snd_es1688_playback));
	memcpy(&pcm1->record.hw, &snd_es1688_record, sizeof(snd_es1688_record));
	pcm1->private_data = codec;
	pcm1->private_free = snd_es1688_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
	    SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD;
	sprintf(pcm->name, "ES%s688 rev %i", codec->hardware == ES1688_HW_688 ? "" : "1", codec->version & 0x0f);
	if (snd_es1688_probe(pcm1) < 0) {
		snd_pcm_free(pcm);
		return NULL;
	}
	return pcm;
}

/*
 *  MIXER part
 */

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

	snd_spin_lock(codec, mixer, &flags);
	reg = snd_es1688_mixer_read(codec, ES1688_REC_DEV);
	oval = reg & ES1688_MIXS_MASK;
	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, (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;
		}
	}	
	snd_spin_unlock(codec, mixer, &flags);
	return change;
}

static int snd_es1688_mixer_stereo_volume_level(int w_flag,
					        int *voices,
					        es1688_t *codec,
					        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;

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

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

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

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

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

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

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

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

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

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

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

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

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

	if (!pcm || !pcm->card)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (!pcm1)
		return NULL;
	codec = (es1688_t *) pcm1->private_data;
	if (!codec)
 		return NULL;
	mixer = snd_mixer_new(pcm->card, pcm->id);
	if (!mixer)
		return NULL;
	strcpy(mixer->name, pcm->name);

	/* build input mux and accumulator */
	if ((mux = snd_mixer_lib_mux1(mixer, SND_MIXER_ELEMENT_INPUT_MUX, 0, 0, 2, (snd_mixer_mux1_control_t *)snd_es1688_mixer_input_mux, codec)) == NULL)
		goto __error;
	if ((accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	codec->me_mux_mix = accu;
	/* build gain control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_IGAIN, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_igain_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mux, element2) < 0)
		goto __error;
	/* buid capture endpoint */
 	if ((element1 = 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, element2, element1) < 0)
		goto __error;
	/* build master volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_master_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, accu, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element1) < 0)
		goto __error;
	/* build PCM volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_pcm_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	/* build LINE volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_LINE, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_line_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	codec->me_mux_line = element2;
	/* build CD volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_cd_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	codec->me_mux_cd = element2;
	/* build FM volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_FM, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "FM Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_fm_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	/* build MIC volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_mic_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	codec->me_mux_mic = element2;
	/* build MIC volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_mic_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	codec->me_mux_mic = element2;
	/* build AUX volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_aux_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	/* build speaker volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_SPEAKER, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PC Speaker Volume", 0, 1, db_range1, (snd_mixer_volume1_control_t *)snd_es1688_mixer_speaker_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;

	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_es1688_export;
#endif

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

void cleanup_module(void)
{
}
