#include "clocks.h"
#include "conf_core.h"
#include <stddef.h>
void clock_osc32k_init(void)
{
#if CORE_CONF_CLK_XOSC32K_ENABLE == 1
	OSC32KCTRL->XOSC32K.bit.CGM =		CORE_CONF_CLK_XOSC32K_CGM;
	OSC32KCTRL->XOSC32K.bit.WRTLOCK =	CORE_CONF_CLK_XOSC32K_WRTLOCK;
	OSC32KCTRL->XOSC32K.bit.STARTUP =	CORE_CONF_CLK_XOSC32K_STARTUP_TIME;
	OSC32KCTRL->XOSC32K.bit.RUNSTDBY =	CORE_CONF_CLK_XOSC32K_RUNSTDBY;
	OSC32KCTRL->XOSC32K.bit.ONDEMAND =	CORE_CONF_CLK_XOSC32K_ONDEMAND;
	OSC32KCTRL->XOSC32K.bit.EN1K =		CORE_CONF_CLK_XOSC32K_EN1K;
	OSC32KCTRL->XOSC32K.bit.EN32K =	CORE_CONF_CLK_XOSC32K_EN32K;
	OSC32KCTRL->XOSC32K.bit.XTALEN =	CORE_CONF_CLK_XOSC32K_XTALEN;
	OSC32KCTRL->CFDCTRL.bit.CFDPRESC =	CORE_CONF_CLK_XOSC32K_CFDPRESC;
	OSC32KCTRL->CFDCTRL.bit.SWBACK =	CORE_CONF_CLK_XOSC32K_SWBACK;
	OSC32KCTRL->CFDCTRL.bit.CFDEN =	CORE_CONF_CLK_XOSC32K_CFDEN;

#if CORE_CONF_CLK_XOSC32K_ENABLE == 1 && CORE_CONF_CLK_XOSC32K_ONDEMAND == 0
	while(OSC32KCTRL->STATUS.bit.XOSC32KRDY == 0);
#endif

#endif
}
void clock_osc_init(void)
{
#if CORE_CONF_CLK_XOSC0_ENABLE == 1
	OSCCTRL->XOSCCTRL[0].bit.XTALEN =			CORE_CONF_CLK_XOSC0_XTALEN;
	OSCCTRL->XOSCCTRL[0].bit.RUNSTDBY =		CORE_CONF_CLK_XOSC0_RUNSTDBY;
	OSCCTRL->XOSCCTRL[0].bit.ONDEMAND =		CORE_CONF_CLK_XOSC0_ONDEMAND;
	OSCCTRL->XOSCCTRL[0].bit.LOWBUFGAIN =		CORE_CONF_CLK_XOSC0_LOWBUFGAIN;
	OSCCTRL->XOSCCTRL[0].bit.IPTAT =			CORE_CONF_CLK_XOSC0_IPTAT;
	OSCCTRL->XOSCCTRL[0].bit.IMULT =			CORE_CONF_CLK_XOSC0_IMULT;
	OSCCTRL->XOSCCTRL[0].bit.ENALC =			CORE_CONF_CLK_XOSC0_ENALC;
	OSCCTRL->XOSCCTRL[0].bit.CFDEN =			CORE_CONF_CLK_XOSC0_CFDEN;
	OSCCTRL->XOSCCTRL[0].bit.CFDPRESC =		CORE_CONF_CLK_XOSC0_CFDPRESC;
	OSCCTRL->XOSCCTRL[0].bit.SWBEN =			CORE_CONF_CLK_XOSC0_SWBEN;
	OSCCTRL->XOSCCTRL[0].bit.STARTUP =			CORE_CONF_CLK_XOSC0_STARTUP_TIME;
	while(0 == OSCCTRL->STATUS.bit.XOSCRDY0);

#if CORE_CONF_CLK_XOSC0_ENALC == 1
	OSCCTRL->XOSCCTRL[0].bit.ENALC =			CORE_CONF_CLK_XOSC0_ENALC;
#endif

#if CORE_CONF_CLK_XOSC0_ONDEMAND == 1
	OSCCTRL->XOSCCTRL[0].bit.ONDEMAND =		CORE_CONF_CLK_XOSC0_ONDEMAND;
#endif
#endif

#if CORE_CONF_CLK_XOSC1_ENABLE == 1
	OSCCTRL->XOSCCTRL[1].bit.XTALEN =			CORE_CONF_CLK_XOSC1_XTALEN;
	OSCCTRL->XOSCCTRL[1].bit.RUNSTDBY =		CORE_CONF_CLK_XOSC1_RUNSTDBY;
	OSCCTRL->XOSCCTRL[1].bit.LOWBUFGAIN =		CORE_CONF_CLK_XOSC1_LOWBUFGAIN;
	OSCCTRL->XOSCCTRL[1].bit.IPTAT =			CORE_CONF_CLK_XOSC1_IPTAT;
	OSCCTRL->XOSCCTRL[1].bit.IMULT =			CORE_CONF_CLK_XOSC1_IMULT;
	OSCCTRL->XOSCCTRL[1].bit.CFDEN =			CORE_CONF_CLK_XOSC1_CFDEN;
	OSCCTRL->XOSCCTRL[1].bit.CFDPRESC =		CORE_CONF_CLK_XOSC1_CFDPRESC;
	OSCCTRL->XOSCCTRL[1].bit.SWBEN =			CORE_CONF_CLK_XOSC1_SWBEN;
	OSCCTRL->XOSCCTRL[1].bit.STARTUP =			CORE_CONF_CLK_XOSC1_STARTUP_TIME;
	while(0 == OSCCTRL->STATUS.bit.XOSCRDY1);

#if CORE_CONF_CLK_XOSC1_ENALC == 1
	OSCCTRL->XOSCCTRL[1].bit.ENALC =			CORE_CONF_CLK_XOSC1_ENALC;
#endif

#if CORE_CONF_CLK_XOSC1_ONDEMAND == 1
	OSCCTRL->XOSCCTRL[1].bit.ONDEMAND =		CORE_CONF_CLK_XOSC1_ONDEMAND;
#endif
#endif
}

void clock_mclk_init(void)
{

}

void clock_gclk_init(void)
{

}

void clock_dpll_init(void)
{
#if CORE_CONF_CLK_DPLL0_ENABLE == 1
	uint32_t volatile *atomic0 = NULL;
#if CORE_CONF_CLK_DPLL0_REFCLK == 0
	// enter critical
	*atomic0 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL0].bit.GEN =
		GCLK_PCHCTRL_GEN(CORE_CONF_CLK_DPLL0_GCLK_SRC);
	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL0].bit.CHEN =
		CORE_CONF_CLK_DPLL0_ENABLE;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);
#endif

	// enter critical
	*atomic0 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dpll ratio
	OSCCTRL->Dpll[0].DPLLRATIO.bit.LDRFRAC = CORE_CONF_CLK_DPLL0_LDRFRAC_VAL;
	OSCCTRL->Dpll[0].DPLLRATIO.bit.LDR = CORE_CONF_CLK_DPLL0_LDR_VAL;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic0);

	// enter critical again
	*atomic0 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dpll ctrlb
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.DIV = CORE_CONF_CLK_DPLL0_DIV_VAL;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.DCOEN = CORE_CONF_CLK_DPLL0_DCOEN;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.DCOFILTER = CORE_CONF_CLK_DPLL0_DCOFILTER;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.LBYPASS = CORE_CONF_CLK_DPLL0_LBYPASS;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.LTIME = CORE_CONF_CLK_DPLL0_LTIME;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.REFCLK = CORE_CONF_CLK_DPLL0_REFCLK;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.WUF = CORE_CONF_CLK_DPLL0_WUF;
	OSCCTRL->Dpll[0].DPLLCTRLB.bit.FILTER = CORE_CONF_CLK_DPLL0_FILTER;
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.RUNSTDBY = CORE_CONF_CLK_DPLL0_RUNSTDBY;
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = CORE_CONF_CLK_DPLL0_ENABLE;

	__DMB();
	__set_PRIMASK(*atomic0);

#endif

#if CORE_CONF_CLK_DPLL1_ENABLE == 1
	uint32_t volatile *atomic1 = NULL;
#if CORE_CONF_CLK_DPLL0_REFCLK == 0
	// enter critical
	*atomic1 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL1].bit.GEN =
		GCLK_PCHCTRL_GEN(CORE_CONF_CLK_DPLL1_GCLK_SRC);
	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_FDPLL1].bit.CHEN =
		CORE_CONF_CLK_DPLL1_ENABLE;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic1);
#endif

	// enter critical
	*atomic1 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dpll ratio
	OSCCTRL->Dpll[1].DPLLRATIO.bit.LDRFRAC = CORE_CONF_CLK_DPLL1_LDRFRAC_VAL;
	OSCCTRL->Dpll[1].DPLLRATIO.bit.LDR = CORE_CONF_CLK_DPLL1_LDR_VAL;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic1);

	// enter critical again
	*atomic1 = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dpll ctrlb
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.DIV =		CORE_CONF_CLK_DPLL1_DIV_VAL;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.DCOEN =		CORE_CONF_CLK_DPLL1_DCOEN;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.DCOFILTER =	CORE_CONF_CLK_DPLL1_DCOFILTER;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.LBYPASS =	CORE_CONF_CLK_DPLL1_LBYPASS;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.LTIME =		CORE_CONF_CLK_DPLL1_LTIME;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.REFCLK =	CORE_CONF_CLK_DPLL1_REFCLK;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.WUF =		CORE_CONF_CLK_DPLL1_WUF;
	OSCCTRL->Dpll[1].DPLLCTRLB.bit.FILTER =	CORE_CONF_CLK_DPLL1_FILTER;
	OSCCTRL->Dpll[1].DPLLCTRLA.bit.RUNSTDBY =	CORE_CONF_CLK_DPLL1_RUNSTDBY;
	OSCCTRL->Dpll[1].DPLLCTRLA.bit.ENABLE =	CORE_CONF_CLK_DPLL1_ENABLE;

	__DMB();
	__set_PRIMASK(*atomic1);

#endif

#if CORE_CONF_CLK_DPLL0_ENABLE == 1
	while(!(OSCCTRL->Dpll[0].DPLLSTATUS.bit.LOCK || OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY));

	// enter critical
	*atomic0 = __get_PRIMASK();
	__disable_irq();
	__DMB();

#if CORE_CONF_CLK_DPLL0_ONDEMAND == 1
	OSCCTRL->Dpll[0].DPLLCTRLA.bit.ONDEMAND = CORE_CONF_CLK_DPLL0_ONDEMAND;
#endif

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic0);

#endif

#if CORE_CONF_CLK_DPLL1_ENABLE == 1
	while(!(OSCCTRL->Dpll[1].DPLLSTATUS.bit.LOCK || OSCCTRL->Dpll[1].DPLLSTATUS.bit.CLKRDY));

	// enter critical
	*atomic1 = __get_PRIMASK();
	__disable_irq();
	__DMB();

#if CORE_CONF_CLK_DPLL1_ONDEMAND == 1
	OSCCTRL->Dpll[1].DPLLCTRLA.bit.ONDEMAND = CORE_CONF_CLK_DPLL1_ONDEMAND;
#endif

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic1);

#endif
}

void clock_dfll_init(void)
{
#if CORE_CONF_CLK_DFLL_ENABLE
	// enter critical
	uint32_t volatile *atomic = NULL;
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	GCLK->GENCTRL[0].bit.SRC = GCLK_GENCTRL_SRC_OSCULP32K;
	while(GCLK->SYNCBUSY.bit.GENCTRL0);

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

	// enter critical again
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// reset stuff
	OSCCTRL->DFLLCTRLA.reg = 0;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

#if CORE_CONF_CLK_DFLL_USBCRM != 1 && CORE_CONF_CLK_DFLL_MODE != 0
	// enter critical
	atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// reset stuff
	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_DFLL48].bit.GEN =
		GCLK_PCHCTRL_GEN(CORE_CONF_CLK_DFLL_GCLK_SRC);
	GCLK->PCHCTRL[OSCCTRL_GCLK_ID_DFLL48].bit.EN = CORE_CONF_CLK_DFLL_ENABLE;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);
#endif

	// enter critical again
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// configure clock multiplier stuff
	OSCCTRL->DFLLMUL.bit.MUL = CORE_CONF_CLK_DFLL_MUL_VAL;
	OSCCTRL->DFLLMUL.bit.CSTEP = CORE_CONF_CLK_DFLL_CSTEP_VAL;
	OSCCTRL->DFLLMUL.bit.FSTEP = CORE_CONF_CLK_DFLL_FSTEP_VAL;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

	while(OSCCTRL->DFLLSYNC.bit.DFLLMUL);

	// enter critical again
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// reset dfll ctrlb
	OSCCTRL->DFLLCTRLB.reg = 0;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

	// wait for ctrlb sync
	while(OSCCTRL->DFLLSYNC.bit.DFLLCTRLB);

#if CORE_CONF_CLK_DFLL_OVERWRITE_CAL == 1
	// enter critical
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// set calib for dfll48m
	OSCCTRL->DFLLVAL.bit.COARSE = CORE_CONF_CLK_DFLL_COARSE_VAL;
	OSCCTRL->DFLLVAL.bit.FINE = CORE_CONF_CLK_DFLL_FINE_VAL;
	OSCCTRL->DFLLVAL.bit.DIFF = CORE_CONF_CLK_DFLL_DIFF_VAL;

	__DMB();
	__set_PRIMASK(*atomic);
#endif

	// enter critical
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dfll val
	// rewriting this for some reason?
	OSCCTRL->DFLLVAL.reg = OSCCTRL->DFLLVAL.reg;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

	// let dfll val sync
	while(OSCCTRL->DFLLSYNC.bit.DFLLVAL);

	// enter critical again
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	// write dfll ctrlb
	OSCCTRL->DFLLCTRLB.bit.WAITLOCK = CORE_CONF_CLK_DFLL_WAITLOCK;
	OSCCTRL->DFLLCTRLB.bit.QLDIS = CORE_CONF_CLK_DFLL_QLDIS;
	OSCCTRL->DFLLCTRLB.bit.USBCRM = CORE_CONF_CLK_DFLL_USBCRM;
	OSCCTRL->DFLLCTRLB.bit.STABLE = CORE_CONF_CLK_DFLL_STABLE_FCALIB;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);

	if (OSCCTRL->DFLLCTRLB.bit.MODE)
	{
		while(OSCCTRL->STATUS.reg != (OSCCTRL_STATUS_DFLLRDY | OSCCTRL_STATUS_DFLLLCKC));
	}
	else
	{
		while(!OSCCTRL->STATUS.bit.DFLLRDY);
	}

#if CORE_CONF_CLK_DFLL_ONDEMAND == 1
	// enter critical
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();

	OSCCTRL->DFLLCTRLA.bit.ONDEMAND = CORE_CONF_CLK_DFLL_ONDEMAND;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);
#endif

	while(GCLK->SYNCBUSY.reg);

	// enter critical
	*atomic = __get_PRIMASK();
	__disable_irq();
	__DMB();


	// reset gclk 0
	GCLK->GENCTRL[0].bit.SRC = CORE_CONF_GCLK_0_CLOCK_SOURCE;

	// leave critical
	__DMB();
	__set_PRIMASK(*atomic);
#endif
}
