/*
 *  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>
 *  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:
 *    many
 *
 *  TODO:
 *    MPU401, OPL3, ADC
 *
 *   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 SOLO_DEBUG */
/* #define SOLO_IDEBUG */
/* #define SOLO_DEBUGIO */
/* #define SOLO_PDEBUG */
/* #define SOLO_DDEBUG */

#ifdef SOLO_DEBUGIO
#define SL_OUTB dbg_outb
#define SL_OUTW dbg_outw
#define SL_OUTL dbg_outl
#define SL_INB dbg_inb
#define SL_INW dbg_inw
static void dbg_outb(unsigned char val, int port)
{
	snd_printk("outb: 0x%x, 0x%x\n", val, port);
	outb(val, port);
}

static void dbg_outw(unsigned int val, int port)
{
	snd_printk("outw: 0x%x, 0x%x\n", val, port);
	outw(val, port);
}

static void dbg_outl(unsigned int val, int port)
{
	snd_printk("outl: 0x%x, 0x%x\n", val, port);
	outl(val, port);
}

static unsigned char dbg_inb(int port)
{
	unsigned char val = inb(port);
	snd_printk("inb: 0x%x, 0x%x\n", val, port);
	return val;
}

static unsigned char dbg_inw(int port)
{
	unsigned int val = inw(port);
	snd_printk("inw: 0x%x, 0x%x\n", val, port);
	return val;
}
#else
#define SL_OUTB outb
#define SL_OUTW outw
#define SL_OUTL outl
#define SL_INB inb
#define SL_INW inw
#endif

static void snd_solo_outbmask(int mask, int val, int reg)
{
	SL_OUTB((val & mask) | (inb(reg) & (~mask)), reg);
}

static void snd_solo_mixer_out(esssolo_t * solo, int reg, int mask, int val)
{
	int a;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_mixer_out: r: 0x%x, m: 0x%x, v: 0x%x\n", reg, mask, val);
#endif
	outb(reg, SLSB_REG(solo, MIXERADDR));
	a = (mask != 0xff) ? (inb(SLSB_REG(solo, MIXERDATA)) & (~mask)) : 0;
	outb((val & mask) | a, SLSB_REG(solo, MIXERDATA));
}

static int snd_solo_mixer_in(esssolo_t * solo, int reg)
{
	SL_OUTB(reg, SLSB_REG(solo, MIXERADDR));
	return SL_INB(SLSB_REG(solo, MIXERDATA));
}

static void snd_solo_write_cmd(esssolo_t * solo, int cmd)
{
	int i, v;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_write_cmd: 0x%x\n", cmd);
#endif
#define WRITE_LOOP_TIMEOUT 1000
	for (i = 0; i < WRITE_LOOP_TIMEOUT; i++) {
		if (!(v = inb(SLSB_REG(solo, READSTATUS)) & 0x80)) {
			outb(cmd, SLSB_REG(solo, WRITEDATA));
			return;
		}
		snd_delay(1);
	}
	printk("snd_solo_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v);
}

static int snd_solo_get_byte(esssolo_t * solo)
{
	int i, v;
#define GET_LOOP_TIMEOUT 100
	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;
}

static void snd_solo_write_reg(esssolo_t * solo, int reg, int val)
{
	snd_delay(1);
	snd_solo_write_cmd(solo, reg);
	snd_delay(1);
	snd_solo_write_cmd(solo, val);
}

static int snd_solo_read_cmd(esssolo_t * solo, int cmd)
{
	int v;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_read_cmd: start 0x%x\n", cmd);
#endif
	snd_delay(1);
	snd_solo_write_reg(solo, ESS_CMD_READREG, cmd);
	snd_delay(1);
	v = snd_solo_get_byte(solo);
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_read_cmd: end 0x%x 0x%x\n", cmd, v);
#endif
	return v;
}


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

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

static unsigned int snd_solo_set_dac_rate(esssolo_t * solo, unsigned int rate, int pcmnum, 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) {
		int f = (((10 * SOLO_FILTERCLK) / (8 * 82 * r0)) & 0xff);
#ifdef SOLO_PDEBUG
		int f0 = SOLO_FILTERCLK / f / 82;
		snd_printk("snd_solo_set_dac_rate: rate: %d (0x%x), filter: %d (0x%x)\n", r0, x0, f0, 256 - f);
#endif
		if (pcmnum) {
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2SAMPLE, 0xff, x0);
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2FILTER, 0xff, 256 - f);
		} else {
			snd_solo_write_cmd(solo, ESS_CMD_EXTSAMPLERATE);
			snd_solo_write_cmd(solo, x0);
			snd_solo_write_cmd(solo, ESS_CMD_FILTERDIV);
			snd_solo_write_cmd(solo, 256 - f);
		}
	}
	return r0;
}

static void snd_solo_enabledma(esssolo_t * solo, int pcmnum)
{
	if (!pcmnum)
		SL_OUTB(SL_INB(SLDM_REG(solo, DMAMASK)) & 0xfe, SLDM_REG(solo, DMAMASK));
	else
		SL_OUTB((SL_INB(SLIO_REG(solo, AUDIO2MODE)) & 0xfd) | 2, SLIO_REG(solo, AUDIO2MODE));
}

static void snd_solo_disabledma(esssolo_t * solo, int pcmnum)
{
	if (!pcmnum)
		SL_OUTB((SL_INB(SLDM_REG(solo, DMAMASK)) & 0xfe) | 1, SLDM_REG(solo, DMAMASK));
	else
		SL_OUTB(SL_INB(SLIO_REG(solo, AUDIO2MODE)) & 0xfd, SLIO_REG(solo, AUDIO2MODE));
}

static void snd_solo_setdma(esssolo_t * solo, void *buffer, int size, char mode, char command, int pcmnum)
{
	snd_solo_disabledma(solo, pcmnum);

	if (!pcmnum) {
		SL_OUTB(mode, SLDM_REG(solo, DMAMODE));
		SL_OUTL(virt_to_bus(buffer), SLDM_REG(solo, DMABASE));
		SL_OUTW(size, SLDM_REG(solo, DMACOUNT));
		SL_OUTB(command, SLDM_REG(solo, DMACOMMAND));
	} else {
		SL_OUTL(virt_to_bus(buffer), SLIO_REG(solo, AUDIO2DMAADDR));
		SL_OUTW(size, SLIO_REG(solo, AUDIO2DMACOUNT));
		SL_OUTB(mode, SLIO_REG(solo, AUDIO2MODE));
	}

	snd_solo_enabledma(solo, pcmnum);
}

static void snd_solo_togglecodec(esssolo_t * solo, int up, int pcmnum)
{
	if (!pcmnum)
		snd_solo_write_reg(solo, ESS_CMD_DMACONTROL,
				   (snd_solo_read_cmd(solo, ESS_CMD_DMACONTROL) & 0xfe) | (up ? 1 : 0));
	else
		snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL1, 3, up ? 3 : 0);
}

static void snd_solo_waitcodec(esssolo_t * solo, int up, int pcmnum)
{
	if (!pcmnum)
		snd_solo_write_reg(solo, ESS_CMD_DMACONTROL,
				   (snd_solo_read_cmd(solo, ESS_CMD_DMACONTROL) & 0xfe) | (up ? 1 : 0));
	else
		snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL1, 3, up ? 3 : 0);
}

//      snd_solo_write_cmd(solo, up ? ESS_CMD_ENABLEAUDIO1 : ESS_CMD_STOPAUDIO1);
//      snd_solo_write_cmd(solo, up ? ESS_CMD_CONTDMA : ESS_CMD_PAUSEDMA);

static void snd_solo_trigger(esssolo_t * solo, int up, int pcmnum)
{
	unsigned long flags;

	spin_lock_irqsave(&solo->reg_lock, flags);
	snd_solo_waitcodec(solo, up, pcmnum);
	spin_unlock_irqrestore(&solo->reg_lock, flags);
#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_trigger: %i, %i\n", pcmnum, up);
#endif
}

/*
 *  PCM part
 */

static int snd_solo_playback_ioctl(void *private_data,
				   snd_pcm_subchn_t * subchn,
				   unsigned int cmd,
				   unsigned long *arg)
{
	esssolo_t *solo = (esssolo_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_solo_set_dac_rate(solo, subchn1->rate, 0, 0);
		return 0;
	}
	return -ENXIO;
}

static int snd_solo_playback2_ioctl(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    unsigned int cmd,
				    unsigned long *arg)
{
	esssolo_t *solo = (esssolo_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_solo_set_dac_rate(solo, subchn1->rate, 1, 0);
		return 0;
	}
	return -ENXIO;
}

static void snd_solo_playback_trigger(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      int up)
{
	snd_solo_trigger((esssolo_t *) private_data, up, 0);
}

static void snd_solo_playback2_trigger(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       int up)
{
	snd_solo_trigger((esssolo_t *) private_data, up, 1);
}

static void snd_solo_playback_prepare(void *private_data,
				      snd_pcm_subchn_t * subchn,
				      unsigned char *buffer,
				      unsigned int size,
				      unsigned int offset,
				      unsigned int count)
{
	unsigned long flags, h;
	esssolo_t *solo = (esssolo_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	int u, is8, mono;

	mono = (subchn1->voices > 1) ? 0 : 1;
	is8 = (subchn1->mode & SND_PCM1_MODE_16) ? 0 : 1;
	u = subchn1->mode & SND_PCM1_MODE_U;

	spin_lock_irqsave(&solo->reg_lock, flags);

#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_playback_prepare: buf: 0x%x, size: 0x%x, ofs: 0x%x, cnt: 0x%x\n",
		(unsigned int) virt_to_bus(buffer), size, offset, count);
#endif

	count >>= 1;
	count = 0x10000 - count;

	/* 1. reset *//* !!! FIXME !!! */

	snd_delay(1);		/* !!! FIXME !!! */

	/* 2. enable extended mode */
	snd_solo_write_cmd(solo, ESS_CMD_ENABLEEXT);

	/* 3. program direction and type */
	snd_solo_write_reg(solo, ESS_CMD_DMACONTROL, (snd_solo_read_cmd(solo, ESS_CMD_DMACONTROL) & 0xfc) | 4);
	h = (snd_solo_read_cmd(solo, ESS_CMD_ANALOGCONTROL) & 0xfc) | (mono ? 2 : 1);
	snd_solo_write_reg(solo, ESS_CMD_ANALOGCONTROL, h);
	snd_solo_write_reg(solo, ESS_CMD_DMATYPE, 2);

	/* 4. set clock and counters */
	snd_solo_set_dac_rate(solo, subchn1->real_rate, 0, 1);
	snd_solo_write_reg(solo, ESS_CMD_DMACNTRELOADL, count & 0xff);
	snd_solo_write_reg(solo, ESS_CMD_DMACNTRELOADH, count >> 8);

	/* 5. initialize and configure DACs */
	snd_solo_write_reg(solo, ESS_CMD_SETFORMAT, u ? 0x80 : 0);
	snd_solo_write_reg(solo, ESS_CMD_SETFORMAT2, u ? 0x51 : 0x71);
	snd_solo_write_reg(solo, ESS_CMD_SETFORMAT2,
	      0x90 | (u ? 0 : 0x20) | (is8 ? 0 : 4) | (mono ? 0x40 : 8));

	/* 6. 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));

	/* 7. configure system interrupt controller and DMA controller */

	snd_solo_setdma(solo, buffer, size, 0x18, 0xc4, 0);

	/* disable switched capacitator filter */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2MODE, 4, 4);

	/* 8. start DMA */

	/* 9. delay 100ms, enable DAC input */
	/* snd_delay(10); *//* !!! FIXME !!! */
	snd_solo_write_cmd(solo, ESS_CMD_ENABLEAUDIO1);


	snd_solo_togglecodec(solo, 1, 0);
	snd_solo_waitcodec(solo, 0, 0);

	spin_unlock_irqrestore(&solo->reg_lock, flags);

}

static void snd_solo_playback2_prepare(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       unsigned char *buffer,
				       unsigned int size,
				       unsigned int offset,
				       unsigned int count)
{
	unsigned long flags;
	esssolo_t *solo = (esssolo_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	int u, is8, mono, save;

	mono = (subchn1->voices > 1) ? 0 : 1;
	is8 = (subchn1->mode & SND_PCM1_MODE_16) ? 0 : 1;
	u = subchn1->mode & SND_PCM1_MODE_U;

	spin_lock_irqsave(&solo->reg_lock, flags);

#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_playback2_prepare: buf: 0x%x, size: 0x%x, ofs: 0x%x, cnt: 0x%x\n",
		(unsigned int) virt_to_bus(buffer), size, offset, count);
#endif

	count >>= 1;
	count = 0x10000 - count;
	/* size--; */

	/* 1. reset */
	save = snd_solo_mixer_in(solo, ESSSB_IREG_AUDIO2);

	SL_OUTB(2, SLSB_REG(solo, RESET));
	snd_delay(1);
	SL_OUTB(0, SLSB_REG(solo, RESET));

	snd_delay(1);		/* !!! FIXME !!! */

	/* 2. program auto-init dma */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL1, 0xff, 0x10);

	/* 3. set clock and counters */
	snd_solo_set_dac_rate(solo, subchn1->real_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, 0xff, 0x40 | (u ? 0 : 4) | (mono ? 0 : 2) | (is8 ? 0 : 1));

	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2MODE, 0xfb, 0x12);

	/* 5. enable IRQ channel */

	/* 6. program DMA */
	snd_solo_setdma(solo, buffer, size, 0x8, 0, 1);

	/* 7. start DMA */


	/* 8. delay, enable Audio 2 DAC playback */
	/*  snd_delay( 10 ); *//* !!! FIXME !!! */
	snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2, 0xff, save);

	snd_solo_enabledma(solo, 1);
	snd_solo_togglecodec(solo, 1, 1);
	snd_solo_waitcodec(solo, 0, 1);

	spin_unlock_irqrestore(&solo->reg_lock, flags);

}

static int snd_solo_playback_open(void *private_data,
				  snd_pcm_subchn_t * subchn)
{
	esssolo_t *solo = (esssolo_t *) private_data;
	int err;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback_open\n");
#endif

	if ((err = snd_pcm1_dma_alloc(subchn, solo->dma1ptr, "ESS Solo-1 (playback)")) < 0)
		return err;
	solo->playback_subchn = subchn;
	solo->playback_subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	return 0;
}

static int snd_solo_playback2_open(void *private_data,
				   snd_pcm_subchn_t * subchn)
{
	esssolo_t *solo = (esssolo_t *) private_data;
	int err;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback2_open\n");
#endif

	if ((err = snd_pcm1_dma_alloc(subchn, solo->dma2ptr,
				      "ESS Solo-1 (playback 2)")) < 0)
		return err;
	solo->playback2_subchn = subchn;
	solo->playback2_subchn1 = (snd_pcm1_subchn_t *) subchn->private_data;
	return 0;
}

static void snd_solo_playback_close(void *private_data,
				    snd_pcm_subchn_t * subchn)
{
	esssolo_t *solo = (esssolo_t *) private_data;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback_close\n");
#endif

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

static void snd_solo_playback2_close(void *private_data,
				     snd_pcm_subchn_t * subchn)
{
	esssolo_t *solo = (esssolo_t *) private_data;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback2_close\n");
#endif

	solo->playback2_subchn = NULL;
	solo->playback2_subchn1 = NULL;
	snd_pcm1_dma_free(subchn);
}

static unsigned int snd_solo_playback_pointer(void *private_data,
					      snd_pcm_subchn_t * subchn,
					      unsigned int used_size)
{
	esssolo_t *solo = (esssolo_t *) private_data;
	int v;
/*  v = inw( SLDM_REG( solo, DMACOUNT ) ); */
	v = used_size - SL_INW(SLIO_REG(solo, AUDIO2DMACOUNT));
#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_playback_pointer: us: 0x%x, v: 0x%x\n", used_size, v);
#endif
	return v;
}

static unsigned int snd_solo_playback2_pointer(void *private_data,
					       snd_pcm_subchn_t * subchn,
					       unsigned int used_size)
{
	esssolo_t *solo = (esssolo_t *) private_data;

	int v;
	v = inw(SLDM_REG(solo, DMACOUNT));
/*      v = used_size - SL_INW(SLIO_REG(solo, AUDIO2DMACOUNT)); */
#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_playback_pointer: us: 0x%x, v: 0x%x\n", used_size, v);
#endif
	return v;
}

static struct snd_stru_pcm1_hardware snd_solo_playback =
{
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	0,			/* align value */
	6,			/* minimal fragment */
	6000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_solo_playback_open,
	snd_solo_playback_close,
	snd_solo_playback_ioctl,
	snd_solo_playback_prepare,
	snd_solo_playback_trigger,
	snd_solo_playback_pointer,
	snd_pcm1_playback_dma_ulaw,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_solo_playback2 =
{
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	0,			/* align value */
	6,			/* minimal fragment */
	6000,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_solo_playback2_open,
	snd_solo_playback2_close,
	snd_solo_playback2_ioctl,
	snd_solo_playback2_prepare,
	snd_solo_playback2_trigger,
	snd_solo_playback2_pointer,
	snd_pcm1_playback_dma_ulaw,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

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

static void snd_solo_pcm2_free(void *private_data)
{
	esssolo_t *solo = (esssolo_t *) private_data;
	solo->pcm2 = NULL;
}

snd_pcm_t *snd_solo_pcm(esssolo_t * solo)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;

	pcm = snd_pcm1_new_device(solo->card, "es1938", 1, 0);
	if (!pcm)
		return NULL;
	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_solo_playback, sizeof(snd_solo_playback));
	pchn1->private_data = solo;
	pcm->private_data = solo;
	pcm->private_free = snd_solo_pcm_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
	    SND_PCM_INFO_PLAYBACK;
	strcpy(pcm->name, "ESS Solo-1");
	return solo->pcm = pcm;
}

snd_pcm_t *snd_solo_pcm2(esssolo_t * solo)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;

	pcm = snd_pcm1_new_device(solo->card, "es1938/2", 1, 0);
	if (!pcm)
		return NULL;
	pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_solo_playback2, sizeof(snd_solo_playback));
	pchn1->private_data = solo;
	pcm->private_data = solo;
	pcm->private_free = snd_solo_pcm2_free;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
	    SND_PCM_INFO_PLAYBACK;
	strcpy(pcm->name, "ESS Solo-1 (2)");
	return solo->pcm2 = pcm;
}

/*
 *  Mixer part
 */

static int snd_es1938_mixer_volume(esssolo_t * codec, int w_flag, int *voices, int reg)
{
	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\n", reg, snd_solo_mixer_in(codec, rg));
#endif
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return change;
}

static int snd_es1938_mixer_output_v(void *private_data, int w_flag, int *voices)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	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(void *private_data,
				 int w_flag,
				 snd_kmixer_element_t ** element)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	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 (*element == codec->mix_mic) {
			nval = ESS_RECSRC_MIC;
		} else if (*element == codec->mix_line) {
			nval = ESS_RECSRC_LINE;
		} else if (*element == codec->mix_cd) {
			nval = ESS_RECSRC_AUXACD;
		} else if (*element == codec->mix_iaccu) {
			nval = ESS_RECSRC_AUXB;
		} else if (*element == 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:
			*element = codec->mix_mic;
			break;
		case ESS_RECSRC_LINE:
			*element = codec->mix_line;
			break;
		case ESS_RECSRC_AUXACD:
			*element = codec->mix_cd;
			break;
		case ESS_RECSRC_AUXB:
			*element = codec->mix_iaccu;
			break;
		case ESS_RECSRC_NONE:
			*element = codec->mix_oaccu;
			break;
		default:
			*element = NULL;
		}
	}
	spin_unlock_irqrestore(&codec->mixer_lock, flags);
	return change;
}

static int snd_es1938_mixer_recmon(void *private_data,
				   int w_flag,
				   snd_kmixer_element_t ** element)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	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 (*element == codec->mix_oaccu) {
			nval = 0x00;
		} else if (*element == 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:
			*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_es1938_mixer_opcm1_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUDIO1);
}

static int snd_es1938_mixer_opcm2_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUDIO2);
}

static int snd_es1938_mixer_omic_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_MICMIX);
}

static int snd_es1938_mixer_oline_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_LINE);
}

static int snd_es1938_mixer_ofm_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_FM);
}

static int snd_es1938_mixer_omono_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_MONO);
}

static int snd_es1938_mixer_ocd_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUXACD);
}

static int snd_es1938_mixer_oaux_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUXB);
}

static int snd_es1938_mixer_ipcm1_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUDIO2RECORD);
}

static int snd_es1938_mixer_imic_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_MICMIXRECORD);
}

static int snd_es1938_mixer_iline_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_LINERECORD);
}

static int snd_es1938_mixer_ifm_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_FMRECORD);
}

static int snd_es1938_mixer_imono_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_MONORECORD);
}

static int snd_es1938_mixer_icd_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUXACDRECORD);
}

static int snd_es1938_mixer_iaux_v(void *private_data, int w_flag, int *voices)
{
	return snd_es1938_mixer_volume((esssolo_t *) private_data, w_flag, voices, ESSSB_IREG_AUXBRECORD);
}

static int snd_es1938_mixer_igain_v(void *private_data, int w_flag, int *voices)
{
	return 0;
	/* this should be CMD to microcontroller, not to sb registers !!! */
	/* snd_es1938_mixer_volume((esssolo_t *)private_data, w_flag, voices, 0xb4); */
}

static int snd_es1938_mixer_output_s(void *private_data, int w_flag, unsigned int *bitmap)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	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(void *private_data, int w_flag, struct snd_mixer_element_3d_effect1 *effect1)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	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(esssolo_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_es1938_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_es1938_mixer_imux(codec, 0, &element);
			if (ugroup->capture & SND_MIXER_CHN_MASK_STEREO)
				element = mux_in;
			if (snd_es1938_mixer_imux(codec, 1, &element) > 0) {
				snd_mixer_element_value_change(file, codec->mix_imux, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_es1938_mixer_igain_g(void *private_data,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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_pcm2_g(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, file, w_flag, ugroup,
					   codec->mix_opcm2_v,
					   snd_es1938_mixer_opcm2_v,
					   NULL, NULL,
					   NULL, NULL,
					   15,
					   NULL);
}

static int snd_es1938_mixer_mic_g(void *private_data,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	esssolo_t *codec = (esssolo_t *) private_data;
	return snd_es1938_mixer_group_ctrl(codec, 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);
}


snd_kmixer_t *snd_solo_mixer(esssolo_t * codec, int pcm1_num, int pcm2_num)
{
	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;
	/* 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 *opcm2_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;

	if (!codec || !codec->card)
		return NULL;
	mixer = snd_mixer_new(codec->card, "ES1938");
	if (!mixer)
		return NULL;
	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_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, "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_opcm1_v = opcm1_v;

	/* PCM2 */
	if ((pcm2_g = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 1, SND_MIXER_OSS_ALTPCM, snd_es1938_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_es1938_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_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_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;
}

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

/*

 */

esssolo_t *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)
{
	esssolo_t *solo;
	int i;
	int c;

	solo = (esssolo_t *) snd_kcalloc(sizeof(esssolo_t), GFP_KERNEL);
	if (!solo)
		return NULL;
	solo->reg_lock = SPIN_LOCK_UNLOCKED;
	solo->card = card;
	solo->pci = pci;
	solo->irqptr = irqptr;
	solo->dma1ptr = dma1ptr;
	solo->dma2ptr = dma2ptr;
#ifdef NEW_PCI
	solo->io_port = pci->resource[0].start;
	solo->sb_port = pci->resource[1].start;
	solo->vc_port = pci->resource[2].start;
	solo->mpu_port = pci->resource[3].start;
	solo->game_port = pci->resource[4].start;
#else
	solo->io_port = pci->base_address[0] & ~PCI_BASE_ADDRESS_SPACE;
	solo->sb_port = pci->base_address[1] & ~PCI_BASE_ADDRESS_SPACE;
	solo->vc_port = pci->base_address[2] & ~PCI_BASE_ADDRESS_SPACE;
	solo->mpu_port = pci->base_address[3] & ~PCI_BASE_ADDRESS_SPACE;
	solo->game_port = pci->base_address[4] & ~PCI_BASE_ADDRESS_SPACE;
#endif
#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_outbmask(1, 1, SLSB_REG(solo, RESET));
	inb(SLSB_REG(solo, RESET));
	snd_solo_outbmask(1, 0, SLSB_REG(solo, RESET));
#define INIT_LOOP_TIMEOUT 1000
	for (i = 0; i < INIT_LOOP_TIMEOUT; i++) {
		snd_delay(1);
		if (inb(SLSB_REG(solo, STATUS)) & 0x80) {
			if (inb(SLSB_REG(solo, READDATA)) == 0xaa)
				break;
		}
	}
	if (i == INIT_LOOP_TIMEOUT)
		snd_printk("ESS Solo-1 reset failed\n");

	/* 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 + 0x10;		/* fix from Thomas Sailer */
	pci_write_config_word(pci, SL_PCI_DDMACONTROL, solo->ddma_port | 1);

	/* set DMA/IRQ policy */
	pci_read_config_dword(pci, SL_PCI_CONFIG, &c);
	c &= (~(0x700 | 0x6000));
	pci_write_config_dword(pci, SL_PCI_CONFIG, c);

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

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

	/* reset FIFO */
	SL_OUTB(3, SLSB_REG(solo, RESET));
	snd_delay(1);
	SL_OUTB(0, SLSB_REG(solo, RESET));

	return solo;
}

void snd_solo_free(esssolo_t * solo)
{
	snd_kfree(solo);
}

void snd_solo_midi(esssolo_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; */
}

void snd_solo_interrupt(esssolo_t * solo)
{
	unsigned char status, s1;
	unsigned long flags;

#ifdef SOLO_IDEBUG
	snd_printk("snd_solo_interrupt start\n");
#endif
	spin_lock_irqsave(&solo->reg_lock, flags);
	outb(0, SLIO_REG(solo, IRQCONTROL));
	status = inb(SLIO_REG(solo, IRQCONTROL));
	s1 = inb(SLSB_REG(solo, STATUS));
	if (status & 0x20) {
		if (solo->pcm2) {
			snd_solo_mixer_out(solo, ESSSB_IREG_AUDIO2CONTROL2, 0x80, 0);
			solo->playback2_subchn1->ack(solo->playback2_subchn);
#ifdef SOLO_IDEBUG
			{
				int v, v2;
				v = inw(SLIO_REG(solo, AUDIO2DMACOUNT));
				v2 = snd_solo_mixer_in(solo, ESSSB_IREG_AUDIO2TCOUNTL);
				v2 += snd_solo_mixer_in(solo, ESSSB_IREG_AUDIO2TCOUNTH) << 8;
				v2 <<= 1;
				v2 = 0x10000 - v2;
				snd_printk("snd_solo_interrupt playback ack, dmaaddr: 0x%x, dmacnt: 0x%x, cnt: 0x%x, pcm1: 0x%x\n",
					   inl(SLIO_REG(solo, AUDIO2DMAADDR)), v, v2, pcm1->playback.processed_bytes);
			}
#endif
		}
	}
	if (status & 0x10) {
		if (solo->pcm) {
			solo->playback_subchn1->ack(solo->playback_subchn);
#ifdef SOLO_IDEBUG
			{
				snd_printk("snd_solo_interrupt playback2 ack\n");
			}
#endif
		}
	}
#ifdef SOLO_IDEBUG
	snd_printk("snd_solo_interrupt: status 0x%x, s1 0x%x, mask 0x%x\n", status, s1, solo->irqmask);
#endif
	outb(solo->irqmask, SLIO_REG(solo, IRQCONTROL));
	spin_unlock_irqrestore(&solo->reg_lock, flags);
#ifdef SOLO_IDEBUG
	snd_printk("snd_solo_interrupt end\n");
#endif
}

EXPORT_SYMBOL(snd_solo_create);
EXPORT_SYMBOL(snd_solo_free);
EXPORT_SYMBOL(snd_solo_interrupt);
EXPORT_SYMBOL(snd_solo_pcm);
EXPORT_SYMBOL(snd_solo_pcm2);
EXPORT_SYMBOL(snd_solo_mixer);
EXPORT_SYMBOL(snd_solo_midi);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
