/* ---------------------------------------------------------------------------- */
/*                  Atmel Microcontroller Software Support                      */
/*                       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 condition is 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
 */

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

#include "chip.h"

#include <string.h>

#include <rtems/irq-extension.h>
#include <rtems/sysinit.h>
#include <bsp/fatal.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

/* Maximum number of interrupt sources that can be defined. This
 * constant can be increased, but the current value is the smallest possible
 * that will be compatible with all existing projects. */
#define MAX_INTERRUPT_SOURCES       7

/*----------------------------------------------------------------------------
 *        Local types
 *----------------------------------------------------------------------------*/

/**
 * Describes a PIO interrupt source, including the PIO instance triggering the
 * interrupt and the associated interrupt handler.
 */
typedef struct _InterruptSource {
	/* Pointer to the source pin instance. */
	const Pin *pPin;

	/* Interrupt handler. */
	void (*handler)(const Pin *, void *arg);

	void *arg;
} InterruptSource;

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

/* List of interrupt sources. */
static InterruptSource _aIntSources[MAX_INTERRUPT_SOURCES];

/* Number of currently defined interrupt sources. */
static uint32_t _dwNumSources = 0;

/*----------------------------------------------------------------------------
 *        Local Functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Handles all interrupts on the given PIO controller.
 * \param id  PIO controller ID.
 * \param pPio  PIO controller base address.
 */
static void PIO_Interrupt(Pio *pPio, uint32_t id)
{
	uint32_t status;
	uint32_t status_save;
	size_t i;

	do {
		status = pPio->PIO_ISR;
		status &= pPio->PIO_IMR;
		status_save = status;

		for (i = 0; status != 0 && i < MAX_INTERRUPT_SOURCES; ++i) {
			const InterruptSource *is = &_aIntSources[i];
			const Pin *pin = is->pPin;;

			if (pin->id == id) {
				uint32_t mask = pin->mask;

				if ((status & mask) != 0) {
					status &= ~mask;
					(*is->handler)(pin, is->arg);
				}
			}
		}
	} while (status_save != 0);
}

/*----------------------------------------------------------------------------
 *        Global Functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Parallel IO Controller A interrupt handler
 * \Redefined PIOA interrupt handler for NVIC interrupt table.
 */
static void PIOA_Interrupt(void *arg)
{
	PIO_Interrupt(arg, ID_PIOA);
}

/**
 * \brief Parallel IO Controller B interrupt handler
 * \Redefined PIOB interrupt handler for NVIC interrupt table.
 */
static void PIOB_Interrupt(void *arg)
{
	PIO_Interrupt(arg, ID_PIOB);
}

/**
 * \brief Parallel IO Controller C interrupt handler
 * \Redefined PIOC interrupt handler for NVIC interrupt table.
 */
static void PIOC_Interrupt(void *arg)
{
	PIO_Interrupt(arg, ID_PIOC);
}


/**
 * \brief Parallel IO Controller D interrupt handler
 * \Redefined PIOD interrupt handler for NVIC interrupt table.
 */
static void PIOD_Interrupt(void *arg)
{
	PIO_Interrupt(arg, ID_PIOD);
}

/**
 * \brief Parallel IO Controller E interrupt handler
 * \Redefined PIOE interrupt handler for NVIC interrupt table.
 */
static void PIOE_Interrupt(void *arg)
{
	PIO_Interrupt(arg, ID_PIOE);
}

static void PIO_SysInitializeInterrupts(void)
{
	rtems_status_code sc;

	TRACE_DEBUG("PIO_Initialize()\n\r");

	/* Configure PIO interrupt sources */
	TRACE_DEBUG("PIO_Initialize: Configuring PIOA\n\r");
	PMC_EnablePeripheral(ID_PIOA);
	PIOA->PIO_ISR;
	PIOA->PIO_IDR = 0xFFFFFFFF;
	sc = rtems_interrupt_handler_install(
		PIOA_IRQn,
		"PIO A",
		RTEMS_INTERRUPT_UNIQUE,
		PIOA_Interrupt,
		PIOA
	);
	if (sc != RTEMS_SUCCESSFUL) {
		bsp_fatal(ATSAM_FATAL_PIO_IRQ_A);
	}

	TRACE_DEBUG("PIO_Initialize: Configuring PIOB\n\r");
	PMC_EnablePeripheral(ID_PIOB);
	PIOB->PIO_ISR;
	PIOB->PIO_IDR = 0xFFFFFFFF;
	sc = rtems_interrupt_handler_install(
		PIOB_IRQn,
		"PIO B",
		RTEMS_INTERRUPT_UNIQUE,
		PIOB_Interrupt,
		PIOB
	);
	if (sc != RTEMS_SUCCESSFUL) {
		bsp_fatal(ATSAM_FATAL_PIO_IRQ_B);
	}

	TRACE_DEBUG("PIO_Initialize: Configuring PIOC\n\r");
	PMC_EnablePeripheral(ID_PIOC);
	PIOC->PIO_ISR;
	PIOC->PIO_IDR = 0xFFFFFFFF;
	sc = rtems_interrupt_handler_install(
		PIOC_IRQn,
		"PIO C",
		RTEMS_INTERRUPT_UNIQUE,
		PIOC_Interrupt,
		PIOC
	);
	if (sc != RTEMS_SUCCESSFUL) {
		bsp_fatal(ATSAM_FATAL_PIO_IRQ_C);
	}

	TRACE_DEBUG("PIO_Initialize: Configuring PIOD\n\r");
	PMC_EnablePeripheral(ID_PIOD);
	PIOD->PIO_ISR;
	PIOD->PIO_IDR = 0xFFFFFFFF;
	sc = rtems_interrupt_handler_install(
		PIOD_IRQn,
		"PIO D",
		RTEMS_INTERRUPT_UNIQUE,
		PIOD_Interrupt,
		PIOD
	);
	if (sc != RTEMS_SUCCESSFUL) {
		bsp_fatal(ATSAM_FATAL_PIO_IRQ_D);
	}

	TRACE_DEBUG("PIO_Initialize: Configuring PIOE\n\r");
	PMC_EnablePeripheral(ID_PIOE);
	PIOE->PIO_ISR;
	PIOE->PIO_IDR = 0xFFFFFFFF;
	sc = rtems_interrupt_handler_install(
		PIOE_IRQn,
		"PIO E",
		RTEMS_INTERRUPT_UNIQUE,
		PIOE_Interrupt,
		PIOE
	);
	if (sc != RTEMS_SUCCESSFUL) {
		bsp_fatal(ATSAM_FATAL_PIO_IRQ_E);
	}
}

RTEMS_SYSINIT_ITEM(PIO_SysInitializeInterrupts, RTEMS_SYSINIT_BSP_START,
    RTEMS_SYSINIT_ORDER_LAST_BUT_5);

/**
 * Configures a PIO or a group of PIO to generate an interrupt on status
 * change. The provided interrupt handler will be called with the triggering
 * pin as its parameter (enabling different pin instances to share the same
 * handler).
 * \param pPin  Pointer to a Pin instance.
 * \param handler  Interrupt handler function pointer.
 * \param arg Pointer to interrupt handler argument
 */
void PIO_ConfigureIt(const Pin *pPin, void (*handler)(const Pin *, void *arg),
    void *arg)
{
	InterruptSource *pSource;
	rtems_interrupt_level level;

	TRACE_DEBUG("PIO_ConfigureIt()\n\r");

	rtems_interrupt_disable(level);

	if (_dwNumSources == MAX_INTERRUPT_SOURCES) {
		bsp_fatal(ATSAM_FATAL_PIO_CONFIGURE_IT);
	}

	pSource = &(_aIntSources[_dwNumSources]);
	pSource->pPin = pPin;
	pSource->handler = handler;
	pSource->arg = arg;

	_dwNumSources++;

	rtems_interrupt_enable(level);

	/* Define new source */
	TRACE_DEBUG("PIO_ConfigureIt: Defining new source #%d.\n\r", _dwNumSources);
}

/**
 * Search for a PIO interrupt and remove it if it is there.
 * \param pPin  Pointer to a Pin instance.
 * \param handler  Interrupt handler function pointer.
 * \param arg Pointer to interrupt handler argument
 * \return RTEMS_SUCCESSFUL if removed.
 * \return RTEMS_UNSATISFIED if the handler couldn't be found
 */
rtems_status_code PIO_RemoveIt(const Pin *pPin,
    void (*handler)(const Pin *, void *arg), void *arg)
{
	InterruptSource *pSource;
	rtems_interrupt_level level;
	rtems_status_code sc = RTEMS_UNSATISFIED;
	uint32_t i;

	TRACE_DEBUG("PIO_RemoveIt()\n\r");

	rtems_interrupt_disable(level);

	for(i = 0; i < _dwNumSources; ++i) {
		pSource = &(_aIntSources[_dwNumSources]);
		if(pSource->pPin == pPin &&
		    pSource->handler == handler &&
		    pSource->arg == arg) {
			if(i + 1 < _dwNumSources) {
				TRACE_DEBUG("PIO_RemoveIt: Remove #%d.\n\r", i);
				/* Move remaining sources */
				memcpy(pSource,
				    &(_aIntSources[i+1]),
				    sizeof(_aIntSources[0])*(_dwNumSources-i-1)
				    );
			}
			_dwNumSources--;
			sc = RTEMS_SUCCESSFUL;
		}
	}

	rtems_interrupt_enable(level);

	return sc;
}
