/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       SAM Software Package License                           */
/* ---------------------------------------------------------------------------- */
/* Copyright (c) 2015, Atmel Corporation                                        */
/*                                                                              */
/* All rights reserved.                                                         */
/*                                                                              */
/* Redistribution and use in source and binary forms, with or without           */
/* modification, are permitted provided that the following condition is met:    */
/*                                                                              */
/* - Redistributions of source code must retain the above copyright notice,     */
/* this list of conditions and the disclaimer below.                            */
/*                                                                              */
/* Atmel's name may not be used to endorse or promote products derived from     */
/* this software without specific prior written permission.                     */
/*                                                                              */
/* DISCLAIMER:  THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR   */
/* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE   */
/* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,      */
/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,  */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    */
/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING         */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, */
/* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                           */
/* ---------------------------------------------------------------------------- */

/** \addtogroup pmc_module Working with PMC
 * The PMC driver provides the Interface to configure the Power Management
 * Controller (PMC).
 *
 *  \section Usage
 * <ul>
 *  <li> Enables/Disable the clock of a peripheral by using
 * PMC_EnablePeripheral() and PMC_DisablePeripheral().</li>
 *  <li> Enables/Disable the clock of all peripherals by using
 * PMC_EnableAllPeripherals() and PMC_DisableAllPeripherals().</li>
 *  <li> Get status of a peripheral using PMC_IsPeriphEnabled().</li>
 *  <li> Manage the clocks using PMC_EnableExtOsc(), PMC_DisableExtOsc(),
 * PMC_SelectExtOsc(), PMC_SelectExtBypassOsc(), PMC_EnableIntRC4_8_12MHz(),
 * PMC_DisableIntRC4_8_12MHz(), PMC_SetPllaClock(), PMC_SetMckSelection(),
 * PMC_DisableAllClocks(), PMC_ConfigureMckWithPlla(),
 * PMC_EnableXT32KFME() and PMC_ConfigurePCK2().</li>
 *
 * </ul>
 * For more accurate information, please look at the PMC section of the Datasheet.
 *
 * Related files :\n
 * \ref pmc.c\n
 * \ref pmc.h.\n
*/

/**
*  \file
*
*  \section Purpose
*
*  Interface for configuring and using Power Management Controller (PMC)
* peripherals.
*
*/

/**
 * \file
 *
 * Implementation of Power Management Controller (PMC).
 *
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"

#include <assert.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

#define MASK_STATUS0 0xFFFFFFFC
#define MASK_STATUS1 0xFFFFFFFF

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/
/**
 * \brief Switch MCK to PLLA clock.
 */
static void _PMC_SwitchMck2PllaClock(void)

{
	/* Select PLLA as input clock for MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_PLLA_CLK;

	/* Wait until the master clock is established */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Switch MCK to main clock.
 */
static void _PMC_SwitchMck2MainClock(void)
{
	/* Select Main Oscillator as input clock for MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_MAIN_CLK;

	/* Wait until the master clock is established */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	PMC->PMC_MCKR = PMC_MCKR_CSS_MAIN_CLK;

	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Switch MCK to slow clock.
 */
static void _PMC_SwitchMck2SlowClock(void)
{
	/* Select Slow Clock as input clock for MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_SLOW_CLK;

	/* Wait until the master clock is established */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Set prescaler for MCK.
 *
 * \param prescaler Master Clock prescaler
 */
static void _PMC_SetMckPrescaler(uint32_t prescaler)
{
	/* Change MCK Prescaler divider in PMC_MCKR register */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | prescaler;

	/* Wait until the master clock is established */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}
/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Enables the clock of a peripheral. The peripheral ID is used
 * to identify which peripheral is targeted.
 *
 * \note The ID must NOT be shifted (i.e. 1 << ID_xxx).
 *
 * \param id  Peripheral ID (ID_xxx).
 */
void PMC_EnablePeripheral(uint32_t dwId)
{
	assert(dwId < 63);

	if (dwId < 32) {
		if ((PMC->PMC_PCSR0 & ((uint32_t)1 << dwId)) == ((uint32_t)1 << dwId)) {
			TRACE_DEBUG("PMC_EnablePeripheral: clock of peripheral" \
						 " %u is already enabled\n\r", (unsigned int)dwId);
		} else
			PMC->PMC_PCER0 = 1 << dwId;
	} else {
		dwId -= 32;

		if ((PMC->PMC_PCSR1 & ((uint32_t)1 << dwId)) == ((uint32_t)1 << dwId)) {
			TRACE_DEBUG("PMC_EnablePeripheral: clock of peripheral" \
						 " %u is already enabled\n\r", (unsigned int)(dwId + 32));
		} else
			PMC->PMC_PCER1 = 1 << dwId;
	}
}

/**
 * \brief Disables the clock of a peripheral. The peripheral ID is used
 * to identify which peripheral is targeted.
 *
 * \note The ID must NOT be shifted (i.e. 1 << ID_xxx).
 *
 * \param id  Peripheral ID (ID_xxx).
 */
void PMC_DisablePeripheral(uint32_t dwId)
{
	assert(dwId < 63);

	if (dwId < 32) {
		if ((PMC->PMC_PCSR0 & ((uint32_t)1 << dwId)) != ((uint32_t)1 << dwId)) {
			TRACE_DEBUG("PMC_DisablePeripheral: clock of peripheral" \
						" %u is not enabled\n\r", (unsigned int)dwId);
		} else
			PMC->PMC_PCDR0 = 1 << dwId;
	} else {
		dwId -= 32;

		if ((PMC->PMC_PCSR1 & ((uint32_t)1 << dwId)) != ((uint32_t)1 << dwId)) {
			TRACE_DEBUG("PMC_DisablePeripheral: clock of peripheral"
						 " %u is not enabled\n\r", (unsigned int)(dwId + 32));
		} else
			PMC->PMC_PCDR1 = 1 << dwId;
	}
}

/**
 * \brief Enable all the periph clock via PMC.
 */
void PMC_EnableAllPeripherals(void)
{
	PMC->PMC_PCER0 = MASK_STATUS0;

	while ((PMC->PMC_PCSR0 & MASK_STATUS0) != MASK_STATUS0);

	PMC->PMC_PCER1 = MASK_STATUS1;

	while ((PMC->PMC_PCSR1 & MASK_STATUS1) != MASK_STATUS1);

	TRACE_DEBUG("Enable all periph clocks\n\r");
}

/**
 * \brief Disable all the periph clock via PMC.
 */
void PMC_DisableAllPeripherals(void)
{
	PMC->PMC_PCDR0 = MASK_STATUS0;

	while ((PMC->PMC_PCSR0 & MASK_STATUS0) != 0);

	PMC->PMC_PCDR1 = MASK_STATUS1;

	while ((PMC->PMC_PCSR1 & MASK_STATUS1) != 0);

	TRACE_DEBUG("Disable all periph clocks\n\r");
}

/**
 * \brief Get Periph Status for the given peripheral ID.
 *
 * \param id  Peripheral ID (ID_xxx).
 */
uint32_t PMC_IsPeriphEnabled(uint32_t dwId)
{
	assert(dwId < ID_PERIPH_COUNT);

	if (dwId < 32)
		return (PMC->PMC_PCSR0 & (1 << dwId));
	else
		return (PMC->PMC_PCSR1 & (1 << (dwId - 32)));
}


/**
 * \brief Enable external oscillator as main clock input.
 */
void PMC_EnableExtOsc(void)
{
	uint32_t   read_MOR;

	/* Before switching MAIN OSC on external crystal : enable it and don't disable
	 * at the same time RC OSC in case of if MAIN OSC is still using RC OSC
	 */

	read_MOR = PMC->CKGR_MOR;
	read_MOR &= ~CKGR_MOR_MOSCRCF_Msk;
	/* reset MOSCRCF field in MOR register before select RC 12MHz */
	read_MOR  |= (CKGR_MOR_KEY_PASSWD
				  | CKGR_MOR_MOSCRCF_12_MHz
				  | CKGR_MOR_MOSCXTEN
				  | CKGR_MOR_MOSCRCEN
				  | CKGR_MOR_MOSCXTST(DEFAUTL_MAIN_OSC_COUNT));
	/* enable external crystal - enable RC OSC */

	PMC->CKGR_MOR = read_MOR;

	while (!(PMC->PMC_SR & PMC_SR_MOSCRCS));

	/* wait end of RC oscillator stabilization */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	read_MOR |= CKGR_MOR_MOSCSEL;
	/* select external crystal */

	PMC->CKGR_MOR = read_MOR;

	while (!(PMC->PMC_SR & PMC_SR_MOSCSELS));

	/* Wait end of Main Oscillator Selection */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Disable external 12MHz oscillator.
 */
void PMC_DisableExtOsc(void)
{
	uint32_t   read_MOR;

	read_MOR = PMC->CKGR_MOR;
	read_MOR &= ~CKGR_MOR_MOSCXTEN; /* disable main xtal osc */
	PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD | read_MOR;

	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Select external OSC.
 */
void PMC_SelectExtOsc(void)
{
	/* switch from internal RC 12 MHz to external OSC 12 MHz */
	/* wait Main XTAL Oscillator stabilisation*/
	if ((PMC->CKGR_MOR & CKGR_MOR_MOSCSEL) == CKGR_MOR_MOSCSEL) {
		PMC_DisableIntRC4_8_12MHz();
		return;
	}

	/* enable external OSC 12 MHz */
	PMC->CKGR_MOR |= CKGR_MOR_MOSCXTEN | CKGR_MOR_KEY_PASSWD;

	/* wait Main CLK Ready */
	while (!(PMC->CKGR_MCFR & CKGR_MCFR_MAINFRDY));

	/* switch MAIN clock to external OSC 12 MHz*/
	PMC->CKGR_MOR |= CKGR_MOR_MOSCSEL | CKGR_MOR_KEY_PASSWD;

	/* wait MAIN clock status change for external OSC 12 MHz selection*/
	while (!(PMC->PMC_SR & PMC_SR_MOSCSELS));

	/* in case where MCK is running on MAIN CLK */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	PMC_DisableIntRC4_8_12MHz();
}


/**
 * \brief Select external OSC.
 */
void PMC_SelectExtBypassOsc(void)
{
	volatile uint32_t timeout;

	if ((PMC->CKGR_MOR & CKGR_MOR_MOSCXTBY) != CKGR_MOR_MOSCXTBY) {
		PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD |
						CKGR_MOR_MOSCRCEN |
						CKGR_MOR_MOSCXTST(0xFF) |
						CKGR_MOR_MOSCXTBY;
		PMC->CKGR_MOR |= CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCSEL;

		/* wait MAIN clock status change for external OSC 12 MHz selection*/
		while (!(PMC->PMC_SR & PMC_SR_MOSCSELS));

		// Check if an external clock is provided
		for (timeout = 0; timeout < 0xffff; timeout++);

		while (!(PMC->CKGR_MCFR & CKGR_MCFR_MAINFRDY));
	}
}

/**
 * \brief Enable internal 4/8/12MHz fast RC as main clock input.
 *
 * \param freqSelect fast RC frequency (FAST_RC_4MHZ, FAST_RC_8MHZ,
 * FAST_RC_12MHZ).
 */
void PMC_EnableIntRC4_8_12MHz(uint32_t freqSelect)
{
	/* Enable Fast RC oscillator but DO NOT switch to RC now */
	PMC->CKGR_MOR |= (CKGR_MOR_KEY_PASSWD | CKGR_MOR_MOSCRCEN);

	/* Wait the Fast RC to stabilize */
	while (!(PMC->PMC_SR & PMC_SR_MOSCRCS));

	/* Change Fast RC oscillator frequency */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCF_Msk) |
					CKGR_MOR_KEY_PASSWD | freqSelect;

	/* Wait the Fast RC to stabilize */
	while (!(PMC->PMC_SR & PMC_SR_MOSCRCS));

	/* Switch to Fast RC */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCSEL) |
					CKGR_MOR_KEY_PASSWD;

	/* wait MAIN clock status change for Fast RC oscillator */
	while (!(PMC->PMC_SR & PMC_SR_MOSCSELS));

	/* in case where MCK is running on MAIN CLK */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

}

/**
 * \brief Disable internal 4/8/12MHz fast RC.
 */
void PMC_DisableIntRC4_8_12MHz(void)
{
	uint32_t   read_MOR;

	read_MOR = PMC->CKGR_MOR;

	read_MOR &= ~CKGR_MOR_MOSCRCF_Msk;   /* reset MOSCRCF field in MOR register */
	read_MOR &= ~CKGR_MOR_MOSCRCEN;      /* disable fast RC */
	PMC->CKGR_MOR = CKGR_MOR_KEY_PASSWD | read_MOR;

	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

/**
 * \brief Configure PLLA clock by giving MUL and DIV.
 *        Disable PLLA when 'mul' set to 0.
 *
 * \param mul  PLL multiplier factor.
 * \param div  PLL divider factor.
 */
void PMC_SetPllaClock(uint32_t mul, uint32_t div)
{
	if (mul != 0) {
		/* Init PLL speed */
		PMC->CKGR_PLLAR = CKGR_PLLAR_ONE
						  | CKGR_PLLAR_PLLACOUNT(DEFAUTL_PLLA_COUNT)
						  | CKGR_PLLAR_MULA(mul - 1)
						  | CKGR_PLLAR_DIVA(div);

		/* Wait for PLL stabilization */
		while (!(PMC->PMC_SR & PMC_SR_LOCKA));
	} else {
		PMC->CKGR_PLLAR = CKGR_PLLAR_ONE; /* disable PLL A */
	}
}

/**
 * \brief Selection of Master Clock.
 *
 * \param clockSource  Master Clock source.
 * \param prescaler    Master Clock prescaler.
 *
 * \note
 * The PMC_MCKR register must not be programmed in a single write
 * operation (see. Product Data Sheet).
 */
void PMC_SetMckSelection(uint32_t clockSource, uint32_t prescaler)
{
	switch (clockSource) {
	case PMC_MCKR_CSS_SLOW_CLK :
		_PMC_SwitchMck2SlowClock();
		_PMC_SetMckPrescaler(prescaler);
		break;

	case PMC_MCKR_CSS_MAIN_CLK :
		_PMC_SwitchMck2MainClock();
		_PMC_SetMckPrescaler(prescaler);
		break;

	case PMC_MCKR_CSS_PLLA_CLK :
		_PMC_SetMckPrescaler(prescaler);
		_PMC_SwitchMck2PllaClock();
		break;
	}
}

/**
 * \brief Disable all clocks.
 */
void PMC_DisableAllClocks(void)
{
	uint32_t   read_reg;

	PMC->PMC_SCDR = PMC_SCDR_PCK0 | PMC_SCDR_PCK1 | PMC_SCDR_PCK2 | PMC_SCDR_PCK3 |
					PMC_SCDR_PCK4 | PMC_SCDR_PCK5 | PMC_SCDR_PCK6;  /* disable PCK */

	_PMC_SwitchMck2MainClock();

	PMC->CKGR_PLLAR = PMC->CKGR_PLLAR & ~CKGR_PLLAR_MULA_Msk;   /* disable PLL A */

	_PMC_SwitchMck2SlowClock();

	read_reg  =  PMC->CKGR_MOR;
	read_reg  =  (read_reg & ~CKGR_MOR_MOSCRCEN) | CKGR_MOR_KEY_PASSWD;
	/* disable RC OSC */

	PMC->CKGR_MOR = read_reg;

	PMC_DisableAllPeripherals(); /* disable all peripheral clocks */
}

/**
 * \brief Configure PLLA as clock input for MCK.
 *
 * \param mul        PLL multiplier factor (not shifted, don't minus 1).
 * \param div        PLL divider factor (not shifted).
 * \param prescaler  Master Clock prescaler (shifted as in register).
 */
void PMC_ConfigureMckWithPlla(uint32_t mul, uint32_t div, uint32_t prescaler)
{
	/* First, select Main OSC as input clock for MCK */
	_PMC_SwitchMck2MainClock();

	/* Then, Set PLLA clock */
	PMC_SetPllaClock(mul, div);

	/* Wait until the master clock is established for the case we already
	    turn on the PLL */
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	/* Finally, select PllA as input clock for MCK */
	PMC_SetMckSelection(PMC_MCKR_CSS_PLLA_CLK, prescaler);
}


/**
 * \brief Configure PLLA as clock input for MCK.
 *
 * \param mul        PLL multiplier factor (not shifted, don't minus 1).
 * \param div        PLL divider factor (not shifted).
 * \param prescaler  Master Clock prescaler (shifted as in register).
 */
void PMC_EnableXT32KFME(void)
{

	uint32_t   read_MOR;

	/* Before switching MAIN OSC on external crystal : enable it and don't
	disable at the same time RC OSC in case of if MAIN OSC is still using
	RC OSC */

	read_MOR = PMC->CKGR_MOR;

	read_MOR |= (CKGR_MOR_KEY_PASSWD | CKGR_MOR_XT32KFME);
	/* enable external crystal - enable RC OSC */

	PMC->CKGR_MOR = read_MOR;

}

/**
 * \brief Configure PLLA as clock input for MCK.
 *
 * \param mul        PLL multiplier factor (not shifted, don't minus 1).
 * \param div        PLL divider factor (not shifted).
 * \param prescaler  Master Clock prescaler (shifted as in register).
 */
void PMC_ConfigurePCK0(uint32_t MasterClk, uint32_t prescaler)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK0;  /* disable PCK */

	while ((PMC->PMC_SCSR)& PMC_SCSR_PCK0);

	PMC->PMC_PCK[0] = MasterClk | prescaler;
	PMC->PMC_SCER = PMC_SCER_PCK0;

	while (!((PMC->PMC_SR) & PMC_SR_PCKRDY0));

}


/**
 * \brief Configure PLLA as clock input for MCK.
 *
 * \param mul        PLL multiplier factor (not shifted, don't minus 1).
 * \param div        PLL divider factor (not shifted).
 * \param prescaler  Master Clock prescaler (shifted as in register).
 */
void PMC_ConfigurePCK1(uint32_t MasterClk, uint32_t prescaler)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK1;  /* disable PCK */

	while ((PMC->PMC_SCSR)& PMC_SCSR_PCK1);

	PMC->PMC_PCK[1] = MasterClk | prescaler;
	PMC->PMC_SCER = PMC_SCER_PCK1;

	while (!((PMC->PMC_SR) & PMC_SR_PCKRDY1));

}

/**
 * \brief Configure PLLA as clock input for MCK.
 *
 * \param mul        PLL multiplier factor (not shifted, don't minus 1).
 * \param div        PLL divider factor (not shifted).
 * \param prescaler  Master Clock prescaler (shifted as in register).
 */
void PMC_ConfigurePCK2(uint32_t MasterClk, uint32_t prescaler)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK2;  /* disable PCK */

	while ((PMC->PMC_SCSR)& PMC_SCSR_PCK2);

	PMC->PMC_PCK[2] = MasterClk | prescaler;
	PMC->PMC_SCER = PMC_SCER_PCK2;

	while (!((PMC->PMC_SR) & PMC_SR_PCKRDY2));

}
