/** 
 * \file pwm.h
 * \brief pwm header file
 * \details File contains structures, enumerations and functions declarations for PWM module
 *
 * \author Juraj Golej
 * \version 1.0
 */


#ifndef PWM_H_
#define PWM_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 PWM channels enumeration
 */
enum PWM_CHANNEL {
	PWM_CHANNEL_0,		///< Channel 0
	PWM_CHANNEL_1,		///< Channel 1
	PWM_CHANNEL_2,		///< Channel 2
	PWM_CHANNEL_3,		///< Channel 3
}; /* enum PWM_CHANNEL */

/** 
 * \brief PWM clocks enumeration
 */
enum PWM_CLK {
	PWM_CLK_MCK,		///< Master clock
	PWM_CLK_MCK2,		///< Master clock divided by 2
	PWM_CLK_MCK4,		///< Master clock divided by 4
	PWM_CLK_MCK8,		///< Master clock divided by 8
	PWM_CLK_MCK16,		///< Master clock divided by 16
	PWM_CLK_MCK32,		///< Master clock divided by 32
	PWM_CLK_MCK64,		///< Master clock divided by 64
	PWM_CLK_MCK128,		///< Master clock divided by 128
	PWM_CLK_MCK256,		///< Master clock divided by 256
	PWM_CLK_MCK512,		///< Master clock divided by 512
	PWM_CLK_MCK1024,	///< Master clock divided by 1024
	PWM_CLK_CLKA,		///< Programmable PWM clock A
	PWM_CLK_CLKB,		///< Programmable PWM clock B
}; /* enum PWM_CLK */

/** 
 * \brief PWM wave alignment
 */
enum PWM_ALIG {
	PMM_ALIG_LEFT,		///< Wave is left alignment
	PWM_ALIG_CENTER,	///< Wave is center alignment
}; /* enum PWM_ALIG */

struct PWM_module;

/**
 * \brief Period end user defined function (callback)
 * 
 * \param[in] module Pointer on PWM module
 */
typedef void (*pwm_per_cb)(struct PWM_module* const module);

/** 
 * \brief PWM module structure
 */
struct PWM_module {
	uint8_t ID;					///< Register status (if >0, module is initialized)
	Pwm* periphery;				///< Pointer on PWM periphery
	enum PWM_CHANNEL channel;	///< PWM periphery channel

	volatile pwm_per_cb cbPeriod;		///< Pointer on callback function
	uint32_t clkFreq;			///< PWM clock frequency
}; /* struct PWM_module */

/**
 * \brief Get PWM peripheral hardware ID
 * 
 * \param[in] periphery Pointer on PWM periphery
 * 
 * \return \b int16_t Peripheral ID or -1, if can't find this \c periphery
 */
static inline int16_t PWM_getPeripheralHWID(Pwm* periphery)
{
	switch((uint32_t)periphery) {
		case (uint32_t)PWM0:
			return ID_PWM0;
		case (uint32_t)PWM1:
			return ID_PWM1;
		default:
			return -1;
	}
}

/**
 * \brief Configure programmable PWM clock A and B
 * 
 * \param[in] periphery Pointer on PWM periphery
 * \param[in] clkA_src Source clock for CLKA
 * \param[in] clkA_div Divider of source clock for CLKA
 * \param[in] clkB_src Source clock for CLKB
 * \param[in] clkB_div Divider of source clock for CLKB
 */
static inline void PWM_clock_configure(Pwm* const periphery, const enum PWM_CLK clkA_src, const uint8_t clkA_div, const enum PWM_CLK clkB_src, const uint8_t clkB_div)
{
	Assert(periphery);
	periphery->PWM_CLK = PWM_CLK_DIVA(clkA_div) | PWM_CLK_DIVB(clkB_div) | PWM_CLK_PREA((uint8_t)clkA_src) | PWM_CLK_PREA((uint8_t)clkB_src);
}

/**
 * \brief Set PWM channel as synchronous (everything is controlled with channel 0)
 * 
 * \param[in] periphery Pointer on PWM periphery
 * \param[in] channel PWM channel
 *
 * \note When a channel is defined as a synchronous channel, the channel 0 is also automatically defined as a synchronous channel
 */
static inline void PWM_setAsSync(Pwm* const periphery, const enum PWM_CHANNEL channel) 
{
	periphery->PWM_SCM |= (1 << channel);
}

/**
 * \brief Initialize PWM module
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] periphery Pointer on PWM periphery
 * \param[in] channel PWM channel
 * \param[in] clk Input clock (counter increment clock)
 * \param[in] callback Pointer on user defined callback function
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Periphery is already in use
 * \retval EInvalidParameter Invalid \c periphery parameter
 *
 * \note Call this function before anything else
 */
enum retCode PWM_init(struct PWM_module* const module, Pwm* const periphery, const enum PWM_CHANNEL channel, const enum PWM_CLK clk);

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

/**
 * \brief Set PWM clock
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] clk Input clock (counter increment clock)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PWM_setClock(struct PWM_module* const module, const enum PWM_CLK clk);

/**
 * \brief Set period and duty
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] period Period (in counter value)
 * \param[in] duty Duty (in counter value)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 * \retval EInvalidParameter Duty is equal or higher number than period
 *
 * \note \c duty number must be smaller number than \c period
 */
enum retCode PWM_setParameters(struct PWM_module* const module, const uint16_t period, const uint16_t duty);

/**
 * \brief Set wave polarity
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] polarity \c true for low-high, \c false for high-low (on positive output, negative output it has opposite)
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PWM_setPolarity(struct PWM_module* const module, const bool polarity);

/**
 * \brief Set wave alignment
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] alignment Alignment
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PWM_setAlignment(struct PWM_module* const module, const enum PWM_ALIG alignment);

/**
 * \brief Set interrupt event
 * 
 * \param[in] module Pointer on PWM module
 * \param[in] event \true for enable, \c false for disable 2 irq per one period
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 *
 * \note Useful only with center alignment mode
 * \note false: The channel counter event occurs at the end of the PWM period.
 * \note true:  The channel counter event occurs at the end of the PWM period and at half the PWM period.
 */
enum retCode PWM_setInterruptEvent(struct PWM_module* const module, const bool event);

/**
 * \brief Start generate waveform
 * 
 * \param[in] module Pointer on PWM module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PWM_start(struct PWM_module* const module);

/**
 * \brief Stop generate waveform
 * 
 * \param[in] module Pointer on PWM module
 * 
 * \return \b enum \b retCode Return code value
 * \retval ENone everything OK
 * \retval EDenied Module isn't initialized
 */
enum retCode PWM_stop(struct PWM_module* const module);

/**
 * \brief Check, if PWM is enabled
 * 
 * \param[in] module Pointer on PWM module
 * 
 * \return \b bool Enable status
 * \retval true PWM is enabled
 * \retval false PWM is disabled or module isn't initialized
 */
inline bool PWM_isEnabled(struct PWM_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	/* Get enable status */
	if(module->periphery->PWM_SR & (1 << module->channel)) {
		return true;
	}

	return false;
}

/**
 * \brief Get clock frequency
 * 
 * \param[in] module Pointer on PWM module
 * 
 * \return \b uint32_t Clock frequency
 */
inline uint32_t PWM_getClkFreq(struct PWM_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

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

	return module->clkFreq;
}

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

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

	/* Enable interrupt */
	if(callback) {
		module->cbPeriod = callback;
		module->periphery->PWM_IER1 = (1 << module->channel);
	}
	else {
		module->periphery->PWM_IDR1 = (1 << module->channel);
		module->cbPeriod = NULL;
	}

	return ENone;
}

#endif /* PWM_H_ */