/*
 **********************************************************************
 *     cardwo.c - PCM output HAL for emu10k1 driver
 *     Copyright 1999, 2000 Creative Labs, Inc.
 *
 **********************************************************************
 *
 *     Date                 Author          Summary of changes
 *     ----                 ------          ------------------
 *     October 20, 1999     Bertrand Lee    base code release
 *
 **********************************************************************
 *
 *     This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation; either version 2 of
 *     the License, or (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public
 *     License along with this program; if not, write to the Free
 *     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *     USA.
 *
 **********************************************************************
 */

#include "hwaccess.h"
#include "mycommon.h"
#include "mmwave.h"
#include "cardwo.h"

/* Volume calcs */
static int set_volume_instance(struct sblive_waveout * card_waveout, struct wave_out * wave_out, struct voice_param * left)
{
	/* only applicable for playback */
	u32 volL, volR, vol = 0;

	volL = (wave_out->localvol & 0xffff);
	volR = ((wave_out->localvol >> 16) & 0xffff);

	if (wave_out->globalvolFactor) 
	{
		volL = ((u32) (((u16) card_waveout->globalvol & 0xffff) * (u16) volL)) / 0xffff;
		volR = ((u32) (((u16) (card_waveout->globalvol >> 16) & 0xffff) * ((u16) volR))) / 0xffff;
	}

	/* BIG ASSUMPTION HERE THAT DEFAULT WAVE PAN/AUX IS 0xff/0xff */
	/* New volume and pan */
		
	if (volL == volR) 
	{
		vol = volL;
		left->unSends.tSends.pan_send = 0xff;
		left->unSends.tSends.aux_send = 0xff;
	} else 
	{
		if (volL > volR) 
		{
			vol = volL;
			left->unSends.tSends.pan_send = 0xff;
			left->unSends.tSends.aux_send = (char) ((volR * 255) / vol);
		} else 
		{
			vol = volR;
			left->unSends.tSends.aux_send = 0xff;
			left->unSends.tSends.pan_send = (char) ((volL * 255) / vol);
		}
	}

	left->initial_attn = 0xff & sumVolumeToAttenuation(vol * 2);

        return vol;
}

static void set_volume(struct sblive_waveout * card_waveout)
{
	struct wave_out * currinst = card_waveout->wave_outlist;
	struct voice_cntlset setting[3];

	while (currinst && currinst->emu_voice) 
	{
		struct voice_param * left = &currinst->emu_voice->voice_params;
		
		/* This flag allows a wave instance to be unaffected by changes to global volume. */
		
		if (currinst->globalvolFactor) 
		{
			u32 volL, volR, vol;

			/* Calculate individual channel volumes based on obj and instance volume */
			
			volL = ((u32) (((u16) card_waveout->globalvol & 0xffff) * ((u16) currinst->localvol & 0xffff))) / 0xffff;
			volR = ((u32) (((u16) (card_waveout->globalvol >> 16) & 0xffff) * ((u16) (currinst->localvol >> 16) & 0xffff))) / 0xffff;

			/* BIG ASSUMPTION HERE THAT DEFAULT WAVE PAN/AUX IS 0xff/0xff */
			/* New volume and pan */
			if (volL == volR) 
			{
				vol = volL;
				left->unSends.tSends.pan_send = 0xff;
				left->unSends.tSends.aux_send = 0xff;
			} else 
			{
				if (volL > volR) 
				{
					vol = volL;
					left->unSends.tSends.pan_send = 0xff;
					left->unSends.tSends.aux_send = (char) ((volR * 255) / vol);
				} else 
				{
					vol = volR;
					left->unSends.tSends.aux_send = 0xff;
					left->unSends.tSends.pan_send = (char) ((volL * 255) / vol);
				}
			}

			left->initial_attn = 0xff & sumVolumeToAttenuation(vol * 2);

			if (currinst->wavexferbuf->is_stereo && currinst->emu_voice->linked_voice) 
			{
				struct voice_param * right = &currinst->emu_voice->linked_voice->voice_params;
				
				right->unSends.tSends.pan_send = 0; /* Left output of right channel is always zero */
				right->unSends.tSends.aux_send = left->unSends.tSends.aux_send; /* Update right channel aux */
				left->unSends.tSends.aux_send = 0; /* Zero out right output of left channel */
				right->initial_attn = 0xff & sumVolumeToAttenuation(vol * 2); /* Update right channel attenuation */

				setting[0].paramID = PSST_FXSENDAMOUNT_C;
				setting[0].value = 0;
				setting[1].paramID = PTRX_FXSENDAMOUNT_B;
				setting[1].value = right->unSends.tSends.aux_send;
				setting[2].paramID = IFATN_ATTENUATION;
				setting[2].value = right->initial_attn;

				sblive_voiceSetControl(currinst->emu_voice->linked_voice, setting, 3);
			}
			
			setting[0].paramID = PSST_FXSENDAMOUNT_C;
			setting[0].value = left->unSends.tSends.pan_send;
			setting[1].paramID = PTRX_FXSENDAMOUNT_B;
			setting[1].value = left->unSends.tSends.aux_send;
			setting[2].paramID = IFATN_ATTENUATION;
			setting[2].value = left->initial_attn;
			sblive_voiceSetControl(currinst->emu_voice, setting, 3);
		}
		
		currinst = currinst->next;
	}
}


/****************************************************************************/
/* Function : sblive_waveVolumeControlFn                                    */
/*                                                                          */
/* About    : Callback function for volume control.                         */
/****************************************************************************/
int sblive_waveVolumeControlFn(u32 event, struct sblive_waveout * card_waveout, u32 left, u32 right)
{
	switch (event) 
	{
	case 0:	/* Get */
		*(u32 *)left = card_waveout->left;
		*(u32 *)right = card_waveout->right;
		break;

	case 1:	/* Set */
		/*
		 ** this is a general set volume affecting all wave instances.
		 ** WODM_SETVOLUME should result in this controlId and affect
		 ** the volume of all wave instances. Apps which can set the
		 ** volume of individual wave instances should use
		 ** WAVEINSTANCEVOLUME instead
		 */
		card_waveout->left = left;
		card_waveout->right = right;
		
		if (!card_waveout->mute) 
		{
			card_waveout->globalvol = (left & 0xffff) | (right << 16);
			set_volume(card_waveout);
		}
		break;
	}
	
	return CTSTATUS_SUCCESS;
}



/****************************************************************************/
/* Function : sblive_waveoutInit                                            */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave out object structure      */
/*            sb_hw - pointer to hardware object                            */
/*                                                                          */
/* Return   : CTSTATUS_SUCCESS  -- successful                               */
/*            CTSTATUS_ERROR    -- failure                                  */
/*                                                                          */
/* About    : initialize card wave output device.                           */
/****************************************************************************/
/* FIXME: This should be a macro */
int sblive_waveoutInit(struct sblive_waveout *card_waveout, u8 *carddesc)
{
	/* Init cardwave caps */
	card_waveout->caps.product_id = MM_CREATIVE_WAVEOUT_PID;
	card_waveout->caps.caps = CARDWAVE_OUT;
	card_waveout->caps.caps |= CARDWAVE_ALLOW_DIRECTXFER;
	card_waveout->caps.controls = CARDWAVE_CONTROL_VOLUME;
	card_waveout->caps.maxchannels = 2;
	card_waveout->caps.minrate = 100;
	card_waveout->caps.maxrate = 100000;
	strcpy(card_waveout->caps.wavedesc, carddesc);

	card_waveout->numplaybackinst = 0;
	card_waveout->maxnumplayinst = CARDWAVE_DEFAULT_MAXPLAYINST;
	card_waveout->wave_outlist = NULL;

	/* Init to invalid values */
	card_waveout->lineid = 0xeeeeeeee;
	card_waveout->ctrlid = 0xffffffff;

	/* Assign default global volume, reverb, chorus */
	card_waveout->globalvol = 0xFFFFFFFF;
	card_waveout->left = 0xFFFF;
	card_waveout->right = 0xFFFF;
	card_waveout->mute = 0;
	card_waveout->globalreverb = 0xFFFFFFFF;
	card_waveout->globalchorus = 0xFFFFFFFF;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutExit                                            */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object structure          */
/*                                                                          */
/* Return   : CTSTATUS_SUCCESS  -- successful                               */
/*            CTSTATUS_ERROR    -- failure                                  */
/*                                                                          */
/* About    : exit card wave operation.                                     */
/****************************************************************************/
int sblive_waveoutExit(struct sblive_waveout *card_waveout)
{
	DPF("sblive_waveoutExit()\n");

	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_waveoutQueryFormat                                   */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave out object structure    */
/*            pwave_fmt - pointer to wave format object                   */
/*            flags - flags that identifies the format to be queried.     */
/*                                                                        */
/* About    : query whether a specified wave format is supported by wave  */
/*            out device.                                                 */
/**************************************************************************/
int sblive_waveoutQueryFormat(struct sblive_waveout *card_waveout, struct wave_format * wave_fmt, u32 flags)
{
	if (flags & CARDWAVE_QF_CHANNEL) 
	  if ((card_waveout->numplaybackinst) > card_waveout->maxnumplayinst)
	    return CTSTATUS_INUSE;
	
	if (flags & CARDWAVE_QF_RATE) 
	  if (wave_fmt->samplingrate >= 0x2EE00) /* FIXME: 0x2ee00 = 192000! Is this valid??? */
	    return CTSTATUS_BADFORMAT_RATE;
	
	if (flags & CARDWAVE_QF_BITS) 
	  if (wave_fmt->bitspersample != 8 && wave_fmt->bitspersample != 16)
	    return CTSTATUS_BADFORMAT_BITS;
	
	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_waveoutOpen                                          */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave object structure        */
/*            wave_fmt - pointer to wave format object                    */
/*            CallbackFn - IRQ call back function                         */
/*            refdata - reference data for call back function             */
/*            fragment_size - size of buffer fragment                     */
/*            numfrags - number of buffer fragments                       */
/*                                                                        */
/* Output   : fragment_size - pointer to the actual fragment size.        */
/*            handle - pointer to the open handle.                        */
/*                                                                        */
/* About    : Open card wave out device.                                  */
/*            1. query whether a specified wave format is supported by    */
/*               device.                                                  */
/*            2. allocate emu channel for the specified channel object.   */
/*            3. attach this wave instance to the channel object list.    */
/*            4. install wave IRQ handler.                                */
/*            5. get wave transfer buffer.                                */
/*            6. get wave instance format.                                */
/**************************************************************************/
int sblive_waveoutOpen(struct sblive_hw *sb_hw, struct wave_format *wave_fmt, CALLBACKFN CallbackFn, u32 refdata, u32 *fragment_size, u32 numfrags, struct wave_out **handle)
{
	struct sblive_waveout * card_waveout = sb_hw->card_waveout;
	struct wave_out * wave_out;
	struct voice_allocdesc voice_allocdesc;
	int status;
	u32 buffsize;
	u8 *buffer;

	status = sblive_waveoutQueryFormat(card_waveout, wave_fmt, CARDWAVE_QF_ALL);
	if (status != CTSTATUS_SUCCESS)
	  return status;

	/* If number of wave instances is greater than the maximum instances allowed, fail the open. */
	if (card_waveout->numplaybackinst + 1 > card_waveout->maxnumplayinst) 
	  return CTSTATUS_ERROR;

	wave_out = (struct wave_out *) kmalloc(sizeof(struct wave_out), GFP_KERNEL);
	if (wave_out == NULL) 
	{
		DPF("struct wave_out alloc fail\n");
		return CTSTATUS_NOMEMORY;
	}
	
#ifdef MEMTEST
	DPD("kmalloc: [%p]\n", wave_out);
#endif

	/* Init channel object */
	wave_out->next = NULL;
	wave_out->status = 0;
	wave_out->state = CARDWAVE_STATE_STOPPED;
	wave_out->synchstart = FALSE;
	wave_out->wave_fmt = *wave_fmt;
	wave_out->CallbackFn = CallbackFn;
	wave_out->emu_voice = NULL;
	wave_out->emu_timer = NULL;
	wave_out->emuaddr = NULL;
	wave_out->wavexferbuf = NULL;
	wave_out->memhandle = 0;
	wave_out->refdata = refdata;
	wave_out->callbacksize = 0;

	/* Assign default local volume */
	/* FIXME: Should we be maxing the initial values like this? */
	wave_out->localvol = 0xffffffff;
	wave_out->localreverb = 0xffffffff;
	wave_out->localchorus = 0xffffffff;
	wave_out->globalvolFactor = 0xffff;
	wave_out->globalreverbFactor = 0xffff;
	wave_out->globalchorusFactor = 0xffff;

	wave_out->process_id = 0;
	wave_out->setpos = FALSE;
	wave_out->position = 0;

	wave_out->playflags = CARDWAVE_PLAY_LOOPING;

	wave_out->wavexferbuf = (struct wave_xferbuf *) kmalloc(sizeof(struct wave_xferbuf ), GFP_KERNEL);

	if (!wave_out->wavexferbuf) 
	{
		DPF("struct wave_xferbuf alloc failed\n");
		kfree((void *) wave_out);
		return CTSTATUS_NOMEMORY;
	}
		
#ifdef MEMTEST
	DPD("kmalloc: [%p]\n", wave_out->wavexferbuf);
#endif
	initWaveOutXferBuffer(wave_out);
	
	/* Allocate voices here, if no voices available, return error.
	 * Init voice_allocdesc first.*/
		
	voice_allocdesc.sb_hw = sb_hw;
	voice_allocdesc.ownertype = VOICEMGR_USAGE_PLAYBACK;

	voice_allocdesc.callback = NULL;
	voice_allocdesc.callback_data = 0;
	voice_allocdesc.numvoicereqs = 1;
	voice_allocdesc.flags[0] = 0;

	if (wave_fmt->channels == 1)
	  voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_MONO;

	if (wave_fmt->bitspersample == 16)
	  voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_16BIT;

	voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_PLAYBACK;

	status = sblive_timerInstallHandler(sb_hw, sblive_waveoutTimerCallback, (unsigned long) wave_out, &wave_out->emu_timer);
		
	if (status == CTSTATUS_SUCCESS) 
	{
		status = sblive_voiceAlloc(&voice_allocdesc, &wave_out->emu_voice);
		if (status != CTSTATUS_SUCCESS)
		{
			sblive_timerUninstallHandler(wave_out->emu_timer);
			DPF("Failed to allocate voive\n");
		}
	}
		
	if (status != CTSTATUS_SUCCESS) 
	{
		DPF("Channel allocation fail\n");
		kfree((void *) wave_out->wavexferbuf);			
		kfree((void *) wave_out);
		return status;
	}
	
	DPD("voicenum -> %d\n", wave_out->emu_voice->voicenum);

	/* Attach this wave instance to the channel object list */
	card_waveout->numplaybackinst++;
	
	osListAttach((struct sblive_list **) & card_waveout->wave_outlist, (struct sblive_list *) wave_out);

	wave_out->callbacksize = *fragment_size;

	buffsize = (*fragment_size ? *fragment_size * numfrags : 0xffff);
		  
	if (sblive_emuGetXferBuffer(sb_hw, wave_out, &buffsize, &buffer) != CTSTATUS_SUCCESS) 
	{
		if (buffsize < *fragment_size) 
		{
			*fragment_size = buffsize;
			sblive_waveoutClose(sb_hw, wave_out);
			return CTSTATUS_INVALIDVALUE;
		}
			  
		DPF("WaveOpen GetBuffer Fail\n");
		sblive_waveoutClose(sb_hw, wave_out);
		return CTSTATUS_ERROR;
	}
		  
	wave_out->callbacksize = buffsize / numfrags;
	*fragment_size = buffsize / numfrags;
	
	*handle = wave_out;

	wave_out->dsDpc.is_active = FALSE;
	wave_out->dsDpc.refdata = (u32)wave_out;
	wave_out->dsDpc.DPCCallBackFn = sblive_waveoutDpcCallback;

	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_waveoutClose                                         */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave object structure        */
/*            wave_out - pointer to the specified wave out instance       */
/*                                                                        */
/* About    : Close wave device.                                          */
/*            1. deallocate transfer buffer, for playback,                */
/*               free emu addx space.                                     */
/*            2. free voice channels.                                     */
/*            3. remove wave instance from channel object list.           */
/**************************************************************************/
int sblive_waveoutClose(struct sblive_hw *sb_hw, struct wave_out *wave_out)
{
	struct sblive_waveout *card_waveout = sb_hw->card_waveout;
	struct wave_out *tempchan;
	u32 dummy;

	if (wave_out->state != CARDWAVE_STATE_STOPPED)
	  sblive_waveoutStop(sb_hw, wave_out, &dummy);

	tempchan = card_waveout->wave_outlist;
	while (tempchan) 
	{
		if ((tempchan->wavexferbuf->xferbuffer
		     == wave_out->wavexferbuf->xferbuffer)
		    && (tempchan != wave_out))
		  break;
		tempchan = (struct wave_out *) tempchan->next;
	}

	osListRemove((struct sblive_list **) & card_waveout->wave_outlist, (struct sblive_list *) wave_out);

	if (tempchan == NULL) 
	{
		if (sblive_emuDeallocXferBuffer(wave_out) != CTSTATUS_SUCCESS)
		{
			DPF("Failed to deallocate buffer\n");
		}
		
		kfree((void *) wave_out->wavexferbuf);
#ifdef MEMTEST
		DPD("kfree: [%p]\n", wave_out->wavexferbuf);
#endif
	}
	
	wave_out->status |= FLAGS_AVAILABLE;
	wave_out->state = CARDWAVE_STATE_STOPPED;
	wave_out->synchstart = FALSE;

	card_waveout->numplaybackinst--;

	if (sblive_voiceFree(wave_out->emu_voice) != CTSTATUS_SUCCESS)
	{
		DPF("Failed to free voice\n");
	}

	if (wave_out->emu_timer != NULL) 
	  if (sblive_timerUninstallHandler(wave_out->emu_timer) != CTSTATUS_SUCCESS)
	  {
		  DPF("Failed to uninstall timer IRQ handler\n");
	  }

	kfree((void *) wave_out);

	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_emuGetXferBuffer                                     */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave object                  */
/*            wave_out - pointer to the specified wave out instance       */
/*            size - pointer to the size requested                        */
/*                                                                        */
/* Output   : size - pointer to the size allocated                        */
/*            buffer - pointer to the buffer pointer allocated            */
/*                                                                        */
/* About    : alloc transfer buffer.                                      */
/**************************************************************************/
int sblive_emuGetXferBuffer(struct sblive_hw *sb_hw, struct wave_out * wave_out, u32 *size, u8 **buffer)
{
	DPF("sblive_emuGetXferBuffer()\n");

	if (!size || !buffer)
	  return CTSTATUS_INVALIDPARAM;

	if (!wave_out) 
	{
		DPF("wave_out == NULL\n");
		return CTSTATUS_ERROR;
	}

	if (*size < wave_out->callbacksize) 
	{
		*size = wave_out->callbacksize;
		return CTSTATUS_INVALIDVALUE;
	}

	if (sblive_emuAllocXferBuffer(sb_hw, wave_out, size, buffer) != CTSTATUS_SUCCESS) 
	{
		DPF("Allocate buffer failed\n");
		return CTSTATUS_ERROR;
	}
	
	/* xferbufsize contains actual transfer buffer size */
	wave_out->wavexferbuf->xferbufsize = *size;
	wave_out->wavexferbuf->xferbuffer = *buffer;
	wave_out->wavexferbuf->xferpos = 0;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutSynchStart                                      */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*                                                                          */
/* About    : Synchronize start all wave playback and recording instances   */
/*            in a particular application according to process ID.          */
/*            Application needs to call SYNCHSTART twice, one is to start   */
/*            all wave playback instances, another to start all wave        */
/*            recording instances. Because wave out and wave in are         */
/*            separate devices.                                             */
/****************************************************************************/
int sblive_waveoutSynchStart(struct sblive_waveout * card_waveout, u32 process_id)
{
	struct wave_out * currinst;

	currinst = card_waveout->wave_outlist;

	/* Start wave playback instances */
	while (currinst) 
	{
		if (currinst->process_id == process_id) 
		{
			if (sblive_voiceStart(currinst->emu_voice) != CTSTATUS_SUCCESS)
			  return CTSTATUS_ERROR;
			
		}
		currinst = currinst->next;
	}

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_emuAllocXferBuffer                                     */
/*                                                                          */
/* Input    : wave_out - pointer to the specified wave out instance         */
/*            size - pointer to the size requested                          */
/*                                                                          */
/* Output   : size - pointer to the size returned                           */
/*            buffer - pointer to the buffer pointer allocated              */
/*                                                                          */
/* About    : allocate buffer for wave out transfer.                        */
/*            1. playback: a) request for emu address space.                */
/*                         b) allocate page-aligned PC memory for playback  */
/*                            buffer.                                       */
/*                         c) allocate memory for physical address table,   */
/*                            copy page table.                              */
/*                         d) determine start, end, startloop and           */
/*                            endloop.                                      */
/*                         e) fill in virtual memory table.                 */
/*                         f) free physical address table.                  */
/****************************************************************************/
int sblive_emuAllocXferBuffer(struct sblive_hw *sb_hw, struct wave_out *wave_out, u32 *size, u8 **buffer)
{
	u32 numpages, reqsize;
	struct emuaddr_allocdesc dsEmuAddxAllocDesc;
	struct wave_xferbuf * wavexferbuf = wave_out->wavexferbuf;
	unsigned long physaddx;
	void *virtaddr;
	u32 *addr_table;
	u32 pagecount;

	reqsize = *size;
	numpages = reqsize / PAGE_SIZE;

	/* If size is not a multiple of PAGE_SIZE then we need to round up */
	if (reqsize % PAGE_SIZE)
	  numpages += 1;
	
	DPD("requested pages is: %d\n", numpages);

	wavexferbuf->numpages = numpages;

	/* Only for playback, request for emu address space */
	dsEmuAddxAllocDesc.sb_hw = sb_hw;
	dsEmuAddxAllocDesc.ownertype = 0;
	dsEmuAddxAllocDesc.callback = 0;
	dsEmuAddxAllocDesc.callback_data = 0;

	/* Support non page-aligned buffer, don't need interpolation page */
	dsEmuAddxAllocDesc.size = numpages * PAGE_SIZE;
	dsEmuAddxAllocDesc.flags = 0;

	if (emu10kaddxmgrAlloc(&dsEmuAddxAllocDesc, &wave_out->emuaddr) != CTSTATUS_SUCCESS)
	  return CTSTATUS_NOMEMORY;

	/* Allocate memory */
	if (osAllocMemPhysical(reqsize, &wave_out->memhandle, &virtaddr, &physaddx) != CTSTATUS_SUCCESS)
	{
		emu10kaddxmgrFree(wave_out->emuaddr);
		return CTSTATUS_NOMEMORY;
	}

	/* Allocate memory for physical addx table, copy page table */
	if (!(addr_table = kmalloc(numpages * sizeof(u32), GFP_KERNEL)))
	{
		emu10kaddxmgrFree(wave_out->emuaddr);
		osFreeMemPhysical(wave_out->memhandle);
		return CTSTATUS_NOMEMORY;
	}
	
	if (osCopyPageTable(virtaddr, 0, numpages, addr_table) != CTSTATUS_SUCCESS)
	{
		emu10kaddxmgrFree(wave_out->emuaddr);
		osFreeMemPhysical(wave_out->memhandle);
		kfree((void *) addr_table);
		return CTSTATUS_ERROR;
	}
	
	{
		struct voice_param *left = &wave_out->emu_voice->voice_params;

		reqsize = reqsize * (2 - wavexferbuf->is16bit) / (wavexferbuf->is_stereo + 1) / 2;
		left->start = wave_out->emuaddr->emustartaddr;
		left->start = left->start / (wavexferbuf->is_stereo + 1) * (2 - wavexferbuf->is16bit);
		left->end = left->start + reqsize;
		left->startloop = left->start;
		left->endloop = left->end;

		DPD("sblive_emuAllocXferBuf: start=%x, end=%x, startloop=%x, endloop=%x\n", 
		    left->start, left->end, left->startloop, left->endloop);

		if (wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
		{
			struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

			/* Do the same thing for right channel */
			right->start = left->start;
			right->end = left->end;
			right->startloop = left->startloop;
			right->endloop = left->endloop;
		}
	}
	
	/* Fill in virtual memory table */
	for (pagecount = 0; pagecount < numpages; pagecount++) 
	{
		/* Preserve emu page number */
		*(((u32 *) (sb_hw->virtualpagetableptr) + (wave_out->emuaddr->emustartaddr >> 11) + pagecount)) &= 0x1fff;

		/* OR with physical address */
		*(((u32 *) (sb_hw->virtualpagetableptr) + (wave_out->emuaddr->emustartaddr >> 11) + pagecount)) |= (addr_table[pagecount] & 0xfffff000) << 1;

		DPD("Physical Addx: %x\n", *(((u32 *) (sb_hw->virtualpagetableptr) + (wave_out->emuaddr->emustartaddr >> 11) + pagecount)));
	}

	kfree((void *) addr_table); /* Free physical address table */
	
	*buffer = (u8 *) virtaddr;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_emuDeallocXferBuffer                                   */
/*                                                                          */
/* Input    : wave_out - pointer to the specified wave out instance         */
/*                                                                          */
/* About    : deallocate transfer buffer                                    */
/*            1. for playback, free emu address space.                      */
/*            2. free PC memory allocated for transfer buffer.              */
/*            3. clear VioceParam.                                          */
/****************************************************************************/
int sblive_emuDeallocXferBuffer(struct wave_out *wave_out)
{
	/* For playback, free emu address space */
	if (wave_out->emuaddr)
	  emu10kaddxmgrFree(wave_out->emuaddr);

	if (wave_out->memhandle) 
	{
		osFreeMemPhysical(wave_out->memhandle);
		wave_out->memhandle = 0;
		wave_out->wavexferbuf->xferbuffer = NULL;
	}
	
	/* Clear VoiceParam */
	if (wave_out->emu_voice != NULL) 
	{
		struct voice_param *left = &wave_out->emu_voice->voice_params;

		left->start = 0;
		left->startloop = 0;
		left->end = 0;
		left->endloop = 0;
		
		if ((wave_out->wave_fmt.channels == 2) && wave_out->emu_voice->linked_voice) 
		{
			struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;
			
			right->start = 0;
			right->startloop = 0;
			right->end = 0;
			right->endloop = 0;
		}
	}
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : initWaveOutXferBuffer                                         */
/*                                                                          */
/* Input    : wave_out - pointer to the specified wave out instance         */
/*                                                                          */
/* About    : init card wave buffer structure                               */
/****************************************************************************/
void initWaveOutXferBuffer(struct wave_out *wave_out)
{
	wave_out->wavexferbuf->xferpos = 0;
	wave_out->wavexferbuf->xferbufsize = 0;
	wave_out->wavexferbuf->numpages = 0;
	wave_out->wavexferbuf->xferbuffer = NULL;
	wave_out->wavexferbuf->is_stereo = (wave_out->wave_fmt.channels == 2) ? 1 : 0;
	wave_out->wavexferbuf->is16bit = (wave_out->wave_fmt.bitspersample == 16) ? 1 : 0;

	if (wave_out->wave_fmt.channels == 2) 
	{
		if (wave_out->wave_fmt.bitspersample == 16)
		  wave_out->wavexferbuf->format = S16;
		else
		  wave_out->wavexferbuf->format = S8;
	} else 
	{
		if (wave_out->wave_fmt.bitspersample == 16)
		  wave_out->wavexferbuf->format = M16;
		else
		  wave_out->wavexferbuf->format = M8;
	}
	
	wave_out->wavexferbuf->conv = 0;
	wave_out->wavexferbuf->interpolationbytes = INTERPOLATION_BYTES * (wave_out->wavexferbuf->is16bit + 1) * (wave_out->wavexferbuf->is_stereo + 1);
	wave_out->wavexferbuf->offset = 0;
	wave_out->wavexferbuf->stopposition = 0;
}


/****************************************************************************/
/* Function : sblive_waveoutSetFormat                                       */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*            wave_out - pointer to the specified wave out instance         */
/*                                                                          */
/* About    : sets a new format for the wave instance                       */
/****************************************************************************/
int sblive_waveoutSetFormat(struct sblive_hw *sb_hw, struct wave_out *wave_out, struct wave_format *wave_fmt)
{
	struct sblive_waveout *card_waveout = sb_hw->card_waveout;
	int status;
	struct voice_allocdesc voice_allocdesc;

	if (wave_out->state != CARDWAVE_STATE_STOPPED)
	  return CTSTATUS_ERROR;

	if ((status = sblive_waveoutQueryFormat(card_waveout, wave_fmt, CARDWAVE_QF_ALL)) != CTSTATUS_SUCCESS)
	  return status;

	if ((wave_fmt->channels != wave_out->wave_fmt.channels) ||
	    (wave_fmt->bitspersample != wave_out->wave_fmt.bitspersample))
	{
		sblive_voiceFree(wave_out->emu_voice);
		
		/* Allocate voices here, if no voices available, return error.  Init voice_allocdesc first. */
		voice_allocdesc.sb_hw = sb_hw;
		voice_allocdesc.ownertype = VOICEMGR_USAGE_PLAYBACK;

		/* for P10, this is to allocate main voices, don't need IRQ callback */
		voice_allocdesc.callback = NULL;
		voice_allocdesc.callback_data = 0;
		voice_allocdesc.numvoicereqs = 1;
		voice_allocdesc.flags[0] = 0;
		
		if (wave_fmt->channels == 1)
		  voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_MONO;
		if (wave_fmt->bitspersample == 16)
		  voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_16BIT;
		
		voice_allocdesc.flags[0] |= VOICEMGR_FLAGS_PLAYBACK;

		if ((status = sblive_voiceAlloc(&voice_allocdesc, &wave_out->emu_voice)) != CTSTATUS_SUCCESS)		
		  return status;
	}
	
	wave_out->wave_fmt = *wave_fmt;
	wave_out->wavexferbuf->is_stereo = (wave_out->wave_fmt.channels == 2) ? 1 : 0;
	wave_out->wavexferbuf->is16bit = (wave_out->wave_fmt.bitspersample == 16) ? 1 : 0;
	
	if (wave_out->wave_fmt.channels == 2) 
	{
		if (wave_out->wave_fmt.bitspersample == 16)
		  wave_out->wavexferbuf->format = S16;
		else
		  wave_out->wavexferbuf->format = S8;
	} else 
	{
		if (wave_out->wave_fmt.bitspersample == 16)
		  wave_out->wavexferbuf->format = M16;
		else
		  wave_out->wavexferbuf->format = M8;
	}
	
	wave_out->wavexferbuf->interpolationbytes = INTERPOLATION_BYTES * (wave_out->wavexferbuf->is16bit + 1) * (wave_out->wavexferbuf->is_stereo + 1);

	{
		u32 reqsize;
		struct wave_xferbuf * wavexferbuf = wave_out->wavexferbuf;
		struct voice_param * left = &wave_out->emu_voice->voice_params;

		/* emu address space is in words, use start and end for actual 
		 * emu start and end address allocated, startloop and endloop 
		 * for loop points.  startloop and endloop contain number of samples. */
		reqsize = wavexferbuf->xferbufsize * (2 - wavexferbuf->is16bit) / (wavexferbuf->is_stereo + 1) / 2;
		left->start = wave_out->emuaddr->emustartaddr;
		left->start = left->start / (wavexferbuf->is_stereo + 1) * (2 - wavexferbuf->is16bit);
		left->end = left->start + reqsize;
		left->startloop = left->start;
		left->endloop = left->end;

		DPD("SetFormat: start=%x, end=%x, startloop=%x, endloop=%x\n", 
		    left->start, left->end, left->startloop, left->endloop);

		if (wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
		{
			struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

			/* Do the same thing for right channel */
			right->start = left->start;
			right->end = left->end;
			right->startloop = left->startloop;
			right->endloop = left->endloop;
		}
	}
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutStart                                           */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*            wave_out - pointer to the specified wave out instance         */
/*                                                                          */
/* About    : start wave transfer.                                          */
/*            playback: a) setup voices.  b) set volume.                    */
/*                      c) enable IRQ.    d) start playback.                */
/****************************************************************************/
int sblive_waveoutStart(struct sblive_hw *sb_hw, struct wave_out *wave_out)
{
	struct sblive_waveout * card_waveout= sb_hw->card_waveout;
	struct voice_param *left = &wave_out->emu_voice->voice_params;
	u32 start, startPosition;
	
	/* If already started, return success */
	if (wave_out->state == CARDWAVE_STATE_STARTED)
	  return CTSTATUS_SUCCESS;

	/* Calculate pitch */
	left->initial_pitch = (u16) (srToPitch(wave_out->wave_fmt.samplingrate) >> 8);
		
	DPD("Initial pitch --> %x\n", left->initial_pitch);

	/* Easy way out.. gotta calculate value */
	left->pitch_target = 0;
	left->volume_target = 0;
	left->FC_target = 0;

	left->byampl_env_sustain = 0x7f;
	left->byampl_env_decay = 0x7f;

	if (wave_out->globalreverbFactor) 
	{
		u8 t = (card_waveout->globalreverb & 0xff) + (wave_out->localreverb & 0xff);
		left->unSends.tSends.reverb_send = (t > 255) ? 255 : t;
	} else 
	{
		left->unSends.tSends.reverb_send = 0;
	}

	if (wave_out->globalchorusFactor) 
	{
		u8 t = (card_waveout->globalchorus & 0xff) + (wave_out->localchorus & 0xff);
		left->unSends.tSends.chorus_send = (t > 255) ? 255 : t;
	} else 
	{
		left->unSends.tSends.chorus_send = 0;
	}

	set_volume_instance(card_waveout, wave_out, left);
	
	left->pan_target = left->unSends.tSends.pan_send;
	left->aux_target = left->unSends.tSends.aux_send;

	if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
	{
		struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

		right->initial_pitch = left->initial_pitch;

		/* Easy way out.. gotta calculate value */
		right->pitch_target = 0;
		right->volume_target = 0;
		right->FC_target = 0;

		right->byampl_env_sustain = 0x7f;
		right->byampl_env_decay = 0x7f;

		right->unSends.tSends.chorus_send = left->unSends.tSends.chorus_send;
		right->unSends.tSends.reverb_send = left->unSends.tSends.reverb_send;

		/* Left output of right channel is always zero */
		right->unSends.tSends.pan_send = 0;

		/* Update right channel aux */
		right->pan_target = 0;
		right->unSends.tSends.aux_send = left->unSends.tSends.aux_send;
		right->aux_target = right->unSends.tSends.aux_send;

		/* Zero out right output of left channel */
		left->unSends.tSends.aux_send = 0;
		left->aux_target = 0;
		
		/* Update right channel attenuation */
		right->initial_attn = left->initial_attn;
	}

	if (wave_out->state == CARDWAVE_STATE_STOPPED && wave_out->setpos)
	  startPosition = wave_out->position / (wave_out->wavexferbuf->is16bit + 1) / (wave_out->wavexferbuf->is_stereo + 1);
	else
	  startPosition = wave_out->wavexferbuf->stopposition;
		
	start = wave_out->emu_voice->voice_params.start;
	wave_out->emu_voice->voice_params.start += startPosition;
		
	DPD("CA is %x", wave_out->emu_voice->voice_params.start);

	if (sblive_voicePlaybackSetup(wave_out->emu_voice) != CTSTATUS_SUCCESS)
	  return CTSTATUS_ERROR;
	
	wave_out->emu_voice->voice_params.start = start;

	/* For playback, enable CLIE here */
/*	if (sblive_voiceEnableIrq(wave_out->emu_voice) != CTSTATUS_SUCCESS) 
	{
		sblive_voiceStop(wave_out->emu_voice);
		return CTSTATUS_ERROR;
	}
*/
	if (wave_out->emu_timer != NULL) 
	{
		u32 delay, bytespersec;
		
		bytespersec = wave_out->wave_fmt.channels * (wave_out->wave_fmt.bitspersample >> 3) * (wave_out->wave_fmt.samplingrate);
		delay = (48000 * wave_out->callbacksize) / bytespersec;
		
		sblive_timerSetMaxDelay(wave_out->emu_timer, delay / 2);
		
		if (sblive_timerEnable(wave_out->emu_timer) != CTSTATUS_SUCCESS) 
		{
			sblive_voiceStop(wave_out->emu_voice);
			return CTSTATUS_ERROR;
		}
	}

	/* Actual start */
	if (!wave_out->synchstart) 
	  if (sblive_voiceStart(wave_out->emu_voice) != CTSTATUS_SUCCESS)
	    return CTSTATUS_ERROR;		
	
	wave_out->state = CARDWAVE_STATE_STARTED;
	wave_out->setpos = FALSE;
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutStop                                            */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*            wave_out - pointer to the specified wave out instance         */
/*            ppending - for later use                                      */
/*                                                                          */
/* About    : stop wave transfer, disable IRQ.                              */
/****************************************************************************/
int sblive_waveoutStop(struct sblive_hw *sb_hw, struct wave_out *wave_out, u32 *pending)
{
	struct sblive_waveout *card_waveout = sb_hw->card_waveout;
	struct wave_xferbuf *wavexferbuf;
	u32 dummy;
	u32 samples = 32;
	u32 position;

	if (!wave_out) 
	  return CTSTATUS_INVALIDPARAM;

	wavexferbuf = wave_out->wavexferbuf;

	if (wave_out->state == CARDWAVE_STATE_STOPPING)
	  return CTSTATUS_SUCCESS;

	if (!wave_out->emu_voice) 
	{
		wave_out->state = CARDWAVE_STATE_STOPPED;
		return CTSTATUS_SUCCESS;
	}

	if (wave_out->state == CARDWAVE_STATE_STOPPED)
	  return CTSTATUS_SUCCESS;

	wave_out->state = CARDWAVE_STATE_STOPPING;

	sblive_waveoutGetXferSize(card_waveout, wave_out, &dummy, pending);
	
	/* Save the stop position */
	if (sblive_voiceGetControl(wave_out->emu_voice, CCCA_CURRADDR, &wave_out->wavexferbuf->stopposition) != CTSTATUS_SUCCESS) 
	{
		wave_out->wavexferbuf->stopposition = 0;
		return CTSTATUS_ERROR;
	}
	
	wave_out->wavexferbuf->stopposition -= wave_out->emu_voice->voice_params.start;

	/* Refer to voicemgr.c, CA is not started at zero.  We need to take this into account. */
	position = wave_out->wavexferbuf->stopposition * (wave_out->wavexferbuf->is16bit + 1) * (wave_out->wavexferbuf->is_stereo + 1);
		
	if (!wave_out->wavexferbuf->is16bit)
	  samples <<= 1;
		
	if (wave_out->wavexferbuf->is_stereo)
	  samples <<= 1;
		
	samples -= 4;
		
	if (position >= samples * (wave_out->wavexferbuf->is16bit + 1))
	  position -= samples * (wave_out->wavexferbuf->is16bit + 1);
	else
	  position += wave_out->wavexferbuf->xferbufsize - samples * (wave_out->wavexferbuf->is16bit + 1);
		
	wave_out->wavexferbuf->stopposition = position / (wave_out->wavexferbuf->is16bit + 1) / (wave_out->wavexferbuf->is_stereo + 1);
	
	DPD("sblive_waveoutstop, position is %x\n", wave_out->wavexferbuf->stopposition);

	/* Stop actual voice */
	if (sblive_voiceStop(wave_out->emu_voice) != CTSTATUS_SUCCESS)
	  return CTSTATUS_ERROR;
	
	/* For playback, disable CLIE here */
/*	if (sblive_voiceDisableIrq(wave_out->emu_voice) != CTSTATUS_SUCCESS)
	  return CTSTATUS_ERROR;
*/	
	if (wave_out->emu_timer != NULL)
	  if (sblive_timerDisable(wave_out->emu_timer) != CTSTATUS_SUCCESS)
	    return CTSTATUS_ERROR;
	
	wave_out->state = CARDWAVE_STATE_STOPPED;
	wave_out->process_id = 0;
	wave_out->setpos = FALSE;
	wave_out->position = 0;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutGetXferSize                                     */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*            wave_out - pointer to the specified wave out instance         */
/*                                                                          */
/* Output   : size - pointer to the transfer size                           */
/*            pending - pointer to the size to be transfered                */
/*                                                                          */
/* About    : get the size of data in bytes that the specified wave device  */
/*            can process at the time of this function is called.           */
/*                                                                          */
/* Note     : this transfer size returned is referring to user buffer.      */
/****************************************************************************/
int sblive_waveoutGetXferSize(struct sblive_waveout *card_waveout, struct wave_out *wave_out, u32 *size, u32 *pending)
{
	u32 curpos;
	struct wave_xferbuf * wavexferbuf = wave_out->wavexferbuf;
	unsigned long flags;
	
	extern spinlock_t sblive_spinlock;

	/* Get position of current address, this is in no. of bytes in play buffer */
	if (sblive_waveoutGetControl(card_waveout, wave_out, WAVECURPOS, &curpos) != CTSTATUS_SUCCESS)
	  return CTSTATUS_ERROR;

	spin_lock_irqsave(&sblive_spinlock, flags);

	if ((curpos > wavexferbuf->xferpos) || ((curpos == wavexferbuf->xferpos) && (wave_out->state == CARDWAVE_STATE_STARTED))
	    || ((curpos == wavexferbuf->xferpos) && (wavexferbuf->xferpos != 0) && (wave_out->state == CARDWAVE_STATE_STOPPED))) 
	{
		*size = curpos - wavexferbuf->xferpos;
		*pending = wavexferbuf->xferbufsize - *size;
	} else 
	{
		*pending = wavexferbuf->xferpos - curpos;
		*size = wavexferbuf->xferbufsize - *pending;
	}

	spin_unlock_irqrestore(&sblive_spinlock, flags);

	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_waveoutXferData                                      */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave object                  */
/*            wave_out - pointer to the specified wave out instance       */
/*            data - pointer to the data to be transfered                 */
/*            size - data size to be transfered(size in user buffer,      */
/*                      for 8-bit sample, this is different from play     */
/*                      buffer.                                           */
/*                                                                        */
/* Output   : size - data size transfered                                 */
/*                                                                        */
/* About    : transfer the data to/from the wave device.                  */
/**************************************************************************/
int sblive_waveoutXferData(struct sblive_waveout *card_waveout, struct wave_out *wave_out, u8 *data, u32 *size)
{
	struct wave_xferbuf * wavexferbuf = wave_out->wavexferbuf;
	u32 sizetocopy, sizetocopyNow, sizecopied;
	
	/* FIXME: Do we need spinlocks in here? */

	if (!data || !size)
	  return CTSTATUS_INVALIDPARAM;
	
	sizetocopy = min(wavexferbuf->xferbufsize, *size);
	sizecopied = 0;

	if (!sizetocopy) 
	{
		*size = 0;
		return CTSTATUS_SUCCESS;
	}

	while (sizecopied < sizetocopy) 
	{
		sizetocopyNow = sizetocopy - sizecopied;
		sizetocopyNow = min(sizetocopyNow, (wavexferbuf->xferbufsize - wavexferbuf->xferpos));
		
		copy_from_user(&wavexferbuf->xferbuffer[wavexferbuf->xferpos], &data[sizecopied], sizetocopyNow);
		
		wavexferbuf->xferpos += sizetocopyNow;
		wavexferbuf->xferpos %= wavexferbuf->xferbufsize;
		sizecopied += sizetocopyNow;
	}
	
	*size = sizetocopy;

	return CTSTATUS_SUCCESS;
}


/**************************************************************************/
/* Function : sblive_waveoutFillSilence                                   */
/*                                                                        */
/* Input    : card_waveout - pointer to card wave object                  */
/*            wave_out - pointer to the specified wave out instance       */
/*            size - data size to be filled                               */
/*                                                                        */
/* Output   : size - data size filled                                     */
/*                                                                        */
/* About    : fill silent data to play buffer,only used for wave output.  */
/**************************************************************************/
int sblive_waveoutFillSilence(struct sblive_waveout *card_waveout, struct wave_out *wave_out, u32 *size)
{
	struct wave_xferbuf *wavexferbuf = wave_out->wavexferbuf;
	u16 wFillData;
	u32 sizetocopy, sizetocopyNow, sizecopied;
	
	/* FIXME: Do we need spinlocks here? */

	if (!size)
	  return CTSTATUS_INVALIDPARAM;

	sizetocopy = min(wavexferbuf->xferbufsize, *size);
	sizecopied = 0;

	if (!sizetocopy) 
	{
		*size = 0;
		return CTSTATUS_SUCCESS;
	}
	
	if (wave_out->wave_fmt.bitspersample == 8)
	  wFillData = 0x8080;
	else
	  wFillData = 0x0000;

	while (sizecopied < sizetocopy) 
	{
		sizetocopyNow = sizetocopy - sizecopied;
		sizetocopyNow = min(sizetocopyNow, wavexferbuf->xferbufsize - wavexferbuf->xferpos);
		
		memset(&wavexferbuf->xferbuffer[wavexferbuf->xferpos], wFillData, sizetocopyNow);

		wavexferbuf->xferpos += sizetocopyNow;
		wavexferbuf->xferpos %= wavexferbuf->xferbufsize;
		sizecopied += sizetocopyNow;
	}
	
	*size = sizetocopy;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutSetControl                                      */
/*                                                                          */
/* Input    : pICardWave - pointer to card wave object                      */
/*            wave_out - pointer to the specified wave out instance         */
/*            ctrl_id - control type                                        */
/*            value - the value of the specified control to be set          */
/*                                                                          */
/* About    : set the specified control value of the wave device.           */
/****************************************************************************/
int sblive_waveoutSetControl(struct sblive_hw *sb_hw, struct wave_out *wave_out, u32 ctrl_id, u32 *control_value)
{
	struct sblive_waveout *card_waveout = sb_hw->card_waveout;
	struct wave_out *currinst = NULL;
	u32 value;
	struct voice_cntlset setting[6];
	unsigned long flags;

	if (card_waveout == NULL)
	  return CTSTATUS_ERROR;

	if (control_value)
	  value = *control_value;
	else
	  value = 0;

	switch (ctrl_id) 
	{
	case WAVEOBJVOLUME:
		return CTSTATUS_SUCCESS;

	case WAVEOBJMUTE:
		/* This is a general mute affecting all wave instances */
		card_waveout->mute = value;
		currinst = card_waveout->wave_outlist;

		if (value) 
		{
			/* Mute */
			card_waveout->globalvol = 0;
		} else 
		{
			/* Unmute */
			card_waveout->globalvol = (0xffff & card_waveout->left) | ((0xffff & card_waveout->right) << 16);

			set_volume(card_waveout);
		}

		return CTSTATUS_SUCCESS;

	case WAVEOBJREVERB:
		/* This is a general set reverb affecting all wave instances.
		 * Apps which can set the reverb of individual wave instances
		 * should use WAVEINSTANCEREVERB instead. */
		
		card_waveout->globalreverb = value;
		currinst = card_waveout->wave_outlist;

		while (currinst && currinst->emu_voice) 
		{
			struct voice_param * left = &currinst->emu_voice->voice_params;

			/* This flag allows a wave instance to be unaffected by changes to global volume. */
			if (currinst->globalreverbFactor) 
			{
				u8 t = (card_waveout->globalreverb & 0xff) + (currinst->localreverb & 0xff);
				left->unSends.tSends.reverb_send = (t > 255) ? 255 : t;

				if (currinst->wavexferbuf->is_stereo && currinst->emu_voice->linked_voice) 
				{
					struct voice_param *right = &currinst->emu_voice->linked_voice->voice_params;

					right->unSends.tSends.reverb_send = left->unSends.tSends.reverb_send;

					setting[0].paramID = PTRX_FXSENDAMOUNT_A;
					setting[0].value = right->unSends.tSends.reverb_send;
					
					sblive_voiceSetControl(currinst->emu_voice->linked_voice, setting, 1);
				}
				
				setting[0].paramID = PTRX_FXSENDAMOUNT_A;
				setting[0].value = left->unSends.tSends.reverb_send;
				
				sblive_voiceSetControl(currinst->emu_voice, setting, 1);
			}
			currinst = currinst->next;
		}
		return CTSTATUS_SUCCESS;

	case WAVEOBJCHORUS:
		/* This is a general set chorus affecting all wave instances.
		 * Apps which can set the chorus of individual wave instances
		 * should use WAVEINSTANCECHORUS instead. */
		
		card_waveout->globalchorus = value;
		currinst = card_waveout->wave_outlist;

		while (currinst && currinst->emu_voice) 
		{
			struct voice_param *left = &currinst->emu_voice->voice_params;

			if (currinst->globalchorusFactor) 
			{
				u8 t = (card_waveout->globalchorus & 0xff) + (currinst->localchorus & 0xff);
				left->unSends.tSends.chorus_send = (t > 255) ? 255 : t;

				if (currinst->wavexferbuf->is_stereo && currinst->emu_voice->linked_voice) 
				{
					struct voice_param *right = &currinst->emu_voice->linked_voice->voice_params;

					right->unSends.tSends.chorus_send = left->unSends.tSends.chorus_send;

					setting[0].paramID = DSL_FXSENDAMOUNT_D;
					setting[0].value = right->unSends.tSends.chorus_send;
					
					sblive_voiceSetControl(currinst->emu_voice->linked_voice, setting, 1);
				}
				setting[0].paramID = DSL_FXSENDAMOUNT_D;
				setting[0].value = left->unSends.tSends.chorus_send;
				
				sblive_voiceSetControl(currinst->emu_voice, setting, 1);
			}
			currinst = currinst->next;
		}
		return CTSTATUS_SUCCESS;

	case WAVESYNCHSTART:
		if (sblive_waveoutSynchStart(card_waveout, value) != CTSTATUS_SUCCESS)
		  return CTSTATUS_ERROR;
		return CTSTATUS_SUCCESS;

	default:
		break;
	}

	if (!wave_out)
	  return CTSTATUS_ERROR;

	switch (ctrl_id) 
	{
	case WAVECURPOS:
		if (wave_out->emu_voice == NULL)
		  return CTSTATUS_ERROR;

		if (wave_out->state == CARDWAVE_STATE_STOPPED) 
		{
			wave_out->setpos = TRUE;
			wave_out->position = value;
		} else 
		{
			struct voice_param * left = &wave_out->emu_voice->voice_params;
			/* struct wave_xferbuf * wavexferbuf = wave_out->wavexferbuf; */
				
			spin_lock_irqsave(&sb_hw->emu_lock, flags);

			if (wave_out->wave_fmt.bitspersample == 8)
			  value <<= 1;
			if (wave_out->wave_fmt.channels == 2)
			  value >>= 1;
			/* get no. of samples per channel */
			value >>= 1;
				
			/* if the position is already beyond the loop, return error. */
			/* WHY is this lock here ???? kabi@i.am */
			/* we could check this value before lock can't we ? */
			if (value >= (left->end - left->start)) 
			{
				spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
				return CTSTATUS_ERROR;
			}
				
			/* Set current position */
			setting[0].paramID = CCCA_CURRADDR;
			setting[0].value = value + left->start;
			sblive_voiceSetControl(wave_out->emu_voice, setting, 1);

			if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
			{
				setting[0].paramID = CCCA_CURRADDR;
				setting[0].value = value + wave_out->emu_voice->linked_voice->voice_params.start;
				
				sblive_voiceSetControl(wave_out->emu_voice->linked_voice, setting, 1);
			}
			spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
		}
		break;
		
	case WAVESTOPPOSITION:
		wave_out->wavexferbuf->stopposition = value;
		break;

	case WAVEWRITEPOINTER:
		/* For wave output device, set write pointer for a particular wave instance.
		 * For wave input device, set the offset of write pointer. */
		wave_out->wavexferbuf->xferpos = value;
		break;

	case WAVEINSTANCEVOLUME:
		/* This is a specific set volume affecting only 1 wave instance and can be only called by the owner.
		 * Apps which can set the volume of individual wave instances should use this message. */

		/* update wave instance volume */
		wave_out->localvol = value;

		if (wave_out->emu_voice != NULL) 
		{
                        u32 vol;
			struct voice_param * left = &wave_out->emu_voice->voice_params;

			vol = set_volume_instance(card_waveout, wave_out, left);

			if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
			{
				struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

				right->unSends.tSends.pan_send = 0; /* Left output of right channel is always zero */
				right->unSends.tSends.aux_send = left->unSends.tSends.aux_send; /* Update right channel aux */
				left->unSends.tSends.aux_send = 0; /* Zero out right output of left channel */
				right->initial_attn = 0xff & sumVolumeToAttenuation(vol * 2); /* Update right channel attenuation */

				setting[0].paramID = IFATN_ATTENUATION;
				setting[0].value = right->initial_attn;

				sblive_voiceSetControl(wave_out->emu_voice->linked_voice, setting, 1);
			}
			
			setting[0].paramID = IFATN_ATTENUATION;
			setting[0].value = left->initial_attn;

			sblive_voiceSetControl(wave_out->emu_voice, setting, 1);
		}
		break;


	case WAVESYNCHSETUP:
		wave_out->process_id = value;
		wave_out->synchstart = TRUE;
		break;

	case WAVESETFREQUENCY:
		if (wave_out->emu_voice != NULL) 
		{
			struct voice_param * left = &wave_out->emu_voice->voice_params;

			left->initial_pitch = (u16) (srToPitch(value) >> 8); /* Calculate pitch */

			setting[0].paramID = IP;
			setting[0].value = left->initial_pitch;
			sblive_voiceSetControl(wave_out->emu_voice, setting, 1);

			if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
			{
				struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

				right->initial_pitch = left->initial_pitch;
				setting[0].paramID = IP;
				setting[0].value = right->initial_pitch;
				
				sblive_voiceSetControl(wave_out->emu_voice->linked_voice, setting, 1);
			}
		}
		wave_out->wave_fmt.samplingrate = value;
		break;

	case WAVESTARTLOOP:
		if (wave_out->emu_voice != NULL) 
		{
			struct voice_param *left = &wave_out->emu_voice->voice_params;

			setting[0].paramID = PSST_LOOPSTARTADDR;
			setting[0].value = left->startloop;
			
			sblive_voiceSetControl(wave_out->emu_voice, setting, 1);

			if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
			{
				struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

				setting[0].paramID = PSST_LOOPSTARTADDR;
				setting[0].value = right->startloop;
				
				sblive_voiceSetControl(wave_out->emu_voice->linked_voice, setting, 1);
			}
		}
		break;

	case WAVEINSTANCEFORMAT:
		return sblive_waveoutSetFormat(sb_hw, wave_out, (struct wave_format *) control_value);
	case WAVEVOLFACTOR:
		wave_out->globalvolFactor = value;
		break;

	case WAVEREVERBFACTOR:
		wave_out->globalreverbFactor = value;
		break;

	case WAVECHORUSFACTOR:
		wave_out->globalchorusFactor = value;
		break;

	case WAVESETSTARTFLAG:
		if (wave_out->playflags != value && (wave_out->state == CARDWAVE_STATE_STARTED)) 
		{
			struct sblive_hw *sb_hw = wave_out->emu_voice->sb_hw;

			if (value & CARDWAVE_PLAY_LOOPING) 
			{
				halClearStopOnLoop(sb_hw, wave_out->emu_voice->voicenum);
				if (wave_out->emu_voice->linked_voice)
				  halClearStopOnLoop(sb_hw, wave_out->emu_voice->linked_voice->voicenum);
			} else 
			{
				halSetStopOnLoop(sb_hw, wave_out->emu_voice->voicenum);
				if (wave_out->emu_voice->linked_voice)
				  halSetStopOnLoop(sb_hw, wave_out->emu_voice->linked_voice->voicenum);
			}
		}
		wave_out->playflags = value;
		break;

	case WAVESETSTOPONLOOP:
		if (wave_out->state == CARDWAVE_STATE_STARTED)
		{
			struct sblive_hw * sb_hw = wave_out->emu_voice->sb_hw;
			u32 CA, offset, endloop;
			struct voice_param * left = &wave_out->emu_voice->voice_params;
			struct voice_cntlset setting[3];

			CA = sblive_readptr(sb_hw, CCCA_CURRADDR, wave_out->emu_voice->voicenum) - left->startloop;
			offset = (CA / wave_out->callbacksize) * wave_out->callbacksize;
			
			if (CA % wave_out->callbacksize)
			  offset += wave_out->callbacksize;
			
			if (CA > (offset - 32))
			  offset += 32;

			offset = CA;
			endloop = offset + left->startloop;
			setting[0].paramID = DSL_LOOPENDADDR;
			setting[0].value = endloop;
			
			sblive_voiceSetControl(wave_out->emu_voice, setting, 1);

			if (wave_out->wavexferbuf->is_stereo && wave_out->emu_voice->linked_voice) 
			{
				struct voice_param *right = &wave_out->emu_voice->linked_voice->voice_params;

				endloop = offset + right->startloop;
				setting[0].paramID = DSL_LOOPENDADDR;
				setting[0].value = endloop;
				
				sblive_voiceSetControl(wave_out->emu_voice, setting, 1);
			}
			
			halSetStopOnLoop(sb_hw, wave_out->emu_voice->voicenum);
			
			if (wave_out->emu_voice->linked_voice)
			  halSetStopOnLoop(sb_hw, wave_out->emu_voice->linked_voice->voicenum);
		}
		break;

	default:
		return CTSTATUS_NOTSUPPORTED;
	}

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutGetControl                                      */
/*                                                                          */
/* Input    : card_waveout - pointer to card wave object                    */
/*            wave_out - pointer to the specified wave out instance         */
/*            ctrl_id - control type                                        */
/*                                                                          */
/* Output   : value - pointer to the control value                          */
/*                                                                          */
/* About    : get the specified control value of the wave device.           */
/****************************************************************************/
int sblive_waveoutGetControl(struct sblive_waveout *card_waveout, struct wave_out *wave_out, u32 ctrl_id, u32 *value)
{
	switch (ctrl_id) 
	{
	case WAVEOBJVOLUME:
		*value = card_waveout->globalvol;
		return CTSTATUS_SUCCESS;

	case WAVEOBJREVERB:
		*value = card_waveout->globalreverb;
		return CTSTATUS_SUCCESS;

	case WAVEOBJCHORUS:
		*value = card_waveout->globalchorus;
		return CTSTATUS_SUCCESS;

	case WAVEQUERYACTIVEINST:
		if (card_waveout->wave_outlist != NULL)
		  return CTSTATUS_SUCCESS;
		else
		  return CTSTATUS_ERROR;

	default:
		break;
	}

	if (!wave_out)
	  return CTSTATUS_ERROR;

	switch (ctrl_id) 
	{
	case WAVEINSTANCEVOLUME:
		*value = wave_out->localvol;
		return CTSTATUS_SUCCESS;

	case WAVEINSTANCEREVERB:
		*value = wave_out->localreverb;
		return CTSTATUS_SUCCESS;

	case WAVEINSTANCECHORUS:
		*value = wave_out->localchorus;
		return CTSTATUS_SUCCESS;

	case WAVECURPOS:
		if (wave_out->emu_voice != NULL) 
		{
			/* There is no actual start yet */
			if (wave_out->state == CARDWAVE_STATE_STOPPED || wave_out->state == CARDWAVE_STATE_SUSPEND) 
			{
				if (wave_out->setpos)
				  *value = wave_out->position;
				else 
				  *value = wave_out->wavexferbuf->stopposition * (wave_out->wavexferbuf->is_stereo + 1) * (wave_out->wavexferbuf->is16bit + 1);
			} else 
			{
				if (sblive_voiceGetControl(wave_out->emu_voice, CCCA_CURRADDR, value) != CTSTATUS_SUCCESS)
				  return CTSTATUS_ERROR;

				*value -= wave_out->emu_voice->voice_params.start;

				/* Get number of bytes in play buffer per channel. 
				 * If 8 bit mode is enabled, this needs to be changed. */
				*value <<= 1;
				{
					u32 samples = 32; 

					*value *= (wave_out->wavexferbuf->is_stereo + 1);
					*value /= (2 - wave_out->wavexferbuf->is16bit);
					
					/* Refer to voicemgr.c, CA is not started at zero.
					 * We need to take this into account. */
					
					if (!wave_out->wavexferbuf->is16bit)
					  samples <<= 1;
					
					if (wave_out->wavexferbuf->is_stereo)
					  samples <<= 1;
					
					samples -= 4;
					
					if (*value >= samples * (wave_out->wavexferbuf->is16bit + 1))
					  *value -= samples * (wave_out->wavexferbuf->is16bit + 1);
					else
					  *value += wave_out->wavexferbuf->xferbufsize - samples * (wave_out->wavexferbuf->is16bit + 1);
				}
			}
		} else
		  return CTSTATUS_ERROR;
		break;

	case WAVESTARTLOOP:
		if (wave_out->emu_voice == NULL)
		  return CTSTATUS_ERROR;

		if (sblive_voiceGetControl(wave_out->emu_voice, PSST_LOOPSTARTADDR, value) != CTSTATUS_SUCCESS)
		  return CTSTATUS_ERROR;

		*value -= wave_out->emu_voice->voice_params.start;

		/* Get number of bytes in play buffer per channel.
		 * If 8 bit mode is enabled, this needs to be changed. */
		*value <<= 1;
		*value *= (wave_out->wavexferbuf->is_stereo + 1);
		*value /= (2 - wave_out->wavexferbuf->is16bit);
		break;

	case WAVEENDLOOP:
		if (wave_out->emu_voice == NULL)
		  return CTSTATUS_ERROR;

		if (sblive_voiceGetControl(wave_out->emu_voice, DSL_LOOPENDADDR, value) != CTSTATUS_SUCCESS)
		  return CTSTATUS_ERROR;
		
		*value -= wave_out->emu_voice->voice_params.start;

		/* Get number of bytes in play buffer per channel.
		 * Iif 8 bit mode is enabled, this needs to be changed. */
		*value <<= 1;
		*value *= (wave_out->wavexferbuf->is_stereo + 1);
		*value /= (2 - wave_out->wavexferbuf->is16bit);
		break;

	case WAVEWRITEPOINTER:
		/* Get write pointer for a particular wave instance */
		*value = wave_out->wavexferbuf->xferpos;
		break;

	default:
		return CTSTATUS_NOTSUPPORTED;
	}

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* Function : sblive_waveoutIrqCallback                                     */
/*                                                                          */
/* Input    : event - event that cause the callback                         */
/*            refdata - reference data for this callback                    */
/*            param - parameter used for this callback                      */
/*                                                                          */
/* About    : wave IRQ callback function.                                   */
/****************************************************************************/
int sblive_waveoutIrqCallback(unsigned long event, unsigned long refdata, unsigned long param)
{
	struct wave_out * wave_out = (struct wave_out *) refdata;

	if (wave_out->state == CARDWAVE_STATE_STOPPED)
	  return CTSTATUS_SUCCESS;

	if (event == VOICECALLBACK_EVENT_CALLBACKSIZEREACHED) 
	{
		if (!wave_out->dsDpc.is_active) 
		{
			wave_out->dsDpc.is_active = TRUE;
			wave_out->dsDpc.param = CARDWAVE_EVENT_CALLBACKSIZEREACHED;
			osScheduleDPC(&wave_out->dsDpc);
		}
		return CTSTATUS_SUCCESS;
	}

	return CTSTATUS_INVALIDPARAM;
}

int sblive_waveoutTimerCallback(unsigned long event, unsigned long refdata, unsigned long param)
{
	struct wave_out * wave_out = (struct wave_out *) refdata;

	if (wave_out->state == CARDWAVE_STATE_STOPPED)
	  return CTSTATUS_SUCCESS;

	if (!wave_out->dsDpc.is_active) 
	{
		wave_out->dsDpc.is_active = TRUE;
		wave_out->dsDpc.param = CARDWAVE_EVENT_TIMER;
		osScheduleDPC(&wave_out->dsDpc);
	}
	
	return CTSTATUS_SUCCESS;
}

int sblive_waveoutDpcCallback(unsigned long refdata, unsigned long param1, unsigned long param2)
{
	struct wave_out * wave_out = (struct wave_out *) refdata;

	wave_out->dsDpc.is_active = FALSE;

	if (wave_out->CallbackFn != NULL)
	  wave_out->CallbackFn(param1, wave_out->refdata, 0);

	return CTSTATUS_SUCCESS;
}
