/*****************************************************************************
*
* Freescale Semiconductor Inc.
* (c) Copyright 2004-2005 Freescale Semiconductor, Inc.
* (c) Copyright 2001-2004 Motorola, Inc.
* ALL RIGHTS RESERVED.
*
******************************************************************************
*
* File Name: ucan_flex.c
*
* Description: uCAN driver for FlexCAN
*
* $Version: 2.0.23.0$
*
*****************************************************************************/

#include "qs.h"
#include "ucan.h"		// public declarations

#if !UCAN_USE_MSCAN		// compile this file only if FlexCAN is to be used

#include "ucan_flex.h"	// internal driver definitions

/************************************************************
*  local variables
************************************************************/

// pointers to callback methods
static UCAN_MBCALLBACK_FUNC ucan_pMBCallback[UCAN_MSGBUFF_COUNT];

// temporary bits to avoid repetitive RXFULL/RXOVR responses in CheckStatusMB
static UWord16 ucan_mbRxOld;

// temporary bits to avoid TX-callback when transmitting RTR
static UWord16 ucan_mbTxRtr;

/************************************************************
*  function prototypes
************************************************************/
void UCAN_BusOffISR (void);
void UCAN_WakeUpISR (void);
void UCAN_ErrorISR  (void);
void UCAN_MbISR     (void);

/************************************************************
*  protection of ReadMB/ReceiveMB data copying code
************************************************************/

/* When UCAN_READMB_UNPROTECTED is set nonzero, no interrupts are disabled during 
   message buffer data-copy operations. This mode may be ONLY be used if the 
   UCAN_ReadMB and UCAN_ReceiveMB  functions are called without risk of being 
   interrupted by either the FlexCAN MB interrupt or any other code which would 
   unlock the FlexCAN buffers (by touching any MB-code-status register or FCAN free
   running timer register fctmr) 
           
   For example, this mode can be used when you call the UCAN_ReadMB or UCAN_ReceiveMB 
   functions in the uCAN MB callbacks ONLY. Callbacks are called from MB interrupt,
   so there is no risk of being self-interrupted again. Also, as the UCAN_CheckStatusMB
   also unlocks the buffers so it must not be called from any higher-priority interrupt 
   (which would not be wise anyway).
*/

#if UCAN_READMB_UNPROTECTED
	#define UCAN_LOCKSECTION_BEGIN(intsave)
	#define UCAN_LOCKSECTION_END(intsave)
#else 
	#define UCAN_LOCKSECTION_BEGIN(intsave) archSaveIntAndDisable(intsave)
	#define UCAN_LOCKSECTION_END(intsave)   archRestoreIntMask(intsave)
#endif

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

// Initialization

UCANRESULT UCAN_Init()
{
	UWord16 i;
	
	// reset FlexCAN and initalize low-level driver by appconfig.h values
	ioctl(UCAN_MODULE, FCAN_INIT, NULL);

	// clear our internal variables	
	for(i=0; i<UCAN_MSGBUFF_COUNT; i++)
		ucan_pMBCallback[i] = NULL;

	ucan_mbRxOld = 0;
	ucan_mbTxRtr = 0;
	
	return UCAN_OK;
}

// test bus-on/bus-off condition

UWord16 UCAN_IsSynchronized()
{
	return !(ioctl(UCAN_MODULE, FCAN_READ_ERR_AND_STATUS, NULL) & FCAN_STATUS_FCS_BUSOFF);
}

// request sleep state

UCANRESULT UCAN_Sleep()
{
	// request sleep state
	ioctl(UCAN_MODULE, FCAN_STOP_MODE, FCAN_ON);
	return UCAN_OK;
}

// wake up

UCANRESULT UCAN_Wakeup()
{
	// awake device
	ioctl(UCAN_MODULE, FCAN_STOP_MODE, FCAN_OFF);
	return UCAN_OK;
}

/****************************************************************************
*
* MB Operations
*
*****************************************************************************/

// initialize buffer for reception or transmission, 

UCANRESULT UCAN_ConfigMB(UWord16 bNum, UCANBMODE bMode, UWord32 id)
{
	register arch_sFlexCAN_MB* pmb;
	register UWord16 mbbit;
	
	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);
	UCAN_ASSERT(bMode < UCAN_BM_COUNT, UCAN_ERR_MODE);
	
	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);
	mbbit = 1 << bNum;

	// clear temporary flags
	ucan_mbRxOld &= ~mbbit;

	// clear pending interrupt
	ioctl(UCAN_MODULE, FCAN_CLEAR_MBINT_FLAGS, mbbit);

	// abort any ongoing buffer operation, initialize for RX or TX
	if(bMode == UCAN_BM_TXDF)
	{
		ioctl(pmb, FCANMB_SET_CODE, FCAN_MB_CODE_TXVOID);
		ioctl(UCAN_MODULE, FCAN_MBINT_ENABLE, mbbit);
	}
	else if(bMode == UCAN_BM_RXDF)
	{
		ioctl(pmb, FCANMB_SET_CODE, FCAN_MB_CODE_RXEMPTY);
		ioctl(UCAN_MODULE, FCAN_MBINT_ENABLE, mbbit);
	}
	else // bMode == UCAN_BM_CLOSED
	{
		ioctl(pmb, FCANMB_SET_CODE, FCAN_MB_CODE_RXVOID);
		ioctl(UCAN_MODULE, FCAN_MBINT_DISABLE, mbbit);
	}

	// should we configure buffer ID ?
	if(id)
	{
		ioctl(pmb, FCANMB_SET_ID_V, id);
	}

	// OK
	return UCAN_OK;
}

// set receive mask for group of MB with common mask

UCANRESULT UCAN_SetRxMaskGlobal(UWord32 mask)
{
	ioctl(UCAN_MODULE, FCAN_SET_RXGMASK_V, mask);
	return UCAN_OK;
}

// set receive mask for a buffer (in FlexCAN only MB14 and MB15 do have their own mask)

UCANRESULT UCAN_SetRxMaskMB(UWord16 bNum, UWord32 mask)
{
	UWord32 idr;

	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);
	UCAN_ASSERT(bNum >= (UCAN_MSGBUFF_COUNT-UCAN_MSGBUFFEX_COUNT), UCAN_ERR_MB);

	// MB15 ?
	if(bNum == (UCAN_MSGBUFF_COUNT - 1))
	{
		ioctl(UCAN_MODULE, FCAN_SET_RX15MASK_V, mask);
	}
	else /* if(bNum == (UCAN_MSGBUFF_COUNT - 2)) */
	{
		ioctl(UCAN_MODULE, FCAN_SET_RX14MASK_V, mask);
	}
	
	return UCAN_OK;
}

/****************************************************************************
*
* MB Operations
*
*****************************************************************************/

// setup the callback function which is to be called when MB state changes

#pragma interrupt called
UCANRESULT UCAN_SetCallbackMB(UWord16 bNum, UCAN_MBCALLBACK_FUNC pCallbackFunc)
{
	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);

	ucan_pMBCallback[bNum] = pCallbackFunc;
	
	return UCAN_OK;
}

// get status of the message buffer

#pragma interrupt called
UCANBSTATUS UCAN_CheckStatusMB(UWord16 bNum)
{
	register arch_sFlexCAN_MB* pmb;
	register UWord16 code;
	register UWord16 intsave;
	register UWord16 mbbit;
	
	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_BS_ERR);

	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);
	mbbit = 1 << bNum;

	// disable interrupts - we must do it to be able to detect
	// any _OLD conditions using the ucan_mbRxOld variable
	archSaveIntAndDisable(intsave);

	// lock & get the status code
	code = ioctl(pmb, FCANMB_GET_CODE, NULL);

	// void ?
	if(code == FCAN_MB_CODE_RXVOID || code == FCAN_MB_CODE_TXVOID)
	{
		code = UCAN_BS_VOID;
	}
	// interrupt pending for this MB ?
	else if(ioctl(UCAN_MODULE, FCAN_READ_MBINT_FLAGS, NULL) & mbbit)
	{
		// we must wait for the interrupt to occur first 
		// (the ISR clears our temporary bits)
		code = UCAN_BS_BUSY;
	}
	else if(code == FCAN_MB_CODE_RXFULL)
	{
		// report RXFULL only once
		if(ucan_mbRxOld & mbbit)
		{
			code = UCAN_BS_RXFULL_OLD;
		}
		else
		{
			code = UCAN_BS_RXFULL;
			ucan_mbRxOld |= mbbit;
		}
	}
	// RX data overrun ?
	else if(code == FCAN_MB_CODE_RXOVERRUN)
	{
		// report RXOVR only once
		if(ucan_mbRxOld & mbbit)
		{
			code = UCAN_BS_RXOVR_OLD;
		}
		else
		{
			code = UCAN_BS_RXOVR;
			ucan_mbRxOld |= mbbit;
		}
	}
	else
	{
		// the buffer is still busy (RXEMPTY or TX)
		code = UCAN_BS_BUSY;
	}
	
	// enable interrupts again (restore previous state)
	archRestoreIntMask(intsave);

	// unlock MB
	ioctl(UCAN_MODULE, FCAN_UNLOCK_ALL_MB, NULL);

	// return MB status
	return code;
}

// enqueue buffer for transmission

#pragma interrupt called
UCANRESULT UCAN_TransmitMB(UWord16 bNum, UCANTXMODE txMode)
{
	register arch_sFlexCAN_MB* pmb;

	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);
	UCAN_ASSERT(txMode < UCAN_TXMODE_COUNT, UCAN_ERR_MODE);

	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);

	if(txMode == UCAN_TX_AUTORESP)
	{
		ioctl(pmb, FCANMB_SET_CODE, FCAN_MB_CODE_TXRALWAYS);
	}
	else
	{
		// set RTR bit ?
		if(txMode == UCAN_TX_RTR)
		{
			// send RTR
			ioctl(pmb, FCANMB_SET_RTR, FCAN_ON);
			// but don't callback after RTR is transmitted
			ucan_mbTxRtr |= 1<<bNum;
		}
		else if(txMode == UCAN_TX_RTR_TXCB)
		{
			// send RTR and do callback after RTR is transmitted
			ioctl(pmb, FCANMB_SET_RTR, FCAN_ON);
		}
			
		ioctl(pmb, FCANMB_SET_CODE, FCAN_MB_CODE_TXONCE);
	}

	return UCAN_OK;
}

// read (copy-out) MB data and return length

#pragma interrupt called
UWord16 UCAN_ReadMB(UWord16 bNum, UWord16* pDestBuff, UCANDMODE copyMode)
{
	register arch_sFlexCAN_MB* pmb;
	register UWord16* pd;
	register UWord16 intsave;
	register UWord16 len;

	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, 0);

	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);

	// disable interrupts - we must do it even with the locking mechanism
	// because other interrupts or other MB interrupt could unlock our buffer 
	// while copying - enabling data corruption to happen
	UCAN_LOCKSECTION_BEGIN(intsave);

	// read CODE (lock MB)
	while(ioctl(pmb, FCANMB_GET_CODE, NULL) == FCAN_MB_CODE_RXBUSY)
	{
		/* wait */;
		asm ( nop );
	}

	if(pDestBuff)
	{
		pd = (UWord16 *) ioctl(pmb, FCANMB_GET_DATAPTR, NULL);
		
		if(copyMode == UCAN_DM_COPY)
		{
			UCAN_Copy(pd, pDestBuff);
		}
		else if(copyMode == UCAN_DM_SWAB16)
		{
			UCAN_CopySwab(pd, pDestBuff);
		}
		else if(copyMode == UCAN_DM_HALF8)
		{
			UCAN_CopySplit(pd, pDestBuff);
		}
	}

	// get frame length
	len = ioctl(pmb, FCANMB_GET_LEN, NULL);

	// enable interrupts again (restore previous state)
	UCAN_LOCKSECTION_END(intsave);
	
	// unlock MB
	ioctl(UCAN_MODULE, FCAN_UNLOCK_ALL_MB, NULL);
	
	// return length
	return len;
}

#pragma interrupt called
UCANRESULT UCAN_ReceiveMB(UWord16 bNum, UCANRXINFO* pDestInfo, UCANDMODE copyMode)
{
	register arch_sFlexCAN_MB* pmb;
	register UWord16* pd;
	register UWord16 intsave;

	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);

	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);

	// disable interrupts - we must do it even with the locking mechanism
	// because other interrupts or other MB interrupt could unlock our buffer 
	// while copying - enabling data corruption to happen
	UCAN_LOCKSECTION_BEGIN(intsave);

	// read CODE (lock MB)
	while(ioctl(pmb, FCANMB_GET_CODE, NULL) == FCAN_MB_CODE_RXBUSY)
	{
		/* wait */;
		asm ( nop );
	}

	// copy frame data
	if(pDestInfo->pDataBuff)
	{
		pd = (UWord16 *) ioctl(pmb, FCANMB_GET_DATAPTR, NULL);
		
		if(copyMode == UCAN_DM_COPY)
		{
			UCAN_Copy(pd, pDestInfo->pDataBuff);
		}
		else if(copyMode == UCAN_DM_SWAB16)
		{
			UCAN_CopySwab(pd, pDestInfo->pDataBuff);
		}
		else if(copyMode == UCAN_DM_HALF8)
		{
			UCAN_CopySplit(pd, pDestInfo->pDataBuff);
		}
	}

	// get frame infoemation	
	pDestInfo->length = ioctl(pmb, FCANMB_GET_LEN, NULL);
	pDestInfo->realID = ioctl(pmb, FCANMB_GET_ID, NULL);
	pDestInfo->timeStamp = ioctl(pmb, FCANMB_GET_TIMESTAMP, NULL);

	// enable interrupts again (restore previous state)
	UCAN_LOCKSECTION_END(intsave);

	// unlock MB
	ioctl(UCAN_MODULE, FCAN_UNLOCK_ALL_MB, NULL);
	
	// no error
	return UCAN_OK;
}

// fill MB with data and return pointer to the MB data space

#pragma interrupt called
UCANRESULT UCAN_LoadMB(UWord16 bNum, UWord16* pSrcBuff, UCANDMODE copyMode, UWord16 len)
{
	register arch_sFlexCAN_MB* pmb;
	
	UCAN_ASSERT(bNum < UCAN_MSGBUFF_COUNT, UCAN_ERR_MB);
	UCAN_ASSERT(len <= 8, UCAN_ERR_LEN);

	// address of our message buffer
	pmb = ioctl(UCAN_MODULE, FCAN_GET_MB_MODULE, bNum);

	// copy data
	if(pSrcBuff)
	{
		if(copyMode == UCAN_DM_COPY)
		{
			UCAN_Copy(pSrcBuff, (UWord16 *) ioctl(pmb, FCANMB_GET_DATAPTR, NULL));
		}
		else if(copyMode == UCAN_DM_SWAB16)
		{
			UCAN_CopySwab(pSrcBuff, (UWord16 *) ioctl(pmb, FCANMB_GET_DATAPTR, NULL));
		}
		else if(copyMode == UCAN_DM_HALF8)
		{
			UCAN_CopyBuild(pSrcBuff, (UWord16 *) ioctl(pmb, FCANMB_GET_DATAPTR, NULL));
		}
	}

	// set length
	ioctl(pmb, FCANMB_SET_LEN, len);

	return UCAN_OK;
}

/****************************************************************************
*
* Interrupt service routines
*
*****************************************************************************/

#pragma interrupt on

void UCAN_BusOffISR()
{
	/* acknowledge the interrupt */
	ioctl(UCAN_MODULE, FCAN_READ_ERR_AND_STATUS, NULL);
	ioctl(UCAN_MODULE, FCAN_CLEAR_BOFF_INT, NULL);
}

void UCAN_WakeUpISR()
{
	/* acknowledge the interrupt */
	ioctl(UCAN_MODULE, FCAN_READ_ERR_AND_STATUS, NULL);
	ioctl(UCAN_MODULE, FCAN_CLEAR_WAKE_INT, NULL);
}

void UCAN_ErrorISR()
{
	/* acknowledge the interrupt */
	ioctl(UCAN_MODULE, FCAN_READ_ERR_AND_STATUS, NULL);
	ioctl(UCAN_MODULE, FCAN_CLEAR_ERR_INT, NULL);
}

#pragma interrupt saveall on
void UCAN_MbISR()
{
	register UWord16 flags = ioctl(UCAN_MODULE, FCAN_READ_MBINT_FLAGS, NULL);
	register UWord16 flagsOrg = flags;
	register UWord16 bNum;

	// new data arrived into RX buffers
	ucan_mbRxOld &= ~flags;

	// do not generate RTR-TX callbacks	
	flags &= ~ucan_mbTxRtr;
	// RTR-TX is auto-reconfigured to RX - enable callback for next time
	ucan_mbTxRtr &= ~flagsOrg;

	for(bNum = 0; flags; flags >>= 1, bNum++)
	{
		// call user
		if((flags & 1) && ucan_pMBCallback[bNum])
		{
			// user can Read or Load&Transmit if he wants
			ucan_pMBCallback[bNum](bNum);
		}
	}
	
	// unlock MB (if one locked)
	ioctl(UCAN_MODULE, FCAN_UNLOCK_ALL_MB, NULL);
	
	// Clear interrupts: in fact, we could use 0xffff here because FlexCAN follows 
	// SRSv2 standard where only bits read-as-one can be cleared by write-one
	
	// On the other hand, using original value (flagsOrg) is more robust - imagine 
	// that user could read the MB interrupt flags register too in his callback 
	// routine - so by writing 0xffff, we could inadvertently clear those interrupts
	// which came within this ISR
	ioctl(UCAN_MODULE, FCAN_CLEAR_MBINT_FLAGS, flagsOrg);
}
#pragma interrupt off


/****************************************************************************
*
* Helper copy routines optimized using assembly code
*
*****************************************************************************/

// simple copy the buffer

void UCAN_Copy(register UWord16* psrcBuff, register UWord16* pdestBuff)
{
	pdestBuff[0] = psrcBuff[0];
	pdestBuff[1] = psrcBuff[1];
	pdestBuff[2] = psrcBuff[2];
	pdestBuff[3] = psrcBuff[3];
}

// unpair 4 words into 8 separate bytes each stored in its own word
// note that we can't use move.l as the buffers may be unaligned

asm void UCAN_CopySplit(register UWord16* psrcBuff, register UWord16* pdestBuff)
{
	// push Y so this function can be interrupt called
	adda        #2,SP
	move.l      Y,X:(SP)
	
	moveu.b X:(psrcBuff+0),Y1
	move.w  Y1,X:(pdestBuff+1)
	moveu.b X:(psrcBuff+1),Y1
	move.w  Y1,X:(pdestBuff)
	moveu.b X:(psrcBuff+2),Y1
	move.w  Y1,X:(pdestBuff+3)
	moveu.b X:(psrcBuff+3),Y1
	move.w  Y1,X:(pdestBuff+2)
	moveu.b X:(psrcBuff+4),Y1
	move.w  Y1,X:(pdestBuff+5)
	moveu.b X:(psrcBuff+5),Y1
	move.w  Y1,X:(pdestBuff+4)
	moveu.b X:(psrcBuff+6),Y1
	move.w  Y1,X:(pdestBuff+7)
	moveu.b X:(psrcBuff+7),Y1
	move.w  Y1,X:(pdestBuff+6)

	// pop Y		
	move.l      X:(SP)-,Y
	rts
}

// pair 8 separate bytes (stored in LSB of 8 separate words) into 4 words

asm void UCAN_CopyBuild(register UWord16* psrcBuff, register UWord16* pdestBuff)
{
	// can not use byte addressing of the destination
	// the destination here may be the FlexCAN registers
	// which can not be addressed in byte mode
	
	// push Y so this function can be interrupt called
	adda        #2,SP
	move.l      Y,X:(SP)

	moveu.b X:(psrcBuff+0),Y0
	moveu.b X:(psrcBuff+2),Y1
	asll.w #8,Y0
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff)

	moveu.b X:(psrcBuff+4),Y0
	moveu.b X:(psrcBuff+6),Y1
	asll.w #8,Y0
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+1)

	moveu.b X:(psrcBuff+8),Y0
	moveu.b X:(psrcBuff+10),Y1
	asll.w #8,Y0
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+2)

	moveu.b X:(psrcBuff+12),Y0
	moveu.b X:(psrcBuff+14),Y1
	asll.w #8,Y0
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+3)
	
	// pop Y		
	move.l      X:(SP)-,Y
	rts
}

// copy 4 words from srcBuff to destBuff while swapping bytes

asm void UCAN_CopySwab(register UWord16* psrcBuff, register UWord16* pdestBuff)
{
	adda        #2,SP
	move.l      Y,X:(SP)

	move.w X:(psrcBuff),Y
	lsrr.l #8,Y
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff)

	move.w X:(psrcBuff+1),Y
	lsrr.l #8,Y
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+1)

	move.w X:(psrcBuff+2),Y
	lsrr.l #8,Y
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+2)

	move.w X:(psrcBuff+3),Y
	lsrr.l #8,Y
	or.w Y0,Y1
	move.w Y1,X:(pdestBuff+3)

	// pop Y		
	move.l      X:(SP)-,Y
	rts
}

#endif /* !UCAN_USE_MSCAN */

