/*
 *  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 record.
 */

/*
 * ES1869 NOTES:
 *
 * - there are a first full duplex pcm and a second playback only pcm
 *   (incompatible with first pcm record)
 * 
 * - there is support for the record 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 recording.
 *
 *   The Windows driver does not suffer of this (although it use Audio1
 *   only for recording). 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 "driver.h"
#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;

        snd_spin_lock(codec, reg, &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:
			snd_spin_unlock(codec, reg, &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:
			snd_spin_unlock(codec, reg, &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:
			snd_spin_unlock(codec, reg, &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 record 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);
	}

        snd_spin_unlock(codec, reg, &flags);

        return 0;
}

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

        snd_spin_lock(codec, reg, &flags);

	/* reset */
	if (snd_es18xx_reset(codec) < 0) {
		snd_spin_unlock(codec, reg, &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) {
		snd_spin_unlock(codec, reg, &flags);
		return hi;
	}
	lo = snd_es18xx_dsp_get_byte(codec);
	if ((lo & 0xf0) != 0x80) {
		snd_spin_unlock(codec, reg, &flags);
		return -ENODEV;
	}
	if (hi == 0x48) {
		snd_spin_unlock(codec, reg, &flags);
		codec->version = 0x488;
		return 0;
	}
	if (hi != 0x68) {
		snd_spin_unlock(codec, reg, &flags);
		return -ENODEV;
	}
	if ((lo & 0x0f) < 8) {
		snd_spin_unlock(codec, reg, &flags);
		codec->version = 0x688;
		return 0;
	}
			
        outb(0x40, codec->port + 0x04);
	hi = inb(codec->port + 0x05);
	lo = inb(codec->port + 0x05);
	if (hi != lo) {
		snd_spin_unlock(codec, reg, &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;
        snd_spin_unlock(codec, reg, &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(snd_pcm1_t * pcm1,
				     unsigned int cmd, unsigned long *arg)
{
        es18xx_t *codec;
        codec = (es18xx_t *) pcm1->private_data;
        switch(cmd) {
        case SND_PCM1_IOCTL_RATE:
                pcm1->playback.real_rate = snd_es18xx_set_rate(codec, pcm1->playback.rate, DAC2, 0);
#if 0
		if ((codec->caps & ES18XX_DUPLEX_SAME) &&
		    pcm1->flags & SND_PCM1_LFLG_RECORD) {
			if (pcm1->playback.real_rate != pcm1->record.real_rate)
				return -EBUSY;
		}
#endif
                return 0;
	case SND_PCM1_IOCTL_VOICES:
		if ((codec->caps & ES18XX_DUPLEX_MONO) &&
		    pcm1->flags & SND_PCM1_LFLG_RECORD &&
		    (pcm1->playback.voices != 1 ||
		     pcm1->record.voices != 1))
			return -EBUSY;
		else
			return 0;
        }
        return -ENXIO;
}

static int snd_es18xx_playback2_ioctl(snd_pcm1_t * pcm1,
				     unsigned int cmd, unsigned long *arg)
{
        es18xx_t *codec;
        codec = (es18xx_t *) pcm1->private_data;
        switch(cmd) {
        case SND_PCM1_IOCTL_RATE:
                pcm1->playback.real_rate = snd_es18xx_set_rate(codec, pcm1->playback.rate, DAC1, 0);
                return 0;
        }
        return -ENXIO;
}

static int snd_es18xx_record_ioctl(snd_pcm1_t * pcm1,
				   unsigned int cmd, unsigned long *arg)
{
        es18xx_t *codec;
        codec = (es18xx_t *) pcm1->private_data;
        switch(cmd) {
        case SND_PCM1_IOCTL_RATE:
                pcm1->record.real_rate = snd_es18xx_set_rate(codec, pcm1->record.rate, ADC1, 0);
#if 0
		if ((codec->caps & ES18XX_DUPLEX_SAME) &&
		    pcm1->flags & SND_PCM1_LFLG_PLAY) {
			if (pcm1->playback.real_rate != pcm1->record.real_rate)
				return -EBUSY;
		}
#endif
                return 0;
	case SND_PCM1_IOCTL_VOICES:
		if ((codec->caps & ES18XX_DUPLEX_MONO) &&
		    pcm1->flags & SND_PCM1_LFLG_PLAY &&
		    (pcm1->playback.voices != 1 ||
		     pcm1->record.voices != 1))
			return -EBUSY;
		else
			return 0;
        }
        return -ENXIO;
}

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

        pchn = &pcm1->playback;

        codec = (es18xx_t *) pcm1->private_data;

        snd_spin_lock(codec, mixer, &flags);

        snd_es18xx_set_rate(codec, pchn->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, 
			      ((pchn->voices == 1) ? 0x00 : 0x02) |
			      ((pchn->mode & SND_PCM1_MODE_16) ? 0x01 : 0x00) |
			      ((pchn->mode & SND_PCM1_MODE_U) ? 0x00 : 0x04),
			      0x07);


        snd_spin_unlock(codec, mixer, &flags);

        /* Set DMA controller */
        snd_dma_program(codec->dma2, buffer, size, DMA_MODE_WRITE | DMA_MODE_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_playback_trigger(snd_pcm1_t * pcm1, int up)
{
        es18xx_t *codec;
        unsigned long flags;

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

	snd_spin_lock(codec, reg, &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 (codec->caps & ES18XX_PCM2)
			/* Restore Audio 2 volume */
			snd_es18xx_mixer_write(codec, 0x7C, codec->audio2_vol);
		else
			/* 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 (codec->caps & ES18XX_PCM2)
			/* Set Audio 2 volume to 0 */
			snd_es18xx_mixer_write(codec, 0x7C, 0);
		else
			/* Disable PCM output */
			snd_es18xx_dsp_command(codec, 0xD3);
        }

        snd_spin_unlock(codec, reg, &flags);
}

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

        pchn = &pcm1->record;
        codec = (es18xx_t *) pcm1->private_data;

        snd_spin_lock(codec, reg, &flags);

	snd_es18xx_reset_fifo(codec);

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

        snd_es18xx_set_rate(codec, pchn->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, 
                         (pchn->mode & SND_PCM1_MODE_U) ? 0x51 : 0x71);
        snd_es18xx_write(codec, 0xB7, 0x90 |
                         ((pchn->voices == 1) ? 0x40 : 0x08) |
                         ((pchn->mode & SND_PCM1_MODE_16) ? 0x04 : 0x00) |
                         ((pchn->mode & SND_PCM1_MODE_U) ? 0x00 : 0x20));

        snd_spin_unlock(codec, reg, &flags);

        /* Set DMA controler */
        snd_dma_program(codec->dma1, buffer, size, DMA_MODE_READ | DMA_MODE_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_record_trigger(snd_pcm1_t *pcm1, int up)
{
        es18xx_t *codec;
        unsigned long flags;

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

	snd_spin_lock(codec, reg, &flags);
	if (up)
                /* Start DMA */
                snd_es18xx_write(codec, 0xB8, 0x0f);
        else
                /* Stop DMA */
                snd_es18xx_write(codec, 0xB8, 0x00);
        snd_spin_unlock(codec, reg, &flags);
}

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

        pchn = &pcm1->playback;
        codec = (es18xx_t *) pcm1->private_data;

        snd_spin_lock(codec, reg, &flags);

	snd_es18xx_reset_fifo(codec);

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

        snd_es18xx_set_rate(codec, pchn->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,
                         (pchn->mode & SND_PCM1_MODE_U) ? 0x80 : 0x00);
        snd_es18xx_write(codec, 0xB7, 
                         (pchn->mode & SND_PCM1_MODE_U) ? 0x51 : 0x71);
        snd_es18xx_write(codec, 0xB7, 0x90 |
                         ((pchn->voices == 1) ? 0x40 : 0x08) |
                         ((pchn->mode & SND_PCM1_MODE_16) ? 0x04 : 0x00) |
                         ((pchn->mode & SND_PCM1_MODE_U) ? 0x00 : 0x20));


        snd_spin_unlock(codec, reg, &flags);

        /* Set DMA controler */
        snd_dma_program(codec->dma1, buffer, size, DMA_MODE_WRITE | DMA_MODE_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(snd_pcm1_t *pcm1, int up)
{
        es18xx_t *codec = (es18xx_t *) pcm1->private_data;
        unsigned long flags;


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

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

void snd_es18xx_interrupt(es18xx_t *codec, unsigned char status)
{
        /* ok.. playback is active */
        if (status & AUDIO2_IRQ) {
                if (codec->active & DAC2) {
                        snd_pcm1_t *pcm1=(snd_pcm1_t *) codec->pcm_a->private_data;
			snd_pcm1_channel_t *pchn = &pcm1->playback;
                        pchn->ack(pcm1);
                }
		/* ack interrupt */
                snd_es18xx_mixer_bits(codec, 0x7A, 0x00, 0x80);
        }

        if (status & AUDIO1_IRQ) {
                /* ok.. record is active */
                if (codec->active & ADC1) {
                        snd_pcm1_t *pcm1=(snd_pcm1_t *) codec->pcm_a->private_data;
			snd_pcm1_channel_t *pchn = &pcm1->record;
                        pchn->ack(pcm1);
                }
                /* ok.. playback2 is active */
                else if (codec->active & DAC1) {
                        snd_pcm1_t *pcm1=(snd_pcm1_t *) codec->pcm_b->private_data;
			snd_pcm1_channel_t *pchn = &pcm1->playback;
                        pchn->ack(pcm1);
                }
		/* ack interrupt */
		inb(codec->port + 0x0E);
        }

}

static int snd_es18xx_playback_open(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;
        int err;

        codec = (es18xx_t *) pcm1->private_data;
	if ((codec->caps & ES18XX_DUPLEX_MONO) &&
	    (pcm1->flags & SND_PCM1_LFLG_RECORD) && 
	    pcm1->record.voices != 1)
		return -EBUSY;
        if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, codec->dma2ptr, "ES18xx (playback)")) < 0)
                return err;
        codec->open |= DAC2;
        return 0;
}

static int snd_es18xx_playback2_open(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;
        int err;

        codec = (es18xx_t *) pcm1->private_data;
        if (codec->open & ADC1)
                return -EBUSY;
        if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, codec->dma1ptr, "ES18xx (playback2)")) < 0)
                return err;
        codec->open |= DAC1;
        return 0;
}

static int snd_es18xx_record_open(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;
        int err;

        codec = (es18xx_t *) pcm1->private_data;
        if (codec->open & DAC1)
                return -EBUSY;
	if ((codec->caps & ES18XX_DUPLEX_MONO) &&
	    (pcm1->flags & SND_PCM1_LFLG_PLAY) && 
	    pcm1->playback.voices != 1)
		return -EBUSY;
        if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_RECORD, codec->dma1ptr, "ES18xx (record)")) < 0)
                return err;
        codec->open |= ADC1;
        return 0;
}

static void snd_es18xx_playback_close(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->private_data;
        snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma2ptr);
        codec->open &= ~DAC2;
}

static void snd_es18xx_playback2_close(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->private_data;
        snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, codec->dma1ptr);
        codec->open &= ~DAC1;
}

static void snd_es18xx_record_close(snd_pcm1_t * pcm1)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->private_data;
        snd_pcm1_dma_free(pcm1, SND_PCM1_RECORD, codec->dma1ptr);
        codec->open &= ~ADC1;
}

static unsigned int snd_es18xx_playback_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->private_data;
        if (!(codec->active & DAC2))
                return 0;
        return used_size - snd_dma_residue(codec->dma2);
}

static unsigned int snd_es18xx_playback2_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->private_data;
        if (!(codec->active & DAC1))
                return 0;
        return used_size - snd_dma_residue(codec->dma1);
}

static unsigned int snd_es18xx_record_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
        es18xx_t *codec;

        codec = (es18xx_t *) pcm1->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 =
{
        NULL,			/* private data */
        NULL,			/* private_free */
        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_playback2 =
{
        NULL,			/* private data */
        NULL,			/* private_free */
        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_playback2_open,
        snd_es18xx_playback2_close,
        snd_es18xx_playback2_ioctl,
        snd_es18xx_playback2_prepare,
        snd_es18xx_playback2_trigger,
        snd_es18xx_playback2_pointer,
        snd_pcm1_playback_dma_ulaw,
        snd_pcm1_dma_move,
        snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_es18xx_record =
{
        NULL,			/* private data */
        NULL,			/* private free */
        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_record_open,
        snd_es18xx_record_close,
        snd_es18xx_record_ioctl,
        snd_es18xx_record_prepare,
        snd_es18xx_record_trigger,
        snd_es18xx_record_pointer,
        snd_pcm1_record_dma_ulaw,
        snd_pcm1_dma_move,
        NULL
};

static void snd_es18xx_free(void *private_data)
{
}

snd_pcm_t *snd_es18xx_pcm_a(es18xx_t * codec)
{
        snd_pcm_t *pcm;
        snd_pcm1_t *pcm1;
	char str[16];
	if (codec->caps & ES18XX_PCM2)
		sprintf(str, "ES%x DAC2/ADC", codec->version);
	else
		sprintf(str, "ES%x", codec->version);
        pcm = snd_pcm1_new_device(codec->card, str);
        if (!pcm)
                return NULL;
        pcm1 = (snd_pcm1_t *) pcm->private_data;
        memcpy(&pcm1->playback.hw, &snd_es18xx_playback, sizeof(snd_es18xx_playback));
        memcpy(&pcm1->record.hw, &snd_es18xx_record, sizeof(snd_es18xx_record));
        pcm1->private_data = codec;
        pcm1->private_free = snd_es18xx_free;
        pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
                SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD |
                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;
	if (codec->caps & ES18XX_PCM2)
		sprintf(pcm->name, "ESS AudioDrive ES%x DAC2/ADC", codec->version);
	else
		sprintf(pcm->name, "ESS AudioDrive ES%x", codec->version);
        codec->pcm_a = pcm;
	return pcm;
}

snd_pcm_t *snd_es18xx_pcm_b(es18xx_t * codec)
{
        snd_pcm_t *pcm;
        snd_pcm1_t *pcm1;
	char str[16];
	sprintf(str, "ES%x DAC1", codec->version);
        pcm = snd_pcm1_new_device(codec->card, str);
        if (!pcm)
                return NULL;
        pcm1 = (snd_pcm1_t *) pcm->private_data;
        memcpy(&pcm1->playback.hw, &snd_es18xx_playback2, sizeof(snd_es18xx_playback2));
        pcm1->private_data = codec;
        pcm1->private_free = snd_es18xx_free;
        pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
                SND_PCM_INFO_PLAYBACK;
        sprintf(pcm->name, "ESS AudioDrive ES%x DAC1", codec->version);
        codec->pcm_b = 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_calloc(sizeof(es18xx_t));
        if (!codec)
                return NULL;
        snd_spin_prepare(codec, reg);
        snd_spin_prepare(codec, mixer);
        snd_spin_prepare(codec, ctrl);
        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->open = 0;
	codec->active = 0;

        if (snd_es18xx_probe(codec) < 0) {
                snd_free(codec, sizeof(es18xx_t));
                return NULL;
        }
        return codec;
}

snd_kmixer_t *snd_es18xx_mixer(es18xx_t *codec)
{
        snd_kmixer_t *mixer;
	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);

	snd_printk("TODO: MIXER!!!\n");

        mixer->private_data = codec;
        return mixer;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_es18xx_export;
#endif

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

void cleanup_module(void)
{
}

