//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft end-user
// license agreement (EULA) under which you licensed this SOFTWARE PRODUCT.
// If you did not accept the terms of the EULA, you are not authorized to use
// this source code. For a copy of the EULA, please see the LICENSE.RTF on your
// install media.
//
/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

   
Module Name:	HWCTXT.CPP

Abstract:		Platform dependent code for the mixing audio driver.

Notes:			The following file contains all the hardware specific code
				for the mixing audio driver.  This code's primary responsibilities
				are:

					* Initialize audio hardware (including codec chip)
					* Schedule DMA operations (move data from/to buffers)
					* Handle audio interrupts

				All other tasks (mixing, volume control, etc.) are handled by the "upper"
				layers of this driver.

				****** IMPORTANT ******
				Presently, audio input (i.e. RECORD functionality) IS NOT implemented.  On 
				this platform, in order to record the TLV320AIC codec chip needs to be 
				configured as MASTER and the CPU's I2S controller configured as SLAVE.  
				Unfortunately, a hardware bug in the current Samsung CPU samples prevents 
				SLAVE mode from working properly.  As a result, only audio playback is 
				supported by configuring the CPU I2S controller as MASTER and the audio
				codec chip as SLAVE.  
				
				The final revision of the Samsung SC2410 CPU is expected to fix this 
				hardware bug.  Once this issue is resolved, the CPU's I2S controller 
				(look at I2S.CPP) should be configured in SLAVE mode and the audio codec 
				chip (see InitCodec() below) should be configured as MASTER.  With these
				changes, the following routines need to be implemented:

					InitInputDMA()	- initialize the input DMA channel
					StartInputDMA()	- starts DMA operations on the input DMA channel
					StopInputDMA()	- stops any current DMA operations on the input DMA channel

				For the SC2410 CPU, DMA channel 2 can be used for both input and output.  In this,
				configuration, however, only one type operation (input or output) can execute.  In 
				order to implement simultaneous playback and recording, two things must be done:
 
					1) Input DMA should be moved to DMA Channel 1; Output DMA still uses DMA Channel 2.
					2) Step #3 in InterruptThread() needs to be implemented so that the DMA interrupt
					   source (input DMA or output DMA?) can be determined.  The interrupt source needs
					   to be determined so that the appropriate buffers can be copied (Steps #4,#5...etc.).

				Lastly, the m_OutputDMAStatus and m_InputDMAStatus variables shouldn't need to be modified.  
				The logic surrounding these drivers is simply used to determine which buffer (A or B) needs
				processing.

Environment:	PocketPC Catfish Reference Platform:

				Hardware - Samsung SC2410 CPU
						   TI TLV320AIC audio codec chip where: 
								1) SPI bus is used for control 
								2) I2S bus is used for data (CPU is master
								   and codec chip is slave)

				Software - Windows CE 3.0 and later.    
-*/

#include "wavemain.h"
#include <p2.h>
#include "s2440.h"
#include "dma.h"
#include "I2S.h"
#include "utldrv.h"
#include "hwctxt.h"

//#define DMA_FLAG 1

//#define DEBUGMSG(a,b)	RETAILMSG(0,b)

#define DMA_CH_MIC 2
#define DMA_CH_OUT 1

#define DELAY_COUNT	0x100000

#define L3M (0x04)	// TOUT2	// charlie
#define L3C (0x10)	// TCLK0
#define L3D (0x08)	// TOUT3

//----- Macro used to send commands to the audio codec chip over the SPI bus -----
//		NOTE: The command format is 16 bits:		bits[15-9]	= register
//													bits[8-0]	= data command
//
//		Refer to the TILV320AC reference guide for details.
//
//#define SEND_CODEC_COMMAND(reg, dat)	{ SPI_SendWord((reg | dat));  }

int rec_mode=0;
//-------------------------------- Global Variables --------------------------------------
volatile IISreg		*v_pIISregs		= NULL;		// I2S control registers

volatile IOPreg		*v_pIOPregs		= NULL;		// GPIO registers (needed to enable I2S and SPI)
volatile DMAreg		*v_pDMAregs		= NULL;		// DMA registers (needed for I/O on I2S bus)
volatile CLKPWRreg	*g_pCLKPWRreg	= NULL;		// Clock power registers (needed to enable I2S and SPI clocks)
volatile INTreg 	*s2440INT 		= NULL;
				
UTL_FASTCALL    g_tblFastCall;							// Needed for fast driver->driver calling mechanism
HANDLE          g_hUTLObject		= INVALID_HANDLE_VALUE;

HardwareContext *g_pHWContext		= NULL;
unsigned int delay_count;
//----------------------------------------------------------------------------------------

DBGPARAM dpCurSettings = {
    TEXT("CONSOLE"), {
        TEXT("0"),TEXT("1"),TEXT("2"),TEXT("3"),
        TEXT("4"),TEXT("5"),TEXT("6"),TEXT("7"),
        TEXT("8"),TEXT("9"),TEXT("10"),TEXT("11"),
        TEXT("12"),TEXT("Function"),TEXT("Init"),TEXT("Error")},
    0x8000  // Errors only, by default
}; 

void dump_audio_input_sfr(void);

// charlie
void _WrL3Addr(unsigned char data)
{	
    int i,j;

    v_pIOPregs->rGPBDAT &= ~(L3D|L3M|L3C);		//L3D=L/L3M=L(in address mode)/L3C=L
    v_pIOPregs->rGPBDAT |= L3C;	                //L3C=H

    for(j=0;j<10;j++);		                    //tsu(L3) > 190ns
    
    //PD[8:6]=L3D:L3C:L3M
    for(i=0;i<8;i++)	                        //LSB first
    {
	if(data&0x1)	                            //if data's LSB is 'H'
	{
	    v_pIOPregs->rGPBDAT &= ~L3C;	            //L3C=L
	    v_pIOPregs->rGPBDAT |= L3D;	            //L3D=H		    
	    for(j=0;j<10;j++);	                    //tcy(L3) > 500ns
	    v_pIOPregs->rGPBDAT |= L3C;	            //L3C=H
	    v_pIOPregs->rGPBDAT |= L3D;	            //L3D=H
	    for(j=0;j<10;j++);	                    //tcy(L3) > 500ns
	}
	else		                                //if data's LSB is 'L'
	{
	    v_pIOPregs->rGPBDAT &= ~L3C;	            //L3C=L
	    v_pIOPregs->rGPBDAT &= ~L3D;	            //L3D=L
	    for(j=0;j<10;j++);	                    //tcy(L3) > 500ns
	    v_pIOPregs->rGPBDAT |= L3C;	            //L3C=H
	    v_pIOPregs->rGPBDAT &= ~L3D;	            //L3D=L
	    for(j=0;j<10;j++);	                    //tcy(L3) > 500ns
	}
	data >>=1;
    }
    v_pIOPregs->rGPBDAT|=L3C|L3M;	            //L3M=H,L3C=H
}


void _WrL3Data(unsigned char data,int halt)
{
    int i,j;

    if(halt)
    {
        v_pIOPregs->rGPBDAT|=L3C;	            //L3C=H(while tstp, L3 interface halt condition)
        for(j=0;j<10;j++);                       //tstp(L3) > 190ns
    }

    v_pIOPregs->rGPBDAT|=L3C|L3M;	            //L3M=H(in data transfer mode)	
    for(j=0;j<10;j++);	                        //tsu(L3)D > 190ns

    //PD[8:6]=L3D:L3C:L3M
    for(i=0;i<8;i++)
    {
        if(data&0x1)	                        //if data's LSB is 'H'
        {
	    v_pIOPregs->rGPBDAT &= ~L3C;	            //L3C=L
            v_pIOPregs->rGPBDAT |= L3D;	        //L3D=H
            for(j=0;j<10;j++);	                //tcy(L3) > 500ns
            v_pIOPregs->rGPBDAT |= (L3C|L3D);    //L3C=H,L3D=H
            for(j=0;j<10;j++);	                //tcy(L3) > 500ns
        }
        else		//if data's LSB is 'L'
        {
	    v_pIOPregs->rGPBDAT &= ~L3C;	            //L3C=L
	    v_pIOPregs->rGPBDAT &= ~L3D;	            //L3D=L
            for(j=0;j<10;j++);	                //tcy(L3) > 500ns
            v_pIOPregs->rGPBDAT |= L3C;	        //L3C=H
	    v_pIOPregs->rGPBDAT &= ~L3D;	            //L3D=L
            for(j=0;j<10;j++);	                //tcy(L3) > 500ns
        }
        data>>=1;	//for check next bit
    }
    
    v_pIOPregs->rGPBDAT|=L3C|L3M;	            //L3M=H,L3C=H
}

BOOL HardwareContext::CreateHWContext(DWORD Index)
{
    if (g_pHWContext)
    {
        return TRUE;
    }

    g_pHWContext = new HardwareContext;
    if (!g_pHWContext)
    {
        return FALSE;
    }

    return g_pHWContext->Init(Index);
}

HardwareContext::HardwareContext()
: m_InputDeviceContext(), m_OutputDeviceContext()
{
    InitializeCriticalSection(&m_Lock);
    m_Initialized=FALSE;
}

HardwareContext::~HardwareContext()
{
    DeleteCriticalSection(&m_Lock);
}

BOOL HardwareContext::Init(DWORD Index)
{
    if (m_Initialized)
    {
        return FALSE;
    }

	//----- 1. Initialize the state/status variables -----
    m_DriverIndex		= Index;
    m_IntrAudio         = SYSINTR_AUDIO;
    m_InPowerHandler    = FALSE;
    m_InputDMARunning   = FALSE;
    m_OutputDMARunning  = FALSE;
	m_InputDMAStatus	= DMA_CLEAR;				
	m_OutputDMAStatus	= DMA_CLEAR;				

    //----- 2. Map the necessary descriptory channel and control registers into the driver's virtual address space -----
	if(!MapRegisters())
	{
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVEDEV.DLL:HardwareContext::Init() - Failed to map config registers.\r\n")));
        goto Exit;
	}

    //----- 3. Map the DMA buffers into driver's virtual address space -----
    if(!MapDMABuffers())
    {
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVEDEV.DLL:HardwareContext::Init() - Failed to map DMA buffers.\r\n")));
        goto Exit;
    }

    //----- 4. Configure the Codec -----
    InitCodec();
    
	//----- 5. Initialize the interrupt thread -----
    if (!InitInterruptThread())
    {
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVEDEV.DLL:HardwareContext::Init() - Failed to initialize interrupt thread.\r\n")));
        goto Exit;
    }
    m_Initialized=TRUE;

    //
    // Power Manager expects us to init in D0.
    // We are normally in D4 unless we are opened for play.
    // Inform the PM.
    //
    m_Dx = D0;
    DevicePowerNotify(_T("WAV1:"),(_CEDEVICE_POWER_STATE)D4, POWER_NAME);
    
Exit:
    return m_Initialized;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		MapRegisters()

Description:	Maps the config registers used by both the SPI and
				I2S controllers.

Notes:			The SPI and I2S controllers both use the GPIO config
				registers, so these MUST be initialized FIRST.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::MapRegisters()
{
    DWORD dwBytes;
    
    v_pIISregs = (volatile IISreg *)VirtualAlloc(0,sizeof(IISreg),MEM_RESERVE, PAGE_NOACCESS);
	if (!v_pIISregs)
	{
		DEBUGMSG(1,(TEXT("IISreg: VirtualAlloc failed!\r\n")));
		return FALSE;
	}
	if (!VirtualCopy((PVOID)v_pIISregs,(PVOID)(IIS_BASE),sizeof(IISreg), PAGE_READWRITE | PAGE_NOCACHE ))
	{
		DEBUGMSG(1,(TEXT("IISreg: VirtualCopy failed!\r\n")));
		return FALSE;
	}

	v_pIOPregs = (volatile IOPreg *)VirtualAlloc(0,sizeof(IOPreg),MEM_RESERVE, PAGE_NOACCESS);
	if (!v_pIOPregs)
	{
		DEBUGMSG(1,(TEXT("IOPreg: VirtualAlloc failed!\r\n")));
		return FALSE;
	}
	if (!VirtualCopy((PVOID)v_pIOPregs,(PVOID)(IOP_BASE),sizeof(IOPreg), PAGE_READWRITE | PAGE_NOCACHE ))
	{
		DEBUGMSG(1,(TEXT("IOPreg: VirtualCopy failed!\r\n")));
		return FALSE;
	}

	v_pDMAregs = (volatile DMAreg *)VirtualAlloc(0,sizeof(DMAreg),MEM_RESERVE, PAGE_NOACCESS);
	if (!v_pDMAregs)
	{
		DEBUGMSG(1,(TEXT("DMAreg: VirtualAlloc failed!\r\n")));
		return FALSE;
	}
	if (!VirtualCopy((PVOID)v_pDMAregs,(PVOID)(DMA_BASE),sizeof(DMAreg), PAGE_READWRITE | PAGE_NOCACHE ))
	{
		DEBUGMSG(1,(TEXT("DMAreg: VirtualCopy failed!\r\n")));
		return FALSE;
	}

	s2440INT = (volatile INTreg *)VirtualAlloc(0,sizeof(INTreg),MEM_RESERVE, PAGE_NOACCESS);
	if (!v_pDMAregs)
	{
		DEBUGMSG(1,(TEXT("INTreg: VirtualAlloc failed!\r\n")));
		return FALSE;
	}
	if (!VirtualCopy((PVOID)s2440INT,(PVOID)(INT_BASE),sizeof(INTreg), PAGE_READWRITE | PAGE_NOCACHE ))
	{
		DEBUGMSG(1,(TEXT("INTreg: VirtualCopy failed!\r\n")));
		return FALSE;
	}

	g_pCLKPWRreg = (volatile CLKPWRreg *)VirtualAlloc(0,sizeof(CLKPWRreg),MEM_RESERVE, PAGE_NOACCESS);
	if (!g_pCLKPWRreg)
	{
		DEBUGMSG(1,(TEXT("DMAreg: VirtualAlloc failed!\r\n")));
		return FALSE;
	}
	if (!VirtualCopy((PVOID)g_pCLKPWRreg,(PVOID)(CLKPWR_BASE),sizeof(CLKPWRreg), PAGE_READWRITE | PAGE_NOCACHE ))
	{
		DEBUGMSG(1,(TEXT("DMAreg: VirtualCopy failed!\r\n")));
		return FALSE;
	}
   
    PowerUp();
	return TRUE;

MAP_ERROR:
	return FALSE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		Deinit()

Description:	Deinitializest the hardware: disables DMA channel(s), 
				clears any pending interrupts, powers down the audio
				codec chip, etc.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::Deinit()
{
	//----- 1. Disable the input/output channels -----
//	AUDIO_IN_DMA_DISABLE();
//	v_pIISregs->rIISCON &= ~RECEIVE_DMA_REQUEST_ENABLE;	
//	v_pIISregs->rIISFCON &= ~( RECEIVE_FIFO_ACCESS_DMA  | RECEIVE_FIFO_ENABLE);	
//	v_pDMAregs->rDMASKTRIG1 |= STOP_DMA_TRANSFER;
//	v_pDMAregs->rDMASKTRIG1 &= ~ENABLE_DMA_CHANNEL;;
	
	//AUDIO_OUT_DMA_DISABLE();
	v_pDMAregs->rDMASKTRIG2 |= STOP_DMA_TRANSFER;
	v_pDMAregs->rDMASKTRIG2 &= ~ENABLE_DMA_CHANNEL;

	//----- 2. Disable/clear DMA input/output interrupts -----
	//AUDIO_IN_CLEAR_INTERRUPTS();
	v_pDMAregs->rDCON1 = v_pDMAregs->rDCON1;
	
	//AUDIO_OUT_CLEAR_INTERRUPTS();
	v_pDMAregs->rDCON2 = v_pDMAregs->rDCON2;

	//----- 3. Turn the audio hardware off -----
    AudioMute(DMA_CH_OUT | DMA_CH_MIC, TRUE);

    //----- 4. Unmap the control registers and DMA buffers -----
    UnmapRegisters();
	UnmapDMABuffers();

    return TRUE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		UnmapRegisters()

Description:	Unmaps the config registers used by both the SPI and
				I2S controllers.

Notes:			The SPI and I2S controllers both use the GPIO config
				registers, so these MUST be deinitialized LAST.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::UnmapRegisters()
{
	//----- 1. Free the fast driver-->driver calling mechanism object -----
	if(g_hUTLObject) 
	{
        CloseHandle(g_hUTLObject);
	}

	return TRUE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		MapDMABuffers()

Description:	Maps the DMA buffers used for audio input/output
				on the I2S bus.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::MapDMABuffers()
{
    BOOL bSuccess=FALSE;

	PBYTE pTemp;

    //----- 1. Allocate a block of virtual memory big enough to hold the DMA buffers -----
    if(!(pTemp = (PBYTE)VirtualAlloc(0, AUDIO_DMA_PAGE_SIZE * 4, MEM_RESERVE, PAGE_NOACCESS)))
	{
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVEDEV.DLL:HardwareContext::MapDMABuffers() - Unable to allocate memory for DMA buffers!\r\n")));
		goto MAP_ERROR;
	}

    //----- 2. Map the physical DMA buffer to the virtual address we just allocated -----
	if(!VirtualCopy((LPVOID)pTemp, (LPVOID)AUDIO_DMA_BUFFER_BASE, (AUDIO_DMA_PAGE_SIZE * 4), 
					PAGE_READWRITE | PAGE_NOCACHE))
	{
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVEDEV.DLL:HardwareContext::MapDMABuffers() - VirtualCopy() failed when binding DMA buffers.\r\n")));
		goto MAP_ERROR;
    }

	//----- 3. Setup the DMA page pointers -----
	//		   NOTE: Currently, input and output each have two DMA pages; these pages are used in a round-robin
	//				 fashion so that the OS can read/write one buffer while the audio codec chip read/writes the
	//				 other buffer.
    m_Output_pbDMA_PAGES[0] = pTemp;
    m_Output_pbDMA_PAGES[1] = pTemp + AUDIO_DMA_PAGE_SIZE;
    m_Input_pbDMA_PAGES[0]  = pTemp + 2*AUDIO_DMA_PAGE_SIZE;
    m_Input_pbDMA_PAGES[1]  = pTemp + 3*AUDIO_DMA_PAGE_SIZE;

    return TRUE;
	
MAP_ERROR:
	if(pTemp)
		VirtualFree(pTemp, 0, MEM_RELEASE);

	return FALSE;
}



/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		UnmapDMABuffers()

Description:	Unmaps the DMA buffers used for audio input/output
				on the I2S bus.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::UnmapDMABuffers()
{
	if(m_Output_pbDMA_PAGES[0])
	{
		VirtualFree((PVOID)m_Output_pbDMA_PAGES[0], 0, MEM_RELEASE);
	}

	return TRUE;
}

BOOL HardwareContext::Codec_channel()
{
    //****** Port B Initialize *****
    v_pIOPregs->rGPBDAT |= L3M|L3C;	//start condition : L3M=H, L3C=H
    v_pIOPregs->rGPBUP  |= 0x1c;	//pull-up disable
    v_pIOPregs->rGPBCON = ((v_pIOPregs->rGPBCON & 0x3ffc0f) | 0x000150);
	
	_WrL3Addr(0x14+2); //STATUS (000101xx+10)    
	if( m_InputDMARunning & m_OutputDMARunning )
	    _WrL3Data(0xa3,0); //1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon
	else if( m_InputDMARunning )
		_WrL3Data(0xa2,0); //1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon
	else if( m_OutputDMARunning )
		_WrL3Data(0xa1,0); //1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon
	else
    	_WrL3Data(0xa0,0); //1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon

    return TRUE;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		InitCodec()

Description:	Initializes the audio codec chip.

Notes:			The audio codec chip is intialized for output mode
				but powered down.  To conserve battery life, the chip
				is only powered up when the user starts playing a 
				file.

				Specifically, the powerup/powerdown logic is done 
				in the AudioMute() function.  If either of the 
				audio channels are unmuted, then the chip is powered
				up; otherwise the chip is powered own.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::InitCodec()
{
	int i;
//	int mode =0;	// play
	
	DEBUGMSG(1,(TEXT("+++InitCodec\n")));
	
    //****** Port B Initialize *****
    v_pIOPregs->rGPBDAT |= L3M|L3C;	//start condition : L3M=H, L3C=H
    v_pIOPregs->rGPBUP  |= 0x1c;	//pull-up disable
    v_pIOPregs->rGPBCON = ((v_pIOPregs->rGPBCON & 0x3ffc0f) | 0x000150);
	
    /****** L3 Interface ******/
#if (AUDIO_CODEC_CLOCK == 256)		// test value
	RETAILMSG(1,(TEXT("256 clock\n")));
    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0x60,0); //0,1,10,000,0	: reset,256fs,no DCfilter,iis

    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0x20,0); //0,0,10,000,0	: no reset,256fs,no DCfilter,iis
    
    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0xa1,0); //1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon
#else
	RETAILMSG(1,(TEXT("384 clock\n")));
    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0x50,0); //0,1,01,000,0	: reset,384fs,no DCfilter,iis

    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0x10,0); //0,0,01,000,0	: no reset,384fs,no DCfilter,iis
    
    _WrL3Addr(0x14+2); //STATUS (000101xx+10)
    _WrL3Data(0xa1,0); ///1,0,0,0,0,0,01	: OGS=0,IGS=0,ADC_NI,DAC_NI,sngl speed,AoffDon
#endif
	_WrL3Addr(0x14 + 0);     //DATA0 (000101xx+00)
	_WrL3Data(0xc2,0);       //11000,010  : DATA0, Extended addr(010) 
	_WrL3Data(0x4d,0);       //010,011,01 : DATA0, MS=9dB, Ch1=on Ch2=off, 

	return TRUE;
}



/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		InitOutputDMA()

Description:	Initializes the DMA channel for output.

Notes:			DMA Channel 2 is used for transmitting output sound
				data from system memory to the I2S controller.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::InitOutputDMA()
{
	int tmp;
	
	//----- 1. Initialize the DMA channel for output mode and use the first output DMA buffer -----
    v_pDMAregs->rDISRC2	= (int)(AUDIO_DMA_BUFFER_PHYS); 
    v_pDMAregs->rDISRCC2 &= ~(SOURCE_PERIPHERAL_BUS | FIXED_SOURCE_ADDRESS);				// Source is system bus, increment addr

    //----- 2. Initialize the DMA channel to send data over the I2S bus -----
    v_pDMAregs->rDIDST2	= (int)IISFIF_PHYS; 
    v_pDMAregs->rDIDSTC2 |= (DESTINATION_PERIPHERAL_BUS | FIXED_DESTINATION_ADDRESS);	// Dest is  periperal bus, fixed addr

	//----- 3. Configure the DMA channel's transfer characteristics: handshake, sync PCLK, interrupt, -----
	//		   single tx, single service, I2SSDO, I2S request, no auto-reload, half-word, tx count
	v_pDMAregs->rDCON2	= (  HANDSHAKE_MODE | GENERATE_INTERRUPT | I2SSDO_DMA2 | DMA_TRIGGERED_BY_HARDWARE 
						   | TRANSFER_HALF_WORD | (AUDIO_DMA_PAGE_SIZE / 2 ) );
	
	//----- 4. Reset the playback pointers -----
	//AUDIO_RESET_PLAYBACK_POINTER();
	ioPlaybackPointerLow  = (AUDIO_DMA_BUFFER_PHYS);
	ioPlaybackPointerHigh = (AUDIO_DMA_BUFFER_PHYS + AUDIO_DMA_PAGE_SIZE);
	
	DEBUGMSG(1,(TEXT("---InitOutputDMA\n")));

	return TRUE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		StartOutputDMA()

Description:	Starts outputting the sound data to the audio codec
				chip via DMA.

Notes:			Currently, both playback and record share the same
				DMA channel.  Consequently, we can only start this
				operation if the input channel isn't using DMA.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::StartOutputDMA()
{
	//RETAILMSG(1,(TEXT("+++StartOutputDMA\n")));
	
	if(!m_OutputDMARunning && (m_Dx == D0) )
//	if(!m_OutputDMARunning)
    {
        //----- 1. Initialize our buffer counters -----
        m_OutputDMARunning=TRUE;
        m_OutBytes[OUT_BUFFER_A]=m_OutBytes[OUT_BUFFER_B]=0;

        //----- 2. Prime the output buffer with sound data -----
		m_OutputDMAStatus = (DMA_DONEA | DMA_DONEB) & ~DMA_BIU;	
		ULONG OutputTransferred = TransferOutputBuffers(m_OutputDMAStatus);
        
		//----- 3. If we did transfer any data to the DMA buffers, go ahead and enable DMA -----
		if(OutputTransferred)
        {
			//----- 4. Configure the DMA channel for playback -----
			if(!InitOutputDMA())
			{
				DEBUGMSG(ZONE_ERROR, (TEXT("HardwareContext::StartOutputDMA() - Unable to initialize output DMA channel!\r\n")));
				goto START_ERROR;
			}

			////////////////////////////////////////////////////////////////////////////////
			// To correct left/right channel on ouput stream,
			// You should reset IISCON[0] bit.
			Lock();
			v_pIISregs->rIISCON  &= ~IIS_INTERFACE_ENABLE;
			
			v_pIISregs->rIISCON  |= TRANSMIT_DMA_REQUEST_ENABLE;
			v_pIISregs->rIISCON  &= ~TRANSMIT_IDLE_CMD;
			v_pIISregs->rIISFCON |= ( TRANSMIT_FIFO_ACCESS_DMA | TRANSMIT_FIFO_ENABLE );
			v_pIISregs->rIISMOD  |= IIS_TRANSMIT_MODE;

			v_pIISregs->rIISCON  |= IIS_INTERFACE_ENABLE;
			Unlock();
			////////////////////////////////////////////////////////////////////////////////
			
			//----- 5. Make sure the audio isn't muted -----
			AudioMute(DMA_CH_OUT, FALSE);					

			//----- 6. Start the DMA controller -----
			//AUDIO_RESET_PLAYBACK_POINTER();
			ioPlaybackPointerLow  = (AUDIO_DMA_BUFFER_PHYS);
			ioPlaybackPointerHigh = (AUDIO_DMA_BUFFER_PHYS + AUDIO_DMA_PAGE_SIZE);
			
			//SELECT_AUDIO_DMA_OUTPUT_BUFFER_A();
			v_pDMAregs->rDISRC2 = (int)(AUDIO_DMA_BUFFER_PHYS);
			
			Codec_channel();								// Turn ON output channel
			// charlie, start A buffer
			//AUDIO_OUT_DMA_ENABLE();
			v_pDMAregs->rDMASKTRIG2 &= ~STOP_DMA_TRANSFER;
			v_pDMAregs->rDMASKTRIG2 |= ENABLE_DMA_CHANNEL;
			
	        // wait for DMA to start.
	        delay_count = 0;
	        while((v_pDMAregs->rDSTAT2&0xfffff)==0){
	        	if( delay_count++ > DELAY_COUNT )	break;
	        }        
	        // change the buffer pointer
	        //SELECT_AUDIO_DMA_OUTPUT_BUFFER_B();
	        v_pDMAregs->rDISRC2 = (int)(AUDIO_DMA_BUFFER_PHYS+AUDIO_DMA_PAGE_SIZE);
	        // Set DMA for B Buffer
        }
        else    // We didn't transfer any data, so DMA wasn't enabled
        {
            m_OutputDMARunning=FALSE;
        }
    }
    
    DEBUGMSG(1,(TEXT("---StartOutputDMA\n")));

	return TRUE;

START_ERROR:
	return FALSE;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		StopOutputDMA()

Description:	Stops any DMA activity on the output channel.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
void HardwareContext::StopOutputDMA()
{
	//----- 1. If the output DMA is running, stop it -----
    if (m_OutputDMARunning)
	{
		m_OutputDMAStatus = DMA_CLEAR;				
		
		//AUDIO_OUT_DMA_DISABLE();
		v_pDMAregs->rDMASKTRIG2 |= STOP_DMA_TRANSFER;
		v_pDMAregs->rDMASKTRIG2 &= ~ENABLE_DMA_CHANNEL;
		
		//AUDIO_OUT_CLEAR_INTERRUPTS();
		v_pDMAregs->rDCON2 = v_pDMAregs->rDCON2;
		
		v_pIISregs->rIISCON &= ~TRANSMIT_DMA_REQUEST_ENABLE;
		v_pIISregs->rIISCON |= TRANSMIT_IDLE_CMD;
		v_pIISregs->rIISFCON &= ~(  TRANSMIT_FIFO_ACCESS_DMA | TRANSMIT_FIFO_ENABLE  );
		v_pIISregs->rIISMOD  &= ~IIS_TRANSMIT_MODE;
		AudioMute(DMA_CH_OUT, TRUE);		
    }

	m_OutputDMARunning = FALSE;
	Codec_channel();
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		InitInputDMA()

Description:	Initializes the DMA channel for input.

Notes:			***** NOT IMPLEMENTED *****

				The following routine is not implemented due to a
				hardware bug in the revision of the Samsung SC2410
				CPU this driver was developed on.  See the header
				at the top of this file for details.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::InitInputDMA()
{

//goto INIT_ERROR;
	DEBUGMSG(1,(TEXT("+++InitInputDMA\n")));
	//============================ Configure DMA Channel 1 ===========================
	//------ On platforms with the revsion of the Samsung SC2410 CPU with the IIS SLAVE bug fix, this -----
	//		 code can be used to configure DMA channel 1 for input.

	//----- 1. Initialize the DMA channel for input mode and use the first input DMA buffer -----
	v_pDMAregs->rDISRC1	= (int)IISFIF_PHYS;	
	v_pDMAregs->rDISRCC1 = (SOURCE_PERIPHERAL_BUS | FIXED_SOURCE_ADDRESS);				// Source is periperal bus, fixed addr
    
    //----- 2. Initialize the DMA channel to receive data over the I2S bus -----
    v_pDMAregs->rDIDST1	= (int)(AUDIO_DMA_BUFFER_PHYS); 
    v_pDMAregs->rDIDSTC1 &= ~(DESTINATION_PERIPHERAL_BUS | FIXED_DESTINATION_ADDRESS);	// Destination is system bus, increment addr

	//----- 3. Configure the DMA channel's transfer characteristics: handshake, sync PCLK, interrupt, -----
	//		   single tx, single service, I2SSDI, I2S request, no auto-reload, half-word, tx count
	v_pDMAregs->rDCON1	= (  HANDSHAKE_MODE | GENERATE_INTERRUPT | I2SSDI_DMA1 | DMA_TRIGGERED_BY_HARDWARE 
						   | TRANSFER_HALF_WORD | (AUDIO_DMA_PAGE_SIZE / 2) );						   

	return TRUE;

INIT_ERROR:
	return FALSE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		StartInputDMA()

Description:	Starts inputting the recorded sound data from the 
				audio codec chip via DMA.

Notes:			***** NOT IMPLEMENTED *****

				The following routine is not implemented due to a
				hardware bug in the revision of the Samsung SC2410
				CPU this driver was developed on.  See the header
				at the top of this file for details.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::StartInputDMA()
{
	int tmp;

//goto START_ERROR;

	//------ On platforms with the revsion of the Samsung SC2410 CPU with the IIS SLAVE bug fix, this -----
	//		 code can be used to configure DMA channel 1 for input.

	DEBUGMSG(1,(_T("StartInputDMA()++\r\n")));

	if(!m_InputDMARunning)
    {
        //----- 1. Initialize our buffer counters -----
        m_InputDMARunning=TRUE;
		Codec_channel();        // Turn On Input channel
        m_InBytes[IN_BUFFER_A]=m_InBytes[IN_BUFFER_B]=0;

        //----- 2. Prime the output buffer with sound data -----
		m_InputDMAStatus = (DMA_DONEA | DMA_DONEB) & ~DMA_BIU;	

		//----- 3. Configure the DMA channel for record -----
		if(!InitInputDMA())
		{
			DEBUGMSG(ZONE_ERROR, (TEXT("HardwareContext::StartInputDMA() - Unable to initialize input DMA channel!\r\n")));
			goto START_ERROR;
		}
		v_pIISregs->rIISCON  |= RECEIVE_DMA_REQUEST_ENABLE;	 
		v_pIISregs->rIISCON  &= ~RECEIVE_IDLE_CMD;
		v_pIISregs->rIISFCON |= ( RECEIVE_FIFO_ACCESS_DMA  | RECEIVE_FIFO_ENABLE);
		v_pIISregs->rIISMOD  |= IIS_RECEIVE_MODE;
	
		//----- 4. Make sure the audio isn't muted -----
		AudioMute(DMA_CH_MIC, FALSE);					

		//----- 5. Start the input DMA -----
		//AUDIO_RESET_RECORD_POINTER();
		ioRecordPointerLow  = (RECORD_DMA_BUFFER_PHYS);	
		ioRecordPointerHigh = (RECORD_DMA_BUFFER_PHYS+ AUDIO_DMA_PAGE_SIZE);
		
		//SELECT_AUDIO_DMA_INPUT_BUFFER_A();
		v_pDMAregs->rDIDST1 = (int)(AUDIO_DMA_BUFFER_PHYS+2*AUDIO_DMA_PAGE_SIZE);

		Codec_channel();        // Turn On Input channel
		v_pDMAregs->rDMASKTRIG1 &= ~STOP_DMA_TRANSFER;		
		v_pDMAregs->rDMASKTRIG1 |= ENABLE_DMA_CHANNEL;		
		

        // wait for DMA to start.
        delay_count = 0;
        while((v_pDMAregs->rDSTAT2&0xfffff)==0){
        	if( delay_count++ > DELAY_COUNT )	break;
        } 
        // change the buffer pointer
        //SELECT_AUDIO_DMA_INPUT_BUFFER_B();
        v_pDMAregs->rDIDST1 = (int)(AUDIO_DMA_BUFFER_PHYS+3*AUDIO_DMA_PAGE_SIZE);
	}

	DEBUGMSG(1,(_T("StartInputDMA()--\r\n")));
	return TRUE;

START_ERROR:
	return FALSE;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		StopInputDMA()

Description:	Stops any DMA activity on the input channel.

Notes:			***** NOT IMPLEMENTED *****

				The following routine is not implemented due to a
				hardware bug in the revision of the Samsung SC2410
				CPU this driver was developed on.  See the header
				at the top of this file for details.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
void HardwareContext::StopInputDMA()
{

	//------ On platforms with the revsion of the Samsung SC2410 CPU with the IIS SLAVE bug fix, this -----
	//		 code can be used to configure DMA channel 1 for input.
	DEBUGMSG(1,(_T("StopInputDMA()++\r\n")));
	//----- 1. If the output DMA is running, stop it -----
    if (m_InputDMARunning)
	{
		m_InputDMAStatus = DMA_CLEAR;				
		
		v_pIISregs->rIISCON &= ~RECEIVE_DMA_REQUEST_ENABLE;
		v_pIISregs->rIISCON |= RECEIVE_IDLE_CMD;
		v_pIISregs->rIISFCON &= ~( RECEIVE_FIFO_ACCESS_DMA  | RECEIVE_FIFO_ENABLE);
		v_pIISregs->rIISMOD  &= ~IIS_RECEIVE_MODE;

		v_pDMAregs->rDMASKTRIG1 |= STOP_DMA_TRANSFER;
		v_pDMAregs->rDMASKTRIG1 &= ~ENABLE_DMA_CHANNEL;

//		AUDIO_IN_DMA_DISABLE();
//		v_pIISregs->rIISCON &= ~RECEIVE_DMA_REQUEST_ENABLE;
//		v_pIISregs->rIISFCON &= ~( RECEIVE_FIFO_ACCESS_DMA  | RECEIVE_FIFO_ENABLE);
//		v_pDMAregs->rDMASKTRIG1 |= STOP_DMA_TRANSFER;

		//AUDIO_IN_CLEAR_INTERRUPTS();
		v_pDMAregs->rDCON1 = v_pDMAregs->rDCON1;
		
		AudioMute(DMA_CH_MIC, TRUE);		
    }

	m_InputDMARunning = FALSE;
	Codec_channel();

	DEBUGMSG(1,(_T("StopInputDMA()--\r\n")));
}


DWORD HardwareContext::GetInterruptThreadPriority()
{
    HKEY hDevKey;
    DWORD dwValType;
    DWORD dwValLen;
    DWORD dwPrio = 249; // Default priority

    hDevKey = OpenDeviceKey((LPWSTR)m_DriverIndex);
    if (hDevKey)
    {
        dwValLen = sizeof(DWORD);
        RegQueryValueEx(
            hDevKey,
            TEXT("Priority256"),
            NULL,
            &dwValType,
            (PUCHAR)&dwPrio,
            &dwValLen);
        RegCloseKey(hDevKey);
    }

    return dwPrio;
}

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// SetForceSpeaker is called from the device context to update the state of the
// m_bForceSpeaker variable.
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8

DWORD HardwareContext::ForceSpeaker( BOOL bForceSpeaker )
{
    // If m_NumForcedSpeaker is non-zero, audio should be routed to an
    // external speaker (if hw permits).
    if (bForceSpeaker)
    {
        m_NumForcedSpeaker++;
        if (m_NumForcedSpeaker==1)
        {
            RecalcSpeakerEnable();
        }
    }
    else
    {
        m_NumForcedSpeaker--;
        if (m_NumForcedSpeaker==0)
        {
            RecalcSpeakerEnable();
        }
    }

    return MMSYSERR_NOERROR;
}

// Control the hardware speaker enable
void HardwareContext::SetSpeakerEnable(BOOL bEnable)
{
    // Code to turn speaker on/off here
    return;
}

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// RecalcSpeakerEnable decides whether to enable the speaker or not.
// For now, it only looks at the m_bForceSpeaker variable, but it could
// also look at whether the headset is plugged in
// and/or whether we're in a voice call. Some mechanism would
// need to be implemented to inform the wave driver of changes in the state of
// these variables however.
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8

void HardwareContext::RecalcSpeakerEnable()
{
    SetSpeakerEnable(m_NumForcedSpeaker);
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		InitInterruptThread()

Description:	Initializes the IST for handling DMA interrupts.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::InitInterruptThread()
{
    BOOL bSuccess;
    
    m_hAudioInterrupt = CreateEvent( NULL, FALSE, FALSE, NULL);

    if (!m_hAudioInterrupt)
    {
        return FALSE;
    }

    bSuccess = InterruptInitialize(m_IntrAudio, m_hAudioInterrupt, NULL, 0);

    m_hAudioInterruptThread  = CreateThread((LPSECURITY_ATTRIBUTES)NULL,
                                            0,
                                            (LPTHREAD_START_ROUTINE)CallInterruptThread,
                                            this,
                                            0,
                                            NULL);
    if (!m_hAudioInterruptThread)
    {
        return FALSE;
    }

    // Bump up the priority since the interrupt must be serviced immediately.
	CeSetThreadPriority(m_hAudioInterruptThread, GetInterruptThreadPriority());

    return TRUE;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		PowerUp()

Description:	Powers up the audio codec chip.

Notes:			Currently, this function is unimplemented because
				the audio codec chip is ONLY powered up when the 
				user wishes to play or record.  The AudioMute() function
				handles the powerup sequence.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
void HardwareContext::PowerUp()
{
//    SPI_Init();
    I2S_Init();
    AudioMute((DMA_CH_OUT | DMA_CH_MIC), FALSE);		// 030711
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		PowerDown()

Description:	Powers down the audio codec chip.

Notes:			Even if the input/output channels are muted, this
				function powers down the audio codec chip in order
				to conserve battery power.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
void HardwareContext::PowerDown()
{
    StopOutputDMA();
	AudioMute((DMA_CH_OUT | DMA_CH_MIC), TRUE);
}


//############################################ Helper Functions #############################################

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		TransferOutputBuffer()

Description:	Retrieves the next "mixed" audio buffer of data to
				DMA into the output channel.

Returns:		Number of bytes needing to be transferred.
-------------------------------------------------------------------*/
ULONG HardwareContext::TransferOutputBuffer(ULONG NumBuf)
{
    ULONG BytesTransferred = 0;

    PBYTE pBufferStart = m_Output_pbDMA_PAGES[NumBuf];
    PBYTE pBufferEnd = pBufferStart + AUDIO_DMA_PAGE_SIZE;
    PBYTE pBufferLast;

	__try
	{
		pBufferLast = m_OutputDeviceContext.TransferBuffer(pBufferStart, pBufferEnd,NULL);
		BytesTransferred = m_OutBytes[NumBuf] = pBufferLast-pBufferStart;

		// Enable if you need to clear the rest of the DMA buffer
		StreamContext::ClearBuffer(pBufferLast,pBufferEnd);
		if(NumBuf == OUT_BUFFER_A)			// Output Buffer A
		{
			m_OutputDMAStatus &= ~DMA_DONEA;
			m_OutputDMAStatus |= DMA_STRTA;
		}
		else								// Output Buffer B
		{
			m_OutputDMAStatus &= ~DMA_DONEB;
			m_OutputDMAStatus |= DMA_STRTB;
		}
	}
	__except(EXCEPTION_EXECUTE_HANDLER) 
	{
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVDEV2.DLL:TransferOutputBuffer() - EXCEPTION: %d"), GetExceptionCode()));
	}

    return BytesTransferred;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		TransferOutputBuffers()

Description:	Determines which output buffer (A or B) needs to 
				be filled with sound data.  The correct buffer is
				then populated with data and ready to DMA to the 
				output channel.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
ULONG HardwareContext::TransferOutputBuffers(DWORD dwDCSR)
{
    ULONG BytesTransferred = 0;

    ULONG BytesTotal;
    DWORD Bits = dwDCSR & (DMA_DONEA|DMA_DONEB|DMA_BIU);

	DEBUGMSG(1,(TEXT("%x\n"),Bits));
	switch (Bits)
    {
    case 0:
    case DMA_BIU:
        // No done bits set- must not be my interrupt
        return 0;
    case DMA_DONEA|DMA_DONEB|DMA_BIU:
        // Load B, then A
        BytesTransferred = TransferOutputBuffer(OUT_BUFFER_B);
        // fall through
    case DMA_DONEA: // This should never happen!
    case DMA_DONEA|DMA_BIU:
		BytesTransferred += TransferOutputBuffer(OUT_BUFFER_A);		// charlie, A => B
        break;
    case DMA_DONEA|DMA_DONEB:
        // Load A, then B
        BytesTransferred = TransferOutputBuffer(OUT_BUFFER_A);

        // charlie
        BytesTransferred += TransferOutputBuffer(OUT_BUFFER_B);
        break;		// charlie

        // fall through
    case DMA_DONEB|DMA_BIU: // This should never happen!
    case DMA_DONEB:
        // Load B
        BytesTransferred += TransferOutputBuffer(OUT_BUFFER_B);		// charlie, B => A
        break;
    }

    // If it was our interrupt, but we weren't able to transfer any bytes
    // (e.g. no full buffers ready to be emptied)
    // and all the output DMA buffers are now empty, then stop the output DMA
    BytesTotal = m_OutBytes[OUT_BUFFER_A]+m_OutBytes[OUT_BUFFER_B];

	if (BytesTotal==0)
    {
		StopOutputDMA();
    }
    return BytesTransferred;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		TransferInputBuffer()

Description:	Retrieves the chunk of recorded sound data and inputs
				it into an audio buffer for potential "mixing".

Returns:		Number of bytes needing to be transferred.
-------------------------------------------------------------------*/
ULONG HardwareContext::TransferInputBuffer(ULONG NumBuf)
{
	int tmp;
    ULONG BytesTransferred = 0;

    PBYTE pBufferStart = m_Input_pbDMA_PAGES[NumBuf];
    PBYTE pBufferEnd = pBufferStart + AUDIO_DMA_PAGE_SIZE;
    PBYTE pBufferLast;

	__try
	{
		pBufferLast = m_InputDeviceContext.TransferBuffer(pBufferStart, pBufferEnd,NULL);
		BytesTransferred = m_InBytes[NumBuf] = pBufferLast-pBufferStart;

		if(NumBuf == IN_BUFFER_A)			// Input Buffer A
		{
			m_InputDMAStatus &= ~DMA_DONEA;
			m_InputDMAStatus |= DMA_STRTA;
		}
		else								// Input Buffer B
		{
			m_InputDMAStatus &= ~DMA_DONEB;
			m_InputDMAStatus |= DMA_STRTB;
		}

	}
	__except(EXCEPTION_EXECUTE_HANDLER) 
	{
		DEBUGMSG(ZONE_ERROR, (TEXT("WAVDEV2.DLL:TransferInputBuffer() - EXCEPTION: %d"), GetExceptionCode()));
	}

    return BytesTransferred;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		TransferInputBuffers()

Description:	Determines which input buffer (A or B) needs to 
				be filled with recorded sound data.  The correct 
				buffer is then populated with recorded sound data
				from the input channel.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
ULONG HardwareContext::TransferInputBuffers(DWORD dwDCSR)
{
    ULONG BytesTransferred=0;
    DWORD Bits = dwDCSR & (DMA_DONEA|DMA_DONEB|DMA_BIU);
    int tmp;

    switch (Bits)
    {
    case 0:
    case DMA_BIU:
        // No done bits set- must not be my interrupt
        return 0;
    case DMA_DONEA|DMA_DONEB|DMA_BIU:
        // Load B, then A
        BytesTransferred = TransferInputBuffer(IN_BUFFER_B);
        // fall through
    case DMA_DONEA: // This should never happen!
    case DMA_DONEA|DMA_BIU:
        // Load A
        BytesTransferred += TransferInputBuffer(IN_BUFFER_A);
        break;
    case DMA_DONEA|DMA_DONEB:
        // Load A, then B
        BytesTransferred = TransferInputBuffer(IN_BUFFER_A);

		BytesTransferred += TransferInputBuffer(IN_BUFFER_B);
		break;

        // fall through
    case DMA_DONEB|DMA_BIU: // This should never happen!
    case DMA_DONEB:
        // Load B
        BytesTransferred += TransferInputBuffer(IN_BUFFER_B);
        break;
    }

    // If it was our interrupt, but we weren't able to transfer any bytes
    // (e.g. no empty buffers ready to be filled)
    // Then stop the input DMA
    if (BytesTransferred==0)
    {
        StopInputDMA();
    }
    return BytesTransferred;
}


void HardwareContext::InterruptThread()
{
    ULONG InputTransferred, OutputTransferred;
	int tmp;
	BOOL dmaInterruptSource = 0;

    // Fast way to access embedded pointers in wave headers in other processes.
	SetProcPermissions((DWORD)-1);

    while(TRUE)
    {
		WaitForSingleObject(m_hAudioInterrupt, INFINITE);
		DEBUGMSG(1,(TEXT("0x%x\n"),s2440INT->rINTMSK));
		DEBUGMSG(1,(TEXT("start\n")));
		dmaInterruptSource = 0;
		//----- 1. Grab the lock -----
		Lock();

		__try
		{
			DEBUGMSG(1,(TEXT("try\n")));

			//----- 3. Determine the interrupt source (input DMA operation or output DMA operation?) -----
			//----- NOTE:	Often, platforms use two separate DMA channels for input/output operations but
			//				have the OAL return SYSINTR_AUDIO as the interrupt source.  If this is the case,
			//				then the interrupt source (input or output DMA channel) must be determined in
			//				this step.
			// charlie, determine the interrupt source

			if( s2440INT->rINTMSK & BIT_DMA1 ){
				dmaInterruptSource |= DMA_CH_MIC;								// Input DMA is supported...
			}
			
			if( s2440INT->rINTMSK & BIT_DMA2 ){
				dmaInterruptSource |= DMA_CH_OUT;								// Output DMA is supported...
			}
						
			// For determine the interrupt source
			//----- 2. Acknowledge the DMA interrupt -----
			InterruptDone(m_IntrAudio);

			if ( m_Dx != D0 ) continue;

			//----- 4. Handle any interrupts on the input source -----
			//		   NOTE: The InterruptDone() call below automatically clears the interrupt.
//			if((m_InputDMARunning) && (dmaInterruptSource == DMA_CH_MIC))
			if((dmaInterruptSource == DMA_CH_MIC))
			{
				//----- Determine which buffer just completed the DMA transfer -----
				if(m_InputDMAStatus & DMA_BIU)
				{
					m_InputDMAStatus &= ~DMA_STRTB;							// Buffer B just completed...
					m_InputDMAStatus |= DMA_DONEB;

					m_InputDMAStatus &= ~DMA_BIU;							// Buffer A is in use

					//SELECT_AUDIO_DMA_INPUT_BUFFER_B();
					v_pDMAregs->rDIDST1 = (int)(AUDIO_DMA_BUFFER_PHYS+3*AUDIO_DMA_PAGE_SIZE);

					DEBUGMSG(1,(TEXT("1\n")));
				}else
				{
					m_InputDMAStatus &= ~DMA_STRTA;							// Buffer A just completed...
					m_InputDMAStatus |= DMA_DONEA;

					m_InputDMAStatus |= DMA_BIU;							// Buffer B is in use

					//SELECT_AUDIO_DMA_INPUT_BUFFER_A();
					v_pDMAregs->rDIDST1 = (int)(AUDIO_DMA_BUFFER_PHYS+2*AUDIO_DMA_PAGE_SIZE);

					DEBUGMSG(1,(TEXT("2\n")));
				}
/*
				//----- 5. Schedule the next DMA transfer -----
				//AUDIO_IN_DMA_ENABLE();
				v_pDMAregs->rDMASKTRIG1 |= ENABLE_DMA_CHANNEL; \
				v_pDMAregs->rDMASKTRIG1 &= ~STOP_DMA_TRANSFER; \
				v_pIISregs->rIISFCON |= ( RECEIVE_FIFO_ACCESS_DMA  | RECEIVE_FIFO_ENABLE);	\
				v_pIISregs->rIISCON  |= RECEIVE_DMA_REQUEST_ENABLE;	
*/
				
				//----- 6. Retrieve the next chunk of recorded data from the non-playing buffer -----
				InputTransferred = TransferInputBuffers(m_InputDMAStatus);
			}

			//----- 7. Handle any interrupts on the output source -----
			//		   NOTE: The InterruptDone() call below automatically clears the interrupt.
//			if((m_OutputDMARunning) && (dmaInterruptSource == DMA_CH_OUT))
//			if((dmaInterruptSource == DMA_CH_OUT))
			else
			{	
				//----- Determine which buffer just completed the DMA transfer -----
				if(m_OutputDMAStatus & DMA_BIU)
				{
					m_OutputDMAStatus &= ~DMA_STRTB;						// Buffer A just completed...
					m_OutputDMAStatus |= DMA_DONEB;

					m_OutputDMAStatus &= ~DMA_BIU;							// Buffer B is in use
					
					delay_count = 0;
				    while((v_pDMAregs->rDSTAT2&0xfffff)==0){
	        			if( delay_count++ > DELAY_COUNT )	break;
	        		} 

					//SELECT_AUDIO_DMA_OUTPUT_BUFFER_B();		// charlie. B => A
					v_pDMAregs->rDISRC2 = (int)(AUDIO_DMA_BUFFER_PHYS+AUDIO_DMA_PAGE_SIZE);

				}else
				{
					m_OutputDMAStatus &= ~DMA_STRTA;						// Buffer B just completed...
					m_OutputDMAStatus |= DMA_DONEA;

					m_OutputDMAStatus |= DMA_BIU;							// Buffer A is in use
					
					delay_count = 0;
					while((v_pDMAregs->rDSTAT2&0xfffff)==0){
	        			if( delay_count++ > DELAY_COUNT )	break;
	        		} 

					//SELECT_AUDIO_DMA_OUTPUT_BUFFER_A();		// charlie. B => A
					v_pDMAregs->rDISRC2 = (int)(AUDIO_DMA_BUFFER_PHYS);

				}


				//----- 8. Schedule the next DMA transfer -----
				//AUDIO_OUT_DMA_ENABLE();
				//v_pDMAregs->rDMASKTRIG2 &= ~STOP_DMA_TRANSFER;
				//v_pDMAregs->rDMASKTRIG2 |= ENABLE_DMA_CHANNEL;

				//----- 9. Fill the non-playing buffer with the next chunk of audio data to play -----
				OutputTransferred = TransferOutputBuffers(m_OutputDMAStatus);			
			}

		}
		__except(EXCEPTION_EXECUTE_HANDLER) 
		{
			DEBUGMSG(ZONE_ERROR, (TEXT("WAVDEV2.DLL:InterruptThread() - EXCEPTION: %d"), GetExceptionCode()));
		}

		//----- 10. Give up the lock ----- 
		Unlock();
	}  
}



/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Function:		AudioMute()

Description:	Mutes/unmutes the specified audio channel.

Notes:			If both audio channels are MUTED, then the chip 
				is powered down to conserve battery life.  
				Alternatively, if either audio channel is unMUTED, 
				the chip is powered up.

Returns:		Boolean indicating success
-------------------------------------------------------------------*/
BOOL HardwareContext::AudioMute(DWORD channel, BOOL bMute)
{
	static DWORD dwActiveChannel = 0;

	DEBUGMSG(1,(TEXT("Mute:%d\n"),bMute));	
	// 
	// Turn off/on mute bit in control register B.
	// 
	if( !m_InputDMARunning && !m_OutputDMARunning )		// 030711
	{
		if(bMute)
		{
			_WrL3Addr(0x14+0); //DATA0 (000101xx+00)
			_WrL3Data(0xa4,0); //mute
		}
		else
		{
			_WrL3Addr(0x14+0); //DATA0, 
			_WrL3Data(0xa0,0); //no mute
		}
	}		
	
	return TRUE;
}


void CallInterruptThread(HardwareContext *pHWContext)
{
    pHWContext->InterruptThread();
}

DWORD 
HardwareContext::Open(
    void
    )
{
    DWORD mmErr = MMSYSERR_NOERROR;
    
    // Don't allow play when not on, if there is a power constraint upon us.
    if ( D0 != m_Dx )
    {
        // Tell the Power Manager we need to power up.
        // If there is a power constraint then fail.
        DWORD dwErr = DevicePowerNotify(_T("WAV1:"), D0, POWER_NAME);
        if ( ERROR_SUCCESS !=  dwErr ) {
            RETAILMSG(1, (TEXT("WAVEDEV::Open:DevicePowerNotify ERROR: %u\r\n"), dwErr ));
            mmErr = MMSYSERR_ERROR;
        }
    }
    
    return mmErr;
}


DWORD 
HardwareContext::Close(
    void
    )
{
    DWORD mmErr = MMSYSERR_NOERROR;
    DWORD dwErr;
    
    // we are done so inform Power Manager to power us down, 030711
//    dwErr = DevicePowerNotify(_T("WAV1:"), (_CEDEVICE_POWER_STATE)D4, POWER_NAME);
    if ( ERROR_SUCCESS !=  dwErr ) {
        RETAILMSG(1, (TEXT("WAVEDEV::Close:DevicePowerNofify ERROR: %u\r\n"), dwErr ));
		mmErr = MMSYSERR_ERROR;
    }
    
    return mmErr;
}


BOOL 
HardwareContext::IOControl( 
    DWORD  dwOpenData,
    DWORD  dwCode,
    PBYTE  pBufIn,
    DWORD  dwLenIn,
    PBYTE  pBufOut,
    DWORD  dwLenOut,
    PDWORD pdwActualOut)
{
    DWORD dwErr = ERROR_SUCCESS;    
    BOOL  bRc = TRUE;

    UNREFERENCED_PARAMETER(dwOpenData);
    
    switch (dwCode) {
        //
        // Power Management
        //
        case IOCTL_POWER_CAPABILITIES: 
        {
            PPOWER_CAPABILITIES ppc;
            
            if ( !pdwActualOut || !pBufOut || (dwLenOut < sizeof(POWER_CAPABILITIES)) ) {
                bRc = FALSE;
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            
            ppc = (PPOWER_CAPABILITIES)pBufOut;
            
            memset(ppc, 0, sizeof(POWER_CAPABILITIES));

            // support D0, D4
            ppc->DeviceDx = 0x11;

            // no wake
            // no inrush

            // Report our nominal power consumption in uAmps rather than mWatts.
            ppc->Flags = POWER_CAP_PREFIX_MICRO | POWER_CAP_UNIT_AMPS;

            // REVIEW: Do we enable all these for normal playback?
            // D0: SPI + I2S + CODEC (Playback) + Headphone= 
            //     0.5 mA + 0.5 mA + (23 mW, into BUGBUG ohms ) + (30 mW, into 32 ohms)
            //     500 uA + 500 uA + 23000 uA + 32000 uA
            ppc->Power[D0] = 56000;
            
            *pdwActualOut = sizeof(POWER_CAPABILITIES);
        } break;

        case IOCTL_POWER_SET: 
        {
            CEDEVICE_POWER_STATE NewDx;
            
            if ( !pdwActualOut || !pBufOut || (dwLenOut < sizeof(CEDEVICE_POWER_STATE)) ) {
                bRc = FALSE;
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            
            NewDx = *(PCEDEVICE_POWER_STATE)pBufOut;

            if ( VALID_DX(NewDx) ) {
                // grab the CS since the normal Xxx_PowerXxx can not.
                Lock();
                switch ( NewDx ) {
                    case D0:
                        if (m_Dx != D0) {
                            PowerUp();
                            m_Dx = D0;
                        }
                        break;

                    default:
                        if (m_Dx != (_CEDEVICE_POWER_STATE)D4) {
                            PowerDown();
                            m_Dx = (_CEDEVICE_POWER_STATE)D4;
                        }
                        break;
                }
                
                // return our state
                *(PCEDEVICE_POWER_STATE)pBufOut = m_Dx;
				//RETAILMSG(1, (TEXT("WAVEDEV: IOCTL_POWER_SET: D%u => D%u \r\n"), NewDx, m_Dx));

                Unlock();
                
                *pdwActualOut = sizeof(CEDEVICE_POWER_STATE);
            } else {
                bRc = FALSE;
                dwErr = ERROR_INVALID_PARAMETER;
            }
            
        } break;

        case IOCTL_POWER_GET: 
            if ( !pdwActualOut || !pBufOut || (dwLenOut < sizeof(CEDEVICE_POWER_STATE)) ) {
                bRc = FALSE;
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }

            *(PCEDEVICE_POWER_STATE)pBufOut = m_Dx;

            RETAILMSG(1, (TEXT("WAVEDEV: IOCTL_POWER_GET: D%u \r\n"), m_Dx));

            *pdwActualOut = sizeof(CEDEVICE_POWER_STATE);
            break;
            
        default:
            bRc = FALSE;
            dwErr = ERROR_INVALID_FUNCTION;
            DEBUGMSG (ZONE_FUNCTION, (TEXT(" Unsupported ioctl 0x%X\r\n"), dwCode));
            break;            
    }
    
    if ( !bRc ) {
        SetLastError(dwErr);
    }
    
    return(bRc);
}

