/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2015, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: 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
 * 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.
 * ----------------------------------------------------------------------------
 */

/**
 *  \file
 *  Implement simple PIT usage as system tick.
 */

/*----------------------------------------------------------------------------
 *         Headers
 *----------------------------------------------------------------------------*/

#include "board.h"
#include "timer.h"
#include "peripherals/tc.h"
#include "peripherals/pit.h"
#include "peripherals/aic.h"
#include "peripherals/pmc.h"

/*----------------------------------------------------------------------------
 *         Local variables
 *----------------------------------------------------------------------------*/

/** Tick Counter */
static volatile uint32_t _timer = 0;
static uint32_t _resolution = 0;

/*----------------------------------------------------------------------------
 *         Exported Functions
 *----------------------------------------------------------------------------*/

/**
 *  \brief Handler for Sytem Tick interrupt.
 */
static void timer_increment(void)
{
	uint32_t status;

	/* Read the PIT status register */
	status = pit_get_status() & PIT_SR_PITS;
	if (status != 0) {

		/* 1 = The Periodic Interval timer has reached PIV
		 * since the last read of PIT_PIVR. Read the PIVR to
		 * acknowledge interrupt and get number of ticks
		 * Returns the number of occurrences of periodic
		 * intervals since the last read of PIT_PIVR. */
		_timer += (pit_get_pivr() >> 20);
	}
}

uint32_t timer_configure(uint32_t resolution)
{
	pit_disable_it();
	if (!resolution)
		resolution = BOARD_TIMER_RESOLUTION;
	_timer = 0;
	pmc_enable_peripheral(ID_PIT);
	pit_init(resolution);
	aic_set_source_vector(ID_PIT, timer_increment);
	aic_enable(ID_PIT);
	pit_enable_it();
	pit_enable();
	_resolution = resolution;
	return 0;
}

uint32_t timer_get_resolution(void)
{
	return _resolution;
}

uint32_t timer_get_interval(uint32_t start, uint32_t end)
{
	if (end >= start)
		return (end - start);
	return (end + (0xFFFFFFFF - start) + 1);
}

void timer_start_timeout(struct _timeout* timeout, uint32_t count)
{
	timeout->start = _timer;
	timeout->count = count;
}

uint8_t timer_timeout_reached(struct _timeout* timeout)
{
	return timer_get_interval(timeout->start, _timer) >= timeout->count;
}

void timer_wait(uint32_t count)
{
	uint32_t start, current;
	start = _timer;
	do {
		current = _timer;
	} while (timer_get_interval(start, current) < count);
}

void timer_sleep(uint32_t count)
{
	uint32_t start, current;
	asm("CPSIE   I");
	start = _timer;

	do {
		asm("WFI");
		current = _timer;
	} while (timer_get_interval(start, current) < count);
}

uint32_t timer_get_tick(void)
{
	return _timer;
}
