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

#include "pwm.h"

static struct PWM_module* gPWM_registered[PWM_INST_NUM][4] = { NULL };	///< List of registered modules

/**
 * \brief General PWM interrupt handler function
 * \param[in] perID module ID
 * \param[in] channel module channel
 */
static inline void _General_irq(uint8_t perID, uint8_t channel)
{
	Assert(gPWM_registered[perID][channel]);
	struct PWM_module* module = gPWM_registered[perID][channel];

	if(module->cbPeriod) {
		module->cbPeriod(module);
	}
}

/**
 * \brief PWM0 Interrupt handler function
 */
void PWM0_Handler()
{
	uint8_t SRreg = (uint8_t)PWM0->PWM_ISR1;
	for(uint8_t i = 0; i < 4; ++i, SRreg >>= 1) {
		if(SRreg & 0x01) {
			_General_irq(0, i);
		}
	}
}

/**
 * \brief PWM1 Interrupt handler function
 */
void PWM1_Handler()
{
	uint8_t SRreg = (uint8_t)PWM0->PWM_ISR1;
	for(uint8_t i = 0; i < 4; ++i, SRreg >>= 1) {
		if(SRreg & 0x01) {
			_General_irq(1, i);
		}
	}
}

enum retCode PWM_init(struct PWM_module* const module, Pwm* const periphery, const enum PWM_CHANNEL channel, const enum PWM_CLK clk)
{
	/* Check NULL pointers */
	Assert(module && periphery);

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

	/* Register PWM module */
	Pwm* tmpInsta[PWM_INST_NUM] = PWM_INSTS;
	for(uint8_t i = 0; i < PWM_INST_NUM; ++i) {
		if(tmpInsta[i] == periphery) {
			for(uint8_t m = 0; m < 4; ++m) {
				if(channel == m) {
					if(!gPWM_registered[i][m]) {
						gPWM_registered[i][m] = module;
						module->ID = i + 1;
						break;
					}
					else {
						return EDenied;
					}
				}
			}
		}
	}

	/* Init PWM module */
	module->periphery = periphery;
	module->channel = channel;
	module->cbPeriod = NULL;

	/* Enable peripheral clock and interrupt */
	if(!NVIC_GetEnableIRQ(tmpPerID)) {
		PMC_peripheral_clock_enable(tmpPerID);
		NVIC_ClearPendingIRQ(tmpPerID);
		NVIC_EnableIRQ(tmpPerID);
	}
	
	/* Set clock, waveform starts at low level and is left alignment */
	periphery->PWM_CH_NUM[(uint8_t)channel].PWM_CMR = 0;
	PWM_setClock(module, clk);

	return ENone;
}

enum retCode PWM_deinit(struct PWM_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Disable interrupt */
	module->periphery->PWM_IDR1 = (1 << module->channel);

	/* Disable channel */
	module->periphery->PWM_DIS = (1 << module->channel);

	/* Erase control register */
	module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR = 0;

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

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

	return ENone;
}

enum retCode PWM_setClock(struct PWM_module* const module, const enum PWM_CLK clk)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set clock */
	module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR = (module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR & ~PWM_CMR_CPRE_Msk) | PWM_CMR_CPRE((uint8_t)clk);

	/* Calculate PWM clock frequency */
	if(clk == PWM_CLK_CLKA) {
		module->clkFreq = (PMC_get_mck_freq() >> ((module->periphery->PWM_CLK & PWM_CLK_PREA_Msk) >> PWM_CLK_PREA_Pos)) / ((module->periphery->PWM_CLK & PWM_CLK_DIVA_Msk) >> PWM_CLK_DIVA_Pos);
	}
	else if(clk == PWM_CLK_CLKB) {
		module->clkFreq = (PMC_get_mck_freq() >> ((module->periphery->PWM_CLK & PWM_CLK_PREB_Msk) >> PWM_CLK_PREB_Pos)) / ((module->periphery->PWM_CLK & PWM_CLK_DIVB_Msk) >> PWM_CLK_DIVB_Pos);
	}
	else {
		module->clkFreq = PMC_get_mck_freq() >> clk;
	}

	return ENone;
}

enum retCode PWM_setParameters(struct PWM_module* const module, const uint16_t period, const uint16_t duty)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Check, if duty is lower number than period */
	Try(duty >= period, EInvalidParameter);

	/* Set period and duty */
	if(module->periphery->PWM_SR & (1 << module->channel)) {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CPRDUPD = period;
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CDTYUPD = duty;
	}
	else {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CPRD = period;
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CDTY = duty;
	}

	return ENone;
}

enum retCode PWM_setPolarity(struct PWM_module* const module, const bool polarity)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Check, if module isn't enabled */
	Try(module->periphery->PWM_SR & (1 << module->channel), EDenied);

	/* Set polarity */
	if(polarity) {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR |= PWM_CMR_CPOL_Msk;
	}
	else {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR &= ~PWM_CMR_CPOL_Msk;
	}

	return ENone;
}

enum retCode PWM_setAlignment(struct PWM_module* const module, const enum PWM_ALIG alignment)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set alignment */
	if(alignment == PWM_ALIG_CENTER) {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR |= PWM_CMR_CALG_Msk;
	}
	else {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR &= ~PWM_CMR_CALG_Msk;
	}

	return ENone;
}

enum retCode PWM_setInterruptEvent(struct PWM_module* const module, const bool event)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Set interrupt event */
	if(event) {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR |= PWM_CMR_CES_Msk;
	}
	else {
		module->periphery->PWM_CH_NUM[(uint8_t)module->channel].PWM_CMR &= ~PWM_CMR_CES_Msk;
	}

	return ENone;
}

enum retCode PWM_start(struct PWM_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Enable channel */
	module->periphery->PWM_ENA = (1 << module->channel);

	return ENone;
}

enum retCode PWM_stop(struct PWM_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Disable channel */
	module->periphery->PWM_DIS = (1 << module->channel);

	return ENone;
}