/**
 * \file
 *
 * \brief Power Manager chip-independent support
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  All devices with a PM v2 module
 * - AppNote:
 *
 * \author               Atmel Corporation: http://www.atmel.com \n
 *                       Support and FAQ: http://support.atmel.no/
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * 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
 * EXPRESSLY AND SPECIFICALLY 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.
 */
#include <assert.h>
#include <interrupt.h>
#include <io.h>
#include <status-codes.h>
#include <timeout.h>
#include <chip/clk.h>

#ifndef CONFIG_PLL_STARTUP_CYCLES
# define CONFIG_PLL_STARTUP_CYCLES	63
#endif

#define PLL_TIMEOUT_MS						\
	div_ceil(1000 * (CONFIG_PLL_STARTUP_CYCLES * 2),	\
			PM_RCOSC_MIN_RATE)

static int8_t osc_count[PM_NR_OSCS];
static int8_t pll_count[PM_NR_PLLS];

#if !defined(CONFIG_CLK_OSC0_MAIN)
unsigned long main_clock_rate = BOARD_OSC0_HZ;

/**
 * \brief Set the source for the main clock to \a source, which is running at
 * \a rate Hz.
 */
void pm_priv_set_main_clock(enum pm_mainclk_src source,
		unsigned long rate)
{
	uint32_t	mcctrl;
	unsigned long	iflags;

	assert(!(PM_MCCTRL_MCSEL(source) & ~PM_MCCTRL_MCSEL_MASK));

	iflags = cpu_irq_save();
	mcctrl = pm_read_reg(MCCTRL);
	mcctrl &= ~PM_MCCTRL_MCSEL_MASK;
	mcctrl |= PM_MCCTRL_MCSEL(source);
	pm_write_reg(MCCTRL, mcctrl);
	pm_read_reg(MCCTRL);

	main_clock_rate = rate;
	cpu_irq_restore(iflags);
}
#endif

int pm_enable_pll_sync(unsigned int id)
{
	struct timeout	timeout;

	assert(id < ARRAY_LEN(pll_count));
	assert(cpu_irq_is_enabled());

	/* Enable the PLL if it hasn't been enabled already */
	cpu_irq_disable();
	assert(pll_count[id] >= 0);
	if (pll_count[id]++ == 0) {
		uint32_t	pllctrl;

		pllctrl = pm_read_reg(PLL(id));
		pllctrl |= PM_PLL_PLLEN;
		pm_write_reg(PLL(id), pllctrl);
	}
	cpu_irq_enable();

	/* Return immediately if it's already running */
	if (pm_read_reg(POSCSR) & PM_LOCK(id))
		return STATUS_OK;

	/* Wait for it to lock */
	timeout_init_ms(&timeout, PLL_TIMEOUT_MS);
	do {
		if (pm_read_reg(POSCSR) & PM_LOCK(id))
			return STATUS_OK;
	} while (!timeout_has_expired(&timeout));

	return -STATUS_TIMEOUT;
}

unsigned long pm_init_pll(unsigned int id, enum pm_pll_src source,
		unsigned int div, unsigned int mul,
		unsigned long flags)
{
	uint32_t	pllctrl;
	unsigned long	rate;

	/* Sanity checks */
	assert(id < PM_NR_PLLS);
	assert(source < 2);
	assert(div > 0 && div < 256);
	assert(mul > 1 && mul < 256);
	assert(!(pm_read_reg(PLL(id)) & PM_PLL_PLLEN));

	/* Calculate the target rate */
	rate = (pm_priv_get_pll_src_rate(source) + div / 2) / div;
	rate *= mul;

	/* Configure the PLL */
	pllctrl = PM_PLL_PLLOSC(source);
	pllctrl |= flags;
	if (PM_VERSION < PM_MKVERSION(3, 0, 0))
		mul--;
	if (PM_VERSION < PM_MKVERSION(2, 0, 0))
		div--;

	pllctrl |= PM_PLL_PLLDIV(div);
	pllctrl |= PM_PLL_PLLMUL(mul);
	pllctrl |= PM_PLL_PLLCOUNT(CONFIG_PLL_STARTUP_CYCLES);
	pllctrl |= pm_priv_pll_rate_opt(rate);
	pm_write_reg(PLL(id), pllctrl);

	if (flags & PM_PLL_OUTPUT_DIV_BY_2)
		rate = (rate + 1) / 2;

	return rate;
}

unsigned long pm_get_pll_rate(unsigned int id)
{
	uint32_t	pllctrl;
	unsigned long	rate;
	unsigned int	source;
	unsigned int	mul;
	unsigned int	div;

	pllctrl = pm_read_reg(PLL(id));
	if (!(pllctrl & PM_PLL_PLLEN))
		return 0;

	source = PM_PLL_GET_PLLOSC(pllctrl);
	mul = PM_PLL_GET_PLLMUL(pllctrl);
	div = PM_PLL_GET_PLLDIV(pllctrl);

	if (PM_VERSION < PM_MKVERSION(2, 0, 0))
		div++;
	if (PM_VERSION < PM_MKVERSION(3, 0, 0))
		mul++;

	rate = (pm_priv_get_pll_src_rate(source) + div / 2) / div;
	rate *= mul;
	if (pllctrl & PM_PLL_OUTPUT_DIV_BY_2)
		rate = (rate + 1) / 2;

	return rate;
}

int pm_priv_enable_osc_sync(unsigned int index, unsigned long startup_us)
{
	struct timeout	timeout;

	assert(index < ARRAY_LEN(osc_count));
	assert(cpu_irq_is_enabled());

	/* Enable the oscillator if it hasn't been enabled already */
	cpu_irq_disable();
	assert(osc_count[index] >= 0);
	if (osc_count[index]++ == 0) {
		uint32_t	mcctrl;

		mcctrl = pm_read_reg(MCCTRL);
		mcctrl |= PM_MCCTRL_OSCEN(index);
		pm_write_reg(MCCTRL, mcctrl);
	}
	cpu_irq_enable();

	/* Return immediately if it's already running */
	if (pm_read_reg(POSCSR) & PM_OSCRDY(index))
		return STATUS_OK;

	/* Otherwise, wait for it to start */
	timeout_init_us(&timeout, startup_us * 2 + 100);
	do {
		if (pm_read_reg(POSCSR) & PM_OSCRDY(index))
			return STATUS_OK;
	} while (!timeout_has_expired(&timeout));

	return -STATUS_TIMEOUT;
}
