/** 
 * \file adc.c
 * \brief adc source file
 * \details File contains functions definitions for ADC module
 *
 * \author Juraj Golej
 * \version 1.0
 */

 #include "adc.h"

 static struct ADC_module* gADC_registered[AFEC_INST_NUM] = { NULL };		///< List of registered modules

 /**
 * \brief General ADC interrupt handler function
 * \param[in] perID module ID
 */
static inline void _General_irq(uint8_t perID)
{
	Assert(gADC_registered[perID]);
	struct ADC_module* module = gADC_registered[perID];
	uint16_t SRreg = ((uint16_t)module->periphery->AFEC_ISR & AFEC_ISR_EOC_Msk);

	uint8_t channel = 0;
	for(channel = 0; !(SRreg & 0x01) || channel > ADC_CHANNEL_10; SRreg >>= 1, ++channel);

	if(module->callback[channel]) {
		module->periphery->AFEC_CSELR = channel;
		module->callback[channel](module, module->periphery->AFEC_CDR);
	}
}

/**
 * \brief AFEC0 Interrupt handler function
 */
void AFEC0_Handler()
{
	_General_irq(0);
}

/**
 * \brief AFEC1 Interrupt handler function
 */
void AFEC1_Handler()
{
	_General_irq(1);
}

enum retCode ADC_init(struct ADC_module* const module, Afec* periphery)
{
	/* Check NULL pointers */
	Assert(module && periphery);

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

	/* Register ADC module */
	Afec* tmpInsta[AFEC_INST_NUM] = AFEC_INSTS;
	for(uint8_t i = 0; i < AFEC_INST_NUM; ++i) {
		if(tmpInsta[i] == periphery) {
			if(!gADC_registered[i]) {
				gADC_registered[i] = module;
				module->ID = i + 1;
				break;
			}
			else {
				return EDenied;
			}
		}
	}

	/* Init ADC module */
	module->periphery = periphery;

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

	/* Reset AFEC periphery to default state */
	module->periphery->AFEC_CR = AFEC_CR_SWRST_Msk;

	/* Set startup time 64*/
	module->periphery->AFEC_MR = AFEC_MR_ONE_Msk | AFEC_MR_STARTUP_SUT64;

	/* Enable gain */
	module->periphery->AFEC_ACR = AFEC_ACR_PGA0EN_Msk | AFEC_ACR_PGA1EN_Msk;

	module->periphery->AFEC_EMR |= AFEC_EMR_STM_Msk;

	return ENone;
}

enum retCode ADC_deinit(struct ADC_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Reset AFEC periphery to default state */
	module->periphery->AFEC_CR = AFEC_CR_SWRST_Msk;

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

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

	return ENone;
}

enum retCode ADC_setStartupTime(struct ADC_module* const module, const enum ADC_SUT startupTime)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set startup time */
	module->periphery->AFEC_MR = (module->periphery->AFEC_MR & ~AFEC_MR_STARTUP_Msk) | AFEC_MR_STARTUP((uint8_t)startupTime) | AFEC_MR_ONE_Msk;

	return ENone;
}

enum retCode ADC_setFrequency(struct ADC_module* const module, const uint32_t frequency)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Check, if frequency is in range */
	Try((frequency < ADC_MIN_FREQ) || (frequency > ADC_MAX_FREQ), EInvalidParameter);

	uint32_t PRESreg = (PMC_get_mck_freq() / frequency) - 1;
	Try((PRESreg == 0) || (PRESreg > 0xFF), EInvalidParameter);

	/* Set prescaler */
	module->periphery->AFEC_MR = (module->periphery->AFEC_MR & ~AFEC_MR_PRESCAL_Msk) | AFEC_MR_PRESCAL((uint8_t)PRESreg) | AFEC_MR_ONE_Msk;

	return ENone;
}

enum retCode ADC_setFreerunMode(struct ADC_module* const module, const bool freerun)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Enable/disable freerun mode */
	if(freerun) {
		module->periphery->AFEC_MR |= AFEC_MR_FREERUN_Msk | AFEC_MR_ONE_Msk;
	}
	else {
		module->periphery->AFEC_MR = (module->periphery->AFEC_MR | AFEC_MR_ONE_Msk) & ~AFEC_MR_FREERUN_Msk;
	}

	return ENone;
}

enum retCode ADC_setSignMode(struct ADC_module* const module, const enum ADC_SIGN sign)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set sign mode */
	module->periphery->AFEC_EMR = (module->periphery->AFEC_EMR & ~AFEC_EMR_SIGNMODE_Msk) | AFEC_EMR_SIGNMODE((uint8_t)sign);

	return ENone;
}

enum retCode ADC_setResolution(struct ADC_module* const module, const enum ADC_RESOLUTION resolution)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set resolution */
	module->periphery->AFEC_EMR = (module->periphery->AFEC_EMR & ~AFEC_EMR_RES_Msk) | AFEC_EMR_RES((uint8_t)resolution);

	return ENone;
}

enum retCode ADC_start(struct ADC_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Start conversion */
	module->periphery->AFEC_CR = AFEC_CR_START_Msk;

	return ENone;
}

enum retCode ADC_enable_channel(struct ADC_module* module, const enum ADC_CHANNEL channel, adc_conv_cb callback)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Enable channel */
	module->callback[(uint8_t)channel] = callback;
	module->periphery->AFEC_CHER = (1UL << (uint32_t)channel);

	/* Set offset */
	module->periphery->AFEC_CSELR = (uint32_t)channel;
	module->periphery->AFEC_COCR = 512;

	/* Enable interrupt */
	if(callback) {
		module->periphery->AFEC_IER = (1UL << (uint32_t)channel);
	}

	return ENone;
}

enum retCode ADC_disable_channel(struct ADC_module* module, const enum ADC_CHANNEL channel)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Disable interrupt */
	module->periphery->AFEC_IDR = (1UL << (uint32_t)channel);

	/* Disable channel */
	module->callback[(uint8_t)channel] = NULL;
	module->periphery->AFEC_CHDR = (1UL << (uint32_t)channel);

	return ENone;
}

int16_t ADC_read(struct ADC_module* module, const enum ADC_CHANNEL channel)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Read value from channel */
	module->periphery->AFEC_CSELR = channel;
	return module->periphery->AFEC_CDR;
}
