/*****************************************************************************
*
* Freescale Semiconductor Inc.
* (c) Copyright 2004-2007 Freescale Semiconductor, Inc.
* (c) Copyright 2001-2004 Motorola, Inc.
* ALL RIGHTS RESERVED.
*
******************************************************************************
*
* File Name: uccp.c
*
* Description: uCCP driver for uCAN
*
* $Version: 2.0.13.0$
*
*****************************************************************************/

#include "qs.h"
#include "ucan.h"
#include "uccp.h"
#include "uccp_cfg.h"	// user's configuration of the uCCP
#include "uccp_priv.h"	// internal driver definitions

// in the case any parameter is not statically configured, define variable
// which will hold the the dynamically configured value (using UCCP_SelectXX)
#ifndef UCCP_TXMB
static UWord16 UCCP_TXMB;
#define UCCP_TXMB_DYNAMIC 1
#endif

// dtto for RX MB
#ifndef UCCP_RXMB
static UWord16 UCCP_RXMB;
#define UCCP_RXMB_DYNAMIC 1
#endif

// dtto for RX identifier (CRO)
#ifndef UCCP_CROID
static UWord16 UCCP_CROID;
#define UCCP_CROID_DYNAMIC 1
#endif

// dtto for TX identifier (DTO)
#ifndef UCCP_DTOID
static UWord16 UCCP_DTOID;
#define UCCP_DTOID_DYNAMIC 1
#endif

// dtto for ECU address (ECU)
#ifndef UCCP_ECU
static UWord16 UCCP_ECU;
#define UCCP_ECU_DYNAMIC 1
#endif

// FLAGS
static union 
{
	struct 
	{
		unsigned bIsConnected : 1;	// CONNECT status
		unsigned bIsOnLine : 1;		// DISCONNECT temporary off line status
	};
	
	UWord16 all;
	
} uccp_flags;

// MTA pointers (mta should address all 24bits even in SMD - this is why it is UWord32)
static UWord16 uccp_mta0Ext;
static UWord16 uccp_mta1Ext;
static UWord32 uccp_mta0Addr;
static UWord32 uccp_mta1Addr;

// ACTION SERVICE callbacks (I don't use struct not to waste memory by alignment)
#if UCCP_ACTION_SERVICE_COUNT > 0
static UWord16               uccp_asServices[UCCP_ACTION_SERVICE_COUNT];
static UCCP_SERVICE_CALLBACK uccp_asCallbacks[UCCP_ACTION_SERVICE_COUNT];
#endif

// DIAG SERVICE callbacks (I don't use struct not to waste memory by alignment)
#if UCCP_DIAG_SERVICE_COUNT > 0
static UWord16               uccp_asServices[UCCP_DIAG_SERVICE_COUNT];
static UCCP_SERVICE_CALLBACK uccp_dsCallbacks[UCCP_DIAG_SERVICE_COUNT];
#endif

// data return strucutre
#if UCCP_ACTION_SERVICE_COUNT > 0 || UCCP_DIAG_SERVICE_COUNT > 0
static UCCP_SERVICE_DATA uccp_serviceData;
#endif

/****************************************************************************
*
* UCAN Driver Configuration Functions
*
*****************************************************************************/

// Initialization

UCCPRESULT UCCP_Init()
{
	// clear software flags
	uccp_flags.all = 0;

	return UCCP_OK;
}

// Selecting MB dynamicaly (only if not fully static)

#if UCCP_RXMB_DYNAMIC || UCCP_TXMB_DYNAMIC

UCCPRESULT UCCP_SelectMB(UWord16 rxMB, UWord16 txMB)
{
	#if UCCP_RXMB_DYNAMIC
	UCCP_RXMB = rxMB;
	#endif
	
	#if UCCP_TXMB_DYNAMIC
	UCCP_TXMB = txMB;
	#endif
	
	return UCCP_OK;
}

#endif /* UCCP_RXMB_DYNAMIC || UCCP_TXMB_DYNAMIC */

// Selecting CRO ID dynamicaly (only if not static)

#if UCCP_CROID_DYNAMIC

UCCPRESULT UCCP_SelectCRO(UWord16 croId)
{
	UCCP_CROID = croId;
	return UCCP_OK;
}

#endif /* UCCP_CROID_DYNAMIC */

// Selecting DTO ID dynamicaly (only if not static)

#if UCCP_DTOID_DYNAMIC

UCCPRESULT UCCP_SelectDTO(UWord16 dtoId)
{
	UCCP_DTOID = dtoId;
	return UCCP_OK;
}

#endif /* UCCP_DTOID_DYNAMIC */

// Selecting ECU address ID dynamicaly (only if not static)

#if UCCP_ECU_DYNAMIC

UCCPRESULT UCCP_SelectECU(UWord16 ecu)
{
	UCCP_ECU = ecu;
	return UCCP_OK;
}

#endif /* UCCP_ECU_DYNAMIC */

// Registering ACTION SERVICE callback

#if UCCP_ACTION_SERVICE_COUNT > 0
UCCPRESULT UCCP_SetActionService(UWord16 serviceId, UCCP_SERVICE_CALLBACK pCallback)
{
	// does this service exist ?
	Word16 ix = UCCP_FindServiceById(serviceId, uccp_asServices, UCCP_ACTION_SERVICE_COUNT);

	// no, find empty item (if setting up a new service)
	if(pCallback && ix < 0)
		ix = UCCP_FindFreeServiceIx(uccp_asCallbacks, UCCP_ACTION_SERVICE_COUNT);

	// no free position left or service not found
	if(ix < 0)
		return UCCP_ERR_SERVID;

	// set (clear) service callback
	uccp_asServices[ix]  = serviceId;
	uccp_asCallbacks[ix] = pCallback;
	return UCCP_OK;
}
#endif

// Registering DIAG SERVICE callback

#if UCCP_DIAG_SERVICE_COUNT > 0
UCCPRESULT UCCP_SetDiagService(UWord16 serviceId, UCCP_SERVICE_CALLBACK pCallback)
{
	// does this service exist ?
	Word16 ix = UCCP_FindServiceById(serviceId, uccp_dsServices, UCCP_DIAG_SERVICE_COUNT);

	// no, find empty item (if setting up a new service)
	if(pCallback && ix < 0)
		ix = UCCP_FindFreeServiceIx(uccp_dsCallbacks, UCCP_DIAG_SERVICE_COUNT);

	// no free position left or service not found
	if(ix < 0)
		return UCCP_ERR_SERVID;

	// set (clear) service callback
	uccp_dsServices[ix]  = serviceId;
	uccp_dsCallbacks[ix] = pCallback;
}
#endif

// Activate CCP

UCCPRESULT UCCP_Start()
{
	// configure receiver callbacks
	UCAN_SetCallbackMB(UCCP_RXMB, UCCP_RxCallback);
	// configure receiving MB
	UCAN_ConfigMB(UCCP_RXMB, UCAN_BM_RXDF, UCCP_CROID);
	
	// configure transmit buffer only if we have both buffers available
	if(UCCP_RXMB != UCCP_TXMB)
	{
		UCAN_SetCallbackMB(UCCP_TXMB, UCCP_TxCallback);
		UCAN_ConfigMB(UCCP_TXMB, UCAN_BM_TXDF, UCCP_DTOID);
	}

	return UCCP_OK;
}

// Deactivate CCP

UCCPRESULT UCCP_Stop()
{
	UCAN_AbortMB(UCCP_RXMB);
	UCAN_SetCallbackMB(UCCP_RXMB, NULL);
		
	// reset transmit callback only if we have both buffers available
	if(UCCP_RXMB != UCCP_TXMB)
	{
		UCAN_AbortMB(UCCP_TXMB);
		UCAN_SetCallbackMB(UCCP_TXMB, NULL);
	}

	// clear software flags (disconnect)
	uccp_flags.all = 0;

	return UCCP_OK;
}

// find index of free service item

static Word16 UCCP_FindFreeServiceIx(UCCP_SERVICE_CALLBACK* list, UWord16 count)
{
	UWord16 ix;
	
	for(ix=0; ix<count; ix++)
		if(!*list++)
			return ix;
	return -1;
	
}

// find index of given number in array of numbers

static Word16 UCCP_FindServiceById(UWord16 id, UWord16* list, UWord16 count)
{
	UWord16 ix;
	
	for(ix=0; ix<count; ix++)
		if(*list++ == id)
			return ix;
	return -1;
	
}

// CAN interrupt callbacks

static void UCCP_RxCallback(UWord16 bNum)
{
	// we use local copy of CAN data for both RX and TX
	// (it simplifies solving of the endianness problem)
	static UCCP_DATABUFF rxData;
	static UCCP_DATABUFF txData;
	
	register UWord16  txLen;
	register UWord16  rxLen;
	register UWord16   cmd;
	register UWord16   ctr;

	// receive data as little-endian (copy to our local static buffer)
	rxLen = UCAN_ReadMB(bNum, rxData.w, UCAN_DM_COPY16_LE);
	
	// extract main CRO parameters
	cmd = PARSE_CRO_CMD(rxData);
	ctr = PARSE_CRO_CTR(rxData);
	
	// send no response by default (each case should fill txData & txLen)
	txLen = 0;

	// but prepare the default (ACK) response (most commands do simply ACK)
	txData.w[0] = MAKEWORD(0xff, CRC_OK);
	txData.b[2] = (UWord8) ctr;

	// CONNECT should be handled separately
	if(cmd == CCP_CONNECT)
	{
		// ECU match ? (note that we have little-endian encoding here!)
		if(PARSE_CONNECT_ECU(rxData) == UCCP_ECU && rxLen >= 4)
		{
			uccp_flags.bIsConnected = 1;
			uccp_flags.bIsOnLine = 1;

			// send ACK (prepared in txData by default)
			txLen = 3;
		}
		// connecting to different node, temporarily disconnect
		else
		{
			uccp_flags.bIsOnLine = 0;
		}
	}
	// TEST should be handled separately too
	else if(cmd == CCP_TEST)
	{
		// ECU match ? (note that little-endian encoding works!)
		if(PARSE_TEST_ECU(rxData) == UCCP_ECU && rxLen >= 4)
		{
			// send ACK (prepared in txData by default)
			txLen = 3;
		}
	}
	// process other commands only if connected
	else if(uccp_flags.bIsConnected && uccp_flags.bIsOnLine)
	{
		switch(cmd)
		{
		case CCP_DISCONNECT:
			// ECU match ? (note that little-endian encoding works!)
			if(PARSE_DISCONNECT_ECU(rxData) == UCCP_ECU && rxLen >= 6)
			{
				// end of session ?
				if(PARSE_DISCONNECT_EOS(rxData))
				{
					// hard disconnect
					uccp_flags.bIsConnected = 0;
				}
				
				// temporary diconnect
				uccp_flags.bIsOnLine = 0;
				
				// send ACK (prepared in txData by default)
				txLen = 3;
			}
			break;
			
		case CCP_GET_CCP_VERSION:
			// just send our version
			txData.b[3] = UCCP_VER_MAJOR;
			txData.b[4] = UCCP_VER_MINOR;
			txLen = 5;
			break;
			
		case CCP_EXCHANGE_ID:
			// ignore received ID information
			// just build the response
			txData.b[3] = 0;				// my ID info empty
			txData.b[4] = 0;				// data type qualifier
			txData.b[5] = UUCP_RES_AM;		// Resource availability mask
			txData.b[6] = UUCP_RES_PM;		// Resource protection mask
			txLen = 7;
			break;
			
		case CCP_SET_MTA:
			if(PARSE_SETMTA_NUM(rxData) == CCP_SETMTA_NUM_STANDARD)
			{
				uccp_mta0Ext = PARSE_SETMTA_EXT(rxData);
				uccp_mta0Addr = PARSE_SETMTA_ADDR(rxData);
			}
			else
			{
				uccp_mta1Ext = PARSE_SETMTA_EXT(rxData);
				uccp_mta1Addr = PARSE_SETMTA_ADDR(rxData);
			}

			// send ACK (prepared in txData by default)
			txLen = 3;
			break;
			
		case CCP_DNLOAD:
		case CCP_DNLOAD6:
			{
				register UWord8* psrc = rxData.b+2;
				register UWord8* pdest;
				register UWord16 len, tmp;

				if(cmd == CCP_DNLOAD)
					len = *psrc++;
				else
					len = 6;

				if(len <= 6)
				{	
					// load 32bit MTA into address register	
					asm ( move.l uccp_mta0Addr, pdest );
					
					while(len--)
					{
						asm {
							move.bp X:(psrc)+,tmp
							move.bp tmp,X:(pdest)+
						}
					}

					// save updated MTA as 32bit number
					asm ( move.l pdest, uccp_mta0Addr );
			
					// send ACK	+ new MTA
					txData.dw[1] = uccp_mta0Addr;
					txData.b[3] = (UWord8) uccp_mta0Ext;
					txLen = 8;
				}
			}
			break;
			
		case CCP_UPLOAD:
			{
				register UWord8* pdest = txData.b+3;
				register UWord8* psrc;
				register UWord16 tmp, len = (UWord8) PARSE_UPLOAD_LEN(rxData);

				if(len <= 5)
				{
					// we will send ACK + data
					txLen = len + 3;
				
					// load 32bit MTA into address register
					asm ( move.l uccp_mta0Addr, psrc );

					while(len--)
					{
						asm {
							move.bp X:(psrc)+,tmp
							move.bp tmp,X:(pdest)+
						}
					}

					// save updated MTA as 32bit number
					asm ( move.l psrc, uccp_mta0Addr );
				}
			}
			break;

		case CCP_SHORT_UP:
			{
				register UWord16 tmp,len = PARSE_SHORTUP_LEN(rxData);
				// UWord16 addrExt = PARSE_SHORTUP_EXT(rxData);
				register UWord8* pdest = txData.b+3;
				register UWord8* psrc;

				if(len <= 5)
				{
					// we will send ACK + data
					txLen = len + 3;

					// load 32bit MTA into address register
					asm ( move.l PARSE_SHORTUP_ADDR(rxData), psrc );
					
					while(len--)
					{
						asm {
							move.bp X:(psrc)+,tmp
							move.bp tmp,X:(pdest)+
						}
					}
				}
			}
			break;

		#if UCCP_ACTION_SERVICE_COUNT > 0
		case CCP_ACTION_SERVICE:
			{
				UWord16 id = PARSE_ACTIONSERVICE_NUM(rxData);
				Word16 ix = UCCP_FindServiceById(id, uccp_asServices, UCCP_ACTION_SERVICE_COUNT);
				UCCP_SERVICE_CALLBACK callback = NULL;
				
				if(ix >= 0)
					callback = uccp_asCallbacks[ix];
		
				if(callback)
				{
					if(callback(id, rxData.w+2, &uccp_serviceData) != 0)
					{
						uccp_mta0Ext = uccp_serviceData.ext;
						uccp_mta0Addr = uccp_serviceData.mta;
						txData.b[3] = (UWord8) uccp_serviceData.len;
						txData.b[4] = (UWord8) uccp_serviceData.type;
					}
					else
					{
						txData.b[3] = 0;
						txData.b[4] = 0;
					}
					// send ACK + service response
					txLen = 5;
				}
				else
				{
					// return error
					txData.b[1] = CRC_RESOURCE_NOT_AVAIL;
					txLen = 3;
				}
				break;
			}			
		#endif
		
		#if UCCP_DIAG_SERVICE_COUNT > 0
		case CCP_DIAG_SERVICE:
			{
				UWord16 id = PARSE_DIAGSERVICE_NUM(rxData);
				Word16 ix = UCCP_FindServiceById(id, uccp_dsServices, UCCP_DIAG_SERVICE_COUNT);
				UCCP_SERVICE_CALLBACK callback = NULL;
				
				if(ix >= 0)
					callback = uccp_dsCallbacks[ix];
				
				if(callback)
				{
					if(callback(id, rxData.w+2, &uccp_serviceData) != 0)
					{
						uccp_mta0Ext = uccp_serviceData.ext;
						uccp_mta0Addr = uccp_serviceData.mta;
						txData.b[3] = uccp_serviceData.len;
						txData.b[4] = uccp_serviceData.type;
					}
					else
					{
						txData.b[3] = 0;
						txData.b[4] = 0;
					}
					// send ACK + service response
					txLen = 5;
				}
				else
				{
					// return error
					txData.b[1] = CRC_RESOURCE_NOT_AVAIL;
					txLen = 3;
				}
				break;
			}			
		#endif
		
		#if UCCP_PROTECT_CAL
		#error UCCP_PROTECT_CAL not yet implemented
		case CCP_GET_SEED:	
			break;
		#endif
			
		#if UCCP_PROTECT_CAL
		#error UCCP_PROTECT_CAL not yet implemented
		case CCP_UNLOCK:
			break;
		#endif
		
		default:
			// unknown command error
			txData.w[0] = MAKEWORD(0xff, CRC_CMD_UNKNOWN);
			txLen = 3;
			break;
		}
	}
		
	// should send anything ?
	if(txLen > 0)
	{	
		// if single buffer is to be used, MB reconfiguration is needed
		if(UCCP_TXMB == UCCP_RXMB)
		{
			UCAN_ConfigMB(UCCP_TXMB, UCAN_BM_TXDF, UCCP_DTOID);
			UCAN_SetCallbackMB(UCCP_TXMB, UCCP_TxCallback);
		}

		// fill transmit buffer (convert from little-endian)
		UCAN_LoadMB(UCCP_TXMB, txData.w, UCAN_DM_COPY16_LE, txLen);
		
		// now we can send it
		UCAN_TransmitMB(UCCP_TXMB, UCAN_TX_ONCE);
	}
}

static void UCCP_TxCallback(UWord16 bNum)
{
	// we don't care normally about tx-finished
	// except when we do have a single buffer only
	if(UCCP_TXMB == UCCP_RXMB)
	{
		// start listening again
		UCAN_ConfigMB(UCCP_RXMB, UCAN_BM_RXDF, UCCP_CROID);
		UCAN_SetCallbackMB(UCCP_RXMB, UCCP_RxCallback);
	}
}

