/** 
 * \file timer.c
 * \brief timer source file
 * \details File contains functions definitions for Timer
 *
 * \author Juraj Golej
 * \version 1.0
 */

#include "timer.h"

static struct TIMER_module* gTIMER_registered[TC_INST_NUM][3] = { NULL };	///< List of registered modules

/**
 * \brief General TIMER interrupt handler function
 * \param[in] perID module ID
 * \param[in] channel Timer channel
 */
static inline void _General_irq(uint8_t perID, uint8_t channel)
{
	Assert(gTIMER_registered[perID][channel]);
	struct TIMER_module* module = gTIMER_registered[perID][channel];
	/*uint32_t SRreg = */module->periphery->TC_CHANNEL[channel].TC_SR;

	if(module->mode == TIMER_MODE_SINGLE) {
		TIMER_stop(module);
	}
	if(module->callback) {
		module->callback(module);
	}
}

/**
 * \brief TC0 channel 0 Interrupt handler function
 */
void TC0_Handler()
{	
	_General_irq(0, 0);
}

/**
 * \brief TC0 channel 1 Interrupt handler function
 */
void TC1_Handler()
{
	_General_irq(0, 1);
}

/**
 * \brief TC0 channel 2 Interrupt handler function
 */
void TC2_Handler()
{
	_General_irq(0, 2);
}

/**
 * \brief TC1 channel 0 Interrupt handler function
 */
void TC3_Handler()
{
	_General_irq(1, 0);
}

/**
 * \brief TC1 channel 1 Interrupt handler function
 */
void TC4_Handler()
{
	_General_irq(1, 1);
}

/**
 * \brief TC1 channel 2 Interrupt handler function
 */
void TC5_Handler()
{
	_General_irq(1, 2);
}

/**
 * \brief TC2 channel 0 Interrupt handler function
 */
void TC6_Handler()
{
	_General_irq(2, 0);
}

/**
 * \brief TC2 channel 1 Interrupt handler function
 */
void TC7_Handler()
{
	_General_irq(2, 1);
}

/**
 * \brief TC2 channel 2 Interrupt handler function
 */
void TC8_Handler()
{
	_General_irq(2, 2);
}

/**
 * \brief TC3 channel 0 Interrupt handler function
 */
void TC9_Handler()
{
	_General_irq(3, 0);
}

/**
 * \brief TC3 channel 1 Interrupt handler function
 */
void TC10_Handler()
{
	_General_irq(3, 1);
}

/**
 * \brief TC3 channel 2 Interrupt handler function
 */
void TC11_Handler()
{
	_General_irq(3, 2);
}

enum retCode TIMER_init(struct TIMER_module* module, Tc* periphery, const enum TIMER_CHANNEL channel, const enum TIMER_CLK clk, const uint32_t oneTick)
{
	/* Check NULL pointers */
	Assert(module && periphery);

	/* Calculate clock frequency */
	switch(clk) {
		case TIMER_CLK_PCK6:
			module->clock_freq = PMC_get_pck_freq(PMC_PCK6);
			break;
		case TIMER_CLK_MCK8:
			module->clock_freq = PMC_get_mck_freq() >> 3;
			break;
		case TIMER_CLK_MCK32:
			module->clock_freq = PMC_get_mck_freq() >> 5;
			break;
		case TIMER_CLK_MCK128:
			module->clock_freq = PMC_get_mck_freq() >> 7;
			break;
		case TIMER_CLK_SLCK:
			module->clock_freq = OSC_get_slck_freq();
			break;
		case TIMER_CLK_MCK:
			module->clock_freq = PMC_get_mck_freq();
			break;
		default:
			return EInvalidParameter;
	}

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

	/* Get peripheral hardware ID */
	int16_t tmpPerID =  TIMER_getPeripheralHWID(periphery, (uint8_t)channel);
	Try(tmpPerID < 0, EInvalidParameter);

	/* Register TIMER module */
	Tc* tmpInsta[TC_INST_NUM] = TC_INSTS;
	for(uint8_t i = 0; i < TC_INST_NUM; ++i) {
		if(tmpInsta[i] == periphery) {
			for(uint8_t m = 0; m < 3; ++m) {
				if(channel == m) {
					if(!gTIMER_registered[i][m]) {
						gTIMER_registered[i][m] = module;
						module->ID = i + 1;
						break;
					}
					else {
						return EDenied;
					}
				}
			}
		}
	}

	/* Init timer module */
	module->periphery = periphery;
	module->channel = channel;
	module->callback = NULL;
	module->mode = TIMER_MODE_SINGLE;

	/* Enable peripheral clock and interrupt */
	PMC_peripheral_clock_enable(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);
	NVIC_EnableIRQ(tmpPerID);

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

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

	/* Enable counter reset with RC compare */
	periphery->TC_CHANNEL[(uint8_t)channel].TC_CMR = TC_CMR_CPCTRG_Msk;

	/* Set counter clock */
	if(clk != TIMER_CLK_MCK) {
		periphery->TC_CHANNEL[(uint8_t)channel].TC_CMR |= TC_CMR_TCCLKS((uint8_t)clk);
		periphery->TC_CHANNEL[(uint8_t)channel].TC_EMR = 0;
	}
	else {
		periphery->TC_CHANNEL[(uint8_t)channel].TC_EMR = TC_EMR_NODIVCLK_Msk;
	}

	/* Enable interrupt (RC compare) */
	periphery->TC_CHANNEL[(uint8_t)channel].TC_IER = TC_IER_CPCS_Msk;

	return ENone;
}

enum retCode TIMER_deinit(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;

	/* Disable interrupt */
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_IDR = 0xFFUL;

	/* Erase timer registers */
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_CMR = 0;
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_EMR = 0;
	module->periphery->TC_CHANNEL[(uint8_t)module->channel].TC_RC = 0;

	/* Disable peripheral clock and interrupt */
	int16_t tmpPerID =  TIMER_getPeripheralHWID(module->periphery, module->channel);
	PMC_peripheral_clock_disable(tmpPerID);
	NVIC_DisableIRQ(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);

	/* Remove module from registered modules */
	gTIMER_registered[module->ID - 1][module->channel] = NULL;
	module->ID = 0;

	return ENone;
}