/*
 *  Dummy soundcard
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *   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_MAIN_OBJECT_FILE
#define __SND_OSS_COMPAT__
#include "../include/driver.h"
#include "../include/pcm1.h"
#include "../include/mixer.h"
#include "../include/midi.h"
#include "../include/initval.h"

#define MAX_PCM_DEVICES		4
#define MAX_PCM_SUBCHANNELS	16
#define MAX_MIDI_DEVICES	2

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_enable[SND_CARDS] = {1, [1 ... (SND_CARDS - 1)] = 0};
int snd_mixer_devs[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
int snd_pcm_devs[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
int snd_pcm_subchannels[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 8};
int snd_midi_devs[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 2};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for dummy soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for dummy soundcard.");
MODULE_PARM(snd_mixer_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mixer_devs, "Mixer devices # (0-1) for dummy driver.");
MODULE_PARM(snd_pcm_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_devs, "PCM devices # (0-4) for dummy driver.");
MODULE_PARM(snd_pcm_subchannels, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_subchannels, "PCM subchannels # (1-16) for dummy driver.");
MODULE_PARM(snd_midi_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_midi_devs, "MIDI devices # (0-2) for dummy driver.");

#define MIXER_ADDR_MASTER	0
#define MIXER_ADDR_LINE		1
#define MIXER_ADDR_MIC		2
#define MIXER_ADDR_SYNTH	3
#define MIXER_ADDR_CD		4
#define MIXER_ADDR_LAST		4

typedef struct snd_card_dummy {
	snd_card_t *card;
	snd_dma_t *dma1ptr;	/* DAC frame */
	snd_dma_t *dma2ptr;	/* ADC frame */
	snd_pcm_t *pcm[MAX_PCM_DEVICES];
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi[MAX_MIDI_DEVICES];
	spinlock_t mixer_lock;
	int mixer_volume[MIXER_ADDR_LAST+1][2];
	int record_source[MIXER_ADDR_LAST+1];
	snd_kmixer_element_t *me_accu;
	snd_kmixer_element_t *me_out_master;
	snd_kmixer_element_t *me_vol_master;
	snd_kmixer_element_t *me_sw_master;
	snd_kmixer_element_t *me_in_line;
	snd_kmixer_element_t *me_vol_line;
	snd_kmixer_element_t *me_sw_line;
	snd_kmixer_element_t *me_in_mic;
	snd_kmixer_element_t *me_vol_mic;
	snd_kmixer_element_t *me_sw_mic;
	snd_kmixer_element_t *me_in_gf1;
	snd_kmixer_element_t *me_vol_gf1;
	snd_kmixer_element_t *me_sw_gf1;
	snd_kmixer_element_t *me_in_cd;
	snd_kmixer_element_t *me_vol_cd;
	snd_kmixer_element_t *me_sw_cd;
	snd_kmixer_element_t *me_vol_pcm;
	snd_kmixer_element_t *me_playback;
} snd_card_dummy_t;

typedef struct snd_card_dummy_pcm {
	snd_card_dummy_t *dummy;
	spinlock_t lock;
	struct timer_list timer;
	unsigned int pcm_size;
	unsigned int pcm_count;
	unsigned int pcm_bps;		/* bytes per second */
	unsigned int pcm_jiffie;	/* bytes per one jiffie */
	unsigned int pcm_irq_pos;	/* IRQ position */
	unsigned int pcm_buf_pos;	/* position in buffer */
	snd_pcm_subchn_t *subchn;
	snd_pcm1_subchn_t *subchn1;
} snd_card_dummy_pcm_t;

snd_card_dummy_t *snd_card_dummy_cards[SND_CARDS] = SND_DEFAULT_PTR;

static void snd_card_dummy_use_inc(snd_card_t * card)
{
	MOD_INC_USE_COUNT;
}

static void snd_card_dummy_use_dec(snd_card_t * card)
{
	MOD_DEC_USE_COUNT;
}


static int snd_card_dummy_playback_ioctl(void *private_data,
				    snd_pcm_subchn_t * subchn,
				    unsigned int cmd,
				    unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = subchn1->rate;
		return 0;
	}
	return -ENXIO;
}

static int snd_card_dummy_capture_ioctl(void *private_data,
					snd_pcm_subchn_t * subchn,
					unsigned int cmd,
					unsigned long *arg)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;

	switch (cmd) {
	case SND_PCM1_IOCTL_RATE:
		subchn1->real_rate = subchn1->rate;
		return 0;
	}
	return -ENXIO;
}

static void snd_card_dummy_pcm_timer_start(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)subchn1->private_data;

	dpcm->timer.expires = 1 + jiffies;
	add_timer(&dpcm->timer);
}

static void snd_card_dummy_pcm_timer_stop(snd_pcm_subchn_t * subchn)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)subchn1->private_data;

	del_timer(&dpcm->timer);
}

static void snd_card_dummy_playback_trigger(void *private_data,
					    snd_pcm_subchn_t * subchn,
					    int up)
{
	if (up) {
		snd_card_dummy_pcm_timer_start(subchn);
	} else {
		snd_card_dummy_pcm_timer_stop(subchn);
	}
}

static void snd_card_dummy_capture_trigger(void *private_data,
					   snd_pcm_subchn_t * subchn,
					   int up)
{
	if (up) {
		snd_card_dummy_pcm_timer_start(subchn);
	} else {
		snd_card_dummy_pcm_timer_stop(subchn);
	}
}

static void snd_card_dummy_pcm_prepare(void *private_data,
				       snd_pcm_subchn_t * subchn,
				       unsigned int size,
				       unsigned int count)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)subchn1->private_data;
	unsigned int bps;

	bps = subchn1->real_rate * subchn1->voices;
	if (subchn1->mode & SND_PCM1_MODE_16) {
		bps <<= 1;
	} else if (subchn1->mode & SND_PCM1_MODE_24) {
		bps *= 3;
	} else if (subchn1->mode & (SND_PCM1_MODE_32|SND_PCM1_MODE_FLOAT)) {
		bps <<= 2;
	} else if (subchn1->mode & SND_PCM1_MODE_FLOAT64) {
		bps <<= 3;
	} else if (subchn1->mode & SND_PCM1_MODE_ADPCM) {
		bps >>= 2;
	}
	dpcm->pcm_bps = bps;
	dpcm->pcm_jiffie = bps / HZ;
	dpcm->pcm_size = size;
	dpcm->pcm_count = count;
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;
}

static void snd_card_dummy_playback_prepare(void *private_data,
					    snd_pcm_subchn_t * subchn,
					    unsigned char *buffer,
					    unsigned int size,
					    unsigned int offset,
					    unsigned int count)
{
	snd_card_dummy_pcm_prepare((snd_card_dummy_t *)private_data,
				   subchn, size, count);
}

static void snd_card_dummy_capture_prepare(void *private_data,
					   snd_pcm_subchn_t * subchn,
					   unsigned char *buffer,
					   unsigned int size,
					   unsigned int offset,
					   unsigned int count)
{
	snd_card_dummy_pcm_prepare((snd_card_dummy_t *)private_data,
				   subchn, size, count);
}

static void snd_card_dummy_pcm_interrupt(unsigned long data)
{
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)data;
	
	dpcm->timer.expires = 1 + jiffies;
	add_timer(&dpcm->timer);
	spin_lock_irq(&dpcm->lock);
	dpcm->pcm_irq_pos += dpcm->pcm_jiffie;
	dpcm->pcm_buf_pos += dpcm->pcm_buf_pos;
	dpcm->pcm_buf_pos %= dpcm->pcm_size;
	while (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
		dpcm->pcm_irq_pos -= dpcm->pcm_count;
		spin_unlock_irq(&dpcm->lock);
		dpcm->subchn1->ack(dpcm->subchn);
		spin_lock_irq(&dpcm->lock);
	}
	spin_unlock_irq(&dpcm->lock);	
}

static int snd_card_dummy_playback_open(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm;
	int err;

	dpcm = snd_kcalloc(sizeof(*dpcm), GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm1_dma_alloc(subchn, dummy->dma1ptr, "Dummy - DAC")) < 0) {
		snd_kfree(dpcm);
		return err;
	}
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_dummy_pcm_interrupt;
	dpcm->lock = SPIN_LOCK_UNLOCKED;
	dpcm->subchn1 = subchn1;
	dpcm->subchn = subchn;
	subchn1->private_data = dpcm;
	subchn1->private_free = snd_kfree;
	return 0;
}

static int snd_card_dummy_capture_open(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *) private_data;
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm;
	int err;

	dpcm = snd_kcalloc(sizeof(*dpcm), GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm1_dma_alloc(subchn, dummy->dma2ptr, "Dummy - ADC")) < 0) {
		snd_kfree(dpcm);
		return err;
	}
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_dummy_pcm_interrupt;
	dpcm->lock = SPIN_LOCK_UNLOCKED;
	dpcm->subchn1 = subchn1;
	dpcm->subchn = subchn;
	subchn1->private_data = dpcm;
	subchn1->private_free = snd_kfree;
	return 0;
}

static void snd_card_dummy_playback_close(void *private_data,
					  snd_pcm_subchn_t * subchn)
{
	snd_pcm1_dma_free(subchn);
}

static void snd_card_dummy_capture_close(void *private_data,
					 snd_pcm_subchn_t * subchn)
{
	snd_pcm1_dma_free(subchn);
}

static unsigned int snd_card_dummy_playback_pointer(void *private_data,
						    snd_pcm_subchn_t * subchn,
						    unsigned int used_size)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)subchn1->private_data;
	unsigned long flags;
	unsigned int result;

	spin_lock_irqsave(&dpcm->lock, flags);
	result = dpcm->pcm_buf_pos;
	spin_unlock_irqrestore(&dpcm->lock, flags);
	return result;
}

static unsigned int snd_card_dummy_capture_pointer(void *private_data,
						   snd_pcm_subchn_t * subchn,
						   unsigned int used_size)
{
	snd_pcm1_subchn_t *subchn1 = (snd_pcm1_subchn_t *)subchn->private_data;
	snd_card_dummy_pcm_t *dpcm = (snd_card_dummy_pcm_t *)subchn1->private_data;
	unsigned long flags;
	unsigned int result;

	spin_lock_irqsave(&dpcm->lock, flags);
	result = dpcm->pcm_buf_pos;
	spin_unlock_irqrestore(&dpcm->lock, flags);
	return result;
}

static struct snd_stru_pcm1_hardware snd_card_dummy_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,	/* hardware formats */
	3,			/* align value */
	6,			/* minimal fragment */
	5500,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_card_dummy_playback_open,
	snd_card_dummy_playback_close,
	snd_card_dummy_playback_ioctl,
	snd_card_dummy_playback_prepare,
	snd_card_dummy_playback_trigger,
	snd_card_dummy_playback_pointer,
	snd_pcm1_playback_dma_ulaw_loud,
	snd_pcm1_dma_move,
	snd_pcm1_playback_dma_neutral
};

static struct snd_stru_pcm1_hardware snd_card_dummy_capture =
{
	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,	/* hardware formats */
	3,			/* align value */
	6,			/* minimal fragment */
	5500,			/* min. rate */
	48000,			/* max. rate */
	2,			/* max. voices */
	snd_card_dummy_capture_open,
	snd_card_dummy_capture_close,
	snd_card_dummy_capture_ioctl,
	snd_card_dummy_capture_prepare,
	snd_card_dummy_capture_trigger,
	snd_card_dummy_capture_pointer,
	snd_pcm1_capture_dma_ulaw,
	snd_pcm1_dma_move,
	NULL
};

static snd_pcm_t *snd_card_dummy_pcm(snd_card_dummy_t *dummy, int subchannels)
{
	snd_pcm_t *pcm;
	snd_pcm1_channel_t *pchn1;

	pcm = snd_pcm1_new_device(dummy->card, "Dummy PCM", subchannels, subchannels);
	if (!pcm)
		return NULL;
	pchn1 = (snd_pcm1_channel_t *)pcm->playback.private_data;
	memcpy(&pchn1->hw, &snd_card_dummy_playback, sizeof(snd_card_dummy_playback));
	pchn1->private_data = dummy;
	pchn1 = (snd_pcm1_channel_t *)pcm->capture.private_data;
	memcpy(&pchn1->hw, &snd_card_dummy_capture, sizeof(snd_card_dummy_capture));
	pchn1->private_data = dummy;
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
			  SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "Dummy PCM");
	return pcm;
}

static int snd_card_dummy_volume_level(snd_card_dummy_t *dummy, int w_flag,
				       int *voices, int addr)
{
	unsigned long flags;
	unsigned int change = 0;

	spin_lock_irqsave(&dummy->mixer_lock, flags);
	if (!w_flag) {
		voices[0] = dummy->mixer_volume[addr][0];
		voices[1] = dummy->mixer_volume[addr][1];
	} else {
		change = (voices[0] % 100) != dummy->mixer_volume[addr][0] ||
			 (voices[1] % 100) != dummy->mixer_volume[addr][1];
		dummy->mixer_volume[addr][0] = (voices[0] % 100);
		dummy->mixer_volume[addr][1] = (voices[1] % 100);
	}
	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
	return change;
}

static int snd_card_dummy_record_source(snd_card_dummy_t *dummy, int w_flag,
				        int *value, int addr)
{
	unsigned long flags;
	unsigned int change = 0;

	spin_lock_irqsave(&dummy->mixer_lock, flags);
	if (!w_flag) {
		*value = dummy->record_source[addr];
	} else {
		*value = *value != 0;
		change = dummy->record_source[addr] != *value;
		dummy->record_source[addr] = *value;
	}
	spin_unlock_irqrestore(&dummy->mixer_lock, flags);
	return change;
}

static int snd_card_dummy_master(void *private_data, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level((snd_card_dummy_t *)private_data,
					   w_flag, voices, MIXER_ADDR_MASTER);
}

static int snd_card_dummy_synth(void *private_data, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level((snd_card_dummy_t *)private_data,
					   w_flag, voices, MIXER_ADDR_SYNTH);
}

static int snd_card_dummy_line(void *private_data, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level((snd_card_dummy_t *)private_data,
					   w_flag, voices, MIXER_ADDR_LINE);
}

static int snd_card_dummy_mic(void *private_data, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level((snd_card_dummy_t *)private_data,
					   w_flag, voices, MIXER_ADDR_MIC);
}

static int snd_card_dummy_cd(void *private_data, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level((snd_card_dummy_t *)private_data,
					   w_flag, voices, MIXER_ADDR_CD);
}

static int snd_card_dummy_group_ctrl1(snd_card_dummy_t *dummy,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup,
				      snd_mixer_volume1_control_t *volume1,
				      snd_kmixer_element_t *volume1_element,
				      int addr)
{
	int voices[2];
	int change = 0;
	int value;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME |
		               SND_MIXER_GRPCAP_CAPTURE |
		               SND_MIXER_GRPCAP_JOINTLY_CAPTURE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		volume1(dummy, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 100;
		snd_card_dummy_record_source(dummy, 0, &value, addr);
		if (value)
			ugroup->capture = SND_MIXER_CHN_MASK_STEREO;
	} else {
		voices[0] = ugroup->volume.names.front_left % 100;
		voices[1] = ugroup->volume.names.front_right % 100;
		if (volume1(dummy, 1, voices) > 0) {
			snd_mixer_element_value_change(file, volume1_element, 0);
			change = 1;
		}
		value = ugroup->capture & SND_MIXER_CHN_MASK_STEREO ? 1 : 0;
		snd_card_dummy_record_source(dummy, 1, &value, addr);
	}
	return change;
}

static int snd_card_dummy_group_master(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *)private_data;

	return snd_card_dummy_group_ctrl1(dummy, file, w_flag, ugroup,
					  snd_card_dummy_master,
				          dummy->me_vol_master,
				          MIXER_ADDR_MASTER);
}

static int snd_card_dummy_group_line(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *)private_data;

	return snd_card_dummy_group_ctrl1(dummy, file, w_flag, ugroup,
				          snd_card_dummy_line,
				          dummy->me_vol_line,
				          MIXER_ADDR_LINE);
}

static int snd_card_dummy_group_mic(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *)private_data;

	return snd_card_dummy_group_ctrl1(dummy, file, w_flag, ugroup,
				          snd_card_dummy_mic,
				          dummy->me_vol_mic,
				          MIXER_ADDR_MIC);
}

static int snd_card_dummy_group_synth(void *private_data,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *)private_data;

	return snd_card_dummy_group_ctrl1(dummy, file, w_flag, ugroup,
				          snd_card_dummy_synth,
				          dummy->me_vol_mic,
				          MIXER_ADDR_SYNTH);
}

static int snd_card_dummy_group_cd(void *private_data,
			 	   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = (snd_card_dummy_t *)private_data;

	return snd_card_dummy_group_ctrl1(dummy, file, w_flag, ugroup,
				          snd_card_dummy_cd,
				          dummy->me_vol_cd,
				          MIXER_ADDR_CD);
}

snd_kmixer_t *snd_card_dummy_new_mixer(snd_card_dummy_t * dummy)
{
	snd_kmixer_t *mixer;
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range ics_range[2] = {
		{0, 100, -9600, 0},
		{0, 100, -9600, 0}
	};

	if (!dummy)
		return NULL;
	dummy->mixer_lock = SPIN_LOCK_UNLOCKED;
	mixer = snd_mixer_new(dummy->card, "Dummy mixer");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, mixer->id);
	if ((dummy->me_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build line-out */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_card_dummy_group_master, dummy)) == NULL)
		goto __error;
	if ((dummy->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if ((dummy->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, ics_range, snd_card_dummy_master, dummy)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, dummy->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_accu, dummy->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_vol_master, dummy->me_out_master) < 0)
		goto __error;
 	/* build line-in */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_card_dummy_group_line, dummy)) == NULL)
		goto __error;
	if ((dummy->me_in_line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((dummy->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Input Volume", 0, 2, ics_range, snd_card_dummy_line, dummy)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, dummy->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_in_line, dummy->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_vol_line, dummy->me_accu) < 0)
		goto __error;
 	/* build MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_card_dummy_group_mic, dummy)) == NULL)
		goto __error;
	if ((dummy->me_in_mic = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((dummy->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Input Volume", 0, 2, ics_range, snd_card_dummy_mic, dummy)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, dummy->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_in_mic, dummy->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_vol_mic, dummy->me_accu) < 0)
		goto __error;
	/* build synthesizer */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_SYNTH, snd_card_dummy_group_synth, dummy)) == NULL)
		goto __error;
	if ((dummy->me_in_gf1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((dummy->me_vol_gf1 = snd_mixer_lib_volume1(mixer, "Synth Input Volume", 0, 2, ics_range, snd_card_dummy_synth, dummy)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, dummy->me_vol_gf1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_in_gf1, dummy->me_vol_gf1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_vol_gf1, dummy->me_accu) < 0)
		goto __error;
	/* build CD */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_card_dummy_group_cd, dummy)) == NULL)
		goto __error;
	if ((dummy->me_in_cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((dummy->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Input Volume", 0, 2, ics_range, snd_card_dummy_cd, dummy)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, dummy->me_vol_cd) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_in_cd, dummy->me_vol_cd) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, dummy->me_vol_cd, dummy->me_accu) < 0)
		goto __error;
	mixer->private_data = dummy;
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}

static int snd_card_dummy_resources(snd_card_dummy_t *dummy)
{
	int err;

	if ((err = snd_register_dma_channel(dummy->card,
					    "Dummy PCM - DAC", 0,
					    SND_DMA_TYPE_PCI, 64,
					    NULL, &dummy->dma1ptr)) < 0)
		return err;
	dummy->dma1ptr->multi = 1;	/* enable multi-alloc */

	if ((err = snd_register_dma_channel(dummy->card,
					    "Dummy PCM - ADC", 1,
					    SND_DMA_TYPE_PCI, 64,
					    NULL, &dummy->dma2ptr)) < 0)
		return err;
	dummy->dma2ptr->multi = 1;	/* enable multi-alloc */

	return 0;
}

static void snd_card_dummy_free(snd_card_dummy_t *dummy)
{
	int idx;

	if (dummy == NULL)
		return;
	snd_card_unregister(dummy->card);
#if 0
	for (idx = 0; idx < MAX_MIDI_DEVICES; idx++) {
		if (dummy->rmidi[idx])
			snd_rawmidi_unregister(dummy->rmidi[idx]);
	}
#endif
	if (dummy->mixer)
		snd_mixer_unregister(dummy->mixer);
	for (idx = 0; idx < MAX_PCM_DEVICES; idx++) {
		if (dummy->pcm[idx])
			snd_pcm_unregister(dummy->pcm[idx]);
	}
	snd_card_free(dummy->card);
	snd_kfree(dummy);
}

static int snd_card_dummy_probe(int dev, struct snd_card_dummy *dummy)
{
	snd_card_t *card;
	snd_kmixer_t *mixer;
	int idx;

	if (!snd_enable[dev]) {
		snd_card_dummy_free(dummy);
		return -ENODEV;
	}
	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_card_dummy_use_inc, snd_card_dummy_use_dec);
	if (!card) {
		snd_card_dummy_free(dummy);
		return -ENOMEM;
	}
	card->type = SND_CARD_TYPE_DUMMY;
	dummy->card = card;
	if (snd_card_dummy_resources(dummy) < 0) {
		snd_card_dummy_free(dummy);
		return -ENOMEM;
	}
	for (idx = 0; idx < MAX_PCM_DEVICES && idx < snd_pcm_devs[dev]; idx++) {
		snd_pcm_t *pcm;
	
		if (snd_pcm_subchannels[dev] < 1)
			snd_pcm_subchannels[dev] = 1;
		if (snd_pcm_subchannels[dev] > MAX_PCM_SUBCHANNELS)
			snd_pcm_subchannels[dev] = MAX_PCM_SUBCHANNELS;
		pcm = snd_card_dummy_pcm(dummy, snd_pcm_subchannels[dev]);
		if (pcm == NULL)
			goto __nodev;
		if (snd_pcm_register(pcm, idx) < 0) {
			snd_pcm_free(pcm);
			goto __nodev;
		}
		dummy->pcm[idx] = pcm;
	}
	mixer = snd_card_dummy_new_mixer(dummy);
	if (mixer == NULL)
		goto __nodev;
	if (snd_mixer_register(mixer, 0) < 0) {
		snd_mixer_free(mixer);
		goto __nodev;
	}
	dummy->mixer = mixer;
	strcpy(card->abbreviation, "Dummy");
	strcpy(card->shortname, "Dummy");
	sprintf(card->longname, "Dummy %i", dev + 1);
	if (!snd_card_register(card))
		return 0;
#if 0
	if (snd_mpu_port[dev] < 0)
		snd_mpu_port[dev] = SND_AUTO_PORT;
	pcm = snd_card_dummy_detect(card, snd_port[dev],
				     snd_mpu_port[dev],
				     dummy->irqptr, dummy->dma1ptr,
				     dummy->dma2ptr == NULL ?
				     	dummy->dma1ptr : dummy->dma2ptr);
	if (!pcm) {
		snd_card_free(card);
		return -ENODEV;
	}
	dummy = (cs4231_t *) pcm->private_data;
	dummy->dummy = dummy;
	dummy->pcm_status_reg = dummy->port + 2;

	mixer = snd_cs4231_new_mixer(pcm, 0);
	if (!mixer)
		goto __nodev;

	if (dummy->mpuirqptr) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_CS4232,
						   snd_mpu_port[dev],
						   dummy->mpuirqptr->irq);
		if (!rmidi)
			goto __nodev;
		if (snd_rawmidi_register(rmidi, 0) < 0)
			goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0)) {
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	strcpy(card->abbreviation, "CS4231");
	strcpy(card->shortname, pcm->name);
	sprintf(card->longname, "%s at 0x%x, irq %i, dma %i",
		pcm->name,
		dummy->port,
		dummy->irqptr->irq,
		dummy->dma1ptr->dma);
	if (dummy->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			dummy->dma2ptr->dma);
	if (!snd_card_register(card)) {
		dummy->card = card;
		dummy->pcm = pcm;
		dummy->mixer = mixer;
		dummy->rmidi = rmidi;
		return 0;
	}
	snd_mixer_unregister(mixer);
	snd_pcm_unregister(pcm);
	snd_card_free(card);
	return -ENOMEM;

      __nodev:
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm)
		snd_pcm_free(pcm);
#endif
      __nodev:
	snd_card_dummy_free(dummy);
	return -ENXIO;
}

int init_module(void)
{
	int dev, cards;
	snd_card_dummy_t *dummy;

	for (dev = cards = 0; dev < SND_CARDS && snd_enable[dev]; dev++) {
		dummy = (struct snd_card_dummy *)
				snd_kcalloc(sizeof(snd_card_dummy_t), GFP_KERNEL);
		if (!dummy)
			continue;
		if (snd_card_dummy_probe(dev, dummy) < 0) {
			snd_printk("Dummy soundcard #%i not found or device busy\n", dev + 1);
			break;
		}
		snd_card_dummy_cards[dev] = dummy;
		cards++;
	}
	if (!cards) {
		snd_printk("Dummy soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

void cleanup_module(void)
{
	int idx;

	for (idx = 0; idx < SND_CARDS; idx++) {
		snd_card_dummy_free(snd_card_dummy_cards[idx]);
	}
}
