/*
 *  Copyright (c) by Jaromir Koutek <miri@punknet.cz>,
 *                   Jaroslav Kysela <perex@suse.cz>,
 *                   Thomas Sailer <sailer@ife.ee.ethz.ch>
 *  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 "driver.h"
#include "info.h"
#include "esssolo1.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, READSTATUS))) & 0x40)
			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_solo_write_cmd(solo, reg);
	snd_solo_write_cmd(solo, val);
}

static int snd_solo_read_cmd(esssolo_t * solo, int cmd)
{
	int i, v;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_read_cmd: start 0x%x\n", cmd);
#endif
	snd_solo_write_reg(solo, SL_CMD_READREG, cmd);
	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, SLSB_IREG_AUDIO2SAMPLE, 0xff, x0);
			snd_solo_mixer_out(solo, SLSB_IREG_AUDIO2FILTER, 0xff, 256 - f);
		} else {
			snd_solo_write_cmd(solo, SL_CMD_EXTSAMPLERATE);
			snd_solo_write_cmd(solo, x0);
			snd_solo_write_cmd(solo, SL_CMD_FILTERDIV);
			snd_solo_write_cmd(solo, 256 - f);
		}
	}
	return r0;
}

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

	snd_spin_lock(solo, reg, &flags);
	if (!pcmnum) {
		int i;
//      unsigned char h1, h, h2;
		//      h1 = snd_solo_read_cmd(solo, SL_CMD_DMACONTROL);
		//        h = h1 & 0xfe;
		//      if (up)
		//          h |= 1;
		snd_solo_write_reg(solo, SL_CMD_DMACONTROL, up ? 5 : 0);
		snd_solo_write_cmd(solo, up ? SL_CMD_ENABLEAUDIO1 : SL_CMD_STOPAUDIO1);
//      h2 = snd_solo_read_cmd(solo, SL_CMD_DMACONTROL);
		//        printk("up %d\n", up);
		//      printk("dma control: 0x%x -> 0x%x/0x%x\n", h1, h | up, h2);
		//        printk("irq control: 0x%x\n", snd_solo_read_cmd(solo, SL_CMD_IRQCONTROL));
		//        printk("drq control: 0x%x\n", snd_solo_read_cmd(solo, SL_CMD_DRQCONTROL));
		/* !!! FIXME !!! */
#if 0
		snd_solo_write_cmd(solo, up ? SL_CMD_CONTDMA : SL_CMD_PAUSEDMA);
		snd_solo_write_cmd(solo, SL_CMD_AUDIO1STATUS);
#define XREAD_LOOP_TIMEOUT 1000
		for (i = 0; i < XREAD_LOOP_TIMEOUT; i++) {
			if (inb(SLSB_REG(solo, STATUS)) & 0x80)
				break;
			snd_delay(1);
		}
		if (i == XREAD_LOOP_TIMEOUT) {
			printk("snd_solo_read_cmd timeout\n");
			goto __end;
		}
		h = inb(SLSB_REG(solo, READDATA));

		printk("dma enable: 0x%x\n", h);
#endif
	} else {
		snd_solo_mixer_out(solo, SLSB_IREG_AUDIO2CONTROL1, 3, up ? 3 : 0);
		SL_OUTB(up ? 0xa : 8, SLIO_REG(solo, AUDIO2MODE));
/*  snd_solo_mask_irq( solo, 0x20, up ? 0x20 : 0 ); */
/*   snd_solo_write_cmd( solo, up ? SL_CMD_CONTDMA : SL_CMD_PAUSEDMA ); */
	}
      __end:
	snd_spin_unlock(solo, reg, &flags);
#ifdef SOLO_PDEBUG
	snd_printk("snd_solo_trigger: %i, %i\n", pcmnum, up);
#endif
}

/*
 *  PCM part
 */

static int snd_solo_playback_ioctl(snd_pcm1_t * pcm1, unsigned int cmd, unsigned long *arg)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = snd_solo_set_dac_rate(solo, pcm1->playback.rate, 0, 0);
		return 0;
	}
	return -ENXIO;
}

static int snd_solo_playback2_ioctl(snd_pcm1_t * pcm1, unsigned int cmd, unsigned long *arg)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		pcm1->playback.real_rate = snd_solo_set_dac_rate(solo, pcm1->playback.rate, 1, 0);
		return 0;
	}
	return -ENXIO;
}

static void snd_solo_playback_trigger(snd_pcm1_t * pcm1, int up)
{
	snd_solo_trigger((esssolo_t *) pcm1->private_data, up, 0);
}

static void snd_solo_playback2_trigger(snd_pcm1_t * pcm1, int up)
{
	snd_solo_trigger((esssolo_t *) pcm1->private_data, up, 1);
}

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

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

	snd_spin_lock(solo, reg, &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 = 0x10000 - count;

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

	snd_solo_mask_irq(solo, 0x40, 0);
	snd_delay(1);		/* !!! FIXME !!! */

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

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

	/* 4. set clock and counters */
	snd_solo_set_dac_rate(solo, pcm1->playback.real_rate, 0, 1);
	snd_solo_write_reg(solo, SL_CMD_DMACNTRELOADL, count & 0xff);
	snd_solo_write_reg(solo, SL_CMD_DMACNTRELOADH, count >> 8);

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

	/* 6. enable/select DMA channel and IRQ channel */
	snd_solo_write_reg(solo, SL_CMD_IRQCONTROL, 0x50);
	snd_solo_write_reg(solo, SL_CMD_DRQCONTROL, 0x50);

	/* !!! FIXME !!! */
	snd_solo_mask_irq(solo, 0x40, 0x40);

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

	SL_OUTB(0xff, SLDM_REG(solo, DMACLEAR));
	SL_OUTB(1, SLDM_REG(solo, DMAMASK));
	SL_OUTB(0x18, SLDM_REG(solo, DMAMODE));
	SL_OUTL(virt_to_bus(buffer), SLDM_REG(solo, DMABASE));
	SL_OUTW(size, SLDM_REG(solo, DMACOUNT));
	SL_OUTB(0xc4, SLDM_REG(solo, DMACOMMAND));
	SL_OUTB(0, SLDM_REG(solo, DMAMASK));

	/* 8. start DMA */
/*      h = snd_solo_read_cmd(solo, SL_CMD_DMACONTROL) | 1;
 *       printk("startdma: 0x%x\n", h);
 *      snd_solo_write_cmd(solo, SL_CMD_DMACONTROL);
 *      snd_solo_write_cmd(solo, h); */

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

	snd_spin_unlock(solo, reg, &flags);

}

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

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

	snd_spin_lock(solo, reg, &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;

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

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

	snd_solo_mask_irq(solo, 0x20, 0);
	snd_delay(1);		/* !!! FIXME !!! */

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

	/* 3. set clock and counters */
	snd_solo_set_dac_rate(solo, pcm1->playback.real_rate, 1, 1);
	snd_solo_mixer_out(solo, SLSB_IREG_AUDIO2TCOUNTL, 0xff, count & 0xff);
	snd_solo_mixer_out(solo, SLSB_IREG_AUDIO2TCOUNTH, 0xff, count >> 8);

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

	snd_solo_mixer_out(solo, SLSB_IREG_AUDIO2MODE, 0xff, 0x12);

	/* 5. enable IRQ channel */
	snd_solo_mask_irq(solo, 0x20, 0x20);

	/* 6. program DMA */
	SL_OUTL(virt_to_bus(buffer), SLIO_REG(solo, AUDIO2DMAADDR));
	SL_OUTW(size, SLIO_REG(solo, AUDIO2DMACOUNT));
	SL_OUTB(0x8, SLIO_REG(solo, AUDIO2MODE));

	/* 7. start DMA */
/*  snd_solo_mixer_out( solo, SLSB_IREG_AUDIO2CONTROL1, 3, 3 ); */

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

	snd_spin_unlock(solo, reg, &flags);

}

static int snd_solo_playback_open(snd_pcm1_t * pcm1)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;
	int err;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback_open\n");
#endif

	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, solo->dma1ptr, "ESS Solo-1 (playback)")) < 0)
		return err;
	return 0;
}

static int snd_solo_playback2_open(snd_pcm1_t * pcm1)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;
	int err;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback2_open\n");
#endif

	if ((err = snd_pcm1_dma_alloc(pcm1, SND_PCM1_PLAYBACK, solo->dma2ptr, "ESS Solo-1 (playback 2)")) < 0)
		return err;
	return 0;
}

static void snd_solo_playback_close(snd_pcm1_t * pcm1)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback_close\n");
#endif

	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, solo->dma1ptr);
}

static void snd_solo_playback2_close(snd_pcm1_t * pcm1)
{
	esssolo_t *solo = (esssolo_t *) pcm1->private_data;
#ifdef SOLO_DEBUG
	snd_printk("snd_solo_playback2_close\n");
#endif

	snd_pcm1_dma_free(pcm1, SND_PCM1_PLAYBACK, solo->dma2ptr);
}

static unsigned int snd_solo_playback_pointer(snd_pcm1_t * pcm1, unsigned int used_size)
{
	esssolo_t *solo = (esssolo_t *) pcm1->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(snd_pcm1_t * pcm1, unsigned int used_size)
{
	esssolo_t *solo = (esssolo_t *) pcm1->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 =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* 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 =
{
	NULL,			/* private data */
	NULL,			/* private_free */
	SND_PCM1_HW_AUTODMA,	/* flags */
	SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* 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_t *pcm1;

	pcm = snd_pcm1_new_device(solo->card, "es1938");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	memcpy(&pcm1->playback.hw, &snd_solo_playback, sizeof(snd_solo_playback));
	pcm1->private_data = solo;
	pcm1->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_t *pcm1;

	pcm = snd_pcm1_new_device(solo->card, "es1938/2");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	memcpy(&pcm1->playback.hw, &snd_solo_playback2, sizeof(snd_solo_playback));
	pcm1->private_data = solo;
	pcm1->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_solo_volume_level(esssolo_t * solo,
				 int w_flag, int *voices,
				 int rg)
{
	unsigned long flags;
	unsigned char val, lreg, rreg;
	int change = 0;

	snd_spin_lock(solo, reg, &flags);
	val = snd_solo_mixer_in(solo, rg);
	lreg = val >> 4;
	rreg = val & 0x0f;
	if (!w_flag) {
		voices[0] = lreg;
		voices[1] = rreg;
	} else {
		change = lreg != voices[0] || rreg != voices[1];
		val = ((voices[0] & 0x0f) | (voices[1] << 4));
		snd_solo_mixer_out(solo, rg, 0xff, val);
#ifdef SOLO_DEBUG
		printk("set_volume 0x%x: 0x%x\n", rg, snd_solo_mixer_in(solo, rg));
#endif
	}
	snd_spin_unlock(solo, reg, &flags);
	return change;
}

static int snd_solomixer_master_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	unsigned long flags;
	unsigned char val, lreg, rreg;
	int change = 0;

	snd_spin_lock(solo, reg, &flags);
	lreg = snd_solo_mixer_in(solo, SLSB_IREG_MASTER_LEFT);
	rreg = snd_solo_mixer_in(solo, SLSB_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(solo, SLSB_IREG_MASTER_LEFT, 0xff, lreg);
		snd_solo_mixer_out(solo, SLSB_IREG_MASTER_RIGHT, 0xff, rreg);
#ifdef SOLO_DEBUG
		printk("set_volume_master 0x%x, 0x%x\n", lreg, rreg);
#endif
	}
	snd_spin_unlock(solo, reg, &flags);
	return change;
}

static int snd_solomixer_audio1_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	return snd_solo_volume_level(solo, w_flag, voices, SLSB_IREG_AUDIO1);
}

static int snd_solomixer_audio2_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	return snd_solo_volume_level(solo, w_flag, voices, SLSB_IREG_AUDIO2);
}

static int snd_solomixer_line_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	return snd_solo_volume_level(solo, w_flag, voices, SLSB_IREG_LINE);
}

static int snd_solomixer_auxacd_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	return snd_solo_volume_level(solo, w_flag, voices, SLSB_IREG_AUXACD);
}

static int snd_solomixer_auxb_volume_level(int w_flag, int *voices, esssolo_t * solo)
{
	return snd_solo_volume_level(solo, w_flag, voices, SLSB_IREG_AUXB);
}

static void snd_solo_mixer_free(void *data)
{
	((esssolo_t *) data)->mixer = NULL;
}

snd_kmixer_t *snd_solo_mixer(esssolo_t * solo, int pcmnum_audio1, int pcmnum_audio2)
{
	snd_kmixer_t *mixer;
	unsigned long flags;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *in_accu, *out_accu;
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	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}
	};
	static struct snd_mixer_element_volume1_range db_range3[2] =
	{
		{0, 63, -3150, 0},
		{0, 63, -3150, 0}
	};

	if (!solo)
		return NULL;
	mixer = snd_mixer_new(solo->card, "ess es1938");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, "ESS Solo-1");
	/* build input and output accumulator */
	if ((in_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build master volume control */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range3, (snd_mixer_volume1_control_t *) snd_solomixer_master_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
		goto __error;
	/* AUDIO 1 */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcmnum_audio1)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM 1 Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *) snd_solomixer_audio1_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, out_accu) < 0)
		goto __error;
	/* AUDIO 2 */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcmnum_audio2)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM 2 Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *) snd_solomixer_audio2_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, out_accu) < 0)
		goto __error;
	/* LINE */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range2, (snd_mixer_volume1_control_t *) snd_solomixer_line_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
		goto __error;
	/* CD */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range2, (snd_mixer_volume1_control_t *) snd_solomixer_auxacd_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
		goto __error;
	/* AUXB */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_AUX, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "AUXB Volume", 0, 2, db_range2, (snd_mixer_volume1_control_t *) snd_solomixer_auxb_volume_level, solo)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
		goto __error;

	mixer->private_data = solo;
	mixer->private_free = snd_solo_mixer_free;

	return solo->mixer = mixer;

      __error:
	snd_mixer_free(mixer);
	return NULL;
}

/*

 */

esssolo_t *snd_solo_create(snd_card_t * card,
			   struct snd_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_calloc(sizeof(esssolo_t));
	if (!solo)
		return NULL;
	snd_spin_prepare(solo, reg);
	solo->card = card;
	solo->pci = pci;
	solo->irqptr = irqptr;
	solo->dma1ptr = dma1ptr;
	solo->dma2ptr = dma2ptr;
	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;
#ifdef SOLO_DDEBUG
	snd_printk("snd_solo_create: irq: 0x%x, dma1: 0x%x, io: 0x%x, sb: 0x%x, vc: 0x%x, mpu: 0x%x, game: 0x%x\n",
		   solo->irqnum, solo->dma1num, 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 */

	/* set DDMA base */

	snd_pci_write_config_word(pci, SL_PCI_DDMACONTROL, solo->vc_port | 1);
	solo->ddma_port = solo->vc_port;

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

	/* disable Audio 1 and Audio 2 IRQ */
	snd_solo_mask_irq(solo, 0xff, 0);

	/* 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_free(solo, sizeof(esssolo_t));
}

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)
{
	snd_pcm1_t *pcm1;
	unsigned char status, s1;
	unsigned long flags;

#ifdef SOLO_IDEBUG
	snd_printk("snd_solo_interrupt start\n");
#endif
	snd_spin_lock(solo, reg, &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, SLSB_IREG_AUDIO2CONTROL2, 0x80, 0);
			pcm1 = (snd_pcm1_t *) solo->pcm2->private_data;
			if (pcm1 && pcm1->playback.ack)
				pcm1->playback.ack(pcm1);
#ifdef SOLO_IDEBUG
			{
				int v, v2;
				v = inw(SLIO_REG(solo, AUDIO2DMACOUNT));
				v2 = snd_solo_mixer_in(solo, SLSB_IREG_AUDIO2TCOUNTL);
				v2 += snd_solo_mixer_in(solo, SLSB_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 & 0x40) {
		if (solo->pcm) {
			pcm1 = (snd_pcm1_t *) solo->pcm->private_data;
			if (pcm1 && pcm1->playback.ack)
				pcm1->playback.ack(pcm1);
#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));
	snd_spin_unlock(solo, reg, &flags);
#ifdef SOLO_IDEBUG
	snd_printk("snd_solo_interrupt end\n");
#endif
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_ess_solo1_export;
#endif

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

void cleanup_module(void)
{
}
