/*
 *  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 "driver.h"
#include "minors.h"
#include "mixer.h"
#include "info.h"
#include "control.h"
#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);
}

/*
 *  INPUT/OUTPUT - read only
 */

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

static void snd_mixer_lib_io_free(struct snd_stru_mixer_lib_io *io)
{
	if (io->voices && io->voices_count > 0)
		snd_free(io->voices, io->voices_count * sizeof(snd_mixer_voice_t));
	snd_free(io, sizeof(*io));
}

snd_kmixer_element_t *snd_mixer_lib_io(snd_kmixer_t *mixer,
				       char *name,
				       int index,
				       int type,
				       unsigned int attribute,
				       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_malloc(sizeof(*io));
	if (!io)
		return NULL;
	io->voices_count = voices_count;
	io->voices = (snd_mixer_voice_t *)snd_malloc(voices_count * sizeof(snd_mixer_voice_t));
	if (!io->voices) {
		snd_free(io, sizeof(struct snd_stru_mixer_lib_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_kmixer_element_info_t *)snd_mixer_lib_io_info;
	nelement.control = NULL;
	nelement.private_data = io;
	nelement.private_free = (snd_kmixer_free_t *)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 attribute)
{
	static snd_mixer_voice_t voice = {SND_MIXER_VOICE_MONO, 0};
	return snd_mixer_lib_io(mixer, name, index, type, attribute, 1, &voice);
}

snd_kmixer_element_t *snd_mixer_lib_io_stereo(snd_kmixer_t *mixer,
					      char *name,
					      int index,
					      int type,
					      unsigned int attribute)
{
	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, attribute, 2, voices);
}

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

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

static void snd_mixer_lib_pcm_free(struct snd_stru_mixer_lib_pcm *pcm)
{
	if (pcm->devices && pcm->devices_count > 0)
		snd_free(pcm->devices, pcm->devices_count * sizeof(int));
	snd_free(pcm, sizeof(*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_malloc(sizeof(*pcm));
	if (!pcm)
		return NULL;
	pcm->devices_count = devices_count;
	pcm->devices = (int *)snd_malloc(devices_count * sizeof(int));
	if (!pcm->devices) {
		snd_free(pcm, sizeof(struct snd_stru_mixer_lib_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_kmixer_element_info_t *)snd_mixer_lib_pcm_info;
	nelement.control = NULL;
	nelement.private_data = pcm;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_pcm_free;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  ADC/DAC - read only
 */

static int snd_mixer_lib_converter_info(snd_kmixer_file_t *mfile,
					snd_mixer_element_info_t *info,
					struct snd_stru_mixer_lib_converter *conv)
{
	info->data.converter.resolution = conv->resolution;
	return 0;
}

static void snd_mixer_lib_converter_free(struct snd_stru_mixer_lib_converter *conv)
{
	snd_free(conv, sizeof(*conv));
}

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_malloc(sizeof(*conv));
	if (!conv)
		return NULL;
	conv->resolution = resolution;
	memset(&nelement, 0, sizeof(nelement));
	nelement.name = name;
	nelement.index = index;
	nelement.type = type;
	nelement.info = (snd_kmixer_element_info_t *)snd_mixer_lib_converter_info;
	nelement.control = NULL;
	nelement.private_data = conv;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_converter_free;
	return snd_mixer_element_new(mixer, &nelement);
}

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

static int snd_mixer_lib_sw1_control(snd_kmixer_file_t *mfile,
				     int cmd,
				     void *data,
				     int size,
				     struct snd_stru_mixer_lib_sw1 *sw1)
{
	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_calloc(bsize);
	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:
			if (verify_area(VERIFY_WRITE, ctrl->data.switch1.psw, bsize1)) {
				result = -EFAULT;
			} else {
				result = sw1->control(0, bitmap, sw1->private_data);
				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;
					copy_to_user(ctrl->data.switch1.psw, bitmap, bsize1);
				}
			}
			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 (verify_area(VERIFY_READ, ctrl->data.switch1.psw, bsize)) {
				result = -EFAULT;
			} else {
				copy_from_user(bitmap, ctrl->data.switch1.psw, bsize);
				result = sw1->control(1, bitmap, sw1->private_data);
			}
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((unsigned int *)data) = sw1->switches;
		result = sw1->control(0, bitmap, sw1->private_data);
		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(1, bitmap, sw1->private_data);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_free(bitmap, bsize);
	return result;
}

static void snd_mixer_lib_sw1_free(struct snd_stru_mixer_lib_sw1 *sw1)
{
	snd_free(sw1, sizeof(*sw1));
}

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_malloc(sizeof(*sw1));
	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_kmixer_element_control_t *)snd_mixer_lib_sw1_control;
	nelement.private_data = sw1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_sw1_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn all switches off */
		bitmap = (unsigned int *)snd_calloc(((switches + 31) / 32) * sizeof(unsigned int));
		if (bitmap) {
			control(1, bitmap, private_data);
			snd_free(bitmap, ((switches + 31) / 32) * sizeof(unsigned int));
		}
	}
	return result;
}

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

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

static void snd_mixer_lib_sw2_free(struct snd_stru_mixer_lib_sw2 *sw2)
{
	snd_free(sw2, sizeof(*sw2));
}

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_malloc(sizeof(*sw2));
	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_kmixer_element_control_t *)snd_mixer_lib_sw2_control;
	nelement.private_data = sw2;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_sw2_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn switch off */
		val = 0;
		control(1, &val, private_data);
	}
	return result;
}

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

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

static int snd_mixer_lib_sw3_control(snd_kmixer_file_t *mfile,
				     int cmd,
				     void *data,
				     int size,
				     struct snd_stru_mixer_lib_sw3 *sw3)
{
	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_calloc(bsize);
	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:
			if (verify_area(VERIFY_WRITE, ctrl->data.switch3.prsw, bsize1)) {
				result = -EFAULT;
			} else {
				result = sw3->control(0, bitmap, sw3->private_data);
				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;
					copy_to_user(ctrl->data.switch3.prsw, bitmap, bsize1);
				}
			}
			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 (verify_area(VERIFY_READ, ctrl->data.switch3.prsw, bsize)) {
				result = -EFAULT;
			} else {
				copy_from_user(bitmap, ctrl->data.switch3.prsw, bsize);
				result = sw3->control(1, bitmap, sw3->private_data);
			}
			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(0, bitmap, sw3->private_data);
		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(1, bitmap, sw3->private_data);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_free(bitmap, bsize);
	return result;
}

static void snd_mixer_lib_sw3_free(struct snd_stru_mixer_lib_sw3 *sw3)
{
	if (sw3->voices && sw3->voices_count > 0)
		snd_free(sw3->voices, sw3->voices_count * sizeof(snd_mixer_voice_t));
	snd_free(sw3, sizeof(*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_malloc(sizeof(*sw3));
	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_malloc(voices_count * sizeof(snd_mixer_voice_t));
	if (!sw3->voices) {
		snd_free(sw3, sizeof(struct snd_stru_mixer_lib_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_kmixer_element_info_t *)snd_mixer_lib_sw3_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_sw3_control;
	nelement.private_data = sw3;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_sw3_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn all switches off */
	 	bitmap = (unsigned int *)snd_calloc(((voices_count + 31) / 32) * sizeof(unsigned int));
		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(1, bitmap, private_data);
			snd_free(bitmap, ((voices_count + 31) / 32) * sizeof(unsigned int));
		}
	}
	return result;
}

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

static int snd_mixer_lib_volume1_info(snd_kmixer_file_t *mfile,
				      snd_mixer_element_info_t *info,
				      struct snd_stru_mixer_lib_volume1 *volume1)
{
	int idx;
	
	if (verify_area(VERIFY_WRITE, info->data.volume1.prange, info->data.volume1.range_size * sizeof(struct snd_mixer_element_volume1_range)))
		return -EFAULT;
	info->data.volume1.range = 0;
	for (idx = 0; idx < info->data.volume1.range_size && idx < volume1->voices; idx++) {
		copy_to_user(&info->data.volume1.prange[idx], &volume1->ranges[idx], sizeof(struct snd_mixer_element_volume1_range));
		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_file_t *mfile,
					 int cmd,
					 void *data,
					 int size,
					 struct snd_stru_mixer_lib_volume1 *volume1)
{
	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_calloc(bsize);
	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:
			if (verify_area(VERIFY_WRITE, ctrl->data.volume1.pvoices, bsize1)) {
				result = -EFAULT;
			} else {
				result = volume1->control(0, voices, volume1->private_data);
				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;
					copy_to_user(ctrl->data.volume1.pvoices, voices, bsize1);
				}
			}
			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 (verify_area(VERIFY_READ, ctrl->data.volume1.pvoices, bsize)) {
				result = -EFAULT;
			} else {
				copy_from_user(voices, ctrl->data.switch1.psw, bsize);
				if (snd_mixer_lib_volume1_verify_range(volume1->voices, voices, volume1->ranges)) {
					result = -EINVAL;
					goto __end;
				}
				result = volume1->control(1, voices, volume1->private_data);
			}
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((int *)data) = volume1->voices;
		result = volume1->control(0, voices, volume1->private_data);
		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(1, voices, volume1->private_data);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_free(voices, bsize);
	return result;
}

static void snd_mixer_lib_volume1_free(struct snd_stru_mixer_lib_volume1 *volume1)
{
	if (volume1) {
		snd_free(volume1->ranges, volume1->voices * sizeof(int));
		snd_free(volume1, sizeof(*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_malloc(sizeof(*volume1));
	if (!volume1)
		return NULL;
	volume1->voices = voices;
	volume1->ranges = snd_malloc(voices * sizeof(struct snd_mixer_element_volume1_range));
	if (!volume1->ranges) {
		snd_free(volume1, sizeof(struct snd_stru_mixer_lib_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_kmixer_element_info_t *)snd_mixer_lib_volume1_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_volume1_control;
	nelement.private_data = volume1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_volume1_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn volume off */
		pvoices = (int *)snd_malloc(sizeof(int) * voices);
		if (pvoices) {
			for (idx = 0; idx < voices; idx++)
				pvoices[idx] = ranges[idx].min;
			control(1, pvoices, private_data);
			snd_free(pvoices, sizeof(int) * voices); 
		}
	}
	return result;
}

/*
 *  Simple accumulator
 */

static int snd_mixer_lib_accu1_info(snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info,
				    struct snd_stru_mixer_lib_accu1 *accu1)
{
	info->data.accu1.attenuation = accu1->attenuation;
	return 0;
}

static void snd_mixer_lib_accu1_free(struct snd_stru_mixer_lib_accu1 *accu1)
{
	snd_free(accu1, sizeof(*accu1));
}

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_malloc(sizeof(*accu1));
	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_kmixer_element_info_t *)snd_mixer_lib_accu1_info;
	nelement.control = NULL;
	nelement.private_data = accu1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_accu1_free;
	return snd_mixer_element_new(mixer, &nelement);
}

/*
 *  Simple accumulator with MONO output
 */

static int snd_mixer_lib_accu2_info(snd_kmixer_file_t *mfile,
				    snd_mixer_element_info_t *info,
				    struct snd_stru_mixer_lib_accu2 *accu2)
{
	info->data.accu2.attenuation = accu2->attenuation;
	return 0;
}

static void snd_mixer_lib_accu2_free(struct snd_stru_mixer_lib_accu2 *accu2)
{
	snd_free(accu2, sizeof(*accu2));
}

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_malloc(sizeof(*accu2));
	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_kmixer_element_info_t *)snd_mixer_lib_accu2_info;
	nelement.control = NULL;
	nelement.private_data = accu2;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_accu2_free;
	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_file_t *mfile,
				      snd_mixer_element_info_t *info,
				      struct snd_stru_mixer_lib_accu3 *accu3)
{
	int idx;
	
	if (verify_area(VERIFY_WRITE, info->data.accu3.prange, info->data.accu3.range_size * sizeof(struct snd_mixer_element_accu3_range)))
		return -EFAULT;
	info->data.accu3.range = 0;
	for (idx = 0; idx < info->data.accu3.range_size && idx < accu3->voices; idx++) {
		copy_to_user(&info->data.accu3.prange[idx], &accu3->ranges[idx], sizeof(struct snd_mixer_element_accu3_range));
		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_file_t *mfile,
					 int cmd,
					 void *data,
					 int size,
					 struct snd_stru_mixer_lib_accu3 *accu3)
{
	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_calloc(bsize);
	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:
			if (verify_area(VERIFY_WRITE, ctrl->data.accu3.pvoices, bsize1)) {
				result = -EFAULT;
			} else {
				result = accu3->control(0, voices, accu3->private_data);
				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;
					copy_to_user(ctrl->data.accu3.pvoices, voices, bsize1);
				}
			}
			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 (verify_area(VERIFY_READ, ctrl->data.accu3.pvoices, bsize)) {
				result = -EFAULT;
			} else {
				copy_from_user(voices, ctrl->data.switch1.psw, bsize);
				if (snd_mixer_lib_accu3_verify_range(accu3->voices, voices, accu3->ranges)) {
					result = -EINVAL;
					goto __end;
				}
				result = accu3->control(1, voices, accu3->private_data);
			}
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < bsize + sizeof(int)) {
			result = -ENOMEM;
			goto __end;
		}
		*((int *)data) = accu3->voices;
		result = accu3->control(0, voices, accu3->private_data);
		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(1, voices, accu3->private_data);
		if (result >= 0)
			result = sizeof(int) + bsize;
		break;
	}
      __end:
	snd_free(voices, bsize);
	return result;
}

static void snd_mixer_lib_accu3_free(struct snd_stru_mixer_lib_accu3 *accu3)
{
	if (accu3) {
		snd_free(accu3->ranges, accu3->voices * sizeof(int));
		snd_free(accu3, sizeof(*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_malloc(sizeof(*accu3));
	if (!accu3)
		return NULL;
	accu3->voices = voices;
	accu3->ranges = snd_malloc(voices * sizeof(struct snd_mixer_element_accu3_range));
	if (!accu3->ranges) {
		snd_free(accu3, sizeof(struct snd_stru_mixer_lib_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_kmixer_element_info_t *)snd_mixer_lib_accu3_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_accu3_control;
	nelement.private_data = accu3;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_accu3_free;
	result = snd_mixer_element_new(mixer, &nelement);
	if (result) {
		/* initial settings - turn volume off */
		pvoices = (int *)snd_malloc(sizeof(int) * voices);
		if (pvoices) {
			for (idx = 0; idx < voices; idx++)
				pvoices[idx] = ranges[idx].min;
			control(1, pvoices, private_data);
			snd_free(pvoices, sizeof(int) * voices); 
		}
	}
	return result;
}

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

static int snd_mixer_lib_mux1_info(snd_kmixer_file_t *mfile,
				   snd_mixer_element_info_t *info,
				   struct snd_stru_mixer_lib_mux1 *mux1)
{
	info->data.mux1.attribute = mux1->attribute;
	return 0;
}

static int snd_mixer_lib_mux1_control(snd_kmixer_file_t *mfile,
				      int cmd,
				      void *data,
				      int size,
				      struct snd_stru_mixer_lib_mux1 *mux1)
{
	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_calloc(bsize);
	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:
			if (verify_area(VERIFY_WRITE, ctrl->data.mux1.poutput, bsize1)) {
				result = -EFAULT;
				goto __err;
			}
			result = mux1->control(0, kelements, mux1->private_data);
			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;
					}
					copy_to_user(&ctrl->data.mux1.poutput[idx], &eid, sizeof(eid));
				}
				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;
			}
			if (verify_area(VERIFY_READ, ctrl->data.mux1.poutput, bsize1)) {
				result = -EFAULT;
				goto __err;
			}
			for (idx = 0; idx < mux1->voices; idx++) {
				copy_from_user(&eid, &ctrl->data.mux1.poutput[idx], sizeof(eid));
				kelement = snd_mixer_element_find(mfile->mixer, eid.name, eid.index, eid.type);
				if (!kelement && !(mux1->attribute & SND_MIXER_MUX1_NONE)) {
					result = -EINVAL;
					goto __err;
				}
				kelements[idx] = kelement;
			}
			result = mux1->control(1, kelements, mux1->private_data);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(int) + mux1->voices * sizeof(eid)) {
			result = -ENOMEM;
			goto __err;
		}
		result = mux1->control(0, kelements, mux1->private_data);
		*((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->attribute & SND_MIXER_MUX1_NONE)) {
				result = -EINVAL;
				goto __err;
			}
			result = mux1->control(1, kelements, mux1->private_data);
			if (result >= 0)
				result = sizeof(int) + mux1->voices * sizeof(eid);
		}
		break;
	}
      __err:
      	snd_free(kelements, bsize);
	return result;
}

static void snd_mixer_lib_mux1_free(struct snd_stru_mixer_lib_mux1 *mux1)
{
	snd_free(mux1, sizeof(*mux1));
}

snd_kmixer_element_t *snd_mixer_lib_mux1(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 unsigned int attribute,
					 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_malloc(sizeof(*mux1));
	if (!mux1)
		return NULL;
	mux1->attribute = attribute;
	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_kmixer_element_info_t *)snd_mixer_lib_mux1_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_mux1_control;
	nelement.private_data = mux1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_mux1_free;
	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_file_t *mfile,
				   snd_mixer_element_info_t *info,
				   struct snd_stru_mixer_lib_mux2 *mux2)
{
	info->data.mux2.attribute = mux2->attribute;
	return 0;
}

static int snd_mixer_lib_mux2_control(snd_kmixer_file_t *mfile,
				      int cmd,
				      void *data,
				      int size,
				      struct snd_stru_mixer_lib_mux2 *mux2)
{
	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(0, &kelement, mux2->private_data);
			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->attribute & SND_MIXER_MUX2_NONE))
				return -EINVAL;
			result = mux2->control(1, &kelement, mux2->private_data);
			break;
		}
		break;
	case SND_MIXER_CMD_STORE:
		if (size < sizeof(eid))
			return -ENOMEM;
		memset(&eid, 0, sizeof(eid));
		result = mux2->control(0, &kelement, mux2->private_data);
		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->attribute & SND_MIXER_MUX2_NONE))
			return -EINVAL;
		result = mux2->control(1, &kelement, mux2->private_data);
		if (result >= 0)
			result = sizeof(eid);
		break;
	}
	return result;
}

static void snd_mixer_lib_mux2_free(struct snd_stru_mixer_lib_mux2 *mux2)
{
	snd_free(mux2, sizeof(*mux2));
}

snd_kmixer_element_t *snd_mixer_lib_mux2(snd_kmixer_t *mixer,
					 char *name,
					 int index,
					 unsigned int attribute,
					 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_malloc(sizeof(*mux2));
	if (!mux2)
		return NULL;
	mux2->attribute = attribute;
	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_kmixer_element_info_t *)snd_mixer_lib_mux2_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_mux2_control;
	nelement.private_data = mux2;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_mux2_free;
	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_file_t *mfile,
					    snd_mixer_element_info_t *info,
					    struct snd_stru_mixer_lib_tone_control1 *tc1)
{
	info->data.tc1 = tc1->data;	
	return 0;
}

static int snd_mixer_lib_tone_control1_control(snd_kmixer_file_t *mfile,
					       int cmd,
					       void *data,
					       int size,
					       struct snd_stru_mixer_lib_tone_control1 *tc1)
{
	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(0, &ctrl->data.tc1, tc1->private_data);
			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(1, &ctrl->data.tc1, tc1->private_data);
			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(0, &tc, tc1->private_data);
		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(1, &tc, tc1->private_data);
		if (result >= 0)
			result = sizeof(tc);
		break;
	}
      __error:
	return result;
}

static void snd_mixer_lib_tone_control1_free(struct snd_stru_mixer_lib_tone_control1 *tc1)
{
	snd_free(tc1, sizeof(*tc1));
}

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_malloc(sizeof(*tc1));
	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_kmixer_element_info_t *)snd_mixer_lib_tone_control1_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_tone_control1_control;
	nelement.private_data = tc1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_tone_control1_free;
	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;
		control(1, &tc, private_data);
	}
	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;
	return 0;	
}

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

static int snd_mixer_lib_3d_effect1_control(snd_kmixer_file_t *mfile,
				      int cmd,
				      void *data,
				      int size,
				      struct snd_stru_mixer_lib_3d_effect1 *effect1)
{
	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(0, &ctrl->data.teffect1, effect1->private_data);
			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(1, &ctrl->data.teffect1, effect1->private_data);
			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(0, &eff, effect1->private_data);
		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(1, &eff, effect1->private_data);
		if (result >= 0)
			result = sizeof(eff);
		break;
	}
      __error:
	return result;
}

static void snd_mixer_lib_3d_effect1_free(struct snd_stru_mixer_lib_3d_effect1 *effect1)
{
	snd_free(effect1, sizeof(*effect1));
}

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_malloc(sizeof(*effect1));
	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_kmixer_element_info_t *)snd_mixer_lib_3d_effect1_info;
	nelement.control = (snd_kmixer_element_control_t *)snd_mixer_lib_3d_effect1_control;
	nelement.private_data = effect1;
	nelement.private_free = (snd_kmixer_free_t *)snd_mixer_lib_3d_effect1_free;
	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;
		control(1, &eff1, private_data);
	}
	return result;
}
