/*
 *  Share 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/minors.h"
#include "../include/initval.h"


MODULE_DESCRIPTION("\
Driver: Share Card\n\
");

/* Another possible solution for this is to define an hacked version of 
   MODULE_PARM and to use { 'a', 'b', 'c' ... } like constructor.
   Probably it isn't worth it... */
#if SND_CARDS * SND_MINOR_PCMS == 64
#define SND_CARDS_x_MINOR_PCMS 64
#else
#define SND_CARDS_x_MINOR_PCMS 256
#warning "Incorrect MODULE_PARMs"
#endif

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 0-MAX */
char *snd_id[SND_CARDS] = {"share", [1 ... (SND_CARDS-1)] = NULL};	/* ID for this card */
int snd_enable[SND_CARDS] = {1, [1 ... (SND_CARDS - 1)] = 0};
int snd_pcm_card[SND_CARDS * SND_MINOR_PCMS] = {0, [1 ... ((SND_CARDS * SND_MINOR_PCMS) - 1)] = -1};
int snd_pcm_dev[SND_CARDS * SND_MINOR_PCMS] = {[0 ... ((SND_CARDS * SND_MINOR_PCMS) - 1)] = 0};
int snd_pcm_subchannel[SND_CARDS * SND_MINOR_PCMS] = {[0 ... ((SND_CARDS * SND_MINOR_PCMS) - 1)] = 0};
int snd_pcm_maxopen[SND_CARDS * SND_MINOR_PCMS] = {[0 ... ((SND_CARDS * SND_MINOR_PCMS) - 1)] = 8};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for share soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for share soundcard.");
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable this share soundcard.");
MODULE_PARM(snd_pcm_card, "1-" __MODULE_STRING(SND_CARDS_x_MINOR_PCMS) "i");
MODULE_PARM_DESC(snd_pcm_card, "PCM card # for share driver.");
MODULE_PARM(snd_pcm_dev, "1-" __MODULE_STRING(SND_CARDS_x_MINOR_PCMS) "i");
MODULE_PARM_DESC(snd_pcm_dev, "PCM device # for share driver.");
MODULE_PARM(snd_pcm_subchannel, "1-" __MODULE_STRING(SND_CARDS_x_MINOR_PCMS) "i");
MODULE_PARM_DESC(snd_pcm_subchannel, "PCM subchannel # for share driver.");
MODULE_PARM(snd_pcm_maxopen, "1-" __MODULE_STRING(SND_CARDS_x_MINOR_PCMS) "i");
MODULE_PARM_DESC(snd_pcm_maxopen, "Max number of contemporary open.");

#define MAX_PCM_SUBCHANNELS	32	/* this is limit */

#define SHARE_VOICES		2	/* Maximum number of voices */
#define SHARE_SAMPLES		32	/* Samples are mixed SHARE_SAMPLES
					   at one time */
#define SHARE_SAMPLE_SIZE	2	/* Signed short */
#define SHARE_SAMPLE_MIN	-32768
#define SHARE_SAMPLE_MAX	32767

#define SHARE_MIXER_RESOLUTION	64

typedef struct snd_card_share {
	snd_card_t *card;
	snd_dma_t *dma1ptr;	/* DAC frame */
	snd_dma_t *dma2ptr;	/* ADC frame */
	int mix_buf[SHARE_SAMPLES*SHARE_VOICES];
	snd_kmixer_t *mixer;
} snd_card_share_t;

typedef struct snd_card_share_pcm snd_card_share_pcm_t;

typedef struct snd_card_share_mixer_element {
	unsigned short volume[SHARE_VOICES];
	unsigned int sw;
	int ttable[SHARE_VOICES*SHARE_VOICES];
	int noop:1,
	    zero:1;
} snd_card_share_mixer_element_t;

typedef struct snd_card_share_mixer_group {
	snd_kmixer_element_t *me_out_vol;
	snd_kmixer_element_t *me_out_sw;
	snd_kmixer_element_t *me_in_vol;
	snd_kmixer_element_t *me_in_sw;
} snd_card_share_mixer_group_t;

typedef struct snd_card_share_pcm_open {
	snd_card_share_t *share;
	int pcm_card;
	int pcm_dev;
	int pcm_subchn;
	int pending_stop;
	struct semaphore open_mutex;
	snd_card_share_pcm_t *pcm_list;
	spinlock_t run_lock;
	snd_card_share_pcm_t *run_pcm_list;
	struct file file;
	snd_pcm_file_t *pcm_file;
} snd_card_share_pcm_open_t;

struct snd_card_share_pcm {
	snd_card_share_pcm_open_t *pcmo;
	spinlock_t lock;
	int running;
	unsigned int pcm_size;
	unsigned int pcm_count;
	unsigned int pcm_buf_pos;	/* position in buffer */
	snd_pcm_subchn_t *subchn;
	snd_card_share_pcm_t *next;	/* next shared PCM subchannel */
	snd_card_share_pcm_t *run_next;	/* next running shared PCM subchannel */
	snd_card_share_mixer_element_t *mix;
};

snd_card_share_t *snd_card_share_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static void load_block(snd_card_share_pcm_open_t *pcmo,
		       snd_card_share_pcm_t *dpcm, short *src, int samples)
{
	int *dst = pcmo->share->mix_buf;
	if (dpcm->mix == NULL || dpcm->mix->noop) {
		while (samples-- > 0) {
			*dst++ = *src++ * SHARE_MIXER_RESOLUTION;
		}
	} else {
		int *ttable = dpcm->mix->ttable;
#if SHARE_VOICES == 2
		while (samples > 0) {
			*dst++ = *src*ttable[0]+*(src+1)*ttable[1];
			*dst++ = *src*ttable[2]+*(src+1)*ttable[3];
			src += SHARE_VOICES;
			samples -= SHARE_VOICES;
		}
#else
#error "SHARE_VOICES != 2 not implemented"
#endif
	}
}

static void mix_block(snd_card_share_pcm_open_t *pcmo,
		      snd_card_share_pcm_t *dpcm, short *src, int samples)
{
	int *dst = pcmo->share->mix_buf;
	if (dpcm->mix == NULL || dpcm->mix->noop) {
		while (samples-- > 0) {
			*dst++ += *src++ * SHARE_MIXER_RESOLUTION;
		}
	} else {
		int *ttable = dpcm->mix->ttable;
#if SHARE_VOICES == 2
		while (samples > 0) {
			*dst++ += *src*ttable[0]+*(src+1)*ttable[1];
			*dst++ += *src*ttable[2]+*(src+1)*ttable[3];
			src += SHARE_VOICES;
			samples -= SHARE_VOICES;
		}
#else
#error "SHARE_VOICES != 2 not implemented"
#endif
	}
}

#define STEP (SHARE_SAMPLES*SHARE_VOICES*SHARE_SAMPLE_SIZE)

static void mix(snd_pcm_subchn_t *subchn, snd_card_share_pcm_open_t *pcmo,
		int frag, int count)
{
	snd_card_share_pcm_t *dpcm;
	snd_pcm_runtime_t *druntime = subchn->runtime;
	snd_pcm_runtime_t *sruntime;
	unsigned int pos;
	char *dst, *src;

	dst = druntime->dma_area->buf;
	dst += ((*druntime->frag_tail + frag) % druntime->frags) * druntime->buf.block.frag_size;
	pos = 0;
	while (count > 0) {
		int subchns = 0;
		short *d;
		int *s;
		int samples = count < STEP ? count : STEP;
		samples /= SHARE_SAMPLE_SIZE;
		for (dpcm = pcmo->run_pcm_list; dpcm; dpcm = dpcm->run_next) {
			if (dpcm->mix && dpcm->mix->zero)
				continue;
			sruntime = dpcm->subchn->runtime;
			src = sruntime->dma_area->buf;
			src += ((*sruntime->frag_tail + frag) % sruntime->frags) * sruntime->buf.block.frag_size;
			src += pos;
			if (subchns == 0)
				load_block(pcmo, dpcm, (short *)src, samples);
			else
				mix_block(pcmo, dpcm, (short *)src, samples);
			subchns++;
		}
		if (subchns == 0) {
			memset(dst, snd_pcm_format_silence(subchn->runtime->format.format), STEP);
		} else {
			s = pcmo->share->mix_buf;
			d = (short*) dst;
			while (samples-- > 0) {
				int v = *s++ / SHARE_MIXER_RESOLUTION;
				if (v < SHARE_SAMPLE_MIN)
					v = SHARE_SAMPLE_MIN;
				else if (v > SHARE_SAMPLE_MAX)
					v = SHARE_SAMPLE_MAX;
				*d++ = v;
			}
		}
		dst += STEP;
		pos += STEP;
		count -= STEP;
	}
}

static void snd_card_share_playback_ack_begin(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, runtime->private_data,);

	if (runtime->dma_area->buf == NULL)
		return;
	spin_lock(&pcmo->run_lock);
	mix(subchn, pcmo, 1, runtime->buf.block.frag_size);
	mix(subchn, pcmo, 2, 128);
	spin_unlock(&pcmo->run_lock);
}

static void cdivide1(snd_card_share_pcm_t *dpcm, 
		     short *dst, short *src, int samples)
{
	int v, *ttable;

	if (dpcm->mix == NULL || dpcm->mix->noop) {
		memcpy(dst, src, samples * SHARE_SAMPLE_SIZE);
		return;
	}
#if SHARE_VOICES == 2
	while (samples > 0) {
		ttable = dpcm->mix->ttable;
		v = (*src*ttable[0]+*(src+1)*ttable[1]) / SHARE_MIXER_RESOLUTION;
		if (v < SHARE_SAMPLE_MIN)
			v = SHARE_SAMPLE_MIN;
		else if (v > SHARE_SAMPLE_MAX)
			v = SHARE_SAMPLE_MAX;
		*dst++ = v;
		v = (*src*ttable[2]+*(src+1)*ttable[3]) / SHARE_MIXER_RESOLUTION;
		if (v < SHARE_SAMPLE_MIN)
			v = SHARE_SAMPLE_MIN;
		else if (v > SHARE_SAMPLE_MAX)
			v = SHARE_SAMPLE_MAX;
		*dst++ = v;
		src += SHARE_VOICES;
		samples -= SHARE_VOICES;
	}
#else
#error "SHARE_VOICES != 2 not implemented"
#endif
}

static void cdivide(snd_pcm_subchn_t *subchn, snd_card_share_pcm_open_t *pcmo, int count)
{
	snd_card_share_pcm_t *dpcm;
	snd_pcm_runtime_t *sruntime = subchn->runtime;
	snd_pcm_runtime_t *druntime;
	unsigned int pos;
	char *dst, *src;

	src = sruntime->dma_area->buf;
	src += (*sruntime->frag_head % sruntime->frags) * sruntime->buf.block.frag_size;
	pos = 0;
	for (dpcm = pcmo->run_pcm_list; dpcm; dpcm = dpcm->run_next) {
		druntime = dpcm->subchn->runtime;
		dst = druntime->dma_area->buf;
		dst += (*druntime->frag_head % druntime->frags) * druntime->buf.block.frag_size;
		if (dpcm->mix == NULL || dpcm->mix->zero) {
			memset(dst, snd_pcm_format_silence(druntime->format.format), count);
		} else {
			cdivide1(dpcm, (short *)dst, (short *)src, count / SHARE_SAMPLE_SIZE);
		}
	}
}

static void snd_card_share_capture_ack_begin(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, runtime->private_data, );

	if (runtime->dma_area->buf == NULL)
		return;
	spin_lock(&pcmo->run_lock);
	if (pcmo->run_pcm_list != NULL)
		cdivide(subchn, pcmo, runtime->buf.block.frag_size);
	spin_unlock(&pcmo->run_lock);
}

static void snd_card_share_pcm_ack_end(snd_pcm_subchn_t *subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, runtime->private_data, );
	snd_card_share_pcm_t *dpcm, *dpcm_next;

	spin_lock(&pcmo->run_lock);
	dpcm = pcmo->run_pcm_list;
	while (dpcm) {
		dpcm->pcm_buf_pos += runtime->buf.block.frag_size;
		dpcm_next = dpcm->run_next;
		snd_pcm_transfer_done(dpcm->subchn);
		dpcm = dpcm_next;
	}
	spin_unlock(&pcmo->run_lock);
}

static int snd_card_share_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_share_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 int snd_card_share_add_to_run_list(snd_card_share_pcm_open_t *pcmo,
					  snd_card_share_pcm_t *dpcm)
{
	unsigned long flags;
	int result;
	
	spin_lock_irqsave(&pcmo->run_lock, flags);
	dpcm->run_next = pcmo->run_pcm_list;
	result = pcmo->run_pcm_list == NULL;
	pcmo->run_pcm_list = dpcm;
	if (pcmo->pending_stop) {
		pcmo->pending_stop = 0;
		result = 0;
	}
	spin_unlock_irqrestore(&pcmo->run_lock, flags);
	return result;
}

static int snd_card_share_remove_from_run_list(snd_card_share_pcm_open_t *pcmo,
					       snd_card_share_pcm_t *dpcm)
{
	unsigned long flags = 0;
	snd_card_share_pcm_t *prev_dpcm;
	int result = 0;
	
	if (!in_interrupt())
		spin_lock_irqsave(&pcmo->run_lock, flags);
	if (pcmo->run_pcm_list != NULL) {
		if (pcmo->run_pcm_list == dpcm) {
			pcmo->run_pcm_list = dpcm->run_next;
			result = pcmo->run_pcm_list == NULL;
		} else {
			prev_dpcm = pcmo->run_pcm_list;
			while (prev_dpcm->run_next && prev_dpcm->run_next != dpcm)
				prev_dpcm = prev_dpcm->run_next;
			if (prev_dpcm->run_next != NULL)
				prev_dpcm->run_next = dpcm->run_next;
		}
	} else {
		result = 1;
	}
	if (!in_interrupt()) {
		if (pcmo->pending_stop) {
			pcmo->pending_stop = 0;
			result = 1;
		}
		spin_unlock_irqrestore(&pcmo->run_lock, flags);
	} else {
		if (result) {
			result = 0;
			pcmo->pending_stop = 1;
		}
	}
	return result;
}

static int snd_card_share_playback_trigger(void *private_data,
					   snd_pcm_subchn_t * subchn,
					   int cmd)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	snd_pcm_subchn_t *dsubchn = dpcm->pcmo->pcm_file->subchn;
	int err;

	if (cmd == SND_PCM_TRIGGER_GO) {
		if (snd_card_share_add_to_run_list(dpcm->pcmo, dpcm)) {
			if ((err = snd_pcm_kernel_playback_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_GO, 0)) < 0)
				return err;
		}
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		if (snd_card_share_remove_from_run_list(dpcm->pcmo, dpcm)) {
			if ((err = snd_pcm_kernel_playback_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_DRAIN, 0)) < 0)
				return err;
		}
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_share_capture_trigger(void *private_data,
					  snd_pcm_subchn_t * subchn,
					  int cmd)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	snd_pcm_subchn_t *dsubchn = dpcm->pcmo->pcm_file->subchn;
	int err;

	if (cmd == SND_PCM_TRIGGER_GO) {
		if (snd_card_share_add_to_run_list(dpcm->pcmo, dpcm)) {
			if ((err = snd_pcm_kernel_capture_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_GO, 0)) < 0)
				return err;
		}
	} else if (cmd == SND_PCM_TRIGGER_STOP) {
		if (snd_card_share_remove_from_run_list(dpcm->pcmo, dpcm)) {
			if ((err = snd_pcm_kernel_capture_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_FLUSH, 0)) < 0)
				return err;
		}
	} else {
		return -EINVAL;
	}
	return 0;
}

static int snd_card_share_playback_prepare(void *private_data,
					   snd_pcm_subchn_t * subchn)
{
	int err;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	if (dpcm->pcmo->run_pcm_list == NULL && dpcm->pcmo->pending_stop == 0) {
		snd_pcm_subchn_t *dsubchn;
		snd_pcm_runtime_t *druntime;
		unsigned char silence;
		dsubchn = dpcm->pcmo->pcm_file->subchn;
		if ((err = snd_pcm_kernel_playback_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_PREPARE, 0)) < 0)
			return err;
		druntime = dsubchn->runtime;
		if (druntime->dma_area == NULL)
			return -EIO;		/* TODO */
		silence = snd_pcm_format_silence(druntime->format.format);
		memset(druntime->dma_area->buf, silence, druntime->buf.block.frag_size + 128);
	}
	return 0;
}

static int snd_card_share_capture_prepare(void *private_data,
					  snd_pcm_subchn_t * subchn)
{
	int err;
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	
	if (dpcm->pcmo->run_pcm_list == NULL && dpcm->pcmo->pending_stop == 0) {
		snd_pcm_subchn_t *dsubchn;
		snd_pcm_runtime_t *druntime;
		dsubchn = dpcm->pcmo->pcm_file->subchn;
		if ((err = snd_pcm_kernel_capture_ioctl(dsubchn, SND_PCM_IOCTL_CHANNEL_PREPARE, 0)) < 0)
			return err;
		druntime = dsubchn->runtime;
		if (druntime->dma_area == NULL)
			return -EIO;		/* TODO */
	}
	return 0;
}

static unsigned int snd_card_share_playback_pointer(void *private_data,
						    snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	unsigned long flags;
	unsigned int result;
	snd_pcm_subchn_t *dsubchn;
	snd_pcm_runtime_t *druntime;

	spin_lock_irqsave(&dpcm->lock, flags);
	result = dpcm->pcm_buf_pos;
	dsubchn = dpcm->pcmo->pcm_file->subchn;
	druntime = dsubchn->runtime;
	result += druntime->hw->pointer(dsubchn->pchn->private_data, dsubchn) % druntime->buf.block.frag_size;
	spin_unlock_irqrestore(&dpcm->lock, flags);
	return result;
}

static unsigned int snd_card_share_capture_pointer(void *private_data,
						   snd_pcm_subchn_t * subchn)
{
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	unsigned long flags;
	unsigned int result;
	snd_pcm_subchn_t *dsubchn;
	snd_pcm_runtime_t *druntime;

	spin_lock_irqsave(&dpcm->lock, flags);
	result = dpcm->pcm_buf_pos;
	dsubchn = dpcm->pcmo->pcm_file->subchn;
	druntime = dsubchn->runtime;
	result += druntime->hw->pointer(dsubchn->pchn->private_data, dsubchn) % druntime->buf.block.frag_size;
	spin_unlock_irqrestore(&dpcm->lock, flags);
	return result;
}

static snd_pcm_hardware_t snd_card_share_playback =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_BLOCK |
	SND_PCM_CHNINFO_INTERLEAVE | SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_44100,
	44100,			/* min. rate */
	44100,			/* max. rate */
	SHARE_VOICES,		/* min. voices */
	SHARE_VOICES,		/* max. voices */
	4096,			/* min. fragment size */
	4096,			/* max. fragment size */
	0x0fff,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* transfer block size */
	snd_card_share_playback_ioctl,
	snd_card_share_playback_prepare,
	snd_card_share_playback_trigger,
	snd_card_share_playback_pointer
};

static snd_pcm_hardware_t snd_card_share_capture =
{
	SND_PCM_CHNINFO_MMAP | SND_PCM_CHNINFO_BLOCK |
	SND_PCM_CHNINFO_INTERLEAVE | SND_PCM_CHNINFO_MMAP_VALID,	/* flags */
	SND_PCM_FMT_S16_LE,	/* hardware formats */
	SND_PCM_RATE_44100,
	44100,			/* min. rate */
	44100,			/* max. rate */
	SHARE_VOICES,		/* min. voices */
	SHARE_VOICES,		/* max. voices */
	4096,			/* min. fragment size */
	4096,			/* max. fragment size */
	0x0fff,			/* fragment align */
	0,			/* FIFO size (unknown) */
	16,			/* transfer block size */
	snd_card_share_capture_ioctl,
	snd_card_share_capture_prepare,
	snd_card_share_capture_trigger,
	snd_card_share_capture_pointer
};

static int snd_card_share_pcm_params(snd_pcm_subchn_t * subchn)
{
	snd_pcm_channel_params_t params;

	memset(&params, 0, sizeof(params));
	params.mode = SND_PCM_MODE_BLOCK;
	params.format.interleave = 1;
	params.format.format = SND_PCM_SFMT_S16_LE;
	params.format.voices = SHARE_VOICES;
	params.format.rate = 44100;
	params.start_mode = SND_PCM_START_GO;
	params.stop_mode = SND_PCM_STOP_ROLLOVER;
	params.buf.block.frag_size = 4096;
	params.buf.block.frags_min = 1;
	params.buf.block.frags_max = -1;
	return snd_pcm_kernel_ioctl(subchn, SND_PCM_IOCTL_CHANNEL_PARAMS, (long)&params);
}

static int snd_card_share_playback_open(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, private_data, -ENXIO);
	snd_card_share_pcm_t *dpcm;
	snd_kmixer_t *mixer;
	snd_kmixer_element_t *element;
	struct file *file;
	int err;

	dpcm = snd_magic_kcalloc(snd_card_share_pcm_t, 0, GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm_dma_alloc(subchn, pcmo->share->dma1ptr, "Share - DAC")) < 0) {
		snd_magic_kfree(dpcm);
		return err;
	}
	down(&pcmo->open_mutex);
	if (pcmo->pcm_list == NULL) {
		file = &pcmo->file;
		memset(file, 0, sizeof(*file));
		file->f_flags = O_WRONLY | O_NONBLOCK;
		file->f_mode = FMODE_WRITE;
		if ((err = snd_pcm_open(-1, pcmo->pcm_card, pcmo->pcm_dev, file, SND_PCM_CHANNEL_PLAYBACK)) < 0) {
			up(&pcmo->open_mutex);
			snd_pcm_dma_free(subchn);
			return err;
		}
		pcmo->pcm_file = snd_magic_cast(snd_pcm_file_t, file->private_data, -ENXIO);
		if ((err = snd_card_share_pcm_params(pcmo->pcm_file->subchn)) < 0) {
			pcmo->pcm_file = NULL;
			up(&pcmo->open_mutex);
			snd_pcm_dma_free(subchn);
			return err;
		}
		pcmo->pcm_file->subchn->runtime->private_data = pcmo;
		pcmo->pcm_file->subchn->runtime->transfer_ack_begin = snd_card_share_playback_ack_begin;
		pcmo->pcm_file->subchn->runtime->transfer_ack_end = snd_card_share_pcm_ack_end;
	}
	dpcm->next = pcmo->pcm_list;
	pcmo->pcm_list = dpcm;
	up(&pcmo->open_mutex);
	spin_lock_init(&dpcm->lock);
	dpcm->subchn = subchn;
	dpcm->pcmo = pcmo;
	subchn->runtime->private_data = dpcm;
	subchn->runtime->private_free = _snd_magic_kfree;
	subchn->runtime->hw = &snd_card_share_playback;

	mixer = pcmo->share->mixer;
	if (mixer) {
		element = snd_mixer_element_find(mixer, SND_MIXER_ELEMENT_PLAYBACK, subchn->number, SND_MIXER_ETYPE_PLAYBACK2);
		if (element) {
			dpcm->mix = snd_magic_cast(snd_card_share_mixer_element_t, element->private_data, -ENXIO);
			snd_pcm_set_mixer(subchn, mixer->device, element);
		} else {
			dpcm->mix = NULL;
		}
	} else {
		dpcm->mix = 0;
	}
	return 0;
}

static int snd_card_share_capture_open(void *private_data,
				       snd_pcm_subchn_t * subchn)
{
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, private_data, -ENXIO);
	snd_card_share_pcm_t *dpcm;
	snd_kmixer_t *mixer;
	snd_kmixer_element_t *element;
	struct file *file;
	int err;

	dpcm = snd_magic_kcalloc(snd_card_share_pcm_t, 0, GFP_KERNEL);
	if (dpcm == NULL)
		return -ENOMEM;
	if ((err = snd_pcm_dma_alloc(subchn, pcmo->share->dma2ptr, "Share - ADC")) < 0) {
		snd_magic_kfree(dpcm);
		return err;
	}
	down(&pcmo->open_mutex);
	if (pcmo->pcm_list == NULL) {
		file = &pcmo->file;
		memset(file, 0, sizeof(*file));
		file->f_flags = O_RDONLY;
		file->f_mode = FMODE_READ;
		if ((err = snd_pcm_open(-1, pcmo->pcm_card, pcmo->pcm_dev, file, SND_PCM_CHANNEL_CAPTURE)) < 0) {
			up(&pcmo->open_mutex);
			snd_pcm_dma_free(subchn);
			return err;
		}
		pcmo->pcm_file = snd_magic_cast(snd_pcm_file_t, file->private_data, -ENXIO);
		if ((err = snd_card_share_pcm_params(pcmo->pcm_file->subchn)) < 0) {
			pcmo->pcm_file = NULL;
			up(&pcmo->open_mutex);
			snd_pcm_dma_free(subchn);
			return err;
		}
		pcmo->pcm_file->subchn->runtime->private_data = pcmo;
		pcmo->pcm_file->subchn->runtime->transfer_ack_begin = snd_card_share_capture_ack_begin;
		pcmo->pcm_file->subchn->runtime->transfer_ack_end = snd_card_share_pcm_ack_end;
	}
	dpcm->next = pcmo->pcm_list;
	pcmo->pcm_list = dpcm;
	up(&pcmo->open_mutex);
	spin_lock_init(&dpcm->lock);
	dpcm->subchn = subchn;
	dpcm->pcmo = pcmo;
	subchn->runtime->private_data = dpcm;
	subchn->runtime->private_free = _snd_magic_kfree;
	subchn->runtime->hw = &snd_card_share_capture;

	mixer = pcmo->share->mixer;
	if (mixer) {
		element = snd_mixer_element_find(mixer, SND_MIXER_ELEMENT_CAPTURE, subchn->number, SND_MIXER_ETYPE_CAPTURE2);
		if (element) {
			dpcm->mix = snd_magic_cast(snd_card_share_mixer_element_t, element->private_data, -ENXIO);
			snd_pcm_set_mixer(subchn, mixer->device, element);
		} else {
			dpcm->mix = NULL;
		}
	} else {
		dpcm->mix = 0;
	}
	return 0;
}

static int snd_card_share_playback_close(void *private_data,
					 snd_pcm_subchn_t * subchn)
{
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	snd_card_share_pcm_t *dpcm_prev;

	down(&pcmo->open_mutex);
	if (pcmo->pcm_list == dpcm) {
		pcmo->pcm_list = dpcm->next;
	} else {
		dpcm_prev = pcmo->pcm_list;
		while (dpcm_prev->next && dpcm_prev->next != dpcm)
			dpcm_prev = dpcm_prev->next;
		if (dpcm_prev->next == NULL) {
			snd_printk("snd_card_share_playback_close: pcm_list is broken\n");
		} else {
			dpcm_prev->next = dpcm->next;
		}
	}
	if (pcmo->pcm_list == NULL) {
		pcmo->pcm_file->subchn->runtime->private_data = NULL;
		pcmo->pcm_file->subchn->runtime->transfer_ack_begin = NULL;
		pcmo->pcm_file->subchn->runtime->transfer_ack_end = NULL;
		pcmo->pcm_file = NULL;
		if (snd_pcm_release(-1, pcmo->pcm_card, pcmo->pcm_dev, &pcmo->file) < 0)
			snd_printk("snd_card_share_playback_close: PCM parent release failed\n");
	}
	up(&pcmo->open_mutex);
	snd_pcm_dma_free(subchn);
	return 0;
}

static int snd_card_share_capture_close(void *private_data,
					snd_pcm_subchn_t * subchn)
{
	snd_card_share_pcm_open_t *pcmo = snd_magic_cast(snd_card_share_pcm_open_t, private_data, -ENXIO);
	snd_pcm_runtime_t *runtime = subchn->runtime;
	snd_card_share_pcm_t *dpcm = snd_magic_cast(snd_card_share_pcm_t, runtime->private_data, -ENXIO);
	snd_card_share_pcm_t *dpcm_prev;

	down(&pcmo->open_mutex);
	if (pcmo->pcm_list == dpcm) {
		pcmo->pcm_list = dpcm->next;
	} else {
		dpcm_prev = pcmo->pcm_list;
		while (dpcm_prev->next && dpcm_prev->next != dpcm)
			dpcm_prev = dpcm_prev->next;
		if (dpcm_prev->next == NULL) {
			snd_printk("snd_card_share_capture_close: pcm_list is broken\n");
		} else {
			dpcm_prev->next = dpcm->next;
		}
	}
	if (pcmo->pcm_list == NULL) {
		pcmo->pcm_file->subchn->runtime->private_data = NULL;
		pcmo->pcm_file->subchn->runtime->transfer_ack_begin = NULL;
		pcmo->pcm_file->subchn->runtime->transfer_ack_end = NULL;
		pcmo->pcm_file = NULL;
		if (snd_pcm_release(-1, pcmo->pcm_card, pcmo->pcm_dev, &pcmo->file) < 0)
			snd_printk("snd_card_share_capture_close: PCM parent release failed\n");
	}
	up(&pcmo->open_mutex);
	snd_pcm_dma_free(subchn);
	return 0;
}

static void snd_card_share_pcm_private_free(void *private_data)
{
	snd_pcm_t *pcm = snd_magic_cast(snd_pcm_t, private_data,);
	
	pcm->card->use_dec(pcm->card);
}

static int __init snd_card_share_pcm(snd_card_share_t *share, int device,
				     int pcm_card, int pcm_dev, int pcm_subchn, int pcm_maxopen,
				     snd_pcm_t **rpcm)
{
	snd_pcm_t *pcm, *pcm_parent;
	snd_card_share_pcm_open_t *pcmo;
	int err;

	if (pcm_card >= SND_CARDS || pcm_dev >= SND_MINOR_PCMS)
		return -EINVAL;
	snd_pcm_lock(0);
	pcm_parent = snd_pcm_devices[(pcm_card * SND_PCM_DEVICES) + pcm_dev];
	if (pcm_parent == NULL) {
		snd_pcm_lock(1);
		return -ENODEV;
	}
	pcm_parent->card->use_inc(pcm_parent->card);
	if (pcm_maxopen > MAX_PCM_SUBCHANNELS)
		pcm_maxopen = MAX_PCM_SUBCHANNELS;
	if ((err = snd_pcm_new(share->card, "Share PCM", device, pcm_maxopen, pcm_maxopen, &pcm)) < 0) {
		pcm_parent->card->use_dec(pcm_parent->card);
		snd_pcm_lock(1);
		return err;
	}
	pcm->private_data = pcm_parent;
	pcm->private_free = snd_card_share_pcm_private_free;
	snd_pcm_lock(1);
	if (pcm_parent->info_flags & SND_PCM_INFO_PLAYBACK) {
		pcmo = snd_magic_kcalloc(snd_card_share_pcm_open_t, 0, GFP_KERNEL);
		if (pcmo == NULL) {
			snd_device_free(share->card, pcm);
			return -ENOMEM;
		}
		init_MUTEX(&pcmo->open_mutex);
		spin_lock_init(&pcmo->run_lock);
		pcmo->share = share;
		pcmo->pcm_card = pcm_card;
		pcmo->pcm_dev = pcm_dev;
		pcmo->pcm_subchn = pcm_subchn;
		pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_data = pcmo;
		pcm->chn[SND_PCM_CHANNEL_PLAYBACK].private_free = _snd_magic_kfree;
		pcm->chn[SND_PCM_CHANNEL_PLAYBACK].open = snd_card_share_playback_open;
		pcm->chn[SND_PCM_CHANNEL_PLAYBACK].close = snd_card_share_playback_close;
	}
	if (pcm_parent->info_flags & SND_PCM_INFO_CAPTURE) {
		pcmo = snd_magic_kcalloc(snd_card_share_pcm_open_t, 0, GFP_KERNEL);
		if (pcmo == NULL) {
			snd_device_free(share->card, pcm);
			return -ENOMEM;
		}
		init_MUTEX(&pcmo->open_mutex);
		spin_lock_init(&pcmo->run_lock);
		pcmo->share = share;
		pcmo->pcm_card = pcm_card;
		pcmo->pcm_dev = pcm_dev;
		pcmo->pcm_subchn = pcm_subchn;
		pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_data = pcmo;
		pcm->chn[SND_PCM_CHANNEL_CAPTURE].private_free = _snd_magic_kfree;
		pcm->chn[SND_PCM_CHANNEL_CAPTURE].open = snd_card_share_capture_open;
		pcm->chn[SND_PCM_CHANNEL_CAPTURE].close = snd_card_share_capture_close;
	}
	pcm->info_flags = pcm_parent->info_flags;
	strcpy(pcm->name, "Share PCM");
	*rpcm = pcm;
	return 0;
}

static void calc_ttable(snd_card_share_mixer_element_t *mix)
{
	int src_voice, dst_voice;
	int *dptr;
	int noop = 1;
	int zero = 1;
        dptr = mix->ttable;
        for (dst_voice = 0; dst_voice < SHARE_VOICES; ++dst_voice) {
                for (src_voice = 0; src_voice < SHARE_VOICES; ++src_voice) {
			int v = 0;
                        if (src_voice == dst_voice) {
				if (snd_mixer_get_bit(&mix->sw, src_voice)) {
					v = mix->volume[src_voice];
					if (v != SHARE_MIXER_RESOLUTION)
						noop = 0;
					if (v != 0)
						zero = 0;
				}
				else
					noop = 0;
			}
			*dptr++ = v;
                }
        }
	mix->noop = noop;
	mix->zero = zero;
}

static int snd_card_share_mixer_volume(snd_kmixer_element_t *element, int w_flag, int *voices)
{
	snd_card_share_mixer_element_t *mix = snd_magic_cast(snd_card_share_mixer_element_t, element->private_data, -ENXIO);
	int v;
	int change = 0;
	if (!w_flag) {
		for (v = 0; v < SHARE_VOICES; ++v)
			voices[v] = mix->volume[v];
	} else {
		for (v = 0; v < SHARE_VOICES; ++v) {
			if (voices[v] != mix->volume[v]) {
				change = 1;
				mix->volume[v] = voices[v];
			}
		}
		if (change)
			calc_ttable(mix);
	}
	return change;
}

static int snd_card_share_mixer_switch(snd_kmixer_element_t *element, int w_flag, unsigned int *bitmap)
{
	snd_card_share_mixer_element_t *mix = snd_magic_cast(snd_card_share_mixer_element_t, element->private_data, -ENXIO);
	int change = 0;
	if (!w_flag) {
		bitmap[0] = mix->sw;
	} else {
		if ((bitmap[0] & ((1<<SHARE_VOICES)-1)) != mix->sw)
			change = 1;
		mix->sw = bitmap[0];
		if (change)
			calc_ttable(mix);
	}
	return change;
}

static int snd_card_share_mixer_group_playback(snd_kmixer_group_t * group,
					       snd_kmixer_file_t *file,
					       int w_flag,
					       snd_mixer_group_t *ugroup)
{
	snd_card_share_mixer_group_t *gmix = snd_magic_cast(snd_card_share_mixer_group_t, group->private_data, -ENXIO);
	int voices[2];
	unsigned int bitmap;
	int change = 0;
	
	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (gmix->me_out_vol) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			snd_card_share_mixer_volume(gmix->me_out_vol, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = SHARE_MIXER_RESOLUTION;
		} else if (gmix->me_in_vol) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			snd_card_share_mixer_volume(gmix->me_in_vol, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = SHARE_MIXER_RESOLUTION;
		}
		if (gmix->me_out_sw) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			snd_card_share_mixer_switch(gmix->me_out_sw, 0, &bitmap);
			ugroup->mute = 0;
			if (!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (gmix->me_in_sw) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			snd_card_share_mixer_switch(gmix->me_in_sw, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}			
	} else {
		if (gmix->me_out_vol) {
			voices[0] = ugroup->volume.names.front_left % (SHARE_MIXER_RESOLUTION+1);
			voices[1] = ugroup->volume.names.front_right % (SHARE_MIXER_RESOLUTION+1);
			if (snd_card_share_mixer_volume(gmix->me_out_vol, 1, voices) > 0) {
				snd_mixer_element_value_change(file, gmix->me_out_vol, 0);
				change = 1;
			}
		}
		else if (gmix->me_in_vol) {
			voices[0] = ugroup->volume.names.front_left % (SHARE_MIXER_RESOLUTION+1);
			voices[1] = ugroup->volume.names.front_right % (SHARE_MIXER_RESOLUTION+1);
			if (snd_card_share_mixer_volume(gmix->me_in_vol, 1, voices) > 0) {
				snd_mixer_element_value_change(file, gmix->me_in_vol, 0);
				change = 1;
			}
		}
		if (gmix->me_out_sw) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (snd_card_share_mixer_switch(gmix->me_out_sw, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, gmix->me_out_sw, 0);
				change = 1;
			}
		}
		if (gmix->me_in_sw) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (snd_card_share_mixer_switch(gmix->me_in_sw, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, gmix->me_in_sw, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_card_share_mixer_group_capture(snd_kmixer_group_t * group,
					      snd_kmixer_file_t *file,
					      int w_flag,
					      snd_mixer_group_t *ugroup)
{
	snd_card_share_mixer_group_t *gmix = snd_magic_cast(snd_card_share_mixer_group_t, group->private_data, -ENXIO);
	int voices[2];
	int change = 0;
	
	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		snd_card_share_mixer_volume(gmix->me_in_vol, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = SHARE_MIXER_RESOLUTION;
	} else {
		voices[0] = ugroup->volume.names.front_left % (SHARE_MIXER_RESOLUTION+1);
		voices[1] = ugroup->volume.names.front_right % (SHARE_MIXER_RESOLUTION+1);
		if (snd_card_share_mixer_volume(gmix->me_in_vol, 1, voices) > 0) {
			snd_mixer_element_value_change(file, gmix->me_in_vol, 0);
			change = 1;
		}
	}
	return change;
}

static int oss_mixer_map[MAX_PCM_SUBCHANNELS] = {
	SND_MIXER_OSS_PCM,
	SND_MIXER_OSS_SPEAKER,
	SND_MIXER_OSS_LINE,
	SND_MIXER_OSS_MIC,
	SND_MIXER_OSS_CD,
	SND_MIXER_OSS_IMIX,
	SND_MIXER_OSS_ALTPCM,
	SND_MIXER_OSS_RECLEV,
	SND_MIXER_OSS_IGAIN,
	SND_MIXER_OSS_OGAIN,
	SND_MIXER_OSS_LINE1,
	SND_MIXER_OSS_LINE2,
	SND_MIXER_OSS_LINE3,
	SND_MIXER_OSS_DIGITAL1,
	SND_MIXER_OSS_DIGITAL2,
	SND_MIXER_OSS_DIGITAL3,
	SND_MIXER_OSS_PHONEIN,
	SND_MIXER_OSS_PHONEOUT,
	SND_MIXER_OSS_VIDEO,
	SND_MIXER_OSS_RADIO,
	SND_MIXER_OSS_MONITOR,
	[ 21 ... MAX_PCM_SUBCHANNELS-1 ] = SND_MIXER_OSS_UNKNOWN 
};


static int __init snd_card_share_mixer(snd_card_share_t *share, int device,
				       snd_pcm_t *pcm,
				       int pcm_card, int pcm_dev, int pcm_subchn,
				       int pcm_maxopen)
{
	int err;
	int k;
	snd_pcm_t *pcm_parent;
	snd_kmixer_t *mixer, *mixerc = NULL;
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *pcm_el, *volume_el, *switch_el;
	snd_kmixer_element_t *accu_el = NULL, *input_el = NULL, *output_el = NULL;
	int playback_voices[SHARE_VOICES] = {
		[0 ... SHARE_VOICES-1] = (SHARE_MIXER_RESOLUTION * 75) / 100
	};
	int capture_voices[SHARE_VOICES] = {
		[0 ... SHARE_VOICES-1] = (SHARE_MIXER_RESOLUTION * 100) / 100
	};
	unsigned int bitmap = (1<<SHARE_VOICES)-1;
	static struct snd_mixer_element_volume1_range range[SHARE_VOICES] = {
		[0 ... SHARE_VOICES-1] = {0, SHARE_MIXER_RESOLUTION, -4600, 0}
	};

	if (pcm_card >= SND_CARDS || pcm_dev >= SND_MINOR_PCMS)
		return -EINVAL;
	snd_pcm_lock(0);
	pcm_parent = snd_pcm_devices[(pcm_card * SND_PCM_DEVICES) + pcm_dev];
	snd_pcm_lock(1);
	if (pcm_parent == NULL)
		return -ENODEV;
	if (pcm_maxopen > MAX_PCM_SUBCHANNELS)
		pcm_maxopen = MAX_PCM_SUBCHANNELS;
	if ((err = snd_mixer_new(share->card, "Share Mixer", device, &mixer)) < 0)
		return err;
        sprintf(mixer->name, "Share Mixer");

	if (pcm_parent->info_flags & SND_PCM_INFO_PLAYBACK) {
		if ((output_el = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER_DIGITAL, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
			goto __error;
		if ((accu_el = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_DIGITAL_ACCU, 0, 0)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, accu_el, output_el))
			goto __error;
	}
	if (pcm_parent->info_flags & SND_PCM_INFO_CAPTURE) {
		if ((input_el = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_DAC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
			goto __error;
		if (pcm_parent->info_flags & SND_PCM_INFO_PLAYBACK) {
			if ((err = snd_mixer_new(share->card, "Share Capture Mixer", device+1, &mixerc)) < 0)
				goto __error;
			sprintf(mixerc->name, "Share Capture Mixer");
		}
	}
	for (k = 0; k < pcm_maxopen; ++k) {
		snd_card_share_mixer_group_t *gmix;
		gmix = snd_magic_kcalloc(snd_card_share_mixer_group_t, 0, GFP_KERNEL);
		if (gmix == NULL)
			goto __error;
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, k, oss_mixer_map[k], snd_card_share_mixer_group_playback, gmix)) == NULL) {
			snd_magic_kfree(gmix);
			goto __error;
		}
		group->private_free = _snd_magic_kfree;
		if (pcm_parent->info_flags & SND_PCM_INFO_PLAYBACK) {
			snd_card_share_mixer_element_t *mix;
			mix = snd_magic_kcalloc(snd_card_share_mixer_element_t, 0, GFP_KERNEL);
			if (mix == NULL)
				goto __error;

			if ((pcm_el = snd_mixer_lib_pcm2(mixer, SND_MIXER_ELEMENT_PLAYBACK, k, SND_MIXER_ETYPE_PLAYBACK2, pcm->device, k)) == NULL)
				goto __error;
			pcm_el->private_data = mix;			

			if ((volume_el = snd_mixer_lib_volume1(mixer, "Playback Volume", k, SHARE_VOICES, range, snd_card_share_mixer_volume, mix)) == NULL)
				goto __error;
			volume_el->private_free = _snd_magic_kfree;
			gmix->me_out_vol = volume_el;
			snd_card_share_mixer_volume(volume_el, 1, playback_voices);

			if ((switch_el = snd_mixer_lib_sw1(mixer, "Playback Switch", k, SHARE_VOICES, snd_card_share_mixer_switch, mix)) == NULL)
				goto __error;
			gmix->me_out_sw = switch_el;
			snd_card_share_mixer_switch(volume_el, 1, &bitmap);
			
			if (snd_mixer_group_element_add(mixer, group, volume_el) < 0)
				goto __error;
			if (snd_mixer_group_element_add(mixer, group, switch_el) < 0)
				goto __error;
			if (snd_mixer_element_route_add(mixer, pcm_el, volume_el))
				goto __error;
			if (snd_mixer_element_route_add(mixer, volume_el, switch_el))
				goto __error;
			if (snd_mixer_element_route_add(mixer, switch_el, accu_el))
				goto __error;
		}
		if (pcm_parent->info_flags & SND_PCM_INFO_CAPTURE) {
			snd_card_share_mixer_element_t *mix;

			mix = snd_magic_kcalloc(snd_card_share_mixer_element_t, 0, GFP_KERNEL);
			if (mix == NULL)
				goto __error;

			if ((pcm_el = snd_mixer_lib_pcm2(mixer, SND_MIXER_ELEMENT_CAPTURE, k, SND_MIXER_ETYPE_CAPTURE2, pcm->device, k)) == NULL)
				goto __error;
			pcm_el->private_data = mix;

			if ((volume_el = snd_mixer_lib_volume1(mixer, "Capture Volume", k, SHARE_VOICES, range, snd_card_share_mixer_volume, mix)) == NULL)
				goto __error;
			volume_el->private_free = _snd_magic_kfree;
			gmix->me_in_vol = volume_el;
			snd_card_share_mixer_volume(volume_el, 1, capture_voices);
			
			if ((switch_el = snd_mixer_lib_sw1(mixer, "Capture Switch", k, SHARE_VOICES, snd_card_share_mixer_switch, mix)) == NULL)
				goto __error;
			gmix->me_in_sw = switch_el;
			snd_card_share_mixer_switch(volume_el, 1, &bitmap);
			
			if (snd_mixer_group_element_add(mixer, group, volume_el) < 0)
				goto __error;
			if (snd_mixer_group_element_add(mixer, group, switch_el) < 0)
				goto __error;
			if (snd_mixer_element_route_add(mixer, input_el, volume_el))
				goto __error;
			if (snd_mixer_element_route_add(mixer, volume_el, switch_el))
				goto __error;
			if (snd_mixer_element_route_add(mixer, switch_el, pcm_el))
				goto __error;
			if (mixerc &&
			    snd_mixer_lib_group_ctrl(mixerc, SND_MIXER_IN_PCM, k, oss_mixer_map[k], snd_card_share_mixer_group_capture, gmix) == NULL)
				goto __error;
		}
	}
	share->mixer = mixer;
	return 0;


 __error:
 	if (mixerc != NULL)
		snd_device_free(share->card, mixerc);
	snd_device_free(share->card, mixer);
	return -ENOMEM;
}

static int __init snd_card_share_resources(snd_card_share_t *share)
{
	int err;

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

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

	return 0;
}

static void snd_card_share_free(snd_card_share_t *share)
{
	if (share == NULL)
		return;
	snd_card_unregister(share->card);
	snd_kfree(share);
}

static int __init snd_card_share_probe(int dev, struct snd_card_share *share)
{
	snd_card_t *card;
	snd_pcm_t *pcm;
	int idx;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_card_share_use_inc, snd_card_share_use_dec);
	if (card == NULL) {
		snd_card_share_free(share);
		return -ENOMEM;
	}
	card->type = SND_CARD_TYPE_SHARE;
	share->card = card;
	if (snd_card_share_resources(share) < 0) {
		snd_card_share_free(share);
		return -ENOMEM;
	}
	for (idx = 0; idx < SND_MINOR_PCMS; idx++) {
		int pcmidx = (dev * SND_MINOR_PCMS) + idx;
		if (snd_pcm_card[pcmidx] < 0)
			break;
		if (snd_pcm_dev[pcmidx] < 0)
			continue;
		if (snd_pcm_subchannel[pcmidx] < 0)
			continue;
		if (snd_pcm_maxopen[pcmidx] < 1)
			continue;
		if (snd_card_share_pcm(share, idx,
				       snd_pcm_card[pcmidx],
				       snd_pcm_dev[pcmidx],
				       snd_pcm_subchannel[pcmidx],
				       snd_pcm_maxopen[pcmidx],
				       &pcm) < 0)
			goto __nodev;
		if (snd_card_share_mixer(share, idx*2, pcm,
					 snd_pcm_card[pcmidx],
					 snd_pcm_dev[pcmidx],
					 snd_pcm_subchannel[pcmidx],
					 snd_pcm_maxopen[pcmidx]) < 0)
			goto __nodev;
	}
	strcpy(card->abbreviation, "Share");
	strcpy(card->shortname, "Share");
	sprintf(card->longname, "Share %i", dev + 1);
	if (!snd_card_register(card))
		return 0;

      __nodev:
	snd_card_share_free(share);
	return -ENXIO;
}

static int __init alsa_card_share_init(void)
{
	int dev, cards;
	snd_card_share_t *share;

	for (dev = cards = 0; dev < SND_CARDS && snd_enable[dev]; dev++) {
		share = (struct snd_card_share *)
				snd_kcalloc(sizeof(snd_card_share_t), GFP_KERNEL);
		if (share == NULL)
			continue;
		if (snd_card_share_probe(dev, share) < 0) {
#ifdef MODULE
			snd_printk("Share soundcard #%i not found or device busy\n", dev + 1);
#endif
			break;
		}
		snd_card_share_cards[dev] = share;
		cards++;
	}
	if (!cards) {
#ifdef MODULE
		snd_printk("Share soundcard #%i not found or device busy\n", dev + 1);
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_share_exit(void)
{
	int idx;

	for (idx = 0; idx < SND_CARDS; idx++) {
		snd_card_share_free(snd_card_share_cards[idx]);
	}
}

module_init(alsa_card_share_init)
module_exit(alsa_card_share_exit)
