//----------------------------------------------------
/**
	\file		ctk/inc/cancomm.h
	\ingroup	can
	\ingroup	all

	\brief		Predek ovladacu CAN periferie
				<<< ALL >>>

	\author		ConTeK s.r.o., Pavel Bazika
	\version	01.01
	\date		06.06.2008
*/
//----------------------------------------------------

#ifndef _CANCOMM_H_
#define _CANCOMM_H_

#include <sync.h>
#include <comm.h>
#include <hwmodel.h>
#include <libsets.h>

//----------------------------------------------------
//!	\brief	Typ ID CAN

#define	CANIDTYPE				uint32_t


//----------------------------------------------------
//@{
//!	\name	Chybove kody CAN

#define CC_ERR_NONE				0			//!< Zadna chyba - nepouziva se
#define CC_ERR_BUFFER_FULL		1			//!< Paket byl zahozen z duvodu nepritomneho prazdneho bufferu
#define CC_ERR_MSG_NOT_ACCEPTED	2			//!< Zpravu neakceptoval zadny z klientu
#define CC_ERR_UNKNOWN			3			//!< Neznama chyba
#define	CC_ERR_MSG_TYPE			4			//!< Obdrzen paket jine verze CAN normy
#define CC_ERR_REINIT			5			//!< Automaticka reinicialzace CAN se nezdarila, CAN neinicializovan
#define CC_ERR_INTERNAL			6			//!< Vnitrni chyba knihoven - nemela by nastat

#define CC_ERR_HW_CRC			0x0008		//!< Chyba CRC zpravy
#define CC_ERR_HW_STUFF			0x0010		//!< Detekovano vice nez 5 bitu stejne polarity
#define CC_ERR_HW_ACK			0x0020		//!< Nikdo nepotvrdil prijeti zpravy
#define CC_ERR_HW_FORM			0x0040		//!< Chyba formatu zpravy
#define CC_ERR_HW_BIT			0x0080		//!< Detekovana jina hodnota na sbernici, nez je vysilana a naprobiha arbitrace
#define CC_ERR_HW_BUS_ERROR		0x0100		//!< Periferie se odpojila od sbernice
#define CC_ERR_HW_ARBLOST		0x0200		//!< Prohra pri arbitraci
#define CC_ERR_HW_ERRPASSIVE	0x0400		//!< Periferie prestava odesilat ERR ramce
#define CC_ERR_HW_DOVF			0x0800		//!< Preteceni prijimaciho bufferu periferie
#define CC_ERR_HW_FAULT			0x1000		//!< Vnitrni chyba HW CAN periferie

// TODO zjistit, k cemu jsou tyto konstanty a jejich hodnoty, pripadne zrusit
#define CC_ERR_TX_MB			0x2000		//!<
#define CC_ERR_RX_MB			0x4000		//!<
//@}


//----------------------------------------------------
//@{
//!	\name	Pouzivane schranky

//! Pocet pouzitych schranek
#define CAN_USED_MBCOUNT	(CAN_USER_MBCOUNT + CAN_TX_MBCOUNT + CAN_RX_MBCOUNT)

//! Prvni schranka pro automaticke prijimani
#define CAN_RX_MB_FIRST		(CAN_USER_MBCOUNT)
//! Posledni schranka pro automaticke prijimani
#define CAN_RX_MB_LAST		(CAN_RX_MBCOUNT + CAN_USER_MBCOUNT - 1)

//! Prvni schranka pro vysilani
#define CAN_TX_MB_FIRST		(CAN_USER_MBCOUNT + CAN_RX_MBCOUNT)
//! Posledni schranka pro vysilani
#define CAN_TX_MB_LAST		(CAN_USED_MBCOUNT - 1)

#if (CAN_RX_MBCOUNT == 1)
	#define CAN_RX_MB	CAN_RX_MB_START
#endif
//@}

//----------------------------------------------------
//!	Delka ID CANu

typedef enum _CANMODE
{
	CAN_MODE_UNDEFINED = 0,
	CAN_MODE_A,		//!< 11-bit ID
	CAN_MODE_B		//!< 31-bit ID
} CANMODE;


//----------------------------------------------------
//!	Konstanty pro rychlost CANu

typedef enum _CANSPD
{
	CANSPD_50,
	CANSPD_100,
	CANSPD_250,
	CANSPD_500,	
	CANSPD_1000
} CANSPD;


//----------------------------------------------------
//!	Rezim schranky

typedef enum _CAN_MB_MODE
{
	CMBM_RX,
	CMBM_TX
} CAN_MB_MODE;


//----------------------------------------------------
//!	Obsazenost vysilaci schranky

typedef enum _CAN_TX_MB_STATUS
{
	CTMS_EMPTY = 0,
	CTMS_FULL
} CAN_TX_MB_STATUS;


//----------------------------------------------------
//!	Nastaveni schranky CANu

typedef struct _CAN_MB_SETTINGS
{
	volatile bool_t	flInit;		//!< TRUE, pokud je schranka inicializovana
	uint32_t	ID;			//!< Nastavene ID v registru schranky
	uint32_t	Mask;		//!< Nastavena maska pro prijem v registru schranky
	CAN_MB_MODE	Mode;		//!< Urceni schranky (vysilani, prijimani...)
	volatile uint8_t	Status;		//!< Stav vysilaci schranky
} CAN_MB_SETTINGS;


//----------------------------------------------------
//!	Buffer zaraditelny do fronty

typedef struct _CAN_MSG
{
	bool_t		Remote;
	uint8_t		Size;				//!< Velikost dat, ktera buffer obsahuje
	CANIDTYPE	ID;					//!< ID CAN zpravy
	uint8_t		Data[8];			//!< Data
} CAN_MSG;


//----------------------------------------------------
//!	Buffer zaraditelny do fronty 


typedef struct _CAN_BUFFER
{
	struct
	{
		volatile status_t	Status;		//!< Stav bufferu
		uint8_t				MailBox;	//!< V pripade prijmu mailbox, do ktereho zprava prisla
		volatile bool_t		Processed;	//!< Priznak zpracovani bufferu
		ticks_t				Timer;		//!< Casovac pro vyrazeni zpravy pri pozastaveni sbernice
		struct _CAN_BUFFER 	*Next;		//!< Ukazatel na nasledujici deskriptor ve fronte
	} Desc;								//!< Deskriptor bufferu

	CAN_MSG		Msg;					//!< Vlastni zprava
} CAN_BUFFER;


//----------------------------------------------------
//!	Fronta bufferu

typedef struct _CAN_BUFQUEUE
{
	CAN_BUFFER	* FirstBuf;		//!< Prvni buffer ve fronte
	CAN_BUFFER	* CurBuf;			//!< Aktualne zpracovavany buffer
	CAN_BUFFER	* LastBuf;			//!< Posledni buffer ve fronte
	volatile bool_t		flTickExec;			//!< Zamek zabranujici reentrantnimu volani tiku nad frontou
} CAN_BUFQUEUE;


//----------------------------------------------------
//!	Forward deklarace kontextu

struct _CANCOMMDESC;


//----------------------------------------------------
//@{
//!	\name	Typy metod objektu

typedef void (* ccmCANSent) ( const struct _CANCOMMDESC * const cmm, const CAN_BUFFER * const msg );

typedef void (* ccmCANSend) ( struct _CANCOMMDESC * const cmm, CAN_BUFFER * const msg );

typedef void (* ccmCANRecvError) ( const struct _CANCOMMDESC * const cmm, const uint32_t ErrCode );

typedef void (* ccmCANTick) ( struct _CANCOMMDESC * const cmm );

typedef bool_t (* ccmCANSendImmediate) ( const struct _CANCOMMDESC * const cmm );

typedef void (* cmmCANDiscardTimeout) ( struct _CANCOMMDESC * const cmm);
#ifdef CAN_MASKMB

typedef void ( *ccmCANSetRecvMaskMB) (struct _CANCOMMDESC * const ctx, const uint8_t MailBox, const uint32_t ID, const uint32_t Mask);

#endif // CAN_MASKMB

// metody volane v preruseni, neni garantovana reentrance standardni knihovny

typedef bool_t (*ccmGetCANMessage) (struct _CANCOMMDESC * const cmm, CAN_BUFFER * const msg);

typedef void (*ccmPutCANMessage) (struct _CANCOMMDESC * const cmm, const CAN_BUFFER * const msg, const uint8_t MailBox);

typedef void (* ccmCANError) ( const struct _CANCOMMDESC * const cmm, const uint16_t ErrCode );

typedef void (* ccmCANPaused) ( const struct _CANCOMMDESC * const cmm, const bool_t Paused);

typedef void (* ccmCANSendMsgEnd) ( const struct _CANCOMMDESC * const cmm );

typedef void (* ccmCANRecvMsgEnd) ( const struct _CANCOMMDESC * const cmm );
//@}


//----------------------------------------------------
//!	Forward deklarace zarizeni

struct _CANDEVICE;


//----------------------------------------------------
//!	Typy metod zarizeni

typedef void (* cdmCANDeviceTick) ( struct _CANDEVICE * const dev, struct _CANCOMMDESC * const ctx );


//----------------------------------------------------
//!	\brief	Struktura polozky retezce zarizeni
//!	\note	Deskriptor kazdeho zarizeni zacina touto strukturou

typedef struct _CANDEVICE
{
	struct _CANDEVICE * Next;			//!< Nasledujici zarizeni
	uint8_t Priority;					//!< Priorita zarizeni - cim mensi, tim vyssi

	cdmCANDeviceTick	Tick;			//!< Metoda tiku

} CANDEVICE;


//----------------------------------------------------
//!	Kontext objektu

typedef struct _CANCOMMDESC
{
	volatile bool_t			flRunning;				//!< Indikace, zda je objekt zinicializovan
	volatile bool_t			flClientsTick;			//!< Zamek branici reentrantimu volani tiku klientu (zarizeni)
	volatile bool_t			flErrPaused;				//!< Indikace, zda CAN pracuje

	CAN_BUFQUEUE			Rcv;					//!< Fronta bufferu pro prijem
	CAN_BUFQUEUE			Snd;					//!< Fronta bufferu pro vysilani
	CAN_BUFFER				* RcvBlock;				//!< Zarazka pri prochazeni prijatych paketu
	volatile bool_t			RcvAvail;				//!< Indikuje, zda je mozno zkoumat prijate pakety

	// metody
	ccmCANSend				Send;					//!< Metoda odeslani zpravy
	ccmCANTick				Tick;					//!< Tik objektu
	ccmGetCANMessage 		GetCANMessage;			//!< Metoda pro ziskani zpravy k vysilani
	ccmPutCANMessage 		PutCANMessage;			//!< Metoda pro ulozeni prijate zpravy
	ccmCANSendImmediate		SendImmediate;			//!< Metoda vraci TRUE, pokud dojde pri zarazeni zpravy k jejimu okamzitemu odeslani
	cmmCANDiscardTimeout	DiscardTimeout;			//!< Metoda zrusi zpravy z vysilaci fronty, jimz vyprsel cas

#ifdef CAN_MASKMB
	ccmCANSetRecvMaskMB		SetRecvMaskMB;			//!< Metoda nastavuje masku prijimaciho mailboxu
#endif // CAN_MASKMB

	// callbacky
	ccmCANSent				cbSent;					//!< CallBack po odvysilani zpravy, navraci uvolneny buffer
	ccmCANRecvMsgEnd		cbRecvMsgEnd;			//!< Oznameni ukonceni prijmu zpravy (z preruseni)
	ccmCANSendMsgEnd		cbSendMsgEnd;			//!< Metoda oznamujici odvysilani zpravy (z preruseni)
	ccmCANRecvError			cbRecvError;			//!< Metoda volana pri chybe prijmu
	ccmCANError				cbError;				//!< Callback volany pri chybe HW zpracovani
	ccmCANPaused			cbPaused;				//!< Callback volany pri pozastaveni CANu

	CANDEVICE				* DeviceList;			//!< Seznam zarizeni (klientu)

	uint32_t				Queued;
	uint32_t				Immediate;
	uint32_t				MaxSize;

	CANMODE					CANMode;				//! Pouzivana norma CAN 2.0 - A nebo B

	ticks_t					SendTimeout;			//!< Cas na odeslani zpravy, 0 = nekonecno

} CANCOMMDESC;


//----------------------------------------------------
/**
	\brief	Funkce (makro) pro testovani stavu bufferu

	\param	buf		CAN buffer

	\return	nenulova hodnota, jestlize operace s bufferem
			(prijem/vysilani) byla dokoncena
*/
//----------------------------------------------------

static inline bool_t CANBufReady(const CAN_BUFFER * const _buf)
{
	return !(((volatile const CAN_BUFFER  *)_buf)->Desc.Status & STS_BUFREADY);
}


//----------------------------------------------------
/**
	\brief	Funkce zjistuje, zda je volny buffer pro prijem

	\param	ctx		Kontext CAN komunikace

	\return TRUE	je-li buffer k dispozici
*/
//----------------------------------------------------

static inline bool_t CANCommFreeRecvBuf(const CANCOMMDESC * const ctx)
{
	return (ctx->Rcv.CurBuf != NULL)
			&& (ctx->Rcv.CurBuf->Desc.Status == STS_WAITING);
}


//----------------------------------------------------
/**
	\brief	Funkce zjistuje, zda ceka nejaky buffer na odeslani

	\param	cmm		Kontext CAN komunikace

	\return TRUE	pokud nejaky buffer ceka na odeslani
*/
//----------------------------------------------------

static inline bool_t CANCommSendBufWaiting(const CANCOMMDESC * const cmm)
{
	return (cmm->Snd.CurBuf != NULL)
			&& (cmm->Snd.CurBuf->Desc.Status == STS_WAITING);
}


//----------------------------------------------------
/**
	\brief	Funkce zjistuje, zda je CAN pozastaven

	\param	cmm		Kontext CAN komunikace

	\return TRUE	pokud je CAN pozastaven
*/
//----------------------------------------------------

static inline bool_t CANCommPaused(CANCOMMDESC * const cmm)
{
	return AtomRead(cmm->flErrPaused);
}


//----------------------------------------------------
/**
	\brief	Funkce navrati prvni z prijimanych bufferu ci NULL

	\param	ctx		Kontext CAN komunikace

	\return NULL	pri chybe, jinak prvni buffer ve fronte
*/
//----------------------------------------------------

static inline CAN_BUFFER * CANCommGetRecvFirst(CANCOMMDESC * const ctx)
{
	if (!AtomRead(ctx->RcvAvail))
	{
		// momentalne neprobiha prijem
		return NULL;
	}

	return ctx->Rcv.FirstBuf;
}


//----------------------------------------------------
/*
	\brief	Funkce navrati nasledujici z prijimanych bufferu ci NULL

	\param	ctx		Kontext CAN komunikace
	\param	buf		Buffer, jehoz nasledovnika pozadujeme

	\return	NULL	pokud naslednik neexistuje, nebo jde o chybu
*/
//-----------------------------------------------------

static inline CAN_BUFFER * CANCommGetRecvNext(CANCOMMDESC * const ctx, const CAN_BUFFER * const msg)
{
	if (!AtomRead(ctx->RcvAvail))
	{
		// momentalne neprobiha prijem
		return NULL;
	}

	if (msg->Desc.Next == ctx->RcvBlock)
	{
		// konec seznamu
		return NULL;
	}
	else {
		return msg->Desc.Next;
	}
}


//----------------------------------------------------
/**
	\brief	Funkce oznaci buffer jako zpracovany

	\param	ctx		Kontext CAN komunikace
	\param	buf		Buffer, ktery chceme oznacit
*/
//----------------------------------------------------

static inline void CANCommAccept(CANCOMMDESC * const ctx, CAN_BUFFER * const msg)
{
	if (!AtomRead(ctx->RcvAvail) || (msg == NULL))
	{
		// chyba
		assert_C(FALSE);
	}
	else {
		msg->Desc.Processed = TRUE;
	}
}


//----------------------------------------------------
/**
	\brief	Funkce vraci rezim CAN

	\param	ctx		Kontext CAN komunikace
*/
//----------------------------------------------------

static inline CANMODE CANCommGetMode(const CANCOMMDESC * const _ctx)
{
	return _ctx->CANMode;
}


//----------------------------------------------------
/**
	\brief	Zarazeni zarizeni do seznamu

	\param	ctx			Kontext
	\param	Device		Zarizeni, ktere se ma vlozit do seznamu

	\note				Zarizeni musi mit inicializovanou polozku priorita
	\note				Operaci lze provadet pouze v inicializacni fazi, kdyz se netika
*/
//----------------------------------------------------

void CANCommDeviceRegister(CANCOMMDESC * const ctx, CANDEVICE * const Device);


//----------------------------------------------------
/**
	\brief	Vyrazeni zarizeni ze seznamu

	\param	ctx			Kontext
	\param	Device		Zarizeni, ktere se ma odebrat ze seznamu

	\return				TRUE, pokud bylo zarizeni uspesne vyrazeno
*/
//----------------------------------------------------

bool_t CANCommDeviceUnregister(CANCOMMDESC * const ctx, const CANDEVICE * const Device);


//----------------------------------------------------
/**
	\brief	Vlozeni bufferu do prijimaci fronty

	\param	cmm		Struktura kontextu automatu
	\param	desc	Ukazatel na vkladany buffer
*/
//----------------------------------------------------

void CANCommRecvBuffEnqueue(CANCOMMDESC * const cmm, CAN_BUFFER * const msg);


//----------------------------------------------------
/**
	\brief	Vlozeni bufferu do fronty k vysilani

	\note	Funkce je urcena pro potomky knihovny

	\param	ctx		Kontext CAN komunikace
	\param	desc	Deskriptor ke vlozeni
*/
//----------------------------------------------------

void CANCommSendBuffEnqueue( CANCOMMDESC * const ctx, CAN_BUFFER * const msg );


//----------------------------------------------------
//!
//!	\brief	Inicializace objektu
//!
//!	\param	ctx			Kontext
//!
//!	\return				TRUE, pokud se inicializace zdarila
//!

bool_t CANCommInitialize(CANCOMMDESC * const ctx, const CANMODE _mode, const ticks_t Timeout);


//----------------------------------------------------
/**
	\brief	Ukonceni objektu

	\param	ctx			Kontext
*/
//----------------------------------------------------
void CANCommFinalize(CANCOMMDESC * const ctx);

#endif // _CANCOMM_H_
