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

#include "clocks.h"

static uint32_t gPMC_HCLK_freq_val = CHIP_FREQ_MAINCK_RC_12MHZ;
static uint32_t gPMC_MCK_freq_val = CHIP_FREQ_MAINCK_RC_12MHZ;
static uint32_t gPMC_PCK_freq_val[8] = { 0UL };
 
enum retCode PMC_master_clock_configure(const enum PMC_osc_source clkSource, const enum PMC_prescaler pres, const enum PMC_divider div)
{
	/* If clkSource is invalid */
	if(clkSource >= PMC_OSC_MCK) {
		return EInvalidParameter;
	}

	/* PLL as clock source must be stabilized */
	if(((clkSource == PMC_OSC_PLLA) && !(PMC->PMC_SR & PMC_SR_LOCKA_Msk)) || ((clkSource == PMC_OSC_UPLL) && !(PMC->PMC_SR & PMC_SR_LOCKU_Msk))) {
		return EDenied;
	}

	/* Set Clock Source (fast to slow frequency) */
	if(clkSource == PMC_OSC_SLCK || clkSource == PMC_OSC_MAINCK) {
		PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS((uint8_t)clkSource);
		while(!(PMC->PMC_SR & PMC_SR_MCKRDY_Msk));
	}

	/* Set prescaler */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | PMC_MCKR_PRES((uint8_t)pres);
	while(!(PMC->PMC_SR & PMC_SR_MCKRDY_Msk));

	/* Set divider */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_MDIV_Msk) | PMC_MCKR_MDIV((uint8_t)div);
	while(!(PMC->PMC_SR & PMC_SR_MCKRDY_Msk));

	/* Set Clock Source (slow to fast frequency) */
	if(clkSource == PMC_OSC_PLLA || clkSource == PMC_OSC_UPLL) {
		PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS((uint8_t)clkSource);
		while(!(PMC->PMC_SR & PMC_SR_MCKRDY_Msk));
	}

	/* Calculate frequency */
	uint32_t inputFreq = 0;
	uint32_t prescaler = 3;
	uint32_t divider = 3;

	switch(clkSource) {
		case PMC_OSC_SLCK:
			inputFreq = OSC_get_slck_freq();
			break;
		case PMC_OSC_MAINCK:
			inputFreq = OSC_get_mainck_freq();
			break;
		case PMC_OSC_PLLA:
			inputFreq = OSC_get_plla_freq();
			break;
		case PMC_OSC_UPLL:
			inputFreq = OSC_get_upll_freq();
			break;
		default:
			break;
	}

	if(pres != PMC_PRES_3) {
		prescaler = (1 << pres);
	}

	if(div != PMC_DIV_3) {
		divider = (1 << div);
	}

	gPMC_HCLK_freq_val = inputFreq / prescaler;
	gPMC_MCK_freq_val = gPMC_HCLK_freq_val / divider;

	return ENone;
}

void PMC_pck_enable(const enum PMC_pck_number pck, const enum PMC_osc_source clkSource, const uint8_t pres)
{
	/* Deinitialize PCK when (re)configuring */
	PMC_pck_disable(pck);

	/* Configure PCK and wait for stabilize */
	PMC->PMC_PCK[(uint8_t)pck] = PMC_PCK_CSS((uint8_t)clkSource) | PMC_PCK_PRES(pres);
	while(!(PMC->PMC_SR & (1 << (PMC_SR_PCKRDY_Pos + (uint8_t)pck))));
	PMC->PMC_SCER = (1 << ((uint8_t)pck + PMC_SCER_PCK0_Pos));

	/* Calculate frequency */
	uint32_t inputFreq = 0;

	switch(clkSource) {
		case PMC_OSC_SLCK:
			inputFreq = OSC_get_slck_freq();
			break;
		case PMC_OSC_MAINCK:
			inputFreq = OSC_get_mainck_freq();
			break;
		case PMC_OSC_PLLA:
			inputFreq = OSC_get_plla_freq();
			break;
		case PMC_OSC_UPLL:
			inputFreq = OSC_get_upll_freq();
			break;
		case PMC_OSC_MCK:
			inputFreq = gPMC_MCK_freq_val;
			break;
		default:
			break;
	}

	gPMC_PCK_freq_val[(uint8_t)pck] = inputFreq / (uint32_t)pres;
}

uint32_t PMC_get_hclk_freq()
{
	return gPMC_HCLK_freq_val;
}

uint32_t PMC_get_mck_freq()
{
	return gPMC_MCK_freq_val;
}

uint32_t PMC_get_pck_freq(const enum PMC_pck_number pck)
{
	return gPMC_PCK_freq_val[(uint8_t)pck];
}