/**
 * \file
 *
 * \brief Chip-specific oscillator management functions
 *
 * Copyright (c) 2010-2011 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * 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 microcontroller 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.
 *
 * \asf_license_stop
 *
 */
#ifndef XMEGA_OSC_H_INCLUDED
#define XMEGA_OSC_H_INCLUDED

#include <compiler.h>
#include <board.h>

/**
 * \weakgroup osc_group
 *
 * \section osc_group_errata Errata
 *   - Auto-calibration does not work on XMEGA A1 revision H and
 *     earlier.
 * @{
 */

//! \name Oscillator identifiers
//@{
//! 2 MHz Internal RC Oscillator
#define OSC_ID_RC2MHZ         OSC_RC2MEN_bm
//! 32 MHz Internal RC Oscillator
#define OSC_ID_RC32MHZ        OSC_RC32MEN_bm
//! 32 KHz Internal RC Oscillator
#define OSC_ID_RC32KHZ        OSC_RC32KEN_bm
//! External Oscillator
#define OSC_ID_XOSC           OSC_XOSCEN_bm
/**
 * \brief Reference from USB Start Of Frame
 * \note This cannot be enabled or disabled, but can be used as a reference for
 * the autocalibration (DFLL).
 */
#define OSC_ID_USBSOF         0xff
//@}

//! \name External oscillator types
//@{
#define XOSC_TYPE_EXTERNAL    0   //!< External clock signal
#define XOSC_TYPE_32KHZ       2   //!< 32.768 kHz resonator on TOSC
#define XOSC_TYPE_XTAL        3   //!< 0.4 to 16 MHz resonator on XTAL
//@}

/**
 * \def CONFIG_XOSC_32KHZ_LPM
 * \brief Define for enabling Low Power Mode for 32 kHz external oscillator.
 */
#ifdef __DOXYGEN__
# define CONFIG_XOSC_32KHZ_LPM
#endif /* __DOXYGEN__ */

/**
 * \def CONFIG_XOSC_STARTUP
 * \brief Board-dependent value that determines the number of start-up cycles
 * for external resonators, based on BOARD_XOSC_STARTUP_US. This is written to
 * the two MSB of the XOSCSEL field of OSC.XOSCCTRL.
 *
 * \note This is automatically computed from BOARD_XOSC_HZ and
 * BOARD_XOSC_STARTUP_US if it is not manually set.
 */

//! \name XTAL resonator start-up cycles
//@{
#define XOSC_STARTUP_256      0   //!< 256 cycle start-up time
#define XOSC_STARTUP_1024     1   //!< 1 k cycle start-up time
#define XOSC_STARTUP_16384    2   //!< 16 k cycle start-up time
//@}

/**
 * \def CONFIG_XOSC_RANGE
 * \brief Board-dependent value that sets the frequencye range of the external
 * oscillator. This is written to the FRQRANGE field of OSC.XOSCCTRL.
 *
 * \note This is automatically computed from BOARD_XOSC_HZ if it is not manually
 * set.
 */

//! \name XTAL resonator frequency range
//@{
//! 0.4 to 2 MHz frequency range
#define XOSC_RANGE_04TO2      OSC_FRQRANGE_04TO2_gc
//! 2 to 9 MHz frequency range
#define XOSC_RANGE_2TO9       OSC_FRQRANGE_2TO9_gc
//! 9 to 12 MHz frequency range
#define XOSC_RANGE_9TO12      OSC_FRQRANGE_9TO12_gc
//! 12 to 16 MHz frequency range
#define XOSC_RANGE_12TO16     OSC_FRQRANGE_12TO16_gc
//@}

/**
 * \def XOSC_STARTUP_TIMEOUT
 * \brief Number of us to wait for XOSC to start
 *
 * This is the number of slow clock cycles corresponding to
 * OSC0_STARTUP_VALUE with an additional 25% safety margin. If the
 * oscillator isn't running when this timeout has expired, it is assumed
 * to have failed to start.
 */

// If application intends to use XOSC.
#ifdef BOARD_XOSC_HZ
// Get start-up config for XOSC, if not manually set.
# ifndef CONFIG_XOSC_STARTUP
#  ifndef BOARD_XOSC_STARTUP_US
#   error BOARD_XOSC_STARTUP_US must be configured.
#  else
//! \internal Number of start-up cycles for the board's XOSC.
#   define BOARD_XOSC_STARTUP_CYCLES \
		(BOARD_XOSC_HZ / 1000000 * BOARD_XOSC_STARTUP_US)

#   if (BOARD_XOSC_TYPE == XOSC_TYPE_XTAL)
#    if (BOARD_XOSC_STARTUP_CYCLES > 16384)
#     error BOARD_XOSC_STARTUP_US is too high for current BOARD_XOSC_HZ.

#    elif (BOARD_XOSC_STARTUP_CYCLES > 1024)
#     define CONFIG_XOSC_STARTUP    XOSC_STARTUP_16384
#     define XOSC_STARTUP_TIMEOUT   (16384*(1000000/BOARD_XOSC_HZ))

#    elif (BOARD_XOSC_STARTUP_CYCLES > 256)
#     define CONFIG_XOSC_STARTUP    XOSC_STARTUP_1024
#     define XOSC_STARTUP_TIMEOUT   (1024*(1000000/BOARD_XOSC_HZ))

#    else
#     define CONFIG_XOSC_STARTUP    XOSC_STARTUP_256
#     define XOSC_STARTUP_TIMEOUT   (256*(1000000/BOARD_XOSC_HZ))
#    endif
#   else /* BOARD_XOSC_TYPE == XOSC_TYPE_XTAL */
#    define CONFIG_XOSC_STARTUP     0
#   endif
#  endif /* BOARD_XOSC_STARTUP_US */
# endif /* CONFIG_XOSC_STARTUP */

// Get frequency range setting for XOSC, if not manually set.
# ifndef CONFIG_XOSC_RANGE
#  if (BOARD_XOSC_TYPE == XOSC_TYPE_XTAL)
#   if (BOARD_XOSC_HZ < 400000)
#    error BOARD_XOSC_HZ is below minimum frequency of 400 kHz.

#   elif (BOARD_XOSC_HZ < 2000000)
#    define CONFIG_XOSC_RANGE    XOSC_RANGE_04TO2

#   elif (BOARD_XOSC_HZ < 9000000)
#    define CONFIG_XOSC_RANGE    XOSC_RANGE_2TO9

#   elif (BOARD_XOSC_HZ < 12000000)
#    define CONFIG_XOSC_RANGE    XOSC_RANGE_9TO12

#   elif (BOARD_XOSC_HZ <= 16000000)
#    define CONFIG_XOSC_RANGE    XOSC_RANGE_12TO16

#   else
#    error BOARD_XOSC_HZ is above maximum frequency of 16 MHz.
#   endif
#  else /* BOARD_XOSC_TYPE == XOSC_TYPE_XTAL */
#   define CONFIG_XOSC_RANGE     0
#  endif
# endif /* CONFIG_XOSC_RANGE */
#endif /* BOARD_XOSC_HZ */

#ifndef __ASSEMBLY__

/**
 * \internal
 * \brief Enable internal oscillator \a id
 *
 * Do not call this function directly. Use osc_enable() instead.
 */
static inline void osc_enable_internal(uint8_t id)
{
	irqflags_t flags;

	Assert(id != OSC_ID_USBSOF);

	flags = cpu_irq_save();
	OSC.CTRL |= id;
	cpu_irq_restore(flags);
}

#if defined(BOARD_XOSC_HZ) || defined(__DOXYGEN__)

/**
 * \internal
 * \brief Enable external oscillator \a id
 *
 * Do not call this function directly. Use osc_enable() instead. Also
 * note that this function is only available if the board actually has
 * an external oscillator crystal.
 */
static inline void osc_enable_external(uint8_t id)
{
	irqflags_t flags;

	Assert(id == OSC_ID_XOSC);

#ifndef CONFIG_XOSC_32KHZ_LPM
	OSC.XOSCCTRL = BOARD_XOSC_TYPE | (CONFIG_XOSC_STARTUP << 2) |
			CONFIG_XOSC_RANGE;
#else
	OSC.XOSCCTRL = BOARD_XOSC_TYPE | (CONFIG_XOSC_STARTUP << 2) |
			CONFIG_XOSC_RANGE | OSC_X32KLPM_bm;
#endif /* CONFIG_XOSC_32KHZ_LPM */

	flags = cpu_irq_save();
	OSC.CTRL |= id;
	cpu_irq_restore(flags);
}
#else

static inline void osc_enable_external(uint8_t id)
{
	Assert(false); // No external oscillator on the selected board
}
#endif

static inline void osc_disable(uint8_t id)
{
	irqflags_t flags;

	Assert(id != OSC_ID_USBSOF);

	flags = cpu_irq_save();
	OSC.CTRL &= ~id;
	cpu_irq_restore(flags);
}

static inline void osc_enable(uint8_t id)
{
	if (id != OSC_ID_XOSC) {
		osc_enable_internal(id);
	} else {
		osc_enable_external(id);
	}
}

static inline bool osc_is_ready(uint8_t id)
{
	Assert(id != OSC_ID_USBSOF);

	return OSC.STATUS & id;
}

//! \name XMEGA-Specific Oscillator Features
//@{

/**
 * \brief Enable DFLL-based automatic calibration of an internal
 * oscillator.
 *
 * The XMEGA features two Digital Frequency Locked Loops (DFLLs) which
 * can be used to improve the accuracy of the 2 MHz and 32 MHz internal
 * RC oscillators. The DFLL compares the oscillator frequency with a
 * more accurate reference clock to do automatic run-time calibration of
 * the oscillator.
 *
 * This function enables auto-calibration for either the 2 MHz or 32 MHz
 * internal oscillator using either the 32.768 kHz calibrated internal
 * oscillator or an external crystal oscillator as a reference. If the
 * latter option is used, the crystal must be connected to the TOSC pins
 * and run at 32.768 kHz.
 *
 * \param id The ID of the oscillator for which to enable
 * auto-calibration:
 * \arg \c OSC_ID_RC2MHZ or \c OSC_ID_RC32MHZ.
 * \param ref_id The ID of the oscillator to use as a reference:
 * \arg \c OSC_ID_RC32KHZ or \c OSC_ID_XOSC for internal or external 32 kHz
 * reference, respectively.
 * \arg \c OSC_ID_USBSOF for 32 MHz only when USB is available and running.
 */
static inline void osc_enable_autocalibration(uint8_t id, uint8_t ref_id)
{
	irqflags_t flags;

	flags = cpu_irq_save();
	switch (id) {
	case OSC_ID_RC2MHZ:
		Assert((ref_id == OSC_ID_RC32KHZ) || (ref_id == OSC_ID_XOSC));

		if (ref_id == OSC_ID_XOSC) {
			OSC.DFLLCTRL |= OSC_RC2MCREF_bm;
		} else {
			OSC.DFLLCTRL &= ~(OSC_RC2MCREF_bm);
		}
		DFLLRC2M.CTRL |= DFLL_ENABLE_bm;
		break;

	case OSC_ID_RC32MHZ:
#if XMEGA_AU || XMEGA_B || XMEGA_C
		Assert((ref_id == OSC_ID_RC32KHZ)
				|| (ref_id == OSC_ID_XOSC)
				|| (ref_id == OSC_ID_USBSOF));

		OSC.DFLLCTRL &= ~(OSC_RC32MCREF_gm);

		if (ref_id == OSC_ID_XOSC) {
			OSC.DFLLCTRL |= OSC_RC32MCREF_XOSC32K_gc;
		}
		else if (ref_id == OSC_ID_USBSOF) {
			/*
			 * Calibrate 32MRC at 48MHz using USB SOF
			 * 48MHz / 1kHz = 0xBB80
			 */
			DFLLRC32M.COMP1 = 0x80;
			DFLLRC32M.COMP2 = 0xBB;
			OSC.DFLLCTRL |= OSC_RC32MCREF_USBSOF_gc;
		}
		else if (ref_id == OSC_ID_RC32KHZ) {
			/*
			 * Calibrate 32MRC at 48MHz using USB SOF
			 * 48MHz / 1kHz = 0xBB80
			 */
			DFLLRC32M.COMP1 = 0x80;
			DFLLRC32M.COMP2 = 0xBB;
			osc_enable(OSC_ID_RC32KHZ);
			OSC.DFLLCTRL |= OSC_RC32MCREF_RC32K_gc;
		}
#else
		Assert((ref_id == OSC_ID_RC32KHZ) || (ref_id == OSC_ID_XOSC));

		if (ref_id == OSC_ID_XOSC) {
			OSC.DFLLCTRL |= OSC_RC32MCREF_bm;
		}
		else {
			OSC.DFLLCTRL &= ~(OSC_RC32MCREF_bm);
		}
#endif
		DFLLRC32M.CTRL |= DFLL_ENABLE_bm;
		break;

	default:
		Assert(false);
		break;
	}
	cpu_irq_restore(flags);
}

/**
 * \brief Disable DFLL-based automatic calibration of an internal
 * oscillator.
 *
 * \see osc_enable_autocalibration
 *
 * \param id The ID of the oscillator for which to disable
 * auto-calibration:
 * \arg \c OSC_ID_RC2MHZ or \c OSC_ID_RC32MHZ.
 */
static inline void osc_disable_autocalibration(uint8_t id)
{
	switch (id) {
	case OSC_ID_RC2MHZ:
		DFLLRC2M.CTRL = 0;
		break;

	case OSC_ID_RC32MHZ:
		DFLLRC32M.CTRL = 0;
		break;

	default:
		Assert(false);
		break;
	}
}

/**
 * \brief Load a specific calibration value for the specified oscillator.
 *
 * \param id The ID of the oscillator for which to disable
 * auto-calibration:
 * \arg \c OSC_ID_RC2MHZ or \c OSC_ID_RC32MHZ.
 * \param calib The specific calibration value required:
 *
 */
static inline void osc_user_calibration(uint8_t id, uint16_t calib)
{
	switch (id) {
	case OSC_ID_RC2MHZ:
		DFLLRC2M.CALA=LSB(calib);
		DFLLRC2M.CALB=MSB(calib);
		break;

	case OSC_ID_RC32MHZ:
		DFLLRC32M.CALA=LSB(calib);
		DFLLRC32M.CALB=MSB(calib);
		break;

	default:
		Assert(false);
		break;
	}
}
//@}

static inline uint32_t osc_get_rate(uint8_t id)
{
	Assert(id != OSC_ID_USBSOF);

	switch (id) {
	case OSC_ID_RC2MHZ:
		return 2000000UL;

	case OSC_ID_RC32MHZ:
#ifdef CONFIG_OSC_RC32_CAL
		return CONFIG_OSC_RC32_CAL;
#else
		return 32000000UL;
#endif

	case OSC_ID_RC32KHZ:
		return 32768UL;

#ifdef BOARD_XOSC_HZ
	case OSC_ID_XOSC:
		return BOARD_XOSC_HZ;
#endif

	default:
		Assert(false);
		return 0;
	}
}

#endif /* __ASSEMBLY__ */

//! @}

#endif /* XMEGA_OSC_H_INCLUDED */
