/**
 * \file
 *
 * \brief SAM Serial Peripheral Interface Driver
 *
 * Copyright (c) 2012-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 "spi.h"

/**
 * \brief Resets the SPI module
 *
 * This function will reset the SPI module to its power on default values and
 * disable it.
 *
 * \param[in,out] module Pointer to the software instance struct
 */
void spi_reset(
		struct spi_module *const module)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

	SercomSpi *const spi_module = &(module->hw->SPI);

	/* Disable the module */
	spi_disable(module);

	while (spi_is_syncing(module)) {
		/* Wait until the synchronization is complete */
	}

	/* Software reset the module */
	spi_module->CTRLA.reg |= SERCOM_SPI_CTRLA_SWRST;
}

/**
 * \brief Set the baudrate of the SPI module
 *
 * This function will set the baudrate of the SPI module.
 *
 * \param[in]  module  Pointer to the software instance struct
 * \param[in]  baudrate  The baudrate wanted
 *
 * \return The status of the configuration.
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 * \retval STATUS_OK               If the configuration was written
 */
enum status_code spi_set_baudrate(
		struct spi_module *const module,
		uint32_t baudrate)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(baudrate);
	Assert(module->hw);

	/* Value to write to BAUD register */
	uint16_t baud = 0;

	SercomSpi *const spi_module = &(module->hw->SPI);

	/* Disable the module */
	spi_disable(module);

	while (spi_is_syncing(module)) {
		/* Wait until the synchronization is complete */
	}

	/* Find frequency of the internal SERCOMi_GCLK_ID_CORE */
	uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);
	uint32_t gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
	uint32_t internal_clock = system_gclk_chan_get_hz(gclk_index);

	/* Get baud value, based on baudrate and the internal clock frequency */
	enum status_code error_code = _sercom_get_sync_baud_val(
			baudrate, internal_clock, &baud);

	if (error_code != STATUS_OK) {
		/* Baud rate calculation error, return status code */
		return STATUS_ERR_INVALID_ARG;
	}

	spi_module->BAUD.reg = (uint8_t)baud;

	while (spi_is_syncing(module)) {
		/* Wait until the synchronization is complete */
	}

	/* Enable the module */
	spi_enable(module);

	while (spi_is_syncing(module)) {
		/* Wait until the synchronization is complete */
	}

	return STATUS_OK;
}

#  if CONF_SPI_SLAVE_ENABLE == true
/**
 * \internal Clears the Transmit Complete interrupt flag.
 *
 * \param[in]  module  Pointer to the software instance struct
 */
static void _spi_clear_tx_complete_flag(
		struct spi_module *const module)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

	SercomSpi *const spi_module = &(module->hw->SPI);

	/* Clear interrupt flag */
	spi_module->INTFLAG.reg = SPI_INTERRUPT_FLAG_TX_COMPLETE;
}
#  endif

/**
 * \internal Writes an SPI SERCOM configuration to the hardware module.
 *
 * This function will write out a given configuration to the hardware module.
 * Can only be done when the module is disabled.
 *
 * \param[in]  module  Pointer to the software instance struct
 * \param[in]  config  Pointer to the configuration struct
 *
 * \return The status of the configuration.
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 * \retval STATUS_OK               If the configuration was written
 */
static enum status_code _spi_set_config(
		struct spi_module *const module,
		const struct spi_config *const config)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(config);
	Assert(module->hw);

	SercomSpi *const spi_module = &(module->hw->SPI);
	Sercom *const hw = module->hw;

	struct system_pinmux_config pin_conf;
	system_pinmux_get_config_defaults(&pin_conf);
	pin_conf.direction = SYSTEM_PINMUX_PIN_DIR_INPUT;
	if(config->mode == SPI_MODE_SLAVE) {
		pin_conf.input_pull = SYSTEM_PINMUX_PIN_PULL_NONE;
	}

	uint32_t pad_pinmuxes[] = {
			config->pinmux_pad0, config->pinmux_pad1,
			config->pinmux_pad2, config->pinmux_pad3
		};

	/* Configure the SERCOM pins according to the user configuration */
	for (uint8_t pad = 0; pad < 4; pad++) {
		uint32_t current_pinmux = pad_pinmuxes[pad];

		if (current_pinmux == PINMUX_DEFAULT) {
			current_pinmux = _sercom_get_default_pad(hw, pad);
		}

		if (current_pinmux != PINMUX_UNUSED) {
			pin_conf.mux_position = current_pinmux & 0xFFFF;
			system_pinmux_pin_set_config(current_pinmux >> 16, &pin_conf);
		}
	}

	module->mode             = config->mode;
	module->character_size   = config->character_size;
	module->receiver_enabled = config->receiver_enable;
#  ifdef FEATURE_SPI_HARDWARE_SLAVE_SELECT
	module->master_slave_select_enable = config->master_slave_select_enable;
#  endif

#  if CONF_SPI_MASTER_ENABLE == true
	/* Value to write to BAUD register */
	uint16_t baud = 0;
#  endif
	/* Value to write to CTRLA register */
	uint32_t ctrla = 0;
	/* Value to write to CTRLB register */
	uint32_t ctrlb = 0;

# if CONF_SPI_MASTER_ENABLE == true
	/* Find baud value and write it */
	if (config->mode == SPI_MODE_MASTER) {
		/* Find frequency of the internal SERCOMi_GCLK_ID_CORE */
		uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);
		uint32_t gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
		uint32_t internal_clock = system_gclk_chan_get_hz(gclk_index);

		/* Get baud value, based on baudrate and the internal clock frequency */
		enum status_code error_code = _sercom_get_sync_baud_val(
				config->mode_specific.master.baudrate,
				internal_clock, &baud);

		if (error_code != STATUS_OK) {
			/* Baud rate calculation error, return status code */
			return STATUS_ERR_INVALID_ARG;
		}

		spi_module->BAUD.reg = (uint8_t)baud;
	}
# endif
# if CONF_SPI_SLAVE_ENABLE == true
	if (config->mode == SPI_MODE_SLAVE) {
		/* Set frame format */
		ctrla = config->mode_specific.slave.frame_format;

		/* Set address mode */
		ctrlb = config->mode_specific.slave.address_mode;

		/* Set address and address mask*/
		spi_module->ADDR.reg |=
				(config->mode_specific.slave.address      << SERCOM_SPI_ADDR_ADDR_Pos) |
				(config->mode_specific.slave.address_mask << SERCOM_SPI_ADDR_ADDRMASK_Pos);

		if (config->mode_specific.slave.preload_enable) {
			/* Enable pre-loading of shift register */
			ctrlb |= SERCOM_SPI_CTRLB_PLOADEN;
		}
	}
# endif
	/* Set data order */
	ctrla |= config->data_order;

	/* Set clock polarity and clock phase */
	ctrla |= config->transfer_mode;

	/* Set MUX setting */
	ctrla |= config->mux_setting;

	/* Set SPI character size */
	ctrlb |= config->character_size;

	/* Set whether module should run in standby. */
	if (config->run_in_standby || system_is_debugger_present()) {
		ctrla |= SERCOM_SPI_CTRLA_RUNSTDBY;
	}

	if (config->receiver_enable) {
		/* Enable receiver */
		ctrlb |= SERCOM_SPI_CTRLB_RXEN;
	}
#  ifdef FEATURE_SPI_SLAVE_SELECT_LOW_DETECT
	if (config->select_slave_low_detect_enable) {
		/* Enable Slave Select Low Detect */
		ctrlb |= SERCOM_SPI_CTRLB_SSDE;
	}
#  endif
#  ifdef FEATURE_SPI_HARDWARE_SLAVE_SELECT
	if (config->master_slave_select_enable) {
		/* Enable Master Slave Select */
		ctrlb |= SERCOM_SPI_CTRLB_MSSEN;
	}
#  endif
	/* Write CTRLA register */
	spi_module->CTRLA.reg |= ctrla;

	/* Write CTRLB register */
	spi_module->CTRLB.reg |= ctrlb;

	return STATUS_OK;
}

#if SPI_CALLBACK_MODE == false
/**
 * \internal Checks an SPI config against current set config
 *
 * This function will check that the config does not alter the
 * configuration of the module. If the new config changes any
 * setting, the initialization will be discarded.
 *
 * \param[in]  module  Pointer to the software instance struct
 * \param[in]  config  Pointer to the configuration struct
 *
 * \return The status of the configuration.
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 * \retval STATUS_ERR_DENIED       If configuration was different from previous
 * \retval STATUS_OK               If the configuration was written
 */
static enum status_code _spi_check_config(
		struct spi_module *const module,
		const struct spi_config *const config)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(config);
	Assert(module->hw);

	SercomSpi *const spi_module = &(module->hw->SPI);
	Sercom *const hw = module->hw;

	uint32_t pad_pinmuxes[] = {
		config->pinmux_pad0, config->pinmux_pad1,
		config->pinmux_pad2, config->pinmux_pad3
	};

	/* Compare the current SERCOM pins against the user configuration */
	for (uint8_t pad = 0; pad < 4; pad++) {
		uint32_t current_pinmux = pad_pinmuxes[pad];

		if (current_pinmux == PINMUX_DEFAULT) {
			current_pinmux = _sercom_get_default_pad(hw, pad);
		}

		if (current_pinmux == PINMUX_UNUSED) {
			continue;
		}

		if ((current_pinmux & 0xFFFF) !=
				system_pinmux_pin_get_mux_position(current_pinmux >> 16)) {
			module->hw = NULL;
			return STATUS_ERR_DENIED;
		}
	}

#  if CONF_SPI_MASTER_ENABLE == true
	/* Value to read BAUD register */
	uint16_t baud;
	uint32_t external_clock = system_gclk_chan_get_hz(SERCOM_GCLK_ID);
#  endif
	/* Value to read CTRLA, CTRLB and ADDR register */
	uint32_t ctrla = 0;
	uint32_t ctrlb = 0;
#  if CONF_SPI_SLAVE_ENABLE == true
	uint32_t addr = 0;
#  endif

#  if CONF_SPI_MASTER_ENABLE == true
	/* Find baud value and compare it */
	if (config->mode == SPI_MODE_MASTER) {
		enum status_code error_code = _sercom_get_sync_baud_val(
				config->mode_specific.master.baudrate,
				external_clock, &baud);

		if (error_code != STATUS_OK) {
			/* Baud rate calculation error, return status code */
			return STATUS_ERR_INVALID_ARG;
		}

		if (spi_module->BAUD.reg !=  (uint8_t)baud) {
			return STATUS_ERR_DENIED;
		}

		ctrla |= SERCOM_SPI_CTRLA_MODE(0x3);
	}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
	if (config->mode == SPI_MODE_SLAVE) {

		/* Set frame format */
		ctrla |= config->mode_specific.slave.frame_format;

		/* Set address mode */
		ctrlb |= config->mode_specific.slave.address_mode;

		/* Set address and address mask*/
		addr |= (config->mode_specific.slave.address      << SERCOM_SPI_ADDR_ADDR_Pos) |
				(config->mode_specific.slave.address_mask << SERCOM_SPI_ADDR_ADDRMASK_Pos);
		if (spi_module->CTRLA.reg != addr) {
			return STATUS_ERR_DENIED;
		}

		if (config->mode_specific.slave.preload_enable) {
			/* Enable pre-loading of shift register */
			ctrlb |= SERCOM_SPI_CTRLB_PLOADEN;
		}
		ctrla |= SERCOM_SPI_CTRLA_MODE(0x2);
	}
#  endif
	/* Set data order */
	ctrla |= config->data_order;

	/* Set clock polarity and clock phase */
	ctrla |= config->transfer_mode;

	/* Set MUX setting */
	ctrla |= config->mux_setting;

	/* Set SPI character size */
	ctrlb |= config->character_size;

	if (config->run_in_standby) {
		/* Enable in sleep mode */
		ctrla |= SERCOM_SPI_CTRLA_RUNSTDBY;
	}

	if (config->receiver_enable) {
		/* Enable receiver */
		ctrlb |= SERCOM_SPI_CTRLB_RXEN;
	}

#  ifdef FEATURE_SPI_SLAVE_SELECT_LOW_DETECT
	if (config->select_slave_low_detect_enable) {
		/* Enable Slave Select Low Detect */
		ctrlb |= SERCOM_SPI_CTRLB_SSDE;
	}
#  endif
#  ifdef FEATURE_SPI_HARDWARE_SLAVE_SELECT
	if (config->master_slave_select_enable) {
		/* Enable Master Slave Select */
		ctrlb |= SERCOM_SPI_CTRLB_MSSEN;
	}
#  endif

	ctrla |= SERCOM_SPI_CTRLA_ENABLE;

	/* Check that same config is set */
	if (spi_module->CTRLA.reg == ctrla &&
			spi_module->CTRLB.reg == ctrlb) {
		module->mode           = config->mode;
		module->character_size = config->character_size;
		return STATUS_OK;
	}

	/* Not same config, wipe module pointer and return */
	module->hw = NULL;

	return STATUS_ERR_DENIED;
}
#endif

/**
 * \brief Initializes the SERCOM SPI module
 *
 * This function will initialize the SERCOM SPI module, based on the values
 * of the config struct.
 *
 * \param[out]  module  Pointer to the software instance struct
 * \param[in]   hw      Pointer to hardware instance
 * \param[in]   config  Pointer to the config struct
 *
 * \return Status of the initialization.
 * \retval STATUS_OK               Module initiated correctly
 * \retval STATUS_ERR_DENIED       If module is enabled
 * \retval STATUS_BUSY             If module is busy resetting
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 */
enum status_code spi_init(
		struct spi_module *const module,
		Sercom *const hw,
		const struct spi_config *const config)
{

	/* Sanity check arguments */
	Assert(module);
	Assert(hw);
	Assert(config);

	/* Initialize device instance */
	module->hw = hw;

	SercomSpi *const spi_module = &(module->hw->SPI);

	/* Check if module is enabled. */
	if (spi_module->CTRLA.reg & SERCOM_SPI_CTRLA_ENABLE) {
#  if SPI_CALLBACK_MODE == false
		/* Check if config is valid */
		return _spi_check_config(module, config);
#  else
		return STATUS_ERR_DENIED;
#  endif
	}

	/* Check if reset is in progress. */
	if (spi_module->CTRLA.reg & SERCOM_SPI_CTRLA_SWRST){
		return STATUS_BUSY;
	}

	uint32_t sercom_index = _sercom_get_sercom_inst_index(module->hw);
	uint32_t pm_index, gclk_index;
#if (SAML21) || (SAMR30) || (SAMR34) || (SAMR35)
	if (sercom_index == 5) {
#  ifdef ID_SERCOM5
		pm_index     = MCLK_APBDMASK_SERCOM5_Pos;
		gclk_index   =  SERCOM5_GCLK_ID_CORE;
#  else
		return STATUS_ERR_INVALID_ARG;
#  endif
	} else {
		pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
		gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
	}
#elif (SAMC21) 
	if (sercom_index == 5) {
#  ifdef ID_SERCOM5
		pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
		gclk_index   =  SERCOM5_GCLK_ID_CORE;
#  else
		return STATUS_ERR_INVALID_ARG;
#  endif
	} else {
		pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
		gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
	}
#elif (SAMC20) || (SAML22)
	pm_index     = sercom_index + MCLK_APBCMASK_SERCOM0_Pos;
	gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
#else
	pm_index     = sercom_index + PM_APBCMASK_SERCOM0_Pos;
	gclk_index   = sercom_index + SERCOM0_GCLK_ID_CORE;
#endif

	/* Turn on module in PM */
#if (SAML21) || (SAMR30) || (SAMR34) || (SAMR35)
	if (sercom_index == 5) {
#  ifdef ID_SERCOM5
		system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBD, 1 << pm_index);
#  else
		return STATUS_ERR_INVALID_ARG;
#  endif
	} else {
		system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index);
	}
#else
	system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, 1 << pm_index);
#endif

	/* Set up the GCLK for the module */
	struct system_gclk_chan_config gclk_chan_conf;
	system_gclk_chan_get_config_defaults(&gclk_chan_conf);
	gclk_chan_conf.source_generator = config->generator_source;
	system_gclk_chan_set_config(gclk_index, &gclk_chan_conf);
	system_gclk_chan_enable(gclk_index);
	sercom_set_gclk_generator(config->generator_source, false);

#  if CONF_SPI_MASTER_ENABLE == true
	if (config->mode == SPI_MODE_MASTER) {
		/* Set the SERCOM in SPI master mode */
		spi_module->CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x3);
	}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
	if (config->mode == SPI_MODE_SLAVE) {
		/* Set the SERCOM in SPI slave mode */
		spi_module->CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(0x2);
	}
#  endif

#if SPI_CALLBACK_MODE == true
	/* Temporary variables */
	uint8_t i;
	uint8_t instance_index;

	/* Initialize parameters */
	for (i = 0; i < SPI_CALLBACK_N; i++) {
		module->callback[i]        = NULL;
	}
	module->tx_buffer_ptr              = NULL;
	module->rx_buffer_ptr              = NULL;
	module->remaining_tx_buffer_length = 0x0000;
	module->remaining_rx_buffer_length = 0x0000;
	module->registered_callback        = 0x00;
	module->enabled_callback           = 0x00;
	module->status                     = STATUS_OK;
	module->dir                        = SPI_DIRECTION_IDLE;
	module->locked                     = false;
	/*
	 * Set interrupt handler and register SPI software module struct in
	 * look-up table
	 */
	instance_index = _sercom_get_sercom_inst_index(module->hw);
	_sercom_set_handler(instance_index, _spi_interrupt_handler);
	_sercom_instances[instance_index] = module;
#endif

	/* Write configuration to module and return status code */
	return _spi_set_config(module, config);
}

/**
 * \brief Reads buffer of \c length SPI characters
 *
 * This function will read a buffer of data from an SPI peripheral by sending
 * dummy SPI character if in master mode, or by waiting for data in slave mode.
 *
 * \note If address matching is enabled for the slave, the first character
 *       received and placed in the buffer will be the address.
 *
 * \param[in]  module   Pointer to the software instance struct
 * \param[out] rx_data  Data buffer for received data
 * \param[in]  length   Length of data to receive
 * \param[in]  dummy    8- or 9-bit dummy byte to shift out in master mode
 *
 * \return Status of the read operation.
 * \retval STATUS_OK              If the read was completed
 * \retval STATUS_ABORTED          If transaction was ended by master before
 *                                 the entire buffer was transferred
 * \retval STATUS_ERR_INVALID_ARG If invalid argument(s) were provided
 * \retval STATUS_ERR_TIMEOUT     If the operation was not completed within the
 *                                timeout in slave mode
 * \retval STATUS_ERR_DENIED      If the receiver is not enabled
 * \retval STATUS_ERR_OVERFLOW    If the data is overflown
 */
enum status_code spi_read_buffer_wait(
		struct spi_module *const module,
		uint8_t *rx_data,
		uint16_t length,
		uint16_t dummy)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);

#  if SPI_CALLBACK_MODE == true
	if (module->status == STATUS_BUSY) {
		/* Check if the SPI module is busy with a job */
		return STATUS_BUSY;
	}
#  endif

	/* Sanity check arguments */
	if (length == 0) {
		return STATUS_ERR_INVALID_ARG;
	}

	if (!(module->receiver_enabled)) {
		return STATUS_ERR_DENIED;
	}
#  if CONF_SPI_SLAVE_ENABLE == true
	if ((module->mode == SPI_MODE_SLAVE) && (spi_is_write_complete(module))) {
		/* Clear TX complete flag */
		_spi_clear_tx_complete_flag(module);
	}
#  endif
	uint16_t rx_pos = 0;

	while (length--) {
#  if CONF_SPI_MASTER_ENABLE == true
		if (module->mode == SPI_MODE_MASTER) {
			/* Wait until the module is ready to write a character */
			while (!spi_is_ready_to_write(module)) {
			}

			/* Send dummy SPI character to read in master mode */
			spi_write(module, dummy);
		}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
		/* Start timeout period for slave */
		if (module->mode == SPI_MODE_SLAVE) {
			for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
				if (spi_is_ready_to_read(module)) {
					break;
				}
			}
			/* Check if master has ended the transaction */
			if (spi_is_write_complete(module)) {
				_spi_clear_tx_complete_flag(module);
				return STATUS_ABORTED;
			}

			if (!spi_is_ready_to_read(module)) {
				/* Not ready to read data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
#  endif

		/* Wait until the module is ready to read a character */
		while (!spi_is_ready_to_read(module)) {
		}

		uint16_t received_data = 0;
		enum status_code retval = spi_read(module, &received_data);

		if (retval != STATUS_OK) {
			/* Overflow, abort */
			return retval;
		}

		/* Read value will be at least 8-bits long */
		rx_data[rx_pos++] = received_data;

		/* If 9-bit data, write next received byte to the buffer */
		if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
			rx_data[rx_pos++] = (received_data >> 8);
		}
	}

	return STATUS_OK;
}

/**
 * \brief Sends and reads a single SPI character
 *
 * This function will transfer a single SPI character via SPI and return the
 * SPI character that is shifted into the shift register.
 *
 * In master mode the SPI character will be sent immediately and the received
 * SPI character will be read as soon as the shifting of the data is
 * complete.
 *
 * In slave mode this function will place the data to be sent into the transmit
 * buffer. It will then block until an SPI master has shifted a complete
 * SPI character, and the received data is available.
 *
 * \note The data to be sent might not be sent before the next transfer, as
 *       loading of the shift register is dependent on SCK.
 * \note If address matching is enabled for the slave, the first character
 *       received and placed in the buffer will be the address.
 *
 * \param[in]  module   Pointer to the software instance struct
 * \param[in]  tx_data  SPI character to transmit
 * \param[out] rx_data  Pointer to store the received SPI character
 *
 * \return Status of the operation.
 * \retval STATUS_OK            If the operation was completed
 * \retval STATUS_ERR_TIMEOUT   If the operation was not completed within the
 *                              timeout in slave mode
 * \retval STATUS_ERR_DENIED    If the receiver is not enabled
 * \retval STATUS_ERR_OVERFLOW  If the incoming data is overflown
 */
enum status_code spi_transceive_wait(
		struct spi_module *const module,
		uint16_t tx_data,
		uint16_t *rx_data)
{
	/* Sanity check arguments */
	Assert(module);

	if (!(module->receiver_enabled)) {
		return STATUS_ERR_DENIED;
	}

#  if SPI_CALLBACK_MODE == true
	if (module->status == STATUS_BUSY) {
		/* Check if the SPI module is busy with a job */
		return STATUS_BUSY;
	}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
	uint16_t j;
#  endif
	enum status_code retval = STATUS_OK;

#  if CONF_SPI_SLAVE_ENABLE == true
	/* Start timeout period for slave */
	if (module->mode == SPI_MODE_SLAVE) {
		for (j = 0; j <= SPI_TIMEOUT; j++) {
			if (spi_is_ready_to_write(module)) {
				break;
			} else if (j == SPI_TIMEOUT) {
				/* Not ready to write data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
	}
#  endif
	/* Wait until the module is ready to write the character */
	while (!spi_is_ready_to_write(module)) {
	}

	/* Write data */
	spi_write(module, tx_data);

#  if CONF_SPI_SLAVE_ENABLE == true
	/* Start timeout period for slave */
	if (module->mode == SPI_MODE_SLAVE) {
		for (j = 0; j <= SPI_TIMEOUT; j++) {
			if (spi_is_ready_to_read(module)) {
				break;
			} else if (j == SPI_TIMEOUT) {
				/* Not ready to read data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
	}
#  endif

	/* Wait until the module is ready to read the character */
	while (!spi_is_ready_to_read(module)) {
	}

	/* Read data */
	retval = spi_read(module, rx_data);

	return retval;
}

 /**
 * \brief Selects slave device
 *
 * This function will drive the slave select pin of the selected device low or
 * high depending on the select Boolean.
 * If slave address recognition is enabled, the address will be sent to the
 * slave when selecting it.
 *
 * \param[in] module  Pointer to the software module struct
 * \param[in] slave   Pointer to the attached slave
 * \param[in] select  Boolean stating if the slave should be selected or
 *                    deselected
 *
 * \return Status of the operation.
 * \retval STATUS_OK                   If the slave device was selected
 * \retval STATUS_ERR_UNSUPPORTED_DEV  If the SPI module is operating in slave
 *                                     mode
 * \retval STATUS_BUSY                 If the SPI module is not ready to write
 *                                     the slave address
 */
enum status_code spi_select_slave(
		struct spi_module *const module,
		struct spi_slave_inst *const slave,
		const bool select)
{
	/* Sanity check arguments */
	Assert(module);
	Assert(module->hw);
	Assert(slave);

	/* Check that the SPI module is operating in master mode */
	if (module->mode != SPI_MODE_MASTER) {
		return STATUS_ERR_UNSUPPORTED_DEV;
	}
#  ifdef FEATURE_SPI_HARDWARE_SLAVE_SELECT
	if(!(module->master_slave_select_enable))
#  endif
	{
		if (select) {
			/* Check if address recognition is enabled */
			if (slave->address_enabled) {
				/* Check if the module is ready to write the address */
				if (!spi_is_ready_to_write(module)) {
					/* Not ready, do not select slave and return */
					port_pin_set_output_level(slave->ss_pin, true);
					return STATUS_BUSY;
				}

				/* Drive Slave Select low */
				port_pin_set_output_level(slave->ss_pin, false);

				/* Write address to slave */
				spi_write(module, slave->address);

				if (!(module->receiver_enabled)) {
					/* Flush contents of shift register shifted back from slave */
					while (!spi_is_ready_to_read(module)) {
					}
					uint16_t flush = 0;
					spi_read(module, &flush);
				}
			} else {
				/* Drive Slave Select low */
				port_pin_set_output_level(slave->ss_pin, false);
			}
		} else {
			/* Drive Slave Select high */
			port_pin_set_output_level(slave->ss_pin, true);
		}
	}
	return STATUS_OK;
}

/**
 * \brief Sends a buffer of \c length SPI characters
 *
 * This function will send a buffer of SPI characters via the SPI
 * and discard any data that is received. To both send and receive a buffer of
 * data, use the \ref spi_transceive_buffer_wait function.
 *
 * Note that this function does not handle the _SS (slave select) pin(s) in
 * master mode; this must be handled by the user application.
 *
 * \param[in] module   Pointer to the software instance struct
 * \param[in] tx_data  Pointer to the buffer to transmit
 * \param[in] length   Number of SPI characters to transfer
 *
 * \return Status of the write operation.
 * \retval STATUS_OK               If the write was completed
 * \retval STATUS_ABORTED          If transaction was ended by master before
 *                                 entire buffer was transferred
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 * \retval STATUS_ERR_TIMEOUT      If the operation was not completed within the
 *                                 timeout in slave mode
 */
enum status_code spi_write_buffer_wait(
		struct spi_module *const module,
		const uint8_t *tx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);

#  if SPI_CALLBACK_MODE == true
	if (module->status == STATUS_BUSY) {
		/* Check if the SPI module is busy with a job */
		return STATUS_BUSY;
	}
#  endif

	if (length == 0) {
		return STATUS_ERR_INVALID_ARG;
	}

#  if CONF_SPI_SLAVE_ENABLE == true
	if ((module->mode == SPI_MODE_SLAVE) && (spi_is_write_complete(module))) {
		/* Clear TX complete flag */
		_spi_clear_tx_complete_flag(module);
	}
#  endif

	uint16_t tx_pos = 0;
	uint16_t flush_length = length;

	/* Write block */
	while (length--) {
#  if CONF_SPI_SLAVE_ENABLE == true
		/* Start timeout period for slave */
		if (module->mode == SPI_MODE_SLAVE) {
			for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
				if (spi_is_ready_to_write(module)) {
					break;
				}
			}
			/* Check if master has ended the transaction */
			if (spi_is_write_complete(module)) {
				_spi_clear_tx_complete_flag(module);
				return STATUS_ABORTED;
			}

			if (!spi_is_ready_to_write(module)) {
				/* Not ready to write data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
#  endif

		/* Wait until the module is ready to write a character */
		while (!spi_is_ready_to_write(module)) {
		}

		/* Write value will be at least 8-bits long */
		uint16_t data_to_send = tx_data[tx_pos++];

		/* If 9-bit data, get next byte to send from the buffer */
		if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
			data_to_send |= (tx_data[tx_pos++] << 8);
		}

		/* Write the data to send */
		spi_write(module, data_to_send);

		if (module->receiver_enabled) {
#  if CONF_SPI_SLAVE_ENABLE == true
			/* Start timeout period for slave */
			if (module->mode == SPI_MODE_SLAVE) {
				for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
					if (length && spi_is_ready_to_write(module)) {
						data_to_send = tx_data[tx_pos++];
						/* If 9-bit data, get next byte to send from the buffer */
						if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
							data_to_send |= (tx_data[tx_pos++] << 8);
						}

						/* Write the data to send */
						spi_write(module, data_to_send);
						length--;
					}
					if (spi_is_ready_to_read(module)) {
						break;
					}
				}

				/* Check if master has ended the transaction */
				if (spi_is_write_complete(module)) {
					_spi_clear_tx_complete_flag(module);
					return STATUS_ABORTED;
				}

				if (!spi_is_ready_to_read(module)) {
					/* Not ready to read data within timeout period */
					return STATUS_ERR_TIMEOUT;
				}
			}
#  endif

			while (!spi_is_ready_to_read(module)) {
			}

			/* Flush read buffer */
			uint16_t flush;
			spi_read(module, &flush);
			flush_length--;
		}
	}

#  if CONF_SPI_MASTER_ENABLE == true
	if (module->mode == SPI_MODE_MASTER) {
		/* Wait for last byte to be transferred */
		while (!spi_is_write_complete(module)) {
		}
	}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
	if (module->mode == SPI_MODE_SLAVE) {
		if (module->receiver_enabled) {
			while (flush_length) {
				/* Start timeout period for slave */
				for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
					if (spi_is_ready_to_read(module)) {
						break;
					}
				}
				if (!spi_is_ready_to_read(module)) {
					/* Not ready to read data within timeout period */
					return STATUS_ERR_TIMEOUT;
				}
				/* Flush read buffer */
				uint16_t flush;
				spi_read(module, &flush);
				flush_length--;
			}
		}
	}
#  endif
	return STATUS_OK;
}

/**
 * \brief Sends and receives a buffer of \c length SPI characters
 *
 * This function will send and receive a buffer of data via the SPI.
 *
 * In master mode the SPI characters will be sent immediately and the
 * received SPI character will  be read as soon as the shifting of the SPI
 * character is complete.
 *
 * In slave mode this function will place the data to be sent into the transmit
 * buffer. It will then block until an SPI master has shifted the complete
 * buffer and the received data is available.
 *
 * \param[in]  module   Pointer to the software instance struct
 * \param[in]  tx_data  Pointer to the buffer to transmit
 * \param[out] rx_data  Pointer to the buffer where received data will be stored
 * \param[in]  length   Number of SPI characters to transfer
 *
 * \return Status of the operation.
 * \retval STATUS_OK               If the operation was completed
 * \retval STATUS_ERR_INVALID_ARG  If invalid argument(s) were provided
 * \retval STATUS_ERR_TIMEOUT      If the operation was not completed within the
 *                                 timeout in slave mode
 * \retval STATUS_ERR_DENIED       If the receiver is not enabled
 * \retval STATUS_ERR_OVERFLOW     If the data is overflown
 */
enum status_code spi_transceive_buffer_wait(
		struct spi_module *const module,
		uint8_t *tx_data,
		uint8_t *rx_data,
		uint16_t length)
{
	/* Sanity check arguments */
	Assert(module);

#  if SPI_CALLBACK_MODE == true
	if (module->status == STATUS_BUSY) {
		/* Check if the SPI module is busy with a job */
		return STATUS_BUSY;
	}
#  endif

	/* Sanity check arguments */
	if (length == 0) {
		return STATUS_ERR_INVALID_ARG;
	}

	if (!(module->receiver_enabled)) {
		return STATUS_ERR_DENIED;
	}

#  if CONF_SPI_SLAVE_ENABLE == true
	if ((module->mode == SPI_MODE_SLAVE) && (spi_is_write_complete(module))) {
		/* Clear TX complete flag */
		_spi_clear_tx_complete_flag(module);
	}
#  endif

	uint16_t tx_pos = 0;
	uint16_t rx_pos = 0;
	uint16_t rx_length = length;

	/* Send and receive buffer */
	while (length--) {
#  if CONF_SPI_SLAVE_ENABLE == true
		/* Start timeout period for slave */
		if (module->mode == SPI_MODE_SLAVE) {
			for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
				if (spi_is_ready_to_write(module)) {
					break;
				}
			}
			/* Check if master has ended the transaction */
			if (spi_is_write_complete(module)) {
				_spi_clear_tx_complete_flag(module);
				return STATUS_ABORTED;
			}

			if (!spi_is_ready_to_write(module)) {
				/* Not ready to write data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
#  endif

		/* Wait until the module is ready to write a character */
		while (!spi_is_ready_to_write(module)) {
		}

		/* Write value will be at least 8-bits long */
		uint16_t data_to_send = tx_data[tx_pos++];

		/* If 9-bit data, get next byte to send from the buffer */
		if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
			data_to_send |= (tx_data[tx_pos++] << 8);
		}

		/* Write the data to send */
		spi_write(module, data_to_send);

#  if CONF_SPI_SLAVE_ENABLE == true
		/* Start timeout period for slave */
		if (module->mode == SPI_MODE_SLAVE) {
			for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
				if (spi_is_ready_to_write(module)) {
					data_to_send = tx_data[tx_pos++];
					/* If 9-bit data, get next byte to send from the buffer */
					if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
						data_to_send |= (tx_data[tx_pos++] << 8);
					}

					/* Write the data to send */
					spi_write(module, data_to_send);
					length--;
				}
				if (spi_is_ready_to_read(module)) {
					break;
				}
			}
			/* Check if master has ended the transaction */
			if (spi_is_write_complete(module)) {
				_spi_clear_tx_complete_flag(module);
				return STATUS_ABORTED;
			}

			if (!spi_is_ready_to_read(module)) {
				/* Not ready to read data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
		}
#  endif

		/* Wait until the module is ready to read a character */
		while (!spi_is_ready_to_read(module)) {
		}

		enum status_code retval;
		uint16_t received_data = 0;
		rx_length--;

		retval = spi_read(module, &received_data);

		if (retval != STATUS_OK) {
			/* Overflow, abort */
			return retval;
		}

		/* Read value will be at least 8-bits long */
		rx_data[rx_pos++] = received_data;

		/* If 9-bit data, write next received byte to the buffer */
		if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
			rx_data[rx_pos++] = (received_data >> 8);
		}
	}

#  if CONF_SPI_MASTER_ENABLE == true
	if (module->mode == SPI_MODE_MASTER) {
		/* Wait for last byte to be transferred */
		while (!spi_is_write_complete(module)) {
		}
	}
#  endif

#  if CONF_SPI_SLAVE_ENABLE == true
	if (module->mode == SPI_MODE_SLAVE) {
		while (rx_length) {
			/* Start timeout period for slave */
			for (uint32_t i = 0; i <= SPI_TIMEOUT; i++) {
				if (spi_is_ready_to_read(module)) {
					break;
				}
			}
			if (!spi_is_ready_to_read(module)) {
				/* Not ready to read data within timeout period */
				return STATUS_ERR_TIMEOUT;
			}
			enum status_code retval;
			uint16_t received_data = 0;
			rx_length--;

			retval = spi_read(module, &received_data);

			if (retval != STATUS_OK) {
				/* Overflow, abort */
				return retval;
			}
			/* Read value will be at least 8-bits long */
			rx_data[rx_pos++] = received_data;

			/* If 9-bit data, write next received byte to the buffer */
			if (module->character_size == SPI_CHARACTER_SIZE_9BIT) {
				rx_data[rx_pos++] = (received_data >> 8);
			}
		}
	}
#  endif
	return STATUS_OK;
}
