/*
 *  Abstract routines for MIXER control - element handling library
 *  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.
 *
 */

#include "../include/driver.h"
#include "../include/minors.h"
#include "../include/mixer.h"
#include "../include/info.h"
#include "../include/control.h"

snd_kmixer_group_t *snd_mixer_lib_group(snd_kmixer_t *mixer,
					char *name,
					int index)
{
	snd_kmixer_group_new_t ngroup;

	memset(&ngroup, 0, sizeof(ngroup));
	ngroup.name = name;
	ngroup.index = index;
	return snd_mixer_group_new(mixer, &ngroup);
}

snd_kmixer_group_t *snd_mixer_lib_group_ctrl(snd_kmixer_t *mixer,
					     char *name,
					     int index,
					     int oss_dev,
					     snd_kmixer_group_control_t *control,
					     void *private_data)
{
	snd_kmixer_group_t *group;

	group = snd_mixer_lib_group(mixer, name, index);
	if (group) {
		group->oss_dev = oss_dev;
		group->control = control;
		group->private_data = private_data;
	}
	return group;
}

/*
 *  INPUT/OUTPUT - read only
 */

static int snd_mixer_lib_io_info(snd_kmixer_element_t *element,
				 snd_kmixer_file_t *mfile,
				 snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_io *io = (struct snd_stru_mixer_lib_io *)snd_mixer_ext_element_private_data(element);
	int idx;
	
	info->data.io.voices = 0;
	for (idx = 0; idx < info->data.io.voices_size && idx < io->voices_count; idx++) {
		if (copy_to_user(&info->data.io.pvoices[idx], &io->voices[idx], sizeof(snd_mixer_voice_t)))
			return -EFAULT;
		info->data.io.voices++;
	}
	info->data.io.voices_over = io->voices_count - idx;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_io(snd_kmixer_t *mixer,
				       char *name,
				       int index,
				       int type,
				       unsigned int attrib,
				       int voices_count,
				       snd_mixer_voice_t *voices)
{
	snd_kmixer_element_t *element;
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_io *io, *io1;

	if (name == NULL || voices_count <= 0 || voices == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = snd_mixer_lib_io_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(*io) + voices_count * sizeof(snd_mixer_voice_t);

	io = (struct snd_stru_mixer_lib_io *)snd_kmalloc(nelement.ext_size, GFP_KERNEL);
	if (io == NULL)
		return NULL;

	nelement.ext_ptr = io;

	io->attrib = attrib;
	io->voices_count = voices_count;
	io->voices = (snd_mixer_voice_t *)(io + 1);
	memcpy(io->voices, voices, voices_count * sizeof(snd_mixer_voice_t));

	element = snd_mixer_element_new(mixer, &nelement);
	if (element) {
		io1 = (struct snd_stru_mixer_lib_io *)snd_mixer_ext_element_private_data(element);
		io1->voices = (snd_mixer_voice_t *)(io1 + 1);
	}

	snd_kfree(io);
	return element;
}

snd_kmixer_element_t *snd_mixer_lib_io_mono(snd_kmixer_t *mixer,
					    char *name,
					    int index,
					    int type,
					    unsigned int attrib)
{
	static snd_mixer_voice_t voice = {SND_MIXER_VOICE_MONO, 0};
	return snd_mixer_lib_io(mixer, name, index, type, attrib, 1, &voice);
}

snd_kmixer_element_t *snd_mixer_lib_io_stereo(snd_kmixer_t *mixer,
					      char *name,
					      int index,
					      int type,
					      unsigned int attrib)
{
	static snd_mixer_voice_t voices[2] = {
		{SND_MIXER_VOICE_LEFT, 0},
		{SND_MIXER_VOICE_RIGHT, 0}
	};
	return snd_mixer_lib_io(mixer, name, index, type, attrib, 2, voices);
}

/*
 *  PCM CAPTURE/PLAYBACK devices - read only
 */

static int snd_mixer_lib_pcm1_info(snd_kmixer_element_t *element,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_pcm1 *pcm = (struct snd_stru_mixer_lib_pcm1 *)snd_mixer_ext_element_private_data(element);
	int idx;
	
	info->data.pcm1.devices = 0;
	for (idx = 0; idx < info->data.pcm1.devices_size && idx < pcm->devices_count; idx++) {
		if (copy_to_user(&info->data.pcm1.pdevices[idx], &pcm->devices[idx], sizeof(int)))
			return -EFAULT;
		info->data.pcm1.devices++;
	}
	info->data.pcm1.devices_over = pcm->devices_count - idx;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_pcm1(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 int type,
					 int devices_count,
					 int *devices)
{
	snd_kmixer_element_t *element;
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_pcm1 *pcm, *pcm1;

	if (name == NULL || devices_count <= 0 || devices == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = snd_mixer_lib_pcm1_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(*pcm) + devices_count * sizeof(int);

	pcm = (struct snd_stru_mixer_lib_pcm1 *)snd_kmalloc(nelement.ext_size, GFP_KERNEL);
	if (pcm == NULL)
		return NULL;
	nelement.ext_ptr = pcm;

	pcm->devices_count = devices_count;
	pcm->devices = (int *)(pcm + 1);
	memcpy(pcm->devices, devices, devices_count * sizeof(int));

	element = snd_mixer_element_new(mixer, &nelement);
	if (element) {
		pcm1 = (struct snd_stru_mixer_lib_pcm1 *)snd_mixer_ext_element_private_data(element);
		pcm1->devices = (int *)(pcm1 + 1);
	}

	snd_kfree(pcm);	
	return element;
}

/*
 *  PCM CAPTURE/PLAYBACK subdevice - read only
 */

static int snd_mixer_lib_pcm2_info(snd_kmixer_element_t *element,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_pcm2 *pcm = (struct snd_stru_mixer_lib_pcm2 *)snd_mixer_ext_element_private_data(element);

	info->data.pcm2.device = pcm->device;
	info->data.pcm2.subdevice = pcm->subdevice;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_pcm2(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 int type,
					 int device,
					 int subdevice)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_pcm2 pcm;

	if (name == NULL)
		return NULL;
	memset(&pcm, 0, sizeof(pcm));
	pcm.device = device;
	pcm.subdevice = subdevice;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = snd_mixer_lib_pcm2_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(pcm);
	nelement.ext_ptr = &pcm;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  ADC/DAC - read only
 */

static int snd_mixer_lib_converter_info(snd_kmixer_element_t *element,
					snd_kmixer_file_t *mfile,
					snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_converter *conv = (struct snd_stru_mixer_lib_converter *)snd_mixer_ext_element_private_data(element);

	info->data.converter.resolution = conv->resolution;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_converter(snd_kmixer_t *mixer,
					      char *name,
					      int index,
					      int type,
					      unsigned int resolution)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_converter conv;

	if (name == NULL || resolution < 8)
		return NULL;
	memset(&conv, 0, sizeof(conv));
	conv.resolution = resolution;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = snd_mixer_lib_converter_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(conv);
	nelement.ext_ptr = &conv;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple on/off switch - read write
 */

static int snd_mixer_lib_sw1_control(snd_kmixer_element_t *element,
				     snd_kmixer_file_t *mfile,
				     int w_flag,
				     snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_sw1 *sw1 = (struct snd_stru_mixer_lib_sw1 *)snd_mixer_ext_element_private_data(element);
	int bsize, bsize1, result;
	unsigned int *bitmap;
	
	result = 0;
	bsize = ((sw1->switches + 31) / 32) * sizeof(unsigned int);
	if (bsize <= 0)
		return -EINVAL;

	bitmap = snd_kcalloc(bsize, GFP_KERNEL);
	if (bitmap == NULL)
		return -ENOMEM;
	if (uelement->data.switch1.sw_size > 0 && uelement->data.switch1.psw == NULL) {
		result = -EINVAL;
		goto __end;
	}
	bsize1 = ((uelement->data.switch1.sw_size + 31) / 32) * sizeof(unsigned int);
	if (bsize1 > bsize)
		bsize1 = bsize;
	if (!w_flag) {
		result = sw1->control(element, 0, bitmap);
		if (result >= 0) {
			uelement->data.switch1.sw = sw1->switches;
			if (uelement->data.switch1.sw > uelement->data.switch1.sw_size)
				uelement->data.switch1.sw = uelement->data.switch1.sw_size;
			uelement->data.switch1.sw_over = sw1->switches - uelement->data.switch1.sw;
			if (copy_to_user(uelement->data.switch1.psw, bitmap, bsize1))
				result = -EFAULT;
		}
	} else {
		if (uelement->data.switch1.sw_size < sw1->switches) {
			result = -EINVAL;
			goto __end;
		}
		if (uelement->data.switch1.sw != sw1->switches) {
			result = -EINVAL;
			goto __end;
		}
		if (copy_from_user(bitmap, uelement->data.switch1.psw, bsize)) {
			result = -EFAULT;
		} else {
			result = sw1->control(element, 1, bitmap);
		}
	}
      __end:
	snd_kfree(bitmap);
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw1_value(snd_kmixer_t *mixer,
				char *name,
				int index,
				int switches,
				snd_mixer_sw1_control_t *control,
				void *private_data,
				unsigned long private_value)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw1 sw1;
	unsigned int *bitmap;

	if (name == NULL || switches <= 0 || control == NULL)
		return NULL;
	memset(&sw1, 0, sizeof(sw1));
	sw1.switches = switches;
	sw1.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_SWITCH1;
	nelement.info = NULL;
	nelement.control = snd_mixer_lib_sw1_control;
	nelement.ext_size = sizeof(sw1);
	nelement.ext_ptr = &sw1;
	nelement.private_data = private_data;
	nelement.private_value = private_value;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn all switches off */
		bitmap = (unsigned int *)snd_kcalloc(((switches + 31) / 32) * sizeof(unsigned int), GFP_KERNEL);
		if (bitmap) {
			control(result, 1, bitmap);
			snd_kfree(bitmap);
		}
	}
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw1(snd_kmixer_t *mixer,
			  char *name,
			  int index,
			  int switches,
			  snd_mixer_sw1_control_t *control,
			  void *private_data)
{
	return snd_mixer_lib_sw1_value(mixer, name, index,
				       switches, control, private_data, 0);
}

/*
 *  Simple on/off switch for each voice - read write
 */

static int snd_mixer_lib_sw2_control(snd_kmixer_element_t *element,
				     snd_kmixer_file_t *mfile,
				     int w_flag,
				     snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_sw2 *sw2 = (struct snd_stru_mixer_lib_sw2 *)snd_mixer_ext_element_private_data(element);
	int result, value;
	
	result = 0;
	if (!w_flag) {
		value = 0;
		result = sw2->control(element, 0, &value);
		uelement->data.switch2.sw = value ? 1 : 0;
	} else {
		value = uelement->data.switch2.sw ? 1 : 0;
		result = sw2->control(element, 1, &value);
	}
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw2_value(snd_kmixer_t *mixer,
				char *name,
				int index,
				snd_mixer_sw2_control_t *control,
				void *private_data,
				unsigned long private_value)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw2 sw2;
	int val;

	if (name == NULL || control == NULL)
		return NULL;
	memset(&sw2, 0, sizeof(sw2));
	sw2.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_SWITCH2;
	nelement.info = NULL;
	nelement.ext_size = sizeof(sw2);
	nelement.ext_ptr = &sw2;
	nelement.control = snd_mixer_lib_sw2_control;
	nelement.private_data = private_data;
	nelement.private_value = private_value;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn switch off */
		val = 0;
		control(result, 1, &val);
	}
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw2(snd_kmixer_t *mixer,
			  char *name,
			  int index,
			  snd_mixer_sw2_control_t *control,
			  void *private_data)
{
	return snd_mixer_lib_sw2_value(mixer, name, index, control,
				       private_data, 0);
}

/*
 *  Route on/off switch - read write
 */

static int snd_mixer_lib_sw3_info(snd_kmixer_element_t *element,
				  snd_kmixer_file_t *mfile,
				  snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_sw3 *sw3 = (struct snd_stru_mixer_lib_sw3 *)snd_mixer_ext_element_private_data(element);
	int idx;
	
	info->data.switch3.voices = 0;
	for (idx = 0; idx < info->data.switch3.voices_size && idx < sw3->voices_count; idx++) {
		if (copy_to_user(&info->data.switch3.pvoices[idx], &sw3->voices[idx], sizeof(snd_mixer_voice_t)))
			return -EFAULT;
		info->data.switch3.voices++;
	}
	info->data.switch3.voices_over = sw3->voices_count - idx;
	return 0;
}

static int snd_mixer_lib_sw3_control(snd_kmixer_element_t *element,
				     snd_kmixer_file_t *mfile,
				     int w_flag,
				     snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_sw3 *sw3 = (struct snd_stru_mixer_lib_sw3 *)snd_mixer_ext_element_private_data(element);
	int bsize, bsize1, result;
	unsigned int *bitmap;
	
	result = 0;
	bsize = ((sw3->voices_count * sw3->voices_count + 31) / 32) * sizeof(unsigned int);
	if (bsize <= 0)
		return -EINVAL;
	bitmap = snd_kcalloc(bsize, GFP_KERNEL);
	if (bitmap == NULL)
		return -ENOMEM;
	if (uelement->data.switch3.rsw_size > 0 && uelement->data.switch3.prsw == NULL) {
		result = -EINVAL;
		goto __end;
	}
	bsize1 = ((uelement->data.switch3.rsw_size + 31) / 32) * sizeof(unsigned int);
	if (bsize1 > bsize)
		bsize1 = bsize;
	if (!w_flag) {
		result = sw3->control(element, 0, bitmap);
		if (result >= 0) {
			uelement->data.switch3.rsw = sw3->voices_count * sw3->voices_count;
			if (uelement->data.switch3.rsw > uelement->data.switch3.rsw_size)
				uelement->data.switch3.rsw = uelement->data.switch3.rsw_size;
			uelement->data.switch3.rsw_over = sw3->voices_count * sw3->voices_count - uelement->data.switch3.rsw;
			if (copy_to_user(uelement->data.switch3.prsw, bitmap, bsize1))
				result = -EFAULT;
		}
	} else {
		if (uelement->data.switch3.rsw_size < sw3->voices_count * sw3->voices_count) {
			result = -EINVAL;
			goto __end;
		}
		if (uelement->data.switch3.rsw != sw3->voices_count * sw3->voices_count) {
			result = -EINVAL;
			goto __end;
		}
		if (copy_from_user(bitmap, uelement->data.switch3.prsw, bsize)) {
			result = -EFAULT;
		} else {
			result = sw3->control(element, 1, bitmap);
		}
	}
      __end:
	snd_kfree(bitmap);
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw3_value(snd_kmixer_t *mixer,
				char *name,
				int index,
				int type,
				int voices_count,
				snd_mixer_voice_t *voices,
				snd_mixer_sw3_control_t *control,
				void *private_data,
				unsigned long private_value)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw3 *sw3, *sw3_1;
	unsigned int *bitmap;
	int idx;

	if (name == NULL || voices_count <= 0 || control == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_SWITCH3;
	nelement.info = snd_mixer_lib_sw3_info;
	nelement.control = snd_mixer_lib_sw3_control;
	nelement.ext_size = sizeof(*sw3) + voices_count * sizeof(snd_mixer_voice_t);
	nelement.private_data = private_data;
	nelement.private_value = private_value;

	sw3 = (struct snd_stru_mixer_lib_sw3 *)snd_kcalloc(nelement.ext_size, GFP_KERNEL);
	if (sw3 == NULL)
		return NULL;

	nelement.ext_ptr = sw3;

	sw3->type = type;
	sw3->voices_count = voices_count;
	sw3->control = control;
	sw3->voices = (snd_mixer_voice_t *)(sw3 + 1);
	memcpy(sw3->voices, voices, voices_count * sizeof(snd_mixer_voice_t));

	result = snd_mixer_element_new(mixer, &nelement);

	snd_kfree(sw3);
	
	if (result) {
		sw3_1 = (struct snd_stru_mixer_lib_sw3 *)snd_mixer_ext_element_private_data(result);
		sw3_1->voices = (snd_mixer_voice_t *)(sw3_1 + 1);
		/* initial settings - turn all switches off */
	 	bitmap = (unsigned int *)snd_kcalloc(((voices_count + 31) / 32) * sizeof(unsigned int), GFP_KERNEL);
		if (bitmap) {
			if (type == SND_MIXER_SWITCH3_ALWAYS_DESTINATION ||
			    type == SND_MIXER_SWITCH3_ALWAYS_ONE_DESTINATION) {
			    	for (idx = 0; idx < voices_count; idx++)
			    		snd_mixer_set_bit(bitmap, (idx * voices_count) + idx, 1);
			}
			control(result, 1, bitmap);
			snd_kfree(bitmap);
		}
	}
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_sw3(snd_kmixer_t *mixer,
			  char *name,
			  int index,
			  int type,
			  int voices_count,
			  snd_mixer_voice_t *voices,
			  snd_mixer_sw3_control_t *control,
			  void *private_data)
{
	return snd_mixer_lib_sw3_value(mixer, name, index, type,
				       voices_count, voices, control,
				       private_data, 0);
}

/*
 *  Volume (attenuation/gain) control - read write
 *
 *    The volume must be always linear!!!
 */

static int snd_mixer_lib_volume1_info(snd_kmixer_element_t *element,
				      snd_kmixer_file_t *mfile,
				      snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_volume1 *volume1 = (struct snd_stru_mixer_lib_volume1 *)snd_mixer_ext_element_private_data(element);
	int idx;
	
	info->data.volume1.range = 0;
	for (idx = 0; idx < info->data.volume1.range_size && idx < volume1->voices; idx++) {
		if (copy_to_user(&info->data.volume1.prange[idx], &volume1->ranges[idx], sizeof(struct snd_mixer_element_volume1_range)))
			return -EFAULT;
		info->data.volume1.range++;
	}
	info->data.volume1.range_over = volume1->voices - idx;
	return 0;
}

static int snd_mixer_lib_volume1_verify_range(int voices, int *pvoices,
				struct snd_mixer_element_volume1_range *ranges)
{
	int idx;

	for (idx = 0; idx < voices; idx++)
		if (pvoices[idx] < ranges[idx].min || pvoices[idx] > ranges[idx].max)
			return 1;
	return 0;
}

static int snd_mixer_lib_volume1_control(snd_kmixer_element_t *element,
					 snd_kmixer_file_t *mfile,
					 int w_flag,
					 snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_volume1 *volume1 = (struct snd_stru_mixer_lib_volume1 *)snd_mixer_ext_element_private_data(element);
	int bsize, bsize1, result;
	int *voices;
	
	result = 0;
	bsize = volume1->voices * sizeof(int);
	if (bsize <= 0)
		return -EINVAL;
	voices = snd_kcalloc(bsize, GFP_KERNEL);
	if (voices == NULL)
		return -ENOMEM;
	if (uelement->data.volume1.voices_size > 0 &&
	    uelement->data.volume1.pvoices == NULL) {
		result = -EINVAL;
		goto __end;
	}
	bsize1 = uelement->data.volume1.voices_size * sizeof(int);
	if (bsize1 > bsize)
		bsize1 = bsize;
	if (!w_flag) {
		result = volume1->control(element, 0, voices);
		if (result >= 0) {
			uelement->data.volume1.voices = volume1->voices;
			if (uelement->data.volume1.voices > uelement->data.volume1.voices_size)
				uelement->data.volume1.voices = uelement->data.volume1.voices_size;
			uelement->data.volume1.voices_over = volume1->voices - uelement->data.volume1.voices;
			if (copy_to_user(uelement->data.volume1.pvoices, voices, bsize1))
				result = -EFAULT;
		}
	} else {
		if (uelement->data.volume1.voices_size < volume1->voices) {
			result = -EINVAL;
			goto __end;
		}
		if (uelement->data.volume1.voices != volume1->voices) {
			result = -EINVAL;
			goto __end;
		}
		if (copy_from_user(voices, uelement->data.switch1.psw, bsize)) {
			result = -EFAULT;
			goto __end;
		}
		if (snd_mixer_lib_volume1_verify_range(volume1->voices, voices, volume1->ranges)) {
			result = -EINVAL;
			goto __end;
		}
		result = volume1->control(element, 1, voices);
	}
      __end:
	snd_kfree(voices);
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_volume1_value(snd_kmixer_t *mixer,
				    char *name,
				    int index,
				    int voices,
				    struct snd_mixer_element_volume1_range *ranges,
				    snd_mixer_volume1_control_t *control,
				    void *private_data,
				    unsigned long private_value)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_volume1 *volume1, *volume1_1;
	int idx, *pvoices;

	if (name == NULL || control == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_VOLUME1;
	nelement.info = snd_mixer_lib_volume1_info;
	nelement.control = snd_mixer_lib_volume1_control;
	nelement.ext_size = sizeof(*volume1) + \
			    voices * sizeof(struct snd_mixer_element_volume1_range);
	nelement.private_data = private_data;
	nelement.private_value = private_value;

	volume1 = (struct snd_stru_mixer_lib_volume1 *)snd_kcalloc(nelement.ext_size, GFP_KERNEL);
	if (volume1 == NULL)
		return NULL;
				
	nelement.ext_ptr = volume1;
		
	volume1->voices = voices;
	volume1->ranges = (struct snd_mixer_element_volume1_range *)(volume1 + 1);
	volume1->control = control;
	memcpy(volume1->ranges, ranges, voices * sizeof(struct snd_mixer_element_volume1_range));

	result = snd_mixer_element_new(mixer, &nelement);

	snd_kfree(volume1);

	if (result) {
		volume1_1 = (struct snd_stru_mixer_lib_volume1 *)snd_mixer_ext_element_private_data(result);
		volume1_1->ranges = (struct snd_mixer_element_volume1_range *)(volume1_1 + 1);
		/* initial settings - turn volume off */
		pvoices = (int *)snd_kmalloc(sizeof(int) * voices, GFP_KERNEL);
		if (pvoices) {
			for (idx = 0; idx < voices; idx++)
				pvoices[idx] = ranges[idx].min;
			control(result, 1, pvoices);
			snd_kfree(pvoices);
		}
	}
	return result;
}

snd_kmixer_element_t *
	snd_mixer_lib_volume1(snd_kmixer_t *mixer,
			      char *name,
			      int index,
			      int voices,
			      struct snd_mixer_element_volume1_range *ranges,
			      snd_mixer_volume1_control_t *control,
			      void *private_data)
{
	return snd_mixer_lib_volume1_value(mixer, name, index, voices,
					   ranges, control, private_data, 0);
}

/*
 *  Simple accumulator
 */

static int snd_mixer_lib_accu1_info(snd_kmixer_element_t *element,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu1 *accu1 = (struct snd_stru_mixer_lib_accu1 *)snd_mixer_ext_element_private_data(element);

	info->data.accu1.attenuation = accu1->attenuation;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_accu1(snd_kmixer_t *mixer,
					  char *name,
					  int index,
					  int attenuation)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_accu1 accu1;

	if (name == NULL)
		return NULL;
	accu1.attenuation = attenuation;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_ACCU1;
	nelement.info = snd_mixer_lib_accu1_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(accu1);
	nelement.ext_ptr = &accu1;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple accumulator with MONO output
 */

static int snd_mixer_lib_accu2_info(snd_kmixer_element_t *element,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu2 *accu2 = (struct snd_stru_mixer_lib_accu2 *)snd_mixer_ext_element_private_data(element);

	info->data.accu2.attenuation = accu2->attenuation;
	return 0;
}

snd_kmixer_element_t *snd_mixer_lib_accu2(snd_kmixer_t *mixer,
					  char *name,
					  int index,
					  int attenuation)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_accu2 accu2;

	if (name == NULL)
		return NULL;
	accu2.attenuation = attenuation;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_ACCU2;
	nelement.info = snd_mixer_lib_accu2_info;
	nelement.control = NULL;
	nelement.ext_size = sizeof(accu2);
	nelement.ext_ptr = &accu2;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple accumulator with programmable attenuation
 *
 *    The volume must be always linear!!!
 */

static int snd_mixer_lib_accu3_info(snd_kmixer_element_t *element,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu3 *accu3 = (struct snd_stru_mixer_lib_accu3 *)snd_mixer_ext_element_private_data(element);
	int idx;
	
	info->data.accu3.range = 0;
	for (idx = 0; idx < info->data.accu3.range_size && idx < accu3->voices; idx++) {
		if (copy_to_user(&info->data.accu3.prange[idx], &accu3->ranges[idx], sizeof(struct snd_mixer_element_accu3_range)))
			return -EFAULT;
		info->data.accu3.range++;
	}
	info->data.accu3.range_over = accu3->voices - idx;
	return 0;
}

static int snd_mixer_lib_accu3_verify_range(int voices, int *pvoices,
				struct snd_mixer_element_accu3_range *ranges)
{
	int idx;

	for (idx = 0; idx < voices; idx++)
		if (pvoices[idx] < ranges[idx].min || pvoices[idx] > ranges[idx].max)
			return 1;
	return 0;
}

static int snd_mixer_lib_accu3_control(snd_kmixer_element_t *element,
				       snd_kmixer_file_t *mfile,
				       int w_flag,
				       snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_accu3 *accu3 = (struct snd_stru_mixer_lib_accu3 *)snd_mixer_ext_element_private_data(element);
	int bsize, bsize1, result;
	int *voices;
	
	result = 0;
	bsize = accu3->voices * sizeof(int);
	if (bsize <= 0)
		return -EINVAL;
	voices = snd_kcalloc(bsize, GFP_KERNEL);
	if (voices == NULL)
		return -ENOMEM;
	if (uelement->data.accu3.voices_size > 0 && uelement->data.accu3.pvoices == NULL) {
		result = -EINVAL;
		goto __end;
	}
	bsize1 = uelement->data.accu3.voices_size * sizeof(int);
	if (bsize1 > bsize)
		bsize1 = bsize;
	if (!w_flag) {
		result = accu3->control(element, 0, voices);
		if (result >= 0) {
			uelement->data.accu3.voices = accu3->voices;
			if (uelement->data.accu3.voices > uelement->data.accu3.voices_size)
				uelement->data.accu3.voices = uelement->data.accu3.voices_size;
			uelement->data.accu3.voices_over = accu3->voices - uelement->data.accu3.voices;
			if (copy_to_user(uelement->data.accu3.pvoices, voices, bsize1))
				result = -EFAULT;
		}
	} else {
		if (uelement->data.accu3.voices_size < accu3->voices) {
			result = -EINVAL;
			goto __end;
		}
		if (uelement->data.accu3.voices != accu3->voices) {
			result = -EINVAL;
			goto __end;
		}
		if (copy_from_user(voices, uelement->data.switch1.psw, bsize))
			result = -EFAULT;
		if (snd_mixer_lib_accu3_verify_range(accu3->voices, voices, accu3->ranges)) {
			result = -EINVAL;
			goto __end;
		}
		result = accu3->control(element, 1, voices);
	}
      __end:
	snd_kfree(voices);
	return result;
}

snd_kmixer_element_t *snd_mixer_lib_accu3(snd_kmixer_t *mixer,
					  char *name,
					  int index,
					  int voices,
					  struct snd_mixer_element_accu3_range *ranges,
					  snd_mixer_accu3_control_t *control,
					  void *private_data)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_accu3 *accu3, *accu3_1;
	int idx, *pvoices;

	if (name == NULL || control == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_ACCU3;
	nelement.info = snd_mixer_lib_accu3_info;
	nelement.control = snd_mixer_lib_accu3_control;
	nelement.private_data = private_data;
	nelement.ext_size = sizeof(*accu3) +
			    voices * sizeof(struct snd_mixer_element_accu3_range);

	accu3 = (struct snd_stru_mixer_lib_accu3 *)snd_kmalloc(nelement.ext_size, GFP_KERNEL);
	if (accu3 == NULL)
		return NULL;

	nelement.ext_ptr = accu3;
	accu3->control = control;
	accu3->voices = voices;
	accu3->ranges = (struct snd_mixer_element_accu3_range *)(accu3 + 1);
	memcpy(accu3->ranges, ranges, voices * sizeof(struct snd_mixer_element_accu3_range));

	result = snd_mixer_element_new(mixer, &nelement);

	snd_kfree(accu3);

	if (result) {
		accu3_1 = (struct snd_stru_mixer_lib_accu3 *)snd_mixer_ext_element_private_data(result);
		accu3_1->ranges = (struct snd_mixer_element_accu3_range *)(accu3_1 + 1);
		/* initial settings - turn volume off */
		pvoices = (int *)snd_kmalloc(sizeof(int) * voices, GFP_KERNEL);
		if (pvoices) {
			for (idx = 0; idx < voices; idx++)
				pvoices[idx] = ranges[idx].min;
			control(result, 1, pvoices);
			snd_kfree(pvoices);
		}
	}
	return result;
}

/*
 *  Simple MUX
 *
 *    This mux allows selection of exactly one (or none - optional) input.
 */

static int snd_mixer_lib_mux1_info(snd_kmixer_element_t *element,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_mux1 *mux1 = (struct snd_stru_mixer_lib_mux1 *)snd_mixer_ext_element_private_data(element);

	info->data.mux1.attrib = mux1->attrib;
	return 0;
}

static int snd_mixer_lib_mux1_control(snd_kmixer_element_t *element,
				      snd_kmixer_file_t *mfile,
				      int w_flag,
				      snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_mux1 *mux1 = (struct snd_stru_mixer_lib_mux1 *)snd_mixer_ext_element_private_data(element);
	snd_kmixer_element_t *kelement, **kelements;
	snd_mixer_eid_t eid;
	int bsize, bsize1, idx;
	int result;
	
	bsize = mux1->voices * sizeof(snd_mixer_eid_t *);
	if (bsize <= 0)
		return -EINVAL;
	kelements = snd_kcalloc(bsize, GFP_KERNEL);
	if (kelements == NULL)
		return -ENOMEM;
	result = 0;
	bsize1 = uelement->data.mux1.output_size * sizeof(snd_mixer_eid_t);
	if (!w_flag) {
		result = mux1->control(element, 0, kelements);
		if (result >= 0) {
			if (mux1->voices > uelement->data.mux1.output_size) {
				uelement->data.mux1.output = uelement->data.mux1.output_size;
			} else {
				uelement->data.mux1.output = mux1->voices;
			}
			for (idx = 0; idx < uelement->data.mux1.output; idx++) {
				kelement = kelements[idx];
				memset(&eid, 0, sizeof(eid));
				if (kelement) {
					strncpy(eid.name, kelement->name, sizeof(eid.name));
					eid.index = kelement->index;
					eid.type = kelement->type;
				}
				if (copy_to_user(&uelement->data.mux1.poutput[idx], &eid, sizeof(eid))) {
					result = -EFAULT;
					goto __err;
				}
			}
			uelement->data.mux1.output_over = mux1->voices - uelement->data.mux1.output;
		}
	} else {
		if (uelement->data.mux1.output != mux1->voices ||
		    uelement->data.mux1.output_size < uelement->data.mux1.output) {
			result = -EINVAL;
			goto __err;
		}
		for (idx = 0; idx < mux1->voices; idx++) {
			if (copy_from_user(&eid, &uelement->data.mux1.poutput[idx], sizeof(eid))) {
				result = -EFAULT;
				goto __err;
			}
			kelement = snd_mixer_element_find(mfile->mixer, eid.name, eid.index, eid.type);
			if (!kelement && !(mux1->attrib & SND_MIXER_MUX1_NONE)) {
				result = -EINVAL;
				goto __err;
			}
			kelements[idx] = kelement;
		}
		result = mux1->control(element, 1, kelements);
	}
      __err:
      	snd_kfree(kelements);
	return result;
}

snd_kmixer_element_t *snd_mixer_lib_mux1(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 unsigned int attrib,
					 int voices,
					 snd_mixer_mux1_control_t *control,
					 void *private_data)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_mux1 mux1;

	if (name == NULL || control == NULL)
		return NULL;
	mux1.attrib = attrib;
	mux1.voices = voices;
	mux1.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_MUX1;
	nelement.info = snd_mixer_lib_mux1_info;
	nelement.control = snd_mixer_lib_mux1_control;
	nelement.ext_size = sizeof(mux1);
	nelement.ext_ptr = &mux1;
	nelement.private_data = private_data;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple MUX
 *
 *    This mux allows selection of exactly one (or none - optional) input.
 */

static int snd_mixer_lib_mux2_info(snd_kmixer_element_t *element,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_mux2 *mux2 = (struct snd_stru_mixer_lib_mux2 *)snd_mixer_ext_element_private_data(element);
	
	info->data.mux2.attrib = mux2->attrib;
	return 0;
}

static int snd_mixer_lib_mux2_control(snd_kmixer_element_t *element,
				      snd_kmixer_file_t *mfile,
				      int w_flag,
				      snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_mux2 *mux2 = (struct snd_stru_mixer_lib_mux2 *)snd_mixer_ext_element_private_data(element);
	snd_kmixer_element_t *kelement;
	int result;
	
	result = 0;
	if (!w_flag) {
		result = mux2->control(element, 0, &kelement);
		if (result >= 0) {
			memset(&uelement->data.mux2.output, 0, sizeof(uelement->data.mux2.output));
			if (kelement) {
				strncpy(uelement->data.mux2.output.name, kelement->name, sizeof(uelement->data.mux2.output.name));
				uelement->data.mux2.output.index = kelement->index;
				uelement->data.mux2.output.type = kelement->type;
			}
		}
	} else {
		kelement = snd_mixer_element_find(mfile->mixer, uelement->data.mux2.output.name, uelement->data.mux2.output.index, uelement->data.mux2.output.type);
		if (kelement == NULL && !(mux2->attrib & SND_MIXER_MUX2_NONE))
			return -EINVAL;
		result = mux2->control(element, 1, &kelement);
	}
	return result;
}

snd_kmixer_element_t *snd_mixer_lib_mux2(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 unsigned int attrib,
					 snd_mixer_mux2_control_t *control,
					 void *private_data)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_mux2 mux2;

	if (name == NULL || control == NULL)
		return NULL;
	mux2.attrib = attrib;
	mux2.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_MUX2;
	nelement.info = snd_mixer_lib_mux2_info;
	nelement.control = snd_mixer_lib_mux2_control;
	nelement.ext_size = sizeof(mux2);
	nelement.ext_ptr = &mux2;
	nelement.private_data = private_data;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple tone control
 */

static int snd_mixer_lib_tone_control1_check(struct snd_mixer_element_tone_control1 *data,
					     struct snd_mixer_element_tone_control1_info *info)
{
	if (data->tc & ~info->tc)
		return 1;
	if (data->tc & SND_MIXER_TC1_BASS)
		if (data->bass < info->min_bass || data->bass > info->max_bass)
			return 1;
	if (data->tc & SND_MIXER_TC1_TREBLE)
		if (data->treble < info->min_treble || data->treble > info->max_treble)
			return 1;
	return 0;	
}

static int snd_mixer_lib_tone_control1_info(snd_kmixer_element_t *element,
					    snd_kmixer_file_t *mfile,
					    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_tone_control1 *tc1 = (struct snd_stru_mixer_lib_tone_control1 *)snd_mixer_ext_element_private_data(element);

	info->data.tc1 = tc1->data;	
	return 0;
}

static int snd_mixer_lib_tone_control1_control(snd_kmixer_element_t *element,
					       snd_kmixer_file_t *mfile,
					       int w_flag,
					       snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_tone_control1 *tc1 = (struct snd_stru_mixer_lib_tone_control1 *)snd_mixer_ext_element_private_data(element);
	int result;

	result = 0;
	if (!w_flag) {
		memset(&uelement->data.tc1, 0, sizeof(uelement->data.teffect1));
		uelement->data.tc1.tc = tc1->data.tc;
		result = tc1->control(element, 0, &uelement->data.tc1);
	} else {
		if (snd_mixer_lib_tone_control1_check(&uelement->data.tc1, &tc1->data)) { 
			result = -EINVAL;
			goto __error;
		}
		result = tc1->control(element, 1, &uelement->data.tc1);
	}
      __error:
	return result;
}

static int value_conv(int omin, int omax, int nmin, int nmax)
{
	int orange = omax - omin, nrange = nmax - nmin;
        
	if (orange == 0)
		return 0;
	if (omin < 0)
		omin = -omin;
	return ((nrange * omin) / orange) - nmin;
}

snd_kmixer_element_t *snd_mixer_lib_tone_control1(snd_kmixer_t *mixer,
						  char *name,
						  int index,
						  struct snd_mixer_element_tone_control1_info *info,
						  snd_mixer_tone_control1_control_t *control,
						  void *private_data)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_tone_control1 tc1;
	struct snd_mixer_element_tone_control1 tc;

	if (name == NULL || info == NULL || control == NULL)
		return NULL;
	memcpy(&tc1.data, info, sizeof(tc1.data));
	tc1.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_TONE_CONTROL1;
	nelement.info = snd_mixer_lib_tone_control1_info;
	nelement.control = snd_mixer_lib_tone_control1_control;
	nelement.ext_size = sizeof(tc1);
	nelement.ext_ptr = &tc1;
	nelement.private_data = private_data;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn all off */
		memset(&tc, 0, sizeof(tc));
		tc.tc = info->tc;
		tc.bass = value_conv(info->min_bass_dB, info->max_bass_dB,
				     info->min_bass, info->max_bass);
		tc.treble = value_conv(info->min_treble_dB, info->max_treble_dB,
				       info->min_treble, info->max_treble);
		control(result, 1, &tc);
	}
	return result;
}

/*
 *  Simple pan control
 */

static int snd_mixer_lib_pan_control1_info(snd_kmixer_element_t *element,
					   snd_kmixer_file_t *mfile,
					   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_pan_control1 *pc1 = (struct snd_stru_mixer_lib_pan_control1 *)snd_mixer_ext_element_private_data(element);
	int idx;
	struct snd_mixer_element_pan_control1_range *ranges = (struct snd_mixer_element_pan_control1_range *)(pc1 + 1);

	info->data.pc1.range = 0;
	for (idx = 0; idx < info->data.pc1.range_size && idx < pc1->pan; idx++) {
		if (copy_to_user(&info->data.pc1.prange[idx], &ranges[idx], sizeof(struct snd_mixer_element_pan_control1_range)))
			return -EFAULT;
		info->data.pc1.range++;
	}
	info->data.pc1.range_over = pc1->pan - idx;
	return 0;
}

static int snd_mixer_lib_pan_control1_verify_range(int pan, int *ppan, struct snd_mixer_element_pan_control1_range *ranges)
{
	int idx;

	for (idx = 0; idx < pan; idx++)
		if (ppan[idx] < ranges[idx].min || ppan[idx] > ranges[idx].max)
			return 1;
	return 0;
}

static int snd_mixer_lib_pan_control1_control(snd_kmixer_element_t *element,
					      snd_kmixer_file_t *mfile,
					      int w_flag,
					      snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_pan_control1 *pc1 = (struct snd_stru_mixer_lib_pan_control1 *)snd_mixer_ext_element_private_data(element);
	struct snd_mixer_element_pan_control1_range *ranges = (struct snd_mixer_element_pan_control1_range *)(pc1 + 1);
	int bsize, bsize1, result;
	int *pan;

	result = 0;
	bsize = pc1->pan * sizeof(int);
	if (bsize <= 0)
		return -EINVAL;
	pan = snd_kcalloc(bsize, GFP_KERNEL);
	if (pan == NULL)
		return -ENOMEM;
	if (uelement->data.pc1.pan_size > 0 &&
	    !uelement->data.pc1.ppan) {
		result = -EINVAL;
		goto __end;
	}
	bsize1 = uelement->data.pc1.pan_size * sizeof(int);
	if (bsize1 > bsize)
		bsize1 = bsize;
	if (!w_flag) {
		result = pc1->control(element, 0, pan);
		if (result >= 0) {
			uelement->data.pc1.pan = pc1->pan;
			if (uelement->data.pc1.pan > uelement->data.pc1.pan_size)
				uelement->data.pc1.pan = uelement->data.pc1.pan_size;
			uelement->data.pc1.pan_over = pc1->pan - uelement->data.pc1.pan;
			if (copy_to_user(uelement->data.pc1.ppan, pan, bsize1))
				result = -EFAULT;
		}
	} else {
		if (uelement->data.pc1.pan_size < pc1->pan) {
			result = -EINVAL;
			goto __end;
		}
		if (uelement->data.pc1.pan != pc1->pan) {
			result = -EINVAL;
			goto __end;
		}
		if (copy_from_user(pan, uelement->data.pc1.ppan, bsize)) {
			result = -EFAULT;
			goto __end;
		}
		if (snd_mixer_lib_pan_control1_verify_range(pc1->pan, pan, ranges)) {
			result = -EINVAL;
			goto __end;
		}
		result = pc1->control(element, 1, pan);
	}
      __end:
	snd_kfree(pan);
	return result;
}

snd_kmixer_element_t *snd_mixer_lib_pan_control1(snd_kmixer_t *mixer,
						 char *name,
						 int index,
						 int pan,
						 struct snd_mixer_element_pan_control1_range *ranges,
						 snd_mixer_pan_control1_control_t *control,
						 void *private_data)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_pan_control1 *pc1;
	int idx, *ppan;

	if (name == NULL || control == NULL)
		return NULL;

	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_PAN_CONTROL1;
	nelement.info = snd_mixer_lib_pan_control1_info;
	nelement.control = snd_mixer_lib_pan_control1_control;
	nelement.private_data = private_data;
	nelement.ext_size = sizeof(*pc1) +
			    pan * sizeof(struct snd_mixer_element_pan_control1_range);

	pc1 = (struct snd_stru_mixer_lib_pan_control1 *)snd_kmalloc(nelement.ext_size, GFP_KERNEL);
	if (pc1 == NULL)
		return NULL;
	pc1->pan = pan;
	pc1->ranges = (struct snd_mixer_element_pan_control1_range *)(pc1 + 1);
	memcpy(pc1->ranges, ranges, pan * sizeof(struct snd_mixer_element_pan_control1_range));
	pc1->control = control;

	nelement.ext_ptr = pc1;

	result = snd_mixer_element_new(mixer, &nelement);
	
	snd_kfree(pc1);
	
	if (result) {
		/* initial settings - turn to middle */
		/* just derive the mid point of range */
		ppan = (int *)snd_kmalloc(sizeof(int) * pan, GFP_KERNEL);
		if (ppan) {
			for (idx = 0; idx < pan; idx++)
				ppan[idx] = (ranges->max - ranges->min) / 2;
			control(result, 1, ppan);
			snd_kfree(ppan);
		}
	}
	return result;
}

/*
 *  Simple 3D effect
 */

static int snd_mixer_lib_3d_effect1_check(struct snd_mixer_element_3d_effect1 *data,
					  struct snd_mixer_element_3d_effect1_info *info)
{
	if (data->effect & ~info->effect)
		return 1;
	if (data->effect & SND_MIXER_EFF1_WIDE)
		if (data->wide < info->min_wide || data->wide > info->max_wide)
			return 1;
	if (data->effect & SND_MIXER_EFF1_VOLUME)
		if (data->volume < info->min_volume || data->volume > info->max_volume)
			return 1;
	if (data->effect & SND_MIXER_EFF1_CENTER)
		if (data->center < info->min_center || data->center > info->max_center)
			return 1;
	if (data->effect & SND_MIXER_EFF1_SPACE)
		if (data->space < info->min_space || data->space > info->max_space)
			return 1;
	if (data->effect & SND_MIXER_EFF1_DEPTH)
		if (data->depth < info->min_depth || data->depth > info->max_depth)
			return 1;
	if (data->effect & SND_MIXER_EFF1_DELAY)
		if (data->delay < info->min_delay || data->delay > info->max_delay)
			return 1;
	if (data->effect & SND_MIXER_EFF1_FEEDBACK)
		if (data->feedback < info->min_feedback || data->feedback > info->max_feedback)
			return 1;
	if (data->effect & SND_MIXER_EFF1_DEPTH_REAR)
		if (data->depth_rear < info->min_depth_rear || data->depth_rear > info->max_depth_rear)
			return 1;
	return 0;	
}

static int snd_mixer_lib_3d_effect1_info(snd_kmixer_element_t *element,
					 snd_kmixer_file_t *mfile,
					 snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_3d_effect1 *effect1 = (struct snd_stru_mixer_lib_3d_effect1 *)snd_mixer_ext_element_private_data(element);
	
	info->data.teffect1 = effect1->data;	
	return 0;
}

static int snd_mixer_lib_3d_effect1_control(snd_kmixer_element_t *element,
					    snd_kmixer_file_t *mfile,
					    int w_flag,
					    snd_mixer_element_t *uelement)
{
	struct snd_stru_mixer_lib_3d_effect1 *effect1 = (struct snd_stru_mixer_lib_3d_effect1 *)snd_mixer_ext_element_private_data(element);
	int result;

	result = 0;
	if (!w_flag) {
		memset(&uelement->data.teffect1, 0, sizeof(uelement->data.teffect1));
		uelement->data.teffect1.effect = effect1->data.effect;
		result = effect1->control(element, 0, &uelement->data.teffect1);
	} else {
		if (snd_mixer_lib_3d_effect1_check(&uelement->data.teffect1, &effect1->data)) {
			result = -EINVAL;
			goto __error;
		}
		result = effect1->control(element, 1, &uelement->data.teffect1);
	}
      __error:
	return result;
}

snd_kmixer_element_t *snd_mixer_lib_3d_effect1(snd_kmixer_t *mixer,
					       char *name,
					       int index,
					       struct snd_mixer_element_3d_effect1_info *info,
					       snd_mixer_3d_effect1_control_t *control,
					       void *private_data)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_3d_effect1 effect1;
	struct snd_mixer_element_3d_effect1 eff1;

	if (name == NULL || info == NULL || control == NULL)
		return NULL;
	memcpy(&effect1.data, info, sizeof(effect1.data));
	effect1.control = control;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_3D_EFFECT1;
	nelement.info = snd_mixer_lib_3d_effect1_info;
	nelement.control = snd_mixer_lib_3d_effect1_control;
	nelement.private_data = private_data;
	nelement.ext_size = sizeof(effect1);
	nelement.ext_ptr = &effect1;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn all off */
		memset(&eff1, 0, sizeof(eff1));
		eff1.effect = info->effect;
		eff1.wide = info->min_wide;
		eff1.volume = info->min_volume;
		eff1.center = info->min_center;
		/* put inital space in the middle */
		eff1.space = (info->min_space + info->max_space) / 2;
		eff1.depth = info->min_depth;
		eff1.delay = info->min_delay;
		eff1.feedback = info->min_feedback;
		eff1.depth_rear = info->min_depth_rear;
		control(result, 1, &eff1);
	}
	return result;
}
