/*
 **********************************************************************
 *     mixer.c - /dev/mixer interface for emu10k1 driver
 *     Copyright 1999, 2000 Creative Labs, Inc.
 *
 *     This program uses some code from es1317.c, Copyright 1998-1999
 *     Thomas Sailer
 *
 **********************************************************************
 *
 *     Date                 Author          Summary of changes
 *     ----                 ------          ------------------
 *     October 20, 1999     Bertrand Lee    base code release
 *     November 2, 1999     Alan Cox        cleaned up stuff
 *
 **********************************************************************
 *
 *     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.
 *
 **********************************************************************
 */

#ifdef MODULE
#define __NO_VERSION__		/* Kernel version only defined once */
#include <linux/module.h>
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif

#include "hwaccess.h"
#include "mycommon.h"

#define AC97_PESSIMISTIC
#undef OSS_DOCUMENTED_MIXER_SEMANTICS

#define vol_to_hw_5(swvol) (31 - (((swvol) * 31) / 100))
#define vol_to_hw_4(swvol) (15 - (((swvol) * 15) / 100))

#define vol_to_sw_5(hwvol) (((31 - (hwvol)) * 100) / 31)
#define vol_to_sw_4(hwvol) (((15 - (hwvol)) * 100) / 15)

/* --------------------------------------------------------------------- */
/*
 * hweightN: returns the hamming weight (i.e. the number
 * of bits set) of a N-bit word
 */

#ifdef hweight32
#undef hweight32
#endif

extern __inline__ unsigned int hweight32(unsigned int w)
{
	unsigned int res = (w & 0x55555555) + ((w >> 1) & 0x55555555);
	res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
	res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F);
	res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF);
	return (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF);
}


/* Mapping arrays */
static const unsigned int recsrc[] =
{
	SOUND_MASK_MIC,
	SOUND_MASK_CD,
	SOUND_MASK_VIDEO,
	SOUND_MASK_LINE1,
	SOUND_MASK_LINE,
	SOUND_MASK_VOLUME,
	SOUND_MASK_OGAIN,	/* Used to be PHONEOUT */
	SOUND_MASK_PHONEIN,
	SOUND_MASK_TREBLE,
	SOUND_MASK_BASS
};

static const unsigned char volreg[SOUND_MIXER_NRDEVICES] =
{
    /* 5 bit stereo */
	[SOUND_MIXER_LINE] = AC97_LINEINVOLUME,
	[SOUND_MIXER_CD] = AC97_CDVOLUME,
	[SOUND_MIXER_VIDEO] = AC97_VIDEOVOLUME,
	[SOUND_MIXER_LINE1] = AC97_AUXVOLUME,
	[SOUND_MIXER_PCM] = AC97_PCMOUTVOLUME,
    /* 6 bit stereo */
	[SOUND_MIXER_VOLUME] = AC97_MASTERVOLUME,
	[SOUND_MIXER_PHONEOUT] = AC97_HEADPHONEVOLUME,
    /* 6 bit mono */
	[SOUND_MIXER_OGAIN] = AC97_MASTERVOLUMEMONO,
	[SOUND_MIXER_PHONEIN] = AC97_PHONEVOLUME,
    /* 4 bit mono but shifted by 1 */
	[SOUND_MIXER_SPEAKER] = AC97_PCBEEPVOLUME,
    /* 6 bit mono + preamp */
	[SOUND_MIXER_MIC] = AC97_MICVOLUME,
    /* 4 bit stereo */
	[SOUND_MIXER_RECLEV] = AC97_RECORDGAIN,
    /* 4 bit mono */
	[SOUND_MIXER_IGAIN] = AC97_RECORDGAINMIC,
    /* test code */
	[SOUND_MIXER_BASS] = AC97_GENERALPUPOSE,
	[SOUND_MIXER_TREBLE] = AC97_MASTERTONE
};

#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS

#define swab(x) ((((x) >> 8) & 0xff) | (((x) << 8) & 0xff00))

static int mixer_rdch(struct sblive_hw * sb_hw, unsigned int ch, int *arg)
{
	u16 reg;
	int j;
	int nL, nR;

	switch (ch) {
	case SOUND_MIXER_LINE:
	case SOUND_MIXER_CD:
	case SOUND_MIXER_VIDEO:
	case SOUND_MIXER_LINE1:
	case SOUND_MIXER_PCM:
	case SOUND_MIXER_VOLUME:
		sblive_readac97(sb_hw, volreg[ch], &reg);
		if (reg & 0x8000)
			nL = nR = 0;
		else {
			nL = 0x64 - (((reg >> 8) & 0x1f) * 3);
			nR = 0x64 - ((reg & 0x1f) * 3);
		}
		DPD("mixer_rdch: l=%d, r=%d\n", nL, nR);

		j = reg;
		if (j & 0x8000)
			return put_user(0, (int *) arg);
		return put_user(0x6464 - (swab(j) & 0x1f1f) * 3, (int *) arg);

	case SOUND_MIXER_OGAIN:
	case SOUND_MIXER_PHONEIN:
		sblive_readac97(sb_hw, volreg[ch], &reg);
		j = reg;
		if (j & 0x8000)
			return put_user(0, (int *) arg);
		return put_user(0x6464 - 0x303 * (j & 0x1f), (int *) arg);

	case SOUND_MIXER_SPEAKER:
		sblive_readac97(sb_hw, volreg[ch], &reg);
		j = reg;
		if (j & 0x8000)
			return put_user(0, (int *) arg);
		return put_user(0x6464 - ((j >> 1) & 0xf) * 0x606, (int *) arg);

	case SOUND_MIXER_MIC:
		sblive_readac97(sb_hw, volreg[ch], &reg);
		j = reg;
		if (j & 0x8000)
			return put_user(0, (int *) arg);
		return put_user(0x4949 - 0x202 * (j & 0x1f) + ((j & 0x40) ? 0x1b1b : 0), (int *) arg);

	case SOUND_MIXER_RECLEV:
		sblive_readac97(sb_hw, volreg[ch], &reg);
		j = reg;
		if (j & 0x8000)
			return put_user(0, (int *) arg);
		return put_user((swab(j) & 0xf0f) * 6 + 0xa0a, (int *) arg);

	case SOUND_MIXER_TREBLE:
	case SOUND_MIXER_BASS:
		return put_user(0x0000, (int *) arg);
	default:
		return -EINVAL;
	}
}

#else				/* OSS_DOCUMENTED_MIXER_SEMANTICS */

static const unsigned char volidx[SOUND_MIXER_NRDEVICES] =
{
    /* 5 bit stereo */
	[SOUND_MIXER_LINE] = 1,
	[SOUND_MIXER_CD] = 2,
	[SOUND_MIXER_VIDEO] = 3,
	[SOUND_MIXER_LINE1] = 4,
	[SOUND_MIXER_PCM] = 5,
    /* 6 bit stereo */
	[SOUND_MIXER_VOLUME] = 6,
	[SOUND_MIXER_PHONEOUT] = 7,
    /* 6 bit mono */
	[SOUND_MIXER_OGAIN] = 8,
	[SOUND_MIXER_PHONEIN] = 9,
    /* 4 bit mono but shifted by 1 */
	[SOUND_MIXER_SPEAKER] = 10,
    /* 6 bit mono + preamp */
	[SOUND_MIXER_MIC] = 11,
    /* 4 bit stereo */
	[SOUND_MIXER_RECLEV] = 12,
    /* 4 bit mono */
	[SOUND_MIXER_IGAIN] = 13,
	[SOUND_MIXER_TREBLE] = 14,
	[SOUND_MIXER_BASS] = 15,
	[SOUND_MIXER_ALTPCM] = 16,
	[SOUND_MIXER_DIGITAL1] = 17,
	[SOUND_MIXER_DIGITAL2] = 18
};

#endif				/* OSS_DOCUMENTED_MIXER_SEMANTICS */

int mixer_wrch(struct sblive_hw * sb_hw, unsigned int ch, int val)
{
	int i;
	unsigned l1, r1;
	u16 wval;

	l1 = val & 0xff;
	r1 = (val >> 8) & 0xff;
	if (l1 > 100)
	  l1 = 100;
	if (r1 > 100)
	  r1 = 100;

	DPD("mixer_wrch() called: ch=%u, l1=%u, r1=%u\n", ch, l1, r1);

	switch (ch) 
	{
	case SOUND_MIXER_ALTPCM:
		DPF("SOUND_MIXER_ALTPCM:\n");
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x10, 0, l1 * (0x7fffffff/100));
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x11, 0, r1 * (0x7fffffff/100));
		return 0;
	case SOUND_MIXER_DIGITAL2:
		DPF("SOUND_MIXER_DIGITAL2:\n");
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x14, 0, l1 * (0x7fffffff/100));
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x15, 0, r1 * (0x7fffffff/100));
		return 0;
	case SOUND_MIXER_DIGITAL1:
		DPF("SOUND_MIXER_DIGITAL1:\n");
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x12, 0, l1 * (0x7fffffff/100));
		sblive_writeptr(sb_hw, FXGPREGBASE + 0x13, 0, r1 * (0x7fffffff/100));
		return 0;
	case SOUND_MIXER_LINE1:
		DPF("SOUND_MIXER_LINE1:\n");
	case SOUND_MIXER_LINE:
		DPF("SOUND_MIXER_LINE:\n");
	case SOUND_MIXER_CD:
		DPF("SOUND_MIXER_CD:\n");
	case SOUND_MIXER_PCM:
		DPF("SOUND_MIXER_PCM:\n");
	case SOUND_MIXER_VOLUME:
		DPF("sound_mixer_volume:\n");
		
		if (l1 < 7 && r1 < 7) 
		{
			sblive_writeac97(sb_hw, volreg[ch], 0x8000);
			return 0;
		}
		
		if (l1 < 7)
		  l1 = 7;
		if (r1 < 7)
		  r1 = 7;
		
		wval = (((100 - l1) / 3) << 8) | ((100 - r1) / 3);
		sblive_writeac97(sb_hw, volreg[ch], wval);
		
		return 0;

	case SOUND_MIXER_OGAIN:
		DPF("SOUND_MIXER_OGAIN:\n");
	case SOUND_MIXER_PHONEIN:
		DPF("SOUND_MIXER_PHONEIN:\n");
		sblive_writeac97(sb_hw, volreg[ch], (l1 < 7) ? 0x8000 : (100 - l1) / 3);
		return 0;

	case SOUND_MIXER_SPEAKER:
		DPF("SOUND_MIXER_SPEAKER:\n");
		sblive_writeac97(sb_hw, volreg[ch], (l1 < 10) ? 0x8000 : ((100 - l1) / 6) << 1);
		return 0;

	case SOUND_MIXER_MIC:
		DPF("SOUND_MIXER_MIC:\n");
		
		if (l1 < 11) 
		{
			sblive_writeac97(sb_hw, volreg[ch], 0x8000);
			return 0;
		}
		
		i = 0;
		
		if (l1 >= 27) 
		{
			l1 -= 27;
			i = 0x40;
		}
		
		if (l1 < 11)
		  l1 = 11;
		
		sblive_writeac97(sb_hw, volreg[ch], ((73 - l1) / 2) | i);
		return 0;

	case SOUND_MIXER_RECLEV:
		DPF("SOUND_MIXER_RECLEV:\n");
		
		if (l1 < 10 || r1 < 10) 
		{
			sblive_writeac97(sb_hw, volreg[ch], 0x8000);
			return 0;
		}
		
		if (l1 < 10)
		  l1 = 10;
		if (r1 < 10)
		  r1 = 10;
		
		sblive_writeac97(sb_hw, volreg[ch], (((l1 - 10) / 6) << 8) | ((r1 - 10) / 6));
		return 0;
		
	case SOUND_MIXER_TREBLE:
		DPF("SOUND_MIXER_TREBLE:\n");
		
		{
			//u8 atnL = sumVolumeToAttenuation(100 - l1);
			//u8 atnR = sumVolumeToAttenuation(100 - r1);
			u8 atnL = (100 - l1) * 0xa0 / 100;
			u8 atnR = (100 - r1) * 0xa0 / 100;
                        int i = 0;

			DPD("atnL = 0x%08x, atnR = 0x%08x\n", atnL, atnR);
			
			while (i < 64) 
			{
				sblive_writeptr(sb_hw, IFATN_ATTENUATION, i++, atnL);
				sblive_writeptr(sb_hw, IFATN_ATTENUATION, i++, atnR);
			}
			return 0;
		}
		
	case SOUND_MIXER_BASS:
		DPF("SOUND_MIXER_BASS:\n");

		{
			static int idxB = 0;
                        u8 peL = (l1 * 255 / 100 - 128) & 0xff;
                        u8 peR = (r1 * 255 / 100 - 128) & 0xff;
			int i = 0; /* Usually first channel */

			idxB += 1;
			idxB &= 0xf;
			DPD("using 0x%08x\n", idxB);
			
			/* for (i = 9; i <= 9; i++) { */
			/* sblive_writeptr(sb_hw, CVCF_CURRENTFILTER, i, idx); */
			/* sblive_writeptr(sb_hw, CVCF_CURRENTVOL, i, idx); */

                        /* this is pure hack - its for 4 channels */
			/* FIXME: This is very odd, most of this code will
			 * never be executed. */
			while (i < 64) 
			{
				if (1) 
				{
					sblive_writeptr(sb_hw, PEFE_FILTERAMOUNT, i++, peL);
					sblive_writeptr(sb_hw, PEFE_FILTERAMOUNT, i++, peR);
				} else 
				  if (0) 
				  {
					  sblive_writeptr(sb_hw, PEFE_PITCHAMOUNT, i++, peL);
					  sblive_writeptr(sb_hw, PEFE_FILTERAMOUNT, i++, peR);
				} else 
				  if (0) 
				  {
					  sblive_writeptr(sb_hw, IFATN_FILTERCUTOFF, i++, peL);
					  sblive_writeptr(sb_hw, IFATN_FILTERCUTOFF, i++, peR);
				} else 
				  if (0) 
				  {
					sblive_writeptr(sb_hw, IP,  i++, idxB << 12);
					sblive_writeptr(sb_hw, IP, i++, idxB << 12);
				}
			}
			return 0;
		}
	default:
		DPF("Got unknown SOUND_MIXER ioctl\n");
		return -EINVAL;
	}
}


static int sblive_mixer_llseek(struct inode *inode, struct file *file, off_t offset, int nOrigin)
{
	DPF("sblive_mixer_llseek() called\n");
	return -ESPIPE;
}

/* Mixer file operations */
/* FIXME: Do we need spinlocks in here? */
static int sblive_mixer_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
        static const char id[] = "SBLive";
        static const char name[] = "Creative SBLive";
	int i, val;
	struct sblive_hw * sb_hw = (struct sblive_hw *) file->private_data;
	u16 reg;

	if (cmd == SOUND_MIXER_INFO) 
	{
		mixer_info info;
		DPF("SOUND_MIXER_INFO\n");
		
		strncpy(info.id, id, sizeof(info.id));
		strncpy(info.name, name, sizeof(info.name));

		if (copy_to_user((void *) arg, &info, sizeof(info)))
		  return -EFAULT;
		return 0;
	}

	if (cmd == OSS_GETVERSION) 
	{
		DPF("OSS_GETVERSION\n");
		return put_user(SOUND_VERSION, (int *) arg);
	}

	if (_IOC_TYPE(cmd) != 'M' || _IOC_SIZE(cmd) != sizeof(int))
	  return -EINVAL;

	if (_IOC_DIR(cmd) == _IOC_READ) 
	{
		switch (_IOC_NR(cmd)) 
		{
		case SOUND_MIXER_RECSRC:	/* Arg contains a bit for each recording source */
			DPF("SOUND_MIXER_READ_RECSRC\n");
			sblive_readac97(sb_hw, AC97_RECORDSELECT, &reg);
			return put_user(recsrc[reg & 7], (int *) arg);

		case SOUND_MIXER_DEVMASK:	/* Arg contains a bit for each supported device */
			DPF("SOUND_MIXER_READ_DEVMASK\n");
			return put_user(SOUND_MASK_LINE | SOUND_MASK_CD |
					SOUND_MASK_OGAIN | SOUND_MASK_LINE1 |
					SOUND_MASK_PCM | SOUND_MASK_VOLUME |
					SOUND_MASK_PHONEIN | SOUND_MASK_MIC |
					/* SOUND_MASK_BASS | SOUND_MASK_TREBLE | */
					SOUND_MASK_RECLEV | SOUND_MASK_SPEAKER |
					SOUND_MASK_ALTPCM | SOUND_MASK_DIGITAL1 |
					SOUND_MASK_DIGITAL2,
					(int *) arg);
		case SOUND_MIXER_RECMASK:	/* Arg contains a bit for each supported recording source */
			DPF("SOUND_MIXER_READ_RECMASK\n");
			return put_user(SOUND_MASK_MIC | SOUND_MASK_CD |
					SOUND_MASK_LINE1 | SOUND_MASK_LINE |
					SOUND_MASK_VOLUME | SOUND_MASK_OGAIN |
					SOUND_MASK_PHONEIN, (int *) arg);

		case SOUND_MIXER_STEREODEVS:	/* Mixer channels supporting stereo */
			DPF("SOUND_MIXER_READ_STEREODEVS\n");
			return put_user(SOUND_MASK_LINE | SOUND_MASK_CD |
					SOUND_MASK_OGAIN | SOUND_MASK_LINE1 |
					SOUND_MASK_PCM | SOUND_MASK_VOLUME |
					/*SOUND_MASK_BASS | SOUND_MASK_TREBLE |*/ /* ...especially since they are enabled here */
					SOUND_MASK_RECLEV | SOUND_MASK_ALTPCM |
					SOUND_MASK_DIGITAL1 | SOUND_MASK_DIGITAL2,
					(int *) arg);

		case SOUND_MIXER_CAPS:
			DPF("SOUND_MIXER_READ_CAPS\n");
			return put_user(SOUND_CAP_EXCL_INPUT, (int *) arg);

		default:
			i = _IOC_NR(cmd);
			DPD("SOUND_MIXER_READ(%d)\n", i);
			if (i >= SOUND_MIXER_NRDEVICES)
			  return -EINVAL;
#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
			return mixer_rdch(sb_hw, i, (int *) arg);
#else				/* OSS_DOCUMENTED_MIXER_SEMANTICS */
			if (!volidx[i])
			  return -EINVAL;
			return put_user(sb_hw->arrwVol[volidx[i] - 1], (int *) arg);

#endif				/* OSS_DOCUMENTED_MIXER_SEMANTICS */
		}
	}			/* End of _IOC_READ */
	
	if (_IOC_DIR(cmd) != (_IOC_READ | _IOC_WRITE))
	  return -EINVAL;

	/* _IOC_WRITE */
	sb_hw->modcnt++;
	
	switch (_IOC_NR(cmd)) 
	{
	case SOUND_MIXER_RECSRC:	/* Arg contains a bit for each recording source */
		DPF("SOUND_MIXER_WRITE_RECSRC\n");
		
		get_user_ret(val, (int *) arg, -EFAULT);
		i = hweight32(val);
		if (i == 0)
		  return 0;	/*val = mixer_recmask(s); */
		else if (i > 1) 
		{
			sblive_readac97(sb_hw, AC97_RECORDSELECT, &reg);
			val &= ~recsrc[reg & 7];
		}
		
		for (i = 0; i < 8; i++) 
		{
			if (val & recsrc[i]) 
			{
				DPD("Selecting record source to be 0x%04x\n", 0x0101 * i);
				sblive_writeac97(sb_hw, AC97_RECORDSELECT, 0x0101 * i);
				return 0;
			}
		}
		return 0;

	default:
		i = _IOC_NR(cmd);
		DPD("SOUND_MIXER_WRITE(%d)\n", i);
		
		if (i >= SOUND_MIXER_NRDEVICES)
		  return -EINVAL;
		get_user_ret(val, (int *) arg, -EFAULT);
		if (mixer_wrch(sb_hw, i, val))
		  return -EINVAL;
		
#ifdef OSS_DOCUMENTED_MIXER_SEMANTICS
		return mixer_rdch(sb_hw, i, (int *) arg);
#else				/* OSS_DOCUMENTED_MIXER_SEMANTICS */
		if (!volidx[i])
			return -EINVAL;
		sb_hw->arrwVol[volidx[i] - 1] = val;
		/* s->mix.vol[volidx[i]-1] = val; */
		return put_user(sb_hw->arrwVol[volidx[i] - 1], (int *) arg);
#endif				/* OSS_DOCUMENTED_MIXER_SEMANTICS */
		
	}
}

static int sblive_mixer_open(struct inode *inode, struct file *file)
{
	int nMinor = MINOR(inode->i_rdev);
	struct sblive_hw * currobj = sblive_devs;

	DPF("sblive_mixer_open() called!\n");

	while (currobj && currobj->mixer_num != nMinor)
	  currobj = currobj->next;
	if (!currobj)
	  return -ENODEV;

	file->private_data = currobj;
	MOD_INC_USE_COUNT;
	return 0;
}

static void sblive_mixer_close(struct inode *inode, struct file *file)
{
	DPF("sblive_mixer_close() called!\n");
	MOD_DEC_USE_COUNT;
}


struct file_operations emu10k1_mixer_fops =
{
	&sblive_mixer_llseek,
	NULL,			/* read */
	NULL,			/* write */
	NULL,			/* readdir */
	NULL,			/* select/poll */
	&sblive_mixer_ioctl,
	NULL,			/* mmap */
	&sblive_mixer_open,
	&sblive_mixer_close,
	NULL,			/* fsync */
	NULL,			/* fasync */
	NULL			/* check_media_change */
};

static const struct initvol 
{
	int mixch;
	int vol;
} initvol[] __initdata = 
{
	{ SOUND_MIXER_VOLUME, 0x5050 },
	{ SOUND_MIXER_OGAIN, 0x5050 },
	{ SOUND_MIXER_SPEAKER, 0x5050 },
	{ SOUND_MIXER_PHONEIN, 0x5050 },
	{ SOUND_MIXER_MIC, 0x5000 },
	{ SOUND_MIXER_LINE, 0x5050 },
	{ SOUND_MIXER_CD, 0x5050 },
	{ SOUND_MIXER_LINE1, 0x5050 },
	{ SOUND_MIXER_ALTPCM, 0x5050 },
	{ SOUND_MIXER_DIGITAL1, 0x5050 },
	{ SOUND_MIXER_DIGITAL2, 0 },
	{ SOUND_MIXER_PCM, 0x5050 },
	{ SOUND_MIXER_RECLEV, 0x5050 } /*,
	{ SOUND_MIXER_TREBLE, 0x5050 },
	{ SOUND_MIXER_BASS, 0x5050 } */
};

/* FIXME: Do we need spinlocks in here? */
int sblive_mixer_init(struct sblive_hw * sb_hw)
{
	int count;

	/* Reset */
	sblive_writeac97(sb_hw, AC97_RESET, 0);

#if 0
	/* Check status word */
	{
		u16 reg;
		sblive_readac97(sb_hw, AC97_RESET, &reg);
		DPD("RESET 0x%x\n", reg);
		sblive_readac97(sb_hw, AC97_MASTERTONE, &reg);
		DPD("MASTER_TONE 0x%x\n", reg);
	}
#endif

	/* Set default recording source to mic in */
	sblive_writeac97(sb_hw, AC97_RECORDSELECT, 0);

	/* Set default volumes for all mixer channels */
	for (count = 0; count < sizeof(initvol) / sizeof(initvol[0]); count++) 
	{
		sb_hw->arrwVol[volidx[initvol[count].mixch] - 1] = initvol[count].vol;
		mixer_wrch(sb_hw, initvol[count].mixch, initvol[count].vol);
	}

	sb_hw->modcnt = 0;	// Should this be here or in open() ?

	return CTSTATUS_SUCCESS;
}
