/**
 * \file
 *
 * \brief SAM D20 SERCOM USART Asynchronous Driver
 *
 * Copyright (C) 2012-2013 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * 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 "usart_interrupt.h"

/**
 * \internal
 * Asynchronous write of a buffer with a given length
 *
 * \param[in]  module   Pointer to USART software instance struct
 * \param[in]  tx_data  Pointer to data to be transmitted
 * \param[in]  length   Length of data buffer
 *
 */
void _usart_write_buffer(
		struct usart_module *const module,
		uint8_t *tx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

	/* Get a pointer to the hardware module instance */
	SercomUsart *const usart_hw = &(module->hw->USART);

	/* Write parameters to the device instance */
	module->remaining_tx_buffer_length = length;
	module->tx_buffer_ptr              = tx_data;
	module->tx_status                  = STATUS_BUSY;

	/* Enable the Data Register Empty Interrupt */
	usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_DRE;
}

/**
 * \internal
 * Asynchronous read of a buffer with a given length
 *
 * \param[in]  module   Pointer to USART software instance struct
 * \param[in]  rx_data  Pointer to data to be received
 * \param[in]  length   Length of data buffer
 *
 */
void _usart_read_buffer(
		struct usart_module *const module,
		uint8_t *rx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

	/* Get a pointer to the hardware module instance */
	SercomUsart *const usart_hw = &(module->hw->USART);

	/* Set length for the buffer and the pointer, and let
	 * the interrupt handler do the rest */
	module->remaining_rx_buffer_length = length;
	module->rx_buffer_ptr              = rx_data;
	module->rx_status                  = STATUS_BUSY;

	/* Enable the RX Complete Interrupt */
	usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_RXC;
}

/**
 * \brief Registers a callback
 *
 * Registers a callback function which is implemented by the user.
 *
 * \note The callback must be enabled by \ref usart_enable_callback,
 *       in order for the interrupt handler to call it when the conditions for
 *       the callback type are met.
 *
 * \param[in]  module         Pointer to USART software instance struct
 * \param[in]  callback_func  Pointer to callback function
 * \param[in]  callback_type  Callback type given by an enum
 *
 */
void usart_register_callback(
		struct usart_module *const module,
		usart_callback_t callback_func,
		enum usart_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 */
	module->callback_reg_mask |= (1 << callback_type);
}

/**
 * \brief Unregisters a callback
 *
 * Unregisters a callback function which is implemented by the user.
 *
 * \param[in,out]  module         Pointer to USART software instance struct
 * \param[in]      callback_type  Callback type given by an enum
 *
 */
void usart_unregister_callback(
		struct usart_module *const module,
		enum usart_callback callback_type)
{
	/* Sanity check arguments */
	Assert(module);

	/* Unregister callback function */
	module->callback[callback_type] = NULL;

	/* Clear the bit corresponding to the callback_type */
	module->callback_reg_mask &= ~(1 << callback_type);
}

/**
 * \brief Asynchronous write a single char
 *
 * Sets up the driver to write the data given. If registered and enabled,
 * a callback function will be called when the transmit is completed.
 *
 * \param[in]  module   Pointer to USART software instance struct
 * \param[in]  tx_data  Data to transfer
 *
 * \returns Status of the operation
 * \retval STATUS_OK         If operation was completed
 * \retval STATUS_BUSY       If operation was not completed, due to the
 *                           USART module being busy
 * \retval STATUS_ERR_DENIED If the transmitter is not enabled
 */
enum status_code usart_write_job(
		struct usart_module *const module,
		const uint16_t tx_data)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);
	/* Check if the USART transmitter is busy */
	if (module->remaining_tx_buffer_length > 0) {
		return STATUS_BUSY;
	}

	/* Check that the transmitter is enabled */
	if (!(module->transmitter_enabled)) {
		return STATUS_ERR_DENIED;
	}

	/* Call internal write buffer function with length 1 */
	_usart_write_buffer(module, (uint8_t *)&tx_data, 1);

	return STATUS_OK;
}

/**
 * \brief Asynchronous read a single char
 *
 * Sets up the driver to read data from the USART module to the data
 * pointer given. If registered and enabled, a callback will be called
 * when the receiving is completed.
 *
 * \param[in]   module   Pointer to USART software instance struct
 * \param[out]  rx_data  Pointer to where received data should be put
 *
 * \returns Status of the operation
 * \retval  STATUS_OK    If operation was completed
 * \retval  STATUS_BUSY  If operation was not completed,
 */
enum status_code usart_read_job(
		struct usart_module *const module,
		uint16_t *const rx_data)
{
	/* Sanity check arguments */
	Assert(module);

	/* Check if the USART receiver is busy */
	if (module->remaining_rx_buffer_length > 0) {
		return STATUS_BUSY;
	}

	/* Call internal read buffer function with length 1 */
	_usart_read_buffer(module, (uint8_t *)rx_data, 1);

	return STATUS_OK;
}

/**
 * \brief Asynchronous buffer write
 *
 * Sets up the driver to write a given buffer over the USART. If registered and
 * enabled, a callback function will be called.
 *
 * \param[in]  module   Pointer to USART software instance struct
 * \param[in]  tx_data  Pointer do data buffer to transmit
 * \param[in]  length   Length of the data to transmit
 *
 * \returns Status of the operation
 * \retval STATUS_OK              If operation was completed successfully.
 * \retval STATUS_BUSY            If operation was not completed, due to the
 *                                USART module being busy
 * \retval STATUS_ERR_INVALID_ARG If operation was not completed, due to invalid
 *                                arguments
 * \retval STATUS_ERR_DENIED      If the transmitter is not enabled
 */
enum status_code usart_write_buffer_job(
		struct usart_module *const module,
		uint8_t *tx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);

	if (length == 0) {
		return STATUS_ERR_INVALID_ARG;
	}

	/* Check if the USART transmitter is busy */
	if (module->remaining_tx_buffer_length > 0) {
		return STATUS_BUSY;
	}
	
	/* Check that the receiver is enabled */
	if (!(module->transmitter_enabled)) {
		return STATUS_ERR_DENIED;
	}

	/* Issue internal asynchronous write */
	_usart_write_buffer(module, tx_data, length);

	return STATUS_OK;
}

/**
 * \brief Asynchronous buffer read
 *
 * Sets up the driver to read from the USART to a given buffer. If registered
 * and enabled, a callback function will be called.
 *
 * \param[in]  module   Pointer to USART software instance struct
 * \param[out] rx_data  Pointer to data buffer to receive
 * \param[in]  length   Data buffer length
 *
 * \returns Status of the operation
 * \retval STATUS_OK              If operation was completed
 * \retval STATUS_BUSY            If operation was not completed, due to the
 *                                USART module being busy
 * \retval STATUS_ERR_INVALID_ARG If operation was not completed, due to invalid
 *                                arguments
 * \retval STATUS_ERR_DENIED      If the transmitter is not enabled
 */
enum status_code usart_read_buffer_job(
		struct usart_module *const module,
		uint8_t *rx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(rx_data);

	if (length == 0) {
		return STATUS_ERR_INVALID_ARG;
	}
	
	/* Check that the receiver is enabled */
	if (!(module->receiver_enabled)) {
		return STATUS_ERR_DENIED;
	}

	/* Check if the USART receiver is busy */
	if (module->remaining_rx_buffer_length > 0) {
		return STATUS_BUSY;
	}

	/* Issue internal asynchronous read */
	_usart_read_buffer(module, rx_data, length);

	return STATUS_OK;
}

/**
 * \brief Cancels ongoing read/write operation
 *
 * Cancels the ongoing read/write operation modifying parameters in the
 * USART software struct.
 *
 * \param[in]  module            Pointer to USART software instance struct
 * \param[in]  transceiver_type  Transfer type to cancel
 */
void usart_abort_job(
		struct usart_module *const module,
		enum usart_transceiver_type transceiver_type)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

	/* Get a pointer to the hardware module instance */
	SercomUsart *const usart_hw = &(module->hw->USART);

	switch(transceiver_type) {
		case USART_TRANSCEIVER_RX:
			/* Clear the interrupt flag in order to prevent the receive
			 * complete callback to fire */
			usart_hw->INTFLAG.reg |= SERCOM_USART_INTFLAG_RXC;

			/* Clear the software reception buffer */
			module->remaining_rx_buffer_length = 0;

			break;

		case USART_TRANSCEIVER_TX:
			/* Clear the interrupt flag in order to prevent the receive
			 * complete callback to fire */
			usart_hw->INTFLAG.reg |= SERCOM_USART_INTFLAG_TXC;

			/* Clear the software reception buffer */
			module->remaining_tx_buffer_length = 0;

			break;
	}
}

/**
 * \brief Get status from the ongoing or last asynchronous transfer operation
 *
 * Returns the error from a given ongoing or last asynchronous transfer operation.
 * Either from a read or write transfer.
 *
 * \param[in]  module            Pointer to USART software instance struct
 * \param[in]  transceiver_type  Transfer type to check
  *
 * \return Status of the given job.
 * \retval STATUS_OK               No error occurred during the last transfer
 * \retval STATUS_BUSY             A transfer is ongoing
 * \retval STATUS_ERR_BAD_DATA     The last operation was aborted due to a
 *                                 parity error. The transfer could be affected
 *                                 by external noise.
 * \retval STATUS_ERR_BAD_FORMAT   The last operation was aborted due to a
 *                                 frame error.
 * \retval STATUS_ERR_OVERFLOW     The last operation was aborted due to a
 *                                 buffer overflow.
 * \retval STATUS_ERR_INVALID_ARG  An invalid transceiver enum given.
 */
enum status_code usart_get_job_status(
		struct usart_module *const module,
		enum usart_transceiver_type transceiver_type)
{
	/* Sanity check arguments */
	Assert(module);

	/* Variable for status code */
	enum status_code status_code;

	switch(transceiver_type) {
	case USART_TRANSCEIVER_RX:
			status_code = module->rx_status;
			break;

	case USART_TRANSCEIVER_TX:
			status_code = module->tx_status;
			break;

	default:
			status_code = STATUS_ERR_INVALID_ARG;
			break;
	}

	return status_code;
}

/**
 * \internal
 * Handles interrupts as they occur, and it will run callback functions
 * which are registered and enabled.
 *
 * \param[in]  instance  ID of the SERCOM instance calling the interrupt
 *                       handler.
 */
void _usart_interrupt_handler(
		uint8_t instance)
{
	/* Temporary variables */
	uint16_t interrupt_status;
	uint16_t callback_status;
	uint8_t error_code;


	/* Get device instance from the look-up table */
	struct usart_module *module
		= (struct usart_module *)_sercom_instances[instance];

	/* Pointer to the hardware module instance */
	SercomUsart *const usart_hw
		= &(module->hw->USART);

	/* Wait for the synchronization to complete */
	_usart_wait_for_sync(module);

	/* Read and mask interrupt flag register */
	interrupt_status = usart_hw->INTFLAG.reg;
	callback_status = module->callback_reg_mask
			&module->callback_enable_mask;

	/* Check if a DATA READY interrupt has occurred,
	 * and if there is more to transfer */
	if (interrupt_status & SERCOM_USART_INTFLAG_DRE) {
		if (module->remaining_tx_buffer_length) {
			/* Write value will be at least 8-bits long */
			uint16_t data_to_send = *(module->tx_buffer_ptr);
			/* Increment 8-bit pointer */
			(module->tx_buffer_ptr)++;

			if (module->character_size == USART_CHARACTER_SIZE_9BIT) {
				data_to_send = (*(module->tx_buffer_ptr) << 8);
				/* Increment 8-bit pointer */
				(module->tx_buffer_ptr)++;
			}
			/* Write the data to send */
			usart_hw->DATA.reg = (data_to_send & SERCOM_USART_DATA_MASK);

			if (--(module->remaining_tx_buffer_length) == 0) {
				/* Disable the Data Register Empty Interrupt */
				usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_DRE;
				/* Enable Transmission Complete interrupt */
				usart_hw->INTENSET.reg = SERCOM_USART_INTFLAG_TXC;

			}
		} else {
			usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_DRE;
		}

	/* Check if the Transmission Complete interrupt has occurred and
	 * that the transmit buffer is empty */
	}
	if (interrupt_status & SERCOM_USART_INTFLAG_TXC) {

		/* Disable TX Complete Interrupt, and set STATUS_OK */
		usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_TXC;
		module->tx_status = STATUS_OK;

		/* Run callback if registered and enabled */
		if( module->remaining_tx_buffer_length == 0 ) /* Added by _RB_ */
		{
			if (callback_status & (1 << USART_CALLBACK_BUFFER_TRANSMITTED)) {
				(*(module->callback[USART_CALLBACK_BUFFER_TRANSMITTED]))(module);
			}
		}

	/* Check if the Receive Complete interrupt has occurred, and that
	 * there's more data to receive */
	}
	if (interrupt_status & SERCOM_USART_INTFLAG_RXC) {

		if (module->remaining_rx_buffer_length) {
			/* Read out the status code and mask away all but the 4 LSBs*/
			error_code = (uint8_t)(usart_hw->STATUS.reg & SERCOM_USART_STATUS_MASK);

			/* Check if an error has occurred during the receiving */
			if (error_code) {
				/* Check which error occurred */
				if (error_code & SERCOM_USART_STATUS_FERR) {
					/* Store the error code and clear flag by writing 1 to it */
					module->rx_status = STATUS_ERR_BAD_FORMAT;
					usart_hw->STATUS.reg |= SERCOM_USART_STATUS_FERR;
				} else if (error_code & SERCOM_USART_STATUS_BUFOVF) {
					/* Store the error code and clear flag by writing 1 to it */
					module->rx_status = STATUS_ERR_OVERFLOW;
					usart_hw->STATUS.reg |= SERCOM_USART_STATUS_BUFOVF;
				} else if (error_code & SERCOM_USART_STATUS_PERR) {
					/* Store the error code and clear flag by writing 1 to it */
					module->rx_status = STATUS_ERR_BAD_DATA;
					usart_hw->STATUS.reg |= SERCOM_USART_STATUS_PERR;
				}

				/* Run callback if registered and enabled */
				if (callback_status
						& (1 << USART_CALLBACK_ERROR)) {
					(*(module->callback[USART_CALLBACK_ERROR]))(module);
				}

			} else {

				/* Read current packet from DATA register,
				 * increment buffer pointer and decrement buffer length */
				uint16_t received_data = (usart_hw->DATA.reg & SERCOM_USART_DATA_MASK);

				/* Read value will be at least 8-bits long */
				*(module->rx_buffer_ptr) = received_data;
				/* Increment 8-bit pointer */
				module->rx_buffer_ptr += 1;

				if (module->character_size == USART_CHARACTER_SIZE_9BIT) {
					/* 9-bit data, write next received byte to the buffer */
					*(module->rx_buffer_ptr) = (received_data >> 8);
					/* Increment 8-bit pointer */
					module->rx_buffer_ptr += 1;
				}

				/* Check if the last character have been received */
				if(--(module->remaining_rx_buffer_length) == 0) {
					/* Disable RX Complete Interrupt,
					 * and set STATUS_OK */
					usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_RXC;
					module->rx_status = STATUS_OK;

					/* Run callback if registered and enabled */
					if (callback_status
							& (1 << USART_CALLBACK_BUFFER_RECEIVED)) {
						(*(module->callback[USART_CALLBACK_BUFFER_RECEIVED]))(module);
					}
				}
			}
		} else {
			/* This should not happen. Disable Receive Complete interrupt. */
			usart_hw->INTENCLR.reg = SERCOM_USART_INTFLAG_RXC;
		}
	}
}
