/** 
 * \file timer.h
 * \brief timer header file
 * \details File contains structures, enumerations and functions declarations for Timer
 *
 * \author Juraj Golej
 * \version 1.1
 */


#ifndef TIMER_H_
#define TIMER_H_

#include "sam.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <clocks/clocks/clocks.h>
#include <utils/retcode/retcode.h>
#include <utils/assert/arm_assert.h>

/** 
 * \brief Timer mode enumeration
 */
enum TIMER_MODE {
	TIMER_MODE_SINGLE,	///< Single mode (only one time period)
	TIMER_MODE_REPEAT,	///< Repeat mode
}; /* enum TIMER_MODE */

/** 
 * \brief Timer clock enumeration
 */
enum TIMER_CLK {
	TIMER_CLK_PCK6,		///< PCK6
	TIMER_CLK_MCK8,		///< Master clock divided by 8
	TIMER_CLK_MCK32,	///< Master clock divided by 32
	TIMER_CLK_MCK128,	///< Master clock divided by 128
	TIMER_CLK_SLCK,		///< Slow clock
	TIMER_CLK_MCK,		///< Master clock no divided
}; /* enum TIMER_CLK */

/** 
 * \brief Timer/counter channel number enumeration
 */
enum TIMER_CHANNEL {
	TIMER_CHANNEL_0,	///< Channel 0
	TIMER_CHANNEL_1,	///< Channel 1
	TIMER_CHANNEL_2,	///< Channel 2
}; /* enum TIMER_CHANNEL */

struct TIMER_module;

/**
 * \brief Timer finish user defined function (callback)
 * 
 * \param[in] module Pointer on timer module
 */
typedef void (*timer_cb)(struct TIMER_module* const module);

/** 
 * \brief Timer module structure
 */
struct TIMER_module {
	uint8_t ID;						///< Register status (if >0, module is initialized)
	Tc* periphery;					///< Pointer on timer/counter periphery
	enum TIMER_CHANNEL channel;		///< Timer/counter periphery channel

	timer_cb callback;				///< Pointer on callback function
	volatile enum TIMER_MODE mode;	///< Timer mode
	uint32_t clock_freq;			///< Clock frequency
}; /* struct TIMER_module */

/**
 * \brief Get TIMER peripheral hardware ID
 * 
 * \param[in] periphery Pointer on timer/counter periphery
 * \param[in] channel Timer/counter periphery channel
 * 
 * \return \b int16_t Peripheral ID or -1, if can't find this \c periphery
 */
static inline int16_t TIMER_getPeripheralHWID(Tc* periphery, const uint8_t channel)
{
	switch((uint32_t)periphery) {
		case (uint32_t)TC0:
			return (ID_TC0_CHANNEL0 + (int16_t)channel);
		case (uint32_t)TC1:
			return (ID_TC1_CHANNEL0 + (int16_t)channel);
		case (uint32_t)TC2:
			return (ID_TC2_CHANNEL0 + (int16_t)channel);
		case (uint32_t)TC3:
			return (ID_TC3_CHANNEL0 + (int16_t)channel);
		default:
			return -1;
	}
}

/**
 * \brief Initialize timer module
 * 
 * \param[in] module Pointer on timer module
 * \param[in] periphery Pointer on timer/counter periphery
 * \param[in] channel Channel number of timer/counter periphery
 * \param[in] clk Clock frequency
 * \param[in] oneTick One timer tick in us
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Periphery is already in use
 * \retval EInvalidParameter Invalid \clk, \c oneTick or \c periphery parameter 
 *
 * \note Call this function before anything else
 */
enum retCode TIMER_init(struct TIMER_module* const module, Tc* periphery, const enum TIMER_CHANNEL channel, const enum TIMER_CLK clk, const uint32_t oneTick);

/**
 * \brief Deinitialize timer module
 * 
 * \param[in] module Pointer on timer module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode TIMER_deinit(struct TIMER_module* const module);

/**
 * \brief Set timer interval
 * 
 * \param[in] module Pointer on timer module
 * \param[in] oneTick One timer tick in us
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 * \retval EInvalidParameter Interval is higher than timer can make
 */
static inline enum retCode TIMER_setInterval(struct TIMER_module* const module, const uint32_t oneTick)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Calculate RC register */
	uint32_t RCreg = (uint32_t)(((float)module->clock_freq / 1000000.0) * (float)oneTick);
	Try((RCreg > 0xFFFFUL) || (RCreg == 0), EInvalidParameter);

	/* Set RC register */
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_RC = (uint16_t)RCreg;

	return ENone;
}

/**
 * \brief Register callback function
 * 
 * \param[in] module Pointer on timer module
 * \param[in] callback Pointer on callback function which is called, when timer finish one period
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
static inline enum retCode TIMER_registerCallback(struct TIMER_module* const module, timer_cb callback)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set callback function */
	module->callback = callback;

	return ENone;
}

/**
 * \brief Enable timer clock
 * 
 * \param[in] module Pointer on timer module
 * \param[in] mode Timer mode (single or repeat)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
static inline enum retCode TIMER_start(struct TIMER_module* const module, const enum TIMER_MODE mode)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set mode and callback function */
	module->mode = mode;

	/* Enable channel clock and trigger */
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_CCR = TC_CCR_CLKEN_Msk | TC_CCR_SWTRG_Msk;

	return ENone;
}

/**
 * \brief Disable timer clock
 * 
 * \param[in] module Pointer on timer module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
static inline enum retCode TIMER_stop(struct TIMER_module* module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Disable channel clock */
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_CCR = TC_CCR_CLKDIS_Msk;

	return ENone;
}

/**
 * \brief Check, if timer is busy
 * 
 * \param[in] module Pointer on timer module
 * 
 * \return \b bool Busy status
 * \retval true Timer is enabled
 * \retval false Timer is disabled
 */
static inline bool TIMER_isBusy(struct TIMER_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, true);

	/* Check enable status */
	if(module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_SR & TC_SR_CLKSTA_Msk) {
		return true;
	}

	return false;
}

#endif /* TIMER_H_ */