/*****************************************************************************
*
* Freescale Semiconductor Inc.
* (c) Copyright 2004-2005 Freescale Semiconductor, Inc.
* (c) Copyright 2001-2004 Motorola, Inc.
* ALL RIGHTS RESERVED.
*
******************************************************************************
*
* File Name: pcmccp.c
*
* Description: FreeMaster (PC Master) services for uCCP
*
* $Version: 2.0.10.0$
*
*****************************************************************************/

#include "qs.h"
#include "uccp.h"
#include "pcmccp.h"
#include "pcmccp_cfg.h"
#include "pcmccp_priv.h"

/***********************************
*  function prototypes
***********************************/

UWord16 PCMCCP_CcpCallback(UWord16 serviceId, UWord16* params, UCCP_SERVICE_DATA* pReturnData);
UWord16 PCMCCP_Compare8S();
UWord16 PCMCCP_Compare8U();
UWord16 PCMCCP_Compare16S();
UWord16 PCMCCP_Compare16U();
UWord16 PCMCCP_Compare32S();
UWord16 PCMCCP_Compare32U();

/*****************************************
*  recorder buffer (may go to far memory)
*****************************************/

// put buffer into far memory ?
#if PCMCCP_REC_FARBUFF
#pragma section fardata begin
#endif

// The Buffer
static UWord8 pcmccp_recBuff[PCMCCP_REC_BUFFSIZE];

// end of far memory section
#if PCMCCP_REC_FARBUFF
#pragma section fardata end
#endif

/***********************************
*  other private variables
***********************************/

// recorder information 
static const PCMCCP_INFO pcmccp_info = 
{
	PCMCCP_VERSION | PCMCCP_VER_MAGIC,	// version & magic
	0 | (1 << 8),			// flags & 1byte data bus width
	PCMCCP_REC_MAXVARS, 	// max variable count
	PCMCCP_REC_BUFFSIZE,	// recorder buffer
	PCMCCP_REC_TIMEBASE,    // recorder time base
};	

// recorder configuration
static PCMCCP_RECSETUP pcmccp_recSetup;

// buffer information for PCMCCP_CMD_GETRECBUFF
static PCMCCP_RECBUFF pcmccp_recBuffInfo;

// sampling pointers
static UWord32 pcmccp_recWritePtr;
static UWord32 pcmccp_recEndBuffPtr;

// sum of recorder variable sizes
static UWord16 pcmccp_recSetSize;

// time divisor
static UWord16 pcmccp_recTimeDiv;

// post-trigger countdown counter
static UWord16 pcmccp_recStopCountDown;

// recorder runtime flags
static volatile union 
{
	UWord16 all;
	
	struct 
	{
		unsigned bIsConfigured : 1;		// recorder is configured
		unsigned bIsRunning : 1;		// recorder is running
		unsigned bIsStopping : 1;		// trigger activated, sample countdown
		unsigned bInvirginCycle : 1;	// virgin cycle of the circular buffer in-progress
		unsigned bTrgCrossActive : 1;	// trigger trheshold was crossed
	};
	
} pcmccp_recFlags;

// table of compare functions
typedef UWord16 (*COMPARE_FUNC)();

// this strange formula assures the array is allocted for 
// six (32bit) function pointers in both LDM and SDM models
static COMPARE_FUNC pcmccp_compareFuncTbl[6*sizeof(COMPARE_FUNC)/sizeof(int*)] = 
{
	PCMCCP_Compare8U, PCMCCP_Compare16U, PCMCCP_Compare32U,	// unsigned comparing
	PCMCCP_Compare8S, PCMCCP_Compare16S, PCMCCP_Compare32S,	// signed comparing
};

// active compare function
static COMPARE_FUNC pcmccp_compareFunc;

/***********************************
*  functions
***********************************/

// main initialization routine, must be called after CAN and CCP are initialized

PCMCCPRESULT PCMCCP_Init()
{
	// install the CCP Action Service callback
	if(UCCP_SetActionService(PCMCCP_ACTION_SERVICE, PCMCCP_CcpCallback))
		return PCMCCP_ERR_INIT;

	// The address of our data buffer will never change, so we can
	// load it to pcmccp_recBuffInfo.buffAddr now already
	
	// We do it in assembly as a 32bit number so we assure it works even in SDM
	// (note that SDM would cut the high word if poiter-type would be used)
	asm 
	{
		move.l #>>pcmccp_recBuff, R0
		move.l R0, pcmccp_recBuffInfo.buffAddr
	}

	// clear all runtime flags
	pcmccp_recFlags.all = 0;

	// setup magic numbers of our structures
	// the master can use the magic numbers for sanity checks
	pcmccp_recBuffInfo.magic = PCMCCP_RECBUFF_MAGIC;

	// no error
	return PCMCCP_OK;
}

// abort recorder before it is going to be reinitialized
// returns PC Master status code

static UWord16 PCMCCP_AbortRec()
{
	// just clear flags
	pcmccp_recFlags.all = 0;
	return PCMCCP_STC_OK;
}

// setup recorder using the data in setup structure (master has just downloaded it)
// returns PC Master status code

static UWord16 PCMCCP_SetupRec()
{
	UWord16 i, tmp;
	UWord32 blen;

	// if anything fails in configuration, recorder should be idle
	PCMCCP_AbortRec();
		
	// now check passed config:
	
	// variable count
	if(pcmccp_recSetup.varCnt >= PCMCCP_REC_MAXVARS)
		return PCMCCP_STC_INVSIZE;

	// sum sizes of all variables
	pcmccp_recSetSize = 0;
	
	for(i=0; i<pcmccp_recSetup.varCnt; i++)
	{
		tmp = pcmccp_recSetup.vars[i].size;
		
		// size can be 1,2,4 only
		if(!tmp || tmp > 4 || tmp == 3)
		    return PCMCCP_STC_INVSIZE;
	
		pcmccp_recSetSize += pcmccp_recSetup.vars[i].size;
	}

	// at least one variable must be configured	
	if(!pcmccp_recSetSize)
		return PCMCCP_STC_INVSIZE;
	
	// any trigger
	if(pcmccp_recSetup.trgMode)
	{
		// get trigger variable size
		tmp = pcmccp_recSetup.trgVarInfo & PCMCCP_REC_TRG_SIZE_MASK;
		
		// size can be 1,2,4 only
		if(!tmp || tmp > 4 || tmp == 3)
		    return PCMCCP_STC_INVSIZE;
		
		// convert tmp to index in compare functions table    
		tmp /= 2;

		// signed comparsion ?
		if(pcmccp_recSetup.trgVarInfo & PCMCCP_REC_TRG_SIGNED)
			tmp += 3;

		// our compare function		
		pcmccp_compareFunc = pcmccp_compareFuncTbl[tmp];
	}
	// no trigger
	else
	{
		// no comparing
		pcmccp_compareFunc = NULL;
	}
	
	// total recorder buffer length
	blen = impyuu(pcmccp_recSetup.totalSmps, pcmccp_recSetSize);

	// memory available ?	
	if(blen > PCMCCP_REC_BUFFSIZE)
	    return PCMCCP_STC_INVSIZE;

	// remember the effective end of circular buffer
	pcmccp_recEndBuffPtr = pcmccp_recBuffInfo.buffAddr + blen;

	// everything is okay	
	pcmccp_recFlags.bIsConfigured = 1;
	return PCMCCP_STC_OK;
}

// pull the trigger (starts recorder stop-countdown)

inline void PCMCCP_TriggerRec()
{
	pcmccp_recFlags.bIsStopping = 1;
	pcmccp_recStopCountDown = pcmccp_recSetup.postTrigger;
}

// start recording (initialize internal recording variables and flags)
// returns PC Master status code

static UWord16 PCMCCP_StartRec()
{
	// must be configured
	if(!pcmccp_recFlags.bIsConfigured)
		return PCMCCP_STC_NOTINIT;
		
	// already running ?
	if(pcmccp_recFlags.bIsRunning)
		return PCMCCP_STC_RECRUN;

	// initialize write pointer
	pcmccp_recWritePtr = pcmccp_recBuffInfo.buffAddr;
	
	// current (first) sample index
	pcmccp_recBuffInfo.startIx = 0;
	
	// initialize time divisor
	pcmccp_recTimeDiv = 0;
	
	// initiate virgin cycle
	pcmccp_recFlags.bIsStopping = 0;		// no trigger active
	pcmccp_recFlags.bTrgCrossActive = 0;	// waiting for threshold crossing
	pcmccp_recFlags.bInvirginCycle = 1;		// initial cycle
	pcmccp_recFlags.bIsRunning = 1;			// is running now!
	
	return PCMCCP_STC_OK;
}

// stop recorder (manual trigger)
// returns PC Master status code

static UWord16 PCMCCP_StopRec()
{
	// must be configured
	if(!pcmccp_recFlags.bIsConfigured)
		return PCMCCP_STC_NOTINIT;
		
	// already stopped ?
	if(!pcmccp_recFlags.bIsRunning)
		return PCMCCP_STC_RECDONE;
	
	// simulate trigger
	PCMCCP_TriggerRec();
	return PCMCCP_STC_OK;
}

// get recorder status
// returns PC Master status code

static UWord16 PCMCCP_GetRecSts()
{
	// must be configured
	if(!pcmccp_recFlags.bIsConfigured)
		return PCMCCP_STC_NOTINIT;
		
	// already stopped ?
	return pcmccp_recFlags.bIsRunning ? PCMCCP_STC_RECRUN : PCMCCP_STC_RECDONE;
}

// check settings before returning recorder buffer information
// returns PC Master status code

static UWord16 PCMCCP_GetRecBuff()
{
	// must be configured
	if(!pcmccp_recFlags.bIsConfigured)
		return PCMCCP_STC_NOTINIT;
		
	// already stopped ?
	if(pcmccp_recFlags.bIsRunning)
		return PCMCCP_STC_SERVBUSY;
		
	return PCMCCP_STC_OK;
}

////////////////////////////////////////////////////////////////////////////////////
//
// main control routine called from uCCP module to handle PC Master's ACTION SERVICE

// we fill the pReturnData structure to
//  - set CCP's MTA (memory transfer address)
//  - return the Action Service DTO with the "length" and "type"
//  - the "type" contains the PC Master return code

static UWord16 PCMCCP_CcpCallback(UWord16 serviceId, UWord16* params, UCCP_SERVICE_DATA* pReturnData)
{
	register UWord16 ret = PCMCCP_STC_OK;
	
	// default return data
	pReturnData->mta = 0;
	pReturnData->ext = 0;
	pReturnData->len = 0;

	// first parameter is PC Master-CCP command code
	switch(params[0])
	{
	case PCMCCP_CMD_GETINFO:
		// return pointer to info structure
		pReturnData->mta = GETBYTEADDR(&pcmccp_info);
		pReturnData->len = sizeof(pcmccp_info);
		break;
		
	case PCMCCP_CMD_SETUPREC0:
		// abort recorder
		ret = PCMCCP_AbortRec();
		// provide configuration structure address
		pReturnData->mta = GETBYTEADDR(&pcmccp_recSetup);
		pReturnData->len = sizeof(pcmccp_recSetup);
		break;
		
	case PCMCCP_CMD_SETUPREC:
		ret = PCMCCP_SetupRec();
		break;
		
	case PCMCCP_CMD_STARTREC:
		ret = PCMCCP_StartRec();
		break;
		
	case PCMCCP_CMD_STOPREC:
		ret = PCMCCP_StopRec();
		break;
		
	case PCMCCP_CMD_GETRECSTS:
		ret = PCMCCP_GetRecSts();
		break;
		
	case PCMCCP_CMD_GETRECBUFF:
		// recorder buffer available ?
		ret = PCMCCP_GetRecBuff();
		// buffer structure
		pReturnData->mta = GETBYTEADDR(&pcmccp_recBuffInfo);
		pReturnData->len = sizeof(pcmccp_recBuffInfo);
		break;
	
	default:
		ret = PCMCCP_STC_INVCMD;
	}

	// return code
	pReturnData->type = (UWord8) ret;

	// handled
	return 1;
}

////////////////////////////////////////////////////////////////////////////////////////
//
// Compare functions used by trigger detection
// 
// compare values at address pcmccp_recSetup.trgVarAddr and pcmccp_recSetup.trgThreshold
//
// return zero when value is lower than threshold
// return non-zero when value is greater than or equal as treshold

#define CMP(v,t) ((v) < (t)) ? 0 : 1;

// note: The following inlines might look strange, but those are to assure correct
//       behavior when SDM model is enabled, thus making address operations in low 16 bits only.
//       So I need these inlines to access the trigger variable because its address is kept 
//       as byte address (x2) in UWord32 variable

inline Word8 PCMCCP_GetW8(register UWord32 addr)
{
	register void* raddr;
	register Word8 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( move.bp X:(raddr),tmp );
	return tmp;
}

inline UWord8 PCMCCP_GetUW8(register UWord32 addr)
{
	register void* raddr;
	register UWord8 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( moveu.bp X:(raddr),tmp );
	return tmp;
}

inline Word16 PCMCCP_GetW16(register UWord32 addr)
{
	register void* raddr;
	register Word16 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( asra raddr );
	asm ( move.w X:(raddr),tmp );
	return tmp;
}

inline UWord16 PCMCCP_GetUW16(register UWord32 addr)
{
	register void* raddr;
	register UWord16 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( asra raddr );
	asm ( move.w X:(raddr),tmp );
	return tmp;
}

inline Word32 PCMCCP_GetW32(register UWord32 addr)
{
	register void* raddr;
	register Word32 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( asra raddr );
	asm ( move.l X:(raddr),tmp );
	return tmp;
}

inline UWord32 PCMCCP_GetUW32(register UWord32 addr)
{
	register void* raddr;
	register UWord32 tmp;
	
	asm ( tfr addr,A );
	asm ( move.l A10, raddr );
	asm ( asra raddr );
	asm ( move.l X:(raddr),tmp );
	return tmp;
}

static UWord16 PCMCCP_Compare8S()
{
	return CMP(PCMCCP_GetW8(pcmccp_recSetup.trgVarAddr), PCMCCP_GetW8(GETBYTEADDR(&pcmccp_recSetup.trgThreshold.w)));
}

static UWord16 PCMCCP_Compare8U()
{
	return CMP(PCMCCP_GetUW8(pcmccp_recSetup.trgVarAddr), PCMCCP_GetUW8(GETBYTEADDR(&pcmccp_recSetup.trgThreshold.w)));
}

static UWord16 PCMCCP_Compare16S()
{
	return CMP(PCMCCP_GetW16(pcmccp_recSetup.trgVarAddr), (Word16) pcmccp_recSetup.trgThreshold.w);
}

static UWord16 PCMCCP_Compare16U()
{
	return CMP(PCMCCP_GetUW16(pcmccp_recSetup.trgVarAddr), pcmccp_recSetup.trgThreshold.w);
}

static UWord16 PCMCCP_Compare32S()
{
	return CMP(PCMCCP_GetW32(pcmccp_recSetup.trgVarAddr), (Word32) pcmccp_recSetup.trgThreshold.dw);
}

static UWord16 PCMCCP_Compare32U()
{
	return CMP(PCMCCP_GetUW32(pcmccp_recSetup.trgVarAddr), pcmccp_recSetup.trgThreshold.dw);
}

//////////////////////////////////////////////////////////////////////////////////////
//
// main recorder routine called from timer interrupt when recorder is running

#pragma interrupt called
static void PCMCCP_Recorder2()
{
	UWord16 i;
	
	// skip this call ?
	if(pcmccp_recTimeDiv)
	{
		// maybe next time...
		pcmccp_recTimeDiv--;
		return;
	}
	
	// re-initialize timer divider
	pcmccp_recTimeDiv = pcmccp_recSetup.timeDiv;
	
	// take sample, assembly is needed to make it work in SDM when far buffer is used
	// we operate with 32bit numbers not to loose high word in SDM's pointer arithmetic
	{
		register PCMCCP_RECVAR* pvar = pcmccp_recSetup.vars;
		register UWord32 addr;
		register UWord16 size;
		register UWord8* pdest;
		register UWord8* psrc;
		register UWord16 tmp;
		
		// get destination pointer (stored in 32bit variable)
		asm ( move.l pcmccp_recWritePtr,pdest );
		
		for(i=0; i<pcmccp_recSetup.varCnt; i++)
		{
			addr = pvar->addr;	// source address as 32bit number 
			size = pvar->size;	// size to register (we can be sure size != 0)
			pvar++;
			
			asm {
				tfr addr,A			// C-assembler does not compile "move.l addr,psrc"
				move.l A10,psrc		// so do it this way - it will be optimized anyway
				
			copy:
				move.bp X:(psrc)+,tmp
				move.bp tmp,X:(pdest)+
				dec.w size
				bne <copy				
			}
		}

		// store destination pointer back to 32bit variable
		asm ( move.l pdest,pcmccp_recWritePtr );
	}
	
	// another sample taken (startIx points after this sample (it points to the oldest sample))
	pcmccp_recBuffInfo.startIx++;
	
	// wrap around (circular buffer) ?
	if(pcmccp_recWritePtr >= pcmccp_recEndBuffPtr)
	{	
		pcmccp_recWritePtr = pcmccp_recBuffInfo.buffAddr;
		pcmccp_recFlags.bInvirginCycle = 0;
		pcmccp_recBuffInfo.startIx = 0;
	}

	// no trigger testing in virgin cycle
	if(pcmccp_recFlags.bInvirginCycle)
		return;

	// test trigger condition if still running
	if(!pcmccp_recFlags.bIsStopping && pcmccp_compareFunc != NULL)
	{
		// compare trigger threshold
		i = pcmccp_compareFunc();
		
		// negated logic ?
		if(pcmccp_recSetup.trgMode == PCMCCP_REC_TRGMODE_FALL)
			i = !i;
		
		// above threshold ?
		if(i)
		{
			// were we at least once below threshold ?
			if(pcmccp_recFlags.bTrgCrossActive)
			{
				// EDGE TRIGGER !
				PCMCCP_TriggerRec();
			}
		}
		else
		{
			// we got bellow threshold, now wait for being above threshold
			pcmccp_recFlags.bTrgCrossActive = 1;
		}
	}
	
	// in stopping mode ? (note that this bit might have been set just above!)
	if(pcmccp_recFlags.bIsStopping)
	{
		// count down post-trigger samples expired ?
		if(!pcmccp_recStopCountDown)
		{
			// STOP RECORDER
			pcmccp_recFlags.bIsRunning = 0;
			return;
		}
		
		// perhaps next time
		pcmccp_recStopCountDown--;
	}
}

//////////////////////////////////////////////////////////////////////////////////////
//
// recorder timer-interrupt routine - can be called from application's timer ISR
//
// we return quickly if recorder is not running, otherwise we call quite lengthy 
// recorder routine which does all the recorder work (sampling, triggering)

#pragma interrupt called
void PCMCCP_Recorder()
{
	// recorder not active
	if(!pcmccp_recFlags.bIsRunning)
		return ;

	// do the hard work		
	PCMCCP_Recorder2();
}
