/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *                   Creative Labs, Inc.
 *  Routines for control of EMU10K1 chips / PCM routines
 *
 *  BUGS:
 *    --
 *
 *  TODO:
 *    --
 *
 *   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.
 *
 */

#include "driver.h"
#include "info.h"
#include "sndpci.h"
#include "emu10k1.h"
#include "dma.h"

static void snd_emu10k1_pcm_interrupt(emu10k1_t *emu, emu10k1_voice_t *voice)
{
	emu10k1_pcm_t *epcm;
	snd_pcm1_t *pcm1;

	if ((epcm = voice->epcm) == NULL)
		return;
	if ((pcm1 = epcm->pcm1) == NULL)
		return;

	if (pcm1 && pcm1->playback.ack)
		pcm1->playback.ack(pcm1);
}

static void snd_emu10k1_pcm_ac97adc_interrupt(emu10k1_t *emu, unsigned int status)
{
	snd_pcm1_t *pcm1 = emu->record_epcm->pcm1;
	if (status & INT_HALF) {
//		if (emu->pcm_capture_subchn->runtime->mode == SND_PCM_MODE_STREAM)
			return;
	}
	if (pcm1 && pcm1->record.ack)
		pcm1->record.ack(pcm1);
}

static int snd_emu10k1_pcm_voice_alloc(emu10k1_pcm_t * epcm, int voices, int stream)
{
	int err;

	if (epcm->voices[1] != NULL && voices < 2) {
		snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
		epcm->voices[1] = NULL;
	}
	if (voices == 1 && epcm->voices[0] != NULL)
		return 0;		/* already allocated */
	if (voices == 2 && epcm->voices[0] != NULL && epcm->voices[1] != NULL)
		return 0;
	if (voices > 1) {
		if (epcm->voices[0] != NULL && epcm->voices[1] == NULL) {
			snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
			epcm->voices[0] = NULL;
		}
	}
	err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, voices > 1, &epcm->voices[0]);
	if (err < 0)
		return err;
	epcm->voices[0]->epcm = epcm;
	if (stream)
		epcm->voices[0]->interrupt_handler = snd_emu10k1_pcm_interrupt;
	if (voices > 1) {
		epcm->voices[1] = &epcm->emu->voices[epcm->voices[0]->number + 1];
		epcm->voices[1]->epcm = epcm;
	}
	if (!stream && epcm->extra == NULL) {
		err = snd_emu10k1_voice_alloc(epcm->emu, EMU10K1_PCM, 0, &epcm->extra);
		if (err < 0)
			return err;
		epcm->extra->epcm = epcm;
		epcm->extra->interrupt_handler = snd_emu10k1_pcm_interrupt;
	}
	return 0;
}

static int capture_sizes[4] = {384, 448, 512, 640};

static unsigned int snd_emu10k1_capture_transfer_size(unsigned int size)
{
	int idx, val;

	for (idx = 30; idx >= 0; idx--) {
		val = capture_sizes[idx & 3] * (1 << (idx >> 2));
		if (val <= size)
			return val;
	}
	return capture_sizes[0];
}

static unsigned int snd_emu10k1_capture_transfer_reg(unsigned int size)
{
	int idx, val;

	for (idx = 30; idx >= 0; idx--) {
		val = capture_sizes[idx & 3] * (1 << (idx >> 2));
		if (val <= size)
			return idx + 1;
	}
	return 1;
}

static unsigned int snd_emu10k1_capture_rate(unsigned int rate)
{
	if (rate < (8000 + 11025) / 2)
		return 8000;
	if (rate < (11025 + 16000) / 2)
		return 11025;
	if (rate < (16000 + 22050) / 2)
		return 16000;
	if (rate < (22050 + 24000) / 2)
		return 22050;
	if (rate < (24000 + 32000) / 2)
		return 24000;
	if (rate < (32000 + 44100) / 2)
		return 32000;
	if (rate < (44100 + 48000) / 2)
		return 44100;
	return 48000;
}

static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate)
{
	switch (rate) {
	case 8000:	return ADC8K;
	case 11025:	return ADC11K;
	case 16000:	return ADC16K;
	case 22050:	return ADC22K;
	case 24000:	return ADC24K;
	case 32000:	return ADC32K;
	case 44100:	return ADC44K;
	case 48000:	return ADC48K;
	default:
			return ADC8K;
	}
}

static int
snd_emu10k1_playback_ioctl (snd_pcm1_t * pcm1,
        unsigned int cmd, unsigned long *arg)
{
        switch (cmd)
        {
        case SND_PCM1_IOCTL_MODE:
                return -ENXIO;

        case SND_PCM1_IOCTL_RATE:
                pcm1->playback.real_rate= pcm1->playback.rate;
                return 0;

        case SND_PCM1_IOCTL_VOICES:
                return -ENXIO;
        case SND_PCM1_IOCTL_PAUSE:
                return -ENXIO;

        case SND_PCM1_IOCTL_FRAG:
                return -ENXIO;
        }
        return -ENXIO;
}

static int
snd_emu10k1_record_ioctl (snd_pcm1_t * pcm1,
        unsigned int cmd, unsigned long *arg)
{
        switch (cmd)
        {
        case SND_PCM1_IOCTL_RATE:
                pcm1->record.real_rate= snd_emu10k1_capture_rate(pcm1->record.rate);
                return 0;
        case SND_PCM1_IOCTL_FRAG:
                *(unsigned int *) arg= snd_emu10k1_capture_transfer_size(*(unsigned int*)arg)/2;
                return 0;
        }
        return -ENXIO;
}

static void snd_emu10k1_pcm_init_voice(emu10k1_t *emu, int master, 
				       int extra, int voice,
				       int stereo, int rate, int w_16,
				       unsigned int start_addr,
				       unsigned int end_addr)
{
	unsigned int pitch, val = 0, start = 0, sample = 0, silent_page;
	int left, right;

	if (!extra) {
		if (stereo) {
			start_addr >>= 1;
			end_addr >>= 1;
		}
	}
	if (w_16) {
		start_addr >>= 1;
		end_addr >>= 1;
	}
	/* volume parameters */
	if (extra) {
		left = right = 0;
	} else {
		left = right = 255;
		if (stereo) {
			if (master)
				right = 0;
			else
				left = 0;
		}
	}
	pitch = snd_emu10k1_rate_to_pitch(rate) >> 8;
	// printk("init voice: master=%i, voice=%i, stereo=%i, w_16=%i\n", master, voice, stereo, w_16);
	// printk("init voice: start_addr=0x%x, end_addr=0x%x, pitch = 0x%x, target = 0x%x\n", start_addr, end_addr, pitch, (int)(IP_TO_CP(pitch) >> 16));
	snd_emu10k1_synth_write(emu, DCYSUSV | voice, ENV_OFF);
	snd_emu10k1_synth_write(emu, VTFT | voice, 0xffff);
	snd_emu10k1_synth_write(emu, CVCF | voice, 0xffff);
	// Stop CA
	// assumption that PT is already 0 so no harm overwritting
	snd_emu10k1_synth_write(emu, PTRX | voice, (0xff << 8) | right);
	if (master) {
		if (stereo) {
			val = 64;
			snd_emu10k1_synth_write(emu, CPF | voice, 0x8000);
			snd_emu10k1_synth_write(emu, CPF | (voice + 1), 0x8000);
		} else {
			val = 32;
			snd_emu10k1_synth_write(emu, CPF | voice, 0);
		}
		if (w_16) {
			sample = 0;
		} else {
			val *= 2;
			sample = 0x80808080;
		}
		val -= 4;
		if (stereo) {
			snd_emu10k1_synth_write(emu, CCTRL | voice, 0x3c << 16);
			snd_emu10k1_synth_write(emu, CCTRL | (voice + 1), val << 16);
			snd_emu10k1_synth_write(emu, CDATA + 0xE0001 + voice, sample);
			snd_emu10k1_synth_write(emu, CDATA + 0xF0001 + voice, sample);
			start = start_addr + val / 2;
			val <<= 25;
			val |= 0x3c << 16;
		} else {
			snd_emu10k1_synth_write(emu, CCTRL | voice, 0x1c << 16);
			snd_emu10k1_synth_write(emu, CDATA + 0xE0000 + voice, sample);
			snd_emu10k1_synth_write(emu, CDATA + 0xF0000 + voice, sample);
			start = start_addr + val;
			val <<= 25;
			val |= 0x1c << 16;
		}
		start |= ROM0;
	}
	snd_emu10k1_synth_write(emu, CSL | voice, end_addr);
	snd_emu10k1_synth_write(emu, PSST | voice, start_addr | (left << 24));
	snd_emu10k1_synth_write(emu, CCCA | voice, start | (w_16 ? 0 : BYTESIZE));
	// Clear filter delay memory
	snd_emu10k1_synth_write(emu, Z1 | voice, 0);
	snd_emu10k1_synth_write(emu, Z2 | voice, 0);
	// invalidate maps
	silent_page = ((unsigned int)virt_to_bus(emu->silent_page) << 1) | 0x1fff;
	snd_emu10k1_synth_write(emu, MAPA | voice, silent_page);
	snd_emu10k1_synth_write(emu, MAPB | voice, silent_page);
	// fill cache
	if (master)
		snd_emu10k1_synth_write(emu, CCTRL | voice, val);

	snd_emu10k1_synth_write(emu, ATKHLDV | voice, 0x7f7f);
	snd_emu10k1_synth_write(emu, LFOVAL1 | voice, 0x8000);
	snd_emu10k1_synth_write(emu, ATKHLD | voice, 0);
	snd_emu10k1_synth_write(emu, DCYSUS | voice, 0x7f);
	snd_emu10k1_synth_write(emu, LFOVAL2 | voice, 0x8000);
	snd_emu10k1_synth_write(emu, IP | voice, pitch);
	snd_emu10k1_synth_write(emu, PEFE | voice, 0x7f);
	snd_emu10k1_synth_write(emu, FMMOD | voice, 0);
	snd_emu10k1_synth_write(emu, TREMFRQ | voice, 0);
	snd_emu10k1_synth_write(emu, FM2FRQ2 | voice, 0);
	snd_emu10k1_synth_write(emu, ENVVAL | voice, 0xBFFF);
	snd_emu10k1_synth_write(emu, ENVVOL | voice, 0xBFFF);
	snd_emu10k1_synth_write(emu, IFATN | voice, 0xff00);
}

static void
snd_emu10k1_playback_prepare (snd_pcm1_t *pcm1,
	emu10k1_pcm_t *epcm,
        unsigned char *buffer,
        unsigned int size,
        unsigned int offset,
        unsigned int count)
{
	emu10k1_t *emu = (emu10k1_t *) pcm1->private_data;
	snd_pcm1_channel_t *pchn = &pcm1->playback;
	unsigned int start_addr, end_addr;
	int stream, err;

//	stream = (pchn->flags & SND_PCM1_FLG_MMAP)? 1: 0;
	stream = 0;
	snd_printd("snd_emu10k1_playback_prepare: stream = %d\n", stream);
	if ((err = snd_emu10k1_pcm_voice_alloc(epcm, pchn->voices, stream)) < 0)
		return;
	start_addr = epcm->start_addr;
	if (!stream) {
		end_addr = pchn->real_block_size;
		if (pchn->voices == 2)
			end_addr >>= 1;
		end_addr += start_addr;
		snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra->number,
					   0,
					   pchn->real_rate,
					   (pchn->mode & SND_PCM1_MODE_16),
					   start_addr, end_addr);
	}
	end_addr = epcm->start_addr + pchn->used_size;
	snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0]->number,
				   (pchn->voices == 2),
				   pchn->real_rate,
				   (pchn->mode & SND_PCM1_MODE_16),
				   start_addr, end_addr);
	if (epcm->voices[1])
		snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1]->number,
					   1,
					   pchn->real_rate,
					   (pchn->mode & SND_PCM1_MODE_16),
					   start_addr, end_addr);
}

static void
snd_emu10k1_playback1_prepare (snd_pcm1_t * pcm1,
        unsigned char *buffer,
        unsigned int size,
        unsigned int offset,
        unsigned int count)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;

	snd_emu10k1_playback_prepare (pcm1, emu->playback1_epcm, buffer, size, offset, count);
}

static void
snd_emu10k1_playback2_prepare (snd_pcm1_t * pcm1,
        unsigned char *buffer,
        unsigned int size,
        unsigned int offset,
        unsigned int count)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;

	snd_emu10k1_playback_prepare (pcm1, emu->playback2_epcm, buffer, size, offset, count);
}

static void
snd_emu10k1_record_prepare (snd_pcm1_t * pcm1,
        unsigned char *buffer,
        unsigned int size,
        unsigned int offset,
        unsigned int count)
{
	emu10k1_t *emu = (emu10k1_t *) pcm1->private_data;
	emu10k1_pcm_t *epcm = (emu10k1_pcm_t *)emu->record_epcm;
	snd_pcm1_channel_t *pchn = &pcm1->record;

	snd_emu10k1_synth_write(emu, ADCBS, 0);
	snd_emu10k1_synth_write(emu, ADCCR, 0);
	snd_emu10k1_synth_write(emu, ADCBA, (unsigned int)virt_to_bus(pcm1->record.buffer));
	epcm->adcbs = snd_emu10k1_capture_transfer_reg(pchn->used_size);
	epcm->adccr = 0x08;
	if (pcm1->record.voices > 1)
		epcm->adccr |= 0x10;
	epcm->adccr |= snd_emu10k1_capture_rate_reg(pchn->real_rate);
}

static void snd_emu10k1_playback_trigger_voice(emu10k1_t *emu, emu10k1_voice_t *voice, unsigned int pitch, int master, int extra, int stream)
{
	unsigned int val, pitch_target;

	if (voice == NULL)
		return;
	pitch_target = IP_TO_CP(pitch) >> 16;
	snd_emu10k1_synth_write(emu, PTRX_PT | voice->number, pitch_target);
	if (master)
		snd_emu10k1_synth_write(emu, CPF_CP | voice->number, pitch_target);
	snd_emu10k1_synth_write(emu, VTFT | voice->number, 0xffff);
	snd_emu10k1_synth_write(emu, CVCF | voice->number, 0xffff);
	snd_emu10k1_voice_clear_loop_stop(emu, voice->number);		
	val = ENV_ON;
	if (!extra) {
		val |= 0x7f7f;
		if (stream && master)
			snd_emu10k1_voice_intr_enable(emu, voice->number);
	} else {
		snd_emu10k1_voice_intr_enable(emu, voice->number);
	}
	snd_emu10k1_synth_write(emu, DCYSUSV | voice->number, val);
}

static void snd_emu10k1_playback_stop_voice(emu10k1_t *emu, emu10k1_voice_t *voice)
{
	if (voice == NULL)
		return;
	snd_emu10k1_synth_write(emu, IFATN | voice->number, 0xffff);
	snd_emu10k1_synth_write(emu, IP | voice->number, 0);
	snd_emu10k1_synth_write(emu, VTFT | voice->number, 0xffff);
	snd_emu10k1_synth_write(emu, PTRX_PT | voice->number, 0);
	snd_emu10k1_voice_intr_disable(emu, voice->number);
}

static void
snd_emu10k1_playback_trigger (snd_pcm1_t * pcm1, emu10k1_pcm_t *epcm, int up)
{
	emu10k1_t *emu = (emu10k1_t *) pcm1->private_data;

	if (!epcm)
		return;

	snd_mutex_lock(emu, reg);
	if (up) {
		unsigned int pitch = snd_emu10k1_rate_to_pitch(epcm->pcm1->playback.rate) >> 8;
//		int stream = (pcm1->playback.flags & SND_PCM1_FLG_MMAP)? 1: 0;
		int stream = 0;
		snd_printd("snd_emu10k1_playback_trigger: stream = %d\n", stream);
		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], pitch, 1, 0, stream);
		snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], pitch, 0, 0, stream);
		snd_emu10k1_playback_trigger_voice(emu, epcm->extra, pitch, 1, 1, stream);
		epcm->running = 1;
	} else {
		epcm->running = 0;
		snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]);
		snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]);
		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
	}
	snd_mutex_unlock(emu, reg);
}

static void
snd_emu10k1_playback1_trigger (snd_pcm1_t * pcm1, int up)
{
	emu10k1_t *emu = (emu10k1_t*)pcm1->private_data;

	snd_emu10k1_playback_trigger (pcm1, emu->playback1_epcm, up);
}

static void
snd_emu10k1_playback2_trigger (snd_pcm1_t * pcm1, int up)
{
	emu10k1_t *emu = (emu10k1_t*)pcm1->private_data;

	snd_emu10k1_playback_trigger (pcm1, emu->playback2_epcm, up);
}

static void
snd_emu10k1_record_trigger (snd_pcm1_t * pcm1, int up)
{
	emu10k1_t *emu = (emu10k1_t *) pcm1->private_data;
	emu10k1_pcm_t *epcm = emu->record_epcm;

	if (!epcm)
		return;

	snd_mutex_lock(emu, reg);
	if (up) {
		snd_emu10k1_intr_enable(emu, ENB_ADC);
		// printk("adccr = 0x%x, adcbs = 0x%x\n", epcm->adccr, epcm->adcbs);
		snd_emu10k1_synth_write(emu, ADCCR, epcm->adccr);
		snd_emu10k1_synth_write(emu, ADCBS, epcm->adcbs);
		epcm->running = 1;
	} else {
		epcm->running = 0;
		snd_emu10k1_intr_disable(emu, ENB_ADC);
		snd_emu10k1_synth_write(emu, ADCBS, 0);
		snd_emu10k1_synth_write(emu, ADCCR, 0);		
	}
	snd_mutex_lock(emu, reg);
}

static unsigned int
snd_emu10k1_playback_pointer (snd_pcm1_t * pcm1,
	emu10k1_pcm_t *epcm,
        unsigned int used_size)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;
	unsigned int result = 0;

	if (!epcm)
		return -EACCES;

	snd_mutex_lock(emu, reg);
	if (epcm->running) {
		result = snd_emu10k1_synth_read(emu, CCCA | epcm->voices[0]->number) & 0x00ffffff;
		if (pcm1->playback.voices > 1)
			result <<= 1;
		if (epcm->pcm1->playback.mode & SND_PCM1_MODE_16)
			result <<= 1;
		result -= epcm->start_addr;
	}
	snd_mutex_lock(emu, reg);
	return result;
}

static unsigned int
snd_emu10k1_playback1_pointer (snd_pcm1_t *pcm1,
        unsigned int used_size)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;

	return snd_emu10k1_playback_pointer(pcm1, emu->playback1_epcm, used_size);
}

static unsigned int
snd_emu10k1_playback2_pointer (snd_pcm1_t *pcm1,
        unsigned int used_size)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;

	return snd_emu10k1_playback_pointer(pcm1, emu->playback2_epcm, used_size);
}

static unsigned int
snd_emu10k1_record_pointer (snd_pcm1_t *pcm1,
        unsigned int used_size)
{
	emu10k1_t *emu = (emu10k1_t*) pcm1->private_data;
	emu10k1_pcm_t *epcm = emu->record_epcm;
	unsigned int result = 0;

	if (!epcm)
		return -EACCES;

	snd_mutex_lock(emu, reg);
	if (epcm->running) {
		result = snd_emu10k1_synth_read(emu, ADCIDX) & 0xffff;	
	}
	snd_mutex_unlock(emu, reg);
	return result;
}

static int
snd_emu10k1_playback1_open (snd_pcm1_t * pcm1)
{
        emu10k1_t *emu= (emu10k1_t *) pcm1->private_data;
        emu10k1_pcm_t *epcm;
        char *buf;
        unsigned int start_addr;
        int err;

        if ((err= snd_pcm1_dma_alloc (pcm1, SND_PCM1_PLAYBACK, emu->dma1ptr, "emu - DAC1")) < 0)
                return err;
        buf = pcm1->playback.buffer;
        if ((err= snd_emu10k1_ptb_alloc(emu, buf, pcm1->playback.size, &start_addr)) < 0 ) {
		snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma1ptr);
                return err;
        }
        epcm= (emu10k1_pcm_t*) snd_calloc(sizeof(*epcm));
        if (epcm == NULL) {
                snd_emu10k1_ptb_free(emu, buf, NULL);
		snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma1ptr);
                return -ENOMEM;
        }
        epcm->emu = emu;
        epcm->pcm1 = pcm1;
        epcm->start_addr = start_addr;
	emu->playback1_epcm= epcm;
//      subchn->private_free = snd_emu10k1_epcm_free;
//      subchn->hw = &snd_emu10k1_playback;

        return 0;
}

static void
snd_emu10k1_playback1_close (snd_pcm1_t * pcm1)
{
        emu10k1_t *emu= (emu10k1_t *) pcm1->private_data;

        snd_emu10k1_ptb_free(emu, pcm1->playback.buffer, NULL);
        snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma1ptr);
}

static int
snd_emu10k1_playback2_open (snd_pcm1_t * pcm1)
{
        emu10k1_t *emu= (emu10k1_t *) pcm1->private_data;
        emu10k1_pcm_t *epcm;
        char *buf;
        unsigned int start_addr;
        int     err;

        if ((err= snd_pcm1_dma_alloc (pcm1, SND_PCM1_PLAYBACK, emu->dma2ptr, "emu - DAC2")) < 0)
                return err;
        buf = pcm1->playback.buffer;
        if ((err= snd_emu10k1_ptb_alloc(emu, buf, pcm1->playback.size, &start_addr)) < 0 ) {
		snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma2ptr);
                return err;
        }
        epcm= (emu10k1_pcm_t*) snd_calloc(sizeof(*epcm));
        if (epcm == NULL) {
                snd_emu10k1_ptb_free(emu, buf, NULL);
		snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma2ptr);
                return -ENOMEM;
        }
        epcm->emu = emu;
        epcm->pcm1 = pcm1;
        epcm->start_addr = start_addr;
	emu->playback2_epcm = epcm;
//      subchn->private_free = snd_emu10k1_epcm_free;
//      subchn->hw = &snd_emu10k1_playback;

        return 0;
}

static void
snd_emu10k1_playback2_close (snd_pcm1_t * pcm1)
{
        emu10k1_t *emu= (emu10k1_t *) pcm1->private_data;

        snd_emu10k1_ptb_free(emu, pcm1->playback.buffer, NULL);
        snd_pcm1_dma_free (pcm1, SND_PCM1_PLAYBACK, emu->dma2ptr);
}

static int
snd_emu10k1_record_open (snd_pcm1_t * pcm1)
{
        emu10k1_t *emu= (emu10k1_t *) pcm1->private_data;
        emu10k1_pcm_t *epcm;
        int     err;

        if ((err= snd_pcm1_dma_alloc (pcm1, SND_PCM1_RECORD, emu->dma2ptr, "emu - ADC")) < 0)
                return err;
        epcm= (emu10k1_pcm_t*) snd_calloc(sizeof(*epcm));
        if (epcm == NULL) {
		snd_pcm1_dma_free (pcm1, SND_PCM1_RECORD, emu->dma3ptr);
                return -ENOMEM;
        }
        epcm->emu = emu;
        epcm->pcm1 = pcm1;
	emu->record_epcm = epcm;
//      subchn->private_free = snd_emu10k1_epcm_free;
//      subchn->hw = &snd_emu10k1_capture;
        emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt;
//      emu->pcm_capture_subchn = subchn;

        return 0;
}

static void
snd_emu10k1_record_close(snd_pcm1_t *pcm1)
{
	emu10k1_t *emu = (emu10k1_t *) pcm1->private_data;

	emu->capture_interrupt = NULL;
//	emu->pcm_capture_subchn = NULL;
	snd_pcm1_dma_free (pcm1, SND_PCM1_RECORD, emu->dma3ptr);
}

/*
 *  Playback support device description
 */

static struct snd_stru_pcm1_hardware snd_emu10k1_playback1=
{
        NULL,                                                           /* private data */
        NULL,                                                           /* private_free */
        SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,                     /* 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,                            /* hardware formats */
        31,                                                             /* align value */
        32,                                                             /* minimal fragment */
        6000,                                                           /* min. rate */
        64000,                                                          /* max. rate */
        2,                                                              /* max. voices */
        snd_emu10k1_playback1_open,
        snd_emu10k1_playback1_close,
        snd_emu10k1_playback_ioctl,
        snd_emu10k1_playback1_prepare,
        snd_emu10k1_playback1_trigger,
        snd_emu10k1_playback1_pointer,
        snd_pcm1_playback_dma_ulaw_loud,
        snd_pcm1_dma_move,
        snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_emu10k1_playback2=
{
        NULL,                                                           /* private data */
        NULL,                                                           /* private_free */
        SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,                     /* 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,                            /* hardware formats */
        3,                                                             /* align value */
        64,                                                             /* minimal fragment */
        4000,                                                           /* min. rate */
        48000,                                                          /* max. rate */
        2,                                                              /* max. voices */
        snd_emu10k1_playback2_open,
        snd_emu10k1_playback2_close,
        snd_emu10k1_playback_ioctl,
        snd_emu10k1_playback2_prepare,
        snd_emu10k1_playback2_trigger,
        snd_emu10k1_playback2_pointer,
        snd_pcm1_playback_dma_ulaw_loud,
        snd_pcm1_dma_move,
        snd_pcm1_playback_dma_neutral
};

/*
 *  Capture support device description
 */

static struct snd_stru_pcm1_hardware snd_emu10k1_record=
{
        NULL,								/* private data */
        NULL,								/* private_free */
        SND_PCM1_HW_AUTODMA | SND_PCM1_HW_BLOCKPTR,			/* flags */
        SND_PCM_FMT_MU_LAW | SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* formats */
        SND_PCM_FMT_S16_LE,						/* hardware formats */
        63,								/* align value */
        384,								/* minimal fragment */
        4000,								/* min. rate */
        48000,								/* max. rate */
        2,								/* max. voices */
        snd_emu10k1_record_open,
        snd_emu10k1_record_close,
        snd_emu10k1_record_ioctl,
        snd_emu10k1_record_prepare,
        snd_emu10k1_record_trigger,
        snd_emu10k1_record_pointer,
        snd_pcm1_record_dma_ulaw,
        snd_pcm1_dma_move,
        NULL
};


static void snd_emu10k1_epcm_free(void *private_data)
{
	emu10k1_pcm_t *epcm = (emu10k1_pcm_t *)private_data;
	emu10k1_t *emu;

	if (epcm) {
		emu = epcm->emu;
		if (epcm->extra)
			snd_emu10k1_voice_free(epcm->emu, epcm->extra);
		if (epcm->voices[1])
			snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]);
		if (epcm->voices[0])
			snd_emu10k1_voice_free(epcm->emu, epcm->voices[0]);
		snd_free(epcm, sizeof(emu10k1_pcm_t));
	}
}



static void snd_emu10k1_pcm_free(void *private_data)
{
	emu10k1_t *emu = (emu10k1_t *) private_data;
	emu->pcm = NULL;
}

snd_pcm_t *
snd_emu10k1_pcm (emu10k1_t * emu)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;

	pcm= snd_pcm1_new_device (emu->card, "SB-LIVE!/1");
	if (!pcm)
		return NULL;

	pcm1= (snd_pcm1_t *) pcm->private_data;
	memcpy (&pcm1->playback.hw, &snd_emu10k1_playback1, sizeof (snd_emu10k1_playback1));
	memcpy (&pcm1->record.hw, &snd_emu10k1_record, sizeof (snd_emu10k1_record));
	pcm1->private_data= emu;
	pcm1->private_free= snd_emu10k1_pcm_free;
	pcm->info_flags= SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP | SND_PCM_INFO_TRIGGER |
		SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD | SND_PCM_INFO_DUPLEX;
	strcpy (pcm->name, "SB-LIVE! DAC1/ADC");
	return emu->pcm= pcm;
}

static void snd_emu10k1_pcm2_free(void *private_data)
{
        emu10k1_t *emu = (emu10k1_t *) private_data;
        emu->pcm = NULL;
}

snd_pcm_t *
snd_emu10k1_pcm2 (emu10k1_t * emu)
{
        snd_pcm_t *pcm;
        snd_pcm1_t *pcm1;

        pcm= snd_pcm1_new_device (emu->card, "SB-LIVE!/2");
        if (!pcm)
                return NULL;

        pcm1= (snd_pcm1_t *) pcm->private_data;
        memcpy (&pcm1->playback.hw, &snd_emu10k1_playback2, sizeof (snd_emu10k1_playback2));
        pcm1->private_data= emu;
        pcm1->private_free= snd_emu10k1_pcm2_free;
        pcm->info_flags= SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP | SND_PCM_INFO_TRIGGER |
                SND_PCM_INFO_PLAYBACK;
        strcpy (pcm->name, "SB-LIVE! DAC2");
        return emu->pcm2= pcm;
}
