/*
 *  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/pcm.h"
#include "../include/mixer.h"
#include "../include/rawmidi.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\n\
Driver: Dummy soundcard (/dev/null)\n\
ALWAYS\n\
");

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

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 0-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_enable, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable this dummy soundcard. [BOOL]");
MODULE_PARM(snd_mixer_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mixer_devs, "Mixer devices # (0-1) for dummy driver. [range=0-1]");
MODULE_PARM(snd_pcm_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_devs, "PCM devices # (0-4) for dummy driver. [range=0-4]");
MODULE_PARM(snd_pcm_subchannels, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_subchannels, "PCM subchannels # (1-16) for dummy driver. [range=1-16]");
MODULE_PARM(snd_midi_devs, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_midi_devs, "MIDI devices # (0-2) for dummy driver. [range=0-2]");

#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 */
	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_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)
{
	return snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
}

static int snd_card_dummy_capture_ioctl(void *private_data,
					snd_pcm_subchn_t * subchn,
					unsigned int cmd,
					unsigned long *arg)
{
	return snd_pcm_lib_ioctl(private_data, subchn, cmd, arg);
}

static void snd_card_dummy_pcm_timer_start(snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, runtime->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_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, runtime->private_data, );

	del_timer(&dpcm->timer);
}

static int snd_card_dummy_playback_trigger(void *private_data,
					   snd_pcm_subchn_t * subchn,
					   int cmd)
{
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_card_dummy_pcm_timer_start(subchn);
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		snd_card_dummy_pcm_timer_stop(subchn);
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_dummy_capture_trigger(void *private_data,
					  snd_pcm_subchn_t * subchn,
					  int cmd)
{
	if (cmd == SND_PCM_TRIGGER_GO) {
		snd_card_dummy_pcm_timer_start(subchn);
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		snd_card_dummy_pcm_timer_stop(subchn);
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_dummy_pcm_prepare(void *private_data,
				      snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, runtime->private_data, -ENXIO);
	unsigned int bps;

	bps = runtime->format.rate * runtime->format.voices;
	bps *= snd_pcm_format_width(runtime->format.format);
	bps /= 8;
	if (bps <= 0)
		return -EINVAL;
	dpcm->pcm_bps = bps;
	dpcm->pcm_jiffie = bps / HZ;
	dpcm->pcm_size = snd_pcm_lib_transfer_size(subchn);
	dpcm->pcm_count = snd_pcm_lib_transfer_fragment(subchn);
	dpcm->pcm_irq_pos = 0;
	dpcm->pcm_buf_pos = 0;
	return 0;
}

static int snd_card_dummy_playback_prepare(void *private_data,
					   snd_pcm_subchn_t * subchn)
{
	return snd_card_dummy_pcm_prepare((snd_card_dummy_t *)private_data, subchn);
}

static int snd_card_dummy_capture_prepare(void *private_data,
					  snd_pcm_subchn_t * subchn)
{
	return snd_card_dummy_pcm_prepare((snd_card_dummy_t *)private_data, subchn);
}

static void snd_card_dummy_pcm_interrupt(unsigned long data)
{
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, (void *)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;
		snd_pcm_transfer_done(dpcm->subchn);
	}
	spin_unlock_irq(&dpcm->lock);	
}

static unsigned int snd_card_dummy_playback_pointer(void *private_data,
						    snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, runtime->private_data, -ENXIO);
	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)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_dummy_pcm_t *dpcm = snd_magic_cast(snd_card_dummy_pcm_t, runtime->private_data, -ENXIO);
	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 snd_pcm_hardware_t snd_card_dummy_playback =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	5500,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_card_dummy_playback_ioctl,
	snd_card_dummy_playback_prepare,
	snd_card_dummy_playback_trigger,
	snd_card_dummy_playback_pointer
};

static snd_pcm_hardware_t snd_card_dummy_capture =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_STREAM |
	SND_PCM_CHNINFO_BLOCK | SND_PCM_CHNINFO_INTERLEAVE |
	SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_U8 | SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_CONTINUOUS | SND_PCM_RATE_8000_48000,
	5500,			/* min. rate */
	48000,			/* max. rate */
	1,			/* min. voices */
	2,			/* max. voices */
	64,			/* min. fragment size */
	65536,			/* max. fragment size */
	3,			/* fragment align */
	0,			/* FIFO size (unknown) */
	4,			/* transfer block size */
	snd_card_dummy_capture_ioctl,
	snd_card_dummy_capture_prepare,
	snd_card_dummy_capture_trigger,
	snd_card_dummy_capture_pointer
};

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

	dpcm = snd_magic_kcalloc(snd_card_dummy_pcm_t, 0, GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm_dma_alloc(subchn, dummy->dma1ptr, "Dummy - DAC")) < 0) {
		snd_magic_kfree(dpcm);
		return err;
	}
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_dummy_pcm_interrupt;
	spin_lock_init(&dpcm->lock);
	dpcm->subchn = subchn;
	subchn->runtime->private_data = dpcm;
	subchn->runtime->private_free = _snd_magic_kfree;
	subchn->runtime->hw = &snd_card_dummy_playback;
	return 0;
}

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

	dpcm = snd_magic_kcalloc(snd_card_dummy_pcm_t, 0, GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm_dma_alloc(subchn, dummy->dma2ptr, "Dummy - ADC")) < 0) {
		snd_magic_kfree(dpcm);
		return err;
	}
	dpcm->timer.data = (unsigned long) dpcm;
	dpcm->timer.function = snd_card_dummy_pcm_interrupt;
	spin_lock_init(&dpcm->lock);
	dpcm->subchn = subchn;
	subchn->runtime->private_data = dpcm;
	subchn->runtime->private_free = _snd_magic_kfree;
	subchn->runtime->hw = &snd_card_dummy_capture;
	return 0;
}

static int snd_card_dummy_playback_close(void *private_data,
					 snd_pcm_subchn_t * subchn)
{
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_card_dummy_capture_close(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	snd_pcm_dma_free(subchn);
	return 0;
}

static int __init snd_card_dummy_pcm(snd_card_dummy_t *dummy, int device, int subchannels)
{
	snd_pcm_t *pcm;
	int err;

	if ((err = snd_pcm_new(dummy->card, "Dummy PCM", device, subchannels, subchannels, &pcm)) < 0)
		return err;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = dummy;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_card_dummy_playback_open;
	pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_card_dummy_playback_close;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = dummy;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_card_dummy_capture_open;
	pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_card_dummy_capture_close;
	pcm->info_flags = SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_CAPTURE |
			  SND_PCM_INFO_DUPLEX;
	strcpy(pcm->name, "Dummy PCM");
	return 0;
}

static int snd_card_dummy_volume_level(snd_kmixer_element_t *element,
				       int w_flag, int *voices, int addr)
{
	snd_card_dummy_t * dummy = snd_magic_cast(snd_card_dummy_t, element->private_data, -ENXIO);
	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_kmixer_element_t *element,
					int w_flag, int *value, int addr)
{
	snd_card_dummy_t * dummy = snd_magic_cast(snd_card_dummy_t, element->private_data, -ENXIO);
	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(snd_kmixer_element_t * element, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level(element, w_flag, voices, MIXER_ADDR_MASTER);
}

static int snd_card_dummy_synth(snd_kmixer_element_t * element, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level(element, w_flag, voices, MIXER_ADDR_SYNTH);
}

static int snd_card_dummy_line(snd_kmixer_element_t * element, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level(element, w_flag, voices, MIXER_ADDR_LINE);
}

static int snd_card_dummy_mic(snd_kmixer_element_t * element, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level(element, w_flag, voices, MIXER_ADDR_MIC);
}

static int snd_card_dummy_cd(snd_kmixer_element_t * element, int w_flag, int *voices)
{
	return snd_card_dummy_volume_level(element, w_flag, voices, MIXER_ADDR_CD);
}

static int snd_card_dummy_group_ctrl1(snd_kmixer_group_t * group,
				      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(volume1_element, 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(volume1_element, 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(volume1_element, 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(volume1_element, 1, &value, addr);
	}
	return change;
}

static int snd_card_dummy_group_master(snd_kmixer_group_t * group,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = snd_magic_cast(snd_card_dummy_t, group->private_data, -ENXIO);

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

static int snd_card_dummy_group_line(snd_kmixer_group_t * group,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = snd_magic_cast(snd_card_dummy_t, group->private_data, -ENXIO);

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

static int snd_card_dummy_group_mic(snd_kmixer_group_t * group,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = snd_magic_cast(snd_card_dummy_t, group->private_data, -ENXIO);

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

static int snd_card_dummy_group_synth(snd_kmixer_group_t * group,
				      snd_kmixer_file_t * file,
				      int w_flag,
				      snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = snd_magic_cast(snd_card_dummy_t, group->private_data, -ENXIO);

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

static int snd_card_dummy_group_cd(snd_kmixer_group_t * group,
			 	   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	snd_card_dummy_t *dummy = snd_magic_cast(snd_card_dummy_t, group->private_data, -ENXIO);

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

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

	snd_debug_check(dummy == NULL, -EINVAL);
	spin_lock_init(&dummy->mixer_lock);
	if ((err = snd_mixer_new(dummy->card, "Dummy mixer", device, &mixer)) < 0)
		return err;
	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 0;

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

static int __init 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)
{
	if (dummy == NULL)
		return;
	snd_card_unregister(dummy->card);
	snd_magic_kfree(dummy);
}

static int __init snd_card_dummy_probe(int dev, struct snd_card_dummy *dummy)
{
	snd_card_t *card;
	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 == NULL) {
		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++) {
		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;
		if (snd_card_dummy_pcm(dummy, idx, snd_pcm_subchannels[dev]) < 0)
			goto __nodev;
	}
	if (snd_card_dummy_new_mixer(dummy, 0) < 0)
		goto __nodev;
	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 == NULL) {
		snd_card_free(card);
		return -ENODEV;
	}
	dummy = snd_magic_cast(cs4231_t, pcm->private_data, -ENXIO);
	dummy->dummy = dummy;
	dummy->pcm_status_reg = dummy->port + 2;

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

	if (dummy->mpuirqptr) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_CS4232,
						   snd_mpu_port[dev],
						   dummy->mpuirqptr->irq);
		if (rmidi == NULL)
			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 alsa_card_dummy_init(void)
{
	int dev, cards;
	snd_card_dummy_t *dummy;

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

static void __exit alsa_card_dummy_exit(void)
{
	int idx;

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

module_init(alsa_card_dummy_init)
module_exit(alsa_card_dummy_exit)
