/* ----------------------------------------------------------------------------
 *         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 conditions are 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
 * \section Purpose
 * The PMC driver provides the Interface for configuration the Power Management
 *  Controller (PMC).
 *
 * \section Usage
 * <ul>
 * <li>  Enable & disable peripherals using pmc_enable_peripheral() and
 * pmc_enable_all_peripherals() or pmc_disable_peripheral() and
 * pmc_disable_all_peripherals().
 * <li>  Get & set maximum frequency clock for giving peripheral using
 * pmc_get_peri_max_freq() and pmc_set_peri_max_clock().
 * <li>  Get Peripheral Status for the given peripheral using pmc_is_periph_enabled()
 * <li>  Select clocks's source using pmc_select_external_crystal(),
 * pmc_select_internal_crystal(), pmc_select_external_osc() and pmc_select_internal_osc().
 * <li>  Switch MCK using pmc_switch_mck_to_pll(), pmc_switch_mck_to_main() and
 * pmc_switch_mck_to_slck().
 * <li>  Config PLL using pmc_set_pll_a() and pmc_disable_pll_a().
 * </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
 *
 * Implementation of PIO (Parallel Input/Output) controller.
 *
 */
/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"
#include "board.h"
#include "peripherals/pmc.h"
#include "trace.h"
#include <assert.h>

/*----------------------------------------------------------------------------
 *        Variables
 *----------------------------------------------------------------------------*/

static uint32_t _pmc_mck = 0;

/*----------------------------------------------------------------------------
 *        Private functions
 *----------------------------------------------------------------------------*/

static void _pmc_compute_mck(void)
{
	uint32_t clk = 0;
	uint32_t mckr = PMC->PMC_MCKR;

	uint32_t css = mckr & PMC_MCKR_CSS_Msk;
	switch (css) {
	case PMC_MCKR_CSS_SLOW_CLK:
		clk = pmc_get_slow_clock();
		break;
	case PMC_MCKR_CSS_MAIN_CLK:
		clk = pmc_get_main_clock();
		break;
	case PMC_MCKR_CSS_PLLA_CLK:
		clk = pmc_get_plla_clock();
		break;
	case PMC_MCKR_CSS_UPLL_CLK:
		clk = pmc_get_upll_clock();
		break;
	default:
		/* should never get here... */
		break;
	}

	uint32_t pres = mckr & PMC_MCKR_PRES_Msk;
	switch (pres) {
	case PMC_MCKR_PRES_CLOCK:
		break;
	case PMC_MCKR_PRES_CLOCK_DIV2:
		clk >>= 1;
		break;
	case PMC_MCKR_PRES_CLOCK_DIV4:
		clk >>= 2;
		break;
	case PMC_MCKR_PRES_CLOCK_DIV8:
		clk >>= 3;
		break;
	case PMC_MCKR_PRES_CLOCK_DIV16:
		clk >>= 4;
		break;
	case PMC_MCKR_PRES_CLOCK_DIV32:
		clk >>= 5;
		break;
	case PMC_MCKR_PRES_CLOCK_DIV64:
		clk >>= 6;
		break;
	default:
		/* should never get here... */
		break;
	}

	uint32_t mdiv = mckr & PMC_MCKR_MDIV_Msk;
	switch (mdiv) {
	case PMC_MCKR_MDIV_EQ_PCK:
		break;
	case PMC_MCKR_MDIV_PCK_DIV2:
		clk >>= 1; // divide by 2
		break;
	case PMC_MCKR_MDIV_PCK_DIV4:
		clk >>= 2; // divide by 4
		break;
	case PMC_MCKR_MDIV_PCK_DIV3:
		clk /= 3;  // divide by 3
		break;
	default:
		/* should never get here... */
		break;
	}

	_pmc_mck = clk;
}

static uint32_t _pmc_get_pck_clock(uint32_t index)
{
	uint32_t clk = 0;
	uint32_t pck = PMC->PMC_PCK[index];

	switch (pck & PMC_PCK_CSS_Msk) {
	case PMC_PCK_CSS_SLOW_CLK:
		clk = pmc_get_slow_clock();
		break;
	case PMC_PCK_CSS_MAIN_CLK:
		clk = pmc_get_main_clock();
		break;
	case PMC_PCK_CSS_PLLA_CLK:
		clk = pmc_get_plla_clock();
		break;
	case PMC_PCK_CSS_UPLL_CLK:
		clk = pmc_get_upll_clock();
		break;
	case PMC_PCK_CSS_MCK_CLK:
		clk = pmc_get_master_clock();
		break;
#ifdef CONFIG_HAVE_PMC_AUDIO_CLOCK
	case PMC_PCK_CSS_AUDIO_CLK:
		clk = pmc_get_audio_pmc_clock();
		break;
#endif
	}

	uint32_t prescaler = (pck & PMC_PCK_PRES_Msk) >> PMC_PCK_PRES_Pos;
	return clk / (prescaler + 1);
}

static bool _pmc_get_system_clock_bits(enum _pmc_system_clock clock,
	uint32_t *scer, uint32_t* scdr, uint32_t *scsr)
{
	uint32_t e, d, s;

	switch (clock)
	{
#ifdef PMC_SCDR_PCK
	case PMC_SYSTEM_CLOCK_PCK:
		e = 0;
		d = PMC_SCDR_PCK;
		s = PMC_SCSR_PCK;
		break;
#endif
	case PMC_SYSTEM_CLOCK_DDR:
		e = PMC_SCER_DDRCK;
		d = PMC_SCDR_DDRCK;
		s = PMC_SCSR_DDRCK;
		break;
	case PMC_SYSTEM_CLOCK_LCD:
		e = PMC_SCER_LCDCK;
		d = PMC_SCDR_LCDCK;
		s = PMC_SCSR_LCDCK;
		break;
#ifdef PMC_SCER_SMDCK
	case PMC_SYSTEM_CLOCK_SMD:
		e = PMC_SCER_SMDCK;
		d = PMC_SCDR_SMDCK;
		s = PMC_SCSR_SMDCK;
		break;
#endif
	case PMC_SYSTEM_CLOCK_UHP:
		e = PMC_SCER_UHP;
		d = PMC_SCDR_UHP;
		s = PMC_SCSR_UHP;
		break;
	case PMC_SYSTEM_CLOCK_UDP:
		e = PMC_SCER_UDP;
		d = PMC_SCDR_UDP;
		s = PMC_SCSR_UDP;
		break;
	case PMC_SYSTEM_CLOCK_PCK0:
		e = PMC_SCER_PCK0;
		d = PMC_SCDR_PCK0;
		s = PMC_SCSR_PCK0;
		break;
	case PMC_SYSTEM_CLOCK_PCK1:
		e = PMC_SCER_PCK1;
		d = PMC_SCDR_PCK1;
		s = PMC_SCSR_PCK1;
		break;
	case PMC_SYSTEM_CLOCK_PCK2:
		e = PMC_SCER_PCK2;
		d = PMC_SCDR_PCK2;
		s = PMC_SCSR_PCK2;
		break;
#ifdef PMC_SCER_ISCCK
	case PMC_SYSTEM_CLOCK_ISC:
		e = PMC_SCER_ISCCK;
		d = PMC_SCDR_ISCCK;
		s = PMC_SCSR_ISCCK;
		break;
#endif
	default:
		return false;
	}

	if (scer) {
		if (e)
			*scer = e;
		else
			return false;
	}

	if (scdr) {
		if (d)
			*scdr = d;
		else
			return false;
	}

	if (scsr) {
		if (s)
			*scsr = s;
		else
			return false;
	}

	return true;
}

/*----------------------------------------------------------------------------
 *        Exported functions (General)
 *----------------------------------------------------------------------------*/

uint32_t pmc_get_master_clock(void)
{
	if (!_pmc_mck) {
		_pmc_compute_mck();
	}
	return _pmc_mck;
}

uint32_t pmc_get_slow_clock(void)
{
	if (SCKC->SCKC_CR & SCKC_CR_OSCSEL)
		return SLOW_CLOCK_INT_OSC; /* on-chip slow clock RC */
	else
		return BOARD_SLOW_CLOCK_EXT_OSC; /* external crystal */
}

uint32_t pmc_get_main_clock(void)
{
	if (PMC->CKGR_MOR & CKGR_MOR_MOSCSEL)
		return MAIN_CLOCK_INT_OSC; /* on-chip main clock RC */
	else
		return BOARD_MAIN_CLOCK_EXT_OSC; /* external crystal */
}

uint32_t pmc_get_plla_clock(void)
{
	uint32_t pllaclk, pllar, pllmula, plldiva;

	if (PMC->CKGR_MOR & CKGR_MOR_MOSCSEL)
		pllaclk = MAIN_CLOCK_INT_OSC; /* on-chip main clock RC */
	else
		pllaclk = BOARD_MAIN_CLOCK_EXT_OSC; /* external crystal */

	pllar = PMC->CKGR_PLLAR;
	pllmula = (pllar & CKGR_PLLAR_MULA_Msk) >> CKGR_PLLAR_MULA_Pos;
	plldiva = (pllar & CKGR_PLLAR_DIVA_Msk) >> CKGR_PLLAR_DIVA_Pos;
	if (plldiva == 0 || pllmula == 0) {
		pllaclk = 0;
	} else {
		pllaclk = pllaclk * (pllmula + 1) / plldiva;
		if (PMC->PMC_MCKR & PMC_MCKR_PLLADIV2)
			pllaclk >>= 1;
	}

	return pllaclk;
}

uint32_t pmc_get_processor_clock(void)
{
	uint32_t procclk, mdiv;

	procclk = pmc_get_master_clock();

	mdiv = PMC->PMC_MCKR & PMC_MCKR_MDIV_Msk;
	switch (mdiv) {
	case PMC_MCKR_MDIV_EQ_PCK:
		break;
	case PMC_MCKR_MDIV_PCK_DIV2:
		procclk <<= 1; // multiply by 2
		break;
	case PMC_MCKR_MDIV_PCK_DIV3:
		procclk *= 3;  // multiply by 3
		break;
	case PMC_MCKR_MDIV_PCK_DIV4:
		procclk <<= 2; // multiply by 4
		break;
	default:
		/* should never get here... */
		break;
	}

	return procclk;
}

void pmc_select_external_crystal(void)
{
	int return_to_sclock = 0;

	if (PMC->PMC_MCKR == PMC_MCKR_CSS(PMC_MCKR_CSS_SLOW_CLK)) {
		pmc_switch_mck_to_main();
		return_to_sclock = 1;
	}

	/* switch from internal RC 32kHz to external OSC 32 kHz */
	SCKC->SCKC_CR = (SCKC->SCKC_CR & ~SCKC_CR_OSCSEL) | SCKC_CR_OSCSEL_XTAL;

	/* Wait 5 slow clock cycles for internal resynchronization */
	volatile int count;
	for (count = 0; count < 0x1000; count++);

	/* Switch to slow clock again if needed */
	if (return_to_sclock)
		pmc_switch_mck_to_slck();
}

void pmc_select_internal_crystal(void)
{
	int return_to_sclock = 0;

	if (PMC->PMC_MCKR == PMC_MCKR_CSS(PMC_MCKR_CSS_SLOW_CLK)) {
		pmc_switch_mck_to_main();
		return_to_sclock = 1;
	}

	/* switch from extenal OSC 32kHz to internal RC 32 kHz */
	/* switch slow clock source to internal OSC 32 kHz */
	SCKC->SCKC_CR = (SCKC->SCKC_CR & ~SCKC_CR_OSCSEL) | SCKC_CR_OSCSEL_RC;

	/* Wait 5 slow clock cycles for internal resynchronization */
	volatile int count;
	for (count = 0; count < 0x1000; count++);

	/* Switch to slow clock again if needed */
	if (return_to_sclock)
		pmc_switch_mck_to_slck();
}

void pmc_select_external_osc(void)
{
	/* Enable external osc 12 MHz when needed */
	if ((PMC->CKGR_MOR & CKGR_MOR_MOSCXTEN) != CKGR_MOR_MOSCXTEN) {
		PMC->CKGR_MOR |= CKGR_MOR_MOSCXTST(18) | CKGR_MOR_MOSCXTEN | CKGR_MOR_KEY_PASSWD;
		/* Wait Main Oscillator ready */
		while(!(PMC->PMC_SR & PMC_SR_MOSCXTS));
	}

	/* Return if external osc had been selected */
	if ((PMC->CKGR_MOR & CKGR_MOR_MOSCSEL) == CKGR_MOR_MOSCSEL)
		return;

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

	/* wait for the command to be taken into account */
	while ((PMC->CKGR_MOR & CKGR_MOR_MOSCSEL) != CKGR_MOR_MOSCSEL);

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

	/* disable internal RC 12 MHz to save power */
	pmc_disable_internal_osc();
}

void pmc_disable_external_osc(void)
{
	/* disable external OSC 12 MHz   */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCXTEN) | CKGR_MOR_KEY_PASSWD;
}

void pmc_select_internal_osc(void)
{
#ifdef CKGR_MOR_MOSCRCEN
	/* Enable internal RC 12 MHz when needed */
	if ((PMC->CKGR_MOR & CKGR_MOR_MOSCRCEN) != CKGR_MOR_MOSCRCEN) {
		PMC->CKGR_MOR |= CKGR_MOR_MOSCRCEN | CKGR_MOR_KEY_PASSWD;
		/* Wait internal 12 MHz RC Startup Time for clock stabilization */
		while (!(PMC->PMC_SR & PMC_SR_MOSCRCS));
	}
#endif

	/* switch MAIN clock to internal RC 12 MHz */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCSEL) | CKGR_MOR_KEY_PASSWD;

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

	/* disable external OSC 12 MHz to save power*/
	pmc_disable_external_osc();
}

void pmc_disable_internal_osc(void)
{
#ifdef CKGR_MOR_MOSCRCEN
	/* disable internal RC 12 MHz   */
	PMC->CKGR_MOR = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCEN) | CKGR_MOR_KEY_PASSWD;
#endif
}

void pmc_switch_mck_to_pll(void)
{
	/* Select PLL as input clock for PCK and MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_PLLA_CLK;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	_pmc_mck = 0;
}

void pmc_switch_mck_to_upll(void)
{
	/* Select UPLL as input clock for PCK and MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS_UPLL_CLK;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	_pmc_mck = 0;
}

void pmc_switch_mck_to_main(void)
{
	/* Select Main Oscillator as input clock for PCK and MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_PCK_CSS_MAIN_CLK;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	_pmc_mck = 0;
}

void pmc_switch_mck_to_slck(void)
{
	/* Select Slow Clock as input clock for PCK and MCK */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_PCK_CSS_SLOW_CLK;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

	_pmc_mck = 0;
}

void pmc_set_mck_prescaler(uint32_t prescaler)
{
	/* Change MCK Prescaler divider in PMC_MCKR register */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | prescaler;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

void pmc_set_mck_plla_div(uint32_t divider)
{
	if ((PMC->PMC_MCKR & PMC_MCKR_PLLADIV2) == PMC_MCKR_PLLADIV2) {
		if (divider == 0) {
			PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PLLADIV2);
		}
	} else {
		if (divider == PMC_MCKR_PLLADIV2) {
			PMC->PMC_MCKR = (PMC->PMC_MCKR | PMC_MCKR_PLLADIV2);
		}
	}
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

void pmc_set_mck_h32mxdiv(uint32_t divider)
{
	if ((PMC->PMC_MCKR & PMC_MCKR_H32MXDIV) == PMC_MCKR_H32MXDIV_H32MXDIV2) {
		if (divider == PMC_MCKR_H32MXDIV_H32MXDIV1) {
			PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_H32MXDIV);
		}
	} else {
		if (divider == PMC_MCKR_H32MXDIV_H32MXDIV2) {
			PMC->PMC_MCKR = (PMC->PMC_MCKR | PMC_MCKR_H32MXDIV_H32MXDIV2);
		}
	}
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

void pmc_set_mck_divider(uint32_t divider)
{
	/* change MCK Prescaler divider in PMC_MCKR register */
	PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_MDIV_Msk) | divider;
	while (!(PMC->PMC_SR & PMC_SR_MCKRDY));
}

void pmc_set_plla(uint32_t pll, uint32_t cpcr)
{
	PMC->CKGR_PLLAR = pll;
	PMC->PMC_PLLICPR = cpcr;

	if ((pll & CKGR_PLLAR_DIVA_Msk) != CKGR_PLLAR_DIVA_0) {
		while (!(PMC->PMC_SR & PMC_SR_LOCKA));
	}
}

void pmc_disable_plla(void)
{
	PMC->CKGR_PLLAR = (PMC->CKGR_PLLAR & ~CKGR_PLLAR_MULA_Msk) | CKGR_PLLAR_MULA(0);
}

void pmc_enable_system_clock(enum _pmc_system_clock clock)
{
	uint32_t scer, scsr;
	if (!_pmc_get_system_clock_bits(clock, &scer, NULL, &scsr))
		return;

	PMC->PMC_SCER |= scer;
	while (!(PMC->PMC_SCSR & scsr));
}

void pmc_disable_system_clock(enum _pmc_system_clock clock)
{
	uint32_t scdr, scsr;
	if (!_pmc_get_system_clock_bits(clock, NULL, &scdr, &scsr))
		return;

	PMC->PMC_SCDR |= scdr;
	while (PMC->PMC_SCSR & scsr);
}

#ifdef CONFIG_HAVE_PMC_FAST_STARTUP
void pmc_set_fast_startup_mode(uint32_t startup_mode)
{
	PMC->PMC_FSMR = startup_mode;
}

void pmc_set_fast_startup_polarity(uint32_t high_level, uint32_t low_level)
{
	PMC->PMC_FSPR &= ~low_level;
	PMC->PMC_FSPR |= high_level;
}
#endif /* CONFIG_HAVE_PMC_FAST_STARTUP */

void pmc_set_custom_pck_mck(struct pck_mck_cfg *cfg)
{
	pmc_switch_mck_to_slck();

	if (cfg->ext12m)
		pmc_select_external_osc();
	else
		pmc_select_internal_osc();

	pmc_switch_mck_to_main();

	if (cfg->ext32k)
		pmc_select_external_crystal();
	else
		pmc_select_internal_crystal();

	pmc_set_mck_prescaler(cfg->pck_pres);
	pmc_set_mck_divider(cfg->mck_div);

	pmc_set_mck_plla_div(cfg->plla_div2 ? PMC_MCKR_PLLADIV2 : 0);
	if (cfg->plla_mul > 0) {
		pmc_disable_plla();
		uint32_t tmp = CKGR_PLLAR_ONE |
			CKGR_PLLAR_PLLACOUNT(0x3F) |
			CKGR_PLLAR_OUTA(0x0) |
			CKGR_PLLAR_MULA(cfg->plla_mul) |
			CKGR_PLLAR_DIVA(cfg->plla_div);
		pmc_set_plla(tmp, PMC_PLLICPR_IPLL_PLLA(0x3));
	} else {
		pmc_disable_plla();
	}

	if (cfg->h32mxdiv2)
		pmc_set_mck_h32mxdiv(PMC_MCKR_H32MXDIV_H32MXDIV2);
	else
		pmc_set_mck_h32mxdiv(PMC_MCKR_H32MXDIV_H32MXDIV1);

	switch (cfg->pck_input) {
	case PMC_MCKR_CSS_PLLA_CLK:
		pmc_switch_mck_to_pll();
		break;

	case PMC_MCKR_CSS_UPLL_CLK:
		pmc_switch_mck_to_upll();
		break;

	case PMC_MCKR_CSS_SLOW_CLK:
		pmc_switch_mck_to_slck();
		pmc_disable_internal_osc();
		pmc_disable_external_osc();
		break;
	}
}

/*----------------------------------------------------------------------------
 *        Exported functions (Peripherals)
 *----------------------------------------------------------------------------*/

void pmc_enable_peripheral(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;

	PMC->PMC_PCR = pcr | PMC_PCR_CMD | PMC_PCR_EN;
}

void pmc_disable_peripheral(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;

	PMC->PMC_PCR = PMC_PCR_CMD | (pcr & ~PMC_PCR_EN);
}

uint32_t pmc_is_peripheral_enabled(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;

	return !!(pcr & PMC_PCR_EN);
}

uint32_t pmc_get_peripheral_clock(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	uint32_t div = get_peripheral_clock_divider(id);
	if (div)
		return pmc_get_master_clock() / div;

	return 0;
}

void pmc_disable_all_peripherals(void)
{
	int i;
	for (i = 2; i < ID_PERIPH_COUNT; i++)
		pmc_disable_peripheral(i);
}

/*----------------------------------------------------------------------------
 *        Exported functions (PCK0-2)
 *----------------------------------------------------------------------------*/

void pmc_configure_pck0(uint32_t clock_source, uint32_t prescaler)
{
	pmc_disable_pck0();
	PMC->PMC_PCK[0] = (clock_source & PMC_PCK_CSS_Msk) | PMC_PCK_PRES(prescaler);
}

void pmc_enable_pck0(void)
{
	PMC->PMC_SCER = PMC_SCER_PCK0;
	while (!(PMC->PMC_SR & PMC_SR_PCKRDY0));
}

void pmc_disable_pck0(void)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK0;
	while (PMC->PMC_SCSR & PMC_SCSR_PCK0);
}

uint32_t pmc_get_pck0_clock(void)
{
	return _pmc_get_pck_clock(0);
}

void pmc_configure_pck1(uint32_t clock_source, uint32_t prescaler)
{
	pmc_disable_pck1();
	PMC->PMC_PCK[1] = (clock_source & PMC_PCK_CSS_Msk) | PMC_PCK_PRES(prescaler);
}

void pmc_enable_pck1(void)
{
	PMC->PMC_SCER = PMC_SCER_PCK1;
	while (!(PMC->PMC_SR & PMC_SR_PCKRDY1));
}

void pmc_disable_pck1(void)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK1;
	while (PMC->PMC_SCSR & PMC_SCSR_PCK1);
}

uint32_t pmc_get_pck1_clock(void)
{
	return _pmc_get_pck_clock(1);
}

void pmc_configure_pck2(uint32_t clock_source, uint32_t prescaler)
{
	pmc_disable_pck2();
	PMC->PMC_PCK[2] = (clock_source & PMC_PCK_CSS_Msk) | PMC_PCK_PRES(prescaler);
}

void pmc_enable_pck2(void)
{
	PMC->PMC_SCER = PMC_SCER_PCK2;
	while (!(PMC->PMC_SR & PMC_SR_PCKRDY2));
}

void pmc_disable_pck2(void)
{
	PMC->PMC_SCDR = PMC_SCDR_PCK2;
	while (PMC->PMC_SCSR & PMC_SCSR_PCK2);
}

uint32_t pmc_get_pck2_clock(void)
{
	return _pmc_get_pck_clock(2);
}

/*----------------------------------------------------------------------------
 *        Exported functions (UPLL)
 *----------------------------------------------------------------------------*/

void pmc_enable_upll_clock(void)
{
	/* enable 480Mhz UPLL */
	PMC->CKGR_UCKR = CKGR_UCKR_UPLLEN | CKGR_UCKR_UPLLCOUNT(0x3)
		| CKGR_UCKR_BIASCOUNT(0x1);

	/* wait until UPLL is locked */
	while (!(PMC->PMC_SR & PMC_SR_LOCKU));
}

void pmc_disable_upll_clock(void)
{
	PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN;
}

uint32_t pmc_get_upll_clock(void)
{
#ifdef SFR_UTMICKTRIM_FREQ_Msk
	uint32_t clktrim = SFR->SFR_UTMICKTRIM & SFR_UTMICKTRIM_FREQ_Msk;
	switch (clktrim) {
		case SFR_UTMICKTRIM_FREQ_16:
			return 30 * BOARD_MAIN_CLOCK_EXT_OSC;
		case SFR_UTMICKTRIM_FREQ_24:
			return 20 * BOARD_MAIN_CLOCK_EXT_OSC;
		default:
			return 40 * BOARD_MAIN_CLOCK_EXT_OSC;
	}
#else
	return 40 * BOARD_MAIN_CLOCK_EXT_OSC;
#endif
}

void pmc_enable_upll_bias(void)
{
	PMC->CKGR_UCKR |= CKGR_UCKR_BIASEN;
}

void pmc_disable_upll_bias(void)
{
	PMC->CKGR_UCKR &= ~CKGR_UCKR_BIASEN;
}

/*----------------------------------------------------------------------------
 *        Exported functions (Generated clocks)
 *----------------------------------------------------------------------------*/

#ifdef CONFIG_HAVE_PMC_GENERATED_CLOCKS
void pmc_configure_gck(uint32_t id, uint32_t clock_source, uint32_t div)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);
	assert(!(clock_source & ~PMC_PCR_GCKCSS_Msk));
	assert(!(div << PMC_PCR_GCKDIV_Pos & ~PMC_PCR_GCKDIV_Msk));

	pmc_disable_gck(id);
	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;
	PMC->PMC_PCR = pcr | (clock_source & PMC_PCR_GCKCSS_Msk) | PMC_PCR_CMD
	    | PMC_PCR_GCKDIV(div);
}

void pmc_enable_gck(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;
	PMC->PMC_PCR = pcr | PMC_PCR_CMD | PMC_PCR_GCKEN;
	while (!(PMC->PMC_SR & PMC_SR_GCKRDY));
}

void pmc_disable_gck(uint32_t id)
{
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;
	PMC->PMC_PCR = PMC_PCR_CMD | (pcr & ~PMC_PCR_GCKEN);
}

uint32_t pmc_get_gck_clock(uint32_t id)
{
	uint32_t clk = 0;
	assert(id > 1 && id < ID_PERIPH_COUNT);

	PMC->PMC_PCR = PMC_PCR_PID(id);
	volatile uint32_t pcr = PMC->PMC_PCR;

	switch (pcr & PMC_PCR_GCKCSS_Msk) {
	case PMC_PCR_GCKCSS_SLOW_CLK:
		clk = pmc_get_slow_clock();
		break;
	case PMC_PCR_GCKCSS_MAIN_CLK:
		clk = pmc_get_main_clock();
		break;
	case PMC_PCR_GCKCSS_PLLA_CLK:
		clk = pmc_get_plla_clock();
		break;
	case PMC_PCR_GCKCSS_UPLL_CLK:
		clk = pmc_get_upll_clock();
		break;
	case PMC_PCR_GCKCSS_MCK_CLK:
		clk = pmc_get_master_clock();
		break;
#ifdef CONFIG_HAVE_PMC_AUDIO_CLOCK
	case PMC_PCR_GCKCSS_AUDIO_CLK:
		clk = pmc_get_audio_pmc_clock();
		break;
#endif
	}

	uint32_t div = (pcr & PMC_PCR_GCKDIV_Msk) >> PMC_PCR_GCKDIV_Pos;
	return ROUND_INT_DIV(clk, div + 1);
}
#endif /* CONFIG_HAVE_PMC_GENERATED_CLOCKS */

/*----------------------------------------------------------------------------
 *        Exported functions (Audio PLL)
 *----------------------------------------------------------------------------*/

#ifdef CONFIG_HAVE_PMC_AUDIO_CLOCK
void pmc_configure_audio(struct _pmc_audio_cfg *cfg)
{
	/* reset audio clock */
	PMC->PMC_AUDIO_PLL0 &= ~PMC_AUDIO_PLL0_RESETN;
	PMC->PMC_AUDIO_PLL0 |= PMC_AUDIO_PLL0_RESETN;

	/* configure values */
	uint32_t pll0 = PMC->PMC_AUDIO_PLL0;
	pll0 &= ~PMC_AUDIO_PLL0_ND_Msk;
	pll0 |= cfg->nd << PMC_AUDIO_PLL0_ND_Pos;
	pll0 &= ~PMC_AUDIO_PLL0_QDPMC_Msk;
	pll0 |= cfg->qdpmc << PMC_AUDIO_PLL0_QDPMC_Pos;
	PMC->PMC_AUDIO_PLL0 = pll0;
	uint32_t pll1 = PMC->PMC_AUDIO_PLL1;
	pll1 &= ~PMC_AUDIO_PLL1_DIV_Msk;
	pll1 |= cfg->div << PMC_AUDIO_PLL1_DIV_Pos;
	pll1 &= ~PMC_AUDIO_PLL1_FRACR_Msk;
	pll1 |= cfg->fracr << PMC_AUDIO_PLL1_FRACR_Pos;
	pll1 &= ~PMC_AUDIO_PLL1_QDAUDIO_Msk;
	pll1 |= cfg->qdaudio << PMC_AUDIO_PLL1_QDAUDIO_Pos;
	PMC->PMC_AUDIO_PLL1 = pll1;
}

void pmc_enable_audio(bool pmc_clock, bool pad_clock)
{
	uint32_t bits = PMC_AUDIO_PLL0_PLLEN | PMC_AUDIO_PLL0_RESETN;
	uint32_t nbits = 0;

	if (pad_clock)
		bits |= PMC_AUDIO_PLL0_PADEN;
	else
		nbits |= PMC_AUDIO_PLL0_PADEN;

	if (pmc_clock)
		bits |= PMC_AUDIO_PLL0_PMCEN;
	else
		nbits |= PMC_AUDIO_PLL0_PMCEN;

	PMC->PMC_AUDIO_PLL0 = (PMC->PMC_AUDIO_PLL0 & ~nbits) | bits;
}

void pmc_disable_audio()
{
	uint32_t nbits = PMC_AUDIO_PLL0_PLLEN | PMC_AUDIO_PLL0_RESETN |
		PMC_AUDIO_PLL0_PADEN | PMC_AUDIO_PLL0_PMCEN;
	PMC->PMC_AUDIO_PLL0 &= ~nbits;
}

uint32_t pmc_get_audio_pmc_clock(void)
{
	uint32_t pll0 = PMC->PMC_AUDIO_PLL0;
	uint32_t pll1 = PMC->PMC_AUDIO_PLL1;

	uint32_t nd = (pll0 & PMC_AUDIO_PLL0_ND_Msk) >> PMC_AUDIO_PLL0_ND_Pos;
	uint32_t fracr = (pll1 & PMC_AUDIO_PLL1_FRACR_Msk) >> PMC_AUDIO_PLL1_FRACR_Pos;
	uint32_t qdpmc = (pll0 & PMC_AUDIO_PLL0_QDPMC_Msk) >> PMC_AUDIO_PLL0_QDPMC_Pos;

	uint64_t clk = BOARD_MAIN_CLOCK_EXT_OSC;
	clk *= ((nd + 1) << 22) + fracr;
	clk /= 1 << 22;
	clk /= qdpmc + 1;
	return (uint32_t)clk;
}

uint32_t pmc_get_audio_pad_clock(void)
{
	uint32_t pll0 = PMC->PMC_AUDIO_PLL0;
	uint32_t pll1 = PMC->PMC_AUDIO_PLL1;

	uint32_t nd = (pll0 & PMC_AUDIO_PLL0_ND_Msk) >> PMC_AUDIO_PLL0_ND_Pos;
	uint32_t fracr = (pll1 & PMC_AUDIO_PLL1_FRACR_Msk) >> PMC_AUDIO_PLL1_FRACR_Pos;
	uint32_t qdaudio = (pll1 & PMC_AUDIO_PLL1_QDAUDIO_Msk) >> PMC_AUDIO_PLL1_QDAUDIO_Pos;
	if (qdaudio == 0)
		return 0;

	uint32_t div = (pll1 & PMC_AUDIO_PLL1_DIV_Msk) >> PMC_AUDIO_PLL1_DIV_Pos;
	if (div != 2 && div != 3)
		return 0;

	uint64_t clk = BOARD_MAIN_CLOCK_EXT_OSC;
	clk *= ((nd + 1) << 22) + fracr;
	clk /= 1 << 22;
	clk /= div * qdaudio;
	return (uint32_t)clk;
}
#endif /* CONFIG_HAVE_PMC_AUDIO_CLOCK */

