/**
 * \file
 *
 * \brief Unit tests for common clock service
 *
 * Copyright (c) 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
 *
 */
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#include <compiler.h>
#include <asf.h>
#include "conf_test.h"

/**
 * \mainpage
 *
 * \section intro Introduction
 * This is the unit test application for the \ref sysclk.h "Common Clock service".
 * It consists of test cases for the following:
 * - Oscillator 0/1 test
 * - 32KHz Oscillator test
 * - Pll and Dfll test
 * - Synchronous clock test
 * - Generic clock test
 *
 * \section files Main Files
 * - \ref unit_tests.c
 * - \ref conf_test.h
 * - \ref conf_board.h
 * - \ref conf_clock.h
 * - \ref conf_usart_serial.h
 *
 * \section device_info Device Info
 * All AVR devices can be used.
 * This example has been tested with the following setup:
 * - EVK1100
 * - EVK1101
 * - EVK1104
 * - EVK1105
 * - UC3C_EK
 * - UC3L_EK
 * - STK600+RCUC3D
 *
 * \section description Description of the unit tests
 * See the documentation for the individual unit test functions \ref unit_tests.c
 * "here" for detailed descriptions of the tests.
 *
 * \section dependencies Dependencies
 * This example depends directly on the following modules:
 * - \ref test_suite_group
 * - \ref sysclk.h "Common Clock service"
 *
 * \section compinfo Compilation info
 * This software was written for the GNU GCC and IAR for AVR. Other compilers
 * may or may not work.
 *
 * \section contactinfo Contact Information
 * For further information, visit <a href="http://www.atmel.com/">Atmel</a>.\n
 * Support and FAQ: http://support.atmel.no/
 */

//! \name Unit test configuration
//@{
/**
 * \def CONF_TEST_USART
 * \brief USART to redirect STDIO to
 */
/**
 * \def CONF_TEST_BAUDRATE
 * \brief Baudrate of USART
 */
/**
 * \def CONF_TEST_CHARLENGTH
 * \brief Character length (bits) of USART
 */
/**
 * \def CONF_TEST_PARITY
 * \brief Parity mode of USART
 */
/**
 * \def CONF_TEST_STOPBITS
 * \brief Stopbit configuration of USART
 */
//@}

//! Pointer to the external low level write function.
extern int (*ptr_put)(void volatile*,int);

//! USART initialize with the CONFIG_PBA_HZ
int usart_ready = 0;

//! \name Common Clock unit test functions
//@{
/**
 * \brief Test Oscillator 0/1
 *
 * This test enables the oscillator 0 and 1 (if have), and then
 * check if they can be stable and ready for main clock after
 * startup time
 *
 * \param test Current test case.
 */
static void run_osc_test(const struct test_case *test)
{
	uint32_t wait;
	bool status;
#ifdef BOARD_OSC0_HZ
	osc_enable(OSC_ID_OSC0);
	for (wait  =0; wait < OSC0_STARTUP_TIMEOUT; wait++) {
		//waiting the oscillator0 stable
		__asm__("nop");
	};
	status = osc_is_ready(OSC_ID_OSC0);
	test_assert_true(test, status, "Oscillator 0 is not stable");
#endif

#ifdef BOARD_OSC1_HZ
	osc_enable(OSC_ID_OSC1);
	for (wait = 0; wait < OSC1_STARTUP_TIMEOUT; wait++) {
		//waiting the oscillator1 stable
		__asm__("nop");
	};
	status = osc_is_ready(OSC_ID_OSC1);
	test_assert_true(test, status, "Oscillator 1 is not stable");
#endif

#ifdef BOARD_RC8M_HZ
	osc_enable(OSC_ID_RC8M);
	status = osc_is_ready(OSC_ID_RC8M);
	test_assert_true(test, status, "8M/1M RC oscillator is not stable");
#endif

#ifdef BOARD_RC120_HZ
	osc_enable(OSC_ID_RC120M);
	status = osc_is_ready(OSC_ID_RC120M);
	test_assert_true(test, status, "120M RC oscillator is not stable");
#endif

}

/**
 * \brief cleanup oscillator test
 *
 * \param test Current test case.
 */
static void cleanup_osc_test(const struct test_case *test)
{
#ifdef BOARD_OSC0_HZ
	osc_disable(OSC_ID_OSC0);
#endif

#ifdef BOARD_OSC1_HZ
	osc_disable(OSC_ID_OSC1);
#endif

#ifdef BOARD_RC8M_HZ
	osc_disable(OSC_ID_RC8M);
#endif

#ifdef BOARD_RC120_HZ
	osc_disable(OSC_ID_RC120M);
#endif
}

/**
 * \brief Test 32KHz oscillator
 *
 * This test enables the 32KHz oscillator, and then
 * check if they can be used after startup time
 *
 * \param test Current test case.
 */
static void run_osc32_test(const struct test_case *test)
{
#ifdef BOARD_OSC32_HZ
	uint32_t osc32_startup_timeout;
	uint32_t wait;
	bool status;

	osc_enable(OSC_ID_OSC32);
	osc32_startup_timeout = (OSC_RCSYS_NOMINAL_HZ/1000) *
		(BOARD_OSC32_STARTUP_US/1000);
	for (wait = 0; wait < osc32_startup_timeout; wait++) {
		//waiting the 32KHz oscillator ready
		__asm__("nop");
		__asm__("nop");
		__asm__("nop");
		__asm__("nop");
		__asm__("nop");
	};
	status = osc_is_ready(OSC_ID_OSC32);
	test_assert_true(test, status, "32KHz oscillator is not stable");
#endif
}

/**
 * \brief cleanup 32KHz oscillator test
 *
 * \param test Current test case.
 */
static void cleanup_osc32_test(const struct test_case *test)
{

#ifdef BOARD_OSC32_HZ
	osc_disable(OSC_ID_OSC32);
#endif

}

/**
 * \brief Test Pll and DFLL (if have)
 *
 * This test enables pll/dfll source clock, sets its mul and div
 * factor, and then enables it. Check if it's locked after max
 * startup time.
 *
 * \param test Current test case.
 */
static void run_pll_dfll_test(const struct test_case *test)
{
	uint32_t wait;
	bool status;
#ifdef CONFIG_PLL0_SOURCE || CONFIG_PLL1_SOURCE
	struct pll_config pllcfg;
#endif

#ifdef CONFIG_PLL0_SOURCE
	pll_enable_source(CONFIG_PLL0_SOURCE);
	pll_config_defaults(&pllcfg, 0);
	pll_enable(&pllcfg, PLL0);
	for (wait = 0; wait < PLL_MAX_STARTUP_CYCLES; wait++) {
		//waiting PLL0 lock
		__asm__("nop");
	};
	status = pll_is_locked(PLL0);
	test_assert_true(test, status, "PLL0 can't be locked");
#endif

#ifdef CONFIG_PLL1_SOURCE
	pll_enable_source(CONFIG_PLL1_SOURCE);
	pll_config_defaults(&pllcfg, 1);
	pll_enable(&pllcfg, PLL1);
	for (wait = 0; wait < PLL_MAX_STARTUP_CYCLES; wait++) {
		//waiting PLL1 lock
		__asm__("nop");
	};
	status = pll_is_locked(PLL1);
	test_assert_true(test, status, "PLL1 can't be locked");
#endif

#ifdef CONFIG_DFLL0_SOURCE
	struct dfll_config dfllcfg;
	osc_enable(OSC_ID_RCSYS);
	osc_wait_ready(OSC_ID_RCSYS);
	dfll_config_defaults(&dfllcfg, 0);
	dfll_enable_closed_loop(&dfllcfg, 0);
	for (wait = 0; wait < DFLL_MAX_LOCK_CYCLES; wait++) {
		//waiting DFLL lock
		__asm__("nop");
	};
	status = dfll_is_fine_locked(0);
	test_assert_true(test, status, "DFLL can't be locked");
#endif

}

/**
 * \brief cleanup pll/dfll test
 *
 * \param test Current test case.
 */
static void cleanup_pll_dfll_test(const struct test_case *test)
{

#ifdef CONFIG_PLL0_SOURCE
	pll_disable(PLL0);
#endif

#ifdef CONFIG_PLL1_SOURCE
	pll_disable(PLL1);
#endif

#ifdef CONFIG_DFLL0_SOURCE
	dfll_disable_closed_loop(0);
	osc_disable(OSC_ID_RCSYS);
#endif

}

/**
 * \brief Test Synchronous clock
 *
 * This test enables source clock for main clock, sets CPU/HSB,
 * PBA,PBB,PBC(if have) division factor, and then check if the chip
 * is set correctly.
 *
 * \note It's not easy to measure CPU/HSB,PBA... frequency, even through
 * some chips have freqm module. Using external module may cause some
 * reliability issue. Since synchronous clocks share the common root
 * main clock, they only need set division factor. So we just test
 * the related registers if set correctly after calling common clock
 * service.
 *
 * \param test Current test case.
 */
static void run_sync_clock_test(const struct test_case *test)
{
	bool status;

	sysclk_init();
	//PBA clock set, usart can be used
	usart_ready = 1;

#ifdef CONFIG_CPU_HZ
	status = (CONFIG_CPU_HZ == sysclk_get_cpu_hz());
	test_assert_true(test, status, "CPU clock set fail");
#endif

#ifdef CONFIG_PBA_HZ
	status = (CONFIG_PBA_HZ == sysclk_get_pba_hz());
	test_assert_true(test, status, "PBA clock set fail");
#endif

#ifdef CONFIG_PBB_HZ
	status = (CONFIG_PBB_HZ == sysclk_get_pbb_hz());
	test_assert_true(test, status, "PBB clock set fail");
#endif

#ifdef CONFIG_PBC_HZ
	status = (CONFIG_PBC_HZ == sysclk_get_pbc_hz());
	test_assert_true(test, status, "PBC clock set fail");
#endif

}

/**
 * \brief Test Generic clock
 *
 * This test enables the generic clock, sets its source and
 * division factor, and then check if it's set correctly.
 *
 * \param test Current test case.
 */
static void run_generic_clock_test(const struct test_case *test)
{
	struct genclk_config gencfg;
	bool status;
	genclk_config_set_source(&gencfg, CONFIG_GENERIC_SRC);
	genclk_config_set_divider(&gencfg, CONFIG_GENERIC_DIV);
	genclk_enable(&gencfg, CONFIG_GENERIC_ID);

	status = (CONFIG_GENERIC_CLOCK == get_generic_clock());
	test_assert_true(test, status, "Generic clock set fail");
}

/**
 * \brief cleanup generic clock test
 *
 * \param test Current test case.
 */
static void cleanup_generic_clock_test(const struct test_case *test)
{
	genclk_disable(CONFIG_GENERIC_ID);
}

//@}

/**
 * \brief Run Common Clock service unit tests
 *
 * Initializes board, serial output, then sets up the
 * clock unit test suite and runs it.
 */
int main (void)
{
	const usart_serial_options_t usart_serial_options = {
		.baudrate   = CONF_TEST_BAUDRATE,
		.charlength = CONF_TEST_CHARLENGTH,
		.paritytype = CONF_TEST_PARITY,
		.stopbits   = CONF_TEST_STOPBITS,
	};

	board_init();
	stdio_serial_init(CONF_TEST_USART, &usart_serial_options);

	//set the low level write function
	//usart can work only after we call sysclk_init, because the
	//PBA clock initialized with correct value which used by usart
	//So, we output the information to one buffer before usart can work.
	ptr_put = (int (*)(void volatile*,int))&usart_write_char_buf;

#if defined(__GNUC__) && defined(__AVR32__)
	// This unit test will be launched on a test-server, using avr32program.
	// The reset is done through the OCD. The 'setbuf' command allows to make the
	// application starts correctly: it will not execute the 'breakpoint'
	// instruction in _fstat_host (used by printf) and the core will not stay
	// blocked on the 'breakpoint' instruction (executed in the debug mode).
	setbuf(stdout, NULL);
#endif

	// Define all the test cases.
	DEFINE_TEST_CASE(osc_test, NULL, run_osc_test, cleanup_osc_test,
			"Oscillator0/1 test");
	DEFINE_TEST_CASE(osc32_test, NULL, run_osc32_test, cleanup_osc32_test,
			"32KHz oscillator test");
	DEFINE_TEST_CASE(pll_dfll_test, NULL, run_pll_dfll_test,
			cleanup_pll_dfll_test, "Pll or Dfll test");
	DEFINE_TEST_CASE(sync_clock_test, NULL, run_sync_clock_test, NULL,
			"CPU/HSB, peripheral bridge clock test");
	DEFINE_TEST_CASE(generic_clock_test, NULL, run_generic_clock_test,
			cleanup_generic_clock_test, "Generic clock test");

	// Put test case addresses in an array.
	DEFINE_TEST_ARRAY(clock_tests) = {
		&osc_test,
		&osc32_test,
		&pll_dfll_test,
		&sync_clock_test,
		&generic_clock_test,
	};

	// Define the test suite.
	DEFINE_TEST_SUITE(clock_suite, clock_tests, "Clock service unit test suite");

	// Run all tests in the test suite.
	test_suite_run(&clock_suite);

	while (1) {
		__asm__("nop");
	};
}
