/** 
 * \file oscillators.c
 * \brief oscillators source file
 * \details File contain functions definitions for configure oscillators
 *
 * \author Juraj Golej
 * \version 1.0
 */ 

#include "oscillators.h"

static uint32_t gOSC_SLCK_freq_val = CHIP_FREQ_SLCK_RC;
static uint32_t gOSC_MAINCK_freq_val = CHIP_FREQ_MAINCK_RC_12MHZ;
static uint32_t gOSC_PLLA_freq_val = 0UL;
static uint32_t gOSC_UPLL_freq_val = 0UL;

enum retCode OSC_mainck_crystal_enable(const uint8_t startUpTime)
{
	/* Turn on Main Crystal Oscillator and waiting for stabilize */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTST_Msk) | CKGR_MOR_MOSCXTEN_Msk | CKGR_MOR_MOSCXTST(startUpTime) | CKGR_MOR_KEY_PASSWD;
	while(!(PMC->PMC_SR & PMC_SR_MOSCXTS_Msk));

	/* Check frequency of Main Crystal Oscillator. If the crystal is not connected, it will turn off */
	PMC->CKGR_MCFR = CKGR_MCFR_CCSS_Msk | CKGR_MCFR_RCMEAS_Msk;
	while(!(PMC->CKGR_MCFR & CKGR_MCFR_MAINFRDY_Msk));
	if((PMC->CKGR_MCFR & CKGR_MCFR_MAINF_Msk) == 0) {
		PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTEN_Msk) | CKGR_MOR_KEY_PASSWD;
		return EHardwareProblem;
	}

	return ENone;
}

enum retCode OSC_mainck_crystal_disable()
{
	/* Main Crystal Oscillator cannot be the source for MAINCK */
	if(PMC->CKGR_MOR & CKGR_MOR_MOSCSEL_Msk) {
		return EDenied;
	}

	/* Turn off Main Crystal Oscillator. */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTEN_Msk) | CKGR_MOR_KEY_PASSWD;

	return ENone;
}

void OSC_mainck_rc_enable(const enum OSC_rc_freq freq)
{
	/* Turn on Main RC Oscillator and waiting for stabilize */
	PMC->CKGR_MOR |= CKGR_MOR_MOSCRCEN_Msk | CKGR_MOR_KEY_PASSWD;
	while(!(PMC->PMC_SR & PMC_SR_MOSCRCS_Msk));

	/* Change frequency and wait for stabilize */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCF_Msk) | CKGR_MOR_MOSCRCF((uint8_t)freq) | CKGR_MOR_KEY_PASSWD;
	while(!(PMC->PMC_SR & PMC_SR_MOSCRCS_Msk));
}

enum retCode OSC_mainck_rc_disable()
{
	/* Main RC Oscillator cannot be the source for MAINCK */
	if(!(PMC->CKGR_MOR & CKGR_MOR_MOSCSEL_Msk)) {
		return EDenied;
	}

	/* Turn off Main RC Oscillator. */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCEN_Msk) | CKGR_MOR_KEY_PASSWD;

	return ENone;
}

enum retCode OSC_mainck_select(const bool crystal)
{
	/* If the selected source isn't stabilized, it can't be switched */
	if(((crystal) && !(PMC->PMC_SR & PMC_SR_MOSCXTS_Msk)) || ((!crystal) && !(PMC->PMC_SR & PMC_SR_MOSCRCS_Msk))) {
		return EDenied;
	}

	/* Switch MAINCK to selected clock source. */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCSEL_Msk) | ((uint8_t)crystal << CKGR_MOR_MOSCSEL_Pos) | CKGR_MOR_KEY_PASSWD;
	while(!(PMC->PMC_SR & PMC_SR_MOSCSELS_Msk));

	/* Save actual MAINCK value */
	if(PMC->CKGR_MOR & CKGR_MOR_MOSCSEL_Msk) {
		gOSC_MAINCK_freq_val = CFG_OSC_CRYSTAL_FREQ;
	}
	else {
		if((PMC->CKGR_MOR & CKGR_MOR_MOSCRCF_Msk) == OSC_FREQ_4MHz) {
			gOSC_MAINCK_freq_val = CHIP_FREQ_MAINCK_RC_4MHZ;
		}
		else if((PMC->CKGR_MOR & CKGR_MOR_MOSCRCF_Msk) == OSC_FREQ_8MHz) {
			gOSC_MAINCK_freq_val = CHIP_FREQ_MAINCK_RC_8MHZ;
		}
		else {
			gOSC_MAINCK_freq_val = CHIP_FREQ_MAINCK_RC_12MHZ;
		}
	}

	return ENone;
}

enum retCode OSC_plla_enable(const uint8_t div, const uint16_t mul, const uint8_t startUpTime)
{
	if(!div || !mul) {
		OSC_plla_disable();
		return EInvalidParameter;
	}

	/* Minimum input frequency must be higher or equal to 8 MHz */
	if(gOSC_MAINCK_freq_val < CHIP_FREQ_MAINCK_RC_8MHZ) {
		return EDenied;
	}

	/* Calculate the PLLA frequency, which must be in interval 160 to 500 MHz */
	uint32_t plla_freq = (gOSC_MAINCK_freq_val/div)*(mul + 1);
	if(plla_freq < 160000000UL || plla_freq > 500000000UL) {
		return EInvalidParameter;
	}

	/* Configure PLLA and wait for stabilize */
	PMC->CKGR_PLLAR = CKGR_PLLAR_ONE_Msk | CKGR_PLLAR_MULA(mul) | CKGR_PLLAR_DIVA(div) | CKGR_PLLAR_PLLACOUNT(startUpTime);
	while(!(PMC->PMC_SR & PMC_SR_LOCKA_Msk));

	gOSC_PLLA_freq_val = plla_freq;

	return ENone;
}

void OSC_plla_disable()
{
	PMC->CKGR_PLLAR = CKGR_PLLAR_ONE_Msk;

	gOSC_PLLA_freq_val = 0UL;
}

uint32_t OSC_get_mainck_freq()
{
	return gOSC_MAINCK_freq_val;
}

uint32_t OSC_get_slck_freq()
{
	return gOSC_SLCK_freq_val;
}

uint32_t OSC_get_plla_freq()
{
	return gOSC_PLLA_freq_val;
}

uint32_t OSC_get_upll_freq()
{
	return gOSC_SLCK_freq_val;
}