/*
 *  Abstract routines for MIXER control
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "../include/driver.h"
#include "../include/minors.h"
#include "../include/mixer.h"
#include "../include/info.h"
#include "../include/control.h"
#include "../include/sndpersist.h"

#define SND_MIXERS		(SND_CARDS * SND_MINOR_MIXERS)

snd_kmixer_t *snd_mixers[SND_MIXERS] = {[0 ... (SND_MIXERS - 1)] = NULL};
static struct snd_stru_mixer_notify * snd_mixer_notify_first = NULL;

static DECLARE_MUTEX(register_mutex);

int snd_mixer_lock(snd_kmixer_t *mixer, int xup)
{
	if (!mixer)
		return -EINVAL;
	if (!xup) {
		down(&mixer->lock);
	} else {
		up(&mixer->lock);
	}
	return 0;
}

static int snd_mixer_open(unsigned short minor, int cardnum, int device,
                          struct file *file)
{
	snd_kmixer_file_t *mfile, *mfile1;
	snd_kmixer_t *mixer;
	unsigned long flags;

	if (!(mixer = snd_mixers[(cardnum * SND_MINOR_MIXERS) + device]))
		return -ENODEV;
	mfile = (snd_kmixer_file_t *) snd_kcalloc(sizeof(*mfile), GFP_KERNEL);
	if (!mfile)
		return -ENOMEM;
	init_waitqueue_head(&mfile->change_sleep);
	mfile->read_lock = SPIN_LOCK_UNLOCKED;
	mfile->mixer = mixer;
	memset(&mfile->read_filter, 0xff, sizeof(mfile->read_filter));
	file->private_data = mfile;
	MOD_INC_USE_COUNT;
	mixer->card->use_inc(mixer->card);
	snd_mixer_lock(mixer, 0);
	spin_lock_irqsave(&mixer->ffile_lock, flags);
	if (mixer->ffile) {
		for (mfile1 = mixer->ffile;
		     mfile1->next;
		     mfile1 = mfile1->next);
		mfile1->next = mfile;
	} else {
		mixer->ffile = mfile;
	}
	spin_unlock_irqrestore(&mixer->ffile_lock, flags);
	snd_mixer_lock(mixer, 1);
	return 0;
}

static int snd_mixer_release(unsigned short minor, int cardnum, int device,
                             struct file *file)
{
	snd_kmixer_file_t *mfile, *mfile1;
	snd_kmixer_t *mixer;
	snd_kmixer_read_t *mread;
	unsigned long flags;

	if (file->private_data) {
		mfile = (snd_kmixer_file_t *) file->private_data;
		file->private_data = NULL;
		mixer = mfile->mixer;
		snd_mixer_lock(mixer, 0);
		spin_lock_irqsave(&mixer->ffile_lock, flags);
		while (mfile->use) {
			spin_unlock_irqrestore(&mixer->ffile_lock, flags);
			current->state = TASK_INTERRUPTIBLE;
			schedule_timeout(1);
			spin_lock_irqsave(&mixer->ffile_lock, flags);
		}
		if (mixer->ffile == mfile) {
			mixer->ffile = mfile->next;
		} else {
			for (mfile1 = mixer->ffile;
			     mfile1->next != mfile;
			     mfile1 = mfile1->next);
			mfile1->next = mfile->next;
		}
		spin_unlock_irqrestore(&mixer->ffile_lock, flags);
		snd_mixer_lock(mixer, 1);
		while (mfile->first_item) {
			mread = mfile->first_item;
			mfile->first_item = mread->next;
			snd_kfree(mread);
		}
		snd_kfree(mfile);
		mixer->card->use_dec(mixer->card);
		MOD_DEC_USE_COUNT;
	}
	return 0;
}

int snd_mixer_busy(snd_kmixer_file_t * mfile)
{
	if (mfile->rebuild || mfile->first_item)
		return 1;
	return 0;
}

static snd_kmixer_read_t *snd_mixer_alloc_read(snd_kmixer_file_t * mfile, int atomic)
{
	snd_kmixer_read_t *item;

	item = snd_kcalloc(sizeof(*item), GFP_KERNEL | (atomic ? GFP_ATOMIC : 0));
	return item;
}

static void snd_mixer_empty_read_queue(snd_kmixer_file_t * mfile, int rebuild, int atomic)
{
	unsigned long flags;
	snd_kmixer_read_t *read1, *read2;

	spin_lock_irqsave(&mfile->read_lock, flags);
	read1 = mfile->first_item;
	mfile->first_item = mfile->last_item = NULL;
	mfile->rebuild = rebuild ? 1 : 0;
	spin_unlock_irqrestore(&mfile->read_lock, flags);
	while (read1) {
		read2 = read1->next;
		snd_kfree(read1);
		read1 = read2;
	}
}

static void snd_mixer_notify_change(snd_kmixer_file_t * mfile, snd_mixer_read_t * mread, int all, int atomic)
{
	unsigned long flags;
	snd_kmixer_file_t *mfile1;
	snd_kmixer_t *mixer;
	snd_kmixer_read_t *nread;

	if (!mfile || !mread || mread->cmd >= 32*8)
		return;
	mixer = mfile->mixer;
	spin_lock_irqsave(&mixer->ffile_lock, flags);
	for (mfile1 = mixer->ffile; mfile1; mfile1 = mfile1->next) {
		if (all || mfile1 != mfile) {
			if (!snd_mixer_get_bit(mfile1->read_filter, mread->cmd))
				continue;
			mfile1->use++;
			spin_unlock_irqrestore(&mixer->ffile_lock, flags);
			if (!mfile1->read_active) {
				mfile1->rebuild = 1;
				goto __continue;
			}
			nread = snd_mixer_alloc_read(mfile1, atomic);
			if (!nread) {
				snd_mixer_empty_read_queue(mfile1, 1, atomic);
			} else {
				memcpy(&nread->data, mread, sizeof(nread->data));
				spin_lock_irqsave(&mfile1->read_lock, flags);
				if (!mfile1->first_item) {
					mfile1->first_item = mfile1->last_item = nread;
				} else {
					mfile1->last_item->next = nread;
					mfile1->last_item = nread;
				}
				spin_unlock_irqrestore(&mfile1->read_lock, flags);
			}
			wake_up(&mfile1->change_sleep);
		      __continue:
			spin_lock_irqsave(&mixer->ffile_lock, flags);
			mfile1->use--;
		}
	}
	spin_unlock_irqrestore(&mixer->ffile_lock, flags);
#ifdef CONFIG_SND_OSSEMUL
	mixer->oss_change_count++;
#endif
}

void snd_mixer_structure_change(snd_kmixer_file_t * mfile, snd_mixer_read_t * read)
{
	snd_mixer_notify_change(mfile, read, 1, 0);
}

void snd_mixer_value_change(snd_kmixer_file_t * mfile, snd_mixer_read_t * read, int atomic)
{
	snd_mixer_notify_change(mfile, read, 0, atomic);
}

void snd_mixer_element_free(snd_kmixer_element_t *element)
{
	if (element) {
		if (element->routes_next.size > 0)
			snd_kfree(element->routes_next.routes);
		if (element->routes_prev.size > 0)
			snd_kfree(element->routes_prev.routes);
		if (element->groups_size > 0)
			snd_kfree(element->groups);
		if (element->private_free)
			element->private_free(element->private_data);
		snd_kfree(element->name);
		snd_kfree(element);
	}
}

void snd_mixer_element_new_free(snd_kmixer_element_new_t *nelement)
{
	if (nelement) {
		if (nelement->private_free)
			nelement->private_free(nelement->private_data);
	}
}

snd_kmixer_element_t *snd_mixer_element_find(snd_kmixer_t * mixer,
						char *name, int index,
						int type)
{
	snd_kmixer_element_t *element;
	
	if (!mixer || !name)
		return NULL;
	for (element = mixer->elements; element; element = element->next)
		if (element->name && !strcmp(element->name, name) &&
		    element->index == index && element->type == type)
			return element;
	return NULL;
}

static void snd_mixer_element_notify(snd_kmixer_t * mixer, snd_kmixer_element_t *element, unsigned int cmd)
{
	snd_mixer_read_t r;
	
	memset(&r, 0, sizeof(r));
	r.cmd = cmd;
	strncpy(r.data.eid.name, element->name, sizeof(r.data.eid.name));
	r.data.eid.index = element->index;
	snd_mixer_structure_change(mixer->ffile, &r);
}

int snd_mixer_element_add(snd_kmixer_t * mixer, snd_kmixer_element_t *element)
{
	if (!mixer || !element)
		return -EINVAL;
	mixer->elements_count++;
	element->next = mixer->elements;
	mixer->elements = element;
	snd_mixer_element_notify(mixer, element, SND_MIXER_READ_ELEMENT_ADD);
	return 0;
}

int snd_mixer_element_remove(snd_kmixer_t * mixer, snd_kmixer_element_t * element)
{
	snd_kmixer_element_t *prev;

	if (!mixer || !element)
		return -EINVAL;
	for (prev = mixer->elements; prev; prev = prev->next) {
		snd_mixer_element_route_remove(mixer, element, prev);
		snd_mixer_element_route_remove(mixer, prev, element);
	}
	if (mixer->elements == element) {
		mixer->elements_count--;
		mixer->elements = element->next;
	} else {
		for (prev = mixer->elements; prev && prev->next != element; prev = prev->next);
		if (prev) {
			mixer->elements_count--;
			prev->next = element->next;
		}
	}
	snd_mixer_element_free(element);
	snd_mixer_element_notify(mixer, element, SND_MIXER_READ_ELEMENT_REMOVE);
	return 0;
}

int snd_mixer_element_rename(snd_kmixer_t * mixer,
			     char *name, int index, int type,
			     char *nname, int nindex)
{
	snd_kmixer_element_t *element;
	char *nname1;
	
	element = snd_mixer_element_find(mixer, name, index, type);
	if (element == NULL)
		return -ENOENT;
	nname1 = snd_kmalloc_strdup(nname, GFP_KERNEL);
	if (nname1 == NULL)
		return -ENOMEM;
	snd_kfree(element->name);
	element->name = nname1;
	element->index = nindex;
	return 0;
}

snd_kmixer_element_t *snd_mixer_element_new(snd_kmixer_t * mixer, snd_kmixer_element_new_t * nelement)
{
	snd_kmixer_element_t *element;
	
	if (!mixer || !nelement) {
		snd_mixer_element_new_free(nelement);
		return NULL;
	}
	element = (snd_kmixer_element_t *)snd_kcalloc(sizeof(*element), GFP_KERNEL);
	if (!element) {
		snd_mixer_element_new_free(nelement);
		return NULL;
	}
	element->name = snd_kmalloc_strdup(nelement->name, GFP_KERNEL);
	if (!element->name) {
		snd_mixer_element_new_free(nelement);
		snd_kfree(element);
		return NULL;
	}
	element->index = nelement->index;
	element->type = nelement->type;
	element->info = nelement->info;
	element->control = nelement->control;
	element->private_data = nelement->private_data;
	element->private_free = nelement->private_free;
	if (snd_mixer_element_add(mixer, element) < 0) {
		snd_mixer_element_free(element);
		return NULL;
	}
	return element;
}

int snd_mixer_element_change(snd_kmixer_t * mixer, snd_kmixer_element_t * element)
{
	if (!mixer || !element)
		return -EINVAL;
	snd_mixer_element_notify(mixer, element, SND_MIXER_READ_ELEMENT_CHANGE);
	return 0;
}

int snd_mixer_element_value_change(snd_kmixer_file_t * mfile, snd_kmixer_element_t * element, int atomic)
{
	snd_mixer_read_t r;
	int idx;
	snd_kmixer_group_t *group;
	
	if (!mfile || !element)
		return -EINVAL;
	memset(&r, 0, sizeof(r));
	r.cmd = SND_MIXER_READ_ELEMENT_VALUE;
	strncpy(r.data.eid.name, element->name, sizeof(r.data.eid.name));
	r.data.eid.index = element->index;
	snd_mixer_value_change(mfile, &r, atomic);
	for (idx = 0; idx < element->groups_count; idx++) {
		group = element->groups[idx];
		if (mfile->ignore_group != group)
			snd_mixer_group_value_change(mfile, group, atomic);
	}
	return 0;
}

int snd_mixer_element_value_change_all(snd_kmixer_t * mixer, snd_kmixer_element_t * element, int atomic)
{
	snd_mixer_read_t r;
	int idx;
	snd_kmixer_group_t *group;
	snd_kmixer_file_t sfile;
	
	if (!mixer || !element)
		return -EINVAL;
	memset(&r, 0, sizeof(r));
	r.cmd = SND_MIXER_READ_ELEMENT_VALUE;
	strncpy(r.data.eid.name, element->name, sizeof(r.data.eid.name));
	r.data.eid.index = element->index;
	memset(&sfile, 0, sizeof(sfile));
	sfile.mixer = mixer;
	init_waitqueue_head(&sfile.change_sleep);
	snd_mixer_value_change(&sfile, &r, atomic);
	for (idx = 0; idx < element->groups_count; idx++) {
		group = element->groups[idx];
		snd_mixer_group_value_change(&sfile, group, atomic);
	}
	return 0;
}

static int snd_mixer_element_list_add(snd_kmixer_element_route_t *route, snd_kmixer_element_t *element)
{
	snd_kmixer_element_t **pelement;

	if (!route || !element)
		return -EINVAL;
	if (route->count + 1 > route->size) {
		pelement = snd_kmalloc((route->size + 4) * sizeof(snd_kmixer_element_t *), GFP_KERNEL);
		if (!pelement)
			return -ENOMEM;
		route->size += 4;
		memcpy(pelement, route->routes, route->count * sizeof(snd_kmixer_element_t *));
		snd_kfree(route->routes);
		route->routes = pelement;
	}
	route->routes[route->count++] = element;
	return 0;
}

static int snd_mixer_element_list_remove(snd_kmixer_element_route_t *route, snd_kmixer_element_t *element)
{
	int idx;

	if (!route || !element)
		return -EINVAL;
	for (idx = 0; idx < route->count; idx++) {
		if (route->routes[idx] == element) {
			if (idx + 1 < route->count)
				memmove(&route->routes[idx],
					&route->routes[idx+1],
					(route->count - idx - 1) * sizeof(snd_kmixer_element_t *));
			route->count--;
			return 0;
		}
	}
	return -ENOENT;
}

int snd_mixer_element_route_add(snd_kmixer_t * mixer,
				snd_kmixer_element_t *src_element,
				snd_kmixer_element_t *dst_element)
{
	int err;

	if (!mixer || !src_element || !dst_element)
		return -EINVAL;
	err = snd_mixer_element_list_add(&src_element->routes_next, dst_element);
	if (err < 0)
		return err;
	err = snd_mixer_element_list_add(&dst_element->routes_prev, src_element);
	if (err < 0) {
		snd_mixer_element_list_remove(&src_element->routes_next, dst_element);
		return err;
	}
	return 0;
}

int snd_mixer_element_route_remove(snd_kmixer_t * mixer,
				   snd_kmixer_element_t *src_element,
				   snd_kmixer_element_t *dst_element)
{
	if (!mixer || !src_element || !dst_element)
		return -EINVAL;
	if (snd_mixer_element_list_remove(&src_element->routes_next, dst_element) < 0)
		snd_printd("mixer - element list remove failed (1), dst = '%s', index = %i, type = %i\n", dst_element->name, dst_element->index, dst_element->type);
	if (snd_mixer_element_list_remove(&dst_element->routes_prev, src_element) < 0)
		snd_printd("mixer - element list remove failed (2), src = '%s', index = %i, type = %i\n", src_element->name, src_element->index, src_element->type);
	return 0;
}

void snd_mixer_group_free(snd_kmixer_group_t * group)
{
	if (group) {
		if (group->elements_size)
			snd_kfree(group->elements);
		snd_kfree(group->name);
		snd_kfree(group);
	}
}

snd_kmixer_group_t *snd_mixer_group_find(snd_kmixer_t * mixer,
						char *name, int index)
{
	snd_kmixer_group_t *group;
	
	if (!mixer || !name)
		return NULL;
	for (group = mixer->groups; group; group = group->next)
		if (group->name && !strcmp(group->name, name) && group->index == index)
			return group;
	return NULL;
}

static void snd_mixer_group_notify(snd_kmixer_t * mixer, snd_kmixer_group_t *group, unsigned int cmd)
{
	snd_mixer_read_t r;
	
	memset(&r, 0, sizeof(r));
	r.cmd = cmd;
	strncpy(r.data.gid.name, group->name, sizeof(r.data.gid.name));
	r.data.gid.index = group->index;
	snd_mixer_structure_change(mixer->ffile, &r);
}

int snd_mixer_group_value_change(snd_kmixer_file_t * mfile, snd_kmixer_group_t * group, int atomic)
{
	snd_mixer_read_t r;

	if (!mfile || !group)
		return -EINVAL;
	memset(&r, 0, sizeof(r));
	r.cmd = SND_MIXER_READ_GROUP_VALUE;
	strncpy(r.data.gid.name, group->name, sizeof(r.data.gid.name));
	r.data.gid.index = group->index;
	snd_mixer_value_change(mfile, &r, atomic);
	return 0;
}

int snd_mixer_group_value_change_all(snd_kmixer_t * mixer, snd_kmixer_group_t * group, int atomic)
{
	snd_mixer_read_t r;
	snd_kmixer_file_t sfile;

	if (!mixer || !group)
		return -EINVAL;
	memset(&r, 0, sizeof(r));
	r.cmd = SND_MIXER_READ_GROUP_VALUE;
	strncpy(r.data.gid.name, group->name, sizeof(r.data.gid.name));
	r.data.gid.index = group->index;
	memset(&sfile, 0, sizeof(sfile));
	sfile.mixer = mixer;
	init_waitqueue_head(&sfile.change_sleep);
	snd_mixer_value_change(&sfile, &r, atomic);
	return 0;
}

int snd_mixer_group_add(snd_kmixer_t * mixer, snd_kmixer_group_t * group)
{
	if (!mixer || !group)
		return -EINVAL;
	mixer->groups_count++;
	group->next = mixer->groups;
	mixer->groups = group;
	snd_mixer_group_notify(mixer, group, SND_MIXER_READ_GROUP_ADD);
	return 0;
}

int snd_mixer_group_remove(snd_kmixer_t * mixer, snd_kmixer_group_t * group)
{
	snd_kmixer_group_t *prev;

	if (!mixer || !group)
		return -EINVAL;
	if (mixer->groups == group) {
		mixer->groups_count--;
		mixer->groups = group->next;
	} else {
		for (prev = mixer->groups; prev && prev->next != group; prev = prev->next);
		if (prev) {
			mixer->groups_count--;
			prev->next = group->next;
		}
	}
	snd_mixer_group_free(group);
	snd_mixer_group_notify(mixer, group, SND_MIXER_READ_GROUP_REMOVE);
	return 0;
}

int snd_mixer_group_rename(snd_kmixer_t * mixer,
			     char *name, int index,
			     char *nname, int nindex,
			     int oss_dev)
{
	snd_kmixer_group_t *group;
	char *nname1;
	
	group = snd_mixer_group_find(mixer, name, index);
	if (group == NULL)
		return -ENOENT;
	nname1 = snd_kmalloc_strdup(nname, GFP_KERNEL);
	if (nname1 == NULL)
		return -ENOMEM;
	snd_kfree(group->name);
	group->name = nname1;
	group->index = nindex;
	if (oss_dev >= 0)
		group->oss_dev = oss_dev;
	return 0;
}

snd_kmixer_group_t *snd_mixer_group_new(snd_kmixer_t * mixer, snd_kmixer_group_new_t * ngroup)
{
	snd_kmixer_group_t *group;
	
	if (!mixer || !ngroup)
		return NULL;
	group = (snd_kmixer_group_t *)snd_kcalloc(sizeof(*group), GFP_KERNEL);
	if (!group) {
		return NULL;
	}
	group->name = snd_kmalloc_strdup(ngroup->name, GFP_KERNEL);
	if (!group->name) {
		snd_kfree(group);
		return NULL;
	}
	group->index = ngroup->index;
	if (snd_mixer_group_add(mixer, group) < 0) {
		snd_mixer_group_free(group);
		return NULL;
	}
	return group;
}

int snd_mixer_group_change(snd_kmixer_t * mixer, snd_kmixer_group_t * group)
{
	if (!mixer || !group)
		return -EINVAL;
	snd_mixer_group_notify(mixer, group, SND_MIXER_READ_GROUP_CHANGE);
	return 0;
}

int snd_mixer_group_element_add(snd_kmixer_t * mixer,
				snd_kmixer_group_t * group,
				snd_kmixer_element_t * element)
{
	snd_kmixer_element_t **pelement;
	snd_kmixer_group_t **pgroup;

	if (group->elements_count + 1 > group->elements_size) {
		pelement = snd_kmalloc((group->elements_size + 4) * sizeof(snd_kmixer_element_t *), GFP_KERNEL);
		if (!pelement)
			return -ENOMEM;
		memcpy(pelement, group->elements, group->elements_count * sizeof(snd_kmixer_element_t *));
		snd_kfree(group->elements);
		group->elements_size += 4;
		group->elements = pelement;
	}
	if (element->groups_count + 1 > element->groups_size) {
		pgroup = snd_kmalloc((element->groups_size + 4) * sizeof(snd_kmixer_group_t *), GFP_KERNEL);
		if (!pgroup)
			return -ENOMEM;
		memcpy(pgroup, element->groups, element->groups_count * sizeof(snd_kmixer_group_t *));
		snd_kfree(element->groups);
		element->groups_size += 4;
		element->groups = pgroup;
	}
	group->elements[group->elements_count++] = element;
	element->groups[element->groups_count++] = group;
	snd_mixer_group_change(mixer, group);
	return 0;
}

int snd_mixer_group_element_remove(snd_kmixer_t * mixer,
				   snd_kmixer_group_t * group,
				   snd_kmixer_element_t * element)
{
	int idx;

	for (idx = 0; idx < group->elements_count; idx++) {
		if (group->elements[idx] == element) {
			if (idx + 1 < group->elements_count)
				memmove(&group->elements[idx],
					&group->elements[idx+1],
					(group->elements_count - idx - 1) * sizeof(snd_kmixer_element_t *));
			group->elements_count--;
			goto __next;
		}
	}
	snd_printd("element_remove: element '%s',%i,%i is not in group '%s',%i\n",
			element->name, element->index, element->type,
			group->name, group->index);
	return -ENOENT;

      __next:
	for (idx = 0; idx < element->groups_count; idx++) {
		if (element->groups[idx] == group) {
			if (idx + 1 < element->groups_count)
				memmove(&element->groups[idx],
					&element->groups[idx+1],
					(element->groups_count - idx - 1) * sizeof(snd_kmixer_group_t *));
			group->elements_count--;
			return 0;
		}
	}
	snd_printd("element_remove: element '%s',%i,%i is not in group '%s',%i (2)\n",
			element->name, element->index, element->type,
			group->name, group->index);
	return -ENOENT;
}

static int snd_mixer_switch_read(snd_kmixer_t * mixer,
				 snd_switch_t * _uswitch)
{
	return snd_switch_read(&mixer->switches, mixer, _uswitch);
}

static int snd_mixer_switch_write(snd_control_t * control,
				  snd_kmixer_t * mixer,
				  snd_switch_t * _uswitch)
{
	int result;

	if (snd_control_busy(control))
		return -EBUSY;
	result = snd_switch_write(&mixer->switches, mixer, _uswitch);
	if (result > 0) {	/* change */
		snd_switch_t uswitch;

		if (copy_from_user(&uswitch, _uswitch, sizeof(uswitch)))
			return -EFAULT;
		snd_control_notify_switch_value_change(control,
						       SND_CTL_IFACE_MIXER,
						       uswitch.name,
						       0);
	}
	return result;
}

static int snd_mixer_info(snd_kmixer_t * mixer, snd_mixer_info_t * _info)
{
	snd_mixer_info_t info;

	memset(&info, 0, sizeof(info));
	info.type = mixer->card->type;
	info.attrib = mixer->attrib;
	info.elements = mixer->elements_count;
	info.groups = mixer->groups_count;
	strncpy(info.id, mixer->id, sizeof(info.id));
	strncpy(info.name, mixer->name, sizeof(info.name));
	info.switches = snd_switch_count(&mixer->switches);
	if (copy_to_user(_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_elements(snd_kmixer_t * mixer, snd_mixer_elements_t * _elements)
{
	snd_mixer_elements_t elements;
	snd_kmixer_element_t *kelement;
	int idx;

	if (copy_from_user(&elements, _elements, sizeof(elements)))
		return -EFAULT;
	if (elements.pelements) {
		if (!elements.elements_size)
			return -EINVAL;
	} else {
		if (elements.elements_size)
			return -EINVAL;
	}
	snd_mixer_lock(mixer, 0);
	elements.elements = 0;
	for (idx = 0, kelement = mixer->elements;
	     kelement && idx < elements.elements_size &&
					     idx < mixer->elements_count;
	     idx++, kelement = kelement->next) {
		snd_mixer_eid_t eid;
		
		memset(&eid, 0, sizeof(eid));
		strncpy(eid.name, kelement->name, sizeof(eid.name));
		eid.index = kelement->index;
		eid.type = kelement->type;
		if (copy_to_user(&elements.pelements[idx], &eid, sizeof(eid))) {
			snd_mixer_lock(mixer, 1);
			return -EFAULT;
		}
		elements.elements++;
	}
	elements.elements_over = mixer->elements_count - idx;
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_elements, &elements, sizeof(*_elements)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_routes(snd_kmixer_t * mixer, snd_mixer_routes_t * _routes)
{
	snd_mixer_routes_t routes;
	snd_kmixer_element_t *kelement, *kelement1;
	int idx;

	if (copy_from_user(&routes, _routes, sizeof(routes)))
		return -EFAULT;
	if (routes.proutes) {
		if (!routes.routes_size)
			return -EINVAL;
	} else {
		if (routes.routes_size)
			return -EINVAL;
	}
	snd_mixer_lock(mixer, 0);
	kelement = snd_mixer_element_find(mixer, routes.eid.name, routes.eid.index, routes.eid.type);
	if (!kelement) {
		snd_mixer_lock(mixer, 1);
		return -ENOENT;
	}
	routes.routes = 0;
	for (idx = 0; idx < routes.routes_size &&
		      idx < kelement->routes_next.count; idx++) {
		snd_mixer_eid_t eid;
		
		kelement1 = kelement->routes_next.routes[idx];
		memset(&eid, 0, sizeof(eid));
		strncpy(eid.name, kelement1->name, sizeof(eid.name));
		eid.index = kelement1->index;
		eid.type = kelement1->type;
		if (copy_to_user(&routes.proutes[idx], &eid, sizeof(eid))) {
			snd_mixer_lock(mixer, 1);
			return -EFAULT;
		}
		routes.routes++;
	}
	routes.routes_over = kelement->routes_next.count - idx;
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_routes, &routes, sizeof(*_routes)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_groups(snd_kmixer_t * mixer, snd_mixer_groups_t * _groups)
{
	snd_mixer_groups_t groups;
	snd_kmixer_group_t *kgroup;
	int idx;

	if (copy_from_user(&groups, _groups, sizeof(groups)))
		return -EFAULT;
	if (groups.pgroups) {
		if (!groups.groups_size)
			return -EINVAL;
	} else {
		if (groups.groups_size)
			return -EINVAL;
	}
	snd_mixer_lock(mixer, 0);
	groups.groups = 0;
	for (idx = 0, kgroup = mixer->groups;
	     kgroup && idx < groups.groups_size &&
					     idx < mixer->groups_count;
	     idx++, kgroup = kgroup->next) {
		snd_mixer_gid_t gid;
		
		memset(&gid, 0, sizeof(gid));
		strncpy(gid.name, kgroup->name, sizeof(gid.name));
		gid.index = kgroup->index;
		if (copy_to_user(&groups.pgroups[idx], &gid, sizeof(gid))) {
			snd_mixer_lock(mixer, 1);
			return -EFAULT;
		}
		groups.groups++;
	}
	groups.groups_over = mixer->groups_count - idx;
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_groups, &groups, sizeof(*_groups)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_group_read(snd_kmixer_file_t * mfile, snd_mixer_group_t * _group)
{
	snd_kmixer_t *mixer;
	snd_mixer_group_t group;
	snd_kmixer_group_t *kgroup;
	snd_kmixer_element_t *kelement;
	int idx;

	mixer = mfile->mixer;
	if (copy_from_user(&group, _group, sizeof(group)))
		return -EFAULT;
	if (group.pelements) {
		if (!group.elements_size)
			return -EINVAL;
	} else {
		if (group.elements_size)
			return -EINVAL;
	}
	snd_mixer_lock(mixer, 0);
	kgroup = snd_mixer_group_find(mixer, group.gid.name, group.gid.index);
	if (!kgroup) {
		snd_mixer_lock(mixer, 1);
		return -ENOENT;
	}
	group.elements = 0;
	for (idx = 0; idx < group.elements_size &&
				     idx < kgroup->elements_count; idx++) {
		snd_mixer_eid_t eid;
		
		kelement = kgroup->elements[idx];
		memset(&eid, 0, sizeof(eid));
		strncpy(eid.name, kelement->name, sizeof(eid.name));
		eid.index = kelement->index;
		eid.type = kelement->type;
		if (copy_to_user(&group.pelements[idx], &eid, sizeof(eid))) {
			snd_mixer_lock(mixer, 1);
			return -EFAULT;
		}
		group.elements++;
	}
	group.elements_over = kgroup->elements_count - idx;
	if (kgroup->control)
		kgroup->control(kgroup->private_data, mfile, 0, &group);
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_group, &group, sizeof(*_group)))
		return -EFAULT;
	return 0;
}

static int snd_mixer_group_write(snd_kmixer_file_t * mfile, snd_mixer_group_t * _group)
{
	snd_kmixer_t *mixer;
	snd_mixer_group_t group, cgroup;
	snd_kmixer_group_t *kgroup;
	int err = -EINVAL;

	mixer = mfile->mixer;
	if (copy_from_user(&group, _group, sizeof(group)))
		return -EFAULT;
	if (group.pelements) {
		if (!group.elements_size)
			return -EINVAL;
	} else {
		if (group.elements_size)
			return -EINVAL;
	}
	snd_mixer_lock(mixer, 0);
	if (snd_mixer_busy(mfile)) {
		snd_mixer_lock(mixer, 1);
		return -EBUSY;
	}
	kgroup = snd_mixer_group_find(mixer, group.gid.name, group.gid.index);
	if (!kgroup) {
		snd_mixer_lock(mixer, 1);
		return -ENOENT;
	}
	group.elements = 0;
	group.elements_over = 0;
	group.pelements = NULL;
	if (group.channels && kgroup->control) {
		memset(&cgroup, 0, sizeof(cgroup));
		kgroup->control(kgroup->private_data, mfile, 0, &cgroup);
		if (cgroup.channels != group.channels)
			goto __fail;
		mfile->ignore_group = kgroup;
		if (kgroup->control(kgroup->private_data, mfile, 1, &group) > 0)
			snd_mixer_group_value_change(mfile, kgroup, 0);
		mfile->ignore_group = NULL;
	} else if (group.channels && !kgroup->control) {
		goto __fail;
	}
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_group, &group, sizeof(*_group)))
		return -EFAULT;
	return 0;

      __fail:
	snd_mixer_lock(mixer, 1);
	return err;
}

static int snd_mixer_element_info(snd_kmixer_file_t * mfile, snd_mixer_element_info_t * _info)
{
	snd_kmixer_t *mixer;
	snd_mixer_element_info_t info;
	snd_kmixer_element_t *kelement;
	int err;

	mixer = mfile->mixer;
	if (copy_from_user(&info, _info, sizeof(info)))
		return -EFAULT;
	snd_mixer_lock(mixer, 0);
	kelement = snd_mixer_element_find(mixer, info.eid.name, info.eid.index, info.eid.type);
	if (!kelement) {
		snd_mixer_lock(mixer, 1);
		return -ENOENT;
	}
	if (!kelement->info) {
		snd_mixer_lock(mixer, 1);
		return -EIO;
	}
	err = kelement->info(kelement->private_data, mfile, &info);
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_info, &info, sizeof(*_info)))
		return -EFAULT;
	return err;
}

static int snd_mixer_element_rw(snd_kmixer_file_t * mfile, snd_mixer_element_t * _element, int w_flag)
{
	snd_kmixer_t *mixer;
	snd_mixer_element_t element;
	snd_kmixer_element_t *kelement;
	int err;

	mixer = mfile->mixer;
	if (copy_from_user(&element, _element, sizeof(element)))
		return -EFAULT;
	snd_mixer_lock(mixer, 0);
	if (w_flag && snd_mixer_busy(mfile)) {
		snd_mixer_lock(mixer, 1);
		return -EBUSY;
	}
	kelement = snd_mixer_element_find(mixer, element.eid.name, element.eid.index, element.eid.type);
	if (!kelement) {
		snd_mixer_lock(mixer, 1);
		return -ENOENT;
	}
	if (!kelement->control) {
		snd_mixer_lock(mixer, 1);
		return -EIO;
	}
	err = kelement->control(kelement->private_data, mfile,
				w_flag ? SND_MIXER_CMD_WRITE : SND_MIXER_CMD_READ,
				&element, sizeof(element));
	if (err > 0 && w_flag)
		snd_mixer_element_value_change(mfile, kelement, 0);
	snd_mixer_lock(mixer, 1);
	if (copy_to_user(_element, &element, sizeof(*_element)))
		return -EFAULT;
	return err;
}

static int snd_mixer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	snd_kmixer_file_t *mfile;
	snd_kmixer_t *mixer;

	mfile = (snd_kmixer_file_t *) file->private_data;
	if (!mfile)
		return -EIO;
	mixer = mfile->mixer;
	if (((cmd >> 8) & 0xff) == 'R') {
		switch (cmd) {
		case SND_MIXER_IOCTL_PVERSION:
			return snd_ioctl_out((long *) arg, SND_MIXER_VERSION);
		case SND_MIXER_IOCTL_INFO:
			return snd_mixer_info(mixer, (snd_mixer_info_t *) arg);
		case SND_MIXER_IOCTL_ELEMENTS:
			return snd_mixer_elements(mixer, (snd_mixer_elements_t *) arg);
		case SND_MIXER_IOCTL_ROUTES:
			return snd_mixer_routes(mixer, (snd_mixer_routes_t *) arg);
		case SND_MIXER_IOCTL_GROUPS:
			return snd_mixer_groups(mixer, (snd_mixer_groups_t *) arg);
		case SND_MIXER_IOCTL_GROUP_READ:
			return snd_mixer_group_read(mfile, (snd_mixer_group_t *) arg);
		case SND_MIXER_IOCTL_GROUP_WRITE:
			return snd_mixer_group_write(mfile, (snd_mixer_group_t *) arg);
		case SND_MIXER_IOCTL_ELEMENT_INFO:
			return snd_mixer_element_info(mfile, (snd_mixer_element_info_t *) arg);
		case SND_MIXER_IOCTL_ELEMENT_READ:
			return snd_mixer_element_rw(mfile, (snd_mixer_element_t *) arg, 0);
		case SND_MIXER_IOCTL_ELEMENT_WRITE:
			return snd_mixer_element_rw(mfile, (snd_mixer_element_t *) arg, 1);
		}
	}
	return -ENXIO;
}

static int snd_mixer_control_ioctl(snd_card_t * card, snd_control_t * control, unsigned int cmd, unsigned long arg)
{
	snd_kmixer_t *mixer;
	unsigned int tmp = card->number * SND_MINOR_MIXERS;
	int idx;

	switch (cmd) {
	case SND_CTL_IOCTL_HW_INFO:
		{
			struct snd_ctl_hw_info *ptr = (struct snd_ctl_hw_info *) arg;

			ptr->mixerdevs = 0;
			for (idx = SND_MINOR_MIXERS - 1; idx >= 0; idx--) {
				if (snd_mixers[tmp + idx]) {
					ptr->mixerdevs = idx + 1;
					break;
				}
			}
			return 0;
		}
	case SND_CTL_IOCTL_MIXER_DEVICE:
		{
			int val = snd_ioctl_in((long *) arg);
			
			if (val < 0 || val >= SND_MINOR_MIXERS)
				return -EINVAL;
			if (!snd_mixers[tmp + val])
				return -EINVAL;
			control->mixer_device = val;
			return 0;
		}
	case SND_CTL_IOCTL_MIXER_INFO:
	case SND_CTL_IOCTL_MIXER_SWITCH_LIST:
	case SND_CTL_IOCTL_MIXER_SWITCH_READ:
	case SND_CTL_IOCTL_MIXER_SWITCH_WRITE:
		mixer = snd_mixers[tmp + control->mixer_device];
		if (!mixer)
			return -ENOENT;
		switch (cmd) {
		case SND_CTL_IOCTL_MIXER_INFO:
			return snd_mixer_info(mixer, (snd_mixer_info_t *) arg);
		case SND_CTL_IOCTL_MIXER_SWITCH_LIST:
			return snd_switch_list(&mixer->switches, (snd_switch_list_t *) arg);
		case SND_CTL_IOCTL_MIXER_SWITCH_READ:
			return snd_mixer_switch_read(mixer, (snd_switch_t *) arg);
		case SND_CTL_IOCTL_MIXER_SWITCH_WRITE:
			return snd_mixer_switch_write(control, mixer, (snd_switch_t *) arg);
		}
		break;
	}
	return -EAGAIN;
}

static long snd_mixer_read(struct file *file, char *buffer, long count)
{
	unsigned long flags;
	snd_kmixer_file_t *mfile;
	snd_kmixer_read_t *mread;
	snd_mixer_read_t rebuild;
	long result;

	mfile = (snd_kmixer_file_t *) file->private_data;
	if (!mfile || !mfile->mixer)
		return -ENOENT;
	spin_lock_irqsave(&mfile->read_lock, flags);
	mfile->read_active = 1;
	spin_unlock_irqrestore(&mfile->read_lock, flags);
	result = 0;
	while (count >= sizeof(snd_mixer_read_t)) {
		spin_lock_irqsave(&mfile->read_lock, flags);
		if (mfile->rebuild) {
			mfile->rebuild = 0;
			spin_unlock_irqrestore(&mfile->read_lock, &flags);
			memset(&rebuild, 0, sizeof(rebuild));
			rebuild.cmd = SND_MIXER_READ_REBUILD;
			copy_to_user(buffer, &rebuild, sizeof(rebuild));
			goto __move;
		} else {
			mread = mfile->first_item;
			if (mread) {
				mfile->first_item = mread->next;
				if (!mfile->first_item)
					mfile->last_item = NULL;
			}
		}
		spin_unlock_irqrestore(&mfile->read_lock, flags);
		if (mread) {
			copy_to_user(buffer, &mread->data, sizeof(snd_mixer_read_t));
			snd_kfree(mread);
		      __move:
			buffer += sizeof(snd_mixer_read_t);
			count -= sizeof(snd_mixer_read_t);
			result += sizeof(snd_mixer_read_t);
		} else {
			break;
		}
	}
	return result;
}

static unsigned int snd_mixer_poll(struct file *file, poll_table * wait)
{
	unsigned long flags;
	unsigned int mask;
	snd_kmixer_file_t *mfile;
	snd_kmixer_t *mixer;

	mfile = (snd_kmixer_file_t *) file->private_data;
	if (!mfile)
		return -EIO;
	mixer = mfile->mixer;

	spin_lock_irqsave(&mfile->read_lock, flags);
	mfile->read_active = 1;
	spin_unlock_irqrestore(&mfile->read_lock, flags);

	poll_wait(file, &mfile->change_sleep, wait);

	mask = 0;
	if (mfile->first_item)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

/*
 *  /proc interface
 */

static void snd_mixer_proc_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_kmixer_t *mixer;

	mixer = (snd_kmixer_t *) private_data;
	snd_iprintf(buffer, "Mixer '%s' '%s'\n", mixer->id, mixer->name);
	snd_iprintf(buffer, "Elements %i\n", mixer->elements_count);
	snd_iprintf(buffer, "Groups %i\n", mixer->groups_count);
	snd_iprintf(buffer, "Switches %i\n", snd_switch_count(&mixer->switches));
}

/*
 *  REGISTRATION PART
 */

static snd_minor_t snd_mixer_reg =
{
	"mixer",		/* comment */
	NULL,			/* reserved */

	NULL,			/* unregister */

	NULL,			/* lseek */
	snd_mixer_read,		/* read */
	NULL,			/* write */
	snd_mixer_open,		/* open */
	snd_mixer_release,	/* release */
	snd_mixer_poll,		/* poll */
	snd_mixer_ioctl,	/* ioctl */
	NULL			/* mmap */
};

snd_kmixer_t *snd_mixer_new(snd_card_t * card, char *id)
{
	snd_kmixer_t *mixer;

	mixer = (snd_kmixer_t *) snd_kcalloc(sizeof(*mixer), GFP_KERNEL);
	if (!mixer)
		return NULL;
	mixer->card = card;
	if (id) {
		strncpy(mixer->id, id, sizeof(mixer->id) - 1);
	}
	mixer->ffile_lock = SPIN_LOCK_UNLOCKED;
	init_MUTEX(&mixer->lock);
	snd_switch_prepare(&mixer->switches);
	return mixer;
}

int snd_mixer_free(snd_kmixer_t * mixer)
{
	snd_kmixer_element_t *element;
	snd_kmixer_group_t *group;

	if (!mixer)
		return -EINVAL;
	while (mixer->elements) {
		element = mixer->elements;
		mixer->elements = element->next;
		snd_mixer_element_free(element);
	}
	while (mixer->groups) {
		group = mixer->groups;
		mixer->groups = group->next;
		snd_mixer_group_free(group);
	}
	snd_switch_free(&mixer->switches);
	if (mixer->private_data && mixer->private_free)
		mixer->private_free(mixer->private_data);
	snd_kfree(mixer);
	return 0;
}

int snd_mixer_switch_add(snd_kmixer_t * mixer, snd_kswitch_t * kswitch)
{
	int err = snd_switch_add(&mixer->switches, kswitch);
	if (err >= 0)
		snd_control_notify_switch_change(mixer->card,
						 SND_CTL_READ_SWITCH_ADD,
						 SND_CTL_IFACE_MIXER,
						 kswitch->name);
	return err;
}

int snd_mixer_switch_remove(snd_kmixer_t * mixer, snd_kswitch_t * kswitch)
{
	int err;

	err = snd_switch_remove(&mixer->switches, kswitch);
	if (err >= 0)
		snd_control_notify_switch_change(mixer->card,
						 SND_CTL_READ_SWITCH_REMOVE,
						 SND_CTL_IFACE_MIXER,
						 kswitch->name);
	return err;
}

snd_kswitch_t *snd_mixer_switch_new(snd_kmixer_t * mixer, snd_kswitch_t * ksw, void *private_data)
{
	snd_kswitch_t *sw;
	
	sw = snd_switch_new(ksw);
	if (!sw)
		return NULL;
	if (snd_mixer_switch_add(mixer, sw) < 0) {
		snd_switch_free_one(sw);
		return NULL;
	}
	sw->private_data = private_data;
	return sw;
}

int snd_mixer_switch_change(snd_kmixer_t * mixer, snd_kswitch_t * kswitch)
{
	if (!mixer || !kswitch)
		return -EINVAL;
	snd_control_notify_switch_change(mixer->card,
					 SND_CTL_READ_SWITCH_CHANGE,
					 SND_CTL_IFACE_MIXER,
					 kswitch->name);
	return 0;
}

static void snd_mixer_store(snd_kmixer_t * mixer)
{
	int size;
	long err;
	unsigned int pos;
	char *buffer;
	snd_kswitch_list_t *lswitch;
	char key[32];
	
	lswitch = &mixer->switches;
	snd_switch_lock(lswitch, 0);
	size = sizeof(mixer->id) + sizeof(mixer->name) +
	       snd_switch_size(lswitch);
	buffer = (char *) snd_vmalloc(size);
	if (!buffer) {
		snd_printk("cannot store mixer settings - no enough memory\n" );
		snd_switch_lock(lswitch, 1);
		return;
	}
	pos = 0;
	memcpy(buffer + pos, mixer->id, sizeof(mixer->id));
	pos += sizeof(mixer->id);
	memcpy(buffer + pos, mixer->name, sizeof(mixer->name));
	pos += sizeof(mixer->name);
	err = snd_switch_store(lswitch, mixer, buffer + pos, size - pos);
	snd_switch_lock(lswitch, 1);
	if (err < 0) {
		snd_printd("cannot store mixer switch settings\n");
		snd_vfree(buffer);
		return;
	}
	sprintf(key, "snd:%i/mixer:%i", mixer->card->number, mixer->device);
	snd_persist_store(key, buffer, size);
	snd_vfree(buffer);
}

static void snd_mixer_restore(snd_kmixer_t * mixer)
{
	int size;
	long err;
	unsigned int pos;
	char *buffer;
	snd_kswitch_list_t *lswitch;
	char key[32];
	
	lswitch = &mixer->switches;
	sprintf(key, "snd:%i/mixer:%i", mixer->card->number, mixer->device);
	size = snd_persist_length(key);
	if (size <= 0)
		return;
	buffer = (char *) snd_vmalloc(size);
	if (!buffer) {
		snd_printk("cannot restore mixer settings: no enough memory\n" );
		return;
	}
	size = snd_persist_restore(key, buffer, size);
	/* ok. I'm very paranoid in the code below */
	if (size < (int)sizeof(unsigned int)) {
		snd_printd("cannot restore mixer settings\n");
		snd_vfree(buffer);
		return;
	}
	pos = 0;
	if (strcmp(buffer + pos, mixer->id)) {
		snd_printd("cannot restore mixer settings: discording id\n");
		snd_vfree(buffer);
		return;
	}
	pos += sizeof(mixer->id);
	if (strcmp(buffer + pos, mixer->name)) {
		snd_printd("cannot restore mixer settings: discording name\n");
		snd_vfree(buffer);
		return;
	}
	pos += sizeof(mixer->name);
	err = snd_switch_restore(lswitch, mixer, buffer + pos, size - pos);
	if (err < 0)
		snd_printd("cannot restore mixer switch settings\n");
	snd_vfree(buffer);
}

int snd_mixer_register(snd_kmixer_t * mixer, int device)
{
	char name[16];
	int idx, err, cardnum;
	snd_info_entry_t *entry;
	struct snd_stru_mixer_notify *notify;

	if (device < 0 || device > 1)
		return -EINVAL;
	if (!mixer->card)
		return -EINVAL;
	idx = ((cardnum = mixer->card->number) * SND_MINOR_MIXERS) + device;
	down(&register_mutex);
	if (snd_mixers[idx]) {
		up(&register_mutex);
		return -EBUSY;
	}
	mixer->device = device;
	snd_mixers[idx] = mixer;
	sprintf(name, "mixerC%iD%i", mixer->card->number, device);
	if ((err = snd_register_device(SND_DEVICE_TYPE_MIXER, mixer->card, device, &snd_mixer_reg, name)) < 0) {
		up(&register_mutex);
		snd_mixers[idx] = NULL;
		return err;
	}
	for (notify = snd_mixer_notify_first; notify; notify=notify->next) {
		if (notify->n_register)
			notify->n_register(SND_MINOR_MIXER + idx, mixer);
	}
	up(&register_mutex);
	sprintf(name, "mixerD%d", device);
	if ((entry = snd_info_create_entry(mixer->card, name)) != NULL) {
		entry->private_data = mixer;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 256;
		entry->t.text.read = snd_mixer_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	mixer->proc_entry = entry;
	snd_mixer_restore(mixer);
	return 0;
}

int snd_mixer_unregister(snd_kmixer_t * mixer)
{
	int idx;
	struct snd_stru_mixer_notify *notify;

	if (!mixer)
		return -EINVAL;
	if (mixer->proc_entry) {
		snd_info_unregister(mixer->proc_entry);
		mixer->proc_entry = NULL;
	}
	snd_mixer_store(mixer);
	down(&register_mutex);
	idx = (mixer->card->number * SND_MINOR_MIXERS) + mixer->device;
	if (snd_mixers[idx] != mixer) {
		up(&register_mutex);
		return -EINVAL;
	}
	snd_unregister_device(SND_DEVICE_TYPE_MIXER, mixer->card, mixer->device);
	for (notify = snd_mixer_notify_first; notify; notify=notify->next) {
		if (notify->n_unregister)
			notify->n_unregister(SND_MINOR_MIXER + idx, mixer);
	}
	snd_mixers[idx] = NULL;
	up(&register_mutex);
	return snd_mixer_free(mixer);
}

int snd_mixer_notify(struct snd_stru_mixer_notify *notify, int nfree)
{
	int idx;
	struct snd_stru_mixer_notify *tnotify;

	if (!notify || !notify->n_register || !notify->n_unregister)
		return -EINVAL;
	down(&register_mutex);
	if (nfree) {
		tnotify = snd_mixer_notify_first;
		if (tnotify == notify) {
			snd_mixer_notify_first = notify->next;
		} else {
			while (tnotify && tnotify->next != notify)
				tnotify = tnotify->next;
			if (!tnotify) {
				up(&register_mutex);
				return -ENOENT;
			}
			tnotify->next = tnotify->next->next;
		}
		for (idx = 0; idx < SND_MIXERS; idx++) {
			if (snd_mixers[idx] == NULL)
				continue;
			notify->n_unregister(idx + SND_MINOR_MIXER,
				             snd_mixers[idx]);
		}
	} else {
		notify->next = NULL;
		if (!snd_mixer_notify_first) {
			snd_mixer_notify_first = notify;
		} else {
			for (tnotify = snd_mixer_notify_first;
			     tnotify->next;
			     tnotify = tnotify->next);
			tnotify->next = notify;
		}
		for (idx = 0; idx < SND_MIXERS; idx++) {
			if (snd_mixers[idx] == NULL)
				continue;
			notify->n_register(idx + SND_MINOR_MIXER,
				           snd_mixers[idx]);
		}
	}
	up(&register_mutex);
	return 0;
}

EXPORT_SYMBOL(snd_mixers);
EXPORT_SYMBOL(snd_mixer_lock);
EXPORT_SYMBOL(snd_mixer_new);
EXPORT_SYMBOL(snd_mixer_free);
EXPORT_SYMBOL(snd_mixer_register);
EXPORT_SYMBOL(snd_mixer_unregister);
EXPORT_SYMBOL(snd_mixer_notify);
EXPORT_SYMBOL(snd_mixer_element_find);
EXPORT_SYMBOL(snd_mixer_element_add);
EXPORT_SYMBOL(snd_mixer_element_remove);
EXPORT_SYMBOL(snd_mixer_element_rename);
EXPORT_SYMBOL(snd_mixer_element_new);
EXPORT_SYMBOL(snd_mixer_element_change);
EXPORT_SYMBOL(snd_mixer_element_value_change);
EXPORT_SYMBOL(snd_mixer_element_value_change_all);
EXPORT_SYMBOL(snd_mixer_element_route_add);
EXPORT_SYMBOL(snd_mixer_element_route_remove);
EXPORT_SYMBOL(snd_mixer_group_find);
EXPORT_SYMBOL(snd_mixer_group_add);
EXPORT_SYMBOL(snd_mixer_group_remove);
EXPORT_SYMBOL(snd_mixer_group_rename);
EXPORT_SYMBOL(snd_mixer_group_new);
EXPORT_SYMBOL(snd_mixer_group_change);
EXPORT_SYMBOL(snd_mixer_group_value_change);
EXPORT_SYMBOL(snd_mixer_group_value_change_all);
EXPORT_SYMBOL(snd_mixer_group_element_add);
EXPORT_SYMBOL(snd_mixer_group_element_remove);
EXPORT_SYMBOL(snd_mixer_switch_add);
EXPORT_SYMBOL(snd_mixer_switch_remove);
EXPORT_SYMBOL(snd_mixer_switch_new);
EXPORT_SYMBOL(snd_mixer_switch_change);
  /* mixer_lib.c */
EXPORT_SYMBOL(snd_mixer_lib_group);
EXPORT_SYMBOL(snd_mixer_lib_group_ctrl);
EXPORT_SYMBOL(snd_mixer_lib_io);
EXPORT_SYMBOL(snd_mixer_lib_io_mono);
EXPORT_SYMBOL(snd_mixer_lib_io_stereo);
EXPORT_SYMBOL(snd_mixer_lib_pcm);
EXPORT_SYMBOL(snd_mixer_lib_converter);
EXPORT_SYMBOL(snd_mixer_lib_sw1);
EXPORT_SYMBOL(snd_mixer_lib_sw2);
EXPORT_SYMBOL(snd_mixer_lib_sw3);
EXPORT_SYMBOL(snd_mixer_lib_volume1);
EXPORT_SYMBOL(snd_mixer_lib_accu1);
EXPORT_SYMBOL(snd_mixer_lib_accu2);
EXPORT_SYMBOL(snd_mixer_lib_accu3);
EXPORT_SYMBOL(snd_mixer_lib_mux1);
EXPORT_SYMBOL(snd_mixer_lib_mux2);
EXPORT_SYMBOL(snd_mixer_lib_tone_control1);
EXPORT_SYMBOL(snd_mixer_lib_3d_effect1);

/*
 *  INIT PART
 */

int init_module(void)
{
	snd_control_register_ioctl(snd_mixer_control_ioctl);
	return 0;
}

void cleanup_module(void)
{
	snd_control_unregister_ioctl(snd_mixer_control_ioctl);
}
