/*
 *  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"
#include "../include/sndpersist.h"

snd_kmixer_group_t *snd_mixer_lib_group(snd_kmixer_t *mixer,
					char *name,
					int index)
{
	snd_kmixer_group_new_t 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(void *private_data,
				 snd_kmixer_file_t *mfile,
				 snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_io *io = (struct snd_stru_mixer_lib_io *)private_data;
	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;
}

static void snd_mixer_lib_io_free(void *private_data)
{
	struct snd_stru_mixer_lib_io *io = (struct snd_stru_mixer_lib_io *)private_data;
	
	if (io) {
		if (io->voices && io->voices_count > 0)
			snd_kfree(io->voices);
		snd_kfree(io);
	}
}

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_new_t nelement;
	struct snd_stru_mixer_lib_io *io;

	if (!name || voices_count <= 0 || !voices)
		return NULL;
	io = (struct snd_stru_mixer_lib_io *)snd_kmalloc(sizeof(*io), GFP_KERNEL);
	if (!io)
		return NULL;
	io->voices_count = voices_count;
	io->voices = (snd_mixer_voice_t *)snd_kmalloc(voices_count * sizeof(snd_mixer_voice_t), GFP_KERNEL);
	if (!io->voices) {
		snd_kfree(io);
		return NULL;
	}
	memcpy(io->voices, voices, voices_count * sizeof(snd_mixer_voice_t));
	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.private_data = io;
	nelement.private_free = snd_mixer_lib_io_free;
	return snd_mixer_element_new(mixer, &nelement);
}

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 - read only
 */

static int snd_mixer_lib_pcm_info(void *private_data,
				  snd_kmixer_file_t *mfile,
				  snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_pcm *pcm = (struct snd_stru_mixer_lib_pcm *)private_data;
	int idx;
	
	info->data.pcm.devices = 0;
	for (idx = 0; idx < info->data.pcm.devices_size && idx < pcm->devices_count; idx++) {
		if (copy_to_user(&info->data.pcm.pdevices[idx], &pcm->devices[idx], sizeof(int)))
			return -EFAULT;
		info->data.pcm.devices++;
	}
	info->data.pcm.devices_over = pcm->devices_count - idx;
	return 0;
}

static void snd_mixer_lib_pcm_free(void *private_data)
{
	struct snd_stru_mixer_lib_pcm *pcm = (struct snd_stru_mixer_lib_pcm *)private_data;

	if (pcm) {
		if (pcm->devices && pcm->devices_count > 0)
			snd_kfree(pcm->devices);
		snd_kfree(pcm);
	}
}

snd_kmixer_element_t *snd_mixer_lib_pcm(snd_kmixer_t *mixer,
				        char *name,
				        int index,
				        int type,
				        int devices_count,
				        int *devices)
{
	snd_kmixer_element_new_t nelement;
	struct snd_stru_mixer_lib_pcm *pcm;

	if (!name || devices_count <= 0 || !devices)
		return NULL;
	pcm = (struct snd_stru_mixer_lib_pcm *)snd_kmalloc(sizeof(*pcm), GFP_KERNEL);
	if (!pcm)
		return NULL;
	pcm->devices_count = devices_count;
	pcm->devices = (int *)snd_kmalloc(devices_count * sizeof(int), GFP_KERNEL);
	if (!pcm->devices) {
		snd_kfree(pcm);
		return NULL;
	}
	memcpy(pcm->devices, devices, devices_count * sizeof(int));
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = snd_mixer_lib_pcm_info;
	nelement.control = NULL;
	nelement.private_data = pcm;
	nelement.private_free = snd_mixer_lib_pcm_free;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  ADC/DAC - read only
 */

static int snd_mixer_lib_converter_info(void *private_data,
					snd_kmixer_file_t *mfile,
					snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_converter *conv = (struct snd_stru_mixer_lib_converter *)private_data;

	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 || resolution < 8)
		return NULL;
	conv = (struct snd_stru_mixer_lib_converter *)snd_kmalloc(sizeof(*conv), GFP_KERNEL);
	if (!conv)
		return NULL;
	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.private_data = conv;
	nelement.private_free = snd_kfree;
	return snd_mixer_element_new(mixer, &nelement);
}

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

static int snd_mixer_lib_sw1_control(void *private_data,
				     snd_kmixer_file_t *mfile,
				     int cmd,
				     void *data,
				     int size)
{
	struct snd_stru_mixer_lib_sw1 *sw1 = (struct snd_stru_mixer_lib_sw1 *)private_data;
	int bsize, bsize1, result;
	unsigned int *bitmap;
	snd_mixer_element_t *ctrl;
	
	result = 0;
	bsize = ((sw1->switches + 31) / 32) * sizeof(unsigned int);
	if (bsize <= 0)
		return -EINVAL;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(unsigned int) + bsize;
#if 0
	snd_memory_debug1();
#endif
	bitmap = snd_kcalloc(bsize, GFP_KERNEL);
	if (!bitmap)
		return -ENOMEM;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		if (ctrl->data.switch1.sw_size > 0 && !ctrl->data.switch1.psw) {
			result = -EINVAL;
			goto __end;
		}
		bsize1 = ((ctrl->data.switch1.sw_size + 31) / 32) * sizeof(unsigned int);
		if (bsize1 > bsize)
			bsize1 = bsize;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = sw1->control(sw1->private_data, 0, bitmap);
			if (result >= 0) {
				ctrl->data.switch1.sw = sw1->switches;
				if (ctrl->data.switch1.sw > ctrl->data.switch1.sw_size)
					ctrl->data.switch1.sw = ctrl->data.switch1.sw_size;
				ctrl->data.switch1.sw_over = sw1->switches - ctrl->data.switch1.sw;
				if (copy_to_user(ctrl->data.switch1.psw, bitmap, bsize1))
					result = -EFAULT;
			}
			break;
		case SND_MIXER_CMD_WRITE:
			if (ctrl->data.switch1.sw_size < sw1->switches) {
				result = -EINVAL;
				goto __end;
			}
			if (ctrl->data.switch1.sw != sw1->switches) {
				result = -EINVAL;
				goto __end;
			}
			if (copy_from_user(bitmap, ctrl->data.switch1.psw, bsize)) {
				result = -EFAULT;
			} else {
				result = sw1->control(sw1->private_data, 1, bitmap);
			}
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((unsigned int *)data) = sw1->switches;
		result = sw1->control(sw1->private_data, 0, bitmap);
		if (result >= 0) {
			memcpy((char *)data + sizeof(int), bitmap, bsize);
			result = sizeof(int) + bsize;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < bsize + sizeof(int)) {
			result = -EINVAL;
			goto __end;
		}
		if (*((unsigned int *)data) != sw1->switches) {
			result = -EINVAL;
			goto __end;
		}
		memcpy(bitmap, (char *)data + sizeof(int), bsize);
		result = sw1->control(sw1->private_data, 1, bitmap);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	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)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw1 *sw1;
	unsigned int *bitmap;

	if (!name || switches <= 0 || !control)
		return NULL;
	sw1 = (struct snd_stru_mixer_lib_sw1 *)snd_kmalloc(sizeof(*sw1), GFP_KERNEL);
	if (!sw1)
		return NULL;
	sw1->switches = switches;
	sw1->control = control;
	sw1->private_data = private_data;
	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.private_data = sw1;
	nelement.private_free = snd_kfree;
	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(private_data, 1, bitmap);
			snd_kfree(bitmap);
		}
	}
	return result;
}

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

static int snd_mixer_lib_sw2_control(void *private_data,
				     snd_kmixer_file_t *mfile,
				     int cmd,
				     void *data,
				     int size)
{
	struct snd_stru_mixer_lib_sw2 *sw2 = (struct snd_stru_mixer_lib_sw2 *)private_data;
	int result, value;
	snd_mixer_element_t *ctrl;
	
	result = 0;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			value = 0;
			result = sw2->control(sw2->private_data, 0, &value);
			ctrl->data.switch2.sw = value ? 1 : 0;
			break;
		case SND_MIXER_CMD_WRITE:
			value = ctrl->data.switch2.sw ? 1 : 0;
			result = sw2->control(sw2->private_data, 1, &value);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < 1)
			return -ENOMEM;
		result = sw2->control(sw2->private_data, 0, &value);
		if (result >= 0) {
			*((unsigned char *)data) = value ? 1 : 0;
			result = 1;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < 1)
			return -EINVAL;
		value = *((unsigned char *)data) ? 1 : 0;
		result = sw2->control(sw2->private_data, 1, &value);
		if (result >= 0)
			result = 1;
		break;
	case SND_MIXER_CMD_SIZE:
		result = 1;
		break;
	}
	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)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw2 *sw2;
	int val;

	if (!name || !control)
		return NULL;
	sw2 = (struct snd_stru_mixer_lib_sw2 *)snd_kmalloc(sizeof(*sw2), GFP_KERNEL);
	if (!sw2)
		return NULL;
	sw2->control = control;
	sw2->private_data = private_data;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = SND_MIXER_ETYPE_SWITCH2;
	nelement.info = NULL;
	nelement.control = snd_mixer_lib_sw2_control;
	nelement.private_data = sw2;
	nelement.private_free = snd_kfree;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn switch off */
		val = 0;
		control(private_data, 1, &val);
	}
	return result;
}

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

static int snd_mixer_lib_sw3_info(void *private_data,
				  snd_kmixer_file_t *mfile,
				  snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_sw3 *sw3 = (struct snd_stru_mixer_lib_sw3 *)private_data;
	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(void *private_data,
				     snd_kmixer_file_t *mfile,
				     int cmd,
				     void *data,
				     int size)
{
	struct snd_stru_mixer_lib_sw3 *sw3 = (struct snd_stru_mixer_lib_sw3 *)private_data;
	int bsize, bsize1, result;
	unsigned int *bitmap;
	snd_mixer_element_t *ctrl;
	
	result = 0;
	bsize = ((sw3->voices_count * sw3->voices_count + 31) / 32) * sizeof(unsigned int);
	if (bsize <= 0)
		return -EINVAL;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(int) + bsize;
#if 0
	snd_memory_debug1();
#endif
	bitmap = snd_kcalloc(bsize, GFP_KERNEL);
	if (!bitmap)
		return -ENOMEM;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		if (ctrl->data.switch3.rsw_size > 0 && !ctrl->data.switch3.prsw) {
			result = -EINVAL;
			goto __end;
		}
		bsize1 = ((ctrl->data.switch3.rsw_size + 31) / 32) * sizeof(unsigned int);
		if (bsize1 > bsize)
			bsize1 = bsize;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = sw3->control(sw3->private_data, 0, bitmap);
			if (result >= 0) {
				ctrl->data.switch3.rsw = sw3->voices_count * sw3->voices_count;
				if (ctrl->data.switch3.rsw > ctrl->data.switch3.rsw_size)
					ctrl->data.switch3.rsw = ctrl->data.switch3.rsw_size;
				ctrl->data.switch3.rsw_over = sw3->voices_count * sw3->voices_count - ctrl->data.switch3.rsw;
				if (copy_to_user(ctrl->data.switch3.prsw, bitmap, bsize1))
					result = -EFAULT;
			}
			break;
		case SND_MIXER_CMD_WRITE:
			if (ctrl->data.switch3.rsw_size < sw3->voices_count * sw3->voices_count) {
				result = -EINVAL;
				goto __end;
			}
			if (ctrl->data.switch3.rsw != sw3->voices_count * sw3->voices_count) {
				result = -EINVAL;
				goto __end;
			}
			if (copy_from_user(bitmap, ctrl->data.switch3.prsw, bsize)) {
				result = -EFAULT;
			} else {
				result = sw3->control(sw3->private_data, 1, bitmap);
			}
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((unsigned int *)data) = sw3->voices_count;
		result = sw3->control(sw3->private_data, 0, bitmap);
		if (result >= 0) {
			memcpy((char *)data + sizeof(int), bitmap, bsize);
			result = sizeof(int) + bsize;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < bsize + sizeof(int)) {
			result = -EINVAL;
			goto __end;
		}
		if (*((unsigned int *)data) != sw3->voices_count) {
			result = -EINVAL;
			goto __end;
		}
		memcpy(bitmap, (char *)data + sizeof(int), bsize);
		result = sw3->control(sw3->private_data, 1, bitmap);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_kfree(bitmap);
	return result;
}

static void snd_mixer_lib_sw3_free(void *private_data)
{
	struct snd_stru_mixer_lib_sw3 *sw3 = (struct snd_stru_mixer_lib_sw3 *)private_data;

	if (sw3) {
		if (sw3->voices && sw3->voices_count > 0)
			snd_kfree(sw3->voices);
		snd_kfree(sw3);
	}
}

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)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_sw3 *sw3;
	unsigned int *bitmap;
	int idx;

	if (!name || voices_count <= 0 || !control)
		return NULL;
	sw3 = (struct snd_stru_mixer_lib_sw3 *)snd_kmalloc(sizeof(*sw3), GFP_KERNEL);
	if (!sw3)
		return NULL;
	sw3->type = type;
	sw3->voices_count = voices_count;
	sw3->control = control;
	sw3->private_data = private_data;
	sw3->voices = (snd_mixer_voice_t *)snd_kmalloc(voices_count * sizeof(snd_mixer_voice_t), GFP_KERNEL);
	if (!sw3->voices) {
		snd_kfree(sw3);
		return NULL;
	}
	memcpy(sw3->voices, voices, voices_count * sizeof(snd_mixer_voice_t));
	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.private_data = sw3;
	nelement.private_free = snd_mixer_lib_sw3_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* 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(private_data, 1, bitmap);
			snd_kfree(bitmap);
		}
	}
	return result;
}

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

static int snd_mixer_lib_volume1_info(void *private_data,
				      snd_kmixer_file_t *mfile,
				      snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_volume1 *volume1 = (struct snd_stru_mixer_lib_volume1 *)private_data;
	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(void *private_data,
					 snd_kmixer_file_t *mfile,
					 int cmd,
					 void *data,
					 int size)
{
	struct snd_stru_mixer_lib_volume1 *volume1 = (struct snd_stru_mixer_lib_volume1 *)private_data;
	int bsize, bsize1, result;
	int *voices;
	snd_mixer_element_t *ctrl;
	
	result = 0;
	bsize = volume1->voices * sizeof(int);
	if (bsize <= 0)
		return -EINVAL;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(int) + bsize;
	voices = snd_kcalloc(bsize, GFP_KERNEL);
	if (!voices)
		return -ENOMEM;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		if (ctrl->data.volume1.voices_size > 0 &&
		    !ctrl->data.volume1.pvoices) {
			result = -EINVAL;
			goto __end;
		}
		bsize1 = ctrl->data.volume1.voices_size * sizeof(int);
		if (bsize1 > bsize)
			bsize1 = bsize;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = volume1->control(volume1->private_data, 0, voices);
			if (result >= 0) {
				ctrl->data.volume1.voices = volume1->voices;
				if (ctrl->data.volume1.voices > ctrl->data.volume1.voices_size)
					ctrl->data.volume1.voices = ctrl->data.volume1.voices_size;
				ctrl->data.volume1.voices_over = volume1->voices - ctrl->data.volume1.voices;
				if (copy_to_user(ctrl->data.volume1.pvoices, voices, bsize1))
					result = -EFAULT;
			}
			break;
		case SND_MIXER_CMD_WRITE:
			if (ctrl->data.volume1.voices_size < volume1->voices) {
				result = -EINVAL;
				goto __end;
			}
			if (ctrl->data.volume1.voices != volume1->voices) {
				result = -EINVAL;
				goto __end;
			}
			if (copy_from_user(voices, ctrl->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(volume1->private_data, 1, voices);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((int *)data) = volume1->voices;
		result = volume1->control(volume1->private_data, 0, voices);
		if (result >= 0) {
			memcpy((char *)data + sizeof(int), voices, bsize);
			result = sizeof(int) + bsize;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < bsize + sizeof(int)) {
			result = -EINVAL;
			goto __end;
		}
		if (*((int *)data) != volume1->voices) {
			result = -EINVAL;
			goto __end;
		}
		memcpy(voices, (char *)data + sizeof(int), bsize);
		if (snd_mixer_lib_volume1_verify_range(volume1->voices, voices, volume1->ranges)) {
			result = -EINVAL;
			goto __end;
		}
		result = volume1->control(volume1->private_data, 1, voices);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_kfree(voices);
	return result;
}

static void snd_mixer_lib_volume1_free(void *private_data)
{
	struct snd_stru_mixer_lib_volume1 *volume1 = (struct snd_stru_mixer_lib_volume1 *)private_data;

	if (volume1) {
		snd_kfree(volume1->ranges);
		snd_kfree(volume1);
	}
}

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)
{
	snd_kmixer_element_new_t nelement;
	snd_kmixer_element_t *result;
	struct snd_stru_mixer_lib_volume1 *volume1;
	int idx, *pvoices;

	if (!name || !control)
		return NULL;
	volume1 = (struct snd_stru_mixer_lib_volume1 *)snd_kmalloc(sizeof(*volume1), GFP_KERNEL);
	if (!volume1)
		return NULL;
	volume1->voices = voices;
	volume1->ranges = snd_kmalloc(voices * sizeof(struct snd_mixer_element_volume1_range), GFP_KERNEL);
	if (!volume1->ranges) {
		snd_kfree(volume1);
		return NULL;
	}
	memcpy(volume1->ranges, ranges, voices * sizeof(struct snd_mixer_element_volume1_range));
	volume1->control = control;
	volume1->private_data = private_data;
	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.private_data = volume1;
	nelement.private_free = snd_mixer_lib_volume1_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* 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(private_data, 1, pvoices);
			snd_kfree(pvoices);
		}
	}
	return result;
}

/*
 *  Simple accumulator
 */

static int snd_mixer_lib_accu1_info(void *private_data,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu1 *accu1 = (struct snd_stru_mixer_lib_accu1 *)private_data;

	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)
		return NULL;
	accu1 = (struct snd_stru_mixer_lib_accu1 *)snd_kmalloc(sizeof(*accu1), GFP_KERNEL);
	if (!accu1)
		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.private_data = accu1;
	nelement.private_free = snd_kfree;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple accumulator with MONO output
 */

static int snd_mixer_lib_accu2_info(void *private_data,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu2 *accu2 = (struct snd_stru_mixer_lib_accu2 *)private_data;

	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)
		return NULL;
	accu2 = (struct snd_stru_mixer_lib_accu2 *)snd_kmalloc(sizeof(*accu2), GFP_KERNEL);
	if (!accu2)
		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.private_data = accu2;
	nelement.private_free = snd_kfree;
	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(void *private_data,
				    snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_accu3 *accu3 = (struct snd_stru_mixer_lib_accu3 *)private_data;
	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(void *private_data,
				       snd_kmixer_file_t *mfile,
				       int cmd,
				       void *data,
				       int size)
{
	struct snd_stru_mixer_lib_accu3 *accu3 = (struct snd_stru_mixer_lib_accu3 *)private_data;
	int bsize, bsize1, result;
	int *voices;
	snd_mixer_element_t *ctrl;
	
	result = 0;
	bsize = accu3->voices * sizeof(int);
	if (bsize <= 0)
		return -EINVAL;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(int) + bsize;
	voices = snd_kcalloc(bsize, GFP_KERNEL);
	if (!voices)
		return -ENOMEM;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		if (ctrl->data.accu3.voices_size > 0 && !ctrl->data.accu3.pvoices) {
			result = -EINVAL;
			goto __end;
		}
		bsize1 = ctrl->data.accu3.voices_size * sizeof(int);
		if (bsize1 > bsize)
			bsize1 = bsize;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = accu3->control(accu3->private_data, 0, voices);
			if (result >= 0) {
				ctrl->data.accu3.voices = accu3->voices;
				if (ctrl->data.accu3.voices > ctrl->data.accu3.voices_size)
					ctrl->data.accu3.voices = ctrl->data.accu3.voices_size;
				ctrl->data.accu3.voices_over = accu3->voices - ctrl->data.accu3.voices;
				if (copy_to_user(ctrl->data.accu3.pvoices, voices, bsize1))
					result = -EFAULT;
			}
			break;
		case SND_MIXER_CMD_WRITE:
			if (ctrl->data.accu3.voices_size < accu3->voices) {
				result = -EINVAL;
				goto __end;
			}
			if (ctrl->data.accu3.voices != accu3->voices) {
				result = -EINVAL;
				goto __end;
			}
			if (copy_from_user(voices, ctrl->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(accu3->private_data, 1, voices);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((int *)data) = accu3->voices;
		result = accu3->control(accu3->private_data, 0, voices);
		if (result >= 0) {
			memcpy((char *)data + sizeof(int), voices, bsize);
			result = sizeof(int) + bsize;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < bsize + sizeof(int)) {
			result = -EINVAL;
			goto __end;
		}
		if (*((int *)data) != accu3->voices) {
			result = -EINVAL;
			goto __end;
		}
		memcpy(voices, (char *)data + sizeof(int), bsize);
		if (snd_mixer_lib_accu3_verify_range(accu3->voices, voices, accu3->ranges)) {
			result = -EINVAL;
			goto __end;
		}
		result = accu3->control(accu3->private_data, 1, voices);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_kfree(voices);
	return result;
}

static void snd_mixer_lib_accu3_free(void *private_data)
{
	struct snd_stru_mixer_lib_accu3 *accu3 = (struct snd_stru_mixer_lib_accu3 *)private_data;

	if (accu3) {
		snd_kfree(accu3->ranges);
		snd_kfree(accu3);
	}
}

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;
	int idx, *pvoices;

	if (!name || !control)
		return NULL;
	accu3 = (struct snd_stru_mixer_lib_accu3 *)snd_kmalloc(sizeof(*accu3), GFP_KERNEL);
	if (!accu3)
		return NULL;
	accu3->voices = voices;
	accu3->ranges = snd_kmalloc(voices * sizeof(struct snd_mixer_element_accu3_range), GFP_KERNEL);
	if (!accu3->ranges) {
		snd_kfree(accu3);
		return NULL;
	}
	memcpy(accu3->ranges, ranges, voices * sizeof(struct snd_mixer_element_accu3_range));
	accu3->control = control;
	accu3->private_data = private_data;
	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 = accu3;
	nelement.private_free = snd_mixer_lib_accu3_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* 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(private_data, 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(void *private_data,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_mux1 *mux1 = (struct snd_stru_mixer_lib_mux1 *)private_data;

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

static int snd_mixer_lib_mux1_control(void *private_data,
				      snd_kmixer_file_t *mfile,
				      int cmd,
				      void *data,
				      int size)
{
	struct snd_stru_mixer_lib_mux1 *mux1 = (struct snd_stru_mixer_lib_mux1 *)private_data;
	snd_mixer_element_t *ctrl;
	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;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(int) + sizeof(eid) * mux1->voices;
	kelements = snd_kcalloc(bsize, GFP_KERNEL);
	if (!kelements)
		return -ENOMEM;
	result = 0;
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		bsize1 = ctrl->data.mux1.output_size * sizeof(snd_mixer_eid_t);
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = mux1->control(mux1->private_data, 0, kelements);
			if (result >= 0) {
				if (mux1->voices > ctrl->data.mux1.output_size) {
					ctrl->data.mux1.output = ctrl->data.mux1.output_size;
				} else {
					ctrl->data.mux1.output = mux1->voices;
				}
				for (idx = 0; idx < ctrl->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(&ctrl->data.mux1.poutput[idx], &eid, sizeof(eid))) {
						result = -EFAULT;
						goto __err;
					}
				}
				ctrl->data.mux1.output_over = mux1->voices - ctrl->data.mux1.output;
			}
			break;
		case SND_MIXER_CMD_WRITE:
			if (ctrl->data.mux1.output != mux1->voices ||
			    ctrl->data.mux1.output_size < ctrl->data.mux1.output) {
				result = -EINVAL;
				goto __err;
			}
			for (idx = 0; idx < mux1->voices; idx++) {
				if (copy_from_user(&eid, &ctrl->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(mux1->private_data, 1, kelements);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(int) + mux1->voices * sizeof(eid)) {
			result = -ENOMEM;
			goto __err;
		}
		result = mux1->control(mux1->private_data, 0, kelements);
		*((int *)data) = mux1->voices;
		if (result >= 0) {
			for (idx = 0; idx < mux1->voices; 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;
				}
				memcpy((char *)data + sizeof(int) + sizeof(eid) * idx, &eid, sizeof(eid));
			}
			result = sizeof(int) + sizeof(eid) * mux1->voices;
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < sizeof(int) * mux1->voices * sizeof(eid)) {
			result = -EINVAL;
			goto __err;
		}
		if (*((int *)data) != mux1->voices) {
			result = -EINVAL;
			goto __err;
		}
		for (idx = 0; idx < mux1->voices; idx++) {
			memcpy(&eid, (char *)data + sizeof(int) + mux1->voices * sizeof(eid), sizeof(eid));
			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;
			}
			result = mux1->control(mux1->private_data, 1, kelements);
			if (result >= 0)
				result = sizeof(int) + mux1->voices * sizeof(eid);
		}
		break;
	}
      __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 || !control)
		return NULL;
	mux1 = (struct snd_stru_mixer_lib_mux1 *)snd_kmalloc(sizeof(*mux1), GFP_KERNEL);
	if (!mux1)
		return NULL;
	mux1->attrib = attrib;
	mux1->voices = voices;
	mux1->control = control;
	mux1->private_data = private_data;
	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.private_data = mux1;
	nelement.private_free = snd_kfree;
	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(void *private_data,
				   snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info)
{
	struct snd_stru_mixer_lib_mux2 *mux2 = (struct snd_stru_mixer_lib_mux2 *)private_data;
	
	info->data.mux2.attrib = mux2->attrib;
	return 0;
}

static int snd_mixer_lib_mux2_control(void *private_data,
				      snd_kmixer_file_t *mfile,
				      int cmd,
				      void *data,
				      int size)
{
	struct snd_stru_mixer_lib_mux2 *mux2 = (struct snd_stru_mixer_lib_mux2 *)private_data;
	snd_mixer_element_t *ctrl;
	snd_kmixer_element_t *kelement;
	snd_mixer_eid_t eid;
	int result;
	
	result = 0;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(eid);
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			result = mux2->control(mux2->private_data, 0, &kelement);
			if (result >= 0) {
				memset(&ctrl->data.mux2.output, 0, sizeof(ctrl->data.mux2.output));
				if (kelement) {
					strncpy(ctrl->data.mux2.output.name, kelement->name, sizeof(ctrl->data.mux2.output.name));
					ctrl->data.mux2.output.index = kelement->index;
					ctrl->data.mux2.output.type = kelement->type;
				}
			}
			break;
		case SND_MIXER_CMD_WRITE:
			kelement = snd_mixer_element_find(mfile->mixer, ctrl->data.mux2.output.name, ctrl->data.mux2.output.index, ctrl->data.mux2.output.type);
			if (!kelement && !(mux2->attrib & SND_MIXER_MUX2_NONE))
				return -EINVAL;
			result = mux2->control(mux2->private_data, 1, &kelement);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(eid))
			return -ENOMEM;
		memset(&eid, 0, sizeof(eid));
		result = mux2->control(mux2->private_data, 0, &kelement);
		if (result >= 0) {
			if (kelement) {
				strncpy(eid.name, kelement->name, sizeof(eid.name));
				eid.index = kelement->index;
				eid.type = kelement->type;
			}
			memcpy(data, &eid, sizeof(eid));
			result = sizeof(eid);
		}
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < sizeof(eid))
			return -EINVAL;
		memcpy(&eid, data, sizeof(eid));
		kelement = snd_mixer_element_find(mfile->mixer, eid.name, eid.index, eid.type);
		if (!kelement && !(mux2->attrib & SND_MIXER_MUX2_NONE))
			return -EINVAL;
		result = mux2->control(mux2->private_data, 1, &kelement);
		if (result >= 0)
			result = sizeof(eid);
		break;
	}
	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 || !control)
		return NULL;
	mux2 = (struct snd_stru_mixer_lib_mux2 *)snd_kmalloc(sizeof(*mux2), GFP_KERNEL);
	if (!mux2)
		return NULL;
	mux2->attrib = attrib;
	mux2->control = control;
	mux2->private_data = private_data;
	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.private_data = mux2;
	nelement.private_free = snd_kfree;
	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(void *private_data,
					    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 *)private_data;

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

static int snd_mixer_lib_tone_control1_control(void *private_data,
					       snd_kmixer_file_t *mfile,
					       int cmd,
					       void *data,
					       int size)
{
	struct snd_stru_mixer_lib_tone_control1 *tc1 = (struct snd_stru_mixer_lib_tone_control1 *)private_data;
	snd_mixer_element_t *ctrl;
	struct snd_mixer_element_tone_control1 tc;
	int result;

	result = 0;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(tc);
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			memset(&ctrl->data.tc1, 0, sizeof(ctrl->data.teffect1));
			ctrl->data.tc1.tc = tc1->data.tc;
			result = tc1->control(tc1->private_data, 0, &ctrl->data.tc1);
			break;
		case SND_MIXER_CMD_WRITE:
			if (snd_mixer_lib_tone_control1_check(&ctrl->data.tc1, &tc1->data)) { 
				result = -EINVAL;
				goto __error;
			}
			result = tc1->control(tc1->private_data, 1, &ctrl->data.tc1);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(tc))
			return -ENOMEM;
		memset(&tc, 0, sizeof(tc));
		tc.tc = tc1->data.tc;
		result = tc1->control(tc1->private_data, 0, &tc);
		if (result >= 0)
			result = sizeof(tc);
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < sizeof(tc))
			return -EINVAL;
		memcpy(&tc, data, sizeof(tc));
		if (snd_mixer_lib_tone_control1_check(&tc, &tc1->data)) { 
			result = -EINVAL;
			goto __error;
		}
		result = tc1->control(tc1->private_data, 1, &tc);
		if (result >= 0)
			result = sizeof(tc);
		break;
	}
      __error:
	return result;
}

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 || !info || !control)
		return NULL;
	tc1 = (struct snd_stru_mixer_lib_tone_control1 *)snd_kmalloc(sizeof(*tc1), GFP_KERNEL);
	if (!tc1)
		return NULL;
	memcpy(&tc1->data, info, sizeof(tc1->data));
	tc1->control = control;
	tc1->private_data = private_data;
	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.private_data = tc1;
	nelement.private_free = snd_kfree;
	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 = info->min_bass;
		tc.treble = info->min_treble;
		control(private_data, 1, &tc);
	}
	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(void *private_data,
				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 *)private_data;
	
	info->data.teffect1 = effect1->data;	
	return 0;
}

static int snd_mixer_lib_3d_effect1_control(void *private_data,
				snd_kmixer_file_t *mfile,
				int cmd,
				void *data,
				int size)
{
	struct snd_stru_mixer_lib_3d_effect1 *effect1 = (struct snd_stru_mixer_lib_3d_effect1 *)private_data;
	snd_mixer_element_t *ctrl;
	struct snd_mixer_element_3d_effect1 eff;
	int result;

	result = 0;
	if (cmd == SND_MIXER_CMD_SIZE)
		return sizeof(eff);
	switch (cmd) {
	case SND_MIXER_CMD_READ:
	case SND_MIXER_CMD_WRITE:
		ctrl = (snd_mixer_element_t *)data;
		switch (cmd) {
		case SND_MIXER_CMD_READ:
			memset(&ctrl->data.teffect1, 0, sizeof(ctrl->data.teffect1));
			ctrl->data.teffect1.effect = effect1->data.effect;
			result = effect1->control(effect1->private_data, 0, &ctrl->data.teffect1);
			break;
		case SND_MIXER_CMD_WRITE:
			if (snd_mixer_lib_3d_effect1_check(&ctrl->data.teffect1, &effect1->data)) {
				result = -EINVAL;
				goto __error;
			}
			result = effect1->control(effect1->private_data, 1, &ctrl->data.teffect1);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(eff))
			return -ENOMEM;
		memset(&eff, 0, sizeof(eff));
		eff.effect = effect1->data.effect;
		result = effect1->control(effect1->private_data, 0, &eff);
		if (result >= 0)
			result = sizeof(eff);
		break;
	case SND_MIXER_CMD_RESTORE:
		if (size < sizeof(eff))
			return -EINVAL;
		memcpy(&eff, data, sizeof(eff));
		if (snd_mixer_lib_3d_effect1_check(&eff, &effect1->data)) {
			result = -EINVAL;
			goto __error;
		}
		result = effect1->control(effect1->private_data, 1, &eff);
		if (result >= 0)
			result = sizeof(eff);
		break;
	}
      __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 || !info || !control)
		return NULL;
	effect1 = (struct snd_stru_mixer_lib_3d_effect1 *)snd_kmalloc(sizeof(*effect1), GFP_KERNEL);
	if (!effect1)
		return NULL;
	memcpy(&effect1->data, info, sizeof(effect1->data));
	effect1->control = control;
	effect1->private_data = private_data;
	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 = effect1;
	nelement.private_free = snd_kfree;
	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;
		eff1.space = info->min_space;
		eff1.depth = info->min_depth;
		eff1.delay = info->min_delay;
		eff1.feedback = info->min_feedback;
		eff1.depth_rear = info->min_depth_rear;
		control(private_data, 1, &eff1);
	}
	return result;
}
