/*
 *  Copyright (c) by Abramo Bagnara
 *  <abbagnara@racine.ra.it>
 *  Routines for control of ESS ES18xx 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.
 *
 */

/* GENERAL NOTES:
 *
 * BUGS:
 * - There are pops (we can't delay in trigger function, cause midlevel 
 *   often need to trigger down and then up very quickly).
 *   Any ideas?
 */

/*
 * ES1868  NOTES:
 * - The chip has one half duplex pcm (with very limited full duplex support).
 *
 * - Duplex stereophonic sound is impossible.
 * - Record and playback must share the same frequency rate.
 *
 * - The driver use dma2 for playback and dma1 for capture.
 */

/*
 * ES1869 NOTES:
 *
 * - there are a first full duplex pcm and a second playback only pcm
 *   (incompatible with first pcm capture)
 * 
 * - there is support for the capture volume and ESS Spatializer 3D effect.
 *
 * - contrarily to some pages in DS_1869.PDF the rates can be set
 *   independently.
 *
 * BUGS:
 *
 * - There is a major trouble I noted:
 *
 *   using both channel for playback stereo 16 bit samples at 44100 Hz
 *   the second pcm (Audio1) DMA slows down irregularly and sound is garbled.
 *   
 *   The same happens using Audio1 for captureing.
 *
 *   The Windows driver does not suffer of this (although it use Audio1
 *   only for captureing). I'm unable to discover why.
 *
 *   --- UPDATE ---
 *   This happened when I used DMA 1 for Audio1 and DMA 0 for Audio2.
 *   Using DMA 0 for Audio1 and DMA1 for Audio2 this trouble is magically
 *   disappeared!!!
 *   Now alsa-lib/test/latency gives a 2000 us latency for fragment size 128.
 *   Formerly the underruns was always present for all fragment sizes.
 *
 */

/*
 * ES1878 NOTES:
 * ES1879 NOTES:
 * ES1887 NOTES:
 * ES1888 NOTES:
 *
 * ------- WARNING --------
 * THESE CHIPS ARE NEVER BEEN TESTED.
 * THEIR PnP ID MUST BE ADDED TO audiodrive18xx.c
 * THEIR CAPABILITIES MUST BE FIXED IN snd_es18xx_probe.
 * ------- WARNING --------
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/es18xx.h"


#define DAC1 0x01
#define ADC1 0x02
#define DAC2 0x04
#define MILLISECOND 10000

static int snd_es18xx_dsp_command(es18xx_t * codec, unsigned char val)
{
        int i;

        for(i = MILLISECOND; i; i--)
                if ((inb(codec->port + 0x0C) & 0x80) == 0) {
                        outb(val, codec->port + 0x0C);
                        return 1;
                }
        snd_printk("snd_es18xx_dsp_command: timeout (0x%x)\n", val);
        return 0;
}

static int snd_es18xx_dsp_get_byte(es18xx_t * codec)
{
        int i;

        for(i = MILLISECOND/10; i; i--)
                if (inb(codec->port + 0x0C) & 0x40)
                        return inb(codec->port + 0x0A);
        snd_printk("es18xx get byte failed: 0x%x = 0x%x!!!\n", codec->port + 0x0A, inb(codec->port + 0x0A));
        return -ENODEV;
}

#undef REG_DEBUG

static int snd_es18xx_write(es18xx_t * codec,
			    unsigned char reg, unsigned char data)
{
#ifdef REG_DEBUG
	snd_printk("Reg %02x := %02x\n", reg, data);
#endif
        if (!snd_es18xx_dsp_command(codec, reg))
                return 0;

        return snd_es18xx_dsp_command(codec, data);
}

static int snd_es18xx_read(es18xx_t *codec, unsigned char reg)
{
	int data;
        /* Read a byte from an extended mode register of ES18xx */
        if (!snd_es18xx_dsp_command(codec, 0xC0))
                return -1;
        if (!snd_es18xx_dsp_command(codec, reg))
                return -1;
	data = snd_es18xx_dsp_get_byte(codec);
#ifdef REG_DEBUG
	snd_printk("Reg %02x == %02x\n", reg, data);
#endif
	return data;
}

static int snd_es18xx_bits(es18xx_t *codec, unsigned char reg,
			   unsigned char set, unsigned char unset)
{
        int old = snd_es18xx_read(codec, reg);
	int new;
        if (old < 0)
                return -1;
	new = (old & ~unset) | set;
	if (new != old)
		return snd_es18xx_write(codec, reg, new);
	return 0;
}

inline void snd_es18xx_mixer_write(es18xx_t * codec,
			    unsigned char reg, unsigned char data)
{
#ifdef REG_DEBUG
	snd_printk("Mixer reg %02x set to %02x\n", reg, data);
#endif
        outb(reg, codec->port + 0x04);
        outb(data, codec->port + 0x05);
}

inline unsigned char snd_es18xx_mixer_read(es18xx_t * codec, unsigned char reg)
{
	unsigned char data;
        outb(reg, codec->port + 0x04);
	data = inb(codec->port + 0x05);
#ifdef REG_DEBUG
	snd_printk("Mixer reg %02x now is %02x\n", reg, data);
#endif
        return data;
}

static inline int snd_es18xx_mixer_bits(es18xx_t *codec, unsigned char reg,
					unsigned char set, unsigned char unset)
{
	unsigned char old;
	unsigned char new;
        outb(reg, codec->port + 0x04);
	old = inb(codec->port + 0x05);
	new = (old & ~unset) | set;
	if (new != old)
		outb(new, codec->port + 0x05);
#ifdef REG_DEBUG
	snd_printk("Mixer reg %02x was %02x, set to %02x\n", reg, old, new);
#endif
	return new;
}

static inline int snd_es18xx_mixer_writable(es18xx_t *codec, unsigned char reg,
					    unsigned char mask)
{
	unsigned char old;
	unsigned char expected;
	unsigned char new;
        outb(reg, codec->port + 0x04);
	old = inb(codec->port + 0x05);
	expected = old ^ mask;
	outb(expected, codec->port + 0x05);
	new = inb(codec->port + 0x05);

#ifdef REG_DEBUG
	snd_printk("Mixer reg %02x was %02x, set to %02x, now is %02x\n", reg, old, expected, new);
#endif
	return expected == new;
}


#if 0
static unsigned char snd_es18xx_config_read(es18xx_t *codec, unsigned char reg)
{
	outb(reg, codec->ctrl_port);
	return inb(codec->ctrl_port+1);
}
#endif

static void snd_es18xx_config_write(es18xx_t *codec, 
				    unsigned char reg, unsigned char data)
{
	outb(reg, codec->ctrl_port);
	outb(data, codec->ctrl_port+1);
}

static int snd_es18xx_reset(es18xx_t * codec)
{
	int i;
        outb(0x03, codec->port + 0x06);
        inb(codec->port + 0x06);
        outb(0x00, codec->port + 0x06);
        for(i = 0; i < MILLISECOND && !(inb(codec->port + 0x0E) & 0x80); i++);
        if (inb(codec->port + 0x0A) != 0xAA)
                return -1;
	return 0;
}

static int snd_es18xx_reset_fifo(es18xx_t * codec)
{
        outb(0x02, codec->port + 0x06);
        inb(codec->port + 0x06);
        outb(0x00, codec->port + 0x06);
	return 0;
}

static int snd_es18xx_initialize(es18xx_t * codec)
{
	int mask = 0;
	unsigned long flags;

        spin_lock_irqsave(&codec->reg_lock, flags);

        /* enable extended mode */
        snd_es18xx_dsp_command(codec, 0xC6);
	/* Reset mixer registers */
	snd_es18xx_mixer_write(codec, 0x00, 0x00);

        /* Audio 1 DMA demand mode (4 bytes/request) */
        snd_es18xx_write(codec, 0xB9, 2);
	if (codec->caps & ES18XX_CONTROL) {
		/* Hardware volume IRQ */
		snd_es18xx_config_write(codec, 0x27, codec->irq);
		if (codec->fm_port > SND_AUTO_PORT) {
			/* FM I/O */
			snd_es18xx_config_write(codec, 0x62, codec->fm_port >> 8);
			snd_es18xx_config_write(codec, 0x63, codec->fm_port & 0xff);
		}
		if (codec->mpu_port > SND_AUTO_PORT) {
			/* MPU-401 I/O */
			snd_es18xx_config_write(codec, 0x64, codec->mpu_port >> 8);
			snd_es18xx_config_write(codec, 0x65, codec->mpu_port & 0xff);
			/* MPU-401 IRQ */
			snd_es18xx_config_write(codec, 0x28, codec->irq);
		}
		/* Audio1 IRQ */
		snd_es18xx_config_write(codec, 0x70, codec->irq);
		/* Audio2 IRQ */
		snd_es18xx_config_write(codec, 0x72, codec->irq);
		/* Audio1 DMA */
		snd_es18xx_config_write(codec, 0x74, codec->dma1);
		/* Audio2 DMA */
		snd_es18xx_config_write(codec, 0x75, codec->dma2);

		/* Enable Audio 1 IRQ */
		snd_es18xx_write(codec, 0xB1, 0x50);
		/* Enable Audio 2 IRQ */
		snd_es18xx_mixer_write(codec, 0x7A, 0x40);
		/* Enable Audio 1 DMA */
		snd_es18xx_write(codec, 0xB2, 0x50);
		/* Enable MPU and hardware volume interrupt */
		snd_es18xx_mixer_write(codec, 0x64, 0x42);
	}
	else {
		int irqmask, dma1mask, dma2mask;
		switch (codec->irq) {
		case 2:
		case 9:
			irqmask = 0;
			break;
		case 5:
			irqmask = 1;
			break;
		case 7:
			irqmask = 2;
			break;
		case 10:
			irqmask = 3;
			break;
		default:
			spin_unlock_irqrestore(&codec->reg_lock, flags);
			snd_printk("es18xx: invalid irq %i\n", codec->irq);
			return -ENODEV;
		}
		switch (codec->dma1) {
		case 0:
			dma1mask = 1;
			break;
		case 1:
			dma1mask = 2;
			break;
		case 3:
			dma1mask = 3;
			break;
		default:
			spin_unlock_irqrestore(&codec->reg_lock, flags);
			snd_printk("es18xx: invalid dma1 %i\n", codec->dma1);
			return -ENODEV;
		}
		switch (codec->dma2) {
		case 0:
			dma2mask = 0;
			break;
		case 1:
			dma2mask = 1;
			break;
		case 3:
			dma2mask = 2;
			break;
		case 5:
			dma2mask = 3;
			break;
		default:
			spin_unlock_irqrestore(&codec->reg_lock, flags);
			snd_printk("es18xx: invalid dma2 %i\n", codec->dma2);
			return -ENODEV;
		}

		/* Enable and set Audio 1 IRQ */
		snd_es18xx_write(codec, 0xB1, 0x50 | (irqmask << 2));
		/* Enable and set Audio 1 DMA */
		snd_es18xx_write(codec, 0xB2, 0x50 | (dma1mask << 2));
		/* Set Audio 2 DMA */
		snd_es18xx_mixer_write(codec, 0x7d, 0x04 | dma2mask);
		/* Enable Audio 2 IRQ and DMA
		   Set capture mixer input */
		snd_es18xx_mixer_write(codec, 0x7A, 0x68);
		/* Enable and set hardware volume interrupt */
		snd_es18xx_mixer_write(codec, 0x64, 0x06);
		if (codec->mpu_port > SND_AUTO_PORT) {
			/* MPU401 share irq with audio
			   Joystick enabled
			   FM enabled */
			snd_es18xx_mixer_write(codec, 0x40, 0x43 | (codec->mpu_port & 0xf0) >> 1);
		}
		snd_es18xx_mixer_write(codec, 0x7f, ((irqmask + 1) << 1) | 0x01);
	}
	if (codec->caps & ES18XX_NEW_RATE) {
		/* Change behaviour of register A1
		   4x oversampling
		   2nd channel DAC asynchronous */
		snd_es18xx_mixer_write(codec, 0x71, 0x32);
	}
	if (!(codec->caps & ES18XX_PCM2)) {
		/* Enable DMA FIFO */
		snd_es18xx_write(codec, 0xB7, 0x80);
	}
	if (codec->caps & ES18XX_3D) {
		/* Set 3D parameter to recommended values */
		snd_es18xx_mixer_write(codec, 0x54, 0x8f);
		snd_es18xx_mixer_write(codec, 0x56, 0x95);
		snd_es18xx_mixer_write(codec, 0x58, 0x94);
		snd_es18xx_mixer_write(codec, 0x5a, 0x80);
	}
	/* Mute input source */
	if (codec->caps & ES18XX_MUTEREC)
		mask = 0x10;
	if (codec->caps & ES18XX_RECMIX)
		snd_es18xx_mixer_write(codec, 0x1c, 0x05 | mask);
	else {
		snd_es18xx_mixer_write(codec, 0x1c, 0x00 | mask);
		snd_es18xx_write(codec, 0xb4, 0x00);
	}

        spin_unlock_irqrestore(&codec->reg_lock, flags);

        return 0;
}

static int snd_es18xx_identify(es18xx_t * codec)
{
	int hi,lo;
        unsigned long flags;

        spin_lock_irqsave(&codec->reg_lock, flags);

	/* reset */
	if (snd_es18xx_reset(codec) < 0) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
                snd_printk("snd_es18xx_reset at 0x%x failed!!!\n", codec->port);
		return -ENODEV;
	}

	snd_es18xx_dsp_command(codec, 0xe7);
	hi = snd_es18xx_dsp_get_byte(codec);
	if (hi < 0) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return hi;
	}
	lo = snd_es18xx_dsp_get_byte(codec);
	if ((lo & 0xf0) != 0x80) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return -ENODEV;
	}
	if (hi == 0x48) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		codec->version = 0x488;
		return 0;
	}
	if (hi != 0x68) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return -ENODEV;
	}
	if ((lo & 0x0f) < 8) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		codec->version = 0x688;
		return 0;
	}
			
        outb(0x40, codec->port + 0x04);
	hi = inb(codec->port + 0x05);
	lo = inb(codec->port + 0x05);
	if (hi != lo) {
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		codec->version = hi << 8 | lo;
		codec->ctrl_port = inb(codec->port + 0x05) << 8;
		codec->ctrl_port += inb(codec->port + 0x05);
		return 0;
	}

	/* If has Hardware volume */
	if (snd_es18xx_mixer_writable(codec, 0x64, 0x04)) {
		/* If has Audio2 */
		if (snd_es18xx_mixer_writable(codec, 0x70, 0x7f)) {
			/* If has volume count */
			if (snd_es18xx_mixer_writable(codec, 0x64, 0x20)) {
				codec->version = 0x1887;
			} else {
				codec->version = 0x1888;
			}
		} else {
			codec->version = 0x1788;
		}
	}
	else
		codec->version = 0x1688;
        spin_unlock_irqrestore(&codec->reg_lock, flags);
	return 0;
}

static int snd_es18xx_probe(es18xx_t *codec)
{
	if (snd_es18xx_identify(codec) < 0) {
                snd_printk("[0x%x] ESS chip not found\n", codec->port);
                return -ENODEV;
	}

	switch (codec->version) {
	case 0x1868:
		codec->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL | ES18XX_HWV;
		break;
	case 0x1869:
		codec->caps = ES18XX_PCM2 | ES18XX_3D | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_SPEAKER | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV;
		break;
	case 0x1878:
		codec->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
		break;
	case 0x1879 | ES18XX_HWV:
		codec->caps = ES18XX_PCM2 | ES18XX_3D | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_SPEAKER | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV;
		break;
	case 0x1887:
		codec->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_SPEAKER | ES18XX_DUPLEX_SAME | ES18XX_HWV;
		break;
	case 0x1888:
		codec->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_SPEAKER | ES18XX_DUPLEX_SAME | ES18XX_HWV;
		break;
	default:
                snd_printk("[0x%x] unsupported chip ES%x\n",
                           codec->port, codec->version);
                return -ENODEV;
        }

        snd_printd("[0x%x] ESS%x chip found\n", codec->port, codec->version);

        return snd_es18xx_initialize(codec);
}

static int snd_es18xx_set_rate(es18xx_t *codec, int rate, int mode, int set)
{
        int div0, div1, diff0, diff1, rate0, rate1;
        unsigned char bits;

	if (codec->caps & ES18XX_NEW_RATE) {
		/* let us calculate the best clock choice */
		div0 = (793800 + (rate / 2)) / rate;
		rate0 = 793800 / div0;
		diff0 = (rate0 > rate) ? (rate0 - rate) : (rate - rate0);
		
		div1 = (768000 + (rate / 2)) / rate;
		rate1 = 768000 / div1;
		diff1 = (rate1 > rate) ? (rate1 - rate) : (rate - rate1);
		
		if (diff0 < diff1) {
			bits = 128 - div0;
			rate = rate0;
		}
		else {
			bits = 256 - div1;
			rate = rate1;
		}
	}
	else {
		if (rate > 22000) {
			div0 = (795444 + (rate / 2)) / rate;
			rate = 795444 / div0;
			bits = 256 - div0;

		}
		else {
			div0 = (397722 + (rate / 2)) / rate;
			rate = 397722 / div0;
			bits = 128 - div0;
		}
	}

	if (set) {
		/* set filter register */
		div0 = 256 - 7160000*20/(8*82*rate);
		
		if ((codec->caps & ES18XX_PCM2) && mode == DAC2) {
			snd_es18xx_mixer_write(codec, 0x70, bits);
			snd_es18xx_mixer_write(codec, 0x72, div0);
		}
		else {
			snd_es18xx_write(codec, 0xA1, bits);
			snd_es18xx_write(codec, 0xA2, div0);
		}
	}
	return rate;
}

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

	switch (subchn->number) {
	case 0:
		switch(cmd) {
		case SND_PCM1_IOCTL_RATE:
			subchn1->real_rate = snd_es18xx_set_rate(codec, subchn1->rate, DAC2, 0);
			return 0;
		case SND_PCM1_IOCTL_VOICES:
			if ((codec->caps & ES18XX_DUPLEX_MONO) &&
			    (codec->capture_a_subchn1) &&
			    subchn1->voices != 1)
				return -EBUSY;
			else
				return 0;
		}
		break;
	case 1:
		switch(cmd) {
		case SND_PCM1_IOCTL_RATE:
			subchn1->real_rate = snd_es18xx_set_rate(codec, subchn1->rate, DAC1, 0);
			return 0;
		}
		break;
	}
        return -ENXIO;
}

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

        switch(cmd) {
        case SND_PCM1_IOCTL_RATE:
                subchn1->real_rate = snd_es18xx_set_rate(codec, subchn1->rate, ADC1, 0);
                return 0;
	case SND_PCM1_IOCTL_VOICES:
		if ((codec->caps & ES18XX_DUPLEX_MONO) &&
		    codec->playback_a_subchn &&
		    subchn1->voices != 1)
			return -EBUSY;
		else
			return 0;
        }
        return -ENXIO;
}

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

        spin_lock_irqsave(&codec->mixer_lock, flags);

        snd_es18xx_set_rate(codec, subchn1->rate, DAC2, 1);

        /* Transfer Count Reload */
        count = 0x10000 - count;
        snd_es18xx_mixer_write(codec, 0x74, count & 0xff);
        snd_es18xx_mixer_write(codec, 0x76, count >> 8);

	/* Set format */
        snd_es18xx_mixer_bits(codec, 0x7A, 
			      ((subchn1->voices == 1) ? 0x00 : 0x02) |
			      ((subchn1->mode & SND_PCM1_MODE_16) ? 0x01 : 0x00) |
			      ((subchn1->mode & SND_PCM1_MODE_U) ? 0x00 : 0x04),
			      0x07);


        spin_unlock_irqrestore(&codec->mixer_lock, flags);

        /* Set DMA controller */
        snd_dma_program(codec->dma2, buffer, size, DMA_MODE_WRITE | DMA_AUTOINIT);

#if 0
	snd_printd("Playback settings = (%d,%d,%s,%s)\n",
		pchn->rate, 
		(pchn->mode & SND_PCM1_MODE_16) ? 16 : 8,
		(pchn->voices==1) ? "mono":"stereo",
		(pchn->mode & SND_PCM1_MODE_U)  ? "unsigned":"signed");
#endif
}

static void snd_es18xx_playback1_trigger(es18xx_t *codec,
					 snd_pcm_subchn_t * subchn,
					 int up)
{
        unsigned long flags;

        if (up) {
		if (codec->active & DAC2)
			return;
		codec->active |= DAC2;
	}
	else {
		if (!(codec->active & DAC2))
			return;
		codec->active &= ~DAC2;
	}

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (up) {	
                /* Start DMA */
		if (codec->dma2 == 5)
			snd_es18xx_mixer_write(codec, 0x78, 0xb3);
		else
			snd_es18xx_mixer_write(codec, 0x78, 0x93);
#if 0
		/* Avoid pops */
                snd_delay(88200/pcm1->playback.real_rate);
#endif
#if 0
		if (codec->caps & ES18XX_PCM2)
			/* Restore Audio 2 volume */
			snd_es18xx_mixer_write(codec, 0x7C, codec->audio2_vol);
		else
#endif
			/* Enable PCM output */
			snd_es18xx_dsp_command(codec, 0xD1);
        }
        else {
                /* Stop DMA */
                snd_es18xx_mixer_write(codec, 0x78, 0x00);
#if 0
                snd_delay(3);
#endif
#if 0
		if (codec->caps & ES18XX_PCM2)
			/* Set Audio 2 volume to 0 */
			snd_es18xx_mixer_write(codec, 0x7C, 0);
		else
#endif
			/* Disable PCM output */
			snd_es18xx_dsp_command(codec, 0xD3);
        }

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

static void snd_es18xx_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;
        es18xx_t *codec = (es18xx_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

        spin_lock_irqsave(&codec->reg_lock, flags);

	snd_es18xx_reset_fifo(codec);

        /* Set stereo/mono */
        snd_es18xx_bits(codec, 0xA8, subchn1->voices == 1 ? 0x02 : 0x01, 0x0b);

        snd_es18xx_set_rate(codec, subchn1->rate, ADC1, 1);

        /* Transfer Count Reload */
	count = 0x10000 - count;
	snd_es18xx_write(codec, 0xA4, count & 0xff);
	snd_es18xx_write(codec, 0xA5, count >> 8);

#if 0
	/* Delay 10 milliseconds to allow the analog circuits to settle */
	snd_delay(100);
#endif

        /* Set format */
        snd_es18xx_write(codec, 0xB7, 
                         (subchn1->mode & SND_PCM1_MODE_U) ? 0x51 : 0x71);
        snd_es18xx_write(codec, 0xB7, 0x90 |
                         ((subchn1->voices == 1) ? 0x40 : 0x08) |
                         ((subchn1->mode & SND_PCM1_MODE_16) ? 0x04 : 0x00) |
                         ((subchn1->mode & SND_PCM1_MODE_U) ? 0x00 : 0x20));

        spin_unlock_irqrestore(&codec->reg_lock, flags);

        /* Set DMA controler */
        snd_dma_program(codec->dma1, buffer, size, DMA_MODE_READ | DMA_AUTOINIT);

#if 0
	snd_printd("Record settings = (%d,%d,%s,%s)\n",
			pchn->rate, (pchn->mode & SND_PCM1_MODE_16) ? 16 : 8,
			(pchn->voices==1) ? "mono":"stereo",
			(pchn->mode & SND_PCM1_MODE_U)  ? "unsigned":"signed");
#endif
}

static void snd_es18xx_capture_trigger(void *private_data,
				       snd_pcm_subchn_t *subchn,
				       int up)
{
        es18xx_t *codec = (es18xx_t *) private_data;
        unsigned long flags;

        if (up) {
		if (codec->active & ADC1)
			return;
		codec->active |= ADC1;
	}
        else {
		if (!(codec->active & ADC1))
			return;
		codec->active &= ~ADC1;
	}

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (up)
                /* Start DMA */
                snd_es18xx_write(codec, 0xB8, 0x0f);
        else
                /* Stop DMA */
                snd_es18xx_write(codec, 0xB8, 0x00);
        spin_unlock_irqrestore(&codec->reg_lock, flags);
}

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

        spin_lock_irqsave(&codec->reg_lock, flags);

	snd_es18xx_reset_fifo(codec);

        /* Set stereo/mono */
        snd_es18xx_bits(codec, 0xA8, subchn1->voices == 1 ? 0x02 : 0x01, 0x03);

        snd_es18xx_set_rate(codec, subchn1->rate, DAC1, 1);

        /* Transfer Count Reload */
	count = 0x10000 - count;
	snd_es18xx_write(codec, 0xA4, count & 0xff);
	snd_es18xx_write(codec, 0xA5, count >> 8);

        /* Set format */
        snd_es18xx_write(codec, 0xB6,
                         (subchn1->mode & SND_PCM1_MODE_U) ? 0x80 : 0x00);
        snd_es18xx_write(codec, 0xB7, 
                         (subchn1->mode & SND_PCM1_MODE_U) ? 0x51 : 0x71);
        snd_es18xx_write(codec, 0xB7, 0x90 |
                         ((subchn1->voices == 1) ? 0x40 : 0x08) |
                         ((subchn1->mode & SND_PCM1_MODE_16) ? 0x04 : 0x00) |
                         ((subchn1->mode & SND_PCM1_MODE_U) ? 0x00 : 0x20));


        spin_unlock_irqrestore(&codec->reg_lock, flags);

        /* Set DMA controler */
        snd_dma_program(codec->dma1, buffer, size, DMA_MODE_WRITE | DMA_AUTOINIT);

#if 0
	snd_printd("Playback2 settings = (%d,%d,%s,%s)\n",
		pchn->rate, 
		(pchn->mode & SND_PCM1_MODE_16) ? 16 : 8,
		(pchn->voices==1) ? "mono":"stereo",
		(pchn->mode & SND_PCM1_MODE_U)  ? "unsigned":"signed");
#endif
}

static void snd_es18xx_playback2_trigger(es18xx_t *codec,
					 snd_pcm_subchn_t *subchn,
					 int up)
{
        unsigned long flags;

        if (up) {
		if (codec->active & DAC1)
			return;
		codec->active |= DAC1;
        }
        else {
		if (!(codec->active & DAC1))
			return;
		codec->active &= ~DAC1;
	}

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (up) {
                /* Start DMA */
                snd_es18xx_write(codec, 0xB8, 0x05);
#if 0
		/* Avoid pops */
                snd_delay(88200/pcm1->playback.real_rate);
#endif
                /* Enable Audio 1 */
                snd_es18xx_dsp_command(codec, 0xD1);
        }
        else {
                /* Stop DMA */
                snd_es18xx_write(codec, 0xB8, 0x00);
#if 0
                snd_delay(3);
#endif
                /* Disable Audio 1 */
                snd_es18xx_dsp_command(codec, 0xD3);
        }
        spin_unlock_irqrestore(&codec->reg_lock, flags);
}

static void snd_es18xx_playback_prepare(void *private_data,
					 snd_pcm_subchn_t *subchn,
                                         unsigned char *buffer,
                                         unsigned int size,
                                         unsigned int offset,
                                         unsigned int count)
{
        es18xx_t *codec = (es18xx_t *) private_data;
	switch (subchn->number) {
	case 0:
		snd_es18xx_playback1_prepare(codec, subchn, buffer, size, offset, count);
		break;
	case 1:
		snd_es18xx_playback2_prepare(codec, subchn, buffer, size, offset, count);
		break;
	}
}

static void snd_es18xx_playback_trigger(void *private_data,
					snd_pcm_subchn_t *subchn,
					int up)
{
        es18xx_t *codec = (es18xx_t *) private_data;
	switch (subchn->number) {
	case 0:
		snd_es18xx_playback1_trigger(codec, subchn, up);
		break;
	case 1:
		snd_es18xx_playback2_trigger(codec, subchn, up);
		break;
	}
}

void snd_es18xx_interrupt(es18xx_t *codec, unsigned char status)
{
        /* ok.. playback is active */
        if (status & AUDIO2_IRQ) {
                if (codec->active & DAC2) {
                	if (codec->playback_a_subchn1->ack)
                		codec->playback_a_subchn1->ack(codec->playback_a_subchn);
                }
		/* ack interrupt */
                snd_es18xx_mixer_bits(codec, 0x7A, 0x00, 0x80);
        }

        if (status & AUDIO1_IRQ) {
                /* ok.. capture is active */
                if (codec->active & ADC1) {
                	if (codec->capture_a_subchn1->ack)
                		codec->capture_a_subchn1->ack(codec->capture_a_subchn);
                }
                /* ok.. playback2 is active */
                else if (codec->active & DAC1) {
                	if (codec->playback_b_subchn1->ack)
                		codec->playback_b_subchn1->ack(codec->playback_b_subchn);
                }
		/* ack interrupt */
		inb(codec->port + 0x0E);
        }

}

static int snd_es18xx_playback_open(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
        es18xx_t *codec = (es18xx_t *) private_data;
        int err;

	switch (subchn->number) {
	case 0:
		if ((codec->caps & ES18XX_DUPLEX_MONO) &&
		    codec->capture_a_subchn1 && 
		    codec->capture_a_subchn1->voices != 1)
			return -EBUSY;
		if ((err = snd_pcm1_dma_alloc(subchn, codec->dma2ptr, "ES18xx (playback)")) < 0)
			return err;
		codec->playback_a_subchn = subchn;
		codec->playback_a_subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
		break;
	case 1:
		if (codec->capture_a_subchn)
			return -EBUSY;
		if ((err = snd_pcm1_dma_alloc(subchn, codec->dma1ptr, "ES18xx (playback2)")) < 0)
			return err;
		codec->playback_b_subchn = subchn;
		codec->playback_b_subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
		break;
	}
        return 0;
}

static int snd_es18xx_capture_open(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
        es18xx_t *codec = (es18xx_t *) private_data;
        int err;

        if (codec->playback_b_subchn)
                return -EBUSY;
	if ((codec->caps & ES18XX_DUPLEX_MONO) &&
	    codec->playback_a_subchn1 &&
	    codec->playback_a_subchn1->voices != 1)
		return -EBUSY;
        if ((err = snd_pcm1_dma_alloc(subchn, codec->dma1ptr, "ES18xx (capture)")) < 0)
                return err;
        codec->capture_a_subchn = subchn;
        codec->capture_a_subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
        return 0;
}

static void snd_es18xx_playback_close(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
        es18xx_t *codec = (es18xx_t *) private_data;

	switch (subchn->number) {
	case 0:
		codec->playback_a_subchn = NULL;
		codec->playback_a_subchn1 = NULL;
		snd_pcm1_dma_free(subchn);
		break;
	case 1:
		codec->playback_b_subchn = NULL;
		codec->playback_b_subchn1 = NULL;
		snd_pcm1_dma_free(subchn);
		break;
	}

}

static void snd_es18xx_capture_close(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
        es18xx_t *codec = (es18xx_t *) private_data;

        codec->capture_a_subchn = NULL;
        codec->capture_a_subchn1 = NULL;
        snd_pcm1_dma_free(subchn);
}

static unsigned int snd_es18xx_playback_pointer(void *private_data,
						snd_pcm_subchn_t * subchn,
						unsigned int used_size)
{
        es18xx_t *codec = (es18xx_t *) private_data;

	switch (subchn->number) {
	case 0:
		if (!(codec->active & DAC2))
			return 0;
		return used_size - snd_dma_residue(codec->dma2);
	case 1:
		if (!(codec->active & DAC1))
			return 0;
		return used_size - snd_dma_residue(codec->dma1);
	}
	return 0;
}

static unsigned int snd_es18xx_capture_pointer(void *private_data,
					       snd_pcm_subchn_t * subchn,
					       unsigned int used_size)
{
        es18xx_t *codec = (es18xx_t *) private_data;

        if (!(codec->active & ADC1))
                return 0;
        return used_size - snd_dma_residue(codec->dma1);
}

static struct snd_stru_pcm1_hardware snd_es18xx_playback =
{
        SND_PCM1_HW_AUTODMA,	/* flags */
        SND_PCM_FMT_MU_LAW |
        SND_PCM_FMT_U8 | SND_PCM_FMT_S8 | 
        SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* formats */
        SND_PCM_FMT_U8 | SND_PCM_FMT_S8 | 
        SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* hardware formats */
        0,			/* align value */
        6,			/* minimal fragment */
        4000,			/* min. rate */
        48000,			/* max. rate */
        2,			/* max. voices */
        snd_es18xx_playback_open,
        snd_es18xx_playback_close,
        snd_es18xx_playback_ioctl,
        snd_es18xx_playback_prepare,
        snd_es18xx_playback_trigger,
        snd_es18xx_playback_pointer,
        snd_pcm1_playback_dma_ulaw,
        snd_pcm1_dma_move,
        snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_es18xx_capture =
{
        SND_PCM1_HW_AUTODMA,	/* flags */
        SND_PCM_FMT_MU_LAW |
        SND_PCM_FMT_U8 | SND_PCM_FMT_S8 | 
        SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* formats */
        SND_PCM_FMT_U8 | SND_PCM_FMT_S8 | 
        SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,	/* hardware formats */
        0,			/* align value */
        6,			/* minimal fragment */
        4000,			/* min. rate */
        48000,			/* max. rate */
        2,			/* max. voices */
        snd_es18xx_capture_open,
        snd_es18xx_capture_close,
        snd_es18xx_capture_ioctl,
        snd_es18xx_capture_prepare,
        snd_es18xx_capture_trigger,
        snd_es18xx_capture_pointer,
        snd_pcm1_capture_dma_ulaw,
        snd_pcm1_dma_move,
        NULL
};

static void snd_es18xx_free(void *private_data)
{
}

snd_pcm_t *snd_es18xx_pcm(es18xx_t * codec)
{
        snd_pcm_t *pcm;
        snd_pcm1_channel_t *pchn1;
	char str[16];

	sprintf(str, "ES%x", codec->version);
	if (codec->caps & ES18XX_PCM2) {
		pcm = snd_pcm1_new_device(codec->card, str, 2, 1);
	} else {
		pcm = snd_pcm1_new_device(codec->card, str, 1, 1);
	}
        if (!pcm)
                return NULL;
        /* playback setup */
        pchn1 = (snd_pcm1_channel_t *)pcm->playback.private_data;
        memcpy(&pchn1->hw, &snd_es18xx_playback, sizeof(snd_es18xx_playback));
	pchn1->private_data = codec;
	/* capture setup */
        pchn1 = (snd_pcm1_channel_t *)pcm->capture.private_data;
        memcpy(&pchn1->hw, &snd_es18xx_capture, sizeof(snd_es18xx_capture));
	pchn1->private_data = codec;
	/* global setup */
        pcm->private_data = codec;
        pcm->private_free = snd_es18xx_free;
        pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
			  SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	if (codec->caps & ES18XX_DUPLEX_SAME)
		pcm->info_flags |= SND_PCM_INFO_DUPLEX_LIMIT;
	if (codec->caps & ES18XX_DUPLEX_MONO)
		pcm->info_flags |= SND_PCM_INFO_DUPLEX_MONO;
	sprintf(pcm->name, "ESS AudioDrive ES%x", codec->version);
        codec->pcm = pcm;
	return pcm;
}

es18xx_t *snd_es18xx_new_device(snd_card_t * card,
                                unsigned short port,
                                unsigned short mpu_port,
                                unsigned short fm_port,
                                snd_irq_t * irqptr,
                                snd_dma_t * dma1ptr,
                                snd_dma_t * dma2ptr)
{
        es18xx_t *codec;

        codec = (es18xx_t *) snd_kcalloc(sizeof(es18xx_t), GFP_KERNEL);
        if (!codec)
                return NULL;
	codec->reg_lock = SPIN_LOCK_UNLOCKED;
        codec->mixer_lock = SPIN_LOCK_UNLOCKED;
        codec->ctrl_lock = SPIN_LOCK_UNLOCKED;
        codec->card = card;
        codec->port = port;
        codec->mpu_port = mpu_port;
        codec->fm_port = fm_port;
        codec->irqptr = irqptr;
        codec->irq = irqptr->irq;
        codec->dma1ptr = dma1ptr;
        codec->dma1 = dma1ptr->dma;
        codec->dma2ptr = dma2ptr;
        codec->dma2 = dma2ptr->dma;
        codec->audio2_vol = 0x00;
	codec->active = 0;

        if (snd_es18xx_probe(codec) < 0) {
                snd_kfree(codec);
                return NULL;
        }
        return codec;
}

static int snd_es18xx_mixer_volume(es18xx_t *codec, int w_flag, int *voices, int reg)
{
        unsigned long flags;
        int change = 0;
        unsigned char oval, nval;

        spin_lock_irqsave(&codec->mixer_lock, flags);
	if (reg >= 0xa0) {
		oval = snd_es18xx_read(codec, reg);
	} else {
		oval = snd_es18xx_mixer_read(codec, reg);
	}
        if (!w_flag) {
                voices[0] = (oval >> 4) & 0x0f;
                voices[1] = oval & 0x0f;
        } else {
                nval = voices[0] << 4 | voices[1];
                if ((change = (oval != nval))) {
			if (reg >= 0xa0) {
				snd_es18xx_write(codec, reg, nval);
			} else {
				snd_es18xx_mixer_write(codec, reg, nval);
			}
		}
        }
        spin_unlock_irqrestore(&codec->mixer_lock, flags);
        return change;
}

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

	spin_lock_irqsave(&codec->mixer_lock, flags);
	reg = snd_es18xx_mixer_read(codec, 0x1c);
	oval = reg & 0x1f;
	if (w_flag) {
		if (*element == codec->mix_mic) {
			nval = 0x00;
		} else if (*element == codec->mix_line) {
			nval = 0x06;
		} else if (*element == codec->mix_cd) {
			nval = 0x02;
		} else if (*element == codec->mix_iaccu) {
			nval = 0x05;
		} else if (*element == codec->mix_oaccu) {
			nval = 0x07;
		} else {
			nval = 0x10;
		}
		if ((change = (nval != oval)))
			snd_es18xx_mixer_write(codec, 0x1c, (reg & ~0x1f) | nval);
	} else {
		switch (oval) {
		case 0x00:
			*element = codec->mix_mic;
			break;
		case 0x06:
			*element = codec->mix_line;
			break;
		case 0x02:
			*element = codec->mix_cd;
			break;
		case 0x05:
			*element = codec->mix_iaccu;
			break;
		case 0x07:
			*element = codec->mix_oaccu;
			break;
		default:
			*element = NULL;
		}
	}	
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

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

	spin_lock_irqsave(&codec->mixer_lock, flags);
	reg = snd_es18xx_read(codec, 0xa8);
	oval = reg & 0x08;
	if (w_flag) {
		if (*element == codec->mix_oaccu) {
			nval = 0x00;
		} else if (*element == codec->mix_igain_v) {
			nval = 0x08;
		} else {
			nval = 0x00;
		}
		if ((change = (nval != oval)))
			snd_es18xx_bits(codec, 0xa8, nval, 0x08);
	} else {
		switch (oval) {
		case 0x00:
			*element = codec->mix_oaccu;
			break;
		case 0x08:
			*element = codec->mix_igain_v;
			break;
		default:
			*element = NULL;
		}
	}	
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}


static int snd_es18xx_mixer_opcm1_v(void *private_data, int w_flag, int *voices)
{
	if (((es18xx_t *)private_data)->caps & ES18XX_PCM2)
		return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x7c);
	else
		return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x14);
}

static int snd_es18xx_mixer_opcm2_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x14);
}

static int snd_es18xx_mixer_omic_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x1a);
}

static int snd_es18xx_mixer_oline_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x3e);
}

static int snd_es18xx_mixer_ofm_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x36);
}

static int snd_es18xx_mixer_omono_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6d);
}

static int snd_es18xx_mixer_ocd_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x38);
}

static int snd_es18xx_mixer_oaux_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x3a);
}

static int snd_es18xx_mixer_ipcm1_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x69);
}

static int snd_es18xx_mixer_imic_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x68);
}

static int snd_es18xx_mixer_iline_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6e);
}

static int snd_es18xx_mixer_ifm_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6b);
}

static int snd_es18xx_mixer_imono_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6f);
}

static int snd_es18xx_mixer_icd_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6a);
}

static int snd_es18xx_mixer_iaux_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0x6c);
}

static int snd_es18xx_mixer_igain_v(void *private_data, int w_flag, int *voices)
{
	return snd_es18xx_mixer_volume((es18xx_t *)private_data, w_flag, voices, 0xb4);
}

static int snd_es18xx_mixer_output_v(void *private_data, int w_flag, int *voices)
{
	es18xx_t *codec = (es18xx_t *)private_data;
        unsigned long flags;
        int change = 0;
        unsigned char oleft, oright;

        spin_lock_irqsave(&codec->mixer_lock, flags);
        oleft = snd_es18xx_mixer_read(codec, 0x60) & 0x3f;
        oright = snd_es18xx_mixer_read(codec, 0x62) & 0x3f;
        if (!w_flag) {
                voices[0] = oleft;
                voices[1] = oright;
        } else {
		if (oleft != voices[0]) {
			snd_es18xx_mixer_bits(codec, 0x60, voices[0], 0x3f);
			change = 1;
		}
		if (oright != voices[1]) {
			snd_es18xx_mixer_bits(codec, 0x62, voices[1], 0x3f);
			change = 1;
		}
        }
        spin_unlock_irqrestore(&codec->mixer_lock, flags);
        return change;
}

static int snd_es18xx_mixer_output_s(void *private_data, int w_flag, unsigned int *bitmap)
{
	es18xx_t *codec = (es18xx_t *)private_data;
        unsigned long flags;
        int change = 0;
	int oleft, oright;
        spin_lock_irqsave(&codec->mixer_lock, flags);
	oleft = !(snd_es18xx_mixer_read(codec, 0x60) & 0x40);
	oright = !(snd_es18xx_mixer_read(codec, 0x62) & 0x40);
	
        if (!w_flag) {
                snd_mixer_set_bit(bitmap, 0, oleft);
                snd_mixer_set_bit(bitmap, 1, oright);
        } else {
		int nleft, nright;
		nleft = snd_mixer_get_bit(bitmap, 0);
		nright = snd_mixer_get_bit(bitmap, 1);
		if (oleft != nleft) {
			snd_es18xx_mixer_bits(codec, 0x60, nleft ? 0x00 : 0x40, 0x40);
			change = 1;
		}
		if (oright != nright) {
			snd_es18xx_mixer_bits(codec, 0x62, nright ? 0x00 : 0x40, 0x40);
			change = 1;
		}
		
	}
        spin_unlock_irqrestore(&codec->mixer_lock, flags);
        return change;
}

static int snd_es18xx_mixer_e3d(void *private_data, int w_flag, struct snd_mixer_element_3d_effect1 *effect1)
{
	es18xx_t *codec = (es18xx_t *)private_data;
        unsigned long flags;
        int change = 0;
        unsigned int osw, oval;
	
        spin_lock_irqsave(&codec->mixer_lock, flags);
        osw = !!(snd_es18xx_mixer_read(codec, 0x50) & 0x0c);
        oval = snd_es18xx_mixer_read(codec, 0x52) & 0x3f;
	if (!w_flag) {
		effect1->sw = osw;
		effect1->space = oval;
	} else {
		if (osw != effect1->sw) {
			snd_es18xx_mixer_bits(codec, 0x50, effect1->sw ? 0x0c : 0x00, 0x0c);
			change = 1;
		}
		if (oval != effect1->space) {
			snd_es18xx_mixer_write(codec, 0x52, effect1->space);
			change = 1;
		}
	}

        spin_unlock_irqrestore(&codec->mixer_lock, flags);
        return change;
}


static int snd_es18xx_mixer_group_ctrl(es18xx_t *codec,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup,
				       snd_kmixer_element_t *oel_v,
				       snd_mixer_volume1_control_t *octrl_v,
				       snd_kmixer_element_t *oel_s,
				       snd_mixer_sw1_control_t *octrl_s,
				       snd_kmixer_element_t *iel_v,
				       snd_mixer_volume1_control_t *ictrl_v,
				       int max,
				       snd_kmixer_element_t *mux_in)
{
	int voices[2];
	snd_kmixer_element_t *element;
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		octrl_v(codec, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = max;
		if (oel_s) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			octrl_s(codec, 0, &bitmap);
			ugroup->mute = 0;
			if(!snd_mixer_get_bit(&bitmap, 0))
                                ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
                        if(!snd_mixer_get_bit(&bitmap, 1))
                                ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE |
				        SND_MIXER_GRPCAP_JOINTLY_CAPTURE |
			                SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_es18xx_mixer_imux(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 (octrl_v(codec, 1, voices) > 0) {
			snd_mixer_element_value_change(file, oel_v, 0);
			change = 1;
		}
		if (oel_s) {
			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 (octrl_s(codec, 1, &bitmap) > 0) {
                                snd_mixer_element_value_change(file, oel_s, 0);
                                change = 1;
                        }
                }

		if (iel_v && ictrl_v && ictrl_v(codec, 1, voices) > 0) {
			snd_mixer_element_value_change(file, iel_v, 0);
			change = 1;
		}
		if (mux_in) {
			snd_es18xx_mixer_imux(codec, 0, &element);
			if (ugroup->capture & SND_MIXER_CHN_MASK_STEREO)
				element = mux_in;
			if (snd_es18xx_mixer_imux(codec, 1, &element) > 0) {
				snd_mixer_element_value_change(file, codec->mix_imux, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_es18xx_mixer_igain_g(void *private_data,
				    snd_kmixer_file_t *file,
				    int w_flag,
				    snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_igain_v,
					   snd_es18xx_mixer_igain_v,
					   NULL, NULL,
					   NULL, NULL,
					   15,
					   codec->mix_iaccu);
}

static int snd_es18xx_mixer_pcm1_g(void *private_data,
				  snd_kmixer_file_t *file,
				  int w_flag,
				  snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_opcm1_v,
					   snd_es18xx_mixer_opcm1_v,
					   NULL, NULL,
					   codec->mix_ipcm1_v,
					   snd_es18xx_mixer_ipcm1_v,
					   15,
					   NULL);
}

static int snd_es18xx_mixer_pcm2_g(void *private_data,
				  snd_kmixer_file_t *file,
				  int w_flag,
				  snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_opcm2_v,
					   snd_es18xx_mixer_opcm2_v,
					   NULL, NULL,
					   NULL, NULL,
					   15,
					   NULL);
}

static int snd_es18xx_mixer_mic_g(void *private_data,
				  snd_kmixer_file_t *file,
				  int w_flag,
				  snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_omic_v,
					   snd_es18xx_mixer_omic_v,
					   NULL, NULL,
					   codec->mix_imic_v,
					   snd_es18xx_mixer_imic_v,
					   15,
					   codec->mix_mic);
}

static int snd_es18xx_mixer_line_g(void *private_data,
				   snd_kmixer_file_t *file,
				   int w_flag,
				   snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_oline_v,
					   snd_es18xx_mixer_oline_v,
					   NULL, NULL,
					   codec->mix_iline_v,
					   snd_es18xx_mixer_iline_v,
					   15,
					   codec->mix_line);
}

static int snd_es18xx_mixer_fm_g(void *private_data,
				 snd_kmixer_file_t *file,
				 int w_flag,
				 snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_ofm_v,
					   snd_es18xx_mixer_ofm_v,
					   NULL, NULL,
					   codec->mix_ifm_v,
					   snd_es18xx_mixer_ifm_v,
					   15,
					   NULL);
}

static int snd_es18xx_mixer_mono_g(void *private_data,
				   snd_kmixer_file_t *file,
				   int w_flag,
				   snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_omono_v,
					   snd_es18xx_mixer_omono_v,
					   NULL, NULL,
					   codec->mix_imono_v,
					   snd_es18xx_mixer_imono_v,
					   15,
					   NULL);
}

static int snd_es18xx_mixer_cd_g(void *private_data,
				 snd_kmixer_file_t *file,
				 int w_flag,
				 snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_ocd_v,
					   snd_es18xx_mixer_ocd_v,
					   NULL, NULL,
					   codec->mix_icd_v,
					   snd_es18xx_mixer_icd_v,
					   15,
					   codec->mix_cd);
}

static int snd_es18xx_mixer_aux_g(void *private_data,
				  snd_kmixer_file_t *file,
				  int w_flag,
				  snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_oaux_v,
					   snd_es18xx_mixer_oaux_v,
					   NULL, NULL,
					   codec->mix_iaux_v,
					   snd_es18xx_mixer_iaux_v,
					   15,
					   NULL);
}

static int snd_es18xx_mixer_output_g(void *private_data,
				     snd_kmixer_file_t *file,
				     int w_flag,
				     snd_mixer_group_t *ugroup)
{
	es18xx_t *codec = (es18xx_t *) private_data;
	return snd_es18xx_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_output_v,
					   snd_es18xx_mixer_output_v,
					   codec->mix_output_s,
					   snd_es18xx_mixer_output_s,
					   NULL, NULL,
					   63,
					   codec->mix_oaccu);
}


snd_kmixer_t *snd_es18xx_mixer(es18xx_t *codec)
{
        snd_kmixer_t *mixer;
	snd_kmixer_group_t *pcm1_g, *pcm2_g;
	snd_kmixer_group_t *mic_g;
	snd_kmixer_group_t *line_g;
	snd_kmixer_group_t *fm_g;
	snd_kmixer_group_t *mono_g;
	snd_kmixer_group_t *cd_g;
	snd_kmixer_group_t *aux_g;
	snd_kmixer_group_t *output_g;
	snd_kmixer_group_t *igain_g;
	snd_kmixer_group_t *e3d_g;
	
	snd_kmixer_element_t *pcm1, *ipcm1_v, *opcm1_v;
	snd_kmixer_element_t *pcm2, *opcm2_v;
	snd_kmixer_element_t *mic, *imic_v, *omic_v;
	snd_kmixer_element_t *line, *iline_v, *oline_v;
	snd_kmixer_element_t *fm, *ifm_v, *ofm_v;
	snd_kmixer_element_t *mono, *imono_v, *omono_v;
	snd_kmixer_element_t *cd, *icd_v, *ocd_v;
	snd_kmixer_element_t *aux, *iaux_v, *oaux_v;
	snd_kmixer_element_t *oaccu, *iaccu;
	snd_kmixer_element_t *imux, *igain_v;
	snd_kmixer_element_t *output_v, *output_s;
	snd_kmixer_element_t *e3d;
	snd_kmixer_element_t *recmon; 
	snd_kmixer_element_t *input, *output, *adc;
	int pcm1_num = 0;
	int pcm2_num = 1;
	/* FIXME */
	static struct snd_mixer_element_volume1_range opcm1_db_range[2] = {
		{0, 15, -4600, 0},
		{0, 15, -4600, 0}
	};
	struct snd_mixer_element_volume1_range *ipcm1_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *opcm2_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *omic_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *imic_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *oline_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *iline_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *ofm_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *ifm_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *omono_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *imono_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *ocd_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *icd_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *oaux_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *iaux_db_range = opcm1_db_range;
	struct snd_mixer_element_volume1_range *igain_db_range = opcm1_db_range;
	static struct snd_mixer_element_volume1_range output_db_range[2] = {
		{0, 63, -4600, 0},
		{0, 63, -4600, 0}
	};

	char str[16];

        if (!codec || !codec->card)
                return NULL;
	sprintf(str, "ES%x", codec->version);
        mixer = snd_mixer_new(codec->card, str);
        if (!mixer)
                return NULL;
        sprintf(mixer->name, "ESS AudioDrive ES%x", codec->version);

        mixer->private_data = codec;

	/* Accumulator and multiplexer */
	if ((oaccu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((imux = snd_mixer_lib_mux2(mixer, SND_MIXER_ELEMENT_INPUT_MUX, 0, (codec->caps & ES18XX_MUTEREC) ? SND_MIXER_MUX2_NONE : 0, snd_es18xx_mixer_imux, codec)) == NULL)
		goto __error;
	codec->mix_imux = imux;

	/* Input gain */
	if ((igain_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN, 0, SND_MIXER_OSS_IGAIN, snd_es18xx_mixer_igain_g, codec)) == NULL)
		goto __error;
	if ((igain_v = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, igain_db_range, snd_es18xx_mixer_igain_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, igain_g, igain_v) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, imux, igain_v))
		goto __error;
	codec->mix_igain_v = igain_v;

	/* Record monitor */
	if ((recmon = snd_mixer_lib_mux2(mixer, "Record Monitor", 0, 0, snd_es18xx_mixer_recmon, codec)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, igain_v, recmon))
		goto __error;
	
	/* 3D effect */
	if (codec->caps & ES18XX_3D) {
		static struct snd_mixer_element_3d_effect1_info einfo;
                memset(&einfo, 0, sizeof(einfo));
                einfo.effect = SND_MIXER_EFF1_SPACE | SND_MIXER_EFF1_SW;
                einfo.max_space = 63;
 		if ((e3d_g = snd_mixer_lib_group(mixer, SND_MIXER_GRP_EFFECT_3D, 0)) == NULL)
			goto __error;
		if ((e3d = snd_mixer_lib_3d_effect1(mixer, SND_MIXER_GRP_EFFECT_3D, 0, &einfo, snd_es18xx_mixer_e3d, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, e3d_g, e3d) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, oaccu, e3d))
			goto __error;
		if (snd_mixer_element_route_add(mixer, e3d, recmon))
			goto __error;
		if (snd_mixer_element_route_add(mixer, e3d, imux))
			goto __error;
		codec->mix_oaccu = e3d;
	}
	else {
		if (snd_mixer_element_route_add(mixer, oaccu, recmon))
			goto __error;
		if (snd_mixer_element_route_add(mixer, oaccu, imux))
			goto __error;
		codec->mix_oaccu = oaccu;
	}


	/* PCM1 */
	if ((pcm1_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_es18xx_mixer_pcm1_g, codec)) == NULL)
		goto __error;
	if ((pcm1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0 , SND_MIXER_ETYPE_PLAYBACK, 1, &pcm1_num)) == NULL)
		goto __error;
	if ((opcm1_v = snd_mixer_lib_volume1(mixer, (codec->caps & ES18XX_PCM2) ? "PCM1 Volume" : "PCM Volume", 0, 2, opcm1_db_range, snd_es18xx_mixer_opcm1_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, pcm1_g, opcm1_v) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, pcm1, opcm1_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, opcm1_v, oaccu))
		goto __error;
	codec->mix_opcm1_v = opcm1_v;

	/* PCM2 */
	if (codec->caps & ES18XX_PCM2) {
		if ((pcm2_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 1, SND_MIXER_OSS_ALTPCM, snd_es18xx_mixer_pcm2_g, codec)) == NULL)
			goto __error;
		if ((pcm2 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0 , SND_MIXER_ETYPE_PLAYBACK, 1, &pcm2_num)) == NULL)
			goto __error;
		if ((opcm2_v = snd_mixer_lib_volume1(mixer, "PCM2 Volume", 1, 2, opcm2_db_range, snd_es18xx_mixer_opcm2_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, pcm2_g, opcm2_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, pcm2, opcm2_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, opcm2_v, oaccu))
			goto __error;
		codec->mix_opcm2_v = opcm2_v;
	}

	/* MIC */
	if ((mic_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_es18xx_mixer_mic_g, codec)) == NULL)
		goto __error;
 	if ((mic = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((omic_v = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 2, omic_db_range, snd_es18xx_mixer_omic_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, mic_g, omic_v) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, mic_g, imux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mic, omic_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, omic_v, oaccu))
		goto __error;
	if (snd_mixer_element_route_add(mixer, mic, imux))
		goto __error;
	codec->mix_mic = mic;
	codec->mix_omic_v = omic_v;

	/* Line */
	if ((line_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_es18xx_mixer_line_g, codec)) == NULL)
		goto __error;
 	if ((line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((oline_v = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, oline_db_range, snd_es18xx_mixer_oline_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, line_g, oline_v) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, line_g, imux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, line, oline_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, oline_v, oaccu))
		goto __error;
	if (snd_mixer_element_route_add(mixer, line, imux))
		goto __error;
	codec->mix_line = line;
	codec->mix_oline_v = oline_v;

	/* FM */
	if ((fm_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_OSS_SYNTH, snd_es18xx_mixer_fm_g, codec)) == NULL)
		goto __error;
 	if ((fm = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_FM, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((ofm_v = snd_mixer_lib_volume1(mixer, "FM Volume", 0, 2, ofm_db_range, snd_es18xx_mixer_ofm_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, fm_g, ofm_v) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, fm, ofm_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, ofm_v, oaccu))
		goto __error;
	codec->mix_ofm_v = ofm_v;

	/* Mono */
	if ((mono_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_OSS_LINE3, snd_es18xx_mixer_mono_g, codec)) == NULL)
		goto __error;
 	if ((mono = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((omono_v = snd_mixer_lib_volume1(mixer, "MONO Volume", 0, 2, omono_db_range, snd_es18xx_mixer_omono_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, mono_g, omono_v) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mono, omono_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, omono_v, oaccu))
		goto __error;
	codec->mix_omono_v = omono_v;

	/* CD */
	if ((cd_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_es18xx_mixer_cd_g, codec)) == NULL)
		goto __error;
 	if ((cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((ocd_v = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, ocd_db_range, snd_es18xx_mixer_ocd_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, cd_g, ocd_v) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, cd_g, imux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, cd, ocd_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, ocd_v, oaccu))
		goto __error;
	if (snd_mixer_element_route_add(mixer, cd, imux))
		goto __error;
	codec->mix_cd = cd;
	codec->mix_ocd_v = ocd_v;

	/* Aux */
	if ((aux_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_OSS_LINE2, snd_es18xx_mixer_aux_g, codec)) == NULL)
		goto __error;
 	if ((aux = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((oaux_v = snd_mixer_lib_volume1(mixer, "Aux Volume", 0, 2, oaux_db_range, snd_es18xx_mixer_oaux_v, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, aux_g, oaux_v) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, aux, oaux_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, oaux_v, oaccu))
		goto __error;
	codec->mix_oaux_v = oaux_v;

	/* Input mixer */
	if (codec->caps & ES18XX_RECMIX) {
		/* Accumulator */
		if ((iaccu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, igain_g, imux) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, iaccu, imux))
			goto __error;
		codec->mix_iaccu = iaccu;
		/* PCM1 */
		if ((ipcm1_v = snd_mixer_lib_volume1(mixer, "PCM1 Input Volume", 0, 2, ipcm1_db_range, snd_es18xx_mixer_ipcm1_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, pcm1_g, ipcm1_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, pcm1, ipcm1_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ipcm1_v, iaccu))
			goto __error;
		codec->mix_ipcm1_v = ipcm1_v;
		/* MIC */
		if ((imic_v = snd_mixer_lib_volume1(mixer, "MIC Input Volume", 0, 2, imic_db_range, snd_es18xx_mixer_imic_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, mic_g, imic_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, mic, imic_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, imic_v, iaccu))
			goto __error;
		codec->mix_imic_v = imic_v;
		/* Line */
		if ((iline_v = snd_mixer_lib_volume1(mixer, "Line Input Volume", 0, 2, iline_db_range, snd_es18xx_mixer_iline_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, line_g, iline_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, line, iline_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, iline_v, iaccu))
			goto __error;
		codec->mix_iline_v = iline_v;
		/* FM */
		if ((ifm_v = snd_mixer_lib_volume1(mixer, "FM Input Volume", 0, 2, ifm_db_range, snd_es18xx_mixer_ifm_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, fm_g, ifm_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, fm, ifm_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ifm_v, iaccu))
			goto __error;
		codec->mix_ifm_v = ifm_v;
		/* Mono */
		if ((imono_v = snd_mixer_lib_volume1(mixer, "MONO Input Volume", 0, 2, imono_db_range, snd_es18xx_mixer_imono_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, mono_g, imono_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, mono, imono_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, imono_v, iaccu))
			goto __error;
		codec->mix_imono_v = imono_v;
		/* CD */
		if ((icd_v = snd_mixer_lib_volume1(mixer, "CD Input Volume", 0, 2, icd_db_range, snd_es18xx_mixer_icd_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, cd_g, icd_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, cd, icd_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, icd_v, iaccu))
			goto __error;
		codec->mix_icd_v = icd_v;
		/* Aux */
		if ((iaux_v = snd_mixer_lib_volume1(mixer, "Aux Input Volume", 0, 2, iaux_db_range, snd_es18xx_mixer_iaux_v, codec)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, aux_g, iaux_v) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, aux, iaux_v))
			goto __error;
		if (snd_mixer_element_route_add(mixer, iaux_v, iaccu))
			goto __error;
		codec->mix_iaux_v = iaux_v;
	}
	else {
		codec->mix_iaccu = 0;
		codec->mix_ipcm1_v = 0;
		codec->mix_imic_v = 0;
		codec->mix_iline_v = 0;
		codec->mix_ifm_v = 0;
		codec->mix_imono_v = 0;
		codec->mix_icd_v = 0;
		codec->mix_iaux_v = 0;
	}
	/* Output */
 	if ((output = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((output_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_es18xx_mixer_output_g, codec)) == NULL)
		goto __error;
	if ((output_v = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, output_db_range, snd_es18xx_mixer_output_v, codec)) == NULL)
		goto __error;
	if ((output_s = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, snd_es18xx_mixer_output_s, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, output_g, output_v) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, output_g, output_s) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, output_g, imux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, recmon, output_v))
		goto __error;
	if (snd_mixer_element_route_add(mixer, output_v, output_s))
		goto __error;
	if (snd_mixer_element_route_add(mixer, output_s, output))
		goto __error;
	codec->mix_output_v = output_v;
	codec->mix_output_s = output_s;


	/* Input */
	if ((adc = 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, igain_v, adc) < 0)
		goto __error;
 	if ((input = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, &pcm1_num)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, adc, input))
		goto __error;

	
        return mixer;

 __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

EXPORT_SYMBOL(snd_es18xx_mixer_write);
EXPORT_SYMBOL(snd_es18xx_mixer_read);
EXPORT_SYMBOL(snd_es18xx_interrupt);
EXPORT_SYMBOL(snd_es18xx_new_device);
EXPORT_SYMBOL(snd_es18xx_mixer);
EXPORT_SYMBOL(snd_es18xx_pcm);

/*
 *  INIT part
 */

int init_module(void)
{
        return 0;
}

void cleanup_module(void)
{
}

