/**
 * \file
 *
 * \brief AT32UC3A3 clock control
 *
 * - Compiler:           IAR EWAVR32 and GNU GCC for AVR32
 * - Supported devices:  AT32UC3A3
 * - 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.
 */
#ifndef CHIP_CLK_H_INCLUDED
#define CHIP_CLK_H_INCLUDED

#include <app/clk-config.h>
#include <board/clk.h>
#include <flash/flashc.h>

/* FIXME: Not documented */
#define PM_RCOSC_MIN_RATE	100000
#define PM_RCOSC_MAX_RATE	120000

#define PM_NR_OSCS		2
#define PM_NR_PLLS		2

/**
 * \brief Main clock source.
 */
enum pm_mainclk_src {
	PM_MAINCLK_SRC_RCOSC	= 0,
	PM_MAINCLK_SRC_OSC0	= 1,
	PM_MAINCLK_SRC_PLL0	= 2,
};

/**
 * \brief PLL clock source.
 */
enum pm_pll_src {
	PM_PLL_SRC_OSC0		= 0,
	PM_PLL_SRC_OSC1		= 1,
};

/**
 * \brief Generic clock source.
 *
 * Each generic clock must be generated from a different clock. These
 * are the available alternatives provided by the chip.
 */
enum pm_genclk_src {
	PM_GENCLK_SRC_OSC0	= 0,
	PM_GENCLK_SRC_OSC1	= 1,
	PM_GENCLK_SRC_PLL0	= 2,
	PM_GENCLK_SRC_PLL1	= 3,
};

#include <pm/pm_v2.h>

/**
 * \internal
 * \brief Get the OSCCTRL startup value corresponding to a given
 * number of microseconds.
 *
 * \param us The startup time in microseconds
 * \return The value to write to the STARTUP field in OSCCTRL.
 */
static inline uint32_t pm_priv_osc_startup(unsigned long us)
{
	if (us == 0)
		return PM_OSC_STARTUP_0;
	if (us <= 560)
		return PM_OSC_STARTUP_64;
	if (us <= 1100)
		return PM_OSC_STARTUP_128;
	if (us <= 18000)
		return PM_OSC_STARTUP_2048;
	if (us <= 36000)
		return PM_OSC_STARTUP_4096;
	if (us <= 71000)
		return PM_OSC_STARTUP_8192;

	build_assert(is_constant(us) && us <= 142000);

	return PM_OSC_STARTUP_16384;
}

static inline unsigned long pm_priv_get_pll_src_rate(enum pm_pll_src src)
{
	switch (src) {
#ifdef BOARD_OSC0_HZ
	case PM_PLL_SRC_OSC0:
		return BOARD_OSC0_HZ;
#endif
#ifdef BOARD_OSC1_HZ
	case PM_PLL_SRC_OSC1:
		return BOARD_OSC1_HZ;
#endif
	default:
		unhandled_case(src);
		return 0;
	}
}

static inline uint32_t pm_priv_pll_rate_opt(unsigned long rate)
{
	if (rate < 170000000)
		return PM_PLL_VCO_MHZ_80_180;
	else
		return PM_PLL_VCO_MHZ_160_240;
}

#if defined(CONFIG_CLK_OSC0_MAIN)
static inline unsigned long get_main_clock_rate(void)
{
	return BOARD_OSC0_HZ;
}
#else
/** \brief The main clock rate in Hz, if not constant */
extern unsigned long main_clock_rate;

static inline void pm_set_main_clock(enum pm_mainclk_src source)
{
	unsigned long old_rate;
	unsigned long new_rate;

	switch (source) {
	case PM_MAINCLK_SRC_RCOSC:
		new_rate = PM_RCOSC_MAX_RATE;
		break;
	case PM_MAINCLK_SRC_OSC0:
		new_rate = BOARD_OSC0_HZ;
		break;
	case PM_MAINCLK_SRC_PLL0:
		new_rate = pm_get_pll_rate(0);
		break;
	}

	old_rate = main_clock_rate;
	if (new_rate > old_rate)
		flashc_adjust_wait_state(new_rate >> CONFIG_CLK_HSB_SHIFT);

	pm_priv_set_main_clock(source, new_rate);

	if (new_rate < old_rate)
		flashc_adjust_wait_state(new_rate >> CONFIG_CLK_HSB_SHIFT);
}

/**
 * \brief Get the main synchronous clock rate in Hz.
 *
 * The main clock is the base clock from which the CPU clock and all
 * the bus clocks are derived.
 *
 * \return The main clock rate in Hz
 */
static inline unsigned long get_main_clock_rate(void)
{
	return main_clock_rate;
}
#endif

#if defined(CONFIG_CLK_FIXED_DIVIDERS)
static inline unsigned long get_cpu_clock_rate(void)
{
	return get_main_clock_rate() >> CONFIG_CLK_CPU_SHIFT;
}

static inline unsigned long get_hsb_clock_rate(void)
{
	return get_main_clock_rate() >> CONFIG_CLK_HSB_SHIFT;
}

static inline unsigned long get_pba_clock_rate(void)
{
	return get_main_clock_rate() >> CONFIG_CLK_PBA_SHIFT;
}

static inline unsigned long get_pbb_clock_rate(void)
{
	return get_main_clock_rate() >> CONFIG_CLK_PBB_SHIFT;
}
#else
extern unsigned long get_sync_clock_rate(unsigned int shift);

/** \brief Get the CPU clock rate in Hz */
static inline unsigned long get_cpu_clock_rate(void)
{
	return get_sync_clock_rate(0);
}

/** \brief Get the HSB clock rate in Hz */
static inline unsigned long get_hsb_clock_rate(void)
{
	return get_sync_clock_rate(8);
}

/** \brief Get the PBA clock rate in Hz */
static inline unsigned long get_pba_clock_rate(void)
{
	return get_sync_clock_rate(16);
}

/** \brief Get the PBB clock rate in Hz */
static inline unsigned long get_pbb_clock_rate(void)
{
	return get_sync_clock_rate(24);
}
#endif

/** \brief Get the rate of the bus clock connected to the SMC */
static inline unsigned long get_smc_hclk_rate(void)
{
	return get_hsb_clock_rate();
}

/**
 * \brief Get the rate of the bus clock connected to a USART
 *
 * \param id The ID of the USART of which to query the bus clock
 * rate.
 * \return The USART bus clock rate in Hz.
 */
static inline unsigned long get_usart_pclk_rate(unsigned int id)
{
	return get_pbb_clock_rate();
}

/**
 * \brief Get the rate of the bus clock connected to a SPI controller
 *
 * \param id The ID of the SPI controller of which to query the bus
 * clock rate.
 * \return The SPI bus clock rate in Hz.
 */
static inline unsigned long get_spi_pclk_rate(unsigned int id)
{
	return get_pbb_clock_rate();
}

/**
 * \brief Get the rate of the bus clock connected to a MMC controller
 *
 * \param id The ID of the MMC controller of which to query the bus
 * clock rate.
 * \return The MMC bus clock rate in Hz.
 */
static inline unsigned long get_mci_pclk_rate(unsigned int id)
{
	return get_pba_clock_rate();
}

/**
 * \brief Enable an on-chip oscillator.
 *
 * \param id The ID of the oscillator to enable (0 or 1).
 *
 * \retval STATUS_OK The oscillator was successfully started
 * \retval -STATUS_TIMEOUT Timed out waiting for the oscillator to start
 */
static inline void pm_init_osc(unsigned int id)
{
	uint32_t	ctrl;

	switch (id) {
	case 0:
		assert(!(pm_read_reg(MCCTRL) & PM_MCCTRL_OSCEN(0)));
		if (BOARD_OSC0_XTAL)
			ctrl = PM_OSC_MODE_XTAL;
		else
			ctrl = PM_OSC_MODE_EXT;
		ctrl |= pm_priv_osc_startup(BOARD_OSC0_STARTUP_US);
		pm_write_reg(OSCCTRL(0), ctrl);
		break;

#ifdef BOARD_OSC1_HZ
	case 1:
		assert(!(pm_read_reg(MCCTRL) & PM_MCCTRL_OSCEN(1)));
		if (BOARD_OSC1_XTAL)
			ctrl = PM_OSC_MODE_XTAL;
		else
			ctrl = PM_OSC_MODE_EXT;
		ctrl |= pm_priv_osc_startup(BOARD_OSC1_STARTUP_US);
		pm_write_reg(OSCCTRL(1), ctrl);
		break;
#endif
	default:
		unhandled_case(id);
		break;
	}
}

/**
 * \brief Enable the USBB clock.
 *
 * \return 0 if successful, or a negative error code otherwise.
 *
 * \todo We should probably distinguish between the UTMI clock and the
 * peripheral bus/HSB clocks.
 */
static inline int clk_enable_usbb(void)
{
	/* USBB needs a 12 MHz clock from generic clock 4 */
#if (BOARD_OSC0_HZ == 12000000)
	return pm_enable_genclk(4, PM_GENCLK_SRC_OSC0, 1);
#elif (BOARD_OSC1_HZ == 12000000)
	return pm_enable_genclk(4, PM_GENCLK_SRC_OSC1, 1);
#else
	/*
	 * The board code needs to set up a PLL. This is too difficult
	 * for us to figure out automatically.
	 */
	return board_enable_usbb_clk();
#endif
}

#endif /* CHIP_CLK_H_INCLUDED */

