/*
 *  Abstract routines for MIXER control
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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_OSS_COMPAT__
#include "driver.h"
#include "info.h"
#include "mixer.h"
#include "control.h"
#include "sndpersist.h"

#define SND_MIXERS		(SND_CARDS * SND_MIXER_DEVICES)

#ifdef __QNXNTO__               /* this is not C */
static snd_kmixer_t *snd_mixers[SND_MIXERS] = {[0 ... (SND_MIXERS - 1)] = NULL};
#else
static snd_kmixer_t *snd_mixers[SND_MIXERS];
#endif

snd_kmixer_channel_t *snd_mixer_oss_channel(snd_kmixer_t * mixer, int device)
{
	int idx;
	snd_kmixer_channel_t *channel;

	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.ossdev == device)
			return channel;
	}
	return NULL;
}

static snd_open_t snd_mixer_open;
static int snd_mixer_open(void *data, snd_ocb_t *ocb)
{
	snd_kmixer_file_t *mfile, *mfile1;
	snd_kmixer_t *mixer = (snd_kmixer_t *)data;

	if (!mixer)
		return -ENODEV;
	mfile = (snd_kmixer_file_t *) snd_calloc(sizeof(snd_kmixer_file_t));
	if (!mfile)
		return -ENOMEM;

	mfile->osscompat = 1;

	mfile->mixer = mixer;
	mfile->ocb = ocb;
	if (!mfile->mixer) {
		snd_free(mfile, sizeof(snd_kmixer_file_t));
		return -ENOMEM;
	}
	ocb->private_data = mfile;
	snd_mutex_lock(mixer, ffile);
	if (mixer->ffile) {
		for (mfile1 = mixer->ffile;
		     mfile1->next;
		     mfile1 = mfile1->next);
		mfile1->next = mfile;
	} else {
		mixer->ffile = mfile;
	}
	snd_mutex_unlock(mixer, ffile);
	return -EOK;
}

static snd_close_t snd_mixer_close;
static int snd_mixer_close(snd_ocb_t *ocb)
{
	snd_kmixer_file_t *mfile, *mfile1;
	snd_kmixer_t *mixer;

	if (ocb->private_data) {
		mfile = (snd_kmixer_file_t *) ocb->private_data;
		mixer = mfile->mixer;
		snd_mutex_lock(mixer, ffile);
		if (mixer->ffile == mfile) {
			mixer->ffile = mfile->next;
		} else {
			for (mfile1 = mixer->ffile;
			     mfile1->next != mfile;
			     mfile1 = mfile1->next);
			mfile1->next = mfile->next;
		}
		snd_mutex_unlock(mixer, ffile);
		snd_free(ocb->private_data, sizeof(snd_kmixer_file_t));
		ocb->private_data = NULL;
	}
	return 0;
}

void snd_mixer_set_kernel_mute(snd_kmixer_t * mixer,
			       unsigned int priority,
                               unsigned int type,
                               unsigned short mute)
{
	int mask, left, right;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_direction *direction;
	unsigned int caps;
	int mutectrl;
	int vmin;

	channel = snd_mixer_find_channel(mixer, priority);
	if (!channel)
		return;
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return;
		direction = &channel->output;
		caps = channel->hw.ocaps;
		vmin = channel->hw.omin;
		mutectrl = SND_MIX_CTRL_MUTE_OUT;
		break;
	case SND_MIX_VOL_INPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return;
		direction = &channel->input;
		caps = channel->hw.icaps;
		vmin = channel->hw.imin;
		mutectrl = SND_MIX_CTRL_MUTE_IN;
		break;
	default:
		return;
	}
	if (!(caps & SND_MIXER_CINFO_DCAP_STEREO) ||
	    (caps & SND_MIXER_CINFO_DCAP_JOINMUTE))
		mute = mute & SND_MIX_MUTE ? SND_MIX_MUTE : 0;
//	if (direction->kmute == mute) return;	/* no change */
	mask = direction->kmute = mute;
	mask |= direction->umute;
	if (mask == direction->mute) return;	/* no change */
	direction->mute = mask;
	if (caps & SND_MIXER_CINFO_DCAP_HWMUTE) {
		channel->hw.set_control(mixer, channel, mutectrl, mask);
	} else {
		left = mask & SND_MIX_MUTE_LEFT ? vmin : direction->uleft;
		right = mask & SND_MIX_MUTE_RIGHT ? vmin : direction->uright;
		if (direction->left != left || direction->right != right) {
			direction->left = left;
			direction->right = right;
			if (caps & SND_MIXER_CINFO_DCAP_VOLUME)
				channel->hw.set_volume_level(mixer, channel,
							     type, left, right);
		}
	}
	return;
}

static void snd_mixer_notify_change(snd_kmixer_file_t * mfile, 
				    unsigned int what,
				    unsigned int idx, int force)
{
	snd_kmixer_file_t *mfile1;
	snd_kmixer_t *mixer;

	mixer = mfile->mixer;
#if 0
	printk("notify change: %i 0x%x (%i)\n", what, (int) (1 << switchidx), (int) switchidx);
#endif
	idx = 1 << idx;
	for (mfile1 = mixer->ffile; mfile1; mfile1 = mfile1->next) {
		if (force || mfile1 != mfile) {
			volatile unsigned int * changes;
			switch (what) {
			case SND_MIXER_CHANGED:
			default:
				changes = &mfile1->changes;
				break;
			case SND_MIXER_OUTPUT_CHANGED:
				changes = &mfile1->ochanges;
				break;
			case SND_MIXER_INPUT_CHANGED:
				changes = &mfile1->ichanges;
				break;
			case SND_MIXER_SWITCH_CHANGED:
				changes = &mfile1->schanges;
				break;
			}
			snd_mutex_lock(mixer, lock);
			if (*changes & idx) {
				snd_mutex_unlock(mixer, lock);
				continue;
			}
			*changes |= idx;
			snd_mutex_unlock(mixer, lock);
			snd_notify_trigger(mfile1->ocb, 1, NOTIFY_INPUT);
		}
	}
}

/* Convert val (range omin .. omax) in a new val (range nmin .. nmax)
   Used for default conversion from linear volume to dB volume and back */
static int change_range(int val, int omin, int omax, int nmin, int nmax)
{
	int orange, nrange;

	orange = omax - omin;
	if (orange == 0)
		return 0;
	nrange = nmax - nmin;
	return ((nrange * (val - omin)) + (orange / 2)) / orange + nmin;
}

static int snd_mixer_channel_readk(snd_kmixer_file_t * mfile,
				   snd_mixer_channel_t * data)
{
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	unsigned int tmp;

	mixer = mfile->mixer;
	tmp = data->channel;
	memset(data, 0, sizeof(*data));
	data->channel = tmp;
	if (!mfile->osscompat) {
		if (tmp >= mixer->channels_visible)
			return -ENODEV;
	}
#ifdef SNDOSS_EMUL
	  else {
		if (tmp >= mixer->channels_count)
			return -ENODEV;
	}
#endif
	channel = mixer->channels[tmp];

	data->flags = 0;

	/* Nothing to do currently */

	return 0;
}

static int snd_mixer_channel_read(snd_kmixer_file_t * mfile,
				  snd_mixer_channel_t * _data)
{
	int err;
	snd_mixer_channel_t data;
	memcpy(&data, _data, sizeof(data));
	err = snd_mixer_channel_readk(mfile, &data);
	if (err >= 0)
		memcpy(_data, &data, sizeof(data));
	return err;
}

static int snd_mixer_channel_writek(snd_kmixer_file_t * mfile,
				   snd_mixer_channel_t * data)
{
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	int force = 0, change = 0;

	mixer = mfile->mixer;
	if (!mfile->osscompat) {
		if (data->channel >= mixer->channels_visible)
			return -ENODEV;
	}
#ifdef SNDCFG_OSSEMUL
	  else {
		if (data->channel >= mixer->channels_count)
			return -ENODEV;
	}
#endif
	channel = mixer->channels[data->channel];

	force = (data->flags & SND_MIXER_DFLG_FORCE) != 0;

	/* Nothing to do currently */

	/* let other mixers know that something were changed */
	if (change)
		snd_mixer_notify_change(mfile, SND_MIXER_CHANGED,
					channel->channel, 0);

	return 0;
}

static int snd_mixer_channel_write(snd_kmixer_file_t * mfile,
				   snd_mixer_channel_t * _data)
{
	snd_mixer_channel_t data;
	memcpy(&data, _data, sizeof(data));
	return snd_mixer_channel_writek(mfile, &data);
}
	  
	
static int snd_mixer_channel_direction_readk(snd_kmixer_file_t * mfile,
					     snd_mixer_channel_direction_t * data,
					     unsigned int type)
{
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_direction *dir;
	unsigned int caps;
	int vmin, vmax, dbmin, dbmax;
	unsigned int tmp;

	mixer = mfile->mixer;
	tmp = data->channel;
	memset(data, 0, sizeof(*data));
	data->channel = tmp;
	if (!mfile->osscompat) {
		if (tmp >= mixer->channels_visible)
			return -ENODEV;
	}
#ifdef SNDOSS_EMUL
	  else {
		if (tmp >= mixer->channels_count)
			return -ENODEV;
	}
#endif
	channel = mixer->channels[tmp];
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return -ENOENT;
		dir = &channel->output;
		caps = channel->hw.ocaps;
		vmin = channel->hw.omin;
		vmax = channel->hw.omax;
		dbmin = channel->hw.omin_dB;
		dbmax = channel->hw.omax_dB;
		break;
	case SND_MIX_VOL_INPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return -ENOENT;
		dir = &channel->input;
		caps = channel->hw.icaps;
		vmin = channel->hw.imin;
		vmax = channel->hw.imax;
		dbmin = channel->hw.imin_dB;
		dbmax = channel->hw.imax_dB;
		break;
	default:
		return -EINVAL;
	}

	snd_mutex_lock(mixer, lock);
	data->flags = 0;
	if (dir->umute & SND_MIX_MUTE_LEFT)
		data->flags |= SND_MIXER_DFLG_MUTE_LEFT;
	if (dir->umute & SND_MIX_MUTE_RIGHT)
		data->flags |= SND_MIXER_DFLG_MUTE_RIGHT;
	if (dir->route & SND_MIX_ROUTE_LTOR)
		data->flags |= SND_MIXER_DFLG_LTOR;
	if (dir->route & SND_MIX_ROUTE_RTOL)
		data->flags |= SND_MIXER_DFLG_RTOL;

	if (caps & SND_MIXER_CINFO_DCAP_VOLUME) {
		if (mfile->exact) {
			data->left = dir->uleft;
			data->right = dir->uright;
		} else {
			data->volume = dir->volume;
			data->balance = dir->balance;
		}
		data->left_dB = change_range(dir->uleft, vmin, vmax, dbmin, dbmax);
		data->right_dB = change_range(dir->uright, vmin, vmax, dbmin, dbmax);
	}
	snd_mutex_unlock(mixer, lock);
	return 0;
}

static int snd_mixer_channel_direction_read(snd_kmixer_file_t * mfile,
					    snd_mixer_channel_direction_t * _data,
					    unsigned int type)
{
	int err;
	snd_mixer_channel_direction_t data;
	memcpy(&data, _data, sizeof(data));
	err = snd_mixer_channel_direction_readk(mfile, &data, type);
	if (err >= 0)
		memcpy(_data, &data, sizeof(data));
	return err;
}

static int snd_mixer_channel_direction_writek(snd_kmixer_file_t * mfile,
					     snd_mixer_channel_direction_t * data,
					     unsigned int type)
{
	unsigned int mask, tmp;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_direction *dir;
	int force = 0, change = 0, aleft, aright, left, right, vol, bal;
	unsigned int caps;
	int vmin, vmax, dbmin, dbmax;
	int mutectrl, routectrl;
	unsigned int changed;

	mixer = mfile->mixer;
	if (!mfile->osscompat) {
		if (data->channel >= mixer->channels_visible)
			return -ENODEV;
	}
#ifdef SNDCFG_OSSEMUL
	  else {
		if (data->channel >= mixer->channels_count)
			return -ENODEV;
	}
#endif
	channel = mixer->channels[data->channel];
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return -ENOENT;
		dir = &channel->output;
		caps = channel->hw.ocaps;
		vmin = channel->hw.omin;
		vmax = channel->hw.omax;
		dbmin = channel->hw.omin_dB;
		dbmax = channel->hw.omax_dB;
		mutectrl = SND_MIX_CTRL_MUTE_OUT;
		routectrl = SND_MIX_CTRL_ROUTE_OUT;
		changed = SND_MIXER_OUTPUT_CHANGED;
		break;
	case SND_MIX_VOL_INPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return -ENOENT;
		dir = &channel->input;
		caps = channel->hw.icaps;
		vmin = channel->hw.imin;
		vmax = channel->hw.imax;
		dbmin = channel->hw.imin_dB;
		dbmax = channel->hw.imax_dB;
		mutectrl = SND_MIX_CTRL_MUTE_IN;
		routectrl = SND_MIX_CTRL_ROUTE_IN;
		changed = SND_MIXER_INPUT_CHANGED;
		break;
	default:
		return -EINVAL;
	}

	force = (data->flags & SND_MIXER_DFLG_FORCE) != 0;
	vol = bal = left = right = 0;

	/* mute */

	mask = 0;
	if (caps & SND_MIXER_CINFO_DCAP_JOINMUTE) {
		if (data->flags & SND_MIXER_DFLG_MUTE)
			mask = SND_MIX_MUTE;
	} else {
		if (data->flags & SND_MIXER_DFLG_MUTE_LEFT)
			mask |= SND_MIX_MUTE_LEFT;
		if (data->flags & SND_MIXER_DFLG_MUTE_RIGHT)
			mask |= SND_MIX_MUTE_RIGHT;
	}
	if (dir->umute != mask) {
		if (mask != SND_MIX_MUTE) {
			int idx;
			for (idx = mixer->channels_count - 1; idx >= 0; idx--) {
				snd_kmixer_channel_t *c1;
				struct snd_stru_mixer_channel_direction *dir1;
				unsigned int caps1;
				c1 = mixer->channels[idx];
				if (c1 == channel)
					continue;
				switch (type) {
				default: /* To shut off the warning */
				case SND_MIX_VOL_OUTPUT:
					if (!(c1->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
						continue;
					caps1 = c1->hw.ocaps;
					dir1 = &c1->output;
					break;
				case SND_MIX_VOL_INPUT:
					if (!(c1->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
						continue;
					caps1 = c1->hw.icaps;
					dir1 = &c1->input;
					break;
				}
				if (!((caps & SND_MIXER_CINFO_DCAP_EXCLUSIVE) ||
				      (caps1 & SND_MIXER_CINFO_DCAP_EXCLUSIVE)))
					continue;
				if (!(caps1 & SND_MIXER_CINFO_DCAP_HWMUTE) ||
				    (!force && dir1->mute == SND_MIX_MUTE))
					continue;
				dir1->mute = dir1->umute = SND_MIX_MUTE;
				c1->hw.set_control(mixer, c1, mutectrl, SND_MIX_MUTE);
				snd_mixer_notify_change(mfile, changed,
							c1->channel, 1);
			}
			change = 1;
			dir->umute = mask;
		}
		else if (caps & SND_MIXER_CINFO_DCAP_NOMUTEALL) {
			int idx;
			int someunmuted = 0;
			for (idx = mixer->channels_count - 1; idx >= 0; idx--) {
				snd_kmixer_channel_t *c1;
				struct snd_stru_mixer_channel_direction *dir1;
				unsigned int caps1;
				c1 = mixer->channels[idx];
				if (c1 == channel)
					continue;
				switch (type) {
				default: /* To shut off the warning */
				case SND_MIX_VOL_OUTPUT:
					if (!(c1->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
						continue;
					caps1 = c1->hw.ocaps;
					dir1 = &c1->input;
					break;
				case SND_MIX_VOL_INPUT:
					if (!(c1->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
						continue;
					caps1 = c1->hw.icaps;
					dir1 = &c1->input;
					break;
				}
				if (!(caps1 & SND_MIXER_CINFO_DCAP_NOMUTEALL))
					continue;
				if (dir1->mute != SND_MIX_MUTE) {
					someunmuted = 1; 
					break;
				}
			}
			if (someunmuted) {
				change = 1;
				dir->umute = mask;
			}
		}
		else {
			change = 1;
			dir->umute = mask;
		}
	}

	/* route */

	mask = 0;
	if (caps & SND_MIXER_CINFO_DCAP_ROUTE) {
		    if (!(caps & SND_MIXER_CINFO_DCAP_SWAPROUTE)) {
			    if (data->flags & SND_MIXER_DFLG_LTOR)
				    mask |= SND_MIX_ROUTE_LTOR;
			    if (data->flags & SND_MIXER_DFLG_RTOL)
				    mask |= SND_MIX_ROUTE_RTOL;
		    } else {
			    if (data->flags & SND_MIXER_DFLG_SWAP)
				    mask = SND_MIX_ROUTE_SWAP;
		    }
		    if (force || dir->route != mask) {
			    change = 1;
			    dir->route = mask;
			    channel->hw.set_control(mixer, channel, routectrl, mask);
		    }
	}

	/* volume */

	if (caps & SND_MIXER_CINFO_DCAP_VOLUME) {
		if (mfile->exact) {
			left = data->left;
			right = data->right;
		} else if (!(data->flags & SND_MIXER_DFLG_DECIBEL)) {
			vol = data->volume;
			bal = data->balance;
		} else {
			left = data->left_dB;
			right = data->right_dB;
			if (left < dbmin)
				left = dbmin;
			if (right < dbmin)
				right = dbmin;
			if (left > dbmax)
				left = dbmax;
			if (right > dbmax)
				right = dbmax;
			left = change_range(left, dbmin, dbmax, vmin, vmax);
			right = change_range(right, dbmin, dbmax, vmin, vmax);
		}
		
		if (mfile->exact || (data->flags & SND_MIXER_DFLG_DECIBEL)) {
			if (!(caps & SND_MIXER_CINFO_DCAP_STEREO))
				right = left = (left + right) / 2;
			if (left < vmin)
				left = vmin;
			if (right < vmin)
				right = vmin;
			if (left > vmax)
				left = vmax;
			if (right > vmax)
				right = vmax;
			aleft = change_range(left, vmin, vmax, 0, 100);
			aright = change_range(left, vmin, vmax, 0, 100);
			vol = max(aleft, aright);
			bal = (aright - aleft);
		} else {
			if (!(caps & SND_MIXER_CINFO_DCAP_STEREO))
				bal = 0;
			if (vol < 0)
				vol = 0;
			if (bal < -100)
				bal = -100;
			if (vol > 100)
				vol = 100;
			if (bal > 100)
				bal = 100;
			tmp = vmax - vmin;
			left = ((tmp * (vol - ((bal > 0)?(vol * bal) / 100:0))) + 50) / 100 + vmin;
			right = ((tmp * (vol + ((bal < 0)?(vol * bal) / 100:0))) + 50) / 100 + vmin;
		}
		if (dir->volume != vol || dir->balance != bal) {
			change = 1;
			dir->volume = vol;
			dir->balance = bal;
		}
		if (dir->uleft != left || dir->uright != right) {
			change = 1;
			dir->uleft = left;
			dir->uright = right;
		}
	}

	/* make changes */
	if (caps & SND_MIXER_CINFO_DCAP_HWMUTE) {
		mask = dir->umute | dir->kmute;
		if (force || dir->mute != mask) {
			dir->mute = mask;
			channel->hw.set_control(mixer, channel, mutectrl, mask);
		}
		change = 1;
	}
	else {
		if (dir->umute & SND_MIX_MUTE_LEFT)
			left = vmin;
		if (dir->umute & SND_MIX_MUTE_RIGHT)
			right = vmin;
	}

	if (caps & SND_MIXER_CINFO_DCAP_VOLUME) {
		if (force || dir->left != left || dir->right != right) {
			dir->left = left;
			dir->right = right;
			channel->hw.set_volume_level(mixer, channel,
						     type, left, right);
			change = 1;
		}
	}

	/* let other mixers know that something were changed */
	if (change)
		snd_mixer_notify_change(mfile, changed, channel->channel, 0);

	return 0;
}

static int snd_mixer_channel_direction_write(snd_kmixer_file_t * mfile,
					     snd_mixer_channel_direction_t * _data,
					     unsigned int type)
{
	snd_mixer_channel_direction_t data;
	memcpy(&data, _data, sizeof(data));
	return snd_mixer_channel_direction_writek(mfile, &data, type);
}

void snd_mixer_hardware_volume(snd_kmixer_t *mixer, unsigned int priority,
			       unsigned int flags, unsigned int type,
			       int left, int right,
			       unsigned short mute)
{
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_direction *dir, *otherdir;
	unsigned int caps, mutectrl;
	int aleft, aright;
	int vmin, vmax;
	int change = 0;
	unsigned int changed, ochanged;
	
	channel = snd_mixer_find_channel(mixer, priority);
	if (!channel) {
		snd_printk("Cannot find channel priority: %x\n", priority);
		return;
	}
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return;
		dir = &channel->output;
		otherdir = &channel->input;
		caps = channel->hw.ocaps;
		vmin = channel->hw.omin;
		vmax = channel->hw.omax;
		mutectrl = SND_MIX_CTRL_MUTE_OUT;
		changed = SND_MIXER_OUTPUT_CHANGED;
		ochanged = SND_MIXER_INPUT_CHANGED;
		break;
	case SND_MIX_VOL_INPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return;
		dir = &channel->input;
		otherdir = &channel->output;
		caps = channel->hw.icaps;
		vmin = channel->hw.imin;
		vmax = channel->hw.imax;
		mutectrl = SND_MIX_CTRL_MUTE_IN;
		changed = SND_MIXER_INPUT_CHANGED;
		ochanged = SND_MIXER_OUTPUT_CHANGED;
		break;
	default:
		return;
	}
	if ( flags & SND_MIX_HW_MATCH_OPPOSITE_CHANNEL) {
		if ( type == SND_MIX_VOL_OUTPUT && !(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return;
		if ( type == SND_MIX_VOL_INPUT && !(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return;
		
		otherdir->left    = dir->left;
		otherdir->right   = dir->right;
		otherdir->volume  = dir->volume;
		otherdir->balance = dir->balance;
		change = 1;
		changed = ochanged;
	} else {
		if (flags & SND_MIX_HW_XOR_MUTE) {
			if (mute) {
				mute ^= mute;
				if ((flags & SND_MIX_HW_JOIN_MUTE) &&
					(dir->mute & SND_MIX_MUTE))
					dir->mute |= SND_MIX_MUTE;
				change = 1;
			}
		}
		if (flags & SND_MIX_HW_ADD_VOLUME) {
			left += dir->left;
			if (left < vmin)
				left = vmin;
			if (left > vmax)
				left = vmax;
			right += dir->right;
			if (right < vmin)
				right = vmin;
			if (right > vmax)
				right = vmax;
		}

		if (dir->umute != mute) {
			dir->umute = mute;
			dir->mute = mute;
			dir->kmute = mute;
			change = 1;
		}

		if (!(dir->uleft == left && dir->uright == right)) {
			dir->uleft = left;
			dir->uright = right;
			aleft = change_range(left, vmin, vmax, 0, 100);
			aright = change_range(right, vmin, vmax, 0, 100);
			dir->volume = max(aleft, aright);
			dir->balance = (aright - aleft);
			change = 1;
		}
		if ((flags & SND_MIX_HW_CHANGE)) {
			if (caps & SND_MIXER_CINFO_DCAP_HWMUTE) {
				channel->hw.set_control(mixer, channel, mutectrl, mute);
			}
			else if ( mute & SND_MIX_MUTE ) {
				left = vmin;
				right = vmin;
			}
			if (!(dir->left == left && dir->right == right)) {
				dir->left = left;
				dir->right = right;
				if (caps & SND_MIXER_CINFO_DCAP_VOLUME) {
					channel->hw.set_volume_level(mixer, channel,
									 type, left, right);
				}
			}
		}
	}
	if (change && mixer->ffile)
		snd_mixer_notify_change(mixer->ffile, changed,
					channel->channel, 1);
}

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

static int snd_mixer_switch_write(snd_kmixer_file_t * mfile,
				  snd_switch_t * _uswitch)
{
	int err;
	if ((err =  snd_switch_write(&mfile->mixer->switches, mfile->mixer, _uswitch)) >= 0 )
		snd_mixer_notify_change(mfile, SND_MIXER_SWITCH_CHANGED, _uswitch->switchn, 0);
	return err;
}

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.channels = mixer->channels_visible;
	info.caps = mixer->hw.caps;
	strncpy(info.id, mixer->id, sizeof(info.id));
	strncpy(info.name, mixer->name, sizeof(info.name));
	info.switches = snd_switch_count(&mixer->switches);
	memcpy(_info, &info, sizeof(info));
	return sizeof (snd_mixer_info_t);
}

static int snd_mixer_channel_info(snd_kmixer_file_t * mfile,
				  snd_mixer_channel_info_t * _info)
{
	snd_kmixer_t *mixer;
	snd_mixer_channel_info_t info;
	snd_kmixer_channel_t *channel;
	int channel_idx, i;

	mixer = mfile->mixer;
	memcpy(&info, _info, sizeof(info));
	channel_idx = info.channel;
	memset(&info, 0, sizeof(info));
	info.channel = channel_idx;
	if (channel_idx >= mixer->channels_visible)
		return -ENODEV;
	channel = mixer->channels[channel_idx];
	strncpy(info.name, channel->hw.name, sizeof(info.name) - 1);
	if (channel->hw.parent_priority != SND_MIXER_PRI_PARENT) {
		for (i = 0; i < mixer->channels_visible; i++) {
			if (mixer->channels[i]->hw.priority == channel->hw.parent_priority) {
				info.parent = i;
				break;
			}
		}
		if (i >= mixer->channels_visible) {
			snd_printd("Oops... Parent 0x%x not found!!!\n", channel->hw.parent_priority);
			info.parent = SND_MIXER_PRI_PARENT;
		}
	} else {
		info.parent = SND_MIXER_PRI_PARENT;
	}
	info.caps = channel->hw.caps;
	memcpy(_info, &info, sizeof(info));
	return sizeof (snd_mixer_channel_info_t);
}

static int snd_mixer_channel_direction_info(snd_kmixer_file_t * mfile,
					    snd_mixer_channel_direction_info_t * _info,
					    unsigned int type)
{
	snd_kmixer_t *mixer;
	snd_mixer_channel_direction_info_t info;
	snd_kmixer_channel_t *channel;
	int channel_idx, i;

	mixer = mfile->mixer;
	memcpy(&info, _info, sizeof(info));
	channel_idx = info.channel;
	memset(&info, 0, sizeof(info));
	info.channel = channel_idx;
	if (channel_idx >= mixer->channels_visible)
		return -ENODEV;
	channel = mixer->channels[channel_idx];
	if (channel->hw.parent_priority != SND_MIXER_PRI_PARENT) {
		for (i = 0; i < mixer->channels_visible; i++) {
			if (mixer->channels[i]->hw.priority == channel->hw.parent_priority) {
				info.parent = i;
				break;
			}
		}
		if (i >= mixer->channels_visible) {
			snd_printd("Oops... Parent 0x%x not found!!!\n", channel->hw.parent_priority);
			info.parent = SND_MIXER_PRI_PARENT;
		}
	} else {
		info.parent = SND_MIXER_PRI_PARENT;
	}

	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
			return -ENOENT;
		if (mfile->exact) {
			info.min = channel->hw.omin;
			info.max = channel->hw.omax;
		} else {
			info.min = 0;
			info.max = 100;
		}
		info.min_dB = channel->hw.omin_dB;
		info.max_dB = channel->hw.omax_dB;
		info.caps = channel->hw.ocaps;
		break;
	case SND_MIX_VOL_INPUT:
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			return -ENOENT;
		if (mfile->exact) {
			info.min = channel->hw.imin;
			info.max = channel->hw.imax;
		} else {
			info.min = 0;
			info.max = 100;
		}
		info.min_dB = channel->hw.imin_dB;
		info.max_dB = channel->hw.imax_dB;
		info.caps = channel->hw.icaps;
		break;
	default:
		return -EINVAL;
	}

	info.caps |= SND_MIXER_CINFO_DCAP_MUTE;

	memcpy(_info, &info, sizeof(info));
	return sizeof (snd_mixer_channel_direction_info_t);
}

#ifdef SNDCFG_OSSEMUL

static int snd_mixer_recmask(snd_kmixer_t * mixer)
{
	int result, idx;
	snd_kmixer_channel_t *channel;

	result = 0;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.ossdev == SND_MIXER_OSS_UNKNOWN)
			continue;
		if (channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT)
			result |= (1 << channel->hw.ossdev);
	}
	return result;
}

static int snd_mixer_get_recsrc(snd_kmixer_t * mixer)
{
	int result, idx;
	snd_kmixer_channel_t *channel;

	result = 0;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.ossdev == SND_MIXER_OSS_UNKNOWN)
			continue;
		if (channel->input.umute != SND_MIX_MUTE)
			result |= (1 << channel->hw.ossdev);
	}
	return result;
}

static int snd_mixer_set_recsrc(snd_kmixer_file_t * mfile, int recsrc)
{
	int idx, flags, err;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	snd_mixer_channel_direction_t direction;

	mixer = mfile->mixer;

	/* Two or more record source */
	if (recsrc & (recsrc-1)) {
		for (idx = SND_MIXER_OSS_DEVS - 1; idx >= 0; idx--) {
			channel = snd_mixer_oss_channel(mixer, idx);
			if (channel == NULL)
				continue;
			if ((recsrc & (1 << idx)) &&
			    (channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT) &&
			    (channel->hw.icaps & SND_MIXER_CINFO_DCAP_EXCLUSIVE)) {
				int mask = snd_mixer_get_recsrc(mixer);
				recsrc ^= mask;
				break;
			}
		}
	}
	for (idx = SND_MIXER_OSS_DEVS - 1; idx >= 0; idx--) {
		channel = snd_mixer_oss_channel(mixer, idx);
		if (channel == NULL)
			continue;
		if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
			continue;

		flags = (recsrc & (1 << idx)) ? 0 : SND_MIXER_DFLG_MUTE;

		direction.channel = channel->channel;
		if ((err = snd_mixer_channel_direction_readk(mfile, &direction, SND_MIX_VOL_INPUT)) < 0) {
			return err;
		}
		if ((direction.flags & SND_MIXER_DFLG_MUTE) != flags) {
			direction.flags &= ~SND_MIXER_DFLG_MUTE;
			direction.flags |= flags;
			if ((err = snd_mixer_channel_direction_writek(mfile, &direction, SND_MIX_VOL_INPUT)) < 0) {
				return err;
			}
			mixer->modify_counter++;
		}
	}
	return 0;
}

static int snd_mixer_devmask(snd_kmixer_t * mixer)
{
	int result, idx, ossdev;

	result = 0;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		ossdev = mixer->channels[idx]->hw.ossdev;
		if (ossdev == SND_MIXER_OSS_UNKNOWN)
			continue;
		result |= (1 << ossdev);
	}
	return result;
}

static int snd_mixer_stereodevs(snd_kmixer_t * mixer)
{
	int result, idx;
	snd_kmixer_channel_t *channel;

	result = 0;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.ossdev == SND_MIXER_OSS_UNKNOWN)
			continue;
		if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO)
			result |= (1 << channel->hw.ossdev);
	}
	return result;
}

static int snd_mixer_osscaps(snd_kmixer_t * mixer)
{
	int result, idx;
	snd_kmixer_channel_t *channel;

	result = 0;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.icaps & SND_MIXER_CINFO_DCAP_EXCLUSIVE) {
			result |= SND_MIXER_OSS_CAP_EXCL_INPUT;
			break;
		}
	}
	return result;
}

static int snd_mixer_set_volume(snd_kmixer_file_t * mfile,
                                int oss_device, int volume)
{
	int err;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	snd_mixer_channel_direction_t direction;

	mixer = mfile->mixer;
	channel = snd_mixer_oss_channel(mixer, oss_device);
	if (!channel)
		return -ENXIO;
	direction.channel = channel->channel;
	if (channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT) {
		if ((err = snd_mixer_channel_direction_readk(mfile, &direction, SND_MIX_VOL_OUTPUT)) < 0) {
			return err;
		}
		direction.left = volume & 0xff;
		direction.right = (volume >> 8) & 0xff;
		if (direction.left) {
			direction.flags &= ~SND_MIXER_DFLG_MUTE_LEFT;
		} else {
			direction.flags |= SND_MIXER_DFLG_MUTE_LEFT;
		}
		if (direction.right) {
			direction.flags &= ~SND_MIXER_DFLG_MUTE_RIGHT;
		} else {
			direction.flags |= SND_MIXER_DFLG_MUTE_RIGHT;
		}
		if ((err = snd_mixer_channel_direction_writek(mfile, &direction, SND_MIX_VOL_OUTPUT)) < 0) {
			return err;
		}
	}
	if ((channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT) &&
	    (channel->hw.icaps & SND_MIXER_CINFO_DCAP_VOLUME)) {
		if ((err = snd_mixer_channel_direction_readk(mfile, &direction, SND_MIX_VOL_INPUT)) < 0) {
			return err;
		}
		direction.left = volume & 0xff;
		direction.right = (volume >> 8) & 0xff;
		if ((err = snd_mixer_channel_direction_writek(mfile, &direction, SND_MIX_VOL_INPUT)) < 0) {
			return err;
		}
	}
	mixer->modify_counter++;
	return (unsigned short) direction.left |
	    ((unsigned short) direction.right << 8);
}

static int snd_mixer_get_volume(snd_kmixer_file_t * mfile, int oss_device)
{
	int err;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	snd_mixer_channel_direction_t direction;
	unsigned short left, right;

	mixer = mfile->mixer;
	channel = snd_mixer_oss_channel(mixer, oss_device);
	if (!channel)
		return -ENXIO;
	direction.channel = channel->channel;
	if ((err = snd_mixer_channel_direction_readk(mfile, &direction, SND_MIX_VOL_OUTPUT)) < 0) {
		return err;
	}

	left = (direction.flags & SND_MIXER_DFLG_MUTE_LEFT) ? 0 : direction.left;
	right = (direction.flags & SND_MIXER_DFLG_MUTE_RIGHT) ? 0 : direction.right;
	return left | (right << 8);
}

#endif /* SNDCFG_OSSEMUL */

int snd_mixer_ioctl_card(snd_card_t * card, snd_ocb_t *ocb,
			 unsigned int cmd, unsigned long arg)
{
	snd_kmixer_file_t *mfile, sfile;
	snd_kmixer_t *mixer;
	int tmp;

	if (card) {
		/* note: this code finds only first mixer for card */
		for (tmp = 0; tmp < SND_MIXERS; tmp++) {
			mixer = snd_mixers[tmp];
			if (mixer && mixer->card == card)
				break;
		}
		if (!mixer)
			return -ENXIO;
		memset(&sfile, 0, sizeof(sfile));
		sfile.mixer = mixer;
		mfile = &sfile;
	} else {
		mfile = (snd_kmixer_file_t *) ocb->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_CHANNELS:
			return snd_ioctl_out((long *) arg, mixer->channels_visible);
		case SND_MIXER_IOCTL_INFO:
			return snd_mixer_info(mixer, (snd_mixer_info_t *) arg);
		case SND_MIXER_IOCTL_EXACT:
			tmp = snd_ioctl_in((long *) arg);
			mfile->exact = tmp != 0;
			return snd_ioctl_out((long *) arg, mfile->exact);
		case SND_MIXER_IOCTL_CHANNEL_INFO:
			return snd_mixer_channel_info(mfile, (snd_mixer_channel_info_t *) arg);
		case SND_MIXER_IOCTL_CHANNEL_READ:
			if ((tmp = snd_mixer_channel_read(mfile, (snd_mixer_channel_t *) arg)) < 0)
				return tmp;
			return sizeof (snd_mixer_channel_t);
		case SND_MIXER_IOCTL_CHANNEL_WRITE:
			if ((tmp = snd_mixer_channel_write(mfile, (snd_mixer_channel_t *) arg)) < 0)
				return tmp;
			return sizeof (snd_mixer_channel_t);
		case SND_MIXER_IOCTL_CHANNEL_OINFO:
			return snd_mixer_channel_direction_info(mfile, (snd_mixer_channel_direction_info_t *) arg, SND_MIX_VOL_OUTPUT);
		case SND_MIXER_IOCTL_CHANNEL_OREAD:
			if ((tmp = snd_mixer_channel_direction_read(mfile, (snd_mixer_channel_direction_t *) arg, SND_MIX_VOL_OUTPUT)) < 0)
				return tmp;
			return sizeof (snd_mixer_channel_direction_t);
		case SND_MIXER_IOCTL_CHANNEL_OWRITE:
			if ((tmp = snd_mixer_channel_direction_write(mfile, (snd_mixer_channel_direction_t *) arg, SND_MIX_VOL_OUTPUT)) <0)
				return tmp;
			return sizeof (snd_mixer_channel_direction_t);
		case SND_MIXER_IOCTL_CHANNEL_IINFO:
			return snd_mixer_channel_direction_info(mfile, (snd_mixer_channel_direction_info_t *) arg, SND_MIX_VOL_INPUT);
		case SND_MIXER_IOCTL_CHANNEL_IREAD:
			if ((tmp = snd_mixer_channel_direction_read(mfile, (snd_mixer_channel_direction_t *) arg, SND_MIX_VOL_INPUT)) < 0)
				return tmp;
			return sizeof (snd_mixer_channel_direction_t);
		case SND_MIXER_IOCTL_CHANNEL_IWRITE:
			if ((tmp = snd_mixer_channel_direction_write(mfile, (snd_mixer_channel_direction_t *) arg, SND_MIX_VOL_INPUT)) < 0)
				return tmp;
			return sizeof (snd_mixer_channel_direction_t);
		case SND_MIXER_IOCTL_SWITCHES:
			*(long*)arg = snd_switch_count(&mfile->mixer->switches);
			return sizeof (long);
		case SND_MIXER_IOCTL_SWITCH_READ:
			if ((tmp = snd_mixer_switch_read(mfile, (snd_switch_t *) arg)) < 0)
				return tmp;
			return sizeof (snd_switch_t);
		case SND_MIXER_IOCTL_SWITCH_WRITE:
			if ((tmp = snd_mixer_switch_write(mfile, (snd_switch_t *) arg)) < 0)
				return tmp;
			return sizeof (snd_switch_t);
		}
		return -ENXIO;
	}
#ifdef SNDCFG_OSSEMUL
	if (((cmd >> 8) & 0xff) != 'M')
		return -ENXIO;
#if 0
	snd_printk(">> mixer ioctl - cmd = 0x%x\n", cmd);
#endif
	if (cmd == SND_MIXER_OSS_INFO) {
		struct snd_oss_mixer_info info;

		memset(&info, 0, sizeof(info));
		strncpy(info.id, mixer->id, sizeof(info.id) - 1);
		strncpy(info.name, mixer->name, sizeof(info.name) - 1);
		info.modify_counter = mixer->modify_counter;
		if (verify_area(VERIFY_WRITE, (void *) arg, sizeof(snd_mixer_info)))
			return -EFAULT;
		copy_to_user((void *) arg, &snd_mixer_info, sizeof(snd_mixer_info));
		return 0;
	}
	if (cmd == SND_MIXER_OSS_OLD_INFO) {
		struct snd_oss_mixer_info_obsolete info;

		memset(&info, 0, sizeof(info));
		strncpy(info.id, mixer->id, sizeof(info.id) - 1);
		strncpy(info.name, mixer->name, sizeof(info.name) - 1);
		if (verify_area(VERIFY_WRITE, (void *) arg, sizeof(snd_mixer_info)))
			return -EFAULT;
		copy_to_user((void *) arg, &snd_mixer_info, sizeof(snd_mixer_info));
		return 0;
	}
	if (cmd & IOC_IN)
		switch (cmd) {
		case SND_MIXER_OSS_SET_RECSRC:
			return snd_ioctl_out((long *) arg, snd_mixer_set_recsrc(mfile, snd_ioctl_in((long *) arg)));
		default:
			tmp = snd_mixer_set_volume(mfile, cmd & 0xff, snd_ioctl_in((long *) arg));
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
	} else if (cmd & IOC_OUT)
		switch (cmd) {
		case SND_OSS_GETVERSION:
			return snd_ioctl_out((long *) arg, SND_OSS_VERSION);
		case SND_MIXER_OSS_DEVMASK:
			return snd_ioctl_out((long *) arg, snd_mixer_devmask(mixer));
		case SND_MIXER_OSS_STEREODEVS:
			return snd_ioctl_out((long *) arg, snd_mixer_stereodevs(mixer));
		case SND_MIXER_OSS_RECMASK:
			return snd_ioctl_out((long *) arg, snd_mixer_recmask(mixer));
		case SND_MIXER_OSS_CAPS:
			return snd_ioctl_out((long *) arg, snd_mixer_osscaps(mixer));
		case SND_MIXER_OSS_RECSRC:
			return snd_ioctl_out((long *) arg, snd_mixer_get_recsrc(mixer));
		default:
			tmp = snd_mixer_get_volume(mfile, cmd & 0xff);
			return tmp >= 0 ? snd_ioctl_out((long *) arg, tmp) : tmp;
		}
#ifdef SNDCFG_DEBUG
	snd_printk("MIXER ERR\n");
	snd_printk("  MIXER REQUEST: 0x%x, 0x%x\n", cmd, (int) snd_ioctl_in((long *) arg));
#endif
#endif /* SNDCFG_OSSEMUL */
	return -ENOSYS;
}

static int snd_mixer_control_ioctl(snd_card_t * card, snd_control_t * control, unsigned int cmd, unsigned long arg)
{
	snd_kmixer_t *mixer;
	snd_kmixer_file_t sfile;
	unsigned int tmp = card->number * SND_MIXER_DEVICES;
	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_MIXER_DEVICES - 1; idx >= 0; idx--) {
				if (snd_mixers[tmp + idx]) {
					ptr->mixerdevs = idx + 1;
					break;
				}
			}
			return sizeof (struct snd_ctl_hw_info);
		}
	case SND_CTL_IOCTL_MIXER_DEVICE:
		{
			int val = snd_ioctl_in((long *) arg);
			
			if (val < 0 || val >= SND_MIXER_DEVICES)
				return -EINVAL;
			if (!snd_mixers[tmp + val])
				return -EINVAL;
			control->mixer_device = val;
			return sizeof(long);
		}
	case SND_CTL_IOCTL_MIXER_INFO:
	case SND_CTL_IOCTL_MIXER_SWITCHES:
	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_SWITCHES:
			*(long*) arg = snd_switch_count(&mixer->switches);
			return sizeof(long);
		case SND_CTL_IOCTL_MIXER_SWITCH_READ:
			memset(&sfile, 0, sizeof(sfile));
			sfile.mixer = mixer;
			if ((tmp = snd_mixer_switch_read(&sfile, (snd_switch_t *) arg)) < 0)
				return tmp;
			return sizeof( snd_switch_t );
		case SND_CTL_IOCTL_MIXER_SWITCH_WRITE:
			memset(&sfile, 0, sizeof(sfile));
			sfile.mixer = mixer;
			return snd_mixer_switch_write(&sfile, (snd_switch_t *) arg);
		}
		break;
	}
	return -EAGAIN;
}

static int snd_mixer_ioctl(snd_ocb_t *ocb,
			   unsigned int cmd,
			   unsigned long arg, int len)
{
	return snd_mixer_ioctl_card(NULL, ocb, cmd, arg);
}

static long snd_mixer_read(snd_context_t *ctp, long count)
{
	unsigned int device;
	unsigned char buf[8];
	snd_kmixer_file_t *mfile;
	snd_kmixer_t *mixer;
	long size = 0;

	mfile = (snd_kmixer_file_t *) ctp->ocb->private_data;
	if (!mfile)
		return -ENOENT;
	mixer = mfile->mixer;
	if (!mixer)
		return -ENOENT;
	if (count < 8)
		return 0;
	memset(buf, 0, sizeof(buf));
	for (device = 0; device < mfile->mixer->channels_visible; device++) {
		*(unsigned int *) &buf[0] = SND_MIXER_CHANGED;	/* general */
		snd_mutex_lock(mixer, lock);
		if (mfile->changes & (1 << device)) {
			mfile->changes &= ~(1 << device);
			snd_mutex_unlock(mixer, lock);
			*(unsigned int *) &buf[4] = device;
			snd_copy_to_user(ctp, buf, 8);
			size += 8;
			count -= 8;
			if (count < 8)
				return size;
		} else {
			snd_mutex_unlock(mixer, lock);
		}
		*(unsigned int *) &buf[0] = SND_MIXER_OUTPUT_CHANGED;	/* output */
		snd_mutex_lock(mixer, lock);
		if (mfile->ochanges & (1 << device)) {
			mfile->ochanges &= ~(1 << device);
			snd_mutex_unlock(mixer, lock);
			*(unsigned int *) &buf[4] = device;
			snd_copy_to_user(ctp, buf, 8);
			size += 8;
			count -= 8;
			if (count < 8)
				return size;
		} else {
			snd_mutex_unlock(mixer, lock);
		}
		*(unsigned int *) &buf[0] = SND_MIXER_INPUT_CHANGED;	/* input */
		snd_mutex_lock(mixer, lock);
		if (mfile->ichanges & (1 << device)) {
			mfile->ichanges &= ~(1 << device);
			snd_mutex_unlock(mixer, lock);
			*(unsigned int *) &buf[4] = device;
			snd_copy_to_user(ctp, buf, 8);
			size += 8;
			count -= 8;
			if (count < 8)
				return size;
		} else {
			snd_mutex_unlock(mixer, lock);
		}
	}
	*(unsigned int *) &buf[0] = SND_MIXER_SWITCH_CHANGED;	/* switch ID */
	for (device = 0; device < 32; device++) {
		snd_mutex_lock(mixer, lock);
		if (mfile->schanges & (1 << device)) {
			mfile->schanges &= ~(1 << device);
			snd_mutex_unlock(mixer, lock);
			*(unsigned int *) &buf[4] = device;
			snd_copy_to_user(ctp, buf, 8);
			size += 8;
			count -= 8;
			if (count < 8)
				return size;
		} else {
			snd_mutex_unlock(mixer, lock);
		}
	}
	return size;
}

static snd_ionotify_t snd_mixer_ionotify;
static int snd_mixer_ionotify(snd_ocb_t *ocb, int *trig)
{
	snd_kmixer_file_t *mfile;
	snd_kmixer_t *mixer;

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

	snd_mutex_lock(mixer, lock);
	if (mfile->changes || mfile->ichanges || mfile->ochanges || mfile->schanges) 
		*trig |= NOTIFY_INPUT;
	snd_mutex_unlock(mixer, lock);
	return 0;
}

/*
 *  /proc interface
 */

static void snd_mixer_proc_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	int idx, type;
	char *des;
	unsigned int caps;
	int vmin, vmax;
	struct snd_stru_mixer_channel_direction *direction;

	mixer = (snd_kmixer_t *) private_data;
	snd_iprintf(buffer, "Mixer '%s' '%s'\n", mixer->id, mixer->name);
	for (idx = 0; idx < mixer->channels_visible; idx++) {
		channel = mixer->channels[idx];
		if (!channel)
			continue;
		snd_iprintf(buffer, "Channel '%s'\n", channel->hw.name);
		if (channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT) {
			for (type = SND_MIX_VOL_OUTPUT; type <= SND_MIX_VOL_INPUT; ++type) {
				switch (type) {
				case SND_MIX_VOL_OUTPUT:
					if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT))
						continue;
					des="Output";
					direction = &channel->output;
					caps = channel->hw.ocaps;
					vmin = channel->hw.omin;
					vmax = channel->hw.omax;
					break;
				case SND_MIX_VOL_INPUT:
					if (!(channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT))
						continue;
					des="Input";
					direction = &channel->input;
					caps = channel->hw.icaps;
					vmin = channel->hw.imin;
					vmax = channel->hw.imax;
					break;
				default:
					continue;
				}
				snd_iprintf(buffer, "  %s", des);
				if (caps & SND_MIXER_CINFO_DCAP_VOLUME) 
					snd_iprintf(buffer, " %d %d %d %d",
						    vmin,
						    vmax,
						    direction->uleft,
						    direction->uright);
				snd_iprintf(buffer, " %s%s%s%s\n",
					    direction->umute & SND_MIX_MUTE_LEFT ? 
					    " lmute" : "",
					    direction->umute & SND_MIX_MUTE_RIGHT ?
					    " rmute" : "",
					    direction->route & SND_MIX_ROUTE_LTOR ?
					    " ltor" : "",
					    direction->route & SND_MIX_ROUTE_RTOL ?
					    " rtol" : "");
			}
		}
		
	}
}

/*
 *  REGISTRATION PART
 */

static snd_io_t snd_mixer_io =
{
	"mixer",

	snd_mixer_read,		/* read */
	NULL,			/* write */
	snd_mixer_open,		/* open */
	snd_mixer_close,	/* close */
	snd_mixer_ionotify,	/* notify */
	snd_mixer_ioctl,	/* ioctl */
};

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

	mixer = (snd_kmixer_t *) snd_calloc(sizeof(*mixer));
	if (!mixer)
		return NULL;
	mixer->card = card;
	if (id) {
		strncpy(mixer->id, id, sizeof(mixer->id) - 1);
	}
	snd_mutex_prepare(mixer, ffile);
	snd_mutex_prepare(mixer, lock);
	snd_switch_prepare(&mixer->switches);
	return mixer;
}

int snd_mixer_free(snd_kmixer_t * mixer)
{
	int idx;
	snd_kmixer_channel_t *channel;

	if (!mixer)
		return -EINVAL;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		snd_free(channel, sizeof(*channel));
	}
	snd_switch_free(&mixer->switches);
	if (mixer->private_data && mixer->private_free)
		mixer->private_free(mixer->private_data);
	snd_mutex_destroy(mixer, ffile);
	snd_mutex_destroy(mixer, lock);
	snd_free(mixer, sizeof(*mixer));
	return 0;
}

snd_kmixer_channel_t *snd_mixer_new_channel(snd_kmixer_t * mixer,
					    struct snd_stru_mixer_channel_hw * hw)
{
	int idx, idx1, priority;
	snd_kmixer_channel_t *channel;
	int err = 0;

	snd_mutex_lock(mixer, ffile);
	if (!mixer || mixer->channels_count >= SND_MIXER_CHANNELS) {
		snd_mutex_unlock(mixer, ffile);
		return NULL;
	}
#if 1
	if ((hw->caps & SND_MIXER_CINFO_CAP_OUTPUT) &&
	    !(hw->ocaps & (SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE))) {
		snd_printk("SND_MIXER_CINFO_CAP_OUTPUT discording for %s\n", hw->name);
		err = 1;
	}
	if ((hw->caps & SND_MIXER_CINFO_CAP_INPUT) &&
	    !(hw->icaps & (SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE))) {
		snd_printk("SND_MIXER_CINFO_CAP_INPUT discording for %s\n", hw->name);
		err = 1;
	}
	if (!(hw->caps & SND_MIXER_CINFO_CAP_OUTPUT) &&
	    (hw->ocaps || hw->omin || hw->omax || hw->omin_dB || hw->omax_dB)) {
		snd_printk("SND_MIXER_CINFO_CAP_OUTPUT discording for %s\n", hw->name);
		err = 1;
	}
	if (!(hw->caps & SND_MIXER_CINFO_CAP_INPUT) &&
	    (hw->icaps || hw->imin || hw->imax || hw->imin_dB || hw->imax_dB)) {
		snd_printk("SND_MIXER_CINFO_CAP_INPUT discording for %s\n", hw->name);
		err = 1;
	}
	if (!!((hw->ocaps | hw->icaps) & SND_MIXER_CINFO_DCAP_VOLUME) !=
	    (hw->set_volume_level != NULL)) {
		snd_printk("set_volume_level discording for %s\n", hw->name);
		err = 1;
	}
	if (!!((hw->ocaps | hw->icaps) & 
	       (SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_ROUTE)) !=
	    (hw->set_control != NULL)) {
		snd_printk("set_control discording for %s\n", hw->name);
		err = 1;
	}
	if (!!(hw->ocaps & SND_MIXER_CINFO_DCAP_VOLUME) !=
	    (hw->omin || hw->omax)) {
		snd_printk("SND_MIXER_CINFO_DCAP_VOLUME discording for %s (output)\n", hw->name);
		err = 1;
	}
	if (!!(hw->icaps & SND_MIXER_CINFO_DCAP_VOLUME) !=
	    (hw->imin || hw->imax)) {
		snd_printk("SND_MIXER_CINFO_DCAP_VOLUME discording for %s (input)\n", hw->name);
		err = 1;
	}
	if (!(hw->ocaps & SND_MIXER_CINFO_DCAP_STEREO) &&
	    (hw->ocaps & (SND_MIXER_CINFO_DCAP_JOINMUTE | SND_MIXER_CINFO_DCAP_ROUTE))) {
		snd_printk("SND_MIXER_CINFO_DCAP_STEREO discording for %s (output)\n", hw->name);
		err = 1;
	}
	if ((hw->ocaps & SND_MIXER_CINFO_DCAP_JOINMUTE) &&
	    !(hw->ocaps & SND_MIXER_CINFO_DCAP_HWMUTE)) {
		snd_printk("SND_MIXER_CINFO_DCAP_HWMUTE discording for %s (output)\n", hw->name);
		err = 1;
	}
	if ((hw->icaps & SND_MIXER_CINFO_DCAP_JOINMUTE) &&
	    !(hw->icaps & SND_MIXER_CINFO_DCAP_HWMUTE)) {
		snd_printk("SND_MIXER_CINFO_DCAP_HWMUTE discording for %s (input)\n", hw->name);
		err = 1;
	}
	if ((hw->ocaps & SND_MIXER_CINFO_DCAP_SWAPROUTE) &&
	    !(hw->ocaps & SND_MIXER_CINFO_DCAP_ROUTE)) {
		snd_printk("SND_MIXER_CINFO_DCAP_ROUTE discording for %s (output)\n", hw->name);
		err = 1;
	}
	if ((hw->icaps & SND_MIXER_CINFO_DCAP_SWAPROUTE) &&
	    !(hw->icaps & SND_MIXER_CINFO_DCAP_ROUTE)) {
		snd_printk("SND_MIXER_CINFO_DCAP_ROUTE discording for %s (input)\n", hw->name);
		err = 1;
	}

	if (err) {
		snd_mutex_unlock(mixer, ffile);
		return NULL;
	}
#endif

	channel = (snd_kmixer_channel_t *) snd_calloc(sizeof(*channel));
	if (!channel) {
		snd_mutex_unlock(mixer, ffile);
		return NULL;
	}
	memcpy(&channel->hw, hw, sizeof(*hw));
	priority = channel->hw.priority;
	for (idx = 0; idx < mixer->channels_count; idx++)
		if (mixer->channels[idx]->hw.priority > priority)
			break;
	for (idx1 = mixer->channels_count; idx1 > idx; idx1--) {
		mixer->channels[idx1] = mixer->channels[idx1 - 1];
	}
	mixer->channels_count++;
	if (channel->hw.priority != SND_MIXER_PRI_HIDDEN)
		mixer->channels_visible++;
	mixer->channels[idx] = channel;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		mixer->channels[idx]->channel = idx;
	}
	snd_mutex_unlock(mixer, ffile);
	return channel;
}

void snd_mixer_reorder_channel(snd_kmixer_t * mixer,
			       snd_kmixer_channel_t * channel)
{
	int idx, idx1;

	if (!mixer || !channel)
		return;
	snd_mutex_lock(mixer, ffile);
	for (idx = 0; idx < mixer->channels_count; idx++) {
		if (mixer->channels[idx] == channel) {
			for (idx1 = idx; idx1 + 1 < mixer->channels_count; idx1++)
				mixer->channels[idx1] = mixer->channels[idx1 + 1];
			mixer->channels_count--;
			break;
		}
	}
	for (idx = 0; idx < mixer->channels_count; idx++)
		if (mixer->channels[idx]->hw.priority > channel->hw.priority)
			break;
	for (idx1 = mixer->channels_count; idx1 > idx; idx1--) {
		mixer->channels[idx1] = mixer->channels[idx1 - 1];
	}
	mixer->channels[idx] = channel;
	mixer->channels_count++;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		mixer->channels[idx]->channel = idx;
	}
	snd_mutex_unlock(mixer, ffile);
}

int snd_mixer_switch_add(snd_kmixer_t * mixer, snd_kswitch_t * kswitch)
{
	return snd_switch_add(&mixer->switches, kswitch);
}

int snd_mixer_switch_remove(snd_kmixer_t * mixer, snd_kswitch_t * kswitch)
{
	return snd_switch_remove(&mixer->switches, kswitch);
}

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;
}

static void snd_mixer_store(snd_kmixer_t * mixer)
{
#if 0 //def __QNXNTO__
	int idx, cid;
	long err;
	snd_mixer_channel_t channel;
	snd_mixer_channel_direction_t direction;
	snd_kswitch_list_t *lswitch;
	snd_kmixer_file_t sfile;
	char key[64];
	cfgkey_t root, subroot;
	
	lswitch = &mixer->switches;
	if ((cid = cfgmgr_attach(NULL)) == -1 )
		return;
	sprintf(key,"/hardware/audio/mixer/%s/%d",mixer->name, 0);
	if ((root = cfgmgr_create(cid, CFGMGR_ROOT, key)) == -1 ) {
		snd_printd("cannot open create/mixer cfg root key\n");
		cfgmgr_detach(cid);
		return;
	}
	snd_switch_lock(lswitch, 0);
	memset(&sfile, 0, sizeof(sfile));
	sfile.mixer = mixer;
	sfile.exact = 1;
	for (idx = 0; idx < mixer->channels_count; idx++) { 
		/* channel */
		sprintf(key,"channel%d",idx);
		if ((subroot = cfgmgr_create(cid, root, key)) == -1 ) {
			snd_printd("cannot create/open mixer cfg sub key\n");
			cfgmgr_detach(cid);
			return;
		}
		memset(&channel, 0, sizeof(channel));
		channel.channel = idx;
		snd_mixer_channel_readk(&sfile, &channel);
		cfgmgr_write(cid, subroot, "", "flags", CFGMGR_INT32, &channel.flags, sizeof(channel.flags));
		/* output direction */
		memset(&direction, 0, sizeof(direction));
		direction.channel = idx;
		snd_mixer_channel_direction_readk(&sfile, &direction, SND_MIX_VOL_OUTPUT);
		cfgmgr_write(cid, subroot, "output", "flags", CFGMGR_INT32, &direction.flags, sizeof(direction.flags));
		cfgmgr_write(cid, subroot, "output", "left", CFGMGR_INT32, &direction.left, sizeof(direction.left));
		cfgmgr_write(cid, subroot, "output", "right", CFGMGR_INT32, &direction.right, sizeof(direction.right));
		/* input direction */
		memset(&direction, 0, sizeof(direction));
		direction.channel = idx;
		snd_mixer_channel_direction_readk(&sfile, &direction, SND_MIX_VOL_INPUT);
		cfgmgr_write(cid, subroot, "input", "flags", CFGMGR_INT32, &direction.flags, sizeof(direction.flags));
		cfgmgr_write(cid, subroot, "input", "left", CFGMGR_INT32, &direction.left, sizeof(direction.left));
		cfgmgr_write(cid, subroot, "input", "right", CFGMGR_INT32, &direction.right, sizeof(direction.right));
		cfgmgr_close(cid, subroot);
	}
	err = snd_switch_store(lswitch, mixer);
	snd_switch_lock(lswitch, 1);
	if (err < 0) {
		snd_printd("cannot store mixer switch settings\n");
		return;
	}
	cfgmgr_close(cid, root);
	cfgmgr_detach(cid);
#endif
}

static void snd_mixer_restore(snd_kmixer_t * mixer)
{
#if 0 //def __QNXNTO__
	int idx, len, cid, type;
	long err;
	snd_mixer_channel_t channel;
	snd_mixer_channel_direction_t direction;
	snd_kswitch_list_t *lswitch;
	snd_kmixer_file_t sfile;
	cfgkey_t root, subroot;
	char key[64];
	
	lswitch = &mixer->switches;
	if ((cid = cfgmgr_attach(NULL)) == -1 )
		return;
	sprintf(key,"/hardware/audio/mixer/%s/%d",mixer->name, 0);
	if ((root = cfgmgr_open(cid, CFGMGR_ROOT, key)) == -1 ) {
		snd_printd("cannot open create/mixer cfg root key\n");
		cfgmgr_detach(cid);
		return;
	}
	memset(&sfile, 0, sizeof(sfile));
	sfile.mixer = mixer;
	sfile.exact = 1;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		/* channel */
		sprintf(key,"channel%d",idx);
		if ((subroot = cfgmgr_open(cid, root, key)) == -1 ) {
			snd_printd("cannot create/open mixer cfg sub key\n");
			cfgmgr_detach(cid);
			return;
		}
		channel.channel = idx;
		len = sizeof(channel.flags);
		if ( cfgmgr_read(cid, subroot, "", "flags", &type, &channel.flags, &len) == 0 && type == CFGMGR_INT32 )	
			snd_mixer_channel_writek(&sfile, &channel);

		/* output direction */
		direction.channel = idx;
		len = sizeof(direction.flags);
		if ( cfgmgr_read(cid, subroot, "output", "flags", &type, &direction.flags, &len) == 0 && type == CFGMGR_INT32 ) {
			len = sizeof( direction.left );
			if ( cfgmgr_read(cid, subroot, "output", "left", &type, &direction.left, &len) == 0 && type == CFGMGR_INT32 ) {
				if ( cfgmgr_read(cid, subroot, "output", "right", &type, &direction.right, &len ) == 0 && type == CFGMGR_INT32 )
					snd_mixer_channel_direction_writek(&sfile, &direction, SND_MIX_VOL_OUTPUT);
			}
		}
		direction.channel = idx;
		len = sizeof(direction.flags);
		if ( cfgmgr_read(cid, subroot, "input", "flags", &type, &direction.flags, &len) == 0 && type == CFGMGR_INT32 ) {
			len = sizeof( direction.left );
			if ( cfgmgr_read(cid, subroot, "input", "left", &type, &direction.left, &len) == 0 && type == CFGMGR_INT32 ) {
				if ( cfgmgr_read(cid, subroot, "input", "right", &type, &direction.right, &len ) == 0 && type == CFGMGR_INT32 ) 
					snd_mixer_channel_direction_writek(&sfile, &direction, SND_MIX_VOL_INPUT);
			}
		}
		cfgmgr_close(cid, subroot);
	}
	err = snd_switch_restore(lswitch, mixer);
	if (err < 0)
		snd_printd("cannot restore mixer switch settings\n");
	cfgmgr_close(cid, root);
	cfgmgr_detach(cid);
#endif
}

int snd_mixer_register(snd_kmixer_t * mixer, int device)
{
	char name[16];
	int idx, err = 0, cardnum;
	snd_kmixer_channel_t *channel;
	snd_kmixer_file_t sfile;
	snd_mixer_channel_direction_t direction;
	snd_info_entry_t *entry;

	if (device < 0 || device > 1)
		return -EINVAL;
	if (!mixer->card)
		return -EINVAL;
	idx = ((cardnum = mixer->card->number) * SND_MIXER_DEVICES) + device;
	if (snd_mixers[idx]) {
		return -EBUSY;
	}
	mixer->device = device;
	snd_mixers[idx] = mixer;
	sprintf(name, "mixer%i%i", mixer->card->number, device);
	if ((mixer->attr = snd_register_io(mixer, name, &snd_mixer_io)) == NULL) {
		snd_mixers[idx] = NULL;
		err = -errno;
		return err;
	}
	sprintf(name, "mixer%i", 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 + (mixer->channels_count * 128);
		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;
	for (idx = 0; idx < mixer->channels_visible; idx++) {
		channel = mixer->channels[idx];
		if (!channel)
			continue;

		memset(&sfile, 0, sizeof(sfile));
		sfile.mixer = mixer;

		memset(&direction, 0, sizeof(direction));
		direction.channel = channel->channel;
		if (channel->hw.caps & SND_MIXER_CINFO_CAP_INPUT) {
			direction.flags = SND_MIXER_DFLG_MUTE | SND_MIXER_DFLG_FORCE;
			direction.left = channel->hw.imin;
			direction.right = channel->hw.imin;
			if ((err = snd_mixer_channel_direction_writek(&sfile, &direction, SND_MIX_VOL_INPUT)) < 0) {
				snd_printk("snd_mixer_register: oops, can't setup channel '%s' (%i) - input!!!\n", channel->hw.name, err);
			}
		}
		if (channel->hw.caps & SND_MIXER_CINFO_CAP_OUTPUT) {
			if (channel->hw.caps & SND_MIXER_CINFO_CAP_EXTINPUT 
#define HACKHACK
#ifdef HACKHACK
				&& channel->hw.priority != SND_MIXER_PRI_CD
#endif
			) {
				direction.flags = SND_MIXER_DFLG_MUTE | SND_MIXER_DFLG_FORCE;
				direction.left = channel->hw.omin;
				direction.right = channel->hw.omin;
			} else {
				direction.flags = SND_MIXER_DFLG_FORCE | SND_MIXER_DFLG_DECIBEL;
				direction.left_dB = 0;
				direction.right_dB = 0;
			}
			if ((err = snd_mixer_channel_direction_writek(&sfile, &direction, SND_MIX_VOL_OUTPUT)) < 0) {
				snd_printk("snd_mixer_register: oops, can't setup channel '%s' (%i) - output!!!\n", channel->hw.name, err);
			}
		}
	}
	snd_mixer_restore(mixer);
	return err;
}

int snd_mixer_unregister(snd_kmixer_t * mixer)
{
	int idx;

	if (!mixer)
		return -EINVAL;
	snd_mixer_store(mixer);
	snd_unregister_io(mixer->attr);
	idx = (mixer->card->number * SND_MIXER_DEVICES) + mixer->device;
	if (snd_mixers[idx] != mixer) {
		return -EINVAL;
	}
	snd_mixers[idx] = NULL;
	if (mixer->proc_entry) {
		snd_info_unregister(mixer->proc_entry);
		mixer->proc_entry = NULL;
	}
	return snd_mixer_free(mixer);
}

/*
 *  INIT PART
 */

snd_kmixer_channel_t *snd_mixer_find_channel(snd_kmixer_t * mixer,
					     unsigned int priority)
{
	int idx;
	snd_kmixer_channel_t *channel;

	if (!mixer)
		return NULL;
	for (idx = 0; idx < mixer->channels_count; idx++) {
		channel = mixer->channels[idx];
		if (channel->hw.priority == priority)
			return channel;
	}
	return NULL;
}

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

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