/**
 * 	\file 	tcDspUpp.cpp
 * 
 * 	\brief 	DSP uPP driver source
 *
 *     o  0
 *     | /       Copyright (c) 2005-2011
 *    (CL)---o   Critical Link, LLC
 *      \
 *       O
 */

#include "DspUpp.h"
#include "memorymap.h"
#include "core/DspSyscfg.h"
#include "core/DspLpsc.h"

#include <tsk.h>
#include <assert.h>
#include <hwi.h>
#include <c62.h>

using namespace MityDSP;

SEM_Handle tcDspUpp::mhGetInstSem = SEM_create(1, NULL);

tcDspUpp* tcDspUpp::mpDspUpp = NULL;

/**
 * Get instance of tcDspUpp.
 */
tcDspUpp* 
tcDspUpp::getInstance()
{
	// Pend on the static mutex so that we do not accidentally 
	// init too many objects
	SEM_pend(mhGetInstSem, SYS_FOREVER);

	// Check if the singleton need initialization
	if (NULL == mpDspUpp)
	{
		mpDspUpp = new tcDspUpp();
	}

	// Safe to return the mutex now
	SEM_post(mhGetInstSem);

	// Return pointer to singleton
	return mpDspUpp;
}

/**
 * Intiailize the uPP device.
 */
int 
tcDspUpp::initialize(tsDspUppConfig const* apDspUppConfig)
{
	// ISR attributes
	HWI_Attrs hwi_attrs = {0, 0, (Arg)this};

	TSK_Attrs tsk_attrs = TSK_ATTRS;

	tuUppcrReg luUppcrReg = {0};
	tuUpctlReg luUpctlReg = {0};
	tuUpicrReg luUpicrReg = {0};
	tuUpivrReg luUpivrReg = {0};
	tuUptcrReg luUptcrReg = {0};
	tuUpiesReg luUpiesReg = {0};

	// Pin Mux functions to make sure uPP is enabled properly for Channel A
	tePinFunc laPinFuncCHA[] = 
		{
			UPP_CH1_WAIT,
			UPP_CH1_ENABLE,
			UPP_CH1_START,
			UPP_CH1_CLK,
			PINFUNC_LIST_TERMINATE
		};	

	// Pin Mux functions to make sure uPP is enabled properly for Channel B
	tePinFunc laPinFuncCHB[] = 
		{
			UPP_CH0_WAIT,
			UPP_CH0_ENABLE, 
			UPP_CH0_START,
			UPP_CH0_CLK,
			PINFUNC_LIST_TERMINATE
		};

	// Pin Mux functions for DATA[15:8]
	tePinFunc laPinFuncData15_8[] = 
		{
			UPP_D8,
			UPP_D9,
			UPP_D10,
			UPP_D11,
			UPP_D12,
			UPP_D13, 
			UPP_D14,
			UPP_D15,
			PINFUNC_LIST_TERMINATE
		};

	// Pin Mux functions for DATA[7:0]
	tePinFunc laPinFuncData7_0[] = 
		{
			UPP_D0,
			UPP_D1,
			UPP_D2,	
			UPP_D3,
			UPP_D4, 
			UPP_D5, 
			UPP_D6,
			UPP_D7,
			PINFUNC_LIST_TERMINATE
		};
		
	// Pin Mux functions for XDATA[15:8]
	tePinFunc laPinFuncXData15_8[] = 
		{	
			UPP_XD8,
			UPP_XD9,
		    UPP_XD10,
			UPP_XD11,
			UPP_XD12,
			UPP_XD13, 
			UPP_XD14, 
			UPP_XD15,
			PINFUNC_LIST_TERMINATE
		};			 
			
	// Pin Mux functions for XDATA[7:0]
	tePinFunc laPinFuncXData7_0[] = 
		{
			UPP_XD0, 
			UPP_XD1,
			UPP_XD2, 
			UPP_XD3,
			UPP_XD4,
			UPP_XD5,
			UPP_XD6,
			UPP_XD7,	
			PINFUNC_LIST_TERMINATE
		};

	// MbxA attributes //TODO: non-default attributes?
	MBX_Attrs lsMbxAttrsA = MBX_ATTRS;
	// MbxB attributes //TODO: non-default attributes?
	MBX_Attrs lsMbxAttrsB = MBX_ATTRS;

	// Length of Chan A MBXs
	uint32_t lnMbxLenA = apDspUppConfig->nMbxLenA;
	// Length of Chan B MBXs
	uint32_t lnMbxLenB = apDspUppConfig->nMbxLenB;


	// Configuration sanity checks
	if (eeDisabled == apDspUppConfig->eChanADir &&
		eeDisabled == apDspUppConfig->eChanBDir)
		return -1;

	if (apDspUppConfig->nHWInterruptLevel > 15 || 
		apDspUppConfig->nHWInterruptLevel < 4) 
	{
		return -1;
	}

	// TODO: Additional checks?
	

	// Check if we've already been initialized
	if (false == mbFirstInit)
	{
		// TODO: any necessary shutdown before the re-init	

		// Reset all pin config to default? (need to? Make sense?)
	}

	mbFirstInit = false;

	// Reset channels to disabled in case there is a failure
	meChanADir = eeDisabled;
	meChanADir = eeDisabled;

	// Set uPP DMA Master Priority
	tcDspSyscfg::SetMasterPriority(tcDspSyscfg::eeUPP, 
		apDspUppConfig->nDmaMasterPriority);

	// Apply the appropriate pin mux settings to enable the uPP 
	// (based on configuration)
	if (eeTransmit == apDspUppConfig->eChanADir || 
		eeTransmit == apDspUppConfig->eChanBDir)
	{
		// Select the appropriate Transmit Clock
		if (eeUPP_2xTXCLK == apDspUppConfig->eTxClockSel)
		{
			tcDspSyscfg::SetChipConfig(UPP_TX_CLKSRC_2xTXCLK);

			// Enable 2xTXCLK pin 
			if (tcDspSyscfg::SetPinMuxConfig(UPP_2xTXCLK) < 0)
				return -1;
		}
		else if (eePLL0_SYSCLK2 == apDspUppConfig->eTxClockSel)
		{
			tcDspSyscfg::SetChipConfig(ASYNC3_CLKSRC_PLL0_SYSCLK2);
			tcDspSyscfg::SetChipConfig(UPP_TX_CLKSRC_ASYNC3);
		}
		else if (eePLL1_SYSCLK2 == apDspUppConfig->eTxClockSel)
		{
			tcDspSyscfg::SetChipConfig(ASYNC3_CLKSRC_PLL1_SYSCLK2);
			tcDspSyscfg::SetChipConfig(UPP_TX_CLKSRC_ASYNC3);
		}
	}	

	if (eeDisabled != apDspUppConfig->eChanADir)
	{
		// Enable Channel A pins if it is not disabled
		if (tcDspSyscfg::SetPinMuxConfig(laPinFuncCHA) < 0)
			return -1;
	}

	if (eeDisabled != apDspUppConfig->eChanBDir)
	{
		// Enable Channel B pins if it is not disabled
		if (tcDspSyscfg::SetPinMuxConfig(laPinFuncCHB) < 0)
			return -1;
	}

	if (eeDisabled == apDspUppConfig->eChanBDir &&
		eeDisabled != apDspUppConfig->eChanADir)
	{
		// Enable lower 8 bits for Channel A
		if (tcDspSyscfg::SetPinMuxConfig(laPinFuncData7_0) < 0)
			return -1;

		if (ee8Bit != apDspUppConfig->eChanBitWidthA)
		{
			// Enable upper 8 bits for Channel A
			if (true == apDspUppConfig->bChanAUseXData)
			{
				if (tcDspSyscfg::SetPinMuxConfig(laPinFuncXData7_0) < 0)
					return -1;
			}
			else
			{
				if (tcDspSyscfg::SetPinMuxConfig(laPinFuncData15_8) < 0)
					return -1;
			}
		}
	}
	else
	{
		if (eeDisabled != apDspUppConfig->eChanADir)
		{
			// Enable lower 8 bits for Channel A
			if (tcDspSyscfg::SetPinMuxConfig(laPinFuncData7_0) < 0)
				return -1;
		
			if (ee8Bit != apDspUppConfig->eChanBitWidthA)
			{
				// Enable upper 8 bits for Channel A
				if (tcDspSyscfg::SetPinMuxConfig(laPinFuncXData7_0) < 0)
					return -1;
			}
		}

		if (eeDisabled != apDspUppConfig->eChanBDir)
		{
			// Enable lower 8 bits for Channel B
			if (tcDspSyscfg::SetPinMuxConfig(laPinFuncData15_8) < 0)
				return -1;

			if (ee8Bit != apDspUppConfig->eChanBitWidthB)
			{
				// Enable upper 8 bits for Channel B
				if (tcDspSyscfg::SetPinMuxConfig(laPinFuncXData15_8) < 0)
					return -1;
			}
		}

	}		
	
	// make sure to enable the power and clocks to the uPP device
	tcDspLpsc::ConfigPeripheral(tcDspLpsc::eeUPP, tcDspLpsc::eeENABLE);

	// Delete Chan A MBXs if they exists
	if (NULL != mhMbxDoneA)
		MBX_delete(mhMbxDoneA);

	if (NULL != mhMbxIntA)
		MBX_delete(mhMbxIntA);

	if (NULL != mhMbxQueueA)
		MBX_delete(mhMbxQueueA);

	// Delete Chan B MBXs if they exists
	if (NULL != mhMbxDoneB)
		MBX_delete(mhMbxDoneB);
	
	if (NULL != mhMbxIntB)
		MBX_delete(mhMbxIntB);

	if (NULL != mhMbxQueueB)
		MBX_delete(mhMbxQueueB);


	// Initialize Chan A MBXs if Chan A is enabled
	if (eeDisabled != apDspUppConfig->eChanADir) 
	{
		lsMbxAttrsA.name = "mhMbxDoneA";
		mhMbxDoneA = MBX_create(sizeof(tsMbxMsg), lnMbxLenA, 
			&lsMbxAttrsA);
		if (NULL == mhMbxDoneA)
			return -1;

		lsMbxAttrsA.name = "mhMbxIntA";
		mhMbxIntA = MBX_create(sizeof(tsMbxMsg), 2, &lsMbxAttrsA);
		if (NULL == mhMbxIntA)
			return -1;

		lsMbxAttrsA.name = "mhMbxQueueA";
		mhMbxQueueA = MBX_create(sizeof(tsMbxMsg), lnMbxLenA, 
			&lsMbxAttrsA);
		if (NULL == mhMbxQueueA)
			return -1;
	}

	// Initialize Chan B MBXs if Chan B is enabled
	if (eeDisabled != apDspUppConfig->eChanBDir) 
	{
		lsMbxAttrsB.name = "mhMbxDoneB";
		mhMbxDoneB = MBX_create(sizeof(tsMbxMsg), lnMbxLenB, 
			&lsMbxAttrsB);
		if (NULL == mhMbxDoneB)
			return -1;

		lsMbxAttrsB.name = "mhMbxIntB";
		mhMbxIntB = MBX_create(sizeof(tsMbxMsg), 2, &lsMbxAttrsB);
		if (NULL == mhMbxIntB)
			return -1;

		lsMbxAttrsB.name = "mhMbxQueueB";
		mhMbxQueueB = MBX_create(sizeof(tsMbxMsg), lnMbxLenB, 
			&lsMbxAttrsB);
		if (NULL == mhMbxQueueB)
			return -1;
	}

	// Reset the uPP
	reset();

	// Program UPCTL reg (mode, data width/format, etc.)
	if (eeTransmit == apDspUppConfig->eChanADir &&
		eeReceive == apDspUppConfig->eChanBDir)
	{
		luUpctlReg.sRegBits.MODE = eeAXmitBRcv; // Xmit/Rcv Mode
	}	
	else if (eeReceive == apDspUppConfig->eChanADir &&
			 eeTransmit == apDspUppConfig->eChanBDir)
	{
		luUpctlReg.sRegBits.MODE = eeARcvBXmit; // Xmit/Rcv Mode
	}
	else if (eeTransmit == apDspUppConfig->eChanADir ||
			 eeTransmit == apDspUppConfig->eChanBDir)
	{
		luUpctlReg.sRegBits.MODE = eeAllXmit; // Xmit/Rcv Mode
	}
	else if (eeReceive == apDspUppConfig->eChanADir ||
			 eeReceive == apDspUppConfig->eChanBDir)
	{
		luUpctlReg.sRegBits.MODE = eeAllRcv; // Xmit/Rcv Mode
	}
	
	if (eeDisabled != apDspUppConfig->eChanBDir || 
		true == apDspUppConfig->bChanAUseXData)
	{
		// Must "enable" both channels if B is active
		// Though if it's just B active, we do not enable A's pinmuxing
		// Or in case where only Channel A is active, but we want CHN=1
		// data bit assignments
		luUpctlReg.sRegBits.CHN = 1; // Only Chan A active, or Chan A/B active
	}

	luUpctlReg.sRegBits.SDRTXIL = 0; // Not supported... yet
	luUpctlReg.sRegBits.DDRDEMUX = 0; // Not supported... yet
	
	luUpctlReg.sRegBits.DRA = 0; // Chan A single/double data rate
	
	if (ee8Bit != apDspUppConfig->eChanBitWidthA)
	{
		luUpctlReg.sRegBits.IWA = 1; // Chan A 8/16-bit interface
	}

	// Mod 8 because 8 and 16 bit = 0
	luUpctlReg.sRegBits.DPWA = (apDspUppConfig->eChanBitWidthA)%8; 
	luUpctlReg.sRegBits.DPFA = eeRJSE; // Chan A data packing format

	luUpctlReg.sRegBits.DRB = 0; // Chan B single/double data rate
	
	if (ee8Bit != apDspUppConfig->eChanBitWidthB)
	{
		luUpctlReg.sRegBits.IWB = 1; // Chan B 8/16-bit interface
	}

	// Mod 8 because 8 and 16 bit = 0
	luUpctlReg.sRegBits.DPWB = (apDspUppConfig->eChanBitWidthB)%8; 
	luUpctlReg.sRegBits.DPFB = eeRJSE; // Chan B data packing format
	mpUppRegs->UPCTL = luUpctlReg.nRegWord;

	// Program UPICR reg (signal enable, clock rate)
	luUpicrReg.sRegBits.STARTPOLA = 0; 
	luUpicrReg.sRegBits.ENAPOLA = 0;
	luUpicrReg.sRegBits.WAITPOLA = 0;
	luUpicrReg.sRegBits.STARTA = apDspUppConfig->bChanAUseStart;
	luUpicrReg.sRegBits.ENAA = 1;
	luUpicrReg.sRegBits.WAITA = 1;
	luUpicrReg.sRegBits.CLKDIVA = apDspUppConfig->nChanAClkDiv;
	luUpicrReg.sRegBits.CLKINVA = 0;	
	luUpicrReg.sRegBits.TRISA = 0; // Chan A high-impedence state

	luUpicrReg.sRegBits.STARTPOLB = 0;
	luUpicrReg.sRegBits.ENAPOLB = 0;
	luUpicrReg.sRegBits.WAITPOLB = 0;
	luUpicrReg.sRegBits.STARTB = apDspUppConfig->bChanBUseStart;
	luUpicrReg.sRegBits.ENAB = 1;
	luUpicrReg.sRegBits.WAITB = 1;
	luUpicrReg.sRegBits.CLKDIVB = apDspUppConfig->nChanBClkDiv;
	luUpicrReg.sRegBits.CLKINVB = 0;	
	luUpicrReg.sRegBits.TRISB = 0; // Chan B high-impedence state

	mpUppRegs->UPICR = luUpicrReg.nRegWord;

	// Program UPIVR reg (idle xmit value)
	luUpivrReg.sRegBits.VALA = 0xFFFF; // Chan A idle value, if TRISA==0
	luUpivrReg.sRegBits.VALB = 0xFFFF; // Chan B idle value, if TRISB==0

	mpUppRegs->UPIVR = luUpivrReg.nRegWord;

	// Program UPTCR reg (i/o threshold)
	luUptcrReg.sRegBits.RDSIZEI = apDspUppConfig->eThresholdRxA;
	luUptcrReg.sRegBits.RDSIZEQ = apDspUppConfig->eThresholdRxB;
	
	luUptcrReg.sRegBits.TXSIZEA = apDspUppConfig->eThresholdTxA;
	luUptcrReg.sRegBits.TXSIZEB = apDspUppConfig->eThresholdTxB;

	mpUppRegs->UPTCR = luUptcrReg.nRegWord;

	// Program UPDLB reg (digital loopback)
	mpUppRegs->UPDLB = 0; // TODO: support internal loopback?

	// Clear all interrupts using UPIEC
	mpUppRegs->UPIEC = 0x1F1F;

	// Program uPP interrupt enable reg (UPIES)
	luUpiesReg.nRegWord = 0;
	// DMA I interrupts
	if (eeDisabled != apDspUppConfig->eChanADir) 
	{
		luUpiesReg.sRegBits.EOWI = 1;
		luUpiesReg.sRegBits.DPEI = 1;
		luUpiesReg.sRegBits.UORI = 1;
		luUpiesReg.sRegBits.ERRI = 1;
		// No need to service end of line interrupt
		luUpiesReg.sRegBits.EOLI = 0; 
	}
	// DMA Q interrupts
	if (eeDisabled != apDspUppConfig->eChanBDir) 
	{
		luUpiesReg.sRegBits.EOWQ = 1;
		luUpiesReg.sRegBits.DPEQ = 1;
		luUpiesReg.sRegBits.UORQ = 1;
		luUpiesReg.sRegBits.ERRQ = 1;
		// No need to service end of line interrupt	
		luUpiesReg.sRegBits.EOLQ = 0; 
	}
	
	mpUppRegs->UPIES = luUpiesReg.nRegWord;
 

	// Register ISR (if enabled)
	// Setup interrupt handling function
	// TODO: Failure codes for these functions?
	HWI_dispatchPlug(apDspUppConfig->nHWInterruptLevel, 
					(Fxn)isr, 
					-1, 
					&hwi_attrs);
	HWI_eventMap(apDspUppConfig->nHWInterruptLevel, 
				 94);
	C62_enableIER(1 << apDspUppConfig->nHWInterruptLevel);

	
	// Store directionality of channels
	meChanADir = apDspUppConfig->eChanADir;
	meChanBDir = apDspUppConfig->eChanBDir;

	// Turn on the uPP and other final UPPCR config
	luUppcrReg.sRegBits.FREE = 1; // Emulation will not halt uPP
	luUppcrReg.sRegBits.EN = 1; // Enable uPP device
	mpUppRegs->UPPCR = luUppcrReg.nRegWord;

	// Start the Chan A thread for handling the DMA
	if (eeDisabled != apDspUppConfig->eChanADir)
	{
		if (NULL != mhDmaTskA)
			TSK_delete(mhDmaTskA);

		tsk_attrs = TSK_ATTRS;
		tsk_attrs.name = "DmaTskA";
		tsk_attrs.stacksize = 1024;
		tsk_attrs.priority  = apDspUppConfig->nTskPriorityChanA;
		mhDmaTskA = TSK_create((Fxn)programDMA, &tsk_attrs, this, 
				eeChanA);

		// Check that task creation was successful
		if (NULL == mhDmaTskA)
			return -1;
	}

	// Start the Chan B thread for handling the DMA
	if (eeDisabled != apDspUppConfig->eChanBDir)
	{
		if (NULL != mhDmaTskB)
			TSK_delete(mhDmaTskB);

		tsk_attrs = TSK_ATTRS;
		tsk_attrs.name = "DmaTskB";
		tsk_attrs.stacksize = 1024;
		tsk_attrs.priority  = apDspUppConfig->nTskPriorityChanB;
		mhDmaTskB = TSK_create((Fxn)programDMA, &tsk_attrs, this,
				eeChanB);

		// Check that task creation was successful
		if (NULL == mhDmaTskB)
			return -1;
	}

	return 0;
}

/**
* Perform software reset of the uPP.
*
* @return None.
*/
void 
tcDspUpp::reset()
{
	tuUppcrReg luUppcrReg = {0};

	// Read current contents of the register
	luUppcrReg.nRegWord = mpUppRegs->UPPCR;

	// Place the uPP in SW reset
	luUppcrReg.sRegBits.SWRST = 1; // SW reset enabled
	mpUppRegs->UPPCR = luUppcrReg.nRegWord;

	// Wait at least 200 cycles 
	TSK_sleep(200);

	// Clear the SW reset bit
	luUppcrReg.sRegBits.SWRST = 0; // SW reset disabled
	mpUppRegs->UPPCR = luUppcrReg.nRegWord;
}

/**
 * Get handle to mailbox for associated channel where info on
 */
MBX_Handle 
tcDspUpp::getMBX(teUppChan aeChan)
{
	return (eeChanB == aeChan)?mhMbxDoneB:mhMbxDoneA;
}

/**
 * Queue transmit of given data buffer. Use getMBX() to get corresponding
 * mailbox where pointer info will be posted once data has been tramsmitted.
 */
int 
tcDspUpp::transmit(teUppChan aeChan,
		 		   const uint8_t* apXmitData,
		 		   uint16_t anByteCnt,
		 		   uint16_t anLineCnt,
		 		   uint16_t anLineOffset)
{
	// MBX Queue msg
	tsMbxMsg lsMbxMsg;
	// Queue MBX
	MBX_Handle lhMbxQueue = (eeChanB == aeChan)?mhMbxQueueB:mhMbxQueueA; 

	// Check aeChan directionality...
	if (eeChanA == aeChan)
	{
		if (eeTransmit != meChanADir)
		{
			return -1;
		}
	}
	else if (eeChanB == aeChan)
	{
		if (eeTransmit != meChanBDir)
		{
			return -1;
		}
	}
	else
	{
		return -1;
	}

	// Check if apXmitData is on 64-bit aligned
	if (0 != (((uint32_t)apXmitData)&0x7))
		return -1;
		
	// Check that anByteCnt is even
	if (0 != (anByteCnt&0x1))
		return -1;
		
	// Check that anLineOffset is 64-bit aligned
	if (0 != (anLineOffset&0x7))
		return -1;
	
	//TODO: Check restrictions on other inputs

	// Setup the request
	lsMbxMsg.pBufPtr = (uint8_t*)apXmitData;
	lsMbxMsg.nByteCnt = anByteCnt;
	lsMbxMsg.nLineCnt = anLineCnt;
	lsMbxMsg.nLineOffset = anLineOffset;
	lsMbxMsg.pOptArg = NULL;

	// Add the request to the queue mailbox
	if (false == MBX_post(lhMbxQueue, &lsMbxMsg, SYS_FOREVER))
	{
		return -1;
	}

	return 0;
	// TODO: failure conditions!
}

/**
* Add buffer to receive queue. Use getMBX() to get corresponding
* mailbox where pointer info will be posted once data has been received. 				
*/
int 
tcDspUpp::receive(teUppChan aeChan,
				  uint8_t* apRcvData,
				  uint16_t anByteCnt,
				  uint16_t anLineCnt,
				  uint16_t anLineOffset)
{
	// MBX Queue msg
	tsMbxMsg lsMbxMsg;
	// Queue MBX
	MBX_Handle lhMbxQueue = (eeChanB == aeChan)?mhMbxQueueB:mhMbxQueueA; 

	// Check aeChan directionality...
	if (eeChanA == aeChan)
	{
		if (eeReceive != meChanADir)
		{
			return -1;
		}
	}
	else if (eeChanB == aeChan)
	{
		if (eeReceive != meChanBDir)
		{
			return -1;
		}
	}
	else
	{
		return -1;
	}

	// Check if apXmitData is on 64-bit aligned
	if (0 != (((uint32_t)apRcvData)&0x7))
		return -1;
		
	// Check that anByteCnt is even
	if (0 != (anByteCnt&0x1))
		return -1;
		
	// Check that anLineOffset is 64-bit aligned
	if (0 != (anLineOffset&0x7))
		return -1;


	// Setup the request
	lsMbxMsg.pBufPtr = apRcvData;
	lsMbxMsg.nByteCnt = anByteCnt;
	lsMbxMsg.nLineCnt = anLineCnt;
	lsMbxMsg.nLineOffset = anLineOffset;
	lsMbxMsg.pOptArg = NULL;

	// Add the request to the queue mailbox
	if (false == MBX_post(lhMbxQueue, &lsMbxMsg, SYS_FOREVER))
	{
		return -1;
	}

	return 0;
	//TODO: failure conditions!
}

/**
 * Thread for programming the DMA for the specified channel.
 */
void 
tcDspUpp::programDMA(tcDspUpp* apDspUpp, teUppChan aeChan)
{
	// Pointer to the tcDspUpp object
	tcDspUpp* lpDspUpp = apDspUpp;
        // Queue MBX
	MBX_Handle lhMbxQueue = (eeChanB == aeChan)? 
        lpDspUpp->mhMbxQueueB:lpDspUpp->mhMbxQueueA; 
	// Intermediate MBX (for buffers being DMAed)
	MBX_Handle lhMbxInt = (eeChanB == aeChan)?
        lpDspUpp->mhMbxIntB:lpDspUpp->mhMbxIntA;
	// Local MBX message for copying and setting DMA
	tsMbxMsg lsMbxMsg;
	// Used for checking DMA status
	tuUpiqs2Reg luUpiqs2Reg = {0};
	// Used for setting the DMA reg 0
	tuUpiqd1Reg luUpiqd1Reg = {0};

    // This thread runs continuously
    while(1)
    {

        // Pend on mhMbxQueue waiting for a new message that can be
        // used to program the DMA
        MBX_pend(lhMbxQueue, &lsMbxMsg, SYS_FOREVER);

        // Post to mhMbxInt waiting for space to open up in DMA 
        MBX_post(lhMbxInt, &lsMbxMsg, SYS_FOREVER);

        // Now we should be able to safely program the DMA

        // Read the appropriate DMA status register
        if (eeChanB == aeChan)
        {
            luUpiqs2Reg.nRegWord = lpDspUpp->mpUppRegs->UPQS2;
        }
        else
        {
            luUpiqs2Reg.nRegWord = lpDspUpp->mpUppRegs->UPIS2;
        }

        // Check if the DMA can be programmed
        while (1 == luUpiqs2Reg.sRegBits.PEND)
        {
            // The DMA is busy, this should not happen
            if (NULL != lpDspUpp->mpErrorCallback)
		        lpDspUpp->mpErrorCallback(0xDEADBEEF);

            // Maybe it will fix itself?
            TSK_sleep(100);
        }

        // Program the DMA
        luUpiqd1Reg.sRegBits.BCNT = lsMbxMsg.nByteCnt;
        luUpiqd1Reg.sRegBits.LNCNT = lsMbxMsg.nLineCnt;

        if (eeChanB == aeChan)
        {
            lpDspUpp->mpUppRegs->UPQD0 = (uint32_t)lsMbxMsg.pBufPtr;
            lpDspUpp->mpUppRegs->UPQD1 = luUpiqd1Reg.nRegWord;
            lpDspUpp->mpUppRegs->UPQD2 = lsMbxMsg.nLineOffset;
        }
        else
        {
            lpDspUpp->mpUppRegs->UPID0 = (uint32_t)lsMbxMsg.pBufPtr;
            lpDspUpp->mpUppRegs->UPID1 = luUpiqd1Reg.nRegWord;
            lpDspUpp->mpUppRegs->UPID2 = lsMbxMsg.nLineOffset;
        }

    }
}

/**
 * Handle any uPP related interrupts that might occur.
 *
 * \return 0 on success, negative on failure. 
 */
int 
tcDspUpp::isr(tcDspUpp* apDspUpp)
{
	// Return value
	int retval = 0;
	// Pointer to the tcDspUpp object
	tcDspUpp* lpDspUpp = apDspUpp;
	// Local copy of uPP registers so that we don't have to 
	// dereference lpDspUpp so many times
	volatile tsUppRegs* const lpUppRegs = lpDspUpp->mpUppRegs;
	// Value of the UPIER register, which tells us which interrupts occurred
	tuUpierReg luUpierReg = {0};
	// Used to clear the UPIER register interrupts once processed
	tuUpierReg luUpierRegClr = {0};
	// Used to update the Done MBX
	tsMbxMsg lsMbxMsg;

	// Check for interrupts
	luUpierReg.nRegWord = lpUppRegs->UPIER;

	// Process all pending interrupts. 
	while (0 != luUpierReg.nRegWord)
	{
		// Check for Channel I programming error interrupt
		if (luUpierReg.sRegBits.DPEI == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.DPEI = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}

		// Check for Channel I underrun/overflow interrupt
		if (luUpierReg.sRegBits.UORI == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.UORI = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;

			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}

		// Check for Channel I error interrupt
		if (luUpierReg.sRegBits.ERRI == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.ERRI = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}

		// Check for Channel I End-of-Window interrupt
		if (luUpierReg.sRegBits.EOWI == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.EOWI = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;

			// Handle the interrupt
			
			// Get the DMAed data info
			if (true == MBX_pend(lpDspUpp->mhMbxIntA,
								 &lsMbxMsg,
								 0))
			{
				// Update the done MBX
				if (false == MBX_post(lpDspUpp->mhMbxDoneA,
					   				  &lsMbxMsg,
								  	  0))
				{
					// Return queue overflow!
					retval = -1;
				}	
			}
			else
			{
				// No data in the intermediate mailbox, but we received
				// an interrupt, this is a problem
				retval = -1;
			}			
		}

		// Check for Channel I End-of-Line interrupt
		if (luUpierReg.sRegBits.EOLI == 1)
		{
			// Clear the interrupt
			luUpierRegClr.sRegBits.EOLI = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			luUpierRegClr.nRegWord = 0;

			// Handle the interrupt

		}	

		// Check for Channel Q programming error interrupt
		if (luUpierReg.sRegBits.DPEQ == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.DPEQ = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}

		// Check for Channel Q underrun/overflow interrupt
		if (luUpierReg.sRegBits.UORQ == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.UORQ = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}

		// Check for Channel Q error interrupt
		if (luUpierReg.sRegBits.ERRQ == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.ERRQ = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			if (NULL != lpDspUpp->mpErrorCallback)
				lpDspUpp->mpErrorCallback(luUpierRegClr.nRegWord);
		}	

		// Check for Channel Q End-of-Window interrupt
		if (luUpierReg.sRegBits.EOWQ == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.EOWQ = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt
			
			// Get the DMAed data info
			if (true == MBX_pend(lpDspUpp->mhMbxIntB,
								 &lsMbxMsg,
								 0))
			{
				// Update the done MBX
				if (false == MBX_post(lpDspUpp->mhMbxDoneB,
					   				  &lsMbxMsg,
								  	  0))
				{
					// Return queue overflow!
					retval = -1;
				}	
			}
			else
			{
				// No data in the intermediate mailbox, but we received
				// an interrupt, this is a problem
				retval = -1;
			}
		}

		// Check for Channel Q End-of-Line interrupt
		if (luUpierReg.sRegBits.EOLQ == 1)
		{
			// Clear the interrupt
			luUpierRegClr.nRegWord = 0;
			luUpierRegClr.sRegBits.EOLQ = 1;
			lpUppRegs->UPIER = luUpierRegClr.nRegWord;
			
			// Handle the interrupt

		}

		// Check for more interrupts
		luUpierReg.nRegWord = lpUppRegs->UPIER;
	}

	// Write end of interrupt vector to allow future calls
	lpUppRegs->UPEOI = 0;

	return retval;
}

/**
 * Set the error callback function.
 */
void 
tcDspUpp::registerErrorCallback(tfErrorCallback afErrorCallback)
{
	mpErrorCallback = afErrorCallback;
}

/**
 * Private constructor.
 */
tcDspUpp::tcDspUpp()
: mpUppRegs((tsUppRegs*)UPP_REG_BASE)
, mhDmaTskA(NULL)
, mhDmaTskB(NULL)
, mhMbxDoneA(NULL)
, mhMbxDoneB(NULL)
, mhMbxIntA(NULL)
, mhMbxIntB(NULL)
, mhMbxQueueA(NULL)
, mhMbxQueueB(NULL)
, mpErrorCallback(NULL)
, mbFirstInit(true)
, meChanADir(eeDisabled)
, meChanBDir(eeDisabled)
{

}

/**
 * Private destructor.
 */
tcDspUpp::~tcDspUpp()
{

}

