/**
 * \file
 *
 * \brief SAM TC - Timer Counter Callback Driver
 *
 * Copyright (c) 2013-2018 Microchip Technology Inc. and its subsidiaries.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip
 * software and any derivatives exclusively with Microchip products.
 * It is your responsibility to comply with third party license terms applicable
 * to your use of third party software (including open source software) that
 * may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * \asf_license_stop
 *
 */

/*
 * Support and FAQ: visit <a href="https://www.microchip.com/support/">Microchip Support</a>
 */

#include "tc_interrupt.h"

void *_tc_instances[TC_INST_NUM];

void _tc_interrupt_handler(uint8_t instance);

/**
 * \brief Registers a callback.
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref tc_enable_callback,
 * in order for the interrupt handler to call it when the conditions for the
 * callback type is met.
 *
 * \param[in]     module        Pointer to TC software instance struct
 * \param[in]     callback_func Pointer to callback function
 * \param[in]     callback_type Callback type given by an enum
 */
enum status_code tc_register_callback(
		struct tc_module *const module,
		tc_callback_t callback_func,
		const enum tc_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(callback_func);

	/* Register callback function */
	module->callback[callback_type] = callback_func;

	/* Set the bit corresponding to the callback_type */
	if (callback_type == TC_CALLBACK_CC_CHANNEL0) {
		module->register_callback_mask |= TC_INTFLAG_MC(1);
	}
	else if (callback_type == TC_CALLBACK_CC_CHANNEL1) {
		module->register_callback_mask |= TC_INTFLAG_MC(2);
	}
	else {
		module->register_callback_mask |= (1 << callback_type);
	}
	return STATUS_OK;
}

/**
 * \brief Unregisters a callback.
 *
 * Unregisters a callback function implemented by the user. The callback should be
 * disabled before it is unregistered.
 *
 * \param[in]     module        Pointer to TC software instance struct
 * \param[in]     callback_type Callback type given by an enum
 */
enum status_code tc_unregister_callback(
		struct tc_module *const module,
		const enum tc_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module);

	/* Unregister callback function */
	module->callback[callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	if (callback_type == TC_CALLBACK_CC_CHANNEL0) {
		module->register_callback_mask &= ~TC_INTFLAG_MC(1);
	}
	else if (callback_type == TC_CALLBACK_CC_CHANNEL1) {
		module->register_callback_mask &= ~TC_INTFLAG_MC(2);
	}
	else {
		module->register_callback_mask &= ~(1 << callback_type);
	}
	return STATUS_OK;
}

/**
 * \internal ISR handler for TC
 *
 * Auto-generate a set of interrupt handlers for each TC in the device.
 */
#define _TC_INTERRUPT_HANDLER(n, m) \
		void TC##n##_Handler(void) \
		{ \
			_tc_interrupt_handler(m); \
		}

#if (SAML21E) || (SAML21G) || (SAMR30E) || (SAMR30G)
	_TC_INTERRUPT_HANDLER(0,0)
	_TC_INTERRUPT_HANDLER(1,1)
	_TC_INTERRUPT_HANDLER(4,2)
#else
	MRECURSION(TC_INST_NUM, _TC_INTERRUPT_HANDLER, TC_INST_MAX_ID)
#endif


/**
 * \internal Interrupt Handler for TC module
 *
 * Handles interrupts as they occur, it will run the callback functions
 * that are registered and enabled.
 *
 * \param[in]  instance  ID of the TC instance calling the interrupt
 *                       handler
 */
void _tc_interrupt_handler(
		uint8_t instance)
{
	/* Temporary variable */
	uint8_t interrupt_and_callback_status_mask;

	/* Get device instance from the look-up table */
	struct tc_module *module
			= (struct tc_module *)_tc_instances[instance];

	/* Read and mask interrupt flag register */
	interrupt_and_callback_status_mask = module->hw->COUNT8.INTFLAG.reg &
			module->register_callback_mask &
			module->enable_callback_mask;

	/* Check if an Overflow interrupt has occurred */
	if (interrupt_and_callback_status_mask & TC_INTFLAG_OVF) {
		/* Invoke registered and enabled callback function */
		(module->callback[TC_CALLBACK_OVERFLOW])(module);
		/* Clear interrupt flag */
		module->hw->COUNT8.INTFLAG.reg = TC_INTFLAG_OVF;
	}

	/* Check if an Error interrupt has occurred */
	if (interrupt_and_callback_status_mask & TC_INTFLAG_ERR) {
		/* Invoke registered and enabled callback function */
		(module->callback[TC_CALLBACK_ERROR])(module);
		/* Clear interrupt flag */
		module->hw->COUNT8.INTFLAG.reg = TC_INTFLAG_ERR;
	}

	/* Check if an Match/Capture Channel 0 interrupt has occurred */
	if (interrupt_and_callback_status_mask & TC_INTFLAG_MC(1)) {
		/* Invoke registered and enabled callback function */
		(module->callback[TC_CALLBACK_CC_CHANNEL0])(module);
		/* Clear interrupt flag */
		module->hw->COUNT8.INTFLAG.reg = TC_INTFLAG_MC(1);
	}

	/* Check if an Match/Capture Channel 1 interrupt has occurred */
	if (interrupt_and_callback_status_mask & TC_INTFLAG_MC(2)) {
		/* Invoke registered and enabled callback function */
		(module->callback[TC_CALLBACK_CC_CHANNEL1])(module);
		/* Clear interrupt flag */
		module->hw->COUNT8.INTFLAG.reg = TC_INTFLAG_MC(2);
	}
}
