/**
 * \file
 *
 * \brief AT45dbx DataFlash Component Unit Test.
 *
 * Copyright (c) 2011-2012 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
 *
 */

/*! \mainpage
 * \section intro Introduction
 * This is the unit test application for the \ref at45dbx.h "AT45dbx component".
 * It consists of test cases for the following:
 * - Memory check
 * - Read/write byte access and data integrity
 * - Read/write sector access and data integrity
 * - Read/write multiple sector access and data integrity
 *
 * \section files Main Files
 * - \ref unit_tests.c: AT45DBx memory unit tests application.
 * - \ref at45dbx.c: The DataFlash driver
 * - \ref at45dbx.h: The DataFlash driver header
 * - \ref conf_test.h: test configuration file
 * - \ref conf_board.h: board initialization process configuration
 * - \ref conf_clock.h: clock specific initialization
 * - \ref conf_at45dbx.h: DataFlash driver configuration (including SPI service
 * selection)
 * - \ref conf_usart_serial.h: Serial port configuration (\see conf_test.h)
 *
 * \section deviceinfo Device Info
 * All AVR devices can be used. This application has been tested
 * with the following setup:
 *   - Evaluation kits with a DataFlash connected to an SPI interface.
 *
 * \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
 */

#include <stdint.h>
#include <stdbool.h>
#include <asf.h>
#include <string.h>
#include "at45dbx.h"
#include "conf_test.h"

//! \name Debug output 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
 */
//@}

//! \name Byte pattern generator
//@{
/**
 * \brief Generates byte pattern 1 for a given address
 */
#define BYTE_PATTERN1(x)                                                       \
	((uint8_t) (((x) * 5 + 1) & 0xff))
/**
 * \brief Generates byte pattern 2 for a given address
 */
#define BYTE_PATTERN2(x)                                                       \
	((uint8_t) (((x) * 3) & 0xff))
/**
 * \brief Generates byte pattern 3 for a given address
 */
#define BYTE_PATTERN3(x)                                                       \
	((uint8_t) (((x) * 7 + 3) & 0xff))
//@}

/**
 * \brief A DataFlash sector-sized buffer for processing data in ram memory
 */
static uint8_t sector_buf[AT45DBX_SECTOR_SIZE];

/**
 * \brief Index of the current sector being processed.
 * Used by \ref run_multiple_sector_access_test
 */
static uint32_t cur_sector = 0;

/**
 * \brief Test case pointer used to refer to the current test case in the
 * function \ref at45dbx_read_multiple_sector_callback
 */
static struct test_case *cur_test;

/**
 * \brief Performs a memory check on all DataFlash memories
 *
 * This function will simply test the output of the function
 * \ref at45dbx_mem_check and returns an error in case of failure.
 *
 * \param test Current test case.
 */
static void run_memory_check_test(const struct test_case *test)
{
	bool status;

	status = at45dbx_mem_check();
	test_assert_true(test, status == true, "Error memory check failed");
}

/**
 * \brief Performs out of range memory test on DataFlash memory
 *
 * This function will simply test the output of the function
 * \ref at45dbx_mem_check and returns an error in case of failure.
 *
 * \param test Current test case.
 */
static void run_memory_range_check_test(const struct test_case *test)
{
	bool status;

	status = at45dbx_read_byte_open(
			(((uint32_t)1<<AT45DBX_MEM_SIZE)*AT45DBX_MEM_CNT));
	test_assert_true(test, status == true, "Error memory range check failed");
	status = at45dbx_read_byte_open(
			(((uint32_t)1<<AT45DBX_MEM_SIZE)*AT45DBX_MEM_CNT)+1);
	test_assert_true(test, status == false, "Error memory range check failed");

	}

/**
 * \brief Test the read and write byte operations on the DataFlash
 *
 * This function will test the read and write functionalities of the DataFlash
 * using byte access. It will first write a known pattern on the 2 first
 * consecutive sectors and read it back by testing each value read.\n
 * In addition to test the data integrity and the byte read / write
 * functions, this will also test the continuity of the sectors as well as the
 * auto-incrementation of the DataFlash address.
 *
 * \param test Current test case.
 */
static void run_byte_access_test(const struct test_case *test)
{
	uint32_t i;
	bool status;
	uint8_t byte;

	/* Write bytes one by one to the 2 first continious sectors
	 */
	status = at45dbx_write_byte_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for write access");
	for (i=0; i<AT45DBX_SECTOR_SIZE * 2; i++) {
		status = at45dbx_write_byte(BYTE_PATTERN1(i));
		test_assert_true(test, status == true,
				"Write byte operation error @ 0x%08x", i);
	}
	at45dbx_write_close();

	/* Read back the 2 first sectors of the DataFlash and check the values
	 */
	status = at45dbx_read_byte_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for read access");
	for (i=0; i<AT45DBX_SECTOR_SIZE * 2; i++) {
		byte = at45dbx_read_byte();
		test_assert_true(test, byte == BYTE_PATTERN1(i),
				"Read byte operation error @ 0x%08x"
				" (read: 0x%02x, expected: 0x%02x)", i,
				byte, BYTE_PATTERN1(i));
	}
	at45dbx_read_close();
}

/**
 * \brief Test the read and write sector operations on the DataFlash
 *
 * This function will test the read and write functionalities of the DataFlash
 * using sector access. It will first fill a sector with a known pattern and
 * read it back by testing each value read.\n
 *
 * \param test Current test case.
 */
static void run_sector_access_test(const struct test_case *test)
{
	uint32_t i;
	bool status;

	/* Fills a ram sector with a known pattern
	 */
	for (i=0; i<AT45DBX_SECTOR_SIZE; i++) {
		sector_buf[i] = BYTE_PATTERN2(i);
	}

	/* Write the ram sector to the DataFlash
	 */
	status = at45dbx_write_sector_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for write access");
	status = at45dbx_write_sector_from_ram(sector_buf);
	test_assert_true(test, status == true,
			"Write sector operation error");
	at45dbx_write_close();

	/* Clear the ram sector buffer
	 */
	for (i=0; i<AT45DBX_SECTOR_SIZE; i++) {
		sector_buf[i] = 0;
	}

	/* Read back the sector of the DataFlash
	 */
	status = at45dbx_read_sector_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for read access");
	status = at45dbx_read_sector_to_ram(sector_buf);
	test_assert_true(test, status == true,
			"Read sector operation error");
	at45dbx_read_close();

	/* Compare the values read from the DataFlash with the expected values
	 */
	for (i=0; i<AT45DBX_SECTOR_SIZE; i++) {
		test_assert_true(test, sector_buf[i] == BYTE_PATTERN2(i),
				"Value not expected @ 0x%08x (read: 0x%02x,"
				" expected: 0x%02x)", i, sector_buf[i],
				BYTE_PATTERN2(i));
	}
}

/**
 * \brief Test the read and write mutliple sector operations on the DataFlash
 *
 * This function will test the read and write functionalities of the DataFlash
 * using multiple sector access. It will first fill the 10 first DataFlash
 * sectors with a known pattern and read it back to test each value.\n
 *
 * \param test Current test case.
 */
static void run_multiple_sector_access_test(const struct test_case *test)
{
	bool status;

	/* Write the ram sector to the DataFlash
	 */
	status = at45dbx_write_sector_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for write access");
	cur_sector = 0;
	status = at45dbx_write_multiple_sector(10);
	test_assert_true(test, status == true,
			"Error while writing sector # %i", cur_sector);
	at45dbx_write_close();

	/* Read back the sector of the DataFlash
	 */
	status = at45dbx_read_sector_open(0);
	test_assert_true(test, status == true,
			"Cannot open the DataFlash memory for read access");
	cur_sector = 0;
	cur_test = (struct test_case *) test;
	status = at45dbx_read_multiple_sector(10);
	test_assert_true(test, status == true,
			"Error while reading sector # %i", cur_sector);
	at45dbx_read_close();
}

/*! \copybrief at45dbx_write_multiple_sector_callback
 * Used by \ref run_multiple_sector_access_test
 */
void at45dbx_write_multiple_sector_callback(void *psector)
{
	uint32_t i;
	uint8_t value;

	/* This reflect the index of the current sector being accessed
	 */
	cur_sector++;

	/* Fills a ram sector with a known pattern
	 */
	for (i=0; i<AT45DBX_SECTOR_SIZE; i++) {
		value = BYTE_PATTERN3(cur_sector * AT45DBX_SECTOR_SIZE + i);
		((uint8_t *) psector)[i] = value;
	}
}

/*! \copybrief at45dbx_read_multiple_sector_callback
 * Used by \ref run_multiple_sector_access_test
 */
void at45dbx_read_multiple_sector_callback(const void *psector)
{
	uint32_t i;
	uint8_t expected;

	/* This reflect the index of the current sector being accessed
	 */
	cur_sector++;

	/* Test each values of the current sector
	 */
	for (i=0; i<AT45DBX_SECTOR_SIZE; i++) {
		expected = BYTE_PATTERN3(cur_sector * AT45DBX_SECTOR_SIZE + i);
		test_assert_true(cur_test, ((uint8_t *) psector)[i] == expected,
				"Value not expected @ 0x%08x in sector # %i"
				" (read: 0x%02x, expected: 0x%02x)", i,
				cur_sector, ((uint8_t *) psector)[i],
				expected);
	}
}

/**
 * \brief Run DataFlash component unit tests
 *
 * Initializes the clock system, board, serial output and DataFlash, then sets
 * up the DataFlash 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,
	};

	// Initialize the board and all the peripheral required
	sysclk_init();
	board_init();
	stdio_serial_init(CONF_TEST_USART, &usart_serial_options);

#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

	at45dbx_init();

        // Define all the test cases
	DEFINE_TEST_CASE(memory_check_test, NULL, run_memory_check_test, NULL,
			"Memory check");
	DEFINE_TEST_CASE(byte_access_test, NULL, run_byte_access_test, NULL,
			"Read/write byte access");
	DEFINE_TEST_CASE(sector_access_test, NULL, run_sector_access_test, NULL,
			"Read/write sector access");
	DEFINE_TEST_CASE(multiple_sector_access_test, NULL,
			run_multiple_sector_access_test, NULL,
			"Read/write multiple sector access");
	DEFINE_TEST_CASE(memory_range_check_test, NULL,
			run_memory_range_check_test, NULL,
			"Memory range address check");

	// Put test case addresses in an array.
	DEFINE_TEST_ARRAY(memory_tests) = {
		&memory_check_test,
		&byte_access_test,
		&sector_access_test,
		&multiple_sector_access_test,
		&memory_range_check_test
	};

	// Define the test suite.
	DEFINE_TEST_SUITE(memory_suite, memory_tests,
			"AT45dbx component unit test suite");

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

	while (1) {
		// Intentionally left empty.
	};
}
