/*
 *  Copyright (c) by Jaromir Koutek <miri@punknet.cz>
 *                   Jaroslav Kysela <perex@suse.cz>
 *                   Thomas Sailer <sailer@ife.ee.ethz.ch>
 *                   Abramo Bagnara <abbagnara@racine.ra.it>
 *                   Markus Gruber <gruber@eikon.tum.de>
 *  Driver for very cheap (and noisy) ESS Solo-1.
 *  Mine has not even a pre-amplifier... Everything sound like from telephone.
 *  Documentation is at ftp://ftp.esstech.com.tw/PCIAudio/Solo1/.
 *  Based on s3_86c617.c source.
 *
 *  BUGS:
 *    Memory allocation problems (use patch for kernel 2.2)
 *
 *  TODO:
 *    MPU401, OPL3
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/info.h"
#include "../../include/es1938.h"

#define RESET_LOOP_TIMEOUT	0x10000
#define WRITE_LOOP_TIMEOUT	0x10000
#define GET_LOOP_TIMEOUT	0x01000

#define SOLO_CLK1		768000
#define SOLO_CLK0		793800
#define SOLO_FILTERCLK		7160000

/* -----------------------------------------------------------------
 * Write to a mixer register
 * -----------------------------------------------------------------*/
static void snd_solo_mixer_out(es1938_t * solo, unsigned char reg, unsigned char mask, unsigned char val)
{
	unsigned char a;

	outb(reg, SLSB_REG(solo, MIXERADDR));
	a = (mask != 0xff) ? (inb(SLSB_REG(solo, MIXERDATA)) & (~mask)) : 0;
	outb((val & mask) | a, SLSB_REG(solo, MIXERDATA));
}

/* -----------------------------------------------------------------
 * Read from a mixer register
 * -----------------------------------------------------------------*/
static int snd_solo_mixer_in(es1938_t * solo, unsigned char reg)
{
	outb(reg, SLSB_REG(solo, MIXERADDR));
	return inb(SLSB_REG(solo, MIXERDATA));
}

/* -----------------------------------------------------------------
 * Write command to Controller Registers
 * -----------------------------------------------------------------*/
static void snd_solo_write_cmd(es1938_t * solo, unsigned char cmd)
{
	int i;
	unsigned char v;
	for (i = 0; i < WRITE_LOOP_TIMEOUT; i++) {
		if (!(v = inb(SLSB_REG(solo, READSTATUS)) & 0x80)) {
			outb(cmd, SLSB_REG(solo, WRITEDATA));
			return;
		}
	}
	printk("snd_solo_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v);
}

/* -----------------------------------------------------------------
 * Read the Read Data Buffer
 * -----------------------------------------------------------------*/
static int snd_solo_get_byte(es1938_t * solo)
{
	int i;
	unsigned char v;
	for (i = GET_LOOP_TIMEOUT; i; i--)
		if ((v = inb(SLSB_REG(solo, STATUS))) & 0x80)
			return inb(SLSB_REG(solo, READDATA));
	snd_printk("snd_solo_get_byte timeout: status 0x02%x\n", v);
	return -ENODEV;
}

/* -----------------------------------------------------------------
 * Write value cmd register
 * -----------------------------------------------------------------*/
static void snd_solo_write_reg(es1938_t * solo, unsigned char reg, unsigned char val)
{
	snd_solo_write_cmd(solo, reg);
	snd_solo_write_cmd(solo, val);
}

/* -----------------------------------------------------------------
 * Read data from cmd register and return it
 * -----------------------------------------------------------------*/
static unsigned char snd_solo_read_cmd(es1938_t * solo, unsigned char cmd)
{
	unsigned char v;

	snd_solo_write_reg(solo, ESS_CMD_READREG, cmd);
	v = snd_solo_get_byte(solo);
	return v;
}

/* --------------------------------------------------------------------
 * Reset the chip
 * --------------------------------------------------------------------*/
static void snd_solo_reset(es1938_t * solo)
{
	int i;

	outb(3, SLSB_REG(solo, RESET));
	udelay(10);
	outb(0, SLSB_REG(solo, RESET));
	for (i = 0; i < RESET_LOOP_TIMEOUT; i++) {
		if (inb(SLSB_REG(solo, STATUS)) & 0x80) {
			if (inb(SLSB_REG(solo, READDATA)) == 0xaa)
				goto __next;
		}
	}
	snd_printk("ESS Solo-1 reset failed\n");

     __next:
	snd_solo_write_cmd(solo, ESS_CMD_ENABLEEXT);
	udelay(100);
	/* Demand transfer DMA: 4 bytes per DMA request: 2 */
	snd_solo_write_reg(solo, ESS_CMD_DMATYPE, 2);
	/* Change behaviour of register A1
	   4x oversampling
	   2nd channel DAC asynchronous */                                                      
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2MODE, 0xff, 0x32);	
	/* enable/select DMA channel and IRQ channel */
	snd_solo_write_reg(solo, ESS_CMD_IRQCONTROL, 0x50 | (snd_solo_read_cmd(solo, ESS_CMD_IRQCONTROL) & 0x0f));
	snd_solo_write_reg(solo, ESS_CMD_DRQCONTROL, 0x50 | (snd_solo_read_cmd(solo, ESS_CMD_DRQCONTROL) & 0x0f));
	/* unmasks channel 2 IRQ */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL2, 0x40, 0x40);
}

/* --------------------------------------------------------------------
 * Reset the FIFOs
 * --------------------------------------------------------------------*/
static void snd_solo_reset_fifo(es1938_t * solo)
{
	outb(2, SLSB_REG(solo, RESET));
	udelay(10);
	outb(0, SLSB_REG(solo, RESET));
	udelay(10);
}

/* ------------------------------------------------------------------
 * IRQCONTROL
 * ------------------------------------------------------------------*/
static void snd_solo_mask_irq(es1938_t * solo, unsigned char mask, unsigned char val)
{
	solo->irqmask &= ~mask;
	solo->irqmask |= val;
	outb(solo->irqmask, SLIO_REG(solo, IRQCONTROL));
}

/* ------------------------------------------------------------------
 * Set sample rate
 * ------------------------------------------------------------------*/
static unsigned int snd_solo_set_dac_rate(es1938_t * solo, unsigned int rate, int playback, int set)
{
	int x0, x1, r0, r1, which;

	if (rate > 48000)
		rate = 48000;
	if (rate < 6000)
		rate = 6000;
	x0 = SOLO_CLK0 / rate;
	x1 = SOLO_CLK1 / rate;
	r0 = SOLO_CLK0 / x0;
	r1 = SOLO_CLK1 / x1;
	which = abs(r0 - rate) < abs(r1 - rate) ? 0 : 128;
	if (which) {
		x0 = x1;
		r0 = r1;
	}
	x0 = which | (128 - x0);	
	if (set) {
		unsigned int f;
		
		f = (rate * 9) / 20;
		f = 256 - SOLO_FILTERCLK / (82 * f);
		if (playback) {
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2SAMPLE, 0xff, x0);
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2FILTER, 0xff, f);
		} else {
			snd_solo_write_reg(solo, ESS_CMD_EXTSAMPLERATE, x0);
			snd_solo_write_reg(solo, ESS_CMD_FILTERDIV, f);
		}
	}
	return r0;
}

/* --------------------------------------------------------------------
 * Enable Direct Memory Access
 * --------------------------------------------------------------------*/
static void snd_solo_enabledma(es1938_t * solo, int playback)
{
	if (!playback) {
		outb(inb(SLDM_REG(solo, DMAMASK)) & 0xfe, SLDM_REG(solo, DMAMASK));
		if (inb(SLDM_REG(solo, DMAMASK)) & 1)
			snd_printk("solo-1 DDMA trouble\n");
	} else {
		outb((inb(SLIO_REG(solo, AUDIO2MODE)) & 0xfd) | 2, SLIO_REG(solo, AUDIO2MODE));
	}
}

/* --------------------------------------------------------------------
 * Disable Direct Memory Access
 * --------------------------------------------------------------------*/
static void snd_solo_disabledma(es1938_t * solo, int playback)
{
	if (!playback)
		outb((inb(SLDM_REG(solo, DMAMASK)) & 0xfe) | 1, SLDM_REG(solo, DMAMASK));
	else
		outb(inb(SLIO_REG(solo, AUDIO2MODE)) & 0xfd, SLIO_REG(solo, AUDIO2MODE));
}

/* --------------------------------------------------------------------
 * Configure Solo1 builtin DMA Controller
 * --------------------------------------------------------------------*/
static void snd_solo_setdma(es1938_t * solo, void *buffer, unsigned int size, unsigned char mode, unsigned char command, int playback)
{
	snd_solo_disabledma(solo, playback);
	if (!playback) {
		/* Enable DMA controller */
		outb(command, SLDM_REG(solo, DMACOMMAND));
		/* 1. Master reset */
		outb(0xff, SLDM_REG(solo, DMACLEAR));
		/* 2. Mask DMA */
		outb(1, SLDM_REG(solo, DMAMASK));
		outb(mode, SLDM_REG(solo, DMAMODE));
		outl(virt_to_bus(buffer), SLDM_REG(solo, DMABASE));
		outw(size, SLDM_REG(solo, DMACOUNT));
	} else {
		outl(virt_to_bus(buffer), SLIO_REG(solo, AUDIO2DMAADDR));
		outw(size, SLIO_REG(solo, AUDIO2DMACOUNT));
		outb(mode, SLIO_REG(solo, AUDIO2MODE));
	}
	/* Unmask */
	snd_solo_enabledma(solo, playback);
}

static int snd_solo_waitcodec(es1938_t * solo, int cmd, int playback)
{
	int val;

	if (!playback) {
		if (cmd == SND_PCM_TRIGGER_GO) {
			val = 1;
		} else if (cmd == SND_PCM_TRIGGER_STOP) {
			val = 0;
		} else {
			return -EINVAL;
		}
		// snd_solo_write_cmd(solo, val ? ESS_CMD_ENABLEAUDIO1 : ESS_CMD_STOPAUDIO1);
		snd_solo_write_reg(solo, ESS_CMD_DMACONTROL,
				   (snd_solo_read_cmd(solo, ESS_CMD_DMACONTROL) & 0xfe) | val);
	} else {
		if (cmd == SND_PCM_TRIGGER_GO) {
			val = 3;
		} else if (cmd == SND_PCM_TRIGGER_STOP) {
			val = 0;
		} else {
			return -EINVAL;
		}
		snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL1, 3, val);
	}
	return 0;
}

static int snd_solo_trigger(es1938_t * solo, int cmd, int playback)
{
	unsigned long flags;
	int result;

	spin_lock_irqsave(&solo->reg_lock, flags);
	result = snd_solo_waitcodec(solo, cmd, playback);
	spin_unlock_irqrestore(&solo->reg_lock, flags);
	return result;
}

/* ----------------------------------------------------------------------
 *
 *                           *** PCM part ***
 */

static int snd_solo_capture_ioctl(void *private_data,
				  snd_pcm_subchn_t * subchn,
				  unsigned int cmd,
				  unsigned long *arg)
{
	int result;
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		subchn->runtime->format.rate = snd_solo_set_dac_rate(solo, subchn->runtime->format.rate, 0, 0);
		if (subchn->runtime->dma_area->size > 0xff00)
			return snd_pcm_lib_set_buffer_size(subchn, 0xff00);
	}
	return 0;
}

static int snd_solo_playback2_ioctl(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    unsigned int cmd,
				    unsigned long *arg)
{
	int result;
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);

	result = snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
	if (result < 0)
		return result;
	if (cmd == SND_PCM_IOCTL1_PARAMS) {
		subchn->runtime->format.rate = snd_solo_set_dac_rate(solo, subchn->runtime->format.rate, 1, 0);
		if (subchn->runtime->dma_area->size > 0xff00)
			return snd_pcm_lib_set_buffer_size(subchn, 0xff00);
	}
	return 0;
}

static int snd_solo_capture_trigger(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    int cmd)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	return snd_solo_trigger(solo, cmd, 0);
}

static int snd_solo_playback2_trigger(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      int cmd)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	return snd_solo_trigger(solo, cmd, 1);
}

/* --------------------------------------------------------------------
 * First channel for Extended Mode Audio 1 ADC Operation
 * --------------------------------------------------------------------*/
static int snd_solo_capture_prepare(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int u, is8, mono;
	unsigned int size = snd_pcm_lib_transfer_size(subchn);
	unsigned int count = snd_pcm_lib_transfer_fragment(subchn);

	solo->c_dma_size = size;
	mono = (runtime->format.voices > 1) ? 0 : 1;
	is8 = snd_pcm_format_width(runtime->format.format) == 16 ? 0 : 1;
	u = snd_pcm_format_unsigned(runtime->format.format);

	spin_lock_irqsave(&solo->reg_lock, flags);

	count = 0x10000 - count;
	size--;

	snd_solo_reset_fifo(solo);
	
	/* 5. program direction and type */
	snd_solo_write_reg(solo, ESS_CMD_DMACONTROL, 0xe);
	snd_solo_write_reg(solo, ESS_CMD_ANALOGCONTROL, (snd_solo_read_cmd(solo, ESS_CMD_ANALOGCONTROL) & 0x04) | (mono ? 2 : 1));

	/* 6. set clock and counters */
	snd_solo_set_dac_rate(solo, runtime->format.rate, 0, 1);
	snd_solo_write_reg(solo, ESS_CMD_DMACNTRELOADL, count & 0xff);
	snd_solo_write_reg(solo, ESS_CMD_DMACNTRELOADH, count >> 8);

	/* 7. delay 300ms */
	mdelay(300);

	/* 8. enable record monitor */

	/* 9. initialize and configure ADC */
	snd_solo_write_reg(solo, ESS_CMD_SETFORMAT2, 0x90 | (u ? 0x11 : 0x31) | (is8 ? 0 : 4) | (mono ? 0x40 : 8));

	snd_solo_reset_fifo(solo);	

	/* 11. configure system interrupt controller and DMA controller */
	snd_solo_setdma(solo, runtime->dma_area->buf, size, 0x54, 0xc4, 0);

	spin_unlock_irqrestore(&solo->reg_lock, flags);

	return 0;
}


/* ------------------------------------------------------------------------------
 * Second Audio channel DAC Operation
 * ------------------------------------------------------------------------------*/
static int snd_solo_playback2_prepare(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	unsigned long flags;
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	int u, is8, mono;
	unsigned int size = snd_pcm_lib_transfer_size(subchn);
	unsigned int count = snd_pcm_lib_transfer_fragment(subchn);

	solo->p_dma_size = size;
	mono = (runtime->format.voices > 1) ? 0 : 1;
	is8 = snd_pcm_format_width(runtime->format.format) == 16 ? 0 : 1;
	u = snd_pcm_format_unsigned(runtime->format.format);

	spin_lock_irqsave(&solo->reg_lock, flags);

	count >>= 1;
	count = 0x10000 - count;
 
	/* 1. reset */
	snd_solo_reset_fifo(solo);
	
	/* 2. program auto-init dma */
	/* set bit 4 high for Auto-Initialize DMA mode */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL1, 0x10, 0x10);

	/* 3. set clock and counters */
	snd_solo_set_dac_rate(solo, runtime->format.rate, 1, 1);
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2TCOUNTL, 0xff, count & 0xff);
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2TCOUNTH, 0xff, count >> 8);

	/* 4. initialize and configure Audio 2 DAC */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL2, 0x07, (u ? 0 : 4) | (mono ? 0 : 2) | (is8 ? 0 : 1));

	snd_solo_reset_fifo(solo);

	/* 6. program DMA */
	snd_solo_setdma(solo, runtime->dma_area->buf, size, 0x8, 0, 1);
	
	spin_unlock_irqrestore(&solo->reg_lock, flags);

	return 0;
}

static unsigned int snd_solo_capture_pointer(void *private_data,
					     snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	
	return solo->c_dma_size - 1 - inw(SLDM_REG(solo, DMACOUNT));
}

static unsigned int snd_solo_playback2_pointer(void *private_data,
					       snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	
	return solo->p_dma_size - inw(SLIO_REG(solo, AUDIO2DMACOUNT));
}

/* ----------------------------------------------------------------------
 * Audio1 Capture (ADC)
 * ----------------------------------------------------------------------*/
static snd_pcm_hardware_t snd_solo_capture =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_BE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_BE,	/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	6000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* block transfer size */
	snd_solo_capture_ioctl,
	snd_solo_capture_prepare,
	snd_solo_capture_trigger,
	snd_solo_capture_pointer
};

/* -----------------------------------------------------------------------
 * Audio2 Playback (DAC)
 * -----------------------------------------------------------------------*/
static snd_pcm_hardware_t snd_solo_playback2 =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_BLOCK_TRANSFER |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE | SND_PCM_FMT_S8 | SND_PCM_FMT_U16_LE,	/* formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	6000,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* block transfer size */
	snd_solo_playback2_ioctl,
	snd_solo_playback2_prepare,
	snd_solo_playback2_trigger,
	snd_solo_playback2_pointer
};

static int snd_solo_capture_open(void *private_data,
				 snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, solo->dma1ptr, "ESS Solo-1 (capture)")) < 0)
		return err;
	solo->capture_subchn = subchn;
	subchn->runtime->hw = &snd_solo_capture;
	snd_pcm_set_mixer(subchn, solo->mixer->device, solo->mix_capture);
	return 0;
}

static int snd_solo_playback2_open(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);
	int err;

	if ((err = snd_pcm_dma_alloc(subchn, solo->dma2ptr,
				     "ESS Solo-1 (playback 2)")) < 0)
		return err;
	solo->playback2_subchn = subchn;
	subchn->runtime->hw = &snd_solo_playback2;
	snd_pcm_set_mixer(subchn, solo->mixer->device, solo->mix_playback);
	return 0;
}

static int snd_solo_capture_close(void *private_data,
				  snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);

	solo->capture_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_solo_playback2_close(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, -ENXIO);

	solo->playback2_subchn = NULL;
	snd_pcm_dma_free(subchn);
	return 0;
}

static void snd_solo_pcm_free(void *private_data)
{
	es1938_t *solo = snd_magic_cast(es1938_t, private_data, );
	solo->pcm = NULL;
}

int snd_solo_new_pcm(es1938_t * solo, int device, snd_pcm_t ** rpcm)
{
	snd_pcm_t *pcm;
	int err;

	*rpcm = NULL;
	if ((err = snd_pcm_new(solo->card, "es-1938-1946", device, 1, 1, &pcm)) < 0)
		return err;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = solo;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_solo_playback2_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_solo_playback2_close;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = solo;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_solo_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_solo_capture_close;
	
	pcm->private_data = solo;
	pcm->private_free = snd_solo_pcm_free;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "ESS Solo-1");
	*rpcm = solo->pcm = pcm;
	return 0;
}

/* -------------------------------------------------------------------
 * 
 *                       *** Mixer part ***
 */

static int snd_es1938_mixer_volume(snd_kmixer_element_t *element, int w_flag, int *voices, int reg)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char val, lreg, rreg;
	int change = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);
	val = snd_solo_mixer_in(codec, reg);
	lreg = val & 0x0f;
	rreg = val >> 4;
	if (!w_flag) {
		voices[0] = lreg;
		voices[1] = rreg;
	} else {
		change = lreg != voices[0] || rreg != voices[1];
		val = ((voices[1] & 0x0f) | (voices[0] << 4));
		snd_solo_mixer_out(codec, reg, 0xff, val);
#ifdef SOLO_DEBUG
		printk("set_volume 0x%x: 0x%x / voice0: 0x%x voice1: 0x%x\n", reg, snd_solo_mixer_in(codec, reg), voices[0], voices[1]);
#endif
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;
}

static int snd_es1938_controller_volume(snd_kmixer_element_t *element, int w_flag, int *voices, int reg)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char val, lreg, rreg;
	int change = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);

	/* Enable extended mode commands */
	snd_solo_write_cmd(codec, ESS_CMD_ENABLEEXT);

	val = snd_solo_read_cmd(codec, reg);
	lreg = val & 0x0f;
	rreg = val >> 4;
	if (!w_flag) {
		voices[0] = lreg;
		voices[1] = rreg;
	} else {
		change = lreg != voices[0] || rreg != voices[1];
		val = ((voices[1] & 0x0f) | (voices[0] << 4));
		snd_solo_write_reg(codec, reg, val);
#ifdef SOLO_DEBUG
		printk("set_volume 0x%x: 0x%x / voice0: 0x%x voice1: 0x%x\n", reg, snd_solo_read_cmd(codec, reg), voices[0], voices[1]);
#endif
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;
}

static int snd_es1938_mixer_output_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char lreg, rreg;
	int change = 0;

	spin_lock_irqsave(&codec->reg_lock, flags);
	lreg = snd_solo_mixer_in(codec, ESSSB_IREG_MASTER_LEFT);
	rreg = snd_solo_mixer_in(codec, ESSSB_IREG_MASTER_RIGHT);
	if (!w_flag) {
		voices[0] = lreg & 0x3f;
		voices[1] = rreg & 0x3f;
	} else {
		change = (lreg & 0x3f) != voices[0] || (rreg & 0x3f) != voices[1];
		lreg = (lreg & 0x40) | voices[0];
		rreg = (rreg & 0x40) | voices[1];
		snd_solo_mixer_out(codec, ESSSB_IREG_MASTER_LEFT, 0xff, lreg);
		snd_solo_mixer_out(codec, ESSSB_IREG_MASTER_RIGHT, 0xff, rreg);
#ifdef SOLO_DEBUG
		printk("set_volume_master 0x%x, 0x%x\n", lreg, rreg);
#endif
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;
}

/* --- code from es18xx.c --- */

static int snd_es1938_mixer_imux(snd_kmixer_element_t * element,
				 int w_flag,
				 snd_kmixer_element_t ** melement)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char reg, oval, nval;
	int change = 0;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	reg = snd_solo_mixer_in(codec, ESSSB_IREG_RECSRC);
	oval = reg & 0x1f;
	if (w_flag) {
		if (*melement == codec->mix_mic) {
			nval = ESS_RECSRC_MIC;
		} else if (*melement == codec->mix_line) {
			nval = ESS_RECSRC_LINE;
		} else if (*melement == codec->mix_cd) {
			nval = ESS_RECSRC_AUXACD;
		} else if (*melement == codec->mix_iaccu) {
			nval = ESS_RECSRC_AUXB;
		} else if (*melement == codec->mix_oaccu) {
			nval = ESS_RECSRC_NONE;
		} else {
			nval = 0x10;
		}
		if ((change = (nval != oval)))
			snd_solo_mixer_out(codec, ESSSB_IREG_RECSRC, 0xff, (reg & ~0x1f) | nval);
	} else {
		switch (oval) {
		case ESS_RECSRC_MIC:
			*melement = codec->mix_mic;
			break;
		case ESS_RECSRC_LINE:
			*melement = codec->mix_line;
			break;
		case ESS_RECSRC_AUXACD:
			*melement = codec->mix_cd;
			break;
		case ESS_RECSRC_AUXB:
			*melement = codec->mix_iaccu;
			break;
		case ESS_RECSRC_NONE:
			*melement = codec->mix_oaccu;
			break;
		default:
			*melement = NULL;
		}
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1938_mixer_recmon(snd_kmixer_element_t * element,
				   int w_flag,
				   snd_kmixer_element_t ** melement)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	unsigned char reg, oval, nval;
	int change = 0;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	reg = snd_solo_read_cmd(codec, ESS_CMD_ANALOGCONTROL);
	oval = reg & 0x08;
	if (w_flag) {
		if (*melement == codec->mix_oaccu) {
			nval = 0x00;
		} else if (*melement == codec->mix_igain_v) {
			nval = 0x08;
		} else {
			nval = 0x00;
		}
		if ((change = (nval != oval)))
			snd_solo_write_reg(codec, ESS_CMD_ANALOGCONTROL, nval | (reg & 0xf7));
	} else {
		switch (oval) {
		case 0x00:
			*melement = codec->mix_oaccu;
			break;
		case 0x08:
			*melement = codec->mix_igain_v;
			break;
		default:
			*melement = NULL;
		}
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1938_mixer_opcm1_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUDIO2);
}

static int snd_es1938_mixer_omic_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_MICMIX);
}

static int snd_es1938_mixer_oline_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_LINE);
}

static int snd_es1938_mixer_ofm_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_FM);
}

static int snd_es1938_mixer_omono_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_MONO);
}

static int snd_es1938_mixer_ocd_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUXACD);
}

static int snd_es1938_mixer_oaux_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUXB);
}

static int snd_es1938_mixer_ipcm1_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUDIO2RECORD);
}

static int snd_es1938_mixer_imic_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_MICMIXRECORD);
}

static int snd_es1938_mixer_iline_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_LINERECORD);
}

static int snd_es1938_mixer_ifm_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_FMRECORD);
}

static int snd_es1938_mixer_imono_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_MONORECORD);
}

static int snd_es1938_mixer_icd_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUXACDRECORD);
}

static int snd_es1938_mixer_iaux_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume(element, w_flag, voices, ESSSB_IREG_AUXBRECORD);
}

static int snd_es1938_mixer_igain_v(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	return snd_es1938_controller_volume(element, w_flag, voices, ESS_CMD_RECLEVEL);
}

static int snd_es1938_mixer_output_s(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	int change = 0;
	int oleft, oright;
	spin_lock_irqsave(&codec->mixer_lock, flags);
	oleft = !(snd_solo_mixer_in(codec, 0x60) & 0x40);
	oright = !(snd_solo_mixer_in(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_solo_mixer_out(codec, ESSSB_IREG_MASTER_LEFT, 0x40, nleft ? 0x00 : 0x40);
			change = 1;
		}
		if (oright != nright) {
			snd_solo_mixer_out(codec, ESSSB_IREG_MASTER_RIGHT, 0x40, nright ? 0x00 : 0x40);
			change = 1;
		}
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1938_mixer_e3d(snd_kmixer_element_t *element, int w_flag, struct snd_mixer_element_3d_effect1 *effect1)
{
	es1938_t *codec = snd_magic_cast(es1938_t, element->private_data, -ENXIO);
	unsigned long flags;
	int change = 0;
	unsigned int osw, oval;

	spin_lock_irqsave(&codec->mixer_lock, flags);
	osw = !!(snd_solo_mixer_in(codec, ESSSB_IREG_SPATCONTROL) & 0x0c);
	oval = snd_solo_mixer_in(codec, ESSSB_IREG_SPATLEVEL) & 0x3f;
	if (!w_flag) {
		effect1->sw = osw;
		effect1->space = oval;
	} else {
		if (osw != effect1->sw) {
			snd_solo_mixer_out(codec, ESSSB_IREG_SPATCONTROL, 0x0c, effect1->sw ? 0x0c : 0x00);
			change = 1;
		}
		if (oval != effect1->space) {
			snd_solo_mixer_out(codec, ESSSB_IREG_SPATLEVEL, 0xff, effect1->space);
			change = 1;
		}
	}

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


static int snd_es1938_mixer_group_ctrl(snd_kmixer_group_t * group,
				       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)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	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(oel_v, 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(oel_s, 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_es1938_mixer_imux(codec->mix_imux, 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(oel_v, 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(oel_s, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, oel_s, 0);
				change = 1;
			}
		}
		if (iel_v && ictrl_v && ictrl_v(iel_v, 1, voices) > 0) {
			snd_mixer_element_value_change(file, iel_v, 0);
			change = 1;
		}
		if (mux_in) {
			snd_es1938_mixer_imux(codec->mix_imux, 0, &element);
			if (ugroup->capture & SND_MIXER_CHN_MASK_STEREO)
				element = mux_in;
			if (snd_es1938_mixer_imux(codec->mix_imux, 1, &element) > 0) {
				snd_mixer_element_value_change(file, codec->mix_imux, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_es1938_mixer_igain_g(snd_kmixer_group_t * group,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_igain_v,
					   snd_es1938_mixer_igain_v,
					   NULL, NULL,
					   NULL, NULL,
					   15,
					   codec->mix_iaccu);
}

static int snd_es1938_mixer_pcm1_g(snd_kmixer_group_t * group,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_opcm1_v,
					   snd_es1938_mixer_opcm1_v,
					   NULL, NULL,
					   codec->mix_ipcm1_v,
					   snd_es1938_mixer_ipcm1_v,
					   15,
					   NULL);
}

static int snd_es1938_mixer_mic_g(snd_kmixer_group_t * group,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_omic_v,
					   snd_es1938_mixer_omic_v,
					   NULL, NULL,
					   codec->mix_imic_v,
					   snd_es1938_mixer_imic_v,
					   15,
					   codec->mix_mic);
}

static int snd_es1938_mixer_line_g(snd_kmixer_group_t * group,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_oline_v,
					   snd_es1938_mixer_oline_v,
					   NULL, NULL,
					   codec->mix_iline_v,
					   snd_es1938_mixer_iline_v,
					   15,
					   codec->mix_line);
}

static int snd_es1938_mixer_fm_g(snd_kmixer_group_t * group,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_ofm_v,
					   snd_es1938_mixer_ofm_v,
					   NULL, NULL,
					   codec->mix_ifm_v,
					   snd_es1938_mixer_ifm_v,
					   15,
					   NULL);
}

static int snd_es1938_mixer_mono_g(snd_kmixer_group_t * group,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_omono_v,
					   snd_es1938_mixer_omono_v,
					   NULL, NULL,
					   codec->mix_imono_v,
					   snd_es1938_mixer_imono_v,
					   15,
					   NULL);
}

static int snd_es1938_mixer_cd_g(snd_kmixer_group_t * group,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_ocd_v,
					   snd_es1938_mixer_ocd_v,
					   NULL, NULL,
					   codec->mix_icd_v,
					   snd_es1938_mixer_icd_v,
					   15,
					   codec->mix_cd);
}

static int snd_es1938_mixer_aux_g(snd_kmixer_group_t * group,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_oaux_v,
					   snd_es1938_mixer_oaux_v,
					   NULL, NULL,
					   codec->mix_iaux_v,
					   snd_es1938_mixer_iaux_v,
					   15,
					   NULL);
}

static int snd_es1938_mixer_output_g(snd_kmixer_group_t * group,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	es1938_t *codec = snd_magic_cast(es1938_t, group->private_data, -ENXIO);
	return snd_es1938_mixer_group_ctrl(group, file, w_flag, ugroup,
					   codec->mix_output_v,
					   snd_es1938_mixer_output_v,
					   codec->mix_output_s,
					   snd_es1938_mixer_output_s,
					   NULL, NULL,
					   63,
					   codec->mix_oaccu);
}


int snd_solo_new_mixer(es1938_t * codec, int device, snd_pcm_t * _pcm, snd_kmixer_t ** rmixer)
{
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *pcm1_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 *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 err;
	/* FIXME */
	static struct snd_mixer_element_volume1_range db_range1[2] =
	{
		{0, 15, -3150, 0},
		{0, 15, -3150, 0}
	};
	static struct snd_mixer_element_volume1_range db_range2[2] =
	{
		{0, 15, -2850, 300},
		{0, 15, -2850, 300}
	};

	struct snd_mixer_element_volume1_range *opcm1_db_range = db_range1;
	struct snd_mixer_element_volume1_range *ipcm1_db_range = db_range1;
	struct snd_mixer_element_volume1_range *omic_db_range = db_range2;
	struct snd_mixer_element_volume1_range *imic_db_range = db_range2;
	struct snd_mixer_element_volume1_range *oline_db_range = db_range2;
	struct snd_mixer_element_volume1_range *iline_db_range = db_range2;
	struct snd_mixer_element_volume1_range *ofm_db_range = db_range1;
	struct snd_mixer_element_volume1_range *ifm_db_range = db_range1;
	struct snd_mixer_element_volume1_range *omono_db_range = db_range2;
	struct snd_mixer_element_volume1_range *imono_db_range = db_range2;
	struct snd_mixer_element_volume1_range *ocd_db_range = db_range2;
	struct snd_mixer_element_volume1_range *icd_db_range = db_range2;
	struct snd_mixer_element_volume1_range *oaux_db_range = db_range2;
	struct snd_mixer_element_volume1_range *iaux_db_range = db_range2;
	struct snd_mixer_element_volume1_range *igain_db_range = db_range2;
	static struct snd_mixer_element_volume1_range output_db_range[2] =
	{
		{0, 63, -3150, 0},
		{0, 63, -3150, 0}
	};
	static struct snd_mixer_element_3d_effect1_info einfo;

	snd_debug_check(rmixer == NULL, -EINVAL);
	*rmixer = NULL;
	snd_debug_check(codec == NULL || codec->card == NULL, -EINVAL);
	if ((err = snd_mixer_new(codec->card, "ES1938", device, &mixer)) < 0)
		return err;
	strcpy(mixer->name, "ESS Solo-1");

	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, SND_MIXER_MUX2_NONE, snd_es1938_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_es1938_mixer_igain_g, codec)) == NULL)
		goto __error;
	if ((igain_v = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, igain_db_range, snd_es1938_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_es1938_mixer_recmon, codec)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, igain_v, recmon))
		goto __error;

	/* 3D effect */
	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_es1938_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;


	/* PCM1 */
	if ((pcm1_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_es1938_mixer_pcm1_g, codec)) == NULL)
		goto __error;
	if ((pcm1 = snd_mixer_lib_pcm1(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK1, 1, &_pcm->device)) == NULL)
		goto __error;
	if ((opcm1_v = snd_mixer_lib_volume1(mixer, "PCM1 Volume", 0, 2, opcm1_db_range, snd_es1938_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_playback = pcm1;
	codec->mix_opcm1_v = opcm1_v;

	/* MIC */
	if ((mic_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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 */
	/* 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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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_es1938_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;
	/* 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_es1938_mixer_output_g, codec)) == NULL)
		goto __error;
	if ((output_v = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, output_db_range, snd_es1938_mixer_output_v, codec)) == NULL)
		goto __error;
	if ((output_s = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, snd_es1938_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_pcm1(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE1, 1, &_pcm->device)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, adc, input))
		goto __error;
	codec->mix_capture = input;

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

      __error:
	snd_device_free(codec->card, mixer);
	return -ENOMEM;
}

/* ---------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------- */

int snd_solo_create(snd_card_t * card,
		    struct pci_dev * pci,
		    snd_dma_t * dma1ptr,
		    snd_dma_t * dma2ptr,
		    snd_irq_t * irqptr,
		    int reverb,
		    int mge,
		    es1938_t ** rsolo)
{
	es1938_t *solo;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_solo_free,
		NULL,
		NULL
	};

	*rsolo = NULL;
	solo = snd_magic_kcalloc(es1938_t, 0, GFP_KERNEL);
	if (solo == NULL)
		return -ENOMEM;
	spin_lock_init(&solo->reg_lock);
	spin_lock_init(&solo->mixer_lock);
	solo->card = card;
	solo->pci = pci;
	solo->irqptr = irqptr;
	solo->dma1ptr = dma1ptr;
	solo->dma2ptr = dma2ptr;
	solo->io_port = pci_resource_start(pci, 0);
	solo->sb_port = pci_resource_start(pci, 1);
	solo->vc_port = pci_resource_start(pci, 2);
	solo->mpu_port = pci_resource_start(pci, 3);
	solo->game_port = pci_resource_start(pci, 4);
#ifdef SOLO_DDEBUG
	snd_printk("snd_solo_create: io: 0x%x, sb: 0x%x, vc: 0x%x, mpu: 0x%x, game: 0x%x\n",
		   solo->io_port, solo->sb_port, solo->vc_port, solo->mpu_port, solo->game_port);
#endif
	/* reset chip */
	snd_solo_reset(solo);

	/* configure native mode */

	/* enable bus master and i/o space */
	pci_write_config_word(pci, SL_PCI_COMMAND, 5);

	/* disable legacy audio */
	pci_write_config_word(pci, SL_PCI_LEGACYCONTROL, 0x805f);

	/* set DDMA base */
	solo->ddma_port = solo->vc_port + 0x00;		/* fix from Thomas Sailer */
	pci_write_config_word(pci, SL_PCI_DDMACONTROL, solo->ddma_port | 1);

	/* set DMA/IRQ policy */
	pci_write_config_dword(pci, SL_PCI_CONFIG, 0);

	/* enable Audio 1, Audio 2 and MPU401 IRQ */
	snd_solo_mask_irq(solo, 0xb0, 0xb0);

	/* reset DMA */
	outb(0xff, SLDM_REG(solo, DMACLEAR));

	/* enable bus mastering */
	pci_set_master(pci);

	if ((err = snd_device_new(card, SND_DEV_LOWLEVEL, solo, 0, &ops, NULL)) < 0) {
		snd_solo_free(solo);
		return err;
	}

	*rsolo = solo;
	return 0;
}

int snd_solo_free(es1938_t * solo)
{
	snd_magic_kfree(solo);
	return 0;
}

void snd_solo_midi(es1938_t * solo, mpu401_t * mpu)
{
	mpu->private_data = solo;
	mpu->open_input = NULL;	/* snd_solo_midi_input_open; */
	mpu->close_input = NULL;	/* snd_solo_midi_input_close; */
	mpu->open_output = NULL;	/* snd_solo_midi_output_open; */
}

/* --------------------------------------------------------------------
 * Interrupt handler
 * -------------------------------------------------------------------- */
void snd_solo_interrupt(es1938_t * solo)
{
	unsigned char status, audiostatus;

	
	status = inb(SLIO_REG(solo, IRQCONTROL));
#if 0
	printk("Solodebug - interrupt status: =0x%x\n", status);
#endif
	
	/* AUDIO 1 */
	if (status & 0x10) {
#if 0
                printk("Solodebug - AUDIO channel 1 interrupt\n");
		printk("Solodebug - AUDIO channel 1 DMAC DMA count: %u\n", inw(SLDM_REG(solo, DMACOUNT)));
		printk("Solodebug - AUDIO channel 1 DMAC DMA base: %u\n", inl(SLDM_REG(solo, DMABASE)));
		printk("Solodebug - AUDIO channel 1 DMAC DMA status: 0x%x\n", inl(SLDM_REG(solo, DMASTATUS)));
#endif
		if (solo->pcm) {
			snd_pcm_transfer_done(solo->capture_subchn);
			/* clear irq */
			audiostatus = inb(SLSB_REG(solo, STATUS));
#if 0
			printk("Solodebug - AUDIO channel 1 status: 0x%x\n", audiostatus);
#endif
		}
	}
	
	/* AUDIO 2 */
	if (status & 0x20) {
#if 0
                printk("Solodebug - AUDIO channel 2 interrupt\n");
		printk("Solodebug - AUDIO channel 2 DMAC DMA count: %u\n", inw(SLIO_REG(solo, AUDIO2DMACOUNT)));
		printk("Solodebug - AUDIO channel 2 DMAC DMA base: %u\n", inl(SLIO_REG(solo, AUDIO2DMAADDR)));

#endif
		if (solo->pcm) {
			/* clear irq */
			spin_lock(&solo->reg_lock);
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL2, 0x80, 0);
			spin_unlock(&solo->reg_lock);
			snd_pcm_transfer_done(solo->playback2_subchn);
		}
	}

	outb(solo->irqmask, SLIO_REG(solo, IRQCONTROL));
}

EXPORT_SYMBOL(snd_solo_create);
EXPORT_SYMBOL(snd_solo_free);
EXPORT_SYMBOL(snd_solo_interrupt);
EXPORT_SYMBOL(snd_solo_new_pcm);
EXPORT_SYMBOL(snd_solo_new_mixer);
EXPORT_SYMBOL(snd_solo_midi);

/*
 *  INIT part
 */

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

static void __exit alsa_es1938_exit(void)
{
}

module_init(alsa_es1938_init)
module_exit(alsa_es1938_exit)
